diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 45283453..ce44739a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -179,7 +179,7 @@ class AppWindow: self.status['text'] = '' # Try to obtain exclusive lock on flight log ASAP - if config.getint('output') & config.OUT_LOG: + if config.getint('output') & config.OUT_LOG_FILE: try: flightlog.openlog() except Exception as e: @@ -251,25 +251,29 @@ class AppWindow: self.system['text'] = data.get('lastSystem') and data.get('lastSystem').get('name') or '' self.station['text'] = data.get('commander') and data.get('commander').get('docked') and data.get('lastStarport') and data.get('lastStarport').get('name') or (EDDB.system(self.system['text']) and self.STATION_UNDOCKED or '') self.edit_menu.entryconfigure(_('Copy'), state=tk.NORMAL) - self.edsm.start_lookup(self.system['text'], EDDB.system(self.system['text'])) - self.system['image'] = self.edsm.result['img'] - self.w.after(int(EDSM_POLL * 1000), self.edsmpoll) # stuff we can do when not docked - if config.getint('output') & config.OUT_LOG: - flightlog.export(data) if config.getint('output') & config.OUT_SHIP_EDS: loadout.export(data) if config.getint('output') & config.OUT_SHIP_CORIOLIS: coriolis.export(data) + if config.getint('output') & config.OUT_LOG_FILE: + flightlog.export(data) + if config.getint('output') & config.OUT_LOG_EDSM: + self.status['text'] = _('Sending data to EDSM...') + self.w.update_idletasks() + edsm.export(data, lambda:self.edsm.lookup(self.system['text'], EDDB.system(self.system['text']))) # Do EDSM lookup during EDSM export + else: + self.edsm.start_lookup(self.system['text'], EDDB.system(self.system['text'])) + self.edsmpoll() if not (config.getint('output') & (config.OUT_CSV|config.OUT_TD|config.OUT_BPC|config.OUT_EDDN)): - # no further output requested + # no station data requested - we're done self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(querytime)).decode('utf-8') elif not data['commander'].get('docked'): self.status['text'] = _("You're not docked at a station!") - # signal as error becuase sometimes 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 if play_sound: hotkeymgr.play_bad() else: diff --git a/README.md b/README.md index 50c820a4..6c364530 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ combination that you use to log into the Elite: Dangerous launcher, and is requi You can also choose here what data to save (refer to the next section for details), whether to set up a hotkey so you don't have to switch to the app in order to “Update”, and whether to attach your Cmdr name or a [pseudo-anonymized](http://en.wikipedia.org/wiki/Pseudonymity) ID to the data. -You are next prompted to authenticate with a “verification code”, which you will shortly receive by email from Frontier. +The first time that you hit “Update” you will be prompted to authenticate with a “verification code”, which you will shortly receive by email from Frontier. Note that each “verification code” is one-time only - if you enter the code incorrectly or quit the app before authenticating you will need to wait for Frontier to send you a new code. @@ -56,15 +56,16 @@ This app can save a variety of data in a variety of formats: * Market data * Elite Dangerous Data Network - sends commodity market, outfitting and shipyard data to “[EDDN](http://eddn-gateway.elite-markets.net/)” from where you and others can use it via online trading tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc. - * Slopey's BPC format - saves commodity market data as files that you can load into [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081). - * Trade Dangerous format - saves commodity market data as files that you can load into [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home). - * CSV format - saves commodity market data as files that you can upload to [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz) or [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools). + * Slopey's BPC format file - saves commodity market data as files that you can load into [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081). + * Trade Dangerous format file - saves commodity market data as files that you can load into [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home). + * CSV format file - saves commodity market data as files that you can upload to [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz) or [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools). * Ship loadout * After every outfitting change saves a record of your ship loadout as a file that you can open in a text editor and that you can import into [E:D Shipyard](http://www.edshipyard.com) or [Coriolis](http://coriolis.io). * Flight log - * Adds a record of your location, ship and cargo to a file that you can open in a text editor or a spreadsheet program such as Excel. Note: Don't edit, rename or move this file - take a copy if you wish to change it. + * Elite Dangerous Star Map - sends a record of your location to “[EDSM](http://www.edsm.net/)” where you can view your logs under My account → Exploration Logs, and optionally add private comments about a system. + * CSV format file - adds a record of your location, ship and cargo to a file that you can open in a text editor or a spreadsheet program such as Excel. Note: Don't edit, rename or move this file - take a copy if you wish to change it. By default these files will be placed in your Documents folder. Since this app will create a lot of files if you use it for a while you may wish to create a separate folder for the files and tell the app to place them there. diff --git a/config.py b/config.py index 87f79f7f..bd94d6eb 100644 --- a/config.py +++ b/config.py @@ -70,9 +70,10 @@ class Config: OUT_TD = 4 OUT_CSV = 8 OUT_SHIP_EDS = 16 - OUT_LOG = 32 + OUT_LOG_FILE = 32 #OUT_STAT = 64 # No longer available OUT_SHIP_CORIOLIS = 128 + OUT_LOG_EDSM = 256 if platform=='darwin': diff --git a/edsm.py b/edsm.py index fa6522a0..d5fb2303 100644 --- a/edsm.py +++ b/edsm.py @@ -1,10 +1,14 @@ import requests import threading from sys import platform +import time import urllib import Tkinter as tk +from config import config +import flightlog + if __debug__: from traceback import print_exc @@ -24,18 +28,43 @@ class EDSM: EDSM._IMG_NEW = tk.PhotoImage(data = 'R0lGODlhEAAQAMZwANKVHtWcIteiHuiqLPCuHOS1MN22ZeW7ROG6Zuu9MOy+K/i8Kf/DAuvCVf/FAP3BNf/JCf/KAPHHSv7ESObHdv/MBv/GRv/LGP/QBPXOPvjPQfjQSvbRSP/UGPLSae7Sfv/YNvLXgPbZhP7dU//iI//mAP/jH//kFv7fU//fV//ebv/iTf/iUv/kTf/iZ/vgiP/hc/vgjv/jbfriiPriiv7ka//if//jd//sJP/oT//tHv/mZv/sLf/rRP/oYv/rUv/paP/mhv/sS//oc//lkf/mif/sUf/uPv/qcv/uTv/uUv/vUP/qhP/xP//pm//ua//sf//ubf/wXv/thv/tif/slv/tjf/smf/yYP/ulf/2R//2Sv/xkP/2av/0gP/ylf/2df/0i//0j//0lP/5cP/7a//1p//5gf/7ev/3o//2sf/5mP/6kv/2vP/3y//+jP///////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAQABAAAAePgH+Cg4SFhoJKPIeHYT+LhVppUTiPg2hrUkKPXWdlb2xHJk9jXoNJQDk9TVtkYCUkOy4wNjdGfy1UXGJYOksnPiwgFwwYg0NubWpmX1ArHREOFYUyWVNIVkxXQSoQhyMoNVUpRU5EixkcMzQaGy8xhwsKHiEfBQkSIg+GBAcUCIIBBDSYYGiAAUMALFR6FAgAOw==') EDSM._IMG_ERROR = tk.PhotoImage(data = 'R0lGODlhDgAOAIABAAAAAP///yH5BAEKAAEALAAAAAAOAA4AAAIcjIGJxqHaIJPypBYvzms77X1dWHlliKYmuI5GAQA7') # BBC Mode 5 '?' + def lookup(self, system_name, known=0): + self.cancel_lookup() + + if system_name in self.syscache: # Cache URLs of systems with known coordinates + self.result = { 'img': EDSM._IMG_KNOWN, 'url': self.syscache[system_name], 'done': True } + elif known: + self.result = { 'img': EDSM._IMG_KNOWN, 'url': 'http://www.edsm.net/needed-distances?systemName=%s' % urllib.quote(system_name), 'done': True } # default URL + self.thread = threading.Thread(target = self.known, name = 'EDSM worker', args = (system_name, self.result)) + else: + self.result = { 'img': '', 'url': 'http://www.edsm.net/needed-distances?systemName=%s' % urllib.quote(system_name), 'done': True } # default URL + r = requests.get('http://www.edsm.net/api-v1/system?sysname=%s&coords=1' % urllib.quote(system_name), timeout=EDSM._TIMEOUT) + r.raise_for_status() + data = r.json() + + if data == -1: + # System not present - but don't create it on the assumption that the caller will + result['img'] = EDSM._IMG_NEW + elif data.get('coords'): + result['img'] = EDSM._IMG_KNOWN + self.thread = threading.Thread(target = self.known, name = 'EDSM worker', args = (system_name, self.result)) + else: + result['img'] = EDSM._IMG_UNKNOWN + + # Asynchronous version of the above def start_lookup(self, system_name, known=0): self.cancel_lookup() - # Cache URLs of systems with known coordinates - if system_name in self.syscache: + if system_name in self.syscache: # Cache URLs of systems with known coordinates self.result = { 'img': EDSM._IMG_KNOWN, 'url': self.syscache[system_name], 'done': True } - return - - self.result = { 'img': '', 'url': 'http://www.edsm.net/needed-distances?systemName=%s' % urllib.quote(system_name), 'done': False } # default URL - self.thread = threading.Thread(target = known and self.known or self.worker, name = 'EDSM worker', args = (system_name, self.result)) - self.thread.daemon = True - self.thread.start() + elif known: + self.result = { 'img': EDSM._IMG_KNOWN, 'url': 'http://www.edsm.net/needed-distances?systemName=%s' % urllib.quote(system_name), 'done': True } # default URL + self.thread = threading.Thread(target = self.known, name = 'EDSM worker', args = (system_name, self.result)) + else: + self.result = { 'img': '', 'url': 'http://www.edsm.net/needed-distances?systemName=%s' % urllib.quote(system_name), 'done': False } # default URL + self.thread = threading.Thread(target = self.worker, name = 'EDSM worker', args = (system_name, self.result)) + self.thread.daemon = True + self.thread.start() def cancel_lookup(self): self.thread = None # orphan any existing thread @@ -53,6 +82,8 @@ class EDSM: result['done'] = True # give feedback immediately requests.get('http://www.edsm.net/api-v1/url?sysname=%s' % urllib.quote(system_name), timeout=EDSM._TIMEOUT) # creates system elif data.get('coords'): + result['img'] = EDSM._IMG_KNOWN + result['done'] = True # give feedback immediately self.known(system_name, result) else: result['img'] = EDSM._IMG_UNKNOWN @@ -64,8 +95,6 @@ class EDSM: # Worker for known known systems - saves initial EDSM API call def known(self, system_name, result): # Prefer to send user to "Show distances" page for systems with known coordinates - result['img'] = EDSM._IMG_KNOWN - result['done'] = True # give feedback immediately try: r = requests.get('http://www.edsm.net/api-v1/url?sysname=%s' % urllib.quote(system_name), timeout=EDSM._TIMEOUT) r.raise_for_status() @@ -73,3 +102,43 @@ class EDSM: result['url'] = self.syscache[system_name] = data['url']['show-system'] except: if __debug__: print_exc() + + +# Flight log - http://www.edsm.net/api-logs +def export(data, edsmlookupfn): + + querytime = config.getint('querytime') or int(time.time()) + + try: + # Look up the system before adding it to the log, since adding it to the log has the side-effect of creating it + edsmlookupfn() + + r = requests.get('http://www.edsm.net/api-logs-v1/set-log?commanderName=%s&apiKey=%s&systemName=%s&dateVisited=%s' % (urllib.quote(config.get('edsm_cmdrname')), urllib.quote(config.get('edsm_apikey')), urllib.quote(data['lastSystem']['name']), urllib.quote(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(querytime)))), timeout=EDSM._TIMEOUT) + r.raise_for_status() + reply = r.json() + (msgnum, msg) = reply['msgnum'], reply['msg'] + except: + if __debug__: print_exc() + raise Exception(_("Error: Can't connect to EDSM")) + + # Message numbers: 1xx = OK, 2xx = fatal error, 3xx = error (but not generated in practice), 4xx = ignorable errors + if msgnum // 100 not in (1,4): + raise Exception(_('Error: EDSM {MSG}').format(MSG=msg)) + + if not config.getint('edsm_historical'): + config.set('edsm_historical', 1) + thread = threading.Thread(target = export_historical, name = 'EDSM export') + thread.daemon = True + thread.start() + +# Make best effort to export existing flight log file. Be silent on error. +def export_historical(): + try: + for (timestamp, system_name) in flightlog.logs(): + r = requests.get('http://www.edsm.net/api-logs-v1/set-log?commanderName=%s&apiKey=%s&systemName=%s&dateVisited=%s' % (urllib.quote(config.get('edsm_cmdrname')), urllib.quote(config.get('edsm_apikey')), urllib.quote(system_name), urllib.quote(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(timestamp)))), timeout=EDSM._TIMEOUT) + r.raise_for_status() + + if r.json()['msgnum'] // 100 == 2: + raise Exception() + except: + if __debug__: print_exc() diff --git a/flightlog.py b/flightlog.py index 295b972b..d5ff7292 100644 --- a/flightlog.py +++ b/flightlog.py @@ -39,9 +39,6 @@ def openlog(): def export(data): - def elapsed(game_time): - return '%3d:%02d:%02d' % ((game_time // 3600) % 3600, (game_time // 60) % 60, game_time % 60) - querytime = config.getint('querytime') or int(time.time()) openlog() @@ -60,3 +57,16 @@ def export(data): ','.join([('%d %s' % (commodities[k], k)) for k in sorted(commodities)]))) logfile.flush() + + +# return log as list of (timestamp, system_name) +def logs(): + entries = [] + with open(join(config.get('outdir'), 'Flight Log.csv'), 'rU') as f: + f.readline() # Assume header + for line in f: + if not line.strip(): continue + cols = line.split(',') + assert len(cols) >= 3, cols + entries.append((time.mktime(time.strptime('%sT%s' % (cols[0], cols[1]), '%Y-%m-%dT%H:%M:%S')), cols[2])) # Convert from local time to UTC + return entries diff --git a/prefs.py b/prefs.py index a15037d5..23f24e37 100644 --- a/prefs.py +++ b/prefs.py @@ -1,12 +1,13 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from os.path import dirname, isdir, sep +from os.path import dirname, expanduser, isdir, sep from sys import platform import Tkinter as tk import ttk import tkFileDialog +from ttkHyperlinkLabel import HyperlinkLabel from config import applongname, config from hotkey import hotkeymgr @@ -64,6 +65,8 @@ class PreferencesDialog(tk.Toplevel): # http://wiki.tcl.tk/13428 parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') + style = ttk.Style() + frame = ttk.Frame(self) frame.grid(sticky=tk.NSEW) @@ -88,34 +91,58 @@ class PreferencesDialog(tk.Toplevel): outframe = ttk.LabelFrame(frame, text=_('Output')) # Section heading in settings outframe.grid(padx=10, pady=10, sticky=tk.NSEW) - outframe.columnconfigure(0, weight=1) output = config.getint('output') or (config.OUT_EDDN | config.OUT_SHIP_EDS) ttk.Label(outframe, text=_('Please choose what data to save')).grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W) self.out_eddn= tk.IntVar(value = (output & config.OUT_EDDN) and 1 or 0) - ttk.Checkbutton(outframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.out_eddn).grid(row=1, columnspan=2, padx=5, sticky=tk.W) - self.out_bpc = tk.IntVar(value = (output & config.OUT_BPC ) and 1 or 0) - ttk.Checkbutton(outframe, text=_("Market data in Slopey's BPC format"), variable=self.out_bpc, command=self.outvarchanged).grid(row=2, columnspan=2, padx=5, sticky=tk.W) - self.out_td = tk.IntVar(value = (output & config.OUT_TD ) and 1 or 0) - ttk.Checkbutton(outframe, text=_('Market data in Trade Dangerous format'), variable=self.out_td, command=self.outvarchanged).grid(row=3, columnspan=2, padx=5, sticky=tk.W) + ttk.Checkbutton(outframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.out_eddn, command=self.outvarchanged).grid(row=1, columnspan=2, padx=5, sticky=tk.W) self.out_csv = tk.IntVar(value = (output & config.OUT_CSV ) and 1 or 0) - ttk.Checkbutton(outframe, text=_('Market data in CSV format'), variable=self.out_csv, command=self.outvarchanged).grid(row=4, columnspan=2, padx=5, sticky=tk.W) + ttk.Checkbutton(outframe, text=_('Market data in CSV format file'), variable=self.out_csv, command=self.outvarchanged).grid(row=2, columnspan=2, padx=5, sticky=tk.W) + self.out_bpc = tk.IntVar(value = (output & config.OUT_BPC ) and 1 or 0) + ttk.Checkbutton(outframe, text=_("Market data in Slopey's BPC format file"), variable=self.out_bpc, command=self.outvarchanged).grid(row=3, columnspan=2, padx=5, sticky=tk.W) + self.out_td = tk.IntVar(value = (output & config.OUT_TD ) and 1 or 0) + ttk.Checkbutton(outframe, text=_('Market data in Trade Dangerous format file'), variable=self.out_td, command=self.outvarchanged).grid(row=4, columnspan=2, padx=5, sticky=tk.W) self.out_ship_eds= tk.IntVar(value = (output & config.OUT_SHIP_EDS) and 1 or 0) - ttk.Checkbutton(outframe, text=_('Ship loadout in E:D Shipyard format'), variable=self.out_ship_eds, command=self.outvarchanged).grid(row=5, columnspan=2, padx=5, sticky=tk.W) + ttk.Checkbutton(outframe, text=_('Ship loadout in E:D Shipyard format file'), variable=self.out_ship_eds, command=self.outvarchanged).grid(row=5, columnspan=2, padx=5, pady=(5,0), sticky=tk.W) self.out_ship_coriolis= tk.IntVar(value = (output & config.OUT_SHIP_CORIOLIS) and 1 or 0) - ttk.Checkbutton(outframe, text=_('Ship loadout in Coriolis format'), variable=self.out_ship_coriolis, command=self.outvarchanged).grid(row=6, columnspan=2, padx=5, sticky=tk.W) - self.out_log = tk.IntVar(value = (output & config.OUT_LOG ) and 1 or 0) - ttk.Checkbutton(outframe, text=_('Flight log'), variable=self.out_log, command=self.outvarchanged).grid(row=7, columnspan=2, padx=5, sticky=tk.W) + ttk.Checkbutton(outframe, text=_('Ship loadout in Coriolis format file'), variable=self.out_ship_coriolis, command=self.outvarchanged).grid(row=6, columnspan=2, padx=5, sticky=tk.W) + self.out_log_edsm = tk.IntVar(value = (output & config.OUT_LOG_EDSM ) and 1 or 0) + ttk.Checkbutton(outframe, text=_('Send flight log to Elite Dangerous Star Map'), variable=self.out_log_edsm, command=self.outvarchanged).grid(row=7, columnspan=2, padx=5, pady=(5,0), sticky=tk.W) + self.out_log_file = tk.IntVar(value = (output & config.OUT_LOG_FILE ) and 1 or 0) + ttk.Checkbutton(outframe, text=_('Flight log in CSV format file'), variable=self.out_log_file, command=self.outvarchanged).grid(row=8, columnspan=2, padx=5, sticky=tk.W) - ttk.Label(outframe, text=(platform=='darwin' and _('Where:') or # Output folder prompt on OSX - _('File location:'))).grid(row=8, padx=5, pady=(5,0), sticky=tk.NSEW) # Output folder prompt on Windows - self.outbutton = ttk.Button(outframe, text=(platform=='darwin' and _('Change...') or # Folder selection button on OSX + self.dir_label = ttk.Label(frame, text=_('File location'), foreground=style.lookup('TLabelframe.Label', 'foreground')) # Section heading in settings + dirframe = ttk.LabelFrame(frame, labelwidget = self.dir_label) + dirframe.grid(padx=10, pady=10, sticky=tk.NSEW) + dirframe.columnconfigure(0, weight=1) + + self.outdir = ttk.Entry(dirframe, takefocus=False) + if config.get('outdir').startswith(expanduser('~')): + self.outdir.insert(0, '~' + config.get('outdir')[len(expanduser('~')):]) + else: + self.outdir.insert(0, config.get('outdir')) + self.outdir.grid(row=0, padx=5, pady=5, sticky=tk.NSEW) + self.outbutton = ttk.Button(dirframe, text=(platform=='darwin' and _('Change...') or # Folder selection button on OSX _('Browse...')), command=self.outbrowse) # Folder selection button on Windows - self.outbutton.grid(row=8, column=1, padx=5, pady=(5,0), sticky=tk.NSEW) - self.outdir = ttk.Entry(outframe, takefocus=False) - self.outdir.insert(0, config.get('outdir')) - self.outdir.grid(row=9, columnspan=2, padx=5, pady=5, sticky=tk.EW) - self.outvarchanged() + self.outbutton.grid(row=0, column=1, padx=5, pady=5, sticky=tk.NSEW) + + self.edsm_label = HyperlinkLabel(frame, text=_('Elite Dangerous Star Map credentials'), disabledforeground=style.lookup('TLabelframe.Label', 'foreground'), url='http://www.edsm.net/settings/api', underline=True) # Section heading in settings + edsmframe = ttk.LabelFrame(frame, labelwidget = self.edsm_label) + edsmframe.grid(padx=10, pady=10, sticky=tk.NSEW) + edsmframe.columnconfigure(1, weight=1) + + ttk.Label(edsmframe, text=_('Cmdr name')).grid(row=0, sticky=tk.W) # EDSM & privacy setting + self.edsm_cmdr = ttk.Entry(edsmframe) + self.edsm_cmdr.insert(0, config.get('edsm_cmdrname') or '') + self.edsm_cmdr.grid(row=0, column=1, sticky=tk.NSEW) + + ttk.Label(edsmframe, text=_('API Key')).grid(row=1, sticky=tk.W) # EDSM setting + self.edsm_apikey = ttk.Entry(edsmframe) + self.edsm_apikey.insert(0, config.get('edsm_apikey') or '') + self.edsm_apikey.grid(row=1, column=1, sticky=tk.NSEW) + + for child in edsmframe.winfo_children(): + child.grid_configure(padx=5, pady=3) if platform in ['darwin','win32']: self.hotkey_code = config.getint('hotkey_code') @@ -145,7 +172,6 @@ class PreferencesDialog(tk.Toplevel): privacyframe = ttk.LabelFrame(frame, text=_('Privacy')) # Section heading in settings privacyframe.grid(padx=10, pady=10, sticky=tk.NSEW) - privacyframe.columnconfigure(0, weight=1) self.out_anon= tk.IntVar(value = config.getint('anonymous') and 1) ttk.Label(privacyframe, text=_('How do you want to be identified in the saved data')).grid(row=0, columnspan=2, padx=5, sticky=tk.W) @@ -162,6 +188,9 @@ class PreferencesDialog(tk.Toplevel): ttk.Button(buttonframe, text=_('OK'), command=self.apply).grid(row=0, column=1, sticky=tk.E) self.protocol("WM_DELETE_WINDOW", self._destroy) + # Selectively disable buttons depending on output settings + self.outvarchanged() + # disable hotkey for the duration hotkeymgr.unregister() @@ -171,13 +200,19 @@ class PreferencesDialog(tk.Toplevel): #self.wait_window(self) # causes duplicate events on OSX def outvarchanged(self): - local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() or self.out_ship_eds.get() or self.out_ship_coriolis.get() or self.out_log.get() - self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED + local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() or self.out_ship_eds.get() or self.out_ship_coriolis.get() or self.out_log_file.get() + self.dir_label['state'] = local and tk.NORMAL or tk.DISABLED + self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED self.outdir['state'] = local and 'readonly' or tk.DISABLED + edsm = self.out_log_edsm.get() + self.edsm_label['state'] = edsm and tk.NORMAL or tk.DISABLED + self.edsm_cmdr['state'] = edsm and tk.NORMAL or tk.DISABLED + self.edsm_apikey['state'] = edsm and tk.NORMAL or tk.DISABLED + def outbrowse(self): if platform != 'win32': - d = tkFileDialog.askdirectory(parent=self, initialdir=self.outdir.get(), title=_('File location:'), mustexist=tk.TRUE) + d = tkFileDialog.askdirectory(parent=self, initialdir=expanduser(self.outdir.get()), title=_('File location'), mustexist=tk.TRUE) else: def browsecallback(hwnd, uMsg, lParam, lpData): # set initial folder @@ -186,10 +221,10 @@ class PreferencesDialog(tk.Toplevel): return 0 browseInfo = BROWSEINFO() - browseInfo.lpszTitle = _('File location:') + browseInfo.lpszTitle = _('File location') browseInfo.ulFlags = BIF_RETURNONLYFSDIRS|BIF_USENEWUI browseInfo.lpfn = BrowseCallbackProc(browsecallback) - browseInfo.lParam = self.outdir.get() + browseInfo.lParam = expanduser(self.outdir.get()) ctypes.windll.ole32.CoInitialize(None) pidl = ctypes.windll.shell32.SHBrowseForFolderW(ctypes.byref(browseInfo)) if pidl: @@ -203,7 +238,10 @@ class PreferencesDialog(tk.Toplevel): if d: self.outdir['state'] = tk.NORMAL # must be writable to update self.outdir.delete(0, tk.END) - self.outdir.insert(0, d.replace('/', sep)) + if d.startswith(expanduser('~')): + self.outdir.insert(0, '~' + d[len(expanduser('~')):]) + else: + self.outdir.insert(0, d) self.outdir['state'] = 'readonly' def hotkeystart(self, event): @@ -251,14 +289,21 @@ class PreferencesDialog(tk.Toplevel): credentials = (config.get('username'), config.get('password')) config.set('username', self.username.get().strip()) config.set('password', self.password.get().strip()) - config.set('output', (self.out_eddn.get() and config.OUT_EDDN or 0) + (self.out_bpc.get() and config.OUT_BPC or 0) + (self.out_td.get() and config.OUT_TD or 0) + (self.out_csv.get() and config.OUT_CSV or 0) + (self.out_ship_eds.get() and config.OUT_SHIP_EDS or 0) + (self.out_log.get() and config.OUT_LOG or 0) + (self.out_ship_coriolis.get() and config.OUT_SHIP_CORIOLIS or 0)) - config.set('outdir', self.outdir.get().strip()) + + config.set('output', (self.out_eddn.get() and config.OUT_EDDN or 0) + (self.out_bpc.get() and config.OUT_BPC or 0) + (self.out_td.get() and config.OUT_TD or 0) + (self.out_csv.get() and config.OUT_CSV or 0) + (self.out_ship_eds.get() and config.OUT_SHIP_EDS or 0) + (self.out_log_file.get() and config.OUT_LOG_FILE or 0) + (self.out_ship_coriolis.get() and config.OUT_SHIP_CORIOLIS or 0) + (self.out_log_edsm.get() and config.OUT_LOG_EDSM or 0)) + config.set('outdir', expanduser(self.outdir.get())) + + config.set('edsm_cmdrname', self.edsm_cmdr.get().strip()) + config.set('edsm_apikey', self.edsm_apikey.get().strip()) + if platform in ['darwin','win32']: config.set('hotkey_code', self.hotkey_code) config.set('hotkey_mods', self.hotkey_mods) config.set('hotkey_always', int(not self.hotkey_only.get())) config.set('hotkey_mute', int(not self.hotkey_play.get())) + config.set('anonymous', self.out_anon.get()) + self._destroy() if credentials != (config.get('username'), config.get('password')) and self.callback: self.callback()