UI: Improve Traffic Graph.

This commit is contained in:
Nodir Temirkhodjaev 2019-02-10 20:55:23 +05:00
parent 02db06fa90
commit ffad054bad
27 changed files with 725 additions and 291 deletions

View File

@ -3,10 +3,13 @@
<file>qml/controls/ButtonMenu.qml</file> <file>qml/controls/ButtonMenu.qml</file>
<file>qml/controls/ButtonPopup.qml</file> <file>qml/controls/ButtonPopup.qml</file>
<file>qml/controls/HSeparator.qml</file> <file>qml/controls/HSeparator.qml</file>
<file>qml/controls/LabelColorRow.qml</file>
<file>qml/controls/LabelSpinRow.qml</file>
<file>qml/controls/LinkButton.qml</file> <file>qml/controls/LinkButton.qml</file>
<file>qml/controls/ListViewControl.qml</file> <file>qml/controls/ListViewControl.qml</file>
<file>qml/controls/RoundButtonTip.qml</file> <file>qml/controls/RoundButtonTip.qml</file>
<file>qml/controls/ScrollBarControl.qml</file> <file>qml/controls/ScrollBarControl.qml</file>
<file>qml/controls/SpinBoxControl.qml</file>
<file>qml/controls/SpinCombo.qml</file> <file>qml/controls/SpinCombo.qml</file>
<file>qml/controls/SpinComboRow.qml</file> <file>qml/controls/SpinComboRow.qml</file>
<file>qml/controls/SpinDouble.qml</file> <file>qml/controls/SpinDouble.qml</file>
@ -31,6 +34,7 @@
<file>qml/pages/apps/AppsTextColumn.qml</file> <file>qml/pages/apps/AppsTextColumn.qml</file>
<file>qml/pages/apps/SpeedLimitButton.qml</file> <file>qml/pages/apps/SpeedLimitButton.qml</file>
<file>qml/pages/log/AppListView.qml</file> <file>qml/pages/log/AppListView.qml</file>
<file>qml/pages/log/GraphButton.qml</file>
<file>qml/pages/log/IpListView.qml</file> <file>qml/pages/log/IpListView.qml</file>
<file>qml/pages/log/TrafOptionsButton.qml</file> <file>qml/pages/log/TrafOptionsButton.qml</file>
<file>qml/pages/schedule/TaskRow.qml</file> <file>qml/pages/schedule/TaskRow.qml</file>

View File

