1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-13 07:47:14 +03:00

Update to use sys.platform over platform

This commit is contained in:
A_D 2022-01-24 13:54:04 +02:00
parent 86292e02e2
commit bff6175ee7
No known key found for this signature in database
GPG Key ID: 4BE9EB7DF45076C4
10 changed files with 267 additions and 253 deletions

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Entry point for the main GUI application.""" """Entry point for the main GUI application."""
from __future__ import annotations
import argparse import argparse
import html import html
@ -14,7 +15,6 @@ import webbrowser
from builtins import object, str from builtins import object, str
from os import chdir, environ from os import chdir, environ
from os.path import dirname, join from os.path import dirname, join
from sys import platform
from time import localtime, strftime, time from time import localtime, strftime, time
from typing import TYPE_CHECKING, Optional, Tuple, Union from typing import TYPE_CHECKING, Optional, Tuple, Union
@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Optional, Tuple, Union
# place for things like config.py reading .gitversion # place for things like config.py reading .gitversion
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
# Under py2exe sys.path[0] is the executable name # Under py2exe sys.path[0] is the executable name
if platform == 'win32': if sys.platform == 'win32':
chdir(dirname(sys.path[0])) chdir(dirname(sys.path[0]))
# Allow executable to be invoked from any cwd # Allow executable to be invoked from any cwd
environ['TCL_LIBRARY'] = join(dirname(sys.path[0]), 'lib', 'tcl') environ['TCL_LIBRARY'] = join(dirname(sys.path[0]), 'lib', 'tcl')
@ -234,7 +234,7 @@ if __name__ == '__main__': # noqa: C901
"""Handle any edmc:// auth callback, else foreground existing window.""" """Handle any edmc:// auth callback, else foreground existing window."""
logger.trace_if('frontier-auth.windows', 'Begin...') logger.trace_if('frontier-auth.windows', 'Begin...')
if platform == 'win32': if sys.platform == 'win32':
# If *this* instance hasn't locked, then another already has and we # If *this* instance hasn't locked, then another already has and we
# now need to do the edmc:// checks for auth callback # now need to do the edmc:// checks for auth callback
@ -370,8 +370,9 @@ if __name__ == '__main__': # noqa: C901
# isort: off # isort: off
if TYPE_CHECKING: if TYPE_CHECKING:
from logging import TRACE # type: ignore # noqa: F401 # Needed to update mypy from logging import TRACE # type: ignore # noqa: F401 # Needed to update mypy
import update
from infi.systray import SysTrayIcon if sys.platform == 'win32':
from infi.systray import SysTrayIcon
# isort: on # isort: on
def _(x: str) -> str: def _(x: str) -> str:
@ -443,7 +444,7 @@ class AppWindow(object):
self.prefsdialog = None self.prefsdialog = None
if platform == 'win32': if sys.platform == 'win32':
from infi.systray import SysTrayIcon from infi.systray import SysTrayIcon
def open_window(systray: 'SysTrayIcon') -> None: def open_window(systray: 'SysTrayIcon') -> None:
@ -456,8 +457,8 @@ class AppWindow(object):
plug.load_plugins(master) plug.load_plugins(master)
if platform != 'darwin': if sys.platform != 'darwin':
if platform == 'win32': if sys.platform == 'win32':
self.w.wm_iconbitmap(default='EDMarketConnector.ico') self.w.wm_iconbitmap(default='EDMarketConnector.ico')
else: else:
@ -527,7 +528,7 @@ class AppWindow(object):
# LANG: Update button in main window # LANG: Update button in main window
self.button = ttk.Button(frame, text=_('Update'), width=28, default=tk.ACTIVE, state=tk.DISABLED) self.button = ttk.Button(frame, text=_('Update'), width=28, default=tk.ACTIVE, state=tk.DISABLED)
self.theme_button = tk.Label(frame, width=32 if platform == 'darwin' else 28, state=tk.DISABLED) self.theme_button = tk.Label(frame, width=32 if sys.platform == 'darwin' else 28, state=tk.DISABLED)
self.status = tk.Label(frame, name='status', anchor=tk.W) self.status = tk.Label(frame, name='status', anchor=tk.W)
ui_row = frame.grid_size()[1] ui_row = frame.grid_size()[1]
@ -540,14 +541,15 @@ class AppWindow(object):
theme.button_bind(self.theme_button, self.capi_request_data) theme.button_bind(self.theme_button, self.capi_request_data)
for child in frame.winfo_children(): for child in frame.winfo_children():
child.grid_configure(padx=self.PADX, pady=(platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0) child.grid_configure(padx=self.PADX, pady=(
sys.platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0)
# The type needs defining for adding the menu entry, but won't be # The type needs defining for adding the menu entry, but won't be
# properly set until later # properly set until later
self.updater: update.Updater = None self.updater: update.Updater = None
self.menubar = tk.Menu() self.menubar = tk.Menu()
if platform == 'darwin': if sys.platform == 'darwin':
# Can't handle (de)iconify if topmost is set, so suppress iconify button # Can't handle (de)iconify if topmost is set, so suppress iconify button
# http://wiki.tcl.tk/13428 and p15 of # http://wiki.tcl.tk/13428 and p15 of
# https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf # https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf
@ -603,7 +605,7 @@ class AppWindow(object):
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w)) self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
self.menubar.add_cascade(menu=self.help_menu) self.menubar.add_cascade(menu=self.help_menu)
if platform == 'win32': if sys.platform == 'win32':
# Must be added after at least one "real" menu entry # Must be added after at least one "real" menu entry
self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) 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.menubar, name='system', tearoff=tk.FALSE)
@ -674,11 +676,11 @@ class AppWindow(object):
if config.get_str('geometry'): if config.get_str('geometry'):
match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry')) match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry'))
if match: if match:
if platform == 'darwin': if sys.platform == 'darwin':
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
if int(match.group(2)) >= 0: if int(match.group(2)) >= 0:
self.w.geometry(config.get_str('geometry')) self.w.geometry(config.get_str('geometry'))
elif platform == 'win32': elif sys.platform == 'win32':
# Check that the titlebar will be at least partly on screen # Check that the titlebar will be at least partly on screen
import ctypes import ctypes
from ctypes.wintypes import POINT from ctypes.wintypes import POINT
@ -776,7 +778,7 @@ class AppWindow(object):
self.suit_shown = True self.suit_shown = True
if not self.suit_shown: if not self.suit_shown:
if platform != 'win32': if sys.platform != 'win32':
pady = 2 pady = 2
else: else:
@ -826,7 +828,7 @@ class AppWindow(object):
self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI
self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI
self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window
if platform == 'darwin': if sys.platform == 'darwin':
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title on OSX self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title on OSX
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title on OSX self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title on OSX
self.menubar.entryconfigure(3, label=_('View')) # LANG: 'View' menu title on OSX self.menubar.entryconfigure(3, label=_('View')) # LANG: 'View' menu title on OSX
@ -873,7 +875,7 @@ class AppWindow(object):
self.button['state'] = self.theme_button['state'] = tk.DISABLED self.button['state'] = self.theme_button['state'] = tk.DISABLED
if platform == 'darwin': if sys.platform == 'darwin':
self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status
self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data
@ -887,7 +889,7 @@ class AppWindow(object):
# LANG: Successfully authenticated with the Frontier website # LANG: Successfully authenticated with the Frontier website
self.status['text'] = _('Authentication successful') self.status['text'] = _('Authentication successful')
if platform == 'darwin': if sys.platform == 'darwin':
self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data
@ -1211,7 +1213,7 @@ class AppWindow(object):
companion.session.invalidate() companion.session.invalidate()
self.login() self.login()
except companion.ServerConnectionError as e: except companion.ServerConnectionError as e: # TODO: unreachable (subclass of ServerLagging -- move to above)
logger.warning(f'Exception while contacting server: {e}') logger.warning(f'Exception while contacting server: {e}')
err = self.status['text'] = str(e) err = self.status['text'] = str(e)
play_bad = True play_bad = True
@ -1429,7 +1431,7 @@ class AppWindow(object):
companion.session.auth_callback() companion.session.auth_callback()
# LANG: Successfully authenticated with the Frontier website # LANG: Successfully authenticated with the Frontier website
self.status['text'] = _('Authentication successful') self.status['text'] = _('Authentication successful')
if platform == 'darwin': if sys.platform == 'darwin':
self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data
@ -1570,11 +1572,11 @@ class AppWindow(object):
# position over parent # position over parent
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
if platform != 'darwin' or parent.winfo_rooty() > 0: if sys.platform != 'darwin' or parent.winfo_rooty() > 0:
self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}') self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
# remove decoration # remove decoration
if platform == 'win32': if sys.platform == 'win32':
self.attributes('-toolwindow', tk.TRUE) self.attributes('-toolwindow', tk.TRUE)
self.resizable(tk.FALSE, tk.FALSE) self.resizable(tk.FALSE, tk.FALSE)
@ -1651,7 +1653,7 @@ class AppWindow(object):
""" """
default_extension: str = '' default_extension: str = ''
if platform == 'darwin': if sys.platform == 'darwin':
default_extension = '.json' default_extension = '.json'
timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime()) timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime())
@ -1676,7 +1678,7 @@ class AppWindow(object):
def onexit(self, event=None) -> None: def onexit(self, event=None) -> None:
"""Application shutdown procedure.""" """Application shutdown procedure."""
if platform == 'win32': if sys.platform == 'win32':
shutdown_thread = threading.Thread(target=self.systray.shutdown) shutdown_thread = threading.Thread(target=self.systray.shutdown)
shutdown_thread.setDaemon(True) shutdown_thread.setDaemon(True)
shutdown_thread.start() shutdown_thread.start()
@ -1684,7 +1686,7 @@ class AppWindow(object):
config.set_shutdown() # Signal we're in shutdown now. config.set_shutdown() # Signal we're in shutdown now.
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
if platform != 'darwin' or self.w.winfo_rooty() > 0: if sys.platform != 'darwin' or self.w.winfo_rooty() > 0:
x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267' x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267'
config.set('geometry', f'+{x}+{y}') config.set('geometry', f'+{x}+{y}')

