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 console

  • socket_queue: A queue of either Commands or AsyncCommands 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 current Command being executed by the Backend (or None if no command is executing currently).

  • current_command_success_cb: The callback for a successful CommandResponse from the Backend.

  • current_command_error_cb: The callback for an error CommandResponse from the Backend.

  • aync_command_callbacks: A dict holding callbacks for AyncCommands 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 Commands and AsyncCommands from the Frontend.

  • Socket Send Thread: Sends CommandResponses, AsyncCommandResponses and str 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 Tk Frames 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 a Commands.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 the GET Command sent in update_ui(). This callback calls update_ui on all the relevant Frames in the GUI using the data in kwargs["result"]. When adding a new Frame to the GUI that holds info requiring GUI updates, you should define update_ui(data) in your new Frame class, and add the appropriate call to update_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. The dir_init argument is the directory to open to the file dialog to. This returns either None, 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 argument dir_init is the directory to open the dialog to and file_init is the default name of the file.

  • _send_command(...): This sends a given Command 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 a wait argument which makes this function wait until the CommandResponse 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 given AsyncCommand 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 a wait parameter which should be used sparingly (hopefully never).