Beispiel #1
0
class MainWindowBase(MainWindowABC, ABC):
    """External UI settings."""
    vpoint_list: List[VPoint]
    vlink_list: List[VLink]

    __tables: Sequence[BaseTableWidget]

    @abstractmethod
    def __init__(self):
        super(MainWindowBase, self).__init__()
        # Environment path
        self.env = ""
        # Alignment mode
        self.alignment_mode = 0
        # Entities list
        self.vpoint_list = []
        self.vlink_list = [VLink(VLink.FRAME, 'White', (), color_rgb)]
        # Condition list of context menus
        self.context = _Context()
        # Preference
        self.prefer = Preferences()
        # Set path from command line
        home_dir = QDir.home()
        self.settings = QSettings(home_dir.absoluteFilePath(".pyslvs.ini"),
                                  QSettings.IniFormat, self)
        if ARGUMENTS.c:
            self.set_locate(QDir(ARGUMENTS.c).absolutePath())
        else:
            home_dir.cd("Desktop")
            env = self.settings.value("ENV", home_dir.absolutePath())
            self.set_locate(str(env))

        # Initialize custom UI
        self.__undo_redo()
        self.__appearance()
        self.__alignment()
        self.__free_move()
        self.__options()
        self.__context_menu()

    def env_path(self) -> str:
        """Return environment path."""
        return self.env

    def set_locate(self, locate: str) -> None:
        """Set environment variables."""
        if locate == self.env or not QDir(locate).exists():
            return
        self.env = locate
        logger.debug(f"~Set workplace to: \"{self.env}\"")

    def __undo_redo(self) -> None:
        """Undo list settings.

        + Undo stack.
        + Undo view widget.
        + Hot keys.
        """
        self.command_stack = QUndoStack(self)
        self.command_stack.setUndoLimit(self.prefer.undo_limit_option)
        self.command_stack.indexChanged.connect(self.command_reload)
        action_redo = self.command_stack.createRedoAction(self, "Redo")
        action_undo = self.command_stack.createUndoAction(self, "Undo")
        action_redo.setShortcuts(["Ctrl+Shift+Z", "Ctrl+Y"])
        action_redo.setStatusTip("Backtracking undo action.")
        action_redo.setIcon(QIcon(QPixmap(":/icons/redo.png")))
        action_undo.setShortcut("Ctrl+Z")
        action_undo.setStatusTip("Recover last action.")
        action_undo.setIcon(QIcon(QPixmap(":/icons/undo.png")))
        self.menu_edit.addActions([action_undo, action_redo])

    def __appearance(self) -> None:
        """Start up and initialize custom widgets."""
        # Entities tables
        tab_bar = self.entities_tab.tabBar()
        tab_bar.setStatusTip("Switch the tabs to change to another view mode.")
        self.entities_point = PointTableWidget(self.entities_point_widget)
        self.entities_point.cellDoubleClicked.connect(self.edit_point)
        self.entities_point.delete_request.connect(self.delete_selected_points)
        self.entities_point_layout.addWidget(self.entities_point)
        self.entities_link = LinkTableWidget(self.entities_link_widget)
        self.entities_link.cellDoubleClicked.connect(self.edit_link)
        self.entities_link.delete_request.connect(self.delete_selected_links)
        self.entities_link_layout.addWidget(self.entities_link)
        self.entities_expr = ExprTableWidget(self.EntitiesExpr_widget)
        self.entities_expr_layout.insertWidget(0, self.entities_expr)
        self.__tables = (
            self.entities_point,
            self.entities_link,
            self.entities_expr,
        )

        # Select all button on the Point and Link tab as corner widget
        select_all_button = QPushButton()
        select_all_button.setIcon(QIcon(QPixmap(":/icons/select_all.png")))
        select_all_button.setToolTip("Select all")
        select_all_button.setStatusTip("Select all item of point table.")

        @Slot()
        def table_select_all() -> None:
            """Distinguish table by tab index."""
            self.__tables[self.entities_tab.currentIndex()].selectAll()

        select_all_button.clicked.connect(table_select_all)
        self.entities_tab.setCornerWidget(select_all_button)
        select_all_action = QAction("Select all point", self)
        select_all_action.triggered.connect(table_select_all)
        select_all_action.setShortcut("Ctrl+A")
        select_all_action.setShortcutContext(Qt.WindowShortcut)
        self.addAction(select_all_action)

        # QPainter canvas window
        self.main_canvas = MainCanvas(self)
        self.entities_tab.currentChanged.connect(
            self.main_canvas.set_selection_mode)
        select_tips = QLabel(self, Qt.ToolTip)

        @Slot(QPoint, str)
        def show_select_tips(pos: QPoint, text: str) -> None:
            select_tips.setText(text)
            select_tips.move(pos - QPoint(0, select_tips.height()))
            select_tips.show()

        self.main_canvas.selected_tips.connect(show_select_tips)
        self.main_canvas.selected_tips_hide.connect(select_tips.hide)

        @Slot(tuple, bool)
        def table_selection(selection: Sequence[int], check_key: bool) -> None:
            """Distinguish table by tab index."""
            index = self.entities_tab.currentIndex()
            self.__tables[index].set_selections(selection, check_key)

        self.main_canvas.selected.connect(table_selection)
        self.entities_point.row_selection_changed.connect(
            self.main_canvas.set_selection)

        @Slot()
        def table_clear_selection() -> None:
            """Clear the selection of specific table by tab index."""
            index = self.entities_tab.currentIndex()
            self.__tables[index].clearSelection()

        clean_selection_action = QAction("Clean selection", self)
        clean_selection_action.triggered.connect(table_clear_selection)
        clean_selection_action.setShortcut("Esc")
        clean_selection_action.setShortcutContext(Qt.WindowShortcut)
        self.main_canvas.no_selected.connect(table_clear_selection)
        self.addAction(clean_selection_action)

        self.main_canvas.free_moved.connect(self.set_free_move)
        self.main_canvas.alt_add.connect(self.add_point_by_pos)
        self.main_canvas.doubleclick_edit.connect(self.edit_point)
        self.main_canvas.zoom_changed.connect(self.zoom_bar.setValue)
        self.main_canvas.tracking.connect(self.set_mouse_pos)
        self.canvas_layout.insertWidget(0, self.main_canvas)
        self.canvas_splitter.setSizes([600, 10, 30])

        # Selection label on status bar right side
        selection_label = SelectionLabel(self)
        self.entities_point.selectionLabelUpdate.connect(
            selection_label.update_select_point)
        self.main_canvas.browse_tracking.connect(
            selection_label.update_mouse_position)
        self.status_bar.addPermanentWidget(selection_label)

        # FPS label on status bar right side
        fps_label = FPSLabel(self)
        self.main_canvas.fps_updated.connect(fps_label.update_text)
        self.status_bar.addPermanentWidget(fps_label)

        # Inputs widget
        self.inputs_widget = InputsWidget(self)
        self.inputs_tab_layout.addWidget(self.inputs_widget)
        self.free_move_button.toggled.connect(
            self.inputs_widget.variable_value_reset)
        self.inputs_widget.about_to_resolve.connect(self.resolve)

        @Slot(tuple, bool)
        def inputs_selection(selections: Sequence[int], _=None) -> None:
            """Distinguish table by tab index."""
            self.inputs_widget.clear_selection()
            if self.entities_tab.currentIndex() == 0:
                self.inputs_widget.set_selection(selections)

        self.main_canvas.selected.connect(inputs_selection)
        self.main_canvas.no_selected.connect(
            self.inputs_widget.clear_selection)
        self.inputs_widget.update_preview_button.clicked.connect(
            self.main_canvas.update_preview_path)

        # Synthesis collections
        self.collections = Collections(self)
        # Number and type synthesis
        self.structure_synthesis = StructureSynthesis(self)
        for widget, name in [
            (self.structure_synthesis, "Structural"),
            (self.collections, "Collections"),
        ]:  # type: QWidget, str
            self.synthesis_tab_widget.addTab(widget, widget.windowIcon(), name)

        # Dimensional synthesis
        self.dimensional_synthesis = DimensionalSynthesis(self)
        self.main_canvas.set_target_point.connect(
            self.dimensional_synthesis.set_point)
        self.synthesis_tab_widget.addTab(
            self.dimensional_synthesis,
            self.dimensional_synthesis.windowIcon(), "Dimensional")
        # Same options of structure previews
        as_node1 = self.collections.structure_widget.graph_link_as_node
        as_node2 = self.structure_synthesis.graph_link_as_node
        as_node1.toggled.connect(as_node2.setChecked)
        as_node2.toggled.connect(as_node1.setChecked)
        show_label1 = self.collections.structure_widget.graph_show_label
        show_label2 = self.structure_synthesis.graph_show_label
        show_label1.toggled.connect(show_label2.setChecked)
        show_label2.toggled.connect(show_label1.setChecked)
        # File widget settings
        self.project_widget = ProjectWidget(self)
        self.project_layout.addWidget(self.project_widget)
        # Zooming and console dock will hide when startup
        self.zoom_widget.hide()
        self.console_widget.hide()
        # Connect to GUI button
        debug_mode = ARGUMENTS.debug_mode
        self.console_disconnect_button.setEnabled(not debug_mode)
        self.console_connect_button.setEnabled(debug_mode)
        # Splitter stretch factor
        self.main_splitter.setStretchFactor(0, 4)
        self.main_splitter.setStretchFactor(1, 15)
        self.mechanism_panel_splitter.setSizes([500, 200])
        # Enable mechanism menu actions when shows
        self.menu_mechanism.aboutToShow.connect(self.enable_mechanism_actions)
        # New main window function
        self.action_new_window.triggered.connect(self.new)

    def __alignment(self) -> None:
        """Menu of alignment function."""
        def switch_icon(m: int, icon_name: str) -> Callable[[], None]:
            @Slot()
            def func() -> None:
                self.alignment_mode = m
                self.alignment_button.setIcon(QIcon(QPixmap(icon_name)))

            return func

        menu = QMenu(self)
        for i, (text, icon) in enumerate([
            ("Vertical alignment", "vertical_align"),
            ("Horizontal alignment", "horizontal_align"),
        ]):
            icon = f":/icons/{icon}.png"
            action = QAction(QIcon(QPixmap(icon)), text, self)
            action.triggered.connect(switch_icon(i, icon))
            menu.addAction(action)
        self.alignment_button.setMenu(menu)
        self.alignment_button.clicked.connect(self.point_alignment)

    def __free_move(self) -> None:
        """Menu of free move mode."""
        def free_move_mode_func(j: int, icon_qt: QIcon) -> Callable[[], None]:
            @Slot()
            def func() -> None:
                self.free_move_button.setIcon(icon_qt)
                self.main_canvas.set_free_move(j)
                self.entities_tab.setCurrentIndex(0)
                self.inputs_widget.variable_stop.click()

            return func

        menu = QMenu(self)
        for i, (text, icon, tip) in enumerate([
            ("View mode", "free_move_off", "Disable free move mode."),
            ("Translate mode", "translate", "Edit by 2 DOF moving."),
            ("Rotate mode", "rotate", "Edit by 1 DOF moving."),
            ("Reflect mode", "reflect", "Edit by flip axis."),
        ]):
            action = QAction(QIcon(QPixmap(f":/icons/{icon}.png")), text, self)
            action.triggered.connect(free_move_mode_func(i, action.icon()))
            action.setShortcut(f"Ctrl+{i + 1}")
            action.setShortcutContext(Qt.WindowShortcut)
            action.setStatusTip(tip)
            menu.addAction(action)
            if i == 0:
                self.free_move_disable = action
        self.free_move_button.setMenu(menu)

    def __options(self) -> None:
        """Signal connection for option widgets.

        + Spin boxes
        + Combo boxes
        + Check boxes
        """
        # While value change, update the canvas widget
        self.zoom_bar.valueChanged.connect(self.main_canvas.set_zoom)
        self.action_show_point_mark.toggled.connect(
            self.main_canvas.set_point_mark)
        self.action_show_dimensions.toggled.connect(
            self.main_canvas.set_show_dimension)

    def __action(self,
                 name: Union[str, QAction],
                 slot: Optional[Callable[..., None]] = None,
                 enable: Optional[_Enable] = None,
                 *,
                 cast_to: Type[_N]) -> _N:
        """New action or menu."""
        is_menu = cast_to is QMenu
        if isinstance(name, QAction):
            menu: Optional[QMenu] = None
            action = name
        elif is_menu:
            menu = QMenu(name, self)
            action = menu.menuAction()
        else:
            menu = None
            action = QAction(name, self)
        if slot is not None:
            action.triggered.connect(slot)
        if enable is not None:
            for target in self.context[enable]:
                if isinstance(target, QMenu):
                    if is_menu:
                        target.addMenu(menu)
                    else:
                        target.addAction(action)
                elif isinstance(target, list):
                    target.append(action)
                else:
                    raise ValueError("not a list or menu")
        if is_menu:
            return cast(QMenu, menu)
        else:
            return action

    def __context_menu(self) -> None:
        """Context menu settings."""
        self.entities_point_widget.customContextMenuRequested.connect(
            self.point_context_menu)
        self.pop_point = QMenu(self)
        self.entities_link_widget.customContextMenuRequested.connect(
            self.link_context_menu)
        self.pop_link = QMenu(self)
        self.main_canvas.setContextMenuPolicy(Qt.CustomContextMenu)
        self.main_canvas.customContextMenuRequested.connect(
            self.canvas_context_menu)
        self.pop_canvas_p = QMenu(self)
        self.pop_canvas_l = QMenu(self)
        for enable, menu in (
            (_Enable.T_P, self.pop_point),
            (_Enable.T_L, self.pop_link),
            (_Enable.C_P, self.pop_canvas_p),
            (_Enable.C_L, self.pop_canvas_l),
        ):
            menu.setSeparatorsCollapsible(True)
            self.context[enable] = menu
        # EntitiesPoint
        two_menus_p = _Enable.T_P | _Enable.C_P
        two_menus_l = _Enable.T_L | _Enable.C_L
        self.__action("&Add",
                      self.new_point,
                      _Enable.T_P | _Enable.P_NO,
                      cast_to=QAction)
        self.__action("&Add",
                      self.add_normal_point,
                      _Enable.C_P | _Enable.P_NO,
                      cast_to=QAction)
        self.__action("Add to [ground]",
                      self.add_fixed_point,
                      _Enable.C_P | _Enable.P_NO,
                      cast_to=QAction)
        self.action_c_add_target = self.__action("Add &Target Point",
                                                 self.add_target_point,
                                                 _Enable.C_P | _Enable.C_L
                                                 | _Enable.P_NO | _Enable.L_NO,
                                                 cast_to=QAction)
        self.__action(self.action_new_link,
                      enable=two_menus_p | two_menus_l | _Enable.P_MUL
                      | _Enable.L_NO,
                      cast_to=QAction)
        self.__action("&Edit",
                      self.edit_point,
                      two_menus_p | _Enable.P_ONE,
                      cast_to=QAction)
        self.action_p_lock = self.__action("&Grounded",
                                           self.lock_points,
                                           two_menus_p | _Enable.P_ANY,
                                           cast_to=QAction)
        self.action_p_lock.setCheckable(True)
        self.pop_point_m = self.__action("Multiple joint",
                                         enable=two_menus_p | _Enable.P_MUL,
                                         cast_to=QMenu)
        self.__action("&Copy Table Data",
                      self.copy_points_table,
                      _Enable.T_P | _Enable.P_ONE,
                      cast_to=QAction)
        self.__action("Copy Coordinate",
                      self.copy_coord,
                      _Enable.T_P | _Enable.P_ONE,
                      cast_to=QAction)
        self.__action("C&lone",
                      self.clone_point,
                      two_menus_p | _Enable.P_ONE,
                      cast_to=QAction)
        self.pop_point.addSeparator()
        self.pop_canvas_p.addSeparator()
        self.__action("&Delete",
                      self.delete_selected_points,
                      two_menus_p | _Enable.P_ANY,
                      cast_to=QAction)
        # EntitiesLink
        self.__action("&Edit",
                      self.edit_link,
                      two_menus_l | _Enable.L_ONE,
                      cast_to=QAction)
        self.pop_link_m = self.__action("Merge Links",
                                        enable=two_menus_l | _Enable.L_MUL,
                                        cast_to=QMenu)
        self.__action("&Copy Table Data",
                      self.copy_links_table,
                      _Enable.T_L | _Enable.L_ONE,
                      cast_to=QAction)
        self.__action("&Release",
                      self.release_ground,
                      two_menus_l | _Enable.L_ONE | _Enable.L_GND,
                      cast_to=QAction)
        self.__action("C&onstrain",
                      self.constrain_link,
                      two_menus_l | _Enable.L_ONE | _Enable.L_N_GND,
                      cast_to=QAction)
        self.pop_link.addSeparator()
        self.pop_canvas_l.addSeparator()
        self.__action("Remove &Redundant Links",
                      self.delete_redundant_links,
                      _Enable.T_L,
                      cast_to=QAction)
        self.__action("&Delete",
                      self.delete_selected_links,
                      two_menus_l | _Enable.L_ANY,
                      cast_to=QAction)

    @Slot(int, name='on_entities_tab_currentChanged')
    def __set_selection_mode(self, index: int) -> None:
        """Connect selection signal for main canvas."""
        # Set selection from click table items
        try:
            for table in self.__tables:
                table.row_selection_changed.disconnect()
        except TypeError:
            pass
        self.__tables[index].row_selection_changed.connect(
            self.main_canvas.set_selection)
        # Double click signal
        try:
            self.main_canvas.doubleclick_edit.disconnect()
        except TypeError:
            pass
        if index == 0:
            self.main_canvas.doubleclick_edit.connect(self.edit_point)
        elif index == 1:
            self.main_canvas.doubleclick_edit.connect(self.edit_link)
        # Clear all selections
        for table in self.__tables:
            table.clearSelection()
        self.inputs_widget.clear_selection()
