tradedangerous 12.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py.typed +1 -0
- trade.py +49 -0
- tradedangerous/__init__.py +43 -0
- tradedangerous/cache.py +1381 -0
- tradedangerous/cli.py +136 -0
- tradedangerous/commands/TEMPLATE.py +74 -0
- tradedangerous/commands/__init__.py +244 -0
- tradedangerous/commands/buildcache_cmd.py +102 -0
- tradedangerous/commands/buy_cmd.py +427 -0
- tradedangerous/commands/commandenv.py +372 -0
- tradedangerous/commands/exceptions.py +94 -0
- tradedangerous/commands/export_cmd.py +150 -0
- tradedangerous/commands/import_cmd.py +222 -0
- tradedangerous/commands/local_cmd.py +243 -0
- tradedangerous/commands/market_cmd.py +207 -0
- tradedangerous/commands/nav_cmd.py +252 -0
- tradedangerous/commands/olddata_cmd.py +270 -0
- tradedangerous/commands/parsing.py +221 -0
- tradedangerous/commands/rares_cmd.py +298 -0
- tradedangerous/commands/run_cmd.py +1521 -0
- tradedangerous/commands/sell_cmd.py +262 -0
- tradedangerous/commands/shipvendor_cmd.py +60 -0
- tradedangerous/commands/station_cmd.py +68 -0
- tradedangerous/commands/trade_cmd.py +181 -0
- tradedangerous/commands/update_cmd.py +67 -0
- tradedangerous/corrections.py +55 -0
- tradedangerous/csvexport.py +234 -0
- tradedangerous/db/__init__.py +27 -0
- tradedangerous/db/adapter.py +192 -0
- tradedangerous/db/config.py +107 -0
- tradedangerous/db/engine.py +259 -0
- tradedangerous/db/lifecycle.py +332 -0
- tradedangerous/db/locks.py +208 -0
- tradedangerous/db/orm_models.py +500 -0
- tradedangerous/db/paths.py +113 -0
- tradedangerous/db/utils.py +661 -0
- tradedangerous/edscupdate.py +565 -0
- tradedangerous/edsmupdate.py +474 -0
- tradedangerous/formatting.py +210 -0
- tradedangerous/fs.py +156 -0
- tradedangerous/gui.py +1146 -0
- tradedangerous/mapping.py +133 -0
- tradedangerous/mfd/__init__.py +103 -0
- tradedangerous/mfd/saitek/__init__.py +3 -0
- tradedangerous/mfd/saitek/directoutput.py +678 -0
- tradedangerous/mfd/saitek/x52pro.py +195 -0
- tradedangerous/misc/checkpricebounds.py +287 -0
- tradedangerous/misc/clipboard.py +49 -0
- tradedangerous/misc/coord64.py +83 -0
- tradedangerous/misc/csvdialect.py +57 -0
- tradedangerous/misc/derp-sentinel.py +35 -0
- tradedangerous/misc/diff-system-csvs.py +159 -0
- tradedangerous/misc/eddb.py +81 -0
- tradedangerous/misc/eddn.py +349 -0
- tradedangerous/misc/edsc.py +437 -0
- tradedangerous/misc/edsm.py +121 -0
- tradedangerous/misc/importeddbstats.py +54 -0
- tradedangerous/misc/prices-json-exp.py +179 -0
- tradedangerous/misc/progress.py +194 -0
- tradedangerous/plugins/__init__.py +249 -0
- tradedangerous/plugins/edcd_plug.py +371 -0
- tradedangerous/plugins/eddblink_plug.py +861 -0
- tradedangerous/plugins/edmc_batch_plug.py +133 -0
- tradedangerous/plugins/spansh_plug.py +2647 -0
- tradedangerous/prices.py +211 -0
- tradedangerous/submit-distances.py +422 -0
- tradedangerous/templates/Added.csv +37 -0
- tradedangerous/templates/Category.csv +17 -0
- tradedangerous/templates/RareItem.csv +143 -0
- tradedangerous/templates/TradeDangerous.sql +338 -0
- tradedangerous/tools.py +40 -0
- tradedangerous/tradecalc.py +1302 -0
- tradedangerous/tradedb.py +2320 -0
- tradedangerous/tradeenv.py +313 -0
- tradedangerous/tradeenv.pyi +109 -0
- tradedangerous/tradeexcept.py +131 -0
- tradedangerous/tradeorm.py +183 -0
- tradedangerous/transfers.py +192 -0
- tradedangerous/utils.py +243 -0
- tradedangerous/version.py +16 -0
- tradedangerous-12.7.6.dist-info/METADATA +106 -0
- tradedangerous-12.7.6.dist-info/RECORD +87 -0
- tradedangerous-12.7.6.dist-info/WHEEL +5 -0
- tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
- tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
- tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
- tradegui.py +24 -0
tradedangerous/gui.py
ADDED
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# --------------------------------------------------------------------
|
|
3
|
+
# Copyright (C) Jonathan 'eyeonus' Jones 2018-2022
|
|
4
|
+
|
|
5
|
+
# You are free to use, redistribute, or even print and eat a copy of
|
|
6
|
+
# this software so long as you include this copyright notice.
|
|
7
|
+
# I guarantee there is at least one bug neither of us knew about.
|
|
8
|
+
# --------------------------------------------------------------------
|
|
9
|
+
# TradeDangerous :: GUI App :: Main Module
|
|
10
|
+
|
|
11
|
+
# Where all the graphics happens. Uses TD CLI under the hood.
|
|
12
|
+
|
|
13
|
+
# Current features:
|
|
14
|
+
# ----------------
|
|
15
|
+
# Drop-down list of all available TD commands
|
|
16
|
+
# Fully populated list of all arguments and switches for each command
|
|
17
|
+
# Automatic setting of default value for the above which have one
|
|
18
|
+
# Procedural generation of all above for future proofing in the
|
|
19
|
+
# event of new import plugins, switches, arguments, commands(?)
|
|
20
|
+
# RAM resident save-state: altered values retain new value while main
|
|
21
|
+
# window remains open
|
|
22
|
+
|
|
23
|
+
# Planned features:
|
|
24
|
+
# ----------------
|
|
25
|
+
# User-defined initial values AKA tdrc files (.tdrc_run, .tdrc_trade, ..)
|
|
26
|
+
# Profiles AKA fromfiles (+ship1, +ship2, ..)
|
|
27
|
+
# Select-able output text
|
|
28
|
+
# graphical render of results
|
|
29
|
+
# send results to new window
|
|
30
|
+
# individual always-on-top for every window
|
|
31
|
+
# Data retrieval from CMDR's journal
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
import threading
|
|
36
|
+
import tkinter as tk
|
|
37
|
+
import traceback
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from tkinter import ttk, filedialog
|
|
40
|
+
|
|
41
|
+
from . import commands
|
|
42
|
+
from . import plugins
|
|
43
|
+
from . import tradedb
|
|
44
|
+
from .commands import exceptions
|
|
45
|
+
from .plugins import PluginException
|
|
46
|
+
from .version import __version__
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
import tkinter as tk
|
|
50
|
+
from tkinter import ttk, scrolledtext
|
|
51
|
+
|
|
52
|
+
class TDWidget:
|
|
53
|
+
"""
|
|
54
|
+
Tkinter widget wrapper supporting appJar-style context management.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, name=None, widget_type='frame', parent=None, row=0, column=0,
|
|
58
|
+
rowspan=1, columnspan=1, sticky='nw', text='', values=None,
|
|
59
|
+
command=None, width=None, height=None, **kwargs):
|
|
60
|
+
self.kwargs = kwargs
|
|
61
|
+
self.name = name
|
|
62
|
+
self.widget_type = widget_type
|
|
63
|
+
self.children = []
|
|
64
|
+
|
|
65
|
+
# Parent widget
|
|
66
|
+
container = parent.widget if isinstance(parent, TDWidget) else parent
|
|
67
|
+
|
|
68
|
+
# Create underlying Tk widget
|
|
69
|
+
if widget_type == 'frame':
|
|
70
|
+
self.widget = tk.Frame(container, width=width, height=height)
|
|
71
|
+
elif widget_type == 'label':
|
|
72
|
+
self.widget = tk.Label(container, text=text, width=width)
|
|
73
|
+
elif widget_type == 'button':
|
|
74
|
+
self.widget = tk.Button(container, text=text, command=command)
|
|
75
|
+
elif widget_type == 'entry':
|
|
76
|
+
self.var = tk.StringVar(value=text)
|
|
77
|
+
self.widget = tk.Entry(container, textvariable=self.var, width=width)
|
|
78
|
+
elif widget_type == 'combo':
|
|
79
|
+
self.var = tk.StringVar(value=text)
|
|
80
|
+
self.widget = ttk.Combobox(container, values=values or [], textvariable=self.var, width=width)
|
|
81
|
+
elif widget_type == 'spin':
|
|
82
|
+
self.var = tk.IntVar(value=text)
|
|
83
|
+
self.widget = tk.Spinbox(container, from_=0, to=values or 100, textvariable=self.var, width=width)
|
|
84
|
+
elif widget_type == 'scrolledtext':
|
|
85
|
+
self.widget = scrolledtext.ScrolledText(container, width=width, height=height)
|
|
86
|
+
elif widget_type == 'notebook':
|
|
87
|
+
self.widget = ttk.Notebook(container)
|
|
88
|
+
self.tabs = {}
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError(f"Unknown widget_type: {widget_type}")
|
|
91
|
+
|
|
92
|
+
# Parent-child management
|
|
93
|
+
self.parent = parent
|
|
94
|
+
if parent and isinstance(parent, TDWidget):
|
|
95
|
+
parent.children.append(self)
|
|
96
|
+
|
|
97
|
+
# Grid placement
|
|
98
|
+
self.widget.grid(row=row, column=column, rowspan=rowspan, columnspan=columnspan, sticky=sticky)
|
|
99
|
+
self.row, self.column = row, column
|
|
100
|
+
|
|
101
|
+
# --- Proxy methods ---
|
|
102
|
+
def config(self, **kwargs):
|
|
103
|
+
self.widget.config(**kwargs)
|
|
104
|
+
|
|
105
|
+
def set(self, value, callFunction=True):
|
|
106
|
+
if hasattr(self, 'var'):
|
|
107
|
+
self.var.set(value)
|
|
108
|
+
if callFunction and self.widget_type == 'combo':
|
|
109
|
+
if hasattr(self.widget, 'event_generate'):
|
|
110
|
+
self.widget.event_generate("<<ComboboxSelected>>")
|
|
111
|
+
|
|
112
|
+
def get(self):
|
|
113
|
+
if hasattr(self, 'var'):
|
|
114
|
+
return self.var.get()
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def add_child(self, child):
|
|
118
|
+
self.children.append(child)
|
|
119
|
+
|
|
120
|
+
# --- Context manager support ---
|
|
121
|
+
def __enter__(self):
|
|
122
|
+
# Return self so "with TDWidget(...) as w:" works
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
126
|
+
# Nothing special to clean up
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
# --- Notebook/tab support ---
|
|
130
|
+
def tab(self, tab_name):
|
|
131
|
+
"""Create a new tab inside a notebook TDWidget"""
|
|
132
|
+
if self.widget_type != 'notebook':
|
|
133
|
+
raise RuntimeError("tab() can only be called on notebook widgets")
|
|
134
|
+
frame = tk.Frame(self.widget)
|
|
135
|
+
self.widget.add(frame, text=tab_name)
|
|
136
|
+
self.tabs[tab_name] = frame
|
|
137
|
+
return TDWidget(parent=self, widget_type='frame', row=0, column=0, columnspan=1, widget=frame)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Plugins available to the 'import' command are stored here.
|
|
141
|
+
# The list is populated by scanning the plugin folder directly,
|
|
142
|
+
# so it updates automagically at start as plugins are added or removed.
|
|
143
|
+
|
|
144
|
+
# Any other command with available plugins must have a similar list.
|
|
145
|
+
importPlugs = [ plug.name[0:plug.name.find('_plug.py')]
|
|
146
|
+
for plug in os.scandir(sys.modules['tradedangerous.plugins'].__path__[0])
|
|
147
|
+
if plug.name.endswith("_plug.py")
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
widgets = {}
|
|
151
|
+
|
|
152
|
+
# All available commands
|
|
153
|
+
Commands = ['help'] + [ cmd for cmd, module in sorted(commands.commandIndex.items()) ]
|
|
154
|
+
# Used to run TD cli commands.
|
|
155
|
+
cmdenv = commands.CommandIndex().parse
|
|
156
|
+
# 'help' output, required & optional arguments, for each command
|
|
157
|
+
cmdHelp = {}
|
|
158
|
+
|
|
159
|
+
# Path of the database
|
|
160
|
+
dbS = str(Path((os.environ.get('TD_DATA') or os.path.join(os.getcwd(), 'data')) / Path('TradeDangerous.db')))
|
|
161
|
+
# Path of the current working directory
|
|
162
|
+
cwdS = str(Path(os.getcwd()))
|
|
163
|
+
|
|
164
|
+
def changeCWD():
|
|
165
|
+
"""
|
|
166
|
+
Opens a folder select dialog for choosing the current working directory.
|
|
167
|
+
"""
|
|
168
|
+
cwd = filedialog.askdirectory(title = "Select the top-level folder for TD to work in...",
|
|
169
|
+
initialdir = argVals['--cwd'])
|
|
170
|
+
# cwd = win.directoryBox("Select the top-level folder for TD to work in...", dirName = argVals['--cwd'])
|
|
171
|
+
if cwd:
|
|
172
|
+
argVals['--cwd'] = str(Path(cwd))
|
|
173
|
+
widgets['cwd']['text'] = argVals['--cwd']
|
|
174
|
+
|
|
175
|
+
def changeDB():
|
|
176
|
+
"""
|
|
177
|
+
Opens a file select dialog for choosing the database file.
|
|
178
|
+
"""
|
|
179
|
+
db = filedialog.askopenfilename(title = "Select the TD database file to use...",
|
|
180
|
+
initialdir = str(Path(argVals['--db']).parent),
|
|
181
|
+
filetypes = [('Data Base File', '*.db')])
|
|
182
|
+
if db:
|
|
183
|
+
argVals['--db'] = str(Path(db))
|
|
184
|
+
widgets['db']['text'] = argVals['--db']
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# A dict of all arguments in TD (mostly auto-generated)
|
|
188
|
+
# Manually add the global arguments for now, maybe figure out how to auto-populate them as well.
|
|
189
|
+
allArgs = {
|
|
190
|
+
'--debug': { 'help': 'Enable/raise level of diagnostic output.',
|
|
191
|
+
'default': 0, 'required': False, 'action': 'count',
|
|
192
|
+
'widget': {'type':'combo', 'values': ['', '-w', '-ww', '-www']}
|
|
193
|
+
},
|
|
194
|
+
'--detail':{ 'help': 'Increase level of detail in output.',
|
|
195
|
+
'default': 0, 'required': False, 'action': 'count',
|
|
196
|
+
'excludes': ['--quiet'], 'widget': {'type':'combo', 'values': ['', '-v', '-vv', '-vvv']}
|
|
197
|
+
},
|
|
198
|
+
'--quiet':{ 'help': 'Reduce level of detail in output.',
|
|
199
|
+
'default': 0, 'required': False, 'action': 'count',
|
|
200
|
+
'excludes': ['--detail'], 'widget': {'type':'combo', 'values': ['', '-q', '-qq', '-qqq']}
|
|
201
|
+
},
|
|
202
|
+
'--db':{ 'help': 'Specify location of the SQLite database.',
|
|
203
|
+
'default': dbS, 'dest': 'dbFilename', 'type': str,
|
|
204
|
+
'widget': {'type':'button', 'func':changeDB}
|
|
205
|
+
},
|
|
206
|
+
'--cwd':{ 'help': 'Change the working directory file accesses are made from.',
|
|
207
|
+
'default': cwdS, 'type': str, 'required': False,
|
|
208
|
+
'widget': {'type':'button', 'func':changeCWD}
|
|
209
|
+
},
|
|
210
|
+
'--link-ly':{ 'help': 'Maximum lightyears between systems to be considered linked.',
|
|
211
|
+
'default': '30',
|
|
212
|
+
'widget': {'type':'entry', 'sub':'numeric'}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Used to save the value of the arguments.
|
|
217
|
+
argVals = {'--debug': '',
|
|
218
|
+
'--detail': '',
|
|
219
|
+
'--quiet': '',
|
|
220
|
+
'--db': dbS,
|
|
221
|
+
'--cwd': cwdS,
|
|
222
|
+
'--link-ly': '30'
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
def buildArgDicts():
|
|
226
|
+
"""
|
|
227
|
+
Procedurally generates the contents of allArgs and argVals
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
cmdenv(['help'])
|
|
231
|
+
except exceptions.UsageError as e:
|
|
232
|
+
cmdHelp['help'] = str(e)
|
|
233
|
+
for cmd in Commands:
|
|
234
|
+
# print(cmd)
|
|
235
|
+
if cmd == 'help':
|
|
236
|
+
continue
|
|
237
|
+
try:
|
|
238
|
+
cmdenv(['trade', cmd, '-h'])
|
|
239
|
+
except exceptions.UsageError as e:
|
|
240
|
+
cmdHelp[cmd] = str(e)
|
|
241
|
+
index = commands.commandIndex[cmd]
|
|
242
|
+
|
|
243
|
+
allArgs[cmd] = {'req': {}, 'opt': {}}
|
|
244
|
+
if index.arguments:
|
|
245
|
+
for arg in index.arguments:
|
|
246
|
+
# print(arg.args[0])
|
|
247
|
+
argVals[arg.args[0]] = arg.kwargs.get('default') or None
|
|
248
|
+
|
|
249
|
+
allArgs[cmd]['req'][arg.args[0]] = {kwarg: arg.kwargs[kwarg] for kwarg in arg.kwargs}
|
|
250
|
+
allArgs[cmd]['req'][arg.args[0]]['widget'] = chooseType(arg)
|
|
251
|
+
# print(allArgs[cmd]['req'])
|
|
252
|
+
|
|
253
|
+
if index.switches:
|
|
254
|
+
for arg in index.switches:
|
|
255
|
+
try:
|
|
256
|
+
argVals[arg.args[0]] = arg.kwargs.get('default') or None
|
|
257
|
+
|
|
258
|
+
allArgs[cmd]['opt'][arg.args[0]] = {kwarg: arg.kwargs[kwarg] for kwarg in arg.kwargs}
|
|
259
|
+
allArgs[cmd]['opt'][arg.args[0]]['widget'] = chooseType(arg)
|
|
260
|
+
|
|
261
|
+
if arg.args[0] == '--option':
|
|
262
|
+
# Currently only the 'import' cmd has the '--plug' option,
|
|
263
|
+
# but this could no longer be the case in future.
|
|
264
|
+
if cmd == 'import':
|
|
265
|
+
plugOptions = {
|
|
266
|
+
plug: plugins.load(cmdenv(['trade', cmd, '--plug', plug, '-O', 'help']).plug,
|
|
267
|
+
"ImportPlugin").pluginOptions for plug in importPlugs
|
|
268
|
+
}
|
|
269
|
+
allArgs[cmd]['opt'][arg.args[0]]['options'] = plugOptions
|
|
270
|
+
|
|
271
|
+
except AttributeError:
|
|
272
|
+
for argGrp in arg.arguments:
|
|
273
|
+
argVals[argGrp.args[0]] = argGrp.kwargs.get('default') or None
|
|
274
|
+
|
|
275
|
+
allArgs[cmd]['opt'][argGrp.args[0]] = {kwarg: argGrp.kwargs[kwarg] for kwarg in argGrp.kwargs}
|
|
276
|
+
allArgs[cmd]['opt'][argGrp.args[0]]['widget'] = chooseType(argGrp)
|
|
277
|
+
|
|
278
|
+
allArgs[cmd]['opt'][argGrp.args[0]]['excludes'] = [excl.args[0] for excl in arg.arguments
|
|
279
|
+
if excl.args[0] != argGrp.args[0]]
|
|
280
|
+
if argGrp.args[0] == '--plug':
|
|
281
|
+
# Currently only the 'import' cmd has the '--plug' option,
|
|
282
|
+
# but this could no longer be the case in the future.
|
|
283
|
+
if cmd == 'import':
|
|
284
|
+
allArgs[cmd]['opt'][argGrp.args[0]]['plugins'] = importPlugs
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def optWindow():
|
|
288
|
+
"""
|
|
289
|
+
Opens a window listing all of the options for the currently selected plugin.
|
|
290
|
+
"""
|
|
291
|
+
# Create a new subwindow for plugin options
|
|
292
|
+
sw = TDWidget.SubWindow("Plugin Options", modal=True)
|
|
293
|
+
|
|
294
|
+
# Clear any previous widgets
|
|
295
|
+
sw.clear()
|
|
296
|
+
|
|
297
|
+
# Build dictionary of current option values
|
|
298
|
+
optDict = {}
|
|
299
|
+
currentOptStr = argVals.get('--option')
|
|
300
|
+
if currentOptStr:
|
|
301
|
+
for option in currentOptStr.split(','):
|
|
302
|
+
if '=' in option:
|
|
303
|
+
key, val = option.split('=', 1)
|
|
304
|
+
optDict[key] = val
|
|
305
|
+
elif option != '':
|
|
306
|
+
optDict[option] = True
|
|
307
|
+
|
|
308
|
+
# Check if a plugin is selected
|
|
309
|
+
selectedPlug = sw.combo('--plug')
|
|
310
|
+
if not selectedPlug:
|
|
311
|
+
sw.message('No import plugin chosen.', width=170)
|
|
312
|
+
else:
|
|
313
|
+
plugOpts = allArgs['import']['opt']['--option']['options'].get(selectedPlug, {})
|
|
314
|
+
for option, tooltip in plugOpts.items():
|
|
315
|
+
if '=' in tooltip:
|
|
316
|
+
sw.entry(option, value=optDict.get(option, ''), label=True, sticky='ew', tooltip=tooltip)
|
|
317
|
+
else:
|
|
318
|
+
sw.check(option, optDict.get(option, False), sticky='ew', tooltip=tooltip)
|
|
319
|
+
|
|
320
|
+
# Add Done and Cancel buttons
|
|
321
|
+
sw.button("Done", setOpts)
|
|
322
|
+
sw.button("Cancel", sw.hide)
|
|
323
|
+
|
|
324
|
+
# Display the subwindow
|
|
325
|
+
sw.show()
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def chooseType(arg):
|
|
329
|
+
"""
|
|
330
|
+
Choose what type of TDWidget to make for the passed argument
|
|
331
|
+
"""
|
|
332
|
+
if arg.kwargs.get('action') in ('store_true', 'store_const'):
|
|
333
|
+
return {'type': 'check'}
|
|
334
|
+
if arg.kwargs.get('type') == int:
|
|
335
|
+
return {'type': 'spin', 'min': 0, 'max': 4096}
|
|
336
|
+
if arg.kwargs.get('choices'):
|
|
337
|
+
return {'type': 'ticks', 'values': list(arg.kwargs.get('choices'))}
|
|
338
|
+
if arg.args[0] == '--plug':
|
|
339
|
+
return {'type': 'combo', 'values': [''] + importPlugs}
|
|
340
|
+
if arg.args[0] == '--option':
|
|
341
|
+
return {'type': 'option', 'func': optWindow}
|
|
342
|
+
if arg.kwargs.get('type') == float:
|
|
343
|
+
return {'type': 'numeric'}
|
|
344
|
+
if arg.kwargs.get('type') == 'credits':
|
|
345
|
+
return {'type': 'credits'}
|
|
346
|
+
return {'type': 'entry'}
|
|
347
|
+
|
|
348
|
+
def addWidget(widgetType, parent=None, cpos=0, rpos=0, **kwargs):
|
|
349
|
+
"""
|
|
350
|
+
Adds a new TDWidget widget and configures it based on passed parameters
|
|
351
|
+
"""
|
|
352
|
+
cspan = kwargs.pop('colspan', None)
|
|
353
|
+
rspan = kwargs.pop('rowspan', None)
|
|
354
|
+
|
|
355
|
+
# Create widget based on type
|
|
356
|
+
if widgetType == 'combo':
|
|
357
|
+
widget = TDWidget.Combo(parent, values=kwargs.get('values'), textvariable=kwargs.get('textvariable'))
|
|
358
|
+
elif widgetType == 'ticks':
|
|
359
|
+
widget = TDWidget.List(parent, values=kwargs.get('values'), selectmode=kwargs.get('selectmode'), height=kwargs.get('height'))
|
|
360
|
+
elif widgetType == 'stext':
|
|
361
|
+
widget = TDWidget.ScrolledText(parent, textvariable=kwargs.get('textvariable'))
|
|
362
|
+
elif widgetType == 'button':
|
|
363
|
+
widget = TDWidget.Button(parent, text=kwargs.get('text'), command=kwargs.get('func'))
|
|
364
|
+
elif widgetType == 'frame':
|
|
365
|
+
widget = TDWidget.Frame(parent)
|
|
366
|
+
elif widgetType == 'tab':
|
|
367
|
+
widget = TDWidget.Tab(parent)
|
|
368
|
+
elif widgetType == 'label':
|
|
369
|
+
widget = TDWidget.Label(parent, text=kwargs.get('text'))
|
|
370
|
+
elif widgetType == 'check':
|
|
371
|
+
widget = TDWidget.Check(parent, text=kwargs.get('text'), variable=kwargs.get('textvariable'),
|
|
372
|
+
onvalue=kwargs.get('onvalue', True), offvalue=kwargs.get('offvalue', False),
|
|
373
|
+
command=kwargs.get('func'))
|
|
374
|
+
elif widgetType == 'spin':
|
|
375
|
+
widget = TDWidget.Spin(parent, min=kwargs.get('min', 0), max=kwargs.get('max', 100),
|
|
376
|
+
textvariable=kwargs.get('textvariable'))
|
|
377
|
+
else: # default entry
|
|
378
|
+
widget = TDWidget.Entry(parent, textvariable=kwargs.get('textvariable'))
|
|
379
|
+
|
|
380
|
+
# Set common attributes
|
|
381
|
+
if 'font' in kwargs:
|
|
382
|
+
widget.font = kwargs['font']
|
|
383
|
+
if 'sticky' in kwargs:
|
|
384
|
+
widget.sticky = kwargs['sticky']
|
|
385
|
+
if 'width' in kwargs:
|
|
386
|
+
widget.width = kwargs['width']
|
|
387
|
+
if 'justify' in kwargs:
|
|
388
|
+
widget.justify = kwargs['justify']
|
|
389
|
+
if 'height' in kwargs:
|
|
390
|
+
widget.height = kwargs['height']
|
|
391
|
+
if 'state' in kwargs:
|
|
392
|
+
widget.state = kwargs['state']
|
|
393
|
+
if 'default' in kwargs:
|
|
394
|
+
widget.set(kwargs['default'])
|
|
395
|
+
|
|
396
|
+
# Place widget in grid
|
|
397
|
+
widget.grid(column=cpos, row=rpos, columnspan=cspan, rowspan=rspan)
|
|
398
|
+
|
|
399
|
+
# Handle tabs
|
|
400
|
+
if 'tab' in kwargs and hasattr(parent, 'add'):
|
|
401
|
+
parent.add(widget, text=kwargs['tab'])
|
|
402
|
+
|
|
403
|
+
return widget
|
|
404
|
+
|
|
405
|
+
def addWidgetFromArg(name, arg, parent):
|
|
406
|
+
"""
|
|
407
|
+
Creates a labeled TDWidget for an argument.
|
|
408
|
+
"""
|
|
409
|
+
widgets[name] = TDWidget.Frame(parent)
|
|
410
|
+
|
|
411
|
+
kwargs = arg['widget'].copy()
|
|
412
|
+
kwargs['textvariable'] = argVals[name]
|
|
413
|
+
widgetType = kwargs.pop('type', None)
|
|
414
|
+
|
|
415
|
+
# Handle special types
|
|
416
|
+
if widgetType == 'ticks':
|
|
417
|
+
kwargs['height'] = len(kwargs['values'])
|
|
418
|
+
argVals[name] = value = kwargs.pop('values', None)
|
|
419
|
+
kwargs['listvariable'] = argVals[name]
|
|
420
|
+
kwargs['selectmode'] = 'extended'
|
|
421
|
+
elif widgetType == 'numeric':
|
|
422
|
+
pass
|
|
423
|
+
elif widgetType == 'credits':
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
# Create label or button for non-check widgets
|
|
427
|
+
if widgetType == 'check':
|
|
428
|
+
kwargs['text'] = name
|
|
429
|
+
kwargs['columnspan'] = 3
|
|
430
|
+
else:
|
|
431
|
+
if widgetType == 'option':
|
|
432
|
+
label = TDWidget.Button(widgets[name], text=name, command=kwargs.pop('func', None))
|
|
433
|
+
widgetType = 'entry'
|
|
434
|
+
else:
|
|
435
|
+
label = TDWidget.Label(widgets[name], text=name)
|
|
436
|
+
label.grid(column=0, row=0)
|
|
437
|
+
kwargs['rpos'] = 0
|
|
438
|
+
kwargs['cpos'] = 1
|
|
439
|
+
kwargs['columnspan'] = 2
|
|
440
|
+
|
|
441
|
+
# Add the actual input widget
|
|
442
|
+
addWidget(widgetType, parent=widgets[name], **kwargs)
|
|
443
|
+
|
|
444
|
+
# Place the container frame
|
|
445
|
+
widgets[name].grid()
|
|
446
|
+
|
|
447
|
+
def makeWidgets(name, arg, sticky='ew', label=True, **kwargs):
|
|
448
|
+
"""
|
|
449
|
+
Creates and places a TDWidget for the given argument, handling all types.
|
|
450
|
+
"""
|
|
451
|
+
kwargs['sticky'] = sticky
|
|
452
|
+
kwargs['label'] = label
|
|
453
|
+
kwargs['change'] = updArgs
|
|
454
|
+
kwargs['tooltip'] = arg.get('help', '')
|
|
455
|
+
kwargs['colspan'] = 1 if arg == allArgs.get(name) else 9
|
|
456
|
+
|
|
457
|
+
widgetDef = arg['widget']
|
|
458
|
+
|
|
459
|
+
# Button
|
|
460
|
+
if widgetDef['type'] == 'button':
|
|
461
|
+
kwargs.pop('change', None)
|
|
462
|
+
kwargs.pop('label', None)
|
|
463
|
+
kwargs.pop('colspan', None)
|
|
464
|
+
TDWidget.Button(win, text=name, command=widgetDef.get('func'), **kwargs)
|
|
465
|
+
|
|
466
|
+
# Checkbutton
|
|
467
|
+
elif widgetDef['type'] == 'check':
|
|
468
|
+
TDWidget.Check(win, text=name, variable=argVals[name] or arg.get('default'), **kwargs)
|
|
469
|
+
|
|
470
|
+
# Spinbox
|
|
471
|
+
elif widgetDef['type'] == 'spin':
|
|
472
|
+
kwargs['item'] = argVals[name] or arg.get('default') or 0
|
|
473
|
+
TDWidget.Spin(win, from_=-widgetDef.get('range', 0), to=widgetDef.get('range', 0), **kwargs)
|
|
474
|
+
|
|
475
|
+
# Combobox / ticks
|
|
476
|
+
elif widgetDef['type'] == 'combo':
|
|
477
|
+
kwargs['sticky'] = 'w'
|
|
478
|
+
if widgetDef.get('sub'):
|
|
479
|
+
kwargs['kind'] = widgetDef['sub']
|
|
480
|
+
kwargs.pop('label', None)
|
|
481
|
+
|
|
482
|
+
combo = TDWidget.Combo(win, values=widgetDef.get('values', []), variable=argVals[name], **kwargs)
|
|
483
|
+
|
|
484
|
+
if not widgetDef.get('sub'):
|
|
485
|
+
argVals[name] = argVals[name] or arg.get('default') or '?'
|
|
486
|
+
combo.set(argVals[name])
|
|
487
|
+
else:
|
|
488
|
+
if argVals[name]:
|
|
489
|
+
for val, vval in argVals[name].items():
|
|
490
|
+
combo.setOption(val, values=vval, callFunction=False)
|
|
491
|
+
|
|
492
|
+
# Option button + entry
|
|
493
|
+
elif widgetDef['type'] == 'option':
|
|
494
|
+
kwargs.pop('change', None)
|
|
495
|
+
kwargs.pop('label', None)
|
|
496
|
+
kwargs.pop('colspan', None)
|
|
497
|
+
TDWidget.Button(win, text='optionButton', command=optionsWin, name='--option', **kwargs)
|
|
498
|
+
|
|
499
|
+
kwargs['sticky'] = sticky
|
|
500
|
+
kwargs['change'] = updArgs
|
|
501
|
+
TDWidget.Entry(win, textvariable=argVals[name] or arg.get('default'), row='p', column=1, colspan=9, **kwargs)
|
|
502
|
+
|
|
503
|
+
# Entry / numeric / credits
|
|
504
|
+
elif widgetDef['type'] == 'entry':
|
|
505
|
+
if widgetDef.get('sub') == 'credits':
|
|
506
|
+
# TODO: Implement credits type handling
|
|
507
|
+
pass
|
|
508
|
+
elif widgetDef.get('sub'):
|
|
509
|
+
kwargs['kind'] = 'numeric'
|
|
510
|
+
|
|
511
|
+
TDWidget.Entry(win, textvariable=argVals[name] or arg.get('default'), **kwargs)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def updateCommandBox(args=None):
|
|
515
|
+
"""
|
|
516
|
+
Updates the argument panes when the selected command is changed using TDWidget.
|
|
517
|
+
"""
|
|
518
|
+
cmd = widgets['Command'].get()
|
|
519
|
+
|
|
520
|
+
# Update help pane
|
|
521
|
+
helpPane = widgets['helpPane']
|
|
522
|
+
helpPane.config(state='normal')
|
|
523
|
+
helpPane.delete(0.0, 'end')
|
|
524
|
+
helpPane.insert(0.0, cmdHelp.get(cmd, ''))
|
|
525
|
+
helpPane.config(state='disabled')
|
|
526
|
+
|
|
527
|
+
# Hide all currently displayed widgets in 'req' and 'opt'
|
|
528
|
+
for child in widgets['req'].winfo_children():
|
|
529
|
+
child.grid_forget()
|
|
530
|
+
for child in widgets['opt'].winfo_children():
|
|
531
|
+
child.grid_forget()
|
|
532
|
+
|
|
533
|
+
if cmd == 'help':
|
|
534
|
+
return
|
|
535
|
+
|
|
536
|
+
# Prepend for 'station' command to avoid name conflicts
|
|
537
|
+
prepend = f"{cmd}~" if cmd == 'station' else ''
|
|
538
|
+
|
|
539
|
+
# Handle required arguments
|
|
540
|
+
if allArgs[cmd]['req']:
|
|
541
|
+
if 'Required:' not in widgets:
|
|
542
|
+
widgets['Required:'] = TDWidget.Label(widgets['req'], text='Required:', sticky='nw')
|
|
543
|
+
else:
|
|
544
|
+
widgets['Required:'].grid()
|
|
545
|
+
|
|
546
|
+
for i, key in enumerate(allArgs[cmd]['req'], start=1):
|
|
547
|
+
fullKey = prepend + key
|
|
548
|
+
if fullKey not in widgets:
|
|
549
|
+
addWidgetFromArg(fullKey, allArgs[cmd]['req'][key], widgets['req'])
|
|
550
|
+
else:
|
|
551
|
+
widgets[fullKey].grid(column=0, row=i)
|
|
552
|
+
|
|
553
|
+
# Handle optional arguments
|
|
554
|
+
if allArgs[cmd]['opt']:
|
|
555
|
+
if 'Optional:' not in widgets:
|
|
556
|
+
widgets['Optional:'] = TDWidget.Label(widgets['opt'], text='Optional:', sticky='nw')
|
|
557
|
+
else:
|
|
558
|
+
widgets['Optional:'].grid()
|
|
559
|
+
|
|
560
|
+
for i, key in enumerate(allArgs[cmd]['opt'], start=1):
|
|
561
|
+
fullKey = prepend + key
|
|
562
|
+
if fullKey not in widgets:
|
|
563
|
+
addWidgetFromArg(fullKey, allArgs[cmd]['opt'][key], widgets['opt'])
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
# Setup the CLI interface and build the main window
|
|
569
|
+
def main(argv=None):
|
|
570
|
+
class IORedirector:
|
|
571
|
+
def __init__(self, TEXT_INFO):
|
|
572
|
+
self.TEXT_INFO = TEXT_INFO
|
|
573
|
+
|
|
574
|
+
class StdoutRedirector(IORedirector):
|
|
575
|
+
def write(self, string):
|
|
576
|
+
current = self.TEXT_INFO.cget('text').rsplit('\r', 1)[0]
|
|
577
|
+
self.TEXT_INFO.config(text=current + string)
|
|
578
|
+
|
|
579
|
+
def flush(self):
|
|
580
|
+
sys.__stdout__.flush()
|
|
581
|
+
|
|
582
|
+
def setOpts():
|
|
583
|
+
"""
|
|
584
|
+
Sets the main window options entry to the checked values in the options window.
|
|
585
|
+
"""
|
|
586
|
+
sw = widgets.get("Plugin Options") # TDWidget window reference
|
|
587
|
+
plug = argVals.get('--plug')
|
|
588
|
+
plugOpts = allArgs['import']['opt']['--option']['options'].get(plug, {})
|
|
589
|
+
argStr = ''
|
|
590
|
+
|
|
591
|
+
if plugOpts:
|
|
592
|
+
for option, val in plugOpts.items():
|
|
593
|
+
w = sw.get(option) # Retrieve the TDWidget by name
|
|
594
|
+
if '=' in val:
|
|
595
|
+
if w and isinstance(w, TDWidget.Entry):
|
|
596
|
+
argStr += f"{option}={w.get()}," # get() for TDWidget entry
|
|
597
|
+
else:
|
|
598
|
+
if w and isinstance(w, TDWidget.Checkbutton) and w.get():
|
|
599
|
+
argStr += f"{option},"
|
|
600
|
+
|
|
601
|
+
argStr = argStr.rstrip(',')
|
|
602
|
+
if '--option' in widgets:
|
|
603
|
+
widgets['--option'].set(argStr) # Update main window entry
|
|
604
|
+
|
|
605
|
+
if sw:
|
|
606
|
+
sw.hide()
|
|
607
|
+
|
|
608
|
+
def optionsWin():
|
|
609
|
+
"""
|
|
610
|
+
Opens a window listing all of the options for the currently selected plugin.
|
|
611
|
+
"""
|
|
612
|
+
# Create a new TDWidget window
|
|
613
|
+
sw = TDWidget.SubWindow(title="Plugin Options", modal=True)
|
|
614
|
+
widgets["Plugin Options"] = sw # store reference for later access
|
|
615
|
+
sw.clear() # empty the container
|
|
616
|
+
|
|
617
|
+
optDict = {}
|
|
618
|
+
current_opts = argVals.get('--option')
|
|
619
|
+
if current_opts:
|
|
620
|
+
for option in current_opts.split(','):
|
|
621
|
+
if '=' in option:
|
|
622
|
+
key, val = option.split('=')
|
|
623
|
+
optDict[key] = val
|
|
624
|
+
elif option != '':
|
|
625
|
+
optDict[option] = True
|
|
626
|
+
|
|
627
|
+
plug = argVals.get('--plug')
|
|
628
|
+
if not plug:
|
|
629
|
+
TDWidget.Label(sw, text="No import plugin chosen.", sticky='ew', colspan=10)
|
|
630
|
+
else:
|
|
631
|
+
plugOpts = allArgs['import']['opt']['--option']['options'].get(plug, {})
|
|
632
|
+
for option, tooltip in plugOpts.items():
|
|
633
|
+
if '=' in tooltip:
|
|
634
|
+
TDWidget.Entry(
|
|
635
|
+
sw,
|
|
636
|
+
name=option,
|
|
637
|
+
textvariable=optDict.get(option, ''),
|
|
638
|
+
label=True,
|
|
639
|
+
sticky='ew',
|
|
640
|
+
colspan=10,
|
|
641
|
+
tooltip=tooltip
|
|
642
|
+
)
|
|
643
|
+
else:
|
|
644
|
+
TDWidget.Checkbutton(
|
|
645
|
+
sw,
|
|
646
|
+
name=option,
|
|
647
|
+
textvariable=optDict.get(option, False),
|
|
648
|
+
sticky='ew',
|
|
649
|
+
colspan=10,
|
|
650
|
+
tooltip=tooltip
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
# Buttons
|
|
654
|
+
TDWidget.Button(sw, text="Done", func=setOpts, column=8)
|
|
655
|
+
TDWidget.Button(sw, text="Cancel", func=sw.hide, row='p', column=9)
|
|
656
|
+
|
|
657
|
+
sw.show()
|
|
658
|
+
|
|
659
|
+
def updArgs(name):
|
|
660
|
+
"""
|
|
661
|
+
Updates the value of argVals[name] when the linked widget's value is changed in the window.
|
|
662
|
+
"""
|
|
663
|
+
|
|
664
|
+
def getWidget(name):
|
|
665
|
+
"""
|
|
666
|
+
Returns the TDWidget instance for the given argument name.
|
|
667
|
+
"""
|
|
668
|
+
return widgets.get(name)
|
|
669
|
+
|
|
670
|
+
# Clear dependent options if the plugin changed
|
|
671
|
+
if name == '--plug' and argVals.get(name) != (w := getWidget(name)).get():
|
|
672
|
+
option_widget = widgets.get('--option')
|
|
673
|
+
if option_widget:
|
|
674
|
+
option_widget.set('')
|
|
675
|
+
|
|
676
|
+
# Update the stored value
|
|
677
|
+
w = getWidget(name)
|
|
678
|
+
if w:
|
|
679
|
+
argVals[name] = w.get()
|
|
680
|
+
else:
|
|
681
|
+
argVals[name] = None
|
|
682
|
+
|
|
683
|
+
# Determine excluded arguments for this argument
|
|
684
|
+
if allArgs.get(name):
|
|
685
|
+
excluded = allArgs[name].get('excludes', [])
|
|
686
|
+
argBase = allArgs
|
|
687
|
+
else:
|
|
688
|
+
try:
|
|
689
|
+
cmd = widgets['Command'].get()
|
|
690
|
+
excluded = allArgs[cmd]['opt'][name].get('excludes', [])
|
|
691
|
+
argBase = allArgs[cmd]['opt']
|
|
692
|
+
except KeyError:
|
|
693
|
+
excluded = []
|
|
694
|
+
|
|
695
|
+
# Reset any arguments excluded by this one if it has a value
|
|
696
|
+
if excluded and argVals.get(name):
|
|
697
|
+
for exclude in excluded:
|
|
698
|
+
w = widgets.get(exclude)
|
|
699
|
+
if not w:
|
|
700
|
+
continue
|
|
701
|
+
widgetType = argBase[exclude]['widget']['type']
|
|
702
|
+
|
|
703
|
+
def updCmd():
|
|
704
|
+
"""
|
|
705
|
+
Updates the argument panes when the selected command is changed.
|
|
706
|
+
"""
|
|
707
|
+
cmd = widgets['Command'].get()
|
|
708
|
+
|
|
709
|
+
# Update help text
|
|
710
|
+
help_widget = widgets.get('helpPane')
|
|
711
|
+
if help_widget:
|
|
712
|
+
help_widget.set(cmdHelp.get(cmd, ''))
|
|
713
|
+
|
|
714
|
+
# Clear current argument frames
|
|
715
|
+
for child in widgets['req'].winfo_children():
|
|
716
|
+
child.grid_forget()
|
|
717
|
+
for child in widgets['opt'].winfo_children():
|
|
718
|
+
child.grid_forget()
|
|
719
|
+
|
|
720
|
+
# Nothing more to do for 'help' command
|
|
721
|
+
if cmd == 'help':
|
|
722
|
+
return
|
|
723
|
+
|
|
724
|
+
# Show required arguments
|
|
725
|
+
req_frame = widgets['req']
|
|
726
|
+
if allArgs[cmd]['req']:
|
|
727
|
+
if 'Required:' not in widgets:
|
|
728
|
+
widgets['Required:'] = TDWidget.Label(req_frame, text='Required:', sticky='w')
|
|
729
|
+
widgets['Required:'].grid(column=0, row=0, sticky='w')
|
|
730
|
+
else:
|
|
731
|
+
widgets['Required:'].grid()
|
|
732
|
+
|
|
733
|
+
i = 1
|
|
734
|
+
for key, arg in allArgs[cmd]['req'].items():
|
|
735
|
+
if key not in widgets:
|
|
736
|
+
addWidgetFromArg(key, arg, req_frame)
|
|
737
|
+
else:
|
|
738
|
+
widgets[key].grid(column=0, row=i)
|
|
739
|
+
i += 1
|
|
740
|
+
|
|
741
|
+
# Show optional arguments
|
|
742
|
+
opt_frame = _
|
|
743
|
+
|
|
744
|
+
def runTD():
|
|
745
|
+
"""
|
|
746
|
+
Executes the TD command selected in the GUI using TDWidget.
|
|
747
|
+
"""
|
|
748
|
+
|
|
749
|
+
from . import tradeexcept
|
|
750
|
+
|
|
751
|
+
def getVals(arg, argBase):
|
|
752
|
+
curArg = argVals.get(arg)
|
|
753
|
+
vals = []
|
|
754
|
+
|
|
755
|
+
# Skip default or empty values
|
|
756
|
+
if curArg is not None and curArg != argBase[arg].get('default'):
|
|
757
|
+
if arg not in ['--detail', '--debug', '--quiet']:
|
|
758
|
+
vals.append(str(arg))
|
|
759
|
+
|
|
760
|
+
widget_info = argBase[arg]['widget']
|
|
761
|
+
wtype = widget_info.get('type')
|
|
762
|
+
sub = widget_info.get('sub')
|
|
763
|
+
|
|
764
|
+
if wtype == 'check':
|
|
765
|
+
# Checkbuttons already handled by presence in argVals
|
|
766
|
+
return vals
|
|
767
|
+
|
|
768
|
+
if sub == 'ticks':
|
|
769
|
+
choices = ''
|
|
770
|
+
for choice, selected in curArg.items():
|
|
771
|
+
if selected:
|
|
772
|
+
choices += choice
|
|
773
|
+
vals.append(choices)
|
|
774
|
+
return vals
|
|
775
|
+
|
|
776
|
+
if sub == 'credits':
|
|
777
|
+
# TODO: Handle 'credits' type
|
|
778
|
+
pass
|
|
779
|
+
|
|
780
|
+
# Default behavior: append the current value
|
|
781
|
+
vals.append(str(curArg))
|
|
782
|
+
|
|
783
|
+
if not vals:
|
|
784
|
+
return None
|
|
785
|
+
|
|
786
|
+
return vals
|
|
787
|
+
|
|
788
|
+
def runTrade():
|
|
789
|
+
# Disable the Run button while executing
|
|
790
|
+
run_btn = widgets.get('Run')
|
|
791
|
+
if run_btn:
|
|
792
|
+
run_btn.disable()
|
|
793
|
+
|
|
794
|
+
# Redirect stdout to the output Text widget
|
|
795
|
+
output_widget = widgets.get('outputText')
|
|
796
|
+
oldout = sys.stdout
|
|
797
|
+
sys.stdout = StdoutRedirector(output_widget)
|
|
798
|
+
print('TD command: "' + ' '.join(argv) + '"')
|
|
799
|
+
|
|
800
|
+
try:
|
|
801
|
+
try:
|
|
802
|
+
try:
|
|
803
|
+
if "CPROF" in os.environ:
|
|
804
|
+
import cProfile
|
|
805
|
+
cProfile.run("trade(argv)")
|
|
806
|
+
else:
|
|
807
|
+
trade(argv)
|
|
808
|
+
except PluginException as e:
|
|
809
|
+
print(f"PLUGIN ERROR: {e}")
|
|
810
|
+
if 'EXCEPTIONS' in os.environ:
|
|
811
|
+
raise e
|
|
812
|
+
sys.exit(1)
|
|
813
|
+
except tradeexcept.TradeException as e:
|
|
814
|
+
print(f"{argv[0]}: {e}")
|
|
815
|
+
if 'EXCEPTIONS' in os.environ:
|
|
816
|
+
raise e
|
|
817
|
+
sys.exit(1)
|
|
818
|
+
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
819
|
+
print("-----------------------------------------------------------")
|
|
820
|
+
print("ERROR: Unexpected unicode error in the wild!")
|
|
821
|
+
print(traceback.format_exc())
|
|
822
|
+
print(
|
|
823
|
+
"Please report this bug (http://kfs.org/td/issues). "
|
|
824
|
+
"You may be able to work around it using '-q'. "
|
|
825
|
+
"Windows users can try 'chcp.com 65001' to enable UTF-8."
|
|
826
|
+
)
|
|
827
|
+
except SystemExit as e:
|
|
828
|
+
print(e)
|
|
829
|
+
|
|
830
|
+
print("Execution complete.")
|
|
831
|
+
# Restore stdout
|
|
832
|
+
sys.stdout = oldout
|
|
833
|
+
# Re-enable Run button
|
|
834
|
+
if run_btn:
|
|
835
|
+
run_btn.enable()
|
|
836
|
+
|
|
837
|
+
# Switch to the Output tab
|
|
838
|
+
tab_frame = widgets.get('tabFrame')
|
|
839
|
+
if tab_frame:
|
|
840
|
+
tab_frame.select_tab('Output')
|
|
841
|
+
|
|
842
|
+
# Build the argv list
|
|
843
|
+
cmd = widgets['Command'].get()
|
|
844
|
+
argv = ['trade', cmd]
|
|
845
|
+
|
|
846
|
+
if cmd != 'help':
|
|
847
|
+
# Required args
|
|
848
|
+
for arg in allArgs[cmd]['req']:
|
|
849
|
+
result = getVals(arg, allArgs[cmd]['req'])
|
|
850
|
+
if result:
|
|
851
|
+
argv += result if '-' in result[0] else result[1:]
|
|
852
|
+
|
|
853
|
+
# Optional args
|
|
854
|
+
for arg in allArgs[cmd]['opt']:
|
|
855
|
+
result = getVals(arg, allArgs[cmd]['opt'])
|
|
856
|
+
if result:
|
|
857
|
+
argv += result
|
|
858
|
+
|
|
859
|
+
# Global args
|
|
860
|
+
for arg in allArgs:
|
|
861
|
+
if arg in Commands:
|
|
862
|
+
continue
|
|
863
|
+
result = getVals(arg, allArgs)
|
|
864
|
+
if result:
|
|
865
|
+
argv += result
|
|
866
|
+
|
|
867
|
+
# Clear previous output
|
|
868
|
+
if output_widget:
|
|
869
|
+
output_widget.set('')
|
|
870
|
+
|
|
871
|
+
# Run trade in a separate thread
|
|
872
|
+
threading.Thread(target=runTrade, name="TDThread", daemon=True).start()
|
|
873
|
+
|
|
874
|
+
def makeWidgets(name, arg, sticky='ew', label=True, **kwargs):
|
|
875
|
+
"""
|
|
876
|
+
Creates and places a widget for the given argument using TDWidget.
|
|
877
|
+
"""
|
|
878
|
+
kwargs['sticky'] = sticky
|
|
879
|
+
kwargs['label'] = label
|
|
880
|
+
kwargs['change'] = updArgs
|
|
881
|
+
kwargs['tooltip'] = arg.get('help', '')
|
|
882
|
+
kwargs['colspan'] = 1 if arg == allArgs.get(name) else 9
|
|
883
|
+
|
|
884
|
+
widget_info = arg['widget']
|
|
885
|
+
wtype = widget_info['type']
|
|
886
|
+
|
|
887
|
+
if wtype == 'button':
|
|
888
|
+
kwargs.pop('change', None)
|
|
889
|
+
kwargs.pop('label', None)
|
|
890
|
+
kwargs.pop('colspan', None)
|
|
891
|
+
TDWidget(
|
|
892
|
+
name=name,
|
|
893
|
+
widget_type='button',
|
|
894
|
+
command=widget_info.get('func'),
|
|
895
|
+
**kwargs
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
elif wtype == 'check':
|
|
899
|
+
TDWidget(
|
|
900
|
+
name=name,
|
|
901
|
+
widget_type='check',
|
|
902
|
+
values=argVals.get(name, arg.get('default', False)),
|
|
903
|
+
text=name,
|
|
904
|
+
**kwargs
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
elif wtype == 'spin':
|
|
908
|
+
TDWidget(
|
|
909
|
+
name=name,
|
|
910
|
+
widget_type='spin',
|
|
911
|
+
values=argVals.get(name, arg.get('default', 0) or 0),
|
|
912
|
+
min_value=widget_info.get('min', 0),
|
|
913
|
+
max_value=widget_info.get('max', 100),
|
|
914
|
+
**kwargs
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
elif wtype == 'combo':
|
|
918
|
+
kwargs['sticky'] = 'w'
|
|
919
|
+
if widget_info.get('sub'):
|
|
920
|
+
kwargs['kind'] = widget_info['sub']
|
|
921
|
+
kwargs.pop('label', None)
|
|
922
|
+
|
|
923
|
+
combo = TDWidget(
|
|
924
|
+
name=name,
|
|
925
|
+
widget_type='combo',
|
|
926
|
+
values=widget_info.get('values', []),
|
|
927
|
+
**kwargs
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
if not widget_info.get('sub'):
|
|
931
|
+
if not isinstance(argVals.get(name), str):
|
|
932
|
+
argVals[name] = None
|
|
933
|
+
default_val = '?' if arg.get('choices') else ''
|
|
934
|
+
combo.set(argVals.get(name) or arg.get('default') or default_val, callFunction=False)
|
|
935
|
+
else:
|
|
936
|
+
if isinstance(argVals.get(name), str):
|
|
937
|
+
argVals[name] = None
|
|
938
|
+
if argVals.get(name):
|
|
939
|
+
for val, vval in argVals[name].items():
|
|
940
|
+
combo.set_option(val, values=vval, callFunction=False)
|
|
941
|
+
|
|
942
|
+
elif wtype == 'option':
|
|
943
|
+
kwargs.pop('change', None)
|
|
944
|
+
kwargs.pop('label', None)
|
|
945
|
+
kwargs.pop('colspan', None)
|
|
946
|
+
|
|
947
|
+
# Option button opens the plugin options window
|
|
948
|
+
TDWidget(
|
|
949
|
+
name='optionButton',
|
|
950
|
+
widget_type='button',
|
|
951
|
+
command=optionsWin,
|
|
952
|
+
option_name='--option',
|
|
953
|
+
**kwargs
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
kwargs['sticky'] = sticky
|
|
957
|
+
kwargs['change'] = updArgs
|
|
958
|
+
TDWidget(
|
|
959
|
+
name=name,
|
|
960
|
+
widget_type='entry',
|
|
961
|
+
values=argVals.get(name) or arg.get('default', ''),
|
|
962
|
+
row='p',
|
|
963
|
+
column=1,
|
|
964
|
+
colspan=9,
|
|
965
|
+
**kwargs
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
elif wtype == 'entry':
|
|
969
|
+
if widget_info.get('sub') == 'credits':
|
|
970
|
+
# TODO: Handle 'credits' type
|
|
971
|
+
pass
|
|
972
|
+
elif widget_info.get('sub'):
|
|
973
|
+
kwargs['kind'] = 'numeric'
|
|
974
|
+
|
|
975
|
+
TDWidget(
|
|
976
|
+
name=name,
|
|
977
|
+
widget_type='entry',
|
|
978
|
+
values=argVals.get(name) or arg.get('default', ''),
|
|
979
|
+
**kwargs
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
sys.argv = ['trade']
|
|
983
|
+
if not argv:
|
|
984
|
+
argv = sys.argv
|
|
985
|
+
if sys.hexversion < 0x30813F0:
|
|
986
|
+
raise SystemExit(
|
|
987
|
+
"Sorry: TradeDangerous requires Python 3.8.19 or higher.\n"
|
|
988
|
+
"For assistance, see:\n"
|
|
989
|
+
"\tBug Tracker: https://github.com/eyeonus/Trade-Dangerous/issues\n"
|
|
990
|
+
"\tDocumentation: https://github.com/eyeonus/Trade-Dangerous/wiki\n"
|
|
991
|
+
"\tEDForum Thread: https://forums.frontier.co.uk/showthread.php/441509\n"
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
buildArgDicts()
|
|
996
|
+
|
|
997
|
+
# --- Root window ---
|
|
998
|
+
main_win = TDWidget(name='root', widget_type='frame')
|
|
999
|
+
main_win.widget.master.title('Trade Dangerous GUI (Beta), TD v.%s' % (__version__,))
|
|
1000
|
+
|
|
1001
|
+
# --- Command Combo ---
|
|
1002
|
+
widgets['Command'] = TDWidget(
|
|
1003
|
+
name='Command', widget_type='combo', parent=main_win,
|
|
1004
|
+
values=Commands, width=10, row=0, column=0, columnspan=5, sticky='ew',
|
|
1005
|
+
command=updCmd
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
# --- Request / Optional Scroll Frames ---
|
|
1009
|
+
widgets['req'] = TDWidget('req', 'frame', parent=main_win, row=1, column=0, columnspan=10, sticky='nsew')
|
|
1010
|
+
widgets['req'].widget.config(width=200, height=75)
|
|
1011
|
+
widgets['opt'] = TDWidget('opt', 'frame', parent=main_win, row=2, column=0, columnspan=10, sticky='nsew')
|
|
1012
|
+
widgets['opt'].widget.config(width=200, height=345)
|
|
1013
|
+
|
|
1014
|
+
# --- Tabbed Frame ---
|
|
1015
|
+
tabFrame = TDWidget('tabFrame', 'notebook', parent=main_win, row=1, column=10, rowspan=2, columnspan=40, sticky='nsew')
|
|
1016
|
+
|
|
1017
|
+
# Help tab
|
|
1018
|
+
help_tab = TDWidget('helpTab', 'frame', parent=tabFrame)
|
|
1019
|
+
tabFrame.widget.add(help_tab.widget, text='Help')
|
|
1020
|
+
widgets['helpPane'] = TDWidget('helpPane', 'scrolledtext', parent=help_tab, row=0, column=0, width=80, height=25)
|
|
1021
|
+
widgets['helpPane'].set(cmdHelp['help'])
|
|
1022
|
+
widgets['helpPane'].widget.config(state='disabled', width=80)
|
|
1023
|
+
|
|
1024
|
+
# Output tab
|
|
1025
|
+
output_tab = TDWidget('outputTab', 'frame', parent=tabFrame)
|
|
1026
|
+
tabFrame.widget.add(output_tab.widget, text='Output')
|
|
1027
|
+
widgets['outPane'] = TDWidget('outPane', 'scrolledtext', parent=output_tab, row=0, column=0, width=80, height=25)
|
|
1028
|
+
widgets['outPane'].set('')
|
|
1029
|
+
widgets['outPane'].widget.config(state='disabled', width=80)
|
|
1030
|
+
|
|
1031
|
+
# --- Option Widgets ---
|
|
1032
|
+
makeWidgets('--link-ly', allArgs['--link-ly'], sticky='w', width=4, row=3, column=2)
|
|
1033
|
+
makeWidgets('--quiet', allArgs['--quiet'], sticky='e', disabled=':', width=1, row=3, column=46)
|
|
1034
|
+
makeWidgets('--detail', allArgs['--detail'], sticky='e', disabled=':', width=1, row=3, column=47)
|
|
1035
|
+
makeWidgets('--debug', allArgs['--debug'], sticky='e', disabled=':', width=1, row=3, column=48)
|
|
1036
|
+
|
|
1037
|
+
# --- Run Button ---
|
|
1038
|
+
TDWidget('Run', 'button', parent=main_win, text='Run', command=runTD, row=3, column=49, sticky='w')
|
|
1039
|
+
|
|
1040
|
+
# --- CWD ---
|
|
1041
|
+
makeWidgets('--cwd', allArgs['--cwd'], width=4, row=4, column=0)
|
|
1042
|
+
cwd_scroll = TDWidget('CWD', 'scrolledtext', parent=main_win, row=4, column=1, columnspan=49, width=70, height=1)
|
|
1043
|
+
cwd_scroll.set(argVals['--cwd'])
|
|
1044
|
+
cwd_scroll.widget.config(state='disabled')
|
|
1045
|
+
widgets['cwd'] = TDWidget('cwd', 'label', parent=main_win, text=argVals['--cwd'], sticky='w', row=4, column=1)
|
|
1046
|
+
|
|
1047
|
+
# --- DB ---
|
|
1048
|
+
makeWidgets('--db', allArgs['--db'], width=4, row=5, column=0)
|
|
1049
|
+
db_scroll = TDWidget('DB', 'scrolledtext', parent=main_win, row=5, column=1, columnspan=49, width=70, height=1)
|
|
1050
|
+
db_scroll.set(argVals['--db'])
|
|
1051
|
+
db_scroll.widget.config(state='disabled')
|
|
1052
|
+
widgets['db'] = TDWidget('db', 'label', parent=main_win, text=argVals['--db'], sticky='w', row=5, column=1)
|
|
1053
|
+
|
|
1054
|
+
# --- Configure row/column stretching for proper layout ---
|
|
1055
|
+
for i in range(50):
|
|
1056
|
+
main_win.widget.columnconfigure(i, weight=1)
|
|
1057
|
+
for i in range(6):
|
|
1058
|
+
main_win.widget.rowconfigure(i, weight=1)
|
|
1059
|
+
|
|
1060
|
+
# --- Show window ---
|
|
1061
|
+
main_win.widget.mainloop()
|
|
1062
|
+
|
|
1063
|
+
# with gui('Trade Dangerous GUI (Beta), TD v.%s' % (__version__,), inPadding=1) as win:
|
|
1064
|
+
# win.setFont(size=8, family='Courier')
|
|
1065
|
+
# win.combo('Command', Commands, change=updCmd, tooltip='Trade Dangerous command to run.',
|
|
1066
|
+
# stretch='none', sticky='ew', width=10, row=0, column=0, colspan=5)
|
|
1067
|
+
# with win.scrollPane('req', disabled='horizontal', row=1, column=0, colspan=10) as pane:
|
|
1068
|
+
# pane.configure(width=200, height=75)
|
|
1069
|
+
#
|
|
1070
|
+
# with win.scrollPane('opt', disabled='horizontal', row=2, column=0, colspan=10) as pane:
|
|
1071
|
+
# pane.configure(width=200, height=345)
|
|
1072
|
+
#
|
|
1073
|
+
# with win.tabbedFrame('tabFrame', disabled='horizontal', row=1, column=10, rowspan=2, colspan=40) as tabFrame:
|
|
1074
|
+
# with win.tab('Help'):
|
|
1075
|
+
# with win.scrollPane('helpPane', disabled='horizontal') as pane:
|
|
1076
|
+
# pane.configure(width=560, height=420)
|
|
1077
|
+
# win.message('helpText', cmdHelp['help'])
|
|
1078
|
+
# win.widgetManager.get(WIDGET_NAMES.Message, 'helpText').config(width=560)
|
|
1079
|
+
#
|
|
1080
|
+
# with win.tab('Output'):
|
|
1081
|
+
# with win.scrollPane('outPane', disabled='horizontal') as pane:
|
|
1082
|
+
# pane.configure(width=560, height=420)
|
|
1083
|
+
# win.message('outputText', '')
|
|
1084
|
+
# win.widgetManager.get(WIDGET_NAMES.Message, 'outputText').config(width=560)
|
|
1085
|
+
#
|
|
1086
|
+
# makeWidgets('--link-ly', allArgs['--link-ly'], sticky='w', width=4, row=3, column=2)
|
|
1087
|
+
#
|
|
1088
|
+
# makeWidgets('--quiet', allArgs['--quiet'], sticky='e', disabled=':', width=1, row=3, column=46)
|
|
1089
|
+
#
|
|
1090
|
+
# makeWidgets('--detail', allArgs['--detail'], sticky='e', disabled=':', width=1, row=3, column=47)
|
|
1091
|
+
#
|
|
1092
|
+
# makeWidgets('--debug', allArgs['--debug'], sticky='e', disabled=':', width=1, row=3, column=48)
|
|
1093
|
+
#
|
|
1094
|
+
# win.button('Run', runTD, tooltip='Execute the selected command.',
|
|
1095
|
+
# sticky='w', row=3, column=49)
|
|
1096
|
+
#
|
|
1097
|
+
# makeWidgets('--cwd', allArgs['--cwd'], width=4, row=4, column=0)
|
|
1098
|
+
# with win.scrollPane('CWD', disabled='vertical', row=4, column=1, colspan=49) as pane:
|
|
1099
|
+
# pane.configure(width=500, height=20)
|
|
1100
|
+
# widgets['cwd'] = win.label('cwd', argVals['--cwd'], sticky='w')
|
|
1101
|
+
#
|
|
1102
|
+
# makeWidgets('--db', allArgs['--db'], width=4, row=5, column=0)
|
|
1103
|
+
# with win.scrollPane('DB', disabled='vertical', row=5, column=1, colspan=49) as pane:
|
|
1104
|
+
# pane.configure(width=500, height=20)
|
|
1105
|
+
# widgets['db'] = win.label('db', argVals['--db'], sticky='w')
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def trade(argv):
|
|
1109
|
+
"""
|
|
1110
|
+
This method represents the trade command.
|
|
1111
|
+
"""
|
|
1112
|
+
cmdIndex = commands.CommandIndex()
|
|
1113
|
+
cmdenv = cmdIndex.parse(argv)
|
|
1114
|
+
|
|
1115
|
+
tdb = tradedb.TradeDB(cmdenv, load = cmdenv.wantsTradeDB)
|
|
1116
|
+
if cmdenv.usesTradeData:
|
|
1117
|
+
tsc = tdb.tradingStationCount
|
|
1118
|
+
if tsc == 0:
|
|
1119
|
+
raise exceptions.NoDataError(
|
|
1120
|
+
"There is no trading data for ANY station in "
|
|
1121
|
+
"the local database. Please enter or import "
|
|
1122
|
+
"price data."
|
|
1123
|
+
)
|
|
1124
|
+
if tsc == 1:
|
|
1125
|
+
raise exceptions.NoDataError(
|
|
1126
|
+
"The local database only contains trading data "
|
|
1127
|
+
"for one station. Please enter or import data "
|
|
1128
|
+
"for additional stations."
|
|
1129
|
+
)
|
|
1130
|
+
if tsc < 8:
|
|
1131
|
+
cmdenv.NOTE(
|
|
1132
|
+
"The local database only contains trading data "
|
|
1133
|
+
"for {} stations. Please enter or import data "
|
|
1134
|
+
"for additional stations.".format(
|
|
1135
|
+
tsc
|
|
1136
|
+
)
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
try:
|
|
1140
|
+
results = cmdenv.run(tdb)
|
|
1141
|
+
finally:
|
|
1142
|
+
# always close tdb
|
|
1143
|
+
tdb.close()
|
|
1144
|
+
|
|
1145
|
+
if results:
|
|
1146
|
+
results.render()
|