Common Tab Methods, Attributes and Properties

Each tab in a pulse design must provide a certain set of methods and attributes. For transform tabs, some of these are implemented in the BaseTransformationTab class and others can only be implemented by its subclasses.

get_raw_gui_data() pulls the data from the GUI and dumps it into a dictionary which it returns to the caller. The data is minimally processed and not judged.

Minimally processed means that the data from textboxes is still strings, radio button values are just the indices of the radio button that was selected, etc. The only processing that occurs before the data is dumped into the dict is that get_raw_gui_data() runs strip() on strings to remove extraneous whitespace.

Not judged means that get_raw_gui_data() is uninterested in the validity of the fields it reads. A field called "temperature" might be set to "hello world". get_raw_gui_data() doesn't care. Data is dumped into the dict as-is.

get_raw_gui_data() is called very frequently, so it's important to keep it simple.

get_cooked_gui_data() cooks the raw data in the dict created by get_raw_gui_data(). Cooking converts the data from strings to the types that the internal objects want to use (float, int, DateTime.DateTime, etc.). get_cooked_gui_data() is where conversions to internal constants happens, too.

Just like get_raw_gui_data(), get_cooked_gui_data() returns a dict. Usually most key names are the same in both dicts. That's due to the fact that, for the most part, there's a 1:1 correspondence between fields in our GUI and attributes on our objects. In the majority of cases, cooking the data usually just means calling float() or int(). Sometimes the conversions are more complicated. For instance, users might enter data in liters per minute, but for internal use you might want to convert to more standard units like hogsheads per fortnight.

get_cooked_gui_data() contains no error handling. It assumes that the raw data is valid. In other words, all the floats are floatable, all the ints are intable, no division by zero will happen, etc. This implies that you need to call validate_gui() before calling get_cooked_gui_data().

It's convenient to pass the dict returned by get_cooked_gui_data() to inflate() methods. Therefore, it saves work if get_cooked_gui_data() constructs a dict with key names that are inflate-friendly.

validate_gui() operates on both the raw & cooked GUI data. It examines each field and lets the user know if any of those fields are not valid. It returns True if the GUI is valid, False otherwise. If it returns False, it displays a message to the user before doing so.

validate_gui() can enforce any rules it likes. For instance, if users enter a value called "volume", then validate_gui() would examine the raw GUI data to see that volume can be turned into a float, and might also examine the cooked GUI data to see that volume is in range ("Volume only goes up to 11.")

When you call validate_gui(), be aware that it displays a message box if it finds anything awry. It'd be trivial to add a "silent=False" default parameter that can be set to True to allow the validation to occur without displaying a message, but so far we haven't needed that.

accept_gui() pulls the data from the GUI (via get_cooked_gui_data()) and calls .inflate() to populate the project's objects with that data. Like get_cooked_gui_data() (qv), this method assumes that the GUI is valid.

run() is where the tab uses the data in the objects to do whatever science it's going to do. At the end of this method, it updates the plot.

_last_run stores the dict generated by get_raw_gui_data() when the tab was last run. This makes it easy to figure out if a tab has changed since it was last run – just compare self._last_run to get_raw_gui_data(). This is how is_synced works. (We check this frequently which is why get_raw_gui_data() gets called so often.)

_last_save is very similar to _last_run except that it tracks the state of the GUI at the last save.

left_neighbor() is a property that returns the tab to the left (which is None for the Basic Info tab). This property doesn't work during a tab's __init__().

is_synced() is a property that returns True or False depending on whether or not the tab's input is in sync with its output (results). is_synced() relies not only on whether or not the tab has changed (see _last_run) but also on whether or not the tab's left neighbor is in sync. Since the output of tab N is the input to tab N+1, the latter can't be in sync unless the former is in sync.

Tabs also know who their left neighbor is when they're created. If that neighbor changes (e.g. a tab is deleted), then the tab becomes out of sync until it's run again.

The validity of some properties depends on values on other tabs. (The "other" tab is almost always the first tab (basic info) because it's home to the machine settings which represent some physical limits that the pulse can't violate.) Each tab is guaranteed that when its validate_gui() is called, all tabs to the left are valid. This makes it safe to call another tab's get_cooked_gui_data(), as long as that tab is to the left.

update_sync_status() is an optional notification method that tabs can implement. It's called by the inner notebook method of the same name. It gives a tab an opportunity to change itself when the sync status changes. As of this writing, only the optimal control transformation tab implements this.

is_saved() is a property that returns True or False depending on whether or not the tab's input is the same as it was when it was last saved.

on_activation is a notification handler. The parent notebook calls it when a tab is made active.

on_child_focus handles the wx event EVT_CHILD_FOCUS.

What Happens When Run Is Clicked

Run makes heavy use of the functions above. When the user clicks a tab's "Run" button, that tab calls the project notebook's .run() method. The notebook then runs the following algorithm –

  1. Make a list of the tabs for which tab.is_synced is False.
  2. Attempt to validate each of those tabs via tab.validate_gui().
  3. If all validate, pull each tab's GUI into the pulse design object via tab.accept_gui().
  4. Run each tab by calling tab.run().