From f0683926fd1ded28868c967be848244572b669b0 Mon Sep 17 00:00:00 2001 From: Jonathan Harris Date: Mon, 1 Feb 2016 03:38:55 +0000 Subject: [PATCH] Dark theme Fixes #84 --- EDMarketConnector.py | 155 +++++++++++++++++++----------- L10n/cs.strings | 20 +++- L10n/de.strings | 29 +++++- L10n/en.template | 20 +++- L10n/es.strings | 23 ++++- L10n/fr.strings | 14 ++- L10n/it.strings | 14 ++- L10n/lv.strings | 14 ++- L10n/nl.strings | 14 ++- L10n/pl.strings | 29 +++++- L10n/ru.strings | 14 ++- L10n/sl.strings | 17 +++- L10n/uk.strings | 20 +++- README.md | 2 + img/mac_dark.png | Bin 0 -> 20789 bytes img/win_dark.png | Bin 0 -> 5177 bytes prefs.py | 62 +++++++++++- theme.py | 221 +++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 598 insertions(+), 70 deletions(-) create mode 100644 img/mac_dark.png create mode 100644 img/win_dark.png create mode 100644 theme.py diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 9b1ad36c..407f8cd0 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -43,6 +43,7 @@ import plug from edproxy import edproxy from hotkey import hotkeymgr from monitor import monitor +from theme import theme EDDB = eddb.EDDB() @@ -65,62 +66,65 @@ class AppWindow: self.w.rowconfigure(0, weight=1) self.w.columnconfigure(0, weight=1) + # Special handling for overrideredict + self.w.bind("", self.onmap) + plug.load_plugins() - if platform == 'win32': - self.w.wm_iconbitmap(default='EDMarketConnector.ico') - elif platform == 'linux2': - from PIL import Image, ImageTk - icon = ImageTk.PhotoImage(Image.open("EDMarketConnector.png")) - self.w.tk.call('wm', 'iconphoto', self.w, '-default', icon) - style = ttk.Style() - style.theme_use('clam') - elif platform=='darwin': - # Default ttk font choice looks bad on El Capitan - font = tkFont.Font(family='TkDefaultFont', size=13, weight=tkFont.NORMAL) - style = ttk.Style() - style.configure('TLabel', font=font) - style.configure('TButton', font=font) - style.configure('TLabelframe.Label', font=font) - style.configure('TCheckbutton', font=font) - style.configure('TRadiobutton', font=font) - style.configure('TEntry', font=font) + if platform != 'darwin': + if platform == 'win32': + self.w.wm_iconbitmap(default='EDMarketConnector.ico') + else: + from PIL import Image, ImageTk + self.w.tk.call('wm', 'iconphoto', self.w, '-default', ImageTk.PhotoImage(Image.open("EDMarketConnector.png"))) + self.theme_icon = tk.PhotoImage(data = 'R0lGODlhEAAQAMYAAAAAAAEAAAEBAQICAgQEAwYFBAsHBAoIBgwIBAwIBQ0IBA8JBBAJBBAKBRMMBRkPBhoQBykWCSoWCCoXCTsfCzwfCkAhDEIjDD8kC0AlDEEmC0EmDEcoDk4oDU8pDU4qEFMrD1ktDlotD1ouD1g0EWAyEWU0EV03EmA4EWo2EW03EWQ6Emw4EWo9FGo+E3Y8FH5AFH1IFoBJFo1RGo1SGY1SGpBTGZFTGZJTGZhYG6piHa1kHa5kHbBkHr9uIMt0IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEKAEAALAAAAAAQABAAAAd7gACCg4SFhoeHGCiIhRs5JwMCkpKGGTIFODaaNjc/D4QaMQMAk5MuEIQOO6OFAiscCIQNPQk8NTO4NDofLwayPi0mIMPDLAcqvoIBDiQWkaUCAykKhAsXAoYCHRKEDDAjIyIiIyEEHhHYhAPr7BQlE+mMABXo8oTx9oWBADs=') + self.theme_minimize = tk.BitmapImage(data = '#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') + self.theme_close = tk.BitmapImage(data = '#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') - frame = ttk.Frame(self.w, name=appname.lower()) + frame = tk.Frame(self.w, name=appname.lower()) frame.grid(sticky=tk.NSEW) frame.columnconfigure(1, weight=1) - ttk.Label(frame, text=_('Cmdr')+':').grid(row=0, column=0, sticky=tk.W) # Main window - ttk.Label(frame, text=_('System')+':').grid(row=1, column=0, sticky=tk.W) # Main window - ttk.Label(frame, text=_('Station')+':').grid(row=2, column=0, sticky=tk.W) # Main window + tk.Label(frame, text=_('Cmdr')+':').grid(row=1, column=0, sticky=tk.W) # Main window + tk.Label(frame, text=_('System')+':').grid(row=2, column=0, sticky=tk.W) # Main window + tk.Label(frame, text=_('Station')+':').grid(row=3, column=0, sticky=tk.W) # Main window - self.cmdr = ttk.Label(frame, width=-21) + self.cmdr = tk.Label(frame, anchor=tk.W) self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.system_url, popup_copy = True) self.station = HyperlinkLabel(frame, url = self.station_url, popup_copy = lambda x: x!=self.STATION_UNDOCKED) - self.cmdr.grid(row=0, column=1, sticky=tk.EW) - self.system.grid(row=1, column=1, sticky=tk.EW) - self.station.grid(row=2, column=1, sticky=tk.EW) + self.cmdr.grid(row=1, column=1, sticky=tk.EW) + self.system.grid(row=2, column=1, sticky=tk.EW) + self.station.grid(row=3, column=1, sticky=tk.EW) for plugname in plug.PLUGINS: appitem = plug.get_plugin_app(plugname, frame) if appitem: appitem.grid(columnspan=2, sticky=tk.W) - self.button = ttk.Button(frame, name='update', text=_('Update'), command=self.getandsend, default=tk.ACTIVE, state=tk.DISABLED) # Update button in main window - self.status = ttk.Label(frame, name='status', width=-25) - self.button.grid(columnspan=2, sticky=tk.NSEW) + minwidth = platform == 'darwin' and 32 or 28 + self.button = ttk.Button(frame, text=_('Update'), width=minwidth, command=self.getandsend, default=tk.ACTIVE, state=tk.DISABLED) # Update button in main window + self.theme_button = tk.Label(frame, text=_('Update'), width=minwidth, state=tk.DISABLED) # Update button in main window + self.status = tk.Label(frame, name='status', anchor=tk.W) + row = frame.grid_size()[1] + self.button.grid(row=row, columnspan=2, sticky=tk.NSEW) + self.theme_button.grid(row=row, columnspan=2, sticky=tk.NSEW) + theme.register_alternate((self.button, self.theme_button), {'row':row, 'columnspan':2, 'sticky':tk.NSEW}) self.status.grid(columnspan=2, sticky=tk.EW) + theme.button_bind(self.theme_button, self.getandsend) self.w.bind('', self.getandsend) self.w.bind('', self.getandsend) for child in frame.winfo_children(): - child.grid_configure(padx=5, pady=(platform=='darwin' and 3 or 2)) + child.grid_configure(padx=5, pady=(platform=='win32' and 1 or 3)) menubar = tk.Menu() if platform=='darwin': - from Foundation import NSBundle + # Can't handle (de)iconify if topmost is set, so suppress iconify button + # http://wiki.tcl.tk/13428 and p15 of https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf + root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox horizontalZoom resizable') + # https://www.tcl.tk/man/tcl/TkCmd/menu.htm apple_menu = tk.Menu(menubar, name='apple') apple_menu.add_command(label=_("About {APP}").format(APP=applongname), command=lambda:self.w.call('tk::mac::standardAboutPanel')) # App menu entry on OSX @@ -135,6 +139,7 @@ class AppWindow: menubar.add_cascade(label=_('View'), menu=self.view_menu) # Menu title on OSX window_menu = tk.Menu(menubar, name='window') menubar.add_cascade(label=_('Window'), menu=window_menu) # Menu title on OSX + self.w['menu'] = menubar # https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0') self.w.createcommand('tkAboutDialog', lambda:self.w.call('tk::mac::standardAboutPanel')) @@ -157,34 +162,53 @@ class AppWindow: self.always_ontop = tk.BooleanVar(value = config.getint('always_ontop')) system_menu = tk.Menu(menubar, name='system', tearoff=tk.FALSE) system_menu.add_separator() - system_menu.add_checkbutton(label=_('Always on top'), variable = self.always_ontop, command=self.ontop_changed) # System menu entry on Windows + system_menu.add_checkbutton(label=_('Always on top'), variable = self.always_ontop, command=self.ontop_changed) # Appearance setting menubar.add_cascade(menu=system_menu) - self.w.wm_attributes('-topmost', self.always_ontop.get()) self.w.bind('', self.copy) self.w.protocol("WM_DELETE_WINDOW", self.onexit) + theme.register(menubar) # menus and children aren't automatically registered + theme.register(file_menu) + theme.register(self.edit_menu) - if platform == 'linux2': - # Fix up menu to use same styling as everything else - (fg, bg, afg, abg) = (style.lookup('TLabel.label', 'foreground'), - style.lookup('TLabel.label', 'background'), - style.lookup('TButton.label', 'foreground', ['active']), - style.lookup('TButton.label', 'background', ['active'])) - menubar.configure( fg = fg, bg = bg, activeforeground = afg, activebackground = abg) - file_menu.configure(fg = fg, bg = bg, activeforeground = afg, activebackground = abg) - self.edit_menu.configure(fg = fg, bg = bg, activeforeground = afg, activebackground = abg) - self.w['menu'] = menubar + # Alternate title bar and menu for dark theme + theme_menubar = tk.Frame(frame) + theme_menubar.columnconfigure(2, weight=1) + theme_titlebar = tk.Label(theme_menubar, text=applongname, image=self.theme_icon, anchor=tk.W, compound=tk.LEFT) + theme_titlebar.grid(columnspan=3, sticky=tk.NSEW) + self.drag_offset = None + theme_titlebar.bind('', self.drag_start) + theme_titlebar.bind('', self.drag_continue) + theme_titlebar.bind('', self.drag_end) + if platform == 'win32': # Can't work out how to deiconify on Linux + theme_minimize = tk.Label(theme_menubar, image=self.theme_minimize) + theme_minimize.grid(row=0, column=3) + theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize) + theme_close = tk.Label(theme_menubar, image=self.theme_close) + theme_close.grid(row=0, column=4) + theme.button_bind(theme_close, self.onexit, image=self.theme_close) + theme_file_menu = tk.Label(theme_menubar, text=_('File'), anchor=tk.W) # Menu title on Windows + theme_file_menu.grid(row=1, column=0, padx=5, sticky=tk.W) + theme.button_bind(theme_file_menu, lambda e: file_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) + theme_edit_menu = tk.Label(theme_menubar, text=_('Edit'), anchor=tk.W) # Menu title + theme_edit_menu.grid(row=1, column=1, sticky=tk.W) + theme.button_bind(theme_edit_menu, lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) + theme.register_highlight(theme_titlebar) + theme.register(self.theme_minimize) # images aren't automatically registered + theme.register(self.theme_close) + theme.register_alternate((menubar, theme_menubar), {'row':0, 'columnspan':2, 'sticky':tk.NSEW}) # update geometry if config.get('geometry'): match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry')) if match and (platform!='darwin' or int(match.group(2))>0): # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 self.w.geometry(config.get('geometry')) - self.w.update_idletasks() - self.w.wait_visibility() - (w, h) = (self.w.winfo_width(), self.w.winfo_height()) - self.w.minsize(w, h) # Minimum size = initial size - if platform != 'linux2': # update_idletasks() doesn't allow for the menubar on Linux - self.w.maxsize(-1, h) # Maximum height = initial height + self.w.attributes('-topmost', config.getint('always_ontop') and 1 or 0) + self.w.resizable(tk.TRUE, tk.FALSE) + + theme.register(frame) + theme.register_highlight(self.system) + theme.register_highlight(self.station) + theme.apply(self.w) # Load updater after UI creation (for WinSparkle) import update @@ -212,7 +236,7 @@ class AppWindow: # call after credentials have changed def login(self): self.status['text'] = _('Logging in...') - self.button['state'] = tk.DISABLED + self.button['state'] = self.theme_button['state'] = tk.DISABLED self.w.update_idletasks() try: self.session.login(config.get('username'), config.get('password')) @@ -250,7 +274,7 @@ class AppWindow: config.save() # Save settings now for use by command-line app except Exception as e: if __debug__: print_exc() - self.button['state'] = tk.NORMAL + self.button['state'] = self.theme_button['state'] = tk.NORMAL self.status['text'] = unicode(e) else: return self.getandsend() # try again @@ -270,7 +294,7 @@ class AppWindow: self.cmdr['text'] = self.system['text'] = self.station['text'] = '' self.system['image'] = '' self.status['text'] = _('Fetching data...') - self.button['state'] = tk.DISABLED + self.theme_button['state'] = tk.DISABLED self.edit_menu.entryconfigure(_('Copy'), state=tk.DISABLED) self.w.update_idletasks() @@ -494,11 +518,11 @@ class AppWindow: def cooldown(self): if time() < self.holdofftime: - self.button['text'] = _('cooldown {SS}s').format(SS = int(self.holdofftime - time())) # Update button in main window + self.button['text'] = self.theme_button['text'] = _('cooldown {SS}s').format(SS = int(self.holdofftime - time())) # Update button in main window self.w.after(1000, self.cooldown) else: - self.button['text'] = _('Update') # Update button in main window - self.button['state'] = tk.NORMAL + self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window + self.button['state'] = self.theme_button['state'] = tk.NORMAL def ontop_changed(self, event=None): config.set('always_ontop', self.always_ontop.get()) @@ -519,6 +543,27 @@ class AppWindow: self.session.close() self.w.destroy() + def drag_start(self, event): + self.drag_offset = (event.x_root - self.w.winfo_rootx(), event.y_root - self.w.winfo_rooty()) + + def drag_continue(self, event): + if self.drag_offset: + self.w.geometry('+%d+%d' % (event.x_root - self.drag_offset[0], event.y_root - self.drag_offset[1])) + + def drag_end(self, event): + self.drag_offset = None + + def oniconify(self, event=None): + self.w.overrideredirect(0) # Can't iconize while overrideredirect + self.w.iconify() + self.w.update_idletasks() # Size and windows styles get recalculated here + self.w.wait_visibility() # Need main window to be re-created before returning + theme.active = None # So theme will be re-applied on map + + def onmap(self, event=None): + if event.widget == self.w: + theme.apply(self.w) + # Run the app if __name__ == "__main__": diff --git a/L10n/cs.strings b/L10n/cs.strings index 183c710b..74754cb0 100644 --- a/L10n/cs.strings +++ b/L10n/cs.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Aimless"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Vždy navrchu"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API klíč"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Vzhled"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Automaticky vytvořit záznam při vstoupení do systému"; @@ -85,12 +88,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Dangerous"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Tmavý"; + /* Combat rank. [stats.py] */ "Deadly" = "Deadly"; /* Trade rank. [stats.py] */ "Dealer" = "Dealer"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Výchozí"; + /* Empire rank. [stats.py] */ "Duke" = "Duke"; @@ -175,6 +184,9 @@ /* CQC rank. [stats.py] */ "Hero" = "Hero"; +/* Dark theme color setting. [prefs.py] */ +"Highlighted text" = "Zvýrazněný text"; + /* Tab heading in settings on Windows. [prefs.py] */ "Hotkey" = "Klávesová zkratka"; @@ -247,6 +259,9 @@ /* No hotkey/shortcut currently defined. [prefs.py] */ "None" = "Žádná"; +/* Dark theme color setting. [prefs.py] */ +"Normal text" = "Normální text"; + /* Combat rank. [stats.py] */ "Novice" = "Novice"; @@ -409,6 +424,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Systém"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Schéma"; + /* Ranking. [stats.py] */ "Trade" = "Trade"; diff --git a/L10n/de.strings b/L10n/de.strings index e3c6d0da..26176d25 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Total planlos"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Immer im Vordergrund"; /* CQC rank. [stats.py] */ @@ -19,9 +19,15 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API-Schlüssel"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Aussehen"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Automatisch Logbucheintrag bei Systemeintritt anlegen"; +/* [prefs.py] */ +"Automatically open uncharted systems’ EDSM pages" = "Automatisch die EDSM Seite unerforschter Systeme öffnen"; + /* Cmdr stats. [stats.py] */ "Balance" = "Kontostand"; @@ -61,6 +67,9 @@ /* Combat rank. [stats.py] */ "Competent" = "Kompetent"; +/* Output settings. [prefs.py] */ +"Connected to {EDPROXY} at {ADDR}" = "Verbunden mit {EDPROXY} bei {ADDR}"; + /* Update button in main window. [EDMarketConnector.py] */ "cooldown {SS}s" = "Wartezeit {SS}s"; @@ -79,12 +88,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Gefährlich"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Dunkel"; + /* Combat rank. [stats.py] */ "Deadly" = "Tödlich"; /* Trade rank. [stats.py] */ "Dealer" = "Kleinhändler"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Standard"; + /* Empire rank. [stats.py] */ "Duke" = "Herzog"; @@ -130,6 +145,9 @@ /* [companion.py] */ "Error: Server is down" = "Fehler: Server nicht erreichbar"; +/* [companion.py] */ +"Error: Verification failed" = "Fehler: Verifizierung fehlgeschlagen"; + /* Item in the File menu on Windows. [EDMarketConnector.py] */ "Exit" = "Beenden"; @@ -166,6 +184,9 @@ /* CQC rank. [stats.py] */ "Hero" = "Held"; +/* Dark theme color setting. [prefs.py] */ +"Highlighted text" = "Hervorgehobener Text"; + /* Tab heading in settings on Windows. [prefs.py] */ "Hotkey" = "Hotkey"; @@ -238,6 +259,9 @@ /* No hotkey/shortcut currently defined. [prefs.py] */ "None" = "Keine Zuweisung"; +/* Dark theme color setting. [prefs.py] */ +"Normal text" = "Standardmäßiger Text"; + /* Combat rank. [stats.py] */ "Novice" = "Neuling"; @@ -400,6 +424,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "System"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Theme"; + /* Ranking. [stats.py] */ "Trade" = "Handelsrang"; diff --git a/L10n/en.template b/L10n/en.template index 409ce08e..bb5a5ba4 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Aimless"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Always on top"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API Key"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Appearance"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Automatically make a log entry on entering a system"; @@ -85,12 +88,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Dangerous"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Dark"; + /* Combat rank. [stats.py] */ "Deadly" = "Deadly"; /* Trade rank. [stats.py] */ "Dealer" = "Dealer"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Default"; + /* Empire rank. [stats.py] */ "Duke" = "Duke"; @@ -175,6 +184,9 @@ /* CQC rank. [stats.py] */ "Hero" = "Hero"; +/* Dark theme color setting. [prefs.py] */ +"Highlighted text" = "Highlighted text"; + /* Tab heading in settings on Windows. [prefs.py] */ "Hotkey" = "Hotkey"; @@ -247,6 +259,9 @@ /* No hotkey/shortcut currently defined. [prefs.py] */ "None" = "None"; +/* Dark theme color setting. [prefs.py] */ +"Normal text" = "Normal text"; + /* Combat rank. [stats.py] */ "Novice" = "Novice"; @@ -409,6 +424,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "System"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Theme"; + /* Ranking. [stats.py] */ "Trade" = "Trade"; diff --git a/L10n/es.strings b/L10n/es.strings index dd7deeda..fae90dd4 100644 --- a/L10n/es.strings +++ b/L10n/es.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Perdido"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Siempre visible"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API Key"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Apariencia"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Crear automáticamente una entrada en el registro al entrar en un sistema"; @@ -64,6 +67,9 @@ /* Combat rank. [stats.py] */ "Competent" = "Competente"; +/* Output settings. [prefs.py] */ +"Connected to {EDPROXY} at {ADDR}" = "Conectado a {EDPROXY} en {ADDR}"; + /* Update button in main window. [EDMarketConnector.py] */ "cooldown {SS}s" = "Espere {SS}s"; @@ -82,12 +88,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Peligroso"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Oscuro"; + /* Combat rank. [stats.py] */ "Deadly" = "Letal"; /* Trade rank. [stats.py] */ "Dealer" = "Distribuidor"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Por defecto"; + /* Empire rank. [stats.py] */ "Duke" = "Duque"; @@ -172,6 +184,9 @@ /* CQC rank. [stats.py] */ "Hero" = "Héroe"; +/* Dark theme color setting. [prefs.py] */ +"Highlighted text" = "Texto resaltado"; + /* Tab heading in settings on Windows. [prefs.py] */ "Hotkey" = "Tecla de acceso directo"; @@ -244,6 +259,9 @@ /* No hotkey/shortcut currently defined. [prefs.py] */ "None" = "Ninguna"; +/* Dark theme color setting. [prefs.py] */ +"Normal text" = "Texto normal"; + /* Combat rank. [stats.py] */ "Novice" = "Novato"; @@ -406,6 +424,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistema"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Tema"; + /* Ranking. [stats.py] */ "Trade" = "Comercio"; diff --git a/L10n/fr.strings b/L10n/fr.strings index 2dd5ea83..4105e632 100644 --- a/L10n/fr.strings +++ b/L10n/fr.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Vagabond"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Toujours visible"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API Key"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Apparence"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Ajouter automatiquement une entrée au journal en entrant dans un système"; @@ -82,12 +85,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Vétéran"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Foncé"; + /* Combat rank. [stats.py] */ "Deadly" = "Létal"; /* Trade rank. [stats.py] */ "Dealer" = "Revendeur"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Par défaut"; + /* Empire rank. [stats.py] */ "Duke" = "Archiduc"; @@ -403,6 +412,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Système"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Thème"; + /* Ranking. [stats.py] */ "Trade" = "Commerçant"; diff --git a/L10n/it.strings b/L10n/it.strings index 28b29a41..609b1ffc 100644 --- a/L10n/it.strings +++ b/L10n/it.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Aimless"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Sempre in primo piano"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API Key"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Aspetto"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Inserisce automaticamente una log entry entrando in un sistema"; @@ -82,12 +85,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Dangerous"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Scuro"; + /* Combat rank. [stats.py] */ "Deadly" = "Deadly"; /* Trade rank. [stats.py] */ "Dealer" = "Dealer"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Predefinito"; + /* Empire rank. [stats.py] */ "Duke" = "Duke"; @@ -403,6 +412,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistema"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Tema"; + /* Ranking. [stats.py] */ "Trade" = "Trade"; diff --git a/L10n/lv.strings b/L10n/lv.strings index 6c74f15b..6c39abf0 100644 --- a/L10n/lv.strings +++ b/L10n/lv.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Aimless"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Vienmēr virspusē"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API atslēga"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Izskats"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Automātiski veikt ierakstu ierodoties sistēmā"; @@ -82,12 +85,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Dangerous"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Tumša"; + /* Combat rank. [stats.py] */ "Deadly" = "Deadly"; /* Trade rank. [stats.py] */ "Dealer" = "Dealer"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Noklusējuma"; + /* Empire rank. [stats.py] */ "Duke" = "Duke"; @@ -403,6 +412,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistēma"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Dizains"; + /* Ranking. [stats.py] */ "Trade" = "Trade"; diff --git a/L10n/nl.strings b/L10n/nl.strings index 1f3fbeda..f9e323b4 100644 --- a/L10n/nl.strings +++ b/L10n/nl.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Aimless"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Altijd op voorgrond"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API Key"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Vormgeving"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Automatisch een log regel aanmaken bij het binnengaan van een stelsel"; @@ -85,12 +88,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Dangerous"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Donker"; + /* Combat rank. [stats.py] */ "Deadly" = "Deadly"; /* Trade rank. [stats.py] */ "Dealer" = "Dealer"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Standaard"; + /* Empire rank. [stats.py] */ "Duke" = "Duke"; @@ -409,6 +418,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Systeem"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Thema"; + /* Ranking. [stats.py] */ "Trade" = "Handels rang"; diff --git a/L10n/pl.strings b/L10n/pl.strings index eaf61101..b09703ab 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Aimless"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Zawsze na wierzchu"; /* CQC rank. [stats.py] */ @@ -19,9 +19,15 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API Key"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Wygląd"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Stwórz automatycznie wpis w logu po wejściu do systemu"; +/* [prefs.py] */ +"Automatically open uncharted systems’ EDSM pages" = "Automatycznie otwieraj nie skatalogowane systemy w EDSM"; + /* Cmdr stats. [stats.py] */ "Balance" = "Saldo"; @@ -61,6 +67,9 @@ /* Combat rank. [stats.py] */ "Competent" = "Competent"; +/* Output settings. [prefs.py] */ +"Connected to {EDPROXY} at {ADDR}" = "Połąćzono do {EDPROXY} na {ADDR}"; + /* Update button in main window. [EDMarketConnector.py] */ "cooldown {SS}s" = "Oczekiwanie {SS} sek."; @@ -79,12 +88,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Dangerous"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Ciemny"; + /* Combat rank. [stats.py] */ "Deadly" = "Deadly"; /* Trade rank. [stats.py] */ "Dealer" = "Dealer"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Domyślne"; + /* Empire rank. [stats.py] */ "Duke" = "Duke"; @@ -130,6 +145,9 @@ /* [companion.py] */ "Error: Server is down" = "Błąd: Serwer padł"; +/* [companion.py] */ +"Error: Verification failed" = "Błąd: Weryfikacja niepoprawna"; + /* Item in the File menu on Windows. [EDMarketConnector.py] */ "Exit" = "Zakończ"; @@ -166,6 +184,9 @@ /* CQC rank. [stats.py] */ "Hero" = "Hero"; +/* Dark theme color setting. [prefs.py] */ +"Highlighted text" = "Podświetlony tekst"; + /* Tab heading in settings on Windows. [prefs.py] */ "Hotkey" = "Skr. Klaw."; @@ -238,6 +259,9 @@ /* No hotkey/shortcut currently defined. [prefs.py] */ "None" = "Brak"; +/* Dark theme color setting. [prefs.py] */ +"Normal text" = "Zwykły tekst"; + /* Combat rank. [stats.py] */ "Novice" = "Novice"; @@ -400,6 +424,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "System"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Schemat kolorystyczny"; + /* Ranking. [stats.py] */ "Trade" = "Trade"; diff --git a/L10n/ru.strings b/L10n/ru.strings index da679804..4d901e2b 100644 --- a/L10n/ru.strings +++ b/L10n/ru.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Бесцельный"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Поверх остальных окон"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "Ключ API"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Внешний вид"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Автоматически заносить в журнал полета каждую посещённую систему"; @@ -82,12 +85,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Опасный"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Темный"; + /* Combat rank. [stats.py] */ "Deadly" = "Смертоносный"; /* Trade rank. [stats.py] */ "Dealer" = "Агент"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "По умолчанию"; + /* Empire rank. [stats.py] */ "Duke" = "Герцог"; @@ -403,6 +412,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Система"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Тема"; + /* Ranking. [stats.py] */ "Trade" = "Торговый"; diff --git a/L10n/sl.strings b/L10n/sl.strings index bb130e7f..daf35b08 100644 --- a/L10n/sl.strings +++ b/L10n/sl.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Aimless"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Vedno na vrhu"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API ključ"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Videz"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Shrani podatke v dnevnik leta, ko prispeš v sistem"; @@ -61,6 +64,9 @@ /* Combat rank. [stats.py] */ "Competent" = "Competent"; +/* Output settings. [prefs.py] */ +"Connected to {EDPROXY} at {ADDR}" = "Connected to {EDPROXY} at {ADDR}"; + /* Update button in main window. [EDMarketConnector.py] */ "cooldown {SS}s" = "Posodobitev v {SS}s"; @@ -79,12 +85,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Dangerous"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Temno"; + /* Combat rank. [stats.py] */ "Deadly" = "Deadly"; /* Trade rank. [stats.py] */ "Dealer" = "Dealer"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Privzeta"; + /* Empire rank. [stats.py] */ "Duke" = "Duke"; @@ -400,6 +412,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistem"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Tema"; + /* Ranking. [stats.py] */ "Trade" = "Trgovanje"; diff --git a/L10n/uk.strings b/L10n/uk.strings index fa00f2a6..29459134 100644 --- a/L10n/uk.strings +++ b/L10n/uk.strings @@ -10,7 +10,7 @@ /* Explorer rank. [stats.py] */ "Aimless" = "Безцільний"; -/* System menu entry on Windows. [EDMarketConnector.py] */ +/* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Поверх інших вікон"; /* CQC rank. [stats.py] */ @@ -19,6 +19,9 @@ /* EDSM setting. [prefs.py] */ "API Key" = "API Ключ"; +/* Tab heading in settings. [prefs.py] */ +"Appearance" = "Зовнішній вигляд"; + /* Output setting. [prefs.py] */ "Automatically make a log entry on entering a system" = "Автоматично робити запис у журналі на вході в систему"; @@ -85,12 +88,18 @@ /* Combat rank. [stats.py] */ "Dangerous" = "Небезпечний"; +/* Appearance theme setting. [prefs.py] */ +"Dark" = "Темний"; + /* Combat rank. [stats.py] */ "Deadly" = "Убивчий"; /* Trade rank. [stats.py] */ "Dealer" = "Дилер"; +/* Appearance theme setting. [prefs.py] */ +"Default" = "Стандарт"; + /* Empire rank. [stats.py] */ "Duke" = "Герцог"; @@ -175,6 +184,9 @@ /* CQC rank. [stats.py] */ "Hero" = "Герой"; +/* Dark theme color setting. [prefs.py] */ +"Highlighted text" = "Виділений текст"; + /* Tab heading in settings on Windows. [prefs.py] */ "Hotkey" = "Горяча клавіша"; @@ -247,6 +259,9 @@ /* No hotkey/shortcut currently defined. [prefs.py] */ "None" = "Нічого"; +/* Dark theme color setting. [prefs.py] */ +"Normal text" = "Нормальний текст"; + /* Combat rank. [stats.py] */ "Novice" = "Новачок"; @@ -409,6 +424,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Система"; +/* Appearance setting. [prefs.py] */ +"Theme" = "Тема"; + /* Ranking. [stats.py] */ "Trade" = "Торговий"; diff --git a/README.md b/README.md index 4fc760dc..1962bc66 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Click on the station name to go to its [Elite: Dangerous Database](http://eddb.i ![Windows screenshot](img/win.png)   ![Mac screenshot](img/mac.png) +![Windows screenshot](img/win_dark.png)   ![Mac screenshot](img/mac_dark.png) + Installation -------- diff --git a/img/mac_dark.png b/img/mac_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0231eaf1e4de4fdf8c35cc46778c92dc82f6dfac GIT binary patch literal 20789 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 -Pm| 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 literal 0 HcmV?d00001 diff --git a/img/win_dark.png b/img/win_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6e98ef46aa0da45a8ad21dc8c0a68c127498f181 GIT binary patch literal 5177 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', self.hotkeylisten) event.widget.bind('', self.hotkeylisten) @@ -396,6 +445,12 @@ class PreferencesDialog(tk.Toplevel): config.set('hotkey_always', int(not self.hotkey_only.get())) config.set('hotkey_mute', int(not self.hotkey_play.get())) + 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) + config.set('anonymous', self.out_anon.get()) self._destroy() @@ -412,6 +467,7 @@ class PreferencesDialog(tk.Toplevel): else: monitor.stop() edproxy.stop() + self.parent.wm_attributes('-topmost', config.getint('always_ontop') and 1 or 0) self.destroy() if platform == 'darwin': diff --git a/theme.py b/theme.py new file mode 100644 index 00000000..e3e3212c --- /dev/null +++ b/theme.py @@ -0,0 +1,221 @@ +# +# Theme support +# +# Because of various ttk limitations this app is an unholy mix of Tk and ttk widgets. +# So can't use ttk's theme support. So have to change colors manually. +# + +from sys import platform + +import Tkinter as tk +import ttk +import tkFont + +from config import appname, applongname, config + + +class _Theme: + + def __init__(self): + self.active = None # Starts out with no theme + self.minwidth = None + self.widgets = set() + self.widgets_highlight = set() + self.widgets_pair = [] + + def register(self, widget): + assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget + if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame): + for child in widget.winfo_children(): + self.register(child) + self.widgets.add(widget) + + def register_highlight(self, widget): + assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget + if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame): + self.register_highlight(widget.winfo_children()) + self.widgets_highlight.add(widget) + + def register_alternate(self, pair, gridopts): + self.widgets_pair.append((pair, gridopts)) + + def button_bind(self, widget, command, image=None): + widget.bind('', command) + widget.bind('', lambda e: self._enter(e, image)) + widget.bind('', lambda e: self._leave(e, image)) + + def _enter(self, event, image): + widget = event.widget + if widget and widget['state'] != tk.DISABLED: + widget.configure(state = tk.ACTIVE) + if image: + image.configure(foreground = self.current['activeforeground'], background = self.current['activebackground']) + + def _leave(self, event, image): + widget = event.widget + if widget and widget['state'] != tk.DISABLED: + widget.configure(state = tk.NORMAL) + if image: + image.configure(foreground = self.current['foreground'], background = self.current['background']) + + # Set up colors + def _colors(self, root, theme): + style = ttk.Style() + if platform == 'linux2': + style.theme_use('clam') + elif platform == 'darwin': + # Default ttk font spacing looks bad on El Capitan + osxfont = tkFont.Font(family='TkDefaultFont', size=13, weight=tkFont.NORMAL) + style.configure('TLabel', font=osxfont) + style.configure('TButton', font=osxfont) + style.configure('TLabelframe.Label', font=osxfont) + style.configure('TCheckbutton', font=osxfont) + style.configure('TRadiobutton', font=osxfont) + style.configure('TEntry', font=osxfont) + + # Default dark theme colors + if not config.get('dark_text'): + config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker + if not config.get('dark_highlight'): + config.set('dark_highlight', 'white') + + if theme: + # Dark + (r, g, b) = root.winfo_rgb(config.get('dark_text')) + self.current = { + 'background' : 'black', + 'foreground' : config.get('dark_text'), + 'activebackground' : config.get('dark_text'), + 'activeforeground' : 'black', + 'disabledforeground' : '#%02x%02x%02x' % (r/384, g/384, b/384), + 'highlight' : config.get('dark_highlight'), + 'font' : 'TkDefaultFont', + } + # Overrides + if platform == 'darwin': + self.current['font'] = osxfont + + else: + # System colors + self.current = { + 'background' : style.lookup('TLabel', 'background'), + 'foreground' : style.lookup('TLabel', 'foreground'), + 'activebackground' : style.lookup('TLabel', 'background', ['active']), + 'activeforeground' : style.lookup('TLabel', 'foreground', ['active']), + 'disabledforeground' : style.lookup('TLabel', 'foreground', ['disabled']), + 'highlight' : 'blue', + 'font' : 'TkDefaultFont', + } + # Overrides + if platform == 'darwin': + self.current['background'] = 'systemMovableModalBackground' + self.current['font'] = osxfont + elif platform == 'win32': + # Menu colors + self.current['activebackground'] = 'SystemHighlight' + self.current['activeforeground'] = 'SystemHighlightText' + + + # Apply configured theme + def apply(self, root): + + theme = config.getint('theme') + self._colors(root, theme) + + # Apply colors + for widget in self.widgets: + if isinstance(widget, tk.BitmapImage): + # not a widget + widget.configure(foreground = self.current['foreground'], + background = self.current['background']) + elif 'activeforeground' in widget.keys(): + # e.g. tk.Button, tk.Label, tk.Menu + widget.configure(foreground = self.current['foreground'], + background = self.current['background'], + activeforeground = self.current['activeforeground'], + activebackground = self.current['activebackground'], + disabledforeground = self.current['disabledforeground'], + font = self.current['font'] + ) + elif 'foreground' in widget.keys(): + # e.g. ttk.Label + widget.configure(foreground = self.current['foreground'], + background = self.current['background'], + font = self.current['font']) + elif 'background' in widget.keys(): + # e.g. Frame + widget.configure(background = self.current['background']) + + for widget in self.widgets_highlight: + widget.configure(foreground = self.current['highlight'], + background = self.current['background']) + + for pair, gridopts in self.widgets_pair: + (default, dark) = pair + if isinstance(default, tk.Menu): + if theme: + root['menu'] = '' + dark.grid(**gridopts) + else: + root['menu'] = default + dark.grid_remove() + else: + old = theme and default or dark + current = theme and dark or default + old.grid_remove() + current.grid(**gridopts) + + if self.active == theme: + return # Don't need to mess with the window manager + else: + self.active = theme + + if platform == 'darwin': + from AppKit import NSApplication, NSAppearance, NSColor + root.update_idletasks() # need main window to be created + appearance = NSAppearance.appearanceNamed_(theme and + 'NSAppearanceNameVibrantDark' or + 'NSAppearanceNameAqua') + for window in NSApplication.sharedApplication().windows(): + window.setAppearance_(appearance) + + if not self.minwidth: + self.minwidth = root.winfo_width() # Minimum width = width on first creation + # resizable(0,0) doesn't do anything on OSX + root.minsize(self.minwidth, root.winfo_height()) + root.maxsize(-1, root.winfo_height()) + + elif platform == 'win32': + # tk8.5.9/win/tkWinWm.c:342 + import ctypes + GWL_STYLE = -16 + WS_BORDER = 0x00800000 + WS_OVERLAPPEDWINDOW =0x00CF0000 + GWL_EXSTYLE = -20 + WS_EX_WINDOWEDGE = 0x00000100 + WS_EX_APPWINDOW = 0x00040000 + root.overrideredirect(theme and 1 or 0) # Destroys any top-level window + root.update_idletasks() # Size and windows styles get recalculated here + hwnd = ctypes.windll.user32.GetParent(root.winfo_id()) + ctypes.windll.user32.SetWindowLongW(hwnd, GWL_STYLE, theme and WS_BORDER or WS_OVERLAPPEDWINDOW) + ctypes.windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, theme and WS_EX_APPWINDOW or WS_EX_WINDOWEDGE) + root.deiconify() + root.wait_visibility() # need main window to be displayed before returning + + if not self.minwidth: + self.minwidth = root.winfo_width() # Minimum width = width on first creation + root.minsize(self.minwidth, -1) + + else: + root.overrideredirect(theme and 1 or 0) + root.withdraw() + root.update_idletasks() # Size gets recalculated here + root.deiconify() + root.wait_visibility() # need main window to be displayed before returning + + if not self.minwidth: + self.minwidth = root.winfo_width() # Minimum width = width on first creation + root.minsize(self.minwidth, -1) + +# singleton +theme = _Theme()