artdaq_mfextensions  v1_05_03a
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  std::lock_guard<std::mutex> lk(filter_mutex_);
352  msgFilters_[d].msgs.push_back(msg);
353  if ((int)d == tabWidget->currentIndex())
354  displayMsg(msg, d);
355  }
356  }
357 }
358 
359 void msgViewerDlg::trim_msg_pool()
360 {
361  bool host_list_update = false;
362  bool app_list_update = false;
363  bool cat_list_update = false;
364  {
365  std::lock_guard<std::mutex> lk(msg_pool_mutex_);
366  while (maxMsgs > 0 && msg_pool_.size() > maxMsgs)
367  {
368  QString const& app = msg_pool_.front()->app();
369  QString const& cat = msg_pool_.front()->cat();
370  QString const& host = msg_pool_.front()->host();
371 
372  // Check if we can remove an app/host/category
373  {
374  auto catIter = std::find(cat_msgs_[cat].begin(), cat_msgs_[cat].end(), msg_pool_.front());
375  auto hostIter = std::find(host_msgs_[host].begin(), host_msgs_[host].end(), msg_pool_.front());
376  auto appIter = std::find(app_msgs_[app].begin(), app_msgs_[app].end(), msg_pool_.front());
377  if (catIter != cat_msgs_[cat].end()) cat_msgs_[cat].erase(catIter);
378  if (hostIter != host_msgs_[host].end()) host_msgs_[host].erase(hostIter);
379  if (appIter != app_msgs_[app].end()) app_msgs_[app].erase(appIter);
380 
381  if (app_msgs_[app].empty())
382  {
383  app_msgs_.erase(app);
384  app_list_update = true;
385  }
386  if (cat_msgs_[cat].empty())
387  {
388  cat_msgs_.erase(cat);
389  cat_list_update = true;
390  }
391  if (host_msgs_[host].empty())
392  {
393  host_msgs_.erase(host);
394  host_list_update = true;
395  }
396  }
397 
398  // Finally, remove the message from the pool so it doesn't appear in new filters
399  msg_pool_.erase(msg_pool_.begin());
400  ++nDeleted;
401  }
402  }
403  {
404  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
405  if (host_list_update)
406  updateList(lwHost, host_msgs_);
407  if (app_list_update)
408  updateList(lwApplication, app_msgs_);
409  if (cat_list_update)
410  updateList(lwCategory, cat_msgs_);
411  }
412 
413  for (size_t d = 0; d < msgFilters_.size(); ++d)
414  {
415  {
416  std::lock_guard<std::mutex> lk(filter_mutex_);
417  while (msgFilters_[d].msgs.size() > maxMsgs)
418  {
419  if ((*msgFilters_[d].msgs.begin())->sev() >= msgFilters_[d].sevThresh)
420  msgFilters_[d].nDisplayedDeletedMsgs++;
421  msgFilters_[d].msgs.erase(msgFilters_[d].msgs.begin());
422  }
423  }
424 
425  if ((int)d == tabWidget->currentIndex())
426  {
427  if (maxDeletedMsgs > 0 && msgFilters_[d].nDisplayedDeletedMsgs > static_cast<int>(maxDeletedMsgs))
428  {
429  displayMsgs(d);
430  }
431  lcdDisplayedDeleted->display(msgFilters_[d].nDisplayedDeletedMsgs);
432  }
433  }
434 
435  lcdDeletedCount->display(nDeleted);
436 }
437 
438 void msgViewerDlg::update_index(msg_ptr_t const& it)
439 {
440  std::lock_guard<std::mutex> lk(msg_classification_mutex_);
441  QString const& app = it->app();
442  QString const& cat = it->cat();
443  QString const& host = it->host();
444 
445  if (cat_msgs_.find(cat) == cat_msgs_.end())
446  {
447  cat_msgs_[cat].push_back(it);
448  updateList(lwCategory, cat_msgs_);
449  }
450  else
451  {
452  cat_msgs_[cat].push_back(it);
453  }
454 
455  if (host_msgs_.find(host) == host_msgs_.end())
456  {
457  host_msgs_[host].push_back(it);
458  updateList(lwHost, host_msgs_);
459  }
460  else
461  {
462  host_msgs_[host].push_back(it);
463  }
464 
465  if (app_msgs_.find(app) == app_msgs_.end())
466  {
467  app_msgs_[app].push_back(it);
468  updateList(lwApplication, app_msgs_);
469  }
470  else
471  {
472  app_msgs_[app].push_back(it);
473  }
474 }
475 
476 void msgViewerDlg::displayMsg(msg_ptr_t const& it, int display)
477 {
478  if (it->sev() < msgFilters_[display].sevThresh) return;
479 
480  msgFilters_[display].nDisplayMsgs++;
481  if (display == tabWidget->currentIndex())
482  {
483  lcdDisplayedMsgs->display(msgFilters_[display].nDisplayMsgs);
484  }
485 
486  auto txt = it->text(shortMode_);
487  QStringList txts;
488  txts.push_back(txt);
489  UpdateTextAreaDisplay(txts, msgFilters_[display].txtDisplay);
490 }
491 
492 void msgViewerDlg::displayMsgs(int display)
493 {
494  int n = 0;
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  n = msgFilters_[display].msgs.size();
503 
504  QProgressDialog progress("Fetching data...", "Cancel", 0, n / 1000, this);
505 
506  progress.setWindowModality(Qt::WindowModal);
507  progress.setMinimumDuration(2000); // 2 seconds
508 
509  int i = 0, prog = 0;
510 
511  for (auto it = msgFilters_[display].msgs.begin(); it != msgFilters_[display].msgs.end(); ++it, ++i)
512  {
513  if ((*it)->sev() >= msgFilters_[display].sevThresh)
514  {
515  txts.push_back((*it)->text(shortMode_));
516  ++msgFilters_[display].nDisplayMsgs;
517  }
518 
519  if (i == 1000)
520  {
521  i = 0;
522  ++prog;
523  progress.setValue(prog);
524  }
525 
526  if (progress.wasCanceled()) break;
527  }
528 
529  if (display == tabWidget->currentIndex())
530  {
531  lcdDisplayedMsgs->display(msgFilters_[display].nDisplayMsgs);
532  }
533  }
534  UpdateTextAreaDisplay(txts, msgFilters_[display].txtDisplay);
535 }
536 
537 // https://stackoverflow.com/questions/13559990/how-to-append-text-to-qplaintextedit-without-adding-newline-and-keep-scroll-at
538 void msgViewerDlg::UpdateTextAreaDisplay(QStringList const& texts, QPlainTextEdit* widget)
539 {
540  const QTextCursor old_cursor = widget->textCursor();
541  const int old_scrollbar_value = widget->verticalScrollBar()->value();
542  const bool is_scrolled_down =
543  old_scrollbar_value >= widget->verticalScrollBar()->maximum() * 0.95; // At least 95% scrolled down
544 
545  if (!paused && !is_scrolled_down)
546  {
547  pause();
548  }
549 
550  QTextCursor new_cursor = QTextCursor(widget->document());
551 
552  new_cursor.beginEditBlock();
553  new_cursor.movePosition(QTextCursor::End);
554 
555  for (int i = 0; i < texts.size(); i++)
556  {
557  new_cursor.insertBlock();
558  new_cursor.insertHtml(texts.at(i));
559  if (!shortMode_) new_cursor.insertBlock();
560  }
561  new_cursor.endEditBlock();
562 
563  if (old_cursor.hasSelection() || paused)
564  {
565  // The user has selected text or scrolled away from the bottom: maintain position.
566  widget->setTextCursor(old_cursor);
567  widget->verticalScrollBar()->setValue(old_scrollbar_value);
568  }
569  else
570  {
571  // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom.
572  widget->moveCursor(QTextCursor::End);
573  widget->verticalScrollBar()->setValue(widget->verticalScrollBar()->maximum());
574  widget->horizontalScrollBar()->setValue(0);
575  }
576 }
577 
578 void msgViewerDlg::scrollToBottom()
579 {
580  int display = tabWidget->currentIndex();
581  msgFilters_[display].txtDisplay->moveCursor(QTextCursor::End);
582  msgFilters_[display].txtDisplay->verticalScrollBar()->setValue(
583  msgFilters_[display].txtDisplay->verticalScrollBar()->maximum());
584  msgFilters_[display].txtDisplay->horizontalScrollBar()->setValue(0);
585 }
586 
587 void msgViewerDlg::updateDisplays()
588 {
589  for (size_t ii = 0; ii < msgFilters_.size(); ++ii)
590  {
591  displayMsgs(ii);
592  }
593 }
594 
595 bool msgViewerDlg::updateList(QListWidget* lw, msgs_map_t const& map)
596 {
597  bool nonSelectedBefore = (lw->currentRow() == -1);
598  bool nonSelectedAfter = true;
599 
600  QString item = nonSelectedBefore ? "" : lw->currentItem()->text();
601 
602  lw->clear();
603  int row = 0;
604  auto it = map.begin();
605 
606  while (it != map.end())
607  {
608  lw->addItem(it->first);
609  if (!nonSelectedBefore && nonSelectedAfter)
610  {
611  if (item == it->first)
612  {
613  lw->setCurrentRow(row);
614  nonSelectedAfter = false;
615  }
616  }
617  ++row;
618  ++it;
619  }
620 
621  if (!nonSelectedBefore && nonSelectedAfter) return true;
622 
623  return false;
624 }
625 
626 msgs_t msgViewerDlg::list_intersect(msgs_t const& l1, msgs_t const& l2)
627 {
628  msgs_t output;
629  auto it1 = l1.begin();
630  auto it2 = l2.begin();
631 
632  while (it1 != l1.end() && it2 != l2.end())
633  {
634  if (*it1 < *it2)
635  {
636  ++it1;
637  }
638  else if (*it2 < *it1)
639  {
640  ++it2;
641  }
642  else
643  {
644  output.push_back(*it1);
645  ++it1;
646  ++it2;
647  }
648  }
649 
650  TLOG(10) << "list_intersect: output list has " << output.size() << " entries";
651  return output;
652 }
653 
654 std::string sev_to_string(sev_code_t s)
655 {
656  switch (s)
657  {
658  case SDEBUG:
659  return "DEBUG";
660  case SINFO:
661  return "INFO";
662  case SWARNING:
663  return "WARNING";
664  case SERROR:
665  return "ERROR";
666  }
667  return "UNKNOWN";
668 }
669 
670 void msgViewerDlg::setFilter()
671 {
672  auto hostFilter = toQStringList(lwHost->selectedItems());
673  auto appFilter = toQStringList(lwApplication->selectedItems());
674  auto catFilter = toQStringList(lwCategory->selectedItems());
675 
676  lwHost->setCurrentRow(-1, QItemSelectionModel::Clear);
677  lwApplication->setCurrentRow(-1, QItemSelectionModel::Clear);
678  lwCategory->setCurrentRow(-1, QItemSelectionModel::Clear);
679 
680  if (hostFilter.isEmpty() && appFilter.isEmpty() && catFilter.isEmpty())
681  {
682  return;
683  }
684 
685  msgs_t result;
686  QString catFilterExpression = "";
687  QString hostFilterExpression = "";
688  QString appFilterExpression = "";
689  bool first = true;
690  {
691  for (auto app = 0; app < appFilter.size(); ++app)
692  { // app-sev index
693  appFilterExpression += QString(first ? "" : " || ") + appFilter[app];
694  first = false;
695  }
696  TLOG(10) << "setFilter: result contains %zu messages", result.size();
697 
698  first = true;
699  if (!hostFilter.isEmpty())
700  {
701  msgs_t hostResult;
702  for (auto host = 0; host < hostFilter.size(); ++host)
703  { // host index
704  hostFilterExpression += QString(first ? "" : " || ") + hostFilter[host];
705  first = false;
706  }
707  }
708 
709  first = true;
710  if (!catFilter.isEmpty())
711  {
712  msgs_t catResult;
713  for (auto cat = 0; cat < catFilter.size(); ++cat)
714  { // cat index
715  catFilterExpression += QString(first ? "" : " || ") + catFilter[cat];
716  first = false;
717  }
718  }
719  }
720  // Create the filter expression
721  auto nFilterExpressions =
722  (appFilterExpression != "" ? 1 : 0) + (hostFilterExpression != "" ? 1 : 0) + (catFilterExpression != "" ? 1 : 0);
723  QString filterExpression = "";
724  if (nFilterExpressions == 1)
725  {
726  filterExpression = catFilterExpression + hostFilterExpression + appFilterExpression;
727  }
728  else
729  {
730  filterExpression = "(" + (catFilterExpression != "" ? catFilterExpression + ") && (" : "") + hostFilterExpression +
731  (hostFilterExpression != "" && appFilterExpression != "" ? ") && (" : "") + appFilterExpression +
732  ")";
733  }
734 
735  for (size_t d = 0; d < msgFilters_.size(); ++d)
736  {
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