@ -41,7 +41,6 @@
FortManager::FortManager(FortSettings *fortSettings, FortManager::FortManager(FortSettings *fortSettings,
QObject *parent) : QObject *parent) :
QObject(parent), QObject(parent),
m_exiting(false),
m_trayIcon(new QSystemTrayIcon(this)), m_trayIcon(new QSystemTrayIcon(this)),
m_engine(nullptr), m_engine(nullptr),
m_appWindow(nullptr), m_appWindow(nullptr),
@ -221,8 +220,7 @@ void FortManager::launch()
{ {
showTrayIcon(); showTrayIcon();
if (m_fortSettings->graphWindowEnabled() if (m_fortSettings->graphWindowVisible()) {
&& m_fortSettings->graphWindowVisible()) {
showGraphWindow(); showGraphWindow();
} }
} }
@ -285,8 +283,9 @@ void FortManager::showGraphWindow()
m_graphWindowState->install(m_graphWindow); m_graphWindowState->install(m_graphWindow);
connect(m_graphWindow, &GraphWindow::aboutToClose, connect(m_graphWindow, &GraphWindow::aboutToClose, [this] {
this, &FortManager::closeGraphWindow); closeGraphWindow();
});
connect(m_graphWindow, &GraphWindow::mouseRightClick, connect(m_graphWindow, &GraphWindow::mouseRightClick,
this, &FortManager::showTrayMenu); this, &FortManager::showTrayMenu);
@ -302,12 +301,12 @@ void FortManager::showGraphWindow()
restoreGraphWindowState(); restoreGraphWindowState();
} }
void FortManager::closeGraphWindow() void FortManager::closeGraphWindow(bool storeVisibility)
{ {
if (!m_graphWindow) if (!m_graphWindow)
return; return;
saveGraphWindowState(); saveGraphWindowState(storeVisibility);
m_graphWindowState->uninstall(m_graphWindow); m_graphWindowState->uninstall(m_graphWindow);
@ -327,11 +326,18 @@ void FortManager::switchGraphWindow()
closeGraphWindow(); closeGraphWindow();
} }
void FortManager::updateGraphWindow()
{
if (!m_graphWindow)
return;
m_graphWindow->updateColors();
m_graphWindow->updateWindowFlags();
}
void FortManager::exit(int retcode) void FortManager::exit(int retcode)
{ {
m_exiting = true; closeGraphWindow(true);
closeGraphWindow();
closeWindow(); closeWindow();
closeEngine(); closeEngine();
@ -538,9 +544,9 @@ void FortManager::restoreWindowState()
m_fortSettings->windowMaximized()); 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->setGraphWindowGeometry(m_graphWindowState->geometry());
m_fortSettings->setGraphWindowMaximized(m_graphWindowState->maximized()); m_fortSettings->setGraphWindowMaximized(m_graphWindowState->maximized());
} }
@ -577,16 +583,14 @@ void FortManager::updateTrayMenu()
this, SLOT(showWindow())); this, SLOT(showWindow()));
addHotKey(optionsAction, fortSettings()->hotKeyOptions(), hotKeyEnabled); addHotKey(optionsAction, fortSettings()->hotKeyOptions(), hotKeyEnabled);
if (!conf.hasPassword() && !m_firewallConfToEdit) { m_graphWindowAction = addAction(
if (fortSettings()->graphWindowEnabled()) { menu, QIcon(":/images/chart_bar.png"), tr("Traffic Graph"),
m_graphWindowAction = addAction( this, SLOT(switchGraphWindow()), true,
menu, QIcon(":/images/chart_bar.png"), tr("Traffic Graph"), (m_graphWindow != nullptr));
this, SLOT(switchGraphWindow()), true, addHotKey(m_graphWindowAction, fortSettings()->hotKeyGraph(),
(m_graphWindow != nullptr)); conf.logStat());
addHotKey(m_graphWindowAction, fortSettings()->hotKeyGraph(),
conf.logStat());
}
if (!conf.hasPassword() && !m_firewallConfToEdit) {
menu->addSeparator(); menu->addSeparator();
m_filterEnabledAction = addAction( m_filterEnabledAction = addAction(

View File

@ -57,8 +57,9 @@ public slots:
void closeWindow(); void closeWindow();
void showGraphWindow(); void showGraphWindow();
void closeGraphWindow(); void closeGraphWindow(bool storeVisibility = false);
void switchGraphWindow(); void switchGraphWindow();
void updateGraphWindow();
void exit(int retcode = 0); void exit(int retcode = 0);
@ -115,7 +116,7 @@ private:
void saveWindowState(); void saveWindowState();
void restoreWindowState(); void restoreWindowState();
void saveGraphWindowState(); void saveGraphWindowState(bool visible);
void restoreGraphWindowState(); void restoreGraphWindowState();
void updateLogger(); void updateLogger();
@ -133,8 +134,6 @@ private:
const QObject *receiver = nullptr, const char *member = nullptr); const QObject *receiver = nullptr, const char *member = nullptr);
private: private:
uint m_exiting : 1;
MainWindow m_window; // dummy window for tray icon MainWindow m_window; // dummy window for tray icon
QSystemTrayIcon *m_trayIcon; QSystemTrayIcon *m_trayIcon;

View File

@ -12,7 +12,9 @@
FortSettings::FortSettings(const QStringList &args, FortSettings::FortSettings(const QStringList &args,
QObject *parent) : QObject *parent) :
QObject(parent), QObject(parent),
m_hasProvBoot(false) m_hasProvBoot(false),
m_bulkUpdating(false),
m_bulkUpdatingEmit(false)
{ {
processArguments(args); processArguments(args);
setupIni(); setupIni();
@ -396,12 +398,39 @@ QVariant FortSettings::iniValue(const QString &key,
void FortSettings::setIniValue(const QString &key, const QVariant &value, void FortSettings::setIniValue(const QString &key, const QVariant &value,
const QVariant &defaultValue) const QVariant &defaultValue)
{ {
if (!defaultValue.isNull() const QVariant oldValue = m_ini->value(key, defaultValue);
&& m_ini->value(key, defaultValue) == value) if (oldValue == value)
return; return;
m_ini->setValue(key, value); 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) void FortSettings::removeIniKey(const QString &key)

View File

@ -18,6 +18,21 @@ class FortSettings : public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool debug READ debug WRITE setDebug NOTIFY iniChanged) Q_PROPERTY(bool debug READ debug WRITE setDebug NOTIFY iniChanged)
Q_PROPERTY(QString language READ language WRITE setLanguage 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 startWithWindows READ startWithWindows WRITE setStartWithWindows NOTIFY startWithWindowsChanged)
Q_PROPERTY(bool hotKeyEnabled READ hotKeyEnabled WRITE setHotKeyEnabled NOTIFY iniChanged) Q_PROPERTY(bool hotKeyEnabled READ hotKeyEnabled WRITE setHotKeyEnabled NOTIFY iniChanged)
Q_PROPERTY(QString logsPath READ logsPath CONSTANT) Q_PROPERTY(QString logsPath READ logsPath CONSTANT)
@ -50,9 +65,6 @@ public:
bool windowMaximized() const { return iniBool("window/maximized"); } bool windowMaximized() const { return iniBool("window/maximized"); }
void setWindowMaximized(bool on) { setIniValue("window/maximized", on); } 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"); } bool graphWindowVisible() const { return iniBool("graphWindow/visible"); }
void setGraphWindowVisible(bool on) { setIniValue("graphWindow/visible", on); } void setGraphWindowVisible(bool on) { setIniValue("graphWindow/visible", on); }
@ -71,6 +83,9 @@ public:
bool graphWindowClickThrough() const { return iniBool("graphWindow/clickThrough"); } bool graphWindowClickThrough() const { return iniBool("graphWindow/clickThrough"); }
void setGraphWindowClickThrough(bool on) { setIniValue("graphWindow/clickThrough", on); } 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); } int graphWindowOpacity() const { return iniInt("graphWindow/opacity", 10); }
void setGraphWindowOpacity(int v) { setIniValue("graphWindow/opacity", v); } void setGraphWindowOpacity(int v) { setIniValue("graphWindow/opacity", v); }
@ -151,6 +166,9 @@ public slots:
bool readConfIni(FirewallConf &conf) const; bool readConfIni(FirewallConf &conf) const;
bool writeConfIni(const FirewallConf &conf); bool writeConfIni(const FirewallConf &conf);
void bulkUpdateBegin();
void bulkUpdateEnd();
private: private:
void processArguments(const QStringList &args); void processArguments(const QStringList &args);
void setupIni(); void setupIni();
@ -190,7 +208,9 @@ private:
static QString startupShortcutPath(); static QString startupShortcutPath();
private: private:
uint m_hasProvBoot : 1; uint m_hasProvBoot : 1;
uint m_bulkUpdating : 1;
uint m_bulkUpdatingEmit : 1;
QString m_profilePath; QString m_profilePath;
QString m_statPath; QString m_statPath;

View File

@ -17,14 +17,13 @@ GraphWindow::GraphWindow(FortSettings *fortSettings,
setupUi(); setupUi();
setupTimer(); setupTimer();
setupWindow(); updateWindowFlags();
updateColors();
connect(m_fortSettings, &FortSettings::iniChanged, this, &GraphWindow::setupWindow);
setMinimumSize(QSize(100, 50)); setMinimumSize(QSize(100, 50));
} }
void GraphWindow::setupWindow() void GraphWindow::updateWindowFlags()
{ {
const bool visible = isVisible(); const bool visible = isVisible();
@ -38,13 +37,36 @@ void GraphWindow::setupWindow()
? Qt::WindowTransparentForInput : Qt::Widget) ? Qt::WindowTransparentForInput : Qt::Widget)
); );
if (visible) {
show(); // setWindowFlags() hides the window
}
}
void GraphWindow::updateColors()
{
setWindowOpacityPercent(m_fortSettings->graphWindowOpacity()); setWindowOpacityPercent(m_fortSettings->graphWindowOpacity());
m_plot->setBackground(QBrush(m_fortSettings->graphWindowColor())); m_plot->setBackground(QBrush(m_fortSettings->graphWindowColor()));
if (visible) { // Axis
show(); // setWindowFlags() hides the window 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() void GraphWindow::setupUi()
@ -70,17 +92,6 @@ void GraphWindow::setupUi()
yAxis->setPadding(1); yAxis->setPadding(1);
yAxis->setTickLabelPadding(2); 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 // Axis Rect
auto axisRect = m_plot->axisRect(); auto axisRect = m_plot->axisRect();
axisRect->setMinimumMargins(QMargins(1, 1, 1, 1)); axisRect->setMinimumMargins(QMargins(1, 1, 1, 1));
@ -92,14 +103,12 @@ void GraphWindow::setupUi()
// Graph Inbound // Graph Inbound
m_graphIn = new QCPBars(m_plot->xAxis, m_plot->yAxis); m_graphIn = new QCPBars(m_plot->xAxis, m_plot->yAxis);
m_graphIn->setAntialiased(false); m_graphIn->setAntialiased(false);
m_graphIn->setPen(QPen(m_fortSettings->graphWindowColorIn()));
m_graphIn->setWidthType(QCPBars::wtAbsolute); m_graphIn->setWidthType(QCPBars::wtAbsolute);
m_graphIn->setWidth(1); m_graphIn->setWidth(1);
// Graph Outbound // Graph Outbound
m_graphOut = new QCPBars(m_plot->xAxis, m_plot->yAxis); m_graphOut = new QCPBars(m_plot->xAxis, m_plot->yAxis);
m_graphOut->setAntialiased(false); m_graphOut->setAntialiased(false);
m_graphOut->setPen(QPen(m_fortSettings->graphWindowColorOut()));
m_graphOut->setWidthType(QCPBars::wtAbsolute); m_graphOut->setWidthType(QCPBars::wtAbsolute);
m_graphOut->setWidth(1); m_graphOut->setWidth(1);
@ -118,9 +127,10 @@ void GraphWindow::setupUi()
void GraphWindow::setupTimer() 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) void GraphWindow::onMouseDoubleClick(QMouseEvent *event)
@ -161,6 +171,12 @@ void GraphWindow::enterEvent(QEvent *event)
{ {
Q_UNUSED(event) Q_UNUSED(event)
if (m_fortSettings->graphWindowHideOnHover()) {
hide();
m_hoverTimer.start(200);
return;
}
setWindowOpacityPercent(m_fortSettings->graphWindowHoverOpacity()); setWindowOpacityPercent(m_fortSettings->graphWindowHoverOpacity());
} }
@ -171,6 +187,16 @@ void GraphWindow::leaveEvent(QEvent *event)
setWindowOpacityPercent(m_fortSettings->graphWindowOpacity()); 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) void GraphWindow::addTraffic(qint64 unixTime, quint32 inBytes, quint32 outBytes)
{ {
const qint64 rangeLower = unixTime - m_fortSettings->graphWindowMaxSeconds(); const qint64 rangeLower = unixTime - m_fortSettings->graphWindowMaxSeconds();

View File

@ -21,10 +21,13 @@ signals:
void mouseRightClick(QMouseEvent *event); void mouseRightClick(QMouseEvent *event);
public slots: public slots:
void updateWindowFlags();
void updateColors();
void addTraffic(qint64 unixTime, quint32 inBytes, quint32 outBytes); void addTraffic(qint64 unixTime, quint32 inBytes, quint32 outBytes);
private slots: private slots:
void setupWindow(); void checkHoverLeave();
void addEmptyTraffic(); void addEmptyTraffic();
@ -59,7 +62,8 @@ private:
QPoint m_mousePressOffset; QPoint m_mousePressOffset;
QTimer m_timer; QTimer m_updateTimer;
QTimer m_hoverTimer;
}; };
#endif // GRAPHWINDOW_H #endif // GRAPHWINDOW_H

