diff --git a/src/ui/fort_qml.qrc b/src/ui/fort_qml.qrc index 6a3379b4..4220c19e 100644 --- a/src/ui/fort_qml.qrc +++ b/src/ui/fort_qml.qrc @@ -3,10 +3,13 @@ qml/controls/ButtonMenu.qml qml/controls/ButtonPopup.qml qml/controls/HSeparator.qml + qml/controls/LabelColorRow.qml + qml/controls/LabelSpinRow.qml qml/controls/LinkButton.qml qml/controls/ListViewControl.qml qml/controls/RoundButtonTip.qml qml/controls/ScrollBarControl.qml + qml/controls/SpinBoxControl.qml qml/controls/SpinCombo.qml qml/controls/SpinComboRow.qml qml/controls/SpinDouble.qml @@ -31,6 +34,7 @@ qml/pages/apps/AppsTextColumn.qml qml/pages/apps/SpeedLimitButton.qml qml/pages/log/AppListView.qml + qml/pages/log/GraphButton.qml qml/pages/log/IpListView.qml qml/pages/log/TrafOptionsButton.qml qml/pages/schedule/TaskRow.qml diff --git a/src/ui/fortmanager.cpp b/src/ui/fortmanager.cpp index 26b73a25..35639687 100644 --- a/src/ui/fortmanager.cpp +++ b/src/ui/fortmanager.cpp @@ -41,7 +41,6 @@ FortManager::FortManager(FortSettings *fortSettings, QObject *parent) : QObject(parent), - m_exiting(false), m_trayIcon(new QSystemTrayIcon(this)), m_engine(nullptr), m_appWindow(nullptr), @@ -221,8 +220,7 @@ void FortManager::launch() { showTrayIcon(); - if (m_fortSettings->graphWindowEnabled() - && m_fortSettings->graphWindowVisible()) { + if (m_fortSettings->graphWindowVisible()) { showGraphWindow(); } } @@ -285,8 +283,9 @@ void FortManager::showGraphWindow() m_graphWindowState->install(m_graphWindow); - connect(m_graphWindow, &GraphWindow::aboutToClose, - this, &FortManager::closeGraphWindow); + connect(m_graphWindow, &GraphWindow::aboutToClose, [this] { + closeGraphWindow(); + }); connect(m_graphWindow, &GraphWindow::mouseRightClick, this, &FortManager::showTrayMenu); @@ -302,12 +301,12 @@ void FortManager::showGraphWindow() restoreGraphWindowState(); } -void FortManager::closeGraphWindow() +void FortManager::closeGraphWindow(bool storeVisibility) { if (!m_graphWindow) return; - saveGraphWindowState(); + saveGraphWindowState(storeVisibility); m_graphWindowState->uninstall(m_graphWindow); @@ -327,11 +326,18 @@ void FortManager::switchGraphWindow() closeGraphWindow(); } +void FortManager::updateGraphWindow() +{ + if (!m_graphWindow) + return; + + m_graphWindow->updateColors(); + m_graphWindow->updateWindowFlags(); +} + void FortManager::exit(int retcode) { - m_exiting = true; - - closeGraphWindow(); + closeGraphWindow(true); closeWindow(); closeEngine(); @@ -538,9 +544,9 @@ void FortManager::restoreWindowState() m_fortSettings->windowMaximized()); } -void FortManager::saveGraphWindowState() +void FortManager::saveGraphWindowState(bool visible) { - m_fortSettings->setGraphWindowVisible(m_exiting); + m_fortSettings->setGraphWindowVisible(visible); m_fortSettings->setGraphWindowGeometry(m_graphWindowState->geometry()); m_fortSettings->setGraphWindowMaximized(m_graphWindowState->maximized()); } @@ -577,16 +583,14 @@ void FortManager::updateTrayMenu() this, SLOT(showWindow())); addHotKey(optionsAction, fortSettings()->hotKeyOptions(), hotKeyEnabled); - if (!conf.hasPassword() && !m_firewallConfToEdit) { - if (fortSettings()->graphWindowEnabled()) { - m_graphWindowAction = addAction( - menu, QIcon(":/images/chart_bar.png"), tr("Traffic Graph"), - this, SLOT(switchGraphWindow()), true, - (m_graphWindow != nullptr)); - addHotKey(m_graphWindowAction, fortSettings()->hotKeyGraph(), - conf.logStat()); - } + m_graphWindowAction = addAction( + menu, QIcon(":/images/chart_bar.png"), tr("Traffic Graph"), + this, SLOT(switchGraphWindow()), true, + (m_graphWindow != nullptr)); + addHotKey(m_graphWindowAction, fortSettings()->hotKeyGraph(), + conf.logStat()); + if (!conf.hasPassword() && !m_firewallConfToEdit) { menu->addSeparator(); m_filterEnabledAction = addAction( diff --git a/src/ui/fortmanager.h b/src/ui/fortmanager.h index 855965f1..00b160e1 100644 --- a/src/ui/fortmanager.h +++ b/src/ui/fortmanager.h @@ -57,8 +57,9 @@ public slots: void closeWindow(); void showGraphWindow(); - void closeGraphWindow(); + void closeGraphWindow(bool storeVisibility = false); void switchGraphWindow(); + void updateGraphWindow(); void exit(int retcode = 0); @@ -115,7 +116,7 @@ private: void saveWindowState(); void restoreWindowState(); - void saveGraphWindowState(); + void saveGraphWindowState(bool visible); void restoreGraphWindowState(); void updateLogger(); @@ -133,8 +134,6 @@ private: const QObject *receiver = nullptr, const char *member = nullptr); private: - uint m_exiting : 1; - MainWindow m_window; // dummy window for tray icon QSystemTrayIcon *m_trayIcon; diff --git a/src/ui/fortsettings.cpp b/src/ui/fortsettings.cpp index b695b5fe..8784c874 100644 --- a/src/ui/fortsettings.cpp +++ b/src/ui/fortsettings.cpp @@ -12,7 +12,9 @@ FortSettings::FortSettings(const QStringList &args, QObject *parent) : QObject(parent), - m_hasProvBoot(false) + m_hasProvBoot(false), + m_bulkUpdating(false), + m_bulkUpdatingEmit(false) { processArguments(args); setupIni(); @@ -396,12 +398,39 @@ QVariant FortSettings::iniValue(const QString &key, void FortSettings::setIniValue(const QString &key, const QVariant &value, const QVariant &defaultValue) { - if (!defaultValue.isNull() - && m_ini->value(key, defaultValue) == value) + const QVariant oldValue = m_ini->value(key, defaultValue); + if (oldValue == value) return; m_ini->setValue(key, value); - emit iniChanged(); + + if (m_bulkUpdating) { + m_bulkUpdatingEmit = true; + } else { + emit iniChanged(); + } +} + +void FortSettings::bulkUpdateBegin() +{ + Q_ASSERT(!m_bulkUpdating); + + m_bulkUpdating = true; + m_bulkUpdatingEmit = false; +} + +void FortSettings::bulkUpdateEnd() +{ + Q_ASSERT(m_bulkUpdating); + + m_bulkUpdating = false; + + const bool doEmit = m_bulkUpdatingEmit; + m_bulkUpdatingEmit = false; + + if (doEmit) { + emit iniChanged(); + } } void FortSettings::removeIniKey(const QString &key) diff --git a/src/ui/fortsettings.h b/src/ui/fortsettings.h index eae5d0e1..b40bdf9a 100644 --- a/src/ui/fortsettings.h +++ b/src/ui/fortsettings.h @@ -18,6 +18,21 @@ class FortSettings : public QObject Q_OBJECT Q_PROPERTY(bool debug READ debug WRITE setDebug NOTIFY iniChanged) Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY iniChanged) + Q_PROPERTY(bool graphWindowVisible READ graphWindowVisible WRITE setGraphWindowVisible NOTIFY iniChanged) + Q_PROPERTY(bool graphWindowAlwaysOnTop READ graphWindowAlwaysOnTop WRITE setGraphWindowAlwaysOnTop NOTIFY iniChanged) + Q_PROPERTY(bool graphWindowFrameless READ graphWindowFrameless WRITE setGraphWindowFrameless NOTIFY iniChanged) + Q_PROPERTY(bool graphWindowClickThrough READ graphWindowClickThrough WRITE setGraphWindowClickThrough NOTIFY iniChanged) + Q_PROPERTY(bool graphWindowHideOnHover READ graphWindowHideOnHover WRITE setGraphWindowHideOnHover NOTIFY iniChanged) + Q_PROPERTY(int graphWindowOpacity READ graphWindowOpacity WRITE setGraphWindowOpacity NOTIFY iniChanged) + Q_PROPERTY(int graphWindowHoverOpacity READ graphWindowHoverOpacity WRITE setGraphWindowHoverOpacity NOTIFY iniChanged) + Q_PROPERTY(int graphWindowMaxSeconds READ graphWindowMaxSeconds WRITE setGraphWindowMaxSeconds NOTIFY iniChanged) + Q_PROPERTY(QColor graphWindowColor READ graphWindowColor WRITE setGraphWindowColor NOTIFY iniChanged) + Q_PROPERTY(QColor graphWindowColorIn READ graphWindowColorIn WRITE setGraphWindowColorIn NOTIFY iniChanged) + Q_PROPERTY(QColor graphWindowColorOut READ graphWindowColorOut WRITE setGraphWindowColorOut NOTIFY iniChanged) + Q_PROPERTY(QColor graphWindowAxisColor READ graphWindowAxisColor WRITE setGraphWindowAxisColor NOTIFY iniChanged) + Q_PROPERTY(QColor graphWindowTickLabelColor READ graphWindowTickLabelColor WRITE setGraphWindowTickLabelColor NOTIFY iniChanged) + Q_PROPERTY(QColor graphWindowLabelColor READ graphWindowLabelColor WRITE setGraphWindowLabelColor NOTIFY iniChanged) + Q_PROPERTY(QColor graphWindowGridColor READ graphWindowGridColor WRITE setGraphWindowGridColor NOTIFY iniChanged) Q_PROPERTY(bool startWithWindows READ startWithWindows WRITE setStartWithWindows NOTIFY startWithWindowsChanged) Q_PROPERTY(bool hotKeyEnabled READ hotKeyEnabled WRITE setHotKeyEnabled NOTIFY iniChanged) Q_PROPERTY(QString logsPath READ logsPath CONSTANT) @@ -50,9 +65,6 @@ public: bool windowMaximized() const { return iniBool("window/maximized"); } void setWindowMaximized(bool on) { setIniValue("window/maximized", on); } - bool graphWindowEnabled() const { return iniBool("graphWindow/enabled"); } - void setGraphWindowEnabled(bool on) { setIniValue("graphWindow/enabled", on); } - bool graphWindowVisible() const { return iniBool("graphWindow/visible"); } void setGraphWindowVisible(bool on) { setIniValue("graphWindow/visible", on); } @@ -71,6 +83,9 @@ public: bool graphWindowClickThrough() const { return iniBool("graphWindow/clickThrough"); } void setGraphWindowClickThrough(bool on) { setIniValue("graphWindow/clickThrough", on); } + bool graphWindowHideOnHover() const { return iniBool("graphWindow/hideOnHover"); } + void setGraphWindowHideOnHover(bool on) { setIniValue("graphWindow/hideOnHover", on); } + int graphWindowOpacity() const { return iniInt("graphWindow/opacity", 10); } void setGraphWindowOpacity(int v) { setIniValue("graphWindow/opacity", v); } @@ -151,6 +166,9 @@ public slots: bool readConfIni(FirewallConf &conf) const; bool writeConfIni(const FirewallConf &conf); + void bulkUpdateBegin(); + void bulkUpdateEnd(); + private: void processArguments(const QStringList &args); void setupIni(); @@ -190,7 +208,9 @@ private: static QString startupShortcutPath(); private: - uint m_hasProvBoot : 1; + uint m_hasProvBoot : 1; + uint m_bulkUpdating : 1; + uint m_bulkUpdatingEmit : 1; QString m_profilePath; QString m_statPath; diff --git a/src/ui/graph/graphwindow.cpp b/src/ui/graph/graphwindow.cpp index 5073d35d..26ba2c64 100644 --- a/src/ui/graph/graphwindow.cpp +++ b/src/ui/graph/graphwindow.cpp @@ -17,14 +17,13 @@ GraphWindow::GraphWindow(FortSettings *fortSettings, setupUi(); setupTimer(); - setupWindow(); - - connect(m_fortSettings, &FortSettings::iniChanged, this, &GraphWindow::setupWindow); + updateWindowFlags(); + updateColors(); setMinimumSize(QSize(100, 50)); } -void GraphWindow::setupWindow() +void GraphWindow::updateWindowFlags() { const bool visible = isVisible(); @@ -38,13 +37,36 @@ void GraphWindow::setupWindow() ? Qt::WindowTransparentForInput : Qt::Widget) ); + if (visible) { + show(); // setWindowFlags() hides the window + } +} + +void GraphWindow::updateColors() +{ setWindowOpacityPercent(m_fortSettings->graphWindowOpacity()); m_plot->setBackground(QBrush(m_fortSettings->graphWindowColor())); - if (visible) { - show(); // setWindowFlags() hides the window - } + // Axis + auto yAxis = m_plot->yAxis; + + const QColor axisColor = m_fortSettings->graphWindowAxisColor(); + yAxis->setBasePen(adjustPen(yAxis->basePen(), axisColor)); + yAxis->setTickPen(adjustPen(yAxis->tickPen(), axisColor)); + yAxis->setSubTickPen(adjustPen(yAxis->subTickPen(), axisColor)); + + yAxis->setTickLabelColor(m_fortSettings->graphWindowTickLabelColor()); + yAxis->setLabelColor(m_fortSettings->graphWindowLabelColor()); + + yAxis->grid()->setPen(adjustPen(yAxis->grid()->pen(), + m_fortSettings->graphWindowGridColor())); + + // Graph Inbound + m_graphIn->setPen(QPen(m_fortSettings->graphWindowColorIn())); + + // Graph Outbound + m_graphOut->setPen(QPen(m_fortSettings->graphWindowColorOut())); } void GraphWindow::setupUi() @@ -70,17 +92,6 @@ void GraphWindow::setupUi() yAxis->setPadding(1); yAxis->setTickLabelPadding(2); - const QColor axisColor = m_fortSettings->graphWindowAxisColor(); - yAxis->setBasePen(adjustPen(yAxis->basePen(), axisColor)); - yAxis->setTickPen(adjustPen(yAxis->tickPen(), axisColor)); - yAxis->setSubTickPen(adjustPen(yAxis->subTickPen(), axisColor)); - - yAxis->setTickLabelColor(m_fortSettings->graphWindowTickLabelColor()); - yAxis->setLabelColor(m_fortSettings->graphWindowLabelColor()); - - yAxis->grid()->setPen(adjustPen(yAxis->grid()->pen(), - m_fortSettings->graphWindowGridColor())); - // Axis Rect auto axisRect = m_plot->axisRect(); axisRect->setMinimumMargins(QMargins(1, 1, 1, 1)); @@ -92,14 +103,12 @@ void GraphWindow::setupUi() // Graph Inbound m_graphIn = new QCPBars(m_plot->xAxis, m_plot->yAxis); m_graphIn->setAntialiased(false); - m_graphIn->setPen(QPen(m_fortSettings->graphWindowColorIn())); m_graphIn->setWidthType(QCPBars::wtAbsolute); m_graphIn->setWidth(1); // Graph Outbound m_graphOut = new QCPBars(m_plot->xAxis, m_plot->yAxis); m_graphOut->setAntialiased(false); - m_graphOut->setPen(QPen(m_fortSettings->graphWindowColorOut())); m_graphOut->setWidthType(QCPBars::wtAbsolute); m_graphOut->setWidth(1); @@ -118,9 +127,10 @@ void GraphWindow::setupUi() void GraphWindow::setupTimer() { - connect(&m_timer, &QTimer::timeout, this, &GraphWindow::addEmptyTraffic); + connect(&m_hoverTimer, &QTimer::timeout, this, &GraphWindow::checkHoverLeave); + connect(&m_updateTimer, &QTimer::timeout, this, &GraphWindow::addEmptyTraffic); - m_timer.start(1000); // 1 second + m_updateTimer.start(1000); // 1 second } void GraphWindow::onMouseDoubleClick(QMouseEvent *event) @@ -161,6 +171,12 @@ void GraphWindow::enterEvent(QEvent *event) { Q_UNUSED(event) + if (m_fortSettings->graphWindowHideOnHover()) { + hide(); + m_hoverTimer.start(200); + return; + } + setWindowOpacityPercent(m_fortSettings->graphWindowHoverOpacity()); } @@ -171,6 +187,16 @@ void GraphWindow::leaveEvent(QEvent *event) setWindowOpacityPercent(m_fortSettings->graphWindowOpacity()); } +void GraphWindow::checkHoverLeave() +{ + const QPoint mousePos = QCursor::pos(); + + if (!geometry().contains(mousePos)) { + m_hoverTimer.stop(); + show(); + } +} + void GraphWindow::addTraffic(qint64 unixTime, quint32 inBytes, quint32 outBytes) { const qint64 rangeLower = unixTime - m_fortSettings->graphWindowMaxSeconds(); diff --git a/src/ui/graph/graphwindow.h b/src/ui/graph/graphwindow.h index f7b689c3..b6f5bc1b 100644 --- a/src/ui/graph/graphwindow.h +++ b/src/ui/graph/graphwindow.h @@ -21,10 +21,13 @@ signals: void mouseRightClick(QMouseEvent *event); public slots: + void updateWindowFlags(); + void updateColors(); + void addTraffic(qint64 unixTime, quint32 inBytes, quint32 outBytes); private slots: - void setupWindow(); + void checkHoverLeave(); void addEmptyTraffic(); @@ -59,7 +62,8 @@ private: QPoint m_mousePressOffset; - QTimer m_timer; + QTimer m_updateTimer; + QTimer m_hoverTimer; }; #endif // GRAPHWINDOW_H diff --git a/src/ui/i18n/i18n_ru.qm b/src/ui/i18n/i18n_ru.qm index 2799a5ef..ee4f68f6 100644 Binary files a/src/ui/i18n/i18n_ru.qm and b/src/ui/i18n/i18n_ru.qm differ diff --git a/src/ui/i18n/i18n_ru.ts b/src/ui/i18n/i18n_ru.ts index 2bc8ecb7..4ea2504b 100644 --- a/src/ui/i18n/i18n_ru.ts +++ b/src/ui/i18n/i18n_ru.ts @@ -50,37 +50,42 @@ FortManager - + Password input Ввод пароля - + Please enter the password Наберите пароль пожалуйста - + Options Опции - + + Traffic Graph + График трафика + + + Filter Enabled Фильтр включен - + Stop Traffic Остановить трафик - + Stop Internet Traffic Остановить Интернет трафик - + Quit Выйти @@ -88,17 +93,17 @@ FortSettings - + Can't write .ini file Не удалось записать .ini файл - + Can't create .conf file Не удалось создать .conf файл - + Can't create backup .conf file Не удалось создать бэкап .conf файла @@ -208,19 +213,19 @@ - + Clear… Очистить… - + Remove Application Удалить приложение - + Clear All Очистить всё @@ -245,7 +250,7 @@ Преобразовать адреса - + Show Blocked Applications Показ блокированных приложений @@ -320,133 +325,133 @@ период, часы - + Block Блокировать - + Allow Разрешить - + Options Опции - + IPv4 Addresses Адреса IPv4 - + Applications Приложения - + Schedule Расписание - + Blocked Блокировано - - + + Statistics Статистика - + Password: Пароль: - + OK OK - + Apply Применить - + Cancel Отмена - + Quit Выйти - + Start with Windows Запускать вместе с Windows - + Block access to network when Fort Firewall is not running Блокировать доступ к сети, когда Fort Firewall не запущен - + Filter Enabled Фильтр включен - + Filter Local Addresses Фильтр локальных адресов - + Stop Traffic Остановить трафик - + Stop Internet Traffic Остановить Интернет трафик - + Hot Keys Горячие клавиши - + Installed Установлен - + Not Installed Не установлен - + Language: Язык: - + Logs Логи - + Profile Профиль - + Releases Релизы @@ -471,12 +476,12 @@ Отключено - + Download speed limit, KiB/s: Ограничение скорости загрузки, KiB/s: - + Upload speed limit, KiB/s: Ограничение скорости выгрузки, KiB/s: @@ -521,32 +526,32 @@ Активный период, часы - + Month starts on: Месяц начинается с: - + Keep days for 'Hourly': Хранить дней для 'Почасовая': - + Keep days for 'Daily': Хранить дней для 'Ежедневная': - + Keep months for 'Monthly': Хранить месяцев для 'Ежемесячная': - + Day's Quota: Квота на день - + Month's Quota: Квота на месяц @@ -581,22 +586,22 @@ Каждый месяц - + Name Наименование - + Interval, hours Интервал, часов - + Last Run Последний запуск - + Last Success Успешный запуск @@ -631,71 +636,71 @@ TiB - + Refresh Обновить - + Units: Единицы: - + Reset Total Сбросить общую - + Collect Traffic Statistics Собирать статистику трафика - + All Все - + Hourly Stat Почасовая - + Daily Stat Ежедневная - + Monthly Stat Ежемесячная - + Total Stat Общая - + Date Дата - + Download Загрузка - + Upload Выгрузка - + Sum Сумма @@ -709,5 +714,80 @@ Allowed Internet Addresses Разрешённые адреса Интернета + + + Graph… + График… + + + + Always on top + Всегда сверху + + + + Frameless + Без рамок + + + + Click through + Сквозной режим + + + + Hide on hover + Скрыть при наведении + + + + Opacity + Прозрачность + + + + Hover opacity + Прозрачность при наведении + + + + Max seconds + Количество секунд + + + + Background color + Цвет фона + + + + Download color + Цвет загрузки + + + + Upload color + Цвет выгрузки + + + + Axis color + Цвет оси + + + + Tick label color + Цвет метки + + + + Label color + Цвет маркировки + + + + Grid color + Цвет сетки + diff --git a/src/ui/qml/controls/LabelColorRow.qml b/src/ui/qml/controls/LabelColorRow.qml new file mode 100644 index 00000000..1c924c57 --- /dev/null +++ b/src/ui/qml/controls/LabelColorRow.qml @@ -0,0 +1,51 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import com.fortfirewall 1.0 + +RowLayout { + + Layout.fillWidth: true + + signal colorEdited() + + readonly property alias label: label + readonly property alias button: button + + property real buttonPreferredWidth: 40 + + property color defaultColor + property color selectedColor: defaultColor + + Label { + id: label + Layout.fillWidth: true + } + + Button { + id: button + Layout.fillWidth: true + Layout.preferredWidth: buttonPreferredWidth + Layout.minimumWidth: buttonPreferredWidth + Layout.maximumWidth: implicitWidth + Layout.preferredHeight: buttonPreferredWidth + + flat: true + background: Rectangle { + implicitWidth: buttonPreferredWidth + implicitHeight: buttonPreferredWidth + border.width: 1 + border.color: button.down ? "gray" : "black" + color: selectedColor + } + + onClicked: { + const color = guiUtil.getColor(selectedColor); + if (!guiUtil.isValidColor(color) || color === selectedColor) + return; + + selectedColor = color; + colorEdited(); + } + } +} diff --git a/src/ui/qml/controls/LabelSpinRow.qml b/src/ui/qml/controls/LabelSpinRow.qml new file mode 100644 index 00000000..8e1da2c9 --- /dev/null +++ b/src/ui/qml/controls/LabelSpinRow.qml @@ -0,0 +1,27 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import com.fortfirewall 1.0 + +RowLayout { + + Layout.fillWidth: true + + readonly property alias label: label + readonly property alias field: field + + property real fieldPreferredWidth: 140 + + Label { + id: label + Layout.fillWidth: true + } + + SpinBoxControl { + id: field + Layout.fillWidth: true + Layout.preferredWidth: fieldPreferredWidth + Layout.minimumWidth: fieldPreferredWidth + Layout.maximumWidth: implicitWidth + } +} diff --git a/src/ui/qml/controls/SpinBoxControl.qml b/src/ui/qml/controls/SpinBoxControl.qml new file mode 100644 index 00000000..eabc613d --- /dev/null +++ b/src/ui/qml/controls/SpinBoxControl.qml @@ -0,0 +1,24 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +SpinBox { + id: field + + editable: true + from: 0 + to: 9999 + + value: defaultValue + + signal valueEdited() + + property int defaultValue + + onValueChanged: { + const value = field.value; + if (value === defaultValue) + return; + + field.valueEdited(); + } +} diff --git a/src/ui/qml/controls/SpinCombo.qml b/src/ui/qml/controls/SpinCombo.qml index 1bfa27d7..672a9a9e 100644 --- a/src/ui/qml/controls/SpinCombo.qml +++ b/src/ui/qml/controls/SpinCombo.qml @@ -12,16 +12,12 @@ RowLayout { property var names: values property var values - SpinBox { + SpinBoxControl { id: field Layout.fillWidth: true Layout.preferredWidth: fieldPreferredWidth Layout.minimumWidth: fieldPreferredWidth - editable: true - from: 0 - to: 9999 - onValueChanged: { combo.updateIndex(value); } diff --git a/src/ui/qml/controls/SpinDouble.qml b/src/ui/qml/controls/SpinDouble.qml index 7b64aa87..3d43c9e3 100644 --- a/src/ui/qml/controls/SpinDouble.qml +++ b/src/ui/qml/controls/SpinDouble.qml @@ -9,25 +9,17 @@ RowLayout { property real fieldPreferredWidth - SpinBox { + SpinBoxControl { id: field1 Layout.fillWidth: true Layout.preferredWidth: fieldPreferredWidth Layout.minimumWidth: fieldPreferredWidth - - editable: true - from: 0 - to: 9999 } - SpinBox { + SpinBoxControl { id: field2 Layout.fillWidth: true Layout.preferredWidth: fieldPreferredWidth Layout.minimumWidth: fieldPreferredWidth - - editable: true - from: 0 - to: 9999 } } diff --git a/src/ui/qml/pages/BasePage.qml b/src/ui/qml/pages/BasePage.qml index 880bc282..784d429c 100644 --- a/src/ui/qml/pages/BasePage.qml +++ b/src/ui/qml/pages/BasePage.qml @@ -6,6 +6,9 @@ Pane { bottomPadding: 0 + function onEditResetted() { + } + function onAboutToSave() { } @@ -14,6 +17,7 @@ Pane { Connections { target: mainPage + onEditResetted: page.onEditResetted() onAboutToSave: page.onAboutToSave() onSaved: page.onSaved() } diff --git a/src/ui/qml/pages/BlockedPage.qml b/src/ui/qml/pages/BlockedPage.qml index 60abec84..fa624231 100644 --- a/src/ui/qml/pages/BlockedPage.qml +++ b/src/ui/qml/pages/BlockedPage.qml @@ -73,9 +73,6 @@ BasePage { && qsTranslate("qml", "Resolve Addresses") checked: firewallConf.resolveAddress onToggled: { - if (firewallConf.resolveAddress === checked) - return; - firewallConf.resolveAddress = checked; fortManager.applyConfImmediateFlags(); diff --git a/src/ui/qml/pages/MainPage.qml b/src/ui/qml/pages/MainPage.qml index 84442617..f1db2e93 100644 --- a/src/ui/qml/pages/MainPage.qml +++ b/src/ui/qml/pages/MainPage.qml @@ -9,12 +9,13 @@ Page { signal opened() signal closed() + signal editResetted() signal aboutToSave() signal saved() property bool confFlagsEdited property bool confEdited - property bool scheduleEdited + property bool othersEdited function setConfFlagsEdited() { confFlagsEdited = true; @@ -24,14 +25,44 @@ Page { confEdited = true; } - function setScheduleEdited() { - scheduleEdited = true; + function setOthersEdited() { + othersEdited = true; } function resetEdited() { confFlagsEdited = false; confEdited = false; - scheduleEdited = false; + othersEdited = false; + + editResetted(); + } + + function save(closeOnSuccess) { + fortSettings.bulkUpdateBegin(); + + mainPage.aboutToSave(); + + var confSaved = true; + if (confFlagsEdited || confEdited) { + const confFlagsOnly = confFlagsEdited && !confEdited; + confSaved = closeOnSuccess + ? fortManager.saveConf(confFlagsOnly) + : fortManager.applyConf(confFlagsOnly); + } + + if (confSaved) { + mainPage.saved(); + } + + fortSettings.bulkUpdateEnd(); + + if (confSaved) { + if (closeOnSuccess) { + closeWindow(); + } else { + resetEdited(); + } + } } onOpened: { @@ -96,38 +127,18 @@ Page { anchors.right: parent.right Button { - enabled: confFlagsEdited || confEdited || scheduleEdited + enabled: confFlagsEdited || confEdited || othersEdited icon.source: "qrc:/images/tick.png" text: translationManager.trTrigger && qsTranslate("qml", "OK") - onClicked: { - mainPage.aboutToSave(); - - if (confFlagsEdited || confEdited) { - if (!fortManager.saveConf(confFlagsEdited && !confEdited)) - return; - } - - mainPage.saved(); - closeWindow(); - } + onClicked: mainPage.save(true) } Button { - enabled: confFlagsEdited || confEdited || scheduleEdited + enabled: confFlagsEdited || confEdited || othersEdited icon.source: "qrc:/images/accept.png" text: translationManager.trTrigger && qsTranslate("qml", "Apply") - onClicked: { - mainPage.aboutToSave(); - - if (confFlagsEdited || confEdited) { - if (!fortManager.applyConf(confFlagsEdited && !confEdited)) - return; - } - - mainPage.saved(); - resetEdited(); - } + onClicked: mainPage.save(false) } Button { icon.source: "qrc:/images/cancel.png" diff --git a/src/ui/qml/pages/OptionsPage.qml b/src/ui/qml/pages/OptionsPage.qml index fd047e45..9adc1eab 100644 --- a/src/ui/qml/pages/OptionsPage.qml +++ b/src/ui/qml/pages/OptionsPage.qml @@ -6,6 +6,18 @@ import com.fortfirewall 1.0 BasePage { + property bool iniEdited + + function setIniEdited() { + iniEdited = true; + + setOthersEdited(); + } + + function onEditResetted() { // override + iniEdited = false; + } + function onAboutToSave() { // override const password = editPassword.text; if (password) { @@ -15,6 +27,8 @@ BasePage { } function onSaved() { // override + if (!iniEdited) return; + fortSettings.startWithWindows = cbStart.checked; fortSettings.hotKeyEnabled = cbHotKeys.checked; } @@ -31,9 +45,7 @@ BasePage { text: translationManager.trTrigger && qsTranslate("qml", "Start with Windows") checked: fortSettings.startWithWindows - onToggled: { - setConfFlagsEdited(); - } + onToggled: setIniEdited() } CheckBox { @@ -104,9 +116,7 @@ BasePage { text: translationManager.trTrigger && qsTranslate("qml", "Hot Keys") checked: fortSettings.hotKeyEnabled - onToggled: { - setConfFlagsEdited(); - } + onToggled: setIniEdited() } Row { diff --git a/src/ui/qml/pages/SchedulePage.qml b/src/ui/qml/pages/SchedulePage.qml index 69bdab0d..68456a17 100644 --- a/src/ui/qml/pages/SchedulePage.qml +++ b/src/ui/qml/pages/SchedulePage.qml @@ -37,6 +37,18 @@ BasePage { qsTranslate("qml", "Monthly") ] + property bool scheduleEdited + + function setScheduleEdited() { + scheduleEdited = true; + + setOthersEdited(); + } + + function onEditResetted() { // override + scheduleEdited = false; + } + function onSaved() { // override if (!scheduleEdited) return; diff --git a/src/ui/qml/pages/StatisticsPage.qml b/src/ui/qml/pages/StatisticsPage.qml index 85c2b10c..10df1a32 100644 --- a/src/ui/qml/pages/StatisticsPage.qml +++ b/src/ui/qml/pages/StatisticsPage.qml @@ -35,6 +35,26 @@ BasePage { qsTranslate("qml", "TiB") ] + property bool graphEdited + + function setGraphEdited() { + graphEdited = true; + + setOthersEdited(); + } + + function onEditResetted() { // override + graphEdited = false; + } + + function onSaved() { // override + if (!graphEdited) return; + + graphButton.save(); + + fortManager.updateGraphWindow(); + } + ColumnLayout { anchors.fill: parent spacing: 10 @@ -78,6 +98,10 @@ BasePage { TrafOptionsButton {} + GraphButton { + id: graphButton + } + Row { spacing: 5 diff --git a/src/ui/qml/pages/apps/AppsColumn.qml b/src/ui/qml/pages/apps/AppsColumn.qml index e15dd3e0..24f7a4b9 100644 --- a/src/ui/qml/pages/apps/AppsColumn.qml +++ b/src/ui/qml/pages/apps/AppsColumn.qml @@ -61,12 +61,8 @@ ColumnLayout { text: translationManager.trTrigger && qsTranslate("qml", "period, hours:") checked: appGroup.periodEnabled - onCheckedChanged: { - const value = checkBox.checked; - if (appGroup.periodEnabled == value) - return; - - appGroup.periodEnabled = value; + onToggled: { + appGroup.periodEnabled = checkBox.checked; setConfEdited(); } @@ -74,13 +70,9 @@ ColumnLayout { field1 { from: 0 to: 24 - value: appGroup.periodFrom - onValueChanged: { - const value = field1.value; - if (appGroup.periodFrom == value) - return; - - appGroup.periodFrom = value; + defaultValue: appGroup.periodFrom + onValueEdited: { + appGroup.periodFrom = field1.value; setConfEdited(); } @@ -88,13 +80,9 @@ ColumnLayout { field2 { from: 0 to: 24 - value: appGroup.periodTo - onValueChanged: { - const value = field2.value; - if (appGroup.periodTo == value) - return; - - appGroup.periodTo = value; + defaultValue: appGroup.periodTo + onValueEdited: { + appGroup.periodTo = field2.value; setConfEdited(); } diff --git a/src/ui/qml/pages/apps/SpeedLimitButton.qml b/src/ui/qml/pages/apps/SpeedLimitButton.qml index db109de0..63efdfba 100644 --- a/src/ui/qml/pages/apps/SpeedLimitButton.qml +++ b/src/ui/qml/pages/apps/SpeedLimitButton.qml @@ -66,12 +66,8 @@ ButtonPopup { text: translationManager.trTrigger && qsTranslate("qml", "Download speed limit, KiB/s:") checked: appGroup.limitInEnabled - onCheckedChanged: { - const value = checkBox.checked; - if (appGroup.limitInEnabled == value) - return; - - appGroup.limitInEnabled = value; + onToggled: { + appGroup.limitInEnabled = checkBox.checked; setConfEdited(); } @@ -79,13 +75,9 @@ ButtonPopup { field { from: 0 to: 99999 - value: appGroup.speedLimitIn - onValueChanged: { - const value = field.value; - if (appGroup.speedLimitIn == value) - return; - - appGroup.speedLimitIn = value; + defaultValue: appGroup.speedLimitIn + onValueEdited: { + appGroup.speedLimitIn = field.value; setConfEdited(); } @@ -99,12 +91,8 @@ ButtonPopup { text: translationManager.trTrigger && qsTranslate("qml", "Upload speed limit, KiB/s:") checked: appGroup.limitOutEnabled - onCheckedChanged: { - const value = checkBox.checked; - if (appGroup.limitOutEnabled == value) - return; - - appGroup.limitOutEnabled = value; + onToggled: { + appGroup.limitOutEnabled = checkBox.checked; setConfEdited(); } @@ -112,13 +100,9 @@ ButtonPopup { field { from: 0 to: 99999 - value: appGroup.speedLimitOut - onValueChanged: { - const value = field.value; - if (appGroup.speedLimitOut == value) - return; - - appGroup.speedLimitOut = value; + defaultValue: appGroup.speedLimitOut + onValueEdited: { + appGroup.speedLimitOut = field.value; setConfEdited(); } diff --git a/src/ui/qml/pages/log/GraphButton.qml b/src/ui/qml/pages/log/GraphButton.qml new file mode 100644 index 00000000..ecdd4106 --- /dev/null +++ b/src/ui/qml/pages/log/GraphButton.qml @@ -0,0 +1,175 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import "../../controls" +import com.fortfirewall 1.0 + +ButtonPopup { + + icon.source: "qrc:/images/chart_bar.png" + text: translationManager.trTrigger + && qsTranslate("qml", "Graph…") + + function save() { + fortSettings.graphWindowAlwaysOnTop = cbAlwaysOnTop.checked; + fortSettings.graphWindowFrameless = cbFrameless.checked; + fortSettings.graphWindowClickThrough = cbClickThrough.checked; + fortSettings.graphWindowHideOnHover = cbHideOnHover.checked; + + fortSettings.graphWindowOpacity = rowOpacity.field.value; + fortSettings.graphWindowHoverOpacity = rowHoverOpacity.field.value; + fortSettings.graphWindowMaxSeconds = rowMaxSeconds.field.value; + + fortSettings.graphWindowColor = rowColor.selectedColor; + fortSettings.graphWindowColorIn = rowColorIn.selectedColor; + fortSettings.graphWindowColorOut = rowColorOut.selectedColor; + fortSettings.graphWindowAxisColor = rowAxisColor.selectedColor; + fortSettings.graphWindowTickLabelColor = rowTickLabelColor.selectedColor; + fortSettings.graphWindowLabelColor = rowLabelColor.selectedColor; + fortSettings.graphWindowGridColor = rowGridColor.selectedColor; + } + + RowLayout { + spacing: 10 + + ColumnLayout { + CheckBox { + id: cbAlwaysOnTop + text: translationManager.trTrigger + && qsTranslate("qml", "Always on top") + checked: fortSettings.graphWindowAlwaysOnTop + onToggled: setGraphEdited() + } + + CheckBox { + id: cbFrameless + text: translationManager.trTrigger + && qsTranslate("qml", "Frameless") + checked: fortSettings.graphWindowFrameless + onToggled: setGraphEdited() + } + + CheckBox { + id: cbClickThrough + text: translationManager.trTrigger + && qsTranslate("qml", "Click through") + checked: fortSettings.graphWindowClickThrough + onToggled: setGraphEdited() + } + + CheckBox { + id: cbHideOnHover + text: translationManager.trTrigger + && qsTranslate("qml", "Hide on hover") + checked: fortSettings.graphWindowHideOnHover + onToggled: setGraphEdited() + } + + HSeparator {} + + LabelSpinRow { + id: rowOpacity + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Opacity")) + ":" + field { + from: 0 + to: 100 + defaultValue: fortSettings.graphWindowOpacity + onValueEdited: setGraphEdited() + } + } + + LabelSpinRow { + id: rowHoverOpacity + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Hover opacity")) + ":" + field { + from: 0 + to: 100 + defaultValue: fortSettings.graphWindowHoverOpacity + onValueEdited: setGraphEdited() + } + } + + LabelSpinRow { + id: rowMaxSeconds + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Max seconds")) + ":" + field { + from: 0 + to: 9999 + defaultValue: fortSettings.graphWindowMaxSeconds + onValueEdited: setGraphEdited() + } + } + + Item { + Layout.fillHeight: true + } + } + + VSeparator {} + + ColumnLayout { + LabelColorRow { + id: rowColor + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Background color")) + ":" + defaultColor: fortSettings.graphWindowColor + onColorEdited: setGraphEdited() + } + + LabelColorRow { + id: rowColorIn + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Download color")) + ":" + defaultColor: fortSettings.graphWindowColorIn + onColorEdited: setGraphEdited() + } + + LabelColorRow { + id: rowColorOut + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Upload color")) + ":" + defaultColor: fortSettings.graphWindowColorOut + onColorEdited: setGraphEdited() + } + + LabelColorRow { + id: rowAxisColor + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Axis color")) + ":" + defaultColor: fortSettings.graphWindowAxisColor + onColorEdited: setGraphEdited() + } + + LabelColorRow { + id: rowTickLabelColor + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Tick label color")) + ":" + defaultColor: fortSettings.graphWindowTickLabelColor + onColorEdited: setGraphEdited() + } + + LabelColorRow { + id: rowLabelColor + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Label color")) + ":" + defaultColor: fortSettings.graphWindowLabelColor + onColorEdited: setGraphEdited() + } + + LabelColorRow { + id: rowGridColor + label.text: (translationManager.trTrigger + && qsTranslate("qml", "Grid color")) + ":" + defaultColor: fortSettings.graphWindowGridColor + onColorEdited: setGraphEdited() + } + + Item { + Layout.fillHeight: true + } + } + } +} diff --git a/src/ui/qml/pages/log/TrafOptionsButton.qml b/src/ui/qml/pages/log/TrafOptionsButton.qml index ee9e5097..37e7f758 100644 --- a/src/ui/qml/pages/log/TrafOptionsButton.qml +++ b/src/ui/qml/pages/log/TrafOptionsButton.qml @@ -67,12 +67,8 @@ ButtonPopup { text: translationManager.trTrigger && qsTranslate("qml", "Active period, hours:") checked: firewallConf.activePeriodEnabled - onCheckedChanged: { - const value = checkBox.checked; - if (firewallConf.activePeriodEnabled == value) - return; - - firewallConf.activePeriodEnabled = value; + onToggled: { + firewallConf.activePeriodEnabled = checkBox.checked; setConfFlagsEdited(); } @@ -80,13 +76,9 @@ ButtonPopup { field1 { from: 0 to: 24 - value: firewallConf.activePeriodFrom - onValueChanged: { - const value = field1.value; - if (firewallConf.activePeriodFrom == value) - return; - - firewallConf.activePeriodFrom = value; + defaultValue: firewallConf.activePeriodFrom + onValueEdited: { + firewallConf.activePeriodFrom = field1.value; setConfFlagsEdited(); } @@ -94,13 +86,9 @@ ButtonPopup { field2 { from: 0 to: 24 - value: firewallConf.activePeriodTo - onValueChanged: { - const value = field2.value; - if (firewallConf.activePeriodTo == value) - return; - - firewallConf.activePeriodTo = value; + defaultValue: firewallConf.activePeriodTo + onValueEdited: { + firewallConf.activePeriodTo = field2.value; setConfFlagsEdited(); } @@ -123,13 +111,9 @@ ButtonPopup { field { from: 1 to: 31 - value: firewallConf.monthStart - onValueChanged: { - const value = field.value; - if (firewallConf.monthStart == value) - return; - - firewallConf.monthStart = value; + defaultValue: firewallConf.monthStart + onValueEdited: { + firewallConf.monthStart = field.value; setConfFlagsEdited(); } @@ -148,13 +132,9 @@ ButtonPopup { } field { from: -1 - value: firewallConf.trafHourKeepDays - onValueChanged: { - const value = field.value; - if (firewallConf.trafHourKeepDays == value) - return; - - firewallConf.trafHourKeepDays = value; + defaultValue: firewallConf.trafHourKeepDays + onValueEdited: { + firewallConf.trafHourKeepDays = field.value; setConfFlagsEdited(); } @@ -171,13 +151,9 @@ ButtonPopup { } field { from: -1 - value: firewallConf.trafDayKeepDays - onValueChanged: { - const value = field.value; - if (firewallConf.trafDayKeepDays == value) - return; - - firewallConf.trafDayKeepDays = value; + defaultValue: firewallConf.trafDayKeepDays + onValueEdited: { + firewallConf.trafDayKeepDays = field.value; setConfFlagsEdited(); } @@ -194,13 +170,9 @@ ButtonPopup { } field { from: -1 - value: firewallConf.trafMonthKeepMonths - onValueChanged: { - const value = field.value; - if (firewallConf.trafMonthKeepMonths == value) - return; - - firewallConf.trafMonthKeepMonths = value; + defaultValue: firewallConf.trafMonthKeepMonths + onValueEdited: { + firewallConf.trafMonthKeepMonths = field.value; setConfFlagsEdited(); } @@ -220,13 +192,9 @@ ButtonPopup { field { from: 0 to: 999 * 1024 - value: firewallConf.quotaDayMb - onValueChanged: { - const value = field.value; - if (firewallConf.quotaDayMb == value) - return; - - firewallConf.quotaDayMb = value; + defaultValue: firewallConf.quotaDayMb + onValueEdited: { + firewallConf.quotaDayMb = field.value; setConfFlagsEdited(); } @@ -244,13 +212,9 @@ ButtonPopup { field { from: 0 to: 999 * 1024 - value: firewallConf.quotaMonthMb - onValueChanged: { - const value = field.value; - if (firewallConf.quotaMonthMb == value) - return; - - firewallConf.quotaMonthMb = value; + defaultValue: firewallConf.quotaMonthMb + onValueEdited: { + firewallConf.quotaMonthMb = field.value; setConfFlagsEdited(); } diff --git a/src/ui/qml/pages/schedule/TaskRow.qml b/src/ui/qml/pages/schedule/TaskRow.qml index 64b90a9b..c3870d1f 100644 --- a/src/ui/qml/pages/schedule/TaskRow.qml +++ b/src/ui/qml/pages/schedule/TaskRow.qml @@ -51,14 +51,8 @@ Row { field { from: 1 to: 24 * 30 * 12 // ~Year - value: taskInfo.intervalHours - - onValueChanged: { - const value = field.value; - if (value != taskInfo.intervalHours) { - setScheduleEdited(); - } - } + defaultValue: taskInfo.intervalHours + onValueEdited: setScheduleEdited() } } diff --git a/src/ui/util/guiutil.cpp b/src/ui/util/guiutil.cpp index a6c728f1..f1f88fe0 100644 --- a/src/ui/util/guiutil.cpp +++ b/src/ui/util/guiutil.cpp @@ -1,6 +1,7 @@ #include "guiutil.h" #include +#include #include #include #include @@ -25,3 +26,13 @@ void GuiUtil::setClipboardData(const QVariant &data) clipboard->setText(data.toString()); } } + +QColor GuiUtil::getColor(const QColor &initial) +{ + return QColorDialog::getColor(initial); +} + +bool GuiUtil::isValidColor(const QColor &color) +{ + return color.isValid(); +} diff --git a/src/ui/util/guiutil.h b/src/ui/util/guiutil.h index c976602a..73dceda3 100644 --- a/src/ui/util/guiutil.h +++ b/src/ui/util/guiutil.h @@ -1,6 +1,7 @@ #ifndef GUIUTIL_H #define GUIUTIL_H +#include #include #include @@ -12,6 +13,9 @@ public: explicit GuiUtil(QObject *parent = nullptr); Q_INVOKABLE static void setClipboardData(const QVariant &data); + + Q_INVOKABLE static QColor getColor(const QColor &initial = Qt::white); + Q_INVOKABLE static bool isValidColor(const QColor &color); }; #endif // GUIUTIL_H