From 62e285b52eb7a08770b6f96158bc2062b76c007b Mon Sep 17 00:00:00 2001
From: Athanasius <Athanasius@miggy.org>
Date: Mon, 5 Dec 2022 11:52:29 +0000
Subject: [PATCH] themes: Use defined constants for which theme throughout

This has been relying on knowledge of the magic numbers for far too long.

As part of this, remove all the obfuscating "oh, default is 0, and we want
that or any other theme, so treat this like a boolean" nonsense.

Also, stop assuming that "> 1" is a synonym for "transparent theme".  Just
Do The Equality Check.
---
 EDMarketConnector.py |  10 ++--
 prefs.py             |  15 ++++--
 theme.py             | 107 +++++++++++++++++++++++++++++--------------
 3 files changed, 87 insertions(+), 45 deletions(-)

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index 60e9f546..74ce8ace 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -1799,18 +1799,14 @@ class AppWindow(object):
 
     def onenter(self, event=None) -> None:
         """Handle when our window gains focus."""
-        # TODO: This assumes that 1) transparent is at least 2, 2) there are
-        #       no new themes added after that.
-        if config.get_int('theme') > 1:
+        if config.get_int('theme') == theme.THEME_TRANSPARENT:
             self.w.attributes("-transparentcolor", '')
             self.blank_menubar.grid_remove()
             self.theme_menubar.grid(row=0, columnspan=2, sticky=tk.NSEW)
 
     def onleave(self, event=None) -> None:
         """Handle when our window loses focus."""
-        # TODO: This assumes that 1) transparent is at least 2, 2) there are
-        #       no new themes added after that.
-        if config.get_int('theme') > 1 and event.widget == self.w:
+        if config.get_int('theme') == theme.THEME_TRANSPARENT and event.widget == self.w:
             self.w.attributes("-transparentcolor", 'grey4')
             self.theme_menubar.grid_remove()
             self.blank_menubar.grid(row=0, columnspan=2, sticky=tk.NSEW)
