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.
Files changed (87) hide show
  1. py.typed +1 -0
  2. trade.py +49 -0
  3. tradedangerous/__init__.py +43 -0
  4. tradedangerous/cache.py +1381 -0
  5. tradedangerous/cli.py +136 -0
  6. tradedangerous/commands/TEMPLATE.py +74 -0
  7. tradedangerous/commands/__init__.py +244 -0
  8. tradedangerous/commands/buildcache_cmd.py +102 -0
  9. tradedangerous/commands/buy_cmd.py +427 -0
  10. tradedangerous/commands/commandenv.py +372 -0
  11. tradedangerous/commands/exceptions.py +94 -0
  12. tradedangerous/commands/export_cmd.py +150 -0
  13. tradedangerous/commands/import_cmd.py +222 -0
  14. tradedangerous/commands/local_cmd.py +243 -0
  15. tradedangerous/commands/market_cmd.py +207 -0
  16. tradedangerous/commands/nav_cmd.py +252 -0
  17. tradedangerous/commands/olddata_cmd.py +270 -0
  18. tradedangerous/commands/parsing.py +221 -0
  19. tradedangerous/commands/rares_cmd.py +298 -0
  20. tradedangerous/commands/run_cmd.py +1521 -0
  21. tradedangerous/commands/sell_cmd.py +262 -0
  22. tradedangerous/commands/shipvendor_cmd.py +60 -0
  23. tradedangerous/commands/station_cmd.py +68 -0
  24. tradedangerous/commands/trade_cmd.py +181 -0
  25. tradedangerous/commands/update_cmd.py +67 -0
  26. tradedangerous/corrections.py +55 -0
  27. tradedangerous/csvexport.py +234 -0
  28. tradedangerous/db/__init__.py +27 -0
  29. tradedangerous/db/adapter.py +192 -0
  30. tradedangerous/db/config.py +107 -0
  31. tradedangerous/db/engine.py +259 -0
  32. tradedangerous/db/lifecycle.py +332 -0
  33. tradedangerous/db/locks.py +208 -0
  34. tradedangerous/db/orm_models.py +500 -0
  35. tradedangerous/db/paths.py +113 -0
  36. tradedangerous/db/utils.py +661 -0
  37. tradedangerous/edscupdate.py +565 -0
  38. tradedangerous/edsmupdate.py +474 -0
  39. tradedangerous/formatting.py +210 -0
  40. tradedangerous/fs.py +156 -0
  41. tradedangerous/gui.py +1146 -0
  42. tradedangerous/mapping.py +133 -0
  43. tradedangerous/mfd/__init__.py +103 -0
  44. tradedangerous/mfd/saitek/__init__.py +3 -0
  45. tradedangerous/mfd/saitek/directoutput.py +678 -0
  46. tradedangerous/mfd/saitek/x52pro.py +195 -0
  47. tradedangerous/misc/checkpricebounds.py +287 -0
  48. tradedangerous/misc/clipboard.py +49 -0
  49. tradedangerous/misc/coord64.py +83 -0
  50. tradedangerous/misc/csvdialect.py +57 -0
  51. tradedangerous/misc/derp-sentinel.py +35 -0
  52. tradedangerous/misc/diff-system-csvs.py +159 -0
  53. tradedangerous/misc/eddb.py +81 -0
  54. tradedangerous/misc/eddn.py +349 -0
  55. tradedangerous/misc/edsc.py +437 -0
  56. tradedangerous/misc/edsm.py +121 -0
  57. tradedangerous/misc/importeddbstats.py +54 -0
  58. tradedangerous/misc/prices-json-exp.py +179 -0
  59. tradedangerous/misc/progress.py +194 -0
  60. tradedangerous/plugins/__init__.py +249 -0
  61. tradedangerous/plugins/edcd_plug.py +371 -0
  62. tradedangerous/plugins/eddblink_plug.py +861 -0
  63. tradedangerous/plugins/edmc_batch_plug.py +133 -0
  64. tradedangerous/plugins/spansh_plug.py +2647 -0
  65. tradedangerous/prices.py +211 -0
  66. tradedangerous/submit-distances.py +422 -0
  67. tradedangerous/templates/Added.csv +37 -0
  68. tradedangerous/templates/Category.csv +17 -0
  69. tradedangerous/templates/RareItem.csv +143 -0
  70. tradedangerous/templates/TradeDangerous.sql +338 -0
  71. tradedangerous/tools.py +40 -0
  72. tradedangerous/tradecalc.py +1302 -0
  73. tradedangerous/tradedb.py +2320 -0
  74. tradedangerous/tradeenv.py +313 -0
  75. tradedangerous/tradeenv.pyi +109 -0
  76. tradedangerous/tradeexcept.py +131 -0
  77. tradedangerous/tradeorm.py +183 -0
  78. tradedangerous/transfers.py +192 -0
  79. tradedangerous/utils.py +243 -0
  80. tradedangerous/version.py +16 -0
  81. tradedangerous-12.7.6.dist-info/METADATA +106 -0
  82. tradedangerous-12.7.6.dist-info/RECORD +87 -0
  83. tradedangerous-12.7.6.dist-info/WHEEL +5 -0
  84. tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
  85. tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
  86. tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
  87. 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()