diff --git a/EDMarketConnector.py b/EDMarketConnector.py index bbf2dfa0..9c7b4a14 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -5,11 +5,9 @@ from builtins import str from builtins import object import sys from sys import platform -from collections import OrderedDict -from functools import partial import json from os import chdir, environ -from os.path import dirname, expanduser, isdir, join +from os.path import dirname, isdir, join import re import html from time import time, localtime, strftime @@ -34,7 +32,6 @@ import tkinter.messagebox from ttkHyperlinkLabel import HyperlinkLabel if __debug__: - from traceback import print_exc if platform != 'win32': import pdb import signal @@ -55,7 +52,7 @@ from dashboard import dashboard from theme import theme -SERVER_RETRY = 5 # retry pause for Companion servers [s] +SERVER_RETRY = 5 # retry pause for Companion servers [s] SHIPYARD_HTML_TEMPLATE = """ <!DOCTYPE HTML> @@ -77,8 +74,8 @@ class AppWindow(object): # Tkinter Event types EVENT_KEYPRESS = 2 - EVENT_BUTTON = 4 - EVENT_VIRTUAL = 35 + EVENT_BUTTON = 4 + EVENT_VIRTUAL = 35 def __init__(self, master): @@ -97,10 +94,10 @@ class AppWindow(object): if platform == 'win32': self.w.wm_iconbitmap(default='EDMarketConnector.ico') else: - self.w.tk.call('wm', 'iconphoto', self.w, '-default', tk.PhotoImage(file = join(config.respath, 'EDMarketConnector.png'))) - self.theme_icon = tk.PhotoImage(data = 'R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') - 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') + self.w.tk.call('wm', 'iconphoto', self.w, '-default', tk.PhotoImage(file=join(config.respath, 'EDMarketConnector.png'))) # noqa: E501 + self.theme_icon = tk.PhotoImage(data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501 + self.theme_minimize = tk.BitmapImage(data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 + self.theme_close = tk.BitmapImage(data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 frame = tk.Frame(self.w, name=appname.lower()) frame.grid(sticky=tk.NSEW) @@ -116,10 +113,10 @@ class AppWindow(object): self.system_label.grid(row=3, column=0, sticky=tk.W) self.station_label.grid(row=4, column=0, sticky=tk.W) - self.cmdr = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name = 'cmdr') - self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.shipyard_url, name = 'ship') - self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.system_url, popup_copy = True, name = 'system') - self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.station_url, name = 'station') + self.cmdr = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='cmdr') + self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship') + self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.system_url, popup_copy=True, name='system') + self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station') self.cmdr.grid(row=1, column=1, sticky=tk.EW) self.ship.grid(row=2, column=1, sticky=tk.EW) @@ -129,39 +126,41 @@ class AppWindow(object): for plugin in plug.PLUGINS: appitem = plugin.get_app(frame) if appitem: - tk.Frame(frame, highlightthickness=1).grid(columnspan=2, sticky=tk.EW) # separator - if isinstance(appitem, tuple) and len(appitem)==2: + tk.Frame(frame, highlightthickness=1).grid(columnspan=2, sticky=tk.EW) # separator + if isinstance(appitem, tuple) and len(appitem) == 2: row = frame.grid_size()[1] appitem[0].grid(row=row, column=0, sticky=tk.W) appitem[1].grid(row=row, column=1, sticky=tk.EW) else: appitem.grid(columnspan=2, sticky=tk.EW) - self.button = ttk.Button(frame, text=_('Update'), width=28, default=tk.ACTIVE, state=tk.DISABLED) # Update button in main window - self.theme_button = tk.Label(frame, width = platform == 'darwin' and 32 or 28, state=tk.DISABLED) + # Update button in main window + 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.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, self.theme_button), {'row':row, 'columnspan':2, 'sticky':tk.NSEW}) + theme.register_alternate((self.button, self.theme_button, self.theme_button), {'row': row, 'columnspan': 2, 'sticky': tk.NSEW}) # noqa: E501 self.status.grid(columnspan=2, sticky=tk.EW) self.button.bind('<Button-1>', self.getandsend) theme.button_bind(self.theme_button, self.getandsend) for child in frame.winfo_children(): - child.grid_configure(padx=5, pady=(platform!='win32' or isinstance(child, tk.Frame)) and 2 or 0) + child.grid_configure(padx=5, pady=(platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0) self.menubar = tk.Menu() - if platform=='darwin': + if platform == 'darwin': # 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 + # 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 resizable') # https://www.tcl.tk/man/tcl/TkCmd/menu.htm self.system_menu = tk.Menu(self.menubar, name='apple') - self.system_menu.add_command(command=lambda:self.w.call('tk::mac::standardAboutPanel')) - self.system_menu.add_command(command=lambda:self.updater.checkForUpdates()) + self.system_menu.add_command(command=lambda: self.w.call('tk::mac::standardAboutPanel')) + self.system_menu.add_command(command=lambda: self.updater.checkForUpdates()) self.menubar.add_cascade(menu=self.system_menu) self.file_menu = tk.Menu(self.menubar, name='file') self.file_menu.add_command(command=self.save_raw) @@ -171,7 +170,7 @@ class AppWindow(object): self.menubar.add_cascade(menu=self.edit_menu) self.w.bind('<Command-c>', self.copy) self.view_menu = tk.Menu(self.menubar, name='view') - self.view_menu.add_command(command=lambda:stats.StatsDialog(self)) + self.view_menu.add_command(command=lambda: stats.StatsDialog(self)) self.menubar.add_cascade(menu=self.view_menu) window_menu = tk.Menu(self.menubar, name='window') self.menubar.add_cascade(menu=window_menu) @@ -183,17 +182,17 @@ class AppWindow(object): self.w['menu'] = self.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')) + self.w.createcommand('tkAboutDialog', lambda: self.w.call('tk::mac::standardAboutPanel')) self.w.createcommand("::tk::mac::Quit", self.onexit) - self.w.createcommand("::tk::mac::ShowPreferences", lambda:prefs.PreferencesDialog(self.w, self.postprefs)) - self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify) # click on app in dock = restore - self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw) # close button shouldn't quit app - self.w.resizable(tk.FALSE, tk.FALSE) # Can't be only resizable on one axis + self.w.createcommand("::tk::mac::ShowPreferences", lambda: prefs.PreferencesDialog(self.w, self.postprefs)) + self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify) # click on app in dock = restore + self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw) # close button shouldn't quit app + self.w.resizable(tk.FALSE, tk.FALSE) # Can't be only resizable on one axis else: self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) - self.file_menu.add_command(command=lambda:stats.StatsDialog(self)) + self.file_menu.add_command(command=lambda: stats.StatsDialog(self)) self.file_menu.add_command(command=self.save_raw) - self.file_menu.add_command(command=lambda:prefs.PreferencesDialog(self.w, self.postprefs)) + self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs)) self.file_menu.add_separator() self.file_menu.add_command(command=self.onexit) self.menubar.add_cascade(menu=self.file_menu) @@ -213,11 +212,13 @@ class AppWindow(object): self.always_ontop = tk.BooleanVar(value=config.getint('always_ontop')) self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) self.system_menu.add_separator() - self.system_menu.add_checkbutton(label=_('Always on top'), variable = self.always_ontop, command=self.ontop_changed) # Appearance setting + self.system_menu.add_checkbutton(label=_('Always on top'), + variable=self.always_ontop, + command=self.ontop_changed) # Appearance setting self.menubar.add_cascade(menu=self.system_menu) self.w.bind('<Control-c>', self.copy) self.w.protocol("WM_DELETE_WINDOW", self.onexit) - theme.register(self.menubar) # menus and children aren't automatically registered + theme.register(self.menubar) # menus and children aren't automatically registered theme.register(self.file_menu) theme.register(self.edit_menu) theme.register(self.help_menu) @@ -225,7 +226,9 @@ class AppWindow(object): # Alternate title bar and menu for dark theme self.theme_menubar = tk.Frame(frame) self.theme_menubar.columnconfigure(2, weight=1) - theme_titlebar = tk.Label(self.theme_menubar, text=applongname, image=self.theme_icon, cursor='fleur', anchor=tk.W, compound=tk.LEFT) + theme_titlebar = tk.Label(self.theme_menubar, text=applongname, + image=self.theme_icon, cursor='fleur', + anchor=tk.W, compound=tk.LEFT) theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW) self.drag_offset = None theme_titlebar.bind('<Button-1>', self.drag_start) @@ -239,37 +242,49 @@ class AppWindow(object): theme.button_bind(theme_close, self.onexit, image=self.theme_close) self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W) self.theme_file_menu.grid(row=1, column=0, padx=5, sticky=tk.W) - theme.button_bind(self.theme_file_menu, lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) + theme.button_bind(self.theme_file_menu, + lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), + e.widget.winfo_rooty() + + e.widget.winfo_height())) self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W) self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W) - theme.button_bind(self.theme_edit_menu, lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) + theme.button_bind(self.theme_edit_menu, + lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), + e.widget.winfo_rooty() + + e.widget.winfo_height())) self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W) self.theme_help_menu.grid(row=1, column=2, sticky=tk.W) - theme.button_bind(self.theme_help_menu, lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) + theme.button_bind(self.theme_help_menu, + lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), + e.widget.winfo_rooty() + + e.widget.winfo_height())) tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=5, sticky=tk.EW) - theme.register(self.theme_minimize) # images aren't automatically registered + theme.register(self.theme_minimize) # images aren't automatically registered theme.register(self.theme_close) self.blank_menubar = tk.Frame(frame) tk.Label(self.blank_menubar).grid() tk.Label(self.blank_menubar).grid() tk.Frame(self.blank_menubar, height=2).grid() - theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), {'row':0, 'columnspan':2, 'sticky':tk.NSEW}) + theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), + {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW}) self.w.resizable(tk.TRUE, tk.FALSE) # update geometry if config.get('geometry'): - match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry')) + match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry')) # noqa: W605 if match: if platform == 'darwin': - if int(match.group(2)) >= 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + if int(match.group(2)) >= 0: self.w.geometry(config.get('geometry')) elif platform == 'win32': # Check that the titlebar will be at least partly on screen import ctypes from ctypes.wintypes import POINT # https://msdn.microsoft.com/en-us/library/dd145064 - MONITOR_DEFAULTTONULL = 0 - if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16), MONITOR_DEFAULTTONULL): + MONITOR_DEFAULTTONULL = 0 # noqa: N806 + if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16), + MONITOR_DEFAULTTONULL): self.w.geometry(config.get('geometry')) else: self.w.geometry(config.get('geometry')) @@ -278,22 +293,22 @@ class AppWindow(object): theme.register(frame) theme.apply(self.w) - self.w.bind('<Map>', self.onmap) # Special handling for overrideredict - self.w.bind('<Enter>', self.onenter) # Special handling for transparency - self.w.bind('<FocusIn>', self.onenter) # " - self.w.bind('<Leave>', self.onleave) # " - self.w.bind('<FocusOut>', self.onleave) # " + self.w.bind('<Map>', self.onmap) # Special handling for overrideredict + self.w.bind('<Enter>', self.onenter) # Special handling for transparency + self.w.bind('<FocusIn>', self.onenter) # Special handling for transparency + self.w.bind('<Leave>', self.onleave) # Special handling for transparency + self.w.bind('<FocusOut>', self.onleave) # Special handling for transparency self.w.bind('<Return>', self.getandsend) self.w.bind('<KP_Enter>', self.getandsend) - self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring - self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring - self.w.bind_all('<<DashboardEvent>>', self.dashboard_event) # Dashboard monitoring - self.w.bind_all('<<PluginError>>', self.plugin_error) # Statusbar - self.w.bind_all('<<CompanionAuthEvent>>', self.auth) # cAPI auth - self.w.bind_all('<<Quit>>', self.onexit) # Updater + self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring + self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring + self.w.bind_all('<<DashboardEvent>>', self.dashboard_event) # Dashboard monitoring + self.w.bind_all('<<PluginError>>', self.plugin_error) # Statusbar + self.w.bind_all('<<CompanionAuthEvent>>', self.auth) # cAPI auth + self.w.bind_all('<<Quit>>', self.onexit) # Updater # Start a protocol handler to handle cAPI registration. Requires main loop to be running. - self.w.after_idle(lambda:protocolhandler.start(self.w)) + self.w.after_idle(lambda: protocolhandler.start(self.w)) # Load updater after UI creation (for WinSparkle) import update @@ -305,7 +320,7 @@ class AppWindow(object): self.updater.checkForUpdates() # Sparkle / WinSparkle does this automatically for packaged apps try: - config.get_password('') # Prod SecureStorage on Linux to initialise + config.get_password('') # Prod SecureStorage on Linux to initialise except RuntimeError: pass @@ -317,93 +332,93 @@ class AppWindow(object): config.delete('password') config.delete('logdir') - self.postprefs(False) # Companion login happens in callback from monitor - + self.postprefs(False) # Companion login happens in callback from monitor # callback after the Preferences dialog is applied def postprefs(self, dologin=True): self.prefsdialog = None - self.set_labels() # in case language has changed + self.set_labels() # in case language has changed # Reset links in case plugins changed them - self.ship.configure(url = self.shipyard_url) - self.system.configure(url = self.system_url) - self.station.configure(url = self.station_url) + self.ship.configure(url=self.shipyard_url) + self.system.configure(url=self.system_url) + self.station.configure(url=self.station_url) # (Re-)install hotkey monitoring hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods')) # (Re-)install log monitoring if not monitor.start(self.w): - self.status['text'] = 'Error: Check %s' % _('E:D journal file location') # Location of the new Journal file in E:D 2.2 + self.status['text'] = f'Error: Check {_("E:D journal file location")}' if dologin and monitor.cmdr: - self.login() # Login if not already logged in with this Cmdr + self.login() # Login if not already logged in with this Cmdr # set main window labels, e.g. after language change def set_labels(self): - self.cmdr_label['text'] = _('Cmdr') + ':' # Main window - self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or # Multicrew role label in main window - _('Ship')) + ':' # Main window - self.system_label['text'] = _('System') + ':' # Main window - self.station_label['text'] = _('Station') + ':' # Main window - self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window + self.cmdr_label['text'] = _('Cmdr') + ':' # Main window + # Multicrew role label in main window + self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':' # Main window + self.system_label['text'] = _('System') + ':' # Main window + self.station_label['text'] = _('Station') + ':' # Main window + self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window if platform == 'darwin': - self.menubar.entryconfigure(1, label=_('File')) # Menu title - self.menubar.entryconfigure(2, label=_('Edit')) # Menu title - self.menubar.entryconfigure(3, label=_('View')) # Menu title on OSX - self.menubar.entryconfigure(4, label=_('Window')) # Menu title on OSX - self.menubar.entryconfigure(5, label=_('Help')) # Menu title - self.system_menu.entryconfigure(0, label=_("About {APP}").format(APP=applongname)) # App menu entry on OSX - self.system_menu.entryconfigure(1, label=_("Check for Updates...")) # Menu item - self.file_menu.entryconfigure(0, label=_('Save Raw Data...')) # Menu item - self.view_menu.entryconfigure(0, label=_('Status')) # Menu item - self.help_menu.entryconfigure(1, label=_('Privacy Policy')) # Help menu item - self.help_menu.entryconfigure(2, label=_('Release Notes')) # Help menu item + self.menubar.entryconfigure(1, label=_('File')) # Menu title + self.menubar.entryconfigure(2, label=_('Edit')) # Menu title + self.menubar.entryconfigure(3, label=_('View')) # Menu title on OSX + self.menubar.entryconfigure(4, label=_('Window')) # Menu title on OSX + self.menubar.entryconfigure(5, label=_('Help')) # Menu title + self.system_menu.entryconfigure(0, label=_("About {APP}").format(APP=applongname)) # App menu entry on OSX + self.system_menu.entryconfigure(1, label=_("Check for Updates...")) # Menu item + self.file_menu.entryconfigure(0, label=_('Save Raw Data...')) # Menu item + self.view_menu.entryconfigure(0, label=_('Status')) # Menu item + self.help_menu.entryconfigure(1, label=_('Privacy Policy')) # Help menu item + self.help_menu.entryconfigure(2, label=_('Release Notes')) # Help menu item else: - self.menubar.entryconfigure(1, label=_('File')) # Menu title - self.menubar.entryconfigure(2, label=_('Edit')) # Menu title - self.menubar.entryconfigure(3, label=_('Help')) # Menu title - self.theme_file_menu['text'] = _('File') # Menu title - self.theme_edit_menu['text'] = _('Edit') # Menu title - self.theme_help_menu['text'] = _('Help') # Menu title + self.menubar.entryconfigure(1, label=_('File')) # Menu title + self.menubar.entryconfigure(2, label=_('Edit')) # Menu title + self.menubar.entryconfigure(3, label=_('Help')) # Menu title + self.theme_file_menu['text'] = _('File') # Menu title + self.theme_edit_menu['text'] = _('Edit') # Menu title + self.theme_help_menu['text'] = _('Help') # Menu title - ## File menu - self.file_menu.entryconfigure(0, label=_('Status')) # Menu item - self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # Menu item - self.file_menu.entryconfigure(2, label=_('Settings')) # Item in the File menu on Windows - self.file_menu.entryconfigure(4, label=_('Exit')) # Item in the File menu on Windows + # File menu + self.file_menu.entryconfigure(0, label=_('Status')) # Menu item + self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # Menu item + self.file_menu.entryconfigure(2, label=_('Settings')) # Item in the File menu on Windows + self.file_menu.entryconfigure(4, label=_('Exit')) # Item in the File menu on Windows - ## Help menu - self.help_menu.entryconfigure(0, label=_('Documentation')) # Help menu item - self.help_menu.entryconfigure(1, label=_('Privacy Policy')) # Help menu item - self.help_menu.entryconfigure(2, label=_('Release Notes')) # Help menu item - self.help_menu.entryconfigure(3, label=_('Check for Updates...')) # Menu item - self.help_menu.entryconfigure(4, label=_("About {APP}").format(APP=applongname)) # App menu entry + # Help menu + self.help_menu.entryconfigure(0, label=_('Documentation')) # Help menu item + self.help_menu.entryconfigure(1, label=_('Privacy Policy')) # Help menu item + self.help_menu.entryconfigure(2, label=_('Release Notes')) # Help menu item + self.help_menu.entryconfigure(3, label=_('Check for Updates...')) # Menu item + self.help_menu.entryconfigure(4, label=_("About {APP}").format(APP=applongname)) # App menu entry - ## Edit menu - self.edit_menu.entryconfigure(0, label=_('Copy')) # As in Copy and Paste + # Edit menu + self.edit_menu.entryconfigure(0, label=_('Copy')) # As in Copy and Paste def login(self): if not self.status['text']: self.status['text'] = _('Logging in...') self.button['state'] = self.theme_button['state'] = tk.DISABLED if platform == 'darwin': - self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status - self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data + self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status + self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data else: - self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status - self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data + self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status + self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data self.w.update_idletasks() try: if companion.session.login(monitor.cmdr, monitor.is_beta): - self.status['text'] = _('Authentication successful') # Successfully authenticated with the Frontier website + # Successfully authenticated with the Frontier website + self.status['text'] = _('Authentication successful') if platform == 'darwin': - self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status - self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data + self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status + self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data else: - self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status - self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data + self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status + self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e: self.status['text'] = str(e) except Exception as e: @@ -418,7 +433,7 @@ class AppWindow(object): play_bad = False if not monitor.cmdr or not monitor.mode or monitor.state['Captain'] or not monitor.system: - return # In CQC or on crew - do nothing + return # In CQC or on crew - do nothing if companion.session.state == companion.Session.STATE_AUTH: # Attempt another Auth @@ -426,10 +441,10 @@ class AppWindow(object): return if not retrying: - if time() < self.holdofftime: # Was invoked by key while in cooldown + if time() < self.holdofftime: # Was invoked by key while in cooldown self.status['text'] = '' if play_sound and (self.holdofftime-time()) < companion.holdoff*0.75: - hotkeymgr.play_bad() # Don't play sound in first few seconds to prevent repeats + hotkeymgr.play_bad() # Don't play sound in first few seconds to prevent repeats return elif play_sound: hotkeymgr.play_good() @@ -444,31 +459,41 @@ class AppWindow(object): # Validation if not data.get('commander', {}).get('name'): - self.status['text'] = _("Who are you?!") # Shouldn't happen - elif (not data.get('lastSystem', {}).get('name') or - (data['commander'].get('docked') and not data.get('lastStarport', {}).get('name'))): # Only care if docked - self.status['text'] = _("Where are you?!") # Shouldn't happen + self.status['text'] = _("Who are you?!") # Shouldn't happen + elif (not data.get('lastSystem', {}).get('name') + or (data['commander'].get('docked') + and not data.get('lastStarport', {}).get('name'))): # Only care if docked + self.status['text'] = _("Where are you?!") # Shouldn't happen elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): - self.status['text'] = _("What are you flying?!") # Shouldn't happen + self.status['text'] = _("What are you flying?!") # Shouldn't happen elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: - raise companion.CmdrError() # Companion API return doesn't match Journal - elif ((auto_update and not data['commander'].get('docked')) or - (data['lastSystem']['name'] != monitor.system) or - ((data['commander']['docked'] and data['lastStarport']['name'] or None) != monitor.station) or - (data['ship']['id'] != monitor.state['ShipID']) or - (data['ship']['name'].lower() != monitor.state['ShipType'])): + # Companion API return doesn't match Journal + raise companion.CmdrError() + elif ((auto_update and not data['commander'].get('docked')) + or (data['lastSystem']['name'] != monitor.system) + or ((data['commander']['docked'] + and data['lastStarport']['name'] or None) != monitor.station) + or (data['ship']['id'] != monitor.state['ShipID']) + or (data['ship']['name'].lower() != monitor.state['ShipType'])): raise companion.ServerLagging() else: - if __debug__: # Recording + if __debug__: # Recording if isdir('dump'): - with open('dump/%s%s.%s.json' % (data['lastSystem']['name'], data['commander'].get('docked') and '.'+data['lastStarport']['name'] or '', strftime('%Y-%m-%dT%H.%M.%S', localtime())), 'wb') as h: - h.write(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')).encode('utf-8')) + with open('dump/{system}{station}.{timestamp}.json'.format( + system=data['lastSystem']['name'], + station=data['commander'].get('docked') and '.'+data['lastStarport']['name'] or '', + timestamp=strftime('%Y-%m-%dT%H.%M.%S', localtime())), 'wb') as h: + h.write(json.dumps(data, + ensure_ascii=False, + indent=2, + sort_keys=True, + separators=(',', ': ')).encode('utf-8')) - if not monitor.state['ShipType']: # Started game in SRV or fighter + if not monitor.state['ShipType']: # Started game in SRV or fighter self.ship['text'] = companion.ship_map.get(data['ship']['name'].lower(), data['ship']['name']) - monitor.state['ShipID'] = data['ship']['id'] + monitor.state['ShipID'] = data['ship']['id'] monitor.state['ShipType'] = data['ship']['name'].lower() if data['commander'].get('credits') is not None: @@ -485,16 +510,19 @@ class AppWindow(object): if config.getint('output') & (config.OUT_STATION_ANY): if not data['commander'].get('docked'): if not self.status['text']: - # Signal as error because the user might actually be docked but the server hosting the Companion API hasn't caught up + # Signal as error because the user might actually be docked + # but the server hosting the Companion API hasn't caught up self.status['text'] = _("You're not docked at a station!") play_bad = True - elif (config.getint('output') & config.OUT_MKT_EDDN) and not (data['lastStarport'].get('commodities') or data['lastStarport'].get('modules')): # Ignore possibly missing shipyard info + # Ignore possibly missing shipyard info + elif (config.getint('output') & config.OUT_MKT_EDDN)\ + and not (data['lastStarport'].get('commodities') or data['lastStarport'].get('modules')): if not self.status['text']: self.status['text'] = _("Station doesn't have anything!") elif not data['lastStarport'].get('commodities'): if not self.status['text']: self.status['text'] = _("Station doesn't have a market!") - elif config.getint('output') & (config.OUT_MKT_CSV|config.OUT_MKT_TD): + elif config.getint('output') & (config.OUT_MKT_CSV | config.OUT_MKT_TD): # Fixup anomalies in the commodity data fixed = companion.fixup(data) if config.getint('output') & config.OUT_MKT_CSV: @@ -511,10 +539,10 @@ class AppWindow(object): play_bad = True else: # Retry once if Companion server is unresponsive - self.w.after(int(SERVER_RETRY * 1000), lambda:self.getandsend(event, True)) - return # early exit to avoid starting cooldown count + self.w.after(int(SERVER_RETRY * 1000), lambda: self.getandsend(event, True)) + return # early exit to avoid starting cooldown count - except companion.CmdrError as e: # Companion API return doesn't match Journal + except companion.CmdrError as e: # Companion API return doesn't match Journal self.status['text'] = str(e) play_bad = True companion.session.invalidate() @@ -552,8 +580,8 @@ class AppWindow(object): data.get('lastStarport', {}).get('ships', {}).get('shipyard_list')): self.eddn.export_shipyard(data, monitor.is_beta) elif tries > 1: # bogus data - retry - self.w.after(int(SERVER_RETRY * 1000), lambda:self.retry_for_shipyard(tries-1)) - except: + self.w.after(int(SERVER_RETRY * 1000), lambda: self.retry_for_shipyard(tries-1)) + except Exception: pass # Handle event(s) from the journal @@ -564,9 +592,9 @@ class AppWindow(object): return { None: '', 'Idle': '', - 'FighterCon': _('Fighter'), # Multicrew role - 'FireCon': _('Gunner'), # Multicrew role - 'FlightCon': _('Helm'), # Multicrew role + 'FighterCon': _('Fighter'), # Multicrew role + 'FireCon': _('Gunner'), # Multicrew role + 'FlightCon': _('Helm'), # Multicrew role }.get(role, role) while True: @@ -577,26 +605,43 @@ class AppWindow(object): # Update main window self.cooldown() if monitor.cmdr and monitor.state['Captain']: - self.cmdr['text'] = '%s / %s' % (monitor.cmdr, monitor.state['Captain']) - self.ship_label['text'] = _('Role') + ':' # Multicrew role label in main window - self.ship.configure(state = tk.NORMAL, text = crewroletext(monitor.state['Role']), url = None) + self.cmdr['text'] = f'{monitor.cmdr} / {monitor.state["Captain"]}' + self.ship_label['text'] = _('Role') + ':' # Multicrew role label in main window + self.ship.configure(state=tk.NORMAL, text=crewroletext(monitor.state['Role']), url=None) elif monitor.cmdr: if monitor.group: - self.cmdr['text'] = '%s / %s' % (monitor.cmdr, monitor.group) + self.cmdr['text'] = f'{monitor.cmdr} / {monitor.group}' else: self.cmdr['text'] = monitor.cmdr - self.ship_label['text'] = _('Ship') + ':' # Main window - self.ship.configure(text = monitor.state['ShipName'] or companion.ship_map.get(monitor.state['ShipType'], monitor.state['ShipType']) or '', - url = self.shipyard_url) + self.ship_label['text'] = _('Ship') + ':' # Main window + self.ship.configure( + text=monitor.state['ShipName'] + or companion.ship_map.get(monitor.state['ShipType'], monitor.state['ShipType']) + or '', + url=self.shipyard_url) else: self.cmdr['text'] = '' - self.ship_label['text'] = _('Ship') + ':' # Main window + self.ship_label['text'] = _('Ship') + ':' # Main window self.ship['text'] = '' - self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED) # Copy + self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED) # Copy - if entry['event'] in ['Undocked', 'StartJump', 'SetUserShipName', 'ShipyardBuy', 'ShipyardSell', 'ShipyardSwap', 'ModuleBuy', 'ModuleSell', 'MaterialCollected', 'MaterialDiscarded', 'ScientificResearch', 'EngineerCraft', 'Synthesis', 'JoinACrew']: - self.status['text'] = '' # Periodically clear any old error + if entry['event'] in ( + 'Undocked', + 'StartJump', + 'SetUserShipName', + 'ShipyardBuy', + 'ShipyardSell', + 'ShipyardSwap', + 'ModuleBuy', + 'ModuleSell', + 'MaterialCollected', + 'MaterialDiscarded', + 'ScientificResearch', + 'EngineerCraft', + 'Synthesis', + 'JoinACrew'): + self.status['text'] = '' # Periodically clear any old error self.w.update_idletasks() # Companion login @@ -606,7 +651,7 @@ class AppWindow(object): self.login() if not entry['event'] or not monitor.mode: - return # Startup or in CQC + return # Startup or in CQC if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started: # Disable WinSparkle automatic update checks, IFF configured to do so when in-game @@ -618,8 +663,7 @@ class AppWindow(object): logger.info("Can't start Status monitoring") # Export loadout - if entry['event'] == 'Loadout'\ - and not monitor.state['Captain']\ + if entry['event'] == 'Loadout' and not monitor.state['Captain']\ and config.getint('output') & config.OUT_SHIP: monitor.export_ship() @@ -636,7 +680,11 @@ class AppWindow(object): hotkeymgr.play_bad() # Auto-Update after docking, but not if auth callback is pending - if entry['event'] in ['StartUp', 'Location', 'Docked'] and monitor.station and not config.getint('output') & config.OUT_MKT_MANUAL and config.getint('output') & config.OUT_STATION_ANY and companion.session.state != companion.Session.STATE_AUTH: + if entry['event'] in ('StartUp', 'Location', 'Docked')\ + and monitor.station\ + and not config.getint('output') & config.OUT_MKT_MANUAL\ + and config.getint('output') & config.OUT_STATION_ANY\ + and companion.session.state != companion.Session.STATE_AUTH: self.w.after(int(SERVER_RETRY * 1000), self.getandsend) if entry['event'] == 'ShutDown': @@ -698,7 +746,7 @@ class AppWindow(object): provider_name=html.escape(str(provider)), ship_name=html.escape(str(shipname)) ), file=f) - + return f'file://localhost/{file_name}' def system_url(self, system): @@ -709,10 +757,12 @@ class AppWindow(object): def cooldown(self): if time() < self.holdofftime: - self.button['text'] = self.theme_button['text'] = _('cooldown {SS}s').format(SS = int(self.holdofftime - time())) # Update button in main window + # Update button in main window + self.button['text'] = self.theme_button['text'] \ + = _('cooldown {SS}s').format(SS=int(self.holdofftime - time())) self.w.after(1000, self.cooldown) else: - self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window + self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window self.button['state'] = self.theme_button['state'] = (monitor.cmdr and monitor.mode and not monitor.state['Captain'] and @@ -726,7 +776,7 @@ class AppWindow(object): def copy(self, event=None): if monitor.system: self.w.clipboard_clear() - self.w.clipboard_append(monitor.station and '%s,%s' % (monitor.system, monitor.station) or monitor.system) + self.w.clipboard_append(monitor.station and f'{monitor.system},{monitor.station}' or monitor.system) def help_general(self, event=None): webbrowser.open('https://github.com/EDCD/EDMarketConnector/wiki') @@ -754,11 +804,11 @@ class AppWindow(object): 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())) + if platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}') # remove decoration - if platform=='win32': + if platform == 'win32': self.attributes('-toolwindow', tk.TRUE) self.resizable(tk.FALSE, tk.FALSE) @@ -862,7 +912,7 @@ class AppWindow(object): # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 if platform != 'darwin' or self.w.winfo_rooty() > 0: config.set('geometry', '+{1}+{2}'.format(*self.w.geometry().split('+'))) - self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen + self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen protocolhandler.close() hotkeymgr.unregister() dashboard.close() @@ -888,7 +938,7 @@ class AppWindow(object): 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 + theme.active = None # So theme will be re-applied on map def onmap(self, event=None): if event.widget == self.w: @@ -987,7 +1037,7 @@ if __name__ == "__main__": #abinit = A.B() - Translations.install(config.get('language') or None) # Can generate errors so wait til log set up + Translations.install(config.get('language') or None) # Can generate errors so wait til log set up root = tk.Tk(className=appname.lower()) app = AppWindow(root)