Mantid
Loading...
Searching...
No Matches
MessageDisplay.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7//-------------------------------------------
8// Includes
9//-------------------------------------------
11
13#include "MantidKernel/Logger.h"
16
17#include <QAction>
18#include <QActionGroup>
19#include <QCoreApplication>
20#include <QHBoxLayout>
21#include <QInputDialog>
22#include <QMenu>
23#include <QPlainTextEdit>
24#include <QPoint>
25#include <QScrollBar>
26#include <QSettings>
27#include <QSignalMapper>
28
29#include <Poco/Logger.h>
30#include <Poco/Message.h>
31#include <Poco/SplitterChannel.h>
32#include <Poco/Version.h>
33
34namespace {
35
36int DEFAULT_LINE_COUNT_MAX = 8192;
37const char *PRIORITY_KEY_NAME = "MessageDisplayPriority";
38const char *LINE_COUNT_MAX_KEY_NAME = "MessageDisplayLineCountMax";
39
40} // namespace
41
43
45
46//-------------------------------------------
47// Public member functions
48//-------------------------------------------
49
50MessageDisplaySettings::MessageDisplaySettings(int logLevel, int maximumLineCount)
51 : m_logLevel(logLevel), m_maximumLineCount(maximumLineCount) {}
52
54
56
58 return MessageDisplaySettings(storage.value(PRIORITY_KEY_NAME, 0).toInt(),
59 storage.value(LINE_COUNT_MAX_KEY_NAME, DEFAULT_LINE_COUNT_MAX).toInt());
60}
61
63 if (settings.logLevel() > 0)
64 ConfigService::Instance().setLogLevel(settings.logLevel(), true);
66}
67
69 return MessageDisplaySettings(Poco::Logger::root().getLevel(), maximumLineCount());
70}
71
72void MessageDisplay::saveSettings(QSettings &storage, const MessageDisplaySettings &settings) const {
73 QSettingsChangeAware writer(storage);
74 writer.setValue(PRIORITY_KEY_NAME, settings.logLevel());
75 writer.setValue(LINE_COUNT_MAX_KEY_NAME, settings.maximumLineCount());
76}
77
82MessageDisplay::MessageDisplay(QWidget *parent) : MessageDisplay(QFont(), parent) {}
83
89MessageDisplay::MessageDisplay(const QFont &font, QWidget *parent)
90 : QWidget(parent), m_logChannel(new QtSignalChannel), m_textDisplay(new QPlainTextEdit(this)), m_formats(),
91 m_loglevels(new QActionGroup(this)), m_logLevelMapping(new QSignalMapper(this)),
92 m_error(new QAction(tr("&Error"), this)), m_warning(new QAction(tr("&Warning"), this)),
93 m_notice(new QAction(tr("&Notice"), this)), m_information(new QAction(tr("&Information"), this)),
94 m_debug(new QAction(tr("&Debug"), this)) {
97 setupTextArea(font);
98}
99
101 // We only attach to splitter channels but we may not have been attached...
102 auto rootChannel = Poco::Logger::root().getChannel();
103#if POCO_VERSION > 0x01090400
104 // getChannel changed to return an AutoPtr
105 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel.get())) {
106#else
107 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel)) {
108#endif
109 splitChannel->removeChannel(m_logChannel);
110 }
111 // The Channel class is ref counted and will delete itself when required
112 m_logChannel->release();
113 delete m_textDisplay;
114}
115
123 // Setup logging. ConfigService needs to be started
124 auto &configSvc = ConfigService::Instance();
125 // The root channel might be a SplitterChannel
126 auto rootChannel = Poco::Logger::root().getChannel();
127#if POCO_VERSION > 0x01090400
128 // getChannel changed to return an AutoPtr
129 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel.get())) {
130#else
131 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel)) {
132#endif
133 splitChannel->addChannel(m_logChannel);
134 } else {
135 throw std::runtime_error("MessageDisplay requires the root logger to be configured with a SplitterChannel.\n"
136 "Set 'logging.loggers.root.channel.class = SplitterChannel' in properties file.");
137 }
138 connect(m_logChannel, SIGNAL(messageReceived(const Message &)), this, SLOT(append(const Message &)));
139 if (logLevel > 0) {
140 configSvc.setLogLevel(logLevel, true);
141 }
142}
143
149
154 m_textDisplay->clear();
155 for (auto const &msg : getHistory()) {
156 if (shouldBeDisplayed(msg)) {
157 m_textDisplay->textCursor().insertText(msg.text(), format(msg.priority()));
158 }
159 }
161}
162
169void MessageDisplay::filePathModified(const QString &oldPath, const QString &newPath) {
170 for (auto &msg : m_messageHistory) {
171 if (msg.scriptPath() == oldPath)
172 msg.setScriptPath(newPath);
173 }
174}
175
182 m_messageHistory.append(msg);
183 while (m_messageHistory.size() > maximumLineCount() && maximumLineCount() > 0)
184 // Use .removeAt(0) since .removeFirst asserts a !.isEmpty() check
185 m_messageHistory.removeAt(0);
186}
187
188//----------------------------------------------------------------------------------------
189// Public slots
190//----------------------------------------------------------------------------------------
194void MessageDisplay::appendFatal(const QString &text) { this->append(Message(text, Message::Priority::PRIO_FATAL)); }
195
199void MessageDisplay::appendError(const QString &text) { this->append(Message(text, Message::Priority::PRIO_ERROR)); }
200
204void MessageDisplay::appendWarning(const QString &text) {
205 this->append(Message(text, Message::Priority::PRIO_WARNING));
206}
207
211void MessageDisplay::appendNotice(const QString &text) { this->append(Message(text, Message::Priority::PRIO_NOTICE)); }
212
216void MessageDisplay::appendInformation(const QString &text) {
217 this->append(Message(text, Message::Priority::PRIO_INFORMATION));
218}
219
223void MessageDisplay::appendDebug(const QString &text) { this->append(Message(text, Message::Priority::PRIO_DEBUG)); }
224
230 appendToHistory(msg);
231 if (shouldBeDisplayed(msg) || msg.priority() <= Message::Priority::PRIO_WARNING) {
232 QTextCursor cursor = moveCursorToEnd();
233 cursor.insertText(msg.text(), format(msg.priority()));
235
236 if (msg.priority() <= Message::Priority::PRIO_ERROR) {
237 NotificationService::showMessage(parentWidget() ? parentWidget()->windowTitle() : "Mantid",
238 "Sorry, there was an error, please look at the message display for "
239 "details.",
240 NotificationService::MessageIcon::Critical);
241 emit errorReceived(msg.text());
242 }
243 if (msg.priority() <= Message::Priority::PRIO_WARNING)
244 emit warningReceived(msg.text());
245 }
246}
247
255void MessageDisplay::appendPython(const QString &text, const int &priority, const QString &filePath) {
256 Message msg = Message(text, static_cast<Message::Priority>(priority), filePath);
257 append(msg);
258}
259
264 clear();
265 append(msg.text());
266}
267
272 m_textDisplay->clear();
273 m_messageHistory.clear();
274}
275
280 QTextCursor cursor(m_textDisplay->textCursor());
281 cursor.movePosition(QTextCursor::End);
282 m_textDisplay->setTextCursor(cursor);
283 return cursor;
284}
285
290 return m_textDisplay->verticalScrollBar()->value() == m_textDisplay->verticalScrollBar()->maximum();
291}
292
297 // Code taken from QtCreator source
298 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->minimum());
299 // QPlainTextEdit destroys the first calls value in case of multiline
300 // text, so make sure that the scroll bar actually gets the value set.
301 // Is a noop if the first call succeeded.
302 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->minimum());
303}
304
309 // Code taken from QtCreator source
310 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->maximum());
311 // QPlainTextEdit destroys the first calls value in case of multiline
312 // text, so make sure that the scroll bar actually gets the value set.
313 // Is a noop if the first call succeeded.
314 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->maximum());
315}
316
317//-----------------------------------------------------------------------------
318// Private slot member functions
319//-----------------------------------------------------------------------------
320
321void MessageDisplay::showContextMenu(const QPoint &mousePos) {
322 QMenu *menu{generateContextMenu()};
323 menu->exec(this->mapToGlobal(mousePos));
324 delete menu;
325}
326
327/*
328 * @param priority An integer that must match the Poco::Message priority
329 * enumeration
330 */
331void MessageDisplay::setLogLevel(int priority) { ConfigService::Instance().setLogLevel(priority); }
332
337 constexpr int minLineCountAllowed(-1);
339 QInputDialog::getInt(this, "", "No. of lines\n(-1 keeps all content)", maximumLineCount(), minLineCountAllowed));
340}
341
342// The text edit works in blocks but it is not entirely clear what a block
343// is defined as. Experiments showed setting a max block count=1 suppressed
344// all output and a min(block count)==2 was required to see a single line.
345// We have asked the user for lines so add 1 to get the behaviour they
346// would expect. Equally we subtract 1 for the value we show them to
347// keep it consistent
348
352int MessageDisplay::maximumLineCount() const { return m_textDisplay->maximumBlockCount() - 1; }
353
358void MessageDisplay::setMaximumLineCount(int count) { m_textDisplay->setMaximumBlockCount(count + 1); }
359
360//-----------------------------------------------------------------------------
361// Private non-slot member functions
362//-----------------------------------------------------------------------------
364 QMenu *menu = m_textDisplay->createStandardContextMenu();
365 menu->addSeparator();
366 if (!m_textDisplay->document()->isEmpty()) {
367 menu->addAction("Clear All", this, SLOT(clear()));
368 menu->addSeparator();
369 }
370 menu->addAction("&Scrollback limit", this, SLOT(setScrollbackLimit()));
371 menu->addSeparator();
372
373 QMenu *logLevelMenu = menu->addMenu("&Log Level");
374 logLevelMenu->addAction(m_error);
375 logLevelMenu->addAction(m_warning);
376 logLevelMenu->addAction(m_notice);
377 logLevelMenu->addAction(m_information);
378 logLevelMenu->addAction(m_debug);
379
380 // check the right level
381 int level = Poco::Logger::root().getLevel();
382 if (level == Poco::Message::PRIO_ERROR)
383 m_error->setChecked(true);
384 if (level == Poco::Message::PRIO_WARNING)
385 m_warning->setChecked(true);
386 if (level == Poco::Message::PRIO_NOTICE)
387 m_notice->setChecked(true);
388 if (level == Poco::Message::PRIO_INFORMATION)
389 m_information->setChecked(true);
390 if (level >= Poco::Message::PRIO_DEBUG)
391 m_debug->setChecked(true);
392 return menu;
393}
394
396 m_error->setCheckable(true);
397 m_warning->setCheckable(true);
398 m_notice->setCheckable(true);
399 m_information->setCheckable(true);
400 m_debug->setCheckable(true);
401
402 m_loglevels->addAction(m_error);
403 m_loglevels->addAction(m_warning);
404 m_loglevels->addAction(m_notice);
405 m_loglevels->addAction(m_information);
406 m_loglevels->addAction(m_debug);
407
408 m_logLevelMapping->setMapping(m_error, Poco::Message::PRIO_ERROR);
409 m_logLevelMapping->setMapping(m_warning, Poco::Message::PRIO_WARNING);
410 m_logLevelMapping->setMapping(m_notice, Poco::Message::PRIO_NOTICE);
411 m_logLevelMapping->setMapping(m_information, Poco::Message::PRIO_INFORMATION);
412 m_logLevelMapping->setMapping(m_debug, Poco::Message::PRIO_DEBUG);
413
414 connect(m_error, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
415 connect(m_warning, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
416 connect(m_notice, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
417 connect(m_information, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
418 connect(m_debug, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
419
420 connect(m_logLevelMapping, SIGNAL(mappedInt(int)), this, SLOT(setLogLevel(int)));
421}
422
427 m_formats.clear();
428 QTextCharFormat textFormat;
429
430 textFormat.setForeground(Qt::red);
431 m_formats[Message::Priority::PRIO_ERROR] = textFormat;
432
433 textFormat.setForeground(QColor::fromRgb(255, 100, 0));
434 m_formats[Message::Priority::PRIO_WARNING] = textFormat;
435
436 textFormat.setForeground(Qt::gray);
437 m_formats[Message::Priority::PRIO_INFORMATION] = textFormat;
438
439 textFormat.setForeground(QColor::fromRgb(65, 105, 225));
440 m_formats[Message::Priority::PRIO_NOTICE] = textFormat;
441}
442
448void MessageDisplay::setupTextArea(const QFont &font) {
449 m_textDisplay->setFont(font);
450 m_textDisplay->setReadOnly(true);
451 m_textDisplay->ensureCursorVisible();
452 setMaximumLineCount(DEFAULT_LINE_COUNT_MAX);
453 m_textDisplay->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
454 m_textDisplay->setMouseTracking(true);
455 m_textDisplay->setUndoRedoEnabled(false);
456
457 auto layoutBox = new QHBoxLayout(this);
458 layoutBox->setContentsMargins(0, 0, 0, 0);
459 layoutBox->addWidget(m_textDisplay);
460
461 this->setFocusProxy(m_textDisplay);
462 m_textDisplay->setContextMenuPolicy(Qt::CustomContextMenu);
463 connect(m_textDisplay, SIGNAL(customContextMenuRequested(const QPoint &)), this,
464 SLOT(showContextMenu(const QPoint &)));
465}
466
471QTextCharFormat MessageDisplay::format(const Message::Priority priority) const {
472 return m_formats.value(priority, QTextCharFormat());
473}
474
480 if (((msg.scriptPath().isEmpty() && showFrameworkOutput()) ||
481 (!msg.scriptPath().isEmpty() && showAllScriptOutput()) ||
482 (showActiveScriptOutput() && (msg.scriptPath() == activeScript()))) &&
483 !QCoreApplication::closingDown())
484 return true;
485 return false;
486}
487} // namespace MantidQt::MantidWidgets
int count
counter
Definition Matrix.cpp:37
MessageDisplaySettings(int logLevel=0, int maximumLineCount=8192)
Provides a widget for display messages in a text box It deals with Message objects which in turn hide...
QTextCursor moveCursorToEnd()
Move the text cursor to after the last character.
void appendNotice(const QString &text)
Convenience method for appending message at notice level.
bool showFrameworkOutput() const
Get whether framework output is being displayed.
void errorReceived(const QString &text)
Indicate that a message of error or higher has been received.
QMenu * generateContextMenu()
Generate the display's context menu QMenu object.
MessageDisplaySettings captureSettings() const override
Capture current widget state without persistent I/O.
QAction * m_error
Log level actions.
void saveSettings(QSettings &storage, const MessageDisplaySettings &settings) const
Persist an explicit snapshot.
MessageDisplaySettings readSettings(const QSettings &storage) const
Query persistent storage without changing the widget.
bool showAllScriptOutput() const
Get whether all script output is being displayed.
QPlainTextEdit * m_textDisplay
The actual widget holding the text.
void appendDebug(const QString &text)
Convenience method for appending message at debug level.
void warningReceived(const QString &text)
Indicate that a message of warning or higher has been received.
void setScrollbackLimit()
Set the number of blocks kept by the display.
void append(const Message &msg)
Write a message after the current contents.
void attachLoggingChannel(int logLevel=0)
Attaches the Mantid logging framework.
QActionGroup * m_loglevels
Mutually exclusive log actions.
void restoreSettings(const MessageDisplaySettings &settings) override
Apply an already-read snapshot without persistent I/O.
bool shouldBeDisplayed(const Message &msg)
Return True if message should be shown given current user settings.
bool showActiveScriptOutput() const
Get whether only active script output is being displayed.
void scrollToTop()
Scroll to the bottom of the text.
QList< Message > m_messageHistory
Keep track of the message history.
const QList< Message > & getHistory()
Get the window's message history.
QSignalMapper * m_logLevelMapping
Map action signal to log level parameter.
void showContextMenu(const QPoint &event)
Provide a custom context menu.
const QString & activeScript() const
Get the path of the currently active script.
void filterMessages()
Filter messages by message type.
bool isScrollbarAtBottom() const
Returns true if scroll-bar is at the bottom of widget.
void appendFatal(const QString &text)
Convenience method for appending message at fatal level.
QtSignalChannel * m_logChannel
A reference to the log channel.
void appendPython(const QString &text, const int &priority, const QString &fileName)
Write a Python script message, intended for use with Python API.
void setMaximumLineCount(int count)
Set the maximum number of lines displayed.
void appendWarning(const QString &text)
Convenience method for appending message at warning level.
void appendToHistory(const Message &msg)
Append a message to the message history.
int maximumLineCount() const
Return the maximum number of lines displayed.
void appendInformation(const QString &text)
Convenience method for appending message at information level.
void setSource(const QString &source)
If set, only Mantid log messages from this source are emitted.
QHash< Message::Priority, QTextCharFormat > m_formats
Map priority to text formatting.
MessageDisplay(QWidget *parent=nullptr)
Default constructor with optional parent.
void appendError(const QString &text)
Convenience method for appending message at error level.
void filePathModified(const QString &oldPath, const QString &newPath)
Method to be called when a file's path is modified.
void setupTextArea(const QFont &font)
Set the properties of the text display.
void initFormats()
Initialize the text formats.
QTextCharFormat format(const Message::Priority priority) const
Return format for given log level.
void replace(const Message &msg)
Replace the display text with the given contents.
void scrollToBottom()
Scroll to the bottom of the text.
void setLogLevel(int priority)
Set the global logging level.
Provides a simple binding of a text message with a priority.
Definition Message.h:28
Mantid::Kernel::Logger::Priority Priority
Priority matches Mantid Logger priority.
Definition Message.h:33
const QString & scriptPath() const
Definition Message.h:56
Priority priority() const
Definition Message.h:54
const QString & text() const
Definition Message.h:52
static void showMessage(const QString &title, const QString &message, MessageIcon icon=MessageIcon::Information, int millisecondsTimeoutHint=5000)
Display a notification.
A QSettings facade that applies only effective changes.
bool setValue(QString const &key, QVariant const &value)
Set a value only when the effective stored value differs.
Provides a translation layer that takes a Poco::Message and converts it to a Qt signal.
void setSource(const QString &source)
If set, only Mantid log messages from this source are emitted.
Mantid::Kernel::SingletonHolder< ConfigServiceImpl > ConfigService