From c95bf2c14b6a12d9f4e809f9aed767006e09be6c Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Jul 2023 19:23:42 -0400 Subject: [PATCH] add image viewer dialog for write_dump image output, help can open link to manual --- tools/lammps-gui/CMakeLists.txt | 2 + tools/lammps-gui/TODO.md | 6 +- tools/lammps-gui/codeeditor.cpp | 9 +- tools/lammps-gui/imageviewer.cpp | 180 +++++++++++++++++++++++++++++++ tools/lammps-gui/imageviewer.h | 64 +++++++++++ tools/lammps-gui/lammpsgui.cpp | 90 ++++++++++++++-- tools/lammps-gui/lammpsgui.h | 5 + tools/lammps-gui/lammpsgui.ui | 27 ++++- tools/lammps-gui/preferences.cpp | 10 +- tools/lammps-gui/preferences.h | 3 +- tools/lammps-gui/stdcapture.cpp | 2 +- 11 files changed, 373 insertions(+), 25 deletions(-) create mode 100644 tools/lammps-gui/imageviewer.cpp create mode 100644 tools/lammps-gui/imageviewer.h diff --git a/tools/lammps-gui/CMakeLists.txt b/tools/lammps-gui/CMakeLists.txt index 8d1958a66b..a736b8405f 100644 --- a/tools/lammps-gui/CMakeLists.txt +++ b/tools/lammps-gui/CMakeLists.txt @@ -66,6 +66,8 @@ set(PROJECT_SOURCES codeeditor.h highlighter.cpp highlighter.h + imageviewer.cpp + imageviewer.h linenumberarea.h lammpsgui.cpp lammpsgui.h diff --git a/tools/lammps-gui/TODO.md b/tools/lammps-gui/TODO.md index 170211516e..8412b11d6d 100644 --- a/tools/lammps-gui/TODO.md +++ b/tools/lammps-gui/TODO.md @@ -3,13 +3,14 @@ LAMMPS-GUI TODO list: # Short term goals - add "Help" entry to menu bar. Should open a popup window with a one page description of how to use it. Use HTML or Markdown text. +- display current working directory - add CTRL-q hotkey to log windows so you can exit the entire application (add do you really want to? dialog to this) -- add "render" dialog where a "write_dump image" can be triggered. dialog should offer options for size, zoom, rotation, colors(?) - add "syntax check" with enabled "-skiprun" flag - add settings dialog where certain properties can be set through customizing the LAMMPS command line - + enable/disable OpenMP (via suffix), select number of OpenMP threads + + enable/disable OpenMP (via suffix), OPT package, select number of OpenMP threads + toggle whether captured screen output should include input file echo + select Font + + snapshot image options - store settings/preferences when changed and read at startup - add list of 5(?) most recently opened/saved files to file dialog (and also write to settings state on exit) (note: must store full path!) @@ -18,4 +19,3 @@ LAMMPS-GUI TODO list: - have command text input file in/above status bar where individual commands can be tested. have insert button to copy line into file at the current point - support text completion as done with lammps-shell - have context menu for known commands to offer retrieving help by dispatching URL to webbrowser (process index from sphinx for that purpose) - (embed html viewer to render pages locally, mathjax??) diff --git a/tools/lammps-gui/codeeditor.cpp b/tools/lammps-gui/codeeditor.cpp index 827a6bb7d3..f0a375efc0 100644 --- a/tools/lammps-gui/codeeditor.cpp +++ b/tools/lammps-gui/codeeditor.cpp @@ -12,8 +12,8 @@ ------------------------------------------------------------------------- */ #include "codeeditor.h" -#include "linenumberarea.h" #include "lammpsgui.h" +#include "linenumberarea.h" #include #include @@ -67,7 +67,7 @@ void CodeEditor::dragEnterEvent(QDragEnterEvent *event) bool CodeEditor::canInsertFromMimeData(const QMimeData *source) const { - return source->hasUrls(); // || source->hasText(); + return source->hasUrls(); // || source->hasText(); } void CodeEditor::dropEvent(QDropEvent *event) @@ -75,7 +75,7 @@ void CodeEditor::dropEvent(QDropEvent *event) if (event->mimeData()->hasUrls()) { event->accept(); auto file = event->mimeData()->urls()[0].url().remove("file://"); - auto gui = dynamic_cast(parent()); + auto gui = dynamic_cast(parent()); if (gui) { moveCursor(QTextCursor::Start, QTextCursor::MoveAnchor); gui->open_file(file); @@ -84,7 +84,8 @@ void CodeEditor::dropEvent(QDropEvent *event) event->accept(); fprintf(stderr, "Drag - Drop for text block not yet implemented: text=%s\n", event->mimeData()->text().toStdString().c_str()); - } else event->ignore(); + } else + event->ignore(); } void CodeEditor::resizeEvent(QResizeEvent *e) diff --git a/tools/lammps-gui/imageviewer.cpp b/tools/lammps-gui/imageviewer.cpp new file mode 100644 index 0000000000..70bd105a96 --- /dev/null +++ b/tools/lammps-gui/imageviewer.cpp @@ -0,0 +1,180 @@ +/* ---------------------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + https://www.lammps.org/, Sandia National Laboratories + LAMMPS development team: developers@lammps.org + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#include "imageviewer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ImageViewer::ImageViewer(const QString &fileName, QWidget *parent) : + QDialog(parent), imageLabel(new QLabel), scrollArea(new QScrollArea), menuBar(new QMenuBar) +{ + imageLabel->setBackgroundRole(QPalette::Base); + imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + imageLabel->setScaledContents(true); + imageLabel->minimumSizeHint(); + + scrollArea->setBackgroundRole(QPalette::Dark); + scrollArea->setWidget(imageLabel); + scrollArea->setVisible(false); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); + + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(menuBar); + mainLayout->addWidget(scrollArea); + mainLayout->addWidget(buttonBox); + setWindowTitle(QString("Image Viewer: ") + QFileInfo(fileName).completeBaseName()); + + createActions(); + + QImageReader reader(fileName); + reader.setAutoTransform(true); + const QImage newImage = reader.read(); + if (newImage.isNull()) { + QMessageBox::warning(this, QGuiApplication::applicationDisplayName(), + tr("Cannot load %1: %2").arg(fileName, reader.errorString())); + return; + } + image = newImage; + imageLabel->setPixmap(QPixmap::fromImage(image)); + scaleFactor = 1.0; + resize(image.width() + 20, image.height() + 50); + + scrollArea->setVisible(true); + fitToWindowAct->setEnabled(true); + updateActions(); + if (!fitToWindowAct->isChecked()) imageLabel->adjustSize(); + setLayout(mainLayout); +} + +void ImageViewer::saveAs() +{ + QString fileName = QFileDialog::getSaveFileName(this, "Save Image File As", QString(), + "Image Files (*.jpg *.png *.bmp *.ppm)"); + saveFile(fileName); +} + +void ImageViewer::copy() {} + +void ImageViewer::zoomIn() +{ + scaleImage(1.25); +} + +void ImageViewer::zoomOut() +{ + scaleImage(0.8); +} + +void ImageViewer::normalSize() +{ + imageLabel->adjustSize(); + scaleFactor = 1.0; +} + +void ImageViewer::fitToWindow() +{ + bool fitToWindow = fitToWindowAct->isChecked(); + scrollArea->setWidgetResizable(fitToWindow); + if (!fitToWindow) normalSize(); + updateActions(); +} + +void ImageViewer::saveFile(const QString &fileName) +{ + if (!fileName.isEmpty()) image.save(fileName); +} + +void ImageViewer::createActions() +{ + QMenu *fileMenu = menuBar->addMenu(tr("&File")); + + saveAsAct = fileMenu->addAction(tr("&Save As..."), this, &ImageViewer::saveAs); + saveAsAct->setEnabled(false); + fileMenu->addSeparator(); + copyAct = fileMenu->addAction(tr("&Copy"), this, &ImageViewer::copy); + copyAct->setShortcut(QKeySequence::Copy); + copyAct->setEnabled(false); + fileMenu->addSeparator(); + QAction *exitAct = fileMenu->addAction(tr("&Close"), this, &QWidget::close); + exitAct->setShortcut(tr("Ctrl+W")); + + QMenu *viewMenu = menuBar->addMenu(tr("&View")); + + zoomInAct = viewMenu->addAction(tr("Zoom &In (25%)"), this, &ImageViewer::zoomIn); + zoomInAct->setShortcut(QKeySequence::ZoomIn); + zoomInAct->setEnabled(false); + + zoomOutAct = viewMenu->addAction(tr("Zoom &Out (25%)"), this, &ImageViewer::zoomOut); + zoomOutAct->setShortcut(QKeySequence::ZoomOut); + zoomOutAct->setEnabled(false); + + normalSizeAct = viewMenu->addAction(tr("&Normal Size"), this, &ImageViewer::normalSize); + normalSizeAct->setShortcut(tr("Ctrl+S")); + normalSizeAct->setEnabled(false); + + viewMenu->addSeparator(); + + fitToWindowAct = viewMenu->addAction(tr("&Fit to Window"), this, &ImageViewer::fitToWindow); + fitToWindowAct->setEnabled(false); + fitToWindowAct->setCheckable(true); + fitToWindowAct->setShortcut(tr("Ctrl+F")); +} + +void ImageViewer::updateActions() +{ + saveAsAct->setEnabled(!image.isNull()); + copyAct->setEnabled(!image.isNull()); + zoomInAct->setEnabled(!fitToWindowAct->isChecked()); + zoomOutAct->setEnabled(!fitToWindowAct->isChecked()); + normalSizeAct->setEnabled(!fitToWindowAct->isChecked()); +} + +void ImageViewer::scaleImage(double factor) +{ + scaleFactor *= factor; + imageLabel->resize(scaleFactor * imageLabel->pixmap(Qt::ReturnByValue).size()); + + adjustScrollBar(scrollArea->horizontalScrollBar(), factor); + adjustScrollBar(scrollArea->verticalScrollBar(), factor); + zoomInAct->setEnabled(scaleFactor < 3.0); + zoomOutAct->setEnabled(scaleFactor > 0.333); +} + +void ImageViewer::adjustScrollBar(QScrollBar *scrollBar, double factor) +{ + scrollBar->setValue( + int(factor * scrollBar->value() + ((factor - 1) * scrollBar->pageStep() / 2))); +} + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/tools/lammps-gui/imageviewer.h b/tools/lammps-gui/imageviewer.h new file mode 100644 index 0000000000..56e0fda63b --- /dev/null +++ b/tools/lammps-gui/imageviewer.h @@ -0,0 +1,64 @@ +/* -*- c++ -*- ---------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + https://www.lammps.org/, Sandia National Laboratories + LAMMPS development team: developers@lammps.org + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#ifndef IMAGEVIEWER_H +#define IMAGEVIEWER_H + +#include +#include + +class QAction; +class QMenuBar; +class QDialogButtonBox; +class QLabel; +class QScrollArea; +class QScrollBar; +class QStatusBar; + +class ImageViewer : public QDialog { + Q_OBJECT + +public: + explicit ImageViewer(const QString &fileName, QWidget *parent = nullptr); + +private slots: + void saveAs(); + void copy(); + void zoomIn(); + void zoomOut(); + void normalSize(); + void fitToWindow(); + +private: + void createActions(); + void updateActions(); + void saveFile(const QString &fileName); + void scaleImage(double factor); + void adjustScrollBar(QScrollBar *scrollBar, double factor); + +private: + QImage image; + QMenuBar *menuBar; + QLabel *imageLabel; + QScrollArea *scrollArea; + QDialogButtonBox *buttonBox; + double scaleFactor = 1.0; + + QAction *saveAsAct; + QAction *copyAct; + QAction *zoomInAct; + QAction *zoomOutAct; + QAction *normalSizeAct; + QAction *fitToWindowAct; +}; +#endif diff --git a/tools/lammps-gui/lammpsgui.cpp b/tools/lammps-gui/lammpsgui.cpp index b454e215dc..88c4902d20 100644 --- a/tools/lammps-gui/lammpsgui.cpp +++ b/tools/lammps-gui/lammpsgui.cpp @@ -14,6 +14,7 @@ #include "lammpsgui.h" #include "highlighter.h" +#include "imageviewer.h" #include "lammpsrunner.h" #include "preferences.h" #include "stdcapture.h" @@ -30,6 +31,9 @@ #include #include #include +#include +#include + #include #include @@ -44,6 +48,12 @@ #include #endif +#if defined(_WIN32) +#include +#else +#include +#endif + // duplicate string static char *mystrdup(const std::string &text) { @@ -54,9 +64,9 @@ static char *mystrdup(const std::string &text) LammpsGui::LammpsGui(QWidget *parent, const char *filename) : QMainWindow(parent), ui(new Ui::LammpsGui), highlighter(nullptr), capturer(nullptr), - status(nullptr), logwindow(nullptr), logupdater(nullptr), progress(nullptr), - prefdialog(nullptr), lammps_handle(nullptr), plugin_handle(nullptr), plugin_path(nullptr), - is_running(false) + status(nullptr), logwindow(nullptr), imagewindow(nullptr), logupdater(nullptr), + progress(nullptr), prefdialog(nullptr), lammps_handle(nullptr), plugin_handle(nullptr), + plugin_path(nullptr), is_running(false) { ui->setupUi(this); this->setCentralWidget(ui->textEdit); @@ -78,6 +88,17 @@ LammpsGui::LammpsGui(QWidget *parent, const char *filename) : #endif #endif + const char *tmpdir = getenv("TMPDIR"); + if (!tmpdir) tmpdir = getenv("TMP"); + if (!tmpdir) tmpdir = getenv("TEMPDIR"); + if (!tmpdir) tmpdir = getenv("TEMP"); +#if _WIN32 + if (!tmpdir) tmpdir = "C:\\Windows\\Temp"; +#else + if (!tmpdir) tmpdir = "/tmp"; +#endif + temp_dir = tmpdir; + lammps_args.clear(); lammps_args.push_back(mystrdup("LAMMPS-GUI")); lammps_args.push_back(mystrdup("-log")); @@ -107,8 +128,10 @@ LammpsGui::LammpsGui(QWidget *parent, const char *filename) : connect(ui->actionRedo, &QAction::triggered, this, &LammpsGui::redo); connect(ui->actionRun_Buffer, &QAction::triggered, this, &LammpsGui::run_buffer); connect(ui->actionStop_LAMMPS, &QAction::triggered, this, &LammpsGui::stop_run); + connect(ui->actionImage, &QAction::triggered, this, &LammpsGui::view_image); connect(ui->actionAbout_LAMMPS_GUI, &QAction::triggered, this, &LammpsGui::about); connect(ui->action_Help, &QAction::triggered, this, &LammpsGui::help); + connect(ui->actionLAMMPS_Manual, &QAction::triggered, this, &LammpsGui::manual); connect(ui->actionEdit_Preferences, &QAction::triggered, this, &LammpsGui::preferences); connect(ui->textEdit->document(), &QTextDocument::modificationChanged, this, &LammpsGui::modified); @@ -158,6 +181,7 @@ LammpsGui::~LammpsGui() delete capturer; delete status; delete logwindow; + delete imagewindow; } void LammpsGui::new_document() @@ -469,6 +493,55 @@ void LammpsGui::run_buffer() logupdater->start(1000); } +void LammpsGui::view_image() +{ + // LAMMPS is not re-entrant, so we can only query LAMMPS when it is not running + if (!is_running) { + start_lammps(); + int box = 0; +#if defined(LAMMPS_GUI_USE_PLUGIN) + box = ((liblammpsplugin_t *)plugin_handle)->extract_setting(lammps_handle, "box_exists"); +#else + box = lammps_extract_setting(lammps_handle, "box_exist"); +#endif + if (!box) { + QMessageBox::warning(this, "ImageViewer Error", + "Cannot create snapshot image without a system box"); + return; + } + + std::string dumpcmd = "write_dump all image '"; + QString dumpfile = temp_dir; +#if defined(_WIN32) + dumpfile += '\\'; +#else + dumpfile += '/'; +#endif + dumpfile += current_file + ".ppm"; + + dumpcmd += dumpfile.toStdString() + "' type type size 800 600"; + +#if defined(LAMMPS_GUI_USE_PLUGIN) + ((liblammpsplugin_t *)plugin_handle)->command(lammps_handle, dumpcmd.c_str()); +#else + lammps_command(lammps_handle, dumpcmd.c_str()); +#endif + imagewindow = new ImageViewer(dumpfile); +#if 0 +#if defined(_WIN32) + _unlink(dumpfile.toLocal8Bit()); +#else + unlink(dumpfile.toLocal8Bit()); +#endif +#endif + } else { + QMessageBox::warning(this, "ImageViewer Error", + "Cannot create snapshot image while LAMMPS is running"); + return; + } + imagewindow->show(); +} + void LammpsGui::clear() { if (lammps_handle) { @@ -500,9 +573,9 @@ void LammpsGui::about() start_lammps(); capturer->BeginCapture(); #if defined(LAMMPS_GUI_USE_PLUGIN) - ((liblammpsplugin_t *)plugin_handle)->commands_string(lammps_handle, "info config"); + ((liblammpsplugin_t *)plugin_handle)->commands(lammps_handle, "info config"); #else - lammps_commands_string(lammps_handle, "info config"); + lammps_command(lammps_handle, "info config"); #endif capturer->EndCapture(); info = capturer->GetCapture(); @@ -515,7 +588,7 @@ void LammpsGui::about() msg.setWindowTitle("About LAMMPS-GUI"); msg.setText(version.c_str()); msg.setInformativeText(info.c_str()); - msg.setIconPixmap(QPixmap(":/lammps-icon-128x128.png").scaled(64,64)); + msg.setIconPixmap(QPixmap(":/lammps-icon-128x128.png").scaled(64, 64)); msg.setStandardButtons(QMessageBox::Ok); QFont font; font.setFixedPitch(true); @@ -532,6 +605,11 @@ void LammpsGui::help() QMessageBox::information(this, "LAMMPS-GUI Help", helpmsg); } +void LammpsGui::manual() +{ + QDesktopServices::openUrl(QUrl("https://docs.lammps.org/")); +} + void LammpsGui::preferences() { QString helpmsg = "This is LAMMPS-GUI version " LAMMPS_GUI_VERSION; diff --git a/tools/lammps-gui/lammpsgui.h b/tools/lammps-gui/lammpsgui.h index 7db1ff2825..d683b677c5 100644 --- a/tools/lammps-gui/lammpsgui.h +++ b/tools/lammps-gui/lammpsgui.h @@ -34,6 +34,7 @@ class QTimer; class Highlighter; class StdCapture; class Preferences; +class ImageViewer; class LammpsGui : public QMainWindow { Q_OBJECT @@ -64,8 +65,10 @@ private slots: void clear(); void run_buffer(); void stop_run(); + void view_image(); void about(); void help(); + void manual(); void logupdate(); void modified(); void preferences(); @@ -76,12 +79,14 @@ private: StdCapture *capturer; QLabel *status; QPlainTextEdit *logwindow; + ImageViewer *imagewindow; QTimer *logupdater; QProgressBar *progress; Preferences *prefdialog; QString current_file; QString current_dir; + QString temp_dir; void *lammps_handle; void *plugin_handle; const char *plugin_path; diff --git a/tools/lammps-gui/lammpsgui.ui b/tools/lammps-gui/lammpsgui.ui index b0f9d38261..9fbdf38446 100644 --- a/tools/lammps-gui/lammpsgui.ui +++ b/tools/lammps-gui/lammpsgui.ui @@ -67,6 +67,8 @@ + + @@ -74,6 +76,7 @@ + @@ -276,6 +279,20 @@ Ctrl+/ + + + + + + &View Snapshot + + + View Snapshot of current LAMMPS state + + + Ctrl+I + + @@ -306,7 +323,15 @@ .. - Edit Preferences... + Edit &Preferences... + + + + + + + + LAMMPS &Manual diff --git a/tools/lammps-gui/preferences.cpp b/tools/lammps-gui/preferences.cpp index 58be0f55fb..d29fcd6d1b 100644 --- a/tools/lammps-gui/preferences.cpp +++ b/tools/lammps-gui/preferences.cpp @@ -13,13 +13,7 @@ #include "preferences.h" -#include #include +#include -Preferences::Preferences(QWidget *parent) : QDialog(parent) -{ - -} - - - +Preferences::Preferences(QWidget *parent) : QDialog(parent) {} diff --git a/tools/lammps-gui/preferences.h b/tools/lammps-gui/preferences.h index 0cd8f374c0..cc15d62ded 100644 --- a/tools/lammps-gui/preferences.h +++ b/tools/lammps-gui/preferences.h @@ -19,8 +19,7 @@ class QTabWidget; class QDialogButtonBox; -class Preferences : public QDialog -{ +class Preferences : public QDialog { Q_OBJECT public: diff --git a/tools/lammps-gui/stdcapture.cpp b/tools/lammps-gui/stdcapture.cpp index 952c01e0d4..428277cc10 100644 --- a/tools/lammps-gui/stdcapture.cpp +++ b/tools/lammps-gui/stdcapture.cpp @@ -46,7 +46,7 @@ StdCapture::StdCapture() : m_oldStdOut(0), m_capturing(false) if (_pipe(m_pipe, 65536, O_BINARY) == -1) return; #else if (pipe(m_pipe) == -1) return; - fcntl(m_pipe[READ], F_SETFL, fcntl(m_pipe[READ], F_GETFL)|O_NONBLOCK); + fcntl(m_pipe[READ], F_SETFL, fcntl(m_pipe[READ], F_GETFL) | O_NONBLOCK); #endif m_oldStdOut = dup(fileno(stdout)); if (m_oldStdOut == -1) return;