artdaq_mfextensions  v1_07_02
mvdlg.cc
1 #include <QMenu>
2 #include <QMessageBox>
3 #include <QProgressDialog>
4 #include <QScrollBar>
5 #include <QtGui>
6 
7 #include "cetlib/filepath_maker.h"
8 #include "fhiclcpp/ParameterSet.h"
9 #include "mfextensions/Binaries/MakeParameterSet.hh"
10 
11 #include "mfextensions/Binaries/mvdlg.hh"
12 
13 #if GCC_VERSION >= 701000 || defined(__clang__)
14 #pragma GCC diagnostic push
15 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
16 #endif
17 
18 #define TRACE_NAME "MessageViewer"
19 #include "trace.h"
20 
21 #if GCC_VERSION >= 701000 || defined(__clang__)
22 #pragma GCC diagnostic pop
23 #endif
24 
25 #include "mvdlg.hh"
26 
27 // replace the ${..} part in the filename with env variable
28 // throw if the env does not exist
29 static void process_fname(std::string& fname)
30 {
31  size_t sub_start = fname.find("${");
32  size_t sub_end = fname.find("}");
33 
34  const size_t npos = std::string::npos;
35 
36  if ((sub_start == npos && sub_end != npos) || (sub_start != npos && sub_end == npos) || (sub_start > sub_end))
37  {
38  throw std::runtime_error("Unrecognized configuration file. Use default configuration instead.");
39  }
40 
41  if (sub_start == npos) return;
42 
43  std::string env = std::string(getenv(fname.substr(sub_start + 2, sub_end - sub_start - 2).c_str()));
44  fname.replace(sub_start, sub_end - sub_start + 1, env);
45 
46  // printf("%s\n", fname.c_str());
47 }
48 
49 static fhicl::ParameterSet readConf(std::string const& fname)
50 {
51  if (fname.empty()) return fhicl::ParameterSet();
52 
53  std::string filename = fname;
54  process_fname(filename);
55 
56  std::string env("FHICL_FILE_PATH=");
57 
58  if (filename[0] == '/')
59  {
60  env.append("/");
61  }
62  else
63  {
64  env.append(".");
65  }
66 
67  char* mfe_path = getenv("MFEXTENSIONS_DIR");
68  if (mfe_path) env.append(":").append(mfe_path).append("/config");
69 
70  env.append("\0"); // So that putenv gets a valid C string
71 
72  putenv(&env[0]);
73 
74  // printf("%s\n", env.c_str());
75 
76  cet::filepath_lookup policy("FHICL_FILE_PATH");
77 
78  // it throws when the file is not parsable
79  fhicl::ParameterSet pset = artdaq::make_pset(filename, policy);
80 
81  return pset;
82 }
83 
84 msgViewerDlg::msgViewerDlg(std::string const& conf, QDialog* parent)
85  : QDialog(parent), paused(false), shortMode_(false), nMsgs(0), nSupMsgs(0), nThrMsgs(0), nFilters(0), nDeleted(0), simpleRender(true), searchStr(""), msg_pool_(), host_msgs_(), cat_msgs_(), app_msgs_(), sup_menu(new QMenu(this)), thr_menu(new QMenu(this)), receivers_(readConf(conf).get<fhicl::ParameterSet>("receivers", fhicl::ParameterSet()))
86 {
87  setupUi(this);
88 
89  // window geo settings
90  readSettings();
91 
92  // read configuration file
93  fhicl::ParameterSet pset = readConf(conf);
94 
95  // parse configuration file
96  parseConf(pset);
97 
98  // associate menu with push buttons
99  btnSuppression->setMenu(sup_menu);
100  btnThrottling->setMenu(thr_menu);
101 
102  // slots
103  connect(btnPause, SIGNAL(clicked()), this, SLOT(pause()));
104  connect(btnScrollToBottom, SIGNAL(clicked()), this, SLOT(scrollToBottom()));
105  connect(btnExit, SIGNAL(clicked()), this, SLOT(exit()));
106  connect(btnClear, SIGNAL(clicked()), this, SLOT(clear()));
107 
108  connect(btnRMode, SIGNAL(clicked()), this, SLOT(renderMode()));
109  connect(btnDisplayMode, SIGNAL(clicked()), this, SLOT(shortMode()));
110 
111  connect(btnSearch, SIGNAL(clicked()), this, SLOT(searchMsg()));
112  connect(btnSearchClear, SIGNAL(clicked()), this, SLOT(searchClear()));
113 
114  connect(btnFilter, SIGNAL(clicked()), this, SLOT(setFilter()));
115 
116  connect(btnError, SIGNAL(clicked()), this, SLOT(setSevError()));
117  connect(btnWarning, SIGNAL(clicked()), this, SLOT(setSevWarning()));
118  connect(btnInfo, SIGNAL(clicked()), this, SLOT(setSevInfo()));
119  connect(btnDebug, SIGNAL(clicked()), this, SLOT(setSevDebug()));
120 
121  connect(sup_menu, SIGNAL(triggered(QAction*)), this, SLOT(setSuppression(QAction*)));
122 
123  connect(thr_menu, SIGNAL(triggered(QAction*)), this, SLOT(setThrottling(QAction*)));
124 
125  connect(vsSeverity, SIGNAL(valueChanged(int)), this, SLOT(changeSeverity(int)));
126 
127  connect(&receivers_, SIGNAL(newMessage(msg_ptr_t)), this, SLOT(onNewMsg(msg_ptr_t)));
128 
129  connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabWidgetCurrentChanged(int)));
130  connect(tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int)));
131  MsgFilterDisplay allMessages;
132  allMessages.txtDisplay = txtMessages;
133  allMessages.nDisplayMsgs = 0;
134  allMessages.filterExpression = "";
135  allMessages.nDisplayedDeletedMsgs = 0;
136  allMessages.sevThresh = SINFO;
137  msgFilters_.push_back(allMessages);
138 
139  // https://stackoverflow.com/questions/2616483/close-button-only-for-some-tabs-in-qt
140  auto tabBar = tabWidget->findChild<QTabBar*>();
141  tabBar->setTabButton(0, QTabBar::RightSide, nullptr);
142  tabBar->setTabButton(0, QTabBar::LeftSide, nullptr);
143 
144  if (simpleRender)
145  btnRMode->setChecked(true);
146  else
147  btnRMode->setChecked(false);
148 
149  btnRMode->setEnabled(false);
150 
151  changeSeverity(SINFO);
152 
153  auto doc = new QTextDocument(txtMessages);
154  txtMessages->setDocument(doc);
155 
156  receivers_.start();
157 }
158 
159 msgViewerDlg::~msgViewerDlg()
160 {
161  receivers_.stop();
162  writeSettings();
163 }
164 
165 static void str_to_suppress(std::vector<std::string> const& vs, std::vector<suppress>& s, QMenu* menu)
166 {
167  QAction* act;
168 
169  if (vs.empty())
170  {
171  act = menu->addAction("None");
172  act->setEnabled(false);
173  return;
174  }
175 
176  s.reserve(vs.size());
177 
178  for (size_t i = 0; i < vs.size(); ++i)
179  {
180  s.emplace_back(vs[i]);
181  act = menu->addAction(QString(vs[i].c_str()));
182  act->setCheckable(true);
183  act->setChecked(true);
184  QVariant v = qVariantFromValue(static_cast<void*>(&s[i]));
185  act->setData(v);
186  }
187 }
188 
189 static void pset_to_throttle(std::vector<fhicl::ParameterSet> const& ps, std::vector<throttle>& t, QMenu* menu)
190 {
191  QAction* act;
192 
193  if (ps.empty())
194  {
195  act = menu->addAction("None");
196  act->setEnabled(false);
197  return;
198  }
199 
200  t.reserve(ps.size());
201 
202  for (size_t i = 0; i < ps.size(); ++i)
203  {
204  auto name = ps[i].get<std::string>("name");
205  t.emplace_back(name, ps[i].get<int>("limit", -1), ps[i].get<int64_t>("timespan", -1));
206  act = menu->addAction(QString(name.c_str()));
207  act->setCheckable(true);
208  act->setChecked(true);
209  QVariant v = qVariantFromValue(static_cast<void*>(&t[i]));
210  act->setData(v);
211  }
212 }
213 
214 void msgViewerDlg::parseConf(fhicl::ParameterSet const& conf)
215 {
216  fhicl::ParameterSet nulp;
217  // QAction * act;
218 
219  // suppression list
220  auto sup = conf.get<fhicl::ParameterSet>("suppress", nulp);
221 
222  auto sup_host = sup.get<std::vector<std::string>>("hosts", std::vector<std::string>());
223  auto sup_app = sup.get<std::vector<std::string>>("applications", std::vector<std::string>());
224  auto sup_cat = sup.get<std::vector<std::string>>("categories", std::vector<std::string>());
225 
226  str_to_suppress(sup_host, e_sup_host, sup_menu);
227  sup_menu->addSeparator();
228 
229  str_to_suppress(sup_app, e_sup_app, sup_menu);
230  sup_menu->addSeparator();
231 
232  str_to_suppress(sup_cat, e_sup_cat, sup_menu);
233 
234  // throttling list
235  auto thr = conf.get<fhicl::ParameterSet>("throttle", nulp);
236 
237  auto thr_host = thr.get<std::vector<fhicl::ParameterSet>>("hosts", std::vector<fhicl::ParameterSet>());
238  auto thr_app = thr.get<std::vector<fhicl::ParameterSet>>("applications", std::vector<fhicl::ParameterSet>());
239  auto thr_cat = thr.get<std::vector<fhicl::ParameterSet>>("categories", std::vector<fhicl::ParameterSet>());
240 
241  pset_to_throttle(thr_host, e_thr_host, thr_menu);
242  thr_menu->addSeparator();
243 
244  pset_to_throttle(thr_app, e_thr_app, thr_menu);
245  thr_menu->addSeparator();
246 
247  pset_to_throttle(thr_cat, e_thr_cat, thr_menu);
248 
249  maxMsgs = conf.get<size_t>("max_message_buffer_size", 100000);
250  maxDeletedMsgs = conf.get<size_t>("max_displayed_deleted_messages", 100000);
251 }
252 
253 bool msgViewerDlg::msg_throttled(msg_ptr_t const& msg)
254 {
255  // suppression list
256 
257  ++nSupMsgs;
258 
259  for (size_t i = 0; i < e_sup_host.size(); ++i)
260  if (e_sup_host[i].match(msg->host().toStdString())) return true;
261 
262  for (size_t i = 0; i < e_sup_app.size(); ++i)
263  if (e_sup_app[i].match(msg->app().toStdString())) return true;
264 
265  for (size_t i = 0; i < e_sup_cat.size(); ++i)
266  if (e_sup_cat[i].match(msg->cat().toStdString())) return true;
267 
268  --nSupMsgs;
269 
270  // throttling
271 
272  ++nThrMsgs;
273 
274  for (size_t i = 0; i < e_thr_host.size(); ++i)
275  if (e_thr_host[i].reach_limit(msg->host().toStdString(), msg->time())) return true;
276 
277  for (size_t i = 0; i < e_thr_app.size(); ++i)
278  if (e_thr_app[i].reach_limit(msg->app().toStdString(), msg->time())) return true;
279 
280  for (size_t i = 0; i < e_thr_cat.size(); ++i)
281  if (e_thr_cat[i].reach_limit(msg->cat().toStdString(), msg->time())) return true;
282 
283  --nThrMsgs;
284 
285  return false;
286 }
287 
288 void msgViewerDlg::writeSettings()
289 {
290  QSettings settings("ARTDAQ", "MsgViewer");
291 
292  settings.beginGroup("MainWindow");
293  settings.setValue("size", size());
294  settings.setValue("pos", pos());
295  settings.endGroup();
296 }
297 
298 void msgViewerDlg::readSettings()
299 {
300  QSettings settings("ARTDAQ", "MsgViewer");
301 
302  settings.beginGroup("MainWindow");
303  QPoint pos = settings.value("pos", QPoint(100, 100)).toPoint();
304  QSize size = settings.value("size", QSize(660, 760)).toSize();
305  resize(size);
306  move(pos);
307  settings.endGroup();
308 }
309 
310 void msgViewerDlg::onNewMsg(msg_ptr_t const& msg)
311 {
312  // 21-Aug-2015, KAB: copying the incrementing (and displaying) of the number
313  // of messages to here. I'm also not sure if we want to
314  // count all messages or just non-suppressed ones or what. But, at least this
315  // change gets the counter incrementing on the display.
316  ++nMsgs;
317  lcdMsgs->display(nMsgs);
318 
319  // test if the message is suppressed or throttled
320  if (msg_throttled(msg))
321  {
322  lcdSuppressionCount->display(nSupMsgs);
323  lcdThrottlingCount->display(nThrMsgs);
324  return;
325  }
326 
327  // push the message to the message pool
328  {
329  //std::lock_guard<std::mutex> lk(msg_pool_mutex_);
330  msg_pool_.emplace_back(msg);
331  }
332  trim_msg_pool();
333 
334  // update corresponding lists of index
335  update_index(msg);
336 
337  // Update filtered displays
338  for (size_t d = 0; d < msgFilters_.size(); ++d)
339  {
340  bool hostMatch =
341  msgFilters_[d].hostFilter.contains(msg->host(), Qt::CaseInsensitive) || msgFilters_[d].hostFilter.empty();
342  bool appMatch =
343  msgFilters_[d].appFilter.contains(msg->app(), Qt::CaseInsensitive) || msgFilters_[d].appFilter.empty();
344  bool catMatch =
345  msgFilters_[d].catFilter.contains(msg->cat(), Qt::CaseInsensitive) || msgFilters_[d].catFilter.empty();
346 
347  // Check to display the message
348  if (hostMatch && appMatch && catMatch)
349  {
350  {
351  //std::lock_guard<std::mutex> lk(filter_mutex_);
352  msgFilters_[d].msgs.push_back(msg);
353  }
354  if ((int)d == tabWidget->currentIndex())
355  displayMsg(msg, d);
356  }
357  }
358 }
359 
360 void msgViewerDlg::trim_msg_pool()
361 {
362  bool host_list_update = false;
363  bool app_list_update = false;
364  bool cat_list_update = false;
365  {
366  std::lock_guard<std::mutex> lk(msg_pool_mutex_);
367  while (maxMsgs > 0 && msg_pool_.size() > maxMsgs)
368  {
369  QString const& app = msg_pool_.front()->app();
370  QString const& cat = msg_pool_.front()->cat();
371  QString const& host = msg_pool_.front()->host();
372 
373  // Check if we can remove an app/host/category
374  {
375  auto catIter = std::find(cat_msgs_[cat].begin(), cat_msgs_[cat].end(), msg_pool_.front());
376  auto hostIter = std::find(host_msgs_[host].begin(), host_msgs_[host].end(), msg_pool_.front());
377  auto appIter = std::find(app_msgs_[app].begin(), app_msgs_[app].end(), msg_pool_.front());
378  if (catIter != cat_msgs_[cat].end()) cat_msgs_[cat].erase(catIter);
379  if (hostIter != host_msgs_[host].end()) host_msgs_[host].erase(hostIter);
380  if (appIter != app_msgs_[app].end()) app_msgs_[app].erase(appIter);
381 
382  if (app_msgs_[app].empty())
383  {
384  app_msgs_.erase(app);
385  app_list_update = true;
386  }
387  if (cat_msgs_[cat].empty())
388  {
389  cat_msgs_.erase(cat);
390  cat_list_update = true;
391  }
392  if (host_msgs_[host].empty())
393  {
394  host_msgs_.erase(host);
395  host_list_update = true;
396  }
397  }
398 
399  // Finally, remove the message from the pool so it doesn't appear in new filters
400  msg_pool_.erase(msg_pool_.begin());
401  ++nDeleted;
402  }
403  }
404  {
405  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
406  if (host_list_update)
407  updateList(lwHost, host_msgs_);
408  if (app_list_update)
409  updateList(lwApplication, app_msgs_);
410  if (cat_list_update)
411  updateList(lwCategory, cat_msgs_);
412  }
413 
414  for (size_t d = 0; d < msgFilters_.size(); ++d)
415  {
416  {
417  std::lock_guard<std::mutex> lk(filter_mutex_);
418  while (msgFilters_[d].msgs.size() > maxMsgs)
419  {
420  if ((*msgFilters_[d].msgs.begin())->sev() >= msgFilters_[d].sevThresh)
421  msgFilters_[d].nDisplayedDeletedMsgs++;
422  msgFilters_[d].msgs.erase(msgFilters_[d].msgs.begin());
423  }
424  }
425 
426  if ((int)d == tabWidget->currentIndex())
427  {
428  if (maxDeletedMsgs > 0 && msgFilters_[d].nDisplayedDeletedMsgs > static_cast<int>(maxDeletedMsgs))
429  {
430  displayMsgs(d);
431  }
432  lcdDisplayedDeleted->display(msgFilters_[d].nDisplayedDeletedMsgs);
433  }
434  }
435 
436  lcdDeletedCount->display(nDeleted);
437 }
438 
439 void msgViewerDlg::update_index(msg_ptr_t const& it)
440 {
441  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
442  QString const& app = it->app();
443  QString const& cat = it->cat();
444  QString const& host = it->host();
445 
446  if (cat_msgs_.find(cat) == cat_msgs_.end())
447  {
448  cat_msgs_[cat].push_back(it);
449  updateList(lwCategory, cat_msgs_);
450  }
451  else
452  {
453  cat_msgs_[cat].push_back(it);
454  }
455 
456  if (host_msgs_.find(host) == host_msgs_.end())
457  {
458  host_msgs_[host].push_back(it);
459  updateList(lwHost, host_msgs_);
460  }
461  else
462  {
463  host_msgs_[host].push_back(it);
464  }
465 
466  if (app_msgs_.find(app) == app_msgs_.end())
467  {
468  app_msgs_[app].push_back(it);
469  updateList(lwApplication, app_msgs_);
470  }
471  else
472  {
473  app_msgs_[app].push_back(it);
474  }
475 }
476 
477 void msgViewerDlg::displayMsg(msg_ptr_t const& it, int display)
478 {
479  if (it->sev() < msgFilters_[display].sevThresh) return;
480 
481  msgFilters_[display].nDisplayMsgs++;
482  if (display == tabWidget->currentIndex())
483  {
484  lcdDisplayedMsgs->display(msgFilters_[display].nDisplayMsgs);
485  }
486 
487  auto txt = it->text(shortMode_);
488  QStringList txts;
489  txts.push_back(txt);
490  UpdateTextAreaDisplay(txts, msgFilters_[display].txtDisplay);
491 }
492 
493 void msgViewerDlg::displayMsgs(int display)
494 {
495  msgFilters_[display].txtDisplay->clear();
496  msgFilters_[display].nDisplayMsgs = 0;
497  msgFilters_[display].nDisplayedDeletedMsgs = 0;
498 
499  QStringList txts;
500  {
501  std::lock_guard<std::mutex> lk(filter_mutex_);
502  for (auto it = msgFilters_[display].msgs.begin(); it != msgFilters_[display].msgs.end(); ++it)
503  {
504  if ((*it)->sev() >= msgFilters_[display].sevThresh)
505  {
506  txts.push_back((*it)->text(shortMode_));
507  ++msgFilters_[display].nDisplayMsgs;
508  }
509  }
510  }
511  if (display == tabWidget->currentIndex())
512  {
513  lcdDisplayedMsgs->display(msgFilters_[display].nDisplayMsgs);
514  }
515  UpdateTextAreaDisplay(txts, msgFilters_[display].txtDisplay);
516 }
517 
518 // https://stackoverflow.com/questions/13559990/how-to-append-text-to-qplaintextedit-without-adding-newline-and-keep-scroll-at
519 void msgViewerDlg::UpdateTextAreaDisplay(QStringList const& texts, QPlainTextEdit* widget)
520 {
521  const QTextCursor old_cursor = widget->textCursor();
522  const int old_scrollbar_value = widget->verticalScrollBar()->value();
523  const bool is_scrolled_down =
524  old_scrollbar_value >= widget->verticalScrollBar()->maximum() * 0.95; // At least 95% scrolled down
525 
526  if (!paused && !is_scrolled_down)
527  {
528  pause();
529  }
530 
531  QTextCursor new_cursor = QTextCursor(widget->document());
532 
533  new_cursor.beginEditBlock();
534  new_cursor.movePosition(QTextCursor::End);
535 
536  for (int i = 0; i < texts.size(); i++)
537  {
538  new_cursor.insertBlock();
539  new_cursor.insertHtml(texts.at(i));
540  if (!shortMode_) new_cursor.insertBlock();
541  }
542  new_cursor.endEditBlock();
543 
544  if (old_cursor.hasSelection() || paused)
545  {
546  // The user has selected text or scrolled away from the bottom: maintain position.
547  widget->setTextCursor(old_cursor);
548  widget->verticalScrollBar()->setValue(old_scrollbar_value);
549  }
550  else
551  {
552  // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom.
553  widget->moveCursor(QTextCursor::End);
554  widget->verticalScrollBar()->setValue(widget->verticalScrollBar()->maximum());
555  widget->horizontalScrollBar()->setValue(0);
556  }
557 }
558 
559 void msgViewerDlg::scrollToBottom()
560 {
561  int display = tabWidget->currentIndex();
562  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
563  msgFilters_[display].txtDisplay->verticalScrollBar()->setValue(
564  msgFilters_[display].txtDisplay->verticalScrollBar()->maximum());
565  msgFilters_[display].txtDisplay->horizontalScrollBar()->setValue(0);
566 }
567 
568 void msgViewerDlg::updateDisplays()
569 {
570  for (size_t ii = 0; ii < msgFilters_.size(); ++ii)
571  {
572  displayMsgs(ii);
573  }
574 }
575 
576 bool msgViewerDlg::updateList(QListWidget* lw, msgs_map_t const& map)
577 {
578  bool nonSelectedBefore = (lw->currentRow() == -1);
579  bool nonSelectedAfter = true;
580 
581  QString item = nonSelectedBefore ? "" : lw->currentItem()->text();
582 
583  lw->clear();
584  int row = 0;
585  auto it = map.begin();
586 
587  while (it != map.end())
588  {
589  lw->addItem(it->first);
590  if (!nonSelectedBefore && nonSelectedAfter)
591  {
592  if (item == it->first)
593  {
594  lw->setCurrentRow(row);
595  nonSelectedAfter = false;
596  }
597  }
598  ++row;
599  ++it;
600  }
601 
602  if (!nonSelectedBefore && nonSelectedAfter) return true;
603 
604  return false;
605 }
606 
607 msgs_t msgViewerDlg::list_intersect(msgs_t const& l1, msgs_t const& l2)
608 {
609  msgs_t output;
610  auto it1 = l1.begin();
611  auto it2 = l2.begin();
612 
613  while (it1 != l1.end() && it2 != l2.end())
614  {
615  if (*it1 < *it2)
616  {
617  ++it1;
618  }
619  else if (*it2 < *it1)
620  {
621  ++it2;
622  }
623  else
624  {
625  output.push_back(*it1);
626  ++it1;
627  ++it2;
628  }
629  }
630 
631  TLOG(TLVL_DEBUG + 35) << "list_intersect: output list has " << output.size() << " entries";
632  return output;
633 }
634 
635 std::string sev_to_string(sev_code_t s)
636 {
637  switch (s)
638  {
639  case SDEBUG:
640  return "DEBUG";
641  case SINFO:
642  return "INFO";
643  case SWARNING:
644  return "WARNING";
645  case SERROR:
646  return "ERROR";
647  }
648  return "UNKNOWN";
649 }
650 
651 void msgViewerDlg::setFilter()
652 {
653  auto hostFilter = toQStringList(lwHost->selectedItems());
654  auto appFilter = toQStringList(lwApplication->selectedItems());
655  auto catFilter = toQStringList(lwCategory->selectedItems());
656 
657  lwHost->setCurrentRow(-1, QItemSelectionModel::Clear);
658  lwApplication->setCurrentRow(-1, QItemSelectionModel::Clear);
659  lwCategory->setCurrentRow(-1, QItemSelectionModel::Clear);
660 
661  if (hostFilter.isEmpty() && appFilter.isEmpty() && catFilter.isEmpty())
662  {
663  return;
664  }
665 
666  msgs_t result;
667  QString catFilterExpression = "";
668  QString hostFilterExpression = "";
669  QString appFilterExpression = "";
670  bool first = true;
671  {
672  for (auto app = 0; app < appFilter.size(); ++app)
673  { // app-sev index
674  appFilterExpression += QString(first ? "" : " || ") + appFilter[app];
675  first = false;
676  }
677  TLOG(TLVL_DEBUG + 35) << "setFilter: result contains %zu messages", result.size();
678 
679  first = true;
680  if (!hostFilter.isEmpty())
681  {
682  msgs_t hostResult;
683  for (auto host = 0; host < hostFilter.size(); ++host)
684  { // host index
685  hostFilterExpression += QString(first ? "" : " || ") + hostFilter[host];
686  first = false;
687  }
688  }
689 
690  first = true;
691  if (!catFilter.isEmpty())
692  {
693  msgs_t catResult;
694  for (auto cat = 0; cat < catFilter.size(); ++cat)
695  { // cat index
696  catFilterExpression += QString(first ? "" : " || ") + catFilter[cat];
697  first = false;
698  }
699  }
700  }
701  // Create the filter expression
702  auto nFilterExpressions =
703  (appFilterExpression != "" ? 1 : 0) + (hostFilterExpression != "" ? 1 : 0) + (catFilterExpression != "" ? 1 : 0);
704  QString filterExpression = "";
705  if (nFilterExpressions == 1)
706  {
707  filterExpression = catFilterExpression + hostFilterExpression + appFilterExpression;
708  }
709  else
710  {
711  filterExpression = "(" + (catFilterExpression != "" ? catFilterExpression + ") && (" : "") + hostFilterExpression +
712  (hostFilterExpression != "" && appFilterExpression != "" ? ") && (" : "") + appFilterExpression +
713  ")";
714  }
715 
716  for (size_t d = 0; d < msgFilters_.size(); ++d)
717  {
718  if (msgFilters_[d].filterExpression == filterExpression)
719  {
720  tabWidget->setCurrentIndex(d);
721  return;
722  }
723  }
724 
725  {
726  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
727  for (auto app = 0; app < appFilter.size(); ++app)
728  { // app-sev index
729  auto it = app_msgs_.find(appFilter[app]);
730  if (it != app_msgs_.end())
731  {
732  msgs_t temp(it->second);
733  TLOG(TLVL_DEBUG + 35) << "setFilter: app " << appFilter[app].toStdString() << " has " << temp.size() << " messages";
734  result.merge(temp);
735  }
736  }
737  TLOG(TLVL_DEBUG + 35) << "setFilter: result contains %zu messages", result.size();
738 
739  if (!hostFilter.isEmpty())
740  {
741  msgs_t hostResult;
742  for (auto host = 0; host < hostFilter.size(); ++host)
743  { // host index
744  auto it = host_msgs_.find(hostFilter[host]);
745  if (it != host_msgs_.end())
746  {
747  msgs_t temp(it->second);
748  TLOG(TLVL_DEBUG + 35) << "setFilter: host " << hostFilter[host].toStdString() << " has " << temp.size() << " messages";
749  hostResult.merge(temp);
750  }
751  }
752  if (result.empty())
753  {
754  result = hostResult;
755  }
756  else
757  {
758  result = list_intersect(result, hostResult);
759  }
760  TLOG(TLVL_DEBUG + 35) << "setFilter: result contains " << result.size() << " messages";
761  }
762 
763  if (!catFilter.isEmpty())
764  {
765  msgs_t catResult;
766  for (auto cat = 0; cat < catFilter.size(); ++cat)
767  { // cat index
768  auto it = cat_msgs_.find(catFilter[cat]);
769  if (it != cat_msgs_.end())
770  {
771  msgs_t temp(it->second);
772  TLOG(TLVL_DEBUG + 35) << "setFilter: cat " << catFilter[cat].toStdString() << " has " << temp.size() << " messages";
773  catResult.merge(temp);
774  }
775  }
776  if (result.empty())
777  {
778  result = catResult;
779  }
780  else
781  {
782  result = list_intersect(result, catResult);
783  }
784  TLOG(TLVL_DEBUG + 35) << "setFilter: result contains " << result.size() << " messages";
785  }
786  }
787 
788  // Add the tab and populate it
789 
790  auto newTabTitle = QString("Filter ") + QString::number(++nFilters);
791  auto newTab = new QWidget();
792 
793  auto txtDisplay = new QPlainTextEdit(newTab);
794  auto doc = new QTextDocument(txtDisplay);
795  txtDisplay->setDocument(doc);
796 
797  auto layout = new QVBoxLayout();
798  layout->addWidget(txtDisplay);
799  layout->setContentsMargins(0, 0, 0, 0);
800  newTab->setLayout(layout);
801 
802  MsgFilterDisplay filteredMessages;
803  filteredMessages.msgs = result;
804  filteredMessages.hostFilter = hostFilter;
805  filteredMessages.appFilter = appFilter;
806  filteredMessages.catFilter = catFilter;
807  filteredMessages.filterExpression = filterExpression;
808  filteredMessages.txtDisplay = txtDisplay;
809  filteredMessages.nDisplayMsgs = result.size();
810  filteredMessages.nDisplayedDeletedMsgs = 0;
811  filteredMessages.sevThresh = SINFO;
812  {
813  std::lock_guard<std::mutex> lk(filter_mutex_);
814  msgFilters_.push_back(filteredMessages);
815  }
816  tabWidget->addTab(newTab, newTabTitle);
817  tabWidget->setTabToolTip(tabWidget->count() - 1, filterExpression);
818  tabWidget->setCurrentIndex(tabWidget->count() - 1);
819 
820  displayMsgs(msgFilters_.size() - 1);
821 }
822 
824 {
825  if (!paused)
826  {
827  paused = true;
828  btnPause->setText("Resume Scrolling");
829  // QMessageBox::about(this, "About MsgViewer", "Message receiving paused ...");
830  }
831  else
832  {
833  paused = false;
834  btnPause->setText("Pause Scrolling");
835  scrollToBottom();
836  }
837 }
838 
839 void msgViewerDlg::exit() { close(); }
840 
842 {
843  int ret =
844  QMessageBox::question(this, tr("Message Viewer"), tr("Are you sure you want to clear all received messages?"),
845  QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
846  switch (ret)
847  {
848  case QMessageBox::Yes:
849  nMsgs = 0;
850  nSupMsgs = 0;
851  nThrMsgs = 0;
852  nDeleted = 0;
853  {
854  std::lock_guard<std::mutex> lk(msg_pool_mutex_);
855  msg_pool_.clear();
856  }
857  {
858  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
859  host_msgs_.clear();
860  cat_msgs_.clear();
861  app_msgs_.clear();
862  updateList(lwApplication, app_msgs_);
863  updateList(lwCategory, cat_msgs_);
864  updateList(lwHost, host_msgs_);
865  }
866  for (auto& display : msgFilters_)
867  {
868  std::lock_guard<std::mutex> lk(filter_mutex_);
869  display.txtDisplay->clear();
870  display.msgs.clear();
871  display.nDisplayMsgs = 0;
872  display.nDisplayedDeletedMsgs = 0;
873  }
874 
875  lcdMsgs->display(nMsgs);
876  lcdDisplayedMsgs->display(0);
877  break;
878  case QMessageBox::No:
879  default:
880  break;
881  }
882 }
883 
885 {
886  if (!shortMode_)
887  {
888  shortMode_ = true;
889  btnDisplayMode->setText("Long View");
890  }
891  else
892  {
893  shortMode_ = false;
894  btnDisplayMode->setText("Compact View");
895  }
896  updateDisplays();
897 }
898 
900 {
901  auto display = tabWidget->currentIndex();
902  switch (sev)
903  {
904  case SERROR:
905  setSevError();
906  break;
907 
908  case SWARNING:
909  setSevWarning();
910  break;
911 
912  case SINFO:
913  setSevInfo();
914  break;
915 
916  default:
917  setSevDebug();
918  }
919 
920  displayMsgs(display);
921 }
922 
923 void msgViewerDlg::setSevError()
924 {
925  auto display = tabWidget->currentIndex();
926  msgFilters_[display].sevThresh = SERROR;
927  btnError->setChecked(true);
928  btnWarning->setChecked(false);
929  btnInfo->setChecked(false);
930  btnDebug->setChecked(false);
931  vsSeverity->setValue(SERROR);
932 }
933 
934 void msgViewerDlg::setSevWarning()
935 {
936  auto display = tabWidget->currentIndex();
937  msgFilters_[display].sevThresh = SWARNING;
938  btnError->setChecked(false);
939  btnWarning->setChecked(true);
940  btnInfo->setChecked(false);
941  btnDebug->setChecked(false);
942  vsSeverity->setValue(SWARNING);
943 }
944 
945 void msgViewerDlg::setSevInfo()
946 {
947  auto display = tabWidget->currentIndex();
948  msgFilters_[display].sevThresh = SINFO;
949  btnError->setChecked(false);
950  btnWarning->setChecked(false);
951  btnInfo->setChecked(true);
952  btnDebug->setChecked(false);
953  vsSeverity->setValue(SINFO);
954 }
955 
956 void msgViewerDlg::setSevDebug()
957 {
958  auto display = tabWidget->currentIndex();
959  msgFilters_[display].sevThresh = SDEBUG;
960  btnError->setChecked(false);
961  btnWarning->setChecked(false);
962  btnInfo->setChecked(false);
963  btnDebug->setChecked(true);
964  vsSeverity->setValue(SDEBUG);
965 }
966 
967 void msgViewerDlg::renderMode()
968 {
969  simpleRender = !simpleRender;
970 
971  if (simpleRender)
972  {
973  btnRMode->setChecked(true);
974  for (auto display : msgFilters_)
975  {
976  display.txtDisplay->setPlainText(display.txtDisplay->toPlainText());
977  }
978  }
979  else
980  {
981  btnRMode->setChecked(false);
982  updateDisplays();
983  }
984 }
985 
986 void msgViewerDlg::searchMsg()
987 {
988  QString search = editSearch->text();
989 
990  if (search.isEmpty()) return;
991 
992  auto display = tabWidget->currentIndex();
993  if (search != searchStr)
994  {
995  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::Start);
996  if (!msgFilters_[display].txtDisplay->find(search))
997  {
998  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
999  searchStr = "";
1000  }
1001  else
1002  searchStr = search;
1003  }
1004  else
1005  {
1006  if (!msgFilters_[display].txtDisplay->find(search))
1007  {
1008  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::Start);
1009  if (!msgFilters_[display].txtDisplay->find(search))
1010  {
1011  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
1012  searchStr = "";
1013  }
1014  }
1015  }
1016 }
1017 
1018 void msgViewerDlg::searchClear()
1019 {
1020  auto display = tabWidget->currentIndex();
1021  editSearch->setText("");
1022  searchStr = "";
1023  msgFilters_[display].txtDisplay->find("");
1024  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
1025 }
1026 
1027 void msgViewerDlg::setSuppression(QAction* act)
1028 {
1029  bool status = act->isChecked();
1030  auto sup = static_cast<suppress*>(act->data().value<void*>());
1031  sup->use(status);
1032 }
1033 
1034 void msgViewerDlg::setThrottling(QAction* act)
1035 {
1036  bool status = act->isChecked();
1037  auto thr = static_cast<throttle*>(act->data().value<void*>());
1038  thr->use(status);
1039 }
1040 
1041 void msgViewerDlg::tabWidgetCurrentChanged(int newTab)
1042 {
1043  displayMsgs(newTab);
1044  lcdDisplayedMsgs->display(msgFilters_[newTab].nDisplayMsgs);
1045 
1046  lwHost->setCurrentRow(-1, QItemSelectionModel::Clear);
1047  lwApplication->setCurrentRow(-1, QItemSelectionModel::Clear);
1048  lwCategory->setCurrentRow(-1, QItemSelectionModel::Clear);
1049 
1050  for (auto const& host : msgFilters_[newTab].hostFilter)
1051  {
1052  auto items = lwHost->findItems(host, Qt::MatchExactly);
1053  if (!items.empty())
1054  {
1055  items[0]->setSelected(true);
1056  }
1057  }
1058  for (auto const& app : msgFilters_[newTab].appFilter)
1059  {
1060  auto items = lwApplication->findItems(app, Qt::MatchExactly);
1061  if (!items.empty())
1062  {
1063  items[0]->setSelected(true);
1064  }
1065  }
1066  for (auto const& cat : msgFilters_[newTab].catFilter)
1067  {
1068  auto items = lwCategory->findItems(cat, Qt::MatchExactly);
1069  if (!items.empty())
1070  {
1071  items[0]->setSelected(true);
1072  }
1073  }
1074 
1075  switch (msgFilters_[newTab].sevThresh)
1076  {
1077  case SDEBUG:
1078  setSevDebug();
1079  break;
1080  case SINFO:
1081  setSevInfo();
1082  break;
1083  case SWARNING:
1084  setSevWarning();
1085  break;
1086  default:
1087  setSevError();
1088  break;
1089  }
1090 }
1091 
1092 void msgViewerDlg::tabCloseRequested(int tabIndex)
1093 {
1094  if (tabIndex == 0 || static_cast<size_t>(tabIndex) >= msgFilters_.size()) return;
1095 
1096  auto widget = tabWidget->widget(tabIndex);
1097  tabWidget->removeTab(tabIndex);
1098  delete widget;
1099 
1100  msgFilters_.erase(msgFilters_.begin() + tabIndex);
1101 }
1102 
1103 void msgViewerDlg::closeEvent(QCloseEvent* event) { event->accept(); }
1104 
1105 QStringList msgViewerDlg::toQStringList(QList<QListWidgetItem*> in)
1106 {
1107  QStringList out;
1108 
1109  for (auto i = 0; i < in.size(); ++i)
1110  {
1111  out << in[i]->text();
1112  }
1113 
1114  return out;
1115 }
void use(bool flag)
Enable or disable this throttle
Definition: throttle.hh:40
void exit()
Exit the program.
Definition: mvdlg.cc:839
void start()
Start all receivers
Suppress messages based on a regular expression
Definition: suppress.hh:13
void clear()
Clear the message buffer.
Definition: mvdlg.cc:841
void use(bool flag)
Set whether the suppression is active
Definition: suppress.hh:33
Throttle messages based on name and time limits. Separate from MessageFacility limiting.
Definition: throttle.hh:17
msgViewerDlg(std::string const &conf, QDialog *parent=nullptr)
Message Viewer Dialog Constructor.
Definition: mvdlg.cc:84
void stop()
Stop all receivers
void pause()
Pause message receiving.
Definition: mvdlg.cc:823
void changeSeverity(int sev)
Change the severity threshold.
Definition: mvdlg.cc:899
void shortMode()
Switch to/from Short message mode.
Definition: mvdlg.cc:884
void closeEvent(QCloseEvent *event)
Perform actions on window close.
Definition: mvdlg.cc:1103