コード例 #1
0
class BLACS(object):

    tab_widget_ids = 7

    def __init__(self, application):
        splash.update_text('loading graphical interface')
        self.qt_application = application
        #self.qt_application.aboutToQuit.connect(self.destroy)
        self._relaunch = False
        self.exiting = False
        self.exit_complete = False

        logger.info('Loading BLACS ui')
        #self.ui = BLACSWindow(self).ui
        loader = UiLoader()
        loader.registerCustomWidget(QueueTreeview)
        #loader.registerCustomPromotion('BLACS',BLACSWindow)
        self.ui = loader.load(os.path.join(BLACS_DIR, 'main.ui'),
                              BLACSWindow())
        logger.info('BLACS ui loaded')
        self.ui.blacs = self
        self.tab_widgets = {}
        self.exp_config = exp_config  # Global variable
        self.settings_path = settings_path  # Global variable
        self.connection_table = connection_table  # Global variable
        self.connection_table_h5file = self.exp_config.get(
            'paths', 'connection_table_h5')
        self.connection_table_labscript = self.exp_config.get(
            'paths', 'connection_table_py')

        # Setup the UI
        self.ui.main_splitter.setStretchFactor(0, 0)
        self.ui.main_splitter.setStretchFactor(1, 1)

        self.tablist = {}
        self.panes = {}
        self.settings_dict = {}

        splash.update_text('loading device front panel settings')
        # Find which devices are connected to BLACS, and what their labscript class names are:
        logger.info('finding connected devices in connection table')
        self.attached_devices = self.connection_table.get_attached_devices()

        # Store the panes in a dictionary for easy access
        self.panes[
            'tab_top_vertical_splitter'] = self.ui.tab_top_vertical_splitter
        self.panes[
            'tab_bottom_vertical_splitter'] = self.ui.tab_bottom_vertical_splitter
        self.panes['tab_horizontal_splitter'] = self.ui.tab_horizontal_splitter
        self.panes['main_splitter'] = self.ui.main_splitter

        # Get settings to restore
        logger.info('Loading front panel settings')
        self.front_panel_settings = FrontPanelSettings(self.settings_path,
                                                       self.connection_table)
        self.front_panel_settings.setup(self)
        settings, question, error, tab_data = self.front_panel_settings.restore(
        )

        # TODO: handle question/error cases

        logger.info('restoring window data')
        self.restore_window(tab_data)

        splash.update_text('creating device tabs...')
        # Create the notebooks
        logger.info('Creating tab widgets')
        for i in range(4):
            self.tab_widgets[i] = DragDropTabWidget(self.tab_widget_ids)
            self.tab_widgets[i].setElideMode(Qt.ElideRight)
            getattr(self.ui,
                    'tab_container_%d' % i).addWidget(self.tab_widgets[i])

        logger.info('Instantiating devices')
        self.failed_device_settings = {}
        for device_name, labscript_device_class_name in list(
                self.attached_devices.items()):
            try:
                self.settings_dict.setdefault(device_name,
                                              {"device_name": device_name})
                # add common keys to settings:
                self.settings_dict[device_name][
                    "connection_table"] = self.connection_table
                self.settings_dict[device_name][
                    "front_panel_settings"] = settings[
                        device_name] if device_name in settings else {}
                self.settings_dict[device_name]["saved_data"] = tab_data[
                    device_name]['data'] if device_name in tab_data else {}
                # Instantiate the device
                logger.info('instantiating %s' % device_name)
                TabClass = labscript_devices.get_BLACS_tab(
                    labscript_device_class_name)
                self.tablist[device_name] = TabClass(
                    self.tab_widgets[0], self.settings_dict[device_name])
            except Exception:
                self.failed_device_settings[device_name] = {
                    "front_panel":
                    self.settings_dict[device_name]["front_panel_settings"],
                    "save_data":
                    self.settings_dict[device_name]["saved_data"]
                }
                del self.settings_dict[device_name]
                del self.attached_devices[device_name]
                self.connection_table.remove_device(device_name)
                raise_exception_in_thread(sys.exc_info())

        splash.update_text('instantiating plugins')
        logger.info('Instantiating plugins')
        # setup the plugin system
        settings_pages = []
        self.plugins = {}
        plugin_settings = eval(
            tab_data['BLACS settings']['plugin_data']
        ) if 'plugin_data' in tab_data['BLACS settings'] else {}
        for module_name, module in plugins.modules.items():
            try:
                # instantiate the plugin
                self.plugins[module_name] = module.Plugin(
                    plugin_settings[module_name] if module_name in
                    plugin_settings else {})
            except Exception:
                logger.exception(
                    'Could not instantiate plugin \'%s\'. Skipping' %
                    module_name)

        logger.info('creating plugin tabs')
        # setup the plugin tabs
        for module_name, plugin in self.plugins.items():
            try:
                if hasattr(plugin, 'get_tab_classes'):
                    tab_dict = {}

                    for tab_name, TabClass in plugin.get_tab_classes().items():
                        settings_key = "{}: {}".format(module_name, tab_name)
                        self.settings_dict.setdefault(settings_key,
                                                      {"tab_name": tab_name})
                        self.settings_dict[settings_key]["front_panel_settings"] = settings[
                            settings_key] if settings_key in settings else {}
                        self.settings_dict[settings_key][
                            "saved_data"] = tab_data[settings_key][
                                'data'] if settings_key in tab_data else {}

                        self.tablist[settings_key] = TabClass(
                            self.tab_widgets[0],
                            self.settings_dict[settings_key])
                        tab_dict[tab_name] = self.tablist[settings_key]

                    if hasattr(plugin, 'tabs_created'):
                        plugin.tabs_created(tab_dict)

            except Exception:
                logger.exception(
                    'Could not instantiate tab for plugin \'%s\'. Skipping')

        logger.info('reordering tabs')
        self.order_tabs(tab_data)

        splash.update_text("initialising analysis submission")
        logger.info('starting analysis submission thread')
        # setup analysis submission
        self.analysis_submission = AnalysisSubmission(self, self.ui)
        if 'analysis_data' not in tab_data['BLACS settings']:
            tab_data['BLACS settings']['analysis_data'] = {}
        else:
            tab_data['BLACS settings']['analysis_data'] = eval(
                tab_data['BLACS settings']['analysis_data'])
        self.analysis_submission.restore_save_data(
            tab_data['BLACS settings']["analysis_data"])

        splash.update_text("starting queue manager")
        logger.info('starting queue manager thread')
        # Setup the QueueManager
        self.queue = QueueManager(self, self.ui)
        if 'queue_data' not in tab_data['BLACS settings']:
            tab_data['BLACS settings']['queue_data'] = {}
        else:
            # quick fix for qt objects not loading that were saved before qtutil 2 changes
            try:
                tab_data['BLACS settings']['queue_data'] = eval(
                    tab_data['BLACS settings']['queue_data'])
            except NameError:
                tab_data['BLACS settings']['queue_data'] = {}
        self.queue.restore_save_data(tab_data['BLACS settings']['queue_data'])

        blacs_data = {
            'exp_config': self.exp_config,
            'ui': self.ui,
            'set_relaunch': self.set_relaunch,
            'plugins': self.plugins,
            'connection_table_h5file': self.connection_table_h5file,
            'connection_table_labscript': self.connection_table_labscript,
            'experiment_queue': self.queue
        }

        def create_menu(parent, menu_parameters):
            if 'name' in menu_parameters:
                if 'menu_items' in menu_parameters:
                    child = parent.addMenu(menu_parameters['name'])
                    for child_menu_params in menu_parameters['menu_items']:
                        create_menu(child, child_menu_params)
                else:
                    if 'icon' in menu_parameters:
                        child = parent.addAction(
                            QIcon(menu_parameters['icon']),
                            menu_parameters['name'])
                    else:
                        child = parent.addAction(menu_parameters['name'])

                if 'action' in menu_parameters:
                    child.triggered.connect(menu_parameters['action'])

            elif 'separator' in menu_parameters:
                parent.addSeparator()

        # setup the Notification system
        logger.info('setting up notification system')
        splash.update_text('setting up notification system')
        self.notifications = Notifications(blacs_data)

        settings_callbacks = []
        for module_name, plugin in self.plugins.items():
            try:
                # Setup settings page
                settings_pages.extend(plugin.get_setting_classes())
                # Setup menu
                if plugin.get_menu_class():
                    # must store a reference or else the methods called when the menu actions are triggered
                    # (contained in this object) will be garbaged collected
                    menu = plugin.get_menu_class()(blacs_data)
                    create_menu(self.ui.menubar, menu.get_menu_items())
                    plugin.set_menu_instance(menu)

                # Setup notifications
                plugin_notifications = {}
                for notification_class in plugin.get_notification_classes():
                    self.notifications.add_notification(notification_class)
                    plugin_notifications[
                        notification_class] = self.notifications.get_instance(
                            notification_class)
                plugin.set_notification_instances(plugin_notifications)

                # Register callbacks
                callbacks = plugin.get_callbacks()
                # save the settings_changed callback in a separate list for setting up later
                if isinstance(callbacks,
                              dict) and 'settings_changed' in callbacks:
                    settings_callbacks.append(callbacks['settings_changed'])

            except Exception:
                logger.exception(
                    'Plugin \'%s\' error. Plugin may not be functional.' %
                    module_name)

        # setup the BLACS preferences system
        splash.update_text('setting up preferences system')
        logger.info('setting up preferences system')
        self.settings = Settings(file=self.settings_path,
                                 parent=self.ui,
                                 page_classes=settings_pages)
        for callback in settings_callbacks:
            self.settings.register_callback(callback)

        # update the blacs_data dictionary with the settings system
        blacs_data['settings'] = self.settings

        for module_name, plugin in self.plugins.items():
            try:
                plugin.plugin_setup_complete(blacs_data)
            except Exception:
                logger.exception(
                    'Error in plugin_setup_complete() for plugin \'%s\'. Trying again with old call signature...'
                    % module_name)
                # backwards compatibility for old plugins
                try:
                    plugin.plugin_setup_complete()
                    logger.warning(
                        'Plugin \'%s\' using old API. Please update Plugin.plugin_setup_complete method to accept a dictionary of blacs_data as the only argument.'
                        % module_name)
                except Exception:
                    logger.exception(
                        'Plugin \'%s\' error. Plugin may not be functional.' %
                        module_name)

        # Connect menu actions
        self.ui.actionOpenPreferences.triggered.connect(
            self.on_open_preferences)
        self.ui.actionSave.triggered.connect(self.on_save_front_panel)
        self.ui.actionOpen.triggered.connect(self.on_load_front_panel)
        self.ui.actionExit.triggered.connect(self.ui.close)

        # Connect the windows AppId stuff:
        if os.name == 'nt':
            self.ui.newWindow.connect(set_win_appusermodel)

        # Add hidden easter egg button to a random tab:
        logger.info('hiding easter eggs')
        import random
        if self.tablist:
            random_tab = random.choice(list(self.tablist.values()))
            self.easter_egg_button = EasterEggButton()
            # Add the button before the other buttons in the tab's header:
            header = random_tab._ui.horizontalLayout
            for i in range(header.count()):
                if isinstance(header.itemAt(i).widget(), QToolButton):
                    header.insertWidget(i, self.easter_egg_button)
                    break

        splash.update_text('done')
        logger.info('showing UI')
        self.ui.show()

    def set_relaunch(self, value):
        self._relaunch = bool(value)

    def restore_window(self, tab_data):
        # read out position settings:
        try:
            # There are some dodgy hacks going on here to try and restore the window position correctly
            # Unfortunately Qt has two ways of measuring teh window position, one with the frame/titlebar
            # and one without. If you use the one that measures including the titlebar, you don't
            # know what the window size was when the window was UNmaximized.
            #
            # Anyway, no idea if this works cross platform (tested on windows 8)
            # Feel free to rewrite this, along with the code in front_panel_settings.py
            # which stores the values
            #
            # Actually this is a waste of time because if you close when maximized, reoopen and then
            # de-maximize, the window moves to a random position (not the position it was at before maximizing)
            # so bleh!
            self.ui.move(
                tab_data['BLACS settings']["window_xpos"] -
                tab_data['BLACS settings']['window_frame_width'] / 2,
                tab_data['BLACS settings']["window_ypos"] -
                tab_data['BLACS settings']['window_frame_height'] +
                tab_data['BLACS settings']['window_frame_width'] / 2)
            self.ui.resize(tab_data['BLACS settings']["window_width"],
                           tab_data['BLACS settings']["window_height"])

            if 'window_maximized' in tab_data['BLACS settings'] and tab_data[
                    'BLACS settings']['window_maximized']:
                self.ui.showMaximized()

            for pane_name, pane in self.panes.items():
                pane.setSizes(tab_data['BLACS settings'][pane_name])

        except Exception as e:
            logger.warning(
                "Unable to load window and notebook defaults. Exception:" +
                str(e))

    def order_tabs(self, tab_data):
        # Move the tabs to the correct notebook
        for tab_name in self.tablist.keys():
            notebook_num = 0
            if tab_name in tab_data:
                notebook_num = int(tab_data[tab_name]["notebook"])
                if notebook_num not in self.tab_widgets:
                    notebook_num = 0

            #Find the notebook the tab is in, and remove it:
            for notebook in self.tab_widgets.values():
                tab_index = notebook.indexOf(self.tablist[tab_name]._ui)
                if tab_index != -1:
                    tab_text = notebook.tabText(tab_index)
                    notebook.removeTab(tab_index)
                    self.tab_widgets[notebook_num].addTab(
                        self.tablist[tab_name]._ui, tab_text)
                    break

        splash.update_text('restoring tab positions...')
        # # Now that all the pages are created, reorder them!
        for tab_name in self.tablist.keys():
            if tab_name in tab_data:
                notebook_num = int(tab_data[tab_name]["notebook"])
                if notebook_num in self.tab_widgets:
                    self.tab_widgets[notebook_num].tab_bar.moveTab(
                        self.tab_widgets[notebook_num].indexOf(
                            self.tablist[tab_name]._ui),
                        int(tab_data[tab_name]["page"]))

        # # Now that they are in the correct order, set the correct one visible
        for tab_name, data in tab_data.items():
            if tab_name == 'BLACS settings':
                continue
            # if the notebook still exists and we are on the entry that is visible
            if bool(data["visible"]) and int(
                    data["notebook"]) in self.tab_widgets:
                self.tab_widgets[int(
                    data["notebook"])].tab_bar.setCurrentIndex(
                        int(data["page"]))

    def update_all_tab_settings(self, settings, tab_data):
        for tab_name, tab in self.tablist.items():
            self.settings_dict[tab_name]["front_panel_settings"] = settings[
                tab_name] if tab_name in settings else {}
            self.settings_dict[tab_name]["saved_data"] = tab_data[tab_name][
                'data'] if tab_name in tab_data else {}
            tab.update_from_settings(self.settings_dict[tab_name])

    def on_load_front_panel(self, *args, **kwargs):
        # get the file:
        # create file chooser dialog
        dialog = QFileDialog(
            None, "Select file to load",
            self.exp_config.get('paths', 'experiment_shot_storage'),
            "HDF5 files (*.h5 *.hdf5)")
        dialog.setViewMode(QFileDialog.Detail)
        dialog.setFileMode(QFileDialog.ExistingFile)
        if dialog.exec_():
            selected_files = dialog.selectedFiles()
            filepath = str(selected_files[0])
            # Qt has this weird behaviour where if you type in the name of a file that exists
            # but does not have the extension you have limited the dialog to, the OK button is greyed out
            # but you can hit enter and the file will be selected.
            # So we must check the extension of each file here!
            if filepath.endswith('.h5') or filepath.endswith('.hdf5'):
                try:
                    # TODO: Warn that this will restore values, but not channels that are locked
                    message = QMessageBox()
                    message.setText(
                        """Warning: This will modify front panel values and cause device output values to update.
                    \nThe queue and files waiting to be sent for analysis will be cleared.
                    \n
                    \nNote: Channels that are locked will not be updated.\n\nDo you wish to continue?"""
                    )
                    message.setIcon(QMessageBox.Warning)
                    message.setWindowTitle("BLACS")
                    message.setStandardButtons(QMessageBox.Yes
                                               | QMessageBox.No)

                    if message.exec_() == QMessageBox.Yes:
                        front_panel_settings = FrontPanelSettings(
                            filepath, self.connection_table)
                        settings, question, error, tab_data = front_panel_settings.restore(
                        )
                        #TODO: handle question/error

                        # Restore window data
                        self.restore_window(tab_data)
                        self.order_tabs(tab_data)
                        self.update_all_tab_settings(settings, tab_data)

                        # restore queue data
                        if 'queue_data' not in tab_data['BLACS settings']:
                            tab_data['BLACS settings']['queue_data'] = {}
                        else:
                            # quick fix for qt objects not loading that were saved before qtutil 2 changes
                            try:
                                tab_data['BLACS settings']['queue_data'] = eval(
                                    tab_data['BLACS settings']['queue_data'])
                            except NameError:
                                tab_data['BLACS settings']['queue_data'] = {}
                        self.queue.restore_save_data(
                            tab_data['BLACS settings']['queue_data'])
                        # restore analysis data
                        if 'analysis_data' not in tab_data['BLACS settings']:
                            tab_data['BLACS settings']['analysis_data'] = {}
                        else:
                            tab_data['BLACS settings']['analysis_data'] = eval(
                                tab_data['BLACS settings']['analysis_data'])
                        self.analysis_submission.restore_save_data(
                            tab_data['BLACS settings']["analysis_data"])
                except Exception as e:
                    logger.exception("Unable to load the front panel in %s." %
                                     (filepath))
                    message = QMessageBox()
                    message.setText(
                        "Unable to load the front panel. The error encountered is printed below.\n\n%s"
                        % str(e))
                    message.setIcon(QMessageBox.Information)
                    message.setWindowTitle("BLACS")
                    message.exec_()
                finally:
                    dialog.deleteLater()
            else:
                dialog.deleteLater()
                message = QMessageBox()
                message.setText(
                    "You did not select a file ending with .h5 or .hdf5. Please try again"
                )
                message.setIcon(QMessageBox.Information)
                message.setWindowTitle("BLACS")
                message.exec_()
                QTimer.singleShot(10, self.on_load_front_panel)

    def on_save_exit(self):
        # Save front panel
        data = self.front_panel_settings.get_save_data()

        if len(self.failed_device_settings) > 0:
            message = (
                'Save data from broken tabs? \n Broken tabs are: \n {}'.format(
                    list(self.failed_device_settings.keys())))
            reply = QMessageBox.question(self.ui, 'Save broken tab data?',
                                         message,
                                         QMessageBox.Yes | QMessageBox.No)
            if reply == QMessageBox.Yes:
                data[0].update(self.failed_device_settings)

        # with h5py.File(self.settings_path,'r+') as h5file:
        # if 'connection table' in h5file:
        # del h5file['connection table']

        self.front_panel_settings.save_front_panel_to_h5(
            self.settings_path,
            data[0],
            data[1],
            data[2],
            data[3], {"overwrite": True},
            force_new_conn_table=True)
        logger.info('Shutting down workers')
        for tab in self.tablist.values():
            tab.shutdown_workers()

        QTimer.singleShot(100, self.finalise_quit)

    def finalise_quit(self, deadline=None, pending_threads=None):
        logger.info('finalise_quit called')
        WORKER_SHUTDOWN_TIMEOUT = 2
        if deadline is None:
            deadline = time.time() + WORKER_SHUTDOWN_TIMEOUT
        if pending_threads is None:
            pending_threads = {}
        overdue = time.time() > deadline
        # Check for worker shutdown completion:
        for name, tab in list(self.tablist.items()):
            fatal_error = tab.state == 'fatal error'
            if not tab.shutdown_workers_complete and overdue or fatal_error:
                # Give up on cleanly shutting down this tab's worker processes:
                tab.shutdown_workers_complete = True
            if tab.shutdown_workers_complete:
                if name not in pending_threads:
                    # Either worker shutdown completed or we gave up. Close the tab.
                    try:
                        current_page = tab.close_tab(finalise=False)
                    except Exception as e:
                        logger.error('Couldn\'t close tab:\n%s' % str(e))
                        del self.tablist[name]
                    else:
                        # Call finalise_close_tab in a thread since it can be blocking.
                        # It has its own timeout however, so we do not need to keep
                        # track of whether tabs are taking too long to finalise_close()
                        pending_threads[name] = inthread(
                            tab.finalise_close_tab, current_page)
                elif not pending_threads[name].is_alive():
                    # finalise_close_tab completed, tab is closed and worker terminated
                    pending_threads[name].join()
                    del pending_threads[name]
                    del self.tablist[name]
        if not self.tablist:
            # All tabs are closed.
            self.exit_complete = True
            logger.info('quitting')
            return
        QTimer.singleShot(
            100, lambda: self.finalise_quit(deadline, pending_threads))

    def on_save_front_panel(self, *args, **kwargs):
        data = self.front_panel_settings.get_save_data()

        # Open save As dialog
        dialog = QFileDialog(
            None, "Save BLACS state",
            self.exp_config.get('paths', 'experiment_shot_storage'),
            "HDF5 files (*.h5)")
        try:
            dialog.setViewMode(QFileDialog.Detail)
            dialog.setFileMode(QFileDialog.AnyFile)
            dialog.setAcceptMode(QFileDialog.AcceptSave)

            if dialog.exec_():
                current_file = str(dialog.selectedFiles()[0])
                if not current_file.endswith('.h5'):
                    current_file += '.h5'
                self.front_panel_settings.save_front_panel_to_h5(
                    current_file, data[0], data[1], data[2], data[3])
        except Exception:
            raise
        finally:
            dialog.deleteLater()

    def on_open_preferences(self, *args, **kwargs):
        self.settings.create_dialog()
