def __init__(self, figure, identifier, filepath): self.identifier = identifier loader = UiLoader() self.ui = loader.load(os.path.join(LYSE_DIR, 'plot_window.ui'), PlotWindow(self)) self.set_window_title(identifier, filepath) # figure.tight_layout() self.figure = figure self.canvas = figure.canvas self.navigation_toolbar = NavigationToolbar(self.canvas, self.ui) self.lock_action = self.navigation_toolbar.addAction( QtGui.QIcon(':qtutils/fugue/lock-unlock'), 'Lock axes', self.on_lock_axes_triggered) self.lock_action.setCheckable(True) self.lock_action.setToolTip('Lock axes') self.copy_to_clipboard_action = self.navigation_toolbar.addAction( QtGui.QIcon(':qtutils/fugue/clipboard--arrow'), 'Copy to clipboard', self.on_copy_to_clipboard_triggered) self.copy_to_clipboard_action.setToolTip('Copy to clipboard') self.copy_to_clipboard_action.setShortcut(QtGui.QKeySequence.Copy) self.ui.verticalLayout_canvas.addWidget(self.canvas) self.ui.verticalLayout_navigation_toolbar.addWidget( self.navigation_toolbar) self.lock_axes = False self.axis_limits = None self.update_window_size() self.ui.show()
def __init__(self, parent=None): super(SimplePythonEditor, self).__init__() loader = UiLoader() loader.registerCustomWidget(SimplePythonEditorTextField) self._ui = loader.load('simplepythoneditor.ui', self) # Connections self._ui.save_toolButton.clicked.connect(self.on_save) self._ui.sendFilename_toolButton.clicked.connect(self.on_filenameTrigger) self._ui.editor_tabWidget.tabCloseRequested.connect(self.closeTab) # restart the find-replace when anything is changed self._ui.find_text_lineEdit.textChanged.connect(self.restart_find_replace_and_search) self._ui.search_forward_toolButton.toggled.connect(self.restart_find_replace) self._ui.case_sensitive_checkBox.toggled.connect(self.restart_find_replace) self._ui.wrap_search_checkBox.toggled.connect(self.restart_find_replace) self._ui.whole_word_checkBox.toggled.connect(self.restart_find_replace) self._ui.do_search_pushButton.clicked.connect(self.on_find_replace) self._ui.goto_line_done_toolButton.clicked.connect(self.toggle_goto_line) self._ui.goto_line_spinBox.editingFinished.connect(self.on_goto_line) # Hide optoinal functions self._ui.goto_line_groupBox.hide() self._ui.find_replace_groupBox.hide()
def __init__(self, figure, identifier, filepath): loader = UiLoader() self.ui = loader.load('plot_window.ui', PlotWindow()) # Tell Windows how to handle our windows in the the taskbar, making pinning work properly and stuff: if os.name == 'nt': self.ui.newWindow.connect(set_win_appusermodel) self.set_window_title(identifier, filepath) # figure.tight_layout() self.figure = figure self.canvas = FigureCanvas(figure) self.navigation_toolbar = NavigationToolbar(self.canvas, self.ui) self.lock_action = self.navigation_toolbar.addAction( QtGui.QIcon(':qtutils/fugue/lock-unlock'), 'Lock axes', self.on_lock_axes_triggered) self.lock_action.setCheckable(True) self.lock_action.setToolTip('Lock axes') self.ui.verticalLayout_canvas.addWidget(self.canvas) self.ui.verticalLayout_navigation_toolbar.addWidget(self.navigation_toolbar) self.lock_axes = False self.axis_limits = None self.update_window_size() self.ui.show()
def __init__(self, figure, identifier, filepath): loader = UiLoader() self.ui = loader.load('plot_window.ui', PlotWindow()) # Tell Windows how to handle our windows in the the taskbar, making pinning work properly and stuff: if os.name == 'nt': self.ui.newWindow.connect(set_win_appusermodel) self.set_window_title(identifier, filepath) # figure.tight_layout() self.figure = figure self.canvas = FigureCanvas(figure) self.navigation_toolbar = NavigationToolbar(self.canvas, self.ui) self.lock_action = self.navigation_toolbar.addAction( QtGui.QIcon(':qtutils/fugue/lock-unlock'), 'Lock axes', self.on_lock_axes_triggered) self.lock_action.setCheckable(True) self.lock_action.setToolTip('Lock axes') self.ui.verticalLayout_canvas.addWidget(self.canvas) self.ui.verticalLayout_navigation_toolbar.addWidget( self.navigation_toolbar) self.lock_axes = False self.axis_limits = None self.update_window_size() self.ui.show()
def initialise_GUI(self): layout = self.get_tab_layout() ui_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'blacs_tab.ui') attributes_ui_filepath = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'attributes_dialog.ui') self.ui = UiLoader().load(ui_filepath) self.ui.pushButton_continuous.clicked.connect( self.on_continuous_clicked) self.ui.pushButton_stop.clicked.connect(self.on_stop_clicked) self.ui.pushButton_snap.clicked.connect(self.on_snap_clicked) self.ui.pushButton_attributes.clicked.connect( self.on_attributes_clicked) self.ui.toolButton_nomax.clicked.connect(self.on_reset_rate_clicked) self.attributes_dialog = UiLoader().load(attributes_ui_filepath) self.attributes_dialog.setParent(self.ui.parent()) self.attributes_dialog.setWindowFlags(QtCore.Qt.Tool) self.attributes_dialog.setWindowTitle("{} attributes".format( self.device_name)) self.attributes_dialog.pushButton_copy.clicked.connect( self.on_copy_clicked) self.attributes_dialog.comboBox.currentIndexChanged.connect( self.on_attr_visibility_level_changed) self.ui.doubleSpinBox_maxrate.valueChanged.connect( self.on_max_rate_changed) layout.addWidget(self.ui) self.image = pg.ImageView() self.image.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.ui.horizontalLayout.addWidget(self.image) self.ui.pushButton_stop.hide() self.ui.doubleSpinBox_maxrate.hide() self.ui.toolButton_nomax.hide() self.ui.label_fps.hide() # Ensure the GUI reserves space for these widgets even if they are hidden. # This prevents the GUI jumping around when buttons are clicked: for widget in [ self.ui.pushButton_stop, self.ui.doubleSpinBox_maxrate, self.ui.toolButton_nomax, ]: size_policy = widget.sizePolicy() if hasattr(size_policy, 'setRetainSizeWhenHidden'): # Qt 5.2+ only size_policy.setRetainSizeWhenHidden(True) widget.setSizePolicy(size_policy) # Start the image receiver ZMQ server: self.image_receiver = ImageReceiver(self.image, self.ui.label_fps) self.acquiring = False self.supports_smart_programming(self.use_smart_programming)
def plugin_setup_complete(self, BLACS): self.BLACS = BLACS self.ui = UiLoader().load(os.path.join(PLUGINS_DIR, module, 'controls.ui')) self.bar = self.ui.bar self.style = QtWidgets.QStyleFactory.create('Fusion') if self.style is None: # If we're on Qt4, fall back to Plastique style: self.style = QtWidgets.QStyleFactory.create('Plastique') if self.style is None: # Not sure what's up, but fall back to app's default style: self.style = QtWidgets.QApplication.style() self.bar.setStyle(self.style) self.bar.setMaximum(BAR_MAX) self.bar.setAlignment(QtCore.Qt.AlignCenter) # Add our controls to the BLACS gui: BLACS['ui'].queue_status_verticalLayout.insertWidget(0, self.ui) # We need to know the name of the master pseudoclock so we can look up # the duration of each shot: self.master_pseudoclock = self.BLACS['experiment_queue'].master_pseudoclock # Check if the wait monitor device, if any, supports wait completed events: with h5py.File(self.BLACS['connection_table_h5file'], 'r') as f: if 'waits' in f: acq_device = f['waits'].attrs['wait_monitor_acquisition_device'] acq_device = _ensure_str(acq_device) if acq_device: props = properties.get(f, acq_device, 'connection_table_properties') if props.get('wait_monitor_supports_wait_completed_events', False): self.wait_completed_events_supported = True self.ui.wait_warning.hide()
def plugin_setup_complete(self, BLACS): self.BLACS = BLACS # Add our controls to the BLACS UI: self.ui = UiLoader().load(os.path.join(PLUGINS_DIR, module, 'controls.ui')) BLACS['ui'].queue_controls_frame.layout().addWidget(self.ui) # Restore settings to the GUI controls: self.ui.spinBox.setValue(self.n_shots_to_keep) # Connect signals: self.ui.spinBox.valueChanged.connect(self.on_spinbox_value_changed) self.ui.reset_button.clicked.connect(self.on_reset_button_clicked) BLACS['ui'].queue_repeat_button.toggled.connect(self.ui.setEnabled) # Our control is only enabled when repeat mode is active: self.ui.setEnabled(BLACS['ui'].queue_repeat_button.isChecked())
def create_dialog(self,notebook): ui = UiLoader().load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'general.ui')) # get the widgets! self.widgets = {} for var in self.var_list: self.widgets[var[0]] = getattr(ui,var[0]) getattr(self.widgets[var[0]],var[3])(self.data[var[0]]) return ui,None
def create_dialog(self, notebook): ui = UiLoader().load(os.path.join(PLUGINS_DIR, 'general', 'general.ui')) # get the widgets! self.widgets = {} for var in self.var_list: self.widgets[var[0]] = getattr(ui, var[0]) getattr(self.widgets[var[0]], var[3])(self.data[var[0]]) return ui, None
def initialise_GUI(self): layout = self.get_tab_layout() ui_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'camera.ui') self.ui = UiLoader().load(ui_filepath) layout.addWidget(self.ui) port = int(self.settings['connection_table'].find_by_name(self.settings["device_name"]).BLACS_connection) self.ui.port_label.setText(str(port)) self.ui.check_connectivity_pushButton.setIcon(QIcon(':/qtutils/fugue/arrow-circle')) self.ui.host_lineEdit.returnPressed.connect(self.update_settings_and_check_connectivity) self.ui.use_zmq_checkBox.toggled.connect(self.update_settings_and_check_connectivity) self.ui.check_connectivity_pushButton.clicked.connect(self.update_settings_and_check_connectivity)
def initialise_GUI(self): layout = self.get_tab_layout() ui_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'camera.ui') self.ui = UiLoader().load(ui_filepath) layout.addWidget(self.ui) self.ui.host_lineEdit.setText("localhost") port = int(self.settings['connection_table'].find_by_name(self.settings["device_name"]).BLACS_connection) self.ui.port_label.setText(str(port)) self.ui.use_zmq_checkBox.setCheckState(True) self.ui.is_responding.setVisible(False) self.ui.is_not_responding.setVisible(False) self.ui.host_lineEdit.returnPressed.connect(self.update_settings_and_check_connectivity) self.ui.use_zmq_checkBox.toggled.connect(self.update_settings_and_check_connectivity) self.ui.check_connectivity_pushButton.clicked.connect(self.update_settings_and_check_connectivity)
def initialise_GUI(self): # Create DDS Output objects dds_prop = { 'dds 0': { 'freq': { 'base_unit': 'Hz', 'min': 0.5e9, 'max': 10e9, 'step': 1e6, 'decimals': 3 }, 'gate': {} } } # Create the output objects self.create_dds_outputs(dds_prop) # Create widgets for output objects dds_widgets, ao_widgets, do_widgets = self.auto_create_widgets() # and auto place the widgets in the UI self.auto_place_widgets(("DDS Outputs", dds_widgets)) self.status_ui = UiLoader().load( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'phasematrixquicksyn.ui')) self.get_tab_layout().addWidget(self.status_ui) self.status_ui.ref_button.clicked.connect(self.update_reference_out) self.status_ui.blanking_button.clicked.connect(self.update_blanking) self.status_ui.lock_recovery_button.clicked.connect( self.update_lock_recovery) # Store the COM port to be used self.address = str(self.settings['connection_table'].find_by_name( self.settings["device_name"]).BLACS_connection) # Create and set the primary worker self.create_worker("main_worker", QuickSynWorker, {'address': self.address}) self.primary_worker = "main_worker" # Set the capabilities of this device self.supports_remote_value_check(True) self.supports_smart_programming(False) self.statemachine_timeout_add(2000, self.status_monitor)
def initialise_GUI(self): """ Define output capabilities of RP and generate UI for manual control of the RP through the front panel. """ # Add widgets from 'red_pitaya_pyrpl.ui' # For editting widgets use Qt designer layout = self.get_tab_layout() ui_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'red_pitaya_pyrpl.ui') self.ui = UiLoader().load(ui_filepath) layout.addWidget(self.ui) # Interact with widgets self.ui.waveform0_box.currentIndexChanged.connect( lambda: self.update_attributes('waveform')) self.ui.amplitude0_spinbox.editingFinished.connect( lambda: self.update_attributes('amplitude')) self.ui.offset0_spinbox.editingFinished.connect( lambda: self.update_attributes('offset')) self.ui.frequency0_spinbox.editingFinished.connect( lambda: self.update_attributes('frequency'))
def initialise_GUI(self): """Loads the standard STBstatus.ui widget and sets the worker defined in __init__""" # load the status_ui for the STB register self.status_ui = UiLoader().load(self.STBui_path) self.get_tab_layout().addWidget(self.status_ui) # generate the dictionaries self.status_bits = [ 'bit 0', 'bit 1', 'bit 2', 'bit 3', 'bit 4', 'bit 5', 'bit 6', 'bit 7' ] self.bit_labels_widgets = {} self.bit_values_widgets = {} self.status = {} for bit in self.status_bits: self.status[bit] = False self.bit_values_widgets[bit] = getattr( self.status_ui, 'status_{0:s}'.format(bit.split()[1])) self.bit_labels_widgets[bit] = getattr( self.status_ui, 'status_label_{0:s}'.format(bit.split()[1])) # Dynamically update status bits with correct names for key in self.status_bits: self.bit_labels_widgets[key].setText(self.status_byte_labels[key]) self.status_ui.clear_button.clicked.connect(self.send_clear) # Store the VISA name to be used self.address = str(self.settings['connection_table'].find_by_name( self.settings["device_name"]).BLACS_connection) #self.device_name = str(self.settings['device_name']) # Create and set the primary worker self.create_worker("main_worker", self.device_worker_class, { 'address': self.address, }) self.primary_worker = "main_worker"
def initialise_GUI(self): do_prop = {} for i in range( self.num_DO ): # 12 is the maximum number of flags on this device (some only have 4 though) do_prop['flag %d' % i] = {} # Create the output objects self.create_digital_outputs(do_prop) # Create widgets for output objects dds_widgets, ao_widgets, do_widgets = self.auto_create_widgets() # Define the sort function for the digital outputs def sort(channel): flag = channel.replace('flag ', '') flag = int(flag) return '%02d' % (flag) # and auto place the widgets in the UI self.auto_place_widgets(("Flags", do_widgets, sort)) # Store the board number to be used connection_object = self.settings['connection_table'].find_by_name( self.device_name) self.board_number = int(connection_object.BLACS_connection) # And which scheme we're using for buffered output programming and triggering: # (default values for backward compat with old connection tables) self.programming_scheme = connection_object.properties.get( 'programming_scheme', 'pb_start/BRANCH') # Create and set the primary worker self.create_worker( "main_worker", self.device_worker_class, { 'board_number': self.board_number, 'num_DO': self.num_DO, 'programming_scheme': self.programming_scheme }) self.primary_worker = "main_worker" # Set the capabilities of this device self.supports_smart_programming(True) #### adding status widgets from PulseBlaster.py # Load status monitor (and start/stop/reset buttons) UI ui = UiLoader().load( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pulseblaster.ui')) self.get_tab_layout().addWidget(ui) # Connect signals for buttons ui.start_button.clicked.connect(self.start) ui.stop_button.clicked.connect(self.stop) ui.reset_button.clicked.connect(self.reset) # initialise dictionaries of data to display and get references to the QLabels self.status_states = ['stopped', 'reset', 'running', 'waiting'] self.status = {} self.status_widgets = {} for state in self.status_states: self.status[state] = False self.status_widgets[state] = getattr(ui, '%s_label' % state) # Status monitor timout self.statemachine_timeout_add(2000, self.status_monitor)
def add_notification(self, notification_class): if notification_class in self._notifications: return False try: # instantiate the notification class # TODO: Do we need to pass anything in here? self._notifications[notification_class] = notification_class( self._BLACS) # get the widget widget = self._notifications[notification_class].get_widget() # get details on whether the widget can be closed or hidden properties = self._notifications[ notification_class].get_properties() # Function shortcuts show_func = lambda callback=False: self.show_notification( notification_class, callback) hide_func = lambda callback=False: self.minimize_notification( notification_class, callback) close_func = lambda callback=False: self.close_notification( notification_class, callback) get_state = lambda: self.get_state(notification_class) # create layout/widget with appropriate buttons and the widget from the notification class ui = UiLoader().load( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'notification_widget.ui')) ui.hide_button.setVisible(bool(properties['can_hide'])) ui.hide_button.clicked.connect(lambda: hide_func(True)) ui.close_button.setVisible(bool(properties['can_close'])) ui.close_button.clicked.connect(lambda: close_func(True)) ui.widget_layout.addWidget(widget) #ui.hide() #save callbacks if 'closed_callback' in properties and callable( properties['closed_callback']): self._closed_callbacks[notification_class] = properties[ 'closed_callback'] elif 'closed_callback' in properties: logger.warning( '"Closed" callback for notification class %s is not callable (and will not be called when the notification is closed. The callback specified was %s.' % (notification_class, properties['closed_callback'])) if 'hidden_callback' in properties and callable( properties['hidden_callback']): self._hidden_callbacks[notification_class] = properties[ 'hidden_callback'] elif 'hidden_callback' in properties: logger.warning( '"Hidden" callback for notification class %s is not callable (and will not be called when the notification is closed. The callback specified was %s.' % (notification_class, properties['hidden_callback'])) if 'shown_callback' in properties and callable( properties['shown_callback']): self._shown_callbacks[notification_class] = properties[ 'shown_callback'] elif 'shown_callback' in properties: logger.warning( '"Shown" callback for notification class %s is not callable (and will not be called when the notification is closed. The callback specified was %s.' % (notification_class, properties['shown_callback'])) #TODO: Make the minimized widget ui2 = UiLoader().load( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'notification_minimized_widget.ui')) #ui2.hide() if not hasattr(self._notifications[notification_class], 'name'): self._notifications[ notification_class].name = notification_class.__name__ ui2.name.setText(self._notifications[notification_class].name) ui2.show_button.setVisible(bool( properties['can_hide'])) #If you can hide, you can also show ui2.show_button.clicked.connect(lambda: show_func(True)) ui2.close_button.setVisible(bool(properties['can_close'])) ui2.close_button.clicked.connect(lambda: close_func(True)) # pass the show/hide/close functions to the notfication class self._widgets[notification_class] = ui self._minimized_widgets[notification_class] = ui2 self._notifications[notification_class].set_functions( show_func, hide_func, close_func, get_state) except: logger.exception('Failed to instantiate Notification class %s.' % notification_class) # Cleanup # TODO: cleanup a little more if notification_class in self._notifications: del self._notifications[notification_class] return False # add the widgets, initially hidden ui.setVisible(False) ui2.setVisible(False) self._BLACS['ui'].notifications.insertWidget(1, ui) self._BLACS['ui'].notifications_minimized.insertWidget(0, ui2) return True
def check_remote_values(self): self._last_remote_values = yield (self.queue_work( self._primary_worker, 'check_remote_values')) for worker in self._secondary_workers: if self._last_remote_values: returned_results = yield (self.queue_work( worker, 'check_remote_values')) self._last_remote_values.update(returned_results) # compare to current front panel values and prompt the user if they don't match # We compare to the last_programmed values so that it doesn't get confused if the user has changed the value on the front panel # and the program_manual command is still queued up # If no results were returned, raise an exception so that we don't keep calling this function over and over again, # filling up the text box with the same error, eventually consuming all CPU/memory of the PC if not self._last_remote_values or type( self._last_remote_values) != type({}): raise Exception( 'Failed to get remote values from device. Is it still connected?' ) # A variable to indicate if any of the channels have a changed value overall_changed = False # A place to store radio buttons in self._changed_radio_buttons = {} # Clean up the previously used layout while not self._ui.changed_layout.isEmpty(): item = self._ui.changed_layout.itemAt(0) # This is the only way I could make the widget actually be removed. # using layout.removeItem/removeWidget causes the layout to still draw the old item in its original space, and # then draw new items over the top of the old. Very odd behaviour, could be a windows 8 bug I suppose! item.widget().setParent(None) #TODO: somehow maintain the state of the radio buttons for specific channels between refreshes of this changed dialog. # TODO: Use the proper sort algorithm as defined for placing widgets to order this prompt # We expect a dictionary of channel:value pairs for channel in sorted(self._last_remote_values): remote_value = self._last_remote_values[channel] if channel not in self._last_programmed_values: raise RuntimeError( 'The worker function check_remote_values for device %s is returning data for channel %s but the BLACS tab is not programmed to handle this channel' % (self.device_name, channel)) # A variable to indicate if this channel has changed changed = False if channel in self._DDS: front_value = self._last_programmed_values[channel] # format the entries for the DDS object correctly, then compare front_values_formatted = {} remote_values_formatted = {} for sub_chnl in front_value: if sub_chnl not in remote_value: raise RuntimeError( 'The worker function check_remote_values has not returned data for the sub-channel %s in channel %s' % (sub_chnl, channel)) if sub_chnl == 'gate': front_values_formatted[sub_chnl] = str( bool(int(front_value[sub_chnl]))) remote_values_formatted[sub_chnl] = str( bool(int(remote_value[sub_chnl]))) else: decimals = self._DDS[channel].__getattribute__( sub_chnl)._decimals front_values_formatted[sub_chnl] = ( "%." + str(decimals) + "f") % front_value[sub_chnl] remote_values_formatted[sub_chnl] = ( "%." + str(decimals) + "f") % remote_value[sub_chnl] if front_values_formatted[ sub_chnl] != remote_values_formatted[sub_chnl]: changed = True if changed: ui = UiLoader().load( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'tab_value_changed_dds.ui')) ui.channel_label.setText(self._DDS[channel].name) for sub_chnl in front_value: ui.__getattribute__( 'front_%s_value' % sub_chnl).setText( front_values_formatted[sub_chnl]) ui.__getattribute__( 'remote_%s_value' % sub_chnl).setText( remote_values_formatted[sub_chnl]) # Hide unused sub_channels of this DDS for sub_chnl in self._DDS[channel].get_unused_subchnl_list( ): ui.__getattribute__('front_%s_value' % sub_chnl).setVisible(False) ui.__getattribute__('front_%s_label' % sub_chnl).setVisible(False) ui.__getattribute__('remote_%s_value' % sub_chnl).setVisible(False) ui.__getattribute__('remote_%s_label' % sub_chnl).setVisible(False) elif channel in self._DO: # This is an easy case! front_value = str( bool(int(self._last_programmed_values[channel]))) remote_value = str(bool(int(remote_value))) if front_value != remote_value: changed = True ui = UiLoader().load( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'tab_value_changed.ui')) ui.channel_label.setText(self._DO[channel].name) ui.front_value.setText(front_value) ui.remote_value.setText(remote_value) elif channel in self._AO: # A intermediately complicated case! front_value = ("%." + str(self._AO[channel]._decimals) + "f") % self._last_programmed_values[channel] remote_value = ("%." + str(self._AO[channel]._decimals) + "f") % remote_value if front_value != remote_value: changed = True ui = UiLoader().load( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'tab_value_changed.ui')) ui.channel_label.setText(self._AO[channel].name) ui.front_value.setText(front_value) ui.remote_value.setText(remote_value) else: raise RuntimeError( 'device_base_class.py is not programmed to handle channel types other than DDS, AO and DO in check_remote_values' ) if changed: overall_changed = True # Add the changed widget for this channel to a layout! self._ui.changed_layout.addWidget(ui) # save the radio buttons so that we can access their state later! self._changed_radio_buttons[channel] = ui.use_remote_values if overall_changed: # TODO: Disable all widgets for this device, including virtual device widgets...how do I do that????? # Probably need to add a disable/enable method to analog/digital/DDS widgets that disables the widget and is orthogonal to the lock/unlock system # Should probably set a tooltip on the widgets too explaining why they are disabled! # self._device_widget.setSensitive(False) # show the remote_values_change dialog self._changed_widget.show() # Add an "apply" button and link to on_resolve_value_inconsistency button = QPushButton("Apply") button.clicked.connect(self.on_resolve_value_inconsistency) self._ui.changed_layout.addWidget(button)
def add_notification(self, notification_class): if notification_class in self._notifications: return False try: # instantiate the notification class # TODO: Do we need to pass anything in here? self._notifications[notification_class] = notification_class(self._BLACS) # get the widget widget = self._notifications[notification_class].get_widget() # get details on whether the widget can be closed or hidden properties = self._notifications[notification_class].get_properties() # Function shortcuts show_func = lambda callback=False: self.show_notification(notification_class, callback) hide_func = lambda callback=False: self.minimize_notification(notification_class, callback) close_func = lambda callback=False: self.close_notification(notification_class, callback) get_state = lambda: self.get_state(notification_class) # create layout/widget with appropriate buttons and the widget from the notification class ui = UiLoader().load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'notification_widget.ui')) ui.hide_button.setVisible(bool(properties['can_hide'])) ui.hide_button.clicked.connect(lambda: hide_func(True)) ui.close_button.setVisible(bool(properties['can_close'])) ui.close_button.clicked.connect(lambda: close_func(True)) ui.widget_layout.addWidget(widget) #ui.hide() #save callbacks if 'closed_callback' in properties and callable(properties['closed_callback']): self._closed_callbacks[notification_class] = properties['closed_callback'] elif 'closed_callback' in properties: logger.warning('"Closed" callback for notification class %s is not callable (and will not be called when the notification is closed. The callback specified was %s.'%(notification_class,properties['closed_callback'])) if 'hidden_callback' in properties and callable(properties['hidden_callback']): self._hidden_callbacks[notification_class] = properties['hidden_callback'] elif 'hidden_callback' in properties: logger.warning('"Hidden" callback for notification class %s is not callable (and will not be called when the notification is closed. The callback specified was %s.'%(notification_class,properties['hidden_callback'])) if 'shown_callback' in properties and callable(properties['shown_callback']): self._shown_callbacks[notification_class] = properties['shown_callback'] elif 'shown_callback' in properties: logger.warning('"Shown" callback for notification class %s is not callable (and will not be called when the notification is closed. The callback specified was %s.'%(notification_class,properties['shown_callback'])) #TODO: Make the minimized widget ui2 = UiLoader().load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'notification_minimized_widget.ui')) #ui2.hide() if not hasattr(self._notifications[notification_class], 'name'): self._notifications[notification_class].name = notification_class.__name__ ui2.name.setText(self._notifications[notification_class].name) ui2.show_button.setVisible(bool(properties['can_hide'])) #If you can hide, you can also show ui2.show_button.clicked.connect(lambda: show_func(True)) ui2.close_button.setVisible(bool(properties['can_close'])) ui2.close_button.clicked.connect(lambda: close_func(True)) # pass the show/hide/close functions to the notfication class self._widgets[notification_class] = ui self._minimized_widgets[notification_class] = ui2 self._notifications[notification_class].set_functions(show_func,hide_func,close_func,get_state) except: logger.exception('Failed to instantiate Notification class %s.'%notification_class) # Cleanup # TODO: cleanup a little more if notification_class in self._notifications: del self._notifications[notification_class] return False # add the widgets, initially hidden ui.setVisible(False) ui2.setVisible(False) self._BLACS['ui'].notifications.insertWidget(1,ui) self._BLACS['ui'].notifications_minimized.insertWidget(0,ui2) return True
def check_remote_values(self): self._last_remote_values = yield(self.queue_work(self._primary_worker,'check_remote_values')) for worker in self._secondary_workers: if self._last_remote_values: returned_results = yield(self.queue_work(worker,'check_remote_values')) self._last_remote_values.update(returned_results) # compare to current front panel values and prompt the user if they don't match # We compare to the last_programmed values so that it doesn't get confused if the user has changed the value on the front panel # and the program_manual command is still queued up # If no results were returned, raise an exception so that we don't keep calling this function over and over again, # filling up the text box with the same error, eventually consuming all CPU/memory of the PC if not self._last_remote_values or type(self._last_remote_values) != type({}): raise Exception('Failed to get remote values from device. Is it still connected?') # A variable to indicate if any of the channels have a changed value overall_changed = False # A place to store radio buttons in self._changed_radio_buttons = {} # Clean up the previously used layout while not self._ui.changed_layout.isEmpty(): item = self._ui.changed_layout.itemAt(0) # This is the only way I could make the widget actually be removed. # using layout.removeItem/removeWidget causes the layout to still draw the old item in its original space, and # then draw new items over the top of the old. Very odd behaviour, could be a windows 8 bug I suppose! item.widget().setParent(None) #TODO: somehow maintain the state of the radio buttons for specific channels between refreshes of this changed dialog. # TODO: Use the proper sort algorithm as defined for placing widgets to order this prompt # We expect a dictionary of channel:value pairs for channel in sorted(self._last_remote_values): remote_value = self._last_remote_values[channel] if channel not in self._last_programmed_values: raise RuntimeError('The worker function check_remote_values for device %s is returning data for channel %s but the BLACS tab is not programmed to handle this channel'%(self.device_name,channel)) # A variable to indicate if this channel has changed changed = False if channel in self._DDS: front_value = self._last_programmed_values[channel] # format the entries for the DDS object correctly, then compare front_values_formatted = {} remote_values_formatted = {} for sub_chnl in front_value: if sub_chnl not in remote_value: raise RuntimeError('The worker function check_remote_values has not returned data for the sub-channel %s in channel %s'%(sub_chnl,channel)) if sub_chnl == 'gate': front_values_formatted[sub_chnl] = str(bool(int(front_value[sub_chnl]))) remote_values_formatted[sub_chnl] = str(bool(int(remote_value[sub_chnl]))) else: decimals = self._DDS[channel].__getattribute__(sub_chnl)._decimals front_values_formatted[sub_chnl] = ("%."+str(decimals)+"f")%front_value[sub_chnl] remote_values_formatted[sub_chnl] = ("%."+str(decimals)+"f")%remote_value[sub_chnl] if front_values_formatted[sub_chnl] != remote_values_formatted[sub_chnl]: changed = True if changed: ui = UiLoader().load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'tab_value_changed_dds.ui')) ui.channel_label.setText(self._DDS[channel].name) for sub_chnl in front_value: ui.__getattribute__('front_%s_value'%sub_chnl).setText(front_values_formatted[sub_chnl]) ui.__getattribute__('remote_%s_value'%sub_chnl).setText(remote_values_formatted[sub_chnl]) # Hide unused sub_channels of this DDS for sub_chnl in self._DDS[channel].get_unused_subchnl_list(): ui.__getattribute__('front_%s_value'%sub_chnl).setVisible(False) ui.__getattribute__('front_%s_label'%sub_chnl).setVisible(False) ui.__getattribute__('remote_%s_value'%sub_chnl).setVisible(False) ui.__getattribute__('remote_%s_label'%sub_chnl).setVisible(False) elif channel in self._DO: # This is an easy case! front_value = str(bool(int(self._last_programmed_values[channel]))) remote_value = str(bool(int(remote_value))) if front_value != remote_value: changed = True ui = UiLoader().load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'tab_value_changed.ui')) ui.channel_label.setText(self._DO[channel].name) ui.front_value.setText(front_value) ui.remote_value.setText(remote_value) elif channel in self._AO: # A intermediately complicated case! front_value = ("%."+str(self._AO[channel]._decimals)+"f")%self._last_programmed_values[channel] remote_value = ("%."+str(self._AO[channel]._decimals)+"f")%remote_value if front_value != remote_value: changed = True ui = UiLoader().load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'tab_value_changed.ui')) ui.channel_label.setText(self._AO[channel].name) ui.front_value.setText(front_value) ui.remote_value.setText(remote_value) else: raise RuntimeError('device_base_class.py is not programmed to handle channel types other than DDS, AO and DO in check_remote_values') if changed: overall_changed = True # Add the changed widget for this channel to a layout! self._ui.changed_layout.addWidget(ui) # save the radio buttons so that we can access their state later! self._changed_radio_buttons[channel] = ui.use_remote_values if overall_changed: # TODO: Disable all widgets for this device, including virtual device widgets...how do I do that????? # Probably need to add a disable/enable method to analog/digital/DDS widgets that disables the widget and is orthogonal to the lock/unlock system # Should probably set a tooltip on the widgets too explaining why they are disabled! # self._device_widget.setSensitive(False) # show the remote_values_change dialog self._changed_widget.show() # Add an "apply" button and link to on_resolve_value_inconsistency button = QPushButton("Apply") button.clicked.connect(self.on_resolve_value_inconsistency) self._ui.changed_layout.addWidget(button)
class Plugin(object): def __init__(self, initial_settings): self.menu = None self.notifications = {} self.initial_settings = initial_settings self.BLACS = None self.ui = None self.n_shots_to_keep = initial_settings.get('n_shots_to_keep', KEEP_ALL_SHOTS) self.delete_queue = initial_settings.get('delete_queue', []) self.event_queue = Queue() self.delete_queue_lock = threading.Lock() self.mainloop_thread = threading.Thread(target=self.mainloop) self.mainloop_thread.daemon = True self.mainloop_thread.start() def plugin_setup_complete(self, BLACS): self.BLACS = BLACS # Add our controls to the BLACS UI: self.ui = UiLoader().load( os.path.join(PLUGINS_DIR, module, 'controls.ui')) BLACS['ui'].queue_controls_frame.layout().addWidget(self.ui) # Restore settings to the GUI controls: self.ui.spinBox.setValue(self.n_shots_to_keep) # Connect signals: self.ui.spinBox.valueChanged.connect(self.on_spinbox_value_changed) self.ui.reset_button.clicked.connect(self.on_reset_button_clicked) BLACS['ui'].queue_repeat_button.toggled.connect(self.ui.setEnabled) # Our control is only enabled when repeat mode is active: self.ui.setEnabled(BLACS['ui'].queue_repeat_button.isChecked()) def on_spinbox_value_changed(self, value): with self.delete_queue_lock: self.n_shots_to_keep = value # If the user reduces the number of shots to keep, but we had a # larger list of shots awaiting deletion, remove shots from the # deletion queue (without deleting them) until the queue is the # same size as the number of shots we are now keeping. This means # that if we set to keep 100 shots, and then we go ahead and run a # hundred shots, if we then set it to keep 5 shots it won't delete # the 95 oldest shots in the queue. Rather it will only delete the # most recent 5 (and not immediately - over the next 5 shots). while len(self.delete_queue) > self.n_shots_to_keep: self.delete_queue.pop(0) def on_reset_button_clicked(self): self.ui.spinBox.setValue(KEEP_ALL_SHOTS) def get_save_data(self): return { 'n_shots_to_keep': self.n_shots_to_keep, 'delete_queue': self.delete_queue } def get_callbacks(self): return {'shot_complete': self.on_shot_complete} def on_shot_complete(self, h5_filepath): # If we're keeping all shots, then there's nothing to do here: if self.n_shots_to_keep == KEEP_ALL_SHOTS: return # Is the file a repeated shot? basename, ext = os.path.splitext(os.path.basename(h5_filepath)) if '_rep' in basename and ext == '.h5': repno = basename.split('_rep')[-1] try: int(repno) except ValueError: # not a rep: return else: # Yes, it is a rep. Queue it for deletion: self.delete_queue.append(h5_filepath) self.event_queue.put('shot complete') def mainloop(self): # We delete shots in a separate thread so that we don't slow down the queue waiting on # network communication to acquire the lock, while True: try: event = self.event_queue.get() if event == 'close': break elif event == 'shot complete': while len(self.delete_queue) > self.n_shots_to_keep: with self.delete_queue_lock: h5_filepath = self.delete_queue.pop(0) # Acquire a lock on the file so that we don't # delete it whilst someone else has it open: with Lock(path_to_agnostic(h5_filepath)): try: os.unlink(h5_filepath) logger.info("Deleted repeated shot file %s" % h5_filepath) except OSError: logger.exception( "Couldn't delete shot file %s" % h5_filepath) else: raise ValueError(event) except Exception: logger.exception( "Exception in repeated shot deletion loop, ignoring.") def close(self): self.event_queue.put('close') self.mainloop_thread.join() # The rest of these are boilerplate: def get_menu_class(self): return None def get_notification_classes(self): return [] def get_setting_classes(self): return [] def set_menu_instance(self, menu): self.menu = menu def set_notification_instances(self, notifications): self.notifications = notifications
class IMAQdxCameraTab(DeviceTab): # Subclasses may override this if all they do is replace the worker class with a # different one: worker_class = 'user_devices.AnalogIMAQdxCamera.blacs_workers.IMAQdxCameraWorker' # Subclasses may override this to False if camera attributes should be set every # shot even if the same values have previously been set: use_smart_programming = True def initialise_GUI(self): layout = self.get_tab_layout() ui_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'blacs_tab.ui') attributes_ui_filepath = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'attributes_dialog.ui') self.ui = UiLoader().load(ui_filepath) self.ui.pushButton_continuous.clicked.connect( self.on_continuous_clicked) self.ui.pushButton_stop.clicked.connect(self.on_stop_clicked) self.ui.pushButton_snap.clicked.connect(self.on_snap_clicked) self.ui.pushButton_attributes.clicked.connect( self.on_attributes_clicked) self.ui.toolButton_nomax.clicked.connect(self.on_reset_rate_clicked) self.attributes_dialog = UiLoader().load(attributes_ui_filepath) self.attributes_dialog.setParent(self.ui.parent()) self.attributes_dialog.setWindowFlags(QtCore.Qt.Tool) self.attributes_dialog.setWindowTitle("{} attributes".format( self.device_name)) self.attributes_dialog.pushButton_copy.clicked.connect( self.on_copy_clicked) self.attributes_dialog.comboBox.currentIndexChanged.connect( self.on_attr_visibility_level_changed) self.ui.doubleSpinBox_maxrate.valueChanged.connect( self.on_max_rate_changed) layout.addWidget(self.ui) self.image = pg.ImageView() self.image.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.ui.horizontalLayout.addWidget(self.image) self.ui.pushButton_stop.hide() self.ui.doubleSpinBox_maxrate.hide() self.ui.toolButton_nomax.hide() self.ui.label_fps.hide() # Ensure the GUI reserves space for these widgets even if they are hidden. # This prevents the GUI jumping around when buttons are clicked: for widget in [ self.ui.pushButton_stop, self.ui.doubleSpinBox_maxrate, self.ui.toolButton_nomax, ]: size_policy = widget.sizePolicy() if hasattr(size_policy, 'setRetainSizeWhenHidden'): # Qt 5.2+ only size_policy.setRetainSizeWhenHidden(True) widget.setSizePolicy(size_policy) # Start the image receiver ZMQ server: self.image_receiver = ImageReceiver(self.image, self.ui.label_fps) self.acquiring = False self.supports_smart_programming(self.use_smart_programming) def get_save_data(self): return { 'attribute_visibility': self.attributes_dialog.comboBox.currentText(), 'acquiring': self.acquiring, 'max_rate': self.ui.doubleSpinBox_maxrate.value(), 'colormap': repr(self.image.ui.histogram.gradient.saveState()) } def restore_save_data(self, save_data): self.attributes_dialog.comboBox.setCurrentText( save_data.get('attribute_visibility', 'simple')) self.ui.doubleSpinBox_maxrate.setValue(save_data.get('max_rate', 0)) if save_data.get('acquiring', False): # Begin acquisition self.on_continuous_clicked(None) if 'colormap' in save_data: self.image.ui.histogram.gradient.restoreState( ast.literal_eval(save_data['colormap'])) def initialise_workers(self): table = self.settings['connection_table'] connection_table_properties = table.find_by_name( self.device_name).properties # The device properties can vary on a shot-by-shot basis, but at startup we will # initially set the values that are configured in the connection table, so they # can be used for manual mode acquisition: with h5py.File(table.filepath, 'r') as f: device_properties = labscript_utils.properties.get( f, self.device_name, "device_properties") worker_initialisation_kwargs = { 'serial_number': connection_table_properties['serial_number'], 'orientation': connection_table_properties['orientation'], 'camera_attributes': device_properties['camera_attributes'], 'manual_mode_camera_attributes': connection_table_properties['manual_mode_camera_attributes'], 'mock': connection_table_properties['mock'], 'image_receiver_port': self.image_receiver.port, } self.create_worker('main_worker', self.worker_class, worker_initialisation_kwargs) self.primary_worker = "main_worker" @define_state(MODE_MANUAL, queue_state_indefinitely=True, delete_stale_states=True) def update_attributes(self): attributes_text = yield (self.queue_work( self.primary_worker, 'get_attributes_as_text', self.attributes_dialog.comboBox.currentText(), )) self.attributes_dialog.plainTextEdit.setPlainText(attributes_text) def on_attributes_clicked(self, button): self.attributes_dialog.show() self.on_attr_visibility_level_changed(None) def on_attr_visibility_level_changed(self, value): self.attributes_dialog.plainTextEdit.setPlainText( "Reading attributes...") self.update_attributes() def on_continuous_clicked(self, button): self.ui.pushButton_snap.setEnabled(False) self.ui.pushButton_attributes.setEnabled(False) self.ui.pushButton_continuous.hide() self.ui.pushButton_stop.show() self.ui.doubleSpinBox_maxrate.show() self.ui.toolButton_nomax.show() self.ui.label_fps.show() self.ui.label_fps.setText('? fps') self.acquiring = True max_fps = self.ui.doubleSpinBox_maxrate.value() dt = 1 / max_fps if max_fps else 0 self.start_continuous(dt) def on_stop_clicked(self, button): self.ui.pushButton_snap.setEnabled(True) self.ui.pushButton_attributes.setEnabled(True) self.ui.pushButton_continuous.show() self.ui.doubleSpinBox_maxrate.hide() self.ui.toolButton_nomax.hide() self.ui.pushButton_stop.hide() self.ui.label_fps.hide() self.acquiring = False self.stop_continuous() def on_copy_clicked(self, button): text = self.attributes_dialog.plainTextEdit.toPlainText() clipboard = QtGui.QApplication.instance().clipboard() clipboard.setText(text) def on_reset_rate_clicked(self): self.ui.doubleSpinBox_maxrate.setValue(0) def on_max_rate_changed(self, max_fps): if self.acquiring: self.stop_continuous() dt = 1 / max_fps if max_fps else 0 self.start_continuous(dt) @define_state(MODE_MANUAL, queue_state_indefinitely=True, delete_stale_states=True) def on_snap_clicked(self, button): yield (self.queue_work(self.primary_worker, 'snap')) @define_state(MODE_MANUAL, queue_state_indefinitely=True, delete_stale_states=True) def start_continuous(self, dt): yield (self.queue_work(self.primary_worker, 'start_continuous', dt)) @define_state(MODE_MANUAL, queue_state_indefinitely=True, delete_stale_states=True) def stop_continuous(self): yield (self.queue_work(self.primary_worker, 'stop_continuous')) def restart(self, *args, **kwargs): # Must manually stop the receiving server upon tab restart, otherwise it does # not get cleaned up: self.image_receiver.shutdown() return DeviceTab.restart(self, *args, **kwargs)