Extract reusable settings controller

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-12 12:22:34 +08:00
parent c9eac77c6c
commit 8771e2fd56
7 changed files with 229 additions and 228 deletions
+29 -8
View File
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""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)
+4 -3
View File
@@ -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."""
+3 -210
View File
@@ -15,177 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""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
+4 -4
View File
@@ -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',
]
+1 -3
View File
@@ -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),
]
+174
View File
@@ -0,0 +1,174 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# 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
+14
View File
@@ -0,0 +1,14 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# 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']