def __init__(self, parent, global_store, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self._measurement_resource_name = None # Defaults. self.plot_settings = PlotSettings() self.unit_conversion = 0 self.enabled = False self.capturing_data = False self.restart_live_view = False self.resource_backup = None # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() # Initialize recorded values. self.init_values() # Plot and toolbar. display_box = wx.BoxSizer(wx.VERTICAL) ## Plot. if plot_available: self.plot = TwoDimensionalPlot(self, color='blue') display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) self.plot.x_label = 'Time (s)' else: display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) ## Controls. if plot_available: controls_box = wx.BoxSizer(wx.HORIZONTAL) display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5) ### Numeric display. numeric_display_static_box = wx.StaticBox(self, label='Reading') numeric_display_box = wx.StaticBoxSizer(numeric_display_static_box, wx.HORIZONTAL) controls_box.Add(numeric_display_box, flag=wx.CENTER) self.numeric_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) self.numeric_display.BackgroundColour = wx.LIGHT_GREY numeric_display_box.Add(self.numeric_display) ### Capture. capture_static_box = wx.StaticBox(self, label='Control') capture_box = wx.StaticBoxSizer(capture_static_box) controls_box.Add(capture_box, flag=wx.CENTER|wx.LEFT, border=10) self.run_button = wx.Button(self, label='Run') self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button) capture_box.Add(self.run_button, flag=wx.CENTER) self.pause_button = wx.Button(self, label='Pause') self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button) capture_box.Add(self.pause_button, flag=wx.CENTER) self.reset_button = wx.Button(self, label='Reset') self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) capture_box.Add(self.reset_button, flag=wx.CENTER|wx.LEFT, border=10) ### Settings. settings_static_box = wx.StaticBox(self, label='Settings') settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10) self.plot_settings_button = wx.Button(self, label='Plot...') self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) settings_box.Add(self.plot_settings_button, flag=wx.CENTER) self.SetSizer(display_box) # Acquisition thread. callback = functools.partial(wx.CallAfter, self.add_value) self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback, running_lock=self.running_lock) self.acq_thread.daemon = True self.acq_thread.start() # Wait for a resource to begin capturing. self.OnPause() self.run_button.Disable() # Subscriptions. pub.subscribe(self.msg_resource, 'resource.added') pub.subscribe(self.msg_resource, 'resource.removed') pub.subscribe(self.msg_data_capture_start, 'data_capture.start') pub.subscribe(self.msg_data_capture_data, 'data_capture.data') pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop')
class ScalarLiveViewPanel(wx.Panel): """ A panel to display a live view plot of a scalar resource. """ def __init__(self, parent, global_store, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self._measurement_resource_name = None # Defaults. self.plot_settings = PlotSettings() self.unit_conversion = 0 self.enabled = False self.capturing_data = False self.restart_live_view = False self.resource_backup = None # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() # Initialize recorded values. self.init_values() # Plot and toolbar. display_box = wx.BoxSizer(wx.VERTICAL) ## Plot. if plot_available: self.plot = TwoDimensionalPlot(self, color='blue') display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) self.plot.x_label = 'Time (s)' else: display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) ## Controls. if plot_available: controls_box = wx.BoxSizer(wx.HORIZONTAL) display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5) ### Numeric display. numeric_display_static_box = wx.StaticBox(self, label='Reading') numeric_display_box = wx.StaticBoxSizer(numeric_display_static_box, wx.HORIZONTAL) controls_box.Add(numeric_display_box, flag=wx.CENTER) self.numeric_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) self.numeric_display.BackgroundColour = wx.LIGHT_GREY numeric_display_box.Add(self.numeric_display) ### Capture. capture_static_box = wx.StaticBox(self, label='Control') capture_box = wx.StaticBoxSizer(capture_static_box) controls_box.Add(capture_box, flag=wx.CENTER|wx.LEFT, border=10) self.run_button = wx.Button(self, label='Run') self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button) capture_box.Add(self.run_button, flag=wx.CENTER) self.pause_button = wx.Button(self, label='Pause') self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button) capture_box.Add(self.pause_button, flag=wx.CENTER) self.reset_button = wx.Button(self, label='Reset') self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) capture_box.Add(self.reset_button, flag=wx.CENTER|wx.LEFT, border=10) ### Settings. settings_static_box = wx.StaticBox(self, label='Settings') settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10) self.plot_settings_button = wx.Button(self, label='Plot...') self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) settings_box.Add(self.plot_settings_button, flag=wx.CENTER) self.SetSizer(display_box) # Acquisition thread. callback = functools.partial(wx.CallAfter, self.add_value) self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback, running_lock=self.running_lock) self.acq_thread.daemon = True self.acq_thread.start() # Wait for a resource to begin capturing. self.OnPause() self.run_button.Disable() # Subscriptions. pub.subscribe(self.msg_resource, 'resource.added') pub.subscribe(self.msg_resource, 'resource.removed') pub.subscribe(self.msg_data_capture_start, 'data_capture.start') pub.subscribe(self.msg_data_capture_data, 'data_capture.data') pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop') @property def running(self): return self.pause_button.Enabled @property def resource(self): return self.acq_thread.resource @resource.setter def resource(self, value): # Ignore unreadable resources. if value is not None and not value.readable: value = None if self.running: # Currently running. running = True self.OnPause() else: running = False self.acq_thread.resource = value self.run_button.Enable(value is not None) # Resume if applicable. if running: self.OnRun() @property def measurement_resource_name(self): if self._measurement_resource_name is None: return '' else: return self._measurement_resource_name @measurement_resource_name.setter def measurement_resource_name(self, value): if value: self._measurement_resource_name = value try: self.resource = self.global_store.resources[self._measurement_resource_name] except KeyError: self.resource = None else: self._measurement_resource_name = None self.resource = None def init_values(self): """ Clear captured values. """ self._points = numpy.array([]) self._times = numpy.array([]) self._values = numpy.array([]) self.current_value = None self.start_time = None def update_plot(self): """ Redraw the plot. """ if not len(self._points) > 0: display_time = [0] display_values = [0] else: if self.plot_settings.time_value == 0: # Time. display_time = self._times if self.plot_settings.time_mode == 0: # Relative. # Calculate the number of seconds passed since each point. max_time = self._times[-1] display_time = [x - max_time for x in display_time] elif self.plot_settings.time_mode == 1: # Absolute. display_time = [x - self.start_time for x in display_time] elif self.plot_settings.time_value == 1: # Points. display_time = self._points if self.plot_settings.time_mode == 0: # Relative. # Calculate the number of seconds passed since each point. max_point = self._points[-1] display_time = [x - max_point for x in display_time] display_values = [x * 10 ** (self.plot_settings.y_scale + self.unit_conversion) for x in self._values] if self.plot_settings.update_x: self.plot.x_autoscale() if self.plot_settings.update_y: self.plot.y_autoscale() self.plot.x_data, self.plot.y_data = display_time, display_values def add_value(self, value): """ Update the plot with a new value. """ if not self.plot_settings.enabled: return # Extract the value of a Quantity. try: # Label with the base dimensions. if self.unit_conversion == 0: self.plot.y_label = '({0})'.format(value.original_units) value = value.original_value except AttributeError: pass # Update values. try: self._points = numpy.append(self._points, self._points[-1] + 1) except IndexError: self._points = numpy.append(self._points, 0) cur_time = time.time() self._times = numpy.append(self._times, cur_time) self._values = numpy.append(self._values, value) if self.start_time is None: self.start_time = cur_time cut_idx = len(self._points) - int(self.plot_settings.num_points) if cut_idx > 0: self._points = self._points[cut_idx:] self._times = self._times[cut_idx:] self._values = self._values[cut_idx:] # Set number display. self.current_value = value * 10 ** (self.plot_settings.y_scale + self.unit_conversion) self.numeric_display.Value = '{0:.6g}'.format(self.current_value) # Plot. self.update_plot() def close(self): """ Perform cleanup. """ # Unsubscriptions. pub.unsubscribe(self.msg_resource, 'resource.added') pub.unsubscribe(self.msg_resource, 'resource.removed') pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start') pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data') pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop') # Ensure the thread exits. self.acq_thread.resource = None self.acq_thread.done = True if not self.running: self.running_lock.release() self.acq_thread.join() del self.acq_thread def OnRun(self, evt=None): """ Let the acquisition thread run. """ self.run_button.Disable() if self.acq_thread.resource is None: return self.running_lock.release() self.pause_button.Enable() def OnPause(self, evt=None): """ Block the acquisition thread. """ if not self.running: return self.running_lock.acquire() if self.acq_thread.resource is not None: self.run_button.Enable() self.pause_button.Disable() def OnReset(self, evt=None): self.init_values() self.update_plot() def OnPlotSettings(self, evt=None): """ Open the plot settings dialog. """ def ok_callback(dlg): self.plot_settings = dlg.GetValue() if self.plot_settings.units_from and self.plot_settings.units_to: try: quantity_from = Quantity(1, self.plot_settings.units_from) quantity_to = Quantity(1, self.plot_settings.units_to) except ValueError as e: self.unit_conversion = 0 MessageDialog(self, str(e), 'Invalid unit').Show() else: # We don't actually care about the units; just the prefix values. self.unit_conversion = math.log(quantity_from.value, 10) - math.log(quantity_to.value, 10) else: self.unit_conversion = 0 self.acq_thread.delay = self.plot_settings.delay if self.plot_settings.time_value == 0: self.plot.x_label = 'Time (s)' elif self.plot_settings.time_value == 1: self.plot.x_label = 'Points' if self.plot_settings.y_scale != 0: self.plot.y_label = '/ 10 ^ {0}'.format(self.plot_settings.y_scale) else: self.plot.y_label = '' if self.plot_settings.units_to: self.plot.y_label += ' ({0})'.format(self.plot_settings.units_to) self.update_plot() dlg = PlotSettingsDialog(self, ok_callback) dlg.SetValue(self.plot_settings) dlg.Show() def msg_resource(self, name, value=None): if self.measurement_resource_name is not None and name == self.measurement_resource_name: self.resource = value def msg_data_capture_start(self, name): if name == self.measurement_resource_name: if self.enabled: self.capturing_data = True # Keep track of whether to restart the capture afterwards. self.restart_live_view = self.running # Disable live view. self.resource_backup = self.resource self.resource = None def msg_data_capture_data(self, name, value): if name == self.measurement_resource_name: if self.capturing_data: self.add_value(value) def msg_data_capture_stop(self, name): if name == self.measurement_resource_name: if self.capturing_data: self.capturing_data = False # Re-enable live view. self.resource = self.resource_backup self.resource_backup = None if self.restart_live_view: self.OnRun()
def __init__(self, parent, global_store, subdevice, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self.delay = Quantity(1.0, 's') # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() self.channel_subdevice = subdevice self.displays = {} self.control_state_displays = {} self.readout_displays = {} self.control_state_list = [ 'persistent_switch_heater', 'virt_sync_currents' ] self.readout_list = [ 'magnet_current', 'power_supply_current', 'persistent_switch_heater', 'high_limit', 'low_limit', 'sweep', 'rate_0', 'rate_1', 'rate_2', 'rate_3', 'rate_4', 'virt_sync_currents' ] self.measurement_resources = [] for name in self.readout_list: self.displays[name] = [] self.measurement_resources.append( (name, self.channel_subdevice.resources[name])) # A list to save acquired data to before outputting to GUI. self.measurements = [None] * len(self.measurement_resources) # Main Box. main_box = wx.BoxSizer(wx.VERTICAL) # Channel Header Box. channel_header_box = wx.BoxSizer(wx.HORIZONTAL) main_box.Add(channel_header_box, flag=wx.EXPAND) self.channel_button = wx.ToggleButton( self, label='Channel {0} Toggle'.format(self.channel_subdevice.channel)) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnChannelToggle, self.channel_button) self.channel_button.SetValue(False) channel_header_box.Add(self.channel_button) ## Control states. control_state_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) channel_header_box.Add((0, 0), 1, wx.EXPAND) channel_header_box.Add(control_state_grid, flag=wx.ALIGN_RIGHT, border=20) for control_state_name in self.control_state_list: control_state_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) control_state_display.BackgroundColour = wx.LIGHT_GREY control_state_grid.Add(control_state_display, flag=wx.ALIGN_RIGHT) self.displays[control_state_name].append(control_state_display) self.control_state_displays[ control_state_name] = control_state_display # reverse our dictionary for key retrieval by item. self.inv_control_state_displays = dict( (v, k) for k, v in self.control_state_displays.iteritems()) # Readouts. readout_static_box = wx.StaticBox(self, label='Readouts') readout_box = wx.StaticBoxSizer(readout_static_box, wx.VERTICAL) main_box.Add(readout_box, flag=wx.EXPAND, proportion=1) # readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=2, hgap=1) readout_grid = wx.FlexGridSizer( rows=len(self.readout_list), cols=3, hgap=1 ) #TODO: for debugging model4g GUI...replace when no longer needed. readout_box.Add(readout_grid, flag=wx.ALIGN_RIGHT) self.checkboxes = {} ## Setup individual labels + displays for resource_name in self.readout_list: ### Checkbox. #TODO: for debugging model4g GUI...remove when no longer needed. checkbox = wx.CheckBox(self) readout_grid.Add(checkbox, flag=wx.ALIGN_LEFT) self.checkboxes[resource_name] = checkbox ### Label. label = resource_name.replace('_', ' ').title() readout_grid.Add(wx.StaticText(self, label=label + ':'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) ### Display. display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) display.BackgroundColour = wx.LIGHT_GREY self.displays[resource_name].append(display) self.readout_displays[resource_name] = display ### Connect display to GUI. readout_grid.Add(self.displays[resource_name][-1], flag=wx.ALIGN_RIGHT) # reverse our dictionary for key retrieval by item. self.inv_readout_displays = dict( (v, k) for k, v in self.readout_displays.iteritems()) # Controls. self.control_static_box = wx.StaticBox(self, label='Controls') self.control_box = wx.StaticBoxSizer(self.control_static_box, wx.VERTICAL) main_box.Add(self.control_box, flag=wx.EXPAND) ## Persistent Heater Switch. heater_box = wx.BoxSizer(wx.HORIZONTAL) self.control_box.Add(heater_box, flag=wx.ALIGN_RIGHT) heater_box.Add(wx.StaticText(self, label='Persistent Switch Heater:'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) self.heater_toggle = wx.ToggleButton(self, label='on/off', size=(100, -1)) initial_state = self.channel_subdevice.persistent_switch_heater self.heater_toggle.SetValue(True if initial_state == 1 else 0) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnHeaterToggle, self.heater_toggle) heater_box.Add(self.heater_toggle, flag=wx.ALIGN_RIGHT) ## Sweeper Control Box. sweeper_static_box = wx.StaticBox(self, label='Sweep') sweeper_box = wx.StaticBoxSizer(sweeper_static_box, wx.VERTICAL) self.control_box.Add(sweeper_box, flag=wx.EXPAND) sweep_buttons_box = wx.BoxSizer(wx.HORIZONTAL) sweeper_box.Add(sweep_buttons_box, flag=wx.CENTER | wx.ALL) ### Sweep buttons. sweep_buttons_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) sweep_buttons_box.Add(sweep_buttons_grid, flag=wx.CENTER | wx.ALL) sweepup_button = wx.Button(self, label='up') sweepzero_button = wx.Button(self, label='zero') sweepdown_button = wx.Button(self, label='down') sweeppause_button = wx.Button(self, label='pause') self.Bind(wx.EVT_BUTTON, self.OnSweepUp, sweepup_button) self.Bind(wx.EVT_BUTTON, self.OnSweepZero, sweepzero_button) self.Bind(wx.EVT_BUTTON, self.OnSweepDown, sweepdown_button) self.Bind(wx.EVT_BUTTON, self.OnSweepPause, sweeppause_button) sweep_buttons_grid.Add(sweepup_button) sweep_buttons_grid.Add(sweepzero_button) sweep_buttons_grid.Add(sweepdown_button) sweep_buttons_grid.Add(sweeppause_button) ### Current syncing. ####some space sync_button = wx.Button(self, label='sync currents') self.Bind(wx.EVT_BUTTON, self.OnSyncCurrents, sync_button) sweep_buttons_box.Add(sync_button, flag=wx.LEFT | wx.CENTER, border=20) ## Limits. limit_static_box = wx.StaticBox(self, label='Limits') limit_box = wx.StaticBoxSizer(limit_static_box, wx.VERTICAL) self.control_box.Add(limit_box, flag=wx.EXPAND) limits_grid = wx.FlexGridSizer(rows=2, cols=3, hgap=1) limits_grid.AddGrowableCol(1, 1) limit_box.Add(limits_grid, flag=wx.ALIGN_RIGHT) ### High Limit limits_grid.Add(wx.StaticText(self, label='High Limit:'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) set_hilim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetHighLimit, set_hilim_button) limits_grid.Add(set_hilim_button, flag=wx.ALIGN_RIGHT) self.hilim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.hilim_input, flag=wx.EXPAND) ### Low Limit limits_grid.Add(wx.StaticText(self, label='Low Limit:'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) set_lolim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetLowLimit, set_lolim_button) limits_grid.Add(set_lolim_button, flag=wx.ALIGN_RIGHT) self.lolim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.lolim_input) ## Rates. rates_static_box = wx.StaticBox(self, label='Rates') rates_box = wx.StaticBoxSizer(rates_static_box, wx.VERTICAL) self.control_box.Add(rates_box, flag=wx.EXPAND) ## used to have content of rates box all right aligned. rates_inner_box = wx.BoxSizer(wx.HORIZONTAL) rates_box.Add(rates_inner_box, flag=wx.ALIGN_RIGHT) menu_items = [] for resource in self.readout_list: if resource.startswith('rate_'): menu_items.append(resource) self.rates_menu = wx.ComboBox(self, choices=menu_items, style=wx.CB_READONLY) self.rates_menu.SetStringSelection(menu_items[0]) rates_inner_box.Add(self.rates_menu, flag=wx.ALIGN_RIGHT) set_rate_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetRate, set_rate_button) rates_inner_box.Add(set_rate_button, flag=wx.ALIGN_RIGHT) self.rate_input = wx.TextCtrl(self, size=(100, -1)) rates_inner_box.Add(self.rate_input, flag=wx.ALIGN_RIGHT) # Finish GUI building. self.SetSizerAndFit(main_box) # Default behaviour. ## start with... ### ...the threads locked out of acquisition. self.running_lock.acquire() ### ...controls disabled. self.RecursiveEnableSizer(self.control_box, False) # Threading. self.acqthreads = [] #TODO: implement with a normal thread instead, to avoid the use of a dummy call to a resource. guicallback = partial(wx.CallAfter, self.Update) self.guiupdatethread = AcquisitionThread( self.delay, guicallback, resource=self.channel_subdevice. resources['persistent_switch_heater'], running_lock=self.running_lock) self.acqthreads.append(self.guiupdatethread) self.guiupdatethread.daemon = True self.guiupdatethread.start()
class Model4GChannelPanel(wx.Panel): def __init__(self, parent, global_store, subdevice, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self.delay = Quantity(1.0, 's') # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() self.channel_subdevice = subdevice self.displays = {} self.control_state_displays = {} self.readout_displays = {} self.control_state_list = [ 'persistent_switch_heater', 'virt_sync_currents' ] self.readout_list = [ 'magnet_current', 'power_supply_current', 'persistent_switch_heater', 'high_limit', 'low_limit', 'sweep', 'rate_0', 'rate_1', 'rate_2', 'rate_3', 'rate_4', 'virt_sync_currents' ] self.measurement_resources = [] for name in self.readout_list: self.displays[name] = [] self.measurement_resources.append( (name, self.channel_subdevice.resources[name])) # A list to save acquired data to before outputting to GUI. self.measurements = [None] * len(self.measurement_resources) # Main Box. main_box = wx.BoxSizer(wx.VERTICAL) # Channel Header Box. channel_header_box = wx.BoxSizer(wx.HORIZONTAL) main_box.Add(channel_header_box, flag=wx.EXPAND) self.channel_button = wx.ToggleButton( self, label='Channel {0} Toggle'.format(self.channel_subdevice.channel)) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnChannelToggle, self.channel_button) self.channel_button.SetValue(False) channel_header_box.Add(self.channel_button) ## Control states. control_state_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) channel_header_box.Add((0, 0), 1, wx.EXPAND) channel_header_box.Add(control_state_grid, flag=wx.ALIGN_RIGHT, border=20) for control_state_name in self.control_state_list: control_state_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) control_state_display.BackgroundColour = wx.LIGHT_GREY control_state_grid.Add(control_state_display, flag=wx.ALIGN_RIGHT) self.displays[control_state_name].append(control_state_display) self.control_state_displays[ control_state_name] = control_state_display # reverse our dictionary for key retrieval by item. self.inv_control_state_displays = dict( (v, k) for k, v in self.control_state_displays.iteritems()) # Readouts. readout_static_box = wx.StaticBox(self, label='Readouts') readout_box = wx.StaticBoxSizer(readout_static_box, wx.VERTICAL) main_box.Add(readout_box, flag=wx.EXPAND, proportion=1) # readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=2, hgap=1) readout_grid = wx.FlexGridSizer( rows=len(self.readout_list), cols=3, hgap=1 ) #TODO: for debugging model4g GUI...replace when no longer needed. readout_box.Add(readout_grid, flag=wx.ALIGN_RIGHT) self.checkboxes = {} ## Setup individual labels + displays for resource_name in self.readout_list: ### Checkbox. #TODO: for debugging model4g GUI...remove when no longer needed. checkbox = wx.CheckBox(self) readout_grid.Add(checkbox, flag=wx.ALIGN_LEFT) self.checkboxes[resource_name] = checkbox ### Label. label = resource_name.replace('_', ' ').title() readout_grid.Add(wx.StaticText(self, label=label + ':'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) ### Display. display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) display.BackgroundColour = wx.LIGHT_GREY self.displays[resource_name].append(display) self.readout_displays[resource_name] = display ### Connect display to GUI. readout_grid.Add(self.displays[resource_name][-1], flag=wx.ALIGN_RIGHT) # reverse our dictionary for key retrieval by item. self.inv_readout_displays = dict( (v, k) for k, v in self.readout_displays.iteritems()) # Controls. self.control_static_box = wx.StaticBox(self, label='Controls') self.control_box = wx.StaticBoxSizer(self.control_static_box, wx.VERTICAL) main_box.Add(self.control_box, flag=wx.EXPAND) ## Persistent Heater Switch. heater_box = wx.BoxSizer(wx.HORIZONTAL) self.control_box.Add(heater_box, flag=wx.ALIGN_RIGHT) heater_box.Add(wx.StaticText(self, label='Persistent Switch Heater:'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) self.heater_toggle = wx.ToggleButton(self, label='on/off', size=(100, -1)) initial_state = self.channel_subdevice.persistent_switch_heater self.heater_toggle.SetValue(True if initial_state == 1 else 0) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnHeaterToggle, self.heater_toggle) heater_box.Add(self.heater_toggle, flag=wx.ALIGN_RIGHT) ## Sweeper Control Box. sweeper_static_box = wx.StaticBox(self, label='Sweep') sweeper_box = wx.StaticBoxSizer(sweeper_static_box, wx.VERTICAL) self.control_box.Add(sweeper_box, flag=wx.EXPAND) sweep_buttons_box = wx.BoxSizer(wx.HORIZONTAL) sweeper_box.Add(sweep_buttons_box, flag=wx.CENTER | wx.ALL) ### Sweep buttons. sweep_buttons_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) sweep_buttons_box.Add(sweep_buttons_grid, flag=wx.CENTER | wx.ALL) sweepup_button = wx.Button(self, label='up') sweepzero_button = wx.Button(self, label='zero') sweepdown_button = wx.Button(self, label='down') sweeppause_button = wx.Button(self, label='pause') self.Bind(wx.EVT_BUTTON, self.OnSweepUp, sweepup_button) self.Bind(wx.EVT_BUTTON, self.OnSweepZero, sweepzero_button) self.Bind(wx.EVT_BUTTON, self.OnSweepDown, sweepdown_button) self.Bind(wx.EVT_BUTTON, self.OnSweepPause, sweeppause_button) sweep_buttons_grid.Add(sweepup_button) sweep_buttons_grid.Add(sweepzero_button) sweep_buttons_grid.Add(sweepdown_button) sweep_buttons_grid.Add(sweeppause_button) ### Current syncing. ####some space sync_button = wx.Button(self, label='sync currents') self.Bind(wx.EVT_BUTTON, self.OnSyncCurrents, sync_button) sweep_buttons_box.Add(sync_button, flag=wx.LEFT | wx.CENTER, border=20) ## Limits. limit_static_box = wx.StaticBox(self, label='Limits') limit_box = wx.StaticBoxSizer(limit_static_box, wx.VERTICAL) self.control_box.Add(limit_box, flag=wx.EXPAND) limits_grid = wx.FlexGridSizer(rows=2, cols=3, hgap=1) limits_grid.AddGrowableCol(1, 1) limit_box.Add(limits_grid, flag=wx.ALIGN_RIGHT) ### High Limit limits_grid.Add(wx.StaticText(self, label='High Limit:'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) set_hilim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetHighLimit, set_hilim_button) limits_grid.Add(set_hilim_button, flag=wx.ALIGN_RIGHT) self.hilim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.hilim_input, flag=wx.EXPAND) ### Low Limit limits_grid.Add(wx.StaticText(self, label='Low Limit:'), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) set_lolim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetLowLimit, set_lolim_button) limits_grid.Add(set_lolim_button, flag=wx.ALIGN_RIGHT) self.lolim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.lolim_input) ## Rates. rates_static_box = wx.StaticBox(self, label='Rates') rates_box = wx.StaticBoxSizer(rates_static_box, wx.VERTICAL) self.control_box.Add(rates_box, flag=wx.EXPAND) ## used to have content of rates box all right aligned. rates_inner_box = wx.BoxSizer(wx.HORIZONTAL) rates_box.Add(rates_inner_box, flag=wx.ALIGN_RIGHT) menu_items = [] for resource in self.readout_list: if resource.startswith('rate_'): menu_items.append(resource) self.rates_menu = wx.ComboBox(self, choices=menu_items, style=wx.CB_READONLY) self.rates_menu.SetStringSelection(menu_items[0]) rates_inner_box.Add(self.rates_menu, flag=wx.ALIGN_RIGHT) set_rate_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetRate, set_rate_button) rates_inner_box.Add(set_rate_button, flag=wx.ALIGN_RIGHT) self.rate_input = wx.TextCtrl(self, size=(100, -1)) rates_inner_box.Add(self.rate_input, flag=wx.ALIGN_RIGHT) # Finish GUI building. self.SetSizerAndFit(main_box) # Default behaviour. ## start with... ### ...the threads locked out of acquisition. self.running_lock.acquire() ### ...controls disabled. self.RecursiveEnableSizer(self.control_box, False) # Threading. self.acqthreads = [] #TODO: implement with a normal thread instead, to avoid the use of a dummy call to a resource. guicallback = partial(wx.CallAfter, self.Update) self.guiupdatethread = AcquisitionThread( self.delay, guicallback, resource=self.channel_subdevice. resources['persistent_switch_heater'], running_lock=self.running_lock) self.acqthreads.append(self.guiupdatethread) self.guiupdatethread.daemon = True self.guiupdatethread.start() def __del__(self): try: # if self.channel_button.GetValue() == False: # self.running_lock.release() for thread in self.acqthreads: thread.resource = None thread.done = True thread.join() del thread # self.close() except Exception: pass def UpdateReadouts(self, resource_name, value): """ Update appropriate readouts with a new resource value. Also update button permissions. """ if resource_name in self.readout_displays.keys(): if self.checkboxes[ resource_name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. return for display in self.displays[resource_name]: #perform alterations to output based on where the resource is being readout in GUI. inv_cont_dict = self.inv_control_state_displays if display in inv_cont_dict.keys(): if inv_cont_dict[display] == 'persistent_switch_heater': if value == 'on': display.BackgroundColour = OK_BACKGROUND_COLOR elif value == 'off': display.BackgroundColour = wx.LIGHT_GREY value_readout = 'heater {0}'.format(value) elif inv_cont_dict[display] == 'virt_sync_currents': if value == 'synced': display.BackgroundColour = OK_BACKGROUND_COLOR elif value == 'not synced': display.BackgroundColour = wx.LIGHT_GREY #display as-is value_readout = value elif display in self.inv_readout_displays.keys(): #display as-is value_readout = value display.SetValue(str(value_readout)) # User Permissions # if currents don't match, heater toggle should be disabled. if display in inv_cont_dict.keys(): if inv_cont_dict[display] == 'virt_sync_currents': if value == 'synced': self.heater_toggle.Enable() else: self.heater_toggle.Disable() def Update(self, dummyval): """ Acquire data and then update the GUI with this data. Note: code taken from sweep controller. """ #this loop sets up separate threads for each resource. thrs = [] for i, (name, resource) in enumerate(self.measurement_resources): if resource is not None: def save_callback(value, i=i): self.measurements[i] = value callback = partial( wx.CallAfter, partial(self.read_resource, name, resource, save_callback)) thr = Thread(target=callback) thrs.append(thr) thr.daemon = True thr.start() for thr in thrs: thr.join() #this code saves the values to the GUI readouts. for i, (name, _) in enumerate(self.measurement_resources): self.UpdateReadouts(name, self.measurements[i]) def read_resource(self, name, resource, save_callback): """ Read a value from a resource and handle exceptions. Note: code taken from sweep controller. """ if name in self.readout_displays.keys(): if self.checkboxes[ name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. return try: value = resource.value except Exception as e: if self.resource_exception_handler is not None: self.resource_exception_handler(name, e, write=False) return save_callback(value) def OnChannelToggle(self, evt=None): toggle = self.channel_button.GetValue() if toggle == True: self.running_lock.release() elif toggle == False: self.running_lock.acquire() self.RecursiveEnableSizer(self.control_box, toggle) #permission defaults. self.heater_toggle.Disable() def OnSweepDown(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'down' def OnSweepUp(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'up' def OnSweepZero(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'zero' def OnSweepPause(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'pause' def OnSetRate(self, evt=None): try: Quantity(self.rate_input.GetValue()) except ValueError as e: MessageDialog(self, str(e), 'Invalid value').Show() return False range_id = self.rates_menu.GetCurrentSelection() resource = self.channel_subdevice.resources['rate_{0}'.format( range_id)] new_value = self.rate_input.GetValue() try: resource.value = resource.convert(new_value) except IncompatibleDimensions: MessageDialog( self, ValueError('Expected dimensions to match "{0}"'.format( resource.units))).Show() def OnHeaterToggle(self, evt=None): if self.heater_toggle.GetValue() == True: new_value = 'on' if self.heater_toggle.GetValue() == False: new_value = 'off' self.channel_subdevice.resources[ 'persistent_switch_heater'].value = new_value def OnSetHighLimit(self, evt=None): try: Quantity(self.hilim_input.GetValue()) except ValueError as e: MessageDialog(self, str(e), 'Invalid value').Show() return False new_value = self.hilim_input.GetValue() resource = self.channel_subdevice.resources['high_limit'] try: resource.value = resource.convert(new_value) except IncompatibleDimensions: MessageDialog( self, str( ValueError('Expected dimensions to match "{0}"'.format( resource.units)))).Show() def OnSetLowLimit(self, evt=None): try: Quantity(self.lolim_input.GetValue()) except ValueError as e: MessageDialog(self, str(e), 'Invalid value').Show() return False new_value = self.lolim_input.GetValue() resource = self.channel_subdevice.resources['low_limit'] try: resource.value = resource.convert(new_value) except IncompatibleDimensions: MessageDialog( self, ValueError('Expected dimensions to match "{0}"'.format( resource.units))).Show() def OnSyncCurrents(self, evt=None): self.channel_subdevice.resources['virt_sync_currents'].value = 'start' def close(self): """ Perform cleanup. """ # Ensure the threads exits. if self.channel_button.GetValue() == False: self.running_lock.release() for thread in self.acqthreads: thread.resource = None thread.done = True thread.join() del thread def RecursiveEnableSizer(self, wx_sizer, toggle): ''' Helper function that accesses all subwindows of a wxPython sizer, and enables or disables them based on toggle. ''' children = wx_sizer.GetChildren() for item in children: window = item.GetWindow() sizer = item.GetSizer() if sizer: #recurse self.RecursiveEnableSizer(sizer, toggle) elif window: window.Enable(toggle)
def __init__(self, parent, global_store, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self._measurement_resource_name = None # Defaults. self.plot_settings = PlotSettings() self.unit_conversion = 0 self.enabled = True self.capturing_data = False self.restart_live_view = False self.resource_backup = None # csv save file information self.save_path = '' self.defaultName = '' # Initialize recorded values. self.init_values() # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() # Plot and toolbar. display_box = wx.BoxSizer(wx.VERTICAL) ## Plot. if plot_available: self.plot = TwoDimensionalPlot(self, color='blue') display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) # self.plot.x_label = 'Waveform time (s)' # self.plot.y_label = 'History' else: display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) ## Controls. if plot_available: controls_box = wx.BoxSizer(wx.HORIZONTAL) display_box.Add(controls_box, flag=wx.CENTER | wx.ALL, border=5) ### Manual data export saveData_static_box = wx.StaticBox(self, label='Last trace') saveData_box = wx.StaticBoxSizer(saveData_static_box, wx.HORIZONTAL) controls_box.Add(saveData_box, flag=wx.CENTER) self.csv_button = wx.Button(self, label='Save to .csv') self.Bind(wx.EVT_BUTTON, self.onSave, self.csv_button) saveData_box.Add(self.csv_button, flag=wx.CENTER) ### Capture. capture_static_box = wx.StaticBox(self, label='Control') capture_box = wx.StaticBoxSizer(capture_static_box) controls_box.Add(capture_box, flag=wx.CENTER) self.run_button = wx.Button(self, label='Run') self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button) capture_box.Add(self.run_button, flag=wx.CENTER) self.pause_button = wx.Button(self, label='Pause') self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button) capture_box.Add(self.pause_button, flag=wx.CENTER) self.reset_button = wx.Button(self, label='Reset') self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) capture_box.Add(self.reset_button, flag=wx.CENTER) ### Settings. settings_static_box = wx.StaticBox(self, label='Settings') settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) controls_box.Add(settings_box, flag=wx.CENTER | wx.LEFT, border=10) self.plot_settings_button = wx.Button(self, label='Plot...') self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) settings_box.Add(self.plot_settings_button, flag=wx.CENTER) self.SetSizer(display_box) # Acquisition thread. callback = functools.partial(wx.CallAfter, self.add_values) self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback, running_lock=self.running_lock) self.acq_thread.daemon = True self.acq_thread.start() # Wait for a resource to begin capturing. self.OnPause() self.run_button.Disable() # Subscriptions. pub.subscribe(self.msg_resource, 'resource.added') pub.subscribe(self.msg_resource, 'resource.removed') pub.subscribe(self.msg_data_capture_start, 'data_capture.start') pub.subscribe(self.msg_data_capture_data, 'data_capture.data') pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop')
class ListLiveViewPanel(wx.Panel): """ A panel to display a live view plot of a list resource. """ def __init__(self, parent, global_store, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self._measurement_resource_name = None # Defaults. self.plot_settings = PlotSettings() self.unit_conversion = 0 self.enabled = True self.capturing_data = False self.restart_live_view = False self.resource_backup = None # csv save file information self.save_path = '' self.defaultName = '' # Initialize recorded values. self.init_values() # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() # Plot and toolbar. display_box = wx.BoxSizer(wx.VERTICAL) ## Plot. if plot_available: self.plot = TwoDimensionalPlot(self, color='blue') display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) # self.plot.x_label = 'Waveform time (s)' # self.plot.y_label = 'History' else: display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) ## Controls. if plot_available: controls_box = wx.BoxSizer(wx.HORIZONTAL) display_box.Add(controls_box, flag=wx.CENTER | wx.ALL, border=5) ### Manual data export saveData_static_box = wx.StaticBox(self, label='Last trace') saveData_box = wx.StaticBoxSizer(saveData_static_box, wx.HORIZONTAL) controls_box.Add(saveData_box, flag=wx.CENTER) self.csv_button = wx.Button(self, label='Save to .csv') self.Bind(wx.EVT_BUTTON, self.onSave, self.csv_button) saveData_box.Add(self.csv_button, flag=wx.CENTER) ### Capture. capture_static_box = wx.StaticBox(self, label='Control') capture_box = wx.StaticBoxSizer(capture_static_box) controls_box.Add(capture_box, flag=wx.CENTER) self.run_button = wx.Button(self, label='Run') self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button) capture_box.Add(self.run_button, flag=wx.CENTER) self.pause_button = wx.Button(self, label='Pause') self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button) capture_box.Add(self.pause_button, flag=wx.CENTER) self.reset_button = wx.Button(self, label='Reset') self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) capture_box.Add(self.reset_button, flag=wx.CENTER) ### Settings. settings_static_box = wx.StaticBox(self, label='Settings') settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) controls_box.Add(settings_box, flag=wx.CENTER | wx.LEFT, border=10) self.plot_settings_button = wx.Button(self, label='Plot...') self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) settings_box.Add(self.plot_settings_button, flag=wx.CENTER) self.SetSizer(display_box) # Acquisition thread. callback = functools.partial(wx.CallAfter, self.add_values) self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback, running_lock=self.running_lock) self.acq_thread.daemon = True self.acq_thread.start() # Wait for a resource to begin capturing. self.OnPause() self.run_button.Disable() # Subscriptions. pub.subscribe(self.msg_resource, 'resource.added') pub.subscribe(self.msg_resource, 'resource.removed') pub.subscribe(self.msg_data_capture_start, 'data_capture.start') pub.subscribe(self.msg_data_capture_data, 'data_capture.data') pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop') @property def running(self): return self.pause_button.Enabled @property def resource(self): return self.acq_thread.resource @resource.setter def resource(self, value): # Ignore unreadable resources. if value is not None and not value.readable: value = None if self.running: # Currently running. running = True self.OnPause() else: running = False self.acq_thread.resource = value self.run_button.Enable(value is not None) # Resume if applicable. if running: self.OnRun() @property def measurement_resource_name(self): if self._measurement_resource_name is None: return '' else: return self._measurement_resource_name @measurement_resource_name.setter def measurement_resource_name(self, value): if value: self._measurement_resource_name = value try: self.resource = self.global_store.resources[ self._measurement_resource_name] except KeyError: self.resource = None else: self._measurement_resource_name = None self.resource = None def init_values(self): """ Clear captured values. """ self._times = numpy.array([]) self._values = numpy.array([]) def update_plot(self): """ Redraw the plot. """ # Wait for at least one line. if not len(self._times) > 0: display_time = [0] display_values = [0] else: display_time = self._times display_values = self._values if self.plot_settings.update_x: self.plot.x_autoscale() if self.plot_settings.update_y: self.plot.y_autoscale() self.plot.x_data, self.plot.y_data = display_time, display_values def add_values(self, values): """ Update the plot with a new list of values. """ if not self.plot_settings.enabled: return # Extract the times and the data values. times, values = list(zip(*values)) # Update values. self._values = numpy.append(numpy.array([]), values) self._times = numpy.append(numpy.array([]), times) # Plot. self.update_plot() def close(self): """ Perform cleanup. """ # Unsubscriptions. pub.unsubscribe(self.msg_resource, 'resource.added') pub.unsubscribe(self.msg_resource, 'resource.removed') pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start') pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data') pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop') # Ensure the thread exits. self.acq_thread.resource = None self.acq_thread.done = True if not self.running: self.running_lock.release() self.acq_thread.join() del self.acq_thread def onSave(self, evt=None): """ save the latest trace to a manually named csv """ outArray = numpy.column_stack((self._times, self._values)) self.defaultName = 'listTrace_{0:04}-{1:02}-{2:02}_{3:02}-{4:02}-{5:02}.csv'.format( *localtime()) fdlg = wx.FileDialog(self, "Save last trace", "", self.defaultName, "CSV files(*.csv)|*.*", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if fdlg.ShowModal() == wx.ID_OK: self.save_path = fdlg.GetPath() if not (self.save_path.endswith(".csv")): self.save_path = self.save_path + ".csv" numpy.savetxt(self.save_path, outArray, delimiter=',') def OnRun(self, evt=None): """ Let the acquisition thread run. """ self.run_button.Disable() if self.acq_thread.resource is None: return self.running_lock.release() self.pause_button.Enable() def OnPause(self, evt=None): """ Block the acquisition thread. """ if not self.running: return self.running_lock.acquire() if self.acq_thread.resource is not None: self.run_button.Enable() self.pause_button.Disable() def OnReset(self, evt=None): self.init_values() self.update_plot() def OnPlotSettings(self, evt=None): """ Open the plot settings dialog. """ def ok_callback(dlg): self.plot_settings = dlg.GetValue() dlg = PlotSettingsDialog(self, ok_callback) dlg.SetValue(self.plot_settings) dlg.Show() def msg_resource(self, name, value=None): if self.measurement_resource_name is not None and name == self.measurement_resource_name: self.resource = value def msg_data_capture_start(self, name): if name == self.measurement_resource_name: if self.enabled: self.capturing_data = True def msg_data_capture_data(self, name, value): if name == self.measurement_resource_name: if self.capturing_data: self.add_values(value) def msg_data_capture_stop(self, name): if name == self.measurement_resource_name: if self.capturing_data: self.capturing_data = False
def __init__(self, parent, global_store, subdevice, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self.delay = Quantity(1.0, 's') # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() self.channel_subdevice = subdevice self.displays = {} self.control_state_displays = {} self.readout_displays = {} self.control_state_list = ['persistent_switch_heater','virt_sync_currents'] self.readout_list = [ 'magnet_current','power_supply_current', 'persistent_switch_heater', 'high_limit','low_limit','sweep', 'rate_0','rate_1','rate_2','rate_3','rate_4', 'virt_sync_currents'] self.measurement_resources = [] for name in self.readout_list: self.displays[name] = [] self.measurement_resources.append((name, self.channel_subdevice.resources[name])) # A list to save acquired data to before outputting to GUI. self.measurements = [None] * len(self.measurement_resources) # Main Box. main_box = wx.BoxSizer(wx.VERTICAL) # Channel Header Box. channel_header_box = wx.BoxSizer(wx.HORIZONTAL) main_box.Add(channel_header_box, flag=wx.EXPAND) self.channel_button = wx.ToggleButton(self, label='Channel {0} Toggle'.format(self.channel_subdevice.channel)) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnChannelToggle, self.channel_button) self.channel_button.SetValue(False) channel_header_box.Add(self.channel_button) ## Control states. control_state_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) channel_header_box.Add((0, 0), 1, wx.EXPAND) channel_header_box.Add(control_state_grid, flag=wx.ALIGN_RIGHT, border = 20) for control_state_name in self.control_state_list: control_state_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) control_state_display.BackgroundColour = wx.LIGHT_GREY control_state_grid.Add(control_state_display, flag=wx.ALIGN_RIGHT) self.displays[control_state_name].append(control_state_display) self.control_state_displays[control_state_name] = control_state_display # reverse our dictionary for key retrieval by item. self.inv_control_state_displays = dict((v,k) for k, v in self.control_state_displays.iteritems()) # Readouts. readout_static_box = wx.StaticBox(self, label = 'Readouts') readout_box = wx.StaticBoxSizer(readout_static_box, wx.VERTICAL) main_box.Add(readout_box, flag=wx.EXPAND, proportion=1) # readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=2, hgap=1) readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=3, hgap=1) #TODO: for debugging model4g GUI...replace when no longer needed. readout_box.Add(readout_grid, flag=wx.ALIGN_RIGHT) self.checkboxes = {} ## Setup individual labels + displays for resource_name in self.readout_list: ### Checkbox. #TODO: for debugging model4g GUI...remove when no longer needed. checkbox = wx.CheckBox(self) readout_grid.Add(checkbox, flag = wx.ALIGN_LEFT) self.checkboxes[resource_name] = checkbox ### Label. label = resource_name.replace('_',' ').title() readout_grid.Add(wx.StaticText(self, label=label + ':'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) ### Display. display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) display.BackgroundColour = wx.LIGHT_GREY self.displays[resource_name].append(display) self.readout_displays[resource_name] = display ### Connect display to GUI. readout_grid.Add(self.displays[resource_name][-1], flag=wx.ALIGN_RIGHT) # reverse our dictionary for key retrieval by item. self.inv_readout_displays = dict((v,k) for k, v in self.readout_displays.iteritems()) # Controls. self.control_static_box = wx.StaticBox(self, label='Controls') self.control_box=wx.StaticBoxSizer(self.control_static_box, wx.VERTICAL) main_box.Add(self.control_box, flag=wx.EXPAND) ## Persistent Heater Switch. heater_box = wx.BoxSizer(wx.HORIZONTAL) self.control_box.Add(heater_box, flag=wx.ALIGN_RIGHT) heater_box.Add(wx.StaticText(self, label='Persistent Switch Heater:'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) self.heater_toggle = wx.ToggleButton(self, label='on/off', size=(100,-1)) initial_state = self.channel_subdevice.persistent_switch_heater self.heater_toggle.SetValue(True if initial_state == 1 else 0) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnHeaterToggle, self.heater_toggle) heater_box.Add(self.heater_toggle,flag=wx.ALIGN_RIGHT) ## Sweeper Control Box. sweeper_static_box = wx.StaticBox(self, label = 'Sweep') sweeper_box = wx.StaticBoxSizer(sweeper_static_box, wx.VERTICAL) self.control_box.Add(sweeper_box, flag=wx.EXPAND) sweep_buttons_box = wx.BoxSizer(wx.HORIZONTAL) sweeper_box.Add(sweep_buttons_box, flag = wx.CENTER|wx.ALL) ### Sweep buttons. sweep_buttons_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) sweep_buttons_box.Add(sweep_buttons_grid, flag=wx.CENTER|wx.ALL) sweepup_button = wx.Button(self, label='up') sweepzero_button = wx.Button(self, label='zero') sweepdown_button = wx.Button(self, label='down') sweeppause_button = wx.Button(self, label='pause') self.Bind(wx.EVT_BUTTON, self.OnSweepUp, sweepup_button) self.Bind(wx.EVT_BUTTON, self.OnSweepZero, sweepzero_button) self.Bind(wx.EVT_BUTTON, self.OnSweepDown, sweepdown_button) self.Bind(wx.EVT_BUTTON, self.OnSweepPause, sweeppause_button) sweep_buttons_grid.Add(sweepup_button) sweep_buttons_grid.Add(sweepzero_button) sweep_buttons_grid.Add(sweepdown_button) sweep_buttons_grid.Add(sweeppause_button) ### Current syncing. ####some space sync_button = wx.Button(self, label='sync currents') self.Bind(wx.EVT_BUTTON, self.OnSyncCurrents, sync_button) sweep_buttons_box.Add(sync_button, flag=wx.LEFT|wx.CENTER, border = 20) ## Limits. limit_static_box = wx.StaticBox(self, label = 'Limits') limit_box = wx.StaticBoxSizer(limit_static_box, wx.VERTICAL) self.control_box.Add(limit_box,flag=wx.EXPAND) limits_grid = wx.FlexGridSizer(rows=2, cols=3, hgap=1) limits_grid.AddGrowableCol(1,1) limit_box.Add(limits_grid, flag=wx.ALIGN_RIGHT) ### High Limit limits_grid.Add(wx.StaticText(self, label='High Limit:'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) set_hilim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetHighLimit, set_hilim_button) limits_grid.Add(set_hilim_button,flag=wx.ALIGN_RIGHT) self.hilim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.hilim_input, flag=wx.EXPAND) ### Low Limit limits_grid.Add(wx.StaticText(self, label='Low Limit:'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) set_lolim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetLowLimit, set_lolim_button) limits_grid.Add(set_lolim_button,flag=wx.ALIGN_RIGHT) self.lolim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.lolim_input) ## Rates. rates_static_box = wx.StaticBox(self, label = 'Rates') rates_box = wx.StaticBoxSizer(rates_static_box, wx.VERTICAL) self.control_box.Add(rates_box,flag=wx.EXPAND) ## used to have content of rates box all right aligned. rates_inner_box = wx.BoxSizer(wx.HORIZONTAL) rates_box.Add(rates_inner_box, flag=wx.ALIGN_RIGHT) menu_items = [] for resource in self.readout_list: if resource.startswith('rate_'): menu_items.append(resource) self.rates_menu = wx.ComboBox(self,choices = menu_items, style=wx.CB_READONLY) self.rates_menu.SetStringSelection(menu_items[0]) rates_inner_box.Add(self.rates_menu, flag=wx.ALIGN_RIGHT) set_rate_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetRate, set_rate_button) rates_inner_box.Add(set_rate_button,flag=wx.ALIGN_RIGHT) self.rate_input = wx.TextCtrl(self, size=(100, -1)) rates_inner_box.Add(self.rate_input, flag=wx.ALIGN_RIGHT) # Finish GUI building. self.SetSizerAndFit(main_box) # Default behaviour. ## start with... ### ...the threads locked out of acquisition. self.running_lock.acquire() ### ...controls disabled. self.RecursiveEnableSizer(self.control_box,False) # Threading. self.acqthreads = [] #TODO: implement with a normal thread instead, to avoid the use of a dummy call to a resource. guicallback = partial(wx.CallAfter, self.Update) self.guiupdatethread = AcquisitionThread(self.delay, guicallback, resource=self.channel_subdevice.resources['persistent_switch_heater'], running_lock=self.running_lock) self.acqthreads.append(self.guiupdatethread) self.guiupdatethread.daemon = True self.guiupdatethread.start()
class Model4GChannelPanel(wx.Panel): def __init__(self, parent, global_store, subdevice, *args, **kwargs): wx.Panel.__init__(self, parent, *args, **kwargs) self.global_store = global_store self.delay = Quantity(1.0, 's') # This lock blocks the acquisition thread from acquiring. self.running_lock = Lock() self.channel_subdevice = subdevice self.displays = {} self.control_state_displays = {} self.readout_displays = {} self.control_state_list = ['persistent_switch_heater','virt_sync_currents'] self.readout_list = [ 'magnet_current','power_supply_current', 'persistent_switch_heater', 'high_limit','low_limit','sweep', 'rate_0','rate_1','rate_2','rate_3','rate_4', 'virt_sync_currents'] self.measurement_resources = [] for name in self.readout_list: self.displays[name] = [] self.measurement_resources.append((name, self.channel_subdevice.resources[name])) # A list to save acquired data to before outputting to GUI. self.measurements = [None] * len(self.measurement_resources) # Main Box. main_box = wx.BoxSizer(wx.VERTICAL) # Channel Header Box. channel_header_box = wx.BoxSizer(wx.HORIZONTAL) main_box.Add(channel_header_box, flag=wx.EXPAND) self.channel_button = wx.ToggleButton(self, label='Channel {0} Toggle'.format(self.channel_subdevice.channel)) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnChannelToggle, self.channel_button) self.channel_button.SetValue(False) channel_header_box.Add(self.channel_button) ## Control states. control_state_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) channel_header_box.Add((0, 0), 1, wx.EXPAND) channel_header_box.Add(control_state_grid, flag=wx.ALIGN_RIGHT, border = 20) for control_state_name in self.control_state_list: control_state_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) control_state_display.BackgroundColour = wx.LIGHT_GREY control_state_grid.Add(control_state_display, flag=wx.ALIGN_RIGHT) self.displays[control_state_name].append(control_state_display) self.control_state_displays[control_state_name] = control_state_display # reverse our dictionary for key retrieval by item. self.inv_control_state_displays = dict((v,k) for k, v in self.control_state_displays.iteritems()) # Readouts. readout_static_box = wx.StaticBox(self, label = 'Readouts') readout_box = wx.StaticBoxSizer(readout_static_box, wx.VERTICAL) main_box.Add(readout_box, flag=wx.EXPAND, proportion=1) # readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=2, hgap=1) readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=3, hgap=1) #TODO: for debugging model4g GUI...replace when no longer needed. readout_box.Add(readout_grid, flag=wx.ALIGN_RIGHT) self.checkboxes = {} ## Setup individual labels + displays for resource_name in self.readout_list: ### Checkbox. #TODO: for debugging model4g GUI...remove when no longer needed. checkbox = wx.CheckBox(self) readout_grid.Add(checkbox, flag = wx.ALIGN_LEFT) self.checkboxes[resource_name] = checkbox ### Label. label = resource_name.replace('_',' ').title() readout_grid.Add(wx.StaticText(self, label=label + ':'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) ### Display. display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) display.BackgroundColour = wx.LIGHT_GREY self.displays[resource_name].append(display) self.readout_displays[resource_name] = display ### Connect display to GUI. readout_grid.Add(self.displays[resource_name][-1], flag=wx.ALIGN_RIGHT) # reverse our dictionary for key retrieval by item. self.inv_readout_displays = dict((v,k) for k, v in self.readout_displays.iteritems()) # Controls. self.control_static_box = wx.StaticBox(self, label='Controls') self.control_box=wx.StaticBoxSizer(self.control_static_box, wx.VERTICAL) main_box.Add(self.control_box, flag=wx.EXPAND) ## Persistent Heater Switch. heater_box = wx.BoxSizer(wx.HORIZONTAL) self.control_box.Add(heater_box, flag=wx.ALIGN_RIGHT) heater_box.Add(wx.StaticText(self, label='Persistent Switch Heater:'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) self.heater_toggle = wx.ToggleButton(self, label='on/off', size=(100,-1)) initial_state = self.channel_subdevice.persistent_switch_heater self.heater_toggle.SetValue(True if initial_state == 1 else 0) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnHeaterToggle, self.heater_toggle) heater_box.Add(self.heater_toggle,flag=wx.ALIGN_RIGHT) ## Sweeper Control Box. sweeper_static_box = wx.StaticBox(self, label = 'Sweep') sweeper_box = wx.StaticBoxSizer(sweeper_static_box, wx.VERTICAL) self.control_box.Add(sweeper_box, flag=wx.EXPAND) sweep_buttons_box = wx.BoxSizer(wx.HORIZONTAL) sweeper_box.Add(sweep_buttons_box, flag = wx.CENTER|wx.ALL) ### Sweep buttons. sweep_buttons_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) sweep_buttons_box.Add(sweep_buttons_grid, flag=wx.CENTER|wx.ALL) sweepup_button = wx.Button(self, label='up') sweepzero_button = wx.Button(self, label='zero') sweepdown_button = wx.Button(self, label='down') sweeppause_button = wx.Button(self, label='pause') self.Bind(wx.EVT_BUTTON, self.OnSweepUp, sweepup_button) self.Bind(wx.EVT_BUTTON, self.OnSweepZero, sweepzero_button) self.Bind(wx.EVT_BUTTON, self.OnSweepDown, sweepdown_button) self.Bind(wx.EVT_BUTTON, self.OnSweepPause, sweeppause_button) sweep_buttons_grid.Add(sweepup_button) sweep_buttons_grid.Add(sweepzero_button) sweep_buttons_grid.Add(sweepdown_button) sweep_buttons_grid.Add(sweeppause_button) ### Current syncing. ####some space sync_button = wx.Button(self, label='sync currents') self.Bind(wx.EVT_BUTTON, self.OnSyncCurrents, sync_button) sweep_buttons_box.Add(sync_button, flag=wx.LEFT|wx.CENTER, border = 20) ## Limits. limit_static_box = wx.StaticBox(self, label = 'Limits') limit_box = wx.StaticBoxSizer(limit_static_box, wx.VERTICAL) self.control_box.Add(limit_box,flag=wx.EXPAND) limits_grid = wx.FlexGridSizer(rows=2, cols=3, hgap=1) limits_grid.AddGrowableCol(1,1) limit_box.Add(limits_grid, flag=wx.ALIGN_RIGHT) ### High Limit limits_grid.Add(wx.StaticText(self, label='High Limit:'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) set_hilim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetHighLimit, set_hilim_button) limits_grid.Add(set_hilim_button,flag=wx.ALIGN_RIGHT) self.hilim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.hilim_input, flag=wx.EXPAND) ### Low Limit limits_grid.Add(wx.StaticText(self, label='Low Limit:'), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) set_lolim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetLowLimit, set_lolim_button) limits_grid.Add(set_lolim_button,flag=wx.ALIGN_RIGHT) self.lolim_input = wx.TextCtrl(self, size=(100, -1)) limits_grid.Add(self.lolim_input) ## Rates. rates_static_box = wx.StaticBox(self, label = 'Rates') rates_box = wx.StaticBoxSizer(rates_static_box, wx.VERTICAL) self.control_box.Add(rates_box,flag=wx.EXPAND) ## used to have content of rates box all right aligned. rates_inner_box = wx.BoxSizer(wx.HORIZONTAL) rates_box.Add(rates_inner_box, flag=wx.ALIGN_RIGHT) menu_items = [] for resource in self.readout_list: if resource.startswith('rate_'): menu_items.append(resource) self.rates_menu = wx.ComboBox(self,choices = menu_items, style=wx.CB_READONLY) self.rates_menu.SetStringSelection(menu_items[0]) rates_inner_box.Add(self.rates_menu, flag=wx.ALIGN_RIGHT) set_rate_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnSetRate, set_rate_button) rates_inner_box.Add(set_rate_button,flag=wx.ALIGN_RIGHT) self.rate_input = wx.TextCtrl(self, size=(100, -1)) rates_inner_box.Add(self.rate_input, flag=wx.ALIGN_RIGHT) # Finish GUI building. self.SetSizerAndFit(main_box) # Default behaviour. ## start with... ### ...the threads locked out of acquisition. self.running_lock.acquire() ### ...controls disabled. self.RecursiveEnableSizer(self.control_box,False) # Threading. self.acqthreads = [] #TODO: implement with a normal thread instead, to avoid the use of a dummy call to a resource. guicallback = partial(wx.CallAfter, self.Update) self.guiupdatethread = AcquisitionThread(self.delay, guicallback, resource=self.channel_subdevice.resources['persistent_switch_heater'], running_lock=self.running_lock) self.acqthreads.append(self.guiupdatethread) self.guiupdatethread.daemon = True self.guiupdatethread.start() def __del__(self): try: # if self.channel_button.GetValue() == False: # self.running_lock.release() for thread in self.acqthreads: thread.resource = None thread.done = True thread.join() del thread # self.close() except Exception: pass def UpdateReadouts(self, resource_name, value): """ Update appropriate readouts with a new resource value. Also update button permissions. """ if resource_name in self.readout_displays.keys(): if self.checkboxes[resource_name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. return for display in self.displays[resource_name]: #perform alterations to output based on where the resource is being readout in GUI. inv_cont_dict = self.inv_control_state_displays if display in inv_cont_dict.keys(): if inv_cont_dict[display] == 'persistent_switch_heater': if value == 'on': display.BackgroundColour = OK_BACKGROUND_COLOR elif value == 'off': display.BackgroundColour = wx.LIGHT_GREY value_readout = 'heater {0}'.format(value) elif inv_cont_dict[display] == 'virt_sync_currents': if value == 'synced': display.BackgroundColour = OK_BACKGROUND_COLOR elif value == 'not synced': display.BackgroundColour = wx.LIGHT_GREY #display as-is value_readout = value elif display in self.inv_readout_displays.keys(): #display as-is value_readout = value display.SetValue(str(value_readout)) # User Permissions # if currents don't match, heater toggle should be disabled. if display in inv_cont_dict.keys(): if inv_cont_dict[display] == 'virt_sync_currents': if value == 'synced': self.heater_toggle.Enable() else: self.heater_toggle.Disable() def Update(self, dummyval): """ Acquire data and then update the GUI with this data. Note: code taken from sweep controller. """ #this loop sets up separate threads for each resource. thrs = [] for i, (name, resource) in enumerate(self.measurement_resources): if resource is not None: def save_callback(value, i=i): self.measurements[i] = value callback = partial(wx.CallAfter, partial(self.read_resource, name, resource, save_callback)) thr = Thread(target=callback) thrs.append(thr) thr.daemon = True thr.start() for thr in thrs: thr.join() #this code saves the values to the GUI readouts. for i, (name,_) in enumerate(self.measurement_resources): self.UpdateReadouts(name, self.measurements[i]) def read_resource(self, name, resource, save_callback): """ Read a value from a resource and handle exceptions. Note: code taken from sweep controller. """ if name in self.readout_displays.keys(): if self.checkboxes[name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. return try: value = resource.value except Exception as e: if self.resource_exception_handler is not None: self.resource_exception_handler(name, e, write=False) return save_callback(value) def OnChannelToggle(self, evt=None): toggle = self.channel_button.GetValue() if toggle == True: self.running_lock.release() elif toggle == False: self.running_lock.acquire() self.RecursiveEnableSizer(self.control_box, toggle) #permission defaults. self.heater_toggle.Disable() def OnSweepDown(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'down' def OnSweepUp(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'up' def OnSweepZero(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'zero' def OnSweepPause(self, evt=None): self.channel_subdevice.resources['sweep'].value = 'pause' def OnSetRate(self, evt=None): try: Quantity(self.rate_input.GetValue()) except ValueError as e: MessageDialog(self, str(e), 'Invalid value').Show() return False range_id = self.rates_menu.GetCurrentSelection() resource = self.channel_subdevice.resources['rate_{0}'.format(range_id)] new_value = self.rate_input.GetValue() try: resource.value = resource.convert(new_value) except IncompatibleDimensions: MessageDialog(self, ValueError('Expected dimensions to match "{0}"'.format(resource.units))).Show() def OnHeaterToggle(self, evt=None): if self.heater_toggle.GetValue() == True: new_value = 'on' if self.heater_toggle.GetValue() == False: new_value = 'off' self.channel_subdevice.resources['persistent_switch_heater'].value = new_value def OnSetHighLimit(self, evt=None): try: Quantity(self.hilim_input.GetValue()) except ValueError as e: MessageDialog(self, str(e), 'Invalid value').Show() return False new_value = self.hilim_input.GetValue() resource = self.channel_subdevice.resources['high_limit'] try: resource.value = resource.convert(new_value) except IncompatibleDimensions: MessageDialog(self, str(ValueError('Expected dimensions to match "{0}"'.format(resource.units)))).Show() def OnSetLowLimit(self, evt=None): try: Quantity(self.lolim_input.GetValue()) except ValueError as e: MessageDialog(self, str(e), 'Invalid value').Show() return False new_value = self.lolim_input.GetValue() resource = self.channel_subdevice.resources['low_limit'] try: resource.value = resource.convert(new_value) except IncompatibleDimensions: MessageDialog(self, ValueError('Expected dimensions to match "{0}"'.format(resource.units))).Show() def OnSyncCurrents(self, evt=None): self.channel_subdevice.resources['virt_sync_currents'].value = 'start' def close(self): """ Perform cleanup. """ # Ensure the threads exits. if self.channel_button.GetValue() == False: self.running_lock.release() for thread in self.acqthreads: thread.resource = None thread.done = True thread.join() del thread def RecursiveEnableSizer(self,wx_sizer, toggle): ''' Helper function that accesses all subwindows of a wxPython sizer, and enables or disables them based on toggle. ''' children = wx_sizer.GetChildren() for item in children: window = item.GetWindow() sizer = item.GetSizer() if sizer: #recurse self.RecursiveEnableSizer(sizer,toggle) elif window: window.Enable(toggle)