Binary file not shown.

View File

@ -50,37 +50,42 @@
<context> <context>
<name>FortManager</name> <name>FortManager</name>
<message> <message>
<location filename="../fortmanager.cpp" line="317"/> <location filename="../fortmanager.cpp" line="355"/>
<source>Password input</source> <source>Password input</source>
<translation>Ввод пароля</translation> <translation>Ввод пароля</translation>
</message> </message>
<message> <message>
<location filename="../fortmanager.cpp" line="317"/> <location filename="../fortmanager.cpp" line="355"/>
<source>Please enter the password</source> <source>Please enter the password</source>
<translation>Наберите пароль пожалуйста</translation> <translation>Наберите пароль пожалуйста</translation>
</message> </message>
<message> <message>
<location filename="../fortmanager.cpp" line="561"/> <location filename="../fortmanager.cpp" line="582"/>
<source>Options</source> <source>Options</source>
<translation>Опции</translation> <translation>Опции</translation>
</message> </message>
<message> <message>
<location filename="../fortmanager.cpp" line="574"/> <location filename="../fortmanager.cpp" line="587"/>
<source>Traffic Graph</source>
<translation>График трафика</translation>
</message>
<message>
<location filename="../fortmanager.cpp" line="597"/>
<source>Filter Enabled</source> <source>Filter Enabled</source>
<translation>Фильтр включен</translation> <translation>Фильтр включен</translation>
</message> </message>
<message> <message>
<location filename="../fortmanager.cpp" line="585"/> <location filename="../fortmanager.cpp" line="604"/>
<source>Stop Traffic</source> <source>Stop Traffic</source>
<translation>Остановить трафик</translation> <translation>Остановить трафик</translation>
</message> </message>
<message> <message>
<location filename="../fortmanager.cpp" line="592"/> <location filename="../fortmanager.cpp" line="611"/>
<source>Stop Internet Traffic</source> <source>Stop Internet Traffic</source>
<translation>Остановить Интернет трафик</translation> <translation>Остановить Интернет трафик</translation>
</message> </message>
<message> <message>
<location filename="../fortmanager.cpp" line="620"/> <location filename="../fortmanager.cpp" line="639"/>
<source>Quit</source> <source>Quit</source>
<translation>Выйти</translation> <translation>Выйти</translation>
</message> </message>
@ -88,17 +93,17 @@
<context> <context>
<name>FortSettings</name> <name>FortSettings</name>
<message> <message>
<location filename="../fortsettings.cpp" line="202"/> <location filename="../fortsettings.cpp" line="204"/>
<source>Can&apos;t write .ini file</source> <source>Can&apos;t write .ini file</source>
<translation>Не удалось записать .ini файл</translation> <translation>Не удалось записать .ini файл</translation>
</message> </message>
<message> <message>
<location filename="../fortsettings.cpp" line="217"/> <location filename="../fortsettings.cpp" line="219"/>
<source>Can&apos;t create .conf file</source> <source>Can&apos;t create .conf file</source>
<translation>Не удалось создать .conf файл</translation> <translation>Не удалось создать .conf файл</translation>
</message> </message>
<message> <message>
<location filename="../fortsettings.cpp" line="212"/> <location filename="../fortsettings.cpp" line="214"/>
<source>Can&apos;t create backup .conf file</source> <source>Can&apos;t create backup .conf file</source>
<translation>Не удалось создать бэкап .conf файла</translation> <translation>Не удалось создать бэкап .conf файла</translation>
</message> </message>
@ -208,19 +213,19 @@
</message> </message>
<message> <message>
<location filename="../qml/pages/BlockedPage.qml" line="32"/> <location filename="../qml/pages/BlockedPage.qml" line="32"/>
<location filename="../qml/pages/StatisticsPage.qml" line="55"/> <location filename="../qml/pages/StatisticsPage.qml" line="75"/>
<source>Clear</source> <source>Clear</source>
<translation>Очистить</translation> <translation>Очистить</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/BlockedPage.qml" line="37"/> <location filename="../qml/pages/BlockedPage.qml" line="37"/>
<location filename="../qml/pages/StatisticsPage.qml" line="60"/> <location filename="../qml/pages/StatisticsPage.qml" line="80"/>
<source>Remove Application</source> <source>Remove Application</source>
<translation>Удалить приложение</translation> <translation>Удалить приложение</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/BlockedPage.qml" line="43"/> <location filename="../qml/pages/BlockedPage.qml" line="43"/>
<location filename="../qml/pages/StatisticsPage.qml" line="71"/> <location filename="../qml/pages/StatisticsPage.qml" line="91"/>
<source>Clear All</source> <source>Clear All</source>
<translation>Очистить всё</translation> <translation>Очистить всё</translation>
</message> </message>
@ -245,7 +250,7 @@
<translation>Преобразовать адреса</translation> <translation>Преобразовать адреса</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/BlockedPage.qml" line="94"/> <location filename="../qml/pages/BlockedPage.qml" line="91"/>
<source>Show Blocked Applications</source> <source>Show Blocked Applications</source>
<translation>Показ блокированных приложений</translation> <translation>Показ блокированных приложений</translation>
</message> </message>
@ -320,133 +325,133 @@
<translation>период, часы</translation> <translation>период, часы</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/apps/AppsColumn.qml" line="113"/> <location filename="../qml/pages/apps/AppsColumn.qml" line="101"/>
<source>Block</source> <source>Block</source>
<translation>Блокировать</translation> <translation>Блокировать</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/apps/AppsColumn.qml" line="133"/> <location filename="../qml/pages/apps/AppsColumn.qml" line="121"/>
<source>Allow</source> <source>Allow</source>
<translation>Разрешить</translation> <translation>Разрешить</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="50"/> <location filename="../qml/pages/MainPage.qml" line="81"/>
<source>Options</source> <source>Options</source>
<translation>Опции</translation> <translation>Опции</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="55"/> <location filename="../qml/pages/MainPage.qml" line="86"/>
<source>IPv4 Addresses</source> <source>IPv4 Addresses</source>
<translation>Адреса IPv4</translation> <translation>Адреса IPv4</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="60"/> <location filename="../qml/pages/MainPage.qml" line="91"/>
<source>Applications</source> <source>Applications</source>
<translation>Приложения</translation> <translation>Приложения</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="75"/> <location filename="../qml/pages/MainPage.qml" line="106"/>
<source>Schedule</source> <source>Schedule</source>
<translation>Расписание</translation> <translation>Расписание</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="65"/> <location filename="../qml/pages/MainPage.qml" line="96"/>
<source>Blocked</source> <source>Blocked</source>
<translation>Блокировано</translation> <translation>Блокировано</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="70"/> <location filename="../qml/pages/MainPage.qml" line="101"/>
<location filename="../qml/pages/OptionsPage.qml" line="184"/> <location filename="../qml/pages/OptionsPage.qml" line="198"/>
<source>Statistics</source> <source>Statistics</source>
<translation>Статистика</translation> <translation>Статистика</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="114"/> <location filename="../qml/pages/OptionsPage.qml" line="128"/>
<source>Password:</source> <source>Password:</source>
<translation>Пароль:</translation> <translation>Пароль:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="102"/> <location filename="../qml/pages/MainPage.qml" line="133"/>
<source>OK</source> <source>OK</source>
<translation>OK</translation> <translation>OK</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="119"/> <location filename="../qml/pages/MainPage.qml" line="140"/>
<source>Apply</source> <source>Apply</source>
<translation>Применить</translation> <translation>Применить</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="135"/> <location filename="../qml/pages/MainPage.qml" line="146"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отмена</translation> <translation>Отмена</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/MainPage.qml" line="141"/> <location filename="../qml/pages/MainPage.qml" line="152"/>
<source>Quit</source> <source>Quit</source>
<translation>Выйти</translation> <translation>Выйти</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="32"/> <location filename="../qml/pages/OptionsPage.qml" line="46"/>
<source>Start with Windows</source> <source>Start with Windows</source>
<translation>Запускать вместе с Windows</translation> <translation>Запускать вместе с Windows</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="41"/> <location filename="../qml/pages/OptionsPage.qml" line="53"/>
<source>Block access to network when Fort Firewall is not running</source> <source>Block access to network when Fort Firewall is not running</source>
<translation>Блокировать доступ к сети, когда Fort Firewall не запущен</translation> <translation>Блокировать доступ к сети, когда Fort Firewall не запущен</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="52"/> <location filename="../qml/pages/OptionsPage.qml" line="67"/>
<source>Filter Enabled</source> <source>Filter Enabled</source>
<translation>Фильтр включен</translation> <translation>Фильтр включен</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="63"/> <location filename="../qml/pages/OptionsPage.qml" line="78"/>
<source>Filter Local Addresses</source> <source>Filter Local Addresses</source>
<translation>Фильтр локальных адресов</translation> <translation>Фильтр локальных адресов</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="77"/> <location filename="../qml/pages/OptionsPage.qml" line="93"/>
<source>Stop Traffic</source> <source>Stop Traffic</source>
<translation>Остановить трафик</translation> <translation>Остановить трафик</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="88"/> <location filename="../qml/pages/OptionsPage.qml" line="104"/>
<source>Stop Internet Traffic</source> <source>Stop Internet Traffic</source>
<translation>Остановить Интернет трафик</translation> <translation>Остановить Интернет трафик</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="101"/> <location filename="../qml/pages/OptionsPage.qml" line="117"/>
<source>Hot Keys</source> <source>Hot Keys</source>
<translation>Горячие клавиши</translation> <translation>Горячие клавиши</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="135"/> <location filename="../qml/pages/OptionsPage.qml" line="149"/>
<source>Installed</source> <source>Installed</source>
<translation>Установлен</translation> <translation>Установлен</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="136"/> <location filename="../qml/pages/OptionsPage.qml" line="150"/>
<source>Not Installed</source> <source>Not Installed</source>
<translation>Не установлен</translation> <translation>Не установлен</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="146"/> <location filename="../qml/pages/OptionsPage.qml" line="160"/>
<source>Language:</source> <source>Language:</source>
<translation>Язык:</translation> <translation>Язык:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="164"/> <location filename="../qml/pages/OptionsPage.qml" line="178"/>
<source>Logs</source> <source>Logs</source>
<translation>Логи</translation> <translation>Логи</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="174"/> <location filename="../qml/pages/OptionsPage.qml" line="188"/>
<source>Profile</source> <source>Profile</source>
<translation>Профиль</translation> <translation>Профиль</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/OptionsPage.qml" line="194"/> <location filename="../qml/pages/OptionsPage.qml" line="208"/>
<source>Releases</source> <source>Releases</source>
<translation>Релизы</translation> <translation>Релизы</translation>
</message> </message>
@ -471,12 +476,12 @@
<translation>Отключено</translation> <translation>Отключено</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/apps/SpeedLimitButton.qml" line="68"/> <location filename="../qml/pages/apps/SpeedLimitButton.qml" line="67"/>
<source>Download speed limit, KiB/s:</source> <source>Download speed limit, KiB/s:</source>
<translation>Ограничение скорости загрузки, KiB/s:</translation> <translation>Ограничение скорости загрузки, KiB/s:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/apps/SpeedLimitButton.qml" line="101"/> <location filename="../qml/pages/apps/SpeedLimitButton.qml" line="92"/>
<source>Upload speed limit, KiB/s:</source> <source>Upload speed limit, KiB/s:</source>
<translation>Ограничение скорости выгрузки, KiB/s:</translation> <translation>Ограничение скорости выгрузки, KiB/s:</translation>
</message> </message>
@ -521,32 +526,32 @@
<translation>Активный период, часы</translation> <translation>Активный период, часы</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/log/TrafOptionsButton.qml" line="121"/> <location filename="../qml/pages/log/TrafOptionsButton.qml" line="109"/>
<source>Month starts on:</source> <source>Month starts on:</source>
<translation>Месяц начинается с:</translation> <translation>Месяц начинается с:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/log/TrafOptionsButton.qml" line="147"/> <location filename="../qml/pages/log/TrafOptionsButton.qml" line="131"/>
<source>Keep days for &apos;Hourly&apos;:</source> <source>Keep days for &apos;Hourly&apos;:</source>
<translation>Хранить дней для &apos;Почасовая&apos;:</translation> <translation>Хранить дней для &apos;Почасовая&apos;:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/log/TrafOptionsButton.qml" line="170"/> <location filename="../qml/pages/log/TrafOptionsButton.qml" line="150"/>
<source>Keep days for &apos;Daily&apos;:</source> <source>Keep days for &apos;Daily&apos;:</source>
<translation>Хранить дней для &apos;Ежедневная&apos;:</translation> <translation>Хранить дней для &apos;Ежедневная&apos;:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/log/TrafOptionsButton.qml" line="193"/> <location filename="../qml/pages/log/TrafOptionsButton.qml" line="169"/>
<source>Keep months for &apos;Monthly&apos;:</source> <source>Keep months for &apos;Monthly&apos;:</source>
<translation>Хранить месяцев для &apos;Ежемесячная&apos;:</translation> <translation>Хранить месяцев для &apos;Ежемесячная&apos;:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/log/TrafOptionsButton.qml" line="218"/> <location filename="../qml/pages/log/TrafOptionsButton.qml" line="190"/>
<source>Day&apos;s Quota:</source> <source>Day&apos;s Quota:</source>
<translation>Квота на день</translation> <translation>Квота на день</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/log/TrafOptionsButton.qml" line="242"/> <location filename="../qml/pages/log/TrafOptionsButton.qml" line="210"/>
<source>Month&apos;s Quota:</source> <source>Month&apos;s Quota:</source>
<translation>Квота на месяц</translation> <translation>Квота на месяц</translation>
</message> </message>
@ -581,22 +586,22 @@
<translation>Каждый месяц</translation> <translation>Каждый месяц</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/SchedulePage.qml" line="65"/> <location filename="../qml/pages/SchedulePage.qml" line="77"/>
<source>Name</source> <source>Name</source>
<translation>Наименование</translation> <translation>Наименование</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/SchedulePage.qml" line="70"/> <location filename="../qml/pages/SchedulePage.qml" line="82"/>
<source>Interval, hours</source> <source>Interval, hours</source>
<translation>Интервал, часов</translation> <translation>Интервал, часов</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/SchedulePage.qml" line="76"/> <location filename="../qml/pages/SchedulePage.qml" line="88"/>
<source>Last Run</source> <source>Last Run</source>
<translation>Последний запуск</translation> <translation>Последний запуск</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/SchedulePage.qml" line="82"/> <location filename="../qml/pages/SchedulePage.qml" line="94"/>
<source>Last Success</source> <source>Last Success</source>
<translation>Успешный запуск</translation> <translation>Успешный запуск</translation>
</message> </message>
@ -631,71 +636,71 @@
<translation>TiB</translation> <translation>TiB</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="47"/> <location filename="../qml/pages/StatisticsPage.qml" line="67"/>
<source>Refresh</source> <source>Refresh</source>
<translation>Обновить</translation> <translation>Обновить</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="92"/> <location filename="../qml/pages/StatisticsPage.qml" line="116"/>
<source>Units:</source> <source>Units:</source>
<translation>Единицы:</translation> <translation>Единицы:</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="66"/> <location filename="../qml/pages/StatisticsPage.qml" line="86"/>
<source>Reset Total</source> <source>Reset Total</source>
<translation>Сбросить общую</translation> <translation>Сбросить общую</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="122"/> <location filename="../qml/pages/StatisticsPage.qml" line="146"/>
<source>Collect Traffic Statistics</source> <source>Collect Traffic Statistics</source>
<translation>Собирать статистику трафика</translation> <translation>Собирать статистику трафика</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="153"/> <location filename="../qml/pages/StatisticsPage.qml" line="177"/>
<source>All</source> <source>All</source>
<translation>Все</translation> <translation>Все</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="170"/> <location filename="../qml/pages/StatisticsPage.qml" line="194"/>
<source>Hourly</source> <source>Hourly</source>
<comment>Stat</comment> <comment>Stat</comment>
<translation>Почасовая</translation> <translation>Почасовая</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="174"/> <location filename="../qml/pages/StatisticsPage.qml" line="198"/>
<source>Daily</source> <source>Daily</source>
<comment>Stat</comment> <comment>Stat</comment>
<translation>Ежедневная</translation> <translation>Ежедневная</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="178"/> <location filename="../qml/pages/StatisticsPage.qml" line="202"/>
<source>Monthly</source> <source>Monthly</source>
<comment>Stat</comment> <comment>Stat</comment>
<translation>Ежемесячная</translation> <translation>Ежемесячная</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="182"/> <location filename="../qml/pages/StatisticsPage.qml" line="206"/>
<source>Total</source> <source>Total</source>
<comment>Stat</comment> <comment>Stat</comment>
<translation>Общая</translation> <translation>Общая</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="193"/> <location filename="../qml/pages/StatisticsPage.qml" line="217"/>
<source>Date</source> <source>Date</source>
<translation>Дата</translation> <translation>Дата</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="198"/> <location filename="../qml/pages/StatisticsPage.qml" line="222"/>
<source>Download</source> <source>Download</source>
<translation>Загрузка</translation> <translation>Загрузка</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="203"/> <location filename="../qml/pages/StatisticsPage.qml" line="227"/>
<source>Upload</source> <source>Upload</source>
<translation>Выгрузка</translation> <translation>Выгрузка</translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/StatisticsPage.qml" line="208"/> <location filename="../qml/pages/StatisticsPage.qml" line="232"/>
<source>Sum</source> <source>Sum</source>
<translation>Сумма</translation> <translation>Сумма</translation>
</message> </message>
@ -709,5 +714,80 @@
<source>Allowed Internet Addresses</source> <source>Allowed Internet Addresses</source>
<translation>Разрешённые адреса Интернета</translation> <translation>Разрешённые адреса Интернета</translation>
</message> </message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="11"/>
<source>Graph</source>
<translation>График</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="39"/>
<source>Always on top</source>
<translation>Всегда сверху</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="47"/>
<source>Frameless</source>
<translation>Без рамок</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="55"/>
<source>Click through</source>
<translation>Сквозной режим</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="63"/>
<source>Hide on hover</source>
<translation>Скрыть при наведении</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="73"/>
<source>Opacity</source>
<translation>Прозрачность</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="85"/>
<source>Hover opacity</source>
<translation>Прозрачность при наведении</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="97"/>
<source>Max seconds</source>
<translation>Количество секунд</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="117"/>
<source>Background color</source>
<translation>Цвет фона</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="125"/>
<source>Download color</source>
<translation>Цвет загрузки</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="133"/>
<source>Upload color</source>
<translation>Цвет выгрузки</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="141"/>
<source>Axis color</source>
<translation>Цвет оси</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="149"/>
<source>Tick label color</source>
<translation>Цвет метки</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="157"/>
<source>Label color</source>
<translation>Цвет маркировки</translation>
</message>
<message>
<location filename="../qml/pages/log/GraphButton.qml" line="165"/>
<source>Grid color</source>
<translation>Цвет сетки</translation>
</message>
</context> </context>
</TS> </TS>