View File

@ -2,11 +2,11 @@
import json import json
import pathlib import pathlib
import sys
import time import time
import tkinter as tk import tkinter as tk
from calendar import timegm from calendar import timegm
from os.path import getsize, isdir, isfile from os.path import getsize, isdir, isfile
from sys import platform
from typing import Any, Dict from typing import Any, Dict
from config import config from config import config
@ -14,11 +14,11 @@ from EDMCLogging import get_main_logger
logger = get_main_logger() logger = get_main_logger()
if platform == 'darwin': if sys.platform == 'darwin':
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer from watchdog.observers import Observer
elif platform == 'win32': elif sys.platform == 'win32':
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer from watchdog.observers import Observer
@ -71,26 +71,25 @@ class Dashboard(FileSystemEventHandler):
# File system events are unreliable/non-existent over network drives on Linux. # File system events are unreliable/non-existent over network drives on Linux.
# We can't easily tell whether a path points to a network drive, so assume # We can't easily tell whether a path points to a network drive, so assume
# any non-standard logdir might be on a network drive and poll instead. # any non-standard logdir might be on a network drive and poll instead.
polling = platform != 'win32' if not (sys.platform != 'win32') and not self.observer:
if not polling and not self.observer:
logger.debug('Setting up observer...') logger.debug('Setting up observer...')
self.observer = Observer() self.observer = Observer()
self.observer.daemon = True self.observer.daemon = True
self.observer.start() self.observer.start()
logger.debug('Done') logger.debug('Done')
elif polling and self.observer: elif (sys.platform != 'win32') and self.observer:
logger.debug('Using polling, stopping observer...') logger.debug('Using polling, stopping observer...')
self.observer.stop() self.observer.stop()
self.observer = None # type: ignore self.observer = None # type: ignore
logger.debug('Done') logger.debug('Done')
if not self.observed and not polling: if not self.observed and not (sys.platform != 'win32'):
logger.debug('Starting observer...') logger.debug('Starting observer...')
self.observed = self.observer.schedule(self, self.currentdir) self.observed = self.observer.schedule(self, self.currentdir)
logger.debug('Done') logger.debug('Done')
logger.info(f'{polling and "Polling" or "Monitoring"} Dashboard "{self.currentdir}"') logger.info(f'{(sys.platform != "win32") and "Polling" or "Monitoring"} Dashboard "{self.currentdir}"')
# Even if we're not intending to poll, poll at least once to process pre-existing # Even if we're not intending to poll, poll at least once to process pre-existing
# data and to check whether the watchdog thread has crashed due to events not # data and to check whether the watchdog thread has crashed due to events not

View File

@ -1,10 +1,10 @@
"""Implements locking of Journal directory.""" """Implements locking of Journal directory."""
import pathlib import pathlib
import sys
import tkinter as tk import tkinter as tk
from enum import Enum from enum import Enum
from os import getpid as os_getpid from os import getpid as os_getpid
from sys import platform
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Callable, Optional from typing import TYPE_CHECKING, Callable, Optional
@ -94,7 +94,7 @@ class JournalLock:
:return: LockResult - See the class Enum definition :return: LockResult - See the class Enum definition
""" """
if platform == 'win32': if sys.platform == 'win32':
logger.trace_if('journal-lock', 'win32, using msvcrt') logger.trace_if('journal-lock', 'win32, using msvcrt')
# win32 doesn't have fcntl, so we have to use msvcrt # win32 doesn't have fcntl, so we have to use msvcrt
import msvcrt import msvcrt
@ -143,7 +143,7 @@ class JournalLock:
return True # We weren't locked, and still aren't return True # We weren't locked, and still aren't
unlocked = False unlocked = False
if platform == 'win32': if sys.platform == 'win32':
logger.trace_if('journal-lock', 'win32, using msvcrt') logger.trace_if('journal-lock', 'win32, using msvcrt')
# win32 doesn't have fcntl, so we have to use msvcrt # win32 doesn't have fcntl, so we have to use msvcrt
import msvcrt import msvcrt
@ -206,10 +206,10 @@ class JournalLock:
self.title(_('Journal directory already locked')) self.title(_('Journal directory already locked'))
# remove decoration # remove decoration
if platform == 'win32': if sys.platform == 'win32':
self.attributes('-toolwindow', tk.TRUE) self.attributes('-toolwindow', tk.TRUE)
elif platform == 'darwin': elif sys.platform == 'darwin':
# http://wiki.tcl.tk/13428 # http://wiki.tcl.tk/13428
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')

21
l10n.py
View File

