GUI Overview¶
The Graphical User Interface (or GUI) is written in python using the tk library (also known as tkinter). This article describes the overall structure of the GUI’s code. The GUI class’ code resides in python/src/gui/app.py
.
Initialization¶
The GUI’s initialization starts in GUI.__init__()
. This executes when the GUI object is created in python/src/main.py
. As compared to the Backend, there are not many instance variables declared in __init__()
.
Here’s a brief description of some of the important instance variables in the GUI class in the order they are defined:
address
: The (IP,Port) of the socket to connect to.socket
: The Backend socket the GUI is connected to (if its connected).backend_connected_event
: An event that signals a new connection.print_queue
: A queue for messages to print to the consolesocket_queue
: A queue of eitherCommand
s orAsyncCommand
s to be sent to the Backend over the socket connection.socket_recv_thread
: The thread that recieves messages from the Backend.socket_send_thread
: The thread that sends messages to the Backend.current_command
: The currentCommand
being executed by the Backend (orNone
if no command is executing currently).current_command_success_cb
: The callback for a successfulCommandResponse
from the Backend.current_command_error_cb
: The callback for an errorCommandResponse
from the Backend.aync_command_callbacks
: Adict
holding callbacks forAyncCommand
s in the form of mappings of UUID:Callback.waiting_for_backend_response
: A flag indicating that the Backend is processing a command.is_mac
: A flag indicating that the OS platform is Mac.sizes
: Holds constants defining widget sizes.
There are still a couple more instance variables defined as well that we did not mention above.
Inside __init__()
, we also call self._build_ui()
, which constructs the entire user interface. Inside _build_ui()
, we define self.root
as the Tk root object and self.main_window
as the main window of the GUI. In this function we also defined all the Frames inside the GUI and put them in their correct positions.
Run Loop¶
The GUI’s run loop is contained in GUI.run()
. This function first attempts to connect to a Backend, then start the Socket Recv and Send Threads, updates the UI from the connected Backend (if it connected), then enters the Tk main event loop. This event loop is handled by Tk, and it is what handles click callbacks and UI updates. We cannot control the event loop, but we can use self.root.after()
to schedule callbacks inside the main loop. Note that the Tk GUI uses the event loop instead of muiltithreading.
Inside GUI.run()
, the Tk mainloop
exits when the user closes the GUI window. After that happens, we simply disconnect from the Backend and stop the two Socket threads.
Threads¶
The Backend currently uses 6 threads (not including the logging thread):
Socket Recv Thread: Receives
Command
s andAsyncCommand
s from the Frontend.Socket Send Thread: Sends
CommandResponse
s,AsyncCommandResponse
s andstr
console print statements to the Frontend.
Important GUI
Methods¶
Here’s some brief descriptions of the important methods in the GUI
class. The majority of methods in the GUI
class are just related to Backend commands (ie they send a specific command and its data to the Backend). Such methods are usually called by the child Frames defined in GUI.build_ui()
, which is why all the child Frame
classes that we define take the current GUI
object as an argument.
_build_ui(self)
: This is where TkFrame
s are placed into the GUI’s main window. When adding a new Frame or Tabbed Notebook Pane, you’ll need to edit this function to place those appropriately.update_ui(self)
: This sends aCommands.GET
Command to the Backend to get all the data necessary to update the GUI. This is called when the GUI starts and after any regular or async Commands finish. This function will rarely need to be updated.update_ui_callback(self, kwargs)
: This is the success callback to theGET
Command sent inupdate_ui()
. This callback callsupdate_ui
on all the relevant Frames in the GUI using the data inkwargs["result"]
. When adding a new Frame to the GUI that holds info requiring GUI updates, you should defineupdate_ui(data)
in your new Frame class, and add the appropriate call toupdate_ui()
inside of this callback function.load_file(self, dir_init, multiple_files=None)
: Opens the Load File dialog so the user can select one or more files to load. Thedir_init
argument is the directory to open to the file dialog to. This returns eitherNone
, a filepath, or list of filepaths (depending on the usage).save_file(self, dir_init, file_init='')
: This opens the Save File Dialog so the user can select the directory and name of a file to save. The argumentdir_init
is the directory to open the dialog to andfile_init
is the default name of the file._send_command(...)
: This sends a givenCommand
with*args
and**kwargs
to the backend. When using this, you may provide appropriate success and error callbacks. You can also set a loading message. There is also await
argument which makes this function wait until theCommandResponse
is received - this should be used as sparingly as possibly.wait
is currently only used for the evaluating debug commands._send_async_command(...)
: This sends a givenAsyncCommand
with*args
and**kwargs
to the backend. You can set a callback if necessary for a response (if you expect one), and there is also await
parameter which should be used sparingly (hopefully never).