From cb14461f58fe9fca363a5b5b72cefb9e4d6c2865 Mon Sep 17 00:00:00 2001 From: jachin Date: Sat, 19 Dec 2020 16:18:25 +0800 Subject: [PATCH] minimize to system tray (#1601) --- src/core/coreconfig.cpp | 16 +++ src/core/coreconfig.h | 9 ++ src/core/editorconfig.cpp | 6 ++ src/core/editorconfig.h | 1 + src/core/iconfig.h | 10 ++ src/core/singleinstanceguard.cpp | 2 + src/core/vnotex.cpp | 6 +- src/main.cpp | 10 +- src/src.pro | 1 + src/widgets/dialogs/settings/editorpage.cpp | 20 ++++ src/widgets/dialogs/settings/editorpage.h | 2 + src/widgets/dialogs/settings/generalpage.cpp | 22 ++++ src/widgets/dialogs/settings/generalpage.h | 4 + src/widgets/mainwindow.cpp | 105 +++++++++++++++++-- src/widgets/mainwindow.h | 14 +++ src/widgets/toolbarhelper.cpp | 8 ++ src/widgets/viewarea.h | 1 + src/widgets/viewwindow.cpp | 2 +- 18 files changed, 226 insertions(+), 13 deletions(-) diff --git a/src/core/coreconfig.cpp b/src/core/coreconfig.cpp index 5160ce19..7c2d7cc6 100644 --- a/src/core/coreconfig.cpp +++ b/src/core/coreconfig.cpp @@ -7,6 +7,7 @@ using namespace vnotex; #define READSTR(key) readString(appObj, userObj, (key)) #define READINT(key) readInt(appObj, userObj, (key)) +#define READBOOL(key) readBool(appObj, userObj, (key)) QStringList CoreConfig::s_availableLocales; @@ -40,6 +41,10 @@ void CoreConfig::init(const QJsonObject &p_app, if (m_toolBarIconSize <= 0) { m_toolBarIconSize = 16; } + + if (!isUndefinedKey(appObj, userObj, "minimize_to_system_tray")) { + m_minimizeToSystemTray = READBOOL(QStringLiteral("minimize_to_system_tray")) ? 1 : 0; + } } QJsonObject CoreConfig::toJson() const @@ -49,6 +54,9 @@ QJsonObject CoreConfig::toJson() const obj[QStringLiteral("locale")] = m_locale; obj[QStringLiteral("shortcuts")] = saveShortcuts(); obj[QStringLiteral("toolbar_icon_size")] = m_toolBarIconSize; + if (m_minimizeToSystemTray != -1) { + obj[QStringLiteral("minimize_to_system_tray")] = m_minimizeToSystemTray > 0; + } return obj; } @@ -122,3 +130,11 @@ void CoreConfig::setToolBarIconSize(int p_size) Q_ASSERT(p_size > 0); updateConfig(m_toolBarIconSize, p_size, this); } + +int CoreConfig::getMinimizeToSystemTray() const { + return m_minimizeToSystemTray; +} + +void CoreConfig::setMinimizeToSystemTray(bool state){ + updateConfig(m_minimizeToSystemTray, int(state), this); +} diff --git a/src/core/coreconfig.h b/src/core/coreconfig.h index 71d61806..533a74d4 100644 --- a/src/core/coreconfig.h +++ b/src/core/coreconfig.h @@ -48,6 +48,9 @@ namespace vnotex int getToolBarIconSize() const; void setToolBarIconSize(int p_size); + int getMinimizeToSystemTray() const; + void setMinimizeToSystemTray(bool state); + static const QStringList &getAvailableLocales(); private: @@ -67,6 +70,12 @@ namespace vnotex // Icon size of MainWindow tool bar. int m_toolBarIconSize = 16; + // Whether to minimize to tray. + // -1 for prompting for user; + // 0 for disabling minimizing to system tray; + // 1 for enabling minimizing to system tray; + int m_minimizeToSystemTray = -1; + static QStringList s_availableLocales; }; } // ns vnotex diff --git a/src/core/editorconfig.cpp b/src/core/editorconfig.cpp index 7bb89158..30e100a3 100644 --- a/src/core/editorconfig.cpp +++ b/src/core/editorconfig.cpp @@ -131,6 +131,12 @@ int EditorConfig::getToolBarIconSize() const return m_toolBarIconSize; } +void EditorConfig::setToolBarIconSize(int p_size) +{ + Q_ASSERT(p_size > 0); + updateConfig(m_toolBarIconSize, p_size, this); +} + const QString &EditorConfig::getShortcut(Shortcut p_shortcut) const { Q_ASSERT(p_shortcut < Shortcut::MaxShortcut); diff --git a/src/core/editorconfig.h b/src/core/editorconfig.h index ff21a233..62952ea4 100644 --- a/src/core/editorconfig.h +++ b/src/core/editorconfig.h @@ -75,6 +75,7 @@ namespace vnotex QJsonObject toJson() const Q_DECL_OVERRIDE; int getToolBarIconSize() const; + void setToolBarIconSize(int p_size); EditorConfig::AutoSavePolicy getAutoSavePolicy() const; void setAutoSavePolicy(EditorConfig::AutoSavePolicy p_policy); diff --git a/src/core/iconfig.h b/src/core/iconfig.h index d018ee58..daf0db78 100644 --- a/src/core/iconfig.h +++ b/src/core/iconfig.h @@ -126,6 +126,16 @@ namespace vnotex return read(p_default, p_user, p_key).toDouble(); } + static bool isUndefinedKey(const QJsonObject &p_default, + const QJsonObject &p_user, + const QString &p_key) + { + if (p_user.find(p_key) == p_user.end() && p_default.find(p_key) == p_default.end()) { + return true; + } + return false; + } + template static void updateConfig(T &p_cur, const T &p_new, diff --git a/src/core/singleinstanceguard.cpp b/src/core/singleinstanceguard.cpp index daeaeed6..f16e6029 100644 --- a/src/core/singleinstanceguard.cpp +++ b/src/core/singleinstanceguard.cpp @@ -23,6 +23,8 @@ bool SingleInstanceGuard::tryRun() // this will attach to the old segment, then exit, freeing the old segment. if (m_sharedMemory.attach()) { qInfo() << "another instance is running"; + // So try to show it? + showInstance(); return false; } diff --git a/src/core/vnotex.cpp b/src/core/vnotex.cpp index 32281554..addf8a0c 100644 --- a/src/core/vnotex.cpp +++ b/src/core/vnotex.cpp @@ -1,6 +1,7 @@ #include "vnotex.h" #include +#include #include #include "notebookmgr.h" @@ -12,6 +13,7 @@ #include + using namespace vnotex; VNoteX::VNoteX(QObject *p_parent) @@ -19,9 +21,7 @@ VNoteX::VNoteX(QObject *p_parent) m_mainWindow(nullptr), m_notebookMgr(nullptr) { - qsrand(QDateTime::currentDateTime().toTime_t()); - - m_instanceId = qrand(); + m_instanceId = QRandomGenerator::global()->generate64(); initThemeMgr(); diff --git a/src/main.cpp b/src/main.cpp index 086e2cb1..b0156529 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include #include +#include using namespace vnotex; @@ -119,7 +120,7 @@ int main(int argc, char *argv[]) loadTranslators(app); - MainWindow window; + MainWindow window(nullptr); window.show(); VNoteX::getInst().getThemeMgr().setBaseBackground(window.palette().color(QPalette::Base)); @@ -127,6 +128,13 @@ int main(int argc, char *argv[]) window.kickOffOnStart(); int ret = app.exec(); + if (ret == RESTART_EXIT_CODE) { + // Ask to restart VNote. + guard.exit(); + QProcess::startDetached(qApp->applicationFilePath(), QStringList()); + return 0; + } + return ret; } diff --git a/src/src.pro b/src/src.pro index b6c45ee0..e7e4ee5f 100644 --- a/src/src.pro +++ b/src/src.pro @@ -5,6 +5,7 @@ equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 12): error("requires Qt 5 QT += core gui widgets webenginewidgets webchannel network svg printsupport CONFIG -= qtquickcompiler +unix:!macx:CONFIG += use_gold_linker # Enable message log in release build DEFINES += QT_MESSAGELOGCONTEXT diff --git a/src/widgets/dialogs/settings/editorpage.cpp b/src/widgets/dialogs/settings/editorpage.cpp index db7ac616..49d8958b 100644 --- a/src/widgets/dialogs/settings/editorpage.cpp +++ b/src/widgets/dialogs/settings/editorpage.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,21 @@ void EditorPage::setupUI() connect(m_autoSavePolicyComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &EditorPage::pageIsChanged); } + + { + m_toolBarIconSizeSpinBox = WidgetsFactory::createSpinBox(this); + m_toolBarIconSizeSpinBox->setToolTip(tr("Icon size of the editor tool bar")); + + m_toolBarIconSizeSpinBox->setRange(1, 48); + m_toolBarIconSizeSpinBox->setSingleStep(1); + + const QString label(tr("Toolbar icon size:")); + mainLayout->addRow(label, m_toolBarIconSizeSpinBox); + addSearchItem(label, m_toolBarIconSizeSpinBox->toolTip(), m_toolBarIconSizeSpinBox); + connect(m_toolBarIconSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), + this, &EditorPage::pageIsChanged); + + } } void EditorPage::loadInternal() @@ -45,6 +61,8 @@ void EditorPage::loadInternal() Q_ASSERT(idx != -1); m_autoSavePolicyComboBox->setCurrentIndex(idx); } + + m_toolBarIconSizeSpinBox->setValue(editorConfig.getToolBarIconSize()); } void EditorPage::saveInternal() @@ -56,6 +74,8 @@ void EditorPage::saveInternal() editorConfig.setAutoSavePolicy(static_cast(policy)); } + editorConfig.setToolBarIconSize(m_toolBarIconSizeSpinBox->value()); + notifyEditorConfigChange(); } diff --git a/src/widgets/dialogs/settings/editorpage.h b/src/widgets/dialogs/settings/editorpage.h index 15442b8d..8e39e791 100644 --- a/src/widgets/dialogs/settings/editorpage.h +++ b/src/widgets/dialogs/settings/editorpage.h @@ -4,6 +4,7 @@ #include "settingspage.h" class QComboBox; +class QSpinBox; namespace vnotex { @@ -27,6 +28,7 @@ namespace vnotex void setupUI(); QComboBox *m_autoSavePolicyComboBox = nullptr; + QSpinBox *m_toolBarIconSizeSpinBox = nullptr; }; } diff --git a/src/widgets/dialogs/settings/generalpage.cpp b/src/widgets/dialogs/settings/generalpage.cpp index 84a83552..d44e2e16 100644 --- a/src/widgets/dialogs/settings/generalpage.cpp +++ b/src/widgets/dialogs/settings/generalpage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -55,6 +56,17 @@ void GeneralPage::setupUI() this, &GeneralPage::pageIsChanged); } #endif + +#if not defined(Q_OS_MACOS) + { + m_systemTrayCheckBox = WidgetsFactory::createCheckBox("System tray"); + mainLayout->addRow(m_systemTrayCheckBox); + + connect(m_systemTrayCheckBox, &QCheckBox::stateChanged, + this, &GeneralPage::pageIsChanged); + } +#endif + } void GeneralPage::loadInternal() @@ -73,6 +85,12 @@ void GeneralPage::loadInternal() Q_ASSERT(idx != -1); m_openGLComboBox->setCurrentIndex(idx); } + + if(m_systemTrayCheckBox){ + auto toTray = coreConfig.getMinimizeToSystemTray(); + if(toTray) + m_systemTrayCheckBox->setChecked(true); + } } void GeneralPage::saveInternal() @@ -89,6 +107,10 @@ void GeneralPage::saveInternal() int opt = m_openGLComboBox->currentData().toInt(); sessionConfig.setOpenGL(static_cast(opt)); } + + if(m_systemTrayCheckBox) { + coreConfig.setMinimizeToSystemTray(m_systemTrayCheckBox->isChecked()); + } } QString GeneralPage::title() const diff --git a/src/widgets/dialogs/settings/generalpage.h b/src/widgets/dialogs/settings/generalpage.h index f81ef5e4..90717e6d 100644 --- a/src/widgets/dialogs/settings/generalpage.h +++ b/src/widgets/dialogs/settings/generalpage.h @@ -4,6 +4,7 @@ #include "settingspage.h" class QComboBox; +class QCheckBox; namespace vnotex { @@ -26,6 +27,9 @@ namespace vnotex QComboBox *m_localeComboBox = nullptr; QComboBox *m_openGLComboBox = nullptr; + + QCheckBox *m_systemTrayCheckBox = nullptr; + }; } diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index be3755ef..f15af38e 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include "toolbox.h" #include "notebookexplorer.h" @@ -32,6 +35,7 @@ #include "outlineviewer.h" #include #include "navigationmodemgr.h" +#include #include @@ -46,6 +50,8 @@ MainWindow::MainWindow(QWidget *p_parent) setupUI(); + initSystemTrayIcon(); + setupShortcuts(); loadStateAndGeometry(); @@ -54,6 +60,9 @@ MainWindow::MainWindow(QWidget *p_parent) QApplication::setQuitOnLastWindowClosed(false); #endif + // The signal is particularly useful if your application has + // to do some last-second cleanup. + // Note that no user interaction is possible in this state. connect(qApp, &QCoreApplication::aboutToQuit, this, &MainWindow::closeOnQuit); } @@ -282,21 +291,61 @@ void MainWindow::closeEvent(QCloseEvent *p_event) { // TODO: support minimized to system tray. + auto toTray = ConfigMgr::getInst().getCoreConfig().getMinimizeToSystemTray(); + bool isExit = m_requestQuit; + m_requestQuit = 0; + if (isVisible()) { saveStateAndGeometry(); } - // Signal out the close event. - auto event = QSharedPointer::create(); - event->m_response = true; - emit mainWindowClosed(event); - if (!event->m_response.toBool()) { - // Stop the close. - p_event->ignore(); +#if defined(Q_OS_MACOS) || defined(Q_OS_MAC) + // Do not support minimized to tray on macOS. + if (!isExit) { + p_event->accept(); return; } +#endif - QMainWindow::closeEvent(p_event); + if(!isExit && toTray == -1){ + int ret = MessageBoxHelper::questionYesNo(MessageBoxHelper::Question, + tr("Close VNote"), + tr("Do you want to minimize VNote to system tray " + "instead of quitting it when closing VNote?"), + tr("You could change the option in Settings later."), + this); + if (ret == QMessageBox::Yes) { + ConfigMgr::getInst().getCoreConfig().setMinimizeToSystemTray(true); + hide(); + } else if (ret == QMessageBox::No) { + ConfigMgr::getInst().getCoreConfig().setMinimizeToSystemTray(false); + isExit = true; + } else { + p_event->ignore(); + return; + } + } + + if(isExit || toTray == 0 || !m_trayIcon->isVisible()){ + // really to quit, process workspace + // TODO: process workspace + + // Signal out the close event. + auto event = QSharedPointer::create(); + event->m_response = true; + emit mainWindowClosed(event); + if (!event->m_response.toBool()) { + // Stop the close. + p_event->ignore(); + return; + } + + QMainWindow::closeEvent(p_event); + qApp->quit(); + }else { + hide(); + p_event->ignore(); + } } void MainWindow::saveStateAndGeometry() @@ -485,3 +534,43 @@ void MainWindow::setStayOnTop(bool p_enabled) show(); } } + +void MainWindow::initSystemTrayIcon(){ + QMenu *menu = new QMenu(this); + QAction *showMainWindowAct = menu->addAction(tr("Show VNote")); + connect(showMainWindowAct, &QAction::triggered, + this, &MainWindow::show); + + QAction *exitAct = menu->addAction(tr("Quit")); + connect(exitAct, &QAction::triggered, + this, [this](){ + this->m_requestQuit = 1; + this->close(); + }); + + QIcon sysIcon(":/vnotex/data/core/logo/vnote.png"); + +#if defined(Q_OS_MACOS) || defined(Q_OS_MAC) + sysIcon.setIsMask(true); +#endif + + m_trayIcon = new QSystemTrayIcon(sysIcon, this); + m_trayIcon->setToolTip(tr("VNote")); + m_trayIcon->setContextMenu(menu); + + connect(m_trayIcon, &QSystemTrayIcon::activated, + this, [this](QSystemTrayIcon::ActivationReason p_reason){ +#if !defined(Q_OS_MACOS) && !defined(Q_OS_MAC) + if (p_reason == QSystemTrayIcon::Trigger) { + this->show(); + this->activateWindow(); + } +#endif + }); + + m_trayIcon->show(); +} + +void MainWindow::restart(){ + QCoreApplication::exit(RESTART_EXIT_CODE); +} diff --git a/src/widgets/mainwindow.h b/src/widgets/mainwindow.h index c4fb96f7..1b739eeb 100644 --- a/src/widgets/mainwindow.h +++ b/src/widgets/mainwindow.h @@ -7,7 +7,10 @@ #include "toolbarhelper.h" #include "statusbarhelper.h" +#define RESTART_EXIT_CODE 1000 + class QDockWidget; +class QSystemTrayIcon; namespace vnotex { @@ -42,6 +45,8 @@ namespace vnotex void setStayOnTop(bool p_enabled); + void restart(); + signals: void mainWindowStarted(); @@ -100,6 +105,14 @@ namespace vnotex void setupShortcuts(); + // Init system tray and correspondign context menu. + void initSystemTrayIcon(); + + // Tray icon. + QSystemTrayIcon *m_trayIcon; + + bool m_requestQuit = false; + ToolBarHelper m_toolBarHelper; StatusBarHelper m_statusBarHelper; @@ -117,6 +130,7 @@ namespace vnotex QVector m_docks; bool m_layoutReset = false; + }; } // ns vnotex diff --git a/src/widgets/toolbarhelper.cpp b/src/widgets/toolbarhelper.cpp index d84e48d1..487492b2 100644 --- a/src/widgets/toolbarhelper.cpp +++ b/src/widgets/toolbarhelper.cpp @@ -305,6 +305,14 @@ QToolBar *ToolBarHelper::setupSettingsToolBar(MainWindow *p_win, QToolBar *p_too [p_win]() { p_win->resetStateAndGeometry(); }); + + menu->addSeparator(); + + menu->addAction(MainWindow::tr("Restart"), + menu, + [p_win]() { + p_win->restart(); + }); } // Help. diff --git a/src/widgets/viewarea.h b/src/widgets/viewarea.h index b4864c33..404c49a7 100644 --- a/src/widgets/viewarea.h +++ b/src/widgets/viewarea.h @@ -13,6 +13,7 @@ class QLayout; class QSplitter; +class QTimer; namespace vnotex { diff --git a/src/widgets/viewwindow.cpp b/src/widgets/viewwindow.cpp index ad641e24..2787a03e 100644 --- a/src/widgets/viewwindow.cpp +++ b/src/widgets/viewwindow.cpp @@ -410,7 +410,7 @@ QAction *ViewWindow::addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Act act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action); connect(act, &QAction::triggered, this, [this]() { - if (m_findAndReplace && m_findAndReplace->isVisible()) { + if (findAndReplaceWidgetVisible()) { hideFindAndReplaceWidget(); } else { showFindAndReplaceWidget();