From fa9cdff45c88673f57ae2dbadf92b424323077d8 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 28 Apr 2025 23:40:32 -0400 Subject: [PATCH] sync LAMMPS-GUI with state from develop branch --- tools/lammps-gui/flagwarnings.cpp | 17 ++++++- tools/lammps-gui/flagwarnings.h | 2 + tools/lammps-gui/help_index.table | 1 + tools/lammps-gui/lammps-gui.appdata.xml | 2 + tools/lammps-gui/logwindow.cpp | 56 ++++++++++++++++++++++ tools/lammps-gui/logwindow.h | 4 ++ tools/lammps-gui/org.lammps.lammps-gui.yml | 2 +- 7 files changed, 81 insertions(+), 3 deletions(-) diff --git a/tools/lammps-gui/flagwarnings.cpp b/tools/lammps-gui/flagwarnings.cpp index e91547288f..deeb9f8306 100644 --- a/tools/lammps-gui/flagwarnings.cpp +++ b/tools/lammps-gui/flagwarnings.cpp @@ -23,18 +23,22 @@ // workaround for Qt-5.12 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) namespace QColorConstants { -const QColor Red = QColor::fromRgb(0xff, 0x00, 0x00); +const QColor Red = QColor::fromRgb(0xff, 0x00, 0x00); +const QColor Blue = QColor::fromRgb(0x00, 0x00, 0xff); } // namespace QColorConstants #endif FlagWarnings::FlagWarnings(QLabel *label, QTextDocument *parent) : - QSyntaxHighlighter(parent), isWarning(QStringLiteral("^(ERROR|WARNING).*$")), summary(label), + QSyntaxHighlighter(parent), isWarning(QStringLiteral("^(ERROR|WARNING).*$")), + isURL(QStringLiteral("^.*(https://docs.lammps.org/err[0-9]+).*$")), summary(label), document(parent) { nwarnings = nlines = 0; formatWarning.setForeground(QColorConstants::Red); formatWarning.setFontWeight(QFont::Bold); + formatURL.setForeground(QColorConstants::Blue); + formatURL.setFontWeight(QFont::Bold); } void FlagWarnings::highlightBlock(const QString &text) @@ -42,11 +46,20 @@ void FlagWarnings::highlightBlock(const QString &text) // nothing to do for empty lines if (text.isEmpty()) return; + // highlight errors or warnings auto match = isWarning.match(text); if (match.hasMatch()) { ++nwarnings; setFormat(match.capturedStart(0), match.capturedLength(0), formatWarning); } + + // highlight ErrorURL links + match = isURL.match(text); + if (match.hasMatch()) { + setFormat(match.capturedStart(1), match.capturedLength(1), formatURL); + } + + // update error summary label if (document && summary) { summary->setText( QString("%1 Warnings / Errors - %2 Lines").arg(nwarnings).arg(document->lineCount())); diff --git a/tools/lammps-gui/flagwarnings.h b/tools/lammps-gui/flagwarnings.h index ad82942d62..b73224223b 100644 --- a/tools/lammps-gui/flagwarnings.h +++ b/tools/lammps-gui/flagwarnings.h @@ -33,7 +33,9 @@ protected: private: QRegularExpression isWarning; + QRegularExpression isURL; QTextCharFormat formatWarning; + QTextCharFormat formatURL; QLabel *summary; QTextDocument *document; int nwarnings, nlines; diff --git a/tools/lammps-gui/help_index.table b/tools/lammps-gui/help_index.table index a17b24fef5..6fbf64557d 100644 --- a/tools/lammps-gui/help_index.table +++ b/tools/lammps-gui/help_index.table @@ -783,6 +783,7 @@ fix_wall.html fix wall/morse fix_wall.html fix wall/table fix_wall_srd.html fix wall/srd fix_widom.html fix widom +geturl.html geturl group2ndx.html group2ndx group2ndx.html ndx2group group.html group diff --git a/tools/lammps-gui/lammps-gui.appdata.xml b/tools/lammps-gui/lammps-gui.appdata.xml index 460e47f509..875f0bfea2 100644 --- a/tools/lammps-gui/lammps-gui.appdata.xml +++ b/tools/lammps-gui/lammps-gui.appdata.xml @@ -63,6 +63,8 @@ Add text fields for editing plot title and axis labels for charts Add option to automatically open tutorial websites (enabled by default) Add preferences tab for charts to set default for title, plot colors, smooth/raw plot, smooth params + Highlight error URLs with pointers to additional explanations in log window + Double-click on highlighted URL opens it in web browser. Also available via context menu. diff --git a/tools/lammps-gui/logwindow.cpp b/tools/lammps-gui/logwindow.cpp index 82689924dc..49b168b616 100644 --- a/tools/lammps-gui/logwindow.cpp +++ b/tools/lammps-gui/logwindow.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ const QString LogWindow::yaml_regex = QStringLiteral("^(keywords:.*$|data:$|---$|\\.\\.\\.$| - \\[.*\\]$)"); +const QString LogWindow::url_regex = QStringLiteral("^.*(https://docs.lammps.org/err[0-9]+).*$"); LogWindow::LogWindow(const QString &_filename, QWidget *parent) : QPlainTextEdit(parent), filename(_filename), warnings(nullptr) @@ -198,8 +200,53 @@ void LogWindow::extract_yaml() file.close(); } +void LogWindow::open_errorurl() +{ + if (!errorurl.isEmpty()) QDesktopServices::openUrl(QUrl(errorurl)); +} + +void LogWindow::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + // select the entire word (non-space text) under the cursor + // we need to do it in this complicated way, since QTextCursor does not recognize + // special characters as part of a word. + auto cursor = textCursor(); + auto line = cursor.block().text(); + int begin = qMin(cursor.positionInBlock(), line.length() - 1); + + while (begin >= 0) { + if (line[begin].isSpace()) break; + --begin; + } + + int end = begin + 1; + while (end < line.length()) { + if (line[end].isSpace()) break; + ++end; + } + cursor.setPosition(cursor.position() - cursor.positionInBlock() + begin + 1); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, end - begin - 1); + + auto text = cursor.selectedText(); + auto url = QRegularExpression(url_regex).match(text); + if (url.hasMatch()) { + errorurl = url.captured(1); + if (!errorurl.isEmpty()) { + QDesktopServices::openUrl(QUrl(errorurl)); + return; + } + } + } + // forward event to parent class for all unhandled cases + QPlainTextEdit::mouseDoubleClickEvent(event); +} + void LogWindow::contextMenuEvent(QContextMenuEvent *event) { + // reposition the cursor here, but only if there is no active selection + if (!textCursor().hasSelection()) setTextCursor(cursorForPosition(event->pos())); + // show augmented context menu auto *menu = createStandardContextMenu(); menu->addSeparator(); @@ -214,6 +261,15 @@ void LogWindow::contextMenuEvent(QContextMenuEvent *event) action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Y)); connect(action, &QAction::triggered, this, &LogWindow::extract_yaml); } + + // process line of text where the cursor is + auto text = textCursor().block().text().replace('\t', ' ').trimmed(); + auto url = QRegularExpression(url_regex).match(text); + if (url.hasMatch()) { + errorurl = url.captured(1); + action = menu->addAction("Open &URL in Web Browser", this, &LogWindow::open_errorurl); + action->setIcon(QIcon(":/icons/help-browser.png")); + } action = menu->addAction("&Jump to next warning or error", this, &LogWindow::next_warning); action->setIcon(QIcon(":/icons/warning.png")); action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_N)); diff --git a/tools/lammps-gui/logwindow.h b/tools/lammps-gui/logwindow.h index 38bca9ba24..a3263bb2d2 100644 --- a/tools/lammps-gui/logwindow.h +++ b/tools/lammps-gui/logwindow.h @@ -32,16 +32,20 @@ private slots: void save_as(); void stop_run(); void next_warning(); + void open_errorurl(); protected: void closeEvent(QCloseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; bool check_yaml(); private: QString filename; + QString errorurl; static const QString yaml_regex; + static const QString url_regex; FlagWarnings *warnings; QLabel *summary; }; diff --git a/tools/lammps-gui/org.lammps.lammps-gui.yml b/tools/lammps-gui/org.lammps.lammps-gui.yml index 86bcc6ff38..05facb753c 100644 --- a/tools/lammps-gui/org.lammps.lammps-gui.yml +++ b/tools/lammps-gui/org.lammps.lammps-gui.yml @@ -1,6 +1,6 @@ id: org.lammps.lammps-gui runtime: org.kde.Platform -runtime-version: "5.15-23.08" +runtime-version: "5.15-24.08" sdk: org.kde.Sdk command: lammps-gui finish-args: