diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b75a579a..c9a1544d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -476,7 +476,11 @@ class AppWindow: self.edit_menu.entryconfigure(0, state=tk.NORMAL) # Copy # stuff we can do when not docked - self.status['text'] = plug.notify_newdata(data) or '' + err = plug.notify_newdata(data, monitor.is_beta) + self.status['text'] = err and err or '' + if err: + play_bad = True + if config.getint('output') & config.OUT_SHIP: loadout.export(data) @@ -717,9 +721,11 @@ class AppWindow: return # Startup or in CQC # Plugins - err = plug.notify_journal_entry(monitor.cmdr, monitor.system, monitor.station, entry, monitor.state) + err = plug.notify_journal_entry(monitor.cmdr, monitor.is_beta, monitor.system, monitor.station, entry, monitor.state) if err: self.status['text'] = err + if not config.getint('hotkey_mute'): + hotkeymgr.play_bad() if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started: # Can start interaction monitoring @@ -800,9 +806,11 @@ class AppWindow: return # Currently we don't do anything with these events - err = plug.notify_interaction(monitor.cmdr, entry) + err = plug.notify_interaction(monitor.cmdr, monitor.is_beta, entry) if err: self.status['text'] = err + if not config.getint('hotkey_mute'): + hotkeymgr.play_bad() def edsmpoll(self): result = self.edsm.result diff --git a/L10n/en.template b/L10n/en.template index 72899cef..e9289929 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -301,6 +301,9 @@ /* Dark theme color setting. [prefs.py] */ "Normal text" = "Normal text"; +/* Displayed when credentials settings are greyed out. [prefs.py] */ +"Not available while E:D is at the main menu" = "Not available while E:D is at the main menu"; + /* Combat rank. [stats.py] */ "Novice" = "Novice"; diff --git a/PLUGINS.md b/PLUGINS.md index 6b66d0b0..d7754f9e 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -41,14 +41,14 @@ You can use `set()`, `get()` and `getint()` from EDMC's config object to retriev Use `numberFromString()` from EDMC's Locale object to parse input numbers in a locale-independent way. -``` +```python import Tkinter as tk import myNotebook as nb from config import config this = sys.modules[__name__] # For holding module globals -def plugin_prefs(parent): +def plugin_prefs(parent, cmdr, is_beta): """ Return a TK Frame for adding to the EDMC settings dialog. """ @@ -63,8 +63,8 @@ def plugin_prefs(parent): This gets called when the user dismisses the settings dialog: -``` -def prefs_changed(): +```python +def prefs_changed(cmdr, is_beta): """ Save settings. """ @@ -77,7 +77,7 @@ You can also have your plugin add an item to the EDMC main window and update it You can use `stringFromNumber()` from EDMC's Locale object to format numbers in a locale-independent way. -``` +```python this = sys.modules[__name__] # For holding module globals def plugin_app(parent): @@ -104,8 +104,8 @@ This gets called when EDMC sees a new entry in the game's journal. `state` is a A special 'StartUp' entry is sent if EDMC is started while the game is already running. In this case you won't receive initial events such as "LoadGame", "Rank", "Location", etc. However the `state` dictionary will reflect the cumulative effect of these missed events. -``` -def journal_entry(cmdr, system, station, entry, state): +```python +def journal_entry(cmdr, is_beta, system, station, entry, state): if entry['event'] == 'FSDJump': # We arrived at a new system! if 'StarPos' in entry: @@ -120,8 +120,8 @@ This gets called when the player interacts with another Cmdr in-game. If EDMC is started while the game is already running EDMC will send the last few interaction events from the current game session. -``` -def interaction(cmdr, entry): +```python +def interaction(cmdr, is_beta, entry): # Log type of interaction, Cmdr name, and local time sys.stderr.write("{} Cmdr {} at {}\n".format(', '.join(entry['Interactions']), entry['Name'].encode('utf-8'), @@ -133,8 +133,8 @@ def interaction(cmdr, entry): This gets called when EDMC has just fetched fresh Cmdr and station data from Frontier's servers. -``` -def cmdr_data(data): +```python +def cmdr_data(data, is_beta): """ We have new data on our commander """ diff --git a/plug.py b/plug.py index ba6f09c0..5ad44d0b 100644 --- a/plug.py +++ b/plug.py @@ -8,8 +8,12 @@ import operator import threading # We don't use it, but plugins might from traceback import print_exc +import Tkinter as tk +import myNotebook as nb + from config import config, appname + # List of loaded Plugins PLUGINS = [] @@ -53,25 +57,42 @@ class Plugin(object): :param parent: the parent frame for this entry. :return: """ - try: - plugin_app = self._get_func('plugin_app') - return plugin_app and plugin_app(parent) - except: - print_exc() - return None + plugin_app = self._get_func('plugin_app') + if plugin_app: + try: + appitem = plugin_app(parent) + if isinstance(appitem, tuple): + if len(appitem) != 2 or not isinstance(appitem[0], tk.Widget) or not isinstance(appitem[1], tk.Widget): + raise AssertionError + elif not isinstance(appitem, tk.Widget): + raise AssertionError + return appitem + except: + print_exc() + return None - def get_prefs(self, parent): + def get_prefs(self, parent, cmdr, is_beta): """ If the plugin provides a prefs frame, create and return it. :param parent: the parent frame for this preference tab. + :param cmdr: current Cmdr name (or None). Relevant if you want to have + different settings for different user accounts. + :param is_beta: whether the player is in a Beta universe. :return: """ - try: - plugin_prefs = self._get_func('plugin_prefs') - return plugin_prefs and plugin_prefs(parent) - except: - print_exc() - return None + plugin_prefs = self._get_func('plugin_prefs') + if plugin_prefs: + try: + if plugin_prefs.func_code.co_argcount == 1: + frame = plugin_prefs(parent) + else: + frame = plugin_prefs(parent, cmdr, is_beta) + if not isinstance(frame, nb.Frame): + raise AssertionError + return frame + except: + print_exc() + return None def load_plugins(): @@ -109,28 +130,54 @@ def load_plugins(): imp.release_lock() -def notify_prefs_changed(): +def notify_prefs_cmdr_changed(cmdr, is_beta): + """ + Notify each plugin that the Cmdr has been changed while the settings dialog is open. + Relevant if you want to have different settings for different user accounts. + :param cmdr: current Cmdr name (or None). + :param is_beta: whether the player is in a Beta universe. + :return: + """ + for plugin in PLUGINS: + prefs_cmdr_changed = plugin._get_func('prefs_cmdr_changed') + if prefs_cmdr_changed: + try: + prefs_cmdr_changed(cmdr, is_beta) + except: + print_exc() + + +def notify_prefs_changed(cmdr, is_beta): """ Notify each plugin that the settings dialog has been closed. + The prefs frame and any widgets you created in your `get_prefs()` callback + will be destroyed on return from this function, so take a copy of any + values that you want to save. + :param cmdr: current Cmdr name (or None). + :param is_beta: whether the player is in a Beta universe. :return: """ for plugin in PLUGINS: prefs_changed = plugin._get_func('prefs_changed') if prefs_changed: try: - prefs_changed() + if prefs_changed.func_code.co_argcount == 0: + prefs_changed() + else: + prefs_changed(cmdr, is_beta) except: print_exc() -def notify_journal_entry(cmdr, system, station, entry, cmdr_state): +def notify_journal_entry(cmdr, is_beta, system, station, entry, state): """ Send a journal entry to each plugin. :param cmdr: The Cmdr name, or None if not yet known :param system: The current system, or None if not yet known :param station: The current station, or None if not docked or not yet known :param entry: The journal entry as a dictionary - :param cmdr_state: A dictionary containing info about the Cmdr, current ship and cargo + :param state: A dictionary containing info about the Cmdr, current ship and cargo + :param is_beta: whether the player is in a Beta universe. :return: Error message from the first plugin that returns one (if any) """ error = None @@ -141,18 +188,21 @@ def notify_journal_entry(cmdr, system, station, entry, cmdr_state): # Pass a copy of the journal entry in case the callee modifies it if journal_entry.func_code.co_argcount == 4: error = error or journal_entry(cmdr, system, station, dict(entry)) + elif journal_entry.func_code.co_argcount == 5: + error = error or journal_entry(cmdr, system, station, dict(entry), dict(state)) else: - error = error or journal_entry(cmdr, system, station, dict(entry), dict(cmdr_state)) + error = error or journal_entry(cmdr, is_beta, system, station, dict(entry), dict(state)) except: print_exc() return error -def notify_interaction(cmdr, entry): +def notify_interaction(cmdr, is_beta, entry): """ Send an interaction entry to each plugin. :param cmdr: The piloting Cmdr name :param entry: The interaction entry as a dictionary + :param is_beta: whether the player is in a Beta universe. :return: Error message from the first plugin that returns one (if any) """ error = None @@ -161,7 +211,10 @@ def notify_interaction(cmdr, entry): if interaction: try: # Pass a copy of the interaction entry in case the callee modifies it - error = error or interaction(cmdr, dict(entry)) + if interaction.func_code.co_argcount == 2: + error = error or interaction(cmdr, dict(entry)) + else: + error = error or interaction(cmdr, is_beta, dict(entry)) except: print_exc() return error @@ -188,10 +241,11 @@ def notify_system_changed(timestamp, system, coordinates): print_exc() -def notify_newdata(data): +def notify_newdata(data, is_beta): """ Send the latest EDMC data from the FD servers to each plugin :param data: + :param is_beta: whether the player is in a Beta universe. :return: Error message from the first plugin that returns one (if any) """ error = None @@ -199,7 +253,10 @@ def notify_newdata(data): cmdr_data = plugin._get_func('cmdr_data') if cmdr_data: try: - error = error or cmdr_data(data) + if cmdr_data.func_code.co_argcount == 1: + error = error or cmdr_data(data) + else: + error = error or cmdr_data(data, is_beta) except: print_exc() return error diff --git a/plugins/eddb.py b/plugins/eddb.py index a68bbd0c..706aaffc 100644 --- a/plugins/eddb.py +++ b/plugins/eddb.py @@ -57,12 +57,12 @@ def plugin_app(parent): this.station = HyperlinkLabel(parent, url = station_url, popup_copy = lambda x: x != STATION_UNDOCKED) return (this.station_label, this.station) -def prefs_changed(): +def prefs_changed(cmdr, is_beta): this.station_label['text'] = _('Station') + ':' -def journal_entry(cmdr, system, station, entry, state): +def journal_entry(cmdr, is_beta, system, station, entry, state): this.system = system this.station['text'] = station or (system_id(system) and STATION_UNDOCKED or '') -def cmdr_data(data): +def cmdr_data(data, is_beta): this.station['text'] = data['commander']['docked'] and data['lastStarport']['name'] or (system_id(data['lastSystem']['name']) and STATION_UNDOCKED or '') diff --git a/prefs.py b/prefs.py index 9c3b5020..a5c97b61 100644 --- a/prefs.py +++ b/prefs.py @@ -82,6 +82,8 @@ class PreferencesDialog(tk.Toplevel): 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) @@ -98,7 +100,7 @@ class PreferencesDialog(tk.Toplevel): nb.Label(credframe, text=_('Credentials')).grid(padx=PADX, sticky=tk.W) # Section heading in settings ttk.Separator(credframe, orient=tk.HORIZONTAL).grid(columnspan=2, padx=PADX, pady=PADY, sticky=tk.EW) - self.cred_label = nb.Label(credframe, text=_('Please log in with your Elite: Dangerous account details')) # Use same text as E:D Launcher's login dialog + self.cred_label = nb.Label(credframe) self.cred_label.grid(padx=PADX, columnspan=2, sticky=tk.W) self.cmdr_label = nb.Label(credframe, text=_('Cmdr')) # Main window self.cmdr_label.grid(row=10, padx=PADX, sticky=tk.W) @@ -212,7 +214,7 @@ class PreferencesDialog(tk.Toplevel): # build plugin prefs tabs for plugin in plug.PLUGINS: - plugframe = plugin.get_prefs(notebook) + plugframe = plugin.get_prefs(notebook, monitor.cmdr, monitor.is_beta) if plugframe: notebook.add(plugframe, text=plugin.name) @@ -371,7 +373,7 @@ class PreferencesDialog(tk.Toplevel): self.protocol("WM_DELETE_WINDOW", self._destroy) # Selectively disable buttons depending on output settings - self.outvarchanged() + self.cmdrchanged() self.themevarchanged() # disable hotkey for the duration @@ -390,11 +392,16 @@ class PreferencesDialog(tk.Toplevel): 0x10000, None, position): self.geometry("+%d+%d" % (position.left, position.top)) - def outvarchanged(self, event=None): - self.cmdr_text['state'] = self.edsm_cmdr_text['state'] = tk.NORMAL # must be writable to update - self.cmdr_text['text'] = self.edsm_cmdr_text['text'] = (monitor.cmdr or _('None')) + (monitor.is_beta and ' [Beta]' or '') # No hotkey/shortcut currently defined - if self.cmdr != monitor.cmdr: + def cmdrchanged(self, event=None): + if self.cmdr != monitor.cmdr or self.is_beta != monitor.is_beta: # Cmdr has changed - update settings + if monitor.cmdr: + self.cred_label['text'] = _('Please log in with your Elite: Dangerous account details') # Use same text as E:D Launcher's login dialog + else: + self.cred_label['text'] = _('Not available while E:D is at the main menu') # Displayed when credentials settings are greyed out + + self.cmdr_label['state'] = self.username_label['state'] = self.password_label['state'] = self.cmdr_text['state'] = self.username['state'] = self.password['state'] = monitor.cmdr and tk.NORMAL or tk.DISABLED + self.cmdr_text['text'] = (monitor.cmdr or _('None')) + (monitor.is_beta and ' [Beta]' or '') # No hotkey/shortcut currently defined self.username['state'] = tk.NORMAL self.username.delete(0, tk.END) self.password['state'] = tk.NORMAL @@ -415,18 +422,23 @@ class PreferencesDialog(tk.Toplevel): self.password.insert(0, config.get('password') or '') self.edsm_user.insert(0,config.get('edsm_cmdrname') or '') self.edsm_apikey.insert(0, config.get('edsm_apikey') or '') + 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 - cmdr_state = monitor.cmdr and tk.NORMAL or tk.DISABLED - self.cred_label['state'] = self.cmdr_label['state'] = self.username_label['state'] = self.password_label['state'] = cmdr_state - self.cmdr_text['state'] = self.username['state'] = self.password['state'] = cmdr_state + # Poll + self.cmdrchanged_alarm = self.after(1000, self.cmdrchanged) + def outvarchanged(self, event=None): self.displaypath(self.outdir, self.outdir_entry) self.displaypath(self.logdir, self.logdir_entry) self.displaypath(self.interactiondir, self.interactiondir_entry) logdir = self.logdir.get() logvalid = logdir and exists(logdir) + if not logvalid: + self.cred_label['text'] = 'Check %s' % _('E:D journal file location') # Location of the new Journal file in E:D 2.2 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() @@ -613,7 +625,7 @@ class PreferencesDialog(tk.Toplevel): config.set('journaldir', logdir) interactiondir = self.interactiondir.get() - if config.default_journal_dir and interactiondir.lower() == config.default_interaction_dir.lower(): + if config.default_interaction_dir and interactiondir.lower() == config.default_interaction_dir.lower(): config.set('interactiondir', '') # default location else: config.set('interactiondir', interactiondir) @@ -637,13 +649,16 @@ class PreferencesDialog(tk.Toplevel): config.set('anonymous', self.out_anon.get()) - plug.notify_prefs_changed() + plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta) self._destroy() if self.callback: self.callback() 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()