import os
from qtpy import QtCore, QtWidgets
from .qtd import Ui_MainWindow
from .cakehistwidget import CakeHistogramWidget
from ..utils import SpinBoxFixStyle
from ..version import __version__
from ..citation import __citation__
from ..utils import InformationBox
# exec(open(os.path.join(os.path.curdir, 'version.py')).read())
# exec(open(os.path.join(os.path.curdir, 'citation.py')).read())


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    """
    Main window
    """

    def __init__(self, parent=None):
        # initialization of the superclass
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)  # setup the GUI --> function generated by pyuic6
        try:
            env = os.environ['CONDA_DEFAULT_ENV']
        except:
            env = 'unknown'
        self.setWindowTitle("PeakPo ver. " + str(__version__) + " on " + env)
        #
        self.build_ui()
        self.connect_channel()
        # the two lines needs to be considred for move from this widget file
        self.actionCiting_PeakPo.triggered.connect(self.about)
        self.actionShortcut_keys.triggered.connect(self.shortcutkeys)

    def build_ui(self):
        # self.pushButton_MakeBasePtn.setEnabled(False)
        """ This was unstable feature but from 06/18/2022 it is enabled.
        self.pushButton_SetJCPDSStepTo0001.setDisabled(True)
        self.pushButton_SetJCPDSStepTo001.setDisabled(True)
        self.pushButton_SetJCPDSStepTo01.setDisabled(True)
        self.pushButton_UpdateJCPDSSteps.setDisabled(True)
        self.doubleSpinBox_JCPDSStep.setDisabled(True)
        """
        self.doubleSpinBox_JCPDSStep.setKeyboardTracking(False)
        self.doubleSpinBox_JCPDSStep.setStyle(SpinBoxFixStyle())
        self.doubleSpinBox_Pressure.setKeyboardTracking(False)
        self.doubleSpinBox_Pressure.setStyle(SpinBoxFixStyle())
        self.doubleSpinBox_Temperature.setKeyboardTracking(False)
        self.doubleSpinBox_Temperature.setStyle(SpinBoxFixStyle())
        self.spinBox_BGParam1.setKeyboardTracking(False)
        self.spinBox_BGParam1.setStyle(SpinBoxFixStyle())
        self.doubleSpinBox_Background_ROI_max.setKeyboardTracking(False)
        self.doubleSpinBox_Background_ROI_min.setKeyboardTracking(False)
        self.doubleSpinBox_Background_ROI_max.setStyle(SpinBoxFixStyle())
        self.doubleSpinBox_Background_ROI_min.setStyle(SpinBoxFixStyle())
        self.doubleSpinBox_SetWavelength.setKeyboardTracking(False)
        self.doubleSpinBox_SetWavelength.setStyle(SpinBoxFixStyle())
        linethicknesses = ['0', '0.1', '0.2', '0.5', '0.75',
                           '1', '1.5', '2', '3', '4', '5']
        self.comboBox_BasePtnLineThickness.addItems(linethicknesses)
        self.comboBox_PtnJCPDSBarThickness.addItems(linethicknesses)
        self.comboBox_CakeJCPDSBarThickness.addItems(linethicknesses)
        self.comboBox_BkgnLineThickness.addItems(linethicknesses)
        self.comboBox_WaterfallLineThickness.addItems(linethicknesses)
        self.comboBox_VertCursorThickness.addItems(linethicknesses)
        self.comboBox_BasePtnLineThickness.setCurrentText('1')
        self.comboBox_PtnJCPDSBarThickness.setCurrentText('1')
        self.comboBox_CakeJCPDSBarThickness.setCurrentText('0.5')
        self.comboBox_BkgnLineThickness.setCurrentText('0.5')
        self.comboBox_WaterfallLineThickness.setCurrentText('0.5')
        self.comboBox_VertCursorThickness.setCurrentText('1')
        fontsizes = ['4', '6', '8', '10', '12', '14', '16', '18', '20', '24',
                     '28', '36', '42']
        self.comboBox_HKLFontSize.addItems(fontsizes)
        self.comboBox_HKLFontSize.setCurrentText('8')
        self.comboBox_PnTFontSize.addItems(fontsizes)
        self.comboBox_PnTFontSize.setCurrentText('16')
        self.label_LegendFontSize = QtWidgets.QLabel(self.groupBox_13)
        self.label_LegendFontSize.setText("Legend")
        self.comboBox_LegendFontSize = QtWidgets.QComboBox(self.groupBox_13)
        self.comboBox_LegendFontSize.setMinimumSize(QtCore.QSize(0, 25))
        self.comboBox_LegendFontSize.addItems(fontsizes)
        self.comboBox_LegendFontSize.setCurrentText('12')
        self.label_WaterfallFontSize = QtWidgets.QLabel(self.groupBox_13)
        self.label_WaterfallFontSize.setText("Waterfall label")
        self.comboBox_WaterfallFontSize = QtWidgets.QComboBox(self.groupBox_13)
        self.comboBox_WaterfallFontSize.setMinimumSize(QtCore.QSize(0, 25))
        self.comboBox_WaterfallFontSize.addItems(fontsizes)
        self.comboBox_WaterfallFontSize.setCurrentText('12')
        self.comboBox_PnTFontSize.setMinimumWidth(96)
        self.comboBox_HKLFontSize.setMinimumWidth(96)
        self.comboBox_LegendFontSize.setMinimumWidth(96)
        self.comboBox_WaterfallFontSize.setMinimumWidth(96)
        self.comboBox_PnTFontSize.setLayoutDirection(QtCore.Qt.RightToLeft)
        self.comboBox_HKLFontSize.setLayoutDirection(QtCore.Qt.RightToLeft)
        self.comboBox_LegendFontSize.setLayoutDirection(QtCore.Qt.RightToLeft)
        self.comboBox_WaterfallFontSize.setLayoutDirection(QtCore.Qt.RightToLeft)
        # Rebuild the original QtDesigner row layout to avoid inherited spacer
        # artifacts and match the "Line thickness" left/right arrangement.
        while self.horizontalLayout_4.count():
            item = self.horizontalLayout_4.takeAt(0)
            w = item.widget()
            if w is not None:
                w.hide()
        self._font_size_grid = QtWidgets.QGridLayout()
        self._font_size_grid.setContentsMargins(0, 0, 0, 0)
        self._font_size_grid.setHorizontalSpacing(18)
        self._font_size_grid.setVerticalSpacing(8)
        self._font_size_grid.setColumnStretch(2, 1)
        self.label_15.show()
        self.comboBox_PnTFontSize.show()
        self.label_3.show()
        self.comboBox_HKLFontSize.show()
        self.label_LegendFontSize.show()
        self.comboBox_LegendFontSize.show()
        self.label_WaterfallFontSize.show()
        self.comboBox_WaterfallFontSize.show()
        self._font_size_grid.addWidget(self.label_15, 0, 0)
        self._font_size_grid.addWidget(self.comboBox_PnTFontSize, 0, 1)
        self._font_size_grid.addWidget(self.label_3, 0, 3)
        self._font_size_grid.addWidget(self.comboBox_HKLFontSize, 0, 4)
        self._font_size_grid.addWidget(self.label_LegendFontSize, 1, 0)
        self._font_size_grid.addWidget(self.comboBox_LegendFontSize, 1, 1)
        self._font_size_grid.addWidget(self.label_WaterfallFontSize, 1, 3)
        self._font_size_grid.addWidget(self.comboBox_WaterfallFontSize, 1, 4)
        self.horizontalLayout_4.addLayout(self._font_size_grid)
        self.comboBox_Symmetry.addItems(['cubic', 'tetragonal',
                                         'hexagonal', 'orthorhombic'])
        self.comboBox_Symmetry.setCurrentText('cubic')
        self._setup_plot_subtabs()
        self._setup_jcpds_bars_layout()
        self._setup_title_config_group()
        self._setup_plot_setup_group()
        self._setup_bg_default_button()
        self._setup_cake_colormap_control()
        self._setup_spectrum_smoothing_control()
        self._setup_ccd_roi_group()
        self._reorder_spectrum_process_groups()
        self._setup_plot_config_python_export()
        self._move_backup_into_file_data_tab()
        self._setup_backup_comment_button()
        self._layout_backup_buttons()
        self._setup_diff_tab()
        self._promote_diff_to_main_tab()
        self._setup_map_tab()
        self._promote_map_to_main_tab()
        self._setup_seq_tab()
        self._promote_seq_to_main_tab()
        self._reorder_main_tabs_and_fit_tab_names()
        self._setup_toolbar_ccd_reset_button()
        self._setup_toolbar_diff_toggle()
        self._spread_top_toolbar_even()
        if hasattr(self, "pushButton_S_Zoom"):
            self.pushButton_S_Zoom.setText("⤢")
            self.pushButton_S_Zoom.setToolTip("Fit X/Y to data (zoom out)")
        if hasattr(self, "checkBox_BgSub"):
            self.checkBox_BgSub.setText("BgSub")
            self.checkBox_BgSub.setToolTip("Subtract background from 1D pattern")
        if hasattr(self, "checkBox_ShowBg"):
            self.checkBox_ShowBg.setText("Bg show")
            self.checkBox_ShowBg.setToolTip("Show fitted background")
        if hasattr(self, "checkBox_ShowCake"):
            self.checkBox_ShowCake.setChecked(True)
            self.checkBox_ShowCake.setVisible(False)
        if hasattr(self, "checkBox_LongCursor"):
            self.checkBox_LongCursor.setText("VCursor")
            self.checkBox_LongCursor.setToolTip("Change cursor to a vertical bar")
        # Legacy toolbar save icon is replaced by the session Save button.
        if hasattr(self, "pushButton_S_SaveSession"):
            self.pushButton_S_SaveSession.setVisible(False)
        for name in ("pushButton_S_PIncrease", "pushButton_S_PDecrease"):
            if hasattr(self, name):
                btn = getattr(self, name)
                btn.setEnabled(False)
                btn.setToolTip("Reserved for a future Rampo update.")
        for name in ("pushButton_S_TIncrease", "pushButton_S_RoomT", "pushButton_S_TDecrease"):
            if hasattr(self, name):
                getattr(self, name).setVisible(False)
        # Session save/backup icon buttons are replaced by the File>Backup tab.
        if hasattr(self, "pushButton_SaveDPP"):
            self.pushButton_SaveDPP.setVisible(False)
        if hasattr(self, "pushButton_OpenBackupInfo"):
            self.pushButton_OpenBackupInfo.setVisible(False)
        if hasattr(self, "groupBox_2"):
            self.groupBox_2.setVisible(False)
        if hasattr(self, "groupBox_10"):
            self.groupBox_10.setVisible(False)
        if hasattr(self, "groupBox_36"):
            self.groupBox_36.setVisible(False)
        if hasattr(self, "groupBox_23"):
            self.groupBox_23.setVisible(False)
        if hasattr(self, "groupBox_14"):
            self.groupBox_14.setVisible(False)
        if hasattr(self, "groupBox_12"):
            self.groupBox_12.setVisible(False)
        if hasattr(self, "pushButton_SaveBgSubCHI"):
            self.pushButton_SaveBgSubCHI.setVisible(False)
        for name in (
            "groupBox_15",
            "tableWidget_UnitCell",
            "plainTextEdit_UCFitOutput",
            "pushButton_PerformUCFit",
            "comboBox_Symmetry",
            "comboBox_PeakFitLabels",
            "pushButton_ExportToUCFit",
            "pushButton_UpdateUCFitSteps",
            "doubleSpinBox_UCFitStep",
            "pushButton_SetUCFitStepTo0_01",
            "pushButton_SetUCFitStepTo0_001",
            "pushButton_SetUCFitStepTo0_0001",
        ):
            if hasattr(self, name):
                getattr(self, name).setVisible(False)
        if hasattr(self, "pushButton_ApplyCakeView"):
            self.pushButton_ApplyCakeView.setVisible(False)
        if hasattr(self, "pushButton_ApplyWaterfallChange"):
            self.pushButton_ApplyWaterfallChange.setVisible(False)
        if hasattr(self, "checkBox_SetToBasePtnLambda"):
            self.checkBox_SetToBasePtnLambda.setVisible(False)
        for name in (
            "groupBox_19",
            "tableWidget_DiffImgAzi",
            "pushButton_AddAzi",
            "pushButton_RemoveAzi",
            "pushButton_ClearAziList",
            "pushButton_LoadCakeMarkerFile",
            "pushButton_SaveCakeMarkerFile",
            "pushButton_InvertCakeBoxes",
            "pushButton_HighlightSelectedMarker",
            "pushButton_IntegrateCake",
        ):
            if hasattr(self, name):
                getattr(self, name).setVisible(False)
        if hasattr(self, "tabWidget_2") and hasattr(self, "tabWidget_2Page2"):
            idx = self.tabWidget_2.indexOf(self.tabWidget_2Page2)
            if idx >= 0:
                self.tabWidget_2.removeTab(idx)
        # Night,cake checkbox is replaced by explicit cake colormap selector
        # under Plot > Control.
        if hasattr(self, "checkBox_WhiteForPeak"):
            self.checkBox_WhiteForPeak.setVisible(False)
        # Legacy DPP options are removed from UI; keep safe defaults in code.
        for name in (
            "groupBox_17", "groupBox_22",
            "checkBox_NavDPP", "checkBox_AutoGenDPP", "checkBox_SaveDPPMove",
            "checkBox_ForceOverwite", "checkBox_IgnoreDirChange",
            "checkBox_AutogenMissing",
        ):
            if hasattr(self, name):
                getattr(self, name).setVisible(False)
        for name in (
            "groupBox_11",
        ):
            if hasattr(self, name):
                getattr(self, name).setVisible(False)
        self._setup_nav_carryover_config()
        self.tableWidget_DiffImgAzi.\
            setHorizontalHeaderLabels(['Notes', '2th', 'Azi', '2th', 'Azi'])
        self._setup_cake_scale_layout()
        self.doubleSpinBox_Pressure.valueChanged.connect(
            self._update_pt_spinbox_colors)
        self.doubleSpinBox_Temperature.valueChanged.connect(
            self._update_pt_spinbox_colors)
        self._update_pt_spinbox_colors()
        self._fix_tab_clipping()
        self._apply_rampo_labels()
        self._disable_rapo_tab()
        self._disable_ccd_tab()
        self._disable_unit_cell_fit()
        # navigation toolbar modification
        """
        self.ntb_WholePtn = QtWidgets.QPushButton()
        self.ntb_WholePtn.setText("Z")
        self.ntb_WholePtn.setToolTip("Zoom Out")
        self.mpl.ntb.addWidget(self.ntb_WholePtn)
        self.ntb_SaveSession = QtWidgets.QPushButton()
        self.ntb_SaveSession.setText("S")
        self.ntb_SaveSession.setToolTip("Save dpp and ppss")
        self.mpl.ntb.addWidget(self.ntb_SaveSession)
        self.ntb_toPkFt = QtWidgets.QPushButton()
        self.ntb_toPkFt.setText("toPkFt")
        self.mpl.ntb.addWidget(self.ntb_toPkFt)
        self.ntb_fromPkFt = QtWidgets.QPushButton()
        self.ntb_fromPkFt.setText("fromPkFt")
        self.mpl.ntb.addWidget(self.ntb_fromPkFt)
        self.ntb_ResetY = QtWidgets.QCheckBox()
        self.ntb_ResetY.setCheckable(True)
        self.ntb_ResetY.setChecked(False)
        self.ntb_ResetY.setText("AutoYScale")
        self.mpl.ntb.addWidget(self.ntb_ResetY)
        self.ntb_Bgsub = QtWidgets.QCheckBox()
        self.ntb_Bgsub.setCheckable(True)
        self.ntb_Bgsub.setChecked(True)
        self.ntb_Bgsub.setText("BgSub")
        self.mpl.ntb.addWidget(self.ntb_Bgsub)
        self.ntb_NightView = QtWidgets.QCheckBox()
        self.ntb_NightView.setCheckable(True)
        self.ntb_NightView.setChecked(True)
        self.ntb_NightView.setText("Night")
        self.mpl.ntb.addWidget(self.ntb_NightView)
        """

    def _setup_toolbar_ccd_reset_button(self):
        if (not hasattr(self, "horizontalLayout_21")) or \
                hasattr(self, "pushButton_S_CCDReset"):
            return
        self.pushButton_S_CCDReset = QtWidgets.QPushButton(self.frame_9)
        self.pushButton_S_CCDReset.setObjectName("pushButton_S_CCDReset")
        self.pushButton_S_CCDReset.setMinimumSize(QtCore.QSize(25, 25))
        self.pushButton_S_CCDReset.setMaximumSize(QtCore.QSize(40, 16777215))
        self.pushButton_S_CCDReset.setText("CCD")
        self.pushButton_S_CCDReset.setToolTip("Reset CCD contrast scale")
        idx_zoom = self.horizontalLayout_21.indexOf(self.pushButton_S_Zoom) \
            if hasattr(self, "pushButton_S_Zoom") else -1
        if idx_zoom >= 0:
            self.horizontalLayout_21.insertWidget(idx_zoom + 1, self.pushButton_S_CCDReset)
        else:
            self.horizontalLayout_21.insertWidget(0, self.pushButton_S_CCDReset)

    def _setup_nav_carryover_config(self):
        if not hasattr(self, "scrollAreaWidgetContents_2") or \
                (not hasattr(self, "verticalLayout_31")):
            return
        self.groupBox_NavCarry = QtWidgets.QGroupBox(
            "SPE Navigation Carry-over", self.scrollAreaWidgetContents_2)
        self.groupBox_NavCarry.setObjectName("groupBox_NavCarry")
        self.groupBox_NavCarry.setSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
        self.gridLayout_NavCarry = QtWidgets.QGridLayout(self.groupBox_NavCarry)
        self.gridLayout_NavCarry.setContentsMargins(12, 12, 12, 12)
        self.gridLayout_NavCarry.setHorizontalSpacing(48)
        self.gridLayout_NavCarry.setVerticalSpacing(14)

        self.checkBox_CarryNavJCPDS = QtWidgets.QCheckBox("RPO")
        self.checkBox_CarryNavPressure = QtWidgets.QCheckBox("Pressure")
        self.checkBox_CarryNavSpectrumSmoothing = QtWidgets.QCheckBox("Spectrum smoothing")
        self.checkBox_CarryNavCakeZScale = QtWidgets.QCheckBox("CCD z scale")
        self.checkBox_CarryNavCCDRoi = QtWidgets.QCheckBox("CCD ROI")
        self.checkBox_CarryNavBackground = QtWidgets.QCheckBox("Background")
        self.checkBox_CarryNavWaterfall = QtWidgets.QCheckBox("Waterfall list")
        self.checkBox_CarryNavFits = QtWidgets.QCheckBox("Fits information")

        # Default non-carry-over categories:
        # backup, cake z scale, background, fits
        self.checkBox_CarryNavJCPDS.setChecked(True)
        self.checkBox_CarryNavPressure.setChecked(True)
        self.checkBox_CarryNavSpectrumSmoothing.setChecked(True)
        self.checkBox_CarryNavCakeZScale.setChecked(False)
        self.checkBox_CarryNavCCDRoi.setChecked(True)
        self.checkBox_CarryNavBackground.setChecked(False)
        self.checkBox_CarryNavWaterfall.setChecked(True)
        self.checkBox_CarryNavFits.setChecked(False)

        left_items = [
            self.checkBox_CarryNavJCPDS,
            self.checkBox_CarryNavPressure,
            self.checkBox_CarryNavSpectrumSmoothing,
            self.checkBox_CarryNavCCDRoi,
        ]
        right_items = [
            self.checkBox_CarryNavCakeZScale,
            self.checkBox_CarryNavBackground,
            self.checkBox_CarryNavWaterfall,
            self.checkBox_CarryNavFits,
        ]
        for row, cb in enumerate(left_items):
            self.gridLayout_NavCarry.addWidget(cb, row, 0, 1, 1)
            self.gridLayout_NavCarry.setRowStretch(row, 1)
        for row, cb in enumerate(right_items):
            self.gridLayout_NavCarry.addWidget(cb, row, 1, 1, 1)
        self.gridLayout_NavCarry.setColumnStretch(0, 1)
        self.gridLayout_NavCarry.setColumnStretch(1, 1)

        insert_idx = self.verticalLayout_31.count()
        if hasattr(self, "groupBox_17"):
            idx = self.verticalLayout_31.indexOf(self.groupBox_17)
            if idx >= 0:
                insert_idx = idx
        self.verticalLayout_31.insertWidget(insert_idx, self.groupBox_NavCarry)

    def _fix_tab_clipping(self):
        # Keep native tab visuals while nudging tab size metrics to prevent
        # clipped ascenders/descenders on some Qt style+font combinations.
        class _NoClipTabStyle(QtWidgets.QProxyStyle):
            def sizeFromContents(self, contents_type, option, size, widget=None):
                sized = super().sizeFromContents(
                    contents_type, option, size, widget)
                if contents_type == QtWidgets.QStyle.CT_TabBarTab:
                    sized.setHeight(sized.height() + 4)
                return sized

        self._tabbar_styles = []
        tab_widgets = [
            self.tabWidget, self.tabWidget_3, self.tabWidget1,
            self.tabWidget_5, self.tabWidget_2, self.tabWidget_4,
            self.tabWidget_PeakFit
        ]
        if hasattr(self, "tabWidget_PlotSub"):
            tab_widgets.append(self.tabWidget_PlotSub)
        for tab_widget in tab_widgets:
            bar = tab_widget.tabBar()
            bar.setStyleSheet("")
            bar.setExpanding(False)
            tab_style = _NoClipTabStyle(bar.style())
            self._tabbar_styles.append(tab_style)
            bar.setStyle(tab_style)

    def _setup_plot_subtabs(self):
        # Build nested tabs inside Plot: Control / Config.
        # Keep original widget IDs so existing controller wiring still works.
        while self.verticalLayout_44.count():
            item = self.verticalLayout_44.takeAt(0)
            w = item.widget()
            if w is not None:
                w.hide()
                w.setParent(None)

        self.tabWidget_PlotSub = QtWidgets.QTabWidget(self.tab_Plot)
        self.tabWidget_PlotSub.setObjectName("tabWidget_PlotSub")

        self.tab_PlotControl = QtWidgets.QWidget()
        self.tab_PlotControl.setObjectName("tab_PlotControl")
        self._plot_ctrl_layout = QtWidgets.QVBoxLayout(self.tab_PlotControl)
        self._plot_ctrl_layout.setContentsMargins(0, 0, 0, 0)
        self.scrollArea_PlotControl = QtWidgets.QScrollArea(self.tab_PlotControl)
        self.scrollArea_PlotControl.setWidgetResizable(True)
        self.scrollArea_PlotControl.setObjectName("scrollArea_PlotControl")
        self.plotControlContents = QtWidgets.QWidget()
        self.plotControlContents.setObjectName("plotControlContents")
        self.verticalLayout_PlotControl = QtWidgets.QVBoxLayout(self.plotControlContents)
        self.verticalLayout_PlotControl.setObjectName("verticalLayout_PlotControl")
        self.scrollArea_PlotControl.setWidget(self.plotControlContents)
        self._plot_ctrl_layout.addWidget(self.scrollArea_PlotControl)

        self.tab_PlotConfig = QtWidgets.QWidget()
        self.tab_PlotConfig.setObjectName("tab_PlotConfig")
        self._plot_cfg_layout = QtWidgets.QVBoxLayout(self.tab_PlotConfig)
        self._plot_cfg_layout.setContentsMargins(0, 0, 0, 0)
        self.scrollArea_PlotConfig = QtWidgets.QScrollArea(self.tab_PlotConfig)
        self.scrollArea_PlotConfig.setWidgetResizable(True)
        self.scrollArea_PlotConfig.setObjectName("scrollArea_PlotConfig")
        self.plotConfigContents = QtWidgets.QWidget()
        self.plotConfigContents.setObjectName("plotConfigContents")
        self.verticalLayout_PlotConfig = QtWidgets.QVBoxLayout(self.plotConfigContents)
        self.verticalLayout_PlotConfig.setObjectName("verticalLayout_PlotConfig")
        self.scrollArea_PlotConfig.setWidget(self.plotConfigContents)
        self._plot_cfg_layout.addWidget(self.scrollArea_PlotConfig)

        self.tabWidget_PlotSub.addTab(self.tab_PlotControl, "Control")
        self.tabWidget_PlotSub.addTab(self.tab_PlotConfig, "Config")
        self.verticalLayout_44.addWidget(self.tabWidget_PlotSub)

        # Control tab: active CCD controls first, disabled future controls after.
        self.groupBox_34.setParent(self.plotControlContents)
        self.groupBox_20.setParent(self.plotControlContents)
        self.groupBox_29.setParent(self.plotControlContents)
        self.groupBox_5.setParent(self.plotControlContents)
        self.groupBox_23.setParent(self.plotControlContents)
        self.verticalLayout_PlotControl.addWidget(self.groupBox_29)
        self.verticalLayout_PlotControl.addWidget(self.groupBox_5)
        self.verticalLayout_PlotControl.addWidget(self.groupBox_34)
        self.verticalLayout_PlotControl.addWidget(self.groupBox_20)
        self.verticalLayout_PlotControl.addWidget(self.groupBox_23)
        self.verticalLayout_PlotControl.addStretch(1)

        # Config tab: remaining plot config groups
        self.groupBox_16.setParent(self.plotConfigContents)
        self.groupBox_24.setParent(self.plotConfigContents)
        self.groupBox_13.setParent(self.plotConfigContents)
        self.groupBox_27.setParent(self.plotConfigContents)
        self.verticalLayout_PlotConfig.addWidget(self.groupBox_16)
        self.verticalLayout_PlotConfig.addWidget(self.groupBox_24)
        self.verticalLayout_PlotConfig.addWidget(self.groupBox_13)
        self.verticalLayout_PlotConfig.addWidget(self.groupBox_27)
        self.verticalLayout_PlotConfig.addStretch(1)

    def _setup_jcpds_bars_layout(self):
        if not hasattr(self, "groupBox_20"):
            return
        if not hasattr(self, "verticalLayout_23"):
            return
        if hasattr(self, "_jcpds_bars_grid"):
            return

        self.groupBox_20.setMinimumHeight(96)
        self.groupBox_20.setMaximumHeight(112)
        self.label_27.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)
        self.label_28.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)
        self.label_27.setMinimumWidth(60)
        self.label_28.setMinimumWidth(60)
        self.label_27.setMaximumWidth(60)
        self.label_28.setMaximumWidth(60)
        self.horizontalSlider_JCPDSBarScale.setMinimumHeight(16)
        self.horizontalSlider_JCPDSBarPosition.setMinimumHeight(16)

        # Replace stacked label/slider widgets with two compact rows.
        while self.verticalLayout_23.count():
            item = self.verticalLayout_23.takeAt(0)
            w = item.widget()
            if w is not None:
                w.setParent(self.groupBox_20)

        self._jcpds_bars_grid = QtWidgets.QGridLayout()
        self._jcpds_bars_grid.setContentsMargins(4, 0, 6, 0)
        self._jcpds_bars_grid.setHorizontalSpacing(0)
        self._jcpds_bars_grid.setVerticalSpacing(7)
        self._jcpds_bars_grid.addWidget(self.label_27, 0, 0, 1, 1)
        self._jcpds_bars_grid.addWidget(self.horizontalSlider_JCPDSBarScale, 0, 1, 1, 1)
        self._jcpds_bars_grid.addWidget(self.label_28, 1, 0, 1, 1)
        self._jcpds_bars_grid.addWidget(self.horizontalSlider_JCPDSBarPosition, 1, 1, 1, 1)
        self._jcpds_bars_grid.setColumnStretch(1, 1)
        self.verticalLayout_23.addLayout(self._jcpds_bars_grid)

    def _setup_title_config_group(self):
        if not hasattr(self, "verticalLayout_PlotConfig"):
            return
        if hasattr(self, "groupBox_TitleConfig"):
            return
        if not hasattr(self, "checkBox_ShortPlotTitle"):
            return

        self.groupBox_TitleConfig = QtWidgets.QGroupBox("Title", self.plotConfigContents)
        self.groupBox_TitleConfig.setObjectName("groupBox_TitleConfig")
        self.groupBox_TitleConfig.setSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
        self.gridLayout_TitleConfig = QtWidgets.QGridLayout(self.groupBox_TitleConfig)
        self.gridLayout_TitleConfig.setContentsMargins(12, 12, 12, 12)
        self.gridLayout_TitleConfig.setHorizontalSpacing(12)
        self.gridLayout_TitleConfig.setVerticalSpacing(10)

        # Move existing title mode checkbox into the dedicated title group.
        self.checkBox_ShortPlotTitle.setParent(self.groupBox_TitleConfig)
        self.checkBox_ShortPlotTitle.setText("Filename only")
        self.checkBox_ShortPlotTitle.setToolTip(
            "Use filename (without path) as the plot title")
        self.gridLayout_TitleConfig.addWidget(self.checkBox_ShortPlotTitle, 0, 0, 1, 2)

        self.checkBox_TitleTruncateMiddle = QtWidgets.QCheckBox(
            "Truncate middle", self.groupBox_TitleConfig)
        self.checkBox_TitleTruncateMiddle.setObjectName("checkBox_TitleTruncateMiddle")
        self.checkBox_TitleTruncateMiddle.setChecked(True)
        self.checkBox_TitleTruncateMiddle.setToolTip(
            "If checked, truncate long title in the middle. If unchecked, truncate from the left.")
        self.gridLayout_TitleConfig.addWidget(self.checkBox_TitleTruncateMiddle, 1, 0, 1, 2)

        self.label_TitleFontSize = QtWidgets.QLabel("Font size", self.groupBox_TitleConfig)
        self.label_TitleFontSize.setObjectName("label_TitleFontSize")
        self.spinBox_TitleFontSize = QtWidgets.QSpinBox(self.groupBox_TitleConfig)
        self.spinBox_TitleFontSize.setObjectName("spinBox_TitleFontSize")
        self.spinBox_TitleFontSize.setMinimum(6)
        self.spinBox_TitleFontSize.setMaximum(72)
        self.spinBox_TitleFontSize.setValue(12)
        self.spinBox_TitleFontSize.setMinimumHeight(25)
        self.spinBox_TitleFontSize.setKeyboardTracking(False)
        self.spinBox_TitleFontSize.setStyle(SpinBoxFixStyle())

        self.label_TitleMaxLength = QtWidgets.QLabel("Max length", self.groupBox_TitleConfig)
        self.label_TitleMaxLength.setObjectName("label_TitleMaxLength")
        self.spinBox_TitleMaxLength = QtWidgets.QSpinBox(self.groupBox_TitleConfig)
        self.spinBox_TitleMaxLength.setObjectName("spinBox_TitleMaxLength")
        self.spinBox_TitleMaxLength.setMinimum(20)
        self.spinBox_TitleMaxLength.setMaximum(500)
        self.spinBox_TitleMaxLength.setValue(140)
        self.spinBox_TitleMaxLength.setMinimumHeight(25)
        self.spinBox_TitleMaxLength.setKeyboardTracking(False)
        self.spinBox_TitleMaxLength.setStyle(SpinBoxFixStyle())

        self.gridLayout_TitleConfig.addWidget(self.label_TitleFontSize, 0, 2, 1, 1)
        self.gridLayout_TitleConfig.addWidget(self.spinBox_TitleFontSize, 0, 3, 1, 1)
        self.gridLayout_TitleConfig.addWidget(self.label_TitleMaxLength, 1, 2, 1, 1)
        self.gridLayout_TitleConfig.addWidget(self.spinBox_TitleMaxLength, 1, 3, 1, 1)
        self.gridLayout_TitleConfig.setColumnStretch(0, 1)
        self.gridLayout_TitleConfig.setColumnStretch(1, 1)
        self.gridLayout_TitleConfig.setColumnStretch(3, 1)

        # Put title controls near the top of Plot > Config.
        self.verticalLayout_PlotConfig.insertWidget(0, self.groupBox_TitleConfig)

    def _setup_plot_setup_group(self):
        if not hasattr(self, "gridLayout_7"):
            return
        if hasattr(self, "checkBox_NightView"):
            self.checkBox_NightView.setText("Dark mode")
            self.checkBox_NightView.setToolTip("Show in dark background")
            self.gridLayout_7.addWidget(self.checkBox_NightView, 0, 1, 1, 1)
        if hasattr(self, "checkBox_ShowLargePnT"):
            self.gridLayout_7.addWidget(self.checkBox_ShowLargePnT, 0, 0, 1, 1)

    def _setup_bg_default_button(self):
        if not hasattr(self, "gridLayout_5"):
            return
        self.label_4.setVisible(False)
        self.spinBox_BGParam0.setVisible(False)
        self.label_5.setVisible(False)
        self.spinBox_BGParam2.setVisible(False)
        self.label_6.setText("Polynomial order:")
        self.spinBox_BGParam1.setMinimum(0)
        self.spinBox_BGParam1.setMaximum(99)
        self.spinBox_BGParam1.setValue(3)
        self.spinBox_BGParam1.setToolTip("Polynomial order for Raman background fit")
        if hasattr(self, "groupBox_4") and hasattr(self, "gridLayout_9"):
            self.groupBox_4.setTitle("Background")
            self.label_2.setText("Minimum Raman shift")
            self.label.setText("Maximum Raman shift")
            if self.label_6.parent() is not self.groupBox_4:
                self.label_6.setParent(self.groupBox_4)
            if self.spinBox_BGParam1.parent() is not self.groupBox_4:
                self.spinBox_BGParam1.setParent(self.groupBox_4)
            self.gridLayout_9.addWidget(self.label_6, 3, 0, 1, 1)
            self.gridLayout_9.addWidget(self.spinBox_BGParam1, 3, 2, 1, 1)
            if hasattr(self, "tableWidget_BackgroundConstraints"):
                self.tableWidget_BackgroundConstraints.setParent(self.groupBox_4)
                self.tableWidget_BackgroundConstraints.setColumnCount(2)
                self.tableWidget_BackgroundConstraints.setHorizontalHeaderLabels(
                    ["Min", "Max"])
                self.tableWidget_BackgroundConstraints.verticalHeader().setVisible(False)
                self.tableWidget_BackgroundConstraints.setSelectionBehavior(
                    QtWidgets.QAbstractItemView.SelectRows)
                self.tableWidget_BackgroundConstraints.setSelectionMode(
                    QtWidgets.QAbstractItemView.SingleSelection)
                self.tableWidget_BackgroundConstraints.setEditTriggers(
                    QtWidgets.QAbstractItemView.NoEditTriggers)
                header = self.tableWidget_BackgroundConstraints.horizontalHeader()
                header.setStretchLastSection(False)
                header.setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
                self.tableWidget_BackgroundConstraints.setMinimumHeight(120)
                self.gridLayout_9.addWidget(self.tableWidget_BackgroundConstraints, 4, 0, 1, 3)
                self.pushButton_BGAreaAdd = QtWidgets.QPushButton("Add area", self.groupBox_4)
                self.pushButton_BGAreaAdd.setCheckable(True)
                self.pushButton_BGAreaRemove = QtWidgets.QPushButton("Remove area", self.groupBox_4)
                self.pushButton_BGAreaClear = QtWidgets.QPushButton("Clear areas", self.groupBox_4)
                self.pushButton_BGFit = QtWidgets.QPushButton("Fit BG", self.groupBox_4)
                self.horizontalLayout_BGAreaButtons = QtWidgets.QHBoxLayout()
                self.horizontalLayout_BGAreaButtons.setContentsMargins(0, 0, 0, 0)
                self.horizontalLayout_BGAreaButtons.addWidget(self.pushButton_BGAreaAdd)
                self.horizontalLayout_BGAreaButtons.addWidget(self.pushButton_BGAreaRemove)
                self.horizontalLayout_BGAreaButtons.addWidget(self.pushButton_BGAreaClear)
                self.gridLayout_9.addLayout(self.horizontalLayout_BGAreaButtons, 5, 0, 1, 3)
                self.gridLayout_9.addWidget(self.pushButton_BGFit, 6, 0, 1, 3)
            self.gridLayout_9.setContentsMargins(12, 12, 12, 12)
            self.gridLayout_9.setVerticalSpacing(12)
        if hasattr(self, "groupBox_7"):
            self.groupBox_7.setVisible(False)

    def _setup_cake_scale_layout(self):
        self.groupBox_29.setTitle("CCD scale")
        self.groupBox_29.setMinimumHeight(300)
        self.groupBox_29.setMaximumHeight(520)
        self.pushButton_ResetCakeScale.setText("Reset")
        self.pushButton_ResetCakeScale.setMinimumHeight(25)
        self.pushButton_ResetCakeScale.setMaximumHeight(25)

        self.cake_hist_widget = CakeHistogramWidget(self.groupBox_29)
        self.cake_hist_widget.setMinimumHeight(190)
        self.cake_hist_widget.setMaximumHeight(210)

        # Hide legacy controls replaced by direct min/max + histogram controls.
        self.label_8.setVisible(False)
        self.horizontalSlider_VMin.setVisible(False)
        self.label_9.setVisible(False)
        self.horizontalSlider_VMax.setVisible(False)
        self.label_19.setVisible(False)
        self.horizontalSlider_MaxScaleBars.setVisible(False)
        self.spinBox_MaxCakeScale.setVisible(False)

        # Clear and rebuild group layout.
        while self.verticalLayout_11.count():
            item = self.verticalLayout_11.takeAt(0)
            w = item.widget()
            if w is not None:
                w.setParent(self.groupBox_29)

        # Top block: start with Min/Max, then log and colormap.
        self.frame_CakeTopGrid = QtWidgets.QFrame(self.groupBox_29)
        self.frame_CakeTopGrid.setObjectName("frame_CakeTopGrid")
        self.gridLayout_CakeTop = QtWidgets.QGridLayout(self.frame_CakeTopGrid)
        self.gridLayout_CakeTop.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_CakeTop.setHorizontalSpacing(10)
        self.gridLayout_CakeTop.setVerticalSpacing(4)

        self.cake_hist_widget.check_log.setParent(self.frame_CakeTopGrid)
        self.cake_hist_widget.check_focus.setVisible(False)
        self.cake_hist_widget.label_low.setVisible(False)
        self.cake_hist_widget.spin_low_pct.setVisible(False)
        self.cake_hist_widget.label_high.setVisible(False)
        self.cake_hist_widget.spin_high_pct.setVisible(False)
        self.cake_hist_widget.button_apply_pct.setVisible(False)

        self.label_CCDScaleMin = QtWidgets.QLabel("Min", self.frame_CakeTopGrid)
        self.doubleSpinBox_CCDScaleMin = QtWidgets.QDoubleSpinBox(self.frame_CakeTopGrid)
        self.doubleSpinBox_CCDScaleMin.setObjectName("doubleSpinBox_CCDScaleMin")
        self.doubleSpinBox_CCDScaleMin.setDecimals(3)
        self.doubleSpinBox_CCDScaleMin.setRange(-1.0e12, 1.0e12)
        self.doubleSpinBox_CCDScaleMin.setMinimumHeight(25)
        self.doubleSpinBox_CCDScaleMin.setKeyboardTracking(False)

        self.label_CCDScaleMax = QtWidgets.QLabel("Max", self.frame_CakeTopGrid)
        self.doubleSpinBox_CCDScaleMax = QtWidgets.QDoubleSpinBox(self.frame_CakeTopGrid)
        self.doubleSpinBox_CCDScaleMax.setObjectName("doubleSpinBox_CCDScaleMax")
        self.doubleSpinBox_CCDScaleMax.setDecimals(3)
        self.doubleSpinBox_CCDScaleMax.setRange(-1.0e12, 1.0e12)
        self.doubleSpinBox_CCDScaleMax.setMinimumHeight(25)
        self.doubleSpinBox_CCDScaleMax.setKeyboardTracking(False)

        self.gridLayout_CakeTop.addWidget(self.label_CCDScaleMin, 0, 0, 1, 1)
        self.gridLayout_CakeTop.addWidget(self.doubleSpinBox_CCDScaleMin, 0, 1, 1, 1)
        self.gridLayout_CakeTop.addWidget(self.label_CCDScaleMax, 0, 2, 1, 1)
        self.gridLayout_CakeTop.addWidget(self.doubleSpinBox_CCDScaleMax, 0, 3, 1, 1)
        self.gridLayout_CakeTop.addWidget(self.cake_hist_widget.check_log, 1, 0, 1, 1)

        if hasattr(self, "groupBox_CakeColormap"):
            self.groupBox_CakeColormap.setVisible(False)
        if hasattr(self, "label_CakeColormap"):
            self.label_CakeColormap.setVisible(False)
        if hasattr(self, "comboBox_CakeColormap"):
            self.comboBox_CakeColormap.setParent(self.frame_CakeTopGrid)
            self.comboBox_CakeColormap.setMinimumHeight(25)
            target_w = 160
            self.comboBox_CakeColormap.setMinimumWidth(target_w)
            self.comboBox_CakeColormap.setMaximumWidth(target_w)
            self.comboBox_CakeColormap.setSizePolicy(
                QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
            self.gridLayout_CakeTop.addWidget(self.comboBox_CakeColormap, 1, 1, 1, 2)

        self.pushButton_ResetCakeScale.setParent(self.frame_CakeTopGrid)
        self.gridLayout_CakeTop.addWidget(self.pushButton_ResetCakeScale, 1, 3, 1, 1)
        self.gridLayout_CakeTop.setColumnStretch(1, 1)
        self.gridLayout_CakeTop.setColumnStretch(3, 1)

        # Histogram plot
        self.cake_hist_widget.canvas.setParent(self.groupBox_29)

        self.verticalLayout_11.setContentsMargins(10, 0, 10, 0)
        self.verticalLayout_11.setSpacing(4)
        self.verticalLayout_11.addWidget(self.frame_CakeTopGrid)
        self.verticalLayout_11.addWidget(self.cake_hist_widget.canvas)

    def _setup_cake_colormap_control(self):
        if not hasattr(self, "verticalLayout_PlotControl"):
            return
        if hasattr(self, "groupBox_CakeColormap"):
            return
        self.groupBox_CakeColormap = QtWidgets.QGroupBox(
            "2D Cake colormap", self.plotControlContents)
        self.groupBox_CakeColormap.setObjectName("groupBox_CakeColormap")
        self.horizontalLayout_CakeColormap = QtWidgets.QHBoxLayout(
            self.groupBox_CakeColormap)
        self.horizontalLayout_CakeColormap.setObjectName("horizontalLayout_CakeColormap")
        self.label_CakeColormap = QtWidgets.QLabel("Colormap", self.groupBox_CakeColormap)
        self.label_CakeColormap.setObjectName("label_CakeColormap")
        self.comboBox_CakeColormap = QtWidgets.QComboBox(self.groupBox_CakeColormap)
        self.comboBox_CakeColormap.setObjectName("comboBox_CakeColormap")
        self.comboBox_CakeColormap.addItems([
            "inferno", "inferno_r",
            "magma", "magma_r",
            "gray", "gray_r",
            "viridis", "viridis_r",
        ])
        self.comboBox_CakeColormap.setCurrentText("inferno")
        self.horizontalLayout_CakeColormap.addWidget(self.label_CakeColormap)
        self.horizontalLayout_CakeColormap.addWidget(self.comboBox_CakeColormap, 1)
        idx_gray = self.verticalLayout_PlotControl.indexOf(self.groupBox_29)
        if idx_gray < 0:
            self.verticalLayout_PlotControl.insertWidget(0, self.groupBox_CakeColormap)
        else:
            self.verticalLayout_PlotControl.insertWidget(idx_gray + 1, self.groupBox_CakeColormap)

    def _setup_ccd_roi_group(self):
        if not hasattr(self, "verticalLayout_21"):
            return
        if hasattr(self, "groupBox_CCDRoi"):
            return
        self.groupBox_CCDRoi = QtWidgets.QGroupBox("CCD ROI", self.scrollAreaWidgetContents_5)
        self.groupBox_CCDRoi.setObjectName("groupBox_CCDRoi")
        self.gridLayout_CCDRoi = QtWidgets.QGridLayout(self.groupBox_CCDRoi)
        self.gridLayout_CCDRoi.setContentsMargins(12, 12, 12, 12)
        self.gridLayout_CCDRoi.setHorizontalSpacing(10)
        self.gridLayout_CCDRoi.setVerticalSpacing(8)
        self.label_CCDRowMin = QtWidgets.QLabel("Row min", self.groupBox_CCDRoi)
        self.spinBox_CCDRowMin = QtWidgets.QSpinBox(self.groupBox_CCDRoi)
        self.spinBox_CCDRowMin.setMinimum(0)
        self.spinBox_CCDRowMin.setMaximum(0)
        self.spinBox_CCDRowMin.setMinimumHeight(25)
        self.label_CCDRowMax = QtWidgets.QLabel("Row max", self.groupBox_CCDRoi)
        self.spinBox_CCDRowMax = QtWidgets.QSpinBox(self.groupBox_CCDRoi)
        self.spinBox_CCDRowMax.setMinimum(0)
        self.spinBox_CCDRowMax.setMaximum(0)
        self.spinBox_CCDRowMax.setMinimumHeight(25)
        self.pushButton_CCDSelectRoi = QtWidgets.QPushButton("Select ROI", self.groupBox_CCDRoi)
        self.pushButton_CCDSelectRoi.setObjectName("pushButton_CCDSelectRoi")
        self.pushButton_CCDSelectRoi.setCheckable(True)
        self.pushButton_CCDFullRoi = QtWidgets.QPushButton("Full CCD", self.groupBox_CCDRoi)
        self.pushButton_CCDFullRoi.setObjectName("pushButton_CCDFullRoi")
        self.gridLayout_CCDRoi.addWidget(self.label_CCDRowMin, 0, 0, 1, 1)
        self.gridLayout_CCDRoi.addWidget(self.spinBox_CCDRowMin, 0, 1, 1, 1)
        self.gridLayout_CCDRoi.addWidget(self.label_CCDRowMax, 0, 2, 1, 1)
        self.gridLayout_CCDRoi.addWidget(self.spinBox_CCDRowMax, 0, 3, 1, 1)
        self.gridLayout_CCDRoi.addWidget(self.pushButton_CCDSelectRoi, 1, 0, 1, 2)
        self.gridLayout_CCDRoi.addWidget(self.pushButton_CCDFullRoi, 1, 2, 1, 2)
        self.gridLayout_CCDRoi.setColumnStretch(1, 1)
        self.gridLayout_CCDRoi.setColumnStretch(3, 1)
        insert_idx = self.verticalLayout_21.count()
        if hasattr(self, "groupBox_SpectrumSmooth"):
            idx = self.verticalLayout_21.indexOf(self.groupBox_SpectrumSmooth)
            if idx >= 0:
                insert_idx = idx
        self.verticalLayout_21.insertWidget(insert_idx, self.groupBox_CCDRoi)

    def _reorder_spectrum_process_groups(self):
        if not hasattr(self, "verticalLayout_21"):
            return
        layout = self.verticalLayout_21
        ordered_widgets = []
        for name in ("groupBox_CCDRoi", "groupBox_SpectrumSmooth", "groupBox_4"):
            if hasattr(self, name):
                widget = getattr(self, name)
                layout.removeWidget(widget)
                ordered_widgets.append(widget)

        while layout.count():
            item = layout.takeAt(0)
            widget = item.widget()
            if widget is not None:
                if widget in ordered_widgets:
                    continue
                widget.setParent(self.scrollAreaWidgetContents_5)

        for widget in ordered_widgets:
            layout.addWidget(widget)
        layout.addStretch(1)

    def _setup_diff_tab(self):
        # Build Diff tab with the same container pattern used by other tabs:
        # QScrollArea -> content widget -> inner boxed panel.
        if not hasattr(self, "tabWidget_5"):
            return
        if hasattr(self, "tab_Diff"):
            return
        self.tab_Diff = QtWidgets.QWidget()
        self.tab_Diff.setObjectName("tab_Diff")
        self.verticalLayout_Diff = QtWidgets.QVBoxLayout(self.tab_Diff)
        self.verticalLayout_Diff.setObjectName("verticalLayout_Diff")
        self.verticalLayout_Diff.setContentsMargins(12, 12, 12, 12)
        self.verticalLayout_Diff.setSpacing(0)

        self.scrollArea_Diff = QtWidgets.QScrollArea(self.tab_Diff)
        self.scrollArea_Diff.setObjectName("scrollArea_Diff")
        self.scrollArea_Diff.setWidgetResizable(True)
        self.scrollArea_Diff.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.diffContents = QtWidgets.QWidget()
        self.diffContents.setObjectName("diffContents")
        self.verticalLayout_DiffContents = QtWidgets.QVBoxLayout(self.diffContents)
        self.verticalLayout_DiffContents.setObjectName("verticalLayout_DiffContents")
        self.verticalLayout_DiffContents.setContentsMargins(8, 8, 8, 8)
        self.verticalLayout_DiffContents.setSpacing(8)

        # Reference picker.
        self.groupBox_DiffRef = QtWidgets.QGroupBox("Reference", self.diffContents)
        self.groupBox_DiffRef.setObjectName("groupBox_DiffRef")
        self.gridLayout_DiffRef = QtWidgets.QGridLayout(self.groupBox_DiffRef)
        self.gridLayout_DiffRef.setObjectName("gridLayout_DiffRef")
        self.lineEdit_DiffRefChi = QtWidgets.QLineEdit(self.groupBox_DiffRef)
        self.lineEdit_DiffRefChi.setObjectName("lineEdit_DiffRefChi")
        self.pushButton_DiffRefBrowse = QtWidgets.QPushButton("Browse...", self.groupBox_DiffRef)
        self.pushButton_DiffRefBrowse.setObjectName("pushButton_DiffRefBrowse")
        self.pushButton_DiffRefBrowse.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        self.pushButton_DiffRefClear = QtWidgets.QPushButton("Clear", self.groupBox_DiffRef)
        self.pushButton_DiffRefClear.setObjectName("pushButton_DiffRefClear")
        self.pushButton_DiffRefClear.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        self.checkBox_UseDiffMode = QtWidgets.QCheckBox("Enable Diff mode", self.groupBox_DiffRef)
        self.checkBox_UseDiffMode.setObjectName("checkBox_UseDiffMode")
        self.label_DiffStatus = QtWidgets.QLabel("No reference selected", self.groupBox_DiffRef)
        self.label_DiffStatus.setObjectName("label_DiffStatus")
        self.gridLayout_DiffRef.addWidget(self.lineEdit_DiffRefChi, 0, 0, 1, 3)
        self.horizontalLayout_DiffRefButtons = QtWidgets.QHBoxLayout()
        self.horizontalLayout_DiffRefButtons.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_DiffRefButtons.setSpacing(8)
        self.horizontalLayout_DiffRefButtons.addWidget(self.pushButton_DiffRefBrowse, 1)
        self.horizontalLayout_DiffRefButtons.addWidget(self.pushButton_DiffRefClear, 1)
        self.gridLayout_DiffRef.addLayout(self.horizontalLayout_DiffRefButtons, 1, 0, 1, 3)
        self.gridLayout_DiffRef.addWidget(self.checkBox_UseDiffMode, 2, 0, 1, 1)
        self.gridLayout_DiffRef.addWidget(self.label_DiffStatus, 2, 1, 1, 2)
        self.gridLayout_DiffRef.setColumnStretch(0, 1)
        self.gridLayout_DiffRef.setColumnStretch(1, 1)
        self.gridLayout_DiffRef.setColumnStretch(2, 1)

        # 2D rendering controls for diff cake.
        self.groupBox_DiffCake = QtWidgets.QGroupBox("Diff 2D Scale", self.diffContents)
        self.groupBox_DiffCake.setObjectName("groupBox_DiffCake")
        self.gridLayout_DiffCake = QtWidgets.QGridLayout(self.groupBox_DiffCake)
        self.gridLayout_DiffCake.setObjectName("gridLayout_DiffCake")
        self.label_DiffScaleMode = QtWidgets.QLabel("Mode", self.groupBox_DiffCake)
        self.label_DiffScaleMode.setObjectName("label_DiffScaleMode")
        self.comboBox_DiffScaleMode = QtWidgets.QComboBox(self.groupBox_DiffCake)
        self.comboBox_DiffScaleMode.setObjectName("comboBox_DiffScaleMode")
        self.comboBox_DiffScaleMode.addItems([
            "0 Centered",
            "Free range",
        ])
        self.label_DiffCmap = QtWidgets.QLabel("Colormap", self.groupBox_DiffCake)
        self.label_DiffCmap.setObjectName("label_DiffCmap")
        self.comboBox_DiffCmap = QtWidgets.QComboBox(self.groupBox_DiffCake)
        self.comboBox_DiffCmap.setObjectName("comboBox_DiffCmap")
        self.label_DiffVmin = QtWidgets.QLabel("Min", self.groupBox_DiffCake)
        self.label_DiffVmin.setObjectName("label_DiffVmin")
        self.doubleSpinBox_DiffVmin = QtWidgets.QDoubleSpinBox(self.groupBox_DiffCake)
        self.doubleSpinBox_DiffVmin.setObjectName("doubleSpinBox_DiffVmin")
        self.doubleSpinBox_DiffVmin.setDecimals(0)
        self.doubleSpinBox_DiffVmin.setMinimum(-1e9)
        self.doubleSpinBox_DiffVmin.setMaximum(1e9)
        self.doubleSpinBox_DiffVmin.setSingleStep(1.0)
        self.doubleSpinBox_DiffVmin.setValue(-1000.0)
        self.label_DiffVmax = QtWidgets.QLabel("Max", self.groupBox_DiffCake)
        self.label_DiffVmax.setObjectName("label_DiffVmax")
        self.doubleSpinBox_DiffVmax = QtWidgets.QDoubleSpinBox(self.groupBox_DiffCake)
        self.doubleSpinBox_DiffVmax.setObjectName("doubleSpinBox_DiffVmax")
        self.doubleSpinBox_DiffVmax.setDecimals(0)
        self.doubleSpinBox_DiffVmax.setMinimum(-1e9)
        self.doubleSpinBox_DiffVmax.setMaximum(1e9)
        self.doubleSpinBox_DiffVmax.setSingleStep(1.0)
        self.doubleSpinBox_DiffVmax.setValue(1000.0)
        self.pushButton_DiffScaleToData = QtWidgets.QPushButton(
            "Use diff min/max", self.groupBox_DiffCake)
        self.pushButton_DiffScaleToData.setObjectName("pushButton_DiffScaleToData")
        self.gridLayout_DiffCake.addWidget(self.label_DiffScaleMode, 0, 0, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.comboBox_DiffScaleMode, 0, 1, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.label_DiffCmap, 0, 2, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.comboBox_DiffCmap, 0, 3, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.label_DiffVmin, 1, 0, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.doubleSpinBox_DiffVmin, 1, 1, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.label_DiffVmax, 1, 2, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.doubleSpinBox_DiffVmax, 1, 3, 1, 1)
        self.gridLayout_DiffCake.addWidget(self.pushButton_DiffScaleToData, 2, 0, 1, 4)

        # Export outputs only on demand.
        self.groupBox_DiffExport = QtWidgets.QGroupBox("Export", self.diffContents)
        self.groupBox_DiffExport.setObjectName("groupBox_DiffExport")
        self.horizontalLayout_DiffExport = QtWidgets.QHBoxLayout(self.groupBox_DiffExport)
        self.horizontalLayout_DiffExport.setObjectName("horizontalLayout_DiffExport")
        self.pushButton_ExportDiffChi = QtWidgets.QPushButton("Export Diff CHI", self.groupBox_DiffExport)
        self.pushButton_ExportDiffChi.setObjectName("pushButton_ExportDiffChi")
        self.pushButton_ExportDiffCakeNpy = QtWidgets.QPushButton("Export Diff Cake NPY", self.groupBox_DiffExport)
        self.pushButton_ExportDiffCakeNpy.setObjectName("pushButton_ExportDiffCakeNpy")
        self.horizontalLayout_DiffExport.addWidget(self.pushButton_ExportDiffChi)
        self.horizontalLayout_DiffExport.addWidget(self.pushButton_ExportDiffCakeNpy)

        self.verticalLayout_DiffContents.addWidget(self.groupBox_DiffRef)
        self.verticalLayout_DiffContents.addWidget(self.groupBox_DiffCake)
        self.verticalLayout_DiffContents.addWidget(self.groupBox_DiffExport)
        self.verticalLayout_DiffContents.addStretch(1)
        self.scrollArea_Diff.setWidget(self.diffContents)
        self.verticalLayout_Diff.addWidget(self.scrollArea_Diff, 1)
        self.tabWidget_5.addTab(self.tab_Diff, "Diff")
        # Diff mode toggle is shown in the top toolbar.
        self.checkBox_UseDiffMode.setVisible(False)
        self.checkBox_UseDiffMode.setChecked(False)
        self.checkBox_UseDiffMode.setEnabled(False)

    def _promote_diff_to_main_tab(self):
        if (not hasattr(self, "tab_Diff")) or (not hasattr(self, "tabWidget")):
            return
        # Remove Diff from Pattern subtabs.
        if hasattr(self, "tabWidget_5"):
            idx_sub = self.tabWidget_5.indexOf(self.tab_Diff)
            if idx_sub >= 0:
                self.tabWidget_5.removeTab(idx_sub)
        # Insert Diff as top-level tab right before Fits.
        if self.tabWidget.indexOf(self.tab_Diff) < 0:
            idx_fits = self.tabWidget.indexOf(self.tab_PkFt) \
                if hasattr(self, "tab_PkFt") else -1
            if idx_fits < 0:
                self.tabWidget.addTab(self.tab_Diff, "Diff")
            else:
                self.tabWidget.insertTab(idx_fits, self.tab_Diff, "Diff")

    def _setup_map_tab(self):
        if not hasattr(self, "tabWidget_5"):
            return
        if hasattr(self, "tab_Map"):
            return

        self.tab_Map = QtWidgets.QWidget()
        self.tab_Map.setObjectName("tab_Map")
        self.verticalLayout_Map = QtWidgets.QVBoxLayout(self.tab_Map)
        self.verticalLayout_Map.setContentsMargins(12, 12, 12, 12)
        self.verticalLayout_Map.setSpacing(8)

        self.scrollArea_Map = QtWidgets.QScrollArea(self.tab_Map)
        self.scrollArea_Map.setWidgetResizable(True)
        self.scrollArea_Map.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.scrollArea_Map.setObjectName("scrollArea_Map")

        self.mapContents = QtWidgets.QWidget()
        self.mapContents.setObjectName("mapContents")
        self.verticalLayout_MapContents = QtWidgets.QVBoxLayout(self.mapContents)
        self.verticalLayout_MapContents.setContentsMargins(8, 8, 8, 8)
        self.verticalLayout_MapContents.setSpacing(8)

        self.groupBox_MapLoad = QtWidgets.QGroupBox("Data", self.mapContents)
        self.groupBox_MapLoad.setObjectName("groupBox_MapLoad")
        self.gridLayout_MapLoad = QtWidgets.QGridLayout(self.groupBox_MapLoad)
        self.gridLayout_MapLoad.setHorizontalSpacing(12)
        self.gridLayout_MapLoad.setVerticalSpacing(8)
        self.pushButton_MapLoadChi = QtWidgets.QPushButton("Load SPE files", self.groupBox_MapLoad)
        self.pushButton_MapLoadChi.setObjectName("pushButton_MapLoadChi")
        self.pushButton_MapLoadChi.setMinimumSize(QtCore.QSize(140, 28))
        self.pushButton_MapLoadChi.setMaximumWidth(180)
        self.label_MapLoaded = QtWidgets.QLabel("Loaded: 0", self.groupBox_MapLoad)
        self.label_MapLoaded.setObjectName("label_MapLoaded")
        self.label_MapLoaded.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        self.label_MapNx = QtWidgets.QLabel("Nx", self.groupBox_MapLoad)
        self.spinBox_MapNx = QtWidgets.QSpinBox(self.groupBox_MapLoad)
        self.spinBox_MapNx.setObjectName("spinBox_MapNx")
        self.spinBox_MapNx.setMinimum(1)
        self.spinBox_MapNx.setMaximum(9999)
        self.spinBox_MapNx.setValue(1)
        self.spinBox_MapNx.setMinimumSize(QtCore.QSize(90, 28))
        self.label_MapNy = QtWidgets.QLabel("Ny", self.groupBox_MapLoad)
        self.spinBox_MapNy = QtWidgets.QSpinBox(self.groupBox_MapLoad)
        self.spinBox_MapNy.setObjectName("spinBox_MapNy")
        self.spinBox_MapNy.setMinimum(1)
        self.spinBox_MapNy.setMaximum(9999)
        self.spinBox_MapNy.setValue(1)
        self.spinBox_MapNy.setMinimumSize(QtCore.QSize(90, 28))
        self.label_MapOrder = QtWidgets.QLabel("Order", self.groupBox_MapLoad)
        self.label_MapOrder.setVisible(False)
        self.comboBox_MapOrder = QtWidgets.QComboBox(self.groupBox_MapLoad)
        self.comboBox_MapOrder.setObjectName("comboBox_MapOrder")
        self.comboBox_MapOrder.addItems(["Row-major", "Snake"])
        self.comboBox_MapOrder.setCurrentText("Snake")
        self.comboBox_MapOrder.setMinimumSize(QtCore.QSize(110, 28))
        self.comboBox_MapOrder.setMaximumWidth(150)
        self.gridLayout_MapLoad.addWidget(self.pushButton_MapLoadChi, 0, 0, 1, 1)
        self.gridLayout_MapLoad.addWidget(self.label_MapLoaded, 0, 1, 1, 1)
        self.gridLayout_MapLoad.addWidget(self.label_MapNx, 0, 2, 1, 1)
        self.gridLayout_MapLoad.addWidget(self.spinBox_MapNx, 0, 3, 1, 1)
        self.gridLayout_MapLoad.addWidget(self.comboBox_MapOrder, 1, 0, 1, 1)
        self.gridLayout_MapLoad.addWidget(self.label_MapNy, 1, 2, 1, 1)
        self.gridLayout_MapLoad.addWidget(self.spinBox_MapNy, 1, 3, 1, 1)
        self.gridLayout_MapLoad.setColumnStretch(0, 1)
        self.gridLayout_MapLoad.setColumnStretch(1, 1)
        self.gridLayout_MapLoad.setColumnStretch(2, 0)
        self.gridLayout_MapLoad.setColumnStretch(3, 0)

        self.groupBox_MapRoi = QtWidgets.QGroupBox("ROI", self.mapContents)
        self.groupBox_MapRoi.setObjectName("groupBox_MapRoi")
        self.gridLayout_MapRoi = QtWidgets.QGridLayout(self.groupBox_MapRoi)
        self.gridLayout_MapRoi.setHorizontalSpacing(8)
        self.gridLayout_MapRoi.setVerticalSpacing(8)
        self.pushButton_MapSetRoi = QtWidgets.QPushButton("Select ROI", self.groupBox_MapRoi)
        self.pushButton_MapSetRoi.setObjectName("pushButton_MapSetRoi")
        self.pushButton_MapSetRoi.setCheckable(True)
        self.pushButton_MapClearRoi = QtWidgets.QPushButton("Clear ROI", self.groupBox_MapRoi)
        self.pushButton_MapClearRoi.setObjectName("pushButton_MapClearRoi")
        self.pushButton_MapCompute = QtWidgets.QPushButton("Compute Map", self.groupBox_MapRoi)
        self.pushButton_MapCompute.setObjectName("pushButton_MapCompute")
        self.pushButton_MapSetRoi.setMinimumHeight(28)
        self.pushButton_MapClearRoi.setMinimumHeight(28)
        self.pushButton_MapCompute.setMinimumHeight(28)
        self.lineEdit_MapRoiSummary = QtWidgets.QLineEdit(self.groupBox_MapRoi)
        self.lineEdit_MapRoiSummary.setObjectName("lineEdit_MapRoiSummary")
        self.lineEdit_MapRoiSummary.setReadOnly(True)
        self.lineEdit_MapRoiSummary.setPlaceholderText("No ROI selected")
        self.gridLayout_MapRoi.addWidget(self.pushButton_MapSetRoi, 0, 0, 1, 1)
        self.gridLayout_MapRoi.addWidget(self.pushButton_MapClearRoi, 0, 1, 1, 1)
        self.gridLayout_MapRoi.addWidget(self.pushButton_MapCompute, 0, 2, 1, 1)
        self.gridLayout_MapRoi.addWidget(self.lineEdit_MapRoiSummary, 1, 0, 1, 3)
        self.gridLayout_MapRoi.setColumnStretch(0, 1)
        self.gridLayout_MapRoi.setColumnStretch(1, 1)
        self.gridLayout_MapRoi.setColumnStretch(2, 1)

        self.groupBox_MapCanvas = QtWidgets.QGroupBox("Map", self.mapContents)
        self.groupBox_MapCanvas.setObjectName("groupBox_MapCanvas")
        self.verticalLayout_MapCanvas = QtWidgets.QVBoxLayout(self.groupBox_MapCanvas)
        self.verticalLayout_MapCanvas.setObjectName("verticalLayout_MapCanvas")
        self.groupBox_MapCanvas.setMinimumHeight(320)

        self.label_MapStatus = QtWidgets.QLabel("Load SPE files to start.", self.mapContents)
        self.label_MapStatus.setObjectName("label_MapStatus")

        self.groupBox_MapScale = QtWidgets.QGroupBox("Map Colors", self.mapContents)
        self.groupBox_MapScale.setObjectName("groupBox_MapScale")
        self.gridLayout_MapScale = QtWidgets.QGridLayout(self.groupBox_MapScale)
        self.gridLayout_MapScale.setHorizontalSpacing(10)
        self.gridLayout_MapScale.setVerticalSpacing(8)
        self.label_MapCmap = QtWidgets.QLabel("Colormap", self.groupBox_MapScale)
        self.label_MapCmap.setVisible(False)
        self.comboBox_MapCmap = QtWidgets.QComboBox(self.groupBox_MapScale)
        self.comboBox_MapCmap.setObjectName("comboBox_MapCmap")
        self.comboBox_MapCmap.addItems([
            "Reds",
            "Blues",
            "Greens",
            "gray",
            "viridis",
            "magma",
            "inferno",
            "cividis",
            "turbo",
        ])
        self.comboBox_MapCmap.setCurrentText("Reds")
        self.comboBox_MapCmap.setMinimumSize(QtCore.QSize(120, 28))
        self.comboBox_MapCmap.setMaximumWidth(150)
        self.checkBox_MapReverseCmap = QtWidgets.QCheckBox("Reverse", self.groupBox_MapScale)
        self.checkBox_MapReverseCmap.setObjectName("checkBox_MapReverseCmap")
        self.checkBox_MapLog = QtWidgets.QCheckBox("Log scale", self.groupBox_MapScale)
        self.checkBox_MapLog.setObjectName("checkBox_MapLog")
        self.label_MapVmin = QtWidgets.QLabel("Min", self.groupBox_MapScale)
        self.doubleSpinBox_MapVmin = QtWidgets.QDoubleSpinBox(self.groupBox_MapScale)
        self.doubleSpinBox_MapVmin.setObjectName("doubleSpinBox_MapVmin")
        self.doubleSpinBox_MapVmin.setDecimals(6)
        self.doubleSpinBox_MapVmin.setMinimum(-1e12)
        self.doubleSpinBox_MapVmin.setMaximum(1e12)
        self.doubleSpinBox_MapVmin.setMinimumSize(QtCore.QSize(100, 28))
        self.label_MapVmax = QtWidgets.QLabel("Max", self.groupBox_MapScale)
        self.doubleSpinBox_MapVmax = QtWidgets.QDoubleSpinBox(self.groupBox_MapScale)
        self.doubleSpinBox_MapVmax.setObjectName("doubleSpinBox_MapVmax")
        self.doubleSpinBox_MapVmax.setDecimals(6)
        self.doubleSpinBox_MapVmax.setMinimum(-1e12)
        self.doubleSpinBox_MapVmax.setMaximum(1e12)
        self.doubleSpinBox_MapVmax.setMinimumSize(QtCore.QSize(100, 28))
        self.pushButton_MapScaleAuto = QtWidgets.QPushButton("Auto", self.groupBox_MapScale)
        self.pushButton_MapScaleAuto.setObjectName("pushButton_MapScaleAuto")
        self.label_MapPct = QtWidgets.QLabel("Low %", self.groupBox_MapScale)
        self.doubleSpinBox_MapPctLow = QtWidgets.QDoubleSpinBox(self.groupBox_MapScale)
        self.doubleSpinBox_MapPctLow.setObjectName("doubleSpinBox_MapPctLow")
        self.doubleSpinBox_MapPctLow.setDecimals(2)
        self.doubleSpinBox_MapPctLow.setRange(0.0, 100.0)
        self.doubleSpinBox_MapPctLow.setValue(1.0)
        self.doubleSpinBox_MapPctLow.setMinimumSize(QtCore.QSize(100, 28))
        self.label_MapPctHigh = QtWidgets.QLabel("High %", self.groupBox_MapScale)
        self.label_MapPctHigh.setObjectName("label_MapPctHigh")
        self.doubleSpinBox_MapPctHigh = QtWidgets.QDoubleSpinBox(self.groupBox_MapScale)
        self.doubleSpinBox_MapPctHigh.setObjectName("doubleSpinBox_MapPctHigh")
        self.doubleSpinBox_MapPctHigh.setDecimals(2)
        self.doubleSpinBox_MapPctHigh.setRange(0.0, 100.0)
        self.doubleSpinBox_MapPctHigh.setValue(99.0)
        self.doubleSpinBox_MapPctHigh.setMinimumSize(QtCore.QSize(100, 28))
        self.pushButton_MapScalePercentile = QtWidgets.QPushButton("Apply %", self.groupBox_MapScale)
        self.pushButton_MapScalePercentile.setObjectName("pushButton_MapScalePercentile")
        self.pushButton_MapScaleReset = QtWidgets.QPushButton("Reset", self.groupBox_MapScale)
        self.pushButton_MapScaleReset.setObjectName("pushButton_MapScaleReset")
        self.pushButton_MapScalePercentile.setMinimumHeight(28)
        self.pushButton_MapScaleAuto.setMinimumHeight(28)
        self.pushButton_MapScaleReset.setMinimumHeight(28)
        self.gridLayout_MapScale.addWidget(self.comboBox_MapCmap, 0, 0, 1, 2)
        self.gridLayout_MapScale.addWidget(self.checkBox_MapReverseCmap, 1, 0, 1, 2)
        self.gridLayout_MapScale.addWidget(self.checkBox_MapLog, 2, 0, 1, 2)
        self.gridLayout_MapScale.addWidget(self.label_MapVmin, 0, 2, 1, 1)
        self.gridLayout_MapScale.addWidget(self.doubleSpinBox_MapVmin, 0, 3, 1, 1)
        self.gridLayout_MapScale.addWidget(self.pushButton_MapScalePercentile, 0, 4, 1, 1)
        self.gridLayout_MapScale.addWidget(self.label_MapVmax, 1, 2, 1, 1)
        self.gridLayout_MapScale.addWidget(self.doubleSpinBox_MapVmax, 1, 3, 1, 1)
        self.gridLayout_MapScale.addWidget(self.pushButton_MapScaleAuto, 1, 4, 1, 1)
        self.gridLayout_MapScale.addWidget(self.label_MapPct, 2, 2, 1, 1)
        self.gridLayout_MapScale.addWidget(self.doubleSpinBox_MapPctLow, 2, 3, 1, 1)
        self.gridLayout_MapScale.addWidget(self.pushButton_MapScaleReset, 2, 4, 1, 1)
        self.gridLayout_MapScale.addWidget(self.label_MapPctHigh, 3, 2, 1, 1)
        self.gridLayout_MapScale.addWidget(self.doubleSpinBox_MapPctHigh, 3, 3, 1, 1)
        self.gridLayout_MapScale.setColumnStretch(0, 1)
        self.gridLayout_MapScale.setColumnStretch(1, 1)
        self.gridLayout_MapScale.setColumnStretch(2, 0)
        self.gridLayout_MapScale.setColumnStretch(3, 0)
        self.gridLayout_MapScale.setColumnStretch(4, 0)

        self.groupBox_MapExport = QtWidgets.QGroupBox("Export", self.mapContents)
        self.groupBox_MapExport.setObjectName("groupBox_MapExport")
        self.horizontalLayout_MapExport = QtWidgets.QHBoxLayout(self.groupBox_MapExport)
        self.horizontalLayout_MapExport.setSpacing(8)
        self.pushButton_MapExportImage = QtWidgets.QPushButton("Export Image", self.groupBox_MapExport)
        self.pushButton_MapExportImage.setObjectName("pushButton_MapExportImage")
        self.pushButton_MapExportNpy = QtWidgets.QPushButton("Export NPY", self.groupBox_MapExport)
        self.pushButton_MapExportNpy.setObjectName("pushButton_MapExportNpy")
        self.horizontalLayout_MapExport.addWidget(self.pushButton_MapExportImage)
        self.horizontalLayout_MapExport.addWidget(self.pushButton_MapExportNpy)
        self.pushButton_MapExportImage.setMinimumHeight(28)
        self.pushButton_MapExportNpy.setMinimumHeight(28)

        self.verticalLayout_MapContents.addWidget(self.groupBox_MapLoad)
        self.verticalLayout_MapContents.addWidget(self.groupBox_MapRoi)
        self.verticalLayout_MapContents.addWidget(self.groupBox_MapCanvas, 1)
        self.verticalLayout_MapContents.addWidget(self.label_MapStatus)
        self.verticalLayout_MapContents.addWidget(self.groupBox_MapScale)
        self.verticalLayout_MapContents.addWidget(self.groupBox_MapExport)
        self.verticalLayout_MapContents.addStretch(1)
        self.scrollArea_Map.setWidget(self.mapContents)
        self.verticalLayout_Map.addWidget(self.scrollArea_Map, 1)

        self.tabWidget_5.addTab(self.tab_Map, "Map")

    def _promote_map_to_main_tab(self):
        if (not hasattr(self, "tab_Map")) or (not hasattr(self, "tabWidget")):
            return
        if hasattr(self, "tabWidget_5"):
            idx_sub = self.tabWidget_5.indexOf(self.tab_Map)
            if idx_sub >= 0:
                self.tabWidget_5.removeTab(idx_sub)
        if self.tabWidget.indexOf(self.tab_Map) < 0:
            idx_fits = self.tabWidget.indexOf(self.tab_PkFt) \
                if hasattr(self, "tab_PkFt") else -1
            if idx_fits < 0:
                self.tabWidget.addTab(self.tab_Map, "Map")
            else:
                self.tabWidget.insertTab(idx_fits, self.tab_Map, "Map")

    def _setup_seq_tab(self):
        if not hasattr(self, "tabWidget_5"):
            return
        if hasattr(self, "tab_Seq"):
            return

        self.tab_Seq = QtWidgets.QWidget()
        self.tab_Seq.setObjectName("tab_Seq")
        self.verticalLayout_Seq = QtWidgets.QVBoxLayout(self.tab_Seq)
        self.verticalLayout_Seq.setContentsMargins(12, 12, 12, 12)
        self.verticalLayout_Seq.setSpacing(8)

        self.scrollArea_Seq = QtWidgets.QScrollArea(self.tab_Seq)
        self.scrollArea_Seq.setWidgetResizable(True)
        self.scrollArea_Seq.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.scrollArea_Seq.setObjectName("scrollArea_Seq")

        self.seqContents = QtWidgets.QWidget()
        self.seqContents.setObjectName("seqContents")
        self.verticalLayout_SeqContents = QtWidgets.QVBoxLayout(self.seqContents)
        self.verticalLayout_SeqContents.setContentsMargins(8, 8, 8, 8)
        self.verticalLayout_SeqContents.setSpacing(8)

        self.groupBox_SeqLoad = QtWidgets.QGroupBox("Data", self.seqContents)
        self.groupBox_SeqLoad.setObjectName("groupBox_SeqLoad")
        self.gridLayout_SeqLoad = QtWidgets.QGridLayout(self.groupBox_SeqLoad)
        self.gridLayout_SeqLoad.setHorizontalSpacing(12)
        self.gridLayout_SeqLoad.setVerticalSpacing(8)
        self.pushButton_SeqLoadChi = QtWidgets.QPushButton("Load SPE files", self.groupBox_SeqLoad)
        self.pushButton_SeqLoadChi.setObjectName("pushButton_SeqLoadChi")
        self.pushButton_SeqLoadChi.setMinimumSize(QtCore.QSize(140, 28))
        self.pushButton_SeqLoadChi.setMaximumWidth(180)
        self.label_SeqLoaded = QtWidgets.QLabel("Loaded: 0", self.groupBox_SeqLoad)
        self.label_SeqLoaded.setObjectName("label_SeqLoaded")
        self.label_SeqLoaded.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        self.gridLayout_SeqLoad.addWidget(self.pushButton_SeqLoadChi, 0, 0, 1, 1)
        self.gridLayout_SeqLoad.addWidget(self.label_SeqLoaded, 0, 1, 1, 1)
        self.gridLayout_SeqLoad.setColumnStretch(0, 1)
        self.gridLayout_SeqLoad.setColumnStretch(1, 1)

        self.groupBox_SeqRoi = QtWidgets.QGroupBox("ROI", self.seqContents)
        self.groupBox_SeqRoi.setObjectName("groupBox_SeqRoi")
        self.gridLayout_SeqRoi = QtWidgets.QGridLayout(self.groupBox_SeqRoi)
        self.gridLayout_SeqRoi.setHorizontalSpacing(8)
        self.gridLayout_SeqRoi.setVerticalSpacing(8)
        self.pushButton_SeqSetRoi = QtWidgets.QPushButton("Select ROI", self.groupBox_SeqRoi)
        self.pushButton_SeqSetRoi.setObjectName("pushButton_SeqSetRoi")
        self.pushButton_SeqSetRoi.setCheckable(True)
        self.pushButton_SeqClearRoi = QtWidgets.QPushButton("Clear ROI", self.groupBox_SeqRoi)
        self.pushButton_SeqClearRoi.setObjectName("pushButton_SeqClearRoi")
        self.pushButton_SeqCompute = QtWidgets.QPushButton("Compute Seq.", self.groupBox_SeqRoi)
        self.pushButton_SeqCompute.setObjectName("pushButton_SeqCompute")
        self.pushButton_SeqSetRoi.setMinimumHeight(28)
        self.pushButton_SeqClearRoi.setMinimumHeight(28)
        self.pushButton_SeqCompute.setMinimumHeight(28)
        self.lineEdit_SeqRoiSummary = QtWidgets.QLineEdit(self.groupBox_SeqRoi)
        self.lineEdit_SeqRoiSummary.setObjectName("lineEdit_SeqRoiSummary")
        self.lineEdit_SeqRoiSummary.setReadOnly(True)
        self.lineEdit_SeqRoiSummary.setPlaceholderText("No ROI selected")
        self.gridLayout_SeqRoi.addWidget(self.pushButton_SeqSetRoi, 0, 0, 1, 1)
        self.gridLayout_SeqRoi.addWidget(self.pushButton_SeqClearRoi, 0, 1, 1, 1)
        self.gridLayout_SeqRoi.addWidget(self.pushButton_SeqCompute, 0, 2, 1, 1)
        self.gridLayout_SeqRoi.addWidget(self.lineEdit_SeqRoiSummary, 1, 0, 1, 3)
        self.gridLayout_SeqRoi.setColumnStretch(0, 1)
        self.gridLayout_SeqRoi.setColumnStretch(1, 1)
        self.gridLayout_SeqRoi.setColumnStretch(2, 1)

        self.groupBox_SeqCanvas = QtWidgets.QGroupBox("Sequence", self.seqContents)
        self.groupBox_SeqCanvas.setObjectName("groupBox_SeqCanvas")
        self.verticalLayout_SeqCanvas = QtWidgets.QVBoxLayout(self.groupBox_SeqCanvas)
        self.verticalLayout_SeqCanvas.setObjectName("verticalLayout_SeqCanvas")
        self.groupBox_SeqCanvas.setMinimumHeight(320)

        self.label_SeqStatus = QtWidgets.QLabel("Load SPE files to start.", self.seqContents)
        self.label_SeqStatus.setObjectName("label_SeqStatus")

        self.groupBox_SeqExport = QtWidgets.QGroupBox("Export", self.seqContents)
        self.groupBox_SeqExport.setObjectName("groupBox_SeqExport")
        self.horizontalLayout_SeqExport = QtWidgets.QHBoxLayout(self.groupBox_SeqExport)
        self.horizontalLayout_SeqExport.setSpacing(8)
        self.pushButton_SeqExportImage = QtWidgets.QPushButton("Export Image", self.groupBox_SeqExport)
        self.pushButton_SeqExportImage.setObjectName("pushButton_SeqExportImage")
        self.pushButton_SeqExportNpy = QtWidgets.QPushButton("Export NPY", self.groupBox_SeqExport)
        self.pushButton_SeqExportNpy.setObjectName("pushButton_SeqExportNpy")
        self.horizontalLayout_SeqExport.addWidget(self.pushButton_SeqExportImage)
        self.horizontalLayout_SeqExport.addWidget(self.pushButton_SeqExportNpy)
        self.pushButton_SeqExportImage.setMinimumHeight(28)
        self.pushButton_SeqExportNpy.setMinimumHeight(28)

        self.verticalLayout_SeqContents.addWidget(self.groupBox_SeqLoad)
        self.verticalLayout_SeqContents.addWidget(self.groupBox_SeqRoi)
        self.verticalLayout_SeqContents.addWidget(self.groupBox_SeqCanvas, 1)
        self.verticalLayout_SeqContents.addWidget(self.label_SeqStatus)
        self.verticalLayout_SeqContents.addWidget(self.groupBox_SeqExport)
        self.verticalLayout_SeqContents.addStretch(1)
        self.scrollArea_Seq.setWidget(self.seqContents)
        self.verticalLayout_Seq.addWidget(self.scrollArea_Seq, 1)

        self.tabWidget_5.addTab(self.tab_Seq, "Seq")

    def _promote_seq_to_main_tab(self):
        if (not hasattr(self, "tab_Seq")) or (not hasattr(self, "tabWidget")):
            return
        if hasattr(self, "tabWidget_5"):
            idx_sub = self.tabWidget_5.indexOf(self.tab_Seq)
            if idx_sub >= 0:
                self.tabWidget_5.removeTab(idx_sub)
        if self.tabWidget.indexOf(self.tab_Seq) < 0:
            idx_fits = self.tabWidget.indexOf(self.tab_PkFt) \
                if hasattr(self, "tab_PkFt") else -1
            if idx_fits < 0:
                self.tabWidget.addTab(self.tab_Seq, "Seq")
            else:
                self.tabWidget.insertTab(idx_fits, self.tab_Seq, "Seq")

    def _reorder_main_tabs_and_fit_tab_names(self):
        if hasattr(self, "tabWidget") and hasattr(self, "tab_PkFt"):
            idx_pk = self.tabWidget.indexOf(self.tab_PkFt)
            if idx_pk >= 0:
                self.tabWidget.setTabText(idx_pk, "PeakFit")

        if not hasattr(self, "tabWidget"):
            return
        # Place Spectrum workflow tabs in the requested order.
        desired = []
        for name in (
            "tab_Main",      # File
            "tab_Plot",      # Plot
            "tab_Bkgn",      # Pattern
            "tab_Map",       # Map
            "tab_Diff",      # Diff
            "tab_Seq",       # Seq
            "tab_PkFt",      # PeakFit
        ):
            if hasattr(self, name):
                desired.append(getattr(self, name))
        for target_idx, tab in enumerate(desired):
            cur_idx = self.tabWidget.indexOf(tab)
            if cur_idx < 0 or cur_idx == target_idx:
                continue
            self.tabWidget.tabBar().moveTab(cur_idx, target_idx)

    def _setup_toolbar_diff_toggle(self):
        if (not hasattr(self, "horizontalLayout_7")) or hasattr(self, "checkBox_Diff"):
            return
        self.checkBox_ShowBg = QtWidgets.QCheckBox(self.frame_2)
        self.checkBox_ShowBg.setObjectName("checkBox_ShowBg")
        self.checkBox_ShowBg.setMinimumSize(QtCore.QSize(0, 25))
        self.checkBox_ShowBg.setChecked(False)
        self.checkBox_ShowBg.setText("Bg show")
        self.checkBox_ShowBg.setToolTip("Show fitted background")
        idx_bgsub = self.horizontalLayout_7.indexOf(self.checkBox_BgSub)
        if idx_bgsub >= 0:
            self.horizontalLayout_7.insertWidget(idx_bgsub + 1, self.checkBox_ShowBg)
        else:
            self.horizontalLayout_7.addWidget(self.checkBox_ShowBg)
        self.checkBox_Diff = QtWidgets.QCheckBox(self.frame_2)
        self.checkBox_Diff.setObjectName("checkBox_Diff")
        self.checkBox_Diff.setMinimumSize(QtCore.QSize(0, 25))
        self.checkBox_Diff.setChecked(False)
        self.checkBox_Diff.setEnabled(False)
        self.checkBox_Diff.setText("Diff")
        self.checkBox_Diff.setToolTip("Enable Diff mode")
        idx_showbg = self.horizontalLayout_7.indexOf(self.checkBox_ShowBg)
        if idx_showbg >= 0:
            self.horizontalLayout_7.insertWidget(idx_showbg + 1, self.checkBox_Diff)
        else:
            self.horizontalLayout_7.addWidget(self.checkBox_Diff)

    def _spread_top_toolbar_even(self):
        if not hasattr(self, "horizontalLayout_7"):
            return
        layout = self.horizontalLayout_7
        for i in range(layout.count()):
            item = layout.itemAt(i)
            w = item.widget()
            if w is None:
                layout.setStretch(i, 0)
                continue
            w.setSizePolicy(
                QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
            w.setMinimumWidth(0)
            w.setMaximumWidth(16777215)
            layout.setStretch(i, 1)

    def _setup_plot_config_python_export(self):
        if not hasattr(self, "verticalLayout_PlotConfig"):
            return
        if hasattr(self, "pushButton_ExportPythonView"):
            return
        self.groupBox_PythonExport = QtWidgets.QGroupBox("Python Export", self.plotConfigContents)
        self.groupBox_PythonExport.setObjectName("groupBox_PythonExport")
        self.horizontalLayout_PythonExport = QtWidgets.QHBoxLayout(self.groupBox_PythonExport)
        self.horizontalLayout_PythonExport.setContentsMargins(12, 12, 12, 12)
        self.horizontalLayout_PythonExport.setObjectName("horizontalLayout_PythonExport")
        self.pushButton_ExportPythonView = QtWidgets.QPushButton(self.groupBox_PythonExport)
        self.pushButton_ExportPythonView.setObjectName("pushButton_ExportPythonView")
        self.pushButton_ExportPythonView.setMinimumSize(QtCore.QSize(0, 25))
        self.pushButton_ExportPythonView.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self.pushButton_ExportPythonView.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        self.pushButton_ExportPythonView.setText("Export Python (Current View)")
        self.pushButton_ExportPythonView.setToolTip(
            "Export current on-screen view as a Python reproducible package")
        self.horizontalLayout_PythonExport.addWidget(self.pushButton_ExportPythonView, 1)
        # place at the top in Plot > Config
        self.verticalLayout_PlotConfig.insertWidget(0, self.groupBox_PythonExport)

    def _setup_backup_comment_button(self):
        if (not hasattr(self, "horizontalLayout_BackupTools")) or \
                hasattr(self, "pushButton_BackupEditComment"):
            return
        self.pushButton_BackupEditComment = QtWidgets.QPushButton(self.frame_BackupTools)
        self.pushButton_BackupEditComment.setObjectName("pushButton_BackupEditComment")
        self.pushButton_BackupEditComment.setMinimumSize(QtCore.QSize(95, 25))
        self.pushButton_BackupEditComment.setMaximumSize(QtCore.QSize(140, 16777215))
        self.pushButton_BackupEditComment.setText("Edit Comment")
        self.pushButton_BackupEditComment.setToolTip("Edit comment for selected backup row")
        idx_restore = self.horizontalLayout_BackupTools.indexOf(self.pushButton_BackupRestore)
        if idx_restore < 0:
            self.horizontalLayout_BackupTools.addWidget(self.pushButton_BackupEditComment)
        else:
            self.horizontalLayout_BackupTools.insertWidget(
                idx_restore, self.pushButton_BackupEditComment)

    def _move_backup_into_file_data_tab(self):
        # Move backup table/tools under File > Data, below Raw image handling.
        if not hasattr(self, "tabWidget_3"):
            return
        if not hasattr(self, "tabWidget_3Page1"):
            return
        if not hasattr(self, "tabWidget_3Page3"):
            return
        if not hasattr(self, "verticalLayout_2"):
            return
        if not hasattr(self, "frame_BackupTools"):
            return
        if not hasattr(self, "tableWidget_BackupInfo"):
            return
        if hasattr(self, "groupBox_FileDataBackup"):
            # Already moved/rebuilt.
            idx_backup = self.tabWidget_3.indexOf(self.tabWidget_3Page3)
            if idx_backup >= 0:
                self.tabWidget_3.removeTab(idx_backup)
            return

        self.groupBox_FileDataBackup = QtWidgets.QGroupBox(
            "Backup", self.scrollAreaWidgetContents)
        self.groupBox_FileDataBackup.setObjectName("groupBox_FileDataBackup")
        self.groupBox_FileDataBackup.setSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout_FileDataBackup = QtWidgets.QVBoxLayout(
            self.groupBox_FileDataBackup)
        self.verticalLayout_FileDataBackup.setContentsMargins(12, 12, 12, 12)
        self.verticalLayout_FileDataBackup.setSpacing(8)

        self.frame_BackupTools.setParent(self.groupBox_FileDataBackup)
        self.tableWidget_BackupInfo.setParent(self.groupBox_FileDataBackup)
        self.verticalLayout_FileDataBackup.addWidget(self.frame_BackupTools)
        self.verticalLayout_FileDataBackup.addWidget(self.tableWidget_BackupInfo, 1)

        self.verticalLayout_2.addWidget(self.groupBox_FileDataBackup)

        idx_backup = self.tabWidget_3.indexOf(self.tabWidget_3Page3)
        if idx_backup >= 0:
            self.tabWidget_3.removeTab(idx_backup)

    def _layout_backup_buttons(self):
        if not hasattr(self, "horizontalLayout_BackupTools"):
            return
        if (not hasattr(self, "pushButton_BackupEditComment")) or \
                (not hasattr(self, "pushButton_BackupRestore")):
            return
        self.pushButton_BackupRestore.setText("Restore")
        common_h = 30
        for btn in (self.pushButton_BackupEditComment, self.pushButton_BackupRestore):
            btn.setMinimumHeight(common_h)
            btn.setMaximumHeight(common_h)
            btn.setMinimumWidth(0)
            btn.setMaximumWidth(16777215)
            btn.setSizePolicy(
                QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)

        # Rebuild row to remove spacer and spread both buttons horizontally.
        while self.horizontalLayout_BackupTools.count():
            item = self.horizontalLayout_BackupTools.takeAt(0)
            w = item.widget()
            if w is not None:
                w.setParent(None)
        self.horizontalLayout_BackupTools.addWidget(self.pushButton_BackupEditComment, 1)
        self.horizontalLayout_BackupTools.addWidget(self.pushButton_BackupRestore, 1)

    def closeEvent(self, event):
        try:
            if hasattr(self, 'mpl') and hasattr(self.mpl, 'shutdown'):
                self.mpl.shutdown()
        except Exception:
            pass
        super().closeEvent(event)

    def connect_channel(self):
        self.pushButton_RoomT.clicked.connect(
            lambda: self.set_temperature(300))
        self.pushButton_1bar.clicked.connect(
            lambda: self.set_pressure(0.0))
        self.pushButton_SetPStepTo1.clicked.connect(
            lambda: self.set_q_pstep(1.))
        self.pushButton_SetPStepTo10.clicked.connect(
            lambda: self.set_q_pstep(10.))
        self.pushButton_SetTStepTo100.clicked.connect(
            lambda: self.set_q_tstep(100))
        self.pushButton_SetTStepTo1000.clicked.connect(
            lambda: self.set_q_tstep(1000))
        self.doubleSpinBox_PStep.valueChanged.connect(self.set_pstep)
        self.spinBox_TStep.valueChanged.connect(self.set_tstep)
        self.pushButton_SetJCPDSStepTo0001.clicked.connect(
            lambda: self.set_jstep(0.001))
        self.pushButton_SetJCPDSStepTo001.clicked.connect(
            lambda: self.set_jstep(0.01))
        self.pushButton_SetJCPDSStepTo01.clicked.connect(
            lambda: self.set_jstep(0.1))
        self.pushButton_AboutPeakpo.clicked.connect(self.about)
        self.pushButton_Help.clicked.connect(self.shortcutkeys)

    def set_jstep(self, value):
        self.doubleSpinBox_JCPDSStep.setValue(value)

    def set_pstep(self):
        self.doubleSpinBox_Pressure.setSingleStep(
            self.doubleSpinBox_PStep.value())

    def set_tstep(self):
        self.doubleSpinBox_Temperature.setSingleStep(
            self.spinBox_TStep.value())

    def set_temperature(self, temperature):
        self.doubleSpinBox_Temperature.setValue(temperature)

    def set_pressure(self, pressure):
        self.doubleSpinBox_Pressure.setValue(pressure)

    def set_q_pstep(self, value=1.):
        self.doubleSpinBox_PStep.setValue(value)

    def set_q_tstep(self, value=1000.):
        self.spinBox_TStep.setValue(value)

    def _update_pt_spinbox_colors(self):
        p = float(self.doubleSpinBox_Pressure.value())
        t = float(self.doubleSpinBox_Temperature.value())
        self._set_pt_spinbox_style(
            self.doubleSpinBox_Pressure,
            alert=(p > 0.0),
            alert_bg="#1565c0")   # blue
        self._set_pt_spinbox_style(
            self.doubleSpinBox_Temperature,
            alert=(t > 300.0),
            alert_bg="#c62828")   # red

    def _set_pt_spinbox_style(self, box, alert=False, alert_bg="#2b2b2b"):
        bg = alert_bg if alert else "#2b2b2b"
        box.setStyleSheet(
            "QDoubleSpinBox {"
            "font: 75 24pt \"Helvetica\";"
            "color: #f0f0f0;"
            "background-color: " + bg + ";"
            "}"
        )

    def _apply_rampo_labels(self):
        self.setWindowTitle("Rampo ver. " + str(__version__))
        if hasattr(self, "checkBox_ShowCake"):
            self.checkBox_ShowCake.setText("CCD")
            self.checkBox_ShowCake.setToolTip("Show CCD image")
        if hasattr(self, "tabWidget") and hasattr(self, "tab_JCPDSList2"):
            idx = self.tabWidget.indexOf(self.tab_JCPDSList2)
            if idx >= 0:
                self.tabWidget.setTabText(idx, "RAPO")
        if hasattr(self, "tabWidget") and hasattr(self, "tab_Cake1"):
            idx = self.tabWidget.indexOf(self.tab_Cake1)
            if idx >= 0:
                self.tabWidget.setTabText(idx, "CCD")
        if hasattr(self, "pushButton_NewJlist"):
            self.pushButton_NewJlist.setText("+ RAPO")
        if hasattr(self, "pushButton_AddToJlist"):
            self.pushButton_AddToJlist.setToolTip("Add a RAPO file to the list")
        if hasattr(self, "pushButton_ViewJCPDS"):
            self.pushButton_ViewJCPDS.setToolTip("Show calculation result for highlighted RAPO entry")
        if hasattr(self, "pushButton_SaveTwkJCPDS"):
            self.pushButton_SaveTwkJCPDS.setToolTip("Save tweaked RAPO/JCPDS entry")
        if hasattr(self, "pushButton_RemoveJCPDS"):
            self.pushButton_RemoveJCPDS.setToolTip("Remove highlighted RAPO entry from the list")
        if hasattr(self, "groupBox_18"):
            self.groupBox_18.setTitle("RAPO list source")
        if hasattr(self, "groupBox_20"):
            self.groupBox_20.setTitle("RAPO bars")
            self.groupBox_20.setEnabled(False)
            self.groupBox_20.setToolTip("Reserved for a future Rampo update.")
        if hasattr(self, "groupBox_34"):
            self.groupBox_34.setEnabled(False)
            self.groupBox_34.setToolTip("Reserved for a future Rampo update.")
        if hasattr(self, "groupBox_27"):
            self.groupBox_27.setTitle("RAPO Opacity")
            self.groupBox_27.setEnabled(False)
            self.groupBox_27.setToolTip("Reserved for a future Rampo update.")
        if hasattr(self, "groupBox_13"):
            self.groupBox_13.setEnabled(False)
            self.groupBox_13.setToolTip("Reserved for a future Rampo update.")
        if hasattr(self, "textEdit_DiffractionImageFilename"):
            self.textEdit_DiffractionImageFilename.setToolTip(
                "CCD image source. For SPE input the source file is the spectrum itself.")
        if hasattr(self, "pushButton_Info"):
            self.pushButton_Info.setVisible(False)
        if hasattr(self, "pushButton_NewBasePtn"):
            self.pushButton_NewBasePtn.setText("Open SPE")
            self.pushButton_NewBasePtn.setToolTip("Open SPE or CHI spectrum file")
        if hasattr(self, "groupBox_3"):
            self.groupBox_3.setTitle("Spectrum")
        if hasattr(self, "tabWidget") and hasattr(self, "tab_Bkgn"):
            idx = self.tabWidget.indexOf(self.tab_Bkgn)
            if idx >= 0:
                self.tabWidget.setTabText(idx, "Spectrum")
        if hasattr(self, "tabWidget_5") and hasattr(self, "tabWidget_5Page1"):
            idx = self.tabWidget_5.indexOf(self.tabWidget_5Page1)
            if idx >= 0:
                self.tabWidget_5.setTabText(idx, "Process")
        if hasattr(self, "doubleSpinBox_SetWavelength"):
            self.doubleSpinBox_SetWavelength.setDecimals(2)
            self.doubleSpinBox_SetWavelength.setSingleStep(0.01)
            self.doubleSpinBox_SetWavelength.setMinimum(1.0)
            self.doubleSpinBox_SetWavelength.setMaximum(2000.0)
            self.doubleSpinBox_SetWavelength.setValue(532.22)
            self.doubleSpinBox_SetWavelength.setToolTip("Excitation laser wavelength (nm)")
        if hasattr(self, "horizontalLayout_11"):
            self.horizontalLayout_11.setContentsMargins(9, 9, 9, 9)
            self.horizontalLayout_11.setSpacing(4)
            if not hasattr(self, "label_LaserWavelength"):
                self.label_LaserWavelength = QtWidgets.QLabel("Laser wavelength", self.frame_4)
                self.label_LaserWavelength.setObjectName("label_LaserWavelength")
                self.horizontalLayout_11.insertWidget(0, self.label_LaserWavelength)
            self.horizontalLayout_11.setAlignment(
                self.label_LaserWavelength, QtCore.Qt.AlignVCenter)
            self.horizontalLayout_11.setAlignment(
                self.doubleSpinBox_SetWavelength, QtCore.Qt.AlignVCenter)
            if hasattr(self, "label_XRayEnergy"):
                self.horizontalLayout_11.setAlignment(
                    self.label_XRayEnergy, QtCore.Qt.AlignVCenter)
        if hasattr(self, "label_XRayEnergy"):
            self.label_XRayEnergy.setText("nm")
            self.label_XRayEnergy.show()
        for name in ("pushButton_SetXEat30", "pushButton_SetXEat37", "pushButton_SetXEat42"):
            if hasattr(self, name):
                getattr(self, name).setVisible(False)
        if hasattr(self, "pushButton_UpdateBackground"):
            self.pushButton_UpdateBackground.setVisible(False)
        if hasattr(self, "pushButton_ResetCakeScale"):
            self.pushButton_ResetCakeScale.setToolTip("Reset CCD contrast scale")
        if hasattr(self, "pushButton_AddRemoveFromMouse"):
            self.pushButton_AddRemoveFromMouse.setText("Pick peaks")
            self.pushButton_AddRemoveFromMouse.setToolTip(
                "Left click to add a peak, left drag to set peak width, right click to remove the nearest peak")
        if hasattr(self, "doubleSpinBox_InitialFWHM"):
            self.doubleSpinBox_InitialFWHM.setValue(5.0)
            self.doubleSpinBox_InitialFWHM.hide()
        if hasattr(self, "label_13"):
            self.label_13.hide()
        if hasattr(self, "pushButton_AboutPeakpo"):
            self.pushButton_AboutPeakpo.setText("About Rampo")
        if hasattr(self, "actionCiting_PeakPo"):
            self.actionCiting_PeakPo.setText("Citing Rampo")
        if hasattr(self, "mplactionOpen"):
            self.mplactionOpen.setText("Open SPE file")
        if hasattr(self, "label_XRayEnergy"):
            self.label_XRayEnergy.show()

    def _disable_unit_cell_fit(self):
        if hasattr(self, "tabWidget_4") and hasattr(self, "tabWidget_4Page2"):
            idx = self.tabWidget_4.indexOf(self.tabWidget_4Page2)
            if idx >= 0:
                self.tabWidget_4.removeTab(idx)
        if hasattr(self, "tabWidget_4") and hasattr(self, "tabWidget_PeakFit") and hasattr(self, "verticalLayout_14"):
            self.tabWidget_PeakFit.setParent(self.tab_PkFt)
            self.verticalLayout_14.addWidget(self.tabWidget_PeakFit)
            self.tabWidget_4.setVisible(False)
        for name in (
            "pushButton_AddRemoveFromJlist",
            "spinBox_PeaksFromJlistIntensity",
            "label_16",
            "pushButton_PlotSelectedPkFtResults",
        ):
            if hasattr(self, name):
                getattr(self, name).setVisible(False)

    def _disable_ccd_tab(self):
        if hasattr(self, "tabWidget") and hasattr(self, "tab_Cake1"):
            idx = self.tabWidget.indexOf(self.tab_Cake1)
            if idx >= 0:
                self.tabWidget.removeTab(idx)

    def _disable_rapo_tab(self):
        if hasattr(self, "tabWidget") and hasattr(self, "tab_JCPDSList2"):
            idx = self.tabWidget.indexOf(self.tab_JCPDSList2)
            if idx >= 0:
                self.tabWidget.removeTab(idx)

    def _setup_spectrum_smoothing_control(self):
        if not hasattr(self, "verticalLayout_21") or hasattr(self, "groupBox_SpectrumSmooth"):
            return
        self.groupBox_SpectrumSmooth = QtWidgets.QGroupBox("Smoothing", self.scrollAreaWidgetContents_5)
        self.groupBox_SpectrumSmooth.setObjectName("groupBox_SpectrumSmooth")
        self.formLayout_SpectrumSmooth = QtWidgets.QFormLayout(self.groupBox_SpectrumSmooth)
        self.formLayout_SpectrumSmooth.setContentsMargins(12, 12, 12, 12)
        self.formLayout_SpectrumSmooth.setHorizontalSpacing(16)
        self.formLayout_SpectrumSmooth.setVerticalSpacing(10)

        self.label_SpectrumDespike = QtWidgets.QLabel("Despike kernel", self.groupBox_SpectrumSmooth)
        self.spinBox_SpectrumDespike = QtWidgets.QSpinBox(self.groupBox_SpectrumSmooth)
        self.spinBox_SpectrumDespike.setObjectName("spinBox_SpectrumDespike")
        self.spinBox_SpectrumDespike.setMinimum(0)
        self.spinBox_SpectrumDespike.setMaximum(99)
        self.spinBox_SpectrumDespike.setSingleStep(2)
        self.spinBox_SpectrumDespike.setValue(0)
        self.spinBox_SpectrumDespike.setKeyboardTracking(False)
        self.spinBox_SpectrumDespike.setStyle(SpinBoxFixStyle())
        self.spinBox_SpectrumDespike.setToolTip("Median despike kernel. Use 0 for off, otherwise odd values such as 3 or 5.")

        self.label_SpectrumSGWindow = QtWidgets.QLabel("SG window", self.groupBox_SpectrumSmooth)
        self.spinBox_SpectrumSGWindow = QtWidgets.QSpinBox(self.groupBox_SpectrumSmooth)
        self.spinBox_SpectrumSGWindow.setObjectName("spinBox_SpectrumSGWindow")
        self.spinBox_SpectrumSGWindow.setMinimum(0)
        self.spinBox_SpectrumSGWindow.setMaximum(999)
        self.spinBox_SpectrumSGWindow.setSingleStep(2)
        self.spinBox_SpectrumSGWindow.setValue(0)
        self.spinBox_SpectrumSGWindow.setKeyboardTracking(False)
        self.spinBox_SpectrumSGWindow.setStyle(SpinBoxFixStyle())
        self.spinBox_SpectrumSGWindow.setToolTip("Savitzky-Golay window length. Use 0 for off, otherwise odd values.")

        self.label_SpectrumSGPoly = QtWidgets.QLabel("SG polyorder", self.groupBox_SpectrumSmooth)
        self.spinBox_SpectrumSGPoly = QtWidgets.QSpinBox(self.groupBox_SpectrumSmooth)
        self.spinBox_SpectrumSGPoly.setObjectName("spinBox_SpectrumSGPoly")
        self.spinBox_SpectrumSGPoly.setMinimum(0)
        self.spinBox_SpectrumSGPoly.setMaximum(20)
        self.spinBox_SpectrumSGPoly.setValue(3)
        self.spinBox_SpectrumSGPoly.setKeyboardTracking(False)
        self.spinBox_SpectrumSGPoly.setStyle(SpinBoxFixStyle())
        self.spinBox_SpectrumSGPoly.setToolTip("Savitzky-Golay polynomial order.")

        self.formLayout_SpectrumSmooth.addRow(self.label_SpectrumDespike, self.spinBox_SpectrumDespike)
        self.formLayout_SpectrumSmooth.addRow(self.label_SpectrumSGWindow, self.spinBox_SpectrumSGWindow)
        self.formLayout_SpectrumSmooth.addRow(self.label_SpectrumSGPoly, self.spinBox_SpectrumSGPoly)
        self.pushButton_SpectrumRaw = QtWidgets.QPushButton("Go back to raw", self.groupBox_SpectrumSmooth)
        self.pushButton_SpectrumRaw.setObjectName("pushButton_SpectrumRaw")
        self.formLayout_SpectrumSmooth.addRow(self.pushButton_SpectrumRaw)
        self.verticalLayout_21.insertWidget(3, self.groupBox_SpectrumSmooth)

    def about(self):
        information = 'Rampo ver.' + __version__ + '<br>' + \
            'A Raman spectroscopy analysis tool for high-pressure experiments<br>' + \
            'by S.-H. Dan Shim, SHDShim@gmail.com<br>' + \
            'Arizona State University<br><br>' + \
            'Derived from the PeakPo workflow.<br><br>' + \
            'how to cite: ' + str(__citation__) + '<br><br>' + \
            'WARNING. Use at your own risk. ' + \
            'This is a free software and no support is provided.<br>'
        infobox = InformationBox(title="About Rampo")
        infobox.setText(information)
        infobox.exec()

        """
        self.textEdit_about.setText(
            'PeakPo ver.' + __version__ + '<br>' +
            'A Visual Diffraction Analysis Tool<br>' +
            'by S.-H. Dan Shim, SHDShim@gmail.com<br>' +
            'Arizona State University<br><br>' +
            'Source: https://github.com/SHDShim/peakpo-v7 <br><br>' +
            'Manual: https://github.com/SHDShim/PeakPo/wiki <br><br>'
            'how to cite: ' + str(__citation__) + '<br><br>'
            'WARNING. Use at your own risk. ' +
            'This is a free software and no support is provided.<br>')
        """

    def shortcutkeys(self):
        information = '** Shortcut Keys ** <br><br>' + \
            'To activate shortcut keys: <br>' + \
            ' - Mouse click the plotting area. <br>' + \
            ' - Make sure no toolbar buttons are in blue. <br><br>' + \
            'Save session: s<br>' + \
            'Rescale vertical: v<br>' + \
            'Whole spectrum: w<br>' + \
            'Home or Reset: H or R<br>' + \
            'Back: left arrow<br>' + \
            'Forward: right arrow<br>' + \
            'Pan: p<br>' + \
            'Zoom: o<br>' + \
            'Peak position read: i<br>' + \
            'Constrain pan/zoom to x axis: hold x when panning/zooming<br>' + \
            'Constrain pan/zoom to y axis: hold y when panning/zooming<br>' + \
            'Preserve aspect ratio: hold CTRL when panning/zooming<br>' + \
            'Toggle x scale (log/lin): L or k when mouse is over an axes<br>' + \
            'Toggle y scale (log/lin): l when mouse is over an axes<br>'
        infobox = InformationBox(title="Help")
        infobox.setText(information)
        infobox.exec()

        """
        self.textEdit_shortcuts.setText(
            '** Shortcut Keys ** <br><br>' +
            'To activate shortcut keys: <br>' +
            ' - Mouse click the plotting area. <br>' +
            ' - Make sure no toolbar buttons are in blue. <br><br>' +
            'Save session: s<br>' +
            'Rescale vertical: v<br>' +
            'Whole spectrum: w<br>' +
            'Home or Reset: H or R<br>' +
            'Back: left arrow<br>' +
            'Forward: right arrow<br>' +
            'Pan: p<br>' +
            'Zoom: o<br>' +
            'Peak position read: i<br>' +
            'Constrain pan/zoom to x axis: hold x when panning/zooming<br>' +
            'Constrain pan/zoom to y axis: hold y when panning/zooming<br>' +
            'Preserve aspect ratio: hold CTRL when panning/zooming<br>' +
            'Toggle x scale (log/lin): L or k when mouse is over an axes<br>' +
            'Toggle y scale (log/lin): l when mouse is over an axes<br>')
        """
