import traceback
from gc import collect
from pathlib import Path
from traceback import format_exc

import matplotlib.pyplot as plt
import pandas as pd
from PySide6.QtCore import QCoreApplication, QAbstractTableModel, QModelIndex, Qt, QTimer
from PySide6.QtGui import QAction, QFont, QColor, QCursor, QIntValidator
from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication, QButtonGroup, QTableWidget, QTableWidgetItem, \
    QHeaderView, QPlainTextEdit, QTableView
from matplotlib import gridspec
from matplotlib.backends.backend_qt import NavigationToolbar2QT
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from matplotlib.collections import PolyCollection
from matplotlib.patches import Rectangle, Polygon

from numpy import array, zeros, append, linspace, place, nan
from overrides import overrides
from pandas import read_csv, DataFrame, Series, concat
from yaml import dump, load, FullLoader

from func.utils.ConfigParams import Filename, Params
from func.Filters.Preprocessing import Butterworth_for_ECG_PreProcess
from func.utils.PublicFunc import PublicFunc
from func.utils.Constants import Constants
from func.utils.Result import Result

from ui.MainWindow.MainWindow_SA_label_v2 import Ui_MainWindow_SA_label
from ui.setting.SA_label_input_setting_v2 import Ui_MainWindow_SA_label_input_setting

ButtonState = {
    "Default": {
        "pushButton_input_setting": True,
        "pushButton_input": True,
        "pushButton_quick_remark_input_waitingForTalk": False,
        "pushButton_quick_remark_input_maybeDesaturation": False,
        "pushButton_quick_remark_input_maybeWrongLabeled": False,
        "pushButton_quick_remark_input_durationNoEnough": False,
        "pushButton_quick_remark_input_littleChange": False,
        "pushButton_quick_remark_input_noNormalRespBetweenArtifact": False,
        "pushButton_quick_remark_input_lowSignalNoiseRatio": False,
        "pushButton_quick_remark_input_changeOnMiddle": False,
        "pushButton_save": False,
        "pushButton_prev": False,
        "pushButton_next": False,
        "pushButton_confirmLabel": False,
        "pushButton_jump_to": False,
        "pushButton_previous10s": False,
        "pushButton_previous30s": False,
        "pushButton_previous_half": False,
        "pushButton_next10s": False,
        "pushButton_next30s": False,
        "pushButton_next_half": False
    },
    "Current": {
        "pushButton_input_setting": True,
        "pushButton_input": True,
        "pushButton_quick_remark_input_waitingForTalk": False,
        "pushButton_quick_remark_input_maybeDesaturation": False,
        "pushButton_quick_remark_input_maybeWrongLabeled": False,
        "pushButton_quick_remark_input_durationNoEnough": False,
        "pushButton_quick_remark_input_littleChange": False,
        "pushButton_quick_remark_input_noNormalRespBetweenArtifact": False,
        "pushButton_quick_remark_input_lowSignalNoiseRatio": False,
        "pushButton_quick_remark_input_changeOnMiddle": False,
        "pushButton_save": False,
        "pushButton_prev": False,
        "pushButton_next": False,
        "pushButton_confirmLabel": False,
        "pushButton_jump_to": False,
        "pushButton_previous10s": False,
        "pushButton_previous30s": False,
        "pushButton_previous_half": False,
        "pushButton_next10s": False,
        "pushButton_next30s": False,
        "pushButton_next_half": False
    }
}


class SettingWindow(QMainWindow):
    def __init__(self, root_path, sampID):
        super(SettingWindow, self).__init__()
        self.ui = Ui_MainWindow_SA_label_input_setting()
        self.ui.setupUi(self)

        self.root_path = root_path
        self.sampID = sampID

        self.msgBox = QMessageBox()
        self.msgBox.setWindowTitle(Constants.MAINWINDOW_MSGBOX_TITLE)

        self.params = {}
        self.connect_signals()
        self.__read_config__()
        # self.__examine_freq__()

    def connect_signals(self):
        self.ui.plainTextEdit_file_path_input_signal_Abd.textChanged.connect(self.__set_plaintext_font_color_by_path__)
        self.ui.plainTextEdit_file_path_input_signal_FlowP.textChanged.connect(
            self.__set_plaintext_font_color_by_path__)
        self.ui.plainTextEdit_file_path_input_signal_FlowT.textChanged.connect(
            self.__set_plaintext_font_color_by_path__)
        self.ui.plainTextEdit_file_path_input_signal_OrgBCG.textChanged.connect(
            self.__set_plaintext_font_color_by_path__)
        self.ui.plainTextEdit_file_path_input_signal_SpO2.textChanged.connect(
            self.__set_plaintext_font_color_by_path__)
        self.ui.plainTextEdit_file_path_input_signal_Tho.textChanged.connect(self.__set_plaintext_font_color_by_path__)
        self.ui.plainTextEdit_file_path_input_signal_Stage.textChanged.connect(
            self.__set_plaintext_font_color_by_path__)

        self.ui.pushButton_confirm.clicked.connect(self.__write_config__)
        self.ui.pushButton_cancel.clicked.connect(self.__rollback_config__)
        self.ui.pushButton_cancel.clicked.connect(self.close)

    def __read_config__(self):
        if not Path(Params.SA_LABEL_CONFIG_FILE_PATH).exists():
            with open(Params.SA_LABEL_CONFIG_FILE_PATH, "w") as f:
                dump(Params.SA_LABEL_CONFIG_NEW_CONTENT, f)

        with open(Params.SA_LABEL_CONFIG_FILE_PATH, "r") as f:
            file_config = load(f.read(), Loader=FullLoader)
            self.params.update({"Config": file_config})

        sync_bcg_path = Path(self.root_path) / Filename.PATH_ORGBCG_ALIGNED / str(self.sampID)
        sync_psg_path = Path(self.root_path) / Filename.PATH_PSG_ALIGNED / str(self.sampID)
        label_path = Path(self.root_path) / Filename.PATH_LABEL / str(self.sampID)

        self.params.update({
            "Path": {
                "psg_path": sync_psg_path,
                "bcg_path": sync_bcg_path,
                "label_path": label_path,
                "Input_OrgBCG": sync_bcg_path,
                "Input_Tho": sync_psg_path,
                "Input_Abd": sync_psg_path,
                "Input_FlowT": sync_psg_path,
                "Input_FlowP": sync_psg_path,
                "Input_SpO2": sync_psg_path,
                "Input_Stage": sync_psg_path,

                "Input_SA_Label": sync_psg_path / f"{Filename.SA_LABEL_SYNC}{Params.ENDSWITH_CSV}",
                "Input_Artifact_A": label_path / f"{Filename.ARTIFACT_A}{Params.ENDSWITH_CSV}",

                "SA_Label_Revised": label_path / f"{Filename.SA_LABEL_CORRECTED}{Params.ENDSWITH_CSV}",
                # "Save_2": label_path / f"{Filename.SA_LABEL_ADD}{Params.ENDSWITH_CSV}"
            }
        })
        self.__auto_find_file__()
        self.__update_ui_info__()

    def __write_config__(self):
        # 从界面写入配置
        self.params["Path"]["Input_Stage"] = self.ui.plainTextEdit_file_path_input_signal_Stage.toPlainText()
        self.params["Path"]["Input_OrgBCG"] = self.ui.plainTextEdit_file_path_input_signal_OrgBCG.toPlainText()
        self.params["Path"]["Input_Tho"] = self.ui.plainTextEdit_file_path_input_signal_Tho.toPlainText()
        self.params["Path"]["Input_Abd"] = self.ui.plainTextEdit_file_path_input_signal_Abd.toPlainText()
        self.params["Path"]["Input_FlowT"] = self.ui.plainTextEdit_file_path_input_signal_FlowT.toPlainText()
        self.params["Path"]["Input_FlowP"] = self.ui.plainTextEdit_file_path_input_signal_FlowP.toPlainText()
        self.params["Path"]["Input_SpO2"] = self.ui.plainTextEdit_file_path_input_signal_SpO2.toPlainText()
        self.params["Path"]["Input_Artifact_A"] = self.ui.plainTextEdit_file_path_input_artifact.toPlainText()
        self.params["Path"]["Input_SA_Label"] = self.ui.plainTextEdit_file_path_input_label.toPlainText()
        self.params["Path"]["SA_Label_Revised"] = self.ui.plainTextEdit_file_path_save.toPlainText()
        # self.params["Path"]["Save_2"] = self.ui.plainTextEdit_file_path_save_2.toPlainText()

        self.params["Config"]["InputConfig"]["OrgBCGFreq"] = self.ui.spinBox_input_freq_signal_OrgBCG.value()
        self.params["Config"]["InputConfig"]["ThoFreq"] = self.ui.spinBox_input_freq_signal_Tho.value()
        self.params["Config"]["InputConfig"]["AbdFreq"] = self.ui.spinBox_input_freq_signal_Abd.value()
        self.params["Config"]["InputConfig"]["FlowTFreq"] = self.ui.spinBox_input_freq_signal_FlowT.value()
        self.params["Config"]["InputConfig"]["FlowPFreq"] = self.ui.spinBox_input_freq_signal_FlowP.value()
        self.params["Config"]["InputConfig"]["SpO2Freq"] = self.ui.spinBox_input_freq_signal_SpO2.value()

        # 保存配置到文件
        with open(Params.SA_LABEL_CONFIG_FILE_PATH, "w") as f:
            dump(self.params["Config"], f)

        self.close()

    def __update_ui_info__(self):
        self.ui.spinBox_input_freq_signal_OrgBCG.setValue(self.params["Config"]["InputConfig"]["OrgBCGFreq"])
        self.ui.spinBox_input_freq_signal_Tho.setValue(self.params["Config"]["InputConfig"]["ThoFreq"])
        self.ui.spinBox_input_freq_signal_Abd.setValue(self.params["Config"]["InputConfig"]["AbdFreq"])
        self.ui.spinBox_input_freq_signal_FlowT.setValue(self.params["Config"]["InputConfig"]["FlowTFreq"])
        self.ui.spinBox_input_freq_signal_FlowP.setValue(self.params["Config"]["InputConfig"]["FlowPFreq"])
        self.ui.spinBox_input_freq_signal_SpO2.setValue(self.params["Config"]["InputConfig"]["SpO2Freq"])

        self.ui.plainTextEdit_file_path_input_signal_Stage.setPlainText(str(self.params["Path"]["Input_Stage"]))
        self.ui.plainTextEdit_file_path_input_signal_OrgBCG.setPlainText(str(self.params["Path"]["Input_OrgBCG"]))
        self.ui.plainTextEdit_file_path_input_signal_Tho.setPlainText(str(self.params["Path"]["Input_Tho"]))
        self.ui.plainTextEdit_file_path_input_signal_Abd.setPlainText(str(self.params["Path"]["Input_Abd"]))
        self.ui.plainTextEdit_file_path_input_signal_FlowT.setPlainText(str(self.params["Path"]["Input_FlowT"]))
        self.ui.plainTextEdit_file_path_input_signal_FlowP.setPlainText(str(self.params["Path"]["Input_FlowP"]))
        self.ui.plainTextEdit_file_path_input_signal_SpO2.setPlainText(str(self.params["Path"]["Input_SpO2"]))
        self.ui.plainTextEdit_file_path_input_artifact.setPlainText(str(self.params["Path"]["Input_Artifact_A"]))
        self.ui.plainTextEdit_file_path_input_label.setPlainText(str(self.params["Path"]["Input_SA_Label"]))
        self.ui.plainTextEdit_file_path_save.setPlainText(str(self.params["Path"]["SA_Label_Revised"]))
        # self.ui.plainTextEdit_file_path_save_2.setPlainText(str(self.params["Path"]["Save_2"]))

    def __auto_find_file__(self):
        check_signal_type_list = ["Input_OrgBCG", "Input_Tho", "Input_Abd", "Input_FlowT", "Input_FlowP", "Input_SpO2",
                                  "Input_Stage"]

        def find_file(file_path: Path, _type, endswith):
            if file_path.is_file():
                file_path = file_path.parent

            filename_start = getattr(Filename, f"{_type.upper()}_SYNC")

            result = PublicFunc.examine_file(file_path, filename_start, endswith)
            if result.status:
                self.params["Path"][f"Input_{_type}"] = result.data["path"]

                self.params["Config"]["InputConfig"][f"{_type}Freq"] = result.data["freq"]
            else:
                filename_start = filename_start.replace("Sync", "RoughCut")
                result = PublicFunc.examine_file(file_path, filename_start, endswith)
                if result.status:
                    self.params["Path"][f"Input_{_type}"] = result.data["path"]

                    self.params["Config"]["InputConfig"][f"{_type}Freq"] = result.data["freq"]
                else:
                    self.params["Path"][f"Input_{_type}"] = file_path
                    self.params["Config"]["InputConfig"][f"{_type}Freq"] = -1

        for signal_type in check_signal_type_list:
            signal_file_path = Path(self.params["Path"][signal_type])

            find_file(signal_file_path, signal_type.split("_")[-1], endswith=Params.ENDSWITH_TXT)

        self.__update_ui_info__()

    def __rollback_config__(self):
        self.__read_config__()

    def __set_plaintext_font_color_by_path__(self):
        if isinstance(self.sender(), QPlainTextEdit):
            input_path = Path(self.sender().toPlainText())
        else:
            raise TypeError("Sender is not a QPlainTextEdit instance.")

        if input_path.exists() and input_path.is_file():
            self.sender().setStyleSheet("QPlainTextEdit { color: green; }")
        else:
            self.sender().setStyleSheet("QPlainTextEdit { color: red; }")