@ -12,7 +12,6 @@ import warnings
from collections import OrderedDict from collections import OrderedDict
from contextlib import suppress from contextlib import suppress
from os.path import basename, dirname, isdir, isfile, join from os.path import basename, dirname, isdir, isfile, join
from sys import platform
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Set, TextIO, Union, cast from typing import TYPE_CHECKING, Dict, Iterable, Optional, Set, TextIO, Union, cast
if TYPE_CHECKING: if TYPE_CHECKING:
@ -37,12 +36,12 @@ LANGUAGE_ID = '!Language'
LOCALISATION_DIR = 'L10n' LOCALISATION_DIR = 'L10n'
if platform == 'darwin': if sys.platform == 'darwin':
from Foundation import ( # type: ignore # exists on Darwin from Foundation import ( # type: ignore # exists on Darwin
NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle
) )
elif platform == 'win32': elif sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR
if TYPE_CHECKING: if TYPE_CHECKING:
@ -176,7 +175,7 @@ class _Translations:
def available(self) -> Set[str]: def available(self) -> Set[str]:
"""Return a list of available language codes.""" """Return a list of available language codes."""
path = self.respath() path = self.respath()
if getattr(sys, 'frozen', False) and platform == 'darwin': if getattr(sys, 'frozen', False) and sys.platform == 'darwin':
available = { available = {
x[:-len('.lproj')] for x in os.listdir(path) x[:-len('.lproj')] for x in os.listdir(path)
if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings')) if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))
@ -204,7 +203,7 @@ class _Translations:
def respath(self) -> pathlib.Path: def respath(self) -> pathlib.Path:
"""Path to localisation files.""" """Path to localisation files."""
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
if platform == 'darwin': if sys.platform == 'darwin':
return (pathlib.Path(sys.executable).parents[0] / os.pardir / 'Resources').resolve() return (pathlib.Path(sys.executable).parents[0] / os.pardir / 'Resources').resolve()
return pathlib.Path(dirname(sys.executable)) / LOCALISATION_DIR return pathlib.Path(dirname(sys.executable)) / LOCALISATION_DIR
@ -233,7 +232,7 @@ class _Translations:
except OSError: except OSError:
logger.exception(f'could not open {f}') logger.exception(f'could not open {f}')
elif getattr(sys, 'frozen', False) and platform == 'darwin': elif getattr(sys, 'frozen', False) and sys.platform == 'darwin':
return (self.respath() / f'{lang}.lproj' / 'Localizable.strings').open('r', encoding='utf-16') return (self.respath() / f'{lang}.lproj' / 'Localizable.strings').open('r', encoding='utf-16')
return (self.respath() / f'{lang}.strings').open('r', encoding='utf-8') return (self.respath() / f'{lang}.strings').open('r', encoding='utf-8')
@ -243,7 +242,7 @@ class _Locale:
"""Locale holds a few utility methods to convert data to and from localized versions.""" """Locale holds a few utility methods to convert data to and from localized versions."""
def __init__(self) -> None: def __init__(self) -> None:
if platform == 'darwin': if sys.platform == 'darwin':
self.int_formatter = NSNumberFormatter.alloc().init() self.int_formatter = NSNumberFormatter.alloc().init()
self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle) self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
self.float_formatter = NSNumberFormatter.alloc().init() self.float_formatter = NSNumberFormatter.alloc().init()
@ -276,7 +275,7 @@ class _Locale:
if decimals == 0 and not isinstance(number, numbers.Integral): if decimals == 0 and not isinstance(number, numbers.Integral):
number = int(round(number)) number = int(round(number))
if platform == 'darwin': if sys.platform == 'darwin':
if not decimals and isinstance(number, numbers.Integral): if not decimals and isinstance(number, numbers.Integral):
return self.int_formatter.stringFromNumber_(number) return self.int_formatter.stringFromNumber_(number)
@ -298,7 +297,7 @@ class _Locale:
:param string: The string to convert :param string: The string to convert
:return: None if the string cannot be parsed, otherwise an int or float dependant on input data. :return: None if the string cannot be parsed, otherwise an int or float dependant on input data.
""" """
if platform == 'darwin': if sys.platform == 'darwin':
return self.float_formatter.numberFromString_(string) return self.float_formatter.numberFromString_(string)
with suppress(ValueError): with suppress(ValueError):
@ -321,10 +320,10 @@ class _Locale:
:return: The preferred language list :return: The preferred language list
""" """
languages: Iterable[str] languages: Iterable[str]
if platform == 'darwin': if sys.platform == 'darwin':
languages = NSLocale.preferredLanguages() languages = NSLocale.preferredLanguages()
elif platform != 'win32': elif sys.platform != 'win32':
# POSIX # POSIX
lang = locale.getlocale()[0] lang = locale.getlocale()[0]
languages = lang and [lang.replace('_', '-')] or [] languages = lang and [lang.replace('_', '-')] or []

View File

@ -4,22 +4,21 @@
# - OSX: page background should be a darker gray than systemWindowBody # - OSX: page background should be a darker gray than systemWindowBody
# selected tab foreground should be White when the window is active # selected tab foreground should be White when the window is active
# #
import sys
from sys import platform
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
# Entire file may be imported by plugins # Entire file may be imported by plugins
# Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult # Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult
if platform == 'darwin': if sys.platform == 'darwin':
from platform import mac_ver from platform import mac_ver
PAGEFG = 'systemButtonText' PAGEFG = 'systemButtonText'
PAGEBG = 'systemButtonActiveDarkShadow' PAGEBG = 'systemButtonActiveDarkShadow'
elif platform == 'win32':
elif sys.platform == 'win32':
PAGEFG = 'SystemWindowText' PAGEFG = 'SystemWindowText'
PAGEBG = 'SystemWindow' # typically white PAGEBG = 'SystemWindow' # typically white
class Notebook(ttk.Notebook): class Notebook(ttk.Notebook):
@ -29,14 +28,14 @@ class Notebook(ttk.Notebook):
ttk.Notebook.__init__(self, master, **kw) ttk.Notebook.__init__(self, master, **kw)
style = ttk.Style() style = ttk.Style()
if platform=='darwin': if sys.platform == 'darwin':
if list(map(int, mac_ver()[0].split('.'))) >= [10,10]: if list(map(int, mac_ver()[0].split('.'))) >= [10, 10]:
# Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see # Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see
# https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25 # https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25
style.configure('TNotebook.Tab', padding=(12,10,12,2)) style.configure('TNotebook.Tab', padding=(12, 10, 12, 2))
style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')]) style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')])
self.grid(sticky=tk.NSEW) # Already padded apropriately self.grid(sticky=tk.NSEW) # Already padded apropriately
elif platform == 'win32': elif sys.platform == 'win32':
style.configure('nb.TFrame', background=PAGEBG) style.configure('nb.TFrame', background=PAGEBG)
style.configure('nb.TButton', background=PAGEBG) style.configure('nb.TButton', background=PAGEBG)
style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
@ -47,56 +46,60 @@ class Notebook(ttk.Notebook):
self.grid(padx=10, pady=10, sticky=tk.NSEW) self.grid(padx=10, pady=10, sticky=tk.NSEW)
class Frame(platform == 'darwin' and tk.Frame or ttk.Frame): class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
if platform == 'darwin': if sys.platform == 'darwin':
kw['background'] = kw.pop('background', PAGEBG) kw['background'] = kw.pop('background', PAGEBG)
tk.Frame.__init__(self, master, **kw) tk.Frame.__init__(self, master, **kw)
tk.Frame(self).grid(pady=5) tk.Frame(self).grid(pady=5)
elif platform == 'win32': elif sys.platform == 'win32':
ttk.Frame.__init__(self, master, style='nb.TFrame', **kw) ttk.Frame.__init__(self, master, style='nb.TFrame', **kw)
ttk.Frame(self).grid(pady=5) # top spacer ttk.Frame(self).grid(pady=5) # top spacer
else: else:
ttk.Frame.__init__(self, master, **kw) ttk.Frame.__init__(self, master, **kw)
ttk.Frame(self).grid(pady=5) # top spacer ttk.Frame(self).grid(pady=5) # top spacer
self.configure(takefocus = 1) # let the frame take focus so that no particular child is focused self.configure(takefocus=1) # let the frame take focus so that no particular child is focused
class Label(tk.Label): class Label(tk.Label):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
if platform in ['darwin', 'win32']: if sys.platform in ['darwin', 'win32']:
kw['foreground'] = kw.pop('foreground', PAGEFG) kw['foreground'] = kw.pop('foreground', PAGEFG)
kw['background'] = kw.pop('background', PAGEBG) kw['background'] = kw.pop('background', PAGEBG)
else: else:
kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground')) kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground'))
kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background')) kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background'))
tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms
class Entry(platform == 'darwin' and tk.Entry or ttk.Entry):
class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
if platform == 'darwin': if sys.platform == 'darwin':
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
tk.Entry.__init__(self, master, **kw) tk.Entry.__init__(self, master, **kw)
else: else:
ttk.Entry.__init__(self, master, **kw) ttk.Entry.__init__(self, master, **kw)
class Button(platform == 'darwin' and tk.Button or ttk.Button):
class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
if platform == 'darwin': if sys.platform == 'darwin':
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
tk.Button.__init__(self, master, **kw) tk.Button.__init__(self, master, **kw)
elif platform == 'win32': elif sys.platform == 'win32':
ttk.Button.__init__(self, master, style='nb.TButton', **kw) ttk.Button.__init__(self, master, style='nb.TButton', **kw)
else: else:
ttk.Button.__init__(self, master, **kw) ttk.Button.__init__(self, master, **kw)
class ColoredButton(platform == 'darwin' and tk.Label or tk.Button):
class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
if platform == 'darwin': if sys.platform == 'darwin':
# Can't set Button background on OSX, so use a Label instead # Can't set Button background on OSX, so use a Label instead
kw['relief'] = kw.pop('relief', tk.RAISED) kw['relief'] = kw.pop('relief', tk.RAISED)
self._command = kw.pop('command', None) self._command = kw.pop('command', None)
@ -105,52 +108,55 @@ class ColoredButton(platform == 'darwin' and tk.Label or tk.Button):
else: else:
tk.Button.__init__(self, master, **kw) tk.Button.__init__(self, master, **kw)
if platform == 'darwin': if sys.platform == 'darwin':
def _press(self, event): def _press(self, event):
self._command() self._command()
class Checkbutton(platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton):
class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
if platform == 'darwin': if sys.platform == 'darwin':
kw['foreground'] = kw.pop('foreground', PAGEFG) kw['foreground'] = kw.pop('foreground', PAGEFG)
kw['background'] = kw.pop('background', PAGEBG) kw['background'] = kw.pop('background', PAGEBG)
tk.Checkbutton.__init__(self, master, **kw) tk.Checkbutton.__init__(self, master, **kw)
elif platform == 'win32': elif sys.platform == 'win32':
ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw) ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw)
else: else:
ttk.Checkbutton.__init__(self, master, **kw) ttk.Checkbutton.__init__(self, master, **kw)
class Radiobutton(platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton):
class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
if platform == 'darwin': if sys.platform == 'darwin':
kw['foreground'] = kw.pop('foreground', PAGEFG) kw['foreground'] = kw.pop('foreground', PAGEFG)
kw['background'] = kw.pop('background', PAGEBG) kw['background'] = kw.pop('background', PAGEBG)
tk.Radiobutton.__init__(self, master, **kw) tk.Radiobutton.__init__(self, master, **kw)
elif platform == 'win32': elif sys.platform == 'win32':
ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw) ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw)
else: else:
ttk.Radiobutton.__init__(self, master, **kw) ttk.Radiobutton.__init__(self, master, **kw)
class OptionMenu(platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu):
class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu):
def __init__(self, master, variable, default=None, *values, **kw): def __init__(self, master, variable, default=None, *values, **kw):
if platform == 'darwin': if sys.platform == 'darwin':
variable.set(default) variable.set(default)
bg = kw.pop('background', PAGEBG) bg = kw.pop('background', PAGEBG)
tk.OptionMenu.__init__(self, master, variable, *values, **kw) tk.OptionMenu.__init__(self, master, variable, *values, **kw)
self['background'] = bg self['background'] = bg
elif platform == 'win32': elif sys.platform == 'win32':
# OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style # 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) ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
self['menu'].configure(background = PAGEBG) self['menu'].configure(background=PAGEBG)
# Workaround for https://bugs.python.org/issue25684 # Workaround for https://bugs.python.org/issue25684
for i in range(0, self['menu'].index('end')+1): for i in range(0, self['menu'].index('end')+1):
self['menu'].entryconfig(i, variable=variable) self['menu'].entryconfig(i, variable=variable)
else: else:
ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw) ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
self['menu'].configure(background = ttk.Style().lookup('TMenu', 'background')) self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
# Workaround for https://bugs.python.org/issue25684 # Workaround for https://bugs.python.org/issue25684
for i in range(0, self['menu'].index('end')+1): for i in range(0, self['menu'].index('end')+1):
self['menu'].entryconfig(i, variable=variable) self['menu'].entryconfig(i, variable=variable)

