Beispiel #1
0
    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()
Beispiel #5
0
    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 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()
Beispiel #8
0
    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
Beispiel #10
0
    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
Beispiel #11
0
 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)
Beispiel #12
0
 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)
Beispiel #13
0
    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)
Beispiel #14
0
    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'))
Beispiel #15
0
    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)
Beispiel #17
0
    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
Beispiel #18
0
    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)
Beispiel #21
0
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)