#!/usr/bin/python3

import os
import re
import subprocess
import sys

import dbus
from PyQt6.QtCore import QObject, pyqtSignal
import logging

#print(f"sys.path: {sys.path}")

# import gettext

BUILD_VERSION='%%VERSION%%'
MX_UPDATER_PATH = "/usr/libexec/mx-updater"

'''
#----------
# Check MX_UPDATER_PATH
#----------
try:
    MX_UPDATER_PATH = os.environ["MX_UPDATER_PATH"]
except KeyError:
    print("MX_UPDATER_PATH missing from environment, exiting")
    #logger.error("MX_UPDATER_PATH missing from environment, exiting")
    sys.exit(1)
'''

sys.path.insert(0, MX_UPDATER_PATH)

if '' in sys.path:
    sys.path.remove('')

# remove the directory of the current script
script_dir = os.path.dirname(os.path.abspath(__file__))
if script_dir in sys.path:
    sys.path.remove(script_dir)

if MX_UPDATER_PATH not in sys.path:
    sys.path.insert(0, MX_UPDATER_PATH)

import updater_config
from updater_config import UpdaterSettingsManager
from updater_translator import Translator

# suppress some warnings
os.environ["QT_LOGGING_RULES"] = "*.debug=false;*.warning=false"
#os.environ["QT_QPA_EGLFS_INTEGRATION"] = "none"
#os.environ["LIBGL_DEBUG"] = "0"
os.environ["EGL_LOG_LEVEL"] = "fatal"


# create a Translator instance
translator = Translator(textdomain='mx-updater')
_ = translator.translate  # Use the translator function

# use _t() to reuse existing translations (or hide until enabled)
_t = _


from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QFrame,
    QRadioButton, QCheckBox, QLabel, QPushButton, QWidget, QGroupBox,
    QButtonGroup, QGridLayout, QSizePolicy, QDialog, QDialogButtonBox,
    QMessageBox, QSpinBox, QToolTip, QComboBox
)
from PyQt6.QtGui import QPixmap, QIcon, QKeyEvent, QGuiApplication, QFont, QPalette
from PyQt6.QtCore import Qt, QSettings
from PyQt6.QtCore import QTranslator, QLocale, QLibraryInfo

from PyQt6.QtGui import (
    QFont, QIcon, QPixmap, QKeyEvent, QGuiApplication,
    QPalette, QColor
    )

from pydbus import SessionBus



logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)

# constants
# dbus
SETTINGS_OBJECT_NAME  = "org.mxlinux.UpdaterSettings"
SETTINGS_OBJECT_PATH  = "/org/mxlinux/UpdaterSettings"
SETTINGS_OBJECT_IFACE = "org.mxlinux.UpdaterSettings"

TRAYICON_OBJECT_NAME  = "org.mxlinux.UpdaterSystemTrayIcon"
TRAYICON_OBJECT_PATH  = "/org/mxlinux/UpdaterSystemTrayIcon"
TRAYICON_OBJECT_IFACE = "org.mxlinux.UpdaterSystemTrayIcon"

VIEW_AND_UPGRADE_OBJECT_NAME  = "org.mxlinux.UpdaterViewAndUpgrade"
VIEW_AND_UPGRADE_OBJECT_PATH  = "/org/mxlinux/UpdaterViewAndUpgrade"
VIEW_AND_UPGRADE_OBJECT_IFACE = "org.mxlinux.UpdaterViewAndUpgrade"

#
AUTO_CLOSE_TIMEOUT_MIN =  1  # in seconds
AUTO_CLOSE_TIMEOUT_MAX = 60  # in seconds

class SettingsService:
    """
    <node>
     <interface name="org.mxlinux.UpdaterSettings">
        <method name="Close">
        </method>
        <method name="Minimize">
        </method>
        <method name="Restore">
        </method>
        <method name="SetValue">
          <arg direction="in"  type="s" name="key" />
          <arg direction="in"  type="s" name="value" />
        </method>
    </interface>
    </node>
    """

    def __init__(self, dialog):
        self.dialog = dialog
        #self._value = ""
        #self._settings = {}
        #self._settings_store = {}
        #self._settings = QSettings("MX-Linux", "mx-updater")
        # QObject wrapper for emitting Qt signals
        class _Emitter(QObject):
            value_changed_qt = pyqtSignal(str, str)
        self._emitter = _Emitter()

    # expose Qt signal to others
    @property
    def value_changed_qt(self):
        return self._emitter.value_changed_qt

    def SetValue(self, key, value):
        if not key.strip():
            return

        # write QSettings - nope will be done in update_setting_dialog
        #self._settings.setValue(key, value)
        #force write to disk
        #self._settings.sync()

        # emit PyQt signal value_changed_qt
        self._emitter.value_changed_qt.emit(key, value)
        # emit dbus signal for external listener
        #self.ValueChanged(key, value)


    def Close(self):
        self.dialog.close()

    def Minimize(self):
        self.dialog.minimize()

    def Restore(self):
        self.dialog.restore()

    # dbus signal not used - as we use directrly PyQt signal
    #def ValueChanged(self):
    #    pass

class SettingsEditorDialog(QDialog):

    value_changed_signal = pyqtSignal(str, str)

    def __init__(self):
        super().__init__()

        self.bus = SessionBus()
        self.service = SettingsService(self)
        self.bus.publish(SETTINGS_OBJECT_NAME, self.service)
        self.dbus_call_back = True

        self._init_done = False

        self._auto_upgrade_state_is_updating = False
        self._periodic_updating = False
        self._last_freq_index = 0

        self.is_detect_plasma = self.detect_plasma()
        self.disable_hide_until = (self.is_detect_plasma,)
        #self.is_detect_fluxbox = self.detect_fluxbox()
        #self.disable_hide_until = (self.is_detect_fluxbox, self.is_detect_plasma)

        # Connect to the service's Qt signal (avoids DBus loopback)
        self.service.value_changed_qt.connect(self.on_value_changed)
        # Connect the PyQt signal to the update_tray_icon method
        self.value_changed_signal.connect(self.update_setting_dialog)

        self.initUI()


    def unattended_upgrade_current_state(self):
        """
        Check and set current unattended upgrade state
        """
        me = "unattended_upgrade_current_state@Settings"
        try:
            # Use the method from previous example to check current state
            current_state = self.is_unattended_upgrade_enabled()

            # Set checkbox to match current state
            self.auto_upgrade_checkbox.setChecked(current_state)

        except Exception as e:
            logger.error("[%s] Checking unattended upgrade state failed: %s", me, str(e))

    def is_unattended_upgrade_enabled(self):
        try:
            raw = self._read_apt_periodic_raw('Unattended-Upgrade')
            if raw == 'always':
                return True
            days = self._raw_to_days(raw)
            return days is not None and days > 0
        except Exception:
            return False


    def on_value_changed(self, key, value):
        # Called when SetValue writes QSettings and emits the Qt signal
        self.value_changed_signal.emit(key, value)

    def update_setting_dialog(self, key, value):
        # update_setting_dialog key,value pair
        # logger.debug(f"Settings dialog updated with key=value: {key}={value}")
        me = "update_setting_dialog@Settings"
        logger.debug("[%s] dialog updated with key=value: %s='%s' of type '%s'", me, key, value, type(value).__name__ )
        logger.debug("[%s] dbus_call_back is %s", me, self.dbus_call_back)

        prefix = 'no-dbus-callback@'
        if key.startswith(prefix):
            self.dbus_call_back = False
            logger.debug("[%s] dbus_call_back set to %s", me, self.dbus_call_back)

            key = key.removeprefix(prefix)

        bool_keys = (
            'auto_close',
            'hide_until_upgrades_available',
            'start_at_login',
            'use_dbus_notifications',
            'use_nala',
            'upgrade_assume_yes',
            'wireframe_transparent',
        )

        if key in bool_keys:
            if isinstance(value, str):
                new_value = value.lower() in ("true", "yes", "on", "1")
            elif isinstance(value, bool):
                new_value = value
            elif isinstance(value, int):
                new_value = bool(value)
            else:
                return

        if key == 'auto_close_timeout':
            try:
                # convert string to integer
                new_value = int(value)
                if not (AUTO_CLOSE_TIMEOUT_MIN <= new_value <= AUTO_CLOSE_TIMEOUT_MAX):
                    logger.debug("[%s] value '%s' for auto_close_timeout, not within allowed range of (%s..%s)",
                                me, value,
                                AUTO_CLOSE_TIMEOUT_MIN, AUTO_CLOSE_TIMEOUT_MAX
                                )

                    if new_value < AUTO_CLOSE_TIMEOUT_MIN:
                        new_value = AUTO_CLOSE_TIMEOUT_MIN

                    if new_value > AUTO_CLOSE_TIMEOUT_MAX:
                        new_value = AUTO_CLOSE_TIMEOUT_MAX

            except ValueError:
                logger.debug("Error: '%s' is not a valid integer.", value)
                return  # ignored

        match key:
            case 'auto_close':
                self.settings[key] = new_value
                self.auto_close_checkbox.setChecked(new_value)

            case 'auto_close_timeout':
                self.settings[key] = new_value
                self.auto_close_timeout.setValue(new_value)

            case 'hide_until_upgrades_available':
                self.settings['hide_until_upgrades_available'] = new_value
                self.hide_until_upgrades_available_checkbox.setChecked(new_value)

            case 'use_dbus_notifications':
                self.settings[key] = new_value
                self.use_dbus_notifications_checkbox.setChecked(new_value)

            case 'use_nala':
                self.settings[key] = new_value
                self.use_nala_checkbox.setChecked(new_value)

            case 'upgrade_assume_yes':
                self.settings[key] = new_value
                self.upgrade_assume_yes_checkbox.setChecked(new_value)

            case 'wireframe_transparent':
                self.settings[key] = new_value
                self.wireframe_transparent_checkbox.setChecked(new_value)

            case _:
                # default if any
                pass


    def minimize(self):
        self.showMinimized()

    def restore(self):
        self.showNormal()
        self.activateWindow()
        QApplication.setActiveWindow(self)

        # check if wmctrl exists and is executable
        wmctrl_path = '/usr/bin/wmctrl'
        if os.path.exists(wmctrl_path) and os.access(wmctrl_path, os.X_OK):
            # check if WAYLAND_DISPLAY is not set
            if 'WAYLAND_DISPLAY' not in os.environ:
                window_title = self.windowTitle()
                command = [wmctrl_path, '-r', window_title, '-u', '-F', '-b', 'remove,hidden,shaded']
                try:
                    subprocess.run(command, check=True)
                except Exception:
                    pass

    def close(self):
        QApplication.quit()

    def initUI(self):

        self.ok_clicked = False
        self.selected_icons = {}
        self.settings = {}
        self.saved_settings = {}

        # Initialize settings manager
        self.settings_manager = UpdaterSettingsManager()
        self.qsettings = self.settings_manager.qsettings

        # Load settings
        self.settings = self.settings_manager.load_all_settings()
        self.icon_set = self.settings_manager.get_icon_set_config()
        self.icon_order = self.settings_manager.get_icon_order()
        self.save_setting = self.settings_manager.save_setting
        self.load_setting = self.settings_manager.load_setting
        self.dialog = self.create_dialog()

        # center window on the screen
        self.center()

        # flag to check if window has been shown
        self.first_resize = True
        self._init_done = True


    def style_margins(self):
        # set framk margins
        gtk_frame_margins = (0, 10, 0, 0)
        breeze_frame_margins = (8, 0, 5, 5)
        other_frame_margins = (8, 3, 5, 3)
        other_frame_margins = (8, 10, 5, 3)
        # Get the current style
        current_style = QApplication.style().objectName()
        #print(f"QApplication.style = {current_style}")
        if "gtk" in current_style:
            frame_margins = gtk_frame_margins
        elif "breeze" in current_style:
            frame_margins = breeze_frame_margins
        else:
            frame_margins = other_frame_margins

        return frame_margins

    def create_dialog(self):

        window_title_updater = _("MX Updater")
        window_title_preferences = _("Preferences")
        window_title  = f"[ {window_title_updater} ] -- {window_title_preferences}"
        self.setWindowTitle(window_title)

        #self.setWindowIcon(QIcon("mx-updater-settings.svg"))
        self.setWindowIcon(QIcon("/usr/share/icons/hicolor/scalable/apps/updater-mx-settings.svg"))
        self.setGeometry(100, 100, 1, 1)  # X, Y ignored (center() overrides); 1,1 lets Qt size to content

        # margins for main layout (left, top, right, bottom)
        overall_margins = (8, 5, 8, 5)
        # frame margins (left, top, right, bottom)
        frame_margins = (0, 8, 0, 0)
        frame_margins = (0, 0, 0, 0)
        frame_margins = (0, 5, 0, 0)
        frame_margins = self.style_margins()

        layout = QVBoxLayout()
        self.setLayout(layout)
        layout.setContentsMargins(*overall_margins)

        #---------------------------------------------------------------
        # upgrade_frame: Upgrade mode
        #---------------------------------------------------------------
        # TRANSLATORS: The label group-box, where user can select
        # the "Upgrade mode" either "full upgrade" or "basic upgrade"
        upgrade_frame = QGroupBox(_("Upgrade mode"))
        _f = upgrade_frame.font(); _f.setBold(True); upgrade_frame.setFont(_f)

        upgrade_layout = QVBoxLayout()
        upgrade_layout.setSpacing(1)  # smaller spacing between options
        # layout margins (left, top, right, bottom)
        upgrade_layout.setContentsMargins(*frame_margins)

        self.upgrade_button_group = QButtonGroup(self)

        # TRANSLATORS: The label of the radio button for "full" upgrade mode.
        self.full_upgrade_radio = QRadioButton(_("full"))

        # TRANSLATORS: The tooltip of the radio button "full" (full upgrade, recommended).
        self.full_upgrade_radio.setToolTip(_("""Upgrades all packages to their latest versions,
which may include adding or removing packages
to keep the system up-to-date and consistent."""))

        # keep old translated strings so xgettext still extracts them
        # TRANSLATORS: The upgrade mode "full upgrade".
        _dummy = _("full upgrade")
        # TRANSLATORS: The upgrade mode "full upgrade (recommended)".
        _dummy = _("full upgrade   (recommended)")

        # TRANSLATORS: The label of the radio button for "basic" upgrade mode.
        self.basic_upgrade_radio = QRadioButton(_("basic"))

        # keep old translated string so xgettext still extracts it
        # TRANSLATORS: The upgrade mode "basic upgrade".
        _dummy = _("basic upgrade")

        # TRANSLATORS: The tooltip of the checkbox "basic upgrade",
        # which tries to explain what this checkbox is about.
        self.basic_upgrade_radio.setToolTip(_("""Upgrades existing packages to their latest versions
without installing new dependencies or removing existing packages."""))

        self.upgrade_button_map = {
            self.full_upgrade_radio: "full-upgrade",
            self.basic_upgrade_radio: "basic-upgrade",
        }

        # radio buttons to button group
        self.upgrade_button_group.addButton(self.full_upgrade_radio)
        self.upgrade_button_group.addButton(self.basic_upgrade_radio)

        try:
            if self.settings["upgrade_type"] in ("dist-upgrade", "full-upgrade"):
                self.full_upgrade_radio.setChecked(True)
            else:
                self.basic_upgrade_radio.setChecked(True)
        except:
            self.full_upgrade_radio.setChecked(True)

        # TRANSLATORS: The label of the checkbox, where user can select
        # to use "nala" package manager instead of "apt"
        _dummy = _("use nala")
        self.use_nala_checkbox = QCheckBox("Nala")
        # TRANSLATORS: The tooltip of the checkbox "nala",
        # which tries to explain what this checkbox is about.
        self.use_nala_checkbox.setToolTip(_("Select this to use nala package manager instead of apt."))

        if self.settings["use_nala"]:
            self.use_nala_checkbox.setChecked(True)

        # frequency combo: one interval for both package refresh and auto upgrade
        # TRANSLATORS: items in the "frequency" dropdown for automatic refresh/upgrade interval.
        self._frequency_map = [
            (_("daily"),          1),
            (_("every 3 days"),   3),
            (_("weekly"),         7),
            (_("every 2 weeks"), 14),
            (_("monthly"),       30),
            (_("never"),          0),
        ]
        self._has_custom_freq_item = False
        self._custom_freq_raw = ''
        self.frequency_combo = QComboBox()
        for label, _val in self._frequency_map:
            self.frequency_combo.addItem(label)

        # grid: row 0 = full/basic/Nala, row 1 = refresh label/interval combo/automatic upgrade
        # col 0: full / refresh label
        # col 1: 16px spacer
        # col 2: basic / interval combo
        # col 3: 16px spacer
        # col 4: Nala / automatic upgrade checkbox
        # col 5: stretch
        upgrade_grid = QGridLayout()
        upgrade_grid.setHorizontalSpacing(4)
        upgrade_grid.setVerticalSpacing(2)
        upgrade_grid.setColumnMinimumWidth(1, 16)
        upgrade_grid.setColumnMinimumWidth(3, 16)
        upgrade_grid.setColumnStretch(5, 1)
        upgrade_grid.addWidget(self.full_upgrade_radio, 0, 0)
        upgrade_grid.addWidget(self.basic_upgrade_radio, 0, 2)

        #---------------------------------------------------------------
        # upgrade_frame connections
        #---------------------------------------------------------------

        # Connect button group signal to slot
        self.upgrade_button_group.buttonClicked.connect(self.on_upgrade_button_clicked)

        # Connect checkbox signals to slot
        self.use_nala_checkbox.toggled.connect(
            lambda checked: self.on_use_nala_checkbox_toggled(checked))

        #---------------------------------------------------------------
        # auto_upgrade
        #---------------------------------------------------------------

        # Allow to select only automatic upgrade

        # TRANSLATORS: Label of the checkbox to enable unattended (automatic) package upgrades.
        # "automatic upgrade" = unattended upgrade running at the interval set in the refresh selector.
        # Note: many languages use the same word for English "update" and "upgrade" -- use whichever
        # word in your language best describes "installing newer versions of existing packages".
        self.auto_upgrade_checkbox = QCheckBox(_("automatic upgrade"))

        # keep old translated strings so xgettext still extracts them
        # TRANSLATORS: Old label of the automatic upgrade checkbox -- kept for translators reference.
        _dummy = _("automatic")
        # TRANSLATORS: Old label of the automatic upgrade checkbox -- kept for translators reference.
        _dummy = _("upgrade automatically")

        # keep old translated string so xgettext still extracts it
        # TRANSLATORS: Old tooltip of the "automatic upgrade" checkbox -- kept for translators reference.
        _dummy = _("""Automatically check for package updates once a day and install them.
Only updates existing packages without changing your system configuration.
The updater icon shows the total number of updates, including automatic updates,
when additional updates are available.""")

        # TRANSLATORS: Tooltip of the "automatic upgrade" checkbox.
        # "refresh selector" = the interval dropdown to the left of this checkbox.
        # "automatic upgrade" = unattended upgrade; uses the same interval as the package list refresh.
        self.auto_upgrade_checkbox.setToolTip(_(
            "Automatically install package upgrades at the interval set in the refresh selector.\n"
            "Only upgrades existing packages without changing your system configuration.\n"
            "The updater icon shows the total number of updates, including automatic upgrades,\n"
            "when additional updates are available."))

        # TRANSLATORS: Label shown before the interval dropdown in the Upgrade mode section.
        # "refresh" = package list refresh (apt-get update). The selected interval also applies
        # to automatic upgrades when the "automatic upgrade" checkbox is checked.
        self.refresh_label = QLabel(_("refresh"))
        # TRANSLATORS: Tooltip for the "refresh" label and interval dropdown in the Upgrade mode section.
        # "automatic upgrade" refers to the checkbox to the right of the dropdown.
        self.refresh_label.setToolTip(_(
            "Sets how often the package list is refreshed (apt-get update).\n"
            "The same interval applies to automatic upgrades when\n"
            "\"automatic upgrade\" is checked."))


        # TRANSLATORS: The label of the checkbox "update automatically", where user can select
        # to perform package cache update "apt-get update" automatically
        self.auto_cache_update_checkbox = QCheckBox(self.squeeze_spaces(_t("update automatically")))

        # TRANSLATORS: The tooltip of the checkbox "update automatically",
        # which tries to explain what this checkbox is about.
        self.auto_cache_update_checkbox.setToolTip(_t("""Automatically update package cache with "apt-get update"."""))

        upgrade_grid.addWidget(self.refresh_label, 1, 0)
        upgrade_grid.addWidget(self.frequency_combo, 1, 2)
        upgrade_grid.addWidget(self.auto_upgrade_checkbox, 1, 4)

        # Does /usr/bin/nala exist and is executable
        if os.path.isfile('/usr/bin/nala') and os.access('/usr/bin/nala', os.X_OK):
            upgrade_grid.addWidget(self.use_nala_checkbox, 0, 4)
        else:
            self.use_nala_checkbox.setChecked(False)

        upgrade_layout.addLayout(upgrade_grid)

        #---------------------------------------------------------------
        # auto_upgrade checkbox set initilal state
        #---------------------------------------------------------------

        # initial auto_upgrade_checkbox and frequency_combo state
        try:
            initial_state = self.is_unattended_upgrade_enabled()
            self.auto_upgrade_checkbox.setChecked(initial_state)
        except Exception as e:
            logging.error("Initial state of auto upgrade failed: %s", str(e))

        try:
            raw_update = self._read_apt_periodic_raw('Update-Package-Lists')
            raw_upgrade = self._read_apt_periodic_raw('Unattended-Upgrade')
            # show the interval the user cares about: upgrade when auto on, refresh when off
            raw_primary = raw_upgrade if initial_state else raw_update
            days_primary = self._raw_to_days(raw_primary)
            matched = False
            if days_primary is not None and days_primary == int(days_primary):
                int_days = int(days_primary)
                for i, (_label, val) in enumerate(self._frequency_map):
                    if val == int_days:
                        self._last_freq_index = i
                        self.frequency_combo.setCurrentIndex(i)
                        matched = True
                        break
            if not matched:
                # always pass raw_update so _custom_freq_val preserves the refresh interval
                self._freq_combo_insert_custom(raw_update)
            if days_primary is not None and days_primary == 0.0:
                self.auto_upgrade_checkbox.setEnabled(False)
        except Exception as e:
            logging.error("Initial frequency state failed: %s", str(e))
        self._update_periodic_tooltip()

        #---------------------------------------------------------------
        # auto_upgrade connections
        #---------------------------------------------------------------
        self.auto_upgrade_checkbox.toggled.connect(self.on_auto_upgrade_checkbox_toggled)
        self.frequency_combo.currentIndexChanged.connect(self.on_frequency_combo_changed)

        upgrade_frame.setLayout(upgrade_layout)
        layout.addWidget(upgrade_frame)

        #---------------------------------------------------------------
        # left_click_frame: Left-click behaviour
        #---------------------------------------------------------------

        left_click_frame = QGroupBox(_("Left-click"))

        # keep old translated string so xgettext still extracts it
        # TRANSLATORS: The label of the group box for left-click behaviour.
        _dummy = _("Left-click behaviour   (when updates are available)")
        _f = left_click_frame.font(); _f.setBold(True); left_click_frame.setFont(_f)

        # TRANSLATORS: Tooltip for the Left-click behaviour group box.
        # Explains left-click behavior and the hidden middle-click action.
        left_click_frame.setToolTip(_(
            "Left-click opens the selected application.\n"
            "Middle-click opens MX Package Installer\n"
            "(or Synaptic if MX Package Installer is not installed).\n"
            "Middle-click does nothing if neither is installed."
        ))

        left_click_layout = QVBoxLayout()
        left_click_layout.setSpacing(1)  # smaller spacing between options
        # layout margins (left, top, right, bottom)
        #left_click_layout.setContentsMargins(0, 10, 0, 0)  # adjust margins
        left_click_layout.setContentsMargins(*frame_margins)  # adjust margins

        self.opens_synaptic_radio = QRadioButton("Synaptic")
        _dummy = _("opens Synaptic")
        self.opens_synaptic_radio.setToolTip(_(
            "Left-click always opens Synaptic package manager,\n"
            "with or without updates available."
        ))

        self.opens_mxpi_radio = QRadioButton("MXPI")
        self.opens_mxpi_radio.setToolTip(_(
            "Left-click always opens MX Package Installer (MXPI),\n"
            "with or without updates available."
        ))

        # TRANSLATORS: The label of the radio button to open the View and Upgrade window on left-click.
        self.opens_view_and_upgrade_radio = QRadioButton(_("View and Upgrade"))

        # TRANSLATORS: Tooltip for the "View and Upgrade" radio button.
        self.opens_view_and_upgrade_radio.setToolTip(_(
            "Left-click opens the \"View and Upgrade\" window\n"
            "when updates are available.\n"
            "When no updates are available, opens Synaptic\n"
            "or MX Package Installer instead."
        ))

        # keep old translated string so xgettext still extracts it
        # TRANSLATORS: The label of the radio button "opens MX Updater 'View and Upgrade' window".
        _dummy = _("opens MX Updater 'View and Upgrade' window")

        _synaptic_available = (os.path.isfile('/usr/bin/synaptic-pkexec') and
                               os.access('/usr/bin/synaptic-pkexec', os.X_OK))
        _mxpi_available = (os.path.isfile('/usr/bin/mx-packageinstaller') and
                           os.access('/usr/bin/mx-packageinstaller', os.X_OK))

        # Add the radio buttons to button group
        self.left_click_button_group = QButtonGroup(self)
        self.left_click_button_group.addButton(self.opens_synaptic_radio)
        self.left_click_button_group.addButton(self.opens_mxpi_radio)
        self.left_click_button_group.addButton(self.opens_view_and_upgrade_radio)

        self.left_click_button_map = {
            self.opens_synaptic_radio: "package_manager",
            self.opens_mxpi_radio: "package_installer",
            self.opens_view_and_upgrade_radio: "view_and_upgrade",
        }

        # set initial state
        _left_click = self.settings.get("left_click", "view_and_upgrade")
        if _left_click == "package_installer" and _mxpi_available:
            self.opens_mxpi_radio.setChecked(True)
        elif _left_click == "package_manager" and _synaptic_available:
            self.opens_synaptic_radio.setChecked(True)
        else:
            self.opens_view_and_upgrade_radio.setChecked(True)

        #---------------------------------------------------------------
        # left_click_frame connections
        #---------------------------------------------------------------

        # Connect button group signal to slot
        self.left_click_button_group.buttonClicked.connect(self.on_left_click_button_clicked)

        if _synaptic_available or _mxpi_available:
            left_click_h_layout = QHBoxLayout()
            if _synaptic_available:
                left_click_h_layout.addWidget(self.opens_synaptic_radio)
                left_click_h_layout.addSpacing(8)
            if _mxpi_available:
                left_click_h_layout.addWidget(self.opens_mxpi_radio)
                left_click_h_layout.addSpacing(8)
            left_click_h_layout.addWidget(self.opens_view_and_upgrade_radio)
            left_click_h_layout.addStretch()
            left_click_layout.addLayout(left_click_h_layout)
            left_click_frame.setLayout(left_click_layout)
            layout.addWidget(left_click_frame)
        else:
            self.opens_view_and_upgrade_radio.setChecked(True)

        #---------------------------------------------------------------
        # icons_frame: Icons
        #---------------------------------------------------------------
        icons_frame = QGroupBox(_("Icons"))
        _f = icons_frame.font(); _f.setBold(True); icons_frame.setFont(_f)
        icons_layout = QGridLayout()

        # layout margins (left, top, right, bottom)
        icons_layout.setContentsMargins(*frame_margins)
        icons_layout.setHorizontalSpacing(8)
        icons_layout.setVerticalSpacing(2)
        icons_layout.setColumnMinimumWidth(1, 32)
        icons_layout.setColumnMinimumWidth(3, 32)
        icons_layout.setColumnStretch(5, 1)

        self.icon_radio_buttons = {}  # name -> icon radio button

        #self.wireframe_transparent_checkbox = QCheckBox(_("use transparent interior for no-updates wireframe"))

        # TRANSLATORS: Please keep it short. Detailed explanation is in the tooltip.
        # "dark/light" refers to the dark and light wireframe icon variants (the no-updates
        # icons). The "some-updates" wireframe icon is always the green app icon, unaffected.
        _dummy = _("transparent wireframe for no updates")
        # TRANSLATORS: Short label for checkbox. "black/white" refers to the two wireframe
        # no-updates icon variants (wireframe-dark has white wires, wireframe-light has black wires).
        # Detailed explanation is in the tooltip.
        self.wireframe_transparent_checkbox = QCheckBox(_("transparent black/white wireframe"))

        # TRANSLATORS: Tooltip for "transparent black/white wireframe" checkbox.
        # Explains when transparent works and when it does not, depending on panel color.
        # "wireframe-dark" has white wires; "wireframe-light" has black/dark wires.
        self.wireframe_transparent_checkbox.setToolTip(_("""Make the black/white wireframe no-updates icon transparent (no color fill).
Works best when panel color contrasts with the wire color:
  wireframe-dark (white wires): visible on dark panel, invisible on light panel.
  wireframe-light (black wires): visible on light panel, invisible on dark panel."""))

        # TRANSLATORS: The strings "dark" or "light" are used for the "dark" or "light" icon-set, respectively.
        dark_string = _("dark")

        # TRANSLATORS: The strings "dark" or "light" are used for the "dark" or "light" icon-set, respectively.
        light_string = _("light")
        # TRANSLATORS: The wireframe icon-set
        wireframe_string = _("wireframe")
        # TRANSLATORS: The pulse icon-set
        pulse_string = _("pulse")
        # TRANSLATORS: The classic icon-set
        classic_string = _("classic")

        for row_idx, icon_name in enumerate(self.icon_order):
            #print(f"Icon_Look(name)={icon_name}")
            icon = self.icon_set.get(icon_name)
            icon_label_string = icon.get('label')
            icon_some = icon.get('icon_some')
            icon_none = icon.get('icon_none')

            if icon_label_string == "wireframe dark":
                icon_label = f"{wireframe_string} -- {dark_string}"
            elif icon_label_string == "wireframe light":
                icon_label = f"{wireframe_string} -- {light_string}"
            elif icon_label_string == "pulse light":
                icon_label = f"{pulse_string} -- {light_string}"
            else:
                icon_label = _(icon_label_string)

            icon_radio_button = QRadioButton(icon_label)
            icon_radio_button.toggled.connect(
                lambda checked, label=icon_label, name=icon_name:
                self.on_icon_radio_button_toggled(checked, label, name))
            #print(f"Icon_Look(icon_name={icon_name}")
            icon_look= self.settings.get('icon_look')
            #print(f"self.settings.get('icon_look')={icon_look}")

            if icon_name == icon_look:
                icon_radio_button.setChecked(True)

            icons_layout.addWidget(icon_radio_button, row_idx, 0)
            icons_layout.addWidget(self.create_icon_label(icon_some), row_idx, 2)
            icons_layout.addWidget(self.create_icon_label(icon_none), row_idx, 4)
            self.icon_radio_buttons[icon_name] = icon_radio_button  # Store the radio button

        # single admin icon set: show for preview but prevent deselection
        if len(self.icon_order) == 1:
            for btn in self.icon_radio_buttons.values():
                btn.setEnabled(False)

        # wireframe transparent toggle: hidden for admin icon sets, otherwise
        # enabled only when the selected icon provides a transparent variant
        _admin_active = self.settings_manager.admin_icon_set_count > 0
        _cur_icon_cfg = self.settings_manager.get_icon_look_config(
            self.settings.get('icon_look', ''))
        _wireframe_enabled = (not _admin_active
                              and 'icon_none_transparent' in _cur_icon_cfg)

        # set connection
        self.wireframe_transparent_checkbox.toggled.connect(
            lambda checked:
            self.on_wireframe_transparent_checkbox_toggled(checked))

        if self.settings["wireframe_transparent"]:
            self.wireframe_transparent_checkbox.setChecked(True)

        self.wireframe_transparent_checkbox.setEnabled(_wireframe_enabled)
        self.wireframe_transparent_checkbox.setVisible(not _admin_active)
        icons_layout.addWidget(self.wireframe_transparent_checkbox, len(self.icon_order), 0, 1, 5)
        icons_frame.setLayout(icons_layout)
        layout.addWidget(icons_frame)

        #---------------------------------------------------------------
        # Frame: Other options
        #---------------------------------------------------------------
        _dummy = _("Other options")
        other_options_frame = QGroupBox(_("Other"))
        _f = other_options_frame.font(); _f.setBold(True); other_options_frame.setFont(_f)
        other_options_layout = QVBoxLayout()
        other_options_layout.setSpacing(1)  # small spacing between options

        other_options_layout.setContentsMargins(*frame_margins)  # with frame margins

        #---------------------------------------------------------------
        # upgrade_assume_yes_checkbox
        #---------------------------------------------------------------

        # TRANSLATORS: The label of the checkbox, where user can select
        # that "--assume-yes" option is added, so "apt" or "nala" won't ask
        # for confirmations to install
        checkbox_label = _("skip upgrade confirmation")
        _dummy = _("automatically confirm all package upgrades")

        # TRANSLATORS: The tooltip of the checkbox "automatically confirm all package upgrades",
        # which tries to explain what this checkbox is about.
        checkbox_tooltip = _("Automatically answers 'yes' to package management prompts during upgrades.\n"
            "Some system configuration changes may still require manual confirmation."
            )

        self.upgrade_assume_yes_checkbox = QCheckBox(checkbox_label)
        self.upgrade_assume_yes_checkbox.setToolTip(checkbox_tooltip)


        # set connection
        self.upgrade_assume_yes_checkbox.toggled.connect(
            lambda checked:
            self.on_upgrade_assume_yes_checkbox_toggled(checked))
        if self.settings.get("upgrade_assume_yes"):
            self.upgrade_assume_yes_checkbox.setChecked(True)

        #---------------------------------------------------------------
        # start_8_login_checkbox

        # TRANSLATORS: The label of the checkbox where user can select
        #to start 'MX Updater' systray icon automatically at login
        #after a delay of seconds.
        checkbox_label = _("autostart delay")
        _dummy = _('start "MX Updater" at login after delay')

        # TRANSLATORS: Explains the start of MX Updater automatically
        # after a delay of seconds. The user can select the number of seconds.
        checkbox_tooltip = _("""Select this to launch "MX Updater" automatically
at login after the specified delay in seconds.""")

        self.start_8_login_checkbox = QCheckBox(checkbox_label)
        self.start_8_login_checkbox.setToolTip(checkbox_tooltip)
        settings_key = "Settings/start_at_login"
        #---------------------------------------------------------------
        # start_8_login_delay spinbox
        self.start_8_login_delay_spinbox = QSpinBox()

        self.start_8_login_delay_spinbox.setRange(1, 60)

        # Load value from settings, or set default
        default_value = 5
        value = self.qsettings.value(f"{settings_key}_delay", default_value, type=int)
        value = default_value if value < 0 else min(value, 60)
        self.start_8_login_delay_spinbox.setValue(value)

        seconds_suffix = _("sec")
        self.start_8_login_delay_spinbox.setSuffix(f" {seconds_suffix}")

        # Connect spinbox value change to save method
        #self.start_8_login_delay_spinbox.valueChanged.connect(self.save_start_8_login_delay)

        self.start_8_login_delay_spinbox.valueChanged.connect(
            lambda value: self.qsettings.setValue(f"{settings_key}_delay", value)
            )

        # Initial state setup
        start_8_login = self.qsettings.value(settings_key, "true")
        start_8_login = start_8_login.lower() in ["true","on", "1"]
        self.start_8_login_checkbox.setChecked(start_8_login)
        self.start_8_login_delay_spinbox.setEnabled(start_8_login)

        self.start_8_login_checkbox.toggled.connect(
            lambda checked: self.start_8_login_delay_spinbox.setEnabled(checked))
        self.start_8_login_checkbox.toggled.connect(
            lambda checked: self.qsettings.setValue(settings_key, checked))

       # horizontal layout for checkbox and spinbox
        start_8_login_layout = QHBoxLayout()
        start_8_login_layout.addWidget(self.start_8_login_checkbox)
        start_8_login_layout.addWidget(self.start_8_login_delay_spinbox)
        start_8_login_layout.addStretch()
        #---------------------------------------------------------------
        # auto_close_checkbox

        # TRANSLATORS: After the idle time in seconds has elapsed,
        # the terminal window is closed once the system update is complete.
        checkbox_label = _("terminal auto-close")
        _dummy = _("close terminal window after idle time")

        # TRANSLATORS: Explains the auto-close behavior of terminal window
        # after system updates with a configurable idle time
        checkbox_tooltip = _("Automatically closes the terminal window after the specified\n"
                             "number of seconds of inactivity following system updates.")

        self.auto_close_checkbox = QCheckBox(checkbox_label)
        self.auto_close_checkbox.setToolTip(checkbox_tooltip)

        #---------------------------------------------------------------

        # auto_close_timeout spinbox
        self.auto_close_timeout = QSpinBox()

        self.auto_close_timeout.setRange(AUTO_CLOSE_TIMEOUT_MIN, AUTO_CLOSE_TIMEOUT_MAX)

        # Load value from settings, or set default
        #auto_close_timeout = self.qsettings.value("Settings/auto_close_timeout", 10, type=int)
        auto_close_timeout = self.load_setting("auto_close_timeout")
        self.auto_close_timeout.setValue(auto_close_timeout)

        seconds_suffix = _("sec")
        self.auto_close_timeout.setSuffix(f" {seconds_suffix}")

        # Connect spinbox value change to save method
        self.auto_close_timeout.valueChanged.connect(self.save_auto_close_timeout)

        # Initial state setup
        #self.toggle_auto_close_timeout_spinbox(self.auto_close_checkbox.checkState())

        # Connect checkbox state change to enable/disable spinbox
        #self.auto_close_checkbox.stateChanged.connect(self.toggle_auto_close_timeout_spinbox)

        #---------------------------------------------------------------
        # set inital state
        if self.settings.get("auto_close"):
            self.auto_close_checkbox.setChecked(True)
            self.auto_close_timeout.setEnabled(True)
        else:
            self.auto_close_timeout.setEnabled(False)
            self.auto_close_checkbox.setChecked(False)

        #---------------------------------------------------------------
        # set connection
        self.auto_close_checkbox.toggled.connect(
            lambda checked:
            self.on_auto_close_checkbox_toggled(checked)
            )

       # horizontal layout to place checkbox and spinbox together
        timeout_layout = QHBoxLayout()
        timeout_layout.addWidget(self.auto_close_checkbox)
        timeout_layout.addWidget(self.auto_close_timeout)
        timeout_layout.addStretch()  # Pushes items to the left

        #---------------------------------------------------------------
        # terminal size and position -- single line with two comboboxes

        # TRANSLATORS: Label before the terminal window size dropdown
        terminal_label = QLabel(_("terminal size"))

        self.terminal_size_combo = QComboBox()
        # TRANSLATORS: terminal window size option: default auto-sizing (original behavior, 2/3 screen capped on large displays)
        self.terminal_size_combo.addItem(_("default"),    "default")
        # TRANSLATORS: terminal window size option: let terminal use its own saved size/position
        self.terminal_size_combo.addItem(_("own"), "own")
        # TRANSLATORS: terminal window size option: one third of the screen
        self.terminal_size_combo.addItem(_("1/3 screen"),    "one-third")
        # TRANSLATORS: terminal window size option: half the screen
        self.terminal_size_combo.addItem(_("1/2 screen"),    "half")
        # TRANSLATORS: terminal window size option: two thirds of the screen
        self.terminal_size_combo.addItem(_("2/3 screen"),    "two-thirds")
        # TRANSLATORS: terminal window size option: three quarters of the screen
        self.terminal_size_combo.addItem(_("3/4 screen"),    "three-quarters")
        # TRANSLATORS: terminal window size option: nine tenths of the screen
        self.terminal_size_combo.addItem(_("9/10 screen"),   "nine-tenths")
        # TRANSLATORS: terminal window size option: full screen
        _dummy = _("full screen")  # not shown: unreliable on Fluxbox (tint panel) and XFCE (window border)

        # TRANSLATORS: Label before the terminal window position dropdown
        self.terminal_position_label = QLabel(_("position"))

        self.terminal_position_combo = QComboBox()
        # TRANSLATORS: terminal window position option
        self.terminal_position_combo.addItem(_("center"),       "center")
        # TRANSLATORS: terminal window position option
        self.terminal_position_combo.addItem(_("top-left"),     "top-left")
        # TRANSLATORS: terminal window position option
        self.terminal_position_combo.addItem(_("top-right"),    "top-right")
        # TRANSLATORS: terminal window position option
        self.terminal_position_combo.addItem(_("bottom-left"),  "bottom-left")
        # TRANSLATORS: terminal window position option
        self.terminal_position_combo.addItem(_("bottom-right"), "bottom-right")

        # TRANSLATORS: Tooltip for the terminal window size label and dropdown.
        # "default (auto)" means the original automatic sizing is used.
        _terminal_size_tooltip = _("""Terminal size and position for system updates:
"default": 2/3 screen size, capped at 960x600 pixels.
"own": use terminal's own settings for size and position.""")
        terminal_label.setToolTip(_terminal_size_tooltip)
        self.terminal_size_combo.setToolTip(_terminal_size_tooltip)

        # TRANSLATORS: Tooltip for the terminal window position dropdown.
        # The option is disabled when "full screen" size is selected, or on Wayland.
        self.terminal_position_combo.setToolTip(_("""Select where the terminal window appears on the screen.
Hidden when "own" or "full screen" size is selected, or when running on Wayland."""))

        # load saved values; revert "full" (disabled) to "default"
        terminal_size = self.load_setting("terminal_size")
        if terminal_size == "full":
            terminal_size = "default"
            self.qsettings.setValue("Settings/terminal_size", terminal_size)
        idx = self.terminal_size_combo.findData(terminal_size)
        if idx >= 0:
            self.terminal_size_combo.setCurrentIndex(idx)

        terminal_position = self.load_setting("terminal_position")
        idx = self.terminal_position_combo.findData(terminal_position)
        if idx >= 0:
            self.terminal_position_combo.setCurrentIndex(idx)

        # position not supported on Wayland; also irrelevant when "own"
        _on_wayland = (os.environ.get("XDG_SESSION_TYPE") == "wayland"
                       or bool(os.environ.get("WAYLAND_DISPLAY")))
        if _on_wayland or terminal_size == "own":
            self.terminal_position_label.setVisible(False)
            self.terminal_position_combo.setVisible(False)


        self.terminal_size_combo.currentIndexChanged.connect(self.on_terminal_size_changed)
        self.terminal_position_combo.currentIndexChanged.connect(self.on_terminal_position_changed)

        terminal_layout = QHBoxLayout()
        terminal_layout.addWidget(terminal_label)
        terminal_layout.addSpacing(4)
        terminal_layout.addWidget(self.terminal_size_combo)
        terminal_layout.addSpacing(8)
        terminal_layout.addWidget(self.terminal_position_label)
        terminal_layout.addSpacing(4)
        terminal_layout.addWidget(self.terminal_position_combo)
        terminal_layout.addStretch()

        # TRANSLATORS: The label of the checkbox "desktop notifications",
        # where user can select to disabled or enable notification "popup" window shown .
        _dummy = _("use desktop notifications")
        # TRANSLATORS: Short label for checkbox to enable or disable desktop notification popups.
        self.use_dbus_notifications_checkbox = QCheckBox(_("desktop notifications"))

        # TRANSLATORS: The tooltip of of the checkbox "use desktop notifications",
        # which tries to exlpain what this checkbox is about.
        self.use_dbus_notifications_checkbox.setToolTip(_("""In addition to change of the tray icon,
pop-up messages will be shown when
system updates are available."""))

       # set connection
        self.use_dbus_notifications_checkbox.toggled.connect(
            lambda checked:
            self.on_use_dbus_notifications_checkbox_toggled(checked))

        if self.settings.get("use_dbus_notifications"):
            self.use_dbus_notifications_checkbox.setChecked(True)

        #---------------------------------------------------------------
        # hide_until_upgrades_available_checkbox
        # TRANSLATORS: The label of the checkbox, where user can select to not show
        # the "MX Updater" systray icon when no package upgrades are available.
        _dummy = _("Hide until updates available")
        # TRANSLATORS: Short label for checkbox. Hides the systray icon when no
        # package upgrades are available.
        self.hide_until_upgrades_available_checkbox = QCheckBox(_("hide when no updates"))

        # TRANSLATORS: The tooltip of of the checkbox "hide until updates available",
        # which tries to exlpain what this checkbox is about.
        self.hide_until_upgrades_available_checkbox.setToolTip(_("""Hide the system update icon when no updates are available.
Untick this box or run "MX Updater" from the menu to make the icon visible again."""))

        # set inital state
        if self.settings.get("hide_until_upgrades_available"):
            self.hide_until_upgrades_available_checkbox.setChecked(True)
        else:
            self.hide_until_upgrades_available_checkbox.setChecked(False)

        # set connection
        self.hide_until_upgrades_available_checkbox.toggled.connect(
            lambda checked:
            self.on_hide_until_upgrades_available_checkbox_toggled(checked))


        if self.settings.get("hide_until_upgrades_available"):
            self.hide_until_upgrades_available_checkbox.setChecked(True)

        #---------------------------------------------------------------
        other_options_layout.addLayout(terminal_layout)
        #other_options_layout.addWidget(self.auto_close_checkbox)
        other_options_layout.addLayout(timeout_layout)
        other_options_layout.addWidget(self.upgrade_assume_yes_checkbox)
        # comment the line below to hide the desktop notifications toggle
        other_options_layout.addWidget(self.use_dbus_notifications_checkbox)
        other_options_layout.addLayout(start_8_login_layout)
        if not any(self.disable_hide_until):
            other_options_layout.addWidget(self.hide_until_upgrades_available_checkbox)

        other_options_frame.setLayout(other_options_layout)
        layout.addWidget(other_options_frame)

        #---------------------------------------------------------------
        # Create a QDialogButtonBox
        """
        self.button_box = QDialogButtonBox(self)
        self.button_box.setStandardButtons(
             QDialogButtonBox.StandardButton.Help
            #|QDialogButtonBox.StandardButton.Cancel
            |QDialogButtonBox.StandardButton.Close
            #|QDialogButtonBox.StandardButton.Ok,
            )

        # Get the Close button
        self.close_button = self.button_box.button(
            QDialogButtonBox.StandardButton.Close
        )

        # Get the Help button
        self.help_button = self.button_box.button(
            QDialogButtonBox.StandardButton.Help
        )
        """
        self.button_box = QDialogButtonBox(self)

        #---------------------------------------------------------------
        # Close button
        self.close_button = self.button_box.addButton(QDialogButtonBox.StandardButton.Close)

        close_text_untranslated = "&Close"
        close_button_text = self.close_button.text()
        # translate close button label
        if  close_button_text == close_text_untranslated:
            #try gettext translation
            close_text = _(close_text_untranslated)

            if  close_text != close_text_untranslated:
                # Set translated text
                self.close_button.setText(close_text)

        # slots
        self.close_button.clicked.connect(self.on_close)

        # set here so we get translations
        updater_help = _("MX Updater Help")

        """
        # TODO:  when we have an actual help content
        #---------------------------------------------------------------
        # Help button
        self.help_button = self.button_box.addButton(QDialogButtonBox.StandardButton.Help)

        help_button_text = self.help_button.text()

        # translate help button label
        if not '&' in help_button_text:
            # translate help button text
            help_text = _("&Help")
            self.help_button.setText(help_text)

        # slot
        self.help_button.clicked.connect(self.on_close)
        #---------------------------------------------------------------
        """

        # button box to layout
        layout.addWidget(self.button_box)
        self.close_button.setFocus()


    #---------------------------------------------------------------
    def on_left_click_radio_toggled(self, checked, left_click):
        if checked:
            # store upgrade type in dictonry
            self.settings["left_click"] = left_click

    def on_left_click_button_clicked(self):
        # get selected button
        radio_button = self.left_click_button_group.checkedButton()
        if radio_button:
            left_click = self.left_click_button_map[radio_button]
            #print(f"toggled left_click: {left_click}")
        else:
            left_click = "view_and_upgrade"
            #print(f"set default left_click: {left_click}")

        #self.qsettings.setValue(f"Settings/left_click", left_click)
        self.settings['left_click'] = left_click
        self.save_setting("left_click", left_click)

        # Attempt to update the running apt-systray icon via D-Bus
        self.update_systray_icon('left_click', left_click)


    #---------------------------------------------------------------
    def on_upgrade_button_clicked(self):
        # get selected button
        radio_button = self.upgrade_button_group.checkedButton()
        if radio_button:
            upgrade_type = self.upgrade_button_map[radio_button]
            #print(f"toggled upgrade_type: {upgrade_type}")
        else:
            upgrade_type = "full-upgrade"

        if  upgrade_type != "full-upgrade":
            upgrade_type = "upgrade"

        self.settings['upgrade_type'] = upgrade_type
        self.qsettings.setValue(f"Settings/upgrade_type", upgrade_type)
        self.qsettings.sync()

        # Attempt to update the running apt-systray icon via D-Bus
        self.update_systray_icon('upgrade_type', upgrade_type)

        # check states of checkboxes
        #self.settings['use_nala'] =  True if self.use_nala_checkbox.isChecked() else False
        #print(f"use_nala: {self.settings.get('use_nala')}")

        #self.settings['auto_update'] =  True if self.auto_upgrade_checkbox.isChecked() else False
        #print(f"auto_update: {self.settings.get('auto_update')}")

    #---------------------------------------------------------------
    def update_systray_icon(self, key, value):
        me = "update_systray_icon"

        if not self._init_done:
            return

        if not self.dbus_call_back:
            self.dbus_call_back = True
            #print(f"No dbus callback for: {key} = {value}")
            return

        logger.debug("[%s] Try to update systray icon via dbus with: %s=%s", me, key, value)
        try:
            # Connect to the session bus
            bus = dbus.SessionBus()

            try:
                # dbus proxy object
                proxy = bus.get_object(TRAYICON_OBJECT_NAME, TRAYICON_OBJECT_PATH)

                # dbus interface
                interface = dbus.Interface(proxy, TRAYICON_OBJECT_IFACE)

                # method call to update systray icon settings with  key/value
                interface.SetValue(str(key), str(value))

            except dbus.exceptions.DBusException as e:
                # handle systray icon is not running
                #print(f"Systray icon not running: {e}")
                logger.debug("[%s] Systray icon not running or dbus error.", me)
                pass

        except Exception as e:
            #print(f"Unexpected D-Bus error: {e}")
            logger.debug("[%s] Unexpected D-Bus error: %s", me. e)


    #---------------------------------------------------------------
    def update_view_and_upgrade(self, key, value):

        me = "update_view_and_upgrade@UpdaterSettings"
        logger.debug("[%s] About to update view_and_upgrade dialog via dbus: %s = %s", me, key, value)
        logger.debug("[%s] dbus_call_back is %s", me, self.dbus_call_back)

        if not self.dbus_call_back:
            logger.debug("[%s] No callback to update view_and_upgrade dialog via dbus: %s = %s", me, key, value)
            logger.debug("[%s] reset dbus_call_back to %s", me, not self.dbus_call_back)
            self.dbus_call_back = True

            return


        prefix = 'no-dbus-callback@'

        logger.debug("Try update_view_and_upgrade via dbus: %s = %s", key, value)

        keys = ('auto_close',
                'auto_close_timeout',
                'upgrade_assume_yes',
                'use_nala',
                )
        if key not in keys:
            return

        bool_keys = (
            'auto_close',
            'use_nala',
            'upgrade_assume_yes',
        )

        if key in bool_keys:
            if isinstance(value, str):
                value = value.lower() in ("true", "yes", "on", "1")
                value = str(value).lower()
            elif isinstance(value, bool):
                value = str(value).lower()
                pass
            elif isinstance(value, int):
                value = bool(value)
                value = str(value).lower()
            else:
                return

        key = f'{prefix}{key}'

        logger.debug("[%s] Try update view_and_upgrade dialog via dbus: %s = %s", me, key, value)
        logger.debug("[%s] dbus-call %s %s %s.SetValue %s %s", me,
                    VIEW_AND_UPGRADE_OBJECT_NAME,
                    VIEW_AND_UPGRADE_OBJECT_PATH,
                    VIEW_AND_UPGRADE_OBJECT_IFACE,
                    key,
                    value,
                    )

        try:
            # Connect to the session bus
            bus = dbus.SessionBus()

            try:
                # dbus proxy object
                proxy = bus.get_object(
                        VIEW_AND_UPGRADE_OBJECT_NAME,
                        VIEW_AND_UPGRADE_OBJECT_PATH
                    )

                # dbus interface
                interface = dbus.Interface(proxy, VIEW_AND_UPGRADE_OBJECT_IFACE)

                # method call to update settings with key/value
                interface.SetValue(str(key), str(value))

            except dbus.exceptions.DBusException as service_error:
                logger.debug("[%s] view_and_upgrade dialog does not appear to be active.", me)
                pass

        except Exception as e:
            logger.error("[%s] Unexpected D-Bus error: %r", me, e)
            pass

    #---------------------------------------------------------------
    def on_use_nala_checkbox_toggled(self, checked):
        # store use_nala selection into settings
        self.settings["use_nala"] = checked
        self.qsettings.setValue(f"Settings/use_nala", checked)
        self.qsettings.sync()
        self.update_view_and_upgrade("use_nala", checked)
        #print(f"toggled use_nala: {checked}")

    def on_auto_upgrade_checkbox_toggledXXX(self, checked):
        # store auto_update selection into settings
        #self.settings["auto_update"] = checked
        #self.qsettings.setValue(f"Settings/auto_update", checked)
        logger.debug("toggled auto_update: %s", checked)
        self.apply_auto_upgrade_checkbox_toggled(checked)

    def on_auto_upgrade_checkbox_toggled(self, checked):
        if self._auto_upgrade_state_is_updating or self._periodic_updating:
            return

        logger.debug("toggled auto_upgrade: %s", checked)
        try:
            self._auto_upgrade_state_is_updating = True

            current_state = self.is_unattended_upgrade_enabled()
            if current_state == checked:
                return

            success = self.apply_auto_upgrade_checkbox_toggled(checked)

            if success:
                self.update_systray_icon("auto_upgrade", str(checked).lower())
            else:
                self.auto_upgrade_checkbox.setChecked(current_state)

        except Exception as e:
            logging.error("Toggle error: %r", e)
            current_state = self.is_unattended_upgrade_enabled()
            self.auto_upgrade_checkbox.setChecked(current_state)
            error = _("Error")
            message = _("An unexpected error occurred:")
            self.show_message(error, f"{message} {e}", QMessageBox.Icon.Critical)

        finally:
            self._auto_upgrade_state_is_updating = False

    def show_message(self, title, message, icon):
        """
        Display a message box with given parameters

        :param title: Message box title
        :param message: Message content
        :param icon: QMessageBox icon type
        """
        msg_box = QMessageBox(self)
        msg_box.setWindowTitle(title)
        msg_box.setText(message)
        msg_box.setIcon(icon)
        msg_box.exec()



    def _read_apt_periodic_raw(self, key):
        cmd = ['apt-config', 'shell', 'val', f'APT::Periodic::{key}']
        result = subprocess.run(cmd, capture_output=True, text=True)
        match = re.search(r"val='([^']*)'", result.stdout)
        return match.group(1).strip() if match else ''

    def _raw_to_days(self, raw):
        if not raw:
            return 0.0
        if raw == 'always':
            return None
        m = re.match(r'^(\d+)([smhd]?)$', raw)
        if not m:
            return None
        n, unit = int(m.group(1)), m.group(2) or 'd'
        if unit == 'd': return float(n)
        if unit == 'h': return n / 24.0
        if unit == 'm': return n / 1440.0
        if unit == 's': return n / 86400.0
        return None

    def _normalize_for_write(self, raw):
        if not raw:
            return '0'
        if raw == 'always':
            return 'always'
        days = self._raw_to_days(raw)
        if days is None:
            return raw
        if days == int(days):
            return str(int(days))
        return raw

    def _freq_label_for(self, val):
        for label, v in self._frequency_map:
            if v == val:
                return label
        return "%dd" % val

    def _raw_periodic_label(self, raw):
        if not raw:
            return self._freq_label_for(0)
        if raw == 'always':
            return 'always'
        days = self._raw_to_days(raw)
        if days is None:
            return 'custom (%s)' % raw
        if days == int(days):
            int_days = int(days)
            for label, v in self._frequency_map:
                if v == int_days:
                    return label
            return '%dd' % int_days
        # non-integer days: keep original unit notation
        m = re.match(r'^(\d+)([smhd])$', raw)
        return ('%s%s' % (m.group(1), m.group(2))) if m else 'custom (%s)' % raw

    def _freq_combo_insert_custom(self, raw_update):
        self._custom_freq_raw = self._normalize_for_write(raw_update)
        raw_upgrade = self._read_apt_periodic_raw('Unattended-Upgrade')
        raw_display = raw_upgrade if self.auto_upgrade_checkbox.isChecked() else raw_update
        # TRANSLATORS: Shown in the frequency combo when the current apt-config value does not
        # match any preset (daily/weekly/etc). %s is a compact interval like "5d", "12h", or "always".
        item_text = _("custom (%s)") % self._raw_periodic_label(raw_display)
        self.frequency_combo.insertItem(0, item_text)
        self.frequency_combo.model().item(0).setEnabled(False)
        self.frequency_combo.setCurrentIndex(0)
        self._has_custom_freq_item = True

    def _update_periodic_tooltip(self):
        raw_update = self._read_apt_periodic_raw('Update-Package-Lists')
        raw_upgrade = self._read_apt_periodic_raw('Unattended-Upgrade')
        if self._has_custom_freq_item:
            raw_display = raw_upgrade if self.auto_upgrade_checkbox.isChecked() else raw_update
            days_display = self._raw_to_days(raw_display)
            preset_matched = False
            if days_display is not None and days_display == int(days_display):
                int_days = int(days_display)
                for i, (_label, val) in enumerate(self._frequency_map):
                    if val == int_days:
                        prev = self._periodic_updating
                        self._periodic_updating = True
                        try:
                            self.frequency_combo.removeItem(0)
                            self._has_custom_freq_item = False
                            self.frequency_combo.setCurrentIndex(i)
                            self._last_freq_index = i
                        finally:
                            self._periodic_updating = prev
                        preset_matched = True
                        break
            if not preset_matched:
                self.frequency_combo.setItemText(0,
                    _("custom (%s)") % self._raw_periodic_label(raw_display))
        # TRANSLATORS: One-line header in the frequency combo tooltip showing current effective
        # apt-config values. First %s = refresh interval, second %s = upgrade interval,
        # each wrapped in parentheses, e.g. "(daily)", "(5d)", "(always)", "(never)".
        current_section = _("Current (apt-config): refresh %s, upgrade %s") % (
            "(%s)" % self._raw_periodic_label(raw_update),
            "(%s)" % self._raw_periodic_label(raw_upgrade))
        # TRANSLATORS: Body text of the refresh interval combo tooltip explaining what the selector does.
        # "automatic upgrade" refers to the checkbox next to the dropdown. "never" is the last combo option.
        static_section = _(
            "Sets the interval for package list refresh (apt-get update).\n"
            "When 'automatic upgrade' is checked, the same interval also applies to the upgrade.\n"
            "'never' disables both the refresh and the automatic upgrade.")
        self.frequency_combo.setToolTip(current_section + "\n\n" + static_section)

    def _revert_combo_to_effective(self, raw_update):
        # save/restore _periodic_updating so we don't prematurely clear it
        # if called from inside on_frequency_combo_changed
        prev = self._periodic_updating
        self._periodic_updating = True
        try:
            if self._has_custom_freq_item:
                self.frequency_combo.removeItem(0)
                self._has_custom_freq_item = False
            raw_upgrade = self._read_apt_periodic_raw('Unattended-Upgrade')
            raw_primary = raw_upgrade if self.auto_upgrade_checkbox.isChecked() else raw_update
            days = self._raw_to_days(raw_primary)
            matched = False
            if days is not None and days == int(days):
                int_days = int(days)
                for i, (_label, val) in enumerate(self._frequency_map):
                    if val == int_days:
                        self.frequency_combo.setCurrentIndex(i)
                        self._last_freq_index = i
                        matched = True
                        break
            if not matched:
                self._freq_combo_insert_custom(raw_update)
        finally:
            self._periodic_updating = prev

    def _verify_periodic(self, update_raw, upgrade_raw):
        read_update = self._read_apt_periodic_raw('Update-Package-Lists')
        read_upgrade = self._read_apt_periodic_raw('Unattended-Upgrade')
        def _days_match(written, read):
            if written == 'always':
                return read == 'always'
            dw = self._raw_to_days(written)
            dr = self._raw_to_days(read)
            return dw is not None and dr is not None and abs(dw - dr) < 0.001
        if _days_match(update_raw, read_update) and _days_match(upgrade_raw, read_upgrade):
            return True
        # TRANSLATORS: Warning shown when a higher-priority apt configuration overrides
        # the setting written by mx-updater. %s values are labels like "daily", "never", "12h".
        msg = _("Could not apply setting: a higher-priority apt configuration\n"
                "is overriding the effective values.\n\n"
                "Requested: refresh %s, upgrade %s\n"
                "Effective: refresh %s, upgrade %s") % (
                    self._raw_periodic_label(update_raw), self._raw_periodic_label(upgrade_raw),
                    self._raw_periodic_label(read_update), self._raw_periodic_label(read_upgrade))
        msg_box = QMessageBox(self)
        msg_box.setIcon(QMessageBox.Icon.Warning)
        # TRANSLATORS: Title of the warning dialog shown when an apt configuration file
        # with higher priority is overriding the value written by mx-updater.
        msg_box.setWindowTitle(_("Setting Overridden"))
        msg_box.setText(msg)
        msg_box.exec()
        self._revert_combo_to_effective(read_update)
        return False

    def apply_periodic(self, update_raw, upgrade_raw, action='auto-update-periodic'):
        logging.debug("apply_periodic: refresh=%s upgrade=%s action=%s", update_raw, upgrade_raw, action)
        try:
            cmd = ['/usr/bin/pkexec',
                   f'/usr/lib/mx-updater/actions/{action}',
                   str(update_raw), str(upgrade_raw)]
            subprocess.run(cmd, check=True, capture_output=True, text=True)
            result = self._verify_periodic(update_raw, upgrade_raw)
            self._update_periodic_tooltip()
            return result
        except subprocess.CalledProcessError as e:
            # TRANSLATORS: Title of the error dialog shown when the pkexec action script
            # to set apt periodic upgrade configuration fails (non-zero exit).
            title = _("Unattended Upgrades Configuration Failed")
            # TRANSLATORS: Body text of the error dialog for a failed apt periodic configuration write.
            message = _("Could not change automatic upgrades configuration")
            # TRANSLATORS: Label in the error detail line, followed by exit code and stderr output,
            # e.g. "Command failed [1]: error message".
            command_failed = _("Command failed")
            details = f"{command_failed} [{e.returncode}]: {str(e.stderr)}"
            self.show_error_popup(title=title, message=message, details=details)
            return False

    def on_frequency_combo_changed(self, index):
        if self._periodic_updating:
            return
        if self._has_custom_freq_item and index == 0:
            return
        self._periodic_updating = True
        try:
            # compute map index without removing the custom item yet --
            # removal only happens on success so a cancelled auth reverts cleanly
            map_index = index - 1 if self._has_custom_freq_item else index
            freq = self._frequency_map[map_index][1]
            if freq == 0:
                self.auto_upgrade_checkbox.setEnabled(False)
                success = self.apply_periodic('0', '0')
                if success:
                    if self._has_custom_freq_item:
                        self.frequency_combo.removeItem(0)
                        self._has_custom_freq_item = False
                    self.auto_upgrade_checkbox.setChecked(False)
                    self._last_freq_index = map_index
                else:
                    self.auto_upgrade_checkbox.setEnabled(True)
                    revert = 0 if self._has_custom_freq_item else self._last_freq_index
                    self.frequency_combo.setCurrentIndex(revert)
            else:
                self.auto_upgrade_checkbox.setEnabled(True)
                upgrade_raw = str(freq) if self.auto_upgrade_checkbox.isChecked() else '0'
                success = self.apply_periodic(str(freq), upgrade_raw)
                if success:
                    if self._has_custom_freq_item:
                        self.frequency_combo.removeItem(0)
                        self._has_custom_freq_item = False
                    self.update_systray_icon("auto_upgrade",
                        str(self.auto_upgrade_checkbox.isChecked()).lower())
                    self._last_freq_index = map_index
                else:
                    revert = 0 if self._has_custom_freq_item else self._last_freq_index
                    self.frequency_combo.setCurrentIndex(revert)
        finally:
            self._periodic_updating = False

    def apply_auto_upgrade_checkbox_toggled(self, checked):
        if self._has_custom_freq_item:
            update_raw = self._custom_freq_raw
        else:
            freq_index = self.frequency_combo.currentIndex()
            update_raw = str(self._frequency_map[freq_index][1])
        upgrade_raw = update_raw if checked else '0'
        action = 'auto-update-periodic-enable' if checked else 'auto-update-periodic-disable'
        return self.apply_periodic(update_raw, upgrade_raw, action)

    def show_error_popup(self, title, message, details=None):
        """
        Display an error popup
        """
        from PyQt6.QtWidgets import QMessageBox

        error_dialog = QMessageBox(self)
        error_dialog.setIcon(QMessageBox.Icon.Critical)
        error_dialog.setWindowTitle(title)
        error_dialog.setText(message)

        if details:
            error_dialog.setDetailedText(details)

        error_dialog.exec()

    def on_wireframe_transparent_checkbox_toggled(self, checked):
        me = "on_wireframe_transparent_checkbox_toggled"
        logger.debug("[%s] toggled: %s", me, str(checked).lower())
        # store transparent icon selection into settings
        self.settings["wireframe_transparent"] = checked
        self.qsettings.setValue(f"Settings/wireframe_transparent", checked)
        self.qsettings.sync()
        name = self.settings.get("icon_look", '')
        icon_config = self.settings_manager.get_icon_look_config(name)
        if 'icon_none_transparent' in icon_config:
            if checked:
                self.update_systray_icon("icon_look", f"{name}:transparent")
            else:
                self.update_systray_icon("icon_look", f"{name}:non-transparent")


    def on_icon_radio_button_toggled(self, checked, label, name):
        me = "on_icon_radio_button_toggled"
        if checked:
            logger.debug("[%s] toggled %s: %s", me, name, str(checked).lower())
            self.selected_icons["icon_selected"] = name
            self.settings["icon_look"] = name
            self.qsettings.setValue(f"Settings/icon_look", name)
            self.qsettings.sync()

            icon_config = self.settings_manager.get_icon_look_config(name)
            has_transparent = 'icon_none_transparent' in icon_config
            if has_transparent:
                self.wireframe_transparent_checkbox.setEnabled(True)
                if self.settings.get("wireframe_transparent"):
                    self.update_systray_icon("icon_look", f"{name}:transparent")
                else:
                    self.update_systray_icon("icon_look", f"{name}:non-transparent")
            else:
                self.wireframe_transparent_checkbox.setEnabled(False)
                self.update_systray_icon("icon_look", name)

    def on_upgrade_assume_yes_checkbox_toggled(self, checked):
        self.settings["upgrade_assume_yes"] = checked
        self.qsettings.setValue(f"Settings/upgrade_assume_yes", checked)
        self.qsettings.sync()
        self.update_view_and_upgrade("upgrade_assume_yes", checked)
        #print(f"toggled upgrade_assume_yes: {checked}")

    def on_auto_close_checkbox_toggled(self, checked):
        self.auto_close_timeout.setEnabled(checked)
        self.settings["auto_close"] = checked
        self.qsettings.setValue(f"Settings/auto_close", checked)
        self.qsettings.sync()
        self.update_view_and_upgrade("auto_close", checked)
        #print(f"toggled auto_close: {checked}")

    def on_use_dbus_notifications_checkbox_toggled(self, checked):
        # save use_dbus_notifications selection into settings dict
        self.settings["use_dbus_notifications"] = checked
        self.qsettings.setValue(f"Settings/use_dbus_notifications", checked)
        self.qsettings.sync()
        #print(f"toggled use_dbus_notifications: {checked}")
        self.update_systray_icon("use_dbus_notifications", checked)


    def on_hide_until_upgrades_available_checkbox_toggled(self, checked):
        # save hide_until_upgrades_available selection into settings dict
        self.settings["hide_until_upgrades_available"] = checked
        self.qsettings.setValue("Settings/hide_until_upgrades_available", checked)
        self.qsettings.sync()
        #print(f"toggled hide_until_upgrades_available: {checked}")
        self.update_systray_icon("hide_until_upgrades_available", checked)

    def save_auto_close_timeout(self, value):
        # save auto_close_timeout value to settings
        self.settings["auto_close_timeout"] = value
        self.qsettings.setValue("Settings/auto_close_timeout", value)
        self.qsettings.sync()
        self.update_view_and_upgrade("auto_close_timeout", value)

    def on_terminal_size_changed(self, index):
        value = self.terminal_size_combo.itemData(index)
        self.settings["terminal_size"] = value
        self.qsettings.setValue("Settings/terminal_size", value)
        self.qsettings.sync()
        _on_wayland = (os.environ.get("XDG_SESSION_TYPE") == "wayland"
                       or bool(os.environ.get("WAYLAND_DISPLAY")))
        if not _on_wayland:
            if value == "own":
                self.terminal_position_label.setVisible(False)
                self.terminal_position_combo.setVisible(False)
            else:
                self.terminal_position_label.setVisible(True)
                self.terminal_position_combo.setVisible(True)
                self.terminal_position_label.setEnabled(True)
                self.terminal_position_combo.setEnabled(True)

    def on_terminal_position_changed(self, index):
        value = self.terminal_position_combo.itemData(index)
        self.settings["terminal_position"] = value
        self.qsettings.setValue("Settings/terminal_position", value)
        self.qsettings.sync()

    def toggle_auto_close_timeout_spinbox(self, state):
        # Enable/disable spinbox based on checkbox state
        is_checked = state == Qt.CheckState.Checked
        self.auto_close_timeout.setEnabled(is_checked)


    #---------------------------------------------------------------
    #---------------------------------------------------------------
    def on_close_clicked(self):
        #print("Close button clicked - on_close_clicked")
        self.close()

    def on_close(self):
        #print("Close button clicked - on_close")
        self.close()

    #---------------------------------------------------------------

    def closeEvent(self, event):
        if self.ok_clicked:
            QApplication.exit(0)  # Exit with code 0 for OK
        else:
            QApplication.exit(1)  # Exit with code 1 for Cancel
        event.accept()  # Accept the event to close the dialog

    """
    def keyPressEvent(self, event: QKeyEvent):
        print(f"event.key()= {event.key()}")
        if event.key() in [Qt.Key.Key_Escape, Qt.Key.Key_Enter, Qt.Key.Key_Return] :  # Use Qt constant for Esc, Enter, and Return key
            self.on_close_clicked()  # Call the close method
        else:
            super().keyPressEvent(event)  # Call the base class method for other keys
    """

    def squeeze_spaces(self, text):
        return re.sub(r'\s+', ' ', text.strip())

    def create_icon_label(self, icon_path):
        label = QLabel()
        display_path = icon_path.replace('/256x256/', '/32x32/')
        pixmap = QPixmap(display_path)
        if pixmap.isNull():
            pixmap = QPixmap(icon_path)
        label.setPixmap(pixmap)
        label.setFixedSize(32, 32)
        label.setScaledContents(True)
        return label

    def name(self):
        return _("Preferences")

    def center(self):
        # Get the screen geometry using QMainWindow
        screen_geometry = self.screen().geometry()
        #print("Screen Geometry (from QMainWindow):", screen_geometry)

        # Get the available geometry using QDesktopWidget
        #desktop = QDesktopWidget()
        desktop = QGuiApplication.primaryScreen()
        available_geometry = desktop.availableGeometry()
        #print("Available Geometry (from primaryScreen()):", available_geometry)

        # Get the window geometry
        window_geometry = self.geometry()

        # Calculate the center position
        x = available_geometry.x() + (available_geometry.width() - self.width()) // 2
        y = available_geometry.y() + (available_geometry.height() - self.height()) // 2

        # Move the window to the center
        self.move(x, y)

    def resizeEventXXX(self, event):
        #return
        # Call the base class implementation
        super().resizeEvent(event)
        # Center the window after resizing
        self.center()

    def resizeEvent(self, event):
        # Call the base class implementation
        super().resizeEvent(event)

        # Center the window only after the first resize
        if self.first_resize:
            self.first_resize = False  # Set the flag to False after the first resize
            self.center()

    def print_existing_settings(self):
        # Create the QSettings object
        settings = QSettings("MX-Linux", "mx-updater")

        # Step 1: Read existing settings into a nested dictionary
        existing_settings = {}
        for key in settings.allKeys():
            parts = key.split('/')  # Assuming '/' is used as a separator for sub-keys
            current_level = existing_settings
            for part in parts[:-1]:
                current_level = current_level.setdefault(part, {})
            current_level[parts[-1]] = settings.value(key)

        # Step 2: Print the existing settings using pprint
        logger.debug("existing_settings: %s", existing_settings)

    def update_settings(self, new_settings):

        # Step 1: Read existing settings
        existing_keys = self.qsettings.allKeys()

        # Step 2: Identify keys to remove
        keys_to_remove = [key for key in existing_keys
                            if key not in new_settings]

        # Step 3: Remove outdated keys
        for key in keys_to_remove:
            self.qsettings.remove(key)

        #print("#", "-" * 40)
        #print(f"Save selected settings:")
        # Step 4: Update existing keys and add new ones
        for key, value in new_settings.items():
            #print(f"{key}={value}")
            self.qsettings.setValue(f"Settings/{key}", value)
        #print("#", "-" * 10, " update_settings ", "-" * 30 )

    def print_window_title(self):
        # Get and print the current window title
        title = self.windowTitle()
        #print(f"Current SettingsEditorDialog Title: {title}")


    def detect_plasma(self):
        # kde/plasma detection
        plasma_indicators = [
            os.environ.get('DESKTOP_SESSION', '').lower() == 'plasma',
            os.environ.get('XDG_CURRENT_DESKTOP', '').lower() == 'kde',
            os.environ.get('KDE_FULL_SESSION', '').lower() == 'true'
        ]

        if any(plasma_indicators):
            return any(plasma_indicators)

        # additional with process check
        try:
            plasma_shell_process = subprocess.run(
                ['pgrep', '-x', 'plasmashell'],
                capture_output=True,
                text=True
            )
            plasma_indicators.append(plasma_shell_process.returncode == 0)
        except Exception:
            try:
                plasma_shell_process = subprocess.run(
                    ['pidof', '-q', 'plasmashell'],
                    capture_output=True,
                    text=True
                )
                plasma_indicators.append(plasma_shell_process.returncode == 0)
            except Exception:
                pass

        return any(plasma_indicators)


    def detect_fluxbox(self):
        # fluxbox detection
        fluxbox_indicators = [
            os.environ.get('DESKTOP_SESSION', '').lower() == 'fluxbox',
            os.environ.get('XDG_SESSION_DESKTOP', '').lower() == 'fluxbox',
            os.environ.get('GDMSESSION', '').lower() == 'fluxbox'
        ]

        if any(fluxbox_indicators):
            return any(fluxbox_indicators)

        # additional with process check
        try:
            fluxbox_process = subprocess.run(
                ['pgrep', '-x', 'fluxbox'],
                capture_output=True,
                text=True
            )
            fluxbox_indicators.append(fluxbox_process.returncode == 0)
        except Exception:
            try:
                fluxbox_process = subprocess.run(
                    ['pidof', '-q', 'fluxbox'],
                    capture_output=True,
                    text=True
                )
                fluxbox_indicators.append(fluxbox_process.returncode == 0)
            except Exception:
                pass

        return any(fluxbox_indicators)


def is_dark_theme():
    return QApplication.palette().color(QPalette.ColorRole.Window).lightness() < 128

def tooltip_stylesheet():
    if is_dark_theme():
        bg = "#FFF0A0"  # dark theme: goldish yellow
    else:
        bg = "#FFFFE0"  # light theme: light yellow
    return """
        QToolTip {
            color: black;
            background-color: %s;
            border: 1px solid #000000;
            padding: 5px;
        }
    """ % bg

if __name__ == "__main__":
    app = QApplication(sys.argv)  # QApplication instance
    app.setApplicationName("updater-mx-settings")
    app.setStyleSheet(tooltip_stylesheet())

    bus = SessionBus()
    try:
        # Check if the app is already running
        if bus.get(SETTINGS_OBJECT_NAME):
            print("'MX Updater preferences' is already running, restoring window...")
            bus.get(SETTINGS_OBJECT_NAME).Restore()
            sys.exit(0)
    except Exception as e:
        print("'MX Updater preferences' is not running, starting new instance...")
        pass


    #----------------------------------------------------------------
    # Create a QTranslator instance
    qtranslator = QTranslator()
    locale = QLocale.system().name()  # Get the system locale

    # Get the path to the translations directory
    translations_path = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
    translation_file_path = f"{translations_path}/qt_{locale}.qm"

    # Load the translation file
    if qtranslator.load(translation_file_path):
        #print(f"Translation file found: {translation_file_path}")
        app.installTranslator(qtranslator)
    else:
        #print(f"Translation file not found: {translation_file_path}")
        pass

    #----------------------------------------------------------------
    dialog = SettingsEditorDialog()

    #print(f"SettingsEditorDialog title: {dialog.windowTitle()}")
    #print(f"qdbus6 {SETTINGS_OBJECT_NAME}  {SETTINGS_OBJECT_PATH}  {SETTINGS_OBJECT_IFACE} ")
    #print(f"/usr/lib/qt6/bin/qdbus {SETTINGS_OBJECT_NAME}  {SETTINGS_OBJECT_PATH}  {SETTINGS_OBJECT_IFACE} ")
    #print(f"/usr/lib/qt6/bin/qdbus {SETTINGS_OBJECT_NAME}  {SETTINGS_OBJECT_PATH}  ")

    dialog.show()

    dialog.print_window_title()

    sys.exit(app.exec())

"""

    # Check the return code after the dialog is closed
    return_code = app.exec()

    if return_code == 0:
        print("Action completed.")
    else:
        print("Cancelled action.")

    sys.exit(return_code)



if __name__ == "__main__":
    MyApp()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MyApp()
    ex.show()
    sys.exit(app.exec_())


"""
