Fraxinus  18.10
An IGT application
cxConsoleWidget.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 
13 #include "cxConsoleWidget.h"
14 
15 #include <iostream>
16 #include <QVBoxLayout>
17 #include <QMenu>
18 #include <QScrollBar>
19 #include <QContextMenuEvent>
20 #include "cxTypeConversions.h"
21 #include "cxEnumConverter.h"
22 #include "cxDefinitionStrings.h"
23 #include "cxSettings.h"
24 #include "cxStringProperty.h"
25 #include "cxHelperWidgets.h"
27 #include "cxMessageListener.h"
28 #include "cxEnumConverter.h"
29 #include "cxUtilHelpers.h"
30 #include <QTimer>
31 #include <QThread>
32 #include <QTableWidget>
33 #include "cxLogMessageFilter.h"
34 #include <QHeaderView>
35 #include <QStackedLayout>
36 #include <QApplication>
37 #include <QClipboard>
38 #include "cxNullDeleter.h"
39 #include <QPushButton>
40 #include "cxProfile.h"
41 #include "cxTime.h"
42 #include "cxPopupToolbarWidget.h"
43 
44 namespace cx
45 {
46 
48 {
49  this->createTextCharFormats();
50 }
51 
53 {
54  mFormat[mlINFO].setForeground(Qt::black);
55  mFormat[mlSUCCESS].setForeground(QColor(60, 179, 113)); // medium sea green
56  mFormat[mlWARNING].setForeground(QColor(255, 140, 0)); //dark orange
57  mFormat[mlERROR].setForeground(Qt::red);
58  mFormat[mlDEBUG].setForeground(QColor(135, 206, 250)); //sky blue
59  mFormat[mlCERR].setForeground(Qt::red);
60  mFormat[mlCOUT].setForeground(Qt::darkGray);
61 }
62 
66 
67 class MyTableWidget : public QTableWidget
68 {
69 public:
70  MyTableWidget(QWidget* parent=NULL) : QTableWidget(parent) {}
71  virtual ~MyTableWidget() {}
72  virtual void keyPressEvent(QKeyEvent* event);
73 };
74 
75 //source: http://stackoverflow.com/questions/3135737/copying-part-of-qtableview
76 void MyTableWidget::keyPressEvent(QKeyEvent* event)
77 {
78  // If Ctrl-C typed
79  // Or use event->matches(QKeySequence::Copy)
80  if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier))
81  {
82  QModelIndexList cells = selectedIndexes();
83  qSort(cells); // Necessary, otherwise they are in column order
84 
85  QString text;
86  int currentRow = 0; // To determine when to insert newlines
87  foreach (const QModelIndex& cell, cells) {
88  if (text.length() == 0) {
89  // First item
90  } else if (cell.row() != currentRow) {
91  // New row
92  text += '\n';
93  } else {
94  // Next cell
95  text += '\t';
96  }
97  currentRow = cell.row();
98  text += cell.data().toString();
99  }
100 
101  QApplication::clipboard()->setText(text);
102  }
103  else
104  {
105  event->ignore();
106  }
107 
108  QTableWidget::keyPressEvent(event);
109 }
110 
112 
114  LogMessageDisplayWidget(parent),
115  mOptions(options),
116  mScrollToBottomEnabled(true)
117 {
118  mTable = new MyTableWidget(this);
119  QVBoxLayout* layout = new QVBoxLayout(this);
120  layout->setMargin(0);
121  this->setLayout(layout);
122  layout->addWidget(mTable);
123 
124  mTable->setShowGrid(false);
125  mTable->setTextElideMode(Qt::ElideLeft);
126  mTable->setWordWrap(false);
127 
128  mTable->setColumnCount(6);
129  mTable->setRowCount(0);
130  mTable->setHorizontalHeaderLabels(QStringList() << "time" << "source" << "function" << "thread" << "level" << "description");
131  mTable->setSelectionBehavior(QAbstractItemView::SelectRows);
132  mTable->verticalHeader()->hide();
133 
134  QFontMetrics metric(this->font());
135  mTable->setColumnWidth(0, metric.width("00:00:00.000xx"));
136  mTable->setColumnWidth(1, metric.width("cxSourceFile.cpp:333xx"));
137  mTable->setColumnWidth(2, metric.width("function()xx"));
138  mTable->setColumnWidth(3, metric.width("mainxx"));
139  mTable->setColumnWidth(4, metric.width("WARNINGxx"));
140  mTable->horizontalHeader()->setStretchLastSection(true);
141 
142  for (int i=0; i<mTable->horizontalHeader()->count(); ++i)
143  {
144  XmlOptionItem headerItem("headerwidth_"+QString::number(i), mOptions.getElement());
145  int value = headerItem.readValue("-1").toInt();
146  if (value<0)
147  continue;
148  mTable->setColumnWidth(i, value);
149  }
150 
151  int textLineHeight = metric.lineSpacing();
152  mTable->verticalHeader()->setDefaultSectionSize(textLineHeight);
153 }
154 
156 {
157  for (int i=0; i<mTable->horizontalHeader()->count(); ++i)
158  {
159  XmlOptionItem headerItem("headerwidth_"+QString::number(i), mOptions.getElement());
160  headerItem.writeValue(QString::number(mTable->columnWidth(i)));
161  }
162 }
163 
165 {
166  mTable->setRowCount(0);
167 }
168 
170 {
171  mTable->horizontalScrollBar()->setValue(mTable->horizontalScrollBar()->minimum());
172 }
173 
175 {
176  int row = mTable->rowCount();
177  mTable->insertRow(row);
178 
179  QString timestamp = message.getTimeStamp().toString("hh:mm:ss.zzz");
180  this->addItem(0, timestamp, message);
181 
182  QString source;
183  if (!message.mSourceFile.isEmpty())
184  source = QString("%1:%2").arg(message.mSourceFile).arg(message.mSourceLine);
185  this->addItem(1, source, message);
186 
187  QString function = message.mSourceFunction;
188  this->addItem(2, function, message);
189 
190  QString thread = message.mThread;
191  this->addItem(3, thread, message);
192 
193  QString level = enum2string(message.getMessageLevel());
194  this->addItem(4, level, message);
195 
196  QString desc = message.getText();
197  this->addItem(5, desc, message);
198 
199  this->scrollToBottom();
200 }
201 
203 {
205  this->scrollToBottom();
206 }
207 
209 {
210  mTable->horizontalHeader()->setVisible(on);
211 }
212 
214 {
216  QTimer::singleShot(0, mTable, SLOT(scrollToBottom()));
217 }
218 
219 QTableWidgetItem* DetailedLogMessageDisplayWidget::addItem(int column, QString text, const Message& message)
220 {
221  int row = mTable->rowCount();
222 
223  QTableWidgetItem* item = new QTableWidgetItem(text);
224  item->setStatusTip(text);
225  item->setToolTip(text);
226  item->setForeground(mFormat[message.getMessageLevel()].foreground());
227  item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
228  mTable->setItem(row-1, column, item);
229  return item;
230 }
231 
235 
237  LogMessageDisplayWidget(parent),
239 {
240  mBrowser = new QTextBrowser(this);
241  mBrowser->setReadOnly(true);
242  mBrowser->setLineWrapMode(QTextEdit::NoWrap);
243  QVBoxLayout* layout = new QVBoxLayout(this);
244  layout->setMargin(0);
245  this->setLayout(layout);
246  layout->addWidget(mBrowser);
247  this->scrollToBottom();
248 }
249 
251 {
252  mBrowser->clear();
253  this->scrollToBottom();
254 }
255 
257 {
258  mBrowser->horizontalScrollBar()->setValue(mBrowser->horizontalScrollBar()->minimum());
259 }
260 
262 {
263  this->format(message);
264 
265  mBrowser->append(this->getCompactMessage(message));
266 
267  this->scrollToBottom();
268 }
269 
271 {
272  mScrollToBottomEnabled = on;
273  this->scrollToBottom();
274 }
275 
276 void SimpleLogMessageDisplayWidget::scrollToBottom()
277 {
278  if (mScrollToBottomEnabled)
279  mBrowser->verticalScrollBar()->setValue(mBrowser->verticalScrollBar()->maximum());
280 }
281 
283 {
284  if(message.mMessageLevel == mlRAW)
285  return message.mText;
286 
287  QString retval;
288  retval= QString("[%1] %2")
289 // .arg(message.mTimeStamp.toString(timestampSecondsFormatNice()))
290  .arg(message.mTimeStamp.toString("hh:mm"))
291  .arg(message.mText);
292  return retval;
293 }
294 
296 {
297  if (!mFormat.count(message.getMessageLevel()))
298  return;
299  mBrowser->setCurrentCharFormat(mFormat[message.getMessageLevel()]);
300 }
301 
305 
306 ConsoleWidget::ConsoleWidget(QWidget* parent, QString uid, QString name, XmlOptionFile options, LogPtr log) :
307  BaseWidget(parent, uid, name),
308  mSeverityAction(NULL),
309  mMessagesWidget(NULL),
310  mMessagesLayout(NULL),
311  mDetailsAction(NULL)
312 {
313  mOptions = options;
314  mLog = log;
315  connect(mLog.get(), &Log::loggingFolderChanged, this, &ConsoleWidget::onLoggingFolderChanged);
316 
317  this->setModified();
318 }
319 
320 ConsoleWidget::ConsoleWidget(QWidget* parent, QString uid, QString name) :
321  BaseWidget(parent, uid, name),
322  mSeverityAction(NULL),
323  mMessagesWidget(NULL),
324  mMessagesLayout(NULL),
325  mDetailsAction(NULL)
326 {
327  mOptions = profile()->getXmlSettings().descend(this->objectName());
328  mLog = reporter();
329  connect(mLog.get(), &Log::loggingFolderChanged, this, &ConsoleWidget::onLoggingFolderChanged);
330 
331  this->setModified();
332 }
333 
335 {
336  if (!mMessagesLayout)
337  {
338  this->createUI();
339  }
340 
341 }
342 
343 void ConsoleWidget::createUI()
344 {
345  mSeverityAction = NULL;
346  mMessagesWidget = NULL;
347 
348  this->setToolTip("Display system information, warnings and errors.");
349 
350  QVBoxLayout* layout = new QVBoxLayout;
351  layout->setMargin(0);
352  layout->setSpacing(0);
353  this->setLayout(layout);
354 
355  mPopupWidget = new PopupToolbarWidget(this);
356  connect(mPopupWidget, &PopupToolbarWidget::popup, this, &ConsoleWidget::updateShowHeader);
357  layout->addWidget(mPopupWidget);
358  this->createButtonWidget(mPopupWidget->getToolbar());
359 
360  mMessagesLayout = new QVBoxLayout;
361  mMessagesLayout->setMargin(0);
362  layout->addLayout(mMessagesLayout);
363 
364  mMessageListener = MessageListener::create(mLog);
365  mMessageFilter.reset(new MessageFilterConsole);
366  mMessageListener->installFilter(mMessageFilter);
367  connect(mMessageListener.get(), &MessageListener::newMessage, this, &ConsoleWidget::receivedMessage);
368  connect(mMessageListener.get(), &MessageListener::newChannel, this, &ConsoleWidget::receivedChannel);
369 
370  QString defVal = enum2string<LOG_SEVERITY>(msINFO);
371  LOG_SEVERITY value = string2enum<LOG_SEVERITY>(this->option("showLevel").readValue(defVal));
372  mMessageFilter->setLowestSeverity(value);
373 
374  mMessageFilter->setActiveChannel(mChannelSelector->getValue());
375  mMessageListener->installFilter(mMessageFilter);
376 
377  this->updateUI();
378 }
379 
380 void ConsoleWidget::createButtonWidget(QWidget* widget)
381 {
382  QHBoxLayout* buttonLayout = new QHBoxLayout(widget);
383  buttonLayout->setMargin(0);
384  buttonLayout->setSpacing(0);
385 
386  this->addSeverityButtons(buttonLayout);
387  buttonLayout->addSpacing(8);
388  this->addDetailsButton(buttonLayout);
389 
390  this->createChannelSelector();
391 
392  LabeledComboBoxWidget* channelSelectorWidget = new LabeledComboBoxWidget(this, mChannelSelector);
393  channelSelectorWidget->showLabel(false);
394  buttonLayout->addSpacing(8);
395  buttonLayout->addWidget(channelSelectorWidget);
396  buttonLayout->setStretch(buttonLayout->count()-1, 0);
397 
398  buttonLayout->addStretch(1);
399 }
400 
401 void ConsoleWidget::updateShowHeader()
402 {
403  bool show = mPopupWidget->popupIsVisible();
404  mMessagesWidget->showHeader(show);
405 }
406 
407 
408 XmlOptionItem ConsoleWidget::option(QString name)
409 {
410  return XmlOptionItem(name, mOptions.getElement());
411 }
412 
414 {
415  if (!mMessageFilter)
416  return;
417 
418  QString levelString = enum2string<LOG_SEVERITY>(mMessageFilter->getLowestSeverity());
419  this->option("showLevel").writeValue(levelString);
420  this->option("showDetails").writeVariant(mDetailsAction->isChecked());
421 }
422 
424 {
425  if (mDetailsAction)
426  {
427  mDetailsAction->setChecked(on);
428  this->updateUI();
429  }
430  else
431  {
432  this->option("showDetails").writeVariant(on);
433  }
434 }
435 
436 void ConsoleWidget::addSeverityButtons(QBoxLayout* buttonLayout)
437 {
438  QAction* actionUp = this->createAction(this,
439  QIcon(":/icons/open_icon_library/zoom-in-3.png"),
440  "More", "More detailed log output",
441  SLOT(onSeverityDown()),
442  buttonLayout, new CXSmallToolButton());
443 
444  this->addSeverityIndicator(buttonLayout);
445 
446  QAction* actionDown = this->createAction(this,
447  QIcon(":/icons/open_icon_library/zoom-out-3.png"),
448  "Less ", "Less detailed log output",
449  SLOT(onSeverityUp()),
450  buttonLayout, new CXSmallToolButton());
451 }
452 
453 void ConsoleWidget::addSeverityIndicator(QBoxLayout* buttonLayout)
454 {
455  QAction* action = new QAction(QIcon(""), "Severity", this);
456  mSeverityAction = action;
457  QString help = "Lowest displayed log severity";
458  action->setStatusTip(help);
459  action->setWhatsThis(help);
460  action->setToolTip(help);
461  QToolButton* button = new CXSmallToolButton();
462  button->setDefaultAction(action);
463  buttonLayout->addWidget(button);
464 }
465 
466 void ConsoleWidget::updateSeverityIndicator()
467 {
468  LOG_SEVERITY severity = mMessageFilter->getLowestSeverity();
469 
470  switch (severity)
471  {
472  case msERROR:
473  this->updateSeverityIndicator("window-close-3.png", "error");
474  break;
475  case msWARNING:
476  this->updateSeverityIndicator("dialog-warning-panel.png", "warning");
477  break;
478  case msINFO:
479  this->updateSeverityIndicator("dialog-information-4.png", "info");
480  break;
481  case msDEBUG:
482  this->updateSeverityIndicator("script-error.png", "debug");
483  break;
484  default:
485  this->updateSeverityIndicator("script-error.png", "");
486  break;
487  }
488 }
489 
490 void ConsoleWidget::updateSeverityIndicator(QString iconname, QString help)
491 {
492  QIcon icon(QString(":/icons/message_levels/%1").arg(iconname));
493  mSeverityAction->setIcon(icon);
494 
495  help = QString("Current log level is %1").arg(help);
496  mSeverityAction->setStatusTip(help);
497  mSeverityAction->setToolTip(help);
498 }
499 
500 void ConsoleWidget::onSeverityUp()
501 {
502  this->onSeverityChange(-1);
503 }
504 
505 void ConsoleWidget::onSeverityDown()
506 {
507  this->onSeverityChange(+1);
508 }
509 
510 void ConsoleWidget::onSeverityChange(int delta)
511 {
512  LOG_SEVERITY severity = mMessageFilter->getLowestSeverity();
513  int val = (int)severity + delta;
514  val = constrainValue(val, 0, int(msCOUNT)-1);
515  severity = static_cast<LOG_SEVERITY>(val);
516 
517  mMessageFilter->setLowestSeverity(severity);
518  mMessageListener->installFilter(mMessageFilter);
519  this->updateUI();
520 }
521 
522 void ConsoleWidget::createChannelSelector()
523 {
524  QString defval = "console";
525  mChannels << "all";
526  mChannels << defval;
527 
528  StringPropertyPtr retval;
529  retval = StringProperty::initialize("ChannelSelector",
530  "", "Log Channel to display",
531  defval, mChannels, mOptions.getElement());
532  connect(retval.get(), &StringPropertyBase::changed, this, &ConsoleWidget::onChannelSelectorChanged);
533  mChannelSelector = retval;
534 }
535 
536 void ConsoleWidget::addDetailsButton(QBoxLayout* buttonLayout)
537 {
538  QIcon icon(":/icons/open_icon_library/system-run-5.png");
539  QAction* action = this->createAction(this,
540  icon,
541  "Details", "Show detailed info on each log entry",
542  SLOT(updateUI()),
543  buttonLayout, new CXSmallToolButton());
544  action->setCheckable(true);
545 
546  bool value = this->option("showDetails").readVariant(false).toBool();
547  action->blockSignals(true);
548  action->setChecked(value);
549  action->blockSignals(false);
550 
551  mDetailsAction = action;
552 }
553 
554 void ConsoleWidget::updateUI()
555 {
556  this->updateSeverityIndicator();
557 
558  this->setWindowTitle("Console: " + mChannelSelector->getValue());
559  this->selectMessagesWidget();
560  this->updateShowHeader();
561  mPopupWidget->refresh();
562 
563  // reset content of browser
564  QTimer::singleShot(0, this, SLOT(clearTable())); // let the messages recently emitted be processed before clearing
565 
566  mMessageListener->restart();
567 }
568 
569 void ConsoleWidget::selectMessagesWidget()
570 {
571  if (mMessagesWidget && (mMessagesWidget->getType()==this->getDetailTypeFromButton()))
572  return;
573 
574  if (mMessagesWidget)
575  {
576  // remove
577  mMessagesLayout->takeAt(0);
578  delete mMessagesWidget;
579  }
580 
581  if (this->getDetailTypeFromButton()=="detail")
582  {
583  mMessagesWidget = new DetailedLogMessageDisplayWidget(this, mOptions);
584  }
585  else
586  {
587  mMessagesWidget = new SimpleLogMessageDisplayWidget(this);
588  }
589 
590  mMessagesLayout->addWidget(mMessagesWidget);
591 }
592 
593 QString ConsoleWidget::getDetailTypeFromButton() const
594 {
595  if (mDetailsAction->isChecked())
596  return "detail";
597  else
598  return "simple";
599 }
600 
601 void ConsoleWidget::clearTable()
602 {
603  if (mMessagesWidget)
604  mMessagesWidget->clear();
605 }
606 
607 void ConsoleWidget::onLoggingFolderChanged()
608 {
609  if (!mMessageFilter)
610  return;
611  mMessageListener->installFilter(mMessageFilter);
612  this->updateUI();
613 }
614 
615 void ConsoleWidget::onChannelSelectorChanged()
616 {
617  mChannelSelector->blockSignals(true);
618 
619  mMessageFilter->setActiveChannel(mChannelSelector->getValue());
620  mMessageListener->installFilter(mMessageFilter);
621  this->updateUI();
622 
623  mChannelSelector->blockSignals(false);
624 }
625 
626 void ConsoleWidget::contextMenuEvent(QContextMenuEvent* event)
627 {
628 // QMenu *menu = mBrowser->createStandardContextMenu();
629 // menu->addSeparator();
630 // menu->addAction(mLineWrappingAction);
631 // menu->exec(event->globalPos());
632 // delete menu;
633 }
634 
635 void ConsoleWidget::showEvent(QShowEvent* event)
636 {
637  if (mMessagesWidget)
638  mMessagesWidget->normalize();
639 }
640 
641 void ConsoleWidget::receivedChannel(QString channel)
642 {
643  if (!mChannels.count(channel))
644  {
645  mChannels.append(channel);
646  mChannelSelector->setValueRange(mChannels);
647  }
648 }
649 
650 void ConsoleWidget::receivedMessage(Message message)
651 {
652  this->receivedChannel(message.mChannel);
653 // if (!mChannels.count(message.mChannel))
654 // {
655 // mChannels.append(message.mChannel);
656 // mChannelSelector->setValueRange(mChannels);
657 // }
658 
659  this->printMessage(message);
660 }
661 
662 void ConsoleWidget::printMessage(const Message& message)
663 {
664  if (mMessagesWidget)
665  mMessagesWidget->add(message);
666 }
667 
668 //void ConsoleWidget::lineWrappingSlot(bool checked)
669 //{
671 //}
672 
673 }//namespace cx
static MessageListenerPtr create(LogPtr log=LogPtr())
mlSUCCESS
Definition: cxDefinitions.h:65
cxResource_EXPORT ProfilePtr profile()
Definition: cxProfile.cpp:160
QString getCompactMessage(Message message)
SimpleLogMessageDisplayWidget(QWidget *parent=NULL)
virtual void showHeader(bool on)=0
mlRAW
Definition: cxDefinitions.h:65
QString mSourceFile
Definition: cxLogMessage.h:77
DetailedLogMessageDisplayWidget(QWidget *parent, XmlOptionFile options)
MyTableWidget(QWidget *parent=NULL)
virtual void keyPressEvent(QKeyEvent *event)
ReporterPtr reporter()
Definition: cxReporter.cpp:38
void newChannel(QString channel)
msDEBUG
Definition: cxDefinitions.h:79
void writeVariant(const QVariant &val)
void format(const Message &message)
formats the text to suit the message level
void loggingFolderChanged()
mlCERR
Definition: cxDefinitions.h:65
virtual QString getType() const =0
QString getText() const
The raw message.
msINFO
Definition: cxDefinitions.h:79
virtual void setScrollToBottom(bool on)
Composite widget for string selection.
QString mThread
Definition: cxLogMessage.h:75
QDomElement getElement()
return the current element
mlDEBUG
Definition: cxDefinitions.h:65
QDateTime getTimeStamp() const
The time at which the message was created.
MESSAGE_LEVEL mMessageLevel
Definition: cxLogMessage.h:69
QAction * createAction(QObject *parent, QIcon iconName, QString text, QString tip, T slot, QLayout *layout=NULL, QToolButton *button=new QToolButton())
Definition: cxBaseWidget.h:129
Helper class for storing one string value in an xml document.
boost::shared_ptr< class StringProperty > StringPropertyPtr
QString mChannel
Definition: cxLogMessage.h:74
msERROR
Definition: cxDefinitions.h:79
boost::shared_ptr< class Log > LogPtr
Definition: cxLog.h:47
std::map< MESSAGE_LEVEL, QTextCharFormat > mFormat
double constrainValue(double val, double min, double max)
QTableWidgetItem * addItem(int column, QString text, const Message &message)
void createTextCharFormats()
sets up the formating rules for the message levels
virtual void showEvent(QShowEvent *event)
updates internal info before showing the widget
void changed()
emit when the underlying data value is changed: The user interface will be updated.
static StringPropertyPtr initialize(const QString &uid, QString name, QString help, QString value, QStringList range, QDomNode root=QDomNode())
void writeValue(const QString &val)
QDateTime mTimeStamp
Definition: cxLogMessage.h:71
virtual void add(const Message &message)=0
mlINFO
Definition: cxDefinitions.h:65
Interface for QWidget which handles widgets uniformly for the system.
Definition: cxBaseWidget.h:88
QString mSourceFunction
Definition: cxLogMessage.h:78
void contextMenuEvent(QContextMenuEvent *event)
void setDetails(bool on)
virtual void add(const Message &message)
QVariant readVariant(const QVariant &defval=QVariant()) const
void popup(bool show)
mlCOUT
Definition: cxDefinitions.h:65
QString mText
Definition: cxLogMessage.h:68
LogMessageDisplayWidget(QWidget *parent)
msWARNING
Definition: cxDefinitions.h:79
virtual void normalize()=0
mlWARNING
Definition: cxDefinitions.h:65
virtual void prePaintEvent()
ConsoleWidget(QWidget *parent, QString uid="console_widget", QString name="Console")
void newMessage(Message message)
virtual void add(const Message &message)
MESSAGE_LEVEL getMessageLevel() const
The category of the message.
mlERROR
Definition: cxDefinitions.h:65
QString enum2string(const ENUM &val)
Helper class for xml files used to store ssc/cx data.
QString readValue(const QString &defval) const
Namespace for all CustusX production code.