View File

@ -3,10 +3,10 @@
import contextlib import contextlib
import logging import logging
import sys
import tkinter as tk import tkinter as tk
import webbrowser import webbrowser
from os.path import expanduser, expandvars, join, normpath from os.path import expanduser, expandvars, join, normpath
from sys import platform
from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812 from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812
from tkinter import ttk from tkinter import ttk
from types import TracebackType from types import TracebackType
@ -154,7 +154,7 @@ class AutoInc(contextlib.AbstractContextManager):
return None return None
if platform == 'darwin': if sys.platform == 'darwin':
import objc # type: ignore import objc # type: ignore
from Foundation import NSFileManager # type: ignore from Foundation import NSFileManager # type: ignore
try: try:
@ -179,7 +179,7 @@ if platform == 'darwin':
was_accessible_at_launch = AXIsProcessTrusted() # type: ignore was_accessible_at_launch = AXIsProcessTrusted() # type: ignore
elif platform == 'win32': elif sys.platform == 'win32':
import ctypes import ctypes
import winreg import winreg
from ctypes.wintypes import HINSTANCE, HWND, LPARAM, LPCWSTR, LPVOID, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT from ctypes.wintypes import HINSTANCE, HWND, LPARAM, LPCWSTR, LPVOID, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
@ -246,7 +246,7 @@ class PreferencesDialog(tk.Toplevel):
self.parent = parent self.parent = parent
self.callback = callback self.callback = callback
if platform == 'darwin': if sys.platform == 'darwin':
# LANG: File > Preferences menu entry for macOS # LANG: File > Preferences menu entry for macOS
self.title(_('Preferences')) self.title(_('Preferences'))
@ -258,15 +258,15 @@ class PreferencesDialog(tk.Toplevel):
self.transient(parent) self.transient(parent)
# position over parent # position over parent
if platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
# TODO this is fixed supposedly. # TODO this is fixed supposedly.
self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}') self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
# remove decoration # remove decoration
if platform == 'win32': if sys.platform == 'win32':
self.attributes('-toolwindow', tk.TRUE) self.attributes('-toolwindow', tk.TRUE)
elif platform == 'darwin': elif sys.platform == 'darwin':
# http://wiki.tcl.tk/13428 # http://wiki.tcl.tk/13428
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
@ -294,7 +294,7 @@ class PreferencesDialog(tk.Toplevel):
self.__setup_appearance_tab(notebook) self.__setup_appearance_tab(notebook)
self.__setup_plugin_tab(notebook) self.__setup_plugin_tab(notebook)
if platform == 'darwin': if sys.platform == 'darwin':
self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes
else: else:
@ -322,7 +322,7 @@ class PreferencesDialog(tk.Toplevel):
self.grab_set() self.grab_set()
# Ensure fully on-screen # Ensure fully on-screen
if platform == 'win32' and CalculatePopupWindowPosition: if sys.platform == 'win32' and CalculatePopupWindowPosition:
position = RECT() position = RECT()
GetWindowRect(GetParent(self.winfo_id()), position) GetWindowRect(GetParent(self.winfo_id()), position)
if CalculatePopupWindowPosition( if CalculatePopupWindowPosition(
@ -396,7 +396,7 @@ class PreferencesDialog(tk.Toplevel):
self.outdir_entry = nb.Entry(output_frame, takefocus=False) self.outdir_entry = nb.Entry(output_frame, takefocus=False)
self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=(0, self.PADY), sticky=tk.EW, row=row.get()) self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=(0, self.PADY), sticky=tk.EW, row=row.get())
if platform == 'darwin': if sys.platform == 'darwin':
text = (_('Change...')) # LANG: macOS Preferences - files location selection button text = (_('Change...')) # LANG: macOS Preferences - files location selection button
else: else:
@ -446,7 +446,7 @@ class PreferencesDialog(tk.Toplevel):
self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=(0, self.PADY), sticky=tk.EW, row=row.get()) self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=(0, self.PADY), sticky=tk.EW, row=row.get())
if platform == 'darwin': if sys.platform == 'darwin':
text = (_('Change...')) # LANG: macOS Preferences - files location selection button text = (_('Change...')) # LANG: macOS Preferences - files location selection button
else: else:
@ -470,7 +470,7 @@ class PreferencesDialog(tk.Toplevel):
state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED
).grid(column=2, pady=self.PADY, sticky=tk.EW, row=row.get()) ).grid(column=2, pady=self.PADY, sticky=tk.EW, row=row.get())
if platform in ('darwin', 'win32'): if sys.platform in ('darwin', 'win32'):
ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid( ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
columnspan=4, padx=self.PADX, pady=self.PADY*4, sticky=tk.EW, row=row.get() columnspan=4, padx=self.PADX, pady=self.PADY*4, sticky=tk.EW, row=row.get()
) )
@ -482,11 +482,11 @@ class PreferencesDialog(tk.Toplevel):
nb.Label( nb.Label(
config_frame, config_frame,
text=_('Keyboard shortcut') if # LANG: Hotkey/Shortcut settings prompt on OSX text=_('Keyboard shortcut') if # LANG: Hotkey/Shortcut settings prompt on OSX
platform == 'darwin' else sys.platform == 'darwin' else
_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows _('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows
).grid(padx=self.PADX, sticky=tk.W, row=row.get()) ).grid(padx=self.PADX, sticky=tk.W, row=row.get())
if platform == 'darwin' and not was_accessible_at_launch: if sys.platform == 'darwin' and not was_accessible_at_launch:
if AXIsProcessTrusted(): if AXIsProcessTrusted():
# Shortcut settings prompt on OSX # Shortcut settings prompt on OSX
nb.Label( nb.Label(
@ -511,7 +511,8 @@ class PreferencesDialog(tk.Toplevel):
) )
else: else:
self.hotkey_text = nb.Entry(config_frame, width=(20 if platform == 'darwin' else 30), justify=tk.CENTER) self.hotkey_text = nb.Entry(config_frame, width=(
20 if sys.platform == 'darwin' else 30), justify=tk.CENTER)
self.hotkey_text.insert( self.hotkey_text.insert(
0, 0,
# No hotkey/shortcut currently defined # No hotkey/shortcut currently defined
@ -741,7 +742,7 @@ class PreferencesDialog(tk.Toplevel):
appearance_frame, text=_('Dark'), variable=self.theme, value=1, command=self.themevarchanged appearance_frame, text=_('Dark'), variable=self.theme, value=1, command=self.themevarchanged
).grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get()) ).grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get())
if platform == 'win32': if sys.platform == 'win32':
nb.Radiobutton( nb.Radiobutton(
appearance_frame, appearance_frame,
# LANG: Label for 'Transparent' theme radio button # LANG: Label for 'Transparent' theme radio button
@ -870,7 +871,7 @@ class PreferencesDialog(tk.Toplevel):
) )
self.ontop_button.grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get()) # Appearance setting self.ontop_button.grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get()) # Appearance setting
if platform == 'win32': if sys.platform == 'win32':
nb.Checkbutton( nb.Checkbutton(
appearance_frame, appearance_frame,
# LANG: Appearance option for Windows "minimize to system tray" # LANG: Appearance option for Windows "minimize to system tray"
@ -997,7 +998,7 @@ class PreferencesDialog(tk.Toplevel):
def tabchanged(self, event: tk.Event) -> None: def tabchanged(self, event: tk.Event) -> None:
"""Handle preferences active tab changing.""" """Handle preferences active tab changing."""
self.outvarchanged() self.outvarchanged()
if platform == 'darwin': if sys.platform == 'darwin':
# Hack to recompute size so that buttons show up under Mojave # Hack to recompute size so that buttons show up under Mojave
notebook = event.widget notebook = event.widget
frame = self.nametowidget(notebook.winfo_parent()) frame = self.nametowidget(notebook.winfo_parent())
@ -1027,9 +1028,8 @@ class PreferencesDialog(tk.Toplevel):
# If encoding isn't UTF-8 we can't use the tkinter dialog # If encoding isn't UTF-8 we can't use the tkinter dialog
current_locale = locale.getlocale(locale.LC_CTYPE) current_locale = locale.getlocale(locale.LC_CTYPE)
from sys import platform as sys_platform
directory = None directory = None
if sys_platform == 'win32' and current_locale[1] not in ('utf8', 'UTF8', 'utf-8', 'UTF-8'): if sys.platform == 'win32' and current_locale[1] not in ('utf8', 'UTF8', 'utf-8', 'UTF-8'):
def browsecallback(hwnd, uMsg, lParam, lpData): # noqa: N803 # Windows API convention def browsecallback(hwnd, uMsg, lParam, lpData): # noqa: N803 # Windows API convention
# set initial folder # set initial folder
if uMsg == BFFM_INITIALIZED and lpData: if uMsg == BFFM_INITIALIZED and lpData:
@ -1075,7 +1075,7 @@ class PreferencesDialog(tk.Toplevel):
# TODO: This is awful. # TODO: This is awful.
entryfield['state'] = tk.NORMAL # must be writable to update entryfield['state'] = tk.NORMAL # must be writable to update
entryfield.delete(0, tk.END) entryfield.delete(0, tk.END)
if platform == 'win32': if sys.platform == 'win32':
start = len(config.home.split('\\')) if pathvar.get().lower().startswith(config.home.lower()) else 0 start = len(config.home.split('\\')) if pathvar.get().lower().startswith(config.home.lower()) else 0
display = [] display = []
components = normpath(pathvar.get()).split('\\') components = normpath(pathvar.get()).split('\\')
@ -1096,7 +1096,7 @@ class PreferencesDialog(tk.Toplevel):
entryfield.insert(0, '\\'.join(display)) entryfield.insert(0, '\\'.join(display))
# None if path doesn't exist # None if path doesn't exist
elif platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()): elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()):
if pathvar.get().startswith(config.home): if pathvar.get().startswith(config.home):
display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[ display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[
len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)): len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)):
@ -1236,7 +1236,7 @@ class PreferencesDialog(tk.Toplevel):
else: else:
config.set('journaldir', logdir) config.set('journaldir', logdir)
if platform in ('darwin', 'win32'): if sys.platform in ('darwin', 'win32'):
config.set('hotkey_code', self.hotkey_code) config.set('hotkey_code', self.hotkey_code)
config.set('hotkey_mods', self.hotkey_mods) config.set('hotkey_mods', self.hotkey_mods)
config.set('hotkey_always', int(not self.hotkey_only.get())) config.set('hotkey_always', int(not self.hotkey_only.get()))
@ -1282,7 +1282,7 @@ class PreferencesDialog(tk.Toplevel):
self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0) self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0)
self.destroy() self.destroy()
if platform == 'darwin': if sys.platform == 'darwin':
def enableshortcuts(self) -> None: def enableshortcuts(self) -> None:
"""Set up macOS preferences shortcut.""" """Set up macOS preferences shortcut."""
self.apply() self.apply()