Beispiel #2
0
class RampMain(SiriusMainWindow):
    """Main window of Booster Ramp Control HLA."""

    loadSignal = Signal(ramp.BoosterRamp)

    def __init__(self, parent=None, prefix=''):
        """Initialize object."""
        super().__init__(parent)
        self.setWindowTitle('Booster Energy Ramping')
        self.setObjectName('BOApp')
        cor = get_appropriate_color(section='BO')
        self.setWindowIcon(qta.icon(
            'mdi.escalator', scale_factor=1.5, color=cor))

        self.prefix = prefix
        self.ramp_config = None
        self._undo_stack = QUndoStack(self)

        self._tunecorr_configname = 'BO.V05.04.M0'
        self._chromcorr_configname = 'BO.V05.04.M0'

        self._setupUi()
        self._connSignals()
        self._addActions()

        self.setFocusPolicy(Qt.StrongFocus)

    def _setupUi(self):
        cw = QWidget(self)
        glay = QGridLayout(cw)
        glay.setHorizontalSpacing(10)
        glay.setVerticalSpacing(10)
        lab = QLabel('<h3>Booster Energy Ramping</h3>', cw)
        lab.setStyleSheet("""
            min-height:1.55em; max-height: 1.55em;
            qproperty-alignment: 'AlignVCenter | AlignRight';
            background-color: qlineargradient(spread:pad, x1:1, y1:0.0227273,
                              x2:0, y2:0, stop:0 rgba(173, 190, 207, 255),
                              stop:1 rgba(213, 213, 213, 255));""")
        glay.addWidget(lab, 0, 0, 1, 2)

        self.settings = Settings(
            self, self.prefix, self.ramp_config,
            self._tunecorr_configname, self._chromcorr_configname)
        self.setMenuBar(self.settings)

        self.status_and_commands = StatusAndCommands(
            self, self.prefix, self.ramp_config)
        glay.addWidget(self.status_and_commands, 1, 1)

        self.config_parameters = ConfigParameters(
            self, self.prefix, self.ramp_config, self._undo_stack,
            self._tunecorr_configname, self._chromcorr_configname)
        self.config_parameters.setObjectName('ConfigParameters')
        glay.addWidget(self.config_parameters, 1, 0)

        glay.setColumnStretch(0, 15)
        glay.setColumnStretch(1, 1)
        self.setCentralWidget(cw)

    def _connSignals(self):
        self.settings.newConfigNameSignal.connect(self._receiveNewConfigName)
        self.settings.loadSignal.connect(self._emitLoadSignal)
        self.settings.opticsSettingsSignal.connect(
            self._handleUpdateOpticsAdjustSettings)
        self.settings.plotUnitSignal.connect(
            self.config_parameters.getPlotUnits)
        self.settings.newNormConfigsSignal.connect(self._receiveNewNormConfigs)
        self.settings.newTIConfigSignal.connect(self._receiveNewTIConfig)
        self.settings.newRFConfigSignal.connect(self._receiveNewRFConfig)

        self.config_parameters.dip_ramp.updateDipoleRampSignal.connect(
            self._verifySync)
        self.config_parameters.dip_ramp.updateDipoleRampSignal.connect(
            self.config_parameters.mult_ramp.updateTable)
        self.config_parameters.dip_ramp.updateDipoleRampSignal.connect(
            self.config_parameters.mult_ramp.updateGraph)
        self.config_parameters.dip_ramp.updateDipoleRampSignal.connect(
            self.config_parameters.rf_ramp.updateGraph)
        self.config_parameters.dip_ramp.updateDipoleRampSignal.connect(
            self.status_and_commands.update_ps_params)
        self.config_parameters.dip_ramp.updateDipoleRampSignal.connect(
            self.status_and_commands.update_ti_params)
        self.config_parameters.dip_ramp.applyChanges2MachineSignal.connect(
            self.status_and_commands.apply_changes)
        self.config_parameters.mult_ramp.updateMultipoleRampSignal.connect(
            self._verifySync)
        self.config_parameters.mult_ramp.updateMultipoleRampSignal.connect(
            self.status_and_commands.update_ps_params)
        self.config_parameters.mult_ramp.applyChanges2MachineSignal.connect(
            self.status_and_commands.apply_changes)
        self.config_parameters.mult_ramp.plotUnitSignal.connect(
            self.config_parameters.getPlotUnits)
        self.config_parameters.rf_ramp.updateRFRampSignal.connect(
            self._verifySync)
        self.config_parameters.rf_ramp.updateRFRampSignal.connect(
            self.status_and_commands.update_rf_params)
        self.config_parameters.rf_ramp.updateRFRampSignal.connect(
            self.status_and_commands.update_ti_params)
        self.config_parameters.rf_ramp.applyChanges2MachineSignal.connect(
            self.status_and_commands.apply_changes)

        self.status_and_commands.inj_eje_times.connect(
            self.config_parameters.dip_ramp.updateInjEjeTimes)

        self.loadSignal.connect(self.settings.getRampConfig)
        self.loadSignal.connect(self.config_parameters.handleLoadRampConfig)
        self.loadSignal.connect(self.status_and_commands.handleLoadRampConfig)

    def _addActions(self):
        self.act_undo = self._undo_stack.createUndoAction(self, 'Undo')
        self.act_undo.setIcon(qta.icon('mdi.undo'))
        self.act_undo.setShortcut(QKeySequence.Undo)
        self.settings.config_menu.addAction(self.act_undo)
        self.act_redo = self._undo_stack.createRedoAction(self, 'Redo')
        self.act_redo.setIcon(qta.icon('mdi.redo'))
        self.act_redo.setShortcut(QKeySequence.Redo)
        self.settings.config_menu.addAction(self.act_redo)

    @Slot(str)
    def _receiveNewConfigName(self, new_config_name):
        if self.ramp_config is None or \
                self.ramp_config.name != new_config_name or \
                self.ramp_config.name == '**New Configuration**':
            self.ramp_config = ramp.BoosterRamp(new_config_name,
                                                auto_update=True)
            self._undo_stack.clear()
        self._emitLoadSignal()

    @Slot(dict)
    def _receiveNewNormConfigs(self, norm_configs):
        old_norm_configs = _dcopy(
            self.config_parameters.mult_ramp.normalized_configs)
        self.ramp_config.ps_normalized_configs_set(norm_configs)
        self.loadSignal.emit(self.ramp_config)
        self._verifySync()
        new_norm_configs = _dcopy(
            self.config_parameters.mult_ramp.normalized_configs)
        self.config_parameters.mult_ramp.stackUndoMultipoleTableCommand(
            description='reconstruct normalized configs from waveforms',
            old=old_norm_configs, new=new_norm_configs)

    @Slot(dict)
    def _receiveNewTIConfig(self, params):
        for param, value in params.items():
            attr_name = 'ti_params_'+param
            setattr(self.ramp_config, attr_name, value)
        self.loadSignal.emit(self.ramp_config)
        self._verifySync()

    @Slot(dict)
    def _receiveNewRFConfig(self, params):
        for param, value in params.items():
            if 'duration' in param:
                continue
            attr_name = 'rf_ramp_'+param
            setattr(self.ramp_config, attr_name, value)
        self.ramp_config.rf_ramp_rampup_start_time = params['bottom_duration']
        self.ramp_config.rf_ramp_rampup_stop_time = \
            params['bottom_duration'] + \
            params['rampup_duration']
        self.ramp_config.rf_ramp_rampdown_start_time = \
            params['bottom_duration'] + \
            params['rampup_duration'] + \
            params['top_duration']
        self.ramp_config.rf_ramp_rampdown_stop_time = \
            params['bottom_duration'] + \
            params['rampup_duration'] + \
            params['top_duration'] + \
            params['rampdown_duration']
        self.loadSignal.emit(self.ramp_config)
        self._verifySync()

    def _emitLoadSignal(self):
        try:
            if self.ramp_config.exist():
                self.ramp_config.load()
        except _ConfigDBException as err:
            QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok)
        else:
            self.loadSignal.emit(self.ramp_config)
        finally:
            self._verifySync()

    def _verifySync(self):
        """Verify sync status related to ConfServer."""
        if self.ramp_config is not None:
            if not self.ramp_config.synchronized:
                pal = self.config_parameters.palette()
                pal.setColor(QPalette.Text, Qt.red)
                self.config_parameters.setPalette(pal)
                self.config_parameters.setToolTip('There are unsaved changes')
            else:
                pal = self.config_parameters.palette()
                pal.setColor(QPalette.Text, Qt.black)
                self.config_parameters.setPalette(pal)
                self.config_parameters.setToolTip('')

    def closeEvent(self, ev):
        """Reimplement closeEvent to avoid forgeting saving changes."""
        if self.ramp_config is None:
            ev.accept()
            super().closeEvent(ev)
        elif not self.ramp_config.synchronized:
            accept = self.settings.verifyUnsavedChanges()
            if accept:
                ev.accept()
                super().closeEvent(ev)
            else:
                ev.ignore()

    @Slot(str, str)
    def _handleUpdateOpticsAdjustSettings(self, tune_cname, chrom_cname):
        self._tunecorr_configname = tune_cname
        self._chromcorr_configname = chrom_cname
        self.config_parameters.updateOpticsAdjustSettings(
            tune_cname, chrom_cname)
Beispiel #3
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()