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