artdaq_mfextensions  v1_05_00
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 
329  // push the message to the message pool
330  {
331  std::lock_guard<std::mutex> lk(msg_pool_mutex_);
332  msg_pool_.emplace_back(msg);
333  }
334  trim_msg_pool();
335 
336  // update corresponding lists of index
337  update_index(msg);
338 
339  // Update filtered displays
340  for (size_t d = 0; d < msgFilters_.size(); ++d)
341  {
342  bool hostMatch =
343  msgFilters_[d].hostFilter.contains(msg->host(), Qt::CaseInsensitive) || msgFilters_[d].hostFilter.empty();
344  bool appMatch =
345  msgFilters_[d].appFilter.contains(msg->app(), Qt::CaseInsensitive) || msgFilters_[d].appFilter.empty();
346  bool catMatch =
347  msgFilters_[d].catFilter.contains(msg->cat(), Qt::CaseInsensitive) || msgFilters_[d].catFilter.empty();
348 
349  // Check to display the message
350  if (hostMatch && appMatch && catMatch)
351  {
352  std::lock_guard<std::mutex> lk(filter_mutex_);
353  msgFilters_[d].msgs.push_back(msg);
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  int n = 0;
496  msgFilters_[display].txtDisplay->clear();
497  msgFilters_[display].nDisplayMsgs = 0;
498  msgFilters_[display].nDisplayedDeletedMsgs = 0;
499 
500  QStringList txts;
501  {
502  std::lock_guard<std::mutex> lk(filter_mutex_);
503  n = msgFilters_[display].msgs.size();
504 
505  QProgressDialog progress("Fetching data...", "Cancel", 0, n / 1000, this);
506 
507  progress.setWindowModality(Qt::WindowModal);
508  progress.setMinimumDuration(2000); // 2 seconds
509 
510  int i = 0, prog = 0;
511 
512  for (auto it = msgFilters_[display].msgs.begin(); it != msgFilters_[display].msgs.end(); ++it, ++i)
513  {
514  if ((*it)->sev() >= msgFilters_[display].sevThresh)
515  {
516  txts.push_back((*it)->text(shortMode_));
517  ++msgFilters_[display].nDisplayMsgs;
518  }
519 
520  if (i == 1000)
521  {
522  i = 0;
523  ++prog;
524  progress.setValue(prog);
525  }
526 
527  if (progress.wasCanceled()) break;
528  }
529 
530  if (display == tabWidget->currentIndex())
531  {
532  lcdDisplayedMsgs->display(msgFilters_[display].nDisplayMsgs);
533  }
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  if (msgFilters_[d].filterExpression == filterExpression)
738  {
739  tabWidget->setCurrentIndex(d);
740  return;
741  }
742  }
743 
744  {
745  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
746  for (auto app = 0; app < appFilter.size(); ++app)
747  { // app-sev index
748  auto it = app_msgs_.find(appFilter[app]);
749  if (it != app_msgs_.end())
750  {
751  msgs_t temp(it->second);
752  TLOG(10) << "setFilter: app " << appFilter[app].toStdString() << " has " << temp.size() << " messages";
753  result.merge(temp);
754  }
755  }
756  TLOG(10) << "setFilter: result contains %zu messages", result.size();
757 
758  if (!hostFilter.isEmpty())
759  {
760  msgs_t hostResult;
761  for (auto host = 0; host < hostFilter.size(); ++host)
762  { // host index
763  auto it = host_msgs_.find(hostFilter[host]);
764  if (it != host_msgs_.end())
765  {
766  msgs_t temp(it->second);
767  TLOG(10) << "setFilter: host " << hostFilter[host].toStdString() << " has " << temp.size() << " messages";
768  hostResult.merge(temp);
769  }
770  }
771  if (result.empty())
772  {
773  result = hostResult;
774  }
775  else
776  {
777  result = list_intersect(result, hostResult);
778  }
779  TLOG(10) << "setFilter: result contains " << result.size() << " messages";
780  }
781 
782  if (!catFilter.isEmpty())
783  {
784  msgs_t catResult;
785  for (auto cat = 0; cat < catFilter.size(); ++cat)
786  { // cat index
787  auto it = cat_msgs_.find(catFilter[cat]);
788  if (it != cat_msgs_.end())
789  {
790  msgs_t temp(it->second);
791  TLOG(10) << "setFilter: cat " << catFilter[cat].toStdString() << " has " << temp.size() << " messages";
792  catResult.merge(temp);
793  }
794  }
795  if (result.empty())
796  {
797  result = catResult;
798  }
799  else
800  {
801  result = list_intersect(result, catResult);
802  }
803  TLOG(10) << "setFilter: result contains " << result.size() << " messages";
804  }
805  }
806 
807  // Add the tab and populate it
808 
809  auto newTabTitle = QString("Filter ") + QString::number(++nFilters);
810  auto newTab = new QWidget();
811 
812  auto txtDisplay = new QPlainTextEdit(newTab);
813  auto doc = new QTextDocument(txtDisplay);
814  txtDisplay->setDocument(doc);
815 
816  auto layout = new QVBoxLayout();
817  layout->addWidget(txtDisplay);
818  layout->setContentsMargins(0, 0, 0, 0);
819  newTab->setLayout(layout);
820 
821  MsgFilterDisplay filteredMessages;
822  filteredMessages.msgs = result;
823  filteredMessages.hostFilter = hostFilter;
824  filteredMessages.appFilter = appFilter;
825  filteredMessages.catFilter = catFilter;
826  filteredMessages.filterExpression = filterExpression;
827  filteredMessages.txtDisplay = txtDisplay;
828  filteredMessages.nDisplayMsgs = result.size();
829  filteredMessages.nDisplayedDeletedMsgs = 0;
830  filteredMessages.sevThresh = SINFO;
831  {
832  std::lock_guard<std::mutex> lk(filter_mutex_);
833  msgFilters_.push_back(filteredMessages);
834  }
835  tabWidget->addTab(newTab, newTabTitle);
836  tabWidget->setTabToolTip(tabWidget->count() - 1, filterExpression);
837  tabWidget->setCurrentIndex(tabWidget->count() - 1);
838 
839  displayMsgs(msgFilters_.size() - 1);
840 }
841 
843 {
844  if (!paused)
845  {
846  paused = true;
847  btnPause->setText("Resume Scrolling");
848  // QMessageBox::about(this, "About MsgViewer", "Message receiving paused ...");
849  }
850  else
851  {
852  paused = false;
853  btnPause->setText("Pause Scrolling");
854  scrollToBottom();
855  }
856 }
857 
858 void msgViewerDlg::exit() { close(); }
859 
861 {
862  int ret =
863  QMessageBox::question(this, tr("Message Viewer"), tr("Are you sure you want to clear all received messages?"),
864  QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
865  switch (ret)
866  {
867  case QMessageBox::Yes:
868  nMsgs = 0;
869  nSupMsgs = 0;
870  nThrMsgs = 0;
871  nDeleted = 0;
872  {
873  std::lock_guard<std::mutex> lk(msg_pool_mutex_);
874  msg_pool_.clear();
875  }
876  {
877  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
878  host_msgs_.clear();
879  cat_msgs_.clear();
880  app_msgs_.clear();
881  updateList(lwApplication, app_msgs_);
882  updateList(lwCategory, cat_msgs_);
883  updateList(lwHost, host_msgs_);
884  }
885  for (auto& display : msgFilters_)
886  {
887  std::lock_guard<std::mutex> lk(filter_mutex_);
888  display.txtDisplay->clear();
889  display.msgs.clear();
890  display.nDisplayMsgs = 0;
891  display.nDisplayedDeletedMsgs = 0;
892  }
893 
894  lcdMsgs->display(nMsgs);
895  lcdDisplayedMsgs->display(0);
896  break;
897  case QMessageBox::No:
898  default:
899  break;
900  }
901 }
902 
904 {
905  if (!shortMode_)
906  {
907  shortMode_ = true;
908  btnDisplayMode->setText("Long View");
909  }
910  else
911  {
912  shortMode_ = false;
913  btnDisplayMode->setText("Compact View");
914  }
915  updateDisplays();
916 }
917 
919 {
920  auto display = tabWidget->currentIndex();
921  switch (sev)
922  {
923  case SERROR:
924  setSevError();
925  break;
926 
927  case SWARNING:
928  setSevWarning();
929  break;
930 
931  case SINFO:
932  setSevInfo();
933  break;
934 
935  default:
936  setSevDebug();
937  }
938 
939  displayMsgs(display);
940 }
941 
942 void msgViewerDlg::setSevError()
943 {
944  auto display = tabWidget->currentIndex();
945  msgFilters_[display].sevThresh = SERROR;
946  btnError->setChecked(true);
947  btnWarning->setChecked(false);
948  btnInfo->setChecked(false);
949  btnDebug->setChecked(false);
950  vsSeverity->setValue(SERROR);
951 }
952 
953 void msgViewerDlg::setSevWarning()
954 {
955  auto display = tabWidget->currentIndex();
956  msgFilters_[display].sevThresh = SWARNING;
957  btnError->setChecked(false);
958  btnWarning->setChecked(true);
959  btnInfo->setChecked(false);
960  btnDebug->setChecked(false);
961  vsSeverity->setValue(SWARNING);
962 }
963 
964 void msgViewerDlg::setSevInfo()
965 {
966  auto display = tabWidget->currentIndex();
967  msgFilters_[display].sevThresh = SINFO;
968  btnError->setChecked(false);
969  btnWarning->setChecked(false);
970  btnInfo->setChecked(true);
971  btnDebug->setChecked(false);
972  vsSeverity->setValue(SINFO);
973 }
974 
975 void msgViewerDlg::setSevDebug()
976 {
977  auto display = tabWidget->currentIndex();
978  msgFilters_[display].sevThresh = SDEBUG;
979  btnError->setChecked(false);
980  btnWarning->setChecked(false);
981  btnInfo->setChecked(false);
982  btnDebug->setChecked(true);
983  vsSeverity->setValue(SDEBUG);
984 }
985 
986 void msgViewerDlg::renderMode()
987 {
988  simpleRender = !simpleRender;
989 
990  if (simpleRender)
991  {
992  btnRMode->setChecked(true);
993  for (auto display : msgFilters_)
994  {
995  display.txtDisplay->setPlainText(display.txtDisplay->toPlainText());
996  }
997  }
998  else
999  {
1000  btnRMode->setChecked(false);
1001  updateDisplays();
1002  }
1003 }
1004 
1005 void msgViewerDlg::searchMsg()
1006 {
1007  QString search = editSearch->text();
1008 
1009  if (search.isEmpty()) return;
1010 
1011  auto display = tabWidget->currentIndex();
1012  if (search != searchStr)
1013  {
1014  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::Start);
1015  if (!msgFilters_[display].txtDisplay->find(search))
1016  {
1017  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
1018  searchStr = "";
1019  }
1020  else
1021  searchStr = search;
1022  }
1023  else
1024  {
1025  if (!msgFilters_[display].txtDisplay->find(search))
1026  {
1027  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::Start);
1028  if (!msgFilters_[display].txtDisplay->find(search))
1029  {
1030  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
1031  searchStr = "";
1032  }
1033  }
1034  }
1035 }
1036 
1037 void msgViewerDlg::searchClear()
1038 {
1039  auto display = tabWidget->currentIndex();
1040  editSearch->setText("");
1041  searchStr = "";
1042  msgFilters_[display].txtDisplay->find("");
1043  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
1044 }
1045 
1046 void msgViewerDlg::setSuppression(QAction* act)
1047 {
1048  bool status = act->isChecked();
1049  auto sup = static_cast<suppress*>(act->data().value<void*>());
1050  sup->use(status);
1051 }
1052 
1053 void msgViewerDlg::setThrottling(QAction* act)
1054 {
1055  bool status = act->isChecked();
1056  auto thr = static_cast<throttle*>(act->data().value<void*>());
1057  thr->use(status);
1058 }
1059 
1060 void msgViewerDlg::tabWidgetCurrentChanged(int newTab)
1061 {
1062  displayMsgs(newTab);
1063  lcdDisplayedMsgs->display(msgFilters_[newTab].nDisplayMsgs);
1064 
1065  lwHost->setCurrentRow(-1, QItemSelectionModel::Clear);
1066  lwApplication->setCurrentRow(-1, QItemSelectionModel::Clear);
1067  lwCategory->setCurrentRow(-1, QItemSelectionModel::Clear);
1068 
1069  for (auto const& host : msgFilters_[newTab].hostFilter)
1070  {
1071  auto items = lwHost->findItems(host, Qt::MatchExactly);
1072  if (!items.empty())
1073  {
1074  items[0]->setSelected(true);
1075  }
1076  }
1077  for (auto const& app : msgFilters_[newTab].appFilter)
1078  {
1079  auto items = lwApplication->findItems(app, Qt::MatchExactly);
1080  if (!items.empty())
1081  {
1082  items[0]->setSelected(true);
1083  }
1084  }
1085  for (auto const& cat : msgFilters_[newTab].catFilter)
1086  {
1087  auto items = lwCategory->findItems(cat, Qt::MatchExactly);
1088  if (!items.empty())
1089  {
1090  items[0]->setSelected(true);
1091  }
1092  }
1093 
1094  switch (msgFilters_[newTab].sevThresh)
1095  {
1096  case SDEBUG:
1097  setSevDebug();
1098  break;
1099  case SINFO:
1100  setSevInfo();
1101  break;
1102  case SWARNING:
1103  setSevWarning();
1104  break;
1105  default:
1106  setSevError();
1107  break;
1108  }
1109 }
1110 
1111 void msgViewerDlg::tabCloseRequested(int tabIndex)
1112 {
1113  if (tabIndex == 0 || static_cast<size_t>(tabIndex) >= msgFilters_.size()) return;
1114 
1115  auto widget = tabWidget->widget(tabIndex);
1116  tabWidget->removeTab(tabIndex);
1117  delete widget;
1118 
1119  msgFilters_.erase(msgFilters_.begin() + tabIndex);
1120 }
1121 
1122 void msgViewerDlg::closeEvent(QCloseEvent* event) { event->accept(); }
1123 
1124 QStringList msgViewerDlg::toQStringList(QList<QListWidgetItem*> in)
1125 {
1126  QStringList out;
1127 
1128  for (auto i = 0; i < in.size(); ++i)
1129  {
1130  out << in[i]->text();
1131  }
1132 
1133  return out;
1134 }
void use(bool flag)
Enable or disable this throttle
Definition: throttle.hh:40
void exit()
Exit the program.
Definition: mvdlg.cc:858
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:860
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:842
void changeSeverity(int sev)
Change the severity threshold.
Definition: mvdlg.cc:918
void shortMode()
Switch to/from Short message mode.
Definition: mvdlg.cc:903
void closeEvent(QCloseEvent *event)
Perform actions on window close.
Definition: mvdlg.cc:1122