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