class BLACS(object):

    tab_widget_ids = 7

    def __init__(self, application):
        self.qt_application = application
        #self.qt_application.aboutToQuit.connect(self.destroy)
        self._relaunch = False
        self.exiting = False
        self.exit_complete = False

        logger.info('Loading BLACS ui')
        #self.ui = BLACSWindow(self).ui
        loader = UiLoader()
        loader.registerCustomWidget(QueueTreeview)
        #loader.registerCustomPromotion('BLACS',BLACSWindow)
        self.ui = loader.load(
            os.path.join(os.path.dirname(os.path.realpath(__file__)),
                         'main.ui'), BLACSWindow())
        logger.info('BLACS ui loaded')
        self.ui.blacs = self
        self.tab_widgets = {}
        self.exp_config = exp_config  # Global variable
        self.settings_path = settings_path  # Global variable
        self.connection_table = connection_table  # Global variable
        self.connection_table_h5file = self.exp_config.get(
            'paths', 'connection_table_h5')
        self.connection_table_labscript = self.exp_config.get(
            'paths', 'connection_table_py')

        # Setup the UI
        self.ui.main_splitter.setStretchFactor(0, 0)
        self.ui.main_splitter.setStretchFactor(1, 1)

        self.tablist = {}
        self.panes = {}
        self.settings_dict = {}

        # Setup progressbar monitoring status queue
        self.ui.TimeNext_progressBar.setRange(0, 100)

        self._countdown_queue = Queue.Queue()
        Queue_Receiver = QueueToSignal(self._countdown_queue)
        Queue_Receiver.mysignal.connect(self.ui.TimeNext_progressBar.setValue)
        thread = QThread()
        Queue_Receiver.moveToThread(thread)
        thread.started.connect(Queue_Receiver.run)
        thread.start()

        # Find which devices are connected to BLACS, and what their labscript class names are:
        logger.info('finding connected devices in connection table')
        self.attached_devices = self.connection_table.get_attached_devices()

        # Store the panes in a dictionary for easy access
        self.panes[
            'tab_top_vertical_splitter'] = self.ui.tab_top_vertical_splitter
        self.panes[
            'tab_bottom_vertical_splitter'] = self.ui.tab_bottom_vertical_splitter
        self.panes['tab_horizontal_splitter'] = self.ui.tab_horizontal_splitter
        self.panes['main_splitter'] = self.ui.main_splitter

        # Get settings to restore
        logger.info('Loading front panel settings')
        self.front_panel_settings = FrontPanelSettings(self.settings_path,
                                                       self.connection_table)
        self.front_panel_settings.setup(self)
        settings, question, error, tab_data = self.front_panel_settings.restore(
        )

        # TODO: handle question/error cases

        logger.info('restoring window data')
        self.restore_window(tab_data)

        #splash.update_text('Creating the device tabs...')
        # Create the notebooks
        logger.info('Creating tab widgets')
        for i in range(4):
            self.tab_widgets[i] = DragDropTabWidget(self.tab_widget_ids)
            getattr(self.ui,
                    'tab_container_%d' % i).addWidget(self.tab_widgets[i])

        logger.info('Instantiating devices')
        for device_name, labscript_device_class_name in self.attached_devices.items(
        ):
            self.settings_dict.setdefault(device_name,
                                          {"device_name": device_name})
            # add common keys to settings:
            self.settings_dict[device_name][
                "connection_table"] = self.connection_table
            self.settings_dict[device_name]["front_panel_settings"] = settings[
                device_name] if device_name in settings else {}
            self.settings_dict[device_name]["saved_data"] = tab_data[
                device_name]['data'] if device_name in tab_data else {}
            # Instantiate the device
            logger.info('instantiating %s' % device_name)
            TabClass = labscript_devices.get_BLACS_tab(
                labscript_device_class_name)
            self.tablist[device_name] = TabClass(
                self.tab_widgets[0], self.settings_dict[device_name])

        logger.info('reordering tabs')
        self.order_tabs(tab_data)

        logger.info('starting analysis submission thread')
        # setup analysis submission
        self.analysis_submission = AnalysisSubmission(self, self.ui)
        if 'analysis_data' not in tab_data['BLACS settings']:
            tab_data['BLACS settings']['analysis_data'] = {}
        else:
            tab_data['BLACS settings']['analysis_data'] = eval(
                tab_data['BLACS settings']['analysis_data'])
        self.analysis_submission.restore_save_data(
            tab_data['BLACS settings']["analysis_data"])

        logger.info('starting queue manager thread')
        # Setup the QueueManager
        self.queue = QueueManager(self, self.ui)
        if 'queue_data' not in tab_data['BLACS settings']:
            tab_data['BLACS settings']['queue_data'] = {}
        else:
            tab_data['BLACS settings']['queue_data'] = eval(
                tab_data['BLACS settings']['queue_data'])
        self.queue.restore_save_data(tab_data['BLACS settings']['queue_data'])

        logger.info('instantiating plugins')
        # setup the plugin system
        settings_pages = []
        self.plugins = {}
        plugin_settings = eval(
            tab_data['BLACS settings']['plugin_data']
        ) if 'plugin_data' in tab_data['BLACS settings'] else {}
        for module_name, module in plugins.modules.items():
            try:
                # instantiate the plugin
                self.plugins[module_name] = module.Plugin(
                    plugin_settings[module_name] if module_name in
                    plugin_settings else {})
            except Exception:
                logger.exception(
                    'Could not instantiate plugin \'%s\'. Skipping')

        blacs_data = {
            'exp_config': self.exp_config,
            'ui': self.ui,
            'set_relaunch': self.set_relaunch,
            'plugins': self.plugins,
            'connection_table_h5file': self.connection_table_h5file,
            'connection_table_labscript': self.connection_table_labscript,
            'experiment_queue': self.queue
        }

        def create_menu(parent, menu_parameters):
            if 'name' in menu_parameters:
                if 'menu_items' in menu_parameters:
                    child = parent.addMenu(menu_parameters['name'])
                    for child_menu_params in menu_parameters['menu_items']:
                        create_menu(child, child_menu_params)
                else:
                    child = parent.addAction(menu_parameters['name'])

                if 'action' in menu_parameters:
                    child.triggered.connect(menu_parameters['action'])

            elif 'separator' in menu_parameters:
                parent.addSeparator()

        # setup the Notification system
        logger.info('setting up notification system')
        self.notifications = Notifications(blacs_data)

        settings_callbacks = []
        for module_name, plugin in self.plugins.items():
            try:
                # Setup settings page
                settings_pages.extend(plugin.get_setting_classes())
                # Setup menu
                if plugin.get_menu_class():
                    # must store a reference or else the methods called when the menu actions are triggered
                    # (contained in this object) will be garbaged collected
                    menu = plugin.get_menu_class()(blacs_data)
                    create_menu(self.ui.menubar, menu.get_menu_items())
                    plugin.set_menu_instance(menu)

                # Setup notifications
                plugin_notifications = {}
                for notification_class in plugin.get_notification_classes():
                    self.notifications.add_notification(notification_class)
                    plugin_notifications[
                        notification_class] = self.notifications.get_instance(
                            notification_class)
                plugin.set_notification_instances(plugin_notifications)

                # Register callbacks
                callbacks = plugin.get_callbacks()
                # save the settings_changed callback in a separate list for setting up later
                if isinstance(callbacks,
                              dict) and 'settings_changed' in callbacks:
                    settings_callbacks.append(callbacks['settings_changed'])

            except Exception:
                logger.exception(
                    'Plugin \'%s\' error. Plugin may not be functional.' %
                    module_name)

        # setup the BLACS preferences system
        logger.info('setting up preferences system')
        self.settings = Settings(file=self.settings_path,
                                 parent=self.ui,
                                 page_classes=settings_pages)
        for callback in settings_callbacks:
            self.settings.register_callback(callback)

        # update the blacs_data dictionary with the settings system
        blacs_data['settings'] = self.settings

        for module_name, plugin in self.plugins.items():
            try:
                plugin.plugin_setup_complete(blacs_data)
            except Exception:
                # backwards compatibility for old plugins
                try:
                    plugin.plugin_setup_complete()
                    logger.warning(
                        'Plugin \'%s\' using old API. Please update Plugin.plugin_setup_complete method to accept a dictionary of blacs_data as the only argument.'
                        % module_name)
                except Exception:
                    logger.exception(
                        'Plugin \'%s\' error. Plugin may not be functional.' %
                        module_name)

        # Connect menu actions
        self.ui.actionOpenPreferences.triggered.connect(
            self.on_open_preferences)
        self.ui.actionSave.triggered.connect(self.on_save_front_panel)
        self.ui.actionOpen.triggered.connect(self.on_load_front_panel)

        # Connect the windows AppId stuff:
        if os.name == 'nt':
            self.ui.newWindow.connect(set_win_appusermodel)

        logger.info('showing UI')
        self.ui.show()

    def set_relaunch(self, value):
        self._relaunch = bool(value)

    def restore_window(self, tab_data):
        # read out position settings:
        try:
            # There are some dodgy hacks going on here to try and restore the window position correctly
            # Unfortunately Qt has two ways of measuring teh window position, one with the frame/titlebar
            # and one without. If you use the one that measures including the titlebar, you don't
            # know what the window size was when the window was UNmaximized.
            #
            # Anyway, no idea if this works cross platform (tested on windows 8)
            # Feel free to rewrite this, along with the code in front_panel_settings.py
            # which stores the values
            #
            # Actually this is a waste of time because if you close when maximized, reoopen and then
            # de-maximize, the window moves to a random position (not the position it was at before maximizing)
            # so bleh!
            self.ui.move(
                tab_data['BLACS settings']["window_xpos"] -
                tab_data['BLACS settings']['window_frame_width'] / 2,
                tab_data['BLACS settings']["window_ypos"] -
                tab_data['BLACS settings']['window_frame_height'] +
                tab_data['BLACS settings']['window_frame_width'] / 2)
            self.ui.resize(tab_data['BLACS settings']["window_width"],
                           tab_data['BLACS settings']["window_height"])

            if 'window_maximized' in tab_data['BLACS settings'] and tab_data[
                    'BLACS settings']['window_maximized']:
                self.ui.showMaximized()

            for pane_name, pane in self.panes.items():
                pane.setSizes(tab_data['BLACS settings'][pane_name])

        except Exception as e:
            logger.warning(
                "Unable to load window and notebook defaults. Exception:" +
                str(e))

    def order_tabs(self, tab_data):
        # Move the tabs to the correct notebook
        for device_name in self.attached_devices:
            notebook_num = 0
            if device_name in tab_data:
                notebook_num = int(tab_data[device_name]["notebook"])
                if notebook_num not in self.tab_widgets:
                    notebook_num = 0

            #Find the notebook the tab is in, and remove it:
            for notebook in self.tab_widgets.values():
                tab_index = notebook.indexOf(self.tablist[device_name]._ui)
                if tab_index != -1:
                    notebook.removeTab(tab_index)
                    self.tab_widgets[notebook_num].addTab(
                        self.tablist[device_name]._ui, device_name)
                    break

        # splash.update_text('restoring tab positions...')
        # # Now that all the pages are created, reorder them!
        for device_name in self.attached_devices:
            if device_name in tab_data:
                notebook_num = int(tab_data[device_name]["notebook"])
                if notebook_num in self.tab_widgets:
                    self.tab_widgets[notebook_num].tab_bar.moveTab(
                        self.tab_widgets[notebook_num].indexOf(
                            self.tablist[device_name]._ui),
                        int(tab_data[device_name]["page"]))

        # # Now that they are in the correct order, set the correct one visible
        for device_name, device_data in tab_data.items():
            if device_name == 'BLACS settings':
                continue
            # if the notebook still exists and we are on the entry that is visible
            if bool(device_data["visible"]) and int(
                    device_data["notebook"]) in self.tab_widgets:
                self.tab_widgets[int(
                    device_data["notebook"])].tab_bar.setCurrentIndex(
                        int(device_data["page"]))

    def update_all_tab_settings(self, settings, tab_data):
        for device_name, tab in self.tablist.items():
            self.settings_dict[device_name]["front_panel_settings"] = settings[
                device_name] if device_name in settings else {}
            self.settings_dict[device_name]["saved_data"] = tab_data[
                device_name]['data'] if device_name in tab_data else {}
            tab.update_from_settings(self.settings_dict[device_name])

    def on_load_front_panel(self, *args, **kwargs):
        # get the file:
        # create file chooser dialog
        dialog = QFileDialog(
            None, "Select file to load",
            self.exp_config.get('paths', 'experiment_shot_storage'),
            "HDF5 files (*.h5 *.hdf5)")
        dialog.setViewMode(QFileDialog.Detail)
        dialog.setFileMode(QFileDialog.ExistingFile)
        if dialog.exec_():
            selected_files = dialog.selectedFiles()
            filepath = str(selected_files[0])
            # Qt has this weird behaviour where if you type in the name of a file that exists
            # but does not have the extension you have limited the dialog to, the OK button is greyed out
            # but you can hit enter and the file will be selected.
            # So we must check the extension of each file here!
            if filepath.endswith('.h5') or filepath.endswith('.hdf5'):
                try:
                    # TODO: Warn that this will restore values, but not channels that are locked
                    message = QMessageBox()
                    message.setText(
                        """Warning: This will modify front panel values and cause device output values to update.
                    \nThe queue and files waiting to be sent for analysis will be cleared.
                    \n
                    \nNote: Channels that are locked will not be updated.\n\nDo you wish to continue?"""
                    )
                    message.setIcon(QMessageBox.Warning)
                    message.setWindowTitle("BLACS")
                    message.setStandardButtons(QMessageBox.Yes
                                               | QMessageBox.No)

                    if message.exec_() == QMessageBox.Yes:
                        front_panel_settings = FrontPanelSettings(
                            filepath, self.connection_table)
                        settings, question, error, tab_data = front_panel_settings.restore(
                        )
                        #TODO: handle question/error

                        # Restore window data
                        self.restore_window(tab_data)
                        self.order_tabs(tab_data)
                        self.update_all_tab_settings(settings, tab_data)

                        # restore queue data
                        if 'queue_data' not in tab_data['BLACS settings']:
                            tab_data['BLACS settings']['queue_data'] = {}
                        else:
                            tab_data['BLACS settings']['queue_data'] = eval(
                                tab_data['BLACS settings']['queue_data'])
                        self.queue.restore_save_data(
                            tab_data['BLACS settings']['queue_data'])
                        # restore analysis data
                        if 'analysis_data' not in tab_data['BLACS settings']:
                            tab_data['BLACS settings']['analysis_data'] = {}
                        else:
                            tab_data['BLACS settings']['analysis_data'] = eval(
                                tab_data['BLACS settings']['analysis_data'])
                        self.analysis_submission.restore_save_data(
                            tab_data['BLACS settings']["analysis_data"])
                except Exception as e:
                    logger.exception("Unable to load the front panel in %s." %
                                     (filepath))
                    message = QMessageBox()
                    message.setText(
                        "Unable to load the front panel. The error encountered is printed below.\n\n%s"
                        % str(e))
                    message.setIcon(QMessageBox.Information)
                    message.setWindowTitle("BLACS")
                    message.exec_()
                finally:
                    dialog.deleteLater()
            else:
                dialog.deleteLater()
                message = QMessageBox()
                message.setText(
                    "You did not select a file ending with .h5 or .hdf5. Please try again"
                )
                message.setIcon(QMessageBox.Information)
                message.setWindowTitle("BLACS")
                message.exec_()
                QTimer.singleShot(10, self.on_load_front_panel)

    def on_save_exit(self):
        # Save front panel
        data = self.front_panel_settings.get_save_data()

        # with h5py.File(self.settings_path,'r+') as h5file:
        # if 'connection table' in h5file:
        # del h5file['connection table']

        self.front_panel_settings.save_front_panel_to_h5(
            self.settings_path,
            data[0],
            data[1],
            data[2],
            data[3], {"overwrite": True},
            force_new_conn_table=True)
        logger.info('Destroying tabs')
        for tab in self.tablist.values():
            tab.destroy()

        #gobject.timeout_add(100,self.finalise_quit,time.time())
        QTimer.singleShot(100, lambda: self.finalise_quit(time.time()))

    def finalise_quit(self, initial_time):
        logger.info('finalise_quit called')
        tab_close_timeout = 2
        # Kill any tabs which didn't close themselves:
        for name, tab in self.tablist.items():
            if tab.destroy_complete:
                del self.tablist[name]
        if self.tablist:
            for name, tab in self.tablist.items():
                # If a tab has a fatal error or is taking too long to close, force close it:
                if (time.time() - initial_time >
                        tab_close_timeout) or tab.state == 'fatal error':
                    try:
                        tab.close_tab()
                    except Exception as e:
                        logger.error('Couldn\'t close tab:\n%s' % str(e))
                    del self.tablist[name]
        if self.tablist:
            QTimer.singleShot(100, lambda: self.finalise_quit(initial_time))
        else:
            self.exit_complete = True
            logger.info('quitting')

    def on_save_front_panel(self, *args, **kwargs):
        data = self.front_panel_settings.get_save_data()

        # Open save As dialog
        dialog = QFileDialog(
            None, "Save BLACS state",
            self.exp_config.get('paths', 'experiment_shot_storage'),
            "HDF5 files (*.h5)")
        try:
            dialog.setViewMode(QFileDialog.Detail)
            dialog.setFileMode(QFileDialog.AnyFile)
            dialog.setAcceptMode(QFileDialog.AcceptSave)

            if dialog.exec_():
                current_file = str(dialog.selectedFiles()[0])
                if not current_file.endswith('.h5'):
                    current_file += '.h5'
                self.front_panel_settings.save_front_panel_to_h5(
                    current_file, data[0], data[1], data[2], data[3])
        except Exception:
            raise
        finally:
            dialog.deleteLater()

    def on_open_preferences(self, *args, **kwargs):
        self.settings.create_dialog()