View File

@ -1,9 +1,9 @@
"""CMDR Status information.""" """CMDR Status information."""
import csv import csv
import json import json
import sys
import tkinter import tkinter
import tkinter as tk import tkinter as tk
from sys import platform
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Any, AnyStr, Callable, Dict, List, NamedTuple, Optional, Sequence, cast from typing import TYPE_CHECKING, Any, AnyStr, Callable, Dict, List, NamedTuple, Optional, Sequence, cast
@ -20,11 +20,9 @@ logger = EDMCLogging.get_main_logger()
if TYPE_CHECKING: if TYPE_CHECKING:
def _(x: str) -> str: ... def _(x: str) -> str: ...
if platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT
if TYPE_CHECKING:
import ctypes.windll # type: ignore # Fake this into existing, its really a magic dll thing
try: try:
CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition
@ -372,15 +370,15 @@ class StatsResults(tk.Toplevel):
self.transient(parent) self.transient(parent)
# position over parent # position over parent
if platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}") self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}")
# remove decoration # remove decoration
self.resizable(tk.FALSE, tk.FALSE) self.resizable(tk.FALSE, tk.FALSE)
if platform == 'win32': if sys.platform == 'win32':
self.attributes('-toolwindow', tk.TRUE) self.attributes('-toolwindow', tk.TRUE)
elif platform == 'darwin': elif sys.platform == 'darwin':
# http://wiki.tcl.tk/13428 # http://wiki.tcl.tk/13428
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
@ -421,7 +419,7 @@ class StatsResults(tk.Toplevel):
ttk.Frame(page).grid(pady=5) # bottom spacer ttk.Frame(page).grid(pady=5) # bottom spacer
notebook.add(page, text=_('Ships')) # LANG: Status dialog title notebook.add(page, text=_('Ships')) # LANG: Status dialog title
if platform != 'darwin': if sys.platform != 'darwin':
buttonframe = ttk.Frame(frame) buttonframe = ttk.Frame(frame)
buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW) # type: ignore # the tuple is supported buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW) # type: ignore # the tuple is supported
buttonframe.columnconfigure(0, weight=1) buttonframe.columnconfigure(0, weight=1)
@ -433,7 +431,7 @@ class StatsResults(tk.Toplevel):
self.grab_set() self.grab_set()
# Ensure fully on-screen # Ensure fully on-screen
if platform == 'win32' and CalculatePopupWindowPosition: if sys.platform == 'win32' and CalculatePopupWindowPosition:
position = RECT() position = RECT()
GetWindowRect(GetParent(self.winfo_id()), position) GetWindowRect(GetParent(self.winfo_id()), position)
if CalculatePopupWindowPosition( if CalculatePopupWindowPosition(

View File

@ -4,7 +4,6 @@ import warnings
from configparser import NoOptionError from configparser import NoOptionError
from os import getenv, makedirs, mkdir, pardir from os import getenv, makedirs, mkdir, pardir
from os.path import dirname, expanduser, isdir, join, normpath from os.path import dirname, expanduser, isdir, join, normpath
from sys import platform
from typing import TYPE_CHECKING, Optional, Union from typing import TYPE_CHECKING, Optional, Union
from config import applongname, appname, update_interval from config import applongname, appname, update_interval
@ -12,13 +11,13 @@ from EDMCLogging import get_main_logger
logger = get_main_logger() logger = get_main_logger()
if platform == 'darwin': if sys.platform == 'darwin':
from Foundation import ( # type: ignore from Foundation import ( # type: ignore
NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains,
NSUserDefaults, NSUserDomainMask NSUserDefaults, NSUserDomainMask
) )
elif platform == 'win32': elif sys.platform == 'win32':
import ctypes import ctypes
import uuid import uuid
from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR
@ -90,7 +89,7 @@ elif platform == 'win32':
CoTaskMemFree(buf) # and free original CoTaskMemFree(buf) # and free original
return retval return retval
elif platform == 'linux': elif sys.platform == 'linux':
import codecs import codecs
from configparser import RawConfigParser from configparser import RawConfigParser
@ -114,7 +113,7 @@ class OldConfig():
OUT_SYS_EDDN = 2048 OUT_SYS_EDDN = 2048
OUT_SYS_DELAY = 4096 OUT_SYS_DELAY = 4096
if platform == 'darwin': # noqa: C901 # It's gating *all* the functions if sys.platform == 'darwin': # noqa: C901 # It's gating *all* the functions
def __init__(self): def __init__(self):
self.app_dir = join( self.app_dir = join(
@ -199,7 +198,7 @@ class OldConfig():
self.save() self.save()
self.defaults = None self.defaults = None
elif platform == 'win32': elif sys.platform == 'win32':
def __init__(self): def __init__(self):
self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore # Not going to change self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore # Not going to change
@ -362,7 +361,7 @@ class OldConfig():
RegCloseKey(self.hkey) RegCloseKey(self.hkey)
self.hkey = None self.hkey = None
elif platform == 'linux': elif sys.platform == 'linux':
SECTION = 'config' SECTION = 'config'
def __init__(self): def __init__(self):

206
theme.py
View File

@ -6,9 +6,9 @@
# #
import os import os
import sys
import tkinter as tk import tkinter as tk
from os.path import join from os.path import join
from sys import platform
from tkinter import font as tkFont from tkinter import font as tkFont
from tkinter import ttk from tkinter import ttk
@ -18,20 +18,20 @@ from ttkHyperlinkLabel import HyperlinkLabel
if __debug__: if __debug__:
from traceback import print_exc from traceback import print_exc
if platform == "linux": 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 from ctypes import POINTER, Structure, byref, c_char_p, c_int, c_long, c_uint, c_ulong, c_void_p, cdll
if platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR
AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW
AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID]
FR_PRIVATE = 0x10 FR_PRIVATE = 0x10
FR_NOT_ENUM = 0x20 FR_NOT_ENUM = 0x20
AddFontResourceEx(join(config.respath, u'EUROCAPS.TTF'), FR_PRIVATE, 0) AddFontResourceEx(join(config.respath, u'EUROCAPS.TTF'), FR_PRIVATE, 0)
elif platform == 'linux': elif sys.platform == 'linux':
# pyright: reportUnboundVariable=false # pyright: reportUnboundVariable=false
XID = c_ulong # from X.h: typedef unsigned long XID XID = c_ulong # from X.h: typedef unsigned long XID
Window = XID Window = XID
@ -40,7 +40,7 @@ elif platform == 'linux':
PropModeReplace = 0 PropModeReplace = 0
PropModePrepend = 1 PropModePrepend = 1
PropModeAppend = 2 PropModeAppend = 2
# From xprops.h # From xprops.h
MWM_HINTS_FUNCTIONS = 1 << 0 MWM_HINTS_FUNCTIONS = 1 << 0
@ -69,16 +69,17 @@ elif platform == 'linux':
('input_mode', c_long), ('input_mode', c_long),
('status', c_ulong), ('status', c_ulong),
] ]
# workaround for https://github.com/EDCD/EDMarketConnector/issues/568 # workaround for https://github.com/EDCD/EDMarketConnector/issues/568
if not os.getenv("EDMC_NO_UI") : if not os.getenv("EDMC_NO_UI"):
try: try:
xlib = cdll.LoadLibrary('libX11.so.6') xlib = cdll.LoadLibrary('libX11.so.6')
XInternAtom = xlib.XInternAtom XInternAtom = xlib.XInternAtom
XInternAtom.argtypes = [POINTER(Display), c_char_p, c_int] XInternAtom.argtypes = [POINTER(Display), c_char_p, c_int]
XInternAtom.restype = Atom XInternAtom.restype = Atom
XChangeProperty = xlib.XChangeProperty XChangeProperty = xlib.XChangeProperty
XChangeProperty.argtypes = [POINTER(Display), Window, Atom, Atom, c_int, c_int, POINTER(MotifWmHints), c_int] XChangeProperty.argtypes = [POINTER(Display), Window, Atom, Atom, c_int,
c_int, POINTER(MotifWmHints), c_int]
XChangeProperty.restype = c_int XChangeProperty.restype = c_int
XFlush = xlib.XFlush XFlush = xlib.XFlush
XFlush.argtypes = [POINTER(Display)] XFlush.argtypes = [POINTER(Display)]
@ -87,29 +88,31 @@ elif platform == 'linux':
XOpenDisplay.argtypes = [c_char_p] XOpenDisplay.argtypes = [c_char_p]
XOpenDisplay.restype = POINTER(Display) XOpenDisplay.restype = POINTER(Display)
XQueryTree = xlib.XQueryTree XQueryTree = xlib.XQueryTree
XQueryTree.argtypes = [POINTER(Display), Window, POINTER(Window), POINTER(Window), POINTER(Window), POINTER(c_uint)] XQueryTree.argtypes = [POINTER(Display), Window, POINTER(
Window), POINTER(Window), POINTER(Window), POINTER(c_uint)]
XQueryTree.restype = c_int XQueryTree.restype = c_int
dpy = xlib.XOpenDisplay(None) dpy = xlib.XOpenDisplay(None)
if not dpy: if not dpy:
raise Exception("Can't find your display, can't continue") raise Exception("Can't find your display, can't continue")
motif_wm_hints_property = XInternAtom(dpy, b'_MOTIF_WM_HINTS', False) motif_wm_hints_property = XInternAtom(dpy, b'_MOTIF_WM_HINTS', False)
motif_wm_hints_normal = MotifWmHints(MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS, motif_wm_hints_normal = MotifWmHints(MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS,
MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE, MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE,
MWM_DECOR_BORDER | MWM_DECOR_RESIZEH | MWM_DECOR_TITLE | MWM_DECOR_MENU | MWM_DECOR_MINIMIZE, MWM_DECOR_BORDER | MWM_DECOR_RESIZEH | MWM_DECOR_TITLE | MWM_DECOR_MENU | MWM_DECOR_MINIMIZE,
0, 0) 0, 0)
motif_wm_hints_dark = MotifWmHints(MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS, motif_wm_hints_dark = MotifWmHints(MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS,
MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE, MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE,
0, 0, 0) 0, 0, 0)
except: except:
if __debug__: print_exc() if __debug__:
print_exc()
dpy = None dpy = None
class _Theme(object): class _Theme(object):
def __init__(self): def __init__(self):
self.active = None # Starts out with no theme self.active = None # Starts out with no theme
self.minwidth = None self.minwidth = None
self.widgets = {} self.widgets = {}
self.widgets_pair = [] self.widgets_pair = []
@ -124,18 +127,18 @@ class _Theme(object):
if not self.defaults: if not self.defaults:
# Can't initialise this til window is created # Windows, MacOS # Can't initialise this til window is created # Windows, MacOS
self.defaults = { self.defaults = {
'fg' : tk.Label()['foreground'], # SystemButtonText, systemButtonText 'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText
'bg' : tk.Label()['background'], # SystemButtonFace, White 'bg': tk.Label()['background'], # SystemButtonFace, White
'font' : tk.Label()['font'], # TkDefaultFont 'font': tk.Label()['font'], # TkDefaultFont
'bitmapfg' : tk.BitmapImage()['foreground'], # '-foreground {} {} #000000 #000000' 'bitmapfg': tk.BitmapImage()['foreground'], # '-foreground {} {} #000000 #000000'
'bitmapbg' : tk.BitmapImage()['background'], # '-background {} {} {} {}' 'bitmapbg': tk.BitmapImage()['background'], # '-background {} {} {} {}'
'entryfg' : tk.Entry()['foreground'], # SystemWindowText, Black 'entryfg': tk.Entry()['foreground'], # SystemWindowText, Black
'entrybg' : tk.Entry()['background'], # SystemWindow, systemWindowBody 'entrybg': tk.Entry()['background'], # SystemWindow, systemWindowBody
'entryfont' : tk.Entry()['font'], # TkTextFont 'entryfont': tk.Entry()['font'], # TkTextFont
'frame' : tk.Frame()['background'], # SystemButtonFace, systemWindowBody 'frame': tk.Frame()['background'], # SystemButtonFace, systemWindowBody
'menufg' : tk.Menu()['foreground'], # SystemMenuText, 'menufg': tk.Menu()['foreground'], # SystemMenuText,
'menubg' : tk.Menu()['background'], # SystemMenu, 'menubg': tk.Menu()['background'], # SystemMenu,
'menufont' : tk.Menu()['font'], # TkTextFont 'menufont': tk.Menu()['font'], # TkTextFont
} }
if widget not in self.widgets: if widget not in self.widgets:
@ -189,26 +192,27 @@ class _Theme(object):
def _enter(self, event, image): def _enter(self, event, image):
widget = event.widget widget = event.widget
if widget and widget['state'] != tk.DISABLED: if widget and widget['state'] != tk.DISABLED:
widget.configure(state = tk.ACTIVE) widget.configure(state=tk.ACTIVE)
if image: if image:
image.configure(foreground = self.current['activeforeground'], background = self.current['activebackground']) image.configure(foreground=self.current['activeforeground'],
background=self.current['activebackground'])
def _leave(self, event, image): def _leave(self, event, image):
widget = event.widget widget = event.widget
if widget and widget['state'] != tk.DISABLED: if widget and widget['state'] != tk.DISABLED:
widget.configure(state = tk.NORMAL) widget.configure(state=tk.NORMAL)
if image: if image:
image.configure(foreground = self.current['foreground'], background = self.current['background']) image.configure(foreground=self.current['foreground'], background=self.current['background'])
# Set up colors # Set up colors
def _colors(self, root, theme): def _colors(self, root, theme):
style = ttk.Style() style = ttk.Style()
if platform == 'linux': if sys.platform == 'linux':
style.theme_use('clam') style.theme_use('clam')
# Default dark theme colors # Default dark theme colors
if not config.get_str('dark_text'): if not config.get_str('dark_text'):
config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker
if not config.get_str('dark_highlight'): if not config.get_str('dark_highlight'):
config.set('dark_highlight', 'white') config.set('dark_highlight', 'white')
@ -216,40 +220,40 @@ class _Theme(object):
# Dark # Dark
(r, g, b) = root.winfo_rgb(config.get_str('dark_text')) (r, g, b) = root.winfo_rgb(config.get_str('dark_text'))
self.current = { self.current = {
'background' : 'grey4', # OSX inactive dark titlebar color 'background': 'grey4', # OSX inactive dark titlebar color
'foreground' : config.get_str('dark_text'), 'foreground': config.get_str('dark_text'),
'activebackground' : config.get_str('dark_text'), 'activebackground': config.get_str('dark_text'),
'activeforeground' : 'grey4', 'activeforeground': 'grey4',
'disabledforeground' : '#%02x%02x%02x' % (int(r/384), int(g/384), int(b/384)), 'disabledforeground': '#%02x%02x%02x' % (int(r/384), int(g/384), int(b/384)),
'highlight' : config.get_str('dark_highlight'), 'highlight': config.get_str('dark_highlight'),
# Font only supports Latin 1 / Supplement / Extended, and a few General Punctuation and Mathematical Operators # Font only supports Latin 1 / Supplement / Extended, and a few General Punctuation and Mathematical Operators
# LANG: Label for commander name in main window # LANG: Label for commander name in main window
'font' : (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and 'font': (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and
tkFont.Font(family='Euro Caps', size=10, weight=tkFont.NORMAL) or tkFont.Font(family='Euro Caps', size=10, weight=tkFont.NORMAL) or
'TkDefaultFont'), 'TkDefaultFont'),
} }
else: else:
# (Mostly) system colors # (Mostly) system colors
style = ttk.Style() style = ttk.Style()
self.current = { self.current = {
'background' : (platform == 'darwin' and 'systemMovableModalBackground' or 'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or
style.lookup('TLabel', 'background')), style.lookup('TLabel', 'background')),
'foreground' : style.lookup('TLabel', 'foreground'), 'foreground': style.lookup('TLabel', 'foreground'),
'activebackground' : (platform == 'win32' and 'SystemHighlight' or 'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or
style.lookup('TLabel', 'background', ['active'])), style.lookup('TLabel', 'background', ['active'])),
'activeforeground' : (platform == 'win32' and 'SystemHighlightText' or 'activeforeground': (sys.platform == 'win32' and 'SystemHighlightText' or
style.lookup('TLabel', 'foreground', ['active'])), style.lookup('TLabel', 'foreground', ['active'])),
'disabledforeground' : style.lookup('TLabel', 'foreground', ['disabled']), 'disabledforeground': style.lookup('TLabel', 'foreground', ['disabled']),
'highlight' : 'blue', 'highlight': 'blue',
'font' : 'TkDefaultFont', 'font': 'TkDefaultFont',
} }
# Apply current theme to a widget and its children, and register it for future updates # Apply current theme to a widget and its children, and register it for future updates
def update(self, widget): def update(self, widget):
assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget
if not self.current: if not self.current:
return # No need to call this for widgets created in plugin_app() return # No need to call this for widgets created in plugin_app()
self.register(widget) self.register(widget)
self._update_widget(widget) self._update_widget(widget)
if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame): if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame):
@ -258,56 +262,57 @@ class _Theme(object):
# Apply current theme to a single widget # Apply current theme to a single widget
def _update_widget(self, widget): def _update_widget(self, widget):
assert widget in self.widgets, '%s %s "%s"' %(widget.winfo_class(), widget, 'text' in widget.keys() and widget['text']) assert widget in self.widgets, '%s %s "%s"' % (
widget.winfo_class(), widget, 'text' in widget.keys() and widget['text'])
attribs = self.widgets.get(widget, []) attribs = self.widgets.get(widget, [])
if isinstance(widget, tk.BitmapImage): if isinstance(widget, tk.BitmapImage):
# not a widget # not a widget
if 'fg' not in attribs: if 'fg' not in attribs:
widget.configure(foreground = self.current['foreground']), widget.configure(foreground=self.current['foreground']),
if 'bg' not in attribs: if 'bg' not in attribs:
widget.configure(background = self.current['background']) widget.configure(background=self.current['background'])
elif 'cursor' in widget.keys() and str(widget['cursor']) not in ['', 'arrow']: elif 'cursor' in widget.keys() and str(widget['cursor']) not in ['', 'arrow']:
# Hack - highlight widgets like HyperlinkLabel with a non-default cursor # Hack - highlight widgets like HyperlinkLabel with a non-default cursor
if 'fg' not in attribs: if 'fg' not in attribs:
widget.configure(foreground = self.current['highlight']), widget.configure(foreground=self.current['highlight']),
if 'insertbackground' in widget.keys(): # tk.Entry if 'insertbackground' in widget.keys(): # tk.Entry
widget.configure(insertbackground = self.current['foreground']), widget.configure(insertbackground=self.current['foreground']),
if 'bg' not in attribs: if 'bg' not in attribs:
widget.configure(background = self.current['background']) widget.configure(background=self.current['background'])
if 'highlightbackground' in widget.keys(): # tk.Entry if 'highlightbackground' in widget.keys(): # tk.Entry
widget.configure(highlightbackground = self.current['background']) widget.configure(highlightbackground=self.current['background'])
if 'font' not in attribs: if 'font' not in attribs:
widget.configure(font = self.current['font']) widget.configure(font=self.current['font'])
elif 'activeforeground' in widget.keys(): elif 'activeforeground' in widget.keys():
# e.g. tk.Button, tk.Label, tk.Menu # e.g. tk.Button, tk.Label, tk.Menu
if 'fg' not in attribs: if 'fg' not in attribs:
widget.configure(foreground = self.current['foreground'], widget.configure(foreground=self.current['foreground'],
activeforeground = self.current['activeforeground'], activeforeground=self.current['activeforeground'],
disabledforeground = self.current['disabledforeground']) disabledforeground=self.current['disabledforeground'])
if 'bg' not in attribs: if 'bg' not in attribs:
widget.configure(background = self.current['background'], widget.configure(background=self.current['background'],
activebackground = self.current['activebackground']) activebackground=self.current['activebackground'])
if platform == 'darwin' and isinstance(widget, tk.Button): if sys.platform == 'darwin' and isinstance(widget, tk.Button):
widget.configure(highlightbackground = self.current['background']) widget.configure(highlightbackground=self.current['background'])
if 'font' not in attribs: if 'font' not in attribs:
widget.configure(font = self.current['font']) widget.configure(font=self.current['font'])
elif 'foreground' in widget.keys(): elif 'foreground' in widget.keys():
# e.g. ttk.Label # e.g. ttk.Label
if 'fg' not in attribs: if 'fg' not in attribs:
widget.configure(foreground = self.current['foreground']), widget.configure(foreground=self.current['foreground']),
if 'bg' not in attribs: if 'bg' not in attribs:
widget.configure(background = self.current['background']) widget.configure(background=self.current['background'])
if 'font' not in attribs: if 'font' not in attribs:
widget.configure(font = self.current['font']) widget.configure(font=self.current['font'])
elif 'background' in widget.keys() or isinstance(widget, tk.Canvas): elif 'background' in widget.keys() or isinstance(widget, tk.Canvas):
# e.g. Frame, Canvas # e.g. Frame, Canvas
if 'bg' not in attribs: if 'bg' not in attribs:
widget.configure(background = self.current['background'], widget.configure(background=self.current['background'],
highlightbackground = self.current['disabledforeground']) highlightbackground=self.current['disabledforeground'])
# Apply configured theme # Apply configured theme
def apply(self, root): def apply(self, root):
theme = config.get_int('theme') theme = config.get_int('theme')
@ -316,7 +321,7 @@ class _Theme(object):
# Apply colors # Apply colors
for widget in set(self.widgets): for widget in set(self.widgets):
if isinstance(widget, tk.Widget) and not widget.winfo_exists(): if isinstance(widget, tk.Widget) and not widget.winfo_exists():
self.widgets.pop(widget) # has been destroyed self.widgets.pop(widget) # has been destroyed
else: else:
self._update_widget(widget) self._update_widget(widget)
@ -334,58 +339,61 @@ class _Theme(object):
pair[theme].grid(**gridopts) pair[theme].grid(**gridopts)
if self.active == theme: if self.active == theme:
return # Don't need to mess with the window manager return # Don't need to mess with the window manager
else: else:
self.active = theme self.active = theme
if platform == 'darwin': if sys.platform == 'darwin':
from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask
root.update_idletasks() # need main window to be created root.update_idletasks() # need main window to be created
appearance = NSAppearance.appearanceNamed_(theme and appearance = NSAppearance.appearanceNamed_(theme and
'NSAppearanceNameDarkAqua' or 'NSAppearanceNameDarkAqua' or
'NSAppearanceNameAqua') 'NSAppearanceNameAqua')
for window in NSApplication.sharedApplication().windows(): for window in NSApplication.sharedApplication().windows():
window.setStyleMask_(window.styleMask() & ~(NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom window.setStyleMask_(window.styleMask() & ~(
NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom
window.setAppearance_(appearance) window.setAppearance_(appearance)
elif platform == 'win32': elif sys.platform == 'win32':
GWL_STYLE = -16 GWL_STYLE = -16
WS_MAXIMIZEBOX = 0x00010000 WS_MAXIMIZEBOX = 0x00010000
# tk8.5.9/win/tkWinWm.c:342 # tk8.5.9/win/tkWinWm.c:342
GWL_EXSTYLE = -20 GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000 WS_EX_APPWINDOW = 0x00040000
WS_EX_LAYERED = 0x00080000 WS_EX_LAYERED = 0x00080000
GetWindowLongW = ctypes.windll.user32.GetWindowLongW GetWindowLongW = ctypes.windll.user32.GetWindowLongW
SetWindowLongW = ctypes.windll.user32.SetWindowLongW SetWindowLongW = ctypes.windll.user32.SetWindowLongW
root.overrideredirect(theme and 1 or 0) root.overrideredirect(theme and 1 or 0)
root.attributes("-transparentcolor", theme > 1 and 'grey4' or '') root.attributes("-transparentcolor", theme > 1 and 'grey4' or '')
root.withdraw() root.withdraw()
root.update_idletasks() # Size and windows styles get recalculated here root.update_idletasks() # Size and windows styles get recalculated here
hwnd = ctypes.windll.user32.GetParent(root.winfo_id()) hwnd = ctypes.windll.user32.GetParent(root.winfo_id())
SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize
SetWindowLongW(hwnd, GWL_EXSTYLE, theme > 1 and WS_EX_APPWINDOW|WS_EX_LAYERED or WS_EX_APPWINDOW) # Add to taskbar SetWindowLongW(hwnd, GWL_EXSTYLE, theme > 1 and WS_EX_APPWINDOW |
WS_EX_LAYERED or WS_EX_APPWINDOW) # Add to taskbar
root.deiconify() root.deiconify()
root.wait_visibility() # need main window to be displayed before returning root.wait_visibility() # need main window to be displayed before returning
else: else:
root.withdraw() root.withdraw()
root.update_idletasks() # Size gets recalculated here root.update_idletasks() # Size gets recalculated here
if dpy: if dpy:
xroot = Window() xroot = Window()
parent = Window() parent = Window()
children = Window() children = Window()
nchildren = c_uint() nchildren = c_uint()
XQueryTree(dpy, root.winfo_id(), byref(xroot), byref(parent), byref(children), byref(nchildren)) XQueryTree(dpy, root.winfo_id(), byref(xroot), byref(parent), byref(children), byref(nchildren))
XChangeProperty(dpy, parent, motif_wm_hints_property, motif_wm_hints_property, 32, PropModeReplace, theme and motif_wm_hints_dark or motif_wm_hints_normal, 5) XChangeProperty(dpy, parent, motif_wm_hints_property, motif_wm_hints_property, 32,
PropModeReplace, theme and motif_wm_hints_dark or motif_wm_hints_normal, 5)
XFlush(dpy) XFlush(dpy)
else: else:
root.overrideredirect(theme and 1 or 0) root.overrideredirect(theme and 1 or 0)
root.deiconify() root.deiconify()
root.wait_visibility() # need main window to be displayed before returning root.wait_visibility() # need main window to be displayed before returning
if not self.minwidth: if not self.minwidth:
self.minwidth = root.winfo_width() # Minimum width = width on first creation self.minwidth = root.winfo_width() # Minimum width = width on first creation
root.minsize(self.minwidth, -1) root.minsize(self.minwidth, -1)

View File

@ -1,14 +1,12 @@
import sys
import tkinter as tk import tkinter as tk
import webbrowser import webbrowser
from sys import platform
from tkinter import font as tkFont from tkinter import font as tkFont
from tkinter import ttk from tkinter import ttk
if platform == 'win32': if sys.platform == 'win32':
import subprocess import subprocess
from winreg import ( from winreg import HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, CloseKey, OpenKeyEx, QueryValueEx
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, CloseKey, OpenKeyEx, QueryValueEx
)
# A clickable ttk Label # A clickable ttk Label
# #
@ -18,19 +16,22 @@ if platform == 'win32':
# popup_copy: Whether right-click on non-empty label text pops up a context menu with a 'Copy' option. Defaults to no context menu. If popup_copy is a function it will be called with the current label text and should return a boolean. # popup_copy: Whether right-click on non-empty label text pops up a context menu with a 'Copy' option. Defaults to no context menu. If popup_copy is a function it will be called with the current label text and should return a boolean.
# #
# May be imported by plugins # May be imported by plugins
class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object):
class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label, object):
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
self.url = 'url' in kw and kw.pop('url') or None self.url = 'url' in kw and kw.pop('url') or None
self.popup_copy = kw.pop('popup_copy', False) 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) # override ttk.Label's underline
self.foreground = kw.get('foreground') or 'blue' self.foreground = kw.get('foreground') or 'blue'
self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup('TLabel', 'foreground', ('disabled',))) # 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
if platform == 'darwin': if sys.platform == 'darwin':
# Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult # Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult
kw['background'] = kw.pop('background', 'systemDialogBackgroundActive') kw['background'] = kw.pop('background', 'systemDialogBackgroundActive')
kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label
tk.Label.__init__(self, master, **kw) tk.Label.__init__(self, master, **kw)
else: else:
ttk.Label.__init__(self, master, **kw) ttk.Label.__init__(self, master, **kw)
@ -39,16 +40,16 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object):
self.menu = tk.Menu(None, tearoff=tk.FALSE) self.menu = tk.Menu(None, tearoff=tk.FALSE)
# LANG: Label for 'Copy' as in 'Copy and Paste' # LANG: Label for 'Copy' as in 'Copy and Paste'
self.menu.add_command(label=_('Copy'), command = self.copy) # As in Copy and Paste self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste
self.bind(platform == 'darwin' and '<Button-2>' or '<Button-3>', self._contextmenu) self.bind(sys.platform == 'darwin' and '<Button-2>' or '<Button-3>', self._contextmenu)
self.bind('<Enter>', self._enter) self.bind('<Enter>', self._enter)
self.bind('<Leave>', self._leave) self.bind('<Leave>', self._leave)
# set up initial appearance # set up initial appearance
self.configure(state = kw.get('state', tk.NORMAL), self.configure(state=kw.get('state', tk.NORMAL),
text = kw.get('text'), text=kw.get('text'),
font = kw.get('font', ttk.Style().lookup('TLabel', 'font'))) font=kw.get('font', ttk.Style().lookup('TLabel', 'font')))
# Change cursor and appearance depending on state and text # Change cursor and appearance depending on state and text
def configure(self, cnf=None, **kw): def configure(self, cnf=None, **kw):
@ -70,17 +71,18 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object):
if 'font' in kw: if 'font' in kw:
self.font_n = kw['font'] self.font_n = kw['font']
self.font_u = tkFont.Font(font = self.font_n) self.font_u = tkFont.Font(font=self.font_n)
self.font_u.configure(underline = True) self.font_u.configure(underline=True)
kw['font'] = self.underline is True and self.font_u or self.font_n kw['font'] = self.underline is True and self.font_u or self.font_n
if 'cursor' not in kw: if 'cursor' not in kw:
if (kw['state'] if 'state' in kw else str(self['state'])) == tk.DISABLED: if (kw['state'] if 'state' in kw else str(self['state'])) == tk.DISABLED:
kw['cursor'] = 'arrow' # System default kw['cursor'] = 'arrow' # System default
elif self.url and (kw['text'] if 'text' in kw else self['text']): elif self.url and (kw['text'] if 'text' in kw else self['text']):
kw['cursor'] = platform=='darwin' and 'pointinghand' or 'hand2' kw['cursor'] = sys.platform == 'darwin' and 'pointinghand' or 'hand2'
else: else:
kw['cursor'] = (platform=='darwin' and 'notallowed') or (platform=='win32' and 'no') or 'circle' kw['cursor'] = (sys.platform == 'darwin' and 'notallowed') or (
sys.platform == 'win32' and 'no') or 'circle'
super(HyperlinkLabel, self).configure(cnf, **kw) super(HyperlinkLabel, self).configure(cnf, **kw)
@ -89,22 +91,22 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object):
def _enter(self, event): def _enter(self, event):
if self.url and self.underline is not False and str(self['state']) != tk.DISABLED: if self.url and self.underline is not False and str(self['state']) != tk.DISABLED:
super(HyperlinkLabel, self).configure(font = self.font_u) super(HyperlinkLabel, self).configure(font=self.font_u)
def _leave(self, event): def _leave(self, event):
if not self.underline: if not self.underline:
super(HyperlinkLabel, self).configure(font = self.font_n) super(HyperlinkLabel, self).configure(font=self.font_n)
def _click(self, event): def _click(self, event):
if self.url and self['text'] and str(self['state']) != tk.DISABLED: if self.url and self['text'] and str(self['state']) != tk.DISABLED:
url = self.url(self['text']) if callable(self.url) else self.url url = self.url(self['text']) if callable(self.url) else self.url
if url: if url:
self._leave(event) # Remove underline before we change window to browser self._leave(event) # Remove underline before we change window to browser
openurl(url) openurl(url)
def _contextmenu(self, event): def _contextmenu(self, event):
if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy): if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
self.menu.post(platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root) self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root)
def copy(self): def copy(self):
self.clipboard_clear() self.clipboard_clear()
@ -112,13 +114,14 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object):
def openurl(url): def openurl(url):
if platform == 'win32': if sys.platform == 'win32':
# On Windows webbrowser.open calls os.startfile which calls ShellExecute which can't handle long arguments, # On Windows webbrowser.open calls os.startfile which calls ShellExecute which can't handle long arguments,
# so discover and launch the browser directly. # so discover and launch the browser directly.
# https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553 # https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553
try: try:
hkey = OpenKeyEx(HKEY_CURRENT_USER, r'Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice') hkey = OpenKeyEx(HKEY_CURRENT_USER,
r'Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice')
(value, typ) = QueryValueEx(hkey, 'ProgId') (value, typ) = QueryValueEx(hkey, 'ProgId')
CloseKey(hkey) CloseKey(hkey)
if value in ['IE.HTTP', 'AppXq0fevzme2pys62n3e0fbqa7peapykr8v']: if value in ['IE.HTTP', 'AppXq0fevzme2pys62n3e0fbqa7peapykr8v']:
@ -128,7 +131,7 @@ def openurl(url):
else: else:
cls = value cls = value
except: except:
cls = 'https' cls = 'https'
if cls: if cls:
try: try: