Skip to content

Commit

Permalink
Merge pull request #1 from ElSaico/ttk
Browse files Browse the repository at this point in the history
* Excluded ttk widgets from theme.theme

* Changed all main window widgets to ttk

* Fixed type signatures caught up by mypy

* As I was saying, fixed type signatures...

* meh

* We don't need to actually change the killswitch

* Added JSON theme

* Simplified style, fixed HyperlinkLabel underline font

* Set baseline for themes

* Integrated ttk themes in theme.apply()

* oops

* Leftover import

* Applying theme palette to Tk widgets more reliably

* Caught a plugin using theme.register() directly, so reinstated as stub

* Added Ttk catalog plugin (enabled by flag)

* Fixed TTK Catalog positioning

* Ttk catalog: replaced alternative styles with HyperlinkLabel frame

* Nicer labels

* 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

* Added Tk widgets to Ttk catalog for comparison

* Theme in pure Tcl

* Fixed theme font

* Ttk catalog: properly labeled Tk widgets

* Added default theme; final touches in dark?

* flake8

* mypy

* Themes as tcl packages, deprecated most of myNotebook

* HyperlinkLabel is a Button. Obviously.

* Fixed underline font

* A possible solution for HyperlinkLabel on the way?

* Dropped most of myNotebook from core plugins (except for EntryMenu)

* HyperlinkLabel seems to work now

* Dropped unnecessary tk call

* Dropping justify config in HyperlinkLabel

* 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

* Added check to whether Euro Caps actually loads on Windows

* Some words of encouragement

* Better deprecation notices on theme.register() and theme.update()

* Something to the logs

* Proper theme/myNotebook deprecation warnings

* We don't need most of nb now, so...

* Dropped ttk::theme lookup on theme files

* Dropped post-merge repetition

* Dropped alternative title bar and update button

* Using native dark mode title bar on Windows

* Removed widget switcheroo, all themes use the same menu

* Style tweaks

* Customizing title bar via PyWinRT

Also updated theme.py to pywin32 ahead of EDCD#2263, in order to minimize the merging mess

* Import shenanigans now that pywinrt works

* We no longer need those drag events

* Bump WinRT dependency (as it missed a DLL)

* Post-merge clarity

* Fixed pywin32 call

* whoops

* Added gap for custom title bar under Transparent

* Using PyWinRT visual layer

* Post-merge ttk catalog fix

* Some simplification and making mypy happier

* Ditched visual layer + setting Windows caption button colors

---------

yoink
  • Loading branch information
Balvald authored Feb 7, 2025
2 parents 11d4c0c + 9f87cd6 commit 2763263
Show file tree
Hide file tree
Showing 24 changed files with 1,111 additions and 800 deletions.
271 changes: 81 additions & 190 deletions EDMarketConnector.py

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ class AbstractConfig(abc.ABC):
plugin_dir_path: pathlib.Path
default_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
Expand All @@ -201,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()
Expand Down Expand Up @@ -244,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
Expand Down
1 change: 1 addition & 0 deletions config/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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?

Expand Down
4 changes: 2 additions & 2 deletions config/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,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()

Expand Down
25 changes: 13 additions & 12 deletions docs/examples/click_counter/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand All @@ -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.
Expand All @@ -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


Expand All @@ -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.
Expand All @@ -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.
Expand Down
64 changes: 16 additions & 48 deletions myNotebook.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,43 @@
"""
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

import sys
import tkinter as tk
import warnings
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."""

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)
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."""

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
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."""

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)
warnings.warn('Migrate to ttk.Label. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2)


class EntryMenu(ttk.Entry):
Expand Down Expand Up @@ -135,49 +114,38 @@ class Button(ttk.Button):
"""Custom ttk.Button class to fix some display issues."""

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)
warnings.warn('Migrate to ttk.Button. Will remove in 6.0 or later', DeprecationWarning, stacklevel=2)
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):
warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
warnings.warn('Migrate to ttk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
tk.Button.__init__(self, master, **kw)


class Checkbutton(ttk.Checkbutton):
"""Custom ttk.Checkbutton class to fix some display issues."""

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
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."""

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
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."""

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)
warnings.warn('Migrate to ttk.OptionMenu. Will be removed in 6.0 or later', DeprecationWarning, stacklevel=2)
25 changes: 18 additions & 7 deletions plug.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from __future__ import annotations

import copy
import importlib
import importlib.util
import logging
import operator
import os
Expand All @@ -19,7 +19,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

Expand Down Expand Up @@ -131,21 +130,21 @@ 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.
:param parent: the parent frame for this preference tab.
: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:
Expand All @@ -163,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():
Expand All @@ -182,6 +184,15 @@ def _load_internal_plugins():
return internal


def _load_ttk_catalog_plugin():
try:
plugin = Plugin('ttk_catalog', config.internal_plugin_dir_path / '_ttk_catalog.py', logger)
plugin.folder = None
return plugin
except Exception:
logger.exception('Failure loading internal Plugin "ttk_catalog"')


def _load_found_plugins():
found = []
# Load any plugins that are also packages first, but note it's *still*
Expand Down
Loading

0 comments on commit 2763263

Please sign in to comment.