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"
15
16#include <QAction>
17#include <QActionGroup>
18#include <QCoreApplication>
19#include <QHBoxLayout>
20#include <QInputDialog>
21#include <QMenu>
22#include <QPlainTextEdit>
23#include <QPoint>
24#include <QScrollBar>
25#include <QSettings>
26#include <QSignalMapper>
27
28#include <Poco/Logger.h>
29#include <Poco/Message.h>
30#include <Poco/SplitterChannel.h>
31#include <Poco/Version.h>
32
33namespace {
34
35int DEFAULT_LINE_COUNT_MAX = 8192;
36const char *PRIORITY_KEY_NAME = "MessageDisplayPriority";
37const char *LINE_COUNT_MAX_KEY_NAME = "MessageDisplayLineCountMax";
38
39} // namespace
40
42
44
45//-------------------------------------------
46// Public member functions
47//-------------------------------------------
48
55void MessageDisplay::readSettings(const QSettings &storage) {
56 const int logLevel = storage.value(PRIORITY_KEY_NAME, 0).toInt();
57 if (logLevel > 0) {
58 ConfigService::Instance().setLogLevel(logLevel, true);
59 }
60 setMaximumLineCount(storage.value(LINE_COUNT_MAX_KEY_NAME, DEFAULT_LINE_COUNT_MAX).toInt());
61}
62
69void MessageDisplay::writeSettings(QSettings &storage) const {
70 storage.setValue(PRIORITY_KEY_NAME, Poco::Logger::root().getLevel());
71 storage.setValue(LINE_COUNT_MAX_KEY_NAME, maximumLineCount());
72}
73
78MessageDisplay::MessageDisplay(QWidget *parent) : MessageDisplay(QFont(), parent) {}
79
85MessageDisplay::MessageDisplay(const QFont &font, QWidget *parent)
86 : QWidget(parent), m_logChannel(new QtSignalChannel), m_textDisplay(new QPlainTextEdit(this)), m_formats(),
87 m_loglevels(new QActionGroup(this)), m_logLevelMapping(new QSignalMapper(this)),
88 m_error(new QAction(tr("&Error"), this)), m_warning(new QAction(tr("&Warning"), this)),
89 m_notice(new QAction(tr("&Notice"), this)), m_information(new QAction(tr("&Information"), this)),
90 m_debug(new QAction(tr("&Debug"), this)) {
93 setupTextArea(font);
94}
95
97 // We only attach to splitter channels but we may not have been attached...
98 auto rootChannel = Poco::Logger::root().getChannel();
99#if POCO_VERSION > 0x01090400
100 // getChannel changed to return an AutoPtr
101 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel.get())) {
102#else
103 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel)) {
104#endif
105 splitChannel->removeChannel(m_logChannel);
106 }
107 // The Channel class is ref counted and will delete itself when required
108 m_logChannel->release();
109 delete m_textDisplay;
110}
111
119 // Setup logging. ConfigService needs to be started
120 auto &configSvc = ConfigService::Instance();
121 // The root channel might be a SplitterChannel
122 auto rootChannel = Poco::Logger::root().getChannel();
123#if POCO_VERSION > 0x01090400
124 // getChannel changed to return an AutoPtr
125 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel.get())) {
126#else
127 if (auto *splitChannel = dynamic_cast<Poco::SplitterChannel *>(rootChannel)) {
128#endif
129 splitChannel->addChannel(m_logChannel);
130 } else {
131 throw std::runtime_error("MessageDisplay requires the root logger to be configured with a SplitterChannel.\n"
132 "Set 'logging.loggers.root.channel.class = SplitterChannel' in properties file.");
133 }
134 connect(m_logChannel, SIGNAL(messageReceived(const Message &)), this, SLOT(append(const Message &)));
135 if (logLevel > 0) {
136 configSvc.setLogLevel(logLevel, true);
137 }
138}
139
145
150 m_textDisplay->clear();
151 for (auto &msg : getHistory()) {
152 if (shouldBeDisplayed(msg)) {
153 m_textDisplay->textCursor().insertText(msg.text(), format(msg.priority()));
154 }
155 }
157}
158
165void MessageDisplay::filePathModified(const QString &oldPath, const QString &newPath) {
166 for (auto &msg : m_messageHistory) {
167 if (msg.scriptPath() == oldPath)
168 msg.setScriptPath(newPath);
169 }
170}
171
178 m_messageHistory.append(msg);
179 while (m_messageHistory.size() > maximumLineCount() && maximumLineCount() > 0)
180 // Use .removeAt(0) since .removeFirst asserts a !.isEmpty() check
181 m_messageHistory.removeAt(0);
182}
183
184//----------------------------------------------------------------------------------------
185// Public slots
186//----------------------------------------------------------------------------------------
190void MessageDisplay::appendFatal(const QString &text) { this->append(Message(text, Message::Priority::PRIO_FATAL)); }
191
195void MessageDisplay::appendError(const QString &text) { this->append(Message(text, Message::Priority::PRIO_ERROR)); }
196
200void MessageDisplay::appendWarning(const QString &text) {
201 this->append(Message(text, Message::Priority::PRIO_WARNING));
202}
203
207void MessageDisplay::appendNotice(const QString &text) { this->append(Message(text, Message::Priority::PRIO_NOTICE)); }
208
212void MessageDisplay::appendInformation(const QString &text) {
213 this->append(Message(text, Message::Priority::PRIO_INFORMATION));
214}
215
219void MessageDisplay::appendDebug(const QString &text) { this->append(Message(text, Message::Priority::PRIO_DEBUG)); }
220
226 appendToHistory(msg);
227 if (shouldBeDisplayed(msg) || msg.priority() <= Message::Priority::PRIO_WARNING) {
228 QTextCursor cursor = moveCursorToEnd();
229 cursor.insertText(msg.text(), format(msg.priority()));
231
232 if (msg.priority() <= Message::Priority::PRIO_ERROR) {
233 NotificationService::showMessage(parentWidget() ? parentWidget()->windowTitle() : "Mantid",
234 "Sorry, there was an error, please look at the message display for "
235 "details.",
236 NotificationService::MessageIcon::Critical);
237 emit errorReceived(msg.text());
238 }
239 if (msg.priority() <= Message::Priority::PRIO_WARNING)
240 emit warningReceived(msg.text());
241 }
242}
243
251void MessageDisplay::appendPython(const QString &text, const int &priority, const QString &filePath) {
252 Message msg = Message(text, static_cast<Message::Priority>(priority), filePath);
253 append(msg);
254}
255
260 clear();
261 append(msg.text());
262}
263
268 m_textDisplay->clear();
269 m_messageHistory.clear();
270}
271
276 QTextCursor cursor(m_textDisplay->textCursor());
277 cursor.movePosition(QTextCursor::End);
278 m_textDisplay->setTextCursor(cursor);
279 return cursor;
280}
281
286 return m_textDisplay->verticalScrollBar()->value() == m_textDisplay->verticalScrollBar()->maximum();
287}
288
293 // Code taken from QtCreator source
294 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->minimum());
295 // QPlainTextEdit destroys the first calls value in case of multiline
296 // text, so make sure that the scroll bar actually gets the value set.
297 // Is a noop if the first call succeeded.
298 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->minimum());
299}
300
305 // Code taken from QtCreator source
306 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->maximum());
307 // QPlainTextEdit destroys the first calls value in case of multiline
308 // text, so make sure that the scroll bar actually gets the value set.
309 // Is a noop if the first call succeeded.
310 m_textDisplay->verticalScrollBar()->setValue(m_textDisplay->verticalScrollBar()->maximum());
311}
312
313//-----------------------------------------------------------------------------
314// Private slot member functions
315//-----------------------------------------------------------------------------
316
317void MessageDisplay::showContextMenu(const QPoint &mousePos) {
318 QMenu *menu{generateContextMenu()};
319 menu->exec(this->mapToGlobal(mousePos));
320 delete menu;
321}
322
323/*
324 * @param priority An integer that must match the Poco::Message priority
325 * enumeration
326 */
327void MessageDisplay::setLogLevel(int priority) { ConfigService::Instance().setLogLevel(priority); }
328
333 constexpr int minLineCountAllowed(-1);
335 QInputDialog::getInt(this, "", "No. of lines\n(-1 keeps all content)", maximumLineCount(), minLineCountAllowed));
336}
337
338// The text edit works in blocks but it is not entirely clear what a block
339// is defined as. Experiments showed setting a max block count=1 suppressed
340// all output and a min(block count)==2 was required to see a single line.
341// We have asked the user for lines so add 1 to get the behaviour they
342// would expect. Equally we subtract 1 for the value we show them to
343// keep it consistent
344
348int MessageDisplay::maximumLineCount() const { return m_textDisplay->maximumBlockCount() - 1; }
349
354void MessageDisplay::setMaximumLineCount(int count) { m_textDisplay->setMaximumBlockCount(count + 1); }
355
356//-----------------------------------------------------------------------------
357// Private non-slot member functions
358//-----------------------------------------------------------------------------
360 QMenu *menu = m_textDisplay->createStandardContextMenu();
361 menu->addSeparator();
362 if (!m_textDisplay->document()->isEmpty()) {
363 menu->addAction("Clear All", this, SLOT(clear()));
364 menu->addSeparator();
365 }
366 menu->addAction("&Scrollback limit", this, SLOT(setScrollbackLimit()));
367 menu->addSeparator();
368
369 QMenu *logLevelMenu = menu->addMenu("&Log Level");
370 logLevelMenu->addAction(m_error);
371 logLevelMenu->addAction(m_warning);
372 logLevelMenu->addAction(m_notice);
373 logLevelMenu->addAction(m_information);
374 logLevelMenu->addAction(m_debug);
375
376 // check the right level
377 int level = Poco::Logger::root().getLevel();
378 if (level == Poco::Message::PRIO_ERROR)
379 m_error->setChecked(true);
380 if (level == Poco::Message::PRIO_WARNING)
381 m_warning->setChecked(true);
382 if (level == Poco::Message::PRIO_NOTICE)
383 m_notice->setChecked(true);
384 if (level == Poco::Message::PRIO_INFORMATION)
385 m_information->setChecked(true);
386 if (level >= Poco::Message::PRIO_DEBUG)
387 m_debug->setChecked(true);
388 return menu;
389}
390
392 m_error->setCheckable(true);
393 m_warning->setCheckable(true);
394 m_notice->setCheckable(true);
395 m_information->setCheckable(true);
396 m_debug->setCheckable(true);
397
398 m_loglevels->addAction(m_error);
399 m_loglevels->addAction(m_warning);
400 m_loglevels->addAction(m_notice);
401 m_loglevels->addAction(m_information);
402 m_loglevels->addAction(m_debug);
403
404 m_logLevelMapping->setMapping(m_error, Poco::Message::PRIO_ERROR);
405 m_logLevelMapping->setMapping(m_warning, Poco::Message::PRIO_WARNING);
406 m_logLevelMapping->setMapping(m_notice, Poco::Message::PRIO_NOTICE);
407 m_logLevelMapping->setMapping(m_information, Poco::Message::PRIO_INFORMATION);
408 m_logLevelMapping->setMapping(m_debug, Poco::Message::PRIO_DEBUG);
409
410 connect(m_error, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
411 connect(m_warning, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
412 connect(m_notice, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
413 connect(m_information, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
414 connect(m_debug, SIGNAL(triggered()), m_logLevelMapping, SLOT(map()));
415
416 connect(m_logLevelMapping, SIGNAL(mapped(int)), this, SLOT(setLogLevel(int)));
417}
418
423 m_formats.clear();
424 QTextCharFormat format;
425
426 format.setForeground(Qt::red);
427 m_formats[Message::Priority::PRIO_ERROR] = format;
428
429 format.setForeground(QColor::fromRgb(255, 100, 0));
430 m_formats[Message::Priority::PRIO_WARNING] = format;
431
432 format.setForeground(Qt::gray);
433 m_formats[Message::Priority::PRIO_INFORMATION] = format;
434
435 format.setForeground(Qt::darkBlue);
436 m_formats[Message::Priority::PRIO_NOTICE] = format;
437}
438
444void MessageDisplay::setupTextArea(const QFont &font) {
445 m_textDisplay->setFont(font);
446 m_textDisplay->setReadOnly(true);
447 m_textDisplay->ensureCursorVisible();
448 setMaximumLineCount(DEFAULT_LINE_COUNT_MAX);
449 m_textDisplay->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
450 m_textDisplay->setMouseTracking(true);
451 m_textDisplay->setUndoRedoEnabled(false);
452
453 auto layoutBox = new QHBoxLayout(this);
454 layoutBox->setContentsMargins(0, 0, 0, 0);
455 layoutBox->addWidget(m_textDisplay);
456
457 this->setFocusProxy(m_textDisplay);
458 m_textDisplay->setContextMenuPolicy(Qt::CustomContextMenu);
459 connect(m_textDisplay, SIGNAL(customContextMenuRequested(const QPoint &)), this,
460 SLOT(showContextMenu(const QPoint &)));
461}
462
467QTextCharFormat MessageDisplay::format(const Message::Priority priority) const {
468 return m_formats.value(priority, QTextCharFormat());
469}
470
476 if (((msg.scriptPath().isEmpty() && showFrameworkOutput()) ||
477 (!msg.scriptPath().isEmpty() && showAllScriptOutput()) ||
478 (showActiveScriptOutput() && (msg.scriptPath() == activeScript()))) &&
479 !QCoreApplication::closingDown())
480 return true;
481 return false;
482}
483} // namespace MantidQt::MantidWidgets
int count
counter
Definition: Matrix.cpp:37
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.
QAction * m_error
Log level actions.
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.
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.
QString activeScript() const
Get the path of the currently active script.
QList< Message > m_messageHistory
Keep track of the message history.
QSignalMapper * m_logLevelMapping
Map action signal to log level parameter.
void showContextMenu(const QPoint &event)
Provide a custom context menu.
void filterMessages()
Filter messages by message type.
void readSettings(const QSettings &storage) override
Load settings from the persistent store.
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 writeSettings(QSettings &storage) const override
Load settings from the persistent store.
void setMaximumLineCount(int count)
Set the maximum number of lines displayed.
QList< Message > getHistory()
Get the window's message history.
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 clear()
Clear all of the text.
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
Priority priority() const
Definition: Message.h:54
QString scriptPath() const
Definition: Message.h:56
static void showMessage(const QString &title, const QString &message, MessageIcon icon=MessageIcon::Information, int millisecondsTimeoutHint=5000)
Display a notification.
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.
Manage the lifetime of a class intended to be a singleton.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...