[1198] | 1 | import tkinter as tk
|
---|
| 2 | import tkinter.ttk as ttk
|
---|
| 3 | from typing import Dict
|
---|
| 4 | from gui.widgets.propertyWindow import PropertyWindow
|
---|
| 5 | from gui.framsutils.FramsInterface import FramsInterface
|
---|
| 6 | from functools import partial
|
---|
| 7 | import operator
|
---|
| 8 | from threading import Semaphore
|
---|
| 9 |
|
---|
| 10 | class ListPopulationsWindow(tk.Toplevel):
|
---|
| 11 | TREEVIEW_WIDTH = 600
|
---|
| 12 |
|
---|
| 13 | class FrameData:
|
---|
| 14 | def __init__(self, frame, treeview, prev, index, text) -> None:
|
---|
| 15 | self.frame = frame
|
---|
| 16 | self.treeview = treeview
|
---|
| 17 | self.prev = prev
|
---|
| 18 | self.index = index
|
---|
| 19 | self.text = text
|
---|
| 20 |
|
---|
| 21 | fields = ["name", "genotype", "energy", "lifespan", "uid"]
|
---|
| 22 | headers = ["Name", "Genotype", "Energy", "Lifespan"]
|
---|
| 23 | headers_width = [0.3, 0.5, 0.1, 0.1]
|
---|
| 24 |
|
---|
| 25 | def __init__(self, parent, posX, posY, height, refreshRate: int, framsSemaphore: Semaphore, frams: FramsInterface):
|
---|
| 26 | super().__init__(parent)
|
---|
| 27 | self.parent = parent
|
---|
| 28 | #self.transient(parent)
|
---|
| 29 | self.protocol("WM_DELETE_WINDOW", self._dismiss)
|
---|
| 30 | self.title("Populations")
|
---|
| 31 |
|
---|
| 32 | self.frams: FramsInterface = None
|
---|
| 33 | self.semaphore: Semaphore = framsSemaphore
|
---|
| 34 | self.frams = frams
|
---|
| 35 |
|
---|
| 36 | self.update()
|
---|
| 37 | self.height = height - self.winfo_rooty() + self.winfo_y()
|
---|
| 38 | self.posX = posX
|
---|
| 39 |
|
---|
| 40 | self.refreshRate = refreshRate
|
---|
| 41 |
|
---|
| 42 | #MAIN SECTION
|
---|
| 43 | frame_main = tk.Frame(master=self)
|
---|
| 44 | self.notebook_notebook = ttk.Notebook(frame_main)
|
---|
| 45 | self.frames: Dict[str, self.FrameData] = {}
|
---|
| 46 |
|
---|
| 47 | #WINDOW
|
---|
| 48 | self.notebook_notebook.grid(row=0, column=0, sticky="NSEW")
|
---|
| 49 | frame_main.columnconfigure(0, weight=1, minsize=0)
|
---|
| 50 | frame_main.rowconfigure(0, weight=1, minsize=0)
|
---|
| 51 | frame_main.grid(row=0, column=0, sticky="NSEW")
|
---|
| 52 |
|
---|
| 53 | self.columnconfigure(0, weight=1, minsize=self.TREEVIEW_WIDTH)
|
---|
| 54 | self.rowconfigure(0, weight=1, minsize=self.height)
|
---|
| 55 |
|
---|
| 56 | self.update()
|
---|
| 57 | width = self.winfo_width()
|
---|
| 58 | self.geometry("%dx%d+%d+%d" % (width, self.height, posX, posY))
|
---|
| 59 | #self.update()
|
---|
| 60 | self.refresh = False
|
---|
| 61 | self.dismissed = False
|
---|
| 62 | self.opened = True
|
---|
| 63 |
|
---|
| 64 | def clearList(self):
|
---|
| 65 | self.update()
|
---|
| 66 |
|
---|
| 67 | def refreshPopulations(self):
|
---|
| 68 | if not self.dismissed and self.frams:
|
---|
| 69 | self.semaphore.acquire()
|
---|
| 70 | populationsDict = self.frams.readPopulations(self.fields)
|
---|
| 71 | groupIndexes = self.frams.readPopulationsGroups()
|
---|
| 72 | self.semaphore.release()
|
---|
| 73 |
|
---|
| 74 | groups = set(populationsDict.keys())
|
---|
| 75 | groups_prev = set(self.frames.keys())
|
---|
| 76 | groups_remove = groups_prev - groups
|
---|
| 77 | for g in groups_remove:
|
---|
| 78 | f = self.frames.pop(g)
|
---|
| 79 | self.notebook_notebook.forget(f.frame)
|
---|
| 80 | groups_add = groups - groups_prev
|
---|
| 81 | for k, v in (sorted(groupIndexes.items(), key=lambda item: item[1])):
|
---|
| 82 | if k in groups_add:
|
---|
| 83 | self._addGroup(k, v)
|
---|
| 84 |
|
---|
| 85 | for (group, populations) in populationsDict.items():
|
---|
| 86 | if populations or (not populations and not self.frames[group].prev):
|
---|
| 87 | prevPop = set(self.frames[group].treeview.get_children())
|
---|
| 88 | popUids = {x["uid"] for x in populations}
|
---|
| 89 |
|
---|
| 90 | popToRemove = prevPop - popUids
|
---|
| 91 | popToAdd = popUids - prevPop
|
---|
| 92 | popToUpdate = popUids.intersection(prevPop)
|
---|
| 93 |
|
---|
| 94 | #first remove unwanted items to reduce time of further operations
|
---|
| 95 | for pop in popToRemove:
|
---|
| 96 | self.frames[group].treeview.delete(pop)
|
---|
| 97 |
|
---|
| 98 | #update remaining items
|
---|
| 99 | for pop in popToUpdate:
|
---|
| 100 | rec = next(x for x in populations if x["uid"] == pop)
|
---|
| 101 | self.frames[group].treeview.item(pop, text="{}-{}".format(rec["group"], rec["uid"]), values=self._makeValue(rec))
|
---|
| 102 |
|
---|
| 103 | #add new items
|
---|
| 104 | for pop in popToAdd:
|
---|
| 105 | rec = next(x for x in populations if x["uid"] == pop)
|
---|
| 106 | self.frames[group].treeview.insert(parent="", index="end", iid=rec["uid"], text="{}-{}".format(rec["group"], rec["uid"]), values=self._makeValue(rec))
|
---|
| 107 | self.frames[group].prev = len(populations)
|
---|
| 108 |
|
---|
| 109 | if self.refresh:
|
---|
| 110 | self.after(self.refreshRate + 1, self.refreshPopulations)
|
---|
| 111 |
|
---|
| 112 | def _makeValue(self, rec):
|
---|
| 113 | ret = []
|
---|
| 114 | for i, label in enumerate(self.fields):
|
---|
| 115 | if isinstance(rec[label], str):
|
---|
| 116 | ret.append(rec[label].replace('\n', "\\n"))
|
---|
| 117 | else:
|
---|
| 118 | ret.append(rec[label])
|
---|
| 119 | if i == len(self.headers) - 1:
|
---|
| 120 | break
|
---|
| 121 |
|
---|
| 122 | return tuple(ret)
|
---|
| 123 |
|
---|
| 124 | def _treeviewSelect(self, event):
|
---|
| 125 | itemId = event.widget.focus()
|
---|
| 126 | if itemId and event.widget.identify_row(event.y) == itemId:
|
---|
| 127 | item = event.widget.item(itemId)
|
---|
| 128 | if item:
|
---|
| 129 | group, uid = item["text"].split('-')
|
---|
| 130 | info = partial(self.frams.readCreatureDetails, group, uid)
|
---|
| 131 | update = partial(self.frams.writeCreatureDetail, uid)
|
---|
| 132 | cw = PropertyWindow(self, "Creature data", self.posX, info, update, self.frams.getError, self.frams, self.semaphore)
|
---|
| 133 |
|
---|
| 134 | def _dismiss(self):
|
---|
| 135 | self.dismissed = True
|
---|
| 136 | self.grab_release()
|
---|
| 137 | self.destroy()
|
---|
| 138 | self.opened = False
|
---|
| 139 |
|
---|
| 140 | def _addGroup(self, g, index):
|
---|
| 141 | masterFrame = tk.Frame(master=self)
|
---|
| 142 | masterFrame.columnconfigure(0, weight=1)
|
---|
| 143 | masterFrame.columnconfigure(1, weight=0)
|
---|
| 144 | masterFrame.rowconfigure(0, weight=1)
|
---|
| 145 |
|
---|
| 146 | treeview = ttk.Treeview(master=masterFrame, columns=tuple(self.headers), selectmode="browse")
|
---|
| 147 | scrollbar_treeview = ttk.Scrollbar(master=masterFrame, orient=tk.VERTICAL, command=treeview.yview)
|
---|
| 148 | treeview.configure(yscrollcommand=scrollbar_treeview.set)
|
---|
| 149 | treeview['show'] = 'headings'
|
---|
| 150 | for h, w in zip(self.headers, self.headers_width):
|
---|
| 151 | treeview.heading(h, text=h)
|
---|
| 152 | treeview.column(h, stretch=tk.YES, width=int(w*self.TREEVIEW_WIDTH))
|
---|
| 153 | treeview.bind("<Double-1>", self._treeviewSelect) #on double left click callback
|
---|
| 154 |
|
---|
| 155 | treeview.grid(row=0, column=0, sticky="NSEW")
|
---|
| 156 | scrollbar_treeview.grid(row=0, column=1, sticky="NSEW")
|
---|
| 157 | masterFrame.grid(row=0, column=0, sticky="NSEW")
|
---|
| 158 |
|
---|
| 159 | self.frames[g] = self.FrameData(masterFrame, treeview, 0, index, g)
|
---|
| 160 |
|
---|
| 161 | selected = self.notebook_notebook.select()
|
---|
| 162 |
|
---|
| 163 | self.notebook_notebook.add(self.frames[g].frame, text=g)
|
---|
| 164 |
|
---|
| 165 | #hide all tabs
|
---|
| 166 | for f in self.frames.values():
|
---|
| 167 | self.notebook_notebook.forget(f.frame)
|
---|
| 168 |
|
---|
| 169 | #show all tabs sorted
|
---|
| 170 | for frame in (sorted(self.frames.values(), key=operator.attrgetter("index"))):
|
---|
| 171 | self.notebook_notebook.add(frame.frame, text=frame.text)
|
---|
| 172 |
|
---|
| 173 | if selected:
|
---|
| 174 | self.notebook_notebook.select(selected)
|
---|
| 175 | else:
|
---|
| 176 | self.notebook_notebook.select(self.frames[g].frame) |
---|