1 | import tkinter as tk
|
---|
2 | import tkinter.ttk as ttk
|
---|
3 | from tkinter import StringVar, simpledialog, messagebox
|
---|
4 | from gui.widgets.glFrame import AppOgl
|
---|
5 | from typing import List
|
---|
6 | from functools import partial
|
---|
7 | from gui.framsutils.FramsInterface import TreeNode, InterfaceType
|
---|
8 | from gui.widgets.mainTreeView import TreeView
|
---|
9 | from gui.widgets.listGenePoolWindow import ListGenePoolWindow
|
---|
10 | from gui.widgets.listPopulationsWindow import ListPopulationsWindow
|
---|
11 | from gui.widgets.dialogBox import DirectoryDialgoBox, FileOpenDialogBox, FileSaveDialogBox
|
---|
12 | from gui.widgets.ConsoleWindow import ConsoleWindow
|
---|
13 | from gui.widgets.importWindow import ImportWindow
|
---|
14 | from gui.widgets.propertyWindow import PropertyWindow
|
---|
15 | from gui.libInterface import LibInterface
|
---|
16 | from gui.socketInterface import SocketInterface
|
---|
17 | from gui.utils import debounce
|
---|
18 | from gui.utils import windowHideAndMaximize, windowShowAndSetGeometry
|
---|
19 | from gui.widgets.ToolTip import CreateToolTip
|
---|
20 | from time import perf_counter
|
---|
21 |
|
---|
22 | class MainPage(tk.Tk):
|
---|
23 | OPENGL_WIDTH = 720
|
---|
24 | OPENGL_HEIGHT = 480
|
---|
25 |
|
---|
26 | SIDEBAR_WIDTH = 400
|
---|
27 | CONTROL_HEIGHT = 50
|
---|
28 | OPTIONS_WIDTH = 100
|
---|
29 | STATUSBAR_HEIGHT = 20
|
---|
30 |
|
---|
31 | OFFSET_HEIGHT = 60
|
---|
32 |
|
---|
33 | OPENGL_ANIMATE_DELAY = 1
|
---|
34 |
|
---|
35 | MENU_CONNECT_TO_SERVER = "Connect to server"
|
---|
36 | MENU_CONNECT_TO_LIB = "Connect to library"
|
---|
37 |
|
---|
38 | WORKAROUND_TKINTER_FREEZE_BUG = True # There is a bug in tkinter that freezes whole app when dialogs are called too fast, hint: https://stackoverflow.com/questions/40666956/tkinter-hangs-on-rapidly-repeated-dialog
|
---|
39 |
|
---|
40 | refresh_rate_dict = {"0.1s": 100, "0.2s": 200, "0.5s": 500, "1s": 1000, "2s": 2000, "5s": 5000, "10s": 10000}
|
---|
41 |
|
---|
42 | #paths which can reload world and tree
|
---|
43 | reload_path = ["/Experiment", "/Advanced scripting", "/World", "/User scripts"]
|
---|
44 |
|
---|
45 | def __init__(self, parent, networkAddress: str = None, libPath: str = None):
|
---|
46 | super().__init__(parent)
|
---|
47 | self.parent = parent
|
---|
48 | self.protocol("WM_DELETE_WINDOW", self._dismiss)
|
---|
49 | self.title("Framsticks GUI for library/server")
|
---|
50 | self.option_add('*tearOff', tk.FALSE)
|
---|
51 |
|
---|
52 | self.listRefreshRate = 1000
|
---|
53 | self.frams = None
|
---|
54 | self.canStep = False #disable step while drawing
|
---|
55 |
|
---|
56 | #OPENGL FRAME
|
---|
57 | self.frame_opengl = AppOgl(self, width=self.OPENGL_WIDTH, height=self.OPENGL_HEIGHT)
|
---|
58 | self.frame_opengl.animate = self.OPENGL_ANIMATE_DELAY
|
---|
59 | self.frame_opengl.bind("<Configure>", self.frame_opengl.onResize)
|
---|
60 | self.frame_opengl.bind("<Motion>", self.frame_opengl.onMouseMotion)
|
---|
61 | self.frame_opengl.bind("<MouseWheel>", self.frame_opengl.onScroll)
|
---|
62 | self.frame_opengl.bind("<Button>", self.frame_opengl.onMouseClick)
|
---|
63 | self.frame_opengl.bind("<ButtonRelease>", self.frame_opengl.onMouseRelease)
|
---|
64 | self.frame_opengl.bind("<Enter>", self.frame_opengl.onMouseEnter)
|
---|
65 |
|
---|
66 | #SIDE FRAME
|
---|
67 | frame_sidebar = tk.Frame(master=self)
|
---|
68 | frame_sidebar.rowconfigure(0, weight=0)
|
---|
69 | frame_sidebar.rowconfigure(1, weight=1)
|
---|
70 | frame_sidebar.columnconfigure(0, weight=1)
|
---|
71 |
|
---|
72 | ##CONTROL PANEL
|
---|
73 | frame_control_panel = tk.Frame(master=frame_sidebar, width=self.SIDEBAR_WIDTH, height=self.CONTROL_HEIGHT)
|
---|
74 | frame_control_panel.columnconfigure(0, weight=1, minsize=0)
|
---|
75 | frame_control_panel.columnconfigure(1, weight=1, minsize=0)
|
---|
76 | frame_control_panel.columnconfigure(2, weight=1, minsize=0)
|
---|
77 | frame_control_panel.columnconfigure(3, weight=1, minsize=0)
|
---|
78 | frame_control_panel.rowconfigure(0, weight=1, minsize=0)
|
---|
79 | frame_control_panel.grid_propagate(0)
|
---|
80 |
|
---|
81 | frame_control_panel_combobox = tk.Frame(master=frame_control_panel, width=int(self.SIDEBAR_WIDTH/4))
|
---|
82 | frame_control_panel_combobox.rowconfigure(0, weight=1, minsize=0)
|
---|
83 | frame_control_panel_combobox.rowconfigure(1, weight=1, minsize=0)
|
---|
84 | frame_control_panel_combobox.columnconfigure(0, weight=1, minsize=0)
|
---|
85 | frame_control_panel_combobox.grid_propagate(0)
|
---|
86 | self.combobox_control_panel_fps = ttk.Combobox(master=frame_control_panel_combobox, state="readonly")
|
---|
87 | self.combobox_control_panel_fps.bind("<<ComboboxSelected>>", self.FPSCbCallback)
|
---|
88 | self.combobox_control_panel_refresh_rate = ttk.Combobox(master=frame_control_panel_combobox, values=list(self.refresh_rate_dict.keys()), state="readonly")
|
---|
89 | self.combobox_control_panel_refresh_rate.set(next(k for k, v in self.refresh_rate_dict.items() if v == self.listRefreshRate))
|
---|
90 | self.combobox_control_panel_refresh_rate.bind("<<ComboboxSelected>>", self.refreshRateCbCallback)
|
---|
91 | CreateToolTip(self.combobox_control_panel_fps, "Simulation steps to show")
|
---|
92 | CreateToolTip(self.combobox_control_panel_refresh_rate, "Refresh rate of gene pools and populations windows")
|
---|
93 |
|
---|
94 | frame_control_panel_buttons = tk.Frame(master=frame_control_panel)
|
---|
95 | frame_control_panel_buttons.columnconfigure(0, weight=1, minsize=0)
|
---|
96 | frame_control_panel_buttons.columnconfigure(1, weight=1, minsize=0)
|
---|
97 | frame_control_panel_buttons.columnconfigure(2, weight=1, minsize=0)
|
---|
98 | frame_control_panel_buttons.rowconfigure(0, weight=1)
|
---|
99 | self.button_control_panel_start = tk.Button(master=frame_control_panel_buttons, text="start", command=self.controlPanelStartCommand)
|
---|
100 | self.button_control_panel_stop = tk.Button(master=frame_control_panel_buttons, text="stop", command=self.controlPanelStopCommand)
|
---|
101 | self.button_control_panel_step = tk.Button(master=frame_control_panel_buttons, text="step", command=self.controlPanelStepCommand)
|
---|
102 | self.button_control_panel_start["state"] = tk.DISABLED
|
---|
103 | self.button_control_panel_stop["state"] = tk.DISABLED
|
---|
104 | self.button_control_panel_step["state"] = tk.DISABLED
|
---|
105 | self.button_control_panel_start.grid(row=0, column=0, sticky="NSEW")
|
---|
106 | self.button_control_panel_stop.grid(row=0, column=1, sticky="NSEW")
|
---|
107 | self.button_control_panel_step.grid(row=0, column=2, sticky="NSEW")
|
---|
108 | self.combobox_control_panel_fps.grid(row=0, column=0, sticky="NSEW")
|
---|
109 | self.combobox_control_panel_refresh_rate.grid(row=1, column=0, sticky="NSEW")
|
---|
110 | frame_control_panel_combobox.grid(row=0, column=0, sticky="NSEW")
|
---|
111 | frame_control_panel_buttons.grid(row=0, column=1, columnspan=3, sticky="NSEW")
|
---|
112 | frame_control_panel.grid(row=0, column=0, sticky="NSEW")
|
---|
113 |
|
---|
114 | ##TREEVIEW
|
---|
115 | frame_treeview = tk.Frame(master=frame_sidebar, width=self.SIDEBAR_WIDTH, height=self.OPENGL_HEIGHT - self.CONTROL_HEIGHT)
|
---|
116 | frame_treeview.columnconfigure(0, weight=1)
|
---|
117 | frame_treeview.columnconfigure(1, weight=0)
|
---|
118 | frame_treeview.rowconfigure(0, weight=1)
|
---|
119 | frame_treeview.rowconfigure(1, weight=0)
|
---|
120 |
|
---|
121 | self.treeview_treeview = TreeView(master=frame_treeview, iconPath="gui/res/icons/", selectmode="browse")
|
---|
122 | scrollbar_treeview = ttk.Scrollbar(master=frame_treeview, orient=tk.VERTICAL, command=self.treeview_treeview.yview)
|
---|
123 | self.treeview_treeview.configure(yscrollcommand=scrollbar_treeview.set)
|
---|
124 | self.treeview_treeview.bind("<Double-1>", self.onTreeViewDoubleClick)
|
---|
125 | button_treeviewRefresh = tk.Button(master=frame_treeview, text="Refresh", command=self.refreshInfoTreeCommand)
|
---|
126 |
|
---|
127 | self.treeview_treeview.grid(row=0, column=0, sticky="NSEW")
|
---|
128 | scrollbar_treeview.grid(row=0, column=1, sticky="NSEW")
|
---|
129 | button_treeviewRefresh.grid(row=1, column=0, sticky="NSEW")
|
---|
130 | frame_treeview.grid(row=1, column=0, sticky="NSEW")
|
---|
131 |
|
---|
132 | #STATUS BAR
|
---|
133 | self.motd_text = StringVar("")
|
---|
134 | label_statusbar_motd = tk.Label(self, textvariable=self.motd_text, bd=1, height=1, relief=tk.SUNKEN, anchor=tk.W)
|
---|
135 |
|
---|
136 | #MENU BAR
|
---|
137 | menu = tk.Menu(self)
|
---|
138 | self.menu_open = tk.Menu(menu, tearoff=0)
|
---|
139 | self.menu_open.add_command(label=self.MENU_CONNECT_TO_SERVER, command=self.menuConnectServerCommand)
|
---|
140 | self.menu_open.add_command(label=self.MENU_CONNECT_TO_LIB, command=self.menuConnectLibCommand)
|
---|
141 | self.menu_open.add_command(label="Disconnect", command=self.menuDisconnectCommand)
|
---|
142 | self.menu_open.entryconfig("Disconnect", state="disabled")
|
---|
143 | self.menu_open.add_separator()
|
---|
144 | self.menu_open.add_command(label="Exit", command=self.menuExitCommand)
|
---|
145 | menu.add_cascade(label="Main", menu=self.menu_open)
|
---|
146 | self.menu_file = tk.Menu(menu, tearoff=0)
|
---|
147 | self.menu_file.add_command(label="Load", command=self.menuFileLoadCommand, state="disabled")
|
---|
148 | self.menu_file.add_command(label="Import", command=self.menuFileImportCommand, state="disabled")
|
---|
149 | self.menu_file.add_command(label="Save experiment state as...", command=self.menuFileSaveESCommand, state="disabled")
|
---|
150 | self.menu_file.add_command(label="Save genotypes as...", command=self.menuFileSaveGCommand, state="disabled")
|
---|
151 | self.menu_file.add_command(label="Save simulator parameters as...", command=self.menuFileSaveSPCommand, state="disabled")
|
---|
152 | menu.add_cascade(label="File", menu=self.menu_file)
|
---|
153 | self.menu_options = tk.Menu(menu, tearoff=0)
|
---|
154 | self.menu_options.add_command(label="Console", command=self.menuConsoleCommand, state="disabled")
|
---|
155 | self.menu_options.add_command(label="Refresh world", command=self.refreshWorld, state="disabled")
|
---|
156 | self.enableColorsVar = tk.BooleanVar(value=False)
|
---|
157 | self.enableColorsVar.trace_add("write", self.menuEnableColorsCommand)
|
---|
158 | self.menu_options.add_checkbutton(label="Enable colors", onvalue=True, offvalue=False, variable=self.enableColorsVar)
|
---|
159 | self.menu_options.add_command(label="Restore windows", command=self.menuRestoreWindowsCommand)
|
---|
160 | menu.add_cascade(label="Options", menu=self.menu_options)
|
---|
161 | self.config(menu=menu)
|
---|
162 |
|
---|
163 | #WINDOW
|
---|
164 | self.columnconfigure(0, weight=1)
|
---|
165 | self.columnconfigure(1, weight=0)
|
---|
166 | self.rowconfigure(0, weight=1)
|
---|
167 | self.rowconfigure(1, weight=0)
|
---|
168 | self.frame_opengl.grid(row=0, column=0, sticky="NSEW")
|
---|
169 | frame_sidebar.grid(row=0, column=1, sticky="NSEW")
|
---|
170 | label_statusbar_motd.grid(row=1, column=0, columnspan=2, sticky="NSEW")
|
---|
171 |
|
---|
172 | #ORGANIZE WINDOWS POSITIONS
|
---|
173 | ## need to do some workaround to determine screen width and height
|
---|
174 | windowHideAndMaximize(self)
|
---|
175 | maxHeight = self.winfo_rooty() + self.winfo_height()
|
---|
176 | maxWidth = self.winfo_rootx() + self.winfo_width()
|
---|
177 | self.rootx = self.winfo_rootx()
|
---|
178 | windowShowAndSetGeometry(self, "%dx%d+%d+%d" % (self.OPENGL_WIDTH + self.SIDEBAR_WIDTH, self.OPENGL_HEIGHT, self.rootx, 0))
|
---|
179 | height = self.winfo_rooty() + self.winfo_height() - self.winfo_y()
|
---|
180 |
|
---|
181 | self.list_gene_pool_window_height = int((maxHeight - height) / 2)
|
---|
182 | self.list_gene_pool_window_pos_y = height
|
---|
183 | self.list_gene_pool_window = ListGenePoolWindow(self, self.rootx, self.list_gene_pool_window_pos_y, self.list_gene_pool_window_height, self.listRefreshRate, self.frame_opengl.read_creature_semaphore, self.frams)
|
---|
184 | height2 = self.list_gene_pool_window.winfo_rooty() + self.list_gene_pool_window.winfo_height() - self.list_gene_pool_window.winfo_y()
|
---|
185 | self.list_populations_window_height = int((maxHeight - height) / 2)
|
---|
186 | self.list_populations_window_pos_y = height + height2
|
---|
187 | self.list_populations_window = ListPopulationsWindow(self, self.rootx, self.list_populations_window_pos_y, self.list_populations_window_height, self.listRefreshRate, self.frame_opengl.read_creature_semaphore, self.frams)
|
---|
188 |
|
---|
189 | self.bind("<FocusIn>", self.on_focus_in)
|
---|
190 | self.bind("<FocusOut>", self.on_focus_out)
|
---|
191 |
|
---|
192 | self.fps = 50
|
---|
193 | self.c_steps = 1
|
---|
194 |
|
---|
195 | if networkAddress and not libPath:
|
---|
196 | self.menuConnectServerCommand(networkAddress)
|
---|
197 | elif libPath and not networkAddress:
|
---|
198 | self.menuConnectLibCommand(libPath)
|
---|
199 |
|
---|
200 | self.sock_adr = "127.0.0.1:9009"
|
---|
201 | self.lib_adr = "D:\\Framsticks50rc25\\"
|
---|
202 |
|
---|
203 | self.FPSCbCallback(None)
|
---|
204 |
|
---|
205 | #step counter
|
---|
206 | self.nb_frames = 0
|
---|
207 | self.c_time = perf_counter()
|
---|
208 | self.sps = 0
|
---|
209 |
|
---|
210 | def _dismiss(self):
|
---|
211 | """dismiss main window, close all connections, release all resources."""
|
---|
212 | if self.frams:
|
---|
213 | self.frams.disconnect()
|
---|
214 |
|
---|
215 | self.destroy()
|
---|
216 |
|
---|
217 | def on_focus_in(self, event):
|
---|
218 | """restart rendering on focusing on main window."""
|
---|
219 | self.frame_opengl.animate = self.OPENGL_ANIMATE_DELAY
|
---|
220 | self.frame_opengl.tkExpose(None)
|
---|
221 |
|
---|
222 | def on_focus_out(self, event):
|
---|
223 | """stop the rendering when main window lose focus."""
|
---|
224 | self.frame_opengl.animate = 0
|
---|
225 |
|
---|
226 | def menuConnectServerCommand(self, adr: str = None):
|
---|
227 | """on "connect to server" button command."""
|
---|
228 | #if connection started from command argument
|
---|
229 | if adr:
|
---|
230 | address = adr
|
---|
231 | #else ask for server address
|
---|
232 | else:
|
---|
233 | address = simpledialog.askstring("Server address", "Address", parent=self, initialvalue=self.sock_adr)
|
---|
234 | if address:
|
---|
235 | ip, port = address.split(":")
|
---|
236 | try:
|
---|
237 | self.frams = SocketInterface()
|
---|
238 | self.frams.registerRunningChangeEventCallback(self.refreshControlPanelButtons)
|
---|
239 | self.frams.registerTreeviewRefreshEventCallback(self.refreshInfoTree)
|
---|
240 | self.frams.connect(ip, int(port)) #try to connect to server
|
---|
241 | self.sock_adr = address
|
---|
242 | except ConnectionError: #if connection cannot be established
|
---|
243 | messagebox.showerror(message="Cannot connect to server")
|
---|
244 | if self.frams:
|
---|
245 | self.frams.disconnect()
|
---|
246 | self.frams = None
|
---|
247 |
|
---|
248 | #if connected successfully
|
---|
249 | if self.frams and self.frams.frams.comm.connected:
|
---|
250 | self._connect(address)
|
---|
251 |
|
---|
252 | def menuConnectLibCommand(self, path: str = None):
|
---|
253 | """on "connect to library" button command."""
|
---|
254 | #if connection started from command argument
|
---|
255 | if path:
|
---|
256 | address = path
|
---|
257 | #else ask for library path
|
---|
258 | else:
|
---|
259 | address = DirectoryDialgoBox("Framsticks library path", "Path", parent=self, initialvalue=self.lib_adr)
|
---|
260 | address = address.result
|
---|
261 | if address:
|
---|
262 | try:
|
---|
263 | self.frams = LibInterface()
|
---|
264 | self.frame_opengl.onDraw = self.onDraw
|
---|
265 | self.prev_run = False
|
---|
266 | self.frams.connect(address, 0)
|
---|
267 | self.lib_adr = address
|
---|
268 | except ConnectionError:
|
---|
269 | messagebox.showerror(message="Cannot find Framsticks library")
|
---|
270 |
|
---|
271 | self._connect(address)
|
---|
272 |
|
---|
273 | def _connect(self, address):
|
---|
274 | """set all control's states if connected."""
|
---|
275 | if address and self.frams:
|
---|
276 | #set creatures read callback
|
---|
277 | self.frame_opengl.frams_readCreatures = self.frams.readCreatures
|
---|
278 | #prepare populations and gene pools windows
|
---|
279 | self.list_populations_window.frams = self.frams
|
---|
280 | self.list_gene_pool_window.frams = self.frams
|
---|
281 | self.list_populations_window.refresh = True
|
---|
282 | self.list_gene_pool_window.refresh = True
|
---|
283 | self.list_populations_window.refreshPopulations()
|
---|
284 | self.list_gene_pool_window.refreshGenePools()
|
---|
285 | #enable control buttons
|
---|
286 | self.button_control_panel_start["state"] = tk.NORMAL
|
---|
287 | self.button_control_panel_stop["state"] = tk.NORMAL
|
---|
288 | self.button_control_panel_step["state"] = tk.NORMAL
|
---|
289 | self.refreshInfoTree()
|
---|
290 | self.motd_text.set(self.frams.getMotd())
|
---|
291 | #setup all menus
|
---|
292 | self.menu_open.entryconfig("Disconnect", state="normal")
|
---|
293 | self.menu_open.entryconfig(self.MENU_CONNECT_TO_SERVER, state="disabled")
|
---|
294 | self.menu_open.entryconfig(self.MENU_CONNECT_TO_LIB, state="disabled")
|
---|
295 | self.menu_file.entryconfig("Load", state="normal")
|
---|
296 | self.menu_file.entryconfig("Import", state="normal")
|
---|
297 | self.menu_file.entryconfig("Save experiment state as...", state="normal")
|
---|
298 | self.menu_file.entryconfig("Save genotypes as...", state="normal")
|
---|
299 | self.menu_file.entryconfig("Save simulator parameters as...", state="normal")
|
---|
300 | self.menu_options.entryconfig("Refresh world", state="normal")
|
---|
301 |
|
---|
302 | if self.frams.interfaceType == InterfaceType.SOCKET:
|
---|
303 | self.menu_options.entryconfig("Console", state="normal")
|
---|
304 | else:
|
---|
305 | self.menu_options.entryconfig("Console", state="disabled")
|
---|
306 |
|
---|
307 | self.fps_values = self.frams.getFPSDefinitions()
|
---|
308 | def mapper(fps, step):
|
---|
309 | return "Every{}".format(", {} fps".format(fps) if fps > 0 else "") if step == 1 else "1:{}".format(step)
|
---|
310 | self.fps_mapvalues = [mapper(fps, step) for fps, step in self.fps_values]
|
---|
311 | init_val = mapper(self.fps, self.c_steps)
|
---|
312 | self.combobox_control_panel_fps['values'] = self.fps_mapvalues
|
---|
313 | self.combobox_control_panel_fps.set(init_val)
|
---|
314 |
|
---|
315 | self.FPSCbCallback(None)
|
---|
316 |
|
---|
317 | self.refreshWorld()
|
---|
318 | self.canStep = True #enable step while drawing
|
---|
319 |
|
---|
320 | def menuDisconnectCommand(self):
|
---|
321 | """set all control's states if disconnected."""
|
---|
322 | if self.frams:
|
---|
323 | self.canStep = False
|
---|
324 | self.frams.disconnect()
|
---|
325 | self.button_control_panel_start["state"] = tk.DISABLED
|
---|
326 | self.button_control_panel_stop["state"] = tk.DISABLED
|
---|
327 | self.button_control_panel_step["state"] = tk.DISABLED
|
---|
328 | self.list_populations_window.refresh = False
|
---|
329 | self.list_gene_pool_window.refresh = False
|
---|
330 | self.list_populations_window.clearList()
|
---|
331 | self.list_gene_pool_window.clearList()
|
---|
332 | self.frame_opengl.frams_readCreatures = None
|
---|
333 | self.frame_opengl.onDraw = lambda: None
|
---|
334 | self.refreshInfoTree()
|
---|
335 | self.frams = None
|
---|
336 | self.frame_opengl.swap_buffer.clear()
|
---|
337 | self.motd_text.set("")
|
---|
338 | self.menu_open.entryconfig("Disconnect", state="disabled")
|
---|
339 | self.menu_open.entryconfig(self.MENU_CONNECT_TO_SERVER, state="normal")
|
---|
340 | self.menu_open.entryconfig(self.MENU_CONNECT_TO_LIB, state="normal")
|
---|
341 | self.menu_options.entryconfig("Console", state="disabled")
|
---|
342 | self.menu_file.entryconfig("Load", state="disabled")
|
---|
343 | self.menu_file.entryconfig("Import", state="disabled")
|
---|
344 | self.menu_file.entryconfig("Save experiment state as...", state="disabled")
|
---|
345 | self.menu_file.entryconfig("Save genotypes as...", state="disabled")
|
---|
346 | self.menu_file.entryconfig("Save simulator parameters as...", state="disabled")
|
---|
347 | self.menu_options.entryconfig("Refresh world", state="disabled")
|
---|
348 |
|
---|
349 | def menuExitCommand(self):
|
---|
350 | self._dismiss()
|
---|
351 |
|
---|
352 | def FPSCbCallback(self, event):
|
---|
353 | """handle fps change."""
|
---|
354 | value = self.combobox_control_panel_fps.get()
|
---|
355 | if value:
|
---|
356 | i = self.fps_mapvalues.index(value)
|
---|
357 | fps, step = self.fps_values[i]
|
---|
358 | self.fps = fps
|
---|
359 | self.c_steps = step
|
---|
360 |
|
---|
361 | if fps == -1:
|
---|
362 | self.frame_opengl.animate = self.OPENGL_ANIMATE_DELAY = 1
|
---|
363 | self.frame_opengl.REFRESH_RATE = 0.001 #set to "as fast as possible" because how often redrawing is called is decided by the timer
|
---|
364 | else:
|
---|
365 | self.frame_opengl.animate = self.OPENGL_ANIMATE_DELAY = int(1000 / fps)
|
---|
366 | self.frame_opengl.REFRESH_RATE = 1 / fps
|
---|
367 |
|
---|
368 | def refreshRateCbCallback(self, event):
|
---|
369 | """handle change of refresh rate of gene pools and populations windows."""
|
---|
370 | value = self.combobox_control_panel_refresh_rate.get()
|
---|
371 | if value:
|
---|
372 | self.listRefreshRate = self.refresh_rate_dict[value]
|
---|
373 | self.list_gene_pool_window.refreshRate = self.listRefreshRate
|
---|
374 | self.list_populations_window.refreshRate = self.listRefreshRate
|
---|
375 |
|
---|
376 | def controlPanelStartCommand(self):
|
---|
377 | if self.frams:
|
---|
378 | self.frams.start()
|
---|
379 |
|
---|
380 | def controlPanelStopCommand(self):
|
---|
381 | if self.frams:
|
---|
382 | self.frams.stop()
|
---|
383 | self.motd_text.set("")
|
---|
384 |
|
---|
385 | def refreshControlPanelButtons(self, is_running: bool):
|
---|
386 | if is_running:
|
---|
387 | self.button_control_panel_start["state"] = tk.DISABLED
|
---|
388 | self.button_control_panel_stop["state"] = tk.NORMAL
|
---|
389 | self.button_control_panel_step["state"] = tk.DISABLED
|
---|
390 | else:
|
---|
391 | self.button_control_panel_start["state"] = tk.NORMAL
|
---|
392 | self.button_control_panel_stop["state"] = tk.DISABLED
|
---|
393 | self.button_control_panel_step["state"] = tk.NORMAL
|
---|
394 |
|
---|
395 | def controlPanelStepCommand(self):
|
---|
396 | """hangle step button."""
|
---|
397 | if self.frams: #if connected to frams
|
---|
398 | if self.frams.interfaceType == InterfaceType.LIB:
|
---|
399 | self.canStep = False #disable step while drawing
|
---|
400 | run = self.frams.getSimulationStatus()
|
---|
401 | self.frams.start()
|
---|
402 | self.frams.step()
|
---|
403 | if not run:
|
---|
404 | self.frams.stop()
|
---|
405 | self.canStep = True #enable step while drawing
|
---|
406 | else:
|
---|
407 | self.canStep = False #disable step while drawing
|
---|
408 | self.frams.step()
|
---|
409 | self.canStep = True #enable step while drawing
|
---|
410 |
|
---|
411 | def refreshInfoTreeCommand(self):
|
---|
412 | self.refreshInfoTree()
|
---|
413 | self.refreshWorld()
|
---|
414 |
|
---|
415 | @debounce(1)
|
---|
416 | def refreshInfoTree(self):
|
---|
417 | """refresh info tree
|
---|
418 | debounce decorator prevents refreshing too often."""
|
---|
419 | self.treeview_treeview.delete(*self.treeview_treeview.get_children())
|
---|
420 | if self.frams:
|
---|
421 | tree: List[TreeNode] = self.frams.makeInfoTree()
|
---|
422 | self._recRefreshInfoTree(tree, True)
|
---|
423 |
|
---|
424 | def _recRefreshInfoTree(self, node: TreeNode, open: bool = False):
|
---|
425 | #self.treeview_treeview.insert("" if not node.parent else node.parent.node.Id, index="end", iid=node.node.Id, text=node.node.Name)
|
---|
426 | self.treeview_treeview.insert(self._generateInfoTreeParent(node), index="end", iid=self._generateInfoTreeId(node), text=node.node.p["name"], ico=node.node.p["id"], open=open)
|
---|
427 | for child in node.children:
|
---|
428 | self._recRefreshInfoTree(child)
|
---|
429 |
|
---|
430 | def _generateInfoTreeParent(self, node: TreeNode):
|
---|
431 | if not node.parent:
|
---|
432 | return ""
|
---|
433 | return self._generateInfoTreeId(node.parent)
|
---|
434 |
|
---|
435 | def _generateInfoTreeId(self, node: TreeNode):
|
---|
436 | """generate unique id for info tree using paths and ids."""
|
---|
437 | response = node.node.p["id"]
|
---|
438 | while node.parent:
|
---|
439 | if node.parent:
|
---|
440 | if node.parent.node.p["id"] != '/':
|
---|
441 | response = node.parent.node.p["id"] + "/" + response
|
---|
442 | else:
|
---|
443 | response = node.parent.node.p["id"] + response
|
---|
444 | node = node.parent
|
---|
445 | return response
|
---|
446 |
|
---|
447 | def onTreeViewDoubleClick(self, event):
|
---|
448 | if self.frams:
|
---|
449 | item = self.treeview_treeview.identify_row(event.y)
|
---|
450 | if item:
|
---|
451 | info = partial(self.frams.readParameterDetails, item)
|
---|
452 | update = partial(self.frams.writeParameterDetail, item)
|
---|
453 |
|
---|
454 | if any(item.lower().endswith(x.lower()) for x in self.reload_path):
|
---|
455 | pw = PropertyWindow(self, item, self.rootx, info, update, self.frams.getError, self.frams, self.frame_opengl.read_creature_semaphore, self.refreshWorld)
|
---|
456 | else:
|
---|
457 | pw = PropertyWindow(self, item, self.rootx, info, update, self.frams.getError, self.frams, self.frame_opengl.read_creature_semaphore)
|
---|
458 | return 'break'
|
---|
459 |
|
---|
460 | def onDraw(self):
|
---|
461 | """on draw callback, only for frams library."""
|
---|
462 | if self.frams:
|
---|
463 | run = self.frams.getSimulationStatus()
|
---|
464 | if run != self.prev_run: #check if simulation status changed since last onDraw
|
---|
465 | self.refreshControlPanelButtons(run) #if so, refresh command buttons
|
---|
466 | self.prev_run = run
|
---|
467 | if run:
|
---|
468 | if self.canStep: #if running and can step, perform N steps
|
---|
469 | with self.frame_opengl.read_creature_semaphore:
|
---|
470 | for i in range(self.c_steps):
|
---|
471 | self.frams.step()
|
---|
472 |
|
---|
473 | #calculate steps per second
|
---|
474 | c_time = perf_counter()
|
---|
475 | self.nb_frames += 1
|
---|
476 | e_time = c_time - self.c_time
|
---|
477 | if e_time >= 1.0:
|
---|
478 | self.sps = int(self.nb_frames / e_time)
|
---|
479 | self.c_time = perf_counter()
|
---|
480 | self.nb_frames = 0
|
---|
481 | self.motd_text.set("{} steps/second".format(self.sps))
|
---|
482 | error = self.frams.getError() #check for errors, not used anymore
|
---|
483 | if error:
|
---|
484 | messagebox.showerror("Framsticks error", error)
|
---|
485 |
|
---|
486 | def menuConsoleCommand(self):
|
---|
487 | if self.frams.interfaceType == InterfaceType.SOCKET:
|
---|
488 | ConsoleWindow(self, self.frams.frams)
|
---|
489 | else:
|
---|
490 | self.menu_options.entryconfig("Console", state="disabled")
|
---|
491 |
|
---|
492 | def menuFileLoadCommand(self):
|
---|
493 | if self.frams:
|
---|
494 | self.canStep = False
|
---|
495 | path = FileOpenDialogBox("Open File", "Path", parent=self, initialvalue=self.lib_adr, filetypes=[("Framsticks files", ['*.expt','*.gen','*.sim']), ("Experiment state", "*.expt"), ("Genotypes", "*.gen"), ("Simulator parameters", "*.sim"), ("All files", "*.*")])
|
---|
496 | path = path.result
|
---|
497 | if path:
|
---|
498 | #acquire read creature semaphore, so render thread could not ask for them, while they are changing
|
---|
499 | with self.frame_opengl.read_creature_semaphore:
|
---|
500 | self.frams.loadFile(path)
|
---|
501 | self.refreshWorld()
|
---|
502 | self.canStep = True
|
---|
503 |
|
---|
504 | def menuFileImportCommand(self):
|
---|
505 | if self.frams:
|
---|
506 | self.canStep = False
|
---|
507 | path = FileOpenDialogBox("Open File", "Path", parent=self, init_val=self.lib_adr, filetypes=[("Framsticks files", ['*.expt','*.gen','*.sim']), ("Experiment state", "*.expt"), ("Genotypes", "*.gen"), ("Simulator parameters", "*.sim"), ("All files", "*.*")])
|
---|
508 | path = path.result
|
---|
509 | if path:
|
---|
510 | #acquire read creature semaphore, so render thread could not ask for them, while they are changing
|
---|
511 | with self.frame_opengl.read_creature_semaphore:
|
---|
512 | def handle_import_options():
|
---|
513 | options = ImportWindow(parent=self, filename=path)
|
---|
514 | options = options.result
|
---|
515 | self.frams.importFile(path, options)
|
---|
516 | if self.WORKAROUND_TKINTER_FREEZE_BUG:
|
---|
517 | self.after(1, handle_import_options)
|
---|
518 | else:
|
---|
519 | handle_import_options()
|
---|
520 |
|
---|
521 | self.canStep = True
|
---|
522 |
|
---|
523 | def menuFileSaveESCommand(self):
|
---|
524 | if self.frams:
|
---|
525 | path = FileSaveDialogBox("Save experiment state to:", "Path", parent=self, initialvalue=self.lib_adr, filetypes=[("Experiment state", "*.expt")])
|
---|
526 | path = path.result
|
---|
527 | if path:
|
---|
528 | if path.split(".")[-1] != "expt":
|
---|
529 | path += ".expt"
|
---|
530 | self.frams.saveFile(path, 1)
|
---|
531 |
|
---|
532 | def menuFileSaveGCommand(self):
|
---|
533 | if self.frams:
|
---|
534 | path = FileSaveDialogBox("Save genotypes to:", "Path", parent=self, initialvalue=self.lib_adr, filetypes=[("Genotypes", "*.gen")])
|
---|
535 | path = path.result
|
---|
536 | if path:
|
---|
537 | if path.split(".")[-1] != "gen":
|
---|
538 | path += ".gen"
|
---|
539 | self.frams.saveFile(path, 2)
|
---|
540 |
|
---|
541 | def menuFileSaveSPCommand(self):
|
---|
542 | if self.frams:
|
---|
543 | path = FileSaveDialogBox("Save simulator parameters to:", "Path", parent=self, initialvalue=self.lib_adr, filetypes=[("Simulator parameters", "*.sim")])
|
---|
544 | path = path.result
|
---|
545 | if path:
|
---|
546 | if path.split(".")[-1] != "sim":
|
---|
547 | path += ".sim"
|
---|
548 | self.frams.saveFile(path, 4)
|
---|
549 |
|
---|
550 | def refreshWorld(self):
|
---|
551 | """refresh world parameters and shape."""
|
---|
552 | worldType = self.frams.getWorldType()
|
---|
553 | worldSize = self.frams.getWorldSize()
|
---|
554 | worldWaterLevel = self.frams.getWorldWaterLevel()
|
---|
555 | worldBoundaries = self.frams.getWorldBoundaries()
|
---|
556 | worldMap = self.frams.getWorldMap()
|
---|
557 | simType = self.frams.getSimtype()
|
---|
558 | self.frame_opengl.reloadWorld(worldType, simType, worldSize, worldMap, worldBoundaries, worldWaterLevel)
|
---|
559 |
|
---|
560 | def menuEnableColorsCommand(self, *_):
|
---|
561 | v = self.enableColorsVar.get()
|
---|
562 | self.frame_opengl.frams_readCreatures_color = v
|
---|
563 |
|
---|
564 | def menuRestoreWindowsCommand(self):
|
---|
565 | self.geometry("%dx%d+%d+%d" % (self.OPENGL_WIDTH + self.SIDEBAR_WIDTH, self.OPENGL_HEIGHT, self.rootx, 0))
|
---|
566 |
|
---|
567 | if self.list_gene_pool_window.opened:
|
---|
568 | self.list_gene_pool_window._dismiss()
|
---|
569 | self.list_gene_pool_window = ListGenePoolWindow(self, self.rootx, self.list_gene_pool_window_pos_y, self.list_gene_pool_window_height, self.listRefreshRate, self.frame_opengl.read_creature_semaphore, self.frams)
|
---|
570 | if self.frams:
|
---|
571 | self.list_gene_pool_window.frams = self.frams
|
---|
572 | self.list_gene_pool_window.refresh = True
|
---|
573 | self.list_gene_pool_window.refreshGenePools()
|
---|
574 |
|
---|
575 | if self.list_populations_window.opened:
|
---|
576 | self.list_populations_window._dismiss()
|
---|
577 | self.list_populations_window = ListPopulationsWindow(self, self.rootx, self.list_populations_window_pos_y, self.list_populations_window_height, self.listRefreshRate, self.frame_opengl.read_creature_semaphore, self.frams)
|
---|
578 | if self.frams:
|
---|
579 | self.list_populations_window.frams = self.frams
|
---|
580 | self.list_populations_window.refresh = True
|
---|
581 | self.list_populations_window.refreshPopulations()
|
---|