コード例 #3
0
class BLACS(object):

    tab_widget_ids = 7

    def __init__(self,application):
        self.qt_application = application
        #self.qt_application.aboutToQuit.connect(self.destroy)
        self._relaunch = False
        self.exiting = False
        self.exit_complete = False

        logger.info('Loading BLACS ui')
        #self.ui = BLACSWindow(self).ui
        loader = UiLoader()
        loader.registerCustomWidget(QueueTreeview)
        #loader.registerCustomPromotion('BLACS',BLACSWindow)
        self.ui = loader.load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'main.ui'), BLACSWindow())
        logger.info('BLACS ui loaded')
        self.ui.blacs=self
        self.tab_widgets = {}
        self.exp_config = exp_config # Global variable
        self.settings_path = settings_path # Global variable
        self.connection_table = connection_table # Global variable
        self.connection_table_h5file = self.exp_config.get('paths','connection_table_h5')
        self.connection_table_labscript = self.exp_config.get('paths','connection_table_py')
        
        # Setup the UI
        self.ui.main_splitter.setStretchFactor(0,0)
        self.ui.main_splitter.setStretchFactor(1,1)

        self.tablist = {}
        self.panes = {}
        self.settings_dict = {}

        # Setup progressbar monitoring status queue
        self.ui.TimeNext_progressBar.setRange(0,100)

        self._countdown_queue = Queue.Queue()
        Queue_Receiver = QueueToSignal( self._countdown_queue)
        Queue_Receiver.mysignal.connect(self.ui.TimeNext_progressBar.setValue)
        thread = QThread()
        Queue_Receiver.moveToThread(thread)
        thread.started.connect(Queue_Receiver.run)
        thread.start()

        # Find which devices are connected to BLACS, and what their labscript class names are:
        logger.info('finding connected devices in connection table')
        self.attached_devices = self.connection_table.get_attached_devices()

        # Store the panes in a dictionary for easy access
        self.panes['tab_top_vertical_splitter'] = self.ui.tab_top_vertical_splitter
        self.panes['tab_bottom_vertical_splitter'] = self.ui.tab_bottom_vertical_splitter
        self.panes['tab_horizontal_splitter'] = self.ui.tab_horizontal_splitter
        self.panes['main_splitter'] = self.ui.main_splitter

        # Get settings to restore
        logger.info('Loading front panel settings')
        self.front_panel_settings = FrontPanelSettings(self.settings_path, self.connection_table)
        self.front_panel_settings.setup(self)
        settings,question,error,tab_data = self.front_panel_settings.restore()

        # TODO: handle question/error cases

        logger.info('restoring window data')
        self.restore_window(tab_data)

        #splash.update_text('Creating the device tabs...')
        # Create the notebooks
        logger.info('Creating tab widgets')
        for i in range(4):
            self.tab_widgets[i] = DragDropTabWidget(self.tab_widget_ids)
            getattr(self.ui,'tab_container_%d'%i).addWidget(self.tab_widgets[i])

        logger.info('Instantiating devices')
        for device_name, labscript_device_class_name in self.attached_devices.items():
            self.settings_dict.setdefault(device_name,{"device_name":device_name})
            # add common keys to settings:
            self.settings_dict[device_name]["connection_table"] = self.connection_table
            self.settings_dict[device_name]["front_panel_settings"] = settings[device_name] if device_name in settings else {}
            self.settings_dict[device_name]["saved_data"] = tab_data[device_name]['data'] if device_name in tab_data else {}
            # Instantiate the device
            logger.info('instantiating %s'%device_name)
            TabClass = labscript_devices.get_BLACS_tab(labscript_device_class_name)
            self.tablist[device_name] = TabClass(self.tab_widgets[0],self.settings_dict[device_name])

        logger.info('reordering tabs')
        self.order_tabs(tab_data)

        logger.info('starting analysis submission thread')
        # setup analysis submission
        self.analysis_submission = AnalysisSubmission(self,self.ui)
        if 'analysis_data' not in tab_data['BLACS settings']:
            tab_data['BLACS settings']['analysis_data'] = {}
        else:
            tab_data['BLACS settings']['analysis_data'] = eval(tab_data['BLACS settings']['analysis_data'])
        self.analysis_submission.restore_save_data(tab_data['BLACS settings']["analysis_data"])

        logger.info('starting queue manager thread')
        # Setup the QueueManager
        self.queue = QueueManager(self,self.ui)
        if 'queue_data' not in tab_data['BLACS settings']:
            tab_data['BLACS settings']['queue_data'] = {}
        else:
            tab_data['BLACS settings']['queue_data'] = eval(tab_data['BLACS settings']['queue_data'])
        self.queue.restore_save_data(tab_data['BLACS settings']['queue_data'])

        logger.info('instantiating plugins')
        # setup the plugin system
        settings_pages = []
        self.plugins = {}
        plugin_settings = eval(tab_data['BLACS settings']['plugin_data']) if 'plugin_data' in tab_data['BLACS settings'] else {}
        for module_name, module in plugins.modules.items():
            try:
                # instantiate the plugin
                self.plugins[module_name] = module.Plugin(plugin_settings[module_name] if module_name in plugin_settings else {})
            except Exception:
                logger.exception('Could not instantiate plugin \'%s\'. Skipping')

        blacs_data = {'exp_config':self.exp_config,
                      'ui':self.ui,
                      'set_relaunch':self.set_relaunch,
                      'plugins':self.plugins,
                      'connection_table_h5file':self.connection_table_h5file,
                      'connection_table_labscript':self.connection_table_labscript,
                      'experiment_queue':self.queue
                     }

        def create_menu(parent, menu_parameters):
            if 'name' in menu_parameters:
                if 'menu_items' in menu_parameters:
                    child = parent.addMenu(menu_parameters['name'])
                    for child_menu_params in menu_parameters['menu_items']:
                        create_menu(child,child_menu_params)
                else:
                    child = parent.addAction(menu_parameters['name'])

                if 'action' in menu_parameters:
                    child.triggered.connect(menu_parameters['action'])

            elif 'separator' in menu_parameters:
                parent.addSeparator()

        # setup the Notification system
        logger.info('setting up notification system')
        self.notifications = Notifications(blacs_data)

        settings_callbacks = []
        for module_name, plugin in self.plugins.items():
            try:
                # Setup settings page
                settings_pages.extend(plugin.get_setting_classes())
                # Setup menu
                if plugin.get_menu_class():
                    # must store a reference or else the methods called when the menu actions are triggered
                    # (contained in this object) will be garbaged collected
                    menu = plugin.get_menu_class()(blacs_data)
                    create_menu(self.ui.menubar,menu.get_menu_items())
                    plugin.set_menu_instance(menu)

                # Setup notifications
                plugin_notifications = {}
                for notification_class in plugin.get_notification_classes():
                    self.notifications.add_notification(notification_class)
                    plugin_notifications[notification_class] = self.notifications.get_instance(notification_class)
                plugin.set_notification_instances(plugin_notifications)

                # Register callbacks
                callbacks = plugin.get_callbacks()
                # save the settings_changed callback in a separate list for setting up later
                if isinstance(callbacks,dict) and 'settings_changed' in callbacks:
                    settings_callbacks.append(callbacks['settings_changed'])

            except Exception:
                logger.exception('Plugin \'%s\' error. Plugin may not be functional.'%module_name)


        # setup the BLACS preferences system
        logger.info('setting up preferences system')
        self.settings = Settings(file=self.settings_path, parent = self.ui, page_classes=settings_pages)
        for callback in settings_callbacks:
            self.settings.register_callback(callback)

        # update the blacs_data dictionary with the settings system
        blacs_data['settings'] = self.settings

        for module_name, plugin in self.plugins.items():
            try:
                plugin.plugin_setup_complete(blacs_data)
            except Exception:
                # backwards compatibility for old plugins
                try:
                    plugin.plugin_setup_complete()
                    logger.warning('Plugin \'%s\' using old API. Please update Plugin.plugin_setup_complete method to accept a dictionary of blacs_data as the only argument.'%module_name)
                except Exception:
                    logger.exception('Plugin \'%s\' error. Plugin may not be functional.'%module_name)

        # Connect menu actions
        self.ui.actionOpenPreferences.triggered.connect(self.on_open_preferences)
        self.ui.actionSave.triggered.connect(self.on_save_front_panel)
        self.ui.actionOpen.triggered.connect(self.on_load_front_panel)

        # Connect the windows AppId stuff:
        if os.name == 'nt':
            self.ui.newWindow.connect(set_win_appusermodel)

        logger.info('showing UI')
        self.ui.show()

    def set_relaunch(self,value):
        self._relaunch = bool(value)

    def restore_window(self,tab_data):
        # read out position settings:
        try:
            # There are some dodgy hacks going on here to try and restore the window position correctly
            # Unfortunately Qt has two ways of measuring teh window position, one with the frame/titlebar
            # and one without. If you use the one that measures including the titlebar, you don't
            # know what the window size was when the window was UNmaximized.
            #
            # Anyway, no idea if this works cross platform (tested on windows 8)
            # Feel free to rewrite this, along with the code in front_panel_settings.py
            # which stores the values
            #
            # Actually this is a waste of time because if you close when maximized, reoopen and then
            # de-maximize, the window moves to a random position (not the position it was at before maximizing)
            # so bleh!
            self.ui.move(tab_data['BLACS settings']["window_xpos"]-tab_data['BLACS settings']['window_frame_width']/2,tab_data['BLACS settings']["window_ypos"]-tab_data['BLACS settings']['window_frame_height']+tab_data['BLACS settings']['window_frame_width']/2)
            self.ui.resize(tab_data['BLACS settings']["window_width"],tab_data['BLACS settings']["window_height"])

            if 'window_maximized' in tab_data['BLACS settings'] and tab_data['BLACS settings']['window_maximized']:
                self.ui.showMaximized()

            for pane_name,pane in self.panes.items():
                pane.setSizes(tab_data['BLACS settings'][pane_name])

        except Exception as e:
            logger.warning("Unable to load window and notebook defaults. Exception:"+str(e))

    def order_tabs(self,tab_data):
        # Move the tabs to the correct notebook
        for device_name in self.attached_devices:
            notebook_num = 0
            if device_name in tab_data:
                notebook_num = int(tab_data[device_name]["notebook"])
                if notebook_num not in self.tab_widgets:
                    notebook_num = 0

            #Find the notebook the tab is in, and remove it:
            for notebook in self.tab_widgets.values():
                tab_index = notebook.indexOf(self.tablist[device_name]._ui)
                if tab_index != -1:
                    notebook.removeTab(tab_index)
                    self.tab_widgets[notebook_num].addTab(self.tablist[device_name]._ui,device_name)
                    break

        # splash.update_text('restoring tab positions...')
        # # Now that all the pages are created, reorder them!
        for device_name in self.attached_devices:
            if device_name in tab_data:
                notebook_num = int(tab_data[device_name]["notebook"])
                if notebook_num in self.tab_widgets:
                    self.tab_widgets[notebook_num].tab_bar.moveTab(self.tab_widgets[notebook_num].indexOf(self.tablist[device_name]._ui),int(tab_data[device_name]["page"]))

        # # Now that they are in the correct order, set the correct one visible
        for device_name,device_data in tab_data.items():
            if device_name == 'BLACS settings':
                continue
            # if the notebook still exists and we are on the entry that is visible
            if bool(device_data["visible"]) and int(device_data["notebook"]) in self.tab_widgets:
                self.tab_widgets[int(device_data["notebook"])].tab_bar.setCurrentIndex(int(device_data["page"]))

    def update_all_tab_settings(self,settings,tab_data):
        for device_name,tab in self.tablist.items():
            self.settings_dict[device_name]["front_panel_settings"] = settings[device_name] if device_name in settings else {}
            self.settings_dict[device_name]["saved_data"] = tab_data[device_name]['data'] if device_name in tab_data else {}
            tab.update_from_settings(self.settings_dict[device_name])

    def on_load_front_panel(self,*args,**kwargs):
        # get the file:
        # create file chooser dialog
        dialog = QFileDialog(None,"Select file to load", self.exp_config.get('paths','experiment_shot_storage'), "HDF5 files (*.h5 *.hdf5)")
        dialog.setViewMode(QFileDialog.Detail)
        dialog.setFileMode(QFileDialog.ExistingFile)
        if dialog.exec_():
            selected_files = dialog.selectedFiles()
            filepath = str(selected_files[0])
            # Qt has this weird behaviour where if you type in the name of a file that exists
            # but does not have the extension you have limited the dialog to, the OK button is greyed out
            # but you can hit enter and the file will be selected.
            # So we must check the extension of each file here!
            if filepath.endswith('.h5') or filepath.endswith('.hdf5'):
                try:
                    # TODO: Warn that this will restore values, but not channels that are locked
                    message = QMessageBox()
                    message.setText("""Warning: This will modify front panel values and cause device output values to update.
                    \nThe queue and files waiting to be sent for analysis will be cleared.
                    \n
                    \nNote: Channels that are locked will not be updated.\n\nDo you wish to continue?""")
                    message.setIcon(QMessageBox.Warning)
                    message.setWindowTitle("BLACS")
                    message.setStandardButtons(QMessageBox.Yes|QMessageBox.No)

                    if message.exec_() == QMessageBox.Yes:
                        front_panel_settings = FrontPanelSettings(filepath, self.connection_table)
                        settings,question,error,tab_data = front_panel_settings.restore()
                        #TODO: handle question/error

                        # Restore window data
                        self.restore_window(tab_data)
                        self.order_tabs(tab_data)
                        self.update_all_tab_settings(settings,tab_data)

                        # restore queue data
                        if 'queue_data' not in tab_data['BLACS settings']:
                            tab_data['BLACS settings']['queue_data'] = {}
                        else:
                            tab_data['BLACS settings']['queue_data'] = eval(tab_data['BLACS settings']['queue_data'])
                        self.queue.restore_save_data(tab_data['BLACS settings']['queue_data'])
                        # restore analysis data
                        if 'analysis_data' not in tab_data['BLACS settings']:
                            tab_data['BLACS settings']['analysis_data'] = {}
                        else:
                            tab_data['BLACS settings']['analysis_data'] = eval(tab_data['BLACS settings']['analysis_data'])
                        self.analysis_submission.restore_save_data(tab_data['BLACS settings']["analysis_data"])
                except Exception as e:
                    logger.exception("Unable to load the front panel in %s."%(filepath))
                    message = QMessageBox()
                    message.setText("Unable to load the front panel. The error encountered is printed below.\n\n%s"%str(e))
                    message.setIcon(QMessageBox.Information)
                    message.setWindowTitle("BLACS")
                    message.exec_()
                finally:
                    dialog.deleteLater()
            else:
                dialog.deleteLater()
                message = QMessageBox()
                message.setText("You did not select a file ending with .h5 or .hdf5. Please try again")
                message.setIcon(QMessageBox.Information)
                message.setWindowTitle("BLACS")
                message.exec_()
                QTimer.singleShot(10,self.on_load_front_panel)

    def on_save_exit(self):
        # Save front panel
        data = self.front_panel_settings.get_save_data()

        # with h5py.File(self.settings_path,'r+') as h5file:
           # if 'connection table' in h5file:
               # del h5file['connection table']

        self.front_panel_settings.save_front_panel_to_h5(self.settings_path,data[0],data[1],data[2],data[3],{"overwrite":True},force_new_conn_table=True)
        logger.info('Destroying tabs')
        for tab in self.tablist.values():
            tab.destroy()

        #gobject.timeout_add(100,self.finalise_quit,time.time())
        QTimer.singleShot(100,lambda: self.finalise_quit(time.time()))

    def finalise_quit(self,initial_time):
        logger.info('finalise_quit called')
        tab_close_timeout = 2
        # Kill any tabs which didn't close themselves:
        for name, tab in self.tablist.items():
            if tab.destroy_complete:
                del self.tablist[name]
        if self.tablist:
            for name, tab in self.tablist.items():
                # If a tab has a fatal error or is taking too long to close, force close it:
                if (time.time() - initial_time > tab_close_timeout) or tab.state == 'fatal error':
                    try:
                        tab.close_tab()
                    except Exception as e:
                        logger.error('Couldn\'t close tab:\n%s'%str(e))
                    del self.tablist[name]
        if self.tablist:
            QTimer.singleShot(100,lambda: self.finalise_quit(initial_time))
        else:
            self.exit_complete = True
            logger.info('quitting')

    def on_save_front_panel(self,*args,**kwargs):
        data = self.front_panel_settings.get_save_data()

        # Open save As dialog
        dialog = QFileDialog(None,"Save BLACS state", self.exp_config.get('paths','experiment_shot_storage'), "HDF5 files (*.h5)")
        try:
            dialog.setViewMode(QFileDialog.Detail)
            dialog.setFileMode(QFileDialog.AnyFile)
            dialog.setAcceptMode(QFileDialog.AcceptSave)

            if dialog.exec_():
                current_file = str(dialog.selectedFiles()[0])
                if not current_file.endswith('.h5'):
                    current_file += '.h5'
                self.front_panel_settings.save_front_panel_to_h5(current_file,data[0],data[1],data[2],data[3])
        except Exception:
            raise
        finally:
            dialog.deleteLater()

    def on_open_preferences(self,*args,**kwargs):
        self.settings.create_dialog()