Ejemplo n.º 1
0
class _QtMenuBar(_AbstractMenuBar):
    def _menu_initialize(self, window=None):
        self._menus = dict()
        self._menu_actions = dict()
        self._menu_bar = QMenuBar()
        self._menu_bar.setNativeMenuBar(False)
        window = self._window if window is None else window
        window.setMenuBar(self._menu_bar)

    def _menu_add_submenu(self, name, desc):
        self._menus[name] = self._menu_bar.addMenu(desc)
        self._menu_actions[name] = dict()

    def _menu_add_button(self, menu_name, name, desc, func):
        menu = self._menus[menu_name]
        self._menu_actions[menu_name][name] = \
            _QtAction(menu.addAction(desc, func))
Ejemplo n.º 2
0
class MainWindow(QMainWindow):
    def __init__(self, log, app):
        self.log = log.getChild('Main')
        self.app = app
        super().__init__()

        self.dark_theme = CONFIG['dark_theme_default']
        self.single_tab_mode = CONFIG['single_tab_mode_default']

        self.loggers_by_name = {}  # name -> LoggerTab
        self.popped_out_loggers = {}

        self.server_running = False
        self.shutting_down = False

        self.setupUi()
        self.start_server()

    def setupUi(self):
        self.resize(800, 600)
        self.setWindowTitle('cutelog')

        self.loggerTabWidget = QTabWidget(self)
        self.loggerTabWidget.setTabsClosable(True)
        self.loggerTabWidget.setMovable(True)
        self.loggerTabWidget.setTabBarAutoHide(True)
        self.loggerTabWidget.currentChanged.connect(self.change_actions_state)
        self.setCentralWidget(self.loggerTabWidget)

        self.statusbar = QStatusBar(self)
        self.setStatusBar(self.statusbar)

        self.setup_menubar()
        self.setup_action_triggers()
        self.setup_shortcuts()

        self.loggerTabWidget.tabCloseRequested.connect(self.close_tab)

        self.reload_stylesheet()
        self.restore_geometry()

        self.show()

    def setup_menubar(self):
        self.menubar = QMenuBar(self)
        self.setMenuBar(self.menubar)

        # File menu
        self.menuFile = self.menubar.addMenu("File")
        self.actionLoadRecords = self.menuFile.addAction('Load records')
        self.actionSaveRecords = self.menuFile.addAction('Save records')
        self.menuFile.addSeparator()
        self.actionDarkTheme = self.menuFile.addAction('Dark theme')
        self.actionDarkTheme.setCheckable(True)
        self.actionDarkTheme.setChecked(self.dark_theme)
        self.actionSingleTab = self.menuFile.addAction('Single tab mode')
        self.actionSingleTab.setCheckable(True)
        self.actionSingleTab.setChecked(self.single_tab_mode)
        # self.actionReloadStyle = self.menuFile.addAction('Reload style')
        self.actionSettings = self.menuFile.addAction('Settings')
        self.menuFile.addSeparator()
        self.actionQuit = self.menuFile.addAction('Quit')

        # Tab menu
        self.menuTab = self.menubar.addMenu("Tab")
        self.actionCloseTab = self.menuTab.addAction('Close')
        self.actionPopOut = self.menuTab.addAction('Pop out')
        self.actionRenameTab = self.menuTab.addAction('Rename')
        self.menuTab.addSeparator()
        self.actionPopIn = self.menuTab.addAction('Pop in tabs...')
        self.actionMergeTabs = self.menuTab.addAction('Merge tabs...')
        self.menuTab.addSeparator()
        self.actionExtraMode = self.menuTab.addAction('Extra mode')
        self.actionExtraMode.setCheckable(True)

        # Server menu
        self.menuServer = self.menubar.addMenu("Server")
        self.actionRestartServer = self.menuServer.addAction('Restart server')
        self.actionStartStopServer = self.menuServer.addAction('Stop server')

        # Records menu
        self.menuRecords = self.menubar.addMenu("Records")
        self.actionTrimTabRecords = self.menuRecords.addAction('Trim records')
        self.actionSetMaxCapacity = self.menuRecords.addAction(
            'Set max capacity')

        # Help menu
        self.menuHelp = self.menubar.addMenu("Help")
        self.actionAbout = self.menuHelp.addAction("About cutelog")

        self.change_actions_state(
        )  # to disable all logger actions, since they don't function yet

    def setup_action_triggers(self):
        # File menu
        self.actionLoadRecords.triggered.connect(self.open_load_records_dialog)
        self.actionSaveRecords.triggered.connect(self.open_save_records_dialog)
        self.actionDarkTheme.toggled.connect(self.toggle_dark_theme)
        self.actionSingleTab.triggered.connect(self.set_single_tab_mode)
        # self.actionReloadStyle.triggered.connect(self.reload_stylesheet)
        self.actionSettings.triggered.connect(self.settings_dialog)
        self.actionQuit.triggered.connect(self.shutdown)

        # Tab meny
        self.actionCloseTab.triggered.connect(self.close_current_tab)
        self.actionPopOut.triggered.connect(self.pop_out_tab)
        self.actionRenameTab.triggered.connect(self.rename_tab_dialog)
        self.actionPopIn.triggered.connect(self.pop_in_tabs_dialog)
        self.actionMergeTabs.triggered.connect(self.merge_tabs_dialog)
        self.actionExtraMode.triggered.connect(self.toggle_extra_mode)

        # Server menu
        self.actionRestartServer.triggered.connect(self.restart_server)
        self.actionStartStopServer.triggered.connect(self.start_or_stop_server)

        # Records menu
        self.actionTrimTabRecords.triggered.connect(self.trim_records_dialog)
        self.actionSetMaxCapacity.triggered.connect(self.max_capacity_dialog)

        # About menu
        self.actionAbout.triggered.connect(self.about_dialog)

    def change_actions_state(self, index=None):
        logger, _ = self.current_logger_and_index()
        # if there are no loggers in tabs, these actions will be disabled:
        actions = [
            self.actionCloseTab, self.actionExtraMode, self.actionPopOut,
            self.actionRenameTab, self.actionPopIn, self.actionTrimTabRecords,
            self.actionSetMaxCapacity, self.actionSaveRecords
        ]

        if not logger:
            for action in actions:
                action.setDisabled(True)
            self.actionExtraMode.setChecked(False)
            if len(self.popped_out_loggers) > 0:
                self.actionPopIn.setDisabled(False)
        else:
            for action in actions:
                action.setDisabled(False)
            if len(self.loggers_by_name) == self.loggerTabWidget.count():
                self.actionPopIn.setDisabled(True)
            self.actionExtraMode.setChecked(logger.extra_mode)

        # if all loggers are popped in
        if len(self.popped_out_loggers) == 0:
            self.actionPopIn.setDisabled(True)

        if len(self.loggers_by_name) <= 1:
            self.actionMergeTabs.setDisabled(True)
        else:
            self.actionMergeTabs.setDisabled(False)

    def set_single_tab_mode(self, enabled):
        self.single_tab_mode = enabled

    def setup_shortcuts(self):
        self.actionQuit.setShortcut('Ctrl+Q')
        self.actionDarkTheme.setShortcut('Ctrl+S')
        # self.actionReloadStyle.setShortcut('Ctrl+R')
        # self.actionSettings.setShortcut('Ctrl+A')
        self.actionCloseTab.setShortcut('Ctrl+W')

    def save_geometry(self):
        CONFIG.save_geometry(self.geometry())

    def restore_geometry(self):
        geometry = CONFIG.load_geometry()
        if geometry:
            self.resize(geometry.width(), geometry.height())

    def settings_dialog(self):
        d = SettingsDialog(self)
        d.setWindowModality(Qt.ApplicationModal)
        d.setAttribute(Qt.WA_DeleteOnClose, True)
        d.open()

    def about_dialog(self):
        d = AboutDialog(self)
        d.open()

    def reload_stylesheet(self):
        if self.dark_theme:
            self.reload_dark_style()
        else:
            self.reload_light_style()

    def reload_light_style(self):
        if CONFIG['light_theme_is_native']:
            self.set_style_to_stock()
            return
        f = QFile(":/light_theme.qss")
        f.open(QFile.ReadOnly | QFile.Text)
        ts = QTextStream(f)
        qss = ts.readAll()
        # f = open(Config.get_resource_path('light_theme.qss', 'resources/ui'), 'r')
        # qss = f.read()
        self.app.setStyleSheet(qss)

    def reload_dark_style(self):
        f = QFile(":/dark_theme.qss")
        f.open(QFile.ReadOnly | QFile.Text)
        ts = QTextStream(f)
        qss = ts.readAll()
        # f = open(Config.get_resource_path('dark_theme.qss', 'resources/ui'), 'r')
        # qss = f.read()
        self.app.setStyleSheet(qss)

    def set_style_to_stock(self):
        self.app.setStyleSheet('')

    def toggle_dark_theme(self, enabled):
        self.dark_theme = enabled
        self.reload_stylesheet()

        for logger in self.loggers_by_name.values():
            logger.set_dark_theme(enabled)

    def toggle_extra_mode(self, enabled):
        logger, _ = self.current_logger_and_index()
        if not logger:
            return
        logger.set_extra_mode(enabled)

    def start_server(self):
        self.log.debug('Starting the server')
        self.server = LogServer(self, self.on_connection, self.log)
        self.server.start()
        self.server_running = True
        self.actionStartStopServer.setText('Stop server')

    def stop_server(self):
        if self.server_running:
            self.log.debug('Stopping the server')
            self.server.close_server()
            self.server_running = False
            del self.server
            self.server = None
        self.actionStartStopServer.setText('Start server')

    def restart_server(self):
        self.log.debug('Restarting the server')
        self.stop_server()
        self.start_server()

    def start_or_stop_server(self):
        if self.server_running:
            self.stop_server()
        else:
            self.start_server()

    def on_connection(self, conn, conn_id):
        self.log.debug('New connection id={}'.format(conn_id))

        if self.single_tab_mode and len(self.loggers_by_name) > 0:
            new_logger = list(self.loggers_by_name.values())[0]
            new_logger.add_connection(conn)
        else:
            new_logger, index = self.create_logger(conn)
            self.loggerTabWidget.setCurrentIndex(index)

        conn.new_record.connect(new_logger.on_record)
        conn.connection_finished.connect(new_logger.remove_connection)

        if self.server.benchmark and conn_id == -1:
            from .listener import BenchmarkMonitor
            bm = BenchmarkMonitor(self, new_logger)
            bm.speed_readout.connect(self.set_status)
            conn.connection_finished.connect(bm.requestInterruption)
            self.server.threads.append(bm)
            bm.start()

    def create_logger(self, conn, name=None):
        name = self.make_logger_name_unique("Logger" if name is None else name)
        new_logger = LoggerTab(self.loggerTabWidget, name, conn, self.log,
                               self)
        new_logger.set_dark_theme(self.dark_theme)
        self.loggers_by_name[name] = new_logger
        index = self.loggerTabWidget.addTab(new_logger, name)
        return new_logger, index

    def make_logger_name_unique(self, name):
        name_f = "{} {{}}".format(name)
        c = 1
        while name in self.loggers_by_name:
            name = name_f.format(c)
            c += 1
        return name

    def set_status(self, string, timeout=3000):
        self.statusBar().showMessage(string, timeout)

    def rename_tab_dialog(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return

        d = QInputDialog(self)
        d.setLabelText('Enter the new name for the "{}" tab:'.format(
            logger.name))
        d.setWindowTitle('Rename the "{}" tab'.format(logger.name))
        d.textValueSelected.connect(self.rename_current_tab)
        d.open()

    def rename_current_tab(self, new_name):
        logger, index = self.current_logger_and_index()
        if new_name in self.loggers_by_name and new_name != logger.name:
            show_warning_dialog(
                self, "Rename error",
                'Logger named "{}" already exists.'.format(new_name))
            return
        self.log.debug('Renaming logger "{}" to "{}"'.format(
            logger.name, new_name))
        del self.loggers_by_name[logger.name]
        logger.name = new_name
        self.loggers_by_name[new_name] = logger
        logger.log.name = '.'.join(
            logger.log.name.split('.')[:-1]) + '.{}'.format(new_name)
        self.loggerTabWidget.setTabText(index, new_name)

    def trim_records_dialog(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return

        d = QInputDialog(self)
        d.setInputMode(QInputDialog.IntInput)
        d.setIntRange(
            0, 100000000)  # because it sets intMaximum to 99 by default. why??
        d.setLabelText('Keep this many records out of {}:'.format(
            logger.record_model.rowCount()))
        d.setWindowTitle('Trim tab records of "{}" logger'.format(logger.name))
        d.intValueSelected.connect(self.trim_current_tab_records)
        d.open()

    def trim_current_tab_records(self, n):
        logger, index = self.current_logger_and_index()
        logger.record_model.trim_except_last_n(n)

    def max_capacity_dialog(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return

        d = QInputDialog(self)
        d.setInputMode(QInputDialog.IntInput)
        d.setIntRange(
            0, 100000000)  # because it sets intMaximum to 99 by default. why??
        max_now = logger.record_model.max_capacity
        max_now = "not set" if max_now is None else max_now
        label_str = 'Set max capacity for "{}" logger\nCurrently {}. Set to 0 to disable:'
        d.setLabelText(label_str.format(logger.name, max_now))
        d.setWindowTitle('Set max capacity')
        d.intValueSelected.connect(self.set_max_capacity)
        d.open()

    def set_max_capacity(self, n):
        logger, index = self.current_logger_and_index()
        logger.set_max_capacity(n)

    def merge_tabs_dialog(self):
        d = MergeDialog(self, self.loggers_by_name)
        d.setWindowModality(Qt.WindowModal)
        d.merge_tabs_signal.connect(self.merge_tabs)
        d.show()

    def merge_tabs(self, dst, srcs, keep_alive):
        self.log.debug('Merging tabs: dst="{}", srcs={}, keep={}'.format(
            dst, srcs, keep_alive))

        dst_logger = self.loggers_by_name[dst]
        for src_name in srcs:
            src_logger = self.loggers_by_name[src_name]

            dst_logger.merge_with_records(src_logger.record_model.records)

            if keep_alive:
                for conn in src_logger.connections:
                    conn.new_record.disconnect(src_logger.on_record)
                    conn.connection_finished.disconnect(
                        src_logger.remove_connection)
                    conn.new_record.connect(dst_logger.on_record)
                    dst_logger.add_connection(conn)
                src_logger.connections.clear()
            self.destroy_logger(src_logger)

    def close_current_tab(self):
        _, index = self.current_logger_and_index()
        if index is None:
            return
        self.close_tab(index)

    def close_tab(self, index):
        self.log.debug("Tab close requested: {}".format(index))
        logger = self.loggerTabWidget.widget(index)
        self.loggerTabWidget.removeTab(index)
        self.log.debug(logger.name)
        self.destroy_logger(logger)

    def destroy_logger(self, logger):
        del self.loggers_by_name[logger.name]
        logger.setParent(None)
        logger.destroy()
        del logger

    def close_popped_out_logger(self, logger):
        del self.loggers_by_name[logger.name]
        del self.popped_out_loggers[logger.name]
        del logger
        if len(self.popped_out_loggers):
            self.actionPopIn.setDisabled(True)

    def current_logger_and_index(self):
        index = self.loggerTabWidget.currentIndex()
        if index == -1:
            return None, None

        logger = self.loggerTabWidget.widget(index)
        return logger, index

    def pop_out_tab(self):
        logger, index = self.current_logger_and_index()
        if not logger:
            return
        self.log.debug("Tab pop out requested: {}".format(int(index)))

        logger.destroyed.connect(logger.closeEvent)
        logger.setAttribute(Qt.WA_DeleteOnClose, True)
        logger.setWindowFlags(Qt.Window)
        logger.setWindowTitle('cutelog: "{}"'.format(
            self.loggerTabWidget.tabText(index)))
        self.popped_out_loggers[logger.name] = logger
        self.loggerTabWidget.removeTab(index)
        logger.popped_out = True
        logger.show()
        center_widget_on_screen(logger)

    def pop_in_tabs_dialog(self):
        d = PopInDialog(self, self.loggers_by_name.values())
        d.pop_in_tabs.connect(self.pop_in_tabs)
        d.setWindowModality(Qt.ApplicationModal)
        d.open()

    def pop_in_tabs(self, names):
        for name in names:
            self.log.debug('Popping in logger "{}"'.format(name))
            logger = self.loggers_by_name[name]
            self.pop_in_tab(logger)

    def pop_in_tab(self, logger):
        logger.setWindowFlags(Qt.Widget)
        logger.setAttribute(Qt.WA_DeleteOnClose, False)
        logger.destroyed.disconnect(logger.closeEvent)
        logger.setWindowTitle(logger.name)
        logger.popped_out = False
        del self.popped_out_loggers[logger.name]
        index = self.loggerTabWidget.addTab(logger, logger.windowTitle())
        self.loggerTabWidget.setCurrentIndex(index)

    def open_load_records_dialog(self):
        d = QFileDialog(self)
        d.setFileMode(QFileDialog.ExistingFile)
        d.fileSelected.connect(self.load_records)
        d.setWindowTitle('Load records from...')
        d.open()

    def load_records(self, load_path):
        import json
        from os import path

        class RecordDecoder(json.JSONDecoder):
            def __init__(self, *args, **kwargs):
                json.JSONDecoder.__init__(self,
                                          object_hook=self.object_hook,
                                          *args,
                                          **kwargs)

            def object_hook(self, obj):
                if '_created' in obj:
                    obj['created'] = obj['_created']
                    del obj['_created']
                    record = LogRecord(obj)
                    del record._logDict['created']
                else:
                    record = LogRecord(obj)
                return record

        name = path.basename(load_path)
        index = None

        try:
            with open(load_path, 'r') as f:
                records = json.load(f, cls=RecordDecoder)
                new_logger, index = self.create_logger(None, name)
                new_logger.merge_with_records(records)
                self.loggerTabWidget.setCurrentIndex(index)
            self.set_status('Records have been loaded into "{}" tab'.format(
                new_logger.name))
        except Exception as e:
            if index:
                self.close_tab(index)
            text = "Error while loading records: \n{}".format(e)
            self.log.error(text, exc_info=True)
            show_critical_dialog(self, "Couldn't load records", text)

    def open_save_records_dialog(self):
        from functools import partial
        logger, _ = self.current_logger_and_index()
        if not logger:
            return

        d = QFileDialog(self)
        d.selectFile(logger.name + '.log')
        d.setFileMode(QFileDialog.AnyFile)
        d.fileSelected.connect(partial(self.save_records, logger))
        d.setWindowTitle('Save records of "{}" tab to...'.format(logger.name))
        d.open()

    def save_records(self, logger, path):
        import json

        # needed because a deque is not serializable
        class RecordList(list):
            def __init__(self, records):
                self.records = records

            def __len__(self):
                return len(self.records)

            def __iter__(self):
                for record in self.records:
                    d = record._logDict
                    if not d.get('created', False) and not d.get(
                            'time', False):
                        d['_created'] = record.created
                    yield d

        try:
            records = logger.record_model.records
            record_list = RecordList(records)
            with open(path, 'w') as f:
                json.dump(record_list, f, indent=2)
            self.set_status('Records have been saved to "{}"'.format(path))

        except Exception as e:
            text = "Error while saving records: \n{}".format(e)
            self.log.error(text, exc_info=True)
            show_critical_dialog(self, "Couldn't save records", text)

    def closeEvent(self, event):
        self.log.info('Close event on main window')
        self.shutdown()
        event.ignore(
        )  # prevents errors due to closing the program before server has stopped

    def destroy_all_tabs(self):
        self.log.debug('Destroying tabs')
        delete_this = list(self.loggers_by_name.values()
                           )  # to prevent changing during iteration
        for logger in delete_this:
            self.destroy_logger(logger)

    def shutdown(self):
        self.log.info('Shutting down')
        if self.shutting_down:
            self.log.error('Exiting forcefully')
            raise SystemExit
        self.shutting_down = True
        self.stop_server()
        self.save_geometry()
        self.destroy_all_tabs()
        self.app.quit()

    def signal_handler(self, *args):
        self.shutdown()
Ejemplo n.º 3
0
class BaseWindow(SiriusMainWindow):
    """Base class."""
    def __init__(self, parent=None, prefix=_VACA_PREFIX):
        """Init."""
        super().__init__(parent)
        self.prefix = prefix
        self._curr_dir = _os.path.abspath(_os.path.dirname(__file__))

    def _setupUi(self):
        # menubar
        self.menubar = QMenuBar(self)
        self.menubar.setNativeMenuBar(False)
        self.setMenuBar(self.menubar)
        self.menu = self.menubar.addMenu("Open...")
        self._setupMenu()

        # auxiliar diagnostics widget
        self.auxdig_wid = None
        self._setupDiagWidget()

        # lattice widget
        self.lattice_wid = QSvgWidget(
            _os.path.join(self._curr_dir, self.SVG_FILE))

        # screens view widget (create only one ScrnView)
        self._scrns_wids_dict = dict()
        self._currScrn = 0
        scrn_wid = SiriusScrnView(parent=self,
                                  prefix=self.prefix,
                                  device=self._scrns[self._currScrn])
        scrn_wid.setVisible(True)
        self._scrns_wids_dict[self._currScrn] = scrn_wid
        self.scrns_wid = QWidget()
        lay_scrns = QGridLayout(self.scrns_wid)
        lay_scrns.addWidget(scrn_wid)

        # correction widget
        self.corr_wid = QGroupBox('Screens and Correctors Panel')
        self._scrns_sel_bg = QButtonGroup(parent=self.corr_wid)
        self._scrns_sel_bg.setExclusive(True)
        self._setupScrnsCorrsWidget()

        vlay1 = QVBoxLayout()
        if self.auxdig_wid:
            vlay1.addWidget(self.auxdig_wid)
        vlay1.addWidget(self.scrns_wid)
        vlay2 = QVBoxLayout()
        vlay2.addWidget(self.lattice_wid)
        vlay2.addWidget(self.corr_wid)

        cw = QWidget()
        lay = QHBoxLayout(cw)
        lay.addLayout(vlay1)
        lay.addLayout(vlay2)
        self.setCentralWidget(cw)

    def _setupMenu(self):
        raise NotImplementedError

    def _setupScrnsCorrsWidget(self):
        raise NotImplementedError

    def _setupDiagWidget(self):
        raise NotImplementedError

    @Slot()
    def _setScrnWidget(self):
        scrn_obj = self._scrns_wids_dict[self._currScrn]
        scrn_obj.setVisible(False)

        sender = self.sender()
        self._currScrn = self._scrns_sel_bg.id(sender)

        if self._currScrn not in self._scrns_wids_dict.keys():
            scrn_obj = SiriusScrnView(parent=self,
                                      prefix=self.prefix,
                                      device=self._scrns[self._currScrn])
            self.scrns_wid.layout().addWidget(scrn_obj, 2, 0)
            self._scrns_wids_dict[self._currScrn] = scrn_obj
        else:
            scrn_obj = self._scrns_wids_dict[self._currScrn]

        self._scrns_wids_dict[self._currScrn].setVisible(True)

    def _create_headerline(self, labels):
        """Create and return a headerline."""
        hl = QWidget()
        hl.setLayout(QHBoxLayout())
        hl.layout().setContentsMargins(0, 9, 0, 0)

        glay = None
        for text, width in labels:
            if not width:
                if glay:
                    hl.layout().addLayout(glay)
                hl.layout().addStretch()
                glay = QGridLayout()
                glay.setAlignment(Qt.AlignCenter)
                glay.setContentsMargins(0, 0, 0, 0)
                c = 0
            else:
                label = QLabel(text, self)
                label.setStyleSheet("""
                    min-width:valueem; min-height:1.29em; max-height:1.29em;
                    font-weight:bold; qproperty-alignment: AlignCenter;
                    """.replace('value', str(width)))
                glay.addWidget(label, 0, c)
                c += 1
        return hl

    def _create_scrn_summwidget(self, scrn_device, scrn_idx):
        """Create and return a screen detail widget."""
        cb_scrn = QCheckBox(scrn_device.get_nickname(dev=True), self)
        self._scrns_sel_bg.addButton(cb_scrn)
        self._scrns_sel_bg.setId(cb_scrn, scrn_idx)
        if scrn_idx == self._currScrn:
            cb_scrn.setChecked(True)
        cb_scrn.clicked.connect(self._setScrnWidget)
        cb_scrn.setStyleSheet("""
            min-width:6.5em; max-width:6.5em; font-weight:bold;""")

        led_camenbl = SiriusLedState(
            self,
            scrn_device.substitute(prefix=self.prefix, propty='CamEnbl-Sts'))
        led_camenbl.setStyleSheet("min-width:3.2em; max-width:3.2em;")

        cb_scrntype = PyDMEnumComboBox(
            self,
            scrn_device.substitute(prefix=self.prefix, propty='ScrnType-Sel'))
        cb_scrntype.setSizePolicy(QSzPlcy.Minimum, QSzPlcy.Fixed)
        cb_scrntype.setStyleSheet("min-width:4.5em;max-width:4.5em;")

        lb_scrntype = PyDMLabel(
            self,
            scrn_device.substitute(prefix=self.prefix, propty='ScrnType-Sts'))
        lb_scrntype.setStyleSheet("min-width:4.5em; max-width:4.5em;")
        lb_scrntype.setAlignment(Qt.AlignCenter)

        led_scrntype = PyDMLed(self,
                               scrn_device.substitute(prefix=self.prefix,
                                                      propty='ScrnType-Sts'),
                               color_list=[
                                   PyDMLed.LightGreen, PyDMLed.Red,
                                   PyDMLed.Red, PyDMLed.Yellow
                               ])
        led_scrntype.shape = 2
        led_scrntype.setStyleSheet("""min-width:4.5em; max-width:4.5em;""")

        wid = QWidget()
        lay = QGridLayout(wid)
        lay.setAlignment(Qt.AlignCenter)
        lay.addWidget(cb_scrn, 1, 1)
        lay.addWidget(led_camenbl, 1, 2)
        lay.addWidget(cb_scrntype, 1, 3)
        lay.addWidget(lb_scrntype, 1, 4)
        lay.addWidget(led_scrntype, 2, 4)
        return wid

    def _create_corr_summwidget(self, corr):
        """Create and return a corrector detail widget."""
        wid = QWidget()
        wid.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Maximum)
        lay = QGridLayout(wid)
        lay.setContentsMargins(0, 0, 0, 0)
        lay.setAlignment(Qt.AlignCenter)

        propty_sp = 'Current-SP' if corr.sec == 'LI' else 'Kick-SP'
        propty_mon = propty_sp.replace('SP', 'Mon')

        led = SiriusLedState(
            self, corr.substitute(prefix=self.prefix, propty='PwrState-Sts'))
        led.setStyleSheet("max-width:1.29em;")
        lay.addWidget(led, 1, 1)

        nickname = corr.get_nickname(sec=corr.sec == 'LI', dev=True)
        pb = QPushButton(nickname, self)
        if corr.dis == 'PU':
            util.connect_window(pb, PUDetailWindow, parent=self, devname=corr)
        else:
            util.connect_window(pb, PSDetailWindow, parent=self, psname=corr)
        pb.setStyleSheet("""
            min-width:6em; max-width:6em; min-height:1.29em;""")
        lay.addWidget(pb, 1, 2)

        sp_kick = PyDMSpinboxScrollbar(
            self, corr.substitute(prefix=self.prefix, propty=propty_sp))
        sp_kick.setStyleSheet("QDoubleSpinBox{min-width:4em; max-width:4em; }"
                              "QScrollBar{max-width:4em;}")
        sp_kick.spinbox.precisionFromPV = False
        sp_kick.spinbox.precision = 1
        sp_kick.scrollbar.limitsFromPV = True
        lay.addWidget(sp_kick, 1, 3, 2, 1)

        lb_kick = PyDMLabel(
            self, corr.substitute(prefix=self.prefix, propty=propty_mon))
        lb_kick.setStyleSheet("""
            min-width:5em; max-width:5em; min-height:1.29em;""")
        lb_kick.showUnits = True
        lb_kick.precisionFromPV = False
        lb_kick.precision = 1
        lb_kick.setAlignment(Qt.AlignCenter)
        lay.addWidget(lb_kick, 1, 4)
        return wid
Ejemplo n.º 4
0
class BONormEdit(SiriusMainWindow):
    """Widget to perform optics adjust in normalized configurations."""

    normConfigChanged = Signal(float, dict)

    def __init__(self, parent=None, prefix='', ramp_config=None,
                 norm_config=None, time=None, energy=None,
                 magnets=dict(), conn_sofb=None,
                 tunecorr_configname=None, chromcorr_configname=None):
        """Initialize object."""
        super().__init__(parent)
        self.setWindowTitle('Edit Normalized Configuration')
        self.setObjectName('BOApp')
        self.prefix = prefix
        self.ramp_config = ramp_config
        self.norm_config = _dcopy(norm_config)
        self.time = time
        self.energy = energy

        self._aux_magnets = magnets
        self._conn_sofb = conn_sofb
        self._tunecorr = BOTuneCorr(tunecorr_configname)
        self._chromcorr = BOChromCorr(chromcorr_configname)

        self._reference = _dcopy(norm_config)
        self._currChrom = self._estimateChrom(use_ref=True)
        self._deltas = {
            'kicks': dict(),
            'factorH': 0.0,
            'factorV': 0.0,
            'tuneX': 0.0,
            'tuneY': 0.0,
            'chromX': self._currChrom[0],
            'chromY': self._currChrom[1],
        }
        self._setupUi()
        self._setupMenu()
        self.verifySync()

    # ---------- setup/build layout ----------

    def _setupUi(self):
        self.label_description = QLabel(
            '<h2>'+self.norm_config['label']+'</h2>', self)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.label_time = QLabel('<h2>T = '+str(self.time)+'ms</h2>', self)
        self.label_time.setAlignment(Qt.AlignCenter)

        self.strengths = self._setupStrengthWidget()
        self.orbit = self._setupOrbitWidget()
        self.tune = self._setupTuneWidget()
        self.chrom = self._setupChromWidget()

        self.bt_apply = QPushButton(qta.icon('fa5s.angle-right'), '', self)
        self.bt_apply.setToolTip('Apply Changes to Machine')
        self.bt_apply.setStyleSheet('icon-size: 30px 30px;')
        self.bt_apply.clicked.connect(self._updateRampConfig)

        cw = QWidget()
        lay = QGridLayout()
        lay.setVerticalSpacing(10)
        lay.setHorizontalSpacing(10)
        lay.addWidget(self.label_description, 0, 0, 1, 2)
        lay.addWidget(self.label_time, 1, 0, 1, 2)
        lay.addWidget(self.strengths, 2, 0, 4, 1)
        lay.addWidget(self.orbit, 2, 1)
        lay.addWidget(self.tune, 3, 1)
        lay.addWidget(self.chrom, 4, 1)
        lay.addWidget(self.bt_apply, 5, 1)
        lay.setColumnStretch(0, 2)
        lay.setColumnStretch(1, 2)
        lay.setRowStretch(0, 2)
        lay.setRowStretch(1, 2)
        lay.setRowStretch(2, 8)
        lay.setRowStretch(3, 8)
        lay.setRowStretch(4, 8)
        lay.setRowStretch(5, 1)
        cw.setLayout(lay)

        cw.setStyleSheet("""
            QGroupBox::title{
                subcontrol-origin: margin;
                subcontrol-position: top center;
                padding: 0 2px 0 2px;}""")
        cw.setFocusPolicy(Qt.StrongFocus)
        self.setCentralWidget(cw)

    def _setupMenu(self):
        self.menubar = QMenuBar(self)
        self.layout().setMenuBar(self.menubar)
        self.menu = self.menubar.addMenu('Options')
        self.act_saveas = self.menu.addAction('Save as...')
        self.act_saveas.triggered.connect(self._showSaveAsPopup)

        self._undo_stack = QUndoStack(self)
        self.act_undo = self._undo_stack.createUndoAction(self, 'Undo')
        self.act_undo.setShortcut(QKeySequence.Undo)
        self.menu.addAction(self.act_undo)
        self.act_redo = self._undo_stack.createRedoAction(self, 'Redo')
        self.act_redo.setShortcut(QKeySequence.Redo)
        self.menu.addAction(self.act_redo)

    def _setupStrengthWidget(self):
        scrollarea = QScrollArea()
        self.nconfig_data = QWidget()
        flay_configdata = QFormLayout()
        psnames = self._get_PSNames()
        self._map_psnames2wigdets = dict()
        for ps in psnames:
            ps = SiriusPVName(ps)
            if ps in ramp.BoosterRamp.PSNAME_DIPOLES:
                ps_value = QLabel(str(self.norm_config[ps])+' GeV', self)
                flay_configdata.addRow(QLabel(ps + ': ', self), ps_value)
            else:
                ps_value = QDoubleSpinBoxPlus(self.nconfig_data)
                ps_value.setDecimals(6)
                ps_value.setMinimum(-10000)
                ps_value.setMaximum(10000)
                ps_value.setValue(self.norm_config[ps])
                ps_value.valueChanged.connect(self._handleStrenghtsSet)

                if ps.dev in {'QD', 'QF', 'QS'}:
                    unit_txt = '1/m'
                elif ps.dev in {'SD', 'SF'}:
                    unit_txt = '1/m²'
                else:
                    unit_txt = 'urad'
                label_unit = QLabel(unit_txt, self)
                label_unit.setStyleSheet("min-width:2.5em; max-width:2.5em;")
                hb = QHBoxLayout()
                hb.addWidget(ps_value)
                hb.addWidget(label_unit)

                flay_configdata.addRow(QLabel(ps + ': ', self), hb)

            ps_value.setObjectName(ps)
            ps_value.setStyleSheet("min-height:1.29em; max-height:1.29em;")
            self._map_psnames2wigdets[ps] = ps_value

        self.nconfig_data.setObjectName('data')
        self.nconfig_data.setStyleSheet("""
            #data{background-color: transparent;}""")
        self.nconfig_data.setLayout(flay_configdata)
        scrollarea.setWidget(self.nconfig_data)

        self.cb_checklims = QCheckBox('Set limits according to energy', self)
        self.cb_checklims.setChecked(False)
        self.cb_checklims.stateChanged.connect(self._handleStrengtsLimits)

        self.bt_graph = QPushButton(qta.icon('mdi.chart-line'), '', self)
        self.bt_graph.clicked.connect(self._show_kicks_graph)

        gbox = QGroupBox()
        gbox.setObjectName('strengths')
        gbox.setStyleSheet('#strengths{min-width:20em;}')
        glay = QGridLayout()
        glay.addWidget(scrollarea, 0, 0, 1, 2)
        glay.addWidget(self.cb_checklims, 1, 0, alignment=Qt.AlignLeft)
        glay.addWidget(self.bt_graph, 1, 1, alignment=Qt.AlignRight)
        gbox.setLayout(glay)
        return gbox

    def _setupOrbitWidget(self):
        self.bt_get_kicks = QPushButton('Get Kicks from SOFB', self)
        self.bt_get_kicks.clicked.connect(self._handleGetKicksFromSOFB)

        label_correctH = QLabel('Correct H', self,
                                alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_correctH = QDoubleSpinBoxPlus(self)
        self.sb_correctH.setValue(self._deltas['factorH'])
        self.sb_correctH.setDecimals(1)
        self.sb_correctH.setMinimum(-10000)
        self.sb_correctH.setMaximum(10000)
        self.sb_correctH.setSingleStep(0.1)
        self.sb_correctH.setObjectName('factorH')
        self.sb_correctH.editingFinished.connect(self._handleCorrFactorsSet)
        labelH = QLabel('%', self)

        label_correctV = QLabel('Correct V', self,
                                alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_correctV = QDoubleSpinBoxPlus(self)
        self.sb_correctV.setValue(self._deltas['factorV'])
        self.sb_correctV.setDecimals(1)
        self.sb_correctV.setMinimum(-10000)
        self.sb_correctV.setMaximum(10000)
        self.sb_correctV.setSingleStep(0.1)
        self.sb_correctV.setObjectName('factorV')
        self.sb_correctV.editingFinished.connect(self._handleCorrFactorsSet)
        labelV = QLabel('%', self)

        self.bt_update_ref_orbit = QPushButton('Update reference', self)
        self.bt_update_ref_orbit.clicked.connect(
            _part(self._updateReference, 'corrs'))

        gbox = QGroupBox('Orbit', self)
        lay = QGridLayout()
        lay.addWidget(self.bt_get_kicks, 0, 0, 1, 4)
        lay.addWidget(label_correctH, 1, 0)
        lay.addWidget(self.sb_correctH, 1, 2)
        lay.addWidget(labelH, 1, 3)
        lay.addWidget(label_correctV, 2, 0)
        lay.addWidget(self.sb_correctV, 2, 2)
        lay.addWidget(labelV, 2, 3)
        lay.addWidget(self.bt_update_ref_orbit, 3, 2, 1, 2)
        lay.setColumnStretch(0, 16)
        lay.setColumnStretch(1, 1)
        lay.setColumnStretch(2, 14)
        lay.setColumnStretch(3, 2)
        gbox.setLayout(lay)
        return gbox

    def _setupTuneWidget(self):
        for cord in ['X', 'Y']:
            setattr(self, 'label_deltaTune'+cord,
                    QLabel('Δν<sub>'+cord+'</sub>: '))
            lab = getattr(self, 'label_deltaTune'+cord)
            lab.setStyleSheet("min-width:1.55em; max-width:1.55em;")

            setattr(self, 'sb_deltaTune'+cord, QDoubleSpinBoxPlus(self))
            sb = getattr(self, 'sb_deltaTune'+cord)
            sb.setDecimals(6)
            sb.setMinimum(-5)
            sb.setMaximum(5)
            sb.setSingleStep(0.0001)
            sb.setObjectName('tune'+cord)
            sb.editingFinished.connect(self._handleDeltaTuneSet)

        label_KL = QLabel('<h4>ΔKL [1/m]</h4>', self)
        label_KL.setStyleSheet("""min-height:1.55em; max-height:1.55em;
                                  qproperty-alignment: AlignCenter;""")
        self.l_deltaKLQF = QLabel('', self)
        self.l_deltaKLQD = QLabel('', self)

        self.bt_update_ref_deltaKL = QPushButton('Update reference', self)
        self.bt_update_ref_deltaKL.clicked.connect(
            _part(self._updateReference, 'quads'))

        gbox = QGroupBox('Tune', self)
        lay = QGridLayout()
        lay.addWidget(self.label_deltaTuneX, 1, 0)
        lay.addWidget(self.sb_deltaTuneX, 1, 1)
        lay.addWidget(self.label_deltaTuneY, 1, 3)
        lay.addWidget(self.sb_deltaTuneY, 1, 4)
        lay.addWidget(label_KL, 3, 0, 1, 5)
        lay.addWidget(QLabel('QF: '), 4, 0)
        lay.addWidget(self.l_deltaKLQF, 4, 1)
        lay.addWidget(QLabel('QD: '), 4, 3)
        lay.addWidget(self.l_deltaKLQD, 4, 4)
        lay.addWidget(self.bt_update_ref_deltaKL, 6, 3, 1, 2)
        lay.setVerticalSpacing(6)
        lay.setColumnStretch(0, 2)
        lay.setColumnStretch(1, 4)
        lay.setColumnStretch(2, 1)
        lay.setColumnStretch(3, 2)
        lay.setColumnStretch(4, 4)
        lay.setRowStretch(0, 1)
        lay.setRowStretch(1, 2)
        lay.setRowStretch(2, 1)
        lay.setRowStretch(3, 2)
        lay.setRowStretch(4, 2)
        lay.setRowStretch(5, 1)
        lay.setRowStretch(6, 2)
        gbox.setLayout(lay)
        return gbox

    def _setupChromWidget(self):
        for cord in ['X', 'Y']:
            setattr(self, 'label_Chrom'+cord,
                    QLabel('ξ<sub>'+cord+'</sub>: '))
            lab = getattr(self, 'label_Chrom'+cord)
            lab.setStyleSheet("min-width:1.55em; max-width:1.55em;")

            setattr(self, 'sb_Chrom'+cord, QDoubleSpinBoxPlus(self))
            sb = getattr(self, 'sb_Chrom'+cord)
            sb.setDecimals(6)
            sb.setMinimum(-5)
            sb.setMaximum(5)
            sb.setSingleStep(0.0001)
            sb.setObjectName('chrom'+cord)
            sb.setValue(self._deltas['chrom'+cord])
            sb.editingFinished.connect(self._handleChromSet)

        label_SL = QLabel('<h4>ΔSL [1/m<sup>2</sup>]</h4>', self)
        label_SL.setStyleSheet("""min-height:1.55em; max-height:1.55em;
                                  qproperty-alignment: AlignCenter;""")
        self.l_deltaSLSF = QLabel('', self)
        self.l_deltaSLSD = QLabel('', self)

        self.bt_update_ref_deltaSL = QPushButton('Update reference', self)
        self.bt_update_ref_deltaSL.clicked.connect(
            _part(self._updateReference, 'sexts'))

        gbox = QGroupBox('Chromaticity', self)
        lay = QGridLayout()
        lay.addWidget(self.label_ChromX, 1, 0)
        lay.addWidget(self.sb_ChromX, 1, 1)
        lay.addWidget(self.label_ChromY, 1, 3)
        lay.addWidget(self.sb_ChromY, 1, 4)
        lay.addWidget(label_SL, 3, 0, 1, 5)
        lay.addWidget(QLabel('SF: '), 4, 0)
        lay.addWidget(self.l_deltaSLSF, 4, 1)
        lay.addWidget(QLabel('SD: '), 4, 3)
        lay.addWidget(self.l_deltaSLSD, 4, 4)
        lay.addWidget(self.bt_update_ref_deltaSL, 6, 3, 1, 2)
        lay.setVerticalSpacing(6)
        lay.setColumnStretch(0, 2)
        lay.setColumnStretch(1, 4)
        lay.setColumnStretch(2, 1)
        lay.setColumnStretch(3, 2)
        lay.setColumnStretch(4, 4)
        lay.setRowStretch(0, 1)
        lay.setRowStretch(1, 2)
        lay.setRowStretch(2, 1)
        lay.setRowStretch(3, 2)
        lay.setRowStretch(4, 2)
        lay.setRowStretch(5, 1)
        lay.setRowStretch(6, 2)
        gbox.setLayout(lay)
        return gbox

    # ---------- server communication ----------

    def _save(self, name):
        try:
            nconf = ramp.BoosterNormalized()
            nconf.value = self.norm_config
            nconf.save(new_name=name)
        except _ConfigDBException as err:
            QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok)

    def _showSaveAsPopup(self):
        self._saveAsPopup = _SaveConfigDialog('bo_normalized', self)
        self._saveAsPopup.configname.connect(self._save)
        self._saveAsPopup.open()

    def verifySync(self):
        if self.ramp_config is None:
            return
        if not self.ramp_config.verify_ps_normalized_synchronized(
                self.time, value=self.norm_config):
            self.label_time.setStyleSheet('color: red;')
            self.label_description.setStyleSheet('color: red;')
            self.setToolTip("There are unsaved changes")
        else:
            self.label_time.setStyleSheet('color: black;')
            self.label_description.setStyleSheet('color: black;')
            self.setToolTip("")

    # ---------- strengths ----------

    def _handleStrenghtsSet(self, new_value):
        psname = self.sender().objectName()
        self._stack_command(
            self.sender(), self.norm_config[psname], new_value,
            message='set '+psname+' strength to {}'.format(new_value))
        self.norm_config[psname] = new_value
        self.verifySync()

    def _handleStrengtsLimits(self, state):
        psnames = list(self.norm_config.keys())
        psnames.remove('BO-Fam:PS-B-1')
        psnames.remove('BO-Fam:PS-B-2')
        psnames.remove('label')
        if state:
            for ps in psnames:
                ps_value = self.nconfig_data.findChild(
                    QDoubleSpinBoxPlus, name=ps)
                ma = _MASearch.conv_psname_2_psmaname(ps)
                aux = self._aux_magnets[ma]
                currs = (aux.current_min, aux.current_max)
                lims = aux.conv_current_2_strength(
                    currents=currs, strengths_dipole=self.energy)
                ps_value.setMinimum(min(lims))
                ps_value.setMaximum(max(lims))
        else:
            for ps in psnames:
                ps_value = self.nconfig_data.findChild(
                    QDoubleSpinBoxPlus, name=ps)
                ps_value.setMinimum(-10000)
                ps_value.setMaximum(10000)

    def _updateStrenghtsWidget(self, pstype):
        psnames = self._get_PSNames(pstype)
        wid2change = psnames if psnames else list(self.norm_config.keys())
        for wid in wid2change:
            value = self.norm_config[wid]
            self._map_psnames2wigdets[wid].setValue(value)

    # ---------- orbit ----------

    def _updateCorrKicks(self):
        for psname, dkick in self._deltas['kicks'].items():
            corr_factor = self._deltas['factorV'] if 'CV' in psname \
                else self._deltas['factorH']
            corr_factor /= 100
            self.norm_config[psname] = self._reference[psname] + \
                dkick*corr_factor

    def _handleGetKicksFromSOFB(self):
        if not self._conn_sofb.connected:
            QMessageBox.warning(
                self, 'Not Connected',
                'There are not connected PVs!', QMessageBox.Ok)
            return
        dkicks = self._conn_sofb.get_deltakicks()

        if not dkicks:
            QMessageBox.warning(
                self, 'Could not get kicks',
                'Could not get kicks from SOFB!', QMessageBox.Ok)
            return

        self._deltas['kicks'] = dkicks
        self._updateCorrKicks()
        self._updateStrenghtsWidget('corrs')
        self.verifySync()

    def _handleCorrFactorsSet(self):
        widget = self.sender()
        factor = widget.objectName()
        dim = ' vertical ' if factor == 'factorV' else ' horizantal '
        new_value = widget.value()
        self._stack_command(
            widget, self._deltas[factor], new_value,
            message='set'+dim+'orbit correction factor to {}'.format(
                    new_value))
        self._deltas[factor] = new_value

        self._updateCorrKicks()
        self._updateStrenghtsWidget('corrs')
        self.verifySync()

    def _resetOrbitChanges(self):
        self._deltas['kicks'] = dict()
        self._deltas['factorH'] = 0.0
        self._deltas['factorV'] = 0.0
        self.sb_correctH.setValue(0.0)
        self.sb_correctV.setValue(0.0)

    # ---------- tune ----------

    def _handleDeltaTuneSet(self):
        widget = self.sender()
        tune = widget.objectName()
        dim = ' vertical ' if tune == 'tuneY' else ' horizantal '
        new_value = widget.value()
        self._stack_command(
            widget, self._deltas[tune], new_value,
            message='set'+dim+'delta tune to {}'.format(
                    new_value))
        self._deltas[tune] = new_value

        self._updateDeltaKL()

    def _updateDeltaKL(self):
        self._deltaKL = self._tunecorr.calculate_deltaKL(
            [self._deltas['tuneX'], self._deltas['tuneY']])
        self.l_deltaKLQF.setText('{: 4f}'.format(self._deltaKL[0]))
        self.l_deltaKLQD.setText('{: 4f}'.format(self._deltaKL[1]))

        self.norm_config['BO-Fam:PS-QF'] = \
            self._reference['BO-Fam:PS-QF'] + self._deltaKL[0]
        self.norm_config['BO-Fam:PS-QD'] = \
            self._reference['BO-Fam:PS-QD'] + self._deltaKL[1]

        self._updateStrenghtsWidget('quads')
        self.verifySync()

    def _resetTuneChanges(self):
        self.sb_deltaTuneX.setValue(0)
        self.sb_deltaTuneY.setValue(0)
        self._deltaKL = [0.0, 0.0]
        self.l_deltaKLQF.setText('{: 6f}'.format(self._deltaKL[0]))
        self.l_deltaKLQD.setText('{: 6f}'.format(self._deltaKL[1]))

    # ---------- chromaticity ----------

    def _estimateChrom(self, use_ref=False):
        nom_SL = self._chromcorr.nominal_intstrengths.flatten()
        if use_ref:
            curr_SL = _np.array([self._reference['BO-Fam:PS-SF'],
                                 self._reference['BO-Fam:PS-SD']])
        else:
            curr_SL = _np.array([self.norm_config['BO-Fam:PS-SF'],
                                 self.norm_config['BO-Fam:PS-SD']])
        delta_SL = curr_SL - nom_SL
        return self._chromcorr.calculate_Chrom(delta_SL)

    def _handleChromSet(self):
        widget = self.sender()
        chrom = widget.objectName()
        dim = ' vertical ' if chrom == 'chromY' else ' horizantal '
        new_value = widget.value()
        self._stack_command(
            widget, self._deltas[chrom], new_value,
            message='set'+dim+'chromaticity to {}'.format(
                    new_value))
        self._deltas[chrom] = new_value

        self._updateDeltaSL()

    def _updateDeltaSL(self):
        desired_Chrom = _np.array([self._deltas['chromX'],
                                   self._deltas['chromY']])
        deltas = desired_Chrom - self._currChrom
        self._deltaSL = self._chromcorr.calculate_deltaSL(
            [deltas[0], deltas[1]])
        self.l_deltaSLSF.setText('{: 4f}'.format(self._deltaSL[0]))
        self.l_deltaSLSD.setText('{: 4f}'.format(self._deltaSL[1]))

        self.norm_config['BO-Fam:PS-SF'] = \
            self._reference['BO-Fam:PS-SF'] + self._deltaSL[0]
        self.norm_config['BO-Fam:PS-SD'] = \
            self._reference['BO-Fam:PS-SD'] + self._deltaSL[1]

        self._updateStrenghtsWidget('sexts')
        self.verifySync()

    def _resetChromChanges(self):
        self._currChrom = self._estimateChrom(use_ref=True)
        self.sb_ChromX.setValue(self._currChrom[0])
        self.sb_ChromY.setValue(self._currChrom[1])
        self._deltaSL = [0.0, 0.0]
        self.l_deltaSLSF.setText('{: 6f}'.format(self._deltaSL[0]))
        self.l_deltaSLSD.setText('{: 6f}'.format(self._deltaSL[1]))

    # ---------- update methods ----------

    def _updateReference(self, pstype):
        psnames = self._get_PSNames(pstype)
        for ps in psnames:
            self._reference[ps] = self.norm_config[ps]

        if pstype == 'corrs':
            self._resetOrbitChanges()
        elif pstype == 'quads':
            self._resetTuneChanges()
        elif pstype == 'sexts':
            self._resetChromChanges()
        else:
            self._resetOrbitChanges()
            self._resetTuneChanges()
            self._resetChromChanges()

        self.verifySync()

    def _updateRampConfig(self):
        if self.norm_config is not None:
            self.normConfigChanged.emit(self.time, _dcopy(self.norm_config))

    def updateTime(self, time):
        """Update norm config time."""
        self.time = time
        self.label_time.setText('<h2>T = '+str(time)+'ms</h2>')
        self.energy = self.ramp_config.ps_waveform_interp_energy(time)
        self._handleStrengtsLimits(self.cb_checklims.checkState())
        self.verifySync()

    def updateLabel(self, label):
        """Update norm config label."""
        self.norm_config['label'] = label
        self.label_description.setText('<h2>'+label+'</h2>')
        self.verifySync()

    @Slot(str, str)
    def updateSettings(self, tunecorr_configname, chromcorr_configname):
        self._tunecorr = BOTuneCorr(tunecorr_configname)
        self._chromcorr = BOChromCorr(chromcorr_configname)
        self._updateDeltaKL()
        self._estimateChrom(use_ref=True)
        self._updateDeltaSL()

    # ---------- handle undo redo stack ----------

    def _stack_command(self, widget, old_value, new_value, message):
        global _flag_stack_next_command, _flag_stacking
        if _flag_stack_next_command and (old_value != new_value):
            _flag_stacking = True
            command = _UndoRedoSpinbox(widget, old_value, new_value, message)
            self._undo_stack.push(command)
        else:
            _flag_stack_next_command = True

    # ---------- helper methods ----------

    def _get_PSNames(self, pstype=None):
        psnames = list()
        if pstype == 'corrs':
            psnames = _PSSearch.get_psnames({'sec': 'BO', 'dev': 'C(V|H)'})
        elif pstype == 'quads':
            psnames = ['BO-Fam:PS-QF', 'BO-Fam:PS-QD']
        elif pstype == 'sexts':
            psnames = ['BO-Fam:PS-SF', 'BO-Fam:PS-SD']
        else:
            psnames = _PSSearch.get_psnames({'sec': 'BO', 'sub': 'Fam'})
            psnames.extend(_PSSearch.get_psnames({'sec': 'BO', 'dev': 'QS'}))
            psnames.extend(_PSSearch.get_psnames({'sec': 'BO', 'dev': 'CH'}))
            psnames.extend(_PSSearch.get_psnames({'sec': 'BO', 'dev': 'CV'}))
        return psnames

    def _show_kicks_graph(self):
        strenghts_dict = _dcopy(self.norm_config)
        strenghts_dict.pop('label')
        graph = _ShowCorrectorKicks(self, self.time, strenghts_dict)
        graph.show()