class Data:
    def __init__(self, config):
        self.config = config
        self.Stage = None
        self.OrgBCG = None
        self.Tho = None
        self.Abd = None
        self.FlowT = None
        self.FlowP = None
        self.SpO2 = None
        self.Stage_Resampled = None
        self.lowPass20Hz = None
        self.lowPassResp = None
        self.OrgBCG_Resampled = None
        self.Tho_Resampled = None
        self.Abd_Resampled = None
        self.FlowT_Resampled = None
        self.FlowP_Resampled = None
        self.SpO2_Resampled = None
        self.lowPass20Hz_Resampled = None
        self.lowPassResp_Resampled = None
        self.channel = {
            "Stage": None,
            "orgdata": None,
            "0.5lowpass_resp": None,
            "Effort Tho": None,
            "Effort Abd": None,
            "Flow T": None,
            "Flow P": None,
            "SpO2": None
        }

        self.event_type_to_value = {
            "Hypopnea": 1,
            "Central apnea": 2,
            "Obstructive apnea": 3,
            "Mixed apnea": 4,
        }

        self.stage_to_value = {
            "NS": 5,
            "W": 5,
            "N1": 3,
            "N2": 2,
            "N3": 1,
            "R": 4,
        }

        self.event_label_origin = None
        self.event_label_revised = None
        self.event_index_origin = None
        self.event_index_revised = None
        self.artifact_label = None

        self.Artifact = None
        self.df_origin = None
        self.df_revised = None

    def open_file(self):
        # 仅判断文件是否存在
        check_file_list = ["Input_OrgBCG", "Input_Tho", "Input_Abd", "Input_FlowT", "Input_FlowP", "Input_SpO2",
                           "Input_SA_Label", "Input_Stage"]

        for file_key in check_file_list:
            if (not Path(self.config["Path"][file_key]).is_file()) or (not Path(self.config["Path"][file_key]).exists()):
                return Result().failure(info=Constants.INPUT_FAILURE + "\n" +
                                             str(self.config["Path"][file_key]) + file_key +
                                             Constants.FAILURE_REASON["Path_Not_Exist"])

        try:
            self.Stage = read_csv(self.config["Path"]["Input_Stage"], encoding=Params.UTF8_ENCODING,
                                  header=None).to_numpy().reshape(-1)
            self.OrgBCG = read_csv(self.config["Path"]["Input_OrgBCG"], encoding=Params.UTF8_ENCODING,
                                   header=None).to_numpy().reshape(-1)
            self.Tho = read_csv(self.config["Path"]["Input_Tho"], encoding=Params.UTF8_ENCODING,
                                header=None).to_numpy().reshape(-1)
            self.Abd = read_csv(self.config["Path"]["Input_Abd"], encoding=Params.UTF8_ENCODING,
                                header=None).to_numpy().reshape(-1)
            self.FlowT = read_csv(self.config["Path"]["Input_FlowT"], encoding=Params.UTF8_ENCODING,
                                  header=None).to_numpy().reshape(-1)
            self.FlowP = read_csv(self.config["Path"]["Input_FlowP"], encoding=Params.UTF8_ENCODING,
                                  header=None).to_numpy().reshape(-1)
            self.SpO2 = read_csv(self.config["Path"]["Input_SpO2"], encoding=Params.UTF8_ENCODING,
                                 header=None).to_numpy().reshape(-1)

            if self.config["Path"]["Input_Artifact_A"].exists() and self.config["Path"]["Input_Artifact_A"].is_file():
                self.Artifact = read_csv(self.config["Path"]["Input_Artifact_A"],
                                         encoding=Params.UTF8_ENCODING,
                                         header=None).to_numpy().reshape(-1)
            else:
                self.Artifact = array([])


        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.INPUT_FAILURE +
                                         Constants.FAILURE_REASON["Open_Data_Exception"] + "\n" + format_exc())

        try:
            # 检查体动标签正确性，长度
            PublicFunc.examine_artifact(self.Artifact)
            self.Artifact = self.Artifact.reshape(-1, 4)
        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.INPUT_FAILURE +
                                         Constants.FAILURE_REASON[
                                             "Get_Artifact_Format_Exception"] + "\n" + format_exc())

        self.config.update({
            "SignalSecond": min(int(len(self.OrgBCG) // self.config["Config"]["InputConfig"]["OrgBCGFreq"]),
                                int(len(self.Tho) // self.config["Config"]["InputConfig"]["ThoFreq"]),
                                )
        })

        # 批量将睡眠分期按照映射转换为数字
        for stage_str, stage_val in self.stage_to_value.items():
            place(self.Stage, self.Stage == stage_str, stage_val)
        self.Stage = self.Stage.astype(int)

        # 根据秒数对信号截断
        self.OrgBCG = self.OrgBCG[:self.config["SignalSecond"] * self.config["Config"]["InputConfig"]["OrgBCGFreq"]]
        self.Tho = self.Tho[:self.config["SignalSecond"] * self.config["Config"]["InputConfig"]["ThoFreq"]]
        self.Abd = self.Abd[:self.config["SignalSecond"] * self.config["Config"]["InputConfig"]["AbdFreq"]]
        self.FlowT = self.FlowT[:self.config["SignalSecond"] * self.config["Config"]["InputConfig"]["FlowTFreq"]]
        self.FlowP = self.FlowP[:self.config["SignalSecond"] * self.config["Config"]["InputConfig"]["FlowPFreq"]]
        self.SpO2 = self.SpO2[:self.config["SignalSecond"] * self.config["Config"]["InputConfig"]["SpO2Freq"]]
        self.Stage = self.Stage[:self.config["SignalSecond"]]

        plot_freq = self.config["Config"]["InputConfig"]["PlotFreq"]
        self.event_label_origin = zeros(self.config["SignalSecond"] * plot_freq)
        self.event_label_revised = zeros(self.config["SignalSecond"] * plot_freq)
        self.event_index_origin = zeros(self.config["SignalSecond"])
        self.event_index_revised = zeros(self.config["SignalSecond"])

        return Result().success(info=Constants.INPUT_FINISHED)

    def get_archive(self):
        # if (not Path(self.config["Path"]["Save"]).exists()) or (not Path(self.config["Path"]["Save_2"]).exists()):
        plot_freq = self.config["Config"]["InputConfig"]["PlotFreq"]
        self.df_origin = read_csv(self.config["Path"]["Input_SA_Label"], encoding="gbk")
        self.df_origin["Start"] = self.df_origin["Start"].astype(int)
        self.df_origin["End"] = self.df_origin["End"].astype(int)
        # 查看index是否存在
        if "Index" not in self.df_origin.columns:
            # 添加Index列
            self.df_origin.insert(0, "Index", range(1, len(self.df_origin) + 1))

        self.df_origin["Index"] = self.df_origin["Index"].astype(int)

        self.df_origin = self.df_origin.sort_values(by="Start", ascending=True)
        for one_data in self.df_origin.index:
            one_data = self.df_origin.loc[one_data]
            start = one_data["Start"]
            end = one_data["End"]

            self.event_label_origin[start * plot_freq:end * plot_freq] = self.event_type_to_value[
                one_data["Event type"]]
            self.event_index_origin[start:end] = one_data["Index"]

        if not Path(self.config["Path"]["SA_Label_Revised"]).exists():
            self.df_revised = self.df_origin.copy()

            self.df_revised["score"] = -1
            self.df_revised["remark"] = ""
            self.df_revised["correct_Start"] = -1
            self.df_revised["correct_End"] = -1
            self.df_revised["correct_EventsType"] = ""
            self.df_revised["isLabeled"] = -1

            self.event_label_revised = self.event_label_origin.copy()
            self.event_index_revised = self.event_index_origin.copy()

            return Result().success(info=Constants.ARCHIVE_NOT_EXIST)
        else:
            try:
                self.df_revised = read_csv(self.config["Path"]["SA_Label_Revised"], encoding="gbk")
                csv_headers = self.df_revised.columns.tolist()
                revised_columns = ["Index", "isLabeled", "correct_EventsType", "score", "correct_Start", "correct_End",
                                   "remark"]
                if not all(col in csv_headers for col in revised_columns):
                    return Result().failure(info=Constants.INPUT_FAILURE + Constants.FAILURE_REASON[
                        "Label_Format_Exception"] + "\n" + f"缺少列: {', '.join(set(revised_columns) - set(csv_headers))}")

                # 类型转换
                self.df_revised["correct_Start"] = self.df_revised["correct_Start"].astype(int)
                self.df_revised["correct_End"] = self.df_revised["correct_End"].astype(int)
                self.df_revised["isLabeled"] = self.df_revised["isLabeled"].astype(int)
                self.df_revised["score"] = self.df_revised["score"].astype(int)
                self.df_revised["Index"] = self.df_revised["Index"].astype(int)
                self.df_revised["remark"] = self.df_revised["remark"].astype(str)
                self.df_revised["correct_EventsType"] = self.df_revised["correct_EventsType"].astype(str)

                for index in self.df_revised.index:
                    one_data = self.df_revised.iloc[index]
                    if one_data["score"] == 3:
                        continue

                    start = one_data["correct_Start"] if one_data["isLabeled"] == 1 else one_data["Start"]
                    end = one_data["correct_End"] if one_data["isLabeled"] == 1 else one_data["End"]
                    event_type = one_data["correct_EventsType"] if one_data["isLabeled"] == 1 else one_data[
                        "Event type"]

                    self.event_label_revised[start * plot_freq:end * plot_freq] = self.event_type_to_value[
                        event_type]
                    self.event_index_revised[start:end] = one_data["Index"]

            except Exception as e:
                traceback.print_exc()
                return Result().failure(info=Constants.INPUT_FAILURE + Constants.FAILURE_REASON[
                    "Label_Format_Exception"] + "\n" + format_exc())

            return Result().success(info=Constants.ARCHIVE_EXIST)

    def preprocess(self):
        if self.OrgBCG is None:
            return Result().failure(info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])
        if self.Tho is None:
            return Result().failure(info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])
        if self.Abd is None:
            return Result().failure(info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])
        try:
            plot_freq = self.config["Config"]["InputConfig"]["PlotFreq"]
            orgbcg_freq = self.config["Config"]["InputConfig"]["OrgBCGFreq"]

            self.lowPass20Hz = Butterworth_for_ECG_PreProcess(self.OrgBCG,
                                                              orgbcg_freq,
                                                              'lowpass', low_cut=20, order=3)
            self.lowPassResp = Butterworth_for_ECG_PreProcess(self.OrgBCG,
                                                              orgbcg_freq,
                                                              'lowpass', low_cut=0.5, order=3)

            self.artifact_label = zeros(len(self.event_label_origin))

            for i, artifact_type, start, end in self.Artifact:
                start = int(start) // (orgbcg_freq // plot_freq)
                end = int(end) // (orgbcg_freq // plot_freq)
                artifact_type = int(artifact_type) + 5
                start = 0 if start < 0 else start
                if end < 0:
                    continue
                if start > len(self.event_label_origin):
                    continue
                self.artifact_label[start:end] = artifact_type

        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.PREPROCESS_FAILURE +
                                         Constants.FAILURE_REASON["Preprocess_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.PREPROCESS_FINISHED)

    def resample(self):
        if self.Tho is None or self.Abd is None or self.FlowT is None or self.FlowP is None or self.SpO2 is None or self.lowPass20Hz is None or self.lowPassResp is None:
            return Result().failure(info=Constants.RESAMPLE_FAILURE +
                                         Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            def _resample_signal(signal, orig_freq, target_freq):
                if orig_freq < target_freq:
                    return signal.repeat(int(target_freq / orig_freq))
                elif orig_freq > target_freq:
                    return signal[::int(orig_freq / target_freq)]
                else:
                    return signal

            plot_freq = self.config["Config"]["InputConfig"]["PlotFreq"]
            orgbcg_freq = self.config["Config"]["InputConfig"]["OrgBCGFreq"]

            self.lowPass20Hz_Resampled = _resample_signal(self.lowPass20Hz, orgbcg_freq, plot_freq)
            self.lowPassResp_Resampled = _resample_signal(self.lowPassResp, orgbcg_freq, plot_freq)
            self.Tho_Resampled = _resample_signal(self.Tho, self.config["Config"]["InputConfig"]["ThoFreq"], plot_freq)
            self.Abd_Resampled = _resample_signal(self.Abd, self.config["Config"]["InputConfig"]["AbdFreq"], plot_freq)
            self.FlowT_Resampled = _resample_signal(self.FlowT, self.config["Config"]["InputConfig"]["FlowTFreq"],
                                                    plot_freq)
            self.FlowP_Resampled = _resample_signal(self.FlowP, self.config["Config"]["InputConfig"]["FlowPFreq"],
                                                    plot_freq)
            self.SpO2_Resampled = _resample_signal(self.SpO2, self.config["Config"]["InputConfig"]["SpO2Freq"],
                                                   plot_freq)
            self.Stage_Resampled = _resample_signal(self.Stage, 1, plot_freq)

            self.channel["orgdata"] = self.lowPass20Hz_Resampled
            self.channel["0.5lowpass_resp"] = self.lowPassResp_Resampled
            self.channel["Effort Tho"] = self.Tho_Resampled
            self.channel["Effort Abd"] = self.Abd_Resampled
            self.channel["Flow T"] = self.FlowT_Resampled
            self.channel["Flow P"] = self.FlowP_Resampled
            self.channel["SpO2"] = self.SpO2_Resampled
            self.channel["Stage"] = self.Stage_Resampled

            # 从内存中删除原始信号
            del self.OrgBCG
            del self.Tho
            del self.Abd
            del self.FlowT
            del self.FlowP
            del self.SpO2
            del self.Stage

        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.RESAMPLE_FAILURE +
                                         Constants.FAILURE_REASON["Resample_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.RESAMPLE_FINISHED)

    def save(self):
        if self.df_revised is None:
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            self.df_revised.to_csv(self.config["Path"]["SA_Label_Revised"], mode='w', index=None, encoding="gbk")
        except PermissionError as e:
            traceback.print_exc()
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_Permission_Denied"])
        except FileNotFoundError as e:
            traceback.print_exc()
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_File_Not_Found"])
        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.SAVE_FAILURE +
                                         Constants.FAILURE_REASON["Save_Exception"] + "\n" + format_exc())

        return Result().success(Constants.SAVE_FINISHED)


class DataFrameModel(QAbstractTableModel):
    def __init__(self, dataframe, display_columns, header_mapping):
        super().__init__()
        self._dataframe = dataframe
        self._display_columns = display_columns
        self._showdata = dataframe[display_columns].copy()  # Copy the DataFrame to avoid modifying the original
        self.filter_txt = ""  # Initialize filter text
        self._header_mapping = header_mapping  # Dictionary for custom header names

    def rowCount(self, parent=QModelIndex()):
        return len(self._showdata)

    def columnCount(self, parent=QModelIndex()):
        return len(self._display_columns)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        row = index.row()
        col = index.column()
        column_name = self._display_columns[col]
        value = self._showdata.iloc[row][column_name]

        if role == Qt.DisplayRole:
            # 返回显示的文本
            return str(value)

        # 当isLabeled列为1时，整行前景变色
        if role == Qt.ForegroundRole and "isLabeled" in self._dataframe.columns:
            try:
                event_index = self._showdata.iloc[row]["Index"]
                is_labeled_value = self._dataframe[self._dataframe["Index"] == event_index]["isLabeled"]
                if is_labeled_value.empty:
                    return None
                is_labeled_value = is_labeled_value.values[0]

                if is_labeled_value != 1:
                    return QColor(0, 0, 0)

                # remark 返回蓝色 score 1 返回绿色， 2返回橙色  3返回灰色 -1返回红色
                # 获取此行的remark
                remark = self._dataframe[self._dataframe["Index"] == event_index]["remark"].values[0]
                score = self._dataframe[self._dataframe["Index"] == event_index]["score"].values[0]
                if "待讨论" in str(remark):
                    return QColor(0, 0, 255)
                elif score == 1:
                    return QColor(0, 255, 0)
                elif score == 2:
                    return QColor(255, 165, 0)
                elif score == 3:
                    return QColor(128, 128, 128)
                elif score == -1:
                    return QColor(255, 0, 0)
                else:
                    # 返回黑色
                    return QColor(0, 0, 0)

            except Exception as e:
                traceback.print_exc()
                return None

        return None

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return None
        if orientation == Qt.Horizontal:
            # Return custom header name if available, otherwise use column name
            return self._header_mapping.get(self._display_columns[section], self._display_columns[section])
        return str(section + 1)

    def get_event_index(self, index):
        if not index.isValid():
            return None
        row = index.row()
        col = "Index"
        if 0 <= row < len(self._showdata):
            return self._showdata.iloc[row][col]
        return None

    def flags(self, index):
        # Make table non-editable
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled

    def filter_data(self, filter_text):
        self.filter_txt = filter_text
        if filter_text == "":
            self._showdata = self._dataframe[self._display_columns].copy()
        else:
            # Filter the DataFrame based on the filter text
            filtered_df = self._dataframe[
                self._dataframe.apply(lambda row: row.astype(str).str.contains(filter_text, case=False).any(), axis=1)]
            filtered_df.reset_index(drop=True, inplace=True)
            self._showdata = filtered_df[self._display_columns].copy()

        self.layoutChanged.emit()  # Notify the view that the data has changed

    def update_all(self, _dataframe):
        # 整张表更新或增删一行
        self._dataframe = _dataframe
        self._showdata = _dataframe[self._display_columns].copy()  # Update the displayed data
        self.filter_data(self.filter_txt)
        self.layoutChanged.emit()


class MainWindow_SA_label(QMainWindow):
    def __init__(self):
        super(MainWindow_SA_label, self).__init__()
        self.ui = Ui_MainWindow_SA_label()
        self.ui.setupUi(self)

        self.root_path = None
        self.sampID = None

        self.data = None

        self.setting = None
        self.config = None

        self.buttonGroup_event = None
        self.buttonGroup_class = None

        # 初始化进度条
        self.progressbar = None
        PublicFunc.add_progressbar(self)

        # 初始化画框
        self.fig = None
        self.canvas = None
        self.figToolbar = None
        self.gs = None

        self.ui.textBrowser_info.setStyleSheet("QTextBrowser { background-color: rgb(255, 255, 200); }")
        PublicFunc.__styleAllButton__(self, ButtonState)

        # 设定事件和其对应颜色
        # event_code  color event
        #      0      黑色    背景
        #      1      粉色    低通气
        #      2      蓝色    中枢性
        #      3      红色    阻塞型
        #      4      灰色    混合型
        #      5      绿色    血氧饱和度下降
        #      6      橙色    大体动
        #      7      橙色    小体动
        #      8      橙色    深呼吸
        #      9      橙色    脉冲体动
        #      10     橙色    无效片段
        self.color_cycle = ["black", "pink", "blue", "red", "silver", "green", "orange", "orange", "orange", "orange",
                            "orange"]

        self.display_columns_origin = ["Index", "Event type", "Start", "End"]
        self.display_columns_name_origin = ["事件编号", "事件类型", "起始时间(s)", "终止时间(s)"]
        self.header_mapping_origin = dict(zip(self.display_columns_origin, self.display_columns_name_origin))

        self.display_columns_revised = ["Index", "correct_EventsType", "score", "correct_Start",
                                        "correct_End"]
        self.display_columns_name_revised = ["事件编号", "修正事件类型", "标签类型", "起始时间(s)",
                                             "终止时间(s)"]
        self.header_mapping_revised = dict(zip(self.display_columns_revised, self.display_columns_name_revised))

        self.event_type_to_radio = {
            "Obstructive apnea": self.ui.radioButton_OSA,
            "Central apnea": self.ui.radioButton_CSA,
            "Mixed apnea": self.ui.radioButton_MSA,
            "Hypopnea": self.ui.radioButton_HPY
        }

        self.score_to_radio = {
            "1": self.ui.radioButton_1_class,
            "2": self.ui.radioButton_2_class,
            "3": self.ui.radioButton_3_class
        }

        self.msgBox = QMessageBox()
        self.msgBox.setWindowTitle(Constants.MAINWINDOW_MSGBOX_TITLE)

        self.event_ploy_collection = {}
        self.selected_event_info = None
        self.selected_event_rect = None
        self.press = None

    @overrides
    def show(self, root_path, sampID):
        super().show()
        self.root_path = root_path
        self.sampID = sampID

        self.setting = SettingWindow(root_path, sampID)
        self.config = self.setting.params
        self.config.update(
            {
                "WindowStartSecond": 0,
                "WindowSignalSecond": int(self.ui.comboBox_window_signal_length.currentText()),
            }
        )

        self.ui.label_sampno.setText(f"编号:{sampID}")

        # 初始化画框
        self.fig = plt.figure(figsize=(12, 9), dpi=100)
        self.canvas = FigureCanvasQTAgg(self.fig)
        self.figToolbar = CustomNavigationToolbar(self.canvas, self)

        self.figToolbar.cid_mouse_press = self.canvas.mpl_connect('button_press_event', self.on_press)
        self.figToolbar.cid_mouse_release = self.canvas.mpl_connect('button_release_event', self.on_release)
        self.figToolbar.cid_mouse_move = self.canvas.mpl_connect('motion_notify_event', self.on_motion)
        self.figToolbar.cid_wheel = self.canvas.mpl_connect('scroll_event', self.__slot_wheel_move__)

        self.ui.verticalLayout_canvas.addWidget(self.canvas)
        self.ui.verticalLayout_canvas.addWidget(self.figToolbar)

        self.gs = gridspec.GridSpec(8, 1, height_ratios=[1, 1, 1, 1, 1, 3, 2, 1])
        self.fig.subplots_adjust(top=0.98, bottom=0.05, right=0.98, left=0.1, hspace=0, wspace=0)
        self.ax0 = self.fig.add_subplot(self.gs[0])
        self.ax0.grid(True)
        self.ax0.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax0.set_ylim((85, 100))
        self.ax0.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        self.ax1 = self.fig.add_subplot(self.gs[1], sharex=self.ax0)
        self.ax1.grid(True)
        self.ax1.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax1.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        self.ax2 = self.fig.add_subplot(self.gs[2], sharex=self.ax0)
        self.ax2.grid(True)
        self.ax2.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax2.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        self.ax3 = self.fig.add_subplot(self.gs[3], sharex=self.ax0)
        self.ax3.grid(True)
        self.ax3.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax3.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        self.ax4 = self.fig.add_subplot(self.gs[4], sharex=self.ax0)
        self.ax4.grid(True)
        self.ax4.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax4.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        self.ax5 = self.fig.add_subplot(self.gs[5], sharex=self.ax0)
        self.ax5.grid(True)
        self.ax5.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax5.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        self.ax6 = self.fig.add_subplot(self.gs[6], sharex=self.ax0)
        self.ax6.grid(True)
        self.ax6.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax7 = self.fig.add_subplot(self.gs[7], sharex=self.ax0)
        self.ax7.grid(True)
        self.ax7.xaxis.set_major_formatter(Params.FORMATTER)

        self.channel_to_ax = {
            "SpO2": self.ax0,
            "Flow T": self.ax1,
            "Flow P": self.ax2,
            "Effort Tho": self.ax3,
            "Effort Abd": self.ax4,
            "0.5lowpass_resp": self.ax5,
            "orgdata": self.ax6,
            "Stage": self.ax7,
        }

        self.channel_to_best_fit_checkbox = {
            "SpO2": None,
            "Stage": None,
            "Flow T": self.ui.checkBox_best_flow,
            "Flow P": self.ui.checkBox_best_flow,
            "Effort Tho": self.ui.checkBox_best_effort,
            "Effort Abd": self.ui.checkBox_best_effort,
            "0.5lowpass_resp": self.ui.checkBox_best_resp,
            "orgdata": self.ui.checkBox_best_raw,
        }

        PublicFunc.__resetAllButton__(self, ButtonState)

        self.ui.comboBox_window_signal_length.lineEdit().setValidator(QIntValidator(10, 1200))

        self.ui.tableView_label_revised.doubleClicked.connect(self.show_selected_event)
        self.ui.tableView_label.doubleClicked.connect(self.show_selected_event)

        self.buttonGroup_event = QButtonGroup(self)
        self.buttonGroup_class = QButtonGroup(self)

        self.buttonGroup_event.addButton(self.ui.radioButton_OSA)
        self.buttonGroup_event.addButton(self.ui.radioButton_CSA)
        self.buttonGroup_event.addButton(self.ui.radioButton_MSA)
        self.buttonGroup_event.addButton(self.ui.radioButton_HPY)
        self.buttonGroup_class.addButton(self.ui.radioButton_1_class)
        self.buttonGroup_class.addButton(self.ui.radioButton_2_class)
        self.buttonGroup_class.addButton(self.ui.radioButton_3_class)
        self.ui.radioButton_OSA.setChecked(True)
        self.ui.radioButton_2_class.setChecked(True)

        self.ui.pushButton_input.clicked.connect(self.__slot_btn_input__)
        self.ui.pushButton_input_setting.clicked.connect(self.setting.show)

        self.ui.comboBox_window_signal_length.currentTextChanged.connect(self.__update_window_signal_length__)
        self.ui.pushButton_best_fit.clicked.connect(self.best_fit)

        # self.ui.pushButton_prev.clicked.connect(self.__slot_btn_move__)
        # self.ui.pushButton_next.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_save.clicked.connect(self.__save_revise_df__)
        self.ui.pushButton_quick_remark_input_waitingForTalk.clicked.connect(self.__slot_btn_quick_remark__)
        self.ui.pushButton_quick_remark_input_maybeDesaturation.clicked.connect(self.__slot_btn_quick_remark__)
        self.ui.pushButton_quick_remark_input_maybeWrongLabeled.clicked.connect(self.__slot_btn_quick_remark__)
        self.ui.pushButton_quick_remark_input_durationNoEnough.clicked.connect(self.__slot_btn_quick_remark__)
        self.ui.pushButton_quick_remark_input_littleChange.clicked.connect(self.__slot_btn_quick_remark__)
        self.ui.pushButton_quick_remark_input_noNormalRespBetweenArtifact.clicked.connect(
            self.__slot_btn_quick_remark__)
        self.ui.pushButton_quick_remark_input_lowSignalNoiseRatio.clicked.connect(self.__slot_btn_quick_remark__)
        self.ui.pushButton_quick_remark_input_changeOnMiddle.clicked.connect(self.__slot_btn_quick_remark__)
        self.ui.pushButton_previous10s.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_previous30s.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_previous_half.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_next10s.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_next30s.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_next_half.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_jump_to.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_confirmLabel.clicked.connect(self.__slot_btn_confirmLabel__)
        self.ui.pushButton_reset_event.clicked.connect(self.__reset_event__)

        self.ui.pushButton_next_half.setProperty("offset",
                                                 int(self.ui.comboBox_window_signal_length.lineEdit().text()) // 2)
        self.ui.pushButton_previous_half.setProperty("offset",
                                                     -int(self.ui.comboBox_window_signal_length.lineEdit().text()) // 2)

        # 输入防抖
        self.debounce_timer1 = QTimer()
        self.debounce_timer1.setSingleShot(True)
        self.debounce_timer1.setInterval(500)
        self.debounce_timer1.timeout.connect(self.apply_text_filter)
        self.ui.lineEdit_filter_label_origin.textChanged.connect(self.debounce_timer1.start)

        self.debounce_timer2 = QTimer()
        self.debounce_timer2.setSingleShot(True)
        self.debounce_timer2.setInterval(500)
        self.debounce_timer2.timeout.connect(self.apply_text_filter)
        self.ui.lineEdit_filter_label_revised.textChanged.connect(self.debounce_timer2.start)

        # 快捷键
        self.ui.pushButton_confirmLabel.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_CONFIRMLABEL_SHORTCUT_KEY))
        self.ui.radioButton_OSA.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_RADIOBUTTON_OSA_SHORTCUT_KEY))
        self.ui.radioButton_CSA.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_RADIOBUTTON_CSA_SHORTCUT_KEY))
        self.ui.radioButton_MSA.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_RADIOBUTTON_MSA_SHORTCUT_KEY))
        self.ui.radioButton_HPY.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_RADIOBUTTON_HPY_SHORTCUT_KEY))
        self.ui.radioButton_1_class.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_RADIOBUTTON_1_CLASS_SHORTCUT_KEY))
        self.ui.radioButton_2_class.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_RADIOBUTTON_2_CLASS_SHORTCUT_KEY))
        self.ui.radioButton_3_class.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_RADIOBUTTON_3_CLASS_SHORTCUT_KEY))
        self.ui.pushButton_quick_remark_input_waitingForTalk.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_QUICK_REMARK_WAITINGFORTALK_SHORTCUT_KEY))
        self.ui.pushButton_previous10s.setShortcut(QCoreApplication.translate("MainWindow", Params.
                                                                              SA_LABEL_BTN_PREV_10s_SHORTCUT_KEY))
        self.ui.pushButton_previous30s.setShortcut(QCoreApplication.translate("MainWindow", Params.
                                                                              SA_LABEL_BTN_PREV_30s_SHORTCUT_KEY))
        self.ui.pushButton_previous_half.setShortcut(QCoreApplication.translate("MainWindow", Params.
                                                                                SA_LABEL_BTN_PREV_HALF_SHORTCUT_KEY))
        self.ui.pushButton_next10s.setShortcut(QCoreApplication.translate("MainWindow", Params.
                                                                          SA_LABEL_BTN_NEXT_10s_SHORTCUT_KEY))
        self.ui.pushButton_next30s.setShortcut(QCoreApplication.translate("MainWindow", Params.
                                                                          SA_LABEL_BTN_NEXT_30s_SHORTCUT_KEY))
        self.ui.pushButton_next_half.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_NEXT_HALF_SHORTCUT_KEY))
        self.ui.pushButton_best_fit.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_BEST_FIT_SHORTCUT_KEY))
        self.ui.pushButton_quick_remark_input_littleChange.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_LITTLE_CHANGED_SHORTCUT_KEY)
        )
        self.ui.pushButton_quick_remark_input_changeOnMiddle.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_CHANGED_ON_MIDDLE_SHORTCUT_KEY)
        )
        self.ui.pushButton_quick_remark_input_noNormalRespBetweenArtifact.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_BETWEEN_ARTIFACT_SHORTCUT_KEY)
        )
        self.ui.pushButton_quick_remark_input_lowSignalNoiseRatio.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_LOW_SNR_SHORTCUT_KEY)
        )

    def load_data_to_table(self):
        try:
            self.data_model_origin = DataFrameModel(self.data.df_origin, self.display_columns_origin,
                                                    self.header_mapping_origin)
            self.ui.tableView_label.setModel(self.data_model_origin)

            self.data_model_revised = DataFrameModel(self.data.df_revised, self.display_columns_revised,
                                                     self.header_mapping_revised)
            self.ui.tableView_label_revised.setModel(self.data_model_revised)

            # 设置选中行为为整行选择
            self.ui.tableView_label.setSelectionBehavior(QTableView.SelectRows)
            self.ui.tableView_label_revised.setSelectionBehavior(QTableView.SelectRows)

            # 按比例设置列宽
            self.set_tableview_column_width_by_ratio(self.ui.tableView_label, [1, 3, 3, 3])
            self.set_tableview_column_width_by_ratio(self.ui.tableView_label_revised, [1, 3, 1, 2, 2])

        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.UPDATE_FAILURE +
                                         Constants.FAILURE_REASON["Update_tableWidget_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.UPDATE_FINISHED)

    def set_tableview_column_width_by_ratio(self, tableview, ratios):
        """按比例设置tableView每列宽度"""
        total_width = tableview.viewport().width()
        sum_ratio = sum(ratios)
        for i, ratio in enumerate(ratios):
            tableview.setColumnWidth(i, int(total_width * ratio / sum_ratio))

    def best_fit(self):
        plot_freq = self.config["Config"]["InputConfig"]["PlotFreq"]
        start_point = self.config["WindowStartSecond"] * plot_freq
        end_point = start_point + self.config["WindowSignalSecond"] * plot_freq
        for channel, ax in self.channel_to_ax.items():
            if (self.channel_to_best_fit_checkbox[channel] is not None) and (
                    not self.channel_to_best_fit_checkbox[channel].isChecked()):
                continue

            signal_max = self.data.channel[channel][start_point: end_point].max()
            signal_min = self.data.channel[channel][start_point: end_point].min()
            if channel == "SpO2":
                signal_max = 100
                signal_min = min(signal_min, 90)
            elif channel == "Stage":
                continue
            # elif channel == "0.7lowpass_resp":
            #     # 上限上移0.1，下限下移0.1
            #     signal_max = signal_max + 0.1
            #     signal_min = signal_min - 0.1
            else:
                # 上限上移2%，下限下移2%
                delta = abs(signal_max - signal_min) * 0.02
                signal_max = signal_max + delta
                signal_min = signal_min - delta

            ax.set_ylim(int(signal_min), int(signal_max))
        self.canvas.draw()

    def apply_text_filter(self):
        # 应用文本过滤器
        if self.sender() == self.debounce_timer1:
            self.data_model_origin.filter_data(self.ui.lineEdit_filter_label_origin.text())


        elif self.sender() == self.debounce_timer2:
            self.data_model_revised.filter_data(self.ui.lineEdit_filter_label_revised.text())

    def show_selected_event(self, index):
        # 通过点击选中
        if isinstance(index, int):
            event_index = index
            keep_xlim = True
        # 从左侧列表中直接获取当前表中选中行的事件编号列的内容
        elif hasattr(index, "model"):
            if index.model() == self.data_model_origin:
                event_index = self.data_model_origin.get_event_index(index)

            elif index.model() == self.data_model_revised:
                event_index = self.data_model_revised.get_event_index(index)
            else:
                return
            keep_xlim = False
        else:
            return
        # print(index, event_index)

        # 表格移动到指定行
        row_list = self.data_model_revised._showdata[
            self.data_model_revised._showdata["Index"] == event_index].index.tolist()
        if row_list:
            row = row_list[0]
            model_index = self.data_model_revised.index(row, 0)
            self.ui.tableView_label_revised.scrollTo(model_index,
                                                     self.ui.tableView_label_revised.ScrollHint.EnsureVisible)
            self.ui.tableView_label_revised.selectRow(row)
        # 显示事件信息
        row_list = self.data_model_origin._showdata[
            self.data_model_origin._showdata["Index"] == event_index].index.tolist()
        if row_list:
            row = row_list[0]

            model_index = self.data_model_origin.index(row, 0)
            self.ui.tableView_label.scrollTo(model_index, self.ui.tableView_label.ScrollHint.EnsureVisible)
            self.ui.tableView_label.selectRow(row)

        self.show_event_info(event_index)
        self.__jump_to__event__(event_index, keep_xlim=keep_xlim)

    def on_press(self, event):
        # 只在编辑模式下响应左键按下事件
        if not (self.figToolbar.edit_active and event.inaxes == self.ax5 and event.button == 1):
            return
        # x轴坐标，因为使用linespace设置，故实际对应秒数
        x_press = event.xdata
        # 边界容差
        side_tol = 0.2
        # print("press", x_press)

        if self.selected_event_info is None:
            # 未选中事件之前，按下不响应，释放时处理
            # print(self.data.event_index_revised[int(x_press)],
            #       self.data.event_index_revised[int(x_press) - 2],
            #       self.data.event_index_revised[int(x_press) + 2])
            left_wo_sa = self.data.event_index_revised[max(0, int(x_press) - 1)] == 0
            right_wo_sa = self.data.event_index_revised[min(self.config["SignalSecond"], int(x_press) + 1)] == 0
            if left_wo_sa and right_wo_sa:
                self.press = x_press, 'empty', -1, -1
            else:
                self.press = None
            # print("press", "1", self.press)
        else:
            # 已经选中事件的情况下
            if self.selected_event_rect is not None:
                x_rect, width = self.__get_x_and_width__(self.selected_event_rect)
                event_left = x_rect
                event_right = x_rect + width

                # 检查是否按在边界上
                if abs(x_press - event_left) < side_tol:
                    self.press = x_press, 'left', x_rect, width
                elif abs(x_press - event_right) < side_tol:
                    self.press = x_press, 'right', x_rect, width
                elif event_left < x_press < event_right:
                    self.press = x_press, 'middle', x_rect, width
                else:
                    # 按下了其他事件上
                    self.press = None
                # print("press", "2", self.press)

        self.canvas.draw_idle()

    def on_motion(self, event):
        if not (self.figToolbar.edit_active and event.inaxes == self.ax5):
            return

        x_motion = event.xdata
        # 边界容差
        side_tol = 0.2
        if event.button == 1:
            # 已经选中事件的情况下
            if self.selected_event_info is not None:
                if self.press is not None:

                    x_press, side, x_rect, width = self.press
                    dx = x_motion - x_press

                    if side == 'left':
                        self.canvas.setCursor(QCursor(Qt.CursorShape.SizeHorCursor))
                        # 吸附到最近的一秒
                        new_start = round(x_press + dx)
                        new_width = width - (new_start - x_rect)
                    elif side == 'right':
                        self.canvas.setCursor(QCursor(Qt.CursorShape.SizeHorCursor))
                        new_end = round(x_rect + width + dx)
                        new_width = new_end - x_rect
                        new_start = x_rect
                    elif side == 'middle':
                        self.canvas.setCursor(QCursor(Qt.CursorShape.ClosedHandCursor))
                        new_start = round(x_rect + dx)
                        new_width = width
                    else:
                        raise ValueError(
                            f'event({self.selected_event_info}) was selected, side:({side}) must be left/right/middle')

                    new_width = max(new_width, 1)  # 最小宽度为1秒

                    self.__set_x_and_width__(self.selected_event_rect, new_start, new_width)

            # 未选中事件下，创建新的事件
            else:
                if self.press is not None and self.press[1] == 'empty':
                    x_press, side, x_rect, width = self.press
                    dx = x_motion - x_press
                    if dx > 1:
                        self.canvas.setCursor(QCursor(Qt.CursorShape.SizeHorCursor))
                        # 是否绑定了rect
                        if self.selected_event_rect is None:
                            # 起始位置向下取整
                            self.selected_event_rect = event.inaxes.axvspan(int(x_press), int(x_press) + 1,
                                                                            color="orange",
                                                                            alpha=0.4)
                        else:
                            x_rect, width = self.__get_x_and_width__(self.selected_event_rect)
                            new_right = round(int(x_press) + dx)
                            new_width = new_right - x_rect
                            self.selected_event_rect.set_width(new_width)
                    else:
                        self.canvas.setCursor(QCursor(Qt.CursorShape.ArrowCursor))
                        # 删除创建的矩形框
                        if self.selected_event_rect is not None:
                            self.selected_event_rect.remove()
                            self.selected_event_rect = None

                else:
                    # 如果没有选中事件，则恢复默认光标样式
                    self.canvas.setCursor(QCursor(Qt.CursorShape.ArrowCursor))
        # 非按下时，靠近边界时，修改光标样式
        elif event.button is None:
            if self.selected_event_info is not None and self.selected_event_rect is not None:
                # 非按下时，靠近边界时，修改光标样式
                x_rect, width = self.__get_x_and_width__(self.selected_event_rect)
                event_left = x_rect
                event_right = x_rect + width

                # 检查是否靠近边界
                if abs(x_motion - event_left) < side_tol:
                    self.canvas.setCursor(QCursor(Qt.CursorShape.SizeHorCursor))
                elif abs(x_motion - event_right) < side_tol:
                    self.canvas.setCursor(QCursor(Qt.CursorShape.SizeHorCursor))
                elif event_left < x_motion < event_right:
                    self.canvas.setCursor(QCursor(Qt.CursorShape.OpenHandCursor))
                else:
                    self.canvas.setCursor(QCursor(Qt.CursorShape.ArrowCursor))
        self.canvas.draw_idle()

    def on_release(self, event):
        if not (self.figToolbar.edit_active and event.inaxes == self.ax5 and event.button == 1):
            return
        # print("release", self.selected_event_info, self.press)
        x_release = event.xdata

        # 检查是否位于SA事件上 用于选中事件
        if self.data.event_index_revised[int(x_release)] != 0:
            event_indices = self.data.event_index_revised[int(x_release)]
            event_row = self.data.df_revised[self.data.df_revised["Index"] == event_indices].iloc[0]
        else:
            event_row = None

        # 未选中SA事件下
        if self.selected_event_info is None:
            # 非拖动的情况下 停在了某个SA事件上 则显示事件信息
            if self.press is None and event_row is not None:
                self.selected_event_info = event_row.copy()
                self.selected_event_rect = self.event_ploy_collection[self.selected_event_info["Index"]]
                self.show_selected_event(int(event_row["Index"]))
                # 高亮 透明度设置为0.4
                self.selected_event_rect.set_alpha(0.4)
                x_rect, width = self.__get_x_and_width__(self.selected_event_rect)
                self.ui.spinBox_correctStart.setValue(x_rect)
                self.ui.spinBox_correctEnd.setValue(x_rect + width)

            elif self.press is None and event_row is None:
                pass

            # 拖动创建事件的情况下，即正在创建SA事件
            elif self.press is not None and self.press[1] == 'empty':
                x_press = self.press[0]
                dx = x_release - x_press
                if dx > 1 and self.selected_event_rect is not None:
                    new_start = int(x_press)
                    new_end = round(int(x_press) + dx)
                    if sum(self.data.event_index_revised[
                               max(0, new_start - 1):min(new_end + 1, self.config["SignalSecond"])]) != 0:
                        # 与已有事件重叠，删除创建的矩形框
                        self.selected_event_rect.remove()
                        self.selected_event_rect = None
                        self.selected_event_info = None
                        QMessageBox.warning(self, "警告", "新建事件与已有事件重叠，请重新创建！")
                    elif new_end - new_start < 10:
                        # 事件过短，删除创建的矩形框
                        QMessageBox.warning(self, "警告", "新建事件持续时间短于十秒！")

                    # 生成临时event_info
                    event_info_temp = {
                        "Index": -1,
                        "isLabeled": 0,
                        "correct_EventsType": "Obstructive apnea",
                        "score": "2",
                        "correct_Start": new_start,
                        "correct_End": new_end,
                        "remark": ""
                    }
                    self.selected_event_info = event_info_temp
                    self.ui.spinBox_correctStart.setValue(new_start)
                    self.ui.spinBox_correctEnd.setValue(new_end)

                else:
                    # 删除创建的矩形框
                    if self.selected_event_rect is not None:
                        self.selected_event_rect.remove()
                    self.selected_event_rect = None
                    self.selected_event_info = None

        # 选中SA事件的情况下
        else:
            # 非拖动的情况下
            if self.press is None:
                # 点击了新建SA事件区域
                if event_row is None and self.selected_event_info["Index"] == -1:
                    x_rect, width = self.__get_x_and_width__(self.selected_event_rect)
                    event_left = x_rect
                    event_right = x_rect + width

                    if event_left <= x_release <= event_right:
                        # 点击选中事件的矩形框内，无需响应
                        pass
                    else:
                        # 点击选中事件的矩形框外，取消选中
                        self.selected_event_rect.remove()
                        self.selected_event_rect = None
                        self.selected_event_info = None
                        self.__clear_event_info__()

                # 点击了空白区域
                elif event_row is None:
                    self.__reset_rect__()
                    self.selected_event_info = None
                    self.selected_event_rect = None
                    self.__clear_event_info__()

                elif self.selected_event_info["Index"] != event_row["Index"]:
                    # TODO 弹窗提醒是否切换
                    self.__reset_rect__()
                    self.selected_event_info = event_row.copy()
                    self.selected_event_rect = self.event_ploy_collection[self.selected_event_info["Index"]]
                    self.selected_event_rect.set_alpha(0.4)
                    self.show_selected_event(int(event_row["Index"]))
                else:
                    # 点击同一个SA事件，无需响应
                    pass

            else:
                # 松开时，更新数据
                if self.press is not None:
                    x_press, side, x_rect, width = self.press
                    dx = x_release - x_press

                    event_left = x_rect
                    event_right = x_rect + width

                    if side == 'left':
                        new_start = round(x_press + dx)
                        new_end = event_right
                    elif side == 'right':
                        new_start = event_left
                        new_end = round(x_rect + width + dx)
                    elif side == 'middle':
                        new_start = round(x_rect + dx)
                        new_end = new_start + width
                    else:
                        raise ValueError(
                            f'event({self.selected_event_info}) was selected, side:({side}) must be left/right/middle')

                    new_start = max(new_start, 0)
                    new_end = max(new_end, new_start + 1)
                    if new_end - new_start < 10:
                        QMessageBox.warning(self, "警告", "事件持续时间短于十秒!")

                    # 更新到spinbox
                    self.ui.spinBox_correctStart.setValue(new_start)
                    self.ui.spinBox_correctEnd.setValue(new_end)

        self.press = None
        self.canvas.setCursor(QCursor(Qt.CursorShape.ArrowCursor))
        self.canvas.draw_idle()

    def __reset_rect__(self):
        if self.selected_event_info["Index"] == -1:
            # 新建事件，删除
            self.selected_event_rect.remove()
            self.selected_event_rect = None
        elif self.selected_event_info is not None and self.selected_event_rect is not None:

            self.selected_event_rect.set_alpha(0.2)
            x_rect, width = self.__get_x_and_width__(self.selected_event_rect)

            event_left = self.selected_event_info["correct_Start"] if self.selected_event_info["isLabeled"] != -1 else \
                self.selected_event_info["Start"]
            event_right = self.selected_event_info["correct_End"] if self.selected_event_info["isLabeled"] != -1 else \
                self.selected_event_info["End"]
            if x_rect != event_left or width != (event_right - event_left):
                self.__set_x_and_width__(self.selected_event_rect, event_left, event_right - event_left)

        self.canvas.draw_idle()

    @overrides
    def closeEvent(self, event):
        reply = QMessageBox.question(self, '确认', '确认退出吗？', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            PublicFunc.__disableAllButton__(self, ButtonState)

            PublicFunc.statusbar_show_msg(self, PublicFunc.format_status_msg(Constants.SHUTTING_DOWN))
            QApplication.processEvents()

            # 清空画框
            if self.ax0 is not None:
                self.ax0.clear()
            if self.ax1 is not None:
                self.ax1.clear()
            if self.ax2 is not None:
                self.ax2.clear()
            if self.ax3 is not None:
                self.ax3.clear()
            if self.ax4 is not None:
                self.ax4.clear()
            if self.ax5 is not None:
                self.ax5.clear()
            if self.ax6 is not None:
                self.ax6.clear()

            # 释放资源
            self.setting.close()
            del self.data
            self.fig.clf()
            plt.close(self.fig)
            self.deleteLater()
            collect()
            self.canvas = None
            event.accept()
        else:
            event.ignore()

    def __save_revise_df__(self):
        try:
            # 过滤掉Index大于origin的行中score为3的行
            origin_max_index = self.data.df_origin["Index"].max()
            mask = (self.data.df_revised["Index"] > origin_max_index) & (self.data.df_revised["score"] == 3)

            self.data.df_revised = self.data.df_revised[~mask].reset_index(drop=True)
            self.data.df_revised.to_csv(self.config["Path"]["SA_Label_Revised"], index=False, encoding="gbk")
        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.SAVE_FAILURE +
                                         Constants.FAILURE_REASON["Save_revised_csv_Exception"] + "\n" + format_exc())
        return Result().success(info=Constants.SAVE_FINISHED)

    def __clear_event_info__(self):
        self.ui.label_BCG_Index.setText("0/0")
        self.ui.label_BCG_Info.setText("Epoch:0 Duration:0s New Duration:0s")
        self.ui.lineEdit_remark.setText("")
        self.ui.radioButton_OSA.setChecked(False)
        self.ui.radioButton_CSA.setChecked(False)
        self.ui.radioButton_MSA.setChecked(False)
        self.ui.radioButton_HPY.setChecked(False)
        self.ui.radioButton_1_class.setChecked(False)
        self.ui.radioButton_2_class.setChecked(False)
        self.ui.radioButton_3_class.setChecked(False)
        self.ui.spinBox_correctStart.setValue(0)
        self.ui.spinBox_correctEnd.setValue(0)

    def __reset__(self):
        ButtonState["Current"].update(ButtonState["Default"].copy())

    def __update_window_signal_length__(self):
        self.config["WindowSignalSecond"] = int(self.ui.comboBox_window_signal_length.currentText())
        self.ui.pushButton_next_half.setProperty("offset", self.config["WindowSignalSecond"] // 2)
        self.ui.pushButton_previous_half.setProperty("offset", -self.config["WindowSignalSecond"] // 2)
        self.__jump_to__by_time__(self.config["WindowStartSecond"])

    def __jump_to__event__(self, index, keep_xlim=True):
        event_info = self.data.df_revised[self.data.df_revised["Index"] == index].iloc[0].copy()
        start_time = event_info["correct_Start"] if event_info["isLabeled"] != -1 else event_info["Start"]
        end_time = event_info["correct_End"] if event_info["isLabeled"] != -1 else event_info["End"]
        if keep_xlim:
            middle_time = self.config["WindowStartSecond"]
        else:
            if end_time - start_time > self.config["WindowSignalSecond"]:
                middle_time = start_time - self.config["WindowSignalSecond"] // 2
            else:
                middle_time = (start_time + end_time - self.config["WindowSignalSecond"]) // 2

        self.__jump_to__by_time__(middle_time)

    def __jump_to__by_time__(self, start_time, offset=0):
        """
        Jump to a specific time in the signal.
        :param start_time: The start time in seconds to jump to.
        :param offset: The offset in seconds to move from the start_time.
        :return:
        """

        # if start_time < 0 or start_time > self.config["SignalSecond"]:
        #     raise ValueError("Time must be within the signal length.")

        # Update the start start_time for the plot
        start_time, end_time = self.check_start_end(start_time, offset)
        self.config["WindowStartSecond"] = start_time
        self.ax0.set_xlim((start_time, end_time))
        self.canvas.draw()

    def show_event_info(self, index):
        event_info = self.data.df_revised[self.data.df_revised["Index"] == index].iloc[0].copy()
        if self.selected_event_info is not None:
            if self.selected_event_info["Index"] != event_info["Index"]:
                if self.selected_event_info["Index"] == -1:
                    # 新建事件，删除
                    if self.selected_event_rect is not None:
                        self.selected_event_rect.remove()
                        self.selected_event_rect = None
                else:
                    # 取消之前选中事件的高亮
                    if self.selected_event_rect is not None:
                        self.selected_event_rect.set_alpha(0.2)

        self.selected_event_info = event_info
        self.selected_event_rect = self.event_ploy_collection[event_info["Index"]]
        if self.selected_event_rect is not None:
            self.selected_event_rect.set_alpha(0.4)
        # show event index info
        bcg_duration = int(event_info["End"]) - int(event_info["Start"])
        bcg_new_duration = int(event_info["correct_End"]) - int(event_info["correct_Start"])
        self.ui.label_BCG_Index.setText(f"{index}/{len(self.data.df_revised)}")
        self.ui.label_BCG_Info.setText(
            f"Epoch:{event_info['Epoch']} Duration:{bcg_duration}s New Duration:{bcg_new_duration}")

        # show remark
        remark = event_info.get("remark", "")
        if pd.isna(remark):
            self.ui.lineEdit_remark.setText("")
        else:
            self.ui.lineEdit_remark.setText(str(remark))

        # show event type
        if event_info["isLabeled"] == -1:
            # If not labeled, show original event type
            event_type = event_info["Event type"]
        else:
            # If labeled, show corrected event type
            event_type = event_info["correct_EventsType"]

        if event_type in self.event_type_to_radio:
            self.event_type_to_radio[event_type].setChecked(True)
        else:
            raise ValueError(f"Unknown event type: {event_type} event_info={event_info}")

        # show score

        # Default to 2 if (not found or -1)
        score = str(event_info.get("score", -1))
        score = "2" if score == "-1" else score

        if score in self.score_to_radio:
            self.score_to_radio[score].setChecked(True)
        else:
            raise ValueError(f"Unknown score: {score} event_info={event_info}")

        # show start and end time to spinbox
        if event_info["isLabeled"] == -1:
            self.ui.spinBox_correctStart.setValue(event_info["Start"])
            self.ui.spinBox_correctEnd.setValue(event_info["End"])
        else:
            self.ui.spinBox_correctStart.setValue(event_info["correct_Start"])
            self.ui.spinBox_correctEnd.setValue(event_info["correct_End"])

    def __slot_btn_confirmLabel__(self):
        if self.selected_event_info is None:
            PublicFunc.msgbox_output(self, "未选中任何事件，无法保存！", Constants.MSGBOX_TYPE_WARNING)
            PublicFunc.text_output(self.ui, "未选中任何事件，无法保存！", Constants.TIPS_TYPE_ERROR)
            return

        if self.data is None or self.data.df_revised is None:
            PublicFunc.msgbox_output(self, "请先载入数据", Constants.MSGBOX_TYPE_WARNING)
            PublicFunc.text_output(self.ui, "请先载入数据", Constants.TIPS_TYPE_ERROR)
            return

        index = int(self.selected_event_info["Index"])

        if self.ui.radioButton_3_class.isChecked():
            if index != -1:
                self.delete_event(index)
        else:
            if index == -1:
                self.add_event()
            elif index < 1 or index > len(self.data.df_revised):
                PublicFunc.msgbox_output(self.ui, f"不合理的事件编号:{index}", Constants.MSGBOX_TYPE_WARNING)
                PublicFunc.text_output(self.ui, f"不合理的事件编号:{index}", Constants.TIPS_TYPE_ERROR)
            else:
                self.edit_event(index)

        if self.ui.checkBox_auto_save.isChecked():
            self.__save_revise_df__()

    def __update_label_from_ui__(self, event_info, is_new=False):
        # Update the event type
        if self.ui.radioButton_OSA.isChecked():
            event_info["correct_EventsType"] = "Obstructive apnea"
        elif self.ui.radioButton_CSA.isChecked():
            event_info["correct_EventsType"] = "Central apnea"
        elif self.ui.radioButton_MSA.isChecked():
            event_info["correct_EventsType"] = "Mixed apnea"
        elif self.ui.radioButton_HPY.isChecked():
            event_info["correct_EventsType"] = "Hypopnea"
        else:
            raise ValueError("No event type selected.")

        # Update the start and end time
        # 边界检查
        if self.ui.spinBox_correctStart.value() >= self.ui.spinBox_correctEnd.value():
            raise ValueError("Start time must be less than end time.")
        if self.ui.spinBox_correctStart.value() < 0 or self.ui.spinBox_correctEnd.value() < 0:
            raise ValueError("Start and end times must be non-negative.")
        signal_length = self.config["SignalSecond"]
        if self.ui.spinBox_correctStart.value() > signal_length or self.ui.spinBox_correctEnd.value() > signal_length:
            raise ValueError("Start and end times must be within the signal length.")

        if is_new:
            event_info["Start"] = self.ui.spinBox_correctStart.value()
            event_info["End"] = self.ui.spinBox_correctEnd.value()
            event_info["EventType"] = event_info["correct_EventsType"]

        event_info["correct_Start"] = self.ui.spinBox_correctStart.value()
        event_info["correct_End"] = self.ui.spinBox_correctEnd.value()

        # Update the remark
        remark_text = str(self.ui.lineEdit_remark.text()).strip()
        if remark_text:
            event_info["remark"] = remark_text
        else:
            event_info["remark"] = None

        # Update the score
        if self.ui.radioButton_1_class.isChecked():
            event_info["score"] = 1
        elif self.ui.radioButton_2_class.isChecked():
            event_info["score"] = 2
        elif self.ui.radioButton_3_class.isChecked():
            event_info["score"] = 3
        else:
            raise ValueError("No score selected.")

        return event_info

    def delete_event(self, index):
        """
        删除一个事件
        :param index: The index of the event in the DataFrame.
        """
        if self.data is None or self.data.df_revised is None:
            raise ValueError("Data or df_revised is not initialized.")

        if index < 1 or index > len(self.data.df_revised):
            raise IndexError("Index out of bounds for df_revised.")

        event_info = self.data.df_revised[self.data.df_revised["Index"] == index].iloc[0].copy()
        event_info = self.__update_label_from_ui__(event_info)
        event_info["isLabeled"] = 1  # Mark as not labeled

        self.data.df_revised[self.data.df_revised["Index"] == index] = event_info

        # 更新表格模型
        self.data_model_revised.update_all(self.data.df_revised)

        # 去除事件索引映射
        self.data.event_index_revised[self.data.event_index_revised == event_info["Index"]] = 0

        # 从poly_collection中移除
        if index in self.event_ploy_collection and self.event_ploy_collection[index] is not None:
            poly = self.event_ploy_collection.pop(index)
            poly.remove()
            self.event_ploy_collection.update({index: None})
            self.canvas.draw_idle()

        self.selected_event_rect = None
        # 清空选中状态
        self.selected_event_info = None
        self.__clear_event_info__()

    def edit_event(self, index):
        """
        Save the revised event information to the DataFrame.
        :param index: The index of the event in the DataFrame.
        """

        # Get the current event info
        event_info = self.data.df_revised[self.data.df_revised["Index"] == index].iloc[0].copy()
        event_info = self.__update_label_from_ui__(event_info)

        # 检查是否已有事件重叠
        overlapping_indices = set(self.data.event_index_revised[
                                      max(0, event_info["correct_Start"] - 1):min(event_info["correct_End"] + 1,
                                                                                  self.config["SignalSecond"])])
        overlapping_indices.discard(event_info["Index"])  # 去除自身
        overlapping_indices.discard(0)  # 去除空白区域标记
        if len(overlapping_indices) > 0:
            PublicFunc.msgbox_output(self, "修改后的事件与已有事件重叠，无法保存！", Constants.MSGBOX_TYPE_WARNING)
            PublicFunc.text_output(self.ui, "修改后的事件与已有事件重叠，无法保存！", Constants.TIPS_TYPE_ERROR)
            return

        # 去除原始事件索引映射
        # 从event_index_revised获取index所在区间
        self.data.event_index_revised[self.data.event_index_revised == event_info["Index"]] = 0

        # Save changes back to DataFrame
        event_info["isLabeled"] = 1  # Mark as labeled
        self.data.df_revised[self.data.df_revised["Index"] == index] = event_info
        # Update table model
        self.data_model_revised.update_all(self.data.df_revised)

        # Update event index mapping
        self.data.event_index_revised[event_info["correct_Start"]:event_info["correct_End"]] = event_info["Index"]

        # Update selected event info
        self.selected_event_info = event_info.copy()
        if self.event_ploy_collection[event_info["Index"]] is not None:
            self.event_ploy_collection[event_info["Index"]].remove()

        self.selected_event_rect = None
        # Redraw rectangle
        color = self.color_cycle[self.data.event_type_to_value[event_info["correct_EventsType"]]]
        self.selected_event_rect = self.ax5.axvspan(event_info["correct_Start"], event_info["correct_End"], color=color,
                                                    alpha=0.4)
        self.event_ploy_collection[event_info["Index"]] = self.selected_event_rect
        self.show_selected_event(int(event_info["Index"]))

    def add_event(self):
        """
        新增一个事件到数据表
        """

        # 获取index
        if self.data.df_revised is not None:
            index = self.data.df_revised["Index"].max() + 1 if not self.data.df_revised.empty else 1
        else:
            raise ValueError("DataFrame df_revised is not initialized.")

        new_event = Params.SA_LABEL_NEW_EVENT_FORMAT.copy()
        new_event["Index"] = index
        new_event = self.__update_label_from_ui__(new_event, is_new=True)
        new_event["isLabeled"] = 1  # 标记为已标记状态

        # 检查是否已有事件重叠
        if sum(self.data.event_index_revised[max(0, new_event["correct_Start"] - 1):min(new_event["correct_End"] + 1,
                                                                                        self.config[
                                                                                            "SignalSecond"])]) != 0:
            PublicFunc.msgbox_output(self, "新建事件与已有事件重叠，无法新建！", Constants.MSGBOX_TYPE_WARNING)
            PublicFunc.text_output(self.ui, "新建事件与已有事件重叠，无法新建！", Constants.TIPS_TYPE_ERROR)
            return

        # 将新事件添加到临时DataFrame
        temp_df = pd.DataFrame([new_event])
        temp_df = temp_df[self.data.df_revised.columns]  # 确保列顺序一致
        temp_df.reset_index(drop=True, inplace=True)

        # 将临时DataFrame添加到df_revised
        if self.data.df_revised is not None:
            self.data.df_revised = pd.concat([self.data.df_revised, temp_df], ignore_index=True)
        else:
            self.data.df_revised = temp_df
        # 更新表格模型
        self.data_model_revised.update_all(self.data.df_revised)

        # 更新事件索引映射
        self.data.event_index_revised[new_event["correct_Start"]:new_event["correct_End"]] = index

        # 更新event_info
        self.selected_event_info = new_event.copy()
        if self.selected_event_rect is not None:
            self.selected_event_rect.remove()

        # 重新绘制矩形框
        color = self.color_cycle[self.data.event_type_to_value[new_event["correct_EventsType"]]]
        self.selected_event_rect = self.ax5.axvspan(new_event["correct_Start"], new_event["correct_End"], color=color,
                                                    alpha=0.4)
        self.event_ploy_collection[new_event["Index"]] = self.selected_event_rect
        self.show_selected_event(int(new_event["Index"]))

    def __reset_event__(self):
        """
        从df_origin中重置事件信息到df_revised, df_origin只有df_revised的部分列，剩余的列恢复默认值
        """
        if self.data is None or self.data.df_revised is None:
            raise ValueError("Data or df_revised is not initialized.")

        event_index = self.selected_event_info["Index"]

        if event_index < 1 or event_index > len(self.data.df_origin):
            PublicFunc.msgbox_output(self, "只能重置原始事件，无法重置新建事件！", Constants.MSGBOX_TYPE_WARNING)
            PublicFunc.text_output(self.ui, "只能重置原始事件，无法重置新建事件！", Constants.TIPS_TYPE_ERROR)
            return

        # 获取原始事件信息
        original_event_info = self.data.df_origin[self.data.df_origin["Index"] == event_index].copy()
        original_event_info["isLabeled"] = -1  # 重置为未标记状态
        original_event_info["correct_EventsType"] = ""  # 重置为None
        original_event_info["score"] = -1  # 重置为None
        original_event_info["correct_Start"] = -1  # 重置为原始起始时间
        original_event_info["correct_End"] = -1  # 重置为原始终止时间
        original_event_info["remark"] = ""  # 重置备注为None
        # 检查是否有重叠
        # 检查是否已有事件重叠

        overlapping_indices = set(self.data.event_index_revised[
                                      max(0, original_event_info["Start"].values[0] - 1):min(
                                          original_event_info["End"].values[0] + 1,
                                          self.config["SignalSecond"])])
        overlapping_indices.discard(event_index)  # 去除自身
        overlapping_indices.discard(0)  # 去除空白区域标记
        if len(overlapping_indices) > 0:
            PublicFunc.msgbox_output(self, "重置事件与已有事件重叠，无法重置！", Constants.MSGBOX_TYPE_WARNING)
            PublicFunc.text_output(self.ui, "重置事件与已有事件重叠，无法重置！", Constants.TIPS_TYPE_ERROR)
            return

        # 更新事件索引映射
        # 先去除原始事件索引映射
        self.data.event_index_revised[self.data.event_index_revised == event_index] = 0
        # 再添加新的事件索引映射
        self.data.event_index_revised[
            original_event_info["Start"].values[0]:original_event_info["End"].values[0]] = event_index

        # 重置事件信息到df_revised
        self.data.df_revised[self.data.df_revised["Index"] == event_index] = original_event_info

        self.data_model_revised.update_all(self.data.df_revised)

        # 更新rect
        if event_index in self.event_ploy_collection:
            if self.event_ploy_collection[event_index] is not None:
                self.event_ploy_collection[event_index].remove()

        color = self.color_cycle[self.data.event_type_to_value[original_event_info["Event type"].values[0]]]
        self.selected_event_rect = self.ax5.axvspan(original_event_info["Start"].values[0],
                                                    original_event_info["End"].values[0], color=color,
                                                    alpha=0.4)
        self.event_ploy_collection[event_index] = self.selected_event_rect
        self.canvas.draw_idle()

    def __slot_btn_input__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)

        self.reset_axes()
        self.canvas.draw()

        self.data = Data(self.config)

        # 导入数据
        PublicFunc.progressbar_update(self, 1, 8, Constants.INPUTTING_DATA, 0)
        result = self.data.open_file()
        if not result.status:
            PublicFunc.text_output(self.ui, "(1/8)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(1/8)" + result.info, Constants.TIPS_TYPE_INFO)

        # 获取存档
        PublicFunc.progressbar_update(self, 2, 8, Constants.LOADING_ARCHIVE, 15)
        result = self.data.get_archive()
        if not result.status:
            PublicFunc.text_output(self.ui, "(2/8)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(2/8)" + result.info, Constants.TIPS_TYPE_INFO)

        # 保存
        PublicFunc.progressbar_update(self, 3, 8, Constants.SAVING_DATA, 20)
        result = self.data.save()
        if not result.status:
            PublicFunc.text_output(self.ui, "(3/8)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(3/8)" + result.info, Constants.TIPS_TYPE_INFO)

        # 保存
        # PublicFunc.progressbar_update(self, 4, 9, Constants.SAVING_DATA, 25)
        # result = self.data.save_2()
        # if not result.status:
        #     PublicFunc.text_output(self.ui, "(4/9)" + result.info, Constants.TIPS_TYPE_ERROR)
        #     PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
        #     PublicFunc.finish_operation(self, ButtonState)
        #     return
        # else:
        #     PublicFunc.text_output(self.ui, "(4/9)" + result.info, Constants.TIPS_TYPE_INFO)

        # 数据预处理
        PublicFunc.progressbar_update(self, 4, 8, Constants.PREPROCESSING_DATA, 30)
        result = self.data.preprocess()
        if not result.status:
            PublicFunc.text_output(self.ui, "(4/8)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(4/8)" + result.info, Constants.TIPS_TYPE_INFO)

        # 重采样
        PublicFunc.progressbar_update(self, 5, 8, Constants.RESAMPLING_DATA, 50)
        result = self.data.resample()
        for key, value in self.data.channel.items():
            PublicFunc.text_output(self.ui, key + "重采样后的长度：" + str(len(value)), Constants.TIPS_TYPE_INFO)
        if not result.status:
            PublicFunc.text_output(self.ui, "(5/8)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(5/8)" + result.info, Constants.TIPS_TYPE_INFO)

        # 绘图
        PublicFunc.progressbar_update(self, 6, 8, Constants.DRAWING_DATA, 70)
        # result = self.__plot__()
        result = self.draw_signal()
        if not result.status:
            PublicFunc.text_output(self.ui, "(6/8)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(6/8)" + result.info, Constants.TIPS_TYPE_INFO)

        # 更新表格
        PublicFunc.progressbar_update(self, 7, 8, Constants.UPDATING_TABLEWIDGET, 90)
        result = self.load_data_to_table()
        if not result.status:
            PublicFunc.text_output(self.ui, "(7/8)" + result.info, Constants.TIPS_TYPE_ERROR)
            PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
            PublicFunc.finish_operation(self, ButtonState)
            return
        else:
            PublicFunc.text_output(self.ui, "(7/8)" + result.info, Constants.TIPS_TYPE_INFO)

        # 更新信息
        PublicFunc.progressbar_update(self, 8, 8, Constants.UPDATING_INFO, 95)
        # result = self.update_UI_Args()
        # if not result.status:
        #     PublicFunc.text_output(self.ui, "(9/9)" + result.info, Constants.TIPS_TYPE_ERROR)
        #     PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
        #     PublicFunc.finish_operation(self, ButtonState)
        #     return
        # else:
        #     PublicFunc.text_output(self.ui, "(9/9)" + result.info, Constants.TIPS_TYPE_INFO)

        # if (self.data.df_revised["isLabeled"] == 1).all():
        #     self.ui.checkBox_examineLabeled.setChecked(False)
        #     PublicFunc.msgbox_output(self, Constants.SA_LABEL_ALL_LABELED, Constants.MSGBOX_TYPE_INFO)

        self.__reset__()
        self.setting.close()

        for action in self.figToolbar._actions.values():
            action.setEnabled(True)

        for key in ButtonState["Current"].keys():
            ButtonState["Current"][key] = True

        ButtonState["Current"]["pushButton_input"] = False
        ButtonState["Current"]["pushButton_input_setting"] = False

        PublicFunc.finish_operation(self, ButtonState)

    def __slot_btn_move__(self):
        sender = self.sender()
        if sender.objectName().startswith("pushButton_previous") or sender.objectName().startswith("pushButton_next"):
            offset = sender.property("offset")
            PublicFunc.text_output(self.ui, f"移动 {offset} 秒", Constants.TIPS_TYPE_INFO)
            self.__jump_to__by_time__(self.config["WindowStartSecond"], offset)
        elif sender.objectName() == "pushButton_jump_to":
            jump_time_str = self.ui.lineEdit_jump_second.text().strip()
            if not jump_time_str.isdigit():
                PublicFunc.msgbox_output(self, "请输入有效的时间（秒）", Constants.MSGBOX_TYPE_WARNING)
                return
            jump_time = int(jump_time_str)
            if jump_time < 0 or jump_time > self.config["SignalSecond"]:
                PublicFunc.msgbox_output(self, "跳转时间必须在信号长度范围内", Constants.MSGBOX_TYPE_WARNING)
                return
            PublicFunc.text_output(self.ui, f"跳转到 {jump_time} 秒", Constants.TIPS_TYPE_INFO)
            self.__jump_to__by_time__(jump_time)

    def __slot_wheel_move__(self, event):
        # 向上滚动，时间向前跳30秒，向下滚动，时间向后跳30秒
        offset = 30 if event.step < 0 else -30
        PublicFunc.text_output(self.ui, f"滚轮移动 {offset} 秒", Constants.TIPS_TYPE_INFO)
        self.__jump_to__by_time__(self.config["WindowStartSecond"], offset)

    def __slot_btn_quick_remark__(self):
        sender = self.sender()

        if sender == self.ui.pushButton_quick_remark_input_waitingForTalk:
            self.ui.lineEdit_remark.setText("待讨论")
        elif sender == self.ui.pushButton_quick_remark_input_maybeDesaturation:
            self.ui.lineEdit_remark.setText("形似潮式呼吸")
            self.ui.radioButton_1_class.setChecked(True)
        elif sender == self.ui.pushButton_quick_remark_input_maybeWrongLabeled:
            self.ui.lineEdit_remark.setText("疑似医生误标")
            self.ui.radioButton_2_class.setChecked(True)
        elif sender == self.ui.pushButton_quick_remark_input_durationNoEnough:
            self.ui.lineEdit_remark.setText("时长不足")
            self.ui.radioButton_2_class.setChecked(True)
        elif sender == self.ui.pushButton_quick_remark_input_littleChange:
            self.ui.lineEdit_remark.setText("起伏变化不大")
            self.ui.radioButton_2_class.setChecked(True)
        elif sender == self.ui.pushButton_quick_remark_input_noNormalRespBetweenArtifact:
            self.ui.lineEdit_remark.setText("体动间无正常呼吸")
            self.ui.radioButton_2_class.setChecked(True)
        elif sender == self.ui.pushButton_quick_remark_input_lowSignalNoiseRatio:
            self.ui.lineEdit_remark.setText("信噪比低")
            self.ui.radioButton_2_class.setChecked(True)
        elif sender == self.ui.pushButton_quick_remark_input_changeOnMiddle:
            self.ui.lineEdit_remark.setText("中间起伏")
            self.ui.radioButton_2_class.setChecked(True)

    # def __slot_checkBox_examineLabeled__(self):
    #     if self.ui.checkBox_examineLabeled.isChecked():
    #         if (self.data.df_corrected.loc[self.config["EventLabelIndexList"]]["isLabeled"] == 1).all():
    #             self.ui.checkBox_examineLabeled.setChecked(False)
    #             PublicFunc.text_output(self.ui, Constants.SA_LABEL_ALL_LABELED, Constants.TIPS_TYPE_INFO)
    #             PublicFunc.msgbox_output(self, Constants.SA_LABEL_ALL_LABELED, Constants.MSGBOX_TYPE_INFO)

    def reset_axes(self):
        if self.ax0 is not None:
            self.ax0.clear()
            self.ax0.grid(True)
            self.ax0.xaxis.set_major_formatter(Params.FORMATTER)
            self.ax0.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        if self.ax1 is not None:
            self.ax1.clear()
            self.ax1.grid(True)
            self.ax1.xaxis.set_major_formatter(Params.FORMATTER)
            self.ax1.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        if self.ax2 is not None:
            self.ax2.clear()
            self.ax2.grid(True)
            self.ax2.xaxis.set_major_formatter(Params.FORMATTER)
            self.ax2.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        if self.ax3 is not None:
            self.ax3.clear()
            self.ax3.grid(True)
            self.ax3.xaxis.set_major_formatter(Params.FORMATTER)
            self.ax3.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
        if self.ax4 is not None:
            self.ax4.clear()
            self.ax4.grid(True)
            self.ax4.xaxis.set_major_formatter(Params.FORMATTER)
            self.ax4.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)

        if self.ax5 is not None:
            self.ax5.clear()
            self.ax5.grid(True)
            self.ax5.xaxis.set_major_formatter(Params.FORMATTER)
            self.ax5.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)

        if self.ax6 is not None:
            self.ax6.clear()
            self.ax6.grid(True)
            self.ax6.xaxis.set_major_formatter(Params.FORMATTER)

    def draw_signal(self):
        try:
            self.plt_channel(channel="SpO2", start=0, end=self.config["SignalSecond"],
                             label_list=self.data.event_label_origin, event_code=[5])
            self.plt_channel(channel="Flow T", start=0, end=self.config["SignalSecond"],
                             label_list=self.data.event_label_origin, event_code=[1, 2, 3, 4])
            self.plt_channel(channel="Flow P", start=0, end=self.config["SignalSecond"],
                             label_list=self.data.event_label_origin, event_code=[1, 2, 3, 4])
            self.plt_channel(channel="Effort Tho", start=0, end=self.config["SignalSecond"],
                             label_list=self.data.event_label_origin, event_code=[1, 2, 3, 4])
            self.plt_channel(channel="Effort Abd", start=0, end=self.config["SignalSecond"],
                             label_list=self.data.event_label_origin, event_code=[1, 2, 3, 4])
            self.plt_channel(channel="0.5lowpass_resp", start=0, end=self.config["SignalSecond"])
            self.plt_interactive_event(channel="0.5lowpass_resp", label_df=self.data.df_revised)
            self.plt_channel(channel="orgdata", start=0, end=self.config["SignalSecond"],
                             label_list=self.data.artifact_label, event_code=[6, 7, 8, 9, 10])
            self.plt_channel(channel="Stage", start=0, end=self.config["SignalSecond"])

            self.ax0.set_xlim(self.check_start_end(self.config["WindowStartSecond"], 0))
            self.canvas.draw()
        except Exception as e:
            traceback.print_exc()
            return Result().failure(info=Constants.DRAW_FAILURE + "\n" + format_exc())

        return Result().success(info=Constants.DRAW_FINISHED)

    def check_start_end(self, start, value):
        if start + value < 0:
            return 0, self.config["WindowSignalSecond"]
        elif start + value + self.config["WindowSignalSecond"] > self.config["SignalSecond"]:
            PublicFunc.msgbox_output(self, "已到达信号末尾，无法继续前进！", Constants.MSGBOX_TYPE_WARNING)
            PublicFunc.text_output(self.ui, "已到达信号末尾，无法继续前进！", Constants.TIPS_TYPE_ERROR)
            return self.config["SignalSecond"] - self.config["WindowSignalSecond"], self.config["SignalSecond"]
        else:
            return start + value, start + value + self.config["WindowSignalSecond"]

    def plt_channel(self, channel, start, end, label_list=None, event_code=[1, 2, 3, 4], event_show_under=False):
        plt_ = self.channel_to_ax[channel]
        plot_freq = self.config["Config"]["InputConfig"]["PlotFreq"]
        start = 0 if start < 0 else start
        plt_.plot(linspace(start, end, (end - start) * plot_freq),
                  self.data.channel[channel][
                      start * plot_freq:end * plot_freq], label=channel,
                  color=self.color_cycle[0])
        if label_list is not None:
            for j in event_code:
                mask = label_list[start * plot_freq:end * plot_freq] == j

                if event_show_under:
                    min_point = self.data.channel[channel][start * plot_freq:end * plot_freq].min()
                    len_segment = end * plot_freq - start * plot_freq
                    y = (min_point.repeat(len_segment) * mask).astype('float')
                    place(y, y == 0, nan)
                else:
                    y = (self.data.channel[channel][start * plot_freq:end * plot_freq] * mask).astype('float')

                place(y, y == 0, nan)
                plt_.plot(linspace(start, end, (end - start) * plot_freq), y, color=self.color_cycle[j], linestyle="-")

        if channel == "Stage":
            plt_.set_yticks([1, 2, 3, 4, 5])
            plt_.set_yticklabels(["N3", "N2", "N1", "REM", "Awake"])
            plt_.set_ylim(0.5, 5.5)

        plt_.legend(fontsize=8, loc=1)

    def plt_interactive_event(self, channel, label_df):
        """
        绘制交互式事件标记
        :param channel: 信号通道名称
        :param label_df: 包含事件标记的DataFrame
        """
        plt_ = self.channel_to_ax[channel]
        plot_freq = self.config["Config"]["InputConfig"]["PlotFreq"]
        for _, row in label_df.iterrows():
            if row["score"] == 3:
                span = None
            else:
                if row["isLabeled"] == -1:
                    start = row["Start"]
                    end = row["End"]
                    event_type = row["Event type"]
                else:
                    start = row["correct_Start"]
                    end = row["correct_End"]
                    event_type = row["correct_EventsType"]

                color = self.color_cycle[self.data.event_type_to_value[event_type]]
                span = plt_.axvspan(start, end, color=color, alpha=0.2, label=event_type)

            self.event_ploy_collection.update({
                row["Index"]: span
            })

    def __get_x_and_width__(self, event_rect):
        """Get the width of a rectangle in data coordinates."""
        # matplotlib >=3.9 axvspan 返回的是一个Rectangle对象
        # matplorlib < 3.9 axvspan 返回的是一个Poly对象
        if isinstance(event_rect, Rectangle):
            x = event_rect.get_x()
            width = event_rect.get_width()
        elif isinstance(event_rect, Polygon):
            verts = event_rect.get_xy()
            x = verts[0, 0]
            width = verts[2, 0] - verts[0, 0]

        else:
            raise TypeError("Unsupported event_rect type")
        return x, width

    def __set_x_and_width__(self, event_rect, x, width):
        """Set the x and width of a rectangle in data coordinates."""
        if isinstance(event_rect, Rectangle):
            event_rect.set_x(x)
            event_rect.set_width(width)
        elif isinstance(event_rect, Polygon):
            verts = event_rect.get_xy()
            verts[0, 0] = x
            verts[1, 0] = x
            verts[2, 0] = x + width
            verts[3, 0] = x + width
            event_rect.set_xy(verts)
        else:
            raise TypeError("Unsupported event_rect type")


