diff --git a/src/ui/fort_images.qrc b/src/ui/fort_images.qrc index 9a43c405..5d93503b 100644 --- a/src/ui/fort_images.qrc +++ b/src/ui/fort_images.qrc @@ -1,5 +1,6 @@ images/cog.png + images/shield.png diff --git a/src/ui/fort_qml.qrc b/src/ui/fort_qml.qrc index d7ae5175..e356c005 100644 --- a/src/ui/fort_qml.qrc +++ b/src/ui/fort_qml.qrc @@ -6,6 +6,7 @@ qml/pages/AddressesPage.qml qml/pages/ApplicationsPage.qml qml/pages/BasePage.qml + qml/pages/MainPage.qml qml/pages/OptionsPage.qml qml/pages/addresses/AddressesColumn.qml qml/pages/apps/AppsColumn.qml diff --git a/src/ui/fortmanager.cpp b/src/ui/fortmanager.cpp index 8d6862d0..fa8459e3 100644 --- a/src/ui/fortmanager.cpp +++ b/src/ui/fortmanager.cpp @@ -1,8 +1,11 @@ #include "fortmanager.h" -#include +#include +#include #include #include +#include +#include #include "conf/addressgroup.h" #include "conf/appgroup.h" @@ -11,13 +14,18 @@ FortManager::FortManager(QObject *parent) : QObject(parent), + m_trayIcon(new QSystemTrayIcon(this)), + m_engine(new QQmlApplicationEngine(this)), m_fortSettings(new FortSettings(qApp->arguments(), this)), m_firewallConf(new FirewallConf(this)), - m_firewallConfToEdit(nullptr) + m_firewallConfToEdit(nullConf()) { m_fortSettings->readConf(*m_firewallConf); registerQmlTypes(); + + setupTrayIcon(); + setupEngine(); } void FortManager::registerQmlTypes() @@ -32,27 +40,52 @@ void FortManager::registerQmlTypes() qmlRegisterType("com.fortfirewall", 1, 0, "FirewallConf"); } -void FortManager::setupContext() +void FortManager::setupTrayIcon() { - QQmlContext *context = m_engine->rootContext(); + m_trayIcon->setToolTip(qApp->applicationDisplayName()); + m_trayIcon->setIcon(QIcon(":/images/shield.png")); - context->setContextProperty("fortManager", this); + connect(m_trayIcon, &QSystemTrayIcon::activated, + [this](QSystemTrayIcon::ActivationReason reason) { + if (reason == QSystemTrayIcon::Trigger) + showWindow(); + }); + + updateTrayMenu(); +} + +void FortManager::setupEngine() +{ + m_engine->rootContext()->setContextProperty("fortManager", this); + + m_engine->load(QUrl("qrc:/qml/main.qml")); + + m_appWindow = qobject_cast( + m_engine->rootObjects().first()); + Q_ASSERT(m_appWindow); +} + +void FortManager::showTrayIcon() +{ + m_trayIcon->show(); } void FortManager::showWindow() { - m_engine = new QQmlApplicationEngine(this); + if (m_firewallConfToEdit == nullConf()) { + setFirewallConfToEdit(cloneConf(*m_firewallConf)); + } - connect(m_engine, &QQmlApplicationEngine::destroyed, - this, &FortManager::handleClosedWindow); + m_appWindow->show(); + m_appWindow->raise(); + m_appWindow->requestActivate(); +} - setupContext(); +void FortManager::closeWindow() +{ + m_appWindow->hide(); - // New conf to edit - Q_ASSERT(!m_firewallConfToEdit); - m_firewallConfToEdit = cloneConf(*m_firewallConf); - - m_engine->load(QUrl("qrc:/qml/main.qml")); + setFirewallConfToEdit(nullConf()); } bool FortManager::saveConf() @@ -62,10 +95,21 @@ bool FortManager::saveConf() bool FortManager::applyConf() { - Q_ASSERT(m_firewallConfToEdit); + Q_ASSERT(m_firewallConfToEdit != nullConf()); return saveSettings(cloneConf(*m_firewallConfToEdit)); } +void FortManager::setFirewallConfToEdit(FirewallConf *conf) +{ + if (m_firewallConfToEdit != nullConf() + && m_firewallConfToEdit != m_firewallConf) { + m_firewallConfToEdit->deleteLater(); + } + + m_firewallConfToEdit = conf; + emit firewallConfToEditChanged(); +} + bool FortManager::saveSettings(FirewallConf *newConf) { if (!m_fortSettings->writeConf(*newConf)) @@ -74,20 +118,11 @@ bool FortManager::saveSettings(FirewallConf *newConf) m_firewallConf->deleteLater(); m_firewallConf = newConf; + updateTrayMenu(); + return true; } -void FortManager::handleClosedWindow() -{ - m_engine->deleteLater(); - m_engine = nullptr; - - if (m_firewallConfToEdit && m_firewallConfToEdit != m_firewallConf) { - m_firewallConfToEdit->deleteLater(); - m_firewallConfToEdit = nullptr; - } -} - FirewallConf *FortManager::cloneConf(const FirewallConf &conf) { FirewallConf *newConf = new FirewallConf(this); @@ -99,3 +134,50 @@ FirewallConf *FortManager::cloneConf(const FirewallConf &conf) return newConf; } + +void FortManager::updateTrayMenu() +{ + QMenu *menu = m_trayIcon->contextMenu(); + if (menu) { + menu->deleteLater(); + } + + menu = new QMenu(&m_window); + + addAction(menu, QIcon(), tr("Show"), this, SLOT(showWindow())); + + menu->addSeparator(); + addAction(menu, QIcon(), tr("Quit"), qApp, SLOT(quit())); + + m_trayIcon->setContextMenu(menu); +} + +QAction *FortManager::addAction(QWidget *widget, + const QIcon &icon, const QString &text, + const QObject *receiver, const char *member, + bool checkable, bool checked) +{ + QAction *action = new QAction(icon, text, widget); + + if (receiver) { + connect(action, SIGNAL(triggered(bool)), receiver, member); + } + if (checkable) { + setActionCheckable(action, checked); + } + + widget->addAction(action); + + return action; +} + +void FortManager::setActionCheckable(QAction *action, bool checked, + const QObject *receiver, const char *member) +{ + action->setCheckable(true); + action->setChecked(checked); + + if (receiver) { + connect(action, SIGNAL(toggled(bool)), receiver, member); + } +} diff --git a/src/ui/fortmanager.h b/src/ui/fortmanager.h index 59a5f05c..5155d475 100644 --- a/src/ui/fortmanager.h +++ b/src/ui/fortmanager.h @@ -2,8 +2,10 @@ #define FORTMANAGER_H #include +#include class QQmlApplicationEngine; +class QSystemTrayIcon; class FortSettings; class FirewallConf; @@ -12,38 +14,58 @@ class FortManager : public QObject { Q_OBJECT Q_PROPERTY(FortSettings *fortSettings READ fortSettings CONSTANT) - Q_PROPERTY(FirewallConf *firewallConf READ firewallConf CONSTANT) - Q_PROPERTY(FirewallConf *firewallConfToEdit READ firewallConfToEdit CONSTANT) + Q_PROPERTY(FirewallConf *firewallConfToEdit READ firewallConfToEdit NOTIFY firewallConfToEditChanged) public: explicit FortManager(QObject *parent = nullptr); FortSettings *fortSettings() const { return m_fortSettings; } - FirewallConf *firewallConf() const { return m_firewallConf; } - FirewallConf *firewallConfToEdit() const { return m_firewallConfToEdit; } + + FirewallConf *firewallConfToEdit() const { + return m_firewallConfToEdit ? m_firewallConfToEdit : m_firewallConf; + } signals: + void firewallConfToEditChanged(); public slots: + void showTrayIcon(); + void showWindow(); + void closeWindow(); bool saveConf(); bool applyConf(); -private slots: - void handleClosedWindow(); - private: + FirewallConf *nullConf() const { return nullptr; } + + void setFirewallConfToEdit(FirewallConf *conf); + static void registerQmlTypes(); - void setupContext(); + void setupTrayIcon(); + void setupEngine(); bool saveSettings(FirewallConf *newConf); FirewallConf *cloneConf(const FirewallConf &conf); + void updateTrayMenu(); + + static QAction *addAction(QWidget *widget, + const QIcon &icon, const QString &text, + const QObject *receiver = 0, const char *member = 0, + bool checkable = false, bool checked = false); + static void setActionCheckable(QAction *action, bool checked = false, + const QObject *receiver = 0, const char *member = 0); + private: + QMainWindow m_window; // dummy window for tray icon + + QSystemTrayIcon *m_trayIcon; QQmlApplicationEngine *m_engine; + QWindow *m_appWindow; FortSettings *m_fortSettings; FirewallConf *m_firewallConf; diff --git a/src/ui/images/shield.png b/src/ui/images/shield.png new file mode 100644 index 00000000..3cb4e257 Binary files /dev/null and b/src/ui/images/shield.png differ diff --git a/src/ui/main.cpp b/src/ui/main.cpp index ac47c659..bfc75e0c 100644 --- a/src/ui/main.cpp +++ b/src/ui/main.cpp @@ -1,4 +1,4 @@ -#include +#include #include "../common/version.h" #include "fortmanager.h" @@ -7,13 +7,13 @@ int main(int argc, char *argv[]) { QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QGuiApplication app(argc, argv); + QApplication app(argc, argv); app.setApplicationName(APP_NAME); app.setApplicationVersion(APP_VERSION_STR); app.setApplicationDisplayName(APP_NAME " v" APP_VERSION_STR); FortManager fortManager; - fortManager.showWindow(); + fortManager.showTrayIcon(); return app.exec(); } diff --git a/src/ui/qml/main.qml b/src/ui/qml/main.qml index 86664d37..20201e97 100644 --- a/src/ui/qml/main.qml +++ b/src/ui/qml/main.qml @@ -1,90 +1,43 @@ import QtQuick 2.9 -import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.2 import QtQuick.Controls 2.2 import "pages" import com.fortfirewall 1.0 ApplicationWindow { - id: mainWindow + id: appWindow width: 800 height: 600 minimumWidth: 700 minimumHeight: 600 - visible: true font.pixelSize: 16 readonly property FortSettings fortSettings: fortManager.fortSettings readonly property FirewallConf firewallConf: fortManager.firewallConfToEdit - Component.onCompleted: { - tabBar.currentItem.forceActiveFocus(); + onClosing: { + if (visible) { + close.accepted = false; + closeWindow(); + } + } + + onVisibleChanged: { + if (visible) { + mainPage.initialize(); + } } function closeWindow() { - mainWindow.close(); + fortManager.closeWindow(); } - Page { + MainPage { + id: mainPage anchors.fill: parent Keys.onEscapePressed: closeWindow() - - header: TabBar { - id: tabBar - currentIndex: swipeView.currentIndex - - TabButton { - text: QT_TRANSLATE_NOOP("qml", "Options") - } - TabButton { - text: QT_TRANSLATE_NOOP("qml", "IPv4 Addresses") - } - TabButton { - text: QT_TRANSLATE_NOOP("qml", "Applications") - } - TabButton { - text: QT_TRANSLATE_NOOP("qml", "Activity") - } - } - - SwipeView { - id: swipeView - anchors.fill: parent - currentIndex: tabBar.currentIndex - - OptionsPage {} - AddressesPage {} - ApplicationsPage {} - ActivityPage {} - } - - footer: Pane { - RowLayout { - anchors.right: parent.right - - Button { - text: QT_TRANSLATE_NOOP("qml", "OK") - onClicked: { - if (fortManager.saveConf()) - closeWindow(); - } - } - Button { - text: QT_TRANSLATE_NOOP("qml", "Apply") - onClicked: fortManager.applyConf() - } - Button { - text: QT_TRANSLATE_NOOP("qml", "Cancel") - onClicked: closeWindow() - } - Button { - text: QT_TRANSLATE_NOOP("qml", "Quit") - onClicked: Qt.quit() - } - } - } } } diff --git a/src/ui/qml/pages/MainPage.qml b/src/ui/qml/pages/MainPage.qml new file mode 100644 index 00000000..6282b8d5 --- /dev/null +++ b/src/ui/qml/pages/MainPage.qml @@ -0,0 +1,67 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 +import com.fortfirewall 1.0 + +Page { + anchors.fill: parent + + function initialize() { + tabBar.currentItem.forceActiveFocus(); + } + + header: TabBar { + id: tabBar + currentIndex: swipeView.currentIndex + + TabButton { + text: QT_TRANSLATE_NOOP("qml", "Options") + } + TabButton { + text: QT_TRANSLATE_NOOP("qml", "IPv4 Addresses") + } + TabButton { + text: QT_TRANSLATE_NOOP("qml", "Applications") + } + TabButton { + text: QT_TRANSLATE_NOOP("qml", "Activity") + } + } + + SwipeView { + id: swipeView + anchors.fill: parent + currentIndex: tabBar.currentIndex + + OptionsPage {} + AddressesPage {} + ApplicationsPage {} + ActivityPage {} + } + + footer: Pane { + RowLayout { + anchors.right: parent.right + + Button { + text: QT_TRANSLATE_NOOP("qml", "OK") + onClicked: { + if (fortManager.saveConf()) + closeWindow(); + } + } + Button { + text: QT_TRANSLATE_NOOP("qml", "Apply") + onClicked: fortManager.applyConf() + } + Button { + text: QT_TRANSLATE_NOOP("qml", "Cancel") + onClicked: closeWindow() + } + Button { + text: QT_TRANSLATE_NOOP("qml", "Quit") + onClicked: Qt.quit() + } + } + } +}