From 96a8447543d312ca7f063a801b609784eb30e4c7 Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Thu, 20 Nov 2014 23:18:19 -0800 Subject: [PATCH 1/3] First, crappy, version of the update gui --- commands/update_gui.py | 293 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 commands/update_gui.py diff --git a/commands/update_gui.py b/commands/update_gui.py new file mode 100644 index 00000000..fef54815 --- /dev/null +++ b/commands/update_gui.py @@ -0,0 +1,293 @@ +import tkinter as tk +import tkinter.messagebox as mbox +import tkinter.ttk as ttk +import sqlite3 + + +class Item(object): + def __init__(self, ID, catID, name, displayNo): + self.ID, self.catID, self.name = ID, catID, name + self.displayNo = displayNo + + +class UpdateFrame(tk.Frame): + def __init__(self, stationID, root=None): + tk.Frame.__init__(self, root, relief=tk.GROOVE) + + self.root = root + + self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff") + self.frame = tk.Frame(self.canvas, background="#ffffff") + self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview) + self.canvas.configure(yscrollcommand=self.vsb.set) + + self.vsb.pack(side="right", fill="y") + self.canvas.pack(side="left", fill="both", expand=True) + self.canvas.create_window((4,4), window=self.frame, anchor="nw", tags="self.frame") + + self.frame.bind("", self.onFrameConfigure) + + self.rowNo = 0 + self.colNo = 0 + self.items = {} + self.categories = [] + self.itemList = [] + self.itemDisplays = [] + self.results = None + self.headings = [] + + self.createWidgets(stationID) + + + def onFrameConfigure(self, event): + self.canvas.configure(scrollregion=self.canvas.bbox("all")) + + + def query(self, itemName, pos): + item = self.items[itemName] + row = self.itemDisplays[item.displayNo] + return item, row, row[pos][1].get() + + + def validate(self, item, row, value, pos): + return True + + + def handleShiftTab(self, itemName, pos, event): + item, row, value = self.query(itemName, pos) + if not self.validate(item, row, value, pos): + return + if pos > 0 or item.displayNo > 0: + # Natural flow + return + self.root.bell() + return "break" + + + def handleTab(self, itemName, pos, event): + item, row, value = self.query(itemName, pos) + if not self.validate(item, row, value, pos): + return + if pos + 1 < len(row): + return + if item.displayNo + 1 < len(self.itemDisplays): + return + self.root.bell() + return "break" + + + def handleReturn(self, itemName, pos, event): + item, row, value = self.query(itemName, pos) + if not self.validate(item, row, value, pos): + return + # advance to the first entry on the next row + newDisplayNo = item.displayNo + 1 + if newDisplayNo >= len(self.itemDisplays): + self.root.bell() + return "break" + row = self.itemDisplays[newDisplayNo] + row[0][0].focus_set() + + + def endRow(self): + self.rowNo += 1 + self.colNo = 0 + + + def addWidget(self, widget, **kwargs): + widget.grid(row=self.rowNo, column=self.colNo, **kwargs) + self.colNo += 1 + return widget + + + def addLabel(self, text): + lab = tk.Label(self.frame, text=text) + return self.addWidget(lab, sticky='W', padx=16) + + + def addSection(self, text): + widget = tk.Label(self.frame, text=text, fg='blue') + widget.grid(row=self.rowNo, column=0, columnspan=5, sticky='W') + self.endRow() + return widget + + + def addInput(self, item, defValue, row): + pos = len(row) + + inputVal = tk.StringVar() + inputVal.set(str(defValue)) + + inputEl = tk.Entry(self.frame, + width=10, + justify=tk.RIGHT, + textvariable=inputVal) + inputEl.bind('', + lambda evt: self.handleShiftTab(item, pos, evt)) + inputEl.bind('', + lambda evt: self.handleTab(item, pos, evt)) + inputEl.bind('', + lambda evt: self.handleReturn(item, pos, evt)) + self.addWidget(inputEl, sticky='E') + row.append((inputEl, inputVal)) + + + def addHeadings(self): + def addHeading(text): + self.headings.append(text) + lab = tk.Label(self.frame, text=text, fg='blue') + self.addWidget(lab, sticky='W', padx=16) + addHeading("Item") + addHeading("Paying") + addHeading("Asking") + addHeading("Demand") + addHeading("Stock") + self.endRow() + + + def addItemRow(self, ID, catID, itemName, paying, asking, demand, stock): + row = [] + + displayNo = len(self.itemDisplays) + item = Item(ID, catID, itemName, displayNo) + self.items[itemName] = item + self.itemList.append(item) + + self.addLabel(itemName) + self.addInput(itemName, paying, row) + self.addInput(itemName, asking, row) + self.addInput(itemName, demand, row) + self.addInput(itemName, stock, row) + + self.itemDisplays.append(row) + + self.endRow() + + + def createWidgets(self, stationID): + self.addHeadings() + + db = sqlite3.connect("data/TradeDangerous.db") + db.row_factory = sqlite3.Row + cur = db.cursor() + + cur.execute(""" + SELECT sys.name, stn.name + FROM Station AS stn + INNER JOIN System AS sys + USING (system_id) + WHERE stn.station_id = ? + LIMIT 1 + """, [stationID]) + (self.sysName, self.stnName) = (cur.fetchone()) + + self.root.title("{} / {}".format(self.sysName, self.stnName)) + + cur.execute(""" + SELECT cat.category_id AS ID, + cat.name AS name + FROM Category AS cat + """) + self.categories = { row["ID"]: row["name"] for row in cur } + + cur.execute(""" + SELECT item.category_id AS catID, + item.item_id AS ID, + item.name AS name, + IFNULL(sb.price, '') AS paying, + IFNULL(ss.price, '') AS asking, + IFNULL(sb.units, 0) AS demandUnits, + IFNULL(sb.level, 0) AS demandLevel, + IFNULL(ss.units, 0) AS stockUnits, + IFNULL(ss.level, 0) AS stockLevel + FROM Category AS cat + INNER JOIN Item item + USING (category_id) + INNER JOIN StationItem si + USING (item_id) + LEFT OUTER JOIN StationBuying sb + USING (station_id, item_id) + LEFT OUTER JOIN StationSelling ss + USING (station_id, item_id) + WHERE si.station_id = ? + ORDER BY cat.name, si.ui_order + """, [stationID]) + + def describeSupply(units, level): + if not level: + return "" + if units == -1 and level == -1: + return "?" + if level == -1: + return "{}?".format(units) + return "{}{}".format(units, ['L', 'M', 'H'][level-1]) + + lastCat = None + for row in cur: + cat = row["catID"] + if cat != lastCat: + self.addSection(self.categories[cat]) + lastCat = cat + itemName = row["name"] + paying, asking = row["paying"], row["asking"] + demand = describeSupply(row["demandUnits"], row["demandLevel"]) + supply = describeSupply(row["stockUnits"], row["stockLevel"]) + self.addItemRow(row["ID"], cat, itemName, paying, asking, demand, supply) + + + def results(self): + lastCat = None + + txt = ( + "# Generated by TDGUI\n" + "\n" + "@ {sys}/{stn}\n".format( + sys=self.sysName.upper(), + stn=self.stnName + ) + ) + for item in self.itemList: + if item.catID != lastCat: + lastCat = item.catID + txt += (" + {}\n".format(self.categories[lastCat])) + + row = self.itemDisplays[item.displayNo] + rowvals = [ val[1].get() for val in row ] + + itemName = item.name + paying = int(rowvals[0] or 0) + asking = int(rowvals[1] or 0) + demand = rowvals[2] + stock = rowvals[3] + + if asking == 0: + stock = "-" + elif asking > 0: + demand = "?" + + txt += (" {item:<30s} " + "{paying:>10} " + "{asking:>10} " + "{demand:>10} " + "{stock:>10}\n".format( + item=itemName, + paying=paying, + asking=asking, + demand=demand, + stock=stock + )) + self.results = txt + + +def render(stationID): + root = tk.Tk() + frame = UpdateFrame(stationID=stationID, root=root) + frame.pack(side="top", fill="both", expand=True) + frame.mainloop() + if not frame.resultsDone: + frame.results() + with open("prices.tmp", "w") as fh: + print(self.results, file=fh) + +if __name__ == "__main__": + render(stationID=374) From c23efd3239647b50e7627dfe9938d1363a51553f Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Thu, 20 Nov 2014 23:47:05 -0800 Subject: [PATCH 2/3] Better alignment with update requirements --- commands/update_gui.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/commands/update_gui.py b/commands/update_gui.py index fef54815..3476c3fa 100644 --- a/commands/update_gui.py +++ b/commands/update_gui.py @@ -2,7 +2,7 @@ import tkinter.messagebox as mbox import tkinter.ttk as ttk import sqlite3 - +from pathlib import Path class Item(object): def __init__(self, ID, catID, name, displayNo): @@ -11,8 +11,8 @@ def __init__(self, ID, catID, name, displayNo): class UpdateFrame(tk.Frame): - def __init__(self, stationID, root=None): - tk.Frame.__init__(self, root, relief=tk.GROOVE) + def __init__(self, root, dbPath, stationID): + tk.Frame.__init__(self, root) self.root = root @@ -36,7 +36,7 @@ def __init__(self, stationID, root=None): self.results = None self.headings = [] - self.createWidgets(stationID) + self.createWidgets(dbPath, stationID) def onFrameConfigure(self, event): @@ -164,10 +164,10 @@ def addItemRow(self, ID, catID, itemName, paying, asking, demand, stock): self.endRow() - def createWidgets(self, stationID): + def createWidgets(self, dbPath, stationID): self.addHeadings() - db = sqlite3.connect("data/TradeDangerous.db") + db = sqlite3.connect(str(dbPath)) db.row_factory = sqlite3.Row cur = db.cursor() @@ -181,7 +181,7 @@ def createWidgets(self, stationID): """, [stationID]) (self.sysName, self.stnName) = (cur.fetchone()) - self.root.title("{} / {}".format(self.sysName, self.stnName)) + self.root.title("{}/{}".format(self.sysName.upper(), self.stnName)) cur.execute(""" SELECT cat.category_id AS ID, @@ -235,7 +235,7 @@ def describeSupply(units, level): self.addItemRow(row["ID"], cat, itemName, paying, asking, demand, supply) - def results(self): + def getResults(self): lastCat = None txt = ( @@ -262,7 +262,7 @@ def results(self): if asking == 0: stock = "-" - elif asking > 0: + elif asking > 0 and not demand: demand = "?" txt += (" {item:<30s} " @@ -279,15 +279,17 @@ def results(self): self.results = txt -def render(stationID): +def render(dbPath, stationID, tmpPath): root = tk.Tk() - frame = UpdateFrame(stationID=stationID, root=root) + frame = UpdateFrame(root, dbPath, stationID) frame.pack(side="top", fill="both", expand=True) frame.mainloop() - if not frame.resultsDone: - frame.results() - with open("prices.tmp", "w") as fh: - print(self.results, file=fh) + if not frame.results: + frame.getResults() + with tmpPath.open("w") as fh: + print(frame.results, file=fh) + if __name__ == "__main__": - render(stationID=374) + render(Path("data/TradeDangerous.db"), 374, Path("update.prices")) + print("- Wrote to update.prices") From 56f37a1a90251df042b9aeab225fa31aee03a731 Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Thu, 20 Nov 2014 23:51:03 -0800 Subject: [PATCH 3/3] First pass of the update gui. 1. it forces demand columns to "?" when they aren't "-", 2. Use tab/shift-tab to go horizontally between columns, 3. Use the enter key to move down to the first col of the next line, --- commands/update_cmd.py | 49 +++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/commands/update_cmd.py b/commands/update_cmd.py index a1133d19..c667d140 100644 --- a/commands/update_cmd.py +++ b/commands/update_cmd.py @@ -73,6 +73,27 @@ class TemporaryFileExistsError(TradeException): pass +def getTemporaryPath(cmdenv): + tmpPath = pathlib.Path("prices.tmp") + if tmpPath.exists(): + if not cmdenv.force: + raise TemporaryFileExistsError( + "Temporary file already exists: {}\n" + "(Check you aren't already editing in another window" + .format(tmpPath) + ) + tmpPath.unlink() + return tmpPath + + +def saveTemporaryFile(tmpPath): + if tmpPath.exists(): + lastPath = pathlib.Path("prices.last") + if lastPath.exists(): + lastPath.unlink() + tmpPath.rename(lastPath) + + def getEditorPaths(cmdenv, editorName, envVar, windowsFolders, winExe, nixExe): cmdenv.DEBUG0("Locating {} editor", editorName) try: @@ -176,13 +197,7 @@ def editUpdate(tdb, cmdenv, stationID): pass # Create a temporary text file with a list of the price data. - tmpPath = pathlib.Path("prices.tmp") - if tmpPath.exists(): - raise TemporaryFileExistsError( - "Temporary file already exists: {}\n" - "(Check you aren't already editing in another window" - .format(tmpPath) - ) + tmpPath = getTemporaryPath(cmdenv) absoluteFilename = None dbFilename = cmdenv.dbFilename or tdb.defaultDB @@ -251,14 +266,21 @@ def editUpdate(tdb, cmdenv, stationID): finally: # Save a copy if absoluteFilename and tmpPath: - lastPath = pathlib.Path("prices.last") - if lastPath.exists(): - lastPath.unlink() - tmpPath.rename(lastPath) + saveTemporaryFile(tmpPath) + +def guidedUpdate(tdb, cmdenv): + stationID = cmdenv.startStation.ID + dbFilename = cmdenv.dbFilename or tdb.defaultDB + tmpPath = getTemporaryPath(cmdenv) -def guidedUpdate(cmdenv, tdb): - raise CommandLineError("Guided update mode not implemented yet. See -h for help.") + from commands.update_gui import render + try: + render(tdb.dbPath, stationID, tmpPath) + cmdenv.DEBUG0("Got results, importing") + importDataFromFile(cmdenv, tdb, tmpPath, stationID, dbFilename) + finally: + saveTemporaryFile(tmpPath) ###################################################################### @@ -267,6 +289,7 @@ def guidedUpdate(cmdenv, tdb): def run(results, cmdenv, tdb): if not cmdenv.editor and not cmdenv.editing: guidedUpdate(tdb, cmdenv) + return None # User specified one of the options to use an editor. editUpdate(tdb, cmdenv, cmdenv.startStation.ID)