class CustomNavigationToolbar(NavigationToolbar2QT):
    def __init__(self, canvas, parent):
        super().__init__(canvas, parent)
        self.edit_active = False

        # 移除原有除了pan的所有按钮
        for action in list(self._actions.values()):
            if action.text() != 'Pan':
                self.removeAction(action)

        # pan禁用按住右键滑动对x轴变化
        self.edit_action = QAction('编辑模式(Z)', self)
        self.edit_action.setFont(QFont(Params.FONT, 14))
        self.edit_action.setCheckable(True)
        self.edit_action.triggered.connect(self.toggle_edit)
        self.edit_action.setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_EDIT_MODE_SHORTCUT_KEY))
        self.insertAction(self._actions['pan'], self.edit_action)

        # Connect Pan action to toggle off Edit
        self._actions['pan'].triggered.connect(self.toggle_pan)
        self._actions['pan'].setShortcut(
            QCoreApplication.translate("MainWindow", Params.SA_LABEL_BTN_PAN_MODE_SHORTCUT_KEY))

    def drag_pan(self, event):
        """Callback for dragging in pan/zoom mode."""
        for ax in self._pan_info.axes:
            # Using the recorded button at the press is safer than the current
            # button, as multiple buttons can get pressed during motion.
            # # 忽略pan按钮右键对时间尺度的拉伸操作
            # if self._pan_info.button == 3:
            #     ax.drag_pan(self._pan_info.button, event.key, ax._pan_start.x, event.y)
            # else:
            #     ax.drag_pan(self._pan_info.button, event.key, event.x, event.y)
            # 忽略对X轴的操作
            ax.drag_pan(self._pan_info.button, event.key, ax._pan_start.x, event.y)

        self.canvas.draw_idle()

    def toggle_edit(self):
        """Handle Edit action toggle."""
        self.edit_active = self.edit_action.isChecked()
        if self.edit_active:
            # Uncheck Pan when Edit is active
            self._actions['pan'].setChecked(False)
            self._active = 'EDIT'
            self.mode = 'edit mode'
        self.set_message(f"编辑事件: {'启用' if self.edit_active else '禁用'}")
        self.canvas.draw()

    def toggle_pan(self):
        """Handle Pan action toggle."""
        if self._actions['pan'].isChecked():
            # Uncheck Edit when Pan is active
            self.edit_action.setChecked(False)
            self.edit_active = False
            self.set_message("Pan 模式: 启用")
        else:
            self.set_message("Pan 模式: 禁用")
        self.canvas.draw()
