1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 15:27:14 +03:00
Athanasius 89f113e190 Use Preferences Save Serial Number instead of flag
* new class `PrefsVersion` in prefs.py.  A singleton `prefsSaved` (note
   case) is created.
 * When new preferences are added and require defaults on first run the
   code should use:

     `if prefsVersion.shouldSetDefaults(<prior release version>, [<optional old test>]):`

   to check if defaults should be set in preferences.  So if prior release
   was '3.4.6.0' and you've added a new preference with defaults you should
   call this with '3.4.6.0' as the first argument.
   The <optional old test> is really only for historical purposes, as a
   fallback in case no 'PrefsVersion' has yet been set in the user's
   Registry/settings file.
 * Any code that adds such a new preference **MUST** make changes to the
   `versions` dictionary in the PrefsVersion class.
     1. Add the predicted next version to the dictionary, with number one
        higher than 'current'
     2. Set 'current' equal to that new value.
   Obviously if other post-last-release code has already done this then you
   don't need to.

   Failure to update the versions dictionary in this manner will lead to an
   Exception being raised, and the code the preferences are for failing
   (i.e. EDDN means no EDDN tab on Settings).

Closes #407
2020-07-01 15:33:42 +01:00

634 lines
35 KiB
Python

# -*- coding: utf-8 -*-
import os
from os.path import dirname, expanduser, expandvars, exists, isdir, join, normpath
from sys import platform
import webbrowser
import tkinter as tk
from tkinter import ttk
from tkinter import colorchooser as tkColorChooser
from ttkHyperlinkLabel import HyperlinkLabel
import myNotebook as nb
from config import applongname, config, appversion
from hotkey import hotkeymgr
from l10n import Translations
from monitor import monitor
from theme import theme
import plug
###########################################################################
# Versioned preferences, so we know whether to set an 'on' default on
# 'new' preferences, or not.
###########################################################################
class PrefsVersion(object):
versions = {
'0.0.0.0': 1,
'1.0.0.0': 2,
'3.4.6.0': 3,
'3.5.1.0': 4,
# Only add new versions that add new Preferences
'current': 4, # Should always match the last specific version, but only increment after you've added the new version. Guess at it if anticipating a new version.
}
def __init__(self):
return
def stringToSerial(self, versionStr: str) -> int:
"""
Convert a version string into a preferences version serial number.
If the version string isn't known returns the 'current' (latest) serial number.
:param versionStr:
:return int:
"""
if versionStr in self.versions:
return self.versions[versionStr]
return self.versions['current']
###########################################################################
# Should defaults be set, given the settings were added after 'addedAfter' ?
#
# config.get('PrefsVersion') is the version preferences we last saved for
###########################################################################
def shouldSetDefaults(self, addedAfter: str, oldTest : bool=True) -> bool:
pv = config.getint('PrefsVersion')
# If no PrefsVersion yet exists then return oldTest
if not pv:
return oldTest
# Convert addedAfter to a version serial number
if addedAfter not in self.versions:
# Assume it was added at the start
aa = 1
else:
aa = self.versions[addedAfter]
# Sanity check, if something was added after then current should be greater
if aa >= self.versions['current']:
raise Exception('ERROR: Call to prefs.py:PrefsVersion.shouldSetDefaults() with "addedAfter" >= current latest in "versions" table. You probably need to increase "current" serial number.')
# If this preference was added after the saved PrefsVersion we should set defaults
if aa >= pv:
return True
return False
###########################################################################
prefsVersion = PrefsVersion()
###########################################################################
if platform == 'darwin':
import objc
from Foundation import NSFileManager
try:
from ApplicationServices import AXIsProcessTrusted, AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt
except:
HIServices = objc.loadBundle('HIServices', globals(), '/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework')
objc.loadBundleFunctions(HIServices, globals(), [('AXIsProcessTrusted', 'B'),
('AXIsProcessTrustedWithOptions', 'B@')])
objc.loadBundleVariables(HIServices, globals(), [('kAXTrustedCheckOptionPrompt', '@^{__CFString=}')])
was_accessible_at_launch = AXIsProcessTrusted()
elif platform=='win32':
# sigh tkFileDialog.askdirectory doesn't support unicode on Windows
import ctypes
from ctypes.wintypes import *
SHGetLocalizedName = ctypes.windll.shell32.SHGetLocalizedName
SHGetLocalizedName.argtypes = [LPCWSTR, LPWSTR, UINT, ctypes.POINTER(ctypes.c_int)]
LoadString = ctypes.windll.user32.LoadStringW
LoadString.argtypes = [HINSTANCE, UINT, LPWSTR, ctypes.c_int]
# https://msdn.microsoft.com/en-us/library/windows/desktop/bb762115
BIF_RETURNONLYFSDIRS = 0x00000001
BIF_USENEWUI = 0x00000050
BFFM_INITIALIZED = 1
BFFM_SETSELECTION = 0x00000467
BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, HWND, ctypes.c_uint, LPARAM, LPARAM)
class BROWSEINFO(ctypes.Structure):
_fields_ = [("hwndOwner", HWND), ("pidlRoot", LPVOID), ("pszDisplayName", LPWSTR), ("lpszTitle", LPCWSTR), ("ulFlags", UINT), ("lpfn", BrowseCallbackProc), ("lParam", LPCWSTR), ("iImage", ctypes.c_int)]
try:
CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition
CalculatePopupWindowPosition.argtypes = [ctypes.POINTER(POINT), ctypes.POINTER(SIZE), UINT, ctypes.POINTER(RECT), ctypes.POINTER(RECT)]
GetParent = ctypes.windll.user32.GetParent
GetParent.argtypes = [HWND]
GetWindowRect = ctypes.windll.user32.GetWindowRect
GetWindowRect.argtypes = [HWND, ctypes.POINTER(RECT)]
except: # Not supported under Wine 4.0
CalculatePopupWindowPosition = None
class PreferencesDialog(tk.Toplevel):
def __init__(self, parent, callback):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.callback = callback
self.title(platform=='darwin' and _('Preferences') or
_('Settings'))
if parent.winfo_viewable():
self.transient(parent)
# position over parent
if platform!='darwin' or parent.winfo_rooty()>0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
self.geometry("+%d+%d" % (parent.winfo_rootx(), parent.winfo_rooty()))
# remove decoration
if platform=='win32':
self.attributes('-toolwindow', tk.TRUE)
elif platform=='darwin':
# http://wiki.tcl.tk/13428
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
self.resizable(tk.FALSE, tk.FALSE)
self.cmdr = False # Note if Cmdr changes in the Journal
self.is_beta = False # Note if Beta status changes in the Journal
self.cmdrchanged_alarm = None
frame = ttk.Frame(self)
frame.grid(sticky=tk.NSEW)
notebook = nb.Notebook(frame)
notebook.bind('<<NotebookTabChanged>>', self.tabchanged) # Recompute on tab change
PADX = 10
BUTTONX = 12 # indent Checkbuttons and Radiobuttons
PADY = 2 # close spacing
outframe = nb.Frame(notebook)
outframe.columnconfigure(0, weight=1)
if prefsVersion.shouldSetDefaults('0.0.0.0', not bool(config.getint('output'))):
output = config.OUT_SHIP # default settings
else:
output = config.getint('output')
self.out_label = nb.Label(outframe, text=_('Please choose what data to save'))
self.out_label.grid(columnspan=2, padx=PADX, sticky=tk.W)
self.out_csv = tk.IntVar(value = (output & config.OUT_MKT_CSV ) and 1)
self.out_csv_button = nb.Checkbutton(outframe, text=_('Market data in CSV format file'), variable=self.out_csv, command=self.outvarchanged)
self.out_csv_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_td = tk.IntVar(value = (output & config.OUT_MKT_TD ) and 1)
self.out_td_button = nb.Checkbutton(outframe, text=_('Market data in Trade Dangerous format file'), variable=self.out_td, command=self.outvarchanged)
self.out_td_button.grid(columnspan=2, padx=BUTTONX, sticky=tk.W)
self.out_ship= tk.IntVar(value = (output & config.OUT_SHIP and 1))
self.out_ship_button = nb.Checkbutton(outframe, text=_('Ship loadout'), variable=self.out_ship, command=self.outvarchanged) # Output setting
self.out_ship_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W)
self.out_auto = tk.IntVar(value = 0 if output & config.OUT_MKT_MANUAL else 1) # inverted
self.out_auto_button = nb.Checkbutton(outframe, text=_('Automatically update on docking'), variable=self.out_auto, command=self.outvarchanged) # Output setting
self.out_auto_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W)
self.outdir = tk.StringVar()
self.outdir.set(config.get('outdir'))
self.outdir_label = nb.Label(outframe, text=_('File location')+':') # Section heading in settings
self.outdir_label.grid(padx=PADX, pady=(5,0), sticky=tk.W)
self.outdir_entry = nb.Entry(outframe, takefocus=False)
self.outdir_entry.grid(columnspan=2, padx=PADX, pady=(0,PADY), sticky=tk.EW)
self.outbutton = nb.Button(outframe, text=(platform=='darwin' and _('Change...') or # Folder selection button on OSX
_('Browse...')), # Folder selection button on Windows
command = lambda:self.filebrowse(_('File location'), self.outdir))
self.outbutton.grid(column=1, padx=PADX, pady=PADY, sticky=tk.NSEW)
nb.Frame(outframe).grid(pady=5) # bottom spacer
notebook.add(outframe, text=_('Output')) # Tab heading in settings
# build plugin prefs tabs
for plugin in plug.PLUGINS:
plugframe = plugin.get_prefs(notebook, monitor.cmdr, monitor.is_beta)
if plugframe:
notebook.add(plugframe, text=plugin.name)
configframe = nb.Frame(notebook)
configframe.columnconfigure(1, weight=1)
self.logdir = tk.StringVar()
self.logdir.set(config.get('journaldir') or config.default_journal_dir or '')
self.logdir_entry = nb.Entry(configframe, takefocus=False)
nb.Label(configframe, text = _('E:D journal file location')+':').grid(columnspan=4, padx=PADX, sticky=tk.W) # Location of the new Journal file in E:D 2.2
self.logdir_entry.grid(columnspan=4, padx=PADX, pady=(0,PADY), sticky=tk.EW)
self.logbutton = nb.Button(configframe, text=(platform=='darwin' and _('Change...') or # Folder selection button on OSX
_('Browse...')), # Folder selection button on Windows
command = lambda:self.filebrowse(_('E:D journal file location'), self.logdir))
self.logbutton.grid(row=10, column=3, padx=PADX, pady=PADY, sticky=tk.EW)
if config.default_journal_dir:
nb.Button(configframe, text=_('Default'), command=self.logdir_reset, state = config.get('journaldir') and tk.NORMAL or tk.DISABLED).grid(row=10, column=2, pady=PADY, sticky=tk.EW) # Appearance theme and language setting
if platform in ['darwin','win32']:
ttk.Separator(configframe, orient=tk.HORIZONTAL).grid(columnspan=4, padx=PADX, pady=PADY*4, sticky=tk.EW)
self.hotkey_code = config.getint('hotkey_code')
self.hotkey_mods = config.getint('hotkey_mods')
self.hotkey_only = tk.IntVar(value = not config.getint('hotkey_always'))
self.hotkey_play = tk.IntVar(value = not config.getint('hotkey_mute'))
nb.Label(configframe, text = platform=='darwin' and
_('Keyboard shortcut') or # Hotkey/Shortcut settings prompt on OSX
_('Hotkey') # Hotkey/Shortcut settings prompt on Windows
).grid(row=20, padx=PADX, sticky=tk.W)
if platform == 'darwin' and not was_accessible_at_launch:
if AXIsProcessTrusted():
nb.Label(configframe, text = _('Re-start {APP} to use shortcuts').format(APP=applongname), foreground='firebrick').grid(padx=PADX, sticky=tk.W) # Shortcut settings prompt on OSX
else:
nb.Label(configframe, text = _('{APP} needs permission to use shortcuts').format(APP=applongname), foreground='firebrick').grid(columnspan=4, padx=PADX, sticky=tk.W) # Shortcut settings prompt on OSX
nb.Button(configframe, text = _('Open System Preferences'), command = self.enableshortcuts).grid(padx=PADX, sticky=tk.E) # Shortcut settings button on OSX
else:
self.hotkey_text = nb.Entry(configframe, width = (platform == 'darwin' and 20 or 30), justify=tk.CENTER)
self.hotkey_text.insert(0, self.hotkey_code and hotkeymgr.display(self.hotkey_code, self.hotkey_mods) or _('None')) # No hotkey/shortcut currently defined
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
self.hotkey_text.grid(row=20, column=1, columnspan=2, pady=(5,0), sticky=tk.W)
self.hotkey_only_btn = nb.Checkbutton(configframe, text=_('Only when Elite: Dangerous is the active app'), variable=self.hotkey_only, state = self.hotkey_code and tk.NORMAL or tk.DISABLED) # Hotkey/Shortcut setting
self.hotkey_only_btn.grid(columnspan=4, padx=PADX, pady=(5,0), sticky=tk.W)
self.hotkey_play_btn = nb.Checkbutton(configframe, text=_('Play sound'), variable=self.hotkey_play, state = self.hotkey_code and tk.NORMAL or tk.DISABLED) # Hotkey/Shortcut setting
self.hotkey_play_btn.grid(columnspan=4, padx=PADX, sticky=tk.W)
# Option to disabled Automatic Check For Updates whilst in-game
ttk.Separator(configframe, orient=tk.HORIZONTAL).grid(columnspan=4, padx=PADX, pady=PADY*4, sticky=tk.EW)
self.disable_autoappupdatecheckingame = tk.IntVar(value = config.getint('disable_autoappupdatecheckingame'))
self.disable_autoappupdatecheckingame_btn = nb.Checkbutton(configframe, text=_('Disable Automatic Application Updates Check when in-game'), variable=self.disable_autoappupdatecheckingame, command=self.disable_autoappupdatecheckingame_changed)
self.disable_autoappupdatecheckingame_btn.grid(columnspan=4, padx=PADX, sticky=tk.W)
ttk.Separator(configframe, orient=tk.HORIZONTAL).grid(columnspan=4, padx=PADX, pady=PADY*4, sticky=tk.EW)
nb.Label(configframe, text=_('Preferred websites')).grid(row=30, columnspan=4, padx=PADX, sticky=tk.W) # Settings prompt for preferred ship loadout, system and station info websites
self.shipyard_provider = tk.StringVar(value = config.get('shipyard_provider') in plug.provides('shipyard_url') and config.get('shipyard_provider') or 'EDSY')
nb.Label(configframe, text=_('Shipyard')).grid(row=31, padx=PADX, pady=2*PADY, sticky=tk.W) # Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis
self.shipyard_button = nb.OptionMenu(configframe, self.shipyard_provider, self.shipyard_provider.get(), *plug.provides('shipyard_url'))
self.shipyard_button.configure(width = 15)
self.shipyard_button.grid(row=31, column=1, sticky=tk.W)
self.system_provider = tk.StringVar(value = config.get('system_provider') in plug.provides('system_url') and config.get('system_provider') or 'EDSM')
nb.Label(configframe, text=_('System')).grid(row=32, padx=PADX, pady=2*PADY, sticky=tk.W)
self.system_button = nb.OptionMenu(configframe, self.system_provider, self.system_provider.get(), *plug.provides('system_url'))
self.system_button.configure(width = 15)
self.system_button.grid(row=32, column=1, sticky=tk.W)
self.station_provider = tk.StringVar(value = config.get('station_provider') in plug.provides('station_url') and config.get('station_provider') or 'eddb')
nb.Label(configframe, text=_('Station')).grid(row=33, padx=PADX, pady=2*PADY, sticky=tk.W)
self.station_button = nb.OptionMenu(configframe, self.station_provider, self.station_provider.get(), *plug.provides('station_url'))
self.station_button.configure(width = 15)
self.station_button.grid(row=33, column=1, sticky=tk.W)
nb.Label(configframe).grid(sticky=tk.W) # big spacer
notebook.add(configframe, text=_('Configuration')) # Tab heading in settings
self.languages = Translations.available_names()
self.lang = tk.StringVar(value = self.languages.get(config.get('language'), _('Default'))) # Appearance theme and language setting
self.always_ontop = tk.BooleanVar(value = config.getint('always_ontop'))
self.theme = tk.IntVar(value = config.getint('theme'))
self.theme_colors = [config.get('dark_text'), config.get('dark_highlight')]
self.theme_prompts = [
_('Normal text'), # Dark theme color setting
_('Highlighted text'), # Dark theme color setting
]
themeframe = nb.Frame(notebook)
themeframe.columnconfigure(2, weight=1)
nb.Label(themeframe, text=_('Language')).grid(row=10, padx=PADX, sticky=tk.W) # Appearance setting prompt
self.lang_button = nb.OptionMenu(themeframe, self.lang, self.lang.get(), *self.languages.values())
self.lang_button.grid(row=10, column=1, columnspan=2, padx=PADX, sticky=tk.W)
ttk.Separator(themeframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY*4, sticky=tk.EW)
nb.Label(themeframe, text=_('Theme')).grid(columnspan=3, padx=PADX, sticky=tk.W) # Appearance setting
nb.Radiobutton(themeframe, text=_('Default'), variable=self.theme, value=0, command=self.themevarchanged).grid(columnspan=3, padx=BUTTONX, sticky=tk.W) # Appearance theme and language setting
nb.Radiobutton(themeframe, text=_('Dark'), variable=self.theme, value=1, command=self.themevarchanged).grid(columnspan=3, padx=BUTTONX, sticky=tk.W) # Appearance theme setting
if platform == 'win32':
nb.Radiobutton(themeframe, text=_('Transparent'), variable=self.theme, value=2, command=self.themevarchanged).grid(columnspan=3, padx=BUTTONX, sticky=tk.W) # Appearance theme setting
self.theme_label_0 = nb.Label(themeframe, text=self.theme_prompts[0])
self.theme_label_0.grid(row=20, padx=PADX, sticky=tk.W)
self.theme_button_0 = nb.ColoredButton(themeframe, text=_('Station'), background='grey4', command=lambda:self.themecolorbrowse(0)) # Main window
self.theme_button_0.grid(row=20, column=1, padx=PADX, pady=PADY, sticky=tk.NSEW)
self.theme_label_1 = nb.Label(themeframe, text=self.theme_prompts[1])
self.theme_label_1.grid(row=21, padx=PADX, sticky=tk.W)
self.theme_button_1 = nb.ColoredButton(themeframe, text=' Hutton Orbital ', background='grey4', command=lambda:self.themecolorbrowse(1)) # Do not translate
self.theme_button_1.grid(row=21, column=1, padx=PADX, pady=PADY, sticky=tk.NSEW)
ttk.Separator(themeframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY*4, sticky=tk.EW)
self.ontop_button = nb.Checkbutton(themeframe, text=_('Always on top'), variable=self.always_ontop, command=self.themevarchanged)
self.ontop_button.grid(columnspan=3, padx=BUTTONX, sticky=tk.W) # Appearance setting
nb.Label(themeframe).grid(sticky=tk.W) # big spacer
notebook.add(themeframe, text=_('Appearance')) # Tab heading in settings
# Plugin settings and info
plugsframe = nb.Frame(notebook)
plugsframe.columnconfigure(0, weight=1)
plugdir = tk.StringVar()
plugdir.set(config.plugin_dir)
nb.Label(plugsframe, text=_('Plugins folder')+':').grid(padx=PADX, sticky=tk.W) # Section heading in settings
plugdirentry = nb.Entry(plugsframe, justify=tk.LEFT)
self.displaypath(plugdir, plugdirentry)
plugdirentry.grid(row=10, padx=PADX, sticky=tk.EW)
nb.Button(plugsframe, text=_('Open'), # Button that opens a folder in Explorer/Finder
command=lambda: webbrowser.open('file:///%s' % plugdir.get())).grid(row=10, column=1, padx=(0,PADX), sticky=tk.NSEW)
nb.Label(plugsframe, text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')).grid( # Help text in settings
columnspan=2, padx=PADX, pady=10, sticky=tk.NSEW)
enabled_plugins = [x for x in plug.PLUGINS if x.folder and x.module]
if len(enabled_plugins):
ttk.Separator(plugsframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY * 8, sticky=tk.EW)
nb.Label(plugsframe, text=_('Enabled Plugins')+':').grid(padx=PADX, sticky=tk.W) # List of plugins in settings
for plugin in enabled_plugins:
if plugin.name == plugin.folder:
label = nb.Label(plugsframe, text=plugin.name)
else:
label = nb.Label(plugsframe, text='%s (%s)' % (plugin.folder, plugin.name))
label.grid(columnspan=2, padx=PADX*2, sticky=tk.W)
############################################################
# Show which plugins don't have Python 3.x support
############################################################
if len(plug.PLUGINS_not_py3):
ttk.Separator(plugsframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY * 8, sticky=tk.EW)
nb.Label(plugsframe, text=_('Plugins Without Python 3.x Support:')+':').grid(padx=PADX, sticky=tk.W)
for plugin in plug.PLUGINS_not_py3:
if plugin.folder: # 'system' ones have this set to None to suppress listing in Plugins prefs tab
nb.Label(plugsframe, text=plugin.name).grid(columnspan=2, padx=PADX*2, sticky=tk.W)
HyperlinkLabel(plugsframe, text=_('Information on migrating plugins'), background=nb.Label().cget('background'), url='https://github.com/EDCD/EDMarketConnector/blob/master/PLUGINS.md#migration-to-python-37', underline=True).grid(columnspan=2, padx=PADX, sticky=tk.W)
############################################################
disabled_plugins = [x for x in plug.PLUGINS if x.folder and not x.module]
if len(disabled_plugins):
ttk.Separator(plugsframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY * 8, sticky=tk.EW)
nb.Label(plugsframe, text=_('Disabled Plugins')+':').grid(padx=PADX, sticky=tk.W) # List of plugins in settings
for plugin in disabled_plugins:
nb.Label(plugsframe, text=plugin.name).grid(columnspan=2, padx=PADX*2, sticky=tk.W)
notebook.add(plugsframe, text=_('Plugins')) # Tab heading in settings
if platform=='darwin':
self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes
else:
buttonframe = ttk.Frame(frame)
buttonframe.grid(padx=PADX, pady=PADX, sticky=tk.NSEW)
buttonframe.columnconfigure(0, weight=1)
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
button.grid(row=0, column=1, sticky=tk.E)
button.bind("<Return>", lambda event:self.apply())
self.protocol("WM_DELETE_WINDOW", self._destroy)
# Selectively disable buttons depending on output settings
self.cmdrchanged()
self.themevarchanged()
# disable hotkey for the duration
hotkeymgr.unregister()
# wait for window to appear on screen before calling grab_set
self.parent.update_idletasks()
self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on OSX & Linux
self.wait_visibility()
self.grab_set()
# Ensure fully on-screen
if platform == 'win32' and CalculatePopupWindowPosition:
position = RECT()
GetWindowRect(GetParent(self.winfo_id()), position)
if CalculatePopupWindowPosition(POINT(parent.winfo_rootx(), parent.winfo_rooty()),
SIZE(position.right - position.left, position.bottom - position.top),
0x10000, None, position):
self.geometry("+%d+%d" % (position.left, position.top))
def cmdrchanged(self, event=None):
if self.cmdr != monitor.cmdr or self.is_beta != monitor.is_beta:
# Cmdr has changed - update settings
if self.cmdr is not False: # Don't notify on first run
plug.notify_prefs_cmdr_changed(monitor.cmdr, monitor.is_beta)
self.cmdr = monitor.cmdr
self.is_beta = monitor.is_beta
# Poll
self.cmdrchanged_alarm = self.after(1000, self.cmdrchanged)
def tabchanged(self, event):
self.outvarchanged()
if platform == 'darwin':
# Hack to recompute size so that buttons show up under Mojave
notebook = event.widget
frame = self.nametowidget(notebook.winfo_parent())
temp = nb.Label(frame)
temp.grid()
temp.update_idletasks()
temp.destroy()
def outvarchanged(self, event=None):
self.displaypath(self.outdir, self.outdir_entry)
self.displaypath(self.logdir, self.logdir_entry)
logdir = self.logdir.get()
logvalid = logdir and exists(logdir)
self.out_label['state'] = self.out_csv_button['state'] = self.out_td_button['state'] = self.out_ship_button['state'] = tk.NORMAL or tk.DISABLED
local = self.out_td.get() or self.out_csv.get() or self.out_ship.get()
self.out_auto_button['state'] = local and logvalid and tk.NORMAL or tk.DISABLED
self.outdir_label['state'] = local and tk.NORMAL or tk.DISABLED
self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED
self.outdir_entry['state'] = local and 'readonly' or tk.DISABLED
def filebrowse(self, title, pathvar):
if platform != 'win32':
import tkinter.filedialog
d = tkinter.filedialog.askdirectory(parent=self, initialdir=expanduser(pathvar.get()), title=title, mustexist=tk.TRUE)
else:
def browsecallback(hwnd, uMsg, lParam, lpData):
# set initial folder
if uMsg==BFFM_INITIALIZED and lpData:
ctypes.windll.user32.SendMessageW(hwnd, BFFM_SETSELECTION, 1, lpData);
return 0
browseInfo = BROWSEINFO()
browseInfo.lpszTitle = title
browseInfo.ulFlags = BIF_RETURNONLYFSDIRS|BIF_USENEWUI
browseInfo.lpfn = BrowseCallbackProc(browsecallback)
browseInfo.lParam = pathvar.get().startswith('~') and join(config.home, pathvar.get()[2:]) or pathvar.get()
ctypes.windll.ole32.CoInitialize(None)
pidl = ctypes.windll.shell32.SHBrowseForFolderW(ctypes.byref(browseInfo))
if pidl:
path = ctypes.create_unicode_buffer(MAX_PATH)
ctypes.windll.shell32.SHGetPathFromIDListW(pidl, path)
ctypes.windll.ole32.CoTaskMemFree(pidl)
d = path.value
else:
d = None
if d:
pathvar.set(d)
self.outvarchanged()
def displaypath(self, pathvar, entryfield):
entryfield['state'] = tk.NORMAL # must be writable to update
entryfield.delete(0, tk.END)
if platform=='win32':
start = pathvar.get().lower().startswith(config.home.lower()) and len(config.home.split('\\')) or 0
display = []
components = normpath(pathvar.get()).split('\\')
buf = ctypes.create_unicode_buffer(MAX_PATH)
pidsRes = ctypes.c_int()
for i in range(start, len(components)):
try:
if (not SHGetLocalizedName('\\'.join(components[:i+1]), buf, MAX_PATH, ctypes.byref(pidsRes)) and
LoadString(ctypes.WinDLL(expandvars(buf.value))._handle, pidsRes.value, buf, MAX_PATH)):
display.append(buf.value)
else:
display.append(components[i])
except:
display.append(components[i])
entryfield.insert(0, '\\'.join(display))
elif platform=='darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()): # None if path doesn't exist
if pathvar.get().startswith(config.home):
display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)):]
else:
display = NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())
entryfield.insert(0, '/'.join(display))
else:
if pathvar.get().startswith(config.home):
entryfield.insert(0, '~' + pathvar.get()[len(config.home):])
else:
entryfield.insert(0, pathvar.get())
entryfield['state'] = 'readonly'
def logdir_reset(self):
if config.default_journal_dir:
self.logdir.set(config.default_journal_dir)
self.outvarchanged()
def disable_autoappupdatecheckingame_changed(self):
config.set('disable_autoappupdatecheckingame', self.disable_autoappupdatecheckingame.get())
# If it's now False, re-enable WinSparkle ? Need access to the AppWindow.updater variable to call down
def themecolorbrowse(self, index):
(rgb, color) = tkColorChooser.askcolor(self.theme_colors[index], title=self.theme_prompts[index], parent=self.parent)
if color:
self.theme_colors[index] = color
self.themevarchanged()
def themevarchanged(self):
self.theme_button_0['foreground'], self.theme_button_1['foreground'] = self.theme_colors
state = self.theme.get() and tk.NORMAL or tk.DISABLED
self.theme_label_0['state'] = state
self.theme_label_1['state'] = state
self.theme_button_0['state'] = state
self.theme_button_1['state'] = state
def hotkeystart(self, event):
event.widget.bind('<KeyPress>', self.hotkeylisten)
event.widget.bind('<KeyRelease>', self.hotkeylisten)
event.widget.delete(0, tk.END)
hotkeymgr.acquire_start()
def hotkeyend(self, event):
event.widget.unbind('<KeyPress>')
event.widget.unbind('<KeyRelease>')
hotkeymgr.acquire_stop() # in case focus was lost while in the middle of acquiring
event.widget.delete(0, tk.END)
self.hotkey_text.insert(0, self.hotkey_code and hotkeymgr.display(self.hotkey_code, self.hotkey_mods) or _('None')) # No hotkey/shortcut currently defined
def hotkeylisten(self, event):
good = hotkeymgr.fromevent(event)
if good:
(hotkey_code, hotkey_mods) = good
event.widget.delete(0, tk.END)
event.widget.insert(0, hotkeymgr.display(hotkey_code, hotkey_mods))
if hotkey_code:
# done
(self.hotkey_code, self.hotkey_mods) = (hotkey_code, hotkey_mods)
self.hotkey_only_btn['state'] = tk.NORMAL
self.hotkey_play_btn['state'] = tk.NORMAL
self.hotkey_only_btn.focus() # move to next widget - calls hotkeyend() implicitly
else:
if good is None: # clear
(self.hotkey_code, self.hotkey_mods) = (0, 0)
event.widget.delete(0, tk.END)
if self.hotkey_code:
event.widget.insert(0, hotkeymgr.display(self.hotkey_code, self.hotkey_mods))
self.hotkey_only_btn['state'] = tk.NORMAL
self.hotkey_play_btn['state'] = tk.NORMAL
else:
event.widget.insert(0, _('None')) # No hotkey/shortcut currently defined
self.hotkey_only_btn['state'] = tk.DISABLED
self.hotkey_play_btn['state'] = tk.DISABLED
self.hotkey_only_btn.focus() # move to next widget - calls hotkeyend() implicitly
return('break') # stops further processing - insertion, Tab traversal etc
def apply(self):
config.set('PrefsVersion', prefsVersion.stringToSerial(appversion))
config.set('output',
(self.out_td.get() and config.OUT_MKT_TD) +
(self.out_csv.get() and config.OUT_MKT_CSV) +
(config.OUT_MKT_MANUAL if not self.out_auto.get() else 0) +
(self.out_ship.get() and config.OUT_SHIP) +
(config.getint('output') & (config.OUT_MKT_EDDN | config.OUT_SYS_EDDN | config.OUT_SYS_DELAY)))
config.set('outdir', self.outdir.get().startswith('~') and join(config.home, self.outdir.get()[2:]) or self.outdir.get())
logdir = self.logdir.get()
if config.default_journal_dir and logdir.lower() == config.default_journal_dir.lower():
config.set('journaldir', '') # default location
else:
config.set('journaldir', logdir)
if platform in ['darwin','win32']:
config.set('hotkey_code', self.hotkey_code)
config.set('hotkey_mods', self.hotkey_mods)
config.set('hotkey_always', int(not self.hotkey_only.get()))
config.set('hotkey_mute', int(not self.hotkey_play.get()))
config.set('shipyard_provider', self.shipyard_provider.get())
config.set('system_provider', self.system_provider.get())
config.set('station_provider', self.station_provider.get())
lang_codes = { v: k for k, v in self.languages.items() } # Codes by name
config.set('language', lang_codes.get(self.lang.get()) or '')
Translations.install(config.get('language') or None)
config.set('always_ontop', self.always_ontop.get())
config.set('theme', self.theme.get())
config.set('dark_text', self.theme_colors[0])
config.set('dark_highlight', self.theme_colors[1])
theme.apply(self.parent)
# Notify
if self.callback:
self.callback()
plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta)
self._destroy()
def _destroy(self):
if self.cmdrchanged_alarm is not None:
self.after_cancel(self.cmdrchanged_alarm)
self.cmdrchanged_alarm = None
self.parent.wm_attributes('-topmost', config.getint('always_ontop') and 1 or 0)
self.destroy()
if platform == 'darwin':
def enableshortcuts(self):
self.apply()
# popup System Preferences dialog
try:
# http://stackoverflow.com/questions/6652598/cocoa-button-opens-a-system-preference-page/6658201
from ScriptingBridge import SBApplication
sysprefs = 'com.apple.systempreferences'
prefs = SBApplication.applicationWithBundleIdentifier_(sysprefs)
pane = [x for x in prefs.panes() if x.id() == 'com.apple.preference.security'][0]
prefs.setCurrentPane_(pane)
anchor = [x for x in pane.anchors() if x.name() == 'Privacy_Accessibility'][0]
anchor.reveal()
prefs.activate()
except:
AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True})
self.parent.event_generate('<<Quit>>', when="tail")