@@ -1887,7 +1883,7 @@ sys.path: {sys.path}'''
                  )
 
     if args.reset_ui:
-        config.set('theme', 0)  # 'Default' theme uses ID 0
+        config.set('theme', theme.THEME_DEFAULT)
         config.set('ui_transparency', 100)  # 100 is completely opaque
         config.delete('font', suppress=True)
         config.delete('font_size', suppress=True)
diff --git a/prefs.py b/prefs.py
index 376b6c67..985ef581 100644
--- a/prefs.py
+++ b/prefs.py
@@ -308,6 +308,7 @@ class PreferencesDialog(tk.Toplevel):
             button.bind("<Return>", lambda event: self.apply())
             self.protocol("WM_DELETE_WINDOW", self._destroy)
 
+        # FIXME: Why are these being called when *creating* the Settings window?
         # Selectively disable buttons depending on output settings
         self.cmdrchanged()
         self.themevarchanged()
@@ -733,13 +734,14 @@ class PreferencesDialog(tk.Toplevel):
         # Appearance theme and language setting
         nb.Radiobutton(
             # LANG: Label for 'Default' theme radio button
-            appearance_frame, text=_('Default'), variable=self.theme, value=0, command=self.themevarchanged
+            appearance_frame, text=_('Default'), variable=self.theme,
+            value=theme.THEME_DEFAULT, command=self.themevarchanged
         ).grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get())
 
         # Appearance theme setting
         nb.Radiobutton(
             # LANG: Label for 'Dark' theme radio button
-            appearance_frame, text=_('Dark'), variable=self.theme, value=1, command=self.themevarchanged
+            appearance_frame, text=_('Dark'), variable=self.theme, value=theme.THEME_DARK, command=self.themevarchanged
         ).grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get())
 
         if sys.platform == 'win32':
@@ -748,7 +750,7 @@ class PreferencesDialog(tk.Toplevel):
                 # LANG: Label for 'Transparent' theme radio button
                 text=_('Transparent'),  # Appearance theme setting
                 variable=self.theme,
-                value=2,
+                value=theme.THEME_TRANSPARENT,
                 command=self.themevarchanged
             ).grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get())
 
@@ -1149,7 +1151,12 @@ class PreferencesDialog(tk.Toplevel):
         """Update theme examples."""
         self.theme_button_0['foreground'], self.theme_button_1['foreground'] = self.theme_colors
 
-        state = tk.NORMAL if self.theme.get() else tk.DISABLED
+        if self.theme.get() == theme.THEME_DEFAULT:
+            state = tk.DISABLED  # type: ignore
+
+        else:
+            state = tk.NORMAL  # type: ignore
+
         self.theme_label_0['state'] = state
         self.theme_label_1['state'] = state
         self.theme_button_0['state'] = state
diff --git a/theme.py b/theme.py
index 2707ad42..503f2dc0 100644
--- a/theme.py
+++ b/theme.py
@@ -259,24 +259,7 @@ class _Theme(object):
         if not config.get_str('dark_highlight'):
             config.set('dark_highlight', 'white')
 
-        if theme:
-            # Dark
-            (r, g, b) = root.winfo_rgb(config.get_str('dark_text'))
-            self.current = {
-                'background': 'grey4',  # OSX inactive dark titlebar color
-                'foreground': config.get_str('dark_text'),
-                'activebackground': config.get_str('dark_text'),
-                'activeforeground': 'grey4',
-                'disabledforeground': f'#{int(r/384):02x}{int(g/384):02x}{int(b/384):02x}',
-                'highlight': config.get_str('dark_highlight'),
-                # Font only supports Latin 1 / Supplement / Extended, and a
-                # few General Punctuation and Mathematical Operators
-                # LANG: Label for commander name in main window
-                'font': (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and
-                         tk_font.Font(family='Euro Caps', size=10, weight=tk_font.NORMAL) or
-                         'TkDefaultFont'),
-            }
-        else:
+        if theme == self.THEME_DEFAULT:
             # (Mostly) system colors
             style = ttk.Style()
             self.current = {
@@ -292,9 +275,30 @@ class _Theme(object):
                 'font': 'TkDefaultFont',
             }
 
-    # Apply current theme to a widget and its children, and register it for future updates
+        else:  # Dark *or* Transparent
+            (r, g, b) = root.winfo_rgb(config.get_str('dark_text'))
+            self.current = {
+                'background': 'grey4',  # OSX inactive dark titlebar color
+                'foreground': config.get_str('dark_text'),
+                'activebackground': config.get_str('dark_text'),
+                'activeforeground': 'grey4',
+                'disabledforeground': f'#{int(r/384):02x}{int(g/384):02x}{int(b/384):02x}',
+                'highlight': config.get_str('dark_highlight'),
+                # Font only supports Latin 1 / Supplement / Extended, and a
+                # few General Punctuation and Mathematical Operators
+                # LANG: Label for commander name in main window
+                'font': (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and
+                         tk_font.Font(family='Euro Caps', size=10, weight=tk_font.NORMAL) or
+                         'TkDefaultFont'),
+            }
 
     def update(self, widget: tk.Widget) -> None:
+        """
+        Apply current theme to a widget and its children.
+
+        Also, register it for future updates.
+        :param widget: Target widget.
+        """
         assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget
         if not self.current:
             return  # No need to call this for widgets created in plugin_app()
@@ -375,8 +379,7 @@ class _Theme(object):
 
     # Apply configured theme
 
-    def apply(self, root: tk.Tk) -> None:  # noqa: CCR001
-
+    def apply(self, root: tk.Tk) -> None:  # noqa: CCR001, C901
         theme = config.get_int('theme')
         self._colors(root, theme)
 
@@ -392,25 +395,31 @@ class _Theme(object):
             for widget in pair:
                 widget.grid_remove()
             if isinstance(pair[0], tk.Menu):
-                if theme:
+                if theme == self.THEME_DEFAULT:
+                    root['menu'] = pair[0]
+
+                else:  # Dark *or* Transparent
                     root['menu'] = ''
                     pair[theme].grid(**gridopts)
-                else:
-                    root['menu'] = pair[0]
+
             else:
                 pair[theme].grid(**gridopts)
 
         if self.active == theme:
             return  # Don't need to mess with the window manager
+
         else:
             self.active = theme
 
         if sys.platform == 'darwin':
             from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask
             root.update_idletasks()  # need main window to be created
-            appearance = NSAppearance.appearanceNamed_(theme and
-                                                       'NSAppearanceNameDarkAqua' or
-                                                       'NSAppearanceNameAqua')
+            if theme == self.THEME_DEFAULT:
+                appearance = NSAppearance.appearanceNamed_('NSAppearanceNameAqua')
+
+            else:  # Dark (Transparent only on win32)
+                appearance = NSAppearance.appearanceNamed_('NSAppearanceNameDarkAqua')
+
             for window in NSApplication.sharedApplication().windows():
                 window.setStyleMask_(window.styleMask() & ~(
                     NSMiniaturizableWindowMask | NSResizableWindowMask))  # disable zoom
@@ -426,14 +435,30 @@ class _Theme(object):
             GetWindowLongW = ctypes.windll.user32.GetWindowLongW  # noqa: N806 # ctypes
             SetWindowLongW = ctypes.windll.user32.SetWindowLongW  # noqa: N806 # ctypes
 
-            root.overrideredirect(theme and True or False)
-            root.attributes("-transparentcolor", theme > 1 and 'grey4' or '')
+            # FIXME: Lose the "treat this like a boolean" bullshit
+            if theme == self.THEME_DEFAULT:
+                root.overrideredirect(False)
+
+            else:
+                root.overrideredirect(True)
+
+            if theme == self.THEME_TRANSPARENT:
+                root.attributes("-transparentcolor", 'grey4')
+
+            else:
+                root.attributes("-transparentcolor", '')
+
             root.withdraw()
             root.update_idletasks()  # Size and windows styles get recalculated here
             hwnd = ctypes.windll.user32.GetParent(root.winfo_id())
             SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX)  # disable maximize
-            SetWindowLongW(hwnd, GWL_EXSTYLE, theme > 1 and WS_EX_APPWINDOW |
-                           WS_EX_LAYERED or WS_EX_APPWINDOW)  # Add to taskbar
+
+            if theme == self.THEME_TRANSPARENT:
+                SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED)  # Add to taskbar
+
+            else:
+                SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW)  # Add to taskbar
+
             root.deiconify()
             root.wait_visibility()  # need main window to be displayed before returning
 
@@ -446,11 +471,25 @@ class _Theme(object):
                 children = Window()
                 nchildren = c_uint()
                 XQueryTree(dpy, root.winfo_id(), byref(xroot), byref(parent), byref(children), byref(nchildren))
-                XChangeProperty(dpy, parent, motif_wm_hints_property, motif_wm_hints_property, 32,
-                                PropModeReplace, theme and motif_wm_hints_dark or motif_wm_hints_normal, 5)
+                if theme == self.THEME_DEFAULT:
+                    wm_hints = motif_wm_hints_normal
+
+                else:  # Dark *or* Transparent
+                    wm_hints = motif_wm_hints_dark
+
+                XChangeProperty(
+                    dpy, parent, motif_wm_hints_property, motif_wm_hints_property, 32, PropModeReplace, wm_hints, 5
+                )
+
                 XFlush(dpy)
+
             else:
-                root.overrideredirect(theme and 1 or 0)
+                if theme == self.THEME_DEFAULT:
+                    root.overrideredirect(False)
+
+                else:  # Dark *or* Transparent
+                    root.overrideredirect(True)
+
             root.deiconify()
             root.wait_visibility()  # need main window to be displayed before returning