[1198] | 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
|
---|
[1202] | 18 | from gui.utils import windowHideAndMaximize, windowShowAndSetGeometry
|
---|
[1198] | 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
|
---|
[1202] | 174 | windowHideAndMaximize(self)
|
---|
[1198] | 175 | maxHeight = self.winfo_rooty() + self.winfo_height()
|
---|
| 176 | maxWidth = self.winfo_rootx() + self.winfo_width()
|
---|
| 177 | self.rootx = self.winfo_rootx()
|
---|
[1202] | 178 | windowShowAndSetGeometry(self, "%dx%d+%d+%d" % (self.OPENGL_WIDTH + self.SIDEBAR_WIDTH, self.OPENGL_HEIGHT, self.rootx, 0))
|
---|
[1198] | 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
|
---|
[1201] | 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", "*.*")])
|
---|
[1198] | 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
|
---|
[1201] | 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", "*.*")])
|
---|
[1198] | 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
|
---|
[1201] | 581 | self.list_populations_window.refreshPopulations()
|
---|