From 66422d36b9a3b6e3fec6b1c1e3aeba141cdb1cad Mon Sep 17 00:00:00 2001 From: Sunshine Date: Mon, 26 Oct 2026 08:52:12 -0400 Subject: [PATCH] make right-click context menu much more versatile --- README.md | 4 ++ inc/liquid.hpp | 3 + inc/liquidappconfigwindow.hpp | 7 ++ inc/liquidappwindow.hpp | 10 +-- inc/lqd.h | 1 + src/liquid.cpp | 12 ++++ src/liquidappconfigwindow.cpp | 15 ++++- src/liquidappwindow.cpp | 119 +++++++++++++++++++++++++++++----- src/main.cpp | 6 ++ 9 files changed, 154 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 108fb41..8e98597 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ What gets stored (if allowed): cookies. | Take snapshot | `Ctrl`+`T` | | | Take full-page snapshot | `Ctrl`+`Shift`+`T` | | | Take vector snapshot (SVG) | `Ctrl`+`Shift`+`S` | | +| Save page | `Ctrl`+`S` | | | Toggle window size lock | `Ctrl`+`L` | | | Toggle mute | `Ctrl`+`M` | | | Refresh current page | `Ctrl`+`R` | | @@ -70,6 +71,9 @@ What gets stored (if allowed): cookies. | Go back | `Ctrl`+`←` | `Backspace` | | Go forward | `Ctrl`+`→` | | | Open link using default web browser | `Ctrl`+_click_ | | +| Edit app settings | `Ctrl`+`,` | | + +Most of these can also be found in the right-click menu. ## Working with the codebase diff --git a/inc/liquid.hpp b/inc/liquid.hpp index 21ec35f..36c754b 100644 --- a/inc/liquid.hpp +++ b/inc/liquid.hpp @@ -28,6 +28,9 @@ class Liquid static QString getUniqueFilePath(const QString dirPath, const QString baseName, const QString extension); static void removeDesktopFile(const QString liquidAppName); static void runLiquidApp(const QString liquidAppName, const QString activationToken = QString()); + // For Liquid apps restarting themselves: they get run again once this program is done (and is no longer in the way) + static void requestRestart(const QString liquidAppName); + static QString getRequestedRestart(void); static void sleep(const int ms); static void updateDesktopFileIcon(const QString liquidAppName); }; diff --git a/inc/liquidappconfigwindow.hpp b/inc/liquidappconfigwindow.hpp index 039b240..af7358c 100644 --- a/inc/liquidappconfigwindow.hpp +++ b/inc/liquidappconfigwindow.hpp @@ -25,6 +25,11 @@ class LiquidAppConfigDialog : public QDialog bool isPlanningToRun(void); void setPlanningToRun(const bool state); + // When opened by the Liquid app itself, the dialog doesn't apply saved settings to it (the Liquid app does that) + void setOpenedFromLiquidApp(const bool state); + // Whether saved settings can only take effect once the Liquid app restarts + bool isRestartRequired(void); + public slots: void save(); void reject() override; @@ -38,6 +43,8 @@ public slots: bool isEditingExistingBool = false; bool isPlanningToRunBool = false; + bool isOpenedFromLiquidAppBool = false; + bool isRestartRequiredBool = false; // Connection to the Liquid app being edited (if running), for previewing changes QLocalSocket* liquidAppConnection = Q_NULLPTR; diff --git a/inc/liquidappwindow.hpp b/inc/liquidappwindow.hpp index 5af834c..cca49af 100644 --- a/inc/liquidappwindow.hpp +++ b/inc/liquidappwindow.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,7 @@ public slots: void waitForNextFrame(void); bool waitForDocumentToFinishLoading(void); void applyIcon(void); + void editSettings(void); void maybeFetchIcon(void); void useFetchedIcon(const LiquidAppIcon& icon); bool isShowingOwnWebsite(void); @@ -92,6 +94,9 @@ public slots: LiquidAppIconFetcher* iconFetcher = Q_NULLPTR; bool iconFetchAttempted = false; + // Settings dialog, while it's open + QPointer settingsDialog; + LiquidAppWebPage* liquidAppWebPage = Q_NULLPTR; QWebEngineProfile* liquidAppWebProfile = Q_NULLPTR; QWebEngineSettings* liquidAppWebSettings = Q_NULLPTR; @@ -122,6 +127,7 @@ public slots: QAction* reloadAction; QAction* reloadAction2; QAction* savePageAction; + QAction* editSettingsAction; QAction* stopLoadingOrExitFullScreenModeAction; QAction* takeSnapshotAction; QAction* takeSnapshotFullPageAction; @@ -139,10 +145,6 @@ public slots: // Context menu and its actions QMenu* contextMenu; QAction* contextMenuCopyUrlAction; - QAction* contextMenuReloadAction; - QAction* contextMenuBackAction; - QAction* contextMenuForwardAction; - QAction* contextMenuCloseAction; // Camera flash effect for snapshots SnapshotFlashOverlay* snapshotFlashOverlay = Q_NULLPTR; diff --git a/inc/lqd.h b/inc/lqd.h index 0f93d01..fa158f8 100644 --- a/inc/lqd.h +++ b/inc/lqd.h @@ -128,6 +128,7 @@ #define LQD_KBD_SEQ_QUIT "Ctrl+Q" #define LQD_KBD_SEQ_QUIT_2 "Ctrl+W" #define LQD_KBD_SEQ_SAVE_PAGE "Ctrl+S" +#define LQD_KBD_SEQ_EDIT_SETTINGS "Ctrl+," #define LQD_KBD_SEQ_TAKE_SNAPSHOT_VECTOR "Ctrl+Shift+S" #define LQD_KBD_SEQ_ZOOM_LVL_INC "Ctrl+=" #define LQD_KBD_SEQ_ZOOM_LVL_INC_FINE "Ctrl+Shift+=" diff --git a/src/liquid.cpp b/src/liquid.cpp index b8ad926..b2912e1 100644 --- a/src/liquid.cpp +++ b/src/liquid.cpp @@ -405,6 +405,18 @@ QByteArray Liquid::makeRaiseMessage(const QString& activationToken) return message; } +static QString liquidAppToRestart; + +void Liquid::requestRestart(const QString liquidAppName) +{ + liquidAppToRestart = liquidAppName; +} + +QString Liquid::getRequestedRestart(void) +{ + return liquidAppToRestart; +} + void Liquid::sleep(const int ms) { // Keep processing events while waiting, but without spinning the CPU: diff --git a/src/liquidappconfigwindow.cpp b/src/liquidappconfigwindow.cpp index ec4766c..14f4329 100644 --- a/src/liquidappconfigwindow.cpp +++ b/src/liquidappconfigwindow.cpp @@ -1273,7 +1273,10 @@ void LiquidAppConfigDialog::save() if (isEditingExistingBool) { const QString instanceId = LQD_INSTANCE_ID_APP_PREFIX + appName; - if (getSettingsRequiringRestart(tempLiquidAppConfig) != settingsRequiringRestartBefore) { + if (isOpenedFromLiquidAppBool) { + // The Liquid app can't be told to quit (and be waited for) from within itself, it takes care of it on its own + isRestartRequiredBool = getSettingsRequiringRestart(tempLiquidAppConfig) != settingsRequiringRestartBefore; + } else if (getSettingsRequiringRestart(tempLiquidAppConfig) != settingsRequiringRestartBefore) { // Restart it (wait for it to quit first, it's not possible to have two instances running at once) if (SingleInstance::sendMessage(instanceId, LQD_INSTANCE_MSG_QUIT)) { QElapsedTimer timer; @@ -1397,3 +1400,13 @@ void LiquidAppConfigDialog::setPlanningToRun(const bool state) } } } + +void LiquidAppConfigDialog::setOpenedFromLiquidApp(const bool state) +{ + isOpenedFromLiquidAppBool = state; +} + +bool LiquidAppConfigDialog::isRestartRequired(void) +{ + return isRestartRequiredBool; +} diff --git a/src/liquidappwindow.cpp b/src/liquidappwindow.cpp index 7dee0ab..91c46de 100644 --- a/src/liquidappwindow.cpp +++ b/src/liquidappwindow.cpp @@ -27,6 +27,7 @@ #include "liquid.hpp" #include "liquidappfile.hpp" #include "liquidappicon.hpp" +#include "liquidappconfigwindow.hpp" #include "liquidappcookiejar.hpp" #include "liquidappwebpage.hpp" #include "liquidappwindow.hpp" @@ -418,6 +419,12 @@ void LiquidAppWindow::bindKeyboardShortcuts(void) addAction(takeSnapshotVectorAction); connect(takeSnapshotVectorAction, &QAction::triggered, this, &LiquidAppWindow::takeVectorSnapshot); + // Connect "edit settings" shortcut + editSettingsAction = new QAction; + editSettingsAction->setShortcut(QKeySequence(tr(LQD_KBD_SEQ_EDIT_SETTINGS))); + addAction(editSettingsAction); + connect(editSettingsAction, &QAction::triggered, this, &LiquidAppWindow::editSettings); + // Connect "save page" shortcut savePageAction = new QAction; savePageAction->setShortcut(QKeySequence(tr(LQD_KBD_SEQ_SAVE_PAGE))); @@ -457,6 +464,11 @@ void LiquidAppWindow::closeEvent(QCloseEvent* event) return; } + // Settings being edited (e.g. when told to quit by the main window) get discarded, and their preview undone + if (settingsDialog) { + settingsDialog->reject(); + } + event->accept(); deleteLater(); } @@ -465,10 +477,22 @@ void LiquidAppWindow::contextMenuEvent(QContextMenuEvent* event) { Q_UNUSED(event); - contextMenuBackAction->setEnabled(history()->canGoBack()); - contextMenuForwardAction->setEnabled(history()->canGoForward()); + // Reflect current state (keyboard shortcuts work regardless, so it's only updated here) + backAction->setEnabled(history()->canGoBack()); + forwardAction->setEnabled(history()->canGoForward()); + muteAudioAction->setChecked(page()->isAudioMuted()); + toggleFullScreenModeAction->setText((isFullScreen()) ? tr("Exit Full Screen") : tr("Enter Full Screen")); + toggleFullScreenModeAction->setIcon(QIcon::fromTheme((isFullScreen()) ? QStringLiteral("view-restore") : QStringLiteral("view-fullscreen"))); + toggleGeometryLockAction->setChecked(windowGeometryIsLocked); + toggleGeometryLockAction->setText((windowGeometryIsLocked) ? tr("Unfreeze Frame Size") : tr("Freeze Frame Size")); + toggleGeometryLockAction->setEnabled(!isFullScreen()); // Frame size can't be frozen while in full-screen mode contextMenu->exec(QCursor::pos()); + + // Disabled actions don't respond to keyboard shortcuts either, and the state they reflect changes all the time + backAction->setEnabled(true); + forwardAction->setEnabled(true); + toggleGeometryLockAction->setEnabled(true); } bool LiquidAppWindow::eventFilter(QObject* watched, QEvent* event) @@ -1136,31 +1160,90 @@ void LiquidAppWindow::saveLiquidAppConfig(void) void LiquidAppWindow::setupContextMenu(void) { - contextMenu = new QMenu; + contextMenu = new QMenu(this); - contextMenuCopyUrlAction = new QAction(QIcon::fromTheme(QStringLiteral("internet-web-browser")), tr("Copy Current URL")); - contextMenuReloadAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), tr("Refresh")); - contextMenuBackAction = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), tr("Go Back")); - contextMenuForwardAction = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), tr("Go Forward")); - contextMenuCloseAction = new QAction(QIcon::fromTheme(QStringLiteral("process-stop")), tr("Quit")); - - contextMenu->addAction(contextMenuCopyUrlAction); - contextMenu->addAction(contextMenuReloadAction); - contextMenu->addAction(contextMenuBackAction); - contextMenu->addAction(contextMenuForwardAction); - contextMenu->addAction(contextMenuCloseAction); + // The menu reuses actions behind keyboard shortcuts, which makes it show the shortcuts next to them + const auto present = [](QAction* action, const char* const iconName, const QString& text) { + action->setIcon(QIcon::fromTheme(QLatin1String(iconName))); + action->setText(text); + action->setShortcutVisibleInContextMenu(true); + }; + contextMenuCopyUrlAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy Current URL"), this); connect(contextMenuCopyUrlAction, &QAction::triggered, this, [this](){ QApplication::clipboard()->setText(page()->url().toString()); }); - connect(contextMenuReloadAction, &QAction::triggered, this, &QWebEngineView::reload); - connect(contextMenuBackAction, &QAction::triggered, this, &QWebEngineView::back); - connect(contextMenuForwardAction, &QAction::triggered, this, &QWebEngineView::forward); - connect(contextMenuCloseAction, SIGNAL(triggered()), this, SLOT(close())); + + present(backAction, "go-previous", tr("Go Back")); + present(forwardAction, "go-next", tr("Go Forward")); + present(reloadAction, "view-refresh", tr("Refresh")); + present(savePageAction, "document-save", tr("Save Page")); + present(takeSnapshotAction, "camera-photo", tr("Take Screenshot")); + present(takeSnapshotFullPageAction, "camera-photo", tr("Take Full-Page Screenshot")); + present(takeSnapshotVectorAction, "image-x-generic", tr("Save as Vector Image")); + present(muteAudioAction, "audio-volume-muted", tr("Mute Audio")); + present(toggleFullScreenModeAction, "view-fullscreen", tr("Enter Full Screen")); + present(toggleGeometryLockAction, "object-locked", tr("Freeze Frame Size")); + present(editSettingsAction, "preferences-system", tr("Settings…")); + present(quitAction, "application-exit", tr("Quit")); + + muteAudioAction->setCheckable(true); + toggleGeometryLockAction->setCheckable(true); + + contextMenu->addAction(backAction); + contextMenu->addAction(forwardAction); + contextMenu->addAction(reloadAction); + contextMenu->addSeparator(); + contextMenu->addAction(contextMenuCopyUrlAction); + contextMenu->addAction(savePageAction); + contextMenu->addSeparator(); + contextMenu->addAction(takeSnapshotAction); + contextMenu->addAction(takeSnapshotFullPageAction); + contextMenu->addAction(takeSnapshotVectorAction); + contextMenu->addSeparator(); + contextMenu->addAction(muteAudioAction); + contextMenu->addAction(toggleFullScreenModeAction); + contextMenu->addAction(toggleGeometryLockAction); + contextMenu->addSeparator(); + contextMenu->addAction(editSettingsAction); + contextMenu->addAction(quitAction); setContextMenuPolicy(Qt::DefaultContextMenu); } +void LiquidAppWindow::editSettings(void) +{ + // Only one at a time + if (settingsDialog) { + Liquid::raiseWindow(settingsDialog); + return; + } + + LiquidAppConfigDialog dialog(this, liquidAppName); + dialog.setOpenedFromLiquidApp(true); + settingsDialog = &dialog; + + // Changes get previewed while editing (the dialog talks to this Liquid app the same way it would from the main window) + const int result = dialog.exec(); + settingsDialog = Q_NULLPTR; + + if (result != QDialog::Accepted) { + return; + } + + if (dialog.isRestartRequired()) { + // Quit (saving everything as usual), and have this program run the Liquid app again on its way out + Liquid::requestRestart(liquidAppName); + close(); + return; + } + + reloadLiquidAppConfig(); + + // Let the main window (if it's open) show the icon, in case it's changed + SingleInstance::sendMessage(LQD_INSTANCE_ID_MAIN, QByteArray(LQD_INSTANCE_MSG_ICON_CHANGED) + " " + liquidAppName.toUtf8()); +} + void LiquidAppWindow::setForgiveNextPageLoadError(const bool ok) { forgiveNextPageLoadError = ok; diff --git a/src/main.cpp b/src/main.cpp index cbf7a49..330336b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -231,5 +231,11 @@ int main(int argc, char **argv) singleInstance = Q_NULLPTR; } + // A Liquid app that has had settings changed that require it to restart (by now, it's saved everything, + // and no longer claims its instance, so the new one won't mistake it for itself already running) + if (!Liquid::getRequestedRestart().isEmpty()) { + Liquid::runLiquidApp(Liquid::getRequestedRestart()); + } + return ret; }