View File

@ -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();
}
}
}

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -12,16 +12,12 @@ RowLayout {
property var names: values property var names: values
property var values property var values
SpinBox { SpinBoxControl {
id: field id: field
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: fieldPreferredWidth Layout.preferredWidth: fieldPreferredWidth
Layout.minimumWidth: fieldPreferredWidth Layout.minimumWidth: fieldPreferredWidth
editable: true
from: 0
to: 9999
onValueChanged: { onValueChanged: {
combo.updateIndex(value); combo.updateIndex(value);
} }

View File

@ -9,25 +9,17 @@ RowLayout {
property real fieldPreferredWidth property real fieldPreferredWidth
SpinBox { SpinBoxControl {
id: field1 id: field1
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: fieldPreferredWidth Layout.preferredWidth: fieldPreferredWidth
Layout.minimumWidth: fieldPreferredWidth Layout.minimumWidth: fieldPreferredWidth
editable: true
from: 0
to: 9999
} }
SpinBox { SpinBoxControl {
id: field2 id: field2
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: fieldPreferredWidth Layout.preferredWidth: fieldPreferredWidth
Layout.minimumWidth: fieldPreferredWidth Layout.minimumWidth: fieldPreferredWidth
editable: true
from: 0
to: 9999
} }
} }

View File

@ -6,6 +6,9 @@ Pane {
bottomPadding: 0 bottomPadding: 0
function onEditResetted() {
}
function onAboutToSave() { function onAboutToSave() {
} }
@ -14,6 +17,7 @@ Pane {
Connections { Connections {
target: mainPage target: mainPage
onEditResetted: page.onEditResetted()
onAboutToSave: page.onAboutToSave() onAboutToSave: page.onAboutToSave()
onSaved: page.onSaved() onSaved: page.onSaved()
} }

View File

@ -73,9 +73,6 @@ BasePage {
&& qsTranslate("qml", "Resolve Addresses") && qsTranslate("qml", "Resolve Addresses")
checked: firewallConf.resolveAddress checked: firewallConf.resolveAddress
onToggled: { onToggled: {
if (firewallConf.resolveAddress === checked)
return;
firewallConf.resolveAddress = checked; firewallConf.resolveAddress = checked;
fortManager.applyConfImmediateFlags(); fortManager.applyConfImmediateFlags();

View File

@ -9,12 +9,13 @@ Page {
signal opened() signal opened()
signal closed() signal closed()
signal editResetted()
signal aboutToSave() signal aboutToSave()
signal saved() signal saved()
property bool confFlagsEdited property bool confFlagsEdited
property bool confEdited property bool confEdited
property bool scheduleEdited property bool othersEdited
function setConfFlagsEdited() { function setConfFlagsEdited() {
confFlagsEdited = true; confFlagsEdited = true;
@ -24,14 +25,44 @@ Page {
confEdited = true; confEdited = true;
} }
function setScheduleEdited() { function setOthersEdited() {
scheduleEdited = true; othersEdited = true;
} }
function resetEdited() { function resetEdited() {
confFlagsEdited = false; confFlagsEdited = false;
confEdited = 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: { onOpened: {
@ -96,38 +127,18 @@ Page {
anchors.right: parent.right anchors.right: parent.right
Button { Button {
enabled: confFlagsEdited || confEdited || scheduleEdited enabled: confFlagsEdited || confEdited || othersEdited
icon.source: "qrc:/images/tick.png" icon.source: "qrc:/images/tick.png"
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "OK") && qsTranslate("qml", "OK")
onClicked: { onClicked: mainPage.save(true)
mainPage.aboutToSave();
if (confFlagsEdited || confEdited) {
if (!fortManager.saveConf(confFlagsEdited && !confEdited))
return;
}
mainPage.saved();
closeWindow();
}
} }
Button { Button {
enabled: confFlagsEdited || confEdited || scheduleEdited enabled: confFlagsEdited || confEdited || othersEdited
icon.source: "qrc:/images/accept.png" icon.source: "qrc:/images/accept.png"
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "Apply") && qsTranslate("qml", "Apply")
onClicked: { onClicked: mainPage.save(false)
mainPage.aboutToSave();
if (confFlagsEdited || confEdited) {
if (!fortManager.applyConf(confFlagsEdited && !confEdited))
return;
}
mainPage.saved();
resetEdited();
}
} }
Button { Button {
icon.source: "qrc:/images/cancel.png" icon.source: "qrc:/images/cancel.png"

View File

@ -6,6 +6,18 @@ import com.fortfirewall 1.0
BasePage { BasePage {
property bool iniEdited
function setIniEdited() {
iniEdited = true;
setOthersEdited();
}
function onEditResetted() { // override
iniEdited = false;
}
function onAboutToSave() { // override function onAboutToSave() { // override
const password = editPassword.text; const password = editPassword.text;
if (password) { if (password) {
@ -15,6 +27,8 @@ BasePage {
} }
function onSaved() { // override function onSaved() { // override
if (!iniEdited) return;
fortSettings.startWithWindows = cbStart.checked; fortSettings.startWithWindows = cbStart.checked;
fortSettings.hotKeyEnabled = cbHotKeys.checked; fortSettings.hotKeyEnabled = cbHotKeys.checked;
} }
@ -31,9 +45,7 @@ BasePage {
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "Start with Windows") && qsTranslate("qml", "Start with Windows")
checked: fortSettings.startWithWindows checked: fortSettings.startWithWindows
onToggled: { onToggled: setIniEdited()
setConfFlagsEdited();
}
} }
CheckBox { CheckBox {
@ -104,9 +116,7 @@ BasePage {
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "Hot Keys") && qsTranslate("qml", "Hot Keys")
checked: fortSettings.hotKeyEnabled checked: fortSettings.hotKeyEnabled
onToggled: { onToggled: setIniEdited()
setConfFlagsEdited();
}
} }
Row { Row {

View File

@ -37,6 +37,18 @@ BasePage {
qsTranslate("qml", "Monthly") qsTranslate("qml", "Monthly")
] ]
property bool scheduleEdited
function setScheduleEdited() {
scheduleEdited = true;
setOthersEdited();
}
function onEditResetted() { // override
scheduleEdited = false;
}
function onSaved() { // override function onSaved() { // override
if (!scheduleEdited) return; if (!scheduleEdited) return;

View File

@ -35,6 +35,26 @@ BasePage {
qsTranslate("qml", "TiB") 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 { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 10 spacing: 10
@ -78,6 +98,10 @@ BasePage {
TrafOptionsButton {} TrafOptionsButton {}
GraphButton {
id: graphButton
}
Row { Row {
spacing: 5 spacing: 5

View File

@ -61,12 +61,8 @@ ColumnLayout {
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "period, hours:") && qsTranslate("qml", "period, hours:")
checked: appGroup.periodEnabled checked: appGroup.periodEnabled
onCheckedChanged: { onToggled: {
const value = checkBox.checked; appGroup.periodEnabled = checkBox.checked;
if (appGroup.periodEnabled == value)
return;
appGroup.periodEnabled = value;
setConfEdited(); setConfEdited();
} }
@ -74,13 +70,9 @@ ColumnLayout {
field1 { field1 {
from: 0 from: 0
to: 24 to: 24
value: appGroup.periodFrom defaultValue: appGroup.periodFrom
onValueChanged: { onValueEdited: {
const value = field1.value; appGroup.periodFrom = field1.value;
if (appGroup.periodFrom == value)
return;
appGroup.periodFrom = value;
setConfEdited(); setConfEdited();
} }
@ -88,13 +80,9 @@ ColumnLayout {
field2 { field2 {
from: 0 from: 0
to: 24 to: 24
value: appGroup.periodTo defaultValue: appGroup.periodTo
onValueChanged: { onValueEdited: {
const value = field2.value; appGroup.periodTo = field2.value;
if (appGroup.periodTo == value)
return;
appGroup.periodTo = value;
setConfEdited(); setConfEdited();
} }

View File

@ -66,12 +66,8 @@ ButtonPopup {
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "Download speed limit, KiB/s:") && qsTranslate("qml", "Download speed limit, KiB/s:")
checked: appGroup.limitInEnabled checked: appGroup.limitInEnabled
onCheckedChanged: { onToggled: {
const value = checkBox.checked; appGroup.limitInEnabled = checkBox.checked;
if (appGroup.limitInEnabled == value)
return;
appGroup.limitInEnabled = value;
setConfEdited(); setConfEdited();
} }
@ -79,13 +75,9 @@ ButtonPopup {
field { field {
from: 0 from: 0
to: 99999 to: 99999
value: appGroup.speedLimitIn defaultValue: appGroup.speedLimitIn
onValueChanged: { onValueEdited: {
const value = field.value; appGroup.speedLimitIn = field.value;
if (appGroup.speedLimitIn == value)
return;
appGroup.speedLimitIn = value;
setConfEdited(); setConfEdited();
} }
@ -99,12 +91,8 @@ ButtonPopup {
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "Upload speed limit, KiB/s:") && qsTranslate("qml", "Upload speed limit, KiB/s:")
checked: appGroup.limitOutEnabled checked: appGroup.limitOutEnabled
onCheckedChanged: { onToggled: {
const value = checkBox.checked; appGroup.limitOutEnabled = checkBox.checked;
if (appGroup.limitOutEnabled == value)
return;
appGroup.limitOutEnabled = value;
setConfEdited(); setConfEdited();
} }
@ -112,13 +100,9 @@ ButtonPopup {
field { field {
from: 0 from: 0
to: 99999 to: 99999
value: appGroup.speedLimitOut defaultValue: appGroup.speedLimitOut
onValueChanged: { onValueEdited: {
const value = field.value; appGroup.speedLimitOut = field.value;
if (appGroup.speedLimitOut == value)
return;
appGroup.speedLimitOut = value;
setConfEdited(); setConfEdited();
} }

View File

@ -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
}
}
}
}

View File

@ -67,12 +67,8 @@ ButtonPopup {
text: translationManager.trTrigger text: translationManager.trTrigger
&& qsTranslate("qml", "Active period, hours:") && qsTranslate("qml", "Active period, hours:")
checked: firewallConf.activePeriodEnabled checked: firewallConf.activePeriodEnabled
onCheckedChanged: { onToggled: {
const value = checkBox.checked; firewallConf.activePeriodEnabled = checkBox.checked;
if (firewallConf.activePeriodEnabled == value)
return;
firewallConf.activePeriodEnabled = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -80,13 +76,9 @@ ButtonPopup {
field1 { field1 {
from: 0 from: 0
to: 24 to: 24
value: firewallConf.activePeriodFrom defaultValue: firewallConf.activePeriodFrom
onValueChanged: { onValueEdited: {
const value = field1.value; firewallConf.activePeriodFrom = field1.value;
if (firewallConf.activePeriodFrom == value)
return;
firewallConf.activePeriodFrom = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -94,13 +86,9 @@ ButtonPopup {
field2 { field2 {
from: 0 from: 0
to: 24 to: 24
value: firewallConf.activePeriodTo defaultValue: firewallConf.activePeriodTo
onValueChanged: { onValueEdited: {
const value = field2.value; firewallConf.activePeriodTo = field2.value;
if (firewallConf.activePeriodTo == value)
return;
firewallConf.activePeriodTo = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -123,13 +111,9 @@ ButtonPopup {
field { field {
from: 1 from: 1
to: 31 to: 31
value: firewallConf.monthStart defaultValue: firewallConf.monthStart
onValueChanged: { onValueEdited: {
const value = field.value; firewallConf.monthStart = field.value;
if (firewallConf.monthStart == value)
return;
firewallConf.monthStart = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -148,13 +132,9 @@ ButtonPopup {
} }
field { field {
from: -1 from: -1
value: firewallConf.trafHourKeepDays defaultValue: firewallConf.trafHourKeepDays
onValueChanged: { onValueEdited: {
const value = field.value; firewallConf.trafHourKeepDays = field.value;
if (firewallConf.trafHourKeepDays == value)
return;
firewallConf.trafHourKeepDays = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -171,13 +151,9 @@ ButtonPopup {
} }
field { field {
from: -1 from: -1
value: firewallConf.trafDayKeepDays defaultValue: firewallConf.trafDayKeepDays
onValueChanged: { onValueEdited: {
const value = field.value; firewallConf.trafDayKeepDays = field.value;
if (firewallConf.trafDayKeepDays == value)
return;
firewallConf.trafDayKeepDays = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -194,13 +170,9 @@ ButtonPopup {
} }
field { field {
from: -1 from: -1
value: firewallConf.trafMonthKeepMonths defaultValue: firewallConf.trafMonthKeepMonths
onValueChanged: { onValueEdited: {
const value = field.value; firewallConf.trafMonthKeepMonths = field.value;
if (firewallConf.trafMonthKeepMonths == value)
return;
firewallConf.trafMonthKeepMonths = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -220,13 +192,9 @@ ButtonPopup {
field { field {
from: 0 from: 0
to: 999 * 1024 to: 999 * 1024
value: firewallConf.quotaDayMb defaultValue: firewallConf.quotaDayMb
onValueChanged: { onValueEdited: {
const value = field.value; firewallConf.quotaDayMb = field.value;
if (firewallConf.quotaDayMb == value)
return;
firewallConf.quotaDayMb = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }
@ -244,13 +212,9 @@ ButtonPopup {
field { field {
from: 0 from: 0
to: 999 * 1024 to: 999 * 1024
value: firewallConf.quotaMonthMb defaultValue: firewallConf.quotaMonthMb
onValueChanged: { onValueEdited: {
const value = field.value; firewallConf.quotaMonthMb = field.value;
if (firewallConf.quotaMonthMb == value)
return;
firewallConf.quotaMonthMb = value;
setConfFlagsEdited(); setConfFlagsEdited();
} }

View File

@ -51,14 +51,8 @@ Row {
field { field {
from: 1 from: 1
to: 24 * 30 * 12 // ~Year to: 24 * 30 * 12 // ~Year
value: taskInfo.intervalHours defaultValue: taskInfo.intervalHours
onValueEdited: setScheduleEdited()
onValueChanged: {
const value = field.value;
if (value != taskInfo.intervalHours) {
setScheduleEdited();
}
}
} }
} }

View File

@ -1,6 +1,7 @@
#include "guiutil.h" #include "guiutil.h"
#include <QClipboard> #include <QClipboard>
#include <QColorDialog>
#include <QGuiApplication> #include <QGuiApplication>
#include <QPixmap> #include <QPixmap>
#include <QImage> #include <QImage>
@ -25,3 +26,13 @@ void GuiUtil::setClipboardData(const QVariant &data)
clipboard->setText(data.toString()); clipboard->setText(data.toString());
} }
} }
QColor GuiUtil::getColor(const QColor &initial)
{
return QColorDialog::getColor(initial);
}
bool GuiUtil::isValidColor(const QColor &color)
{
return color.isValid();
}

View File

@ -1,6 +1,7 @@
#ifndef GUIUTIL_H #ifndef GUIUTIL_H
#define GUIUTIL_H #define GUIUTIL_H
#include <QColor>
#include <QObject> #include <QObject>
#include <QVariant> #include <QVariant>
@ -12,6 +13,9 @@ public:
explicit GuiUtil(QObject *parent = nullptr); explicit GuiUtil(QObject *parent = nullptr);
Q_INVOKABLE static void setClipboardData(const QVariant &data); 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 #endif // GUIUTIL_H