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