From d0ca7aa9c7b4c6af1b5379566b900ae967268a10 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 26 May 2024 23:48:58 +0100 Subject: [PATCH 01/59] Excluded ttk widgets from theme.theme --- theme.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/theme.py b/theme.py index 94e99f7a6..796ad0ed5 100644 --- a/theme.py +++ b/theme.py @@ -163,7 +163,7 @@ def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, 'menufont': tk.Menu()['font'], # TkTextFont } - if widget not in self.widgets: + if widget not in self.widgets and not isinstance(widget, ttk.Widget): # No general way to tell whether the user has overridden, so compare against widget-type specific defaults attribs = set() if isinstance(widget, tk.BitmapImage): @@ -171,14 +171,14 @@ def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, attribs.add('fg') if widget['background'] not in ['', self.defaults['bitmapbg']]: attribs.add('bg') - elif isinstance(widget, (tk.Entry, ttk.Entry)): + elif isinstance(widget, tk.Entry): if widget['foreground'] not in ['', self.defaults['entryfg']]: attribs.add('fg') if widget['background'] not in ['', self.defaults['entrybg']]: attribs.add('bg') if 'font' in widget.keys() and str(widget['font']) not in ['', self.defaults['entryfont']]: attribs.add('font') - elif isinstance(widget, (tk.Canvas, tk.Frame, ttk.Frame)): + elif isinstance(widget, (tk.Canvas, tk.Frame)): if ( ('background' in widget.keys() or isinstance(widget, tk.Canvas)) and widget['background'] not in ['', self.defaults['frame']] From 343ee8a6d026458b4de17c924325fc1f3aa43267 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 26 May 2024 23:52:20 +0100 Subject: [PATCH 02/59] Changed all main window widgets to ttk --- EDMarketConnector.py | 64 +++++++++++++++++++++---------------------- plugins/eddn.py | 65 ++++++++++++++++++++++---------------------- ttkHyperlinkLabel.py | 2 +- 3 files changed, 65 insertions(+), 66 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 7f870108f..d64205788 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -478,19 +478,19 @@ def open_window(systray: 'SysTrayIcon') -> None: self.theme_close = tk.BitmapImage( data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 - frame = tk.Frame(self.w, name=appname.lower()) + frame = ttk.Frame(self.w, name=appname.lower()) frame.grid(sticky=tk.NSEW) frame.columnconfigure(1, weight=1) - self.cmdr_label = tk.Label(frame, name='cmdr_label') - self.cmdr = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='cmdr') - self.ship_label = tk.Label(frame, name='ship_label') + self.cmdr_label = ttk.Label(frame, name='cmdr_label') + self.cmdr = ttk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='cmdr') + self.ship_label = ttk.Label(frame, name='ship_label') self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship', popup_copy=True) - self.suit_label = tk.Label(frame, name='suit_label') - self.suit = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='suit') - self.system_label = tk.Label(frame, name='system_label') + self.suit_label = ttk.Label(frame, name='suit_label') + self.suit = ttk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='suit') + self.system_label = ttk.Label(frame, name='system_label') self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.system_url, popup_copy=True, name='system') - self.station_label = tk.Label(frame, name='station_label') + self.station_label = ttk.Label(frame, name='station_label') self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station', popup_copy=True) # system and station text is set/updated by the 'provider' plugins # edsm and inara. Look for: @@ -523,11 +523,9 @@ def open_window(systray: 'SysTrayIcon') -> None: plugin_no = 0 for plugin in plug.PLUGINS: # Per plugin separator - plugin_sep = tk.Frame( - frame, highlightthickness=1, name=f"plugin_hr_{plugin_no + 1}" - ) + plugin_sep = ttk.Separator(frame, name=f"plugin_hr_{plugin_no + 1}") # Per plugin frame, for it to use as its parent for own widgets - plugin_frame = tk.Frame( + plugin_frame = ttk.Frame( frame, name=f"plugin_{plugin_no + 1}" ) @@ -562,7 +560,7 @@ def open_window(systray: 'SysTrayIcon') -> None: default=tk.ACTIVE, state=tk.DISABLED ) - self.theme_button = tk.Label( + self.theme_button = ttk.Label( frame, name='themed_update_button', width=28, @@ -578,12 +576,12 @@ def open_window(systray: 'SysTrayIcon') -> None: theme.button_bind(self.theme_button, self.capi_request_data) # Bottom 'status' line. - self.status = tk.Label(frame, name='status', anchor=tk.W) + self.status = ttk.Label(frame, name='status', anchor=tk.W) self.status.grid(columnspan=2, sticky=tk.EW) for child in frame.winfo_children(): child.grid_configure(padx=self.PADX, pady=( - sys.platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0) + sys.platform != 'win32' or isinstance(child, ttk.Frame)) and 2 or 0) self.menubar = tk.Menu() @@ -645,9 +643,9 @@ def open_window(systray: 'SysTrayIcon') -> None: theme.register(self.help_menu) # Alternate title bar and menu for dark theme - self.theme_menubar = tk.Frame(frame, name="alternate_menubar") + self.theme_menubar = ttk.Frame(frame, name="alternate_menubar") self.theme_menubar.columnconfigure(2, weight=1) - theme_titlebar = tk.Label( + theme_titlebar = ttk.Label( self.theme_menubar, name="alternate_titlebar", text=applongname, @@ -659,37 +657,37 @@ def open_window(systray: 'SysTrayIcon') -> None: theme_titlebar.bind('', self.drag_start) theme_titlebar.bind('', self.drag_continue) theme_titlebar.bind('', self.drag_end) - theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize) + theme_minimize = ttk.Label(self.theme_menubar, image=self.theme_minimize) theme_minimize.grid(row=0, column=3, padx=2) theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize) - theme_close = tk.Label(self.theme_menubar, image=self.theme_close) + theme_close = ttk.Label(self.theme_menubar, image=self.theme_close) theme_close.grid(row=0, column=4, padx=2) theme.button_bind(theme_close, self.onexit, image=self.theme_close) - self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W) + self.theme_file_menu = ttk.Label(self.theme_menubar, anchor=tk.W) self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W) theme.button_bind(self.theme_file_menu, lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) - self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W) + self.theme_edit_menu = ttk.Label(self.theme_menubar, anchor=tk.W) self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W) theme.button_bind(self.theme_edit_menu, lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) - self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W) + self.theme_help_menu = ttk.Label(self.theme_menubar, anchor=tk.W) self.theme_help_menu.grid(row=1, column=2, sticky=tk.W) theme.button_bind(self.theme_help_menu, lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) - tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) + ttk.Separator(self.theme_menubar).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) theme.register(self.theme_minimize) # images aren't automatically registered theme.register(self.theme_close) - self.blank_menubar = tk.Frame(frame, name="blank_menubar") - tk.Label(self.blank_menubar).grid() - tk.Label(self.blank_menubar).grid() - tk.Frame(self.blank_menubar, height=2).grid() + self.blank_menubar = ttk.Frame(frame, name="blank_menubar") + ttk.Label(self.blank_menubar).grid() + ttk.Label(self.blank_menubar).grid() + ttk.Frame(self.blank_menubar, height=2).grid() theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW}) self.w.resizable(tk.TRUE, tk.FALSE) @@ -2008,22 +2006,22 @@ def show_killswitch_poppup(root=None): tl.columnconfigure(1, weight=1) tl.title("EDMC Features have been disabled") - frame = tk.Frame(tl) + frame = ttk.Frame(tl) frame.grid() - t = tk.Label(frame, text=text) + t = ttk.Label(frame, text=text) t.grid(columnspan=2) idx = 1 for version in kills: - tk.Label(frame, text=f'Version: {version.version}').grid(row=idx, sticky=tk.W) + ttk.Label(frame, text=f'Version: {version.version}').grid(row=idx, sticky=tk.W) idx += 1 for id, kill in version.kills.items(): - tk.Label(frame, text=id).grid(column=0, row=idx, sticky=tk.W, padx=(10, 0)) - tk.Label(frame, text=kill.reason).grid(column=1, row=idx, sticky=tk.E, padx=(0, 10)) + ttk.Label(frame, text=id).grid(column=0, row=idx, sticky=tk.W, padx=(10, 0)) + ttk.Label(frame, text=kill.reason).grid(column=1, row=idx, sticky=tk.E, padx=(0, 10)) idx += 1 idx += 1 - ok_button = tk.Button(frame, text="Ok", command=tl.destroy) + ok_button = ttk.Button(frame, text="Ok", command=tl.destroy) ok_button.grid(columnspan=2, sticky=tk.EW) diff --git a/plugins/eddn.py b/plugins/eddn.py index 2b2230363..ba84ea875 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -31,6 +31,7 @@ from platform import system from textwrap import dedent from threading import Lock +from tkinter import ttk from typing import Any, Iterator, Mapping, MutableMapping import requests import companion @@ -111,16 +112,16 @@ def __init__(self): self.eddn_delay_button: nb.Checkbutton # Tracking UI - self.ui: tk.Frame - self.ui_system_name: tk.Label - self.ui_system_address: tk.Label - self.ui_j_body_name: tk.Label - self.ui_j_body_id: tk.Label - self.ui_j_body_type: tk.Label - self.ui_s_body_name: tk.Label - self.ui_station_name: tk.Label - self.ui_station_type: tk.Label - self.ui_station_marketid: tk.Label + self.ui: ttk.Frame + self.ui_system_name: ttk.Label + self.ui_system_address: ttk.Label + self.ui_j_body_name: ttk.Label + self.ui_j_body_id: ttk.Label + self.ui_j_body_type: ttk.Label + self.ui_s_body_name: ttk.Label + self.ui_station_name: ttk.Label + self.ui_station_type: ttk.Label + self.ui_station_marketid: ttk.Label this = This() @@ -2009,7 +2010,7 @@ def plugin_start3(plugin_dir: str) -> str: return 'EDDN' -def plugin_app(parent: tk.Tk) -> tk.Frame | None: +def plugin_app(parent: tk.Tk) -> ttk.Frame | None: """ Set up any plugin-specific UI. @@ -2023,7 +2024,7 @@ def plugin_app(parent: tk.Tk) -> tk.Frame | None: this.eddn = EDDN(parent) if config.eddn_tracking_ui: - this.ui = tk.Frame(parent) + this.ui = ttk.Frame(parent) row = this.ui.grid_size()[1] @@ -2031,15 +2032,15 @@ def plugin_app(parent: tk.Tk) -> tk.Frame | None: # System ####################################################################### # SystemName - system_name_label = tk.Label(this.ui, text="J:SystemName:") + system_name_label = ttk.Label(this.ui, text="J:SystemName:") system_name_label.grid(row=row, column=0, sticky=tk.W) - this.ui_system_name = tk.Label(this.ui, name='eddn_track_system_name', anchor=tk.W) + this.ui_system_name = ttk.Label(this.ui, name='eddn_track_system_name', anchor=tk.W) this.ui_system_name.grid(row=row, column=1, sticky=tk.E) row += 1 # SystemAddress - system_address_label = tk.Label(this.ui, text="J:SystemAddress:") + system_address_label = ttk.Label(this.ui, text="J:SystemAddress:") system_address_label.grid(row=row, column=0, sticky=tk.W) - this.ui_system_address = tk.Label(this.ui, name='eddn_track_system_address', anchor=tk.W) + this.ui_system_address = ttk.Label(this.ui, name='eddn_track_system_address', anchor=tk.W) this.ui_system_address.grid(row=row, column=1, sticky=tk.E) row += 1 ####################################################################### @@ -2048,27 +2049,27 @@ def plugin_app(parent: tk.Tk) -> tk.Frame | None: # Body ####################################################################### # Body Name from Journal - journal_body_name_label = tk.Label(this.ui, text="J:BodyName:") + journal_body_name_label = ttk.Label(this.ui, text="J:BodyName:") journal_body_name_label.grid(row=row, column=0, sticky=tk.W) - this.ui_j_body_name = tk.Label(this.ui, name='eddn_track_j_body_name', anchor=tk.W) + this.ui_j_body_name = ttk.Label(this.ui, name='eddn_track_j_body_name', anchor=tk.W) this.ui_j_body_name.grid(row=row, column=1, sticky=tk.E) row += 1 # Body ID from Journal - journal_body_id_label = tk.Label(this.ui, text="J:BodyID:") + journal_body_id_label = ttk.Label(this.ui, text="J:BodyID:") journal_body_id_label.grid(row=row, column=0, sticky=tk.W) - this.ui_j_body_id = tk.Label(this.ui, name='eddn_track_j_body_id', anchor=tk.W) + this.ui_j_body_id = ttk.Label(this.ui, name='eddn_track_j_body_id', anchor=tk.W) this.ui_j_body_id.grid(row=row, column=1, sticky=tk.E) row += 1 # Body Type from Journal - journal_body_type_label = tk.Label(this.ui, text="J:BodyType:") + journal_body_type_label = ttk.Label(this.ui, text="J:BodyType:") journal_body_type_label.grid(row=row, column=0, sticky=tk.W) - this.ui_j_body_type = tk.Label(this.ui, name='eddn_track_j_body_type', anchor=tk.W) + this.ui_j_body_type = ttk.Label(this.ui, name='eddn_track_j_body_type', anchor=tk.W) this.ui_j_body_type.grid(row=row, column=1, sticky=tk.E) row += 1 # Body Name from Status.json - status_body_name_label = tk.Label(this.ui, text="S:BodyName:") + status_body_name_label = ttk.Label(this.ui, text="S:BodyName:") status_body_name_label.grid(row=row, column=0, sticky=tk.W) - this.ui_s_body_name = tk.Label(this.ui, name='eddn_track_s_body_name', anchor=tk.W) + this.ui_s_body_name = ttk.Label(this.ui, name='eddn_track_s_body_name', anchor=tk.W) this.ui_s_body_name.grid(row=row, column=1, sticky=tk.E) row += 1 ####################################################################### @@ -2077,21 +2078,21 @@ def plugin_app(parent: tk.Tk) -> tk.Frame | None: # Station ####################################################################### # Name - status_station_name_label = tk.Label(this.ui, text="J:StationName:") + status_station_name_label = ttk.Label(this.ui, text="J:StationName:") status_station_name_label.grid(row=row, column=0, sticky=tk.W) - this.ui_station_name = tk.Label(this.ui, name='eddn_track_station_name', anchor=tk.W) + this.ui_station_name = ttk.Label(this.ui, name='eddn_track_station_name', anchor=tk.W) this.ui_station_name.grid(row=row, column=1, sticky=tk.E) row += 1 # Type - status_station_type_label = tk.Label(this.ui, text="J:StationType:") + status_station_type_label = ttk.Label(this.ui, text="J:StationType:") status_station_type_label.grid(row=row, column=0, sticky=tk.W) - this.ui_station_type = tk.Label(this.ui, name='eddn_track_station_type', anchor=tk.W) + this.ui_station_type = ttk.Label(this.ui, name='eddn_track_station_type', anchor=tk.W) this.ui_station_type.grid(row=row, column=1, sticky=tk.E) row += 1 # MarketID - status_station_marketid_label = tk.Label(this.ui, text="J:StationID:") + status_station_marketid_label = ttk.Label(this.ui, text="J:StationID:") status_station_marketid_label.grid(row=row, column=0, sticky=tk.W) - this.ui_station_marketid = tk.Label(this.ui, name='eddn_track_station_id', anchor=tk.W) + this.ui_station_marketid = ttk.Label(this.ui, name='eddn_track_station_id', anchor=tk.W) this.ui_station_marketid.grid(row=row, column=1, sticky=tk.E) row += 1 ####################################################################### @@ -2107,11 +2108,11 @@ def tracking_ui_update() -> None: return this.ui_system_name['text'] = '≪None≫' - if this.ui_system_name is not None: + if this.system_name is not None: this.ui_system_name['text'] = this.system_name this.ui_system_address['text'] = '≪None≫' - if this.ui_system_address is not None: + if this.system_address is not None: this.ui_system_address['text'] = this.system_address this.ui_j_body_name['text'] = '≪None≫' diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 6266d9fb2..0398d3cbd 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -49,7 +49,7 @@ """ -class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore +class HyperlinkLabel(ttk.Label): """Clickable label for HTTP links.""" def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None: From 34dab8eb706f49a867c8f8f480d0a45e754cd0cc Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 26 May 2024 23:58:16 +0100 Subject: [PATCH 03/59] Fixed type signatures caught up by mypy --- stats.py | 2 +- ttkHyperlinkLabel.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stats.py b/stats.py index db61f7892..921b151e1 100644 --- a/stats.py +++ b/stats.py @@ -305,7 +305,7 @@ def export_ships(companion_data: dict[str, Any], filename: AnyStr) -> None: class StatsDialog(): """Status dialog containing all of the current cmdr's stats.""" - def __init__(self, parent: tk.Tk, status: tk.Label) -> None: + def __init__(self, parent: tk.Tk, status: ttk.Label) -> None: self.parent: tk.Tk = parent self.status = status self.showstats() diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 0398d3cbd..e056915a7 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -124,7 +124,7 @@ def open_station(self, url: str): def configure( # noqa: CCR001 self, cnf: dict[str, Any] | None = None, **kw: Any - ) -> dict[str, tuple[str, str, str, Any, Any]] | None: + ) -> dict[str, tuple[str, str, str, Any, Any]]: """Change cursor and appearance depending on state and text.""" # This class' state for thing in ('url', 'popup_copy', 'underline'): From fa3ab46b6e79dad94a3b2ff8fea1634a7fc25526 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 00:09:32 +0100 Subject: [PATCH 04/59] As I was saying, fixed type signatures... --- ttkHyperlinkLabel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index e056915a7..17a99b93f 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -123,8 +123,8 @@ def open_station(self, url: str): return webbrowser.open(opener) def configure( # noqa: CCR001 - self, cnf: dict[str, Any] | None = None, **kw: Any - ) -> dict[str, tuple[str, str, str, Any, Any]]: + self, cnf: dict[str, Any] | None = None, **kw + ) -> dict[str, tuple[str, str, str, Any, Any]] | None: """Change cursor and appearance depending on state and text.""" # This class' state for thing in ('url', 'popup_copy', 'underline'): From a017139ad911f8961fdc40b51fed90fc8b1e0aab Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 00:22:55 +0100 Subject: [PATCH 05/59] meh --- ttkHyperlinkLabel.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 17a99b93f..7fd2e84f2 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -26,7 +26,7 @@ import webbrowser from tkinter import font as tk_font from tkinter import ttk -from typing import Any +from typing import Any, no_type_check import plug from os import path from config import config, logger @@ -122,8 +122,9 @@ def open_station(self, url: str): if opener: return webbrowser.open(opener) + @no_type_check def configure( # noqa: CCR001 - self, cnf: dict[str, Any] | None = None, **kw + self, cnf: dict[str, Any] | None = None, **kw: Any ) -> dict[str, tuple[str, str, str, Any, Any]] | None: """Change cursor and appearance depending on state and text.""" # This class' state @@ -174,7 +175,7 @@ def _enter(self, event: tk.Event) -> None: def _leave(self, event: tk.Event) -> None: if not self.underline: - super().configure(font=self.font_n) + super().configure(font=self.font_n) # type: ignore def _click(self, event: tk.Event) -> None: if self.url and self['text'] and str(self['state']) != tk.DISABLED: From d18a8d436f6465d6895666be724cb98bbeb273e0 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 00:27:11 +0100 Subject: [PATCH 06/59] We don't need to actually change the killswitch --- EDMarketConnector.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index d64205788..4d5bef6b3 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -2006,22 +2006,22 @@ def show_killswitch_poppup(root=None): tl.columnconfigure(1, weight=1) tl.title("EDMC Features have been disabled") - frame = ttk.Frame(tl) + frame = tk.Frame(tl) frame.grid() - t = ttk.Label(frame, text=text) + t = tk.Label(frame, text=text) t.grid(columnspan=2) idx = 1 for version in kills: - ttk.Label(frame, text=f'Version: {version.version}').grid(row=idx, sticky=tk.W) + tk.Label(frame, text=f'Version: {version.version}').grid(row=idx, sticky=tk.W) idx += 1 for id, kill in version.kills.items(): - ttk.Label(frame, text=id).grid(column=0, row=idx, sticky=tk.W, padx=(10, 0)) - ttk.Label(frame, text=kill.reason).grid(column=1, row=idx, sticky=tk.E, padx=(0, 10)) + tk.Label(frame, text=id).grid(column=0, row=idx, sticky=tk.W, padx=(10, 0)) + tk.Label(frame, text=kill.reason).grid(column=1, row=idx, sticky=tk.E, padx=(0, 10)) idx += 1 idx += 1 - ok_button = ttk.Button(frame, text="Ok", command=tl.destroy) + ok_button = tk.Button(frame, text="Ok", command=tl.destroy) ok_button.grid(columnspan=2, sticky=tk.EW) From a40a10ed652b55e814616d492960aa4f22ecacc9 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 03:00:31 +0100 Subject: [PATCH 07/59] Added JSON theme --- EDMarketConnector.py | 14 ++++++++--- themes/dark.json | 55 ++++++++++++++++++++++++++++++++++++++++++++ ttkHyperlinkLabel.py | 7 ++++-- 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 themes/dark.json diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4d5bef6b3..4b40f0ef7 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -350,7 +350,8 @@ def already_running_popup(): handle_edmc_callback_or_foregrounding() - if locked == JournalLockResult.ALREADY_LOCKED: + if False: + #if locked == JournalLockResult.ALREADY_LOCKED: # There's a copy already running. logger.info("An EDMarketConnector.exe process was already running, exiting.") @@ -399,6 +400,7 @@ def already_running_popup(): # isort: on +import json import tkinter as tk import tkinter.filedialog import tkinter.font @@ -462,6 +464,11 @@ def open_window(systray: 'SysTrayIcon') -> None: self.systray.start() plug.load_plugins(master) + self.style = ttk.Style() + with open('themes/dark.json') as f: + dark = json.load(f) + self.style.theme_create('dark', 'default', dark) + self.style.theme_use('dark') if sys.platform == 'win32': self.w.wm_iconbitmap(default='EDMarketConnector.ico') @@ -523,7 +530,7 @@ def open_window(systray: 'SysTrayIcon') -> None: plugin_no = 0 for plugin in plug.PLUGINS: # Per plugin separator - plugin_sep = ttk.Separator(frame, name=f"plugin_hr_{plugin_no + 1}") + plugin_sep = ttk.Frame(frame, name=f"plugin_hr_{plugin_no + 1}", style='Sep.TFrame') # Per plugin frame, for it to use as its parent for own widgets plugin_frame = ttk.Frame( frame, @@ -564,6 +571,7 @@ def open_window(systray: 'SysTrayIcon') -> None: frame, name='themed_update_button', width=28, + anchor=tk.CENTER, state=tk.DISABLED ) @@ -681,7 +689,7 @@ def open_window(systray: 'SysTrayIcon') -> None: lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) - ttk.Separator(self.theme_menubar).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) + ttk.Frame(self.theme_menubar, style='Sep.TFrame').grid(columnspan=5, padx=self.PADX, sticky=tk.EW) theme.register(self.theme_minimize) # images aren't automatically registered theme.register(self.theme_close) self.blank_menubar = ttk.Frame(frame, name="blank_menubar") diff --git a/themes/dark.json b/themes/dark.json new file mode 100644 index 000000000..fc28b1e56 --- /dev/null +++ b/themes/dark.json @@ -0,0 +1,55 @@ +{ + "TFrame": { + "configure": { + "background": "grey4", + "highlightbackground": "#aa5500" + } + }, + "Sep.TFrame": { + "configure": { + "background": "#ff8000", + "padding": 2 + } + }, + "TLabel": { + "configure": { + "background": "grey4", + "foreground": "#ff8000", + "padding": 1 + }, + "map": { + "background": [ + ["active", "#ff8000"] + ], + "foreground": [ + ["active", "grey4"], + ["disabled", "#aa5500"] + ] + } + }, + "Hyperlink.TLabel": { + "configure": { + "foreground": "white" + } + }, + "TButton": { + "configure": { + "background": "grey4", + "foreground": "#ff8000", + "relief": "raised", + "padding": 2 + }, + "map": { + "background": [ + ["pressed", "#ff8000"] + ], + "foreground": [ + ["pressed", "grey4"], + ["disabled", "#aa5500"] + ], + "relief": [ + ["pressed", "sunken"] + ] + } + } +} diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 7fd2e84f2..0e59c305f 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -61,12 +61,15 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non """ self.font_u: tk_font.Font self.font_n = None + self.style = 'Hyperlink.TLabel' self.url = kw.pop('url', None) self.popup_copy = kw.pop('popup_copy', False) self.underline = kw.pop('underline', None) # override ttk.Label's underline - self.foreground = kw.get('foreground', 'blue') + self.foreground = kw.get('foreground', ttk.Style().lookup( + 'Hyperlink.TLabel', 'foreground')) + # ttk.Label doesn't support disabledforeground option self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup( - 'TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option + 'Hyperlink.TLabel', 'foreground', ('disabled',))) ttk.Label.__init__(self, master, **kw) self.bind('', self._click) From 19994acc642945a012baefbde97de423ebd307c0 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 13:32:19 +0100 Subject: [PATCH 08/59] Simplified style, fixed HyperlinkLabel underline font --- themes/dark.json | 17 +++++++++-------- ttkHyperlinkLabel.py | 13 +++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/themes/dark.json b/themes/dark.json index fc28b1e56..634405fce 100644 --- a/themes/dark.json +++ b/themes/dark.json @@ -1,20 +1,22 @@ { - "TFrame": { + ".": { "configure": { "background": "grey4", + "foreground": "#ff8000" + } + }, + "TFrame": { + "configure": { "highlightbackground": "#aa5500" } }, "Sep.TFrame": { "configure": { - "background": "#ff8000", "padding": 2 } }, "TLabel": { "configure": { - "background": "grey4", - "foreground": "#ff8000", "padding": 1 }, "map": { @@ -27,15 +29,14 @@ ] } }, - "Hyperlink.TLabel": { + "Link.TLabel": { "configure": { - "foreground": "white" + "foreground": "white", + "font": "TkDefaultFont" } }, "TButton": { "configure": { - "background": "grey4", - "foreground": "#ff8000", "relief": "raised", "padding": 2 }, diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 0e59c305f..688e13d16 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -61,15 +61,14 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non """ self.font_u: tk_font.Font self.font_n = None - self.style = 'Hyperlink.TLabel' + self.style = kw.get('style', 'Link.TLabel') self.url = kw.pop('url', None) self.popup_copy = kw.pop('popup_copy', False) self.underline = kw.pop('underline', None) # override ttk.Label's underline - self.foreground = kw.get('foreground', ttk.Style().lookup( - 'Hyperlink.TLabel', 'foreground')) + self.foreground = kw.get('foreground', ttk.Style().lookup(self.style, 'foreground')) # ttk.Label doesn't support disabledforeground option - self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup( - 'Hyperlink.TLabel', 'foreground', ('disabled',))) + self.disabledforeground = kw.pop('disabledforeground', + ttk.Style().lookup(self.style, 'foreground', ('disabled',))) ttk.Label.__init__(self, master, **kw) self.bind('', self._click) @@ -79,9 +78,7 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non self.bind('', self._leave) # set up initial appearance - self.configure(state=kw.get('state', tk.NORMAL), - text=kw.get('text'), - font=kw.get('font', ttk.Style().lookup('TLabel', 'font'))) + self.configure(font=kw.get('font', ttk.Style().lookup(self.style, 'font'))) # Add Menu Options self.plug_options = kw.pop('plug_options', None) From 9ba7ee5ccbbeb5d76e173d21f59b5e4582362b84 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 14:37:15 +0100 Subject: [PATCH 09/59] Set baseline for themes --- EDMarketConnector.py | 6 +++++- themes/dark.json | 2 +- ttkHyperlinkLabel.py | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4b40f0ef7..6e614984a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -465,9 +465,13 @@ def open_window(systray: 'SysTrayIcon') -> None: plug.load_plugins(master) self.style = ttk.Style() + # ttk.Separator does not allow to configure its thickness, so we have to make our own + self.style.configure('Sep.TFrame', padding=2, + background=self.style.lookup('TLabel', 'foreground', ['disabled'])) + self.style.configure('Link.TLabel', font='TkDefaultFont', foreground='blue') # HyperlinkLabel with open('themes/dark.json') as f: dark = json.load(f) - self.style.theme_create('dark', 'default', dark) + self.style.theme_create('dark', self.style.theme_use(), dark) self.style.theme_use('dark') if sys.platform == 'win32': diff --git a/themes/dark.json b/themes/dark.json index 634405fce..f3c14a297 100644 --- a/themes/dark.json +++ b/themes/dark.json @@ -12,7 +12,7 @@ }, "Sep.TFrame": { "configure": { - "padding": 2 + "background": "#ff8000" } }, "TLabel": { diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 688e13d16..b96c56d61 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -78,7 +78,8 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non self.bind('', self._leave) # set up initial appearance - self.configure(font=kw.get('font', ttk.Style().lookup(self.style, 'font'))) + self.configure(state=kw.get('state', tk.NORMAL), + font=kw.get('font', ttk.Style().lookup(self.style, 'font'))) # Add Menu Options self.plug_options = kw.pop('plug_options', None) From a034bb707a486c059153c492aad0dcdbc90b10b7 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 15:34:58 +0100 Subject: [PATCH 10/59] Integrated ttk themes in theme.apply() --- EDMarketConnector.py | 14 +------ theme.py | 84 ++++++++++++++++++++--------------------- themes/transparent.json | 57 ++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 55 deletions(-) create mode 100644 themes/transparent.json diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 6e614984a..f3190e14a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -400,7 +400,6 @@ def already_running_popup(): # isort: on -import json import tkinter as tk import tkinter.filedialog import tkinter.font @@ -412,7 +411,6 @@ def already_running_popup(): import protocol import stats import td -from commodity import COMMODITY_CSV from dashboard import dashboard from edmc_data import ship_name_map from hotkey import hotkeymgr @@ -446,6 +444,7 @@ def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly f self.minimizing = False self.w.rowconfigure(0, weight=1) self.w.columnconfigure(0, weight=1) + theme.initialize() # companion needs to be able to send <> events companion.session.set_tk_master(self.w) @@ -464,15 +463,6 @@ def open_window(systray: 'SysTrayIcon') -> None: self.systray.start() plug.load_plugins(master) - self.style = ttk.Style() - # ttk.Separator does not allow to configure its thickness, so we have to make our own - self.style.configure('Sep.TFrame', padding=2, - background=self.style.lookup('TLabel', 'foreground', ['disabled'])) - self.style.configure('Link.TLabel', font='TkDefaultFont', foreground='blue') # HyperlinkLabel - with open('themes/dark.json') as f: - dark = json.load(f) - self.style.theme_create('dark', self.style.theme_use(), dark) - self.style.theme_use('dark') if sys.platform == 'win32': self.w.wm_iconbitmap(default='EDMarketConnector.ico') @@ -966,7 +956,7 @@ def export_market_data(self, data: 'CAPIData') -> bool: # noqa: CCR001 # Fixup anomalies in the comodity data fixed = companion.fixup(data) if output_flags & config.OUT_MKT_CSV: - commodity.export(fixed, COMMODITY_CSV) + commodity.export(fixed, commodity.COMMODITY_CSV) if output_flags & config.OUT_MKT_TD: td.export(fixed) diff --git a/theme.py b/theme.py index 796ad0ed5..929e34ada 100644 --- a/theme.py +++ b/theme.py @@ -10,11 +10,11 @@ """ from __future__ import annotations +import json import os import sys import tkinter as tk from os.path import join -from tkinter import font as tk_font from tkinter import ttk from typing import Callable from l10n import translations as tr @@ -131,6 +131,7 @@ class _Theme: THEME_DEFAULT = 0 THEME_DARK = 1 THEME_TRANSPARENT = 2 + style: ttk.Style def __init__(self) -> None: self.active: int | None = None # Starts out with no theme @@ -141,6 +142,28 @@ def __init__(self) -> None: self.current: dict = {} self.default_ui_scale: float | None = None # None == not yet known self.startup_ui_scale: int | None = None + self.default_ttk_theme = 'clam' + + def initialize(self): + self.style = ttk.Style() + if sys.platform == 'win32': + self.default_ttk_theme = self.style.theme_use() + + # ttk.Separator does not allow to configure its thickness, so we have to make our own + self.style.configure('Sep.TFrame', padding=2, + background=self.style.lookup('TLabel', 'foreground', ['disabled'])) + self.style.configure('Link.TLabel', font='TkDefaultFont', foreground='blue') # HyperlinkLabel + + # Default dark theme colors + if not config.get_str('dark_text'): + config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker + if not config.get_str('dark_highlight'): + config.set('dark_highlight', 'white') + + with open('themes/dark.json') as f: + self.style.theme_create('dark', self.default_ttk_theme, json.load(f)) + with open('themes/transparent.json') as f: + self.style.theme_create('transparent', self.default_ttk_theme, json.load(f)) def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 # Note widget and children for later application of a theme. Note if @@ -250,48 +273,23 @@ def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: logger.exception(f'Failure configuring image: {image=}') # Set up colors - def _colors(self, root: tk.Tk, theme: int) -> None: - style = ttk.Style() - if sys.platform == 'linux': - style.theme_use('clam') - - # Default dark theme colors - if not config.get_str('dark_text'): - config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker - if not config.get_str('dark_highlight'): - config.set('dark_highlight', 'white') - + def _colors(self, theme: int) -> None: if theme == self.THEME_DEFAULT: - # (Mostly) system colors - style = ttk.Style() - self.current = { - 'background': (style.lookup('TLabel', 'background')), - 'foreground': style.lookup('TLabel', 'foreground'), - 'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or - style.lookup('TLabel', 'background', ['active'])), - 'activeforeground': (sys.platform == 'win32' and 'SystemHighlightText' or - style.lookup('TLabel', 'foreground', ['active'])), - 'disabledforeground': style.lookup('TLabel', 'foreground', ['disabled']), - 'highlight': 'blue', - 'font': 'TkDefaultFont', - } - - 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(tr.tl('Cmdr')[0]) < 0x3000 and - tk_font.Font(family='Euro Caps', size=10, weight=tk_font.NORMAL) or - 'TkDefaultFont'), - } + self.style.theme_use(self.default_ttk_theme) + elif theme == self.THEME_DARK: + self.style.theme_use('dark') + elif theme == self.THEME_TRANSPARENT: + self.style.theme_use('transparent') + + self.current = { + 'background': self.style.lookup('TLabel', 'background'), + 'foreground': self.style.lookup('TLabel', 'foreground'), + 'activebackground': self.style.lookup('TLabel', 'background', ['active']), + 'activeforeground': self.style.lookup('TLabel', 'foreground', ['active']), + 'disabledforeground': self.style.lookup('TLabel', 'foreground', ['disabled']), + 'highlight': self.style.lookup('Link.TLabel', 'foreground'), + 'font': self.style.lookup('.', 'font') or 'TkDefaultFont', + } def update(self, widget: tk.Widget) -> None: """ @@ -390,7 +388,7 @@ def _update_widget(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: C def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 theme = config.get_int('theme') - self._colors(root, theme) + self._colors(theme) # Apply colors for widget in set(self.widgets): diff --git a/themes/transparent.json b/themes/transparent.json new file mode 100644 index 000000000..4b99780a5 --- /dev/null +++ b/themes/transparent.json @@ -0,0 +1,57 @@ +{ + ".": { + "configure": { + "background": "grey4", + "foreground": "#ff8000", + "font": "\"Euro Caps\" 10" + } + }, + "TFrame": { + "configure": { + "highlightbackground": "#aa5500" + } + }, + "Sep.TFrame": { + "configure": { + "background": "#ff8000" + } + }, + "TLabel": { + "configure": { + "padding": 1 + }, + "map": { + "background": [ + ["active", "#ff8000"] + ], + "foreground": [ + ["active", "grey4"], + ["disabled", "#aa5500"] + ] + } + }, + "Link.TLabel": { + "configure": { + "foreground": "white", + "font": "\"Euro Caps\" 10" + } + }, + "TButton": { + "configure": { + "relief": "raised", + "padding": 2 + }, + "map": { + "background": [ + ["pressed", "#ff8000"] + ], + "foreground": [ + ["pressed", "grey4"], + ["disabled", "#aa5500"] + ], + "relief": [ + ["pressed", "sunken"] + ] + } + } +} From 7fd086bb6dd9bf9d3063f8f0fcf40803d7661609 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 15:38:54 +0100 Subject: [PATCH 11/59] oops --- EDMarketConnector.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index f3190e14a..7fb62b488 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -350,8 +350,7 @@ def already_running_popup(): handle_edmc_callback_or_foregrounding() - if False: - #if locked == JournalLockResult.ALREADY_LOCKED: + if locked == JournalLockResult.ALREADY_LOCKED: # There's a copy already running. logger.info("An EDMarketConnector.exe process was already running, exiting.") From 899c773294da51d5a426a668d25e0a337658e1ff Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 15:58:47 +0100 Subject: [PATCH 12/59] Leftover import --- theme.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/theme.py b/theme.py index 929e34ada..af40d0283 100644 --- a/theme.py +++ b/theme.py @@ -6,7 +6,7 @@ See LICENSE file. Because of various ttk limitations this app is an unholy mix of Tk and ttk widgets. -So can't use ttk's theme support. So have to change colors manually. +So can't just use ttk's theme support. So have to change colors manually. """ from __future__ import annotations @@ -17,7 +17,6 @@ from os.path import join from tkinter import ttk from typing import Callable -from l10n import translations as tr from config import config from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel From 47de60721345f27505e1209fb992832e59787639 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 18:08:36 +0100 Subject: [PATCH 13/59] Applying theme palette to Tk widgets more reliably --- EDMarketConnector.py | 7 -- theme.py | 209 ++++++------------------------------------- 2 files changed, 26 insertions(+), 190 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 7fb62b488..c75ff85b0 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -638,10 +638,6 @@ def open_window(systray: 'SysTrayIcon') -> None: self.w.bind("", self.default_iconify) self.w.protocol("WM_DELETE_WINDOW", self.onexit) - theme.register(self.menubar) # menus and children aren't automatically registered - theme.register(self.file_menu) - theme.register(self.edit_menu) - theme.register(self.help_menu) # Alternate title bar and menu for dark theme self.theme_menubar = ttk.Frame(frame, name="alternate_menubar") @@ -683,8 +679,6 @@ def open_window(systray: 'SysTrayIcon') -> None: e.widget.winfo_rooty() + e.widget.winfo_height())) ttk.Frame(self.theme_menubar, style='Sep.TFrame').grid(columnspan=5, padx=self.PADX, sticky=tk.EW) - theme.register(self.theme_minimize) # images aren't automatically registered - theme.register(self.theme_close) self.blank_menubar = ttk.Frame(frame, name="blank_menubar") ttk.Label(self.blank_menubar).grid() ttk.Label(self.blank_menubar).grid() @@ -712,7 +706,6 @@ def open_window(systray: 'SysTrayIcon') -> None: self.w.attributes('-topmost', config.get_int('always_ontop') and 1 or 0) - theme.register(frame) theme.apply(self.w) self.w.bind('', self.onmap) # Special handling for overrideredict diff --git a/theme.py b/theme.py index af40d0283..3669bb4ff 100644 --- a/theme.py +++ b/theme.py @@ -17,9 +17,9 @@ from os.path import join from tkinter import ttk from typing import Callable +from typing_extensions import deprecated from config import config from EDMCLogging import get_main_logger -from ttkHyperlinkLabel import HyperlinkLabel logger = get_main_logger() @@ -135,10 +135,8 @@ class _Theme: def __init__(self) -> None: self.active: int | None = None # Starts out with no theme self.minwidth: int | None = None - self.widgets: dict[tk.Widget | tk.BitmapImage, set] = {} + self.bitmaps: list = [] self.widgets_pair: list = [] - self.defaults: dict = {} - self.current: dict = {} self.default_ui_scale: float | None = None # None == not yet known self.startup_ui_scale: int | None = None self.default_ttk_theme = 'clam' @@ -164,70 +162,6 @@ def initialize(self): with open('themes/transparent.json') as f: self.style.theme_create('transparent', self.default_ttk_theme, json.load(f)) - def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 - # Note widget and children for later application of a theme. Note if - # the widget has explicit fg or bg attributes. - assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget - if not self.defaults: - # Can't initialise this til window is created # Windows - self.defaults = { - 'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText - 'bg': tk.Label()['background'], # SystemButtonFace, White - 'font': tk.Label()['font'], # TkDefaultFont - 'bitmapfg': tk.BitmapImage()['foreground'], # '-foreground {} {} #000000 #000000' - 'bitmapbg': tk.BitmapImage()['background'], # '-background {} {} {} {}' - 'entryfg': tk.Entry()['foreground'], # SystemWindowText, Black - 'entrybg': tk.Entry()['background'], # SystemWindow, systemWindowBody - 'entryfont': tk.Entry()['font'], # TkTextFont - 'frame': tk.Frame()['background'], # SystemButtonFace, systemWindowBody - 'menufg': tk.Menu()['foreground'], # SystemMenuText, - 'menubg': tk.Menu()['background'], # SystemMenu, - 'menufont': tk.Menu()['font'], # TkTextFont - } - - if widget not in self.widgets and not isinstance(widget, ttk.Widget): - # No general way to tell whether the user has overridden, so compare against widget-type specific defaults - attribs = set() - if isinstance(widget, tk.BitmapImage): - if widget['foreground'] not in ['', self.defaults['bitmapfg']]: - attribs.add('fg') - if widget['background'] not in ['', self.defaults['bitmapbg']]: - attribs.add('bg') - elif isinstance(widget, tk.Entry): - if widget['foreground'] not in ['', self.defaults['entryfg']]: - attribs.add('fg') - if widget['background'] not in ['', self.defaults['entrybg']]: - attribs.add('bg') - if 'font' in widget.keys() and str(widget['font']) not in ['', self.defaults['entryfont']]: - attribs.add('font') - elif isinstance(widget, (tk.Canvas, tk.Frame)): - if ( - ('background' in widget.keys() or isinstance(widget, tk.Canvas)) - and widget['background'] not in ['', self.defaults['frame']] - ): - attribs.add('bg') - elif isinstance(widget, HyperlinkLabel): - pass # Hack - HyperlinkLabel changes based on state, so skip - elif isinstance(widget, tk.Menu): - if widget['foreground'] not in ['', self.defaults['menufg']]: - attribs.add('fg') - if widget['background'] not in ['', self.defaults['menubg']]: - attribs.add('bg') - if widget['font'] not in ['', self.defaults['menufont']]: - attribs.add('font') - else: # tk.Button, tk.Label - if 'foreground' in widget.keys() and widget['foreground'] not in ['', self.defaults['fg']]: - attribs.add('fg') - if 'background' in widget.keys() and widget['background'] not in ['', self.defaults['bg']]: - attribs.add('bg') - if 'font' in widget.keys() and widget['font'] not in ['', self.defaults['font']]: - attribs.add('font') - self.widgets[widget] = attribs - - if isinstance(widget, (tk.Frame, ttk.Frame)): - for child in widget.winfo_children(): - self.register(child) - def register_alternate(self, pair: tuple, gridopts: dict) -> None: self.widgets_pair.append((pair, gridopts)) @@ -237,6 +171,8 @@ def button_bind( widget.bind('', command) widget.bind('', lambda e: self._enter(e, image)) widget.bind('', lambda e: self._leave(e, image)) + if image: + self.bitmaps.append(image) def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None: widget = event.widget @@ -249,8 +185,8 @@ def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None: if image: try: - image.configure(foreground=self.current['activeforeground'], - background=self.current['activebackground']) + image['background'] = self.style.lookup('TLabel', 'background', ['active']) + image['foreground'] = self.style.lookup('TLabel', 'foreground', ['active']) except Exception: logger.exception(f'Failure configuring image: {image=}') @@ -266,30 +202,13 @@ def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: if image: try: - image.configure(foreground=self.current['foreground'], background=self.current['background']) + image['background'] = self.style.lookup('TLabel', 'background') + image['foreground'] = self.style.lookup('TLabel', 'foreground') except Exception: logger.exception(f'Failure configuring image: {image=}') - # Set up colors - def _colors(self, theme: int) -> None: - if theme == self.THEME_DEFAULT: - self.style.theme_use(self.default_ttk_theme) - elif theme == self.THEME_DARK: - self.style.theme_use('dark') - elif theme == self.THEME_TRANSPARENT: - self.style.theme_use('transparent') - - self.current = { - 'background': self.style.lookup('TLabel', 'background'), - 'foreground': self.style.lookup('TLabel', 'foreground'), - 'activebackground': self.style.lookup('TLabel', 'background', ['active']), - 'activeforeground': self.style.lookup('TLabel', 'foreground', ['active']), - 'disabledforeground': self.style.lookup('TLabel', 'foreground', ['disabled']), - 'highlight': self.style.lookup('Link.TLabel', 'foreground'), - 'font': self.style.lookup('.', 'font') or 'TkDefaultFont', - } - + @deprecated('Theme colors are now applied automatically, even after initialization') def update(self, widget: tk.Widget) -> None: """ Apply current theme to a widget and its children. @@ -298,103 +217,27 @@ def update(self, widget: tk.Widget) -> None: :param widget: Target widget. """ assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget - if not self.current: - return # No need to call this for widgets created in plugin_app() - - self.register(widget) - self._update_widget(widget) - if isinstance(widget, (tk.Frame, ttk.Frame)): - for child in widget.winfo_children(): - self._update_widget(child) - - # Apply current theme to a single widget - def _update_widget(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 - if widget not in self.widgets: - if isinstance(widget, tk.Widget): - w_class = widget.winfo_class() - w_keys: list[str] = widget.keys() - - else: - # There is no tk.BitmapImage.winfo_class() - w_class = '' - # There is no tk.BitmapImage.keys() - w_keys = [] - - assert_str = f'{w_class} {widget} "{"text" in w_keys and widget["text"]}"' - raise AssertionError(assert_str) - - attribs: set = self.widgets.get(widget, set()) - - try: - if isinstance(widget, tk.BitmapImage): - # not a widget - if 'fg' not in attribs: - widget['foreground'] = self.current['foreground'] - - if 'bg' not in attribs: - widget['background'] = self.current['background'] - - elif 'cursor' in widget.keys() and str(widget['cursor']) not in ['', 'arrow']: - # Hack - highlight widgets like HyperlinkLabel with a non-default cursor - if 'fg' not in attribs: - widget['foreground'] = self.current['highlight'] - if 'insertbackground' in widget.keys(): # tk.Entry - widget['insertbackground'] = self.current['foreground'] - - if 'bg' not in attribs: - widget['background'] = self.current['background'] - if 'highlightbackground' in widget.keys(): # tk.Entry - widget['highlightbackground'] = self.current['background'] - - if 'font' not in attribs: - widget['font'] = self.current['font'] - - elif 'activeforeground' in widget.keys(): - # e.g. tk.Button, tk.Label, tk.Menu - if 'fg' not in attribs: - widget['foreground'] = self.current['foreground'] - widget['activeforeground'] = self.current['activeforeground'] - widget['disabledforeground'] = self.current['disabledforeground'] - - if 'bg' not in attribs: - widget['background'] = self.current['background'] - widget['activebackground'] = self.current['activebackground'] - - if 'font' not in attribs: - widget['font'] = self.current['font'] - - elif 'foreground' in widget.keys(): - # e.g. ttk.Label - if 'fg' not in attribs: - widget['foreground'] = self.current['foreground'] - - if 'bg' not in attribs: - widget['background'] = self.current['background'] - - if 'font' not in attribs: - widget['font'] = self.current['font'] - - elif 'background' in widget.keys() or isinstance(widget, tk.Canvas): - # e.g. Frame, Canvas - if 'bg' not in attribs: - widget['background'] = self.current['background'] - widget['highlightbackground'] = self.current['disabledforeground'] - - except Exception: - logger.exception(f'Plugin widget issue ? {widget=}') - - # Apply configured theme def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 theme = config.get_int('theme') - self._colors(theme) + if theme == self.THEME_DEFAULT: + self.style.theme_use(self.default_ttk_theme) + elif theme == self.THEME_DARK: + self.style.theme_use('dark') + elif theme == self.THEME_TRANSPARENT: + self.style.theme_use('transparent') - # Apply colors - for widget in set(self.widgets): - if isinstance(widget, tk.Widget) and not widget.winfo_exists(): - self.widgets.pop(widget) # has been destroyed - else: - self._update_widget(widget) + root.tk_setPalette( + background=self.style.lookup('TLabel', 'background'), + foreground=self.style.lookup('TLabel', 'foreground'), + activebackground=self.style.lookup('TLabel', 'background', ['active']), + activeforeground=self.style.lookup('TLabel', 'foreground', ['active']), + disabledforeground=self.style.lookup('TLabel', 'foreground', ['disabled']), + highlight=self.style.lookup('Link.TLabel', 'foreground'), + ) + for image in self.bitmaps: + image['background'] = self.style.lookup('TLabel', 'background') + image['foreground'] = self.style.lookup('TLabel', 'foreground') # Switch menus for pair, gridopts in self.widgets_pair: From 7c4ea7020f59ac2573fc95108e01923de9fbe230 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 18:25:58 +0100 Subject: [PATCH 14/59] Caught a plugin using theme.register() directly, so reinstated as stub --- theme.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/theme.py b/theme.py index 3669bb4ff..770c92c1e 100644 --- a/theme.py +++ b/theme.py @@ -162,6 +162,10 @@ def initialize(self): with open('themes/transparent.json') as f: self.style.theme_create('transparent', self.default_ttk_theme, json.load(f)) + @deprecated('Theme colors are now applied automatically, even after initialization') + def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 + assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget + def register_alternate(self, pair: tuple, gridopts: dict) -> None: self.widgets_pair.append((pair, gridopts)) From 881840c5a959f02ed8971c2e7f88894379c78f3d Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 27 May 2024 19:17:07 +0100 Subject: [PATCH 15/59] Added Ttk catalog plugin (enabled by flag) --- EDMarketConnector.py | 9 ++ config/__init__.py | 14 ++ plug.py | 16 ++- plugins/_ttk_catalog.py | 297 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 plugins/_ttk_catalog.py diff --git a/EDMarketConnector.py b/EDMarketConnector.py index c75ff85b0..aa278d1a2 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -184,6 +184,12 @@ '--killswitches-file', help='Specify a custom killswitches file', ) + + parser.add_argument( + '--ttk-catalog', + help='Replace plugins with a catalog of Ttk widgets', + action='store_true', + ) ########################################################################### args: argparse.Namespace = parser.parse_args() @@ -220,6 +226,9 @@ if args.eddn_tracking_ui: config.set_eddn_tracking_ui() + if args.ttk_catalog: + config.set_ttk_catalog() + if args.force_edmc_protocol: if sys.platform == 'win32': config.set_auth_force_edmc_protocol() diff --git a/config/__init__.py b/config/__init__.py index 1a0ae24ae..9adf79304 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -202,6 +202,7 @@ class AbstractConfig(abc.ABC): __auth_force_edmc_protocol = False # Should we force edmc:// protocol ? __eddn_url = None # Non-default EDDN URL __eddn_tracking_ui = False # Show EDDN tracking UI ? + __ttk_catalog = False # Load Ttk catalog plugin ? def __init__(self) -> None: self.home_path = pathlib.Path.home() @@ -245,6 +246,19 @@ def auth_force_edmc_protocol(self) -> bool: """ return self.__auth_force_edmc_protocol + def set_ttk_catalog(self): + """Set flag to load the Ttk widget catalog plugin.""" + self.__ttk_catalog = True + + @property + def ttk_catalog(self) -> bool: + """ + Determine if the Ttk widget catalog plugin is loaded. + + :return: bool - Should the Ttk catalog plugin be loaded? + """ + return self.__ttk_catalog + def set_eddn_url(self, eddn_url: str): """Set the specified eddn URL.""" self.__eddn_url = eddn_url diff --git a/plug.py b/plug.py index bd98f2bbd..162d756a4 100644 --- a/plug.py +++ b/plug.py @@ -162,8 +162,11 @@ def load_plugins(master: tk.Tk) -> None: # Add plugin folder to load path so packages can be loaded from plugin folder sys.path.append(config.plugin_dir) - found = _load_found_plugins() - PLUGINS.extend(sorted(found, key=lambda p: operator.attrgetter('name')(p).lower())) + if config.ttk_catalog: + PLUGINS.append(_load_ttk_catalog_plugin()) + else: + found = _load_found_plugins() + PLUGINS.extend(sorted(found, key=lambda p: operator.attrgetter('name')(p).lower())) def _load_internal_plugins(): @@ -179,6 +182,15 @@ def _load_internal_plugins(): return internal +def _load_ttk_catalog_plugin(): + try: + plugin = Plugin('ttk_catalog', os.path.join(config.internal_plugin_dir_path, '_ttk_catalog.py'), logger) + plugin.folder = None + return plugin + except Exception: + logger.exception(f'Failure loading internal Plugin "ttk_catalog"') + + def _load_found_plugins(): found = [] # Load any plugins that are also packages first, but note it's *still* diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py new file mode 100644 index 000000000..ed7dae3bb --- /dev/null +++ b/plugins/_ttk_catalog.py @@ -0,0 +1,297 @@ +""" +_ttk_catalog.py - Catalog of ttk widgets. + +Copyright (c) EDCD, All Rights Reserved +Licensed under the GNU General Public License. +See LICENSE file. + +Based on https://github.com/rdbende/Azure-ttk-theme/blob/main/example.py +""" +import tkinter as tk +from tkinter import ttk + +from EDMCLogging import get_main_logger + +logger = get_main_logger() + + +class Catalog(ttk.Frame): + def __init__(self, parent): + super().__init__() + + # Make the app responsive + for index in [0, 1, 2]: + self.columnconfigure(index=index, weight=1) + self.rowconfigure(index=index, weight=1) + + # Create value lists + self.option_menu_list = ["", "OptionMenu", "Option 1", "Option 2"] + self.combo_list = ["Combobox", "Editable item 1", "Editable item 2"] + self.readonly_combo_list = ["Readonly combobox", "Item 1", "Item 2"] + + # Create control variables + self.var_0 = tk.BooleanVar() + self.var_1 = tk.BooleanVar(value=True) + self.var_2 = tk.BooleanVar() + self.var_3 = tk.IntVar(value=2) + self.var_4 = tk.StringVar(value=self.option_menu_list[1]) + self.var_5 = tk.DoubleVar(value=75.0) + + # Create widgets :) + self.setup_widgets() + + def setup_widgets(self): + # Create a Frame for the Checkbuttons + check_frame = ttk.LabelFrame(self, text="Checkbuttons", padding=(20, 10)) + check_frame.grid( + row=0, column=0, padx=(20, 10), pady=(20, 10), sticky="nsew" + ) + + # Checkbuttons + check_1 = ttk.Checkbutton( + check_frame, text="Unchecked", variable=self.var_0 + ) + check_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") + + check_2 = ttk.Checkbutton( + check_frame, text="Checked", variable=self.var_1 + ) + check_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") + + check_3 = ttk.Checkbutton( + check_frame, text="Third state", variable=self.var_2 + ) + check_3.state(["alternate"]) + check_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") + + check_4 = ttk.Checkbutton( + check_frame, text="Disabled", state="disabled" + ) + check_4.state(["disabled !alternate"]) + check_4.grid(row=3, column=0, padx=5, pady=10, sticky="nsew") + + # Separator + separator = ttk.Separator(self) + separator.grid(row=1, column=0, padx=(20, 10), pady=10, sticky="ew") + + # Create a Frame for the Radiobuttons + radio_frame = ttk.LabelFrame(self, text="Radiobuttons", padding=(20, 10)) + radio_frame.grid(row=2, column=0, padx=(20, 10), pady=10, sticky="nsew") + + # Radiobuttons + radio_1 = ttk.Radiobutton( + radio_frame, text="Unselected", variable=self.var_3, value=1 + ) + radio_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") + radio_2 = ttk.Radiobutton( + radio_frame, text="Selected", variable=self.var_3, value=2 + ) + radio_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") + radio_4 = ttk.Radiobutton( + radio_frame, text="Disabled", state="disabled" + ) + radio_4.grid(row=3, column=0, padx=5, pady=10, sticky="nsew") + + # Create a Frame for input widgets + widgets_frame = ttk.Frame(self, padding=(0, 0, 0, 10)) + widgets_frame.grid( + row=0, column=1, padx=10, pady=(30, 10), sticky="nsew", rowspan=3 + ) + widgets_frame.columnconfigure(index=0, weight=1) + + # Entry + entry = ttk.Entry(widgets_frame) + entry.insert(0, "Entry") + entry.grid(row=0, column=0, padx=5, pady=(0, 10), sticky="ew") + + # Spinbox + spinbox = ttk.Spinbox(widgets_frame, from_=0, to=100, increment=0.1) + spinbox.insert(0, "Spinbox") + spinbox.grid(row=1, column=0, padx=5, pady=10, sticky="ew") + + # Combobox + combobox = ttk.Combobox(widgets_frame, values=self.combo_list) + combobox.current(0) + combobox.grid(row=2, column=0, padx=5, pady=10, sticky="ew") + + # Read-only combobox + readonly_combo = ttk.Combobox( + widgets_frame, state="readonly", values=self.readonly_combo_list + ) + readonly_combo.current(0) + readonly_combo.grid(row=3, column=0, padx=5, pady=10, sticky="ew") + + # Menu for the Menubutton + menu = tk.Menu(self) + menu.add_command(label="Menu item 1") + menu.add_command(label="Menu item 2") + menu.add_separator() + menu.add_command(label="Menu item 3") + menu.add_command(label="Menu item 4") + + # Menubutton + menubutton = ttk.Menubutton( + widgets_frame, text="Menubutton", menu=menu, direction="below" + ) + menubutton.grid(row=4, column=0, padx=5, pady=10, sticky="nsew") + + # OptionMenu + optionmenu = ttk.OptionMenu( + widgets_frame, self.var_4, *self.option_menu_list + ) + optionmenu.grid(row=5, column=0, padx=5, pady=10, sticky="nsew") + + # Button + button = ttk.Button(widgets_frame, text="Button") + button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") + + # Accentbutton + accentbutton = ttk.Button( + widgets_frame, text="Accent button", style="Accent.TButton" + ) + accentbutton.grid(row=7, column=0, padx=5, pady=10, sticky="nsew") + + # Togglebutton + togglebutton = ttk.Checkbutton( + widgets_frame, text="Toggle button", style="Toggle.TButton" + ) + togglebutton.grid(row=8, column=0, padx=5, pady=10, sticky="nsew") + + # Switch + switch = ttk.Checkbutton( + widgets_frame, text="Switch", style="Switch.TCheckbutton" + ) + switch.grid(row=9, column=0, padx=5, pady=10, sticky="nsew") + + # Panedwindow + paned = ttk.PanedWindow(self) + paned.grid(row=0, column=2, pady=(25, 5), sticky="nsew", rowspan=3) + + # Pane #1 + pane_1 = ttk.Frame(paned, padding=5) + paned.add(pane_1, weight=1) + + # Scrollbar + scrollbar = ttk.Scrollbar(pane_1) + scrollbar.pack(side="right", fill="y") + + # Treeview + treeview = ttk.Treeview( + pane_1, + selectmode="browse", + yscrollcommand=scrollbar.set, + columns=(1, 2), + height=10, + ) + treeview.pack(expand=True, fill="both") + scrollbar.config(command=treeview.yview) + + # Treeview columns + treeview.column("#0", anchor="w", width=120) + treeview.column(1, anchor="w", width=120) + treeview.column(2, anchor="w", width=120) + + # Treeview headings + treeview.heading("#0", text="Column 1", anchor="center") + treeview.heading(1, text="Column 2", anchor="center") + treeview.heading(2, text="Column 3", anchor="center") + + # Define treeview data + treeview_data = [ + ("", 1, "Parent", ("Item 1", "Value 1")), + (1, 2, "Child", ("Subitem 1.1", "Value 1.1")), + (1, 3, "Child", ("Subitem 1.2", "Value 1.2")), + (1, 4, "Child", ("Subitem 1.3", "Value 1.3")), + (1, 5, "Child", ("Subitem 1.4", "Value 1.4")), + ("", 6, "Parent", ("Item 2", "Value 2")), + (6, 7, "Child", ("Subitem 2.1", "Value 2.1")), + (6, 8, "Sub-parent", ("Subitem 2.2", "Value 2.2")), + (8, 9, "Child", ("Subitem 2.2.1", "Value 2.2.1")), + (8, 10, "Child", ("Subitem 2.2.2", "Value 2.2.2")), + (8, 11, "Child", ("Subitem 2.2.3", "Value 2.2.3")), + (6, 12, "Child", ("Subitem 2.3", "Value 2.3")), + (6, 13, "Child", ("Subitem 2.4", "Value 2.4")), + ("", 14, "Parent", ("Item 3", "Value 3")), + (14, 15, "Child", ("Subitem 3.1", "Value 3.1")), + (14, 16, "Child", ("Subitem 3.2", "Value 3.2")), + (14, 17, "Child", ("Subitem 3.3", "Value 3.3")), + (14, 18, "Child", ("Subitem 3.4", "Value 3.4")), + ("", 19, "Parent", ("Item 4", "Value 4")), + (19, 20, "Child", ("Subitem 4.1", "Value 4.1")), + (19, 21, "Sub-parent", ("Subitem 4.2", "Value 4.2")), + (21, 22, "Child", ("Subitem 4.2.1", "Value 4.2.1")), + (21, 23, "Child", ("Subitem 4.2.2", "Value 4.2.2")), + (21, 24, "Child", ("Subitem 4.2.3", "Value 4.2.3")), + (19, 25, "Child", ("Subitem 4.3", "Value 4.3")), + ] + + # Insert treeview data + for item in treeview_data: + treeview.insert( + parent=item[0], index="end", iid=item[1], text=item[2], values=item[3] + ) + if item[0] == "" or item[1] in {8, 21}: + treeview.item(item[1], open=True) # Open parents + + # Select and scroll + treeview.selection_set(10) + treeview.see(7) + + # Notebook, pane #2 + pane_2 = ttk.Frame(paned, padding=5) + paned.add(pane_2, weight=3) + + # Notebook, pane #2 + notebook = ttk.Notebook(pane_2) + notebook.pack(fill="both", expand=True) + + # Tab #1 + tab_1 = ttk.Frame(notebook) + for index in [0, 1]: + tab_1.columnconfigure(index=index, weight=1) + tab_1.rowconfigure(index=index, weight=1) + notebook.add(tab_1, text="Tab 1") + + # Scale + scale = ttk.Scale( + tab_1, + from_=100, + to=0, + variable=self.var_5, + command=lambda event: self.var_5.set(scale.get()), + ) + scale.grid(row=0, column=0, padx=(20, 10), pady=(20, 0), sticky="ew") + + # Progressbar + progress = ttk.Progressbar( + tab_1, value=0, variable=self.var_5, mode="determinate" + ) + progress.grid(row=0, column=1, padx=(10, 20), pady=(20, 0), sticky="ew") + + # Label + label = ttk.Label( + tab_1, + text="ttk widgets for EDMC", + justify="center", + font=("-size", 15, "-weight", "bold"), + ) + label.grid(row=1, column=0, pady=10, columnspan=2) + + # Tab #2 + tab_2 = ttk.Frame(notebook) + notebook.add(tab_2, text="Tab 2") + + # Tab #3 + tab_3 = ttk.Frame(notebook) + notebook.add(tab_3, text="Tab 3") + + # Sizegrip + sizegrip = ttk.Sizegrip(self) + sizegrip.grid(row=100, column=100, padx=(0, 5), pady=(0, 5)) + + +def plugin_start3(path: str) -> str: + return 'TtkCatalog' + + +plugin_app = Catalog From b971c261ae799273b7cb7dfaba0a3b2ba81dc35e Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 28 May 2024 12:15:12 +0100 Subject: [PATCH 16/59] Fixed TTK Catalog positioning --- plugins/_ttk_catalog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py index ed7dae3bb..3864d0bc3 100644 --- a/plugins/_ttk_catalog.py +++ b/plugins/_ttk_catalog.py @@ -16,8 +16,8 @@ class Catalog(ttk.Frame): - def __init__(self, parent): - super().__init__() + def __init__(self, parent: ttk.Frame): + super().__init__(parent) # Make the app responsive for index in [0, 1, 2]: From 2b8158d76bc97e5521e5d8222e0ce196996947cb Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sat, 1 Jun 2024 23:49:22 +0100 Subject: [PATCH 17/59] Ttk catalog: replaced alternative styles with HyperlinkLabel frame --- plugins/_ttk_catalog.py | 103 ++++++++++++++-------------------------- ttkHyperlinkLabel.py | 2 +- 2 files changed, 36 insertions(+), 69 deletions(-) diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py index 3864d0bc3..d7ffa9db1 100644 --- a/plugins/_ttk_catalog.py +++ b/plugins/_ttk_catalog.py @@ -11,9 +11,12 @@ from tkinter import ttk from EDMCLogging import get_main_logger +from ttkHyperlinkLabel import HyperlinkLabel logger = get_main_logger() +URL = 'https://github.com/EDCD/EDMarketConnector' + class Catalog(ttk.Frame): def __init__(self, parent: ttk.Frame): @@ -41,32 +44,20 @@ def __init__(self, parent: ttk.Frame): self.setup_widgets() def setup_widgets(self): - # Create a Frame for the Checkbuttons check_frame = ttk.LabelFrame(self, text="Checkbuttons", padding=(20, 10)) - check_frame.grid( - row=0, column=0, padx=(20, 10), pady=(20, 10), sticky="nsew" - ) + check_frame.grid(row=0, column=0, padx=(20, 10), pady=(20, 10), sticky="nsew") - # Checkbuttons - check_1 = ttk.Checkbutton( - check_frame, text="Unchecked", variable=self.var_0 - ) + check_1 = ttk.Checkbutton(check_frame, text="Unchecked", variable=self.var_0) check_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") - check_2 = ttk.Checkbutton( - check_frame, text="Checked", variable=self.var_1 - ) + check_2 = ttk.Checkbutton(check_frame, text="Checked", variable=self.var_1) check_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") - check_3 = ttk.Checkbutton( - check_frame, text="Third state", variable=self.var_2 - ) + check_3 = ttk.Checkbutton(check_frame, text="Third state", variable=self.var_2) check_3.state(["alternate"]) check_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") - check_4 = ttk.Checkbutton( - check_frame, text="Disabled", state="disabled" - ) + check_4 = ttk.Checkbutton(check_frame, text="Disabled", state="disabled") check_4.state(["disabled !alternate"]) check_4.grid(row=3, column=0, padx=5, pady=10, sticky="nsew") @@ -78,25 +69,18 @@ def setup_widgets(self): radio_frame = ttk.LabelFrame(self, text="Radiobuttons", padding=(20, 10)) radio_frame.grid(row=2, column=0, padx=(20, 10), pady=10, sticky="nsew") - # Radiobuttons - radio_1 = ttk.Radiobutton( - radio_frame, text="Unselected", variable=self.var_3, value=1 - ) + radio_1 = ttk.Radiobutton(radio_frame, text="Unselected", variable=self.var_3, value=1) radio_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") - radio_2 = ttk.Radiobutton( - radio_frame, text="Selected", variable=self.var_3, value=2 - ) + + radio_2 = ttk.Radiobutton(radio_frame, text="Selected", variable=self.var_3, value=2) radio_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") - radio_4 = ttk.Radiobutton( - radio_frame, text="Disabled", state="disabled" - ) - radio_4.grid(row=3, column=0, padx=5, pady=10, sticky="nsew") + + radio_3 = ttk.Radiobutton(radio_frame, text="Disabled", state="disabled") + radio_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") # Create a Frame for input widgets widgets_frame = ttk.Frame(self, padding=(0, 0, 0, 10)) - widgets_frame.grid( - row=0, column=1, padx=10, pady=(30, 10), sticky="nsew", rowspan=3 - ) + widgets_frame.grid(row=0, column=1, padx=10, pady=(30, 10), sticky="nsew", rowspan=2) widgets_frame.columnconfigure(index=0, weight=1) # Entry @@ -115,9 +99,7 @@ def setup_widgets(self): combobox.grid(row=2, column=0, padx=5, pady=10, sticky="ew") # Read-only combobox - readonly_combo = ttk.Combobox( - widgets_frame, state="readonly", values=self.readonly_combo_list - ) + readonly_combo = ttk.Combobox(widgets_frame, state="readonly", values=self.readonly_combo_list) readonly_combo.current(0) readonly_combo.grid(row=3, column=0, padx=5, pady=10, sticky="ew") @@ -130,38 +112,31 @@ def setup_widgets(self): menu.add_command(label="Menu item 4") # Menubutton - menubutton = ttk.Menubutton( - widgets_frame, text="Menubutton", menu=menu, direction="below" - ) + menubutton = ttk.Menubutton(widgets_frame, text="Menubutton", menu=menu, direction="below") menubutton.grid(row=4, column=0, padx=5, pady=10, sticky="nsew") # OptionMenu - optionmenu = ttk.OptionMenu( - widgets_frame, self.var_4, *self.option_menu_list - ) + optionmenu = ttk.OptionMenu(widgets_frame, self.var_4, *self.option_menu_list) optionmenu.grid(row=5, column=0, padx=5, pady=10, sticky="nsew") # Button button = ttk.Button(widgets_frame, text="Button") button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") - # Accentbutton - accentbutton = ttk.Button( - widgets_frame, text="Accent button", style="Accent.TButton" - ) - accentbutton.grid(row=7, column=0, padx=5, pady=10, sticky="nsew") + hyperlink_frame = ttk.LabelFrame(self, text="HyperlinkLabels", padding=(20, 10)) + hyperlink_frame.grid(row=2, column=1, padx=10, pady=10, sticky="nsew") - # Togglebutton - togglebutton = ttk.Checkbutton( - widgets_frame, text="Toggle button", style="Toggle.TButton" - ) - togglebutton.grid(row=8, column=0, padx=5, pady=10, sticky="nsew") + hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="underline=None", url=URL) + hyperlink_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") - # Switch - switch = ttk.Checkbutton( - widgets_frame, text="Switch", style="Switch.TCheckbutton" - ) - switch.grid(row=9, column=0, padx=5, pady=10, sticky="nsew") + hyperlink_2 = HyperlinkLabel(hyperlink_frame, text="underline=True", url=URL, underline=True) + hyperlink_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") + + hyperlink_3 = HyperlinkLabel(hyperlink_frame, text="underline=False", url=URL, underline=False) + hyperlink_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") + + hyperlink_4 = HyperlinkLabel(hyperlink_frame, text="Disabled", url=URL, state=tk.DISABLED) + hyperlink_4.grid(row=3, column=0, padx=5, pady=10, sticky="nsew") # Panedwindow paned = ttk.PanedWindow(self) @@ -226,12 +201,10 @@ def setup_widgets(self): ] # Insert treeview data - for item in treeview_data: - treeview.insert( - parent=item[0], index="end", iid=item[1], text=item[2], values=item[3] - ) - if item[0] == "" or item[1] in {8, 21}: - treeview.item(item[1], open=True) # Open parents + for parent, iid, text, values in treeview_data: + treeview.insert(parent=parent, index="end", iid=iid, text=text, values=values) + if parent == "" or iid in {8, 21}: + treeview.item(iid, open=True) # Select and scroll treeview.selection_set(10) @@ -263,9 +236,7 @@ def setup_widgets(self): scale.grid(row=0, column=0, padx=(20, 10), pady=(20, 0), sticky="ew") # Progressbar - progress = ttk.Progressbar( - tab_1, value=0, variable=self.var_5, mode="determinate" - ) + progress = ttk.Progressbar(tab_1, value=0, variable=self.var_5, mode="determinate") progress.grid(row=0, column=1, padx=(10, 20), pady=(20, 0), sticky="ew") # Label @@ -285,10 +256,6 @@ def setup_widgets(self): tab_3 = ttk.Frame(notebook) notebook.add(tab_3, text="Tab 3") - # Sizegrip - sizegrip = ttk.Sizegrip(self) - sizegrip.grid(row=100, column=100, padx=(0, 5), pady=(0, 5)) - def plugin_start3(path: str) -> str: return 'TtkCatalog' diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index b96c56d61..8d6235c2d 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -52,7 +52,7 @@ class HyperlinkLabel(ttk.Label): """Clickable label for HTTP links.""" - def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None: + def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: """ Initialize the HyperlinkLabel. From 08a031cfc966a42b60227025f93085d7b51a5687 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sat, 1 Jun 2024 23:54:09 +0100 Subject: [PATCH 18/59] Nicer labels --- plugins/_ttk_catalog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py index d7ffa9db1..89e6b623a 100644 --- a/plugins/_ttk_catalog.py +++ b/plugins/_ttk_catalog.py @@ -126,13 +126,13 @@ def setup_widgets(self): hyperlink_frame = ttk.LabelFrame(self, text="HyperlinkLabels", padding=(20, 10)) hyperlink_frame.grid(row=2, column=1, padx=10, pady=10, sticky="nsew") - hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="underline=None", url=URL) + hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="Default", url=URL) hyperlink_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") - hyperlink_2 = HyperlinkLabel(hyperlink_frame, text="underline=True", url=URL, underline=True) + hyperlink_2 = HyperlinkLabel(hyperlink_frame, text="Underline", url=URL, underline=True) hyperlink_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") - hyperlink_3 = HyperlinkLabel(hyperlink_frame, text="underline=False", url=URL, underline=False) + hyperlink_3 = HyperlinkLabel(hyperlink_frame, text="No underline", url=URL, underline=False) hyperlink_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") hyperlink_4 = HyperlinkLabel(hyperlink_frame, text="Disabled", url=URL, state=tk.DISABLED) From 6bb0475446750caf9eadbd86cf38ef385786473d Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 2 Jun 2024 02:24:27 +0100 Subject: [PATCH 19/59] Using clam as the base ttk theme on Windows The native themes - vista, xpnative, winnative - use elements (e.g. buttons, notebooks) whose customization is very limited; most notably, their background colors cannot be changed --- theme.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/theme.py b/theme.py index 770c92c1e..9747e2bda 100644 --- a/theme.py +++ b/theme.py @@ -139,12 +139,10 @@ def __init__(self) -> None: self.widgets_pair: list = [] self.default_ui_scale: float | None = None # None == not yet known self.startup_ui_scale: int | None = None - self.default_ttk_theme = 'clam' def initialize(self): self.style = ttk.Style() - if sys.platform == 'win32': - self.default_ttk_theme = self.style.theme_use() + self.style.theme_use('clam') # ttk.Separator does not allow to configure its thickness, so we have to make our own self.style.configure('Sep.TFrame', padding=2, @@ -158,9 +156,9 @@ def initialize(self): config.set('dark_highlight', 'white') with open('themes/dark.json') as f: - self.style.theme_create('dark', self.default_ttk_theme, json.load(f)) + self.style.theme_create('dark', 'clam', json.load(f)) with open('themes/transparent.json') as f: - self.style.theme_create('transparent', self.default_ttk_theme, json.load(f)) + self.style.theme_create('transparent', 'clam', json.load(f)) @deprecated('Theme colors are now applied automatically, even after initialization') def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 @@ -225,19 +223,20 @@ def update(self, widget: tk.Widget) -> None: def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 theme = config.get_int('theme') if theme == self.THEME_DEFAULT: - self.style.theme_use(self.default_ttk_theme) + self.style.theme_use('clam') elif theme == self.THEME_DARK: self.style.theme_use('dark') elif theme == self.THEME_TRANSPARENT: self.style.theme_use('transparent') + # TODO hover menu / click button root.tk_setPalette( - background=self.style.lookup('TLabel', 'background'), - foreground=self.style.lookup('TLabel', 'foreground'), - activebackground=self.style.lookup('TLabel', 'background', ['active']), - activeforeground=self.style.lookup('TLabel', 'foreground', ['active']), - disabledforeground=self.style.lookup('TLabel', 'foreground', ['disabled']), - highlight=self.style.lookup('Link.TLabel', 'foreground'), + background=self.style.lookup('.', 'background'), + foreground=self.style.lookup('.', 'foreground'), + activebackground=self.style.lookup('.', 'background', ['active']), + activeforeground=self.style.lookup('.', 'foreground', ['active']), + disabledforeground=self.style.lookup('.', 'foreground', ['disabled']), + highlightcolor=self.style.lookup('Link.TLabel', 'foreground'), ) for image in self.bitmaps: image['background'] = self.style.lookup('TLabel', 'background') From 4cfe9907f0baf1aacd49dec5a6371526be086801 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 2 Jun 2024 12:03:49 +0100 Subject: [PATCH 20/59] Added Tk widgets to Ttk catalog for comparison --- plugins/_ttk_catalog.py | 98 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py index 89e6b623a..c02812096 100644 --- a/plugins/_ttk_catalog.py +++ b/plugins/_ttk_catalog.py @@ -48,18 +48,18 @@ def setup_widgets(self): check_frame.grid(row=0, column=0, padx=(20, 10), pady=(20, 10), sticky="nsew") check_1 = ttk.Checkbutton(check_frame, text="Unchecked", variable=self.var_0) - check_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") + check_1.grid(row=0, column=0, sticky="nsew") check_2 = ttk.Checkbutton(check_frame, text="Checked", variable=self.var_1) - check_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") + check_2.grid(row=1, column=0, sticky="nsew") check_3 = ttk.Checkbutton(check_frame, text="Third state", variable=self.var_2) check_3.state(["alternate"]) - check_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") + check_3.grid(row=2, column=0, sticky="nsew") check_4 = ttk.Checkbutton(check_frame, text="Disabled", state="disabled") check_4.state(["disabled !alternate"]) - check_4.grid(row=3, column=0, padx=5, pady=10, sticky="nsew") + check_4.grid(row=3, column=0, sticky="nsew") # Separator separator = ttk.Separator(self) @@ -70,13 +70,13 @@ def setup_widgets(self): radio_frame.grid(row=2, column=0, padx=(20, 10), pady=10, sticky="nsew") radio_1 = ttk.Radiobutton(radio_frame, text="Unselected", variable=self.var_3, value=1) - radio_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") + radio_1.grid(row=0, column=0, sticky="nsew") radio_2 = ttk.Radiobutton(radio_frame, text="Selected", variable=self.var_3, value=2) - radio_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") + radio_2.grid(row=1, column=0, sticky="nsew") radio_3 = ttk.Radiobutton(radio_frame, text="Disabled", state="disabled") - radio_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") + radio_3.grid(row=2, column=0, sticky="nsew") # Create a Frame for input widgets widgets_frame = ttk.Frame(self, padding=(0, 0, 0, 10)) @@ -121,22 +121,22 @@ def setup_widgets(self): # Button button = ttk.Button(widgets_frame, text="Button") - button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") + button.grid(row=6, column=0, sticky="nsew") hyperlink_frame = ttk.LabelFrame(self, text="HyperlinkLabels", padding=(20, 10)) hyperlink_frame.grid(row=2, column=1, padx=10, pady=10, sticky="nsew") hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="Default", url=URL) - hyperlink_1.grid(row=0, column=0, padx=5, pady=10, sticky="nsew") + hyperlink_1.grid(row=0, column=0, sticky="nsew") hyperlink_2 = HyperlinkLabel(hyperlink_frame, text="Underline", url=URL, underline=True) - hyperlink_2.grid(row=1, column=0, padx=5, pady=10, sticky="nsew") + hyperlink_2.grid(row=1, column=0, sticky="nsew") hyperlink_3 = HyperlinkLabel(hyperlink_frame, text="No underline", url=URL, underline=False) - hyperlink_3.grid(row=2, column=0, padx=5, pady=10, sticky="nsew") + hyperlink_3.grid(row=2, column=0, sticky="nsew") hyperlink_4 = HyperlinkLabel(hyperlink_frame, text="Disabled", url=URL, state=tk.DISABLED) - hyperlink_4.grid(row=3, column=0, padx=5, pady=10, sticky="nsew") + hyperlink_4.grid(row=3, column=0, sticky="nsew") # Panedwindow paned = ttk.PanedWindow(self) @@ -256,6 +256,80 @@ def setup_widgets(self): tab_3 = ttk.Frame(notebook) notebook.add(tab_3, text="Tab 3") + check_frame = tk.LabelFrame(self, text="Checkbuttons", padx=20, pady=10) + check_frame.grid(row=0, column=3, padx=(20, 10), pady=(20, 10), sticky="nsew") + + check_1 = tk.Checkbutton(check_frame, text="Unchecked", variable=self.var_0) + check_1.grid(row=0, column=0, sticky="nsew") + + check_2 = tk.Checkbutton(check_frame, text="Checked", variable=self.var_1) + check_2.grid(row=1, column=0, sticky="nsew") + + check_4 = tk.Checkbutton(check_frame, text="Disabled", state="disabled") + check_4.grid(row=3, column=0, sticky="nsew") + + # Create a Frame for the Radiobuttons + radio_frame = tk.LabelFrame(self, text="Radiobuttons", padx=20, pady=10) + radio_frame.grid(row=2, column=3, padx=(20, 10), pady=10, sticky="nsew") + + radio_1 = tk.Radiobutton(radio_frame, text="Unselected", variable=self.var_3, value=1) + radio_1.grid(row=0, column=0, sticky="nsew") + + radio_2 = tk.Radiobutton(radio_frame, text="Selected", variable=self.var_3, value=2) + radio_2.grid(row=1, column=0, sticky="nsew") + + radio_3 = tk.Radiobutton(radio_frame, text="Disabled", state="disabled") + radio_3.grid(row=2, column=0, sticky="nsew") + + # Create a Frame for input widgets + widgets_frame = tk.Frame(self) + widgets_frame.grid(row=0, column=4, padx=10, pady=(30, 10), sticky="nsew", rowspan=2) + widgets_frame.columnconfigure(index=0, weight=1) + + # Entry + entry = tk.Entry(widgets_frame) + entry.insert(0, "Entry") + entry.grid(row=0, column=0, padx=5, pady=(0, 10), sticky="ew") + + # Spinbox + spinbox = tk.Spinbox(widgets_frame, from_=0, to=100, increment=0.1) + spinbox.grid(row=1, column=0, padx=5, pady=10, sticky="ew") + + # Menu for the Menubutton + menu = tk.Menu(self) + menu.add_command(label="Menu item 1") + menu.add_command(label="Menu item 2") + menu.add_separator() + menu.add_command(label="Menu item 3") + menu.add_command(label="Menu item 4") + + # Menubutton + menubutton = tk.Menubutton(widgets_frame, text="Menubutton", menu=menu, direction="below") + menubutton.grid(row=4, column=0, padx=5, pady=10, sticky="nsew") + + # OptionMenu + optionmenu = tk.OptionMenu(widgets_frame, self.var_4, *self.option_menu_list) + optionmenu.grid(row=5, column=0, padx=5, pady=10, sticky="nsew") + + # Button + button = tk.Button(widgets_frame, text="Button") + button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") + + hyperlink_frame = tk.LabelFrame(self, text="HyperlinkLabels (legacy)", padx=20, pady=10) + hyperlink_frame.grid(row=2, column=4, padx=10, pady=10, sticky="nsew") + + hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="Default", url=URL) + hyperlink_1.grid(row=0, column=0, sticky="nsew") + + hyperlink_2 = HyperlinkLabel(hyperlink_frame, text="Underline", url=URL, underline=True) + hyperlink_2.grid(row=1, column=0, sticky="nsew") + + hyperlink_3 = HyperlinkLabel(hyperlink_frame, text="No underline", url=URL, underline=False) + hyperlink_3.grid(row=2, column=0, sticky="nsew") + + hyperlink_4 = HyperlinkLabel(hyperlink_frame, text="Disabled", url=URL, state=tk.DISABLED) + hyperlink_4.grid(row=3, column=0, sticky="nsew") + def plugin_start3(path: str) -> str: return 'TtkCatalog' From ca4a5eff3765dcad254c1e5862b2aed2e496252a Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 2 Jun 2024 17:32:32 +0100 Subject: [PATCH 21/59] Theme in pure Tcl --- EDMarketConnector.py | 6 +-- config/__init__.py | 1 + config/linux.py | 1 + config/windows.py | 4 +- theme.py | 38 ++++++------------- themes/dark.json | 56 --------------------------- themes/dark.tcl | 84 +++++++++++++++++++++++++++++++++++++++++ themes/transparent.json | 57 ---------------------------- ttkHyperlinkLabel.py | 37 ++++++++++-------- 9 files changed, 124 insertions(+), 160 deletions(-) delete mode 100644 themes/dark.json create mode 100644 themes/dark.tcl delete mode 100644 themes/transparent.json diff --git a/EDMarketConnector.py b/EDMarketConnector.py index aa278d1a2..5a7c61531 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -452,7 +452,7 @@ def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly f self.minimizing = False self.w.rowconfigure(0, weight=1) self.w.columnconfigure(0, weight=1) - theme.initialize() + theme.initialize(self.w) # companion needs to be able to send <> events companion.session.set_tk_master(self.w) @@ -532,7 +532,7 @@ def open_window(systray: 'SysTrayIcon') -> None: plugin_no = 0 for plugin in plug.PLUGINS: # Per plugin separator - plugin_sep = ttk.Frame(frame, name=f"plugin_hr_{plugin_no + 1}", style='Sep.TFrame') + plugin_sep = ttk.Separator(frame, name=f"plugin_hr_{plugin_no + 1}") # Per plugin frame, for it to use as its parent for own widgets plugin_frame = ttk.Frame( frame, @@ -687,7 +687,7 @@ def open_window(systray: 'SysTrayIcon') -> None: lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) - ttk.Frame(self.theme_menubar, style='Sep.TFrame').grid(columnspan=5, padx=self.PADX, sticky=tk.EW) + ttk.Separator(self.theme_menubar).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) self.blank_menubar = ttk.Frame(frame, name="blank_menubar") ttk.Label(self.blank_menubar).grid() ttk.Label(self.blank_menubar).grid() diff --git a/config/__init__.py b/config/__init__.py index 9adf79304..cf3ff984b 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -192,6 +192,7 @@ class AbstractConfig(abc.ABC): app_dir_path: pathlib.Path plugin_dir_path: pathlib.Path internal_plugin_dir_path: pathlib.Path + internal_theme_dir_path: pathlib.Path respath_path: pathlib.Path home_path: pathlib.Path default_journal_dir_path: pathlib.Path diff --git a/config/linux.py b/config/linux.py index 51a406262..99806af47 100644 --- a/config/linux.py +++ b/config/linux.py @@ -37,6 +37,7 @@ def __init__(self, filename: str | None = None) -> None: self.respath_path = pathlib.Path(__file__).parent.parent self.internal_plugin_dir_path = self.respath_path / 'plugins' + self.internal_theme_dir_path = self.respath_path / 'themes' self.default_journal_dir_path = None # type: ignore self.identifier = f'uk.org.marginal.{appname.lower()}' # TODO: Unused? diff --git a/config/windows.py b/config/windows.py index 75e3f6977..624738fa6 100644 --- a/config/windows.py +++ b/config/windows.py @@ -57,10 +57,10 @@ def __init__(self) -> None: if getattr(sys, 'frozen', False): self.respath_path = pathlib.Path(sys.executable).parent - self.internal_plugin_dir_path = self.respath_path / 'plugins' else: self.respath_path = pathlib.Path(__file__).parent.parent - self.internal_plugin_dir_path = self.respath_path / 'plugins' + self.internal_plugin_dir_path = self.respath_path / 'plugins' + self.internal_theme_dir_path = self.respath_path / 'themes' self.home_path = pathlib.Path.home() diff --git a/theme.py b/theme.py index 9747e2bda..b2928d390 100644 --- a/theme.py +++ b/theme.py @@ -10,7 +10,6 @@ """ from __future__ import annotations -import json import os import sys import tkinter as tk @@ -140,25 +139,21 @@ def __init__(self) -> None: self.default_ui_scale: float | None = None # None == not yet known self.startup_ui_scale: int | None = None - def initialize(self): + def initialize(self, root: tk.Tk): self.style = ttk.Style() self.style.theme_use('clam') - # ttk.Separator does not allow to configure its thickness, so we have to make our own - self.style.configure('Sep.TFrame', padding=2, - background=self.style.lookup('TLabel', 'foreground', ['disabled'])) - self.style.configure('Link.TLabel', font='TkDefaultFont', foreground='blue') # HyperlinkLabel - # Default dark theme colors if not config.get_str('dark_text'): config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker if not config.get_str('dark_highlight'): config.set('dark_highlight', 'white') - with open('themes/dark.json') as f: - self.style.theme_create('dark', 'clam', json.load(f)) - with open('themes/transparent.json') as f: - self.style.theme_create('transparent', 'clam', json.load(f)) + for theme_file in config.internal_theme_dir_path.glob('[!._]*.tcl'): + try: + root.tk.call('source', theme_file) + except tk.TclError: + logger.exception(f'Failure loading internal theme "{theme_file}"') @deprecated('Theme colors are now applied automatically, even after initialization') def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 @@ -187,8 +182,8 @@ def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None: if image: try: - image['background'] = self.style.lookup('TLabel', 'background', ['active']) - image['foreground'] = self.style.lookup('TLabel', 'foreground', ['active']) + image['background'] = self.style.lookup('.', 'selectbackground') + image['foreground'] = self.style.lookup('.', 'selectforeground') except Exception: logger.exception(f'Failure configuring image: {image=}') @@ -204,8 +199,8 @@ def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: if image: try: - image['background'] = self.style.lookup('TLabel', 'background') - image['foreground'] = self.style.lookup('TLabel', 'foreground') + image['background'] = self.style.lookup('.', 'background') + image['foreground'] = self.style.lookup('.', 'foreground') except Exception: logger.exception(f'Failure configuring image: {image=}') @@ -229,18 +224,9 @@ def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 elif theme == self.THEME_TRANSPARENT: self.style.theme_use('transparent') - # TODO hover menu / click button - root.tk_setPalette( - background=self.style.lookup('.', 'background'), - foreground=self.style.lookup('.', 'foreground'), - activebackground=self.style.lookup('.', 'background', ['active']), - activeforeground=self.style.lookup('.', 'foreground', ['active']), - disabledforeground=self.style.lookup('.', 'foreground', ['disabled']), - highlightcolor=self.style.lookup('Link.TLabel', 'foreground'), - ) for image in self.bitmaps: - image['background'] = self.style.lookup('TLabel', 'background') - image['foreground'] = self.style.lookup('TLabel', 'foreground') + image['background'] = self.style.lookup('.', 'background') + image['foreground'] = self.style.lookup('.', 'foreground') # Switch menus for pair, gridopts in self.widgets_pair: diff --git a/themes/dark.json b/themes/dark.json deleted file mode 100644 index f3c14a297..000000000 --- a/themes/dark.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - ".": { - "configure": { - "background": "grey4", - "foreground": "#ff8000" - } - }, - "TFrame": { - "configure": { - "highlightbackground": "#aa5500" - } - }, - "Sep.TFrame": { - "configure": { - "background": "#ff8000" - } - }, - "TLabel": { - "configure": { - "padding": 1 - }, - "map": { - "background": [ - ["active", "#ff8000"] - ], - "foreground": [ - ["active", "grey4"], - ["disabled", "#aa5500"] - ] - } - }, - "Link.TLabel": { - "configure": { - "foreground": "white", - "font": "TkDefaultFont" - } - }, - "TButton": { - "configure": { - "relief": "raised", - "padding": 2 - }, - "map": { - "background": [ - ["pressed", "#ff8000"] - ], - "foreground": [ - ["pressed", "grey4"], - ["disabled", "#aa5500"] - ], - "relief": [ - ["pressed", "sunken"] - ] - } - } -} diff --git a/themes/dark.tcl b/themes/dark.tcl new file mode 100644 index 000000000..db6b5f056 --- /dev/null +++ b/themes/dark.tcl @@ -0,0 +1,84 @@ +package require Tk 8.6 + +namespace eval ttk::theme::dark { + + variable version 1.0 + package provide ttk::theme::dark $version + variable colors + array set colors { + -fg "#ff8000" + -bg "grey4" + -disabledfg "#aa5500" + -disabledbg "grey" + -selectfg "grey4" + -selectbg "#ff8000" + -highlight "white" + } + + ttk::style theme create dark -parent clam -settings { + ttk::style configure . \ + -background $colors(-bg) \ + -foreground $colors(-fg) \ + -troughcolor $colors(-bg) \ + -focuscolor $colors(-selectbg) \ + -selectbackground $colors(-selectbg) \ + -selectforeground $colors(-selectfg) \ + -insertwidth 1 \ + -insertcolor $colors(-fg) \ + -fieldbackground $colors(-bg) \ + -font {TkDefaultFont 10} \ + -borderwidth 1 \ + -relief flat + + ttk::style map . -foreground [list disabled $colors(-disabledfg)] + + tk_setPalette background [ttk::style lookup . -background] \ + foreground [ttk::style lookup . -foreground] \ + highlightColor [ttk::style lookup . -focuscolor] \ + selectBackground [ttk::style lookup . -selectbackground] \ + selectForeground [ttk::style lookup . -selectforeground] \ + activeBackground [ttk::style lookup . -selectbackground] \ + activeForeground [ttk::style lookup . -selectforeground] + + option add *font [ttk::style lookup . -font] + + ttk::style configure Link.TLabel -foreground $colors(-highlight) + + ttk::style configure TSeparator -background $colors(-fg) + + ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style configure TMenubutton -padding {8 4 4 4} + + ttk::style configure TOptionMenu -padding {8 4 4 4} + + ttk::style configure TCheckbutton -padding 4 + + ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style configure TRadiobutton -padding 4 + + ttk::style map TCombobox -selectbackground [list \ + {!focus} $colors(-selectbg) \ + {readonly hover} $colors(-selectbg) \ + {readonly focus} $colors(-selectbg) \ + ] + + ttk::style map TCombobox -selectforeground [list \ + {!focus} $colors(-selectfg) \ + {readonly hover} $colors(-selectfg) \ + {readonly focus} $colors(-selectfg) \ + ] + + ttk::style configure TNotebook -padding 2 + + ttk::style configure Treeview -background $colors(-bg) + ttk::style configure Treeview.Item -padding {2 0 0 0} + + ttk::style map Treeview \ + -background [list selected $colors(-selectbg)] \ + -foreground [list selected $colors(-selectfg)] + } +} diff --git a/themes/transparent.json b/themes/transparent.json deleted file mode 100644 index 4b99780a5..000000000 --- a/themes/transparent.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - ".": { - "configure": { - "background": "grey4", - "foreground": "#ff8000", - "font": "\"Euro Caps\" 10" - } - }, - "TFrame": { - "configure": { - "highlightbackground": "#aa5500" - } - }, - "Sep.TFrame": { - "configure": { - "background": "#ff8000" - } - }, - "TLabel": { - "configure": { - "padding": 1 - }, - "map": { - "background": [ - ["active", "#ff8000"] - ], - "foreground": [ - ["active", "grey4"], - ["disabled", "#aa5500"] - ] - } - }, - "Link.TLabel": { - "configure": { - "foreground": "white", - "font": "\"Euro Caps\" 10" - } - }, - "TButton": { - "configure": { - "relief": "raised", - "padding": 2 - }, - "map": { - "background": [ - ["pressed", "#ff8000"] - ], - "foreground": [ - ["pressed", "grey4"], - ["disabled", "#aa5500"] - ], - "relief": [ - ["pressed", "sunken"] - ] - } - } -} diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 8d6235c2d..aeea39956 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -61,14 +61,14 @@ def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: """ self.font_u: tk_font.Font self.font_n = None - self.style = kw.get('style', 'Link.TLabel') self.url = kw.pop('url', None) self.popup_copy = kw.pop('popup_copy', False) self.underline = kw.pop('underline', None) # override ttk.Label's underline - self.foreground = kw.get('foreground', ttk.Style().lookup(self.style, 'foreground')) - # ttk.Label doesn't support disabledforeground option - self.disabledforeground = kw.pop('disabledforeground', - ttk.Style().lookup(self.style, 'foreground', ('disabled',))) + self.legacy = not isinstance(master, ttk.Widget) + if self.legacy: + self.foreground = kw.get('foreground') + self.disabledforeground = kw.pop('disabledforeground', None) + kw.setdefault('style', 'Link.TLabel') ttk.Label.__init__(self, master, **kw) self.bind('', self._click) @@ -76,10 +76,11 @@ def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: self.bind('', self._enter) self.bind('', self._leave) + self.bind('<>', self._theme) # set up initial appearance self.configure(state=kw.get('state', tk.NORMAL), - font=kw.get('font', ttk.Style().lookup(self.style, 'font'))) + font=kw.get('font', ttk.Style().lookup(kw['style'], 'font'))) # Add Menu Options self.plug_options = kw.pop('plug_options', None) @@ -132,17 +133,14 @@ def configure( # noqa: CCR001 for thing in ('url', 'popup_copy', 'underline'): if thing in kw: setattr(self, thing, kw.pop(thing)) - for thing in ('foreground', 'disabledforeground'): - if thing in kw: - setattr(self, thing, kw[thing]) + if self.legacy: + for thing in ('foreground', 'disabledforeground'): + if thing in kw: + setattr(self, thing, kw[thing]) - # Emulate disabledforeground option for ttk.Label - if 'state' in kw: - state = kw['state'] - if state == tk.DISABLED and 'foreground' not in kw: - kw['foreground'] = self.disabledforeground - elif state != tk.DISABLED and 'foreground' not in kw: - kw['foreground'] = self.foreground + # Emulate disabledforeground option for ttk.Label + if 'state' in kw and 'foreground' not in kw: + self._theme(None) if 'font' in kw: self.font_n = kw['font'] @@ -178,6 +176,13 @@ def _leave(self, event: tk.Event) -> None: if not self.underline: super().configure(font=self.font_n) # type: ignore + def _theme(self, event: tk.Event | None = None) -> None: + if self.legacy: + if str(self['state']) == tk.DISABLED: + super().configure(foreground=self.disabledforeground or ttk.Style().lookup('TLabel', 'foreground', ['disabled'])) + else: + super().configure(foreground=self.foreground or ttk.Style().lookup('Link.TLabel', 'foreground')) + def _click(self, event: tk.Event) -> None: if self.url and self['text'] and str(self['state']) != tk.DISABLED: url = self.url(self['text']) if callable(self.url) else self.url From 65a33bb801e72bcc314390dc7e0e4f7f8b62e058 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 2 Jun 2024 17:36:27 +0100 Subject: [PATCH 22/59] Fixed theme font --- themes/dark.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/dark.tcl b/themes/dark.tcl index db6b5f056..0d7708660 100644 --- a/themes/dark.tcl +++ b/themes/dark.tcl @@ -26,7 +26,7 @@ namespace eval ttk::theme::dark { -insertwidth 1 \ -insertcolor $colors(-fg) \ -fieldbackground $colors(-bg) \ - -font {TkDefaultFont 10} \ + -font {TkDefaultFont} \ -borderwidth 1 \ -relief flat From 960fef9753ac5fa0cb8bf12878140c40e99c0746 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 2 Jun 2024 21:48:34 +0100 Subject: [PATCH 23/59] Ttk catalog: properly labeled Tk widgets --- plugins/_ttk_catalog.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py index c02812096..a2f28f372 100644 --- a/plugins/_ttk_catalog.py +++ b/plugins/_ttk_catalog.py @@ -29,6 +29,7 @@ def __init__(self, parent: ttk.Frame): # Create value lists self.option_menu_list = ["", "OptionMenu", "Option 1", "Option 2"] + self.tk_option_menu_list = ["", "tk.OptionMenu", "Option 1", "Option 2"] self.combo_list = ["Combobox", "Editable item 1", "Editable item 2"] self.readonly_combo_list = ["Readonly combobox", "Item 1", "Item 2"] @@ -39,12 +40,13 @@ def __init__(self, parent: ttk.Frame): self.var_3 = tk.IntVar(value=2) self.var_4 = tk.StringVar(value=self.option_menu_list[1]) self.var_5 = tk.DoubleVar(value=75.0) + self.var_6 = tk.StringVar(value=self.tk_option_menu_list[1]) # Create widgets :) self.setup_widgets() def setup_widgets(self): - check_frame = ttk.LabelFrame(self, text="Checkbuttons", padding=(20, 10)) + check_frame = ttk.LabelFrame(self, text="Checkbutton", padding=(20, 10)) check_frame.grid(row=0, column=0, padx=(20, 10), pady=(20, 10), sticky="nsew") check_1 = ttk.Checkbutton(check_frame, text="Unchecked", variable=self.var_0) @@ -66,7 +68,7 @@ def setup_widgets(self): separator.grid(row=1, column=0, padx=(20, 10), pady=10, sticky="ew") # Create a Frame for the Radiobuttons - radio_frame = ttk.LabelFrame(self, text="Radiobuttons", padding=(20, 10)) + radio_frame = ttk.LabelFrame(self, text="Radiobutton", padding=(20, 10)) radio_frame.grid(row=2, column=0, padx=(20, 10), pady=10, sticky="nsew") radio_1 = ttk.Radiobutton(radio_frame, text="Unselected", variable=self.var_3, value=1) @@ -123,7 +125,7 @@ def setup_widgets(self): button = ttk.Button(widgets_frame, text="Button") button.grid(row=6, column=0, sticky="nsew") - hyperlink_frame = ttk.LabelFrame(self, text="HyperlinkLabels", padding=(20, 10)) + hyperlink_frame = ttk.LabelFrame(self, text="HyperlinkLabel", padding=(20, 10)) hyperlink_frame.grid(row=2, column=1, padx=10, pady=10, sticky="nsew") hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="Default", url=URL) @@ -256,7 +258,7 @@ def setup_widgets(self): tab_3 = ttk.Frame(notebook) notebook.add(tab_3, text="Tab 3") - check_frame = tk.LabelFrame(self, text="Checkbuttons", padx=20, pady=10) + check_frame = tk.LabelFrame(self, text="tk.Checkbutton", padx=20, pady=10) check_frame.grid(row=0, column=3, padx=(20, 10), pady=(20, 10), sticky="nsew") check_1 = tk.Checkbutton(check_frame, text="Unchecked", variable=self.var_0) @@ -269,7 +271,7 @@ def setup_widgets(self): check_4.grid(row=3, column=0, sticky="nsew") # Create a Frame for the Radiobuttons - radio_frame = tk.LabelFrame(self, text="Radiobuttons", padx=20, pady=10) + radio_frame = tk.LabelFrame(self, text="tk.Radiobutton", padx=20, pady=10) radio_frame.grid(row=2, column=3, padx=(20, 10), pady=10, sticky="nsew") radio_1 = tk.Radiobutton(radio_frame, text="Unselected", variable=self.var_3, value=1) @@ -288,7 +290,7 @@ def setup_widgets(self): # Entry entry = tk.Entry(widgets_frame) - entry.insert(0, "Entry") + entry.insert(0, "tk.Entry") entry.grid(row=0, column=0, padx=5, pady=(0, 10), sticky="ew") # Spinbox @@ -304,18 +306,18 @@ def setup_widgets(self): menu.add_command(label="Menu item 4") # Menubutton - menubutton = tk.Menubutton(widgets_frame, text="Menubutton", menu=menu, direction="below") + menubutton = tk.Menubutton(widgets_frame, text="tk.Menubutton", menu=menu, direction="below") menubutton.grid(row=4, column=0, padx=5, pady=10, sticky="nsew") # OptionMenu - optionmenu = tk.OptionMenu(widgets_frame, self.var_4, *self.option_menu_list) + optionmenu = tk.OptionMenu(widgets_frame, self.var_6, *self.tk_option_menu_list) optionmenu.grid(row=5, column=0, padx=5, pady=10, sticky="nsew") # Button - button = tk.Button(widgets_frame, text="Button") + button = tk.Button(widgets_frame, text="tk.Button") button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") - hyperlink_frame = tk.LabelFrame(self, text="HyperlinkLabels (legacy)", padx=20, pady=10) + hyperlink_frame = tk.LabelFrame(self, text="HyperlinkLabel (legacy)", padx=20, pady=10) hyperlink_frame.grid(row=2, column=4, padx=10, pady=10, sticky="nsew") hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="Default", url=URL) From f1ad8d7c27b75eddfb914f877321f2a226dce917 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 2 Jun 2024 23:28:50 +0100 Subject: [PATCH 24/59] Added default theme; final touches in dark? --- theme.py | 13 +++++-- themes/dark.tcl | 54 ++++++++++++++++++++--------- themes/default.tcl | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 themes/default.tcl diff --git a/theme.py b/theme.py index b2928d390..2fcbcbd4f 100644 --- a/theme.py +++ b/theme.py @@ -141,7 +141,6 @@ def __init__(self) -> None: def initialize(self, root: tk.Tk): self.style = ttk.Style() - self.style.theme_use('clam') # Default dark theme colors if not config.get_str('dark_text'): @@ -218,12 +217,22 @@ def update(self, widget: tk.Widget) -> None: def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 theme = config.get_int('theme') if theme == self.THEME_DEFAULT: - self.style.theme_use('clam') + self.style.theme_use('edmc') elif theme == self.THEME_DARK: self.style.theme_use('dark') elif theme == self.THEME_TRANSPARENT: self.style.theme_use('transparent') + root.tk_setPalette( + background=self.style.lookup('.', 'background'), + foreground=self.style.lookup('.', 'foreground'), + highlightColor=self.style.lookup('.', 'focuscolor'), + selectBackground=self.style.lookup('.', 'selectbackground'), + selectForeground=self.style.lookup('.', 'selectforeground'), + activeBackground=self.style.lookup('.', 'selectbackground'), + activeForeground=self.style.lookup('.', 'selectforeground'), + ) + for image in self.bitmaps: image['background'] = self.style.lookup('.', 'background') image['foreground'] = self.style.lookup('.', 'foreground') diff --git a/themes/dark.tcl b/themes/dark.tcl index 0d7708660..26b882925 100644 --- a/themes/dark.tcl +++ b/themes/dark.tcl @@ -9,11 +9,11 @@ namespace eval ttk::theme::dark { -fg "#ff8000" -bg "grey4" -disabledfg "#aa5500" - -disabledbg "grey" -selectfg "grey4" -selectbg "#ff8000" -highlight "white" } + set flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] ttk::style theme create dark -parent clam -settings { ttk::style configure . \ @@ -32,33 +32,44 @@ namespace eval ttk::theme::dark { ttk::style map . -foreground [list disabled $colors(-disabledfg)] - tk_setPalette background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] - option add *font [ttk::style lookup . -font] + option add *Menu.selectcolor $colors(-fg) + + ttk::style configure TLabel -padding 1 ttk::style configure Link.TLabel -foreground $colors(-highlight) + ttk::style configure TLabelframe {*}$flatborder + ttk::style configure TSeparator -background $colors(-fg) - ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center + ttk::style configure TEntry -padding 2 {*}$flatborder + + ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center {*}$flatborder + + ttk::style map TButton -background [list \ + {pressed} $colors(-selectbg) \ + ] + + ttk::style map TButton -foreground [list \ + {pressed} $colors(-selectfg) \ + ] ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center - ttk::style configure TMenubutton -padding {8 4 4 4} + ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder - ttk::style configure TOptionMenu -padding {8 4 4 4} + ttk::style configure TOptionMenu -padding {8 4 4 4} {*}$flatborder - ttk::style configure TCheckbutton -padding 4 + ttk::style configure TCheckbutton -padding 4 -indicatormargin 4 ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center - ttk::style configure TRadiobutton -padding 4 + ttk::style configure TRadiobutton -padding 4 -indicatormargin 4 + + ttk::style configure TSpinbox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) -arrowsize 10 + + ttk::style configure TCombobox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) ttk::style map TCombobox -selectbackground [list \ {!focus} $colors(-selectbg) \ @@ -72,13 +83,24 @@ namespace eval ttk::theme::dark { {readonly focus} $colors(-selectfg) \ ] - ttk::style configure TNotebook -padding 2 + ttk::style configure TNotebook -padding 2 {*}$flatborder + ttk::style configure TNotebook.Tab -padding 2 {*}$flatborder + ttk::style map TNotebook.Tab \ + -background [list selected $colors(-selectbg)] \ + -foreground [list selected $colors(-selectfg)] \ + -lightcolor [list selected $colors(-selectbg)] - ttk::style configure Treeview -background $colors(-bg) + ttk::style configure Treeview {*}$flatborder ttk::style configure Treeview.Item -padding {2 0 0 0} ttk::style map Treeview \ -background [list selected $colors(-selectbg)] \ -foreground [list selected $colors(-selectfg)] + + ttk::style configure TScrollbar {*}$flatborder -background $colors(-selectbg) + + ttk::style configure TScale {*}$flatborder -background $colors(-selectbg) + + ttk::style configure TProgressbar {*}$flatborder -background $colors(-selectbg) } } diff --git a/themes/default.tcl b/themes/default.tcl new file mode 100644 index 000000000..e26a341a0 --- /dev/null +++ b/themes/default.tcl @@ -0,0 +1,86 @@ +package require Tk 8.6 + +namespace eval ttk::theme::edmc { + + variable version 1.0 + package provide ttk::theme::edmc $version + variable colors + array set colors { + -fg "black" + -bg "#dcdad5" + -disabledfg "#999999" + -selectfg "white" + -selectbg "#9e9a91" + -highlight "blue" + } + + ttk::style theme create edmc -parent clam -settings { + ttk::style configure . \ + -background $colors(-bg) \ + -foreground $colors(-fg) \ + -troughcolor $colors(-bg) \ + -focuscolor $colors(-selectbg) \ + -selectbackground $colors(-selectbg) \ + -selectforeground $colors(-selectfg) \ + -insertwidth 1 \ + -insertcolor $colors(-fg) \ + -fieldbackground $colors(-bg) \ + -font {TkDefaultFont} \ + -borderwidth 1 \ + -relief flat + + ttk::style map . -foreground [list disabled $colors(-disabledfg)] + + option add *font [ttk::style lookup . -font] + option add *Menu.selectcolor $colors(-fg) + + ttk::style configure TLabel -padding 1 + + ttk::style configure Link.TLabel -foreground $colors(-highlight) + + ttk::style configure TLabelframe -relief groove + + ttk::style configure TEntry -padding 2 + + ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center -relief groove + + ttk::style map TButton -background [list \ + {pressed} $colors(-selectbg) \ + ] + + ttk::style map TButton -foreground [list \ + {pressed} $colors(-selectfg) \ + ] + + ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style configure TMenubutton -padding {8 4 4 4} -relief groove + + ttk::style configure TOptionMenu -padding {8 4 4 4} -relief groove + + ttk::style configure TCheckbutton -padding 4 -indicatormargin 4 + + ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style configure TRadiobutton -padding 4 -indicatormargin 4 + + ttk::style configure TSpinbox -padding 2 -arrowsize 10 + + ttk::style configure TCombobox -padding 2 + + ttk::style configure TNotebook -padding 2 + ttk::style configure TNotebook.Tab -padding 2 + + ttk::style configure Treeview.Item -padding {2 0 0 0} + + ttk::style map Treeview \ + -background [list selected $colors(-selectbg)] \ + -foreground [list selected $colors(-selectfg)] + + ttk::style configure TScrollbar -troughcolor $colors(-selectbg) + + ttk::style configure TScale -troughcolor $colors(-selectbg) + + ttk::style configure TProgressbar -troughcolor $colors(-selectbg) + } +} From 0955304dee4f8d2af8b7922853891e819d2b569a Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 3 Jun 2024 01:17:07 +0100 Subject: [PATCH 25/59] flake8 --- plug.py | 2 +- ttkHyperlinkLabel.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plug.py b/plug.py index 162d756a4..6a13058ab 100644 --- a/plug.py +++ b/plug.py @@ -188,7 +188,7 @@ def _load_ttk_catalog_plugin(): plugin.folder = None return plugin except Exception: - logger.exception(f'Failure loading internal Plugin "ttk_catalog"') + logger.exception('Failure loading internal Plugin "ttk_catalog"') def _load_found_plugins(): diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index aeea39956..63fc66776 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -179,7 +179,8 @@ def _leave(self, event: tk.Event) -> None: def _theme(self, event: tk.Event | None = None) -> None: if self.legacy: if str(self['state']) == tk.DISABLED: - super().configure(foreground=self.disabledforeground or ttk.Style().lookup('TLabel', 'foreground', ['disabled'])) + super().configure(foreground=self.disabledforeground or + ttk.Style().lookup('TLabel', 'foreground', ['disabled'])) else: super().configure(foreground=self.foreground or ttk.Style().lookup('Link.TLabel', 'foreground')) From c3328921b7614b403720ce0273c318b4b97ff5be Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 3 Jun 2024 01:53:56 +0100 Subject: [PATCH 26/59] mypy --- plugins/_ttk_catalog.py | 106 +++++++++++++++++++--------------------- theme.py | 11 ++--- 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py index a2f28f372..71cc3268f 100644 --- a/plugins/_ttk_catalog.py +++ b/plugins/_ttk_catalog.py @@ -19,6 +19,8 @@ class Catalog(ttk.Frame): + """A display of ttk widgets under the EDMC themes.""" + def __init__(self, parent: ttk.Frame): super().__init__(parent) @@ -45,7 +47,8 @@ def __init__(self, parent: ttk.Frame): # Create widgets :) self.setup_widgets() - def setup_widgets(self): + def setup_widgets(self) -> None: + """Widget creation.""" check_frame = ttk.LabelFrame(self, text="Checkbutton", padding=(20, 10)) check_frame.grid(row=0, column=0, padx=(20, 10), pady=(20, 10), sticky="nsew") @@ -157,7 +160,7 @@ def setup_widgets(self): pane_1, selectmode="browse", yscrollcommand=scrollbar.set, - columns=(1, 2), + columns=("1", "2"), height=10, ) treeview.pack(expand=True, fill="both") @@ -204,13 +207,13 @@ def setup_widgets(self): # Insert treeview data for parent, iid, text, values in treeview_data: - treeview.insert(parent=parent, index="end", iid=iid, text=text, values=values) + treeview.insert(parent=str(parent), index="end", iid=str(iid), text=text, values=values) if parent == "" or iid in {8, 21}: - treeview.item(iid, open=True) + treeview.item(str(iid), open=True) # Select and scroll - treeview.selection_set(10) - treeview.see(7) + treeview.selection_set("10") + treeview.see("7") # Notebook, pane #2 pane_2 = ttk.Frame(paned, padding=5) @@ -246,7 +249,7 @@ def setup_widgets(self): tab_1, text="ttk widgets for EDMC", justify="center", - font=("-size", 15, "-weight", "bold"), + font=["-size", 15, "-weight", "bold"], ) label.grid(row=1, column=0, pady=10, columnspan=2) @@ -258,82 +261,75 @@ def setup_widgets(self): tab_3 = ttk.Frame(notebook) notebook.add(tab_3, text="Tab 3") - check_frame = tk.LabelFrame(self, text="tk.Checkbutton", padx=20, pady=10) - check_frame.grid(row=0, column=3, padx=(20, 10), pady=(20, 10), sticky="nsew") + tk_check_frame = tk.LabelFrame(self, text="tk.Checkbutton", padx=20, pady=10) + tk_check_frame.grid(row=0, column=3, padx=(20, 10), pady=(20, 10), sticky="nsew") - check_1 = tk.Checkbutton(check_frame, text="Unchecked", variable=self.var_0) - check_1.grid(row=0, column=0, sticky="nsew") + tk_check_1 = tk.Checkbutton(tk_check_frame, text="Unchecked", variable=self.var_0) + tk_check_1.grid(row=0, column=0, sticky="nsew") - check_2 = tk.Checkbutton(check_frame, text="Checked", variable=self.var_1) - check_2.grid(row=1, column=0, sticky="nsew") + tk_check_2 = tk.Checkbutton(tk_check_frame, text="Checked", variable=self.var_1) + tk_check_2.grid(row=1, column=0, sticky="nsew") - check_4 = tk.Checkbutton(check_frame, text="Disabled", state="disabled") - check_4.grid(row=3, column=0, sticky="nsew") + tk_check_4 = tk.Checkbutton(tk_check_frame, text="Disabled", state="disabled") + tk_check_4.grid(row=3, column=0, sticky="nsew") # Create a Frame for the Radiobuttons - radio_frame = tk.LabelFrame(self, text="tk.Radiobutton", padx=20, pady=10) - radio_frame.grid(row=2, column=3, padx=(20, 10), pady=10, sticky="nsew") + tk_radio_frame = tk.LabelFrame(self, text="tk.Radiobutton", padx=20, pady=10) + tk_radio_frame.grid(row=2, column=3, padx=(20, 10), pady=10, sticky="nsew") - radio_1 = tk.Radiobutton(radio_frame, text="Unselected", variable=self.var_3, value=1) - radio_1.grid(row=0, column=0, sticky="nsew") + tk_radio_1 = tk.Radiobutton(tk_radio_frame, text="Unselected", variable=self.var_3, value=1) + tk_radio_1.grid(row=0, column=0, sticky="nsew") - radio_2 = tk.Radiobutton(radio_frame, text="Selected", variable=self.var_3, value=2) - radio_2.grid(row=1, column=0, sticky="nsew") + tk_radio_2 = tk.Radiobutton(tk_radio_frame, text="Selected", variable=self.var_3, value=2) + tk_radio_2.grid(row=1, column=0, sticky="nsew") - radio_3 = tk.Radiobutton(radio_frame, text="Disabled", state="disabled") - radio_3.grid(row=2, column=0, sticky="nsew") + tk_radio_3 = tk.Radiobutton(tk_radio_frame, text="Disabled", state="disabled") + tk_radio_3.grid(row=2, column=0, sticky="nsew") # Create a Frame for input widgets - widgets_frame = tk.Frame(self) - widgets_frame.grid(row=0, column=4, padx=10, pady=(30, 10), sticky="nsew", rowspan=2) - widgets_frame.columnconfigure(index=0, weight=1) + tk_widgets_frame = tk.Frame(self) + tk_widgets_frame.grid(row=0, column=4, padx=10, pady=(30, 10), sticky="nsew", rowspan=2) + tk_widgets_frame.columnconfigure(index=0, weight=1) # Entry - entry = tk.Entry(widgets_frame) - entry.insert(0, "tk.Entry") - entry.grid(row=0, column=0, padx=5, pady=(0, 10), sticky="ew") + tk_entry = tk.Entry(tk_widgets_frame) + tk_entry.insert(0, "tk.Entry") + tk_entry.grid(row=0, column=0, padx=5, pady=(0, 10), sticky="ew") # Spinbox - spinbox = tk.Spinbox(widgets_frame, from_=0, to=100, increment=0.1) - spinbox.grid(row=1, column=0, padx=5, pady=10, sticky="ew") - - # Menu for the Menubutton - menu = tk.Menu(self) - menu.add_command(label="Menu item 1") - menu.add_command(label="Menu item 2") - menu.add_separator() - menu.add_command(label="Menu item 3") - menu.add_command(label="Menu item 4") + tk_spinbox = tk.Spinbox(tk_widgets_frame, from_=0, to=100, increment=0.1) + tk_spinbox.grid(row=1, column=0, padx=5, pady=10, sticky="ew") # Menubutton - menubutton = tk.Menubutton(widgets_frame, text="tk.Menubutton", menu=menu, direction="below") - menubutton.grid(row=4, column=0, padx=5, pady=10, sticky="nsew") + tk_menubutton = tk.Menubutton(tk_widgets_frame, text="tk.Menubutton", menu=menu, direction="below") + tk_menubutton.grid(row=4, column=0, padx=5, pady=10, sticky="nsew") # OptionMenu - optionmenu = tk.OptionMenu(widgets_frame, self.var_6, *self.tk_option_menu_list) - optionmenu.grid(row=5, column=0, padx=5, pady=10, sticky="nsew") + tk_optionmenu = tk.OptionMenu(tk_widgets_frame, self.var_6, *self.tk_option_menu_list) + tk_optionmenu.grid(row=5, column=0, padx=5, pady=10, sticky="nsew") # Button - button = tk.Button(widgets_frame, text="tk.Button") - button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") + tk_button = tk.Button(tk_widgets_frame, text="tk.Button") + tk_button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") - hyperlink_frame = tk.LabelFrame(self, text="HyperlinkLabel (legacy)", padx=20, pady=10) - hyperlink_frame.grid(row=2, column=4, padx=10, pady=10, sticky="nsew") + tk_hyperlink_frame = tk.LabelFrame(self, text="HyperlinkLabel (legacy)", padx=20, pady=10) + tk_hyperlink_frame.grid(row=2, column=4, padx=10, pady=10, sticky="nsew") - hyperlink_1 = HyperlinkLabel(hyperlink_frame, text="Default", url=URL) - hyperlink_1.grid(row=0, column=0, sticky="nsew") + tk_hyperlink_1 = HyperlinkLabel(tk_hyperlink_frame, text="Default", url=URL) + tk_hyperlink_1.grid(row=0, column=0, sticky="nsew") - hyperlink_2 = HyperlinkLabel(hyperlink_frame, text="Underline", url=URL, underline=True) - hyperlink_2.grid(row=1, column=0, sticky="nsew") + tk_hyperlink_2 = HyperlinkLabel(tk_hyperlink_frame, text="Underline", url=URL, underline=True) + tk_hyperlink_2.grid(row=1, column=0, sticky="nsew") - hyperlink_3 = HyperlinkLabel(hyperlink_frame, text="No underline", url=URL, underline=False) - hyperlink_3.grid(row=2, column=0, sticky="nsew") + tk_hyperlink_3 = HyperlinkLabel(tk_hyperlink_frame, text="No underline", url=URL, underline=False) + tk_hyperlink_3.grid(row=2, column=0, sticky="nsew") - hyperlink_4 = HyperlinkLabel(hyperlink_frame, text="Disabled", url=URL, state=tk.DISABLED) - hyperlink_4.grid(row=3, column=0, sticky="nsew") + tk_hyperlink_4 = HyperlinkLabel(tk_hyperlink_frame, text="Disabled", url=URL, state=tk.DISABLED) + tk_hyperlink_4.grid(row=3, column=0, sticky="nsew") def plugin_start3(path: str) -> str: + """Plugin initialization.""" return 'TtkCatalog' diff --git a/theme.py b/theme.py index 2fcbcbd4f..358561f45 100644 --- a/theme.py +++ b/theme.py @@ -13,10 +13,9 @@ import os import sys import tkinter as tk -from os.path import join +import warnings from tkinter import ttk from typing import Callable -from typing_extensions import deprecated from config import config from EDMCLogging import get_main_logger @@ -36,7 +35,7 @@ AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore FR_PRIVATE = 0x10 FR_NOT_ENUM = 0x20 - AddFontResourceEx(join(config.respath, 'EUROCAPS.TTF'), FR_PRIVATE, 0) + AddFontResourceEx(os.path.join(config.respath, 'EUROCAPS.TTF'), FR_PRIVATE, 0) elif sys.platform == 'linux': # pyright: reportUnboundVariable=false @@ -154,9 +153,9 @@ def initialize(self, root: tk.Tk): except tk.TclError: logger.exception(f'Failure loading internal theme "{theme_file}"') - @deprecated('Theme colors are now applied automatically, even after initialization') - def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 + def register(self, widget: tk.Widget | tk.BitmapImage) -> None: assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget + warnings.warn('Theme postprocessing is no longer necessary', category=DeprecationWarning) def register_alternate(self, pair: tuple, gridopts: dict) -> None: self.widgets_pair.append((pair, gridopts)) @@ -204,7 +203,6 @@ def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: except Exception: logger.exception(f'Failure configuring image: {image=}') - @deprecated('Theme colors are now applied automatically, even after initialization') def update(self, widget: tk.Widget) -> None: """ Apply current theme to a widget and its children. @@ -213,6 +211,7 @@ def update(self, widget: tk.Widget) -> None: :param widget: Target widget. """ assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget + warnings.warn('Theme postprocessing is no longer necessary', category=DeprecationWarning) def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 theme = config.get_int('theme') From 18c1c3ac78f016a270a3481a4fc835869b914d0a Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 4 Jun 2024 00:35:26 +0100 Subject: [PATCH 27/59] Themes as tcl packages, deprecated most of myNotebook --- EDMarketConnector.py | 10 +- myNotebook.py | 56 +++------- plugins/eddn.py | 3 +- prefs.py | 129 ++++++++++++------------ theme.py | 94 +++++++---------- themes/{ => dark}/dark.tcl | 8 ++ themes/dark/pkgIndex.tcl | 2 + themes/{default.tcl => light/light.tcl} | 14 ++- themes/light/pkgIndex.tcl | 2 + themes/transparent/pkgIndex.tcl | 2 + themes/transparent/transparent.tcl | 114 +++++++++++++++++++++ 11 files changed, 257 insertions(+), 177 deletions(-) rename themes/{ => dark}/dark.tcl (88%) create mode 100644 themes/dark/pkgIndex.tcl rename themes/{default.tcl => light/light.tcl} (81%) create mode 100644 themes/light/pkgIndex.tcl create mode 100644 themes/transparent/pkgIndex.tcl create mode 100644 themes/transparent/transparent.tcl diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 1ebca51b4..2be3598bf 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -715,9 +715,7 @@ def open_window(systray: 'SysTrayIcon') -> None: self.w.attributes('-topmost', config.get_int('always_ontop') and 1 or 0) - theme.apply(self.w) - - self.w.bind('', self.onmap) # Special handling for overrideredict + self.w.bind('', self.onmap) # Special handling for overrideredirect self.w.bind('', self.onenter) # Special handling for transparency self.w.bind('', self.onenter) # Special handling for transparency self.w.bind('', self.onleave) # Special handling for transparency @@ -1946,11 +1944,11 @@ def oniconify(self, event=None) -> None: self.w.wait_visibility() # Need main window to be re-created before returning theme.active = None # So theme will be re-applied on map - # TODO: Confirm this is unused and remove. def onmap(self, event=None) -> None: - """Perform a now unused function.""" + """Handle when our window is rendered.""" if event.widget == self.w: - theme.apply(self.w) + # TODO decouple theme switch and window manager stuff + theme.apply() def onenter(self, event=None) -> None: """Handle when our window gains focus.""" diff --git a/myNotebook.py b/myNotebook.py index 0b083c237..96eef270d 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -9,54 +9,37 @@ """ from __future__ import annotations -import sys import tkinter as tk from tkinter import ttk, messagebox from PIL import ImageGrab from l10n import translations as tr -if sys.platform == 'win32': - PAGEFG = 'SystemWindowText' - PAGEBG = 'SystemWindow' # typically white - class Notebook(ttk.Notebook): """Custom ttk.Notebook class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Notebook. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) - style = ttk.Style() - if sys.platform == 'win32': - style.configure('nb.TFrame', background=PAGEBG) - style.configure('nb.TButton', background=PAGEBG) - style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) - style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG) - style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG) self.grid(padx=10, pady=10, sticky=tk.NSEW) class Frame(ttk.Frame): """Custom ttk.Frame class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Frame. Will remove in 6.0 or later. def __init__(self, master: ttk.Notebook | None = None, **kw): - if sys.platform == 'win32': - ttk.Frame.__init__(self, master, style='nb.TFrame', **kw) - ttk.Frame(self).grid(pady=5) # top spacer - else: - ttk.Frame.__init__(self, master, **kw) - ttk.Frame(self).grid(pady=5) # top spacer + ttk.Frame.__init__(self, master, **kw) + ttk.Frame(self).grid(pady=5) # top spacer self.configure(takefocus=1) # let the frame take focus so that no particular child is focused class Label(tk.Label): """Custom tk.Label class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Label. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): - kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32' - else ttk.Style().lookup('TLabel', 'foreground')) - kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32' - else ttk.Style().lookup('TLabel', 'background')) super().__init__(master, **kw) @@ -132,17 +115,15 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class Button(ttk.Button): """Custom ttk.Button class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'win32': - ttk.Button.__init__(self, master, style='nb.TButton', **kw) - else: - ttk.Button.__init__(self, master, **kw) + ttk.Button.__init__(self, master, **kw) class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" - # DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later. + # DEPRECATED: Migrate to ttk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): tk.Button.__init__(self, master, **kw) @@ -150,31 +131,22 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class Checkbutton(ttk.Checkbutton): """Custom ttk.Checkbutton class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Checkbutton. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): - style = 'nb.TCheckbutton' if sys.platform == 'win32' else None - super().__init__(master, style=style, **kw) # type: ignore + super().__init__(master, **kw) # type: ignore class Radiobutton(ttk.Radiobutton): """Custom ttk.Radiobutton class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Radiobutton. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): - style = 'nb.TRadiobutton' if sys.platform == 'win32' else None - super().__init__(master, style=style, **kw) # type: ignore + super().__init__(master, **kw) # type: ignore class OptionMenu(ttk.OptionMenu): """Custom ttk.OptionMenu class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.OptionMenu. Will remove in 6.0 or later. def __init__(self, master, variable, default=None, *values, **kw): - if sys.platform == 'win32': - # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style - ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw) - self['menu'].configure(background=PAGEBG) - else: - ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw) - self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background')) - - # Workaround for https://bugs.python.org/issue25684 - for i in range(0, self['menu'].index('end') + 1): - self['menu'].entryconfig(i, variable=variable) + ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw) diff --git a/plugins/eddn.py b/plugins/eddn.py index ba84ea875..f8f005339 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -43,7 +43,6 @@ from config import applongname, appname, appversion_nobuild, config, debug_senders, user_agent from EDMCLogging import get_main_logger from monitor import monitor -from myNotebook import Frame from prefs import prefsVersion from ttkHyperlinkLabel import HyperlinkLabel from util import text @@ -2146,7 +2145,7 @@ def tracking_ui_update() -> None: this.ui.update_idletasks() -def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: +def plugin_prefs(parent, cmdr: str, is_beta: bool) -> nb.Frame: """ Set up Preferences pane for this plugin. diff --git a/prefs.py b/prefs.py index 298d8cb31..1fdf9c5a4 100644 --- a/prefs.py +++ b/prefs.py @@ -15,7 +15,6 @@ from tkinter import ttk from types import TracebackType from typing import Any, Callable, Optional, Type -import myNotebook as nb # noqa: N813 import plug from config import appversion_nobuild, config from EDMCLogging import edmclogger, get_main_logger @@ -269,7 +268,8 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): frame.rowconfigure(0, weight=1) frame.rowconfigure(1, weight=0) - notebook: nb.Notebook = nb.Notebook(frame) + notebook = ttk.Notebook(frame) + notebook.grid(padx=10, pady=10, sticky=tk.NSEW) notebook.bind('<>', self.tabchanged) # Recompute on tab change self.PADX = 10 @@ -333,7 +333,7 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): self.wm_minsize(min_width, min_height) def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: - output_frame = nb.Frame(root_notebook) + output_frame = ttk.Frame(root_notebook) output_frame.columnconfigure(0, weight=1) if prefsVersion.shouldSetDefaults('0.0.0.0', not bool(config.get_int('output'))): @@ -345,11 +345,11 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: row = AutoInc(start=0) # LANG: Settings > Output - choosing what data to save to files - self.out_label = nb.Label(output_frame, text=tr.tl('Please choose what data to save')) + self.out_label = ttk.Label(output_frame, text=tr.tl('Please choose what data to save')) self.out_label.grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) self.out_csv = tk.IntVar(value=1 if (output & config.OUT_MKT_CSV) else 0) - self.out_csv_button = nb.Checkbutton( + self.out_csv_button = ttk.Checkbutton( output_frame, text=tr.tl('Market data in CSV format file'), # LANG: Settings > Output option variable=self.out_csv, @@ -358,7 +358,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.out_csv_button.grid(columnspan=2, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) self.out_td = tk.IntVar(value=1 if (output & config.OUT_MKT_TD) else 0) - self.out_td_button = nb.Checkbutton( + self.out_td_button = ttk.Checkbutton( output_frame, text=tr.tl('Market data in Trade Dangerous format file'), # LANG: Settings > Output option variable=self.out_td, @@ -368,7 +368,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.out_ship = tk.IntVar(value=1 if (output & config.OUT_SHIP) else 0) # Output setting - self.out_ship_button = nb.Checkbutton( + self.out_ship_button = ttk.Checkbutton( output_frame, text=tr.tl('Ship loadout'), # LANG: Settings > Output option variable=self.out_ship, @@ -378,7 +378,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.out_auto = tk.IntVar(value=0 if output & config.OUT_MKT_MANUAL else 1) # inverted # Output setting - self.out_auto_button = nb.Checkbutton( + self.out_auto_button = ttk.Checkbutton( output_frame, text=tr.tl('Automatically update on docking'), # LANG: Settings > Output option variable=self.out_auto, @@ -389,7 +389,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.outdir = tk.StringVar() self.outdir.set(str(config.get_str('outdir'))) # LANG: Settings > Output - Label for "where files are located" - self.outdir_label = nb.Label(output_frame, text=tr.tl('File location')+':') # Section heading in settings + self.outdir_label = ttk.Label(output_frame, text=tr.tl('File location')+':') # Section heading in settings # Type ignored due to incorrect type annotation. a 2 tuple does padding for each side self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore @@ -418,7 +418,7 @@ def __setup_plugin_tabs(self, notebook: ttk.Notebook) -> None: notebook.add(plugin_frame, text=plugin.name) def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 - config_frame = nb.Frame(notebook) + config_frame = ttk.Frame(notebook) config_frame.columnconfigure(1, weight=1) row = AutoInc(start=0) @@ -432,7 +432,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.logdir_entry = ttk.Entry(config_frame, takefocus=False) # Location of the Journal files - nb.Label( + ttk.Label( config_frame, # LANG: Settings > Configuration - Label for Journal files location text=tr.tl('E:D journal file location')+':' @@ -468,12 +468,12 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) - nb.Label( + ttk.Label( config_frame, text=tr.tl('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) - nb.Checkbutton( + ttk.Checkbutton( config_frame, # LANG: Configuration - Enable or disable the Fleet Carrier CAPI calls text=tr.tl('Enable Fleetcarrier CAPI Queries'), @@ -490,7 +490,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.hotkey_only = tk.IntVar(value=not config.get_int('hotkey_always')) self.hotkey_play = tk.IntVar(value=not config.get_int('hotkey_mute')) with row as cur_row: - nb.Label( + ttk.Label( config_frame, text=tr.tl('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) @@ -509,7 +509,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row) # Hotkey/Shortcut setting - self.hotkey_only_btn = nb.Checkbutton( + self.hotkey_only_btn = ttk.Checkbutton( config_frame, # LANG: Configuration - Act on hotkey only when ED is in foreground text=tr.tl('Only when Elite: Dangerous is the active app'), @@ -520,7 +520,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.hotkey_only_btn.grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Hotkey/Shortcut setting - self.hotkey_play_btn = nb.Checkbutton( + self.hotkey_play_btn = ttk.Checkbutton( config_frame, # LANG: Configuration - play sound when hotkey used text=tr.tl('Play sound'), @@ -536,7 +536,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) with row as curr_row: - nb.Label(config_frame, text=tr.tl('Update Track')).grid( # LANG: Select the Update Track (Beta, Stable) + ttk.Label(config_frame, text=tr.tl('Update Track')).grid( # LANG: Select the Update Track (Beta, Stable) padx=self.PADX, pady=self.PADY, sticky=tk.W, row=curr_row ) self.curr_update_track = "Beta" if config.get_bool('beta_optin') else "Stable" @@ -546,7 +546,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 tr.tl("Stable"), # LANG: Stable Version of EDMC tr.tl("Beta") # LANG: Beta Version of EDMC ] - self.update_track = nb.OptionMenu( + self.update_track = ttk.OptionMenu( config_frame, self.update_paths, self.update_paths.get(), *update_paths ) @@ -554,7 +554,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.update_track.grid(column=1, pady=self.BOXY, padx=self.PADX, sticky=tk.W, row=curr_row) self.disable_autoappupdatecheckingame = tk.IntVar(value=config.get_int('disable_autoappupdatecheckingame')) - self.disable_autoappupdatecheckingame_btn = nb.Checkbutton( + self.disable_autoappupdatecheckingame_btn = ttk.Checkbutton( config_frame, # LANG: Configuration - disable checks for app updates when in-game text=tr.tl('Disable Automatic Application Updates Check when in-game'), @@ -572,7 +572,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Settings prompt for preferred ship loadout, system and station info websites # LANG: Label for preferred shipyard, system and station 'providers' - nb.Label(config_frame, text=tr.tl('Preferred websites')).grid( + ttk.Label(config_frame, text=tr.tl('Preferred websites')).grid( columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -583,10 +583,10 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) # Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis # LANG: Label for Shipyard provider selection - nb.Label(config_frame, text=tr.tl('Shipyard')).grid( + ttk.Label(config_frame, text=tr.tl('Shipyard')).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) - self.shipyard_button = nb.OptionMenu( + self.shipyard_button = ttk.OptionMenu( config_frame, self.shipyard_provider, self.shipyard_provider.get(), *plug.provides('shipyard_url') ) @@ -594,7 +594,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.shipyard_button.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row) # Option for alternate URL opening self.alt_shipyard_open = tk.IntVar(value=config.get_int('use_alt_shipyard_open')) - self.alt_shipyard_open_btn = nb.Checkbutton( + self.alt_shipyard_open_btn = ttk.Checkbutton( config_frame, # LANG: Label for checkbox to utilise alternative Coriolis URL method text=tr.tl('Use alternate URL method'), @@ -611,8 +611,8 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) # LANG: Configuration - Label for selection of 'System' provider website - nb.Label(config_frame, text=tr.tl('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) - self.system_button = nb.OptionMenu( + ttk.Label(config_frame, text=tr.tl('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + self.system_button = ttk.OptionMenu( config_frame, self.system_provider, self.system_provider.get(), @@ -629,8 +629,9 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) # LANG: Configuration - Label for selection of 'Station' provider website - nb.Label(config_frame, text=tr.tl('Station')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) - self.station_button = nb.OptionMenu( + ttk.Label(config_frame, text=tr.tl('Station')).grid( + padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + self.station_button = ttk.OptionMenu( config_frame, self.station_provider, self.station_provider.get(), @@ -647,7 +648,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 with row as cur_row: # Set the current loglevel - nb.Label( + ttk.Label( config_frame, # LANG: Configuration - Label for selection of Log Level text=tr.tl('Log Level') @@ -664,7 +665,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 )) ) - self.loglevel_dropdown = nb.OptionMenu( + self.loglevel_dropdown = ttk.OptionMenu( config_frame, self.select_loglevel, self.select_loglevel.get(), @@ -682,28 +683,28 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ).grid(column=2, padx=self.PADX, pady=0, sticky=tk.NSEW, row=cur_row) # Big spacer - nb.Label(config_frame).grid(sticky=tk.W, row=row.get()) + ttk.Label(config_frame).grid(sticky=tk.W, row=row.get()) # LANG: Label for 'Configuration' tab in Settings notebook.add(config_frame, text=tr.tl('Configuration')) def __setup_privacy_tab(self, notebook: ttk.Notebook) -> None: - privacy_frame = nb.Frame(notebook) + privacy_frame = ttk.Frame(notebook) self.hide_multicrew_captain = tk.BooleanVar(value=config.get_bool('hide_multicrew_captain', default=False)) self.hide_private_group = tk.BooleanVar(value=config.get_bool('hide_private_group', default=False)) row = AutoInc(start=0) # LANG: UI elements privacy section header in privacy tab of preferences - nb.Label(privacy_frame, text=tr.tl('Main UI privacy options')).grid( + ttk.Label(privacy_frame, text=tr.tl('Main UI privacy options')).grid( row=row.get(), column=0, sticky=tk.W, padx=self.PADX, pady=self.PADY ) - nb.Checkbutton( + ttk.Checkbutton( # LANG: Hide private group owner name from UI checkbox privacy_frame, text=tr.tl('Hide private group name in UI'), variable=self.hide_private_group ).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W) - nb.Checkbutton( + ttk.Checkbutton( # LANG: Hide multicrew captain name from main UI checkbox privacy_frame, text=tr.tl('Hide multi-crew captain name'), variable=self.hide_multicrew_captain @@ -729,14 +730,14 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: row = AutoInc(start=0) - appearance_frame = nb.Frame(notebook) + appearance_frame = ttk.Frame(notebook) appearance_frame.columnconfigure(2, weight=1) with row as cur_row: # LANG: Appearance - Label for selection of application display language - nb.Label(appearance_frame, text=tr.tl('Language')).grid( + ttk.Label(appearance_frame, text=tr.tl('Language')).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) - self.lang_button = nb.OptionMenu(appearance_frame, self.lang, self.lang.get(), *self.languages.values()) + self.lang_button = ttk.OptionMenu(appearance_frame, self.lang, self.lang.get(), *self.languages.values()) self.lang_button.grid(column=1, columnspan=2, padx=0, pady=self.BOXY, sticky=tk.W, row=cur_row) ttk.Separator(appearance_frame, orient=tk.HORIZONTAL).grid( @@ -745,26 +746,26 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: # Appearance setting # LANG: Label for Settings > Appearance > Theme selection - nb.Label(appearance_frame, text=tr.tl('Theme')).grid( + ttk.Label(appearance_frame, text=tr.tl('Theme')).grid( columnspan=3, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) # Appearance theme and language setting - nb.Radiobutton( + ttk.Radiobutton( # LANG: Label for 'Default' theme radio button appearance_frame, text=tr.tl('Default'), variable=self.theme, value=theme.THEME_DEFAULT, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance theme setting - nb.Radiobutton( + ttk.Radiobutton( # LANG: Label for 'Dark' theme radio button appearance_frame, text=tr.tl('Dark'), variable=self.theme, value=theme.THEME_DARK, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) if sys.platform == 'win32': - nb.Radiobutton( + ttk.Radiobutton( appearance_frame, # LANG: Label for 'Transparent' theme radio button text=tr.tl('Transparent'), # Appearance theme setting @@ -774,7 +775,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) with row as cur_row: - self.theme_label_0 = nb.Label(appearance_frame, text=self.theme_prompts[0]) + self.theme_label_0 = ttk.Label(appearance_frame, text=self.theme_prompts[0]) self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) # Main window @@ -789,7 +790,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: self.theme_button_0.grid(column=1, padx=0, pady=self.BOXY, sticky=tk.NSEW, row=cur_row) with row as cur_row: - self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1]) + self.theme_label_1 = ttk.Label(appearance_frame, text=self.theme_prompts[1]) self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) self.theme_button_1 = tk.Button( appearance_frame, @@ -813,7 +814,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: ) with row as cur_row: # LANG: Appearance - Label for selection of UI scaling - nb.Label(appearance_frame, text=tr.tl('UI Scale Percentage')).grid( + ttk.Label(appearance_frame, text=tr.tl('UI Scale Percentage')).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) @@ -831,7 +832,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: ) self.uiscale_bar.grid(column=1, padx=0, pady=self.BOXY, sticky=tk.W, row=cur_row) - self.ui_scaling_defaultis = nb.Label( + self.ui_scaling_defaultis = ttk.Label( appearance_frame, # LANG: Appearance - Help/hint text for UI scaling selection text=tr.tl('100 means Default{CR}Restart Required for{CR}changes to take effect!') @@ -844,7 +845,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: with row as cur_row: # LANG: Appearance - Label for selection of main window transparency - nb.Label(appearance_frame, text=tr.tl("Main window transparency")).grid( + ttk.Label(appearance_frame, text=tr.tl("Main window transparency")).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) self.transparency = tk.IntVar() @@ -861,7 +862,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: command=lambda _: self.parent.wm_attributes("-alpha", self.transparency.get() / 100) ) - nb.Label( + ttk.Label( appearance_frame, # LANG: Appearance - Help/hint text for Main window transparency selection text=tr.tl( @@ -883,7 +884,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) - self.ontop_button = nb.Checkbutton( + self.ontop_button = ttk.Checkbutton( appearance_frame, # LANG: Appearance - Label for checkbox to select if application always on top text=tr.tl('Always on top'), @@ -895,7 +896,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: ) # Appearance setting if sys.platform == 'win32': - nb.Checkbutton( + ttk.Checkbutton( appearance_frame, # LANG: Appearance option for Windows "minimize to system tray" text=tr.tl('Minimize to system tray'), @@ -903,14 +904,14 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance setting - nb.Label(appearance_frame).grid(sticky=tk.W) # big spacer + ttk.Label(appearance_frame).grid(sticky=tk.W) # big spacer # LANG: Label for Settings > Appearance tab notebook.add(appearance_frame, text=tr.tl('Appearance')) # Tab heading in settings def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Plugin settings and info - plugins_frame = nb.Frame(notebook) + plugins_frame = ttk.Frame(notebook) plugins_frame.columnconfigure(0, weight=1) plugdir = tk.StringVar() plugdir.set(config.plugin_dir) @@ -918,7 +919,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Section heading in settings # LANG: Label for location of third-party plugins folder - nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid( + ttk.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -927,7 +928,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) with row as cur_row: - nb.Label( + ttk.Label( plugins_frame, # Help text in settings # LANG: Tip/label about how to disable plugins @@ -947,7 +948,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid( columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) - nb.Label( + ttk.Label( plugins_frame, # LANG: Label on list of enabled plugins text=tr.tl('Enabled Plugins')+':' # List of plugins in settings @@ -955,10 +956,10 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 for plugin in enabled_plugins: if plugin.name == plugin.folder: - label = nb.Label(plugins_frame, text=plugin.name) + label = ttk.Label(plugins_frame, text=plugin.name) else: - label = nb.Label(plugins_frame, text=f'{plugin.folder} ({plugin.name})') + label = ttk.Label(plugins_frame, text=f'{plugin.folder} ({plugin.name})') label.grid(columnspan=2, padx=self.LISTX, pady=self.PADY, sticky=tk.W, row=row.get()) @@ -970,21 +971,21 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) # LANG: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x - nb.Label(plugins_frame, text=tr.tl('Plugins Without Python 3.x Support')+':').grid( + ttk.Label(plugins_frame, text=tr.tl('Plugins Without Python 3.x Support')+':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) HyperlinkLabel( # LANG: Plugins - Label on URL to documentation about migrating plugins from Python 2.7 plugins_frame, text=tr.tl('Information on migrating plugins'), - background=nb.Label().cget('background'), + background=ttk.Label().cget('background'), url='https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#migration-from-python-27', underline=True ).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) for plugin in plug.PLUGINS_not_py3: if plugin.folder: # 'system' ones have this set to None to suppress listing in Plugins prefs tab - nb.Label(plugins_frame, text=plugin.name).grid( + ttk.Label(plugins_frame, text=plugin.name).grid( columnspan=2, padx=self.LISTX, pady=self.PADY, sticky=tk.W, row=row.get() ) ############################################################ @@ -995,14 +996,14 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid( columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) - nb.Label( + ttk.Label( plugins_frame, # LANG: Label on list of user-disabled plugins text=tr.tl('Disabled Plugins')+':' # List of plugins in settings ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) for plugin in disabled_plugins: - nb.Label(plugins_frame, text=plugin.name).grid( + ttk.Label(plugins_frame, text=plugin.name).grid( columnspan=2, padx=self.LISTX, pady=self.PADY, sticky=tk.W, row=row.get() ) ############################################################ @@ -1013,13 +1014,13 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) # LANG: Plugins - Label for list of 'broken' plugins that failed to load - nb.Label(plugins_frame, text=tr.tl('Broken Plugins')+':').grid( + ttk.Label(plugins_frame, text=tr.tl('Broken Plugins')+':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) for plugin in plug.PLUGINS_broken: if plugin.folder: # 'system' ones have this set to None to suppress listing in Plugins prefs tab - nb.Label(plugins_frame, text=plugin.name).grid( + ttk.Label(plugins_frame, text=plugin.name).grid( columnspan=2, padx=self.LISTX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -1273,7 +1274,7 @@ def apply(self) -> None: config.set('theme', self.theme.get()) config.set('dark_text', self.theme_colors[0]) config.set('dark_highlight', self.theme_colors[1]) - theme.apply(self.parent) + theme.apply() # Send to the Post Config if we updated the update branch post_flags = { diff --git a/theme.py b/theme.py index 358561f45..5e2bbecf8 100644 --- a/theme.py +++ b/theme.py @@ -121,14 +121,17 @@ class MotifWmHints(Structure): class _Theme: - - # Enum ? Remember these are, probably, based on 'value' of a tk - # RadioButton set. Looking in prefs.py, they *appear* to be hard-coded - # there as well. + # TODO ditch indexes, support additional themes in user folder THEME_DEFAULT = 0 THEME_DARK = 1 THEME_TRANSPARENT = 2 + packages = { + THEME_DEFAULT: 'light', # 'default' is the name of a builtin theme + THEME_DARK: 'dark', + THEME_TRANSPARENT: 'transparent', + } style: ttk.Style + root: tk.Tk def __init__(self) -> None: self.active: int | None = None # Starts out with no theme @@ -140,6 +143,7 @@ def __init__(self) -> None: def initialize(self, root: tk.Tk): self.style = ttk.Style() + self.root = root # Default dark theme colors if not config.get_str('dark_text'): @@ -147,11 +151,12 @@ def initialize(self, root: tk.Tk): if not config.get_str('dark_highlight'): config.set('dark_highlight', 'white') - for theme_file in config.internal_theme_dir_path.glob('[!._]*.tcl'): + for theme_file in config.internal_theme_dir_path.glob('*/pkgIndex.tcl'): try: - root.tk.call('source', theme_file) + self.root.tk.call('source', theme_file) + logger.info(f'loading theme package from "{theme_file}"') except tk.TclError: - logger.exception(f'Failure loading internal theme "{theme_file}"') + logger.exception(f'Failure loading theme package "{theme_file}"') def register(self, widget: tk.Widget | tk.BitmapImage) -> None: assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget @@ -174,7 +179,6 @@ def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None: if widget and widget['state'] != tk.DISABLED: try: widget.configure(state=tk.ACTIVE) - except Exception: logger.exception(f'Failure setting widget active: {widget=}') @@ -182,7 +186,6 @@ def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None: try: image['background'] = self.style.lookup('.', 'selectbackground') image['foreground'] = self.style.lookup('.', 'selectforeground') - except Exception: logger.exception(f'Failure configuring image: {image=}') @@ -191,7 +194,6 @@ def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: if widget and widget['state'] != tk.DISABLED: try: widget.configure(state=tk.NORMAL) - except Exception: logger.exception(f'Failure setting widget normal: {widget=}') @@ -199,7 +201,6 @@ def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: try: image['background'] = self.style.lookup('.', 'background') image['foreground'] = self.style.lookup('.', 'foreground') - except Exception: logger.exception(f'Failure configuring image: {image=}') @@ -213,24 +214,11 @@ def update(self, widget: tk.Widget) -> None: assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget warnings.warn('Theme postprocessing is no longer necessary', category=DeprecationWarning) - def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 + def apply(self) -> None: # noqa: CCR001 theme = config.get_int('theme') - if theme == self.THEME_DEFAULT: - self.style.theme_use('edmc') - elif theme == self.THEME_DARK: - self.style.theme_use('dark') - elif theme == self.THEME_TRANSPARENT: - self.style.theme_use('transparent') - - root.tk_setPalette( - background=self.style.lookup('.', 'background'), - foreground=self.style.lookup('.', 'foreground'), - highlightColor=self.style.lookup('.', 'focuscolor'), - selectBackground=self.style.lookup('.', 'selectbackground'), - selectForeground=self.style.lookup('.', 'selectforeground'), - activeBackground=self.style.lookup('.', 'selectbackground'), - activeForeground=self.style.lookup('.', 'selectforeground'), - ) + theme_name = self.packages[theme] + self.root.tk.call('package', 'require', f'ttk::theme::{theme_name}') + self.root.tk.call('ttk::setTheme', theme_name) for image in self.bitmaps: image['background'] = self.style.lookup('.', 'background') @@ -244,12 +232,10 @@ def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 if isinstance(pair[0], tk.Menu): if theme == self.THEME_DEFAULT: - root['menu'] = pair[0] - + self.root['menu'] = pair[0] else: # Dark *or* Transparent - root['menu'] = '' + self.root['menu'] = '' pair[theme].grid(**gridopts) - else: pair[theme].grid(**gridopts) @@ -267,45 +253,37 @@ def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 GetWindowLongW = ctypes.windll.user32.GetWindowLongW # noqa: N806 # ctypes SetWindowLongW = ctypes.windll.user32.SetWindowLongW # noqa: N806 # ctypes - # FIXME: Lose the "treat this like a boolean" bullshit - if theme == self.THEME_DEFAULT: - root.overrideredirect(False) - - else: - root.overrideredirect(True) + self.root.overrideredirect(theme != self.THEME_DEFAULT) if theme == self.THEME_TRANSPARENT: - root.attributes("-transparentcolor", 'grey4') - + self.root.attributes("-transparentcolor", 'grey4') else: - root.attributes("-transparentcolor", '') + self.root.attributes("-transparentcolor", '') - root.withdraw() - root.update_idletasks() # Size and windows styles get recalculated here - hwnd = ctypes.windll.user32.GetParent(root.winfo_id()) + self.root.withdraw() + self.root.update_idletasks() # Size and windows styles get recalculated here + hwnd = ctypes.windll.user32.GetParent(self.root.winfo_id()) SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize 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 + self.root.deiconify() + self.root.wait_visibility() # need main window to be displayed before returning else: - root.withdraw() - root.update_idletasks() # Size gets recalculated here + self.root.withdraw() + self.root.update_idletasks() # Size gets recalculated here if dpy: xroot = Window() parent = Window() children = Window() nchildren = c_uint() - XQueryTree(dpy, root.winfo_id(), byref(xroot), byref(parent), byref(children), byref(nchildren)) + XQueryTree(dpy, self.root.winfo_id(), byref(xroot), byref(parent), byref(children), byref(nchildren)) if theme == self.THEME_DEFAULT: wm_hints = motif_wm_hints_normal - else: # Dark *or* Transparent wm_hints = motif_wm_hints_dark @@ -316,18 +294,14 @@ def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 XFlush(dpy) else: - if theme == self.THEME_DEFAULT: - root.overrideredirect(False) - - else: # Dark *or* Transparent - root.overrideredirect(True) + self.root.overrideredirect(theme != self.THEME_DEFAULT) - root.deiconify() - root.wait_visibility() # need main window to be displayed before returning + self.root.deiconify() + self.root.wait_visibility() # need main window to be displayed before returning if not self.minwidth: - self.minwidth = root.winfo_width() # Minimum width = width on first creation - root.minsize(self.minwidth, -1) + self.minwidth = self.root.winfo_width() # Minimum width = width on first creation + self.root.minsize(self.minwidth, -1) # singleton diff --git a/themes/dark.tcl b/themes/dark/dark.tcl similarity index 88% rename from themes/dark.tcl rename to themes/dark/dark.tcl index 26b882925..a4e6d78ce 100644 --- a/themes/dark.tcl +++ b/themes/dark/dark.tcl @@ -30,6 +30,14 @@ namespace eval ttk::theme::dark { -borderwidth 1 \ -relief flat + tk_setPalette background [ttk::style lookup . -background] \ + foreground [ttk::style lookup . -foreground] \ + highlightColor [ttk::style lookup . -focuscolor] \ + selectBackground [ttk::style lookup . -selectbackground] \ + selectForeground [ttk::style lookup . -selectforeground] \ + activeBackground [ttk::style lookup . -selectbackground] \ + activeForeground [ttk::style lookup . -selectforeground] + ttk::style map . -foreground [list disabled $colors(-disabledfg)] option add *font [ttk::style lookup . -font] diff --git a/themes/dark/pkgIndex.tcl b/themes/dark/pkgIndex.tcl new file mode 100644 index 000000000..50341687c --- /dev/null +++ b/themes/dark/pkgIndex.tcl @@ -0,0 +1,2 @@ +package ifneeded ttk::theme::dark 1.0 \ + [list source [file join [file dirname [info script]] dark.tcl]] diff --git a/themes/default.tcl b/themes/light/light.tcl similarity index 81% rename from themes/default.tcl rename to themes/light/light.tcl index e26a341a0..16a1c8c31 100644 --- a/themes/default.tcl +++ b/themes/light/light.tcl @@ -1,9 +1,9 @@ package require Tk 8.6 -namespace eval ttk::theme::edmc { +namespace eval ttk::theme::light { variable version 1.0 - package provide ttk::theme::edmc $version + package provide ttk::theme::light $version variable colors array set colors { -fg "black" @@ -14,7 +14,7 @@ namespace eval ttk::theme::edmc { -highlight "blue" } - ttk::style theme create edmc -parent clam -settings { + ttk::style theme create light -parent clam -settings { ttk::style configure . \ -background $colors(-bg) \ -foreground $colors(-fg) \ @@ -29,6 +29,14 @@ namespace eval ttk::theme::edmc { -borderwidth 1 \ -relief flat + tk_setPalette background [ttk::style lookup . -background] \ + foreground [ttk::style lookup . -foreground] \ + highlightColor [ttk::style lookup . -focuscolor] \ + selectBackground [ttk::style lookup . -selectbackground] \ + selectForeground [ttk::style lookup . -selectforeground] \ + activeBackground [ttk::style lookup . -selectbackground] \ + activeForeground [ttk::style lookup . -selectforeground] + ttk::style map . -foreground [list disabled $colors(-disabledfg)] option add *font [ttk::style lookup . -font] diff --git a/themes/light/pkgIndex.tcl b/themes/light/pkgIndex.tcl new file mode 100644 index 000000000..b22a47876 --- /dev/null +++ b/themes/light/pkgIndex.tcl @@ -0,0 +1,2 @@ +package ifneeded ttk::theme::light 1.0 \ + [list source [file join [file dirname [info script]] light.tcl]] diff --git a/themes/transparent/pkgIndex.tcl b/themes/transparent/pkgIndex.tcl new file mode 100644 index 000000000..3387f8b24 --- /dev/null +++ b/themes/transparent/pkgIndex.tcl @@ -0,0 +1,2 @@ +package ifneeded ttk::theme::transparent 1.0 \ + [list source [file join [file dirname [info script]] transparent.tcl]] diff --git a/themes/transparent/transparent.tcl b/themes/transparent/transparent.tcl new file mode 100644 index 000000000..e4b6225e9 --- /dev/null +++ b/themes/transparent/transparent.tcl @@ -0,0 +1,114 @@ +package require Tk 8.6 + +namespace eval ttk::theme::transparent { + + variable version 1.0 + package provide ttk::theme::transparent $version + variable colors + array set colors { + -fg "#ff8000" + -bg "grey4" + -disabledfg "#aa5500" + -selectfg "grey4" + -selectbg "#ff8000" + -highlight "white" + } + set flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] + + ttk::style theme create transparent -parent clam -settings { + ttk::style configure . \ + -background $colors(-bg) \ + -foreground $colors(-fg) \ + -troughcolor $colors(-bg) \ + -focuscolor $colors(-selectbg) \ + -selectbackground $colors(-selectbg) \ + -selectforeground $colors(-selectfg) \ + -insertwidth 1 \ + -insertcolor $colors(-fg) \ + -fieldbackground $colors(-bg) \ + -font {"Euro Caps" 10} \ + -borderwidth 1 \ + -relief flat + + tk_setPalette background [ttk::style lookup . -background] \ + foreground [ttk::style lookup . -foreground] \ + highlightColor [ttk::style lookup . -focuscolor] \ + selectBackground [ttk::style lookup . -selectbackground] \ + selectForeground [ttk::style lookup . -selectforeground] \ + activeBackground [ttk::style lookup . -selectbackground] \ + activeForeground [ttk::style lookup . -selectforeground] + + ttk::style map . -foreground [list disabled $colors(-disabledfg)] + + option add *font [ttk::style lookup . -font] + option add *Menu.selectcolor $colors(-fg) + + ttk::style configure TLabel -padding 1 + + ttk::style configure Link.TLabel -foreground $colors(-highlight) + + ttk::style configure TLabelframe {*}$flatborder + + ttk::style configure TSeparator -background $colors(-fg) + + ttk::style configure TEntry -padding 2 {*}$flatborder + + ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center {*}$flatborder + + ttk::style map TButton -background [list \ + {pressed} $colors(-selectbg) \ + ] + + ttk::style map TButton -foreground [list \ + {pressed} $colors(-selectfg) \ + ] + + ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder + + ttk::style configure TOptionMenu -padding {8 4 4 4} {*}$flatborder + + ttk::style configure TCheckbutton -padding 4 -indicatormargin 4 + + ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center + + ttk::style configure TRadiobutton -padding 4 -indicatormargin 4 + + ttk::style configure TSpinbox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) -arrowsize 10 + + ttk::style configure TCombobox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) + + ttk::style map TCombobox -selectbackground [list \ + {!focus} $colors(-selectbg) \ + {readonly hover} $colors(-selectbg) \ + {readonly focus} $colors(-selectbg) \ + ] + + ttk::style map TCombobox -selectforeground [list \ + {!focus} $colors(-selectfg) \ + {readonly hover} $colors(-selectfg) \ + {readonly focus} $colors(-selectfg) \ + ] + + ttk::style configure TNotebook -padding 2 {*}$flatborder + ttk::style configure TNotebook.Tab -padding 2 {*}$flatborder + ttk::style map TNotebook.Tab \ + -background [list selected $colors(-selectbg)] \ + -foreground [list selected $colors(-selectfg)] \ + -lightcolor [list selected $colors(-selectbg)] + + ttk::style configure Treeview {*}$flatborder + ttk::style configure Treeview.Item -padding {2 0 0 0} + + ttk::style map Treeview \ + -background [list selected $colors(-selectbg)] \ + -foreground [list selected $colors(-selectfg)] + + ttk::style configure TScrollbar {*}$flatborder -background $colors(-selectbg) + + ttk::style configure TScale {*}$flatborder -background $colors(-selectbg) + + ttk::style configure TProgressbar {*}$flatborder -background $colors(-selectbg) + } +} From 4f54b49705a057230d04d991fffd3edae7fde3c0 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 4 Jun 2024 18:40:22 +0100 Subject: [PATCH 28/59] HyperlinkLabel is a Button. Obviously. --- themes/dark/dark.tcl | 7 +++++-- ttkHyperlinkLabel.py | 27 +++++++-------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/themes/dark/dark.tcl b/themes/dark/dark.tcl index a4e6d78ce..2f9d7748e 100644 --- a/themes/dark/dark.tcl +++ b/themes/dark/dark.tcl @@ -45,8 +45,6 @@ namespace eval ttk::theme::dark { ttk::style configure TLabel -padding 1 - ttk::style configure Link.TLabel -foreground $colors(-highlight) - ttk::style configure TLabelframe {*}$flatborder ttk::style configure TSeparator -background $colors(-fg) @@ -63,6 +61,11 @@ namespace eval ttk::theme::dark { {pressed} $colors(-selectfg) \ ] + ttk::style configure Link.TButton -foreground $colors(-highlight) -padding 2 -anchor left -relief flat -background blue + ttk::style map Link.TButton -font [list active {[ttk::style lookup . -font] underline}] + ttk::style map Link.TButton -foreground [list disabled $colors(-disabledfg)] + ttk::style map Link.TButton -cursor [list disabled arrow] + ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 63fc66776..024ac799b 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -49,7 +49,7 @@ """ -class HyperlinkLabel(ttk.Label): +class HyperlinkLabel(ttk.Button): """Clickable label for HTTP links.""" def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: @@ -63,24 +63,21 @@ def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: self.font_n = None self.url = kw.pop('url', None) self.popup_copy = kw.pop('popup_copy', False) - self.underline = kw.pop('underline', None) # override ttk.Label's underline + self.underline = kw.pop('underline', None) self.legacy = not isinstance(master, ttk.Widget) if self.legacy: self.foreground = kw.get('foreground') self.disabledforeground = kw.pop('disabledforeground', None) - kw.setdefault('style', 'Link.TLabel') - ttk.Label.__init__(self, master, **kw) + kw.setdefault('style', 'Link.TButton') + super().__init__(master, **kw) self.bind('', self._click) self.bind('', self._contextmenu) - self.bind('', self._enter) - self.bind('', self._leave) self.bind('<>', self._theme) # set up initial appearance - self.configure(state=kw.get('state', tk.NORMAL), - font=kw.get('font', ttk.Style().lookup(kw['style'], 'font'))) + self.configure(state=kw.get('state', tk.NORMAL)) # Add Menu Options self.plug_options = kw.pop('plug_options', None) @@ -168,27 +165,17 @@ def __setitem__(self, key: str, value: Any) -> None: """ self.configure(**{key: value}) - def _enter(self, event: tk.Event) -> None: - if self.url and self.underline is not False and str(self['state']) != tk.DISABLED: - super().configure(font=self.font_u) - - def _leave(self, event: tk.Event) -> None: - if not self.underline: - super().configure(font=self.font_n) # type: ignore - def _theme(self, event: tk.Event | None = None) -> None: if self.legacy: if str(self['state']) == tk.DISABLED: - super().configure(foreground=self.disabledforeground or - ttk.Style().lookup('TLabel', 'foreground', ['disabled'])) + ... # TODO self.disabledforeground else: - super().configure(foreground=self.foreground or ttk.Style().lookup('Link.TLabel', 'foreground')) + ... # TODO self.foreground def _click(self, event: tk.Event) -> None: if self.url and self['text'] and str(self['state']) != tk.DISABLED: url = self.url(self['text']) if callable(self.url) else self.url if url: - self._leave(event) # Remove underline before we change window to browser webbrowser.open(url) def _contextmenu(self, event: tk.Event) -> None: From de89528bda8752c29e35927f9187a75e85fdba4f Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Wed, 5 Jun 2024 00:46:09 +0100 Subject: [PATCH 29/59] Fixed underline font --- themes/dark/dark.tcl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/themes/dark/dark.tcl b/themes/dark/dark.tcl index 2f9d7748e..b94ca071c 100644 --- a/themes/dark/dark.tcl +++ b/themes/dark/dark.tcl @@ -13,7 +13,8 @@ namespace eval ttk::theme::dark { -selectbg "#ff8000" -highlight "white" } - set flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] + variable flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] + variable font_u [font create {*}[font configure TkDefaultFont] -underline 1] ttk::style theme create dark -parent clam -settings { ttk::style configure . \ @@ -61,10 +62,10 @@ namespace eval ttk::theme::dark { {pressed} $colors(-selectfg) \ ] - ttk::style configure Link.TButton -foreground $colors(-highlight) -padding 2 -anchor left -relief flat -background blue - ttk::style map Link.TButton -font [list active {[ttk::style lookup . -font] underline}] - ttk::style map Link.TButton -foreground [list disabled $colors(-disabledfg)] - ttk::style map Link.TButton -cursor [list disabled arrow] + ttk::style configure Link.TLabel -foreground $colors(-highlight) -relief flat + ttk::style map Link.TLabel -font [list active $font_u] + ttk::style map Link.TLabel -foreground [list disabled $colors(-disabledfg)] + ttk::style map Link.TLabel -cursor [list disabled arrow] ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center From 2fd9f7fdd29f5f0bc728a92562413ad2229becb7 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Fri, 7 Jun 2024 17:44:14 +0100 Subject: [PATCH 30/59] A possible solution for HyperlinkLabel on the way? --- themes/dark/dark.tcl | 23 ++++++---------- ttkHyperlinkLabel.py | 64 +++++++++++++------------------------------- 2 files changed, 27 insertions(+), 60 deletions(-) diff --git a/themes/dark/dark.tcl b/themes/dark/dark.tcl index b94ca071c..39428007e 100644 --- a/themes/dark/dark.tcl +++ b/themes/dark/dark.tcl @@ -53,19 +53,15 @@ namespace eval ttk::theme::dark { ttk::style configure TEntry -padding 2 {*}$flatborder ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center {*}$flatborder - - ttk::style map TButton -background [list \ - {pressed} $colors(-selectbg) \ - ] - - ttk::style map TButton -foreground [list \ - {pressed} $colors(-selectfg) \ - ] + ttk::style map TButton \ + -background [list pressed $colors(-selectbg)] \ + -foreground [list pressed $colors(-selectfg)] ttk::style configure Link.TLabel -foreground $colors(-highlight) -relief flat - ttk::style map Link.TLabel -font [list active $font_u] - ttk::style map Link.TLabel -foreground [list disabled $colors(-disabledfg)] - ttk::style map Link.TLabel -cursor [list disabled arrow] + ttk::style map Link.TLabel \ + -font [list active $font_u] \ + -foreground [list disabled $colors(-disabledfg)] \ + -cursor [list disabled arrow] ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center @@ -82,13 +78,11 @@ namespace eval ttk::theme::dark { ttk::style configure TSpinbox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) -arrowsize 10 ttk::style configure TCombobox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) - ttk::style map TCombobox -selectbackground [list \ {!focus} $colors(-selectbg) \ {readonly hover} $colors(-selectbg) \ {readonly focus} $colors(-selectbg) \ ] - ttk::style map TCombobox -selectforeground [list \ {!focus} $colors(-selectfg) \ {readonly hover} $colors(-selectfg) \ @@ -103,11 +97,10 @@ namespace eval ttk::theme::dark { -lightcolor [list selected $colors(-selectbg)] ttk::style configure Treeview {*}$flatborder - ttk::style configure Treeview.Item -padding {2 0 0 0} - ttk::style map Treeview \ -background [list selected $colors(-selectbg)] \ -foreground [list selected $colors(-selectfg)] + ttk::style configure Treeview.Item -padding {2 0 0 0} ttk::style configure TScrollbar {*}$flatborder -background $colors(-selectbg) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 024ac799b..02f4b0683 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -21,10 +21,10 @@ from __future__ import annotations import html from functools import partial -import sys +import random +import string import tkinter as tk import webbrowser -from tkinter import font as tk_font from tkinter import ttk from typing import Any, no_type_check import plug @@ -47,10 +47,16 @@ """ +LABEL_TO_STYLE = ['anchor', 'background', 'font', 'foreground', 'justify', 'relief'] + + +def _generate_random_style(): + return f'{"".join(random.choices(string.ascii_letters + string.digits, k=8))}.Link.TLabel' class HyperlinkLabel(ttk.Button): """Clickable label for HTTP links.""" + _legacy_style: str def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: """ @@ -59,25 +65,22 @@ def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: :param master: The master widget. :param kw: Additional keyword arguments. """ - self.font_u: tk_font.Font - self.font_n = None self.url = kw.pop('url', None) self.popup_copy = kw.pop('popup_copy', False) self.underline = kw.pop('underline', None) - self.legacy = not isinstance(master, ttk.Widget) - if self.legacy: - self.foreground = kw.get('foreground') - self.disabledforeground = kw.pop('disabledforeground', None) - kw.setdefault('style', 'Link.TButton') - super().__init__(master, **kw) + kw.setdefault('command', self._click) + kw.setdefault('style', 'Link.TLabel') - self.bind('', self._click) - self.bind('', self._contextmenu) + self._legacy_options = {opt: kw[opt] for opt in LABEL_TO_STYLE if opt in kw} + if len(self._legacy_options) > 0: + self._legacy_style = _generate_random_style() + kw['style'] = self._legacy_style + ttk.Style().configure(self._legacy_style, **self._legacy_options) + # TODO underline, disabledforeground, cursor - self.bind('<>', self._theme) + super().__init__(master, **kw) - # set up initial appearance - self.configure(state=kw.get('state', tk.NORMAL)) + self.bind('', self._contextmenu) # Add Menu Options self.plug_options = kw.pop('plug_options', None) @@ -130,30 +133,8 @@ def configure( # noqa: CCR001 for thing in ('url', 'popup_copy', 'underline'): if thing in kw: setattr(self, thing, kw.pop(thing)) - if self.legacy: - for thing in ('foreground', 'disabledforeground'): - if thing in kw: - setattr(self, thing, kw[thing]) - - # Emulate disabledforeground option for ttk.Label - if 'state' in kw and 'foreground' not in kw: - self._theme(None) - - if 'font' in kw: - self.font_n = kw['font'] - self.font_u = tk_font.Font(font=self.font_n) - self.font_u.configure(underline=True) - kw['font'] = self.font_u if self.underline is True else self.font_n - - if 'cursor' not in kw: - state = kw.get('state', str(self['state'])) - if state == tk.DISABLED: - kw['cursor'] = 'arrow' # System default - elif self.url and (kw['text'] if 'text' in kw else self['text']): - kw['cursor'] = 'hand2' - else: - kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle') + # TODO _legacy_options, underline, disabledforeground, cursor return super().configure(cnf, **kw) def __setitem__(self, key: str, value: Any) -> None: @@ -165,13 +146,6 @@ def __setitem__(self, key: str, value: Any) -> None: """ self.configure(**{key: value}) - def _theme(self, event: tk.Event | None = None) -> None: - if self.legacy: - if str(self['state']) == tk.DISABLED: - ... # TODO self.disabledforeground - else: - ... # TODO self.foreground - def _click(self, event: tk.Event) -> None: if self.url and self['text'] and str(self['state']) != tk.DISABLED: url = self.url(self['text']) if callable(self.url) else self.url From 168bc3e2758cb412a5ac4065fa6c58d37f4a4832 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sat, 8 Jun 2024 22:37:41 +0100 Subject: [PATCH 31/59] Dropped most of myNotebook from core plugins (except for EntryMenu) --- docs/examples/click_counter/load.py | 25 ++++++++++---------- plug.py | 9 ++++---- plugins/coriolis.py | 28 +++++++++++----------- plugins/eddn.py | 18 +++++++-------- plugins/edsm.py | 36 ++++++++++++++--------------- plugins/inara.py | 25 ++++++++++---------- stats.py | 12 +++++----- 7 files changed, 74 insertions(+), 79 deletions(-) diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py index 3620e1594..1821a5c02 100644 --- a/docs/examples/click_counter/load.py +++ b/docs/examples/click_counter/load.py @@ -7,9 +7,10 @@ import logging import tkinter as tk +from tkinter import ttk -import myNotebook as nb # noqa: N813 from config import appname, config +from myNotebook import EntryMenu # This **MUST** match the name of the folder the plugin is in. PLUGIN_NAME = "click_counter" @@ -48,7 +49,7 @@ def on_unload(self) -> None: """ self.on_preferences_closed("", False) # Save our prefs - def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None: + def setup_preferences(self, parent: ttk.Notebook, cmdr: str, is_beta: bool) -> ttk.Frame | None: """ setup_preferences is called by plugin_prefs below. @@ -60,11 +61,11 @@ def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb :return: The frame to add to the settings window """ current_row = 0 - frame = nb.Frame(parent) + frame = ttk.Frame(parent) # setup our config in a "Click Count: number" - nb.Label(frame, text='Click Count').grid(row=current_row) - nb.EntryMenu(frame, textvariable=self.click_count).grid(row=current_row, column=1) + ttk.Label(frame, text='Click Count').grid(row=current_row) + EntryMenu(frame, textvariable=self.click_count).grid(row=current_row, column=1) current_row += 1 # Always increment our row counter, makes for far easier tkinter design. return frame @@ -81,7 +82,7 @@ def on_preferences_closed(self, cmdr: str, is_beta: bool) -> None: # `config.get_int()` will work for re-loading the value. config.set('click_counter_count', int(self.click_count.get())) - def setup_main_ui(self, parent: tk.Frame) -> tk.Frame: + def setup_main_ui(self, parent: ttk.Frame) -> ttk.Frame: """ Create our entry on the main EDMC UI. @@ -91,16 +92,16 @@ def setup_main_ui(self, parent: tk.Frame) -> tk.Frame: :return: Our frame """ current_row = 0 - frame = tk.Frame(parent) - button = tk.Button( + frame = ttk.Frame(parent) + button = ttk.Button( frame, text="Count me", command=lambda: self.click_count.set(str(int(self.click_count.get()) + 1)) ) button.grid(row=current_row) current_row += 1 - tk.Label(frame, text="Count:").grid(row=current_row, sticky=tk.W) - tk.Label(frame, textvariable=self.click_count).grid(row=current_row, column=1) + ttk.Label(frame, text="Count:").grid(row=current_row, sticky=tk.W) + ttk.Label(frame, textvariable=self.click_count).grid(row=current_row, column=1) return frame @@ -127,7 +128,7 @@ def plugin_stop() -> None: return cc.on_unload() -def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None: +def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> ttk.Frame | None: """ Handle preferences tab for the plugin. @@ -145,7 +146,7 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None: return cc.on_preferences_closed(cmdr, is_beta) -def plugin_app(parent: tk.Frame) -> tk.Frame | None: +def plugin_app(parent: ttk.Frame) -> ttk.Frame | None: """ Set up the UI of the plugin. diff --git a/plug.py b/plug.py index 6a13058ab..8a5f28268 100644 --- a/plug.py +++ b/plug.py @@ -8,7 +8,7 @@ from __future__ import annotations import copy -import importlib +import importlib.util import logging import operator import os @@ -18,7 +18,6 @@ from typing import Any, Mapping, MutableMapping import companion -import myNotebook as nb # noqa: N813 from config import config from EDMCLogging import get_main_logger @@ -130,7 +129,7 @@ def get_app(self, parent: tk.Frame) -> tk.Frame | None: return None - def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame | None: + def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> ttk.Frame | None: """ If the plugin provides a prefs frame, create and return it. @@ -138,13 +137,13 @@ def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb :param cmdr: current Cmdr name (or None). Relevant if you want to have different settings for different user accounts. :param is_beta: whether the player is in a Beta universe. - :returns: a myNotebook Frame + :returns: a ttk Frame """ plugin_prefs = self._get_func('plugin_prefs') if plugin_prefs: try: frame = plugin_prefs(parent, cmdr, is_beta) - if isinstance(frame, nb.Frame): + if isinstance(frame, ttk.Frame): return frame raise AssertionError except Exception: diff --git a/plugins/coriolis.py b/plugins/coriolis.py index a9eabcedb..0f2000de3 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -27,8 +27,8 @@ import json import tkinter as tk from tkinter import ttk -import myNotebook as nb # noqa: N813 # its not my fault. from EDMCLogging import get_main_logger +from myNotebook import EntryMenu from plug import show_error from config import config from l10n import translations as tr @@ -80,7 +80,7 @@ def plugin_start3(path: str) -> str: return 'Coriolis' -def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame: +def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> ttk.Frame: """Set up plugin preferences.""" PADX = 10 # noqa: N806 PADY = 1 # noqa: N806 @@ -91,45 +91,45 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr coriolis_config.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal coriolis_config.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta - conf_frame = nb.Frame(parent) + conf_frame = ttk.Frame(parent) conf_frame.columnconfigure(index=1, weight=1) cur_row = 0 # LANG: Settings>Coriolis: Help/hint for changing coriolis URLs - nb.Label(conf_frame, text=tr.tl( + ttk.Label(conf_frame, text=tr.tl( "Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" )).grid(sticky=tk.EW, row=cur_row, column=0, padx=PADX, pady=PADY, columnspan=3) cur_row += 1 # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL - nb.Label(conf_frame, text=tr.tl('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) - nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid( + ttk.Label(conf_frame, text=tr.tl('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) + EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=tr.tl("Reset"), - command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( + ttk.Button(conf_frame, text=tr.tl("Reset"), + command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL - nb.Label(conf_frame, text=tr.tl('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) - nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid( + ttk.Label(conf_frame, text=tr.tl('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) + EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=tr.tl('Reset'), - command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( + ttk.Button(conf_frame, text=tr.tl('Reset'), + command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 # TODO: This needs a help/hint text to be sure users know what it's for. # LANG: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL - nb.Label(conf_frame, text=tr.tl('Override Beta/Normal Selection')).grid( + ttk.Label(conf_frame, text=tr.tl('Override Beta/Normal Selection')).grid( sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY ) - nb.OptionMenu( + ttk.OptionMenu( conf_frame, coriolis_config.override_textvar, coriolis_config.override_textvar.get(), diff --git a/plugins/eddn.py b/plugins/eddn.py index f8f005339..adc02f4c0 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -37,7 +37,6 @@ import companion import edmc_data import killswitch -import myNotebook as nb # noqa: N813 import plug from companion import CAPIData, category_map from config import applongname, appname, appversion_nobuild, config, debug_senders, user_agent @@ -102,13 +101,13 @@ def __init__(self): # tkinter UI bits. self.eddn_station: tk.IntVar - self.eddn_station_button: nb.Checkbutton + self.eddn_station_button: ttk.Checkbutton self.eddn_system: tk.IntVar - self.eddn_system_button: nb.Checkbutton + self.eddn_system_button: ttk.Checkbutton self.eddn_delay: tk.IntVar - self.eddn_delay_button: nb.Checkbutton + self.eddn_delay_button: ttk.Checkbutton # Tracking UI self.ui: ttk.Frame @@ -2145,7 +2144,7 @@ def tracking_ui_update() -> None: this.ui.update_idletasks() -def plugin_prefs(parent, cmdr: str, is_beta: bool) -> nb.Frame: +def plugin_prefs(parent, cmdr: str, is_beta: bool) -> ttk.Frame: """ Set up Preferences pane for this plugin. @@ -2164,20 +2163,19 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> nb.Frame: else: output = config.get_int('output') - eddnframe = nb.Frame(parent) + eddnframe = ttk.Frame(parent) cur_row = 0 HyperlinkLabel( eddnframe, text='Elite Dangerous Data Network', - background=nb.Label().cget('background'), url='https://github.com/EDCD/EDDN#eddn---elite-dangerous-data-network', underline=True ).grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) # Don't translate cur_row += 1 this.eddn_station = tk.IntVar(value=(output & config.OUT_EDDN_SEND_STATION_DATA) and 1) - this.eddn_station_button = nb.Checkbutton( + this.eddn_station_button = ttk.Checkbutton( eddnframe, # LANG: Enable EDDN support for station data checkbox label text=tr.tl('Send station data to the Elite Dangerous Data Network'), @@ -2189,7 +2187,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> nb.Frame: this.eddn_system = tk.IntVar(value=(output & config.OUT_EDDN_SEND_NON_STATION) and 1) # Output setting new in E:D 2.2 - this.eddn_system_button = nb.Checkbutton( + this.eddn_system_button = ttk.Checkbutton( eddnframe, # LANG: Enable EDDN support for system and other scan data checkbox label text=tr.tl('Send system and scan data to the Elite Dangerous Data Network'), @@ -2201,7 +2199,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> nb.Frame: this.eddn_delay = tk.IntVar(value=(output & config.OUT_EDDN_DELAY) and 1) # Output setting under 'Send system and scan data to the Elite Dangerous Data Network' new in E:D 2.2 - this.eddn_delay_button = nb.Checkbutton( + this.eddn_delay_button = ttk.Checkbutton( eddnframe, # LANG: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this text=tr.tl('Delay sending until docked'), diff --git a/plugins/edsm.py b/plugins/edsm.py index 1d9f8cb85..fbb056f8b 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -32,12 +32,12 @@ import requests import killswitch import monitor -import myNotebook as nb # noqa: N813 import plug from companion import CAPIData from config import applongname, appname, appversion, config, debug_senders, user_agent from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT from EDMCLogging import get_main_logger +from myNotebook import EntryMenu from ttkHyperlinkLabel import HyperlinkLabel from l10n import translations as tr @@ -107,14 +107,14 @@ def __init__(self): self.label: tk.Widget | None = None - self.cmdr_label: nb.Label | None = None - self.cmdr_text: nb.Label | None = None + self.cmdr_label: ttk.Label | None = None + self.cmdr_text: ttk.Label | None = None - self.user_label: nb.Label | None = None - self.user: nb.EntryMenu | None = None + self.user_label: ttk.Label | None = None + self.user: EntryMenu | None = None - self.apikey_label: nb.Label | None = None - self.apikey: nb.EntryMenu | None = None + self.apikey_label: ttk.Label | None = None + self.apikey: EntryMenu | None = None this = This() @@ -277,7 +277,7 @@ def toggle_password_visibility(): this.apikey.config(show="*") # type: ignore -def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame: +def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> ttk.Frame: """ Plugin preferences setup hook. @@ -295,21 +295,20 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr BOXY = 2 # noqa: N806 SEPY = 10 # noqa: N806 - frame = nb.Frame(parent) + frame = ttk.Frame(parent) frame.columnconfigure(1, weight=1) cur_row = 0 HyperlinkLabel( frame, text='Elite Dangerous Star Map', - background=nb.Label().cget('background'), url='https://www.edsm.net/', underline=True ).grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W) cur_row += 1 this.log = tk.IntVar(value=config.get_int('edsm_out') and 1) - this.log_button = nb.Checkbutton( + this.log_button = ttk.Checkbutton( frame, # LANG: Settings>EDSM - Label on checkbox for 'send data' text=tr.tl('Send flight log and CMDR status to EDSM'), @@ -328,30 +327,29 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr this.label = HyperlinkLabel( frame, text=tr.tl('Elite Dangerous Star Map credentials'), # LANG: Elite Dangerous Star Map credentials - background=nb.Label().cget('background'), url='https://www.edsm.net/settings/api', underline=True ) if this.label: this.label.grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W) cur_row += 1 - this.cmdr_label = nb.Label(frame, text=tr.tl('Cmdr')) # LANG: Game Commander name label in EDSM settings + this.cmdr_label = ttk.Label(frame, text=tr.tl('Cmdr')) # LANG: Game Commander name label in EDSM settings this.cmdr_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.cmdr_text = nb.Label(frame) + this.cmdr_text = ttk.Label(frame) this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.W) cur_row += 1 # LANG: EDSM Commander name label in EDSM settings - this.user_label = nb.Label(frame, text=tr.tl('Commander Name')) + this.user_label = ttk.Label(frame, text=tr.tl('Commander Name')) this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.user = nb.EntryMenu(frame) + this.user = EntryMenu(frame) this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 # LANG: EDSM API key label - this.apikey_label = nb.Label(frame, text=tr.tl('API Key')) + this.apikey_label = ttk.Label(frame, text=tr.tl('API Key')) this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.apikey = nb.EntryMenu(frame, show="*", width=50) + this.apikey = EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 @@ -359,7 +357,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr show_password_var.set(False) # Password is initially masked - show_password_checkbox = nb.Checkbutton( + show_password_checkbox = ttk.Checkbutton( frame, text=tr.tl('Show API Key'), # LANG: Text EDSM Show API Key variable=show_password_var, diff --git a/plugins/inara.py b/plugins/inara.py index f2c9830e7..603c05448 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -34,13 +34,13 @@ import requests import edmc_data import killswitch -import myNotebook as nb # noqa: N813 import plug import timeout_session from companion import CAPIData from config import applongname, appname, appversion, config, debug_senders from EDMCLogging import get_main_logger from monitor import monitor +from myNotebook import EntryMenu from ttkHyperlinkLabel import HyperlinkLabel from l10n import translations as tr @@ -120,10 +120,10 @@ def __init__(self): # Prefs UI self.log: 'tk.IntVar' - self.log_button: nb.Checkbutton + self.log_button: ttk.Checkbutton self.label: HyperlinkLabel - self.apikey: nb.EntryMenu - self.apikey_label: tk.Label + self.apikey: EntryMenu + self.apikey_label: ttk.Label self.events: dict[Credentials, Deque[Event]] = defaultdict(deque) self.event_lock: Lock = threading.Lock() # protects events, for use when rewriting events @@ -241,7 +241,7 @@ def toggle_password_visibility(): this.apikey.config(show="*") -def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: +def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> ttk.Frame: """Plugin Preferences UI hook.""" PADX = 10 # noqa: N806 BUTTONX = 12 # noqa: N806 # indent Checkbuttons and Radiobuttons @@ -250,16 +250,16 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: SEPY = 10 # noqa: N806 # seperator line spacing cur_row = 0 - frame = nb.Frame(parent) + frame = ttk.Frame(parent) frame.columnconfigure(1, weight=1) HyperlinkLabel( - frame, text='Inara', background=nb.Label().cget('background'), url='https://inara.cz/', underline=True + frame, text='Inara', url='https://inara.cz/', underline=True ).grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W) # Don't translate cur_row += 1 this.log = tk.IntVar(value=config.get_int('inara_out') and 1) - this.log_button = nb.Checkbutton( + this.log_button = ttk.Checkbutton( frame, text=tr.tl('Send flight log and Cmdr status to Inara'), # LANG: Checkbox to enable INARA API Usage variable=this.log, @@ -277,8 +277,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: # Section heading in settings this.label = HyperlinkLabel( frame, - text=tr.tl('Inara credentials'), # LANG: Text for INARA API keys link ( goes to https://inara.cz/settings-api ) - background=nb.Label().cget('background'), + text=tr.tl('Inara credentials'), # LANG: Text for INARA API keys link (goes to https://inara.cz/settings-api) url='https://inara.cz/settings-api', underline=True ) @@ -287,16 +286,16 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: cur_row += 1 # LANG: Inara API key label - this.apikey_label = nb.Label(frame, text=tr.tl('API Key')) # Inara setting + this.apikey_label = ttk.Label(frame, text=tr.tl('API Key')) # Inara setting this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.apikey = nb.EntryMenu(frame, show="*", width=50) + this.apikey = EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 prefs_cmdr_changed(cmdr, is_beta) show_password_var.set(False) # Password is initially masked - show_password_checkbox = nb.Checkbutton( + show_password_checkbox = ttk.Checkbutton( frame, text=tr.tl('Show API Key'), # LANG: Text Inara Show API key variable=show_password_var, diff --git a/stats.py b/stats.py index 921b151e1..e8adc9ad7 100644 --- a/stats.py +++ b/stats.py @@ -15,7 +15,6 @@ from typing import Any, AnyStr, Callable, NamedTuple, Sequence, cast import companion import EDMCLogging -import myNotebook as nb # noqa: N813 from edmc_data import ship_name_map from hotkey import hotkeymgr from l10n import Locale, translations as tr @@ -382,7 +381,8 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: frame = ttk.Frame(self) frame.grid(sticky=tk.NSEW) - notebook = nb.Notebook(frame) + notebook = ttk.Notebook(frame) + notebook.grid(padx=10, pady=10, sticky=tk.NSEW) page = self.addpage(notebook) for thing in stats[CR_LINES_START:CR_LINES_END]: @@ -446,7 +446,7 @@ def addpage( if header is None: header = [] - page = nb.Frame(parent) + page = ttk.Frame(parent) page.grid(pady=10, sticky=tk.NSEW) page.columnconfigure(0, weight=1) if header: @@ -482,7 +482,7 @@ def addpagerow( row = -1 # To silence unbound warnings for i, col_content in enumerate(content): # label = HyperlinkLabel(parent, text=col_content, popup_copy=True) - label = nb.Label(parent, text=col_content) + label = ttk.Label(parent, text=col_content) if with_copy: label.bind('', self.copy_callback(label, col_content)) @@ -502,7 +502,7 @@ def credits(self, value: int) -> str: return Locale.string_from_number(value, 0) + ' Cr' # type: ignore @staticmethod - def copy_callback(label: tk.Label, text_to_copy: str) -> Callable[..., None]: + def copy_callback(label: ttk.Label, text_to_copy: str) -> Callable[..., None]: """Copy data in Label to clipboard.""" def do_copy(event: tk.Event) -> None: label.clipboard_clear() @@ -510,6 +510,6 @@ def do_copy(event: tk.Event) -> None: old_bg = label['bg'] label['bg'] = 'gray49' - label.after(100, (lambda: label.configure(bg=old_bg))) + label.after(100, (lambda: label.configure(background=old_bg))) return do_copy From 853accd59e22e2b33df845fe85eef08858c088db Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sat, 8 Jun 2024 23:16:53 +0100 Subject: [PATCH 32/59] HyperlinkLabel seems to work now --- prefs.py | 1 - themes/dark/dark.tcl | 31 +++++++------- themes/light/light.tcl | 17 ++++---- themes/transparent/transparent.tcl | 42 ++++++++---------- ttkHyperlinkLabel.py | 69 +++++++++++++++++++++--------- 5 files changed, 91 insertions(+), 69 deletions(-) diff --git a/prefs.py b/prefs.py index 1fdf9c5a4..a86aa4819 100644 --- a/prefs.py +++ b/prefs.py @@ -978,7 +978,6 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 HyperlinkLabel( # LANG: Plugins - Label on URL to documentation about migrating plugins from Python 2.7 plugins_frame, text=tr.tl('Information on migrating plugins'), - background=ttk.Label().cget('background'), url='https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#migration-from-python-27', underline=True ).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) diff --git a/themes/dark/dark.tcl b/themes/dark/dark.tcl index 39428007e..b870ec46a 100644 --- a/themes/dark/dark.tcl +++ b/themes/dark/dark.tcl @@ -46,6 +46,12 @@ namespace eval ttk::theme::dark { ttk::style configure TLabel -padding 1 + ttk::style configure Link.TLabel -foreground $colors(-highlight) -relief flat + ttk::style map Link.TLabel \ + -font [list active $font_u] \ + -foreground [list disabled $colors(-disabledfg)] \ + -cursor [list disabled arrow] + ttk::style configure TLabelframe {*}$flatborder ttk::style configure TSeparator -background $colors(-fg) @@ -57,12 +63,6 @@ namespace eval ttk::theme::dark { -background [list pressed $colors(-selectbg)] \ -foreground [list pressed $colors(-selectfg)] - ttk::style configure Link.TLabel -foreground $colors(-highlight) -relief flat - ttk::style map Link.TLabel \ - -font [list active $font_u] \ - -foreground [list disabled $colors(-disabledfg)] \ - -cursor [list disabled arrow] - ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder @@ -78,16 +78,15 @@ namespace eval ttk::theme::dark { ttk::style configure TSpinbox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) -arrowsize 10 ttk::style configure TCombobox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) - ttk::style map TCombobox -selectbackground [list \ - {!focus} $colors(-selectbg) \ - {readonly hover} $colors(-selectbg) \ - {readonly focus} $colors(-selectbg) \ - ] - ttk::style map TCombobox -selectforeground [list \ - {!focus} $colors(-selectfg) \ - {readonly hover} $colors(-selectfg) \ - {readonly focus} $colors(-selectfg) \ - ] + ttk::style map TCombobox \ + -selectbackground [list \ + {!focus} $colors(-selectbg) \ + {readonly hover} $colors(-selectbg) \ + {readonly focus} $colors(-selectbg)] \ + -selectforeground [list \ + {!focus} $colors(-selectfg) \ + {readonly hover} $colors(-selectfg) \ + {readonly focus} $colors(-selectfg)] ttk::style configure TNotebook -padding 2 {*}$flatborder ttk::style configure TNotebook.Tab -padding 2 {*}$flatborder diff --git a/themes/light/light.tcl b/themes/light/light.tcl index 16a1c8c31..e2c960c38 100644 --- a/themes/light/light.tcl +++ b/themes/light/light.tcl @@ -13,6 +13,7 @@ namespace eval ttk::theme::light { -selectbg "#9e9a91" -highlight "blue" } + variable font_u [font create {*}[font configure TkDefaultFont] -underline 1] ttk::style theme create light -parent clam -settings { ttk::style configure . \ @@ -45,20 +46,19 @@ namespace eval ttk::theme::light { ttk::style configure TLabel -padding 1 ttk::style configure Link.TLabel -foreground $colors(-highlight) + ttk::style map Link.TLabel \ + -font [list active $font_u] \ + -foreground [list disabled $colors(-disabledfg)] \ + -cursor [list disabled arrow] ttk::style configure TLabelframe -relief groove ttk::style configure TEntry -padding 2 ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center -relief groove - - ttk::style map TButton -background [list \ - {pressed} $colors(-selectbg) \ - ] - - ttk::style map TButton -foreground [list \ - {pressed} $colors(-selectfg) \ - ] + ttk::style map TButton \ + -background [list pressed $colors(-selectbg)] \ + -foreground [list pressed $colors(-selectfg)] ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center @@ -80,7 +80,6 @@ namespace eval ttk::theme::light { ttk::style configure TNotebook.Tab -padding 2 ttk::style configure Treeview.Item -padding {2 0 0 0} - ttk::style map Treeview \ -background [list selected $colors(-selectbg)] \ -foreground [list selected $colors(-selectfg)] diff --git a/themes/transparent/transparent.tcl b/themes/transparent/transparent.tcl index e4b6225e9..e7280eb80 100644 --- a/themes/transparent/transparent.tcl +++ b/themes/transparent/transparent.tcl @@ -13,7 +13,8 @@ namespace eval ttk::theme::transparent { -selectbg "#ff8000" -highlight "white" } - set flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] + variable flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] + variable font_u [font create -family "Euro Caps" -size 10 -underline 1] ttk::style theme create transparent -parent clam -settings { ttk::style configure . \ @@ -46,6 +47,10 @@ namespace eval ttk::theme::transparent { ttk::style configure TLabel -padding 1 ttk::style configure Link.TLabel -foreground $colors(-highlight) + ttk::style map Link.TLabel \ + -font [list active $font_u] \ + -foreground [list disabled $colors(-disabledfg)] \ + -cursor [list disabled arrow] ttk::style configure TLabelframe {*}$flatborder @@ -54,14 +59,9 @@ namespace eval ttk::theme::transparent { ttk::style configure TEntry -padding 2 {*}$flatborder ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center {*}$flatborder - - ttk::style map TButton -background [list \ - {pressed} $colors(-selectbg) \ - ] - - ttk::style map TButton -foreground [list \ - {pressed} $colors(-selectfg) \ - ] + ttk::style map TButton \ + -background [list pressed $colors(-selectbg)] \ + -foreground [list pressed $colors(-selectfg)] ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center @@ -78,18 +78,15 @@ namespace eval ttk::theme::transparent { ttk::style configure TSpinbox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) -arrowsize 10 ttk::style configure TCombobox -padding 2 {*}$flatborder -arrowcolor $colors(-fg) - - ttk::style map TCombobox -selectbackground [list \ - {!focus} $colors(-selectbg) \ - {readonly hover} $colors(-selectbg) \ - {readonly focus} $colors(-selectbg) \ - ] - - ttk::style map TCombobox -selectforeground [list \ - {!focus} $colors(-selectfg) \ - {readonly hover} $colors(-selectfg) \ - {readonly focus} $colors(-selectfg) \ - ] + ttk::style map TCombobox \ + -selectbackground [list \ + {!focus} $colors(-selectbg) \ + {readonly hover} $colors(-selectbg) \ + {readonly focus} $colors(-selectbg)] \ + -selectforeground [list \ + {!focus} $colors(-selectfg) \ + {readonly hover} $colors(-selectfg) \ + {readonly focus} $colors(-selectfg)] ttk::style configure TNotebook -padding 2 {*}$flatborder ttk::style configure TNotebook.Tab -padding 2 {*}$flatborder @@ -99,11 +96,10 @@ namespace eval ttk::theme::transparent { -lightcolor [list selected $colors(-selectbg)] ttk::style configure Treeview {*}$flatborder - ttk::style configure Treeview.Item -padding {2 0 0 0} - ttk::style map Treeview \ -background [list selected $colors(-selectbg)] \ -foreground [list selected $colors(-selectfg)] + ttk::style configure Treeview.Item -padding {2 0 0 0} ttk::style configure TScrollbar {*}$flatborder -background $colors(-selectbg) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 02f4b0683..d38ce9169 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -47,16 +47,49 @@ """ -LABEL_TO_STYLE = ['anchor', 'background', 'font', 'foreground', 'justify', 'relief'] - - -def _generate_random_style(): - return f'{"".join(random.choices(string.ascii_letters + string.digits, k=8))}.Link.TLabel' +LABEL_TO_STYLE = ['anchor', 'background', 'font', 'foreground', 'relief'] class HyperlinkLabel(ttk.Button): """Clickable label for HTTP links.""" - _legacy_style: str + + _legacy_style: str | None = None + + def _handle_legacy_options(self, options: dict): # noqa: CCR001 + label_options = {opt: options.pop(opt) for opt in LABEL_TO_STYLE if opt in options} + disabledforeground = options.pop('disabledforeground', None) + wraplength = options.pop('wraplength', None) + if len(label_options) > 0 or disabledforeground or wraplength or self.font or self.underline is not None: + if not self._legacy_style: + self._legacy_style = f'{"".join(random.choices(string.ascii_letters+string.digits, k=8))}.Link.TLabel' + if len(label_options) > 0: + ttk.Style().configure(self._legacy_style, **label_options) + if disabledforeground: + ttk.Style().map(self._legacy_style, foreground=[('disabled', disabledforeground)]) + if self.font: + font_u = tk.font.Font(font=self.font) + if self.underline is None: + ttk.Style().configure(self._legacy_style, font=self.font) + font_u.configure(underline=True) + ttk.Style().map(self._legacy_style, font=[('active', font_u.name)]) + else: + font_u.configure(underline=self.underline) + ttk.Style().configure(self._legacy_style, font=font_u.name) + else: + font_n = ttk.Style().lookup('Link.TLabel', 'font') + font_u = ttk.Style().lookup('Link.TLabel', 'font', ['active']) + if self.underline is None: + ttk.Style().configure(self._legacy_style, font=font_n) + ttk.Style().map(self._legacy_style, font=[('active', font_u)]) + elif self.underline: + ttk.Style().configure(self._legacy_style, font=font_u) + ttk.Style().map(self._legacy_style, font=[('active', font_u)]) + else: + ttk.Style().configure(self._legacy_style, font=font_n) + ttk.Style().map(self._legacy_style, font=[('active', font_n)]) + # TODO emulate wraplength + options['style'] = self._legacy_style + return options def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: """ @@ -68,19 +101,14 @@ def __init__(self, master: tk.Widget | None = None, **kw: Any) -> None: self.url = kw.pop('url', None) self.popup_copy = kw.pop('popup_copy', False) self.underline = kw.pop('underline', None) + self.font = kw.pop('font', None) kw.setdefault('command', self._click) kw.setdefault('style', 'Link.TLabel') - - self._legacy_options = {opt: kw[opt] for opt in LABEL_TO_STYLE if opt in kw} - if len(self._legacy_options) > 0: - self._legacy_style = _generate_random_style() - kw['style'] = self._legacy_style - ttk.Style().configure(self._legacy_style, **self._legacy_options) - # TODO underline, disabledforeground, cursor - + kw = self._handle_legacy_options(kw) super().__init__(master, **kw) self.bind('', self._contextmenu) + self.bind('<>', self._theme) # Add Menu Options self.plug_options = kw.pop('plug_options', None) @@ -125,16 +153,14 @@ def open_station(self, url: str): return webbrowser.open(opener) @no_type_check - def configure( # noqa: CCR001 + def configure( self, cnf: dict[str, Any] | None = None, **kw: Any ) -> dict[str, tuple[str, str, str, Any, Any]] | None: """Change cursor and appearance depending on state and text.""" - # This class' state - for thing in ('url', 'popup_copy', 'underline'): + for thing in ('url', 'popup_copy', 'underline', 'font'): if thing in kw: setattr(self, thing, kw.pop(thing)) - - # TODO _legacy_options, underline, disabledforeground, cursor + kw = self._handle_legacy_options(kw) return super().configure(cnf, **kw) def __setitem__(self, key: str, value: Any) -> None: @@ -146,7 +172,10 @@ def __setitem__(self, key: str, value: Any) -> None: """ self.configure(**{key: value}) - def _click(self, event: tk.Event) -> None: + def _theme(self, event: tk.Event): + self._handle_legacy_options({}) + + def _click(self) -> None: if self.url and self['text'] and str(self['state']) != tk.DISABLED: url = self.url(self['text']) if callable(self.url) else self.url if url: From a33c07e72ac62bec057b49c5b35dcff3838aca0f Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sat, 8 Jun 2024 23:48:33 +0100 Subject: [PATCH 33/59] Dropped unnecessary tk call --- theme.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/theme.py b/theme.py index 5e2bbecf8..4318a5359 100644 --- a/theme.py +++ b/theme.py @@ -216,9 +216,7 @@ def update(self, widget: tk.Widget) -> None: def apply(self) -> None: # noqa: CCR001 theme = config.get_int('theme') - theme_name = self.packages[theme] - self.root.tk.call('package', 'require', f'ttk::theme::{theme_name}') - self.root.tk.call('ttk::setTheme', theme_name) + self.root.tk.call('ttk::setTheme', self.packages[theme]) for image in self.bitmaps: image['background'] = self.style.lookup('.', 'background') From 1f0beddca8a68684c458a8314cd70734744c1fb8 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sat, 8 Jun 2024 23:51:23 +0100 Subject: [PATCH 34/59] Dropping justify config in HyperlinkLabel --- ttkHyperlinkLabel.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index d38ce9169..1861ee425 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -58,8 +58,9 @@ class HyperlinkLabel(ttk.Button): def _handle_legacy_options(self, options: dict): # noqa: CCR001 label_options = {opt: options.pop(opt) for opt in LABEL_TO_STYLE if opt in options} disabledforeground = options.pop('disabledforeground', None) - wraplength = options.pop('wraplength', None) - if len(label_options) > 0 or disabledforeground or wraplength or self.font or self.underline is not None: + justify = options.pop('justify', None) # noqa: F841 + wraplength = options.pop('wraplength', None) # noqa: F841 + if len(label_options) > 0 or disabledforeground or self.font or self.underline is not None: if not self._legacy_style: self._legacy_style = f'{"".join(random.choices(string.ascii_letters+string.digits, k=8))}.Link.TLabel' if len(label_options) > 0: @@ -87,7 +88,7 @@ def _handle_legacy_options(self, options: dict): # noqa: CCR001 else: ttk.Style().configure(self._legacy_style, font=font_n) ttk.Style().map(self._legacy_style, font=[('active', font_n)]) - # TODO emulate wraplength + # TODO emulate justify and wraplength options['style'] = self._legacy_style return options From 4ef48808eb5973faffa168ef749df3d9829d121c Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 10:36:40 +0100 Subject: [PATCH 35/59] Slight cleanup on font loading code - why two ifs for linux at the beginning? - using pathlib operator instead of os.path.join - FR_NOT_ENUM is not used - AddFontResourceExW.restypes should be argtypes - AddFontResourceExW has only one signature so argtypes is pointless anyway --- theme.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/theme.py b/theme.py index 4318a5359..92d27d7ec 100644 --- a/theme.py +++ b/theme.py @@ -24,21 +24,13 @@ if __debug__: from traceback import print_exc -if sys.platform == "linux": - from ctypes import POINTER, Structure, byref, c_char_p, c_int, c_long, c_uint, c_ulong, c_void_p, cdll - - if sys.platform == 'win32': - import ctypes - from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR - AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW - AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore + from ctypes import windll FR_PRIVATE = 0x10 - FR_NOT_ENUM = 0x20 - AddFontResourceEx(os.path.join(config.respath, 'EUROCAPS.TTF'), FR_PRIVATE, 0) + windll.gdi32.AddFontResourceExW(str(config.respath_path / 'EUROCAPS.TTF'), FR_PRIVATE, 0) elif sys.platform == 'linux': - # pyright: reportUnboundVariable=false + from ctypes import POINTER, Structure, byref, c_char_p, c_int, c_long, c_uint, c_ulong, c_void_p, cdll XID = c_ulong # from X.h: typedef unsigned long XID Window = XID Atom = c_ulong @@ -248,8 +240,8 @@ def apply(self) -> None: # noqa: CCR001 GWL_EXSTYLE = -20 # noqa: N806 # ctypes WS_EX_APPWINDOW = 0x00040000 # noqa: N806 # ctypes WS_EX_LAYERED = 0x00080000 # noqa: N806 # ctypes - GetWindowLongW = ctypes.windll.user32.GetWindowLongW # noqa: N806 # ctypes - SetWindowLongW = ctypes.windll.user32.SetWindowLongW # noqa: N806 # ctypes + GetWindowLongW = windll.user32.GetWindowLongW # noqa: N806 # ctypes + SetWindowLongW = windll.user32.SetWindowLongW # noqa: N806 # ctypes self.root.overrideredirect(theme != self.THEME_DEFAULT) @@ -260,7 +252,7 @@ def apply(self) -> None: # noqa: CCR001 self.root.withdraw() self.root.update_idletasks() # Size and windows styles get recalculated here - hwnd = ctypes.windll.user32.GetParent(self.root.winfo_id()) + hwnd = windll.user32.GetParent(self.root.winfo_id()) SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize if theme == self.THEME_TRANSPARENT: From 87fb27ad105445ddbcd060dcf78d7c9c6863ce81 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 10:41:05 +0100 Subject: [PATCH 36/59] Added check to whether Euro Caps actually loads on Windows --- theme.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/theme.py b/theme.py index 92d27d7ec..45b98358e 100644 --- a/theme.py +++ b/theme.py @@ -27,7 +27,9 @@ if sys.platform == 'win32': from ctypes import windll FR_PRIVATE = 0x10 - windll.gdi32.AddFontResourceExW(str(config.respath_path / 'EUROCAPS.TTF'), FR_PRIVATE, 0) + fonts_loaded = windll.gdi32.AddFontResourceExW(str(config.respath_path / 'EUROCAPS.TTF'), FR_PRIVATE, 0) + if fonts_loaded < 1: + logger.error('Unable to load Euro Caps font for Transparent theme') elif sys.platform == 'linux': from ctypes import POINTER, Structure, byref, c_char_p, c_int, c_long, c_uint, c_ulong, c_void_p, cdll From 254ec5d676eaa0148d135d7f735afa090c7e9300 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 10:48:00 +0100 Subject: [PATCH 37/59] Some words of encouragement --- theme.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/theme.py b/theme.py index 45b98358e..73c7c72e8 100644 --- a/theme.py +++ b/theme.py @@ -5,8 +5,7 @@ Licensed under the GNU General Public License. See LICENSE file. -Because of various ttk limitations this app is an unholy mix of Tk and ttk widgets. -So can't just use ttk's theme support. So have to change colors manually. +Believe us, this used to be much worse before ttk's theme support was properly leveraged. """ from __future__ import annotations From 94399cd8826386f1bad32246b7b8f1de0935a991 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 10:53:15 +0100 Subject: [PATCH 38/59] Better deprecation notices on theme.register() and theme.update() --- theme.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/theme.py b/theme.py index 73c7c72e8..e8640df6a 100644 --- a/theme.py +++ b/theme.py @@ -153,7 +153,8 @@ def initialize(self, root: tk.Tk): def register(self, widget: tk.Widget | tk.BitmapImage) -> None: assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget - warnings.warn('Theme postprocessing is no longer necessary', category=DeprecationWarning) + warnings.warn('theme.register() is no longer necessary as theme attributes are set on tk level', + category=DeprecationWarning) def register_alternate(self, pair: tuple, gridopts: dict) -> None: self.widgets_pair.append((pair, gridopts)) @@ -205,7 +206,8 @@ def update(self, widget: tk.Widget) -> None: :param widget: Target widget. """ assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget - warnings.warn('Theme postprocessing is no longer necessary', category=DeprecationWarning) + warnings.warn('theme.update() is no longer necessary as theme attributes are set on tk level', + category=DeprecationWarning) def apply(self) -> None: # noqa: CCR001 theme = config.get_int('theme') From f13636d66c5d45d14cdf9558df823e6327411232 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 11:02:03 +0100 Subject: [PATCH 39/59] Something to the logs --- theme.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/theme.py b/theme.py index e8640df6a..cad31d8d2 100644 --- a/theme.py +++ b/theme.py @@ -209,9 +209,12 @@ def update(self, widget: tk.Widget) -> None: warnings.warn('theme.update() is no longer necessary as theme attributes are set on tk level', category=DeprecationWarning) - def apply(self) -> None: # noqa: CCR001 + def apply(self) -> None: # noqa: CCR001, C901 theme = config.get_int('theme') - self.root.tk.call('ttk::setTheme', self.packages[theme]) + try: + self.root.tk.call('ttk::setTheme', self.packages[theme]) + except tk.TclError: + logger.exception(f'Failure setting theme: {self.packages[theme]}') for image in self.bitmaps: image['background'] = self.style.lookup('.', 'background') From 9829575fd26deb57f50c4d046e1b69483f830525 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 12:06:05 +0100 Subject: [PATCH 40/59] Proper theme/myNotebook deprecation warnings --- myNotebook.py | 19 ++++++++++--------- theme.py | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 96eef270d..a416de5f9 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -10,6 +10,7 @@ from __future__ import annotations import tkinter as tk +import warnings from tkinter import ttk, messagebox from PIL import ImageGrab from l10n import translations as tr @@ -18,29 +19,29 @@ class Notebook(ttk.Notebook): """Custom ttk.Notebook class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Notebook. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) + warnings.warn('Migrate to ttk.Notebook. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) self.grid(padx=10, pady=10, sticky=tk.NSEW) class Frame(ttk.Frame): """Custom ttk.Frame class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Frame. Will remove in 6.0 or later. def __init__(self, master: ttk.Notebook | None = None, **kw): ttk.Frame.__init__(self, master, **kw) ttk.Frame(self).grid(pady=5) # top spacer self.configure(takefocus=1) # let the frame take focus so that no particular child is focused + warnings.warn('Migrate to ttk.Frame. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class Label(tk.Label): """Custom tk.Label class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Label. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) + warnings.warn('Migrate to ttk.Label. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class EntryMenu(ttk.Entry): @@ -107,46 +108,46 @@ def paste(self) -> None: class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" - # DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): EntryMenu.__init__(self, master, **kw) + warnings.warn('Migrate to EntryMenu. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class Button(ttk.Button): """Custom ttk.Button class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): ttk.Button.__init__(self, master, **kw) + warnings.warn('Migrate to ttk.Button. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): tk.Button.__init__(self, master, **kw) + warnings.warn('Migrate to ttk.Button. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class Checkbutton(ttk.Checkbutton): """Custom ttk.Checkbutton class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Checkbutton. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) # type: ignore + warnings.warn('Migrate to ttk.Checkbutton. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class Radiobutton(ttk.Radiobutton): """Custom ttk.Radiobutton class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Radiobutton. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) # type: ignore + warnings.warn('Migrate to ttk.Radiobutton. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class OptionMenu(ttk.OptionMenu): """Custom ttk.OptionMenu class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.OptionMenu. Will remove in 6.0 or later. def __init__(self, master, variable, default=None, *values, **kw): ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw) + warnings.warn('Migrate to ttk.OptionMenu. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) diff --git a/theme.py b/theme.py index cad31d8d2..b5e6e3def 100644 --- a/theme.py +++ b/theme.py @@ -154,7 +154,7 @@ def initialize(self, root: tk.Tk): def register(self, widget: tk.Widget | tk.BitmapImage) -> None: assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget warnings.warn('theme.register() is no longer necessary as theme attributes are set on tk level', - category=DeprecationWarning) + DeprecationWarning, stacklevel=2) def register_alternate(self, pair: tuple, gridopts: dict) -> None: self.widgets_pair.append((pair, gridopts)) @@ -207,7 +207,7 @@ def update(self, widget: tk.Widget) -> None: """ assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget warnings.warn('theme.update() is no longer necessary as theme attributes are set on tk level', - category=DeprecationWarning) + DeprecationWarning, stacklevel=2) def apply(self) -> None: # noqa: CCR001, C901 theme = config.get_int('theme') From 2634e6e99f2612fe8a32304be4ee3aa1da7526e0 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 12:15:23 +0100 Subject: [PATCH 41/59] We don't need most of nb now, so... --- myNotebook.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index a416de5f9..57a4fb90d 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -1,11 +1,7 @@ """ Custom `ttk.Notebook` to fix various display issues. -Hacks to fix various display issues with notebooks and their child widgets on Windows. - -- Windows: page background should be White, not SystemButtonFace - -Entire file may be imported by plugins. +This is mostly no longer necessary, with ttk themes applying consistent behaviour across the board. """ from __future__ import annotations From ebb3a70103ac695da95cfe66e71e84fbebf0107e Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 12:36:33 +0100 Subject: [PATCH 42/59] Dropped ttk::theme lookup on theme files --- themes/dark/dark.tcl | 21 +++++++++++---------- themes/light/light.tcl | 19 ++++++++++--------- themes/transparent/transparent.tcl | 21 +++++++++++---------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/themes/dark/dark.tcl b/themes/dark/dark.tcl index b870ec46a..1f5e5206f 100644 --- a/themes/dark/dark.tcl +++ b/themes/dark/dark.tcl @@ -13,8 +13,9 @@ namespace eval ttk::theme::dark { -selectbg "#ff8000" -highlight "white" } - variable flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] + variable font TkDefaultFont variable font_u [font create {*}[font configure TkDefaultFont] -underline 1] + variable flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] ttk::style theme create dark -parent clam -settings { ttk::style configure . \ @@ -27,21 +28,21 @@ namespace eval ttk::theme::dark { -insertwidth 1 \ -insertcolor $colors(-fg) \ -fieldbackground $colors(-bg) \ - -font {TkDefaultFont} \ + -font $font \ -borderwidth 1 \ -relief flat - tk_setPalette background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] + tk_setPalette background $colors(-bg) \ + foreground $colors(-fg) \ + highlightColor $colors(-selectbg) \ + selectBackground $colors(-selectbg) \ + selectForeground $colors(-fg) \ + activeBackground $colors(-selectbg) \ + activeForeground $colors(-fg) ttk::style map . -foreground [list disabled $colors(-disabledfg)] - option add *font [ttk::style lookup . -font] + option add *Font $font option add *Menu.selectcolor $colors(-fg) ttk::style configure TLabel -padding 1 diff --git a/themes/light/light.tcl b/themes/light/light.tcl index e2c960c38..3342a0ad2 100644 --- a/themes/light/light.tcl +++ b/themes/light/light.tcl @@ -13,6 +13,7 @@ namespace eval ttk::theme::light { -selectbg "#9e9a91" -highlight "blue" } + variable font TkDefaultFont variable font_u [font create {*}[font configure TkDefaultFont] -underline 1] ttk::style theme create light -parent clam -settings { @@ -26,21 +27,21 @@ namespace eval ttk::theme::light { -insertwidth 1 \ -insertcolor $colors(-fg) \ -fieldbackground $colors(-bg) \ - -font {TkDefaultFont} \ + -font $font \ -borderwidth 1 \ -relief flat - tk_setPalette background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] + tk_setPalette background $colors(-bg) \ + foreground $colors(-fg) \ + highlightColor $colors(-selectbg) \ + selectBackground $colors(-selectbg) \ + selectForeground $colors(-fg) \ + activeBackground $colors(-selectbg) \ + activeForeground $colors(-fg) ttk::style map . -foreground [list disabled $colors(-disabledfg)] - option add *font [ttk::style lookup . -font] + option add *Font $font option add *Menu.selectcolor $colors(-fg) ttk::style configure TLabel -padding 1 diff --git a/themes/transparent/transparent.tcl b/themes/transparent/transparent.tcl index e7280eb80..2c51b1485 100644 --- a/themes/transparent/transparent.tcl +++ b/themes/transparent/transparent.tcl @@ -13,8 +13,9 @@ namespace eval ttk::theme::transparent { -selectbg "#ff8000" -highlight "white" } - variable flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] + variable font [font create -family "Euro Caps" -size 10] variable font_u [font create -family "Euro Caps" -size 10 -underline 1] + variable flatborder [list -relief groove -bordercolor $colors(-fg) -darkcolor $colors(-bg) -lightcolor $colors(-bg)] ttk::style theme create transparent -parent clam -settings { ttk::style configure . \ @@ -27,21 +28,21 @@ namespace eval ttk::theme::transparent { -insertwidth 1 \ -insertcolor $colors(-fg) \ -fieldbackground $colors(-bg) \ - -font {"Euro Caps" 10} \ + -font $font \ -borderwidth 1 \ -relief flat - tk_setPalette background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] + tk_setPalette background $colors(-bg) \ + foreground $colors(-fg) \ + highlightColor $colors(-selectbg) \ + selectBackground $colors(-selectbg) \ + selectForeground $colors(-fg) \ + activeBackground $colors(-selectbg) \ + activeForeground $colors(-fg) ttk::style map . -foreground [list disabled $colors(-disabledfg)] - option add *font [ttk::style lookup . -font] + option add *Font $font option add *Menu.selectcolor $colors(-fg) ttk::style configure TLabel -padding 1 From 91569187d3426f43aa473c0ff108481f664f054d Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 11 Jun 2024 01:00:09 +0100 Subject: [PATCH 43/59] Dropped post-merge repetition --- myNotebook.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 2a7e0219b..57a4fb90d 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -105,7 +105,6 @@ class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) EntryMenu.__init__(self, master, **kw) warnings.warn('Migrate to EntryMenu. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) @@ -122,7 +121,6 @@ class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) tk.Button.__init__(self, master, **kw) warnings.warn('Migrate to ttk.Button. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) From e45e8c0cbfd91e13dd0f8ef4d645db427d4911e0 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 11 Jun 2024 16:43:49 +0100 Subject: [PATCH 44/59] Dropped alternative title bar and update button --- EDMarketConnector.py | 69 ++++++++------------------------------------ theme.py | 43 ++++----------------------- 2 files changed, 17 insertions(+), 95 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 7e5310afd..cef298367 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -570,21 +570,10 @@ def open_window(systray: 'SysTrayIcon') -> None: default=tk.ACTIVE, state=tk.DISABLED ) - self.theme_button = ttk.Label( - frame, - name='themed_update_button', - width=28, - anchor=tk.CENTER, - state=tk.DISABLED - ) ui_row = frame.grid_size()[1] self.button.grid(row=ui_row, columnspan=2, sticky=tk.NSEW) - self.theme_button.grid(row=ui_row, columnspan=2, sticky=tk.NSEW) - theme.register_alternate((self.button, self.theme_button, self.theme_button), - {'row': ui_row, 'columnspan': 2, 'sticky': tk.NSEW}) self.button.bind('', self.capi_request_data) - theme.button_bind(self.theme_button, self.capi_request_data) # Bottom 'status' line. self.status = ttk.Label(frame, name='status', anchor=tk.W) @@ -652,38 +641,21 @@ def open_window(systray: 'SysTrayIcon') -> None: # Alternate title bar and menu for dark theme self.theme_menubar = ttk.Frame(frame, name="alternate_menubar") self.theme_menubar.columnconfigure(2, weight=1) - theme_titlebar = ttk.Label( - self.theme_menubar, - name="alternate_titlebar", - text=applongname, - image=self.theme_icon, cursor='fleur', - anchor=tk.W, compound=tk.LEFT - ) - theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW) self.drag_offset: tuple[int | None, int | None] = (None, None) - theme_titlebar.bind('', self.drag_start) - theme_titlebar.bind('', self.drag_continue) - theme_titlebar.bind('', self.drag_end) - theme_minimize = ttk.Label(self.theme_menubar, image=self.theme_minimize) - theme_minimize.grid(row=0, column=3, padx=2) - theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize) - theme_close = ttk.Label(self.theme_menubar, image=self.theme_close) - theme_close.grid(row=0, column=4, padx=2) - theme.button_bind(theme_close, self.onexit, image=self.theme_close) self.theme_file_menu = ttk.Label(self.theme_menubar, anchor=tk.W) - self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W) + self.theme_file_menu.grid(row=0, column=0, padx=self.PADX, sticky=tk.W) theme.button_bind(self.theme_file_menu, lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) self.theme_edit_menu = ttk.Label(self.theme_menubar, anchor=tk.W) - self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W) + self.theme_edit_menu.grid(row=0, column=1, sticky=tk.W) theme.button_bind(self.theme_edit_menu, lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) self.theme_help_menu = ttk.Label(self.theme_menubar, anchor=tk.W) - self.theme_help_menu.grid(row=1, column=2, sticky=tk.W) + self.theme_help_menu.grid(row=0, column=2, sticky=tk.W) theme.button_bind(self.theme_help_menu, lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() @@ -695,7 +667,8 @@ def open_window(systray: 'SysTrayIcon') -> None: ttk.Frame(self.blank_menubar, height=2).grid() theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW}) - self.w.resizable(tk.TRUE, tk.FALSE) + self.w.resizable(tk.FALSE, tk.FALSE) + theme.apply() # update geometry if config.get_str('geometry'): @@ -716,7 +689,6 @@ def open_window(systray: 'SysTrayIcon') -> None: self.w.attributes('-topmost', config.get_int('always_ontop') and 1 or 0) - self.w.bind('', self.onmap) # Special handling for overrideredirect self.w.bind('', self.onenter) # Special handling for transparency self.w.bind('', self.onenter) # Special handling for transparency self.w.bind('', self.onleave) # Special handling for transparency @@ -750,10 +722,7 @@ def open_window(systray: 'SysTrayIcon') -> None: self.toggle_suit_row(visible=False) if args.start_min: logger.warning("Trying to start minimized") - if root.overrideredirect(): - self.oniconify() - else: - self.w.wm_iconify() + self.w.wm_iconify() def update_suit_text(self) -> None: """Update the suit text for current type and loadout.""" @@ -856,7 +825,7 @@ def set_labels(self): self.suit_label['text'] = tr.tl('Suit') + ':' # LANG: Label for 'Suit' line in main UI self.system_label['text'] = tr.tl('System') + ':' # LANG: Label for 'System' line in main UI self.station_label['text'] = tr.tl('Station') + ':' # LANG: Label for 'Station' line in main UI - self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window + self.button['text'] = tr.tl('Update') # LANG: Update button in main window self.menubar.entryconfigure(1, label=tr.tl('File')) # LANG: 'File' menu title self.menubar.entryconfigure(2, label=tr.tl('Edit')) # LANG: 'Edit' menu title self.menubar.entryconfigure(3, label=tr.tl('Help')) # LANG: 'Help' menu title @@ -900,7 +869,7 @@ def login(self): # LANG: Status - Attempting to get a Frontier Auth Access Token self.status['text'] = tr.tl('Logging in...') - self.button['state'] = self.theme_button['state'] = tk.DISABLED + self.button['state'] = tk.DISABLED self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data @@ -1050,7 +1019,7 @@ def capi_request_data(self, event=None) -> None: # noqa: CCR001 # LANG: Status - Attempting to retrieve data from Frontier CAPI self.status['text'] = tr.tl('Fetching data...') - self.button['state'] = self.theme_button['state'] = tk.DISABLED + self.button['state'] = tk.DISABLED self.w.update_idletasks() query_time = int(time()) @@ -1668,11 +1637,11 @@ def cooldown(self) -> None: # Update button in main window cooldown_time = int(self.capi_query_holdoff_time - time()) # LANG: Cooldown on 'Update' button - self.button['text'] = self.theme_button['text'] = tr.tl('cooldown {SS}s').format(SS=cooldown_time) + self.button['text'] = tr.tl('cooldown {SS}s').format(SS=cooldown_time) self.w.after(1000, self.cooldown) else: - self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window - self.button['state'] = self.theme_button['state'] = ( + self.button['text'] = tr.tl('Update') # LANG: Update button in main window + self.button['state'] = ( monitor.cmdr and monitor.mode and monitor.mode != 'CQC' and @@ -1937,20 +1906,6 @@ def default_iconify(self, event=None) -> None: if str(event.widget) == '.': self.w.withdraw() - def oniconify(self, event=None) -> None: - """Handle the minimize button on non-Default theme main window.""" - self.w.overrideredirect(False) # Can't iconize while overrideredirect - self.w.iconify() - self.w.update_idletasks() # Size and windows styles get recalculated here - self.w.wait_visibility() # Need main window to be re-created before returning - theme.active = None # So theme will be re-applied on map - - def onmap(self, event=None) -> None: - """Handle when our window is rendered.""" - if event.widget == self.w: - # TODO decouple theme switch and window manager stuff - theme.apply() - def onenter(self, event=None) -> None: """Handle when our window gains focus.""" if config.get_int('theme') == theme.THEME_TRANSPARENT: diff --git a/theme.py b/theme.py index b5e6e3def..a35d83b66 100644 --- a/theme.py +++ b/theme.py @@ -129,7 +129,6 @@ class _Theme: def __init__(self) -> None: self.active: int | None = None # Starts out with no theme self.minwidth: int | None = None - self.bitmaps: list = [] self.widgets_pair: list = [] self.default_ui_scale: float | None = None # None == not yet known self.startup_ui_scale: int | None = None @@ -159,16 +158,12 @@ def register(self, widget: tk.Widget | tk.BitmapImage) -> None: def register_alternate(self, pair: tuple, gridopts: dict) -> None: self.widgets_pair.append((pair, gridopts)) - def button_bind( - self, widget: tk.Widget, command: Callable, image: tk.BitmapImage | None = None - ) -> None: + def button_bind(self, widget: tk.Widget, command: Callable) -> None: widget.bind('', command) - widget.bind('', lambda e: self._enter(e, image)) - widget.bind('', lambda e: self._leave(e, image)) - if image: - self.bitmaps.append(image) + widget.bind('', self._enter) + widget.bind('', self._leave) - def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None: + def _enter(self, event: tk.Event) -> None: widget = event.widget if widget and widget['state'] != tk.DISABLED: try: @@ -176,14 +171,7 @@ def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None: except Exception: logger.exception(f'Failure setting widget active: {widget=}') - if image: - try: - image['background'] = self.style.lookup('.', 'selectbackground') - image['foreground'] = self.style.lookup('.', 'selectforeground') - except Exception: - logger.exception(f'Failure configuring image: {image=}') - - def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: + def _leave(self, event: tk.Event) -> None: widget = event.widget if widget and widget['state'] != tk.DISABLED: try: @@ -191,13 +179,6 @@ def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None: except Exception: logger.exception(f'Failure setting widget normal: {widget=}') - if image: - try: - image['background'] = self.style.lookup('.', 'background') - image['foreground'] = self.style.lookup('.', 'foreground') - except Exception: - logger.exception(f'Failure configuring image: {image=}') - def update(self, widget: tk.Widget) -> None: """ Apply current theme to a widget and its children. @@ -216,10 +197,6 @@ def apply(self) -> None: # noqa: CCR001, C901 except tk.TclError: logger.exception(f'Failure setting theme: {self.packages[theme]}') - for image in self.bitmaps: - image['background'] = self.style.lookup('.', 'background') - image['foreground'] = self.style.lookup('.', 'foreground') - # Switch menus for pair, gridopts in self.widgets_pair: for widget in pair: @@ -249,13 +226,6 @@ def apply(self) -> None: # noqa: CCR001, C901 GetWindowLongW = windll.user32.GetWindowLongW # noqa: N806 # ctypes SetWindowLongW = windll.user32.SetWindowLongW # noqa: N806 # ctypes - self.root.overrideredirect(theme != self.THEME_DEFAULT) - - if theme == self.THEME_TRANSPARENT: - self.root.attributes("-transparentcolor", 'grey4') - else: - self.root.attributes("-transparentcolor", '') - self.root.withdraw() self.root.update_idletasks() # Size and windows styles get recalculated here hwnd = windll.user32.GetParent(self.root.winfo_id()) @@ -289,9 +259,6 @@ def apply(self) -> None: # noqa: CCR001, C901 XFlush(dpy) - else: - self.root.overrideredirect(theme != self.THEME_DEFAULT) - self.root.deiconify() self.root.wait_visibility() # need main window to be displayed before returning From e6f85800938984b3bef973d88af748ee711d51d4 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 11 Jun 2024 17:06:15 +0100 Subject: [PATCH 45/59] Using native dark mode title bar on Windows --- theme.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/theme.py b/theme.py index a35d83b66..e8dc302e1 100644 --- a/theme.py +++ b/theme.py @@ -24,7 +24,7 @@ from traceback import print_exc if sys.platform == 'win32': - from ctypes import windll + from ctypes import windll, byref, c_int FR_PRIVATE = 0x10 fonts_loaded = windll.gdi32.AddFontResourceExW(str(config.respath_path / 'EUROCAPS.TTF'), FR_PRIVATE, 0) if fonts_loaded < 1: @@ -216,6 +216,8 @@ def apply(self) -> None: # noqa: CCR001, C901 return # Don't need to mess with the window manager self.active = theme + self.root.withdraw() + self.root.update_idletasks() # Size gets recalculated here if sys.platform == 'win32': GWL_STYLE = -16 # noqa: N806 # ctypes WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes @@ -223,11 +225,17 @@ def apply(self) -> None: # noqa: CCR001, C901 GWL_EXSTYLE = -20 # noqa: N806 # ctypes WS_EX_APPWINDOW = 0x00040000 # noqa: N806 # ctypes WS_EX_LAYERED = 0x00080000 # noqa: N806 # ctypes + DWMWA_USE_IMMERSIVE_DARK_MODE = 20 + DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19 GetWindowLongW = windll.user32.GetWindowLongW # noqa: N806 # ctypes SetWindowLongW = windll.user32.SetWindowLongW # noqa: N806 # ctypes + DwmSetWindowAttribute = windll.dwmapi.DwmSetWindowAttribute + + if theme == self.THEME_DEFAULT: + dark = 0 + else: + dark = 1 - self.root.withdraw() - self.root.update_idletasks() # Size and windows styles get recalculated here hwnd = windll.user32.GetParent(self.root.winfo_id()) SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize @@ -236,12 +244,9 @@ def apply(self) -> None: # noqa: CCR001, C901 else: SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW) # Add to taskbar - self.root.deiconify() - self.root.wait_visibility() # need main window to be displayed before returning - + if DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, byref(c_int(dark)), 4) != 0: + DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, byref(c_int(dark)), 4) else: - self.root.withdraw() - self.root.update_idletasks() # Size gets recalculated here if dpy: xroot = Window() parent = Window() @@ -259,8 +264,8 @@ def apply(self) -> None: # noqa: CCR001, C901 XFlush(dpy) - self.root.deiconify() - self.root.wait_visibility() # need main window to be displayed before returning + self.root.deiconify() + self.root.wait_visibility() # need main window to be displayed before returning if not self.minwidth: self.minwidth = self.root.winfo_width() # Minimum width = width on first creation From 199110e085c69eff9a4fcc1a748e26f06fe557d6 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Thu, 13 Jun 2024 22:53:23 +0100 Subject: [PATCH 46/59] Removed widget switcheroo, all themes use the same menu --- EDMarketConnector.py | 55 +++++------------------------- theme.py | 46 ++++--------------------- themes/dark/dark.tcl | 4 +++ themes/light/light.tcl | 4 +++ themes/transparent/transparent.tcl | 4 +++ 5 files changed, 26 insertions(+), 87 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index cef298367..b64056eaa 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -480,14 +480,6 @@ def open_window(systray: 'SysTrayIcon') -> None: self.w.tk.call('wm', 'iconphoto', self.w, '-default', tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png'))) - # TODO: Export to files and merge from them in future ? - self.theme_icon = tk.PhotoImage( - data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501 - self.theme_minimize = tk.BitmapImage( - data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 - self.theme_close = tk.BitmapImage( - data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 - frame = ttk.Frame(self.w, name=appname.lower()) frame.grid(sticky=tk.NSEW) frame.columnconfigure(1, weight=1) @@ -583,8 +575,6 @@ def open_window(systray: 'SysTrayIcon') -> None: child.grid_configure(padx=self.PADX, pady=( sys.platform != 'win32' or isinstance(child, ttk.Frame)) and 2 or 0) - self.menubar = tk.Menu() - # This used to be *after* the menu setup for some reason, but is testing # as working (both internal and external) like this. -Ath import update @@ -597,17 +587,15 @@ def open_window(systray: 'SysTrayIcon') -> None: self.updater = update.Updater(tkroot=self.w) self.updater.check_for_updates() # Sparkle / WinSparkle does this automatically for packaged apps - self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) + self.file_menu = self.view_menu = tk.Menu(self.w, tearoff=tk.FALSE) self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status)) self.file_menu.add_command(command=self.save_raw) self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs)) self.file_menu.add_separator() self.file_menu.add_command(command=self.onexit) - self.menubar.add_cascade(menu=self.file_menu) - self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) + self.edit_menu = tk.Menu(self.w, tearoff=tk.FALSE) self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy) - self.menubar.add_cascade(menu=self.edit_menu) - self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) # type: ignore + self.help_menu = tk.Menu(self.w, tearoff=tk.FALSE) self.help_menu.add_command(command=self.help_general) # Documentation self.help_menu.add_command(command=self.help_troubleshooting) # Troubleshooting self.help_menu.add_command(command=self.help_report_a_bug) # Report A Bug @@ -620,17 +608,15 @@ def open_window(systray: 'SysTrayIcon') -> None: self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder self.help_menu.add_command(command=lambda: prefs.help_open_system_profiler(self)) # Open Log Folde - self.menubar.add_cascade(menu=self.help_menu) if sys.platform == 'win32': # Must be added after at least one "real" menu entry self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) - self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) + self.system_menu = tk.Menu(self.w, name='system', tearoff=tk.FALSE) self.system_menu.add_separator() # LANG: Appearance - Label for checkbox to select if application always on top self.system_menu.add_checkbutton(label=tr.tl('Always on top'), variable=self.always_ontop, command=self.ontop_changed) # Appearance setting - self.menubar.add_cascade(menu=self.system_menu) self.w.bind('', self.copy) # Bind to the Default theme minimise button @@ -638,35 +624,17 @@ def open_window(systray: 'SysTrayIcon') -> None: self.w.protocol("WM_DELETE_WINDOW", self.onexit) - # Alternate title bar and menu for dark theme self.theme_menubar = ttk.Frame(frame, name="alternate_menubar") self.theme_menubar.columnconfigure(2, weight=1) self.drag_offset: tuple[int | None, int | None] = (None, None) - self.theme_file_menu = ttk.Label(self.theme_menubar, anchor=tk.W) + self.theme_file_menu = ttk.Menubutton(self.theme_menubar, menu=self.file_menu, style='Menubar.TMenubutton') self.theme_file_menu.grid(row=0, column=0, padx=self.PADX, sticky=tk.W) - theme.button_bind(self.theme_file_menu, - lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), - e.widget.winfo_rooty() - + e.widget.winfo_height())) - self.theme_edit_menu = ttk.Label(self.theme_menubar, anchor=tk.W) + self.theme_edit_menu = ttk.Menubutton(self.theme_menubar, menu=self.edit_menu, style='Menubar.TMenubutton') self.theme_edit_menu.grid(row=0, column=1, sticky=tk.W) - theme.button_bind(self.theme_edit_menu, - lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), - e.widget.winfo_rooty() - + e.widget.winfo_height())) - self.theme_help_menu = ttk.Label(self.theme_menubar, anchor=tk.W) + self.theme_help_menu = ttk.Menubutton(self.theme_menubar, menu=self.help_menu, style='Menubar.TMenubutton') self.theme_help_menu.grid(row=0, column=2, sticky=tk.W) - theme.button_bind(self.theme_help_menu, - lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), - e.widget.winfo_rooty() - + e.widget.winfo_height())) ttk.Separator(self.theme_menubar).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) - self.blank_menubar = ttk.Frame(frame, name="blank_menubar") - ttk.Label(self.blank_menubar).grid() - ttk.Label(self.blank_menubar).grid() - ttk.Frame(self.blank_menubar, height=2).grid() - theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), - {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW}) + self.theme_menubar.grid(row=0, columnspan=2, sticky=tk.NSEW) self.w.resizable(tk.FALSE, tk.FALSE) theme.apply() @@ -826,9 +794,6 @@ def set_labels(self): self.system_label['text'] = tr.tl('System') + ':' # LANG: Label for 'System' line in main UI self.station_label['text'] = tr.tl('Station') + ':' # LANG: Label for 'Station' line in main UI self.button['text'] = tr.tl('Update') # LANG: Update button in main window - self.menubar.entryconfigure(1, label=tr.tl('File')) # LANG: 'File' menu title - self.menubar.entryconfigure(2, label=tr.tl('Edit')) # LANG: 'Edit' menu title - self.menubar.entryconfigure(3, label=tr.tl('Help')) # LANG: 'Help' menu title self.theme_file_menu['text'] = tr.tl('File') # LANG: 'File' menu title self.theme_edit_menu['text'] = tr.tl('Edit') # LANG: 'Edit' menu title self.theme_help_menu['text'] = tr.tl('Help') # LANG: 'Help' menu title @@ -1910,15 +1875,11 @@ def onenter(self, event=None) -> None: """Handle when our window gains focus.""" 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.""" 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) def test_logging() -> None: diff --git a/theme.py b/theme.py index e8dc302e1..2087a499d 100644 --- a/theme.py +++ b/theme.py @@ -129,7 +129,6 @@ class _Theme: def __init__(self) -> None: self.active: int | None = None # Starts out with no theme self.minwidth: int | None = None - self.widgets_pair: list = [] self.default_ui_scale: float | None = None # None == not yet known self.startup_ui_scale: int | None = None @@ -156,28 +155,10 @@ def register(self, widget: tk.Widget | tk.BitmapImage) -> None: DeprecationWarning, stacklevel=2) def register_alternate(self, pair: tuple, gridopts: dict) -> None: - self.widgets_pair.append((pair, gridopts)) + ... # does any plugin even use this? def button_bind(self, widget: tk.Widget, command: Callable) -> None: - widget.bind('', command) - widget.bind('', self._enter) - widget.bind('', self._leave) - - def _enter(self, event: tk.Event) -> None: - widget = event.widget - if widget and widget['state'] != tk.DISABLED: - try: - widget.configure(state=tk.ACTIVE) - except Exception: - logger.exception(f'Failure setting widget active: {widget=}') - - def _leave(self, event: tk.Event) -> None: - widget = event.widget - if widget and widget['state'] != tk.DISABLED: - try: - widget.configure(state=tk.NORMAL) - except Exception: - logger.exception(f'Failure setting widget normal: {widget=}') + ... # does any plugin even use this? def update(self, widget: tk.Widget) -> None: """ @@ -190,28 +171,13 @@ def update(self, widget: tk.Widget) -> None: warnings.warn('theme.update() is no longer necessary as theme attributes are set on tk level', DeprecationWarning, stacklevel=2) - def apply(self) -> None: # noqa: CCR001, C901 + def apply(self) -> None: theme = config.get_int('theme') try: self.root.tk.call('ttk::setTheme', self.packages[theme]) except tk.TclError: logger.exception(f'Failure setting theme: {self.packages[theme]}') - # Switch menus - for pair, gridopts in self.widgets_pair: - for widget in pair: - if isinstance(widget, tk.Widget): - widget.grid_remove() - - if isinstance(pair[0], tk.Menu): - if theme == self.THEME_DEFAULT: - self.root['menu'] = pair[0] - else: # Dark *or* Transparent - self.root['menu'] = '' - pair[theme].grid(**gridopts) - else: - pair[theme].grid(**gridopts) - if self.active == theme: return # Don't need to mess with the window manager self.active = theme @@ -225,11 +191,11 @@ def apply(self) -> None: # noqa: CCR001, C901 GWL_EXSTYLE = -20 # noqa: N806 # ctypes WS_EX_APPWINDOW = 0x00040000 # noqa: N806 # ctypes WS_EX_LAYERED = 0x00080000 # noqa: N806 # ctypes - DWMWA_USE_IMMERSIVE_DARK_MODE = 20 - DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19 + DWMWA_USE_IMMERSIVE_DARK_MODE = 20 # noqa: N806 # ctypes + DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19 # noqa: N806 # ctypes GetWindowLongW = windll.user32.GetWindowLongW # noqa: N806 # ctypes SetWindowLongW = windll.user32.SetWindowLongW # noqa: N806 # ctypes - DwmSetWindowAttribute = windll.dwmapi.DwmSetWindowAttribute + DwmSetWindowAttribute = windll.dwmapi.DwmSetWindowAttribute # noqa: N806 # ctypes if theme == self.THEME_DEFAULT: dark = 0 diff --git a/themes/dark/dark.tcl b/themes/dark/dark.tcl index 1f5e5206f..567b56460 100644 --- a/themes/dark/dark.tcl +++ b/themes/dark/dark.tcl @@ -67,6 +67,10 @@ namespace eval ttk::theme::dark { ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder + ttk::style map TMenubutton \ + -background [list active $colors(-selectbg)] \ + -foreground [list active $colors(-selectfg)] + ttk::style configure Menubar.TMenubutton -padding 2 -relief flat -arrowsize 0 ttk::style configure TOptionMenu -padding {8 4 4 4} {*}$flatborder diff --git a/themes/light/light.tcl b/themes/light/light.tcl index 3342a0ad2..191e21175 100644 --- a/themes/light/light.tcl +++ b/themes/light/light.tcl @@ -64,6 +64,10 @@ namespace eval ttk::theme::light { ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center ttk::style configure TMenubutton -padding {8 4 4 4} -relief groove + ttk::style map TMenubutton \ + -background [list active $colors(-selectbg)] \ + -foreground [list active $colors(-selectfg)] + ttk::style configure Menubar.TMenubutton -padding 2 -relief flat -arrowsize 0 ttk::style configure TOptionMenu -padding {8 4 4 4} -relief groove diff --git a/themes/transparent/transparent.tcl b/themes/transparent/transparent.tcl index 2c51b1485..054cc388c 100644 --- a/themes/transparent/transparent.tcl +++ b/themes/transparent/transparent.tcl @@ -67,6 +67,10 @@ namespace eval ttk::theme::transparent { ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder + ttk::style map TMenubutton \ + -background [list active $colors(-selectbg)] \ + -foreground [list active $colors(-selectfg)] + ttk::style configure Menubar.TMenubutton -padding 2 -relief flat -arrowsize 0 ttk::style configure TOptionMenu -padding {8 4 4 4} {*}$flatborder From 2a7db416ef2e812a577b0b7171bcde0e1198de93 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Fri, 14 Jun 2024 08:53:36 +0100 Subject: [PATCH 47/59] Style tweaks --- themes/dark/dark.tcl | 7 +++---- themes/light/light.tcl | 7 +++---- themes/transparent/transparent.tcl | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/themes/dark/dark.tcl b/themes/dark/dark.tcl index 567b56460..7825a88f9 100644 --- a/themes/dark/dark.tcl +++ b/themes/dark/dark.tcl @@ -66,14 +66,13 @@ namespace eval ttk::theme::dark { ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center - ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder + ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder -arrowcolor $colors(-fg) ttk::style map TMenubutton \ -background [list active $colors(-selectbg)] \ - -foreground [list active $colors(-selectfg)] + -foreground [list active $colors(-selectfg)] \ + -arrowcolor [list active $colors(-selectfg)] ttk::style configure Menubar.TMenubutton -padding 2 -relief flat -arrowsize 0 - ttk::style configure TOptionMenu -padding {8 4 4 4} {*}$flatborder - ttk::style configure TCheckbutton -padding 4 -indicatormargin 4 ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center diff --git a/themes/light/light.tcl b/themes/light/light.tcl index 191e21175..e201ae37c 100644 --- a/themes/light/light.tcl +++ b/themes/light/light.tcl @@ -63,14 +63,13 @@ namespace eval ttk::theme::light { ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center - ttk::style configure TMenubutton -padding {8 4 4 4} -relief groove + ttk::style configure TMenubutton -padding {8 4 4 4} -relief groove -arrowcolor $colors(-fg) ttk::style map TMenubutton \ -background [list active $colors(-selectbg)] \ - -foreground [list active $colors(-selectfg)] + -foreground [list active $colors(-selectfg)] \ + -arrowcolor [list active $colors(-selectfg)] ttk::style configure Menubar.TMenubutton -padding 2 -relief flat -arrowsize 0 - ttk::style configure TOptionMenu -padding {8 4 4 4} -relief groove - ttk::style configure TCheckbutton -padding 4 -indicatormargin 4 ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center diff --git a/themes/transparent/transparent.tcl b/themes/transparent/transparent.tcl index 054cc388c..b4e3f1115 100644 --- a/themes/transparent/transparent.tcl +++ b/themes/transparent/transparent.tcl @@ -66,14 +66,13 @@ namespace eval ttk::theme::transparent { ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center - ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder + ttk::style configure TMenubutton -padding {8 4 4 4} {*}$flatborder -arrowcolor $colors(-fg) ttk::style map TMenubutton \ -background [list active $colors(-selectbg)] \ - -foreground [list active $colors(-selectfg)] + -foreground [list active $colors(-selectfg)] \ + -arrowcolor [list active $colors(-selectfg)] ttk::style configure Menubar.TMenubutton -padding 2 -relief flat -arrowsize 0 - ttk::style configure TOptionMenu -padding {8 4 4 4} {*}$flatborder - ttk::style configure TCheckbutton -padding 4 -indicatormargin 4 ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center From 44d1e6891ce0072ac49d25cb61ee853f3d948a5b Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 24 Jun 2024 11:27:25 +0100 Subject: [PATCH 48/59] Customizing title bar via PyWinRT Also updated theme.py to pywin32 ahead of #2263, in order to minimize the merging mess --- EDMarketConnector.py | 12 +++++++++++- requirements-dev.txt | 2 -- requirements.txt | 4 ++++ theme.py | 34 ++++++++++++---------------------- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 087322d34..da99dc31e 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1990,7 +1990,7 @@ def validate_providers(): # Run the app -if __name__ == "__main__": # noqa: C901 +def main(): logger.info(f'Startup v{appversion()} : Running on Python v{sys.version}') logger.debug(f'''Platform: {sys.platform} {sys.platform == "win32" and sys.getwindowsversion()} argv[0]: {sys.argv[0]} @@ -2245,3 +2245,13 @@ def messagebox_not_py3(): logger.info("Ctrl+C Detected, Attempting Clean Shutdown") app.onexit() logger.info('Exiting') + + +if __name__ == '__main__': + if sys.platform == 'win32': + from winrt.microsoft.windows.applicationmodel.dynamicdependency import bootstrap + + with bootstrap.initialize(options=bootstrap.InitializeOptions.ON_NO_MATCH_SHOW_UI): + main() + else: + main() diff --git a/requirements-dev.txt b/requirements-dev.txt index 05c5ad291..c79908e36 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -42,8 +42,6 @@ pytest==8.2.0 pytest-cov==5.0.0 # Pytest code coverage support coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 -# For manipulating folder permissions and the like. -pywin32==306; sys_platform == 'win32' # All of the normal requirements diff --git a/requirements.txt b/requirements.txt index 22f0a360f..e6dbc8735 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,8 @@ requests==2.32.3 pillow==10.3.0 watchdog==4.0.0 simplesystray==0.1.0; sys_platform == 'win32' +pywin32==306; sys_platform == 'win32' +winrt-Microsoft.UI.Interop==2.1.0; sys_platform == 'win32' +winrt-Microsoft.UI.Windowing==2.1.0; sys_platform == 'win32' +winrt-Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap==2.1.0; sys_platform == 'win32' semantic-version==2.10.0 diff --git a/theme.py b/theme.py index 2087a499d..016d803c6 100644 --- a/theme.py +++ b/theme.py @@ -24,7 +24,11 @@ from traceback import print_exc if sys.platform == 'win32': - from ctypes import windll, byref, c_int + import win32con + import win32gui + from winrt.microsoft.ui.interop import get_window_id_from_window + from winrt.microsoft.ui.windowing import AppWindow + from ctypes import windll FR_PRIVATE = 0x10 fonts_loaded = windll.gdi32.AddFontResourceExW(str(config.respath_path / 'EUROCAPS.TTF'), FR_PRIVATE, 0) if fonts_loaded < 1: @@ -185,33 +189,19 @@ def apply(self) -> None: self.root.withdraw() self.root.update_idletasks() # Size gets recalculated here if sys.platform == 'win32': - GWL_STYLE = -16 # noqa: N806 # ctypes - WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes - # tk8.5.9/win/tkWinWm.c:342 - GWL_EXSTYLE = -20 # noqa: N806 # ctypes - WS_EX_APPWINDOW = 0x00040000 # noqa: N806 # ctypes - WS_EX_LAYERED = 0x00080000 # noqa: N806 # ctypes - DWMWA_USE_IMMERSIVE_DARK_MODE = 20 # noqa: N806 # ctypes - DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19 # noqa: N806 # ctypes - GetWindowLongW = windll.user32.GetWindowLongW # noqa: N806 # ctypes - SetWindowLongW = windll.user32.SetWindowLongW # noqa: N806 # ctypes - DwmSetWindowAttribute = windll.dwmapi.DwmSetWindowAttribute # noqa: N806 # ctypes + hwnd = win32gui.GetParent(self.root.winfo_id()) + window = AppWindow.get_from_window_id(get_window_id_from_window(hwnd)) if theme == self.THEME_DEFAULT: - dark = 0 + window.title_bar.reset_to_default() else: - dark = 1 - - hwnd = windll.user32.GetParent(self.root.winfo_id()) - SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize + window.title_bar.extends_content_into_title_bar = True if theme == self.THEME_TRANSPARENT: - SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED) # Add to taskbar + win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, + win32con.WS_EX_APPWINDOW | win32con.WS_EX_LAYERED) # Add to taskbar else: - SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW) # Add to taskbar - - if DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, byref(c_int(dark)), 4) != 0: - DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, byref(c_int(dark)), 4) + win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32con.WS_EX_APPWINDOW) # Add to taskbar else: if dpy: xroot = Window() From a7361fea849d00b921868e2cff0b20a2c66184c3 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 24 Jun 2024 20:34:15 +0100 Subject: [PATCH 49/59] Import shenanigans now that pywinrt works --- EDMarketConnector.py | 17 +++++++---------- requirements.txt | 1 + 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index da99dc31e..be0a4ed32 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -415,6 +415,7 @@ def already_running_popup(): import tkinter.messagebox from tkinter import ttk import commodity +import companion import plug import prefs import protocol @@ -672,7 +673,7 @@ def open_window(systray: 'SysTrayIcon', *args) -> None: self.w.bind_all('<>', self.onexit) # Updater # Check for Valid Providers - validate_providers() + validate_providers(self.w) if monitor.cmdr is None: self.status['text'] = tr.tl("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game @@ -857,7 +858,7 @@ def login(self): self.cooldown() - def export_market_data(self, data: 'CAPIData') -> bool: # noqa: CCR001 + def export_market_data(self, data: 'companion.CAPIData') -> bool: # noqa: CCR001 """ Export CAPI market data. @@ -1203,7 +1204,7 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001 if (suit := loadout.get('suit')) is not None: if (suitname := suit.get('edmcName')) is not None: # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there - loadout_name = index_possibly_sparse_list( + loadout_name = companion.index_possibly_sparse_list( capi_response.capi_data['loadouts'], loadout['loadoutSlotId'] )['name'] @@ -1943,7 +1944,7 @@ def show_killswitch_poppup(root=None): ok_button.grid(columnspan=2, sticky=tk.EW) -def validate_providers(): +def validate_providers(root): """Check if Config has an invalid provider set, and reset to default if we do.""" reset_providers = {} station_provider: str = config.get_str("station_provider") @@ -1990,7 +1991,8 @@ def validate_providers(): # Run the app -def main(): +def main(): # noqa: C901, CCR001 + """Run the main code of the program.""" logger.info(f'Startup v{appversion()} : Running on Python v{sys.version}') logger.debug(f'''Platform: {sys.platform} {sys.platform == "win32" and sys.getwindowsversion()} argv[0]: {sys.argv[0]} @@ -2068,11 +2070,6 @@ def main(): else: log_locale('After switching to UTF-8 encoding (same language)') - # HACK: n/a | 2021-11-24: --force-localserver-auth does not work if companion is imported early -cont. - # HACK: n/a | 2021-11-24: as we modify config before this is used. - import companion - from companion import CAPIData, index_possibly_sparse_list - # Do this after locale silliness, just in case if args.forget_frontier_auth: logger.info("Dropping all fdev tokens as --forget-frontier-auth was passed") diff --git a/requirements.txt b/requirements.txt index e6dbc8735..f9049cba2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ pillow==10.3.0 watchdog==4.0.0 simplesystray==0.1.0; sys_platform == 'win32' pywin32==306; sys_platform == 'win32' +winrt-Microsoft.UI==2.1.0; sys_platform == 'win32' winrt-Microsoft.UI.Interop==2.1.0; sys_platform == 'win32' winrt-Microsoft.UI.Windowing==2.1.0; sys_platform == 'win32' winrt-Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap==2.1.0; sys_platform == 'win32' From f680682556d69cc8370c19954b292a1dcca7e4e3 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 24 Jun 2024 21:34:01 +0100 Subject: [PATCH 50/59] We no longer need those drag events --- EDMarketConnector.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index be0a4ed32..da9192a0e 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -627,7 +627,6 @@ def open_window(systray: 'SysTrayIcon', *args) -> None: self.theme_menubar = ttk.Frame(frame, name="alternate_menubar") self.theme_menubar.columnconfigure(2, weight=1) - self.drag_offset: tuple[int | None, int | None] = (None, None) self.theme_file_menu = ttk.Menubutton(self.theme_menubar, menu=self.file_menu, style='Menubar.TMenubutton') self.theme_file_menu.grid(row=0, column=0, padx=self.PADX, sticky=tk.W) self.theme_edit_menu = ttk.Menubutton(self.theme_menubar, menu=self.edit_menu, style='Menubar.TMenubutton') @@ -1849,23 +1848,8 @@ def onexit(self, event=None) -> None: logger.info('Done.') - def drag_start(self, event) -> None: - """Initiate dragging the window.""" - self.drag_offset = (event.x_root - self.w.winfo_rootx(), event.y_root - self.w.winfo_rooty()) - - def drag_continue(self, event) -> None: - """Continued handling of window drag.""" - if self.drag_offset[0]: - offset_x = event.x_root - self.drag_offset[0] - offset_y = event.y_root - self.drag_offset[1] - self.w.geometry(f'+{offset_x:d}+{offset_y:d}') - - def drag_end(self, event) -> None: - """Handle end of window dragging.""" - self.drag_offset = (None, None) - def default_iconify(self, event=None) -> None: - """Handle the Windows default theme 'minimise' button.""" + """Handle the Windows 'minimize' button.""" # If we're meant to "minimize to system tray" then hide the window so no taskbar icon is seen if sys.platform == 'win32' and config.get_bool('minimize_system_tray'): # This gets called for more than the root widget, so only react to that From 810fa2b7d23881cd89602c728fd98110a5b1b94e Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 22 Jul 2024 09:49:14 +0100 Subject: [PATCH 51/59] Bump WinRT dependency (as it missed a DLL) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a44090444..319956c5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,5 @@ pywin32==306; sys_platform == 'win32' winrt-Microsoft.UI==2.1.0; sys_platform == 'win32' winrt-Microsoft.UI.Interop==2.1.0; sys_platform == 'win32' winrt-Microsoft.UI.Windowing==2.1.0; sys_platform == 'win32' -winrt-Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap==2.1.0; sys_platform == 'win32' +winrt-Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap==2.1.0.post1; sys_platform == 'win32' semantic-version==2.10.0 From 64be73c388760f554ccd37a67aabfc716c1a6411 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 22 Jul 2024 10:12:23 +0100 Subject: [PATCH 52/59] Post-merge clarity --- myNotebook.py | 5 +++-- requirements.txt | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 57a4fb90d..3cee0b6d9 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -104,17 +104,18 @@ def paste(self) -> None: class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" + # DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): + warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) EntryMenu.__init__(self, master, **kw) - warnings.warn('Migrate to EntryMenu. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class Button(ttk.Button): """Custom ttk.Button class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): + warnings.warn('Migrate to ttk.Button. Will remove in 6.0 or later', DeprecationWarning, stacklevel=2) ttk.Button.__init__(self, master, **kw) - warnings.warn('Migrate to ttk.Button. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class ColoredButton(tk.Button): diff --git a/requirements.txt b/requirements.txt index 5de650550..ba09a40ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,11 @@ requests==2.32.3 pillow==10.3.0 watchdog==4.0.1 simplesystray==0.1.0; sys_platform == 'win32' +semantic-version==2.10.0 +# For manipulating folder permissions and the like. pywin32==306; sys_platform == 'win32' +psutil==5.9.8 winrt-Microsoft.UI==2.1.0; sys_platform == 'win32' winrt-Microsoft.UI.Interop==2.1.0; sys_platform == 'win32' winrt-Microsoft.UI.Windowing==2.1.0; sys_platform == 'win32' winrt-Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap==2.1.0.post1; sys_platform == 'win32' -semantic-version==2.10.0 -# For manipulating folder permissions and the like. -pywin32==306; sys_platform == 'win32' -psutil==5.9.8 From 50d9e41d47e52dc3aaa6fc9417218ac2bc17cd8a Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 22 Jul 2024 10:25:25 +0100 Subject: [PATCH 53/59] Fixed pywin32 call --- EDMarketConnector.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4139621fe..0eef0614c 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -267,7 +267,7 @@ def handle_edmc_callback_or_foregrounding() -> None: # noqa: CCR001 # If *this* instance hasn't locked, then another already has and we # now need to do the edmc:// checks for auth callback if locked != JournalLockResult.LOCKED: - from ctypes import windll, create_unicode_buffer, WINFUNCTYPE + from ctypes import windll, WINFUNCTYPE from ctypes.wintypes import BOOL, HWND, LPARAM import win32gui import win32api @@ -302,25 +302,22 @@ def enumwindowsproc(window_handle, l_param): # noqa: CCR001 :param l_param: The second parameter to the EnumWindows() call. :return: False if we found a match, else True to continue iteration """ - # class name limited to 256 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms633576 - cls = create_unicode_buffer(257) # This conditional is exploded to make debugging slightly easier - if win32gui.GetClassName(window_handle, cls, 257): - if cls.value == 'TkTopLevel': - if window_title(window_handle) == applongname: - if GetProcessHandleFromHwnd(window_handle): - # If GetProcessHandleFromHwnd succeeds then the app is already running as this user - if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler_redirect): - CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) - # Wait for it to be responsive to avoid ShellExecute recursing - win32gui.ShowWindow(window_handle, win32con.SW_RESTORE) - win32api.ShellExecute(0, None, sys.argv[1], None, None, win32con.SW_RESTORE) - - else: - ShowWindowAsync(window_handle, win32con.SW_RESTORE) - win32gui.SetForegroundWindow(window_handle) - - return False # Indicate window found, so stop iterating + if win32gui.GetClassName(window_handle) == 'TkTopLevel': + if window_title(window_handle) == applongname: + if GetProcessHandleFromHwnd(window_handle): + # If GetProcessHandleFromHwnd succeeds then the app is already running as this user + if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler_redirect): + CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) + # Wait for it to be responsive to avoid ShellExecute recursing + win32gui.ShowWindow(window_handle, win32con.SW_RESTORE) + win32api.ShellExecute(0, None, sys.argv[1], None, None, win32con.SW_RESTORE) + + else: + ShowWindowAsync(window_handle, win32con.SW_RESTORE) + win32gui.SetForegroundWindow(window_handle) + + return False # Indicate window found, so stop iterating # Indicate that EnumWindows() needs to continue iterating return True # Do not remove, else this function as a callback breaks From d3fef97749eec753a128e79066668436bfd43138 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Mon, 22 Jul 2024 10:32:25 +0100 Subject: [PATCH 54/59] whoops --- myNotebook.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/myNotebook.py b/myNotebook.py index 3cee0b6d9..603f8c98b 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -121,9 +121,10 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): + warnings.warn('Migrate to ttk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) tk.Button.__init__(self, master, **kw) - warnings.warn('Migrate to ttk.Button. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) class Checkbutton(ttk.Checkbutton): From ee589e7db2a95af08b649f5e6d924b19ce71eea9 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 23 Jul 2024 15:09:41 +0100 Subject: [PATCH 55/59] Added gap for custom title bar under Transparent --- EDMarketConnector.py | 8 +++++--- theme.py | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 0eef0614c..9a8ab381b 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -611,12 +611,14 @@ def open_window(systray: 'SysTrayIcon', *args) -> None: self.theme_menubar = ttk.Frame(frame, name="alternate_menubar") self.theme_menubar.columnconfigure(2, weight=1) + self.title_gap = ttk.Frame(self.theme_menubar, name='title_gap') + self.title_gap.grid(row=0, columnspan=3) self.theme_file_menu = ttk.Menubutton(self.theme_menubar, menu=self.file_menu, style='Menubar.TMenubutton') - self.theme_file_menu.grid(row=0, column=0, padx=self.PADX, sticky=tk.W) + self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W) self.theme_edit_menu = ttk.Menubutton(self.theme_menubar, menu=self.edit_menu, style='Menubar.TMenubutton') - self.theme_edit_menu.grid(row=0, column=1, sticky=tk.W) + self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W) self.theme_help_menu = ttk.Menubutton(self.theme_menubar, menu=self.help_menu, style='Menubar.TMenubutton') - self.theme_help_menu.grid(row=0, column=2, sticky=tk.W) + self.theme_help_menu.grid(row=1, column=2, sticky=tk.W) ttk.Separator(self.theme_menubar).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) self.theme_menubar.grid(row=0, columnspan=2, sticky=tk.NSEW) self.w.resizable(tk.FALSE, tk.FALSE) diff --git a/theme.py b/theme.py index 016d803c6..4b127aa21 100644 --- a/theme.py +++ b/theme.py @@ -15,7 +15,7 @@ import warnings from tkinter import ttk from typing import Callable -from config import config +from config import appname, config from EDMCLogging import get_main_logger logger = get_main_logger() @@ -191,11 +191,14 @@ def apply(self) -> None: if sys.platform == 'win32': hwnd = win32gui.GetParent(self.root.winfo_id()) window = AppWindow.get_from_window_id(get_window_id_from_window(hwnd)) + title_gap: ttk.Frame = self.root.nametowidget(f'{appname.lower()}.alternate_menubar.title_gap') if theme == self.THEME_DEFAULT: window.title_bar.reset_to_default() + title_gap['height'] = 0 else: window.title_bar.extends_content_into_title_bar = True + title_gap['height'] = window.title_bar.height if theme == self.THEME_TRANSPARENT: win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, From 0ad93ad3a03992c1c45e658d1dfc615312c32a50 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 20 Aug 2024 17:55:08 +0100 Subject: [PATCH 56/59] Using PyWinRT visual layer --- prefs.py | 2 +- requirements-win.txt | 17 +++++++++++++++++ requirements.txt | 7 ------- theme.py | 24 ++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 requirements-win.txt diff --git a/prefs.py b/prefs.py index 0c7f156fc..6d9e015cb 100644 --- a/prefs.py +++ b/prefs.py @@ -1293,7 +1293,7 @@ def apply(self) -> None: # noqa: CCR001 config.set('dark_text', self.theme_colors[0]) config.set('dark_highlight', self.theme_colors[1]) theme.apply() - if self.plugdir.get() != config.get('plugin_dir'): + if self.plugdir.get() != config.get_str('plugin_dir'): config.set( 'plugin_dir', str(Path(config.home_path, self.plugdir.get()[2:])) if self.plugdir.get().startswith('~') else diff --git a/requirements-win.txt b/requirements-win.txt new file mode 100644 index 000000000..b87e41243 --- /dev/null +++ b/requirements-win.txt @@ -0,0 +1,17 @@ +simplesystray==0.1.0 +pywin32==306 + +winrt-Microsoft.UI==2.2.0 +winrt-Microsoft.UI.Interop==2.2.0 +winrt-Microsoft.UI.Windowing==2.2.0 +winrt-Microsoft.Windows.ApplicationModel.DynamicDependency==2.2.0 +winrt-Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap==2.2.0 +winrt-Windows.Foundation.Numerics==2.2.0 +winrt-Windows.System==2.2.0 +winrt-Windows.System.Interop==2.2.0 +winrt-Windows.UI==2.2.0 +winrt-Windows.UI.Composition==2.2.0 +winrt-Windows.UI.Composition.Desktop==2.2.0 +winrt-Windows.UI.Composition.Interop==2.2.0 + +-r requirements.txt diff --git a/requirements.txt b/requirements.txt index ba09a40ce..a1ef51c11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,5 @@ requests==2.32.3 pillow==10.3.0 watchdog==4.0.1 -simplesystray==0.1.0; sys_platform == 'win32' semantic-version==2.10.0 -# For manipulating folder permissions and the like. -pywin32==306; sys_platform == 'win32' psutil==5.9.8 -winrt-Microsoft.UI==2.1.0; sys_platform == 'win32' -winrt-Microsoft.UI.Interop==2.1.0; sys_platform == 'win32' -winrt-Microsoft.UI.Windowing==2.1.0; sys_platform == 'win32' -winrt-Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap==2.1.0.post1; sys_platform == 'win32' diff --git a/theme.py b/theme.py index 4b127aa21..5c4155f70 100644 --- a/theme.py +++ b/theme.py @@ -28,6 +28,11 @@ import win32gui from winrt.microsoft.ui.interop import get_window_id_from_window from winrt.microsoft.ui.windowing import AppWindow + from winrt.windows.foundation.numerics import Vector2, Vector3 + from winrt.windows.system.interop import create_dispatcher_queue_controller + from winrt.windows.ui import Color + from winrt.windows.ui.composition import Compositor, ContainerVisual + from winrt.windows.ui.composition.interop import create_desktop_window_target from ctypes import windll FR_PRIVATE = 0x10 fonts_loaded = windll.gdi32.AddFontResourceExW(str(config.respath_path / 'EUROCAPS.TTF'), FR_PRIVATE, 0) @@ -129,6 +134,9 @@ class _Theme: } style: ttk.Style root: tk.Tk + dispatcher = None + compositor = None + compositor_target = None def __init__(self) -> None: self.active: int | None = None # Starts out with no theme @@ -153,6 +161,15 @@ def initialize(self, root: tk.Tk): except tk.TclError: logger.exception(f'Failure loading theme package "{theme_file}"') + if sys.platform == 'win32': + self.dispatcher = create_dispatcher_queue_controller() + self.compositor = Compositor() + self.compositor_target = create_desktop_window_target(self.compositor, self.root.winfo_id()) + c_root = self.compositor.create_container_visual() + c_root.relative_size_adjustment = Vector2(1, 1) + c_root.offset = Vector3(0, 0, 0) + self.compositor_target.root = c_root + def register(self, widget: tk.Widget | tk.BitmapImage) -> None: assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget warnings.warn('theme.register() is no longer necessary as theme attributes are set on tk level', @@ -200,6 +217,13 @@ def apply(self) -> None: window.title_bar.extends_content_into_title_bar = True title_gap['height'] = window.title_bar.height + visuals = ContainerVisual._from(self.compositor_target.root).children + element = self.compositor.create_sprite_visual() + element.brush = self.compositor.create_color_brush(Color(255, 10, 10, 10)) + element.size = Vector2(self.root.winfo_width(), 48) + element.offset = Vector3(0, 0, 0) + visuals.insert_at_top(element) + if theme == self.THEME_TRANSPARENT: win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32con.WS_EX_APPWINDOW | win32con.WS_EX_LAYERED) # Add to taskbar From 5f444e7dd4119b3e6eba4d202bb90be88ca49165 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Tue, 20 Aug 2024 22:47:44 +0100 Subject: [PATCH 57/59] Post-merge ttk catalog fix --- plug.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plug.py b/plug.py index 3ac7b22d0..4194b9949 100644 --- a/plug.py +++ b/plug.py @@ -186,7 +186,7 @@ def _load_internal_plugins(): def _load_ttk_catalog_plugin(): try: - plugin = Plugin('ttk_catalog', os.path.join(config.internal_plugin_dir_path, '_ttk_catalog.py'), logger) + plugin = Plugin('ttk_catalog', config.internal_plugin_dir_path / '_ttk_catalog.py', logger) plugin.folder = None return plugin except Exception: From c4fab484e2a851b88da9255976d1c515f5fa4fc2 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Wed, 21 Aug 2024 19:05:36 +0100 Subject: [PATCH 58/59] Some simplification and making mypy happier --- plugins/_ttk_catalog.py | 15 --------------- theme.py | 8 +++++--- ttkHyperlinkLabel.py | 15 +++++---------- 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/plugins/_ttk_catalog.py b/plugins/_ttk_catalog.py index 71cc3268f..a0b016067 100644 --- a/plugins/_ttk_catalog.py +++ b/plugins/_ttk_catalog.py @@ -312,21 +312,6 @@ def setup_widgets(self) -> None: tk_button = tk.Button(tk_widgets_frame, text="tk.Button") tk_button.grid(row=6, column=0, padx=5, pady=10, sticky="nsew") - tk_hyperlink_frame = tk.LabelFrame(self, text="HyperlinkLabel (legacy)", padx=20, pady=10) - tk_hyperlink_frame.grid(row=2, column=4, padx=10, pady=10, sticky="nsew") - - tk_hyperlink_1 = HyperlinkLabel(tk_hyperlink_frame, text="Default", url=URL) - tk_hyperlink_1.grid(row=0, column=0, sticky="nsew") - - tk_hyperlink_2 = HyperlinkLabel(tk_hyperlink_frame, text="Underline", url=URL, underline=True) - tk_hyperlink_2.grid(row=1, column=0, sticky="nsew") - - tk_hyperlink_3 = HyperlinkLabel(tk_hyperlink_frame, text="No underline", url=URL, underline=False) - tk_hyperlink_3.grid(row=2, column=0, sticky="nsew") - - tk_hyperlink_4 = HyperlinkLabel(tk_hyperlink_frame, text="Disabled", url=URL, state=tk.DISABLED) - tk_hyperlink_4.grid(row=3, column=0, sticky="nsew") - def plugin_start3(path: str) -> str: """Plugin initialization.""" diff --git a/theme.py b/theme.py index 5c4155f70..12704be67 100644 --- a/theme.py +++ b/theme.py @@ -29,9 +29,11 @@ from winrt.microsoft.ui.interop import get_window_id_from_window from winrt.microsoft.ui.windowing import AppWindow from winrt.windows.foundation.numerics import Vector2, Vector3 + from winrt.windows.system import DispatcherQueueController from winrt.windows.system.interop import create_dispatcher_queue_controller from winrt.windows.ui import Color from winrt.windows.ui.composition import Compositor, ContainerVisual + from winrt.windows.ui.composition.desktop import DesktopWindowTarget from winrt.windows.ui.composition.interop import create_desktop_window_target from ctypes import windll FR_PRIVATE = 0x10 @@ -134,9 +136,9 @@ class _Theme: } style: ttk.Style root: tk.Tk - dispatcher = None - compositor = None - compositor_target = None + dispatcher: DispatcherQueueController + compositor: Compositor + compositor_target: DesktopWindowTarget def __init__(self) -> None: self.active: int | None = None # Starts out with no theme diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index c233f9d63..c2c376991 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -78,15 +78,10 @@ def _handle_legacy_options(self, options: dict): # noqa: CCR001 else: font_n = ttk.Style().lookup('Link.TLabel', 'font') font_u = ttk.Style().lookup('Link.TLabel', 'font', ['active']) - if self.underline is None: - ttk.Style().configure(self._legacy_style, font=font_n) - ttk.Style().map(self._legacy_style, font=[('active', font_u)]) - elif self.underline: - ttk.Style().configure(self._legacy_style, font=font_u) - ttk.Style().map(self._legacy_style, font=[('active', font_u)]) - else: - ttk.Style().configure(self._legacy_style, font=font_n) - ttk.Style().map(self._legacy_style, font=[('active', font_n)]) + font_default = font_u if self.underline else font_n + font_active = font_n if self.underline is False else font_u + ttk.Style().configure(self._legacy_style, font=font_default) + ttk.Style().map(self._legacy_style, font=[('active', font_active)]) # TODO emulate justify and wraplength options['style'] = self._legacy_style return options @@ -230,4 +225,4 @@ def copy(self) -> None: def copy_slef(self) -> None: """Copy the current text to the clipboard.""" self.clipboard_clear() - self.clipboard_append(monitor.slef) + self.clipboard_append(monitor.slef or '') From 9f87cd61ccf13d4b13bd3c4f51b54718299df9cd Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Wed, 21 Aug 2024 20:40:03 +0100 Subject: [PATCH 59/59] Ditched visual layer + setting Windows caption button colors --- EDMarketConnector.py | 18 ++------------- theme.py | 54 +++++++++++++++++++++++--------------------- 2 files changed, 30 insertions(+), 42 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index a7fc52c83..761c1e8b8 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -644,10 +644,6 @@ def open_window(systray: 'SysTrayIcon', *args) -> None: self.w.attributes('-topmost', config.get_int('always_ontop') and 1 or 0) - self.w.bind('', self.onenter) # Special handling for transparency - self.w.bind('', self.onenter) # Special handling for transparency - self.w.bind('', self.onleave) # Special handling for transparency - self.w.bind('', self.onleave) # Special handling for transparency self.w.bind('', self.capi_request_data) self.w.bind('', self.capi_request_data) self.w.bind_all('<>', self.capi_request_data) # Ask for CAPI queries to be performed @@ -1856,16 +1852,6 @@ def default_iconify(self, event=None) -> None: if str(event.widget) == '.': self.w.withdraw() - def onenter(self, event=None) -> None: - """Handle when our window gains focus.""" - if config.get_int('theme') == theme.THEME_TRANSPARENT: - self.w.attributes("-transparentcolor", '') - - def onleave(self, event=None) -> None: - """Handle when our window loses focus.""" - if config.get_int('theme') == theme.THEME_TRANSPARENT and event.widget == self.w: - self.w.attributes("-transparentcolor", 'grey4') - def test_logging() -> None: """Simple test of top level logging.""" @@ -1892,7 +1878,7 @@ def setup_killswitches(filename: str | None): killswitch.setup_main_list(filename) -def show_killswitch_poppup(root=None): +def show_killswitch_poppup(root: tk.Tk): """Show a warning popup if there are any killswitches that match the current version.""" if len(kills := killswitch.kills_for_version()) == 0: return @@ -1928,7 +1914,7 @@ def show_killswitch_poppup(root=None): ok_button.grid(columnspan=2, sticky=tk.EW) -def validate_providers(root): +def validate_providers(root: tk.Tk): """Check if Config has an invalid provider set, and reset to default if we do.""" reset_providers = {} station_provider: str = config.get_str("station_provider") diff --git a/theme.py b/theme.py index 12704be67..cf919325c 100644 --- a/theme.py +++ b/theme.py @@ -28,13 +28,7 @@ import win32gui from winrt.microsoft.ui.interop import get_window_id_from_window from winrt.microsoft.ui.windowing import AppWindow - from winrt.windows.foundation.numerics import Vector2, Vector3 - from winrt.windows.system import DispatcherQueueController - from winrt.windows.system.interop import create_dispatcher_queue_controller - from winrt.windows.ui import Color - from winrt.windows.ui.composition import Compositor, ContainerVisual - from winrt.windows.ui.composition.desktop import DesktopWindowTarget - from winrt.windows.ui.composition.interop import create_desktop_window_target + from winrt.windows.ui import Color, Colors from ctypes import windll FR_PRIVATE = 0x10 fonts_loaded = windll.gdi32.AddFontResourceExW(str(config.respath_path / 'EUROCAPS.TTF'), FR_PRIVATE, 0) @@ -136,9 +130,7 @@ class _Theme: } style: ttk.Style root: tk.Tk - dispatcher: DispatcherQueueController - compositor: Compositor - compositor_target: DesktopWindowTarget + binds: dict[str, str] = {} def __init__(self) -> None: self.active: int | None = None # Starts out with no theme @@ -163,15 +155,6 @@ def initialize(self, root: tk.Tk): except tk.TclError: logger.exception(f'Failure loading theme package "{theme_file}"') - if sys.platform == 'win32': - self.dispatcher = create_dispatcher_queue_controller() - self.compositor = Compositor() - self.compositor_target = create_desktop_window_target(self.compositor, self.root.winfo_id()) - c_root = self.compositor.create_container_visual() - c_root.relative_size_adjustment = Vector2(1, 1) - c_root.offset = Vector3(0, 0, 0) - self.compositor_target.root = c_root - def register(self, widget: tk.Widget | tk.BitmapImage) -> None: assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget warnings.warn('theme.register() is no longer necessary as theme attributes are set on tk level', @@ -194,6 +177,23 @@ def update(self, widget: tk.Widget) -> None: warnings.warn('theme.update() is no longer necessary as theme attributes are set on tk level', DeprecationWarning, stacklevel=2) + def transparent_onenter(self, event=None): + self.root.attributes("-transparentcolor", '') + if sys.platform == 'win32': + self.set_title_buttons_background(Color(255, 10, 10, 10)) + + def transparent_onleave(self, event=None): + if event.widget == self.root: + self.root.attributes("-transparentcolor", 'grey4') + if sys.platform == 'win32': + self.set_title_buttons_background(Colors.transparent) + + def set_title_buttons_background(self, color: Color): + hwnd = win32gui.GetParent(self.root.winfo_id()) + window = AppWindow.get_from_window_id(get_window_id_from_window(hwnd)) + window.title_bar.button_background_color = color + window.title_bar.button_inactive_background_color = color + def apply(self) -> None: theme = config.get_int('theme') try: @@ -217,20 +217,22 @@ def apply(self) -> None: title_gap['height'] = 0 else: window.title_bar.extends_content_into_title_bar = True + self.set_title_buttons_background(Color(255, 10, 10, 10)) title_gap['height'] = window.title_bar.height - visuals = ContainerVisual._from(self.compositor_target.root).children - element = self.compositor.create_sprite_visual() - element.brush = self.compositor.create_color_brush(Color(255, 10, 10, 10)) - element.size = Vector2(self.root.winfo_width(), 48) - element.offset = Vector3(0, 0, 0) - visuals.insert_at_top(element) - if theme == self.THEME_TRANSPARENT: + # TODO prevent loss of focus when hovering the title bar area win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32con.WS_EX_APPWINDOW | win32con.WS_EX_LAYERED) # Add to taskbar + self.binds[''] = self.root.bind('', self.transparent_onenter) + self.binds[''] = self.root.bind('', self.transparent_onenter) + self.binds[''] = self.root.bind('', self.transparent_onleave) + self.binds[''] = self.root.bind('', self.transparent_onleave) else: win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32con.WS_EX_APPWINDOW) # Add to taskbar + for event, bind in self.binds.items(): + self.root.unbind(event, bind) + self.binds.clear() else: if dpy: xroot = Window()