diff --git a/Furious/Actions/Configuration.py b/Furious/Actions/Configuration.py
index ab61b4a..83fc86c 100644
--- a/Furious/Actions/Configuration.py
+++ b/Furious/Actions/Configuration.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-"""Implement tray actions for edit configuration."""
+"""Provide the tray action that foregrounds the Home page."""
from __future__ import annotations
@@ -23,20 +23,41 @@ from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
-__all__ = ['EditConfigurationAction']
+__all__ = ['ShowHomePageAction']
-class EditConfigurationAction(AppQAction):
- """Handle the edit configuration action."""
+class ShowHomePageAction(AppQAction):
+ """Foreground the main window and navigate to server management."""
def __init__(self, **kwargs):
- """Initialize the EditConfigurationAction."""
+ """Initialize the Home-page navigation action."""
super().__init__(
- _('Edit Configuration...'),
- icon=bootstrapIcon('pencil-square.svg'),
+ _('Show Home Page...'),
+ icon=bootstrapIcon('house-door.svg'),
**kwargs,
)
+ self.setToolTip(_('Show the server management Home page'))
+ self.setStatusTip(_('Show the server management Home page'))
+
def triggeredCallback(self, checked):
"""Handle activation of the action."""
- APP().mainWindow.show()
+ mainWindow = APP().mainWindow
+
+ if mainWindow.isMinimized():
+ mainWindow.showNormal()
+ else:
+ mainWindow.show()
+
+ mainWindow.showPage('home')
+ mainWindow.raise_()
+ mainWindow.activateWindow()
+
+ def retranslate(self):
+ """Refresh the action text and explanatory hover text."""
+ super().retranslate()
+
+ description = _('Show the server management Home page')
+
+ self.setToolTip(description)
+ self.setStatusTip(description)
diff --git a/Furious/Actions/Connection.py b/Furious/Actions/Connection.py
index 3db008b..2dfe7b1 100644
--- a/Furious/Actions/Connection.py
+++ b/Furious/Actions/Connection.py
@@ -122,10 +122,11 @@ class ConnectAction(AppQAction):
APP().systemTray.RoutingAction.setDisabled(value)
APP().systemTray.SystemProxyAction.setDisabled(value)
- # TODO: Need this?
if PLATFORM == 'Linux' or SystemRuntime.isAdmin():
- TUNModeAction = APP().systemTray.SettingsAction.getTUNModeAction()
- TUNModeAction.setDisabled(value)
+ try:
+ APP().mainWindow.settingsPage.setTUNModeControlEnabled(not value)
+ except (AttributeError, RuntimeError):
+ pass
def isConnected(self) -> bool:
"""Return whether connected."""
diff --git a/Furious/Actions/Settings.py b/Furious/Actions/Settings.py
index e1d260d..486394b 100644
--- a/Furious/Actions/Settings.py
+++ b/Furious/Actions/Settings.py
@@ -15,177 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-"""Implement tray actions for settings."""
+"""Provide the compatibility action used by the Settings-page TUN control."""
from __future__ import annotations
+from Furious.Controllers import SettingsController
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
-from Furious.Service.TrafficStatsManager import (
- CLEAR_TRAFFIC_USAGE_ON_RECONNECT_SETTING,
-)
-__all__ = ['SettingsAction', 'SettingsController', 'TUNModeAction']
-
-registerAppSettings('VPNMode', isBinary=True)
-registerAppSettings('DarkMode', isBinary=True)
-registerAppSettings('UseMonochromeTrayIcon', isBinary=True)
-
-if PLATFORM == 'Darwin':
- registerAppSettings('HideDockIcon', isBinary=True)
-
-registerAppSettings('StartupOnBoot', isBinary=True, default=AppBinarySettings.ON_)
-registerAppSettings('PowerSaveMode', isBinary=True, default=AppBinarySettings.ON_)
-registerAppSettings(
- 'ForceToLocalhostWhenSettingLocalProxy',
- isBinary=True,
- default=AppBinarySettings.OFF,
-)
-registerAppSettings(
- 'AutoUpdateAssetFiles', isBinary=True, default=AppBinarySettings.ON_
-)
-registerAppSettings(
- 'ShowProgressBarWhenConnecting', isBinary=True, default=AppBinarySettings.ON_
-)
-registerAppSettings('ShowTabAndSpacesInEditor', isBinary=True)
-
-
-class SettingsController:
- """Apply application settings independently from their presentation."""
-
- @staticmethod
- def setTUNMode(enabled: bool):
- """Persist the global TUN mode and notify active workflows."""
- if PLATFORM != 'Linux':
- assert SystemRuntime.isAdmin()
-
- if enabled:
- AppSettings.turnON_('VPNMode')
- else:
- AppSettings.turnOFF('VPNMode')
-
- showMBoxNewChangesNextTime()
-
- @staticmethod
- def setDarkMode(enabled: bool):
- """Switch between the explicit dark theme and automatic mode."""
- if enabled:
- AppSettings.turnON_('DarkMode')
-
- try:
- APP().switchToDarkMode()
- except Exception:
- # Any non-exit exceptions
-
- pass
- else:
- AppSettings.turnOFF('DarkMode')
-
- try:
- APP().switchToAutoMode()
- except Exception:
- # Any non-exit exceptions
-
- pass
-
- @staticmethod
- def setLanguage(language: str):
- """Persist a supported UI language and refresh translated objects."""
- if language not in SUPPORTED_LANGUAGE:
- return
-
- if AppSettings.get('Language') != language:
- AppSettings.set('Language', language)
- Mixins.QTranslatable.retranslateAll()
-
- @staticmethod
- def setMonochromeTrayIcon(enabled: bool):
- """Persist and immediately refresh the tray-icon presentation."""
- if enabled:
- AppSettings.turnON_('UseMonochromeTrayIcon')
- APP().systemTray.setMonochromeIcon()
- else:
- AppSettings.turnOFF('UseMonochromeTrayIcon')
-
- if APP().isSystemTrayConnected():
- APP().systemTray.setConnectedIcon()
- else:
- APP().systemTray.setDisconnectedIcon()
-
- @staticmethod
- def setDockIconHidden(enabled: bool):
- """Apply the macOS dock-icon visibility preference."""
- if enabled:
- APP().installDockIconVisibilityFeature()
- AppSettings.turnON_('HideDockIcon')
- else:
- APP().installDockIconVisibilityFeature(remove=True)
- AppSettings.turnOFF('HideDockIcon')
-
- @staticmethod
- def setStartupOnBoot(enabled: bool):
- """Apply the platform startup registration preference."""
- if enabled:
- StartupOnBoot.on_()
- AppSettings.turnON_('StartupOnBoot')
- else:
- StartupOnBoot.off()
- AppSettings.turnOFF('StartupOnBoot')
-
- @staticmethod
- def setPowerSaveMode(enabled: bool):
- """Persist power-saving behavior for the next connection."""
- if enabled:
- AppSettings.turnON_('PowerSaveMode')
- else:
- AppSettings.turnOFF('PowerSaveMode')
-
- showMBoxNewChangesNextTime()
-
- @staticmethod
- def setForceLocalProxy(enabled: bool):
- """Persist local system-proxy address normalization."""
- if enabled:
- AppSettings.turnON_('ForceToLocalhostWhenSettingLocalProxy')
- else:
- AppSettings.turnOFF('ForceToLocalhostWhenSettingLocalProxy')
-
- showMBoxNewChangesNextTime()
-
- @staticmethod
- def setAutoUpdateAssets(enabled: bool):
- """Persist automatic core-asset updates."""
- if enabled:
- AppSettings.turnON_('AutoUpdateAssetFiles')
- else:
- AppSettings.turnOFF('AutoUpdateAssetFiles')
-
- @staticmethod
- def setConnectionProgressVisible(enabled: bool):
- """Persist connection-progress visibility."""
- if enabled:
- AppSettings.turnON_('ShowProgressBarWhenConnecting')
- else:
- AppSettings.turnOFF('ShowProgressBarWhenConnecting')
-
- @staticmethod
- def setClearTrafficUsageOnReconnect(enabled: bool):
- """Persist whether reconnecting starts a fresh usage session."""
- if enabled:
- AppSettings.turnON_(CLEAR_TRAFFIC_USAGE_ON_RECONNECT_SETTING)
- else:
- AppSettings.turnOFF(CLEAR_TRAFFIC_USAGE_ON_RECONNECT_SETTING)
-
- @staticmethod
- def setEditorWhitespaceVisible(enabled: bool):
- """Apply and persist editor whitespace visibility."""
- if enabled:
- APP().mainWindow.showTabAndSpaces()
- AppSettings.turnON_('ShowTabAndSpacesInEditor')
- else:
- APP().mainWindow.hideTabAndSpaces()
- AppSettings.turnOFF('ShowTabAndSpacesInEditor')
+__all__ = ['TUNModeAction']
class TUNModeAction(AppQAction):
@@ -211,49 +50,3 @@ class TUNModeAction(AppQAction):
def triggeredCallback(self, checked):
"""Handle activation of the action."""
SettingsController.setTUNMode(checked)
-
-
-class SettingsChildAction(AppQAction):
- """Handle the settings child action."""
-
- def __init__(self, *args, **kwargs):
- """Initialize the SettingsChildAction."""
- super().__init__(*args, **kwargs)
-
- def triggeredCallback(self, checked):
- """Handle activation of the action."""
- if self.textCompare('Dark Mode'):
- SettingsController.setDarkMode(checked)
- elif self.textCompare('Use Monochrome Tray Icon'):
- SettingsController.setMonochromeTrayIcon(checked)
- elif self.textCompare('Hide Dock Icon'):
- SettingsController.setDockIconHidden(checked)
- elif self.textCompare('Startup On Boot'):
- SettingsController.setStartupOnBoot(checked)
- elif self.textCompare('Power Save Mode'):
- SettingsController.setPowerSaveMode(checked)
- elif self.textCompare('Force To 127.0.0.1 When Setting Local Proxy'):
- SettingsController.setForceLocalProxy(checked)
- elif self.textCompare('Automatically Update Asset Files'):
- SettingsController.setAutoUpdateAssets(checked)
- elif self.textCompare('Show Progress Bar When Connecting'):
- SettingsController.setConnectionProgressVisible(checked)
- elif self.textCompare('Show Tab And Spaces In Editor'):
- SettingsController.setEditorWhitespaceVisible(checked)
-
-
-class SettingsAction(AppQAction):
- """Navigate from the system tray to the page-based settings UI."""
-
- def __init__(self, **kwargs):
- """Initialize the SettingsAction."""
- super().__init__(
- _('Settings'),
- icon=bootstrapIcon('gear-wide-connected.svg'),
- callback=lambda: APP().mainWindow.showSettingsPage(),
- **kwargs,
- )
-
- def getTUNModeAction(self) -> AppQAction:
- """Return the Settings page's compatibility TUN action."""
- return APP().mainWindow.settingsPage.tunModeAction
diff --git a/Furious/Actions/__init__.py b/Furious/Actions/__init__.py
index 245cf8d..acf7a98 100644
--- a/Furious/Actions/__init__.py
+++ b/Furious/Actions/__init__.py
@@ -19,7 +19,7 @@
from __future__ import annotations
-from .Configuration import EditConfigurationAction
+from .Configuration import ShowHomePageAction
from .Connection import ConnectAction
from .Exit import ExitAction
from .Import import (
@@ -31,12 +31,11 @@ from .Import import (
)
from .Language import LanguageAction
from .Routing import RoutingAction
-from .Settings import SettingsAction
+from .Settings import TUNModeAction
from .SystemProxy import SystemProxyAction
__all__ = [
'ConnectAction',
- 'EditConfigurationAction',
'ExitAction',
'ImportAction',
'ImportFromFileAction',
@@ -45,6 +44,7 @@ __all__ = [
'ImportURIFromClipboardAction',
'LanguageAction',
'RoutingAction',
- 'SettingsAction',
+ 'ShowHomePageAction',
+ 'TUNModeAction',
'SystemProxyAction',
]
diff --git a/Furious/Application/TrayIcon.py b/Furious/Application/TrayIcon.py
index 5656f8f..5fddb8b 100644
--- a/Furious/Application/TrayIcon.py
+++ b/Furious/Application/TrayIcon.py
@@ -59,9 +59,7 @@ class TrayIcon(
SystemProxyAction(isTrayAction=True),
AppQSeperator(),
ImportAction(isTrayAction=True),
- EditConfigurationAction(isTrayAction=True),
- AppQSeperator(),
- SettingsAction(isTrayAction=True),
+ ShowHomePageAction(isTrayAction=True),
AppQSeperator(),
ExitAction(isTrayAction=True),
]
diff --git a/Furious/Controllers/SettingsController.py b/Furious/Controllers/SettingsController.py
new file mode 100644
index 0000000..a82c214
--- /dev/null
+++ b/Furious/Controllers/SettingsController.py
@@ -0,0 +1,174 @@
+# Copyright (C) 2024–present Loren Eteval & contributors
+#
+# This file is part of Furious.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+"""Apply persistent application preferences independently from their UI."""
+
+from __future__ import annotations
+
+from Furious.Frozenlib import *
+from Furious.Qt.DynamicTranslate import SUPPORTED_LANGUAGE
+from Furious.Qt.QtWidgets import showMBoxNewChangesNextTime
+from Furious.Service.TrafficStatsManager import (
+ CLEAR_TRAFFIC_USAGE_ON_RECONNECT_SETTING,
+ METRICS_COLLECTION_SETTING,
+)
+
+__all__ = ['SettingsController']
+
+registerAppSettings('VPNMode', isBinary=True)
+registerAppSettings('DarkMode', isBinary=True)
+registerAppSettings('UseMonochromeTrayIcon', isBinary=True)
+
+if PLATFORM == 'Darwin':
+ registerAppSettings('HideDockIcon', isBinary=True)
+
+registerAppSettings('StartupOnBoot', isBinary=True, default=AppBinarySettings.ON_)
+registerAppSettings('PowerSaveMode', isBinary=True, default=AppBinarySettings.ON_)
+registerAppSettings(
+ 'ForceToLocalhostWhenSettingLocalProxy',
+ isBinary=True,
+ default=AppBinarySettings.OFF,
+)
+registerAppSettings(
+ 'AutoUpdateAssetFiles', isBinary=True, default=AppBinarySettings.ON_
+)
+registerAppSettings(
+ 'ShowProgressBarWhenConnecting', isBinary=True, default=AppBinarySettings.ON_
+)
+registerAppSettings('ShowTabAndSpacesInEditor', isBinary=True)
+
+
+class SettingsController:
+ """Apply application settings independently from their presentation."""
+
+ @staticmethod
+ def _setBinary(settingName: str, enabled: bool):
+ """Persist one registered binary setting."""
+ if enabled:
+ AppSettings.turnON_(settingName)
+ else:
+ AppSettings.turnOFF(settingName)
+
+ @classmethod
+ def setTUNMode(cls, enabled: bool):
+ """Persist the global TUN mode and notify active workflows."""
+ if PLATFORM != 'Linux':
+ assert SystemRuntime.isAdmin()
+
+ cls._setBinary('VPNMode', enabled)
+ showMBoxNewChangesNextTime()
+
+ @classmethod
+ def setDarkMode(cls, enabled: bool):
+ """Switch between the explicit dark theme and automatic mode."""
+ cls._setBinary('DarkMode', enabled)
+
+ try:
+ if enabled:
+ APP().switchToDarkMode()
+ else:
+ APP().switchToAutoMode()
+ except Exception:
+ # The controller can be exercised before the full desktop UI exists.
+ pass
+
+ @staticmethod
+ def setLanguage(language: str):
+ """Persist a supported UI language and refresh translated objects."""
+ if language not in SUPPORTED_LANGUAGE:
+ return
+
+ if AppSettings.get('Language') != language:
+ AppSettings.set('Language', language)
+ Mixins.QTranslatable.retranslateAll()
+
+ @classmethod
+ def setMonochromeTrayIcon(cls, enabled: bool):
+ """Persist and immediately refresh the tray-icon presentation."""
+ cls._setBinary('UseMonochromeTrayIcon', enabled)
+
+ try:
+ if enabled:
+ APP().systemTray.setMonochromeIcon()
+ elif APP().isSystemTrayConnected():
+ APP().systemTray.setConnectedIcon()
+ else:
+ APP().systemTray.setDisconnectedIcon()
+ except (AttributeError, RuntimeError):
+ pass
+
+ @classmethod
+ def setDockIconHidden(cls, enabled: bool):
+ """Apply the macOS dock-icon visibility preference."""
+ if enabled:
+ APP().installDockIconVisibilityFeature()
+ else:
+ APP().installDockIconVisibilityFeature(remove=True)
+
+ cls._setBinary('HideDockIcon', enabled)
+
+ @classmethod
+ def setStartupOnBoot(cls, enabled: bool):
+ """Apply the platform startup registration preference."""
+ if enabled:
+ StartupOnBoot.on_()
+ else:
+ StartupOnBoot.off()
+
+ cls._setBinary('StartupOnBoot', enabled)
+
+ @classmethod
+ def setPowerSaveMode(cls, enabled: bool):
+ """Persist power-saving behavior for the next connection."""
+ cls._setBinary('PowerSaveMode', enabled)
+ showMBoxNewChangesNextTime()
+
+ @classmethod
+ def setForceLocalProxy(cls, enabled: bool):
+ """Persist local system-proxy address normalization."""
+ cls._setBinary('ForceToLocalhostWhenSettingLocalProxy', enabled)
+ showMBoxNewChangesNextTime()
+
+ @classmethod
+ def setAutoUpdateAssets(cls, enabled: bool):
+ """Persist automatic core-asset updates."""
+ cls._setBinary('AutoUpdateAssetFiles', enabled)
+
+ @classmethod
+ def setConnectionProgressVisible(cls, enabled: bool):
+ """Persist connection-progress visibility."""
+ cls._setBinary('ShowProgressBarWhenConnecting', enabled)
+
+ @classmethod
+ def setClearTrafficUsageOnReconnect(cls, enabled: bool):
+ """Persist whether reconnecting starts a fresh usage session."""
+ cls._setBinary(CLEAR_TRAFFIC_USAGE_ON_RECONNECT_SETTING, enabled)
+
+ @classmethod
+ def setMetricsCollectionEnabled(cls, enabled: bool):
+ """Persist and immediately apply network metrics collection."""
+ cls._setBinary(METRICS_COLLECTION_SETTING, enabled)
+
+ try:
+ APP().mainWindow.trafficStatsManager.setCollectionEnabled(enabled)
+ except (AttributeError, RuntimeError):
+ pass
+
+ @classmethod
+ def setEditorWhitespaceVisible(cls, enabled: bool):
+ """Apply and persist editor whitespace visibility."""
+ cls._setBinary('ShowTabAndSpacesInEditor', enabled)
+
+ try:
+ if enabled:
+ APP().mainWindow.showTabAndSpaces()
+ else:
+ APP().mainWindow.hideTabAndSpaces()
+ except (AttributeError, RuntimeError):
+ pass
diff --git a/Furious/Controllers/__init__.py b/Furious/Controllers/__init__.py
new file mode 100644
index 0000000..9e05402
--- /dev/null
+++ b/Furious/Controllers/__init__.py
@@ -0,0 +1,14 @@
+# Copyright (C) 2024–present Loren Eteval & contributors
+#
+# This file is part of Furious.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+"""Expose presentation-independent application controllers."""
+
+from .SettingsController import SettingsController
+
+__all__ = ['SettingsController']