class Window(QOpenGLWindow): def __init__(self): super(Window, self).__init__() self.timer = QElapsedTimer() self.timer.start() def initializeGL(self): super(Window, self).initializeGL() gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glEnable(gl.GL_PROGRAM_POINT_SIZE) gl.glEnable(gl.GL_DEPTH_TEST) self.square = SquareObject(parent=self.context()) def paintGL(self): retinaScale = self.devicePixelRatio() gl.glViewport(0, 0, int(self.width() * retinaScale), int(self.height() * retinaScale)) gl.glClearColor(0.2, 0.3, 0.3, 1.0) gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) transform = QMatrix4x4() transform.setToIdentity() transform.rotate(self.timer.elapsed() / 10, QVector3D(0, 0, 1)) transform.translate(QVector3D(0.25, 0.25, -2.)) projection = QMatrix4x4() projection.setToIdentity() projection.perspective(45., float(self.width()) / self.height(), 0.1, 100.) self.square.render(projection * transform)
def __init__(self, totaltime=0): super().__init__() # 在主窗口左侧添加题干和选项 lsplitter = QSplitter(Qt.Vertical) self.question_panel = Question_panel() lsplitter.addWidget(self.question_panel) # 在主窗口右侧添加绘图和文本编辑,并把比例设置为3比1 rsplitter = QSplitter(Qt.Vertical) self.painter = QGraphicsView(rsplitter) self.note = QTextEdit(rsplitter) rsplitter.setStretchFactor(0, 3) rsplitter.setStretchFactor(1, 1) # 添加插件的顺序会导致左右不同 mainSplitter = QSplitter(Qt.Horizontal) mainSplitter.addWidget(lsplitter) mainSplitter.addWidget(rsplitter) self.setCentralWidget(mainSplitter) # 点击暂停按钮切换图标和停继时间 self.question_panel.ui.pushButtonPause.clicked.connect( self.toggle_play_and_pause) self.totaltime = totaltime # 当前试卷答题总时间 self.elapsed_time = QElapsedTimer() # 答题总时间的计时器 self.paused = False # 默认刚打开时,还未暂停时间 self.setTime() # 更新时间显示 self.timer = QTimer() self.timer.timeout.connect(self.setTime) # 每秒更新时间显示的定时器
class InUse(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.ui = UiInUse() self.ui.setupUi(self) self.db = DataBase() self.time = QTime(0, 0) self.elapsed_timer = QElapsedTimer() self.timer = QTimer(self) self.info = {} def update_time(self): # TODO: Is there more clean way to show timer...? self.ui.label_time.setText( (self.time.addMSecs(self.elapsed_timer.elapsed())).toString("hh:mm:ss") ) def set_page(self, contact): self.info = self.db.get_info(contact) self.elapsed_timer.start() self.timer.timeout.connect(self.update_time) self.timer.start(500) self.ui.label_time.setText(self.time.toString("hh:mm:ss")) self.ui.label_name.setText(self.info["name"] + " 님") def clear_page(self): self.timer.stop() self.ui.label_time.setText("00:00:00") self.ui.label_name.setText("???")
def __init__(self, window, G, options, main=None): super().__init__() self.timer = QTimer() self.alive_timer = QElapsedTimer() self.g = G self.window = window self.view_menu = QMenu() self.file_menu = QMenu() self.forces_menu = QMenu() self.main_widget = window self.docks = {} self.widgets = {} self.widgets_by_type = {} available_geometry = QApplication.desktop().availableGeometry() window.move((available_geometry.width() - window.width()) / 2, (available_geometry.height() - window.height()) / 2) self.__initialize_file_menu() viewMenu = window.menuBar().addMenu(window.tr("&View")) forcesMenu = window.menuBar().addMenu(window.tr("&Forces")) actionsMenu = window.menuBar().addMenu(window.tr("&Actions")) restart_action = actionsMenu.addAction("Restart") self.__initialize_views(options, main) self.alive_timer.start() self.timer.start(500)
def __init__(self, parent=None): super(TabDisplays, self).__init__(parent) # Initialize logging logging_conf_file = os.path.join(os.path.dirname(__file__), 'cfg/aecgviewer_aecg_logging.conf') logging.config.fileConfig(logging_conf_file) self.logger = logging.getLogger(__name__) self.studyindex_info = aecg.tools.indexer.StudyInfo() self.validator = QWidget() self.studyinfo = QWidget() self.statistics = QWidget() self.waveforms = QWidget() self.waveforms.setAccessibleName("Waveforms") self.scatterplot = QWidget() self.histogram = QWidget() self.trends = QWidget() self.xmlviewer = QWidget() self.xml_display = QTextEdit(self.xmlviewer) self.options = QWidget() self.aecg_display_area = QScrollArea() self.cbECGLayout = QComboBox() self.aecg_display = EcgDisplayWidget(self.aecg_display_area) self.aecg_display_area.setWidget(self.aecg_display) self.addTab(self.validator, "Study information") self.addTab(self.waveforms, "Waveforms") self.addTab(self.xmlviewer, "XML") self.addTab(self.options, "Options") self.setup_validator() self.setup_waveforms() self.setup_xmlviewer() self.setup_options() size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) size.setHeightForWidth(False) self.setSizePolicy(size) # Initialized a threpool with 2 threads 1 for the GUI, 1 for long # tasks, so GUI remains responsive self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(2) self.validator_worker = None self.indexing_timer = QElapsedTimer()
def __init__(self, parent=None): QWidget.__init__(self, parent) self.ui = UiInUse() self.ui.setupUi(self) self.db = DataBase() self.time = QTime(0, 0) self.elapsed_timer = QElapsedTimer() self.timer = QTimer(self) self.info = {}
def __init__(self): super(Window, self).__init__() self.timer = QElapsedTimer() self.timer.start() self.cubePositions = [ QVector3D( 0.0, 0.0, 0.0), QVector3D( 2.0, 5.0, -15.0), QVector3D(-1.5, -2.2, - 2.5), QVector3D(-3.8, -2.0, -12.3), QVector3D( 2.4, -0.4, - 3.5), QVector3D(-1.7, 3.0, - 7.5), QVector3D( 1.3, -2.0, - 2.5), QVector3D( 1.5, 2.0, - 2.5), QVector3D( 1.5, 0.2, - 1.5), QVector3D(-1.3, 1.0, - 1.5) ]
def __init__(self, parent=None): QWidget.__init__(self, parent) # UI for the page self.ui = Ui_InUse() self.ui.setupUi(self) # reader self.reader = TestDatabaseReader() self.userData = dict() self.userTime = QTime(0, 0) # Set timer self.elapsedTimer = QElapsedTimer() self.timer = QTimer(self) self.ui.UsedTime.setText("00:00:00") self.ui.UserName.setText("")
def initiateBlinkSequence(self, component: 'Component') -> None: """ Starts the blink sequence by setting timers and executing an event loop. NOTE: Because this function executes an event loop, it is blocking. :param component: The component to select. :type component: Component :return: None :rtype: NoneType """ self._component = component self._component.top_level_parent().set_focus() self._timer = QTimer(self) self._timer.timeout.connect(lambda: self.tick()) self._stopWatch = QElapsedTimer() self._timer.start(Blinker.INTERVAL_MILLIS) self._stopWatch.start() self.exec_()
class InUse(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) # UI for the page self.ui = Ui_InUse() self.ui.setupUi(self) # reader self.reader = TestDatabaseReader() self.userData = dict() self.userTime = QTime(0, 0) # Set timer self.elapsedTimer = QElapsedTimer() self.timer = QTimer(self) self.ui.UsedTime.setText("00:00:00") self.ui.UserName.setText("") def updateTime(self): self.ui.UsedTime.setText((self.userTime.addMSecs( self.elapsedTimer.elapsed())).toString("hh:mm:ss")) def setPage(self): self.elapsedTimer.start() self.timer.timeout.connect(self.updateTime) self.timer.start(500) self.userData = self.reader.getCurrentData() self.userTime = QTime.fromString(self.userData["time_used"], "hh:mm:ss") self.ui.UsedTime.setText(self.userTime.toString("hh:mm:ss")) self.ui.UserName.setText(self.userData["name"] + " 님") def clearPage(self): self.timer.stop() self.ui.UsedTime.setText("00:00:00") self.ui.UserName.setText("") """TODO: ADD CODES FOR WRITING CURRENT USERTIME TO DB HERE"""
def __init__(self, usbif, read_msg, data_msg, num_banks, bank_size, switches, aux_switch=None): QObject.__init__(self) self._usbif = usbif self._read_msg = read_msg self._data_msg = data_msg self._num_banks = num_banks self._bank_size = bank_size self._data = [] self._read_addrs = [] self._bank = 0 self._switches = switches self._aux_switch = aux_switch self._timer = QElapsedTimer() self._timer.start() self._check_timer = QTimer() self._check_timer.timeout.connect(self._check_progress) usbif.listen(self)
def start_time(cls): """Start or resume the timer.""" Clock.connect(on_tick=cls.update_time) if cls.time is None: cls.time = QElapsedTimer() cls.time.start() # Do not clobber timestamp when restoring from backup if not cls.from_backup: cls.timestamp = QDateTime.currentDateTime() else: # After restoring from backup, forget about it. # We need to be able to create new timestamps afterward. cls.from_backup = False else: cls.time.restart() cls.paused = False
class ProfilerFactor(Singleton): def __init__(self): pass def init(self, mainwindow): self.factor_init = True self.mainwindow = mainwindow self.timer = None def timer_start(self): self.timer = QElapsedTimer() self.timer.start() def timer_elapsed(self): elapsed = self.timer.elapsed() LogFactor().debug("timer elapsed:{}".format(elapsed)) self.timer.restart() def timer_finish(self): elapsed = self.timer.elapsed() LogFactor().debug("timer elapsed:{}".format(elapsed)) self.timer = None
class MemoryDump(QObject): finished = Signal() def __init__(self, usbif, read_msg, data_msg, num_banks, bank_size, switches, aux_switch=None): QObject.__init__(self) self._usbif = usbif self._read_msg = read_msg self._data_msg = data_msg self._num_banks = num_banks self._bank_size = bank_size self._data = [] self._read_addrs = [] self._bank = 0 self._switches = switches self._aux_switch = aux_switch self._timer = QElapsedTimer() self._timer.start() self._check_timer = QTimer() self._check_timer.timeout.connect(self._check_progress) usbif.listen(self) def handle_msg(self, msg): if isinstance(msg, self._data_msg): if msg.addr in self._read_addrs: self._timer.restart() bisect.insort(self._data, msg) last_addr = self._read_addrs[-1] self._read_addrs.remove(msg.addr) if not self._read_addrs: self._dump_next_bank() elif msg.addr == last_addr: self._dump_addrs(self._read_addrs) def _check_progress(self): if self._timer.elapsed() >= 80: self._dump_addrs(self._read_addrs) def _dump_addrs(self, addrs): self._read_addrs = addrs for a in addrs: self._usbif.send(self._read_msg(a)) def _dump_next_bank(self): while self._bank < self._num_banks: if self._bank < 0o44: sw = self._switches[self._bank] else: sw = self._aux_switch if sw.isChecked(): sw.setCheckState(Qt.PartiallyChecked) break self._bank += 1 if self._bank == self._num_banks: self._complete_dump() return bank_start = self._bank * self._bank_size bank_end = bank_start + self._bank_size self._dump_addrs(list(range(bank_start, bank_end))) self._bank += 1 def _complete_dump(self): self._check_timer.stop() for sw in self._switches: sw.setTristate(False) sw.update() if self._aux_switch: self._aux_switch.setTristate(False) self._aux_switch.update() data = array.array('H') data.fromlist([agc.pack_word(m.data, m.parity) for m in self._data]) data.byteswap() with open(self._filename, 'wb') as f: data.tofile(f) self.finished.emit() def dump_memory(self, filename): self._filename = filename self._bank = 0 self._data = [] self._timer.restart() self._check_timer.start(20) self._dump_next_bank()
def __init__(self): super(Window, self).__init__() self.timer = QElapsedTimer() self.timer.start()
def timer_start(self): self.timer = QElapsedTimer() self.timer.start()
class Window(QOpenGLWindow): def __init__(self): super(Window, self).__init__() self.timer = QElapsedTimer() self.timer.start() self.cubePositions = [ QVector3D( 0.0, 0.0, 0.0), QVector3D( 2.0, 5.0, -15.0), QVector3D(-1.5, -2.2, - 2.5), QVector3D(-3.8, -2.0, -12.3), QVector3D( 2.4, -0.4, - 3.5), QVector3D(-1.7, 3.0, - 7.5), QVector3D( 1.3, -2.0, - 2.5), QVector3D( 1.5, 2.0, - 2.5), QVector3D( 1.5, 0.2, - 1.5), QVector3D(-1.3, 1.0, - 1.5) ] def initializeGL(self): super(Window, self).initializeGL() gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glEnable(gl.GL_PROGRAM_POINT_SIZE) gl.glEnable(gl.GL_DEPTH_TEST) self.cube = CubeObject(parent=self.context()) def paintGL(self): retinaScale = self.devicePixelRatio() gl.glViewport(0, 0, int(self.width() * retinaScale), int(self.height() * retinaScale)) gl.glClearColor(0.2, 0.3, 0.3, 1.0) gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) self.cube.program.bind() projection = QMatrix4x4() projection.setToIdentity() projection.perspective(45., float(self.width()) / self.height(), 0.1, 100.) self.cube.program.setUniformValue('projection', projection) # Initialize view matrix. view = QMatrix4x4() view.setToIdentity() view.translate(0., 0., -10.) self.cube.program.setUniformValue('view', view) for i, t in enumerate(self.cubePositions): transform = QMatrix4x4() transform.setToIdentity() transform.translate(t) transform.rotate(1.2 ** ((i + 1) * 5) * self.timer.elapsed() / 10, QVector3D(0.5, 1, 0).normalized()) self.cube.render(transform) self.cube.program.release()
class TabDisplays(QTabWidget): def __init__(self, parent=None): super(TabDisplays, self).__init__(parent) # Initialize logging logging_conf_file = os.path.join(os.path.dirname(__file__), 'cfg/aecgviewer_aecg_logging.conf') logging.config.fileConfig(logging_conf_file) self.logger = logging.getLogger(__name__) self.studyindex_info = aecg.tools.indexer.StudyInfo() self.validator = QWidget() self.studyinfo = QWidget() self.statistics = QWidget() self.waveforms = QWidget() self.waveforms.setAccessibleName("Waveforms") self.scatterplot = QWidget() self.histogram = QWidget() self.trends = QWidget() self.xmlviewer = QWidget() self.xml_display = QTextEdit(self.xmlviewer) self.options = QWidget() self.aecg_display_area = QScrollArea() self.cbECGLayout = QComboBox() self.aecg_display = EcgDisplayWidget(self.aecg_display_area) self.aecg_display_area.setWidget(self.aecg_display) self.addTab(self.validator, "Study information") self.addTab(self.waveforms, "Waveforms") self.addTab(self.xmlviewer, "XML") self.addTab(self.options, "Options") self.setup_validator() self.setup_waveforms() self.setup_xmlviewer() self.setup_options() size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) size.setHeightForWidth(False) self.setSizePolicy(size) # Initialized a threpool with 2 threads 1 for the GUI, 1 for long # tasks, so GUI remains responsive self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(2) self.validator_worker = None self.indexing_timer = QElapsedTimer() def setup_validator(self): self.directory_indexer = None # aecg.indexing.DirectoryIndexer() self.validator_layout_container = QWidget() self.validator_layout = QFormLayout() self.validator_form_layout = QFormLayout( self.validator_layout_container) self.validator_grid_layout = QGridLayout() self.study_info_file = QLineEdit() self.study_info_file.setToolTip("Study index file") self.study_info_description = QLineEdit() self.study_info_description.setToolTip("Description") self.app_type = QLineEdit() self.app_type.setToolTip("Application type (e.g., NDA, IND, BLA, IDE)") self.app_num = QLineEdit() self.app_num.setToolTip("Six-digit application number") self.app_num.setValidator(QIntValidator(self.app_num)) self.study_id = QLineEdit() self.study_id.setToolTip("Study identifier") self.study_sponsor = QLineEdit() self.study_sponsor.setToolTip("Sponsor of the study") self.study_annotation_aecg_cb = QComboBox() self.study_annotation_aecg_cb.addItems( ["Rhythm", "Derived beat", "Holter-rhythm", "Holter-derived"]) self.study_annotation_aecg_cb.setToolTip( "Waveforms used to perform the ECG measurements (i.e., " "annotations)\n" "\tRhythm: annotations in a rhythm strip or discrete ECG " "extraction (e.g., 10-s strips)\n" "\tDerived beat: annotations in a representative beat derived " "from a rhythm strip\n" "\tHolter-rhythm: annotations in a the analysis window of a " "continuous recording\n" "\tHolter-derived: annotations in a representative beat derived " "from analysis window of a continuous recording\n") self.study_annotation_lead_cb = QComboBox() self.ui_leads = ["GLOBAL"] + aecg.STD_LEADS[0:12] +\ [aecg.KNOWN_NON_STD_LEADS[1]] + aecg.STD_LEADS[12:15] + ["Other"] self.study_annotation_lead_cb.addItems(self.ui_leads) self.study_annotation_lead_cb.setToolTip( "Primary analysis lead annotated per protocol. There could be " "annotations in other leads also, but only the primary lead should" " be selected here.\n" "Select global if all leads were used at the " "same time (e.g., superimposed on screen).\n" "Select other if the primary lead used is not in the list.") self.study_numsubjects = QLineEdit() self.study_numsubjects.setToolTip( "Number of subjects with ECGs in the study") self.study_numsubjects.setValidator( QIntValidator(self.study_numsubjects)) self.study_aecgpersubject = QLineEdit() self.study_aecgpersubject.setToolTip( "Number of scheduled ECGs (or analysis windows) per subject as " "specified in the study protocol.\n" "Enter average number of ECGs " "per subject if the protocol does not specify a fixed number of " "ECGs per subject.") self.study_aecgpersubject.setValidator( QIntValidator(self.study_aecgpersubject)) self.study_numaecg = QLineEdit() self.study_numaecg.setToolTip( "Total number of aECG XML files in the study") self.study_numaecg.setValidator(QIntValidator(self.study_numaecg)) self.study_annotation_numbeats = QLineEdit() self.study_annotation_numbeats.setToolTip( "Minimum number of beats annotated in each ECG or analysis window" ".\nEnter 1 if annotations were done in the derived beat.") self.study_annotation_numbeats.setValidator( QIntValidator(self.study_annotation_numbeats)) self.aecg_numsubjects = QLineEdit() self.aecg_numsubjects.setToolTip( "Number of subjects found across the provided aECG XML files") self.aecg_numsubjects.setReadOnly(True) self.aecg_aecgpersubject = QLineEdit() self.aecg_aecgpersubject.setToolTip( "Average number of ECGs per subject found across the provided " "aECG XML files") self.aecg_aecgpersubject.setReadOnly(True) self.aecg_numaecg = QLineEdit() self.aecg_numaecg.setToolTip( "Number of aECG XML files found in the study aECG directory") self.aecg_numaecg.setReadOnly(True) self.subjects_less_aecgs = QLineEdit() self.subjects_less_aecgs.setToolTip( "Percentage of subjects with less aECGs than specified per " "protocol") self.subjects_less_aecgs.setReadOnly(True) self.subjects_more_aecgs = QLineEdit() self.subjects_more_aecgs.setToolTip( "Percentage of subjects with more aECGs than specified per " "protocol") self.subjects_more_aecgs.setReadOnly(True) self.aecgs_no_annotations = QLineEdit() self.aecgs_no_annotations.setToolTip( "Percentage of aECGs with no annotations") self.aecgs_no_annotations.setReadOnly(True) self.aecgs_less_qt_in_primary_lead = QLineEdit() self.aecgs_less_qt_in_primary_lead.setToolTip( "Percentage of aECGs with less QT intervals in the primary lead " "than specified per protocol") self.aecgs_less_qt_in_primary_lead.setReadOnly(True) self.aecgs_less_qts = QLineEdit() self.aecgs_less_qts.setToolTip( "Percentage of aECGs with less QT intervals than specified per " "protocol") self.aecgs_less_qts.setReadOnly(True) self.aecgs_annotations_multiple_leads = QLineEdit() self.aecgs_annotations_multiple_leads.setToolTip( "Percentage of aECGs with QT annotations in multiple leads") self.aecgs_annotations_multiple_leads.setReadOnly(True) self.aecgs_annotations_no_primary_lead = QLineEdit() self.aecgs_annotations_no_primary_lead.setToolTip( "Percentage of aECGs with QT annotations not in the primary lead") self.aecgs_annotations_no_primary_lead.setReadOnly(True) self.aecgs_with_errors = QLineEdit() self.aecgs_with_errors.setToolTip("Number of aECG files with errors") self.aecgs_with_errors.setReadOnly(True) self.aecgs_potentially_digitized = QLineEdit() self.aecgs_potentially_digitized.setToolTip( "Number of aECG files potentially digitized (i.e., with more than " "5% of samples missing)") self.aecgs_potentially_digitized.setReadOnly(True) self.study_dir = QLineEdit() self.study_dir.setToolTip("Directory containing the aECG files") self.study_dir_button = QPushButton("...") self.study_dir_button.clicked.connect(self.select_study_dir) self.study_dir_button.setToolTip("Open select directory dialog") self.validator_form_layout.addRow("Application Type", self.app_type) self.validator_form_layout.addRow("Application Number", self.app_num) self.validator_form_layout.addRow("Study name/ID", self.study_id) self.validator_form_layout.addRow("Sponsor", self.study_sponsor) self.validator_form_layout.addRow("Study description", self.study_info_description) self.validator_form_layout.addRow("Annotations in", self.study_annotation_aecg_cb) self.validator_form_layout.addRow("Annotations primary lead", self.study_annotation_lead_cb) self.validator_grid_layout.addWidget(QLabel(""), 0, 0) self.validator_grid_layout.addWidget( QLabel("Per study protocol or report"), 0, 1) self.validator_grid_layout.addWidget(QLabel("Found in aECG files"), 0, 2) self.validator_grid_layout.addWidget(QLabel("Number of subjects"), 1, 0) self.validator_grid_layout.addWidget(self.study_numsubjects, 1, 1) self.validator_grid_layout.addWidget(self.aecg_numsubjects, 1, 2) self.validator_grid_layout.addWidget( QLabel("Number of aECG per subject"), 2, 0) self.validator_grid_layout.addWidget(self.study_aecgpersubject, 2, 1) self.validator_grid_layout.addWidget(self.aecg_aecgpersubject, 2, 2) self.validator_grid_layout.addWidget(QLabel("Total number of aECG"), 3, 0) self.validator_grid_layout.addWidget(self.study_numaecg, 3, 1) self.validator_grid_layout.addWidget(self.aecg_numaecg, 3, 2) self.validator_grid_layout.addWidget( QLabel("Number of beats per aECG"), 4, 0) self.validator_grid_layout.addWidget(self.study_annotation_numbeats, 4, 1) self.validator_grid_layout.addWidget( QLabel("Subjects with fewer ECGs"), 5, 1) self.validator_grid_layout.addWidget(self.subjects_less_aecgs, 5, 2) self.validator_grid_layout.addWidget(QLabel("Subjects with more ECGs"), 6, 1) self.validator_grid_layout.addWidget(self.subjects_more_aecgs, 6, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without annotations"), 7, 1) self.validator_grid_layout.addWidget(self.aecgs_no_annotations, 7, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without expected number of QTs in primary lead"), 8, 1) self.validator_grid_layout.addWidget( self.aecgs_less_qt_in_primary_lead, 8, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without expected number of QTs"), 9, 1) self.validator_grid_layout.addWidget(self.aecgs_less_qts, 9, 2) self.validator_grid_layout.addWidget( QLabel("aECGs annotated in multiple leads"), 10, 1) self.validator_grid_layout.addWidget( self.aecgs_annotations_multiple_leads, 10, 2) self.validator_grid_layout.addWidget( QLabel("aECGs with annotations not in primary lead"), 11, 1) self.validator_grid_layout.addWidget( self.aecgs_annotations_no_primary_lead, 11, 2) self.validator_grid_layout.addWidget(QLabel("aECGs with errors"), 12, 1) self.validator_grid_layout.addWidget(self.aecgs_with_errors, 12, 2) self.validator_grid_layout.addWidget( QLabel("Potentially digitized aECGs"), 13, 1) self.validator_grid_layout.addWidget(self.aecgs_potentially_digitized, 13, 2) self.validator_form_layout.addRow(self.validator_grid_layout) tmp = QHBoxLayout() tmp.addWidget(self.study_dir) tmp.addWidget(self.study_dir_button) self.validator_form_layout.addRow("Study aECGs directory", tmp) self.validator_form_layout.addRow("Study index file", self.study_info_file) self.validator_layout.addWidget(self.validator_layout_container) self.validator_effective_dirs = QLabel("") self.validator_effective_dirs.setWordWrap(True) self.validator_layout.addWidget(self.validator_effective_dirs) self.val_button = QPushButton("Generate/update study index") self.val_button.clicked.connect(self.importstudy_dialog) self.validator_layout.addWidget(self.val_button) self.cancel_val_button = QPushButton("Cancel study index generation") self.cancel_val_button.clicked.connect(self.cancel_validator) self.cancel_val_button.setEnabled(False) self.validator_layout.addWidget(self.cancel_val_button) self.validator_pl = QLabel("") self.validator_layout.addWidget(self.validator_pl) self.validator_pb = QProgressBar() self.validator_layout.addWidget(self.validator_pb) self.validator.setLayout(self.validator_layout) self.stop_indexing = False self.lastindexing_starttime = None self.update_validator_effective_dirs() def effective_aecgs_dir(self, navwidget, silent=False): aecgs_effective_dir = self.study_dir.text() if navwidget.project_loaded != '': # Path specified in the GUI potential_aecgs_dirs = [self.study_dir.text()] # StudyDir path from current working directory potential_aecgs_dirs += [self.studyindex_info.StudyDir] # StudyDir path from directory where the index is located potential_aecgs_dirs += [ os.path.join(os.path.dirname(navwidget.project_loaded), self.studyindex_info.StudyDir) ] # StudyDir replaced with the directory where the index is located potential_aecgs_dirs += [os.path.dirname(navwidget.project_loaded)] dir_found = False # Get xml and zip filenames from first element in the index aecg_xml_file = navwidget.data_index["AECGXML"][0] zipfile = "" if aecg_xml_file != "": zipfile = navwidget.data_index["ZIPFILE"][0] for p in potential_aecgs_dirs: testfn = os.path.join(p, aecg_xml_file) if zipfile != "": testfn = os.path.join(p, zipfile) if os.path.isfile(testfn): dir_found = True aecgs_effective_dir = p break if not silent: if not dir_found: QMessageBox.warning( self, f"Study aECGs directory not found", f"The following paths were checked:" f"{[','.join(p) for p in potential_aecgs_dirs]} and " f"none of them is valid") elif p != self.study_dir.text(): QMessageBox.warning( self, f"Study aECGs directory not found", f"The path specified in the study aECGs directory is " f"not valid and {p} is being used instead. Check and " f"update the path in Study aECGs directory textbox if " f"the suggested path is not the adequate path") return aecgs_effective_dir def update_validator_effective_dirs(self): msg = f"Working directory: {os.getcwd()}" if self.parent() is not None: if isinstance(self.parent(), QSplitter): navwidget = self.parent().parent() else: # Tabs widget has not been allocated the QSplitter yet navwidget = self.parent() project_loaded = navwidget.project_loaded if project_loaded != '': msg = f"{msg}\nLoaded project index: "\ f"{navwidget.project_loaded}" effective_aecgs_path = self.effective_aecgs_dir(navwidget) msg = f"{msg}\nEffective study aECGs directory: "\ f"{effective_aecgs_path}" else: msg = f"{msg}\nLoaded project index: None" else: msg = f"{msg}\nLoaded project index: None" self.validator_effective_dirs.setText(msg) def load_study_info(self, fileName): self.study_info_file.setText(fileName) try: study_info = pd.read_excel(fileName, sheet_name="Info") self.studyindex_info = aecg.tools.indexer.StudyInfo() self.studyindex_info.__dict__.update( study_info.set_index("Property").transpose().reset_index( drop=True).to_dict('index')[0]) sponsor = "" description = "" if self.studyindex_info.Sponsor is not None and\ isinstance(self.studyindex_info.Sponsor, str): sponsor = self.studyindex_info.Sponsor if self.studyindex_info.Description is not None and\ isinstance(self.studyindex_info.Description, str): description = self.studyindex_info.Description self.study_sponsor.setText(sponsor) self.study_info_description.setText(description) self.app_type.setText(self.studyindex_info.AppType) self.app_num.setText(f"{int(self.studyindex_info.AppNum):06d}") self.study_id.setText(self.studyindex_info.StudyID) self.study_numsubjects.setText(str(self.studyindex_info.NumSubj)) self.study_aecgpersubject.setText( str(self.studyindex_info.NECGSubj)) self.study_numaecg.setText(str(self.studyindex_info.TotalECGs)) anns_in = self.studyindex_info.AnMethod.upper() idx = 0 if anns_in == "RHYTHM": idx = 0 elif anns_in == "DERIVED": idx = 1 elif anns_in == "HOLTER_RHYTHM": idx = 2 elif anns_in == "HOLTER_MEDIAN_BEAT": idx = 3 else: idx = int(anns_in) - 1 self.study_annotation_aecg_cb.setCurrentIndex(idx) the_lead = self.studyindex_info.AnLead idx = self.study_annotation_lead_cb.findText(str(the_lead)) if idx == -1: idx = self.study_annotation_lead_cb.findText("MDC_ECG_LEAD_" + str(the_lead)) if idx == -1: idx = int(the_lead) self.study_annotation_lead_cb.setCurrentIndex(idx) self.study_annotation_numbeats.setText( str(self.studyindex_info.AnNbeats)) if self.studyindex_info.StudyDir == "": self.studyindex_info.StudyDir = os.path.dirname(fileName) self.study_dir.setText(self.studyindex_info.StudyDir) self.update_validator_effective_dirs() self.setCurrentWidget(self.validator) except Exception as ex: QMessageBox.critical( self, "Import study error", "Error reading the study information file: '" + fileName + "'") def setup_waveforms(self): wflayout = QVBoxLayout() # ECG plot layout selection box self.cbECGLayout.addItems( ['12-lead stacked', '3x4 + lead II rhythm', 'Superimposed']) self.cbECGLayout.currentIndexChanged.connect( self.ecgplotlayout_changed) # Zoom buttons blayout = QHBoxLayout() pb_zoomin = QPushButton() pb_zoomin.setText("Zoom +") pb_zoomin.clicked.connect(self.zoom_in) pb_zoomreset = QPushButton() pb_zoomreset.setText("Zoom 1:1") pb_zoomreset.clicked.connect(self.zoom_reset) pb_zoomout = QPushButton() pb_zoomout.setText("Zoom -") pb_zoomout.clicked.connect(self.zoom_out) blayout.addWidget(self.cbECGLayout) blayout.addWidget(pb_zoomout) blayout.addWidget(pb_zoomreset) blayout.addWidget(pb_zoomin) wflayout.addLayout(blayout) # Add QScrollArea to main layout of waveforms tab self.aecg_display_area.setWidgetResizable(False) wflayout.addWidget(self.aecg_display_area) self.waveforms.setLayout(wflayout) size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size.setHeightForWidth(False) self.aecg_display_area.setSizePolicy(size) self.waveforms.setSizePolicy(size) def setup_xmlviewer(self): wf_layout = QHBoxLayout() wf_layout.addWidget(self.xml_display) self.xmlviewer.setLayout(wf_layout) def setup_options(self): self.options_layout = QFormLayout() self.aecg_schema_filename = QLineEdit(aecg.get_aecg_schema_location()) self.options_layout.addRow("aECG XML schema path", self.aecg_schema_filename) self.save_index_every_n_aecgs = QSpinBox() self.save_index_every_n_aecgs.setMinimum(0) self.save_index_every_n_aecgs.setMaximum(50000) self.save_index_every_n_aecgs.setValue(0) self.save_index_every_n_aecgs.setSingleStep(100) self.save_index_every_n_aecgs.setSuffix(" aECGs") self.save_index_every_n_aecgs.setToolTip( "Set o 0 to save the study index file only after its generation " "is completed.\nOtherwise, the file is saved everytime the " " specified number of ECGs have been appended to the index.") self.options_layout.addRow("Save index every ", self.save_index_every_n_aecgs) self.save_all_intervals_cb = QCheckBox("") self.save_all_intervals_cb.setChecked(False) self.options_layout.addRow("Save individual beat intervals", self.save_all_intervals_cb) self.parallel_processing_cb = QCheckBox("") self.parallel_processing_cb.setChecked(True) self.options_layout.addRow("Parallel processing of files", self.parallel_processing_cb) self.options.setLayout(self.options_layout) def zoom_in(self): self.aecg_display.apply_zoom(self.aecg_display.zoom_factor + 0.1) def zoom_out(self): self.aecg_display.apply_zoom(self.aecg_display.zoom_factor - 0.1) def zoom_reset(self): self.aecg_display.apply_zoom(1.0) def ecgplotlayout_changed(self, i): self.aecg_display.update_aecg_plot( ecg_layout=aecg.utils.ECG_plot_layout(i + 1)) def update_search_progress(self, i, n): self.validator_pl.setText( f"Searching aECGs in directory ({n} XML files found)") def update_progress(self, i, n): j = i m = n if i <= 1: j = 1 if self.validator_pb.value() > 0: j = self.validator_pb.value() + 1 m = self.validator_pb.maximum() running_time = self.indexing_timer.elapsed() * 1e-3 # in seconds time_per_item = running_time / j # reamining = seconds per item so far * total pending items to process remaining_time = time_per_item * (m - j) eta = datetime.datetime.now() +\ datetime.timedelta(seconds=round(remaining_time, 0)) self.validator_pl.setText( f"Validating aECG {j}/{m} | " f"Execution time: " f"{str(datetime.timedelta(0,seconds=round(running_time)))} | " f"{round(1/time_per_item,2)} aECGs per second | " f"ETA: {eta.isoformat(timespec='seconds')}") self.validator_pb.setValue(j) if self.save_index_every_n_aecgs.value() > 0 and\ len(self.directory_indexer.studyindex) % \ self.save_index_every_n_aecgs.value() == 0: self.save_validator_results( pd.concat(self.directory_indexer.studyindex, ignore_index=True)) def save_validator_results(self, res): if res.shape[0] > 0: self.studyindex_info = aecg.tools.indexer.StudyInfo() self.studyindex_info.StudyDir = self.study_dir.text() self.studyindex_info.IndexFile = self.study_info_file.text() self.studyindex_info.Sponsor = self.study_sponsor.text() self.studyindex_info.Description =\ self.study_info_description.text() self.studyindex_info.Date = self.lastindexing_starttime.isoformat() self.studyindex_info.End_date = datetime.datetime.now().isoformat() self.studyindex_info.Version = aecg.__version__ self.studyindex_info.AppType = self.app_type.text() self.studyindex_info.AppNum = f"{int(self.app_num.text()):06d}" self.studyindex_info.StudyID = self.study_id.text() self.studyindex_info.NumSubj = int(self.study_numsubjects.text()) self.studyindex_info.NECGSubj = int( self.study_aecgpersubject.text()) self.studyindex_info.TotalECGs = int(self.study_numaecg.text()) anmethod = aecg.tools.indexer.AnnotationMethod( self.study_annotation_aecg_cb.currentIndex()) self.studyindex_info.AnMethod = anmethod.name self.studyindex_info.AnLead =\ self.study_annotation_lead_cb.currentText() self.studyindex_info.AnNbeats = int( self.study_annotation_numbeats.text()) # Calculate stats study_stats = aecg.tools.indexer.StudyStats( self.studyindex_info, res) # Save to file aecg.tools.indexer.save_study_index(self.studyindex_info, res, study_stats) validator_data_ready = Signal() def save_validator_results_and_load_index(self, res): self.save_validator_results(res) self.validator_data_ready.emit() def indexer_validator_results(self, res): self.studyindex_df = pd.concat([self.studyindex_df, res], ignore_index=True) def subindex_thread_complete(self): return def index_directory_thread_complete(self): tmp = self.validator_pl.text().replace("ETA:", "Completed: ").replace( "Validating", "Validated") self.validator_pl.setText(tmp) self.val_button.setEnabled(True) self.cancel_val_button.setEnabled(False) self.validator_layout_container.setEnabled(True) def index_directory(self, progress_callback): self.lastindexing_starttime = datetime.datetime.now() self.indexing_timer.start() studyindex_df = [] n_cores = os.cpu_count() aecg_schema = None if self.aecg_schema_filename.text() != "": aecg_schema = self.aecg_schema_filename.text() if self.parallel_processing_cb.isChecked(): studyindex_df = self.directory_indexer.index_directory( self.save_all_intervals_cb.isChecked(), aecg_schema, n_cores, progress_callback) else: studyindex_df = self.directory_indexer.index_directory( self.save_all_intervals_cb.isChecked(), aecg_schema, 1, progress_callback) return studyindex_df def importstudy_dialog(self): dirName = os.path.normpath(self.study_dir.text()) if dirName != "": if os.path.exists(dirName): self.directory_indexer = aecg.indexing.DirectoryIndexer() self.directory_indexer.set_aecg_dir( dirName, self.update_search_progress) self.validator_pb.setMaximum(self.directory_indexer.num_files) self.validator_pb.reset() self.stop_indexing = False self.validator_layout_container.setEnabled(False) self.val_button.setEnabled(False) self.cancel_val_button.setEnabled(True) self.validator_worker = Worker(self.index_directory) self.validator_worker.signals.result.connect( self.save_validator_results_and_load_index) self.validator_worker.signals.finished.connect( self.index_directory_thread_complete) self.validator_worker.signals.progress.connect( self.update_progress) # Execute self.threadpool.start(self.validator_worker) else: QMessageBox.critical( self, "Directory not found", f"Specified study directory not found:\n{dirName}") else: QMessageBox.critical(self, "Import study error", "Study directory cannot be empty") def cancel_validator(self): self.cancel_val_button.setEnabled(False) self.stop_indexing = True self.directory_indexer.cancel_indexing = True self.threadpool.waitForDone(3000) self.val_button.setEnabled(True) def select_study_dir(self): cd = self.study_dir.text() if cd == "": cd = "." dir = QFileDialog.getExistingDirectory( self, "Open Directory", cd, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) if dir != "": self.study_dir.setText(dir)
class Blinker(QThread): """ The Blinker class is used to draw a box around a given element at a specified frequency for a small amount of time. Because the box sometimes disappears on its own, this can cause a blinking affect. """ componentNotFound = Signal(str) INTERVAL_MILLIS = 250 DURATION_MILLIS = 10_000 colors = ["red", "green", "blue"] curColorIdx = 0 def __init__(self, pid: int, backend: str, superToken: 'SuperToken') -> None: """ Creates a blinker that will draw a box around the component represented by SuperToken periodically if the component can be found. :param pid: The id of the target application's process. :type pid: int :param backend: either "win32" or "uia" depending on target application. :type backend: str :param superToken: The supertoken that represents the component that we want to draw a box around. :return: None :retype: NoneType """ QThread.__init__(self) self._pid = pid self._backend = backend self._superToken = superToken self._color = Blinker.colors[Blinker.curColorIdx % len(Blinker.colors)] Blinker.curColorIdx += 1 def run(self) -> None: """ DO NOT CALL THIS METHOD! This method is called automatically when the start() method is called. This method searches for a Component in the target GUI by traversing :return: None :rtype: NoneType """ self._process = psutil.Process(self._pid) app = Application(backend=self._backend) app.setProcess(self._process) options = { MatchOption.ExactToken, MatchOption.CloseToken, MatchOption.PWABestMatch } finder = ComponentFinder(app, options) try: component = finder.find(self._superToken) except ComponentNotFoundException: self.componentNotFound.emit( "The selected component could not be\nfound in the target GUI." ) else: self.initiateBlinkSequence(component) def initiateBlinkSequence(self, component: 'Component') -> None: """ Starts the blink sequence by setting timers and executing an event loop. NOTE: Because this function executes an event loop, it is blocking. :param component: The component to select. :type component: Component :return: None :rtype: NoneType """ self._component = component self._component.top_level_parent().set_focus() self._timer = QTimer(self) self._timer.timeout.connect(lambda: self.tick()) self._stopWatch = QElapsedTimer() self._timer.start(Blinker.INTERVAL_MILLIS) self._stopWatch.start() self.exec_() def tick(self) -> None: """ Draws an outline around the component of interest. :return: None :rtype: NoneType """ try: self._component.draw_outline(colour=self._color, thickness=5) self._component.top_level_parent().set_focus() except: pass if self._stopWatch.hasExpired(Blinker.DURATION_MILLIS): self.stop() def stop(self) -> None: """ Stops the blinker regardless of whether it was running or not. :return: None :rtype: NoneType """ try: self._timer.stop() except: pass finally: self.quit()
class DSRViewer(QObject): save_graph_signal = Signal() close_window_signal = Signal() reset_viewer = Signal(QWidget) def __init__(self, window, G, options, main=None): super().__init__() self.timer = QTimer() self.alive_timer = QElapsedTimer() self.g = G self.window = window self.view_menu = QMenu() self.file_menu = QMenu() self.forces_menu = QMenu() self.main_widget = window self.docks = {} self.widgets = {} self.widgets_by_type = {} available_geometry = QApplication.desktop().availableGeometry() window.move((available_geometry.width() - window.width()) / 2, (available_geometry.height() - window.height()) / 2) self.__initialize_file_menu() viewMenu = window.menuBar().addMenu(window.tr("&View")) forcesMenu = window.menuBar().addMenu(window.tr("&Forces")) actionsMenu = window.menuBar().addMenu(window.tr("&Actions")) restart_action = actionsMenu.addAction("Restart") self.__initialize_views(options, main) self.alive_timer.start() self.timer.start(500) # self.init() #intialize processor number # connect(timer, SIGNAL(timeout()), self, SLOT(compute())) def __del__(self): settings = QSettings("RoboComp", "DSR") settings.beginGroup("MainWindow") settings.setValue("size", self.window.size()) settings.setValue("pos", self.window.pos()) settings.endGroup() def get_widget_by_type(self, widget_type) -> QWidget: if widget_type in self.widgets_by_type: return self.widgets_by_type[widget_type].widget return None def get_widget_by_name(self, name) -> QWidget: if name in self.widgets: return self.widgets[name].widget return None def add_custom_widget_to_dock(self, name, custom_view): widget_c = WidgetContainer() widget_c.name = name widget_c.type = View.none widget_c.widget = custom_view self.widgets[name] = widget_c self.__create_dock_and_menu(name, custom_view) # Tabification of current docks previous = None for dock_name, dock in self.docks.items(): if previous and previous != dock: self.window.tabifyDockWidget(previous, self.docks[name]) break previous = dock self.docks[name].raise_() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.close_window_signal.emit() # SLOTS def save_graph_slot(self, state): self.save_graph_signal.emit() def restart_app(self, state): pass def switch_view(self, state, container): widget = container.widget dock = container.dock if state: widget.blockSignals(True) dock.hide() else: widget.blockSignals(False) self.reset_viewer.emit(widget) dock.show() dock.raise_() def compute(self): pass def __create_dock_and_menu(self, name, view): # TODO: Check if name exists in docks if name in self.docks: dock_widget = self.docks[name] self.window.removeDockWidget(dock_widget) else: dock_widget = QDockWidget(name) new_action = QAction(name, self) new_action.setStatusTip("Create a new file") new_action.setCheckable(True) new_action.setChecked(True) new_action.triggered.connect( lambda state: self.switch_view(state, self.widgets[name])) self.view_menu.addAction(new_action) self.docks[name] = dock_widget self.widgets[name].dock = dock_widget dock_widget.setWidget(view) dock_widget.setAllowedAreas(Qt.AllDockWidgetAreas) self.window.addDockWidget(Qt.RightDockWidgetArea, dock_widget) dock_widget.raise_() def __initialize_views(self, options, central): # Create docks view and main widget valid_options = [(View.graph, "Graph"), (View.tree, "Tree"), (View.osg, "3D"), (View.scene, "2D")] # Creation of docks and mainwidget for widget_type, widget_name in valid_options: if widget_type == central and central != View.none: viewer = self.__create_widget(widget_type) self.window.setCentralWidget(viewer) widget_c = WidgetContainer() widget_c.widget = viewer widget_c.name = widget_name widget_c.type = widget_type self.widgets[widget_name] = widget_c self.widgets_by_type[widget_type] = widget_c self.main_widget = viewer elif options & widget_type: viewer = self.__create_widget(widget_type) widget_c = WidgetContainer() widget_c.widget = viewer widget_c.name = widget_name widget_c.type = widget_type self.widgets[widget_name] = widget_c self.widgets_by_type[widget_type] = widget_c self.__create_dock_and_menu(widget_name, viewer) if View.graph in self.widgets_by_type: new_action = QAction("Animation", self) new_action.setStatusTip("Toggle animation") new_action.setCheckable(True) new_action.setChecked(False) self.forces_menu.addAction(new_action) new_action.triggered.connect(lambda: self.widgets_by_type[ View.graph].widget.toggle_animation(True)) # Tabification of current docks previous = None for dock_name, dock_widget in self.docks.items(): if previous: self.window.tabifyDockWidget(previous, dock_widget) previous = dock_widget # Connection of tree to graph signals if "Tree" in self.docks: if self.main_widget: graph_widget = self.main_widget if graph_widget: tree_widget = self.docks["Tree"].widget() tree_widget.node_check_state_changed_signal.connect( lambda node_id: graph_widget.hide_show_node_SLOT( node_id, 2)) if len(self.docks) > 0 or central != None: self.window.show() else: self.window.showMinimized() def __initialize_file_menu(self): file_menu = self.window.menuBar().addMenu(self.window.tr("&File")) file_submenu = file_menu.addMenu("Save") save_action = QAction("Save", self) file_submenu.addAction(save_action) rgbd = QAction("RGBD", self) rgbd.setCheckable(True) rgbd.setChecked(False) file_submenu.addAction(rgbd) laser = QAction("Laser", self) laser.setCheckable(True) laser.setChecked(False) file_submenu.addAction(laser) # save_action save_action.triggered.connect( lambda: self.__save_json_file(rgbd, laser)) def __save_json_file(self, rgbd, laser): file_name = QFileDialog.getSaveFileName( None, "Save file", "/home/robocomp/robocomp/components/dsr-graph/etc", "JSON Files (*.json)", None, QFileDialog.Option.DontUseNativeDialog) skip_content = [] if not rgbd.isChecked(): skip_content.push_back("rgbd") if not laser.isChecked(): skip_content.push_back("laser") self.g.write_to_json_file(file_name.toStdString(), skip_content) print("File saved") def __create_widget(self, widget_type): widget_view = None if widget_type == View.graph: widget_view = GraphViewer(self.g) elif widget_type == View.osg: widget_view = OSG3dViewer(self.g, 1, 1) elif widget_type == View.tree: widget_view = TreeViewer(self.g) elif widget_type == View.scene: widget_view = QScene2dViewer(self.g) elif widget_type == View.none: widget_view = None # self.reset_viewer.connect(self.reload) return widget_view
class Ui_Query(QMainWindow): about_close = Signal() def __init__(self, totaltime=0): super().__init__() # 在主窗口左侧添加题干和选项 lsplitter = QSplitter(Qt.Vertical) self.question_panel = Question_panel() lsplitter.addWidget(self.question_panel) # 在主窗口右侧添加绘图和文本编辑,并把比例设置为3比1 rsplitter = QSplitter(Qt.Vertical) self.painter = QGraphicsView(rsplitter) self.note = QTextEdit(rsplitter) rsplitter.setStretchFactor(0, 3) rsplitter.setStretchFactor(1, 1) # 添加插件的顺序会导致左右不同 mainSplitter = QSplitter(Qt.Horizontal) mainSplitter.addWidget(lsplitter) mainSplitter.addWidget(rsplitter) self.setCentralWidget(mainSplitter) # 点击暂停按钮切换图标和停继时间 self.question_panel.ui.pushButtonPause.clicked.connect( self.toggle_play_and_pause) self.totaltime = totaltime # 当前试卷答题总时间 self.elapsed_time = QElapsedTimer() # 答题总时间的计时器 self.paused = False # 默认刚打开时,还未暂停时间 self.setTime() # 更新时间显示 self.timer = QTimer() self.timer.timeout.connect(self.setTime) # 每秒更新时间显示的定时器 def getDateTime(self): """获取当前日期及时间""" return QDateTime.currentDateTime().toTime_t() def setTime(self): """更新时间显示""" time = (self.elapsed_time.elapsed() + self.totaltime) // 1000 # totaltime还包括上次答题所用的时间 hours = time // 3600 minutes = time % 3600 // 60 seconds = time % 3600 % 60 self.question_panel.ui.labelTimeUsed.setText( f"{hours}:{minutes:02}:{seconds:02}") # :02表示补全数字到2位,填充0 def toggle_play_and_pause(self): if self.paused: # 继续计时 # 把继续图标换回暂停图标 icon_pause = QIcon() icon_pause.addPixmap( QPixmap(":/icons/icons/ic_pause_black_48dp.png"), QIcon.Normal, QIcon.Off, ) self.question_panel.ui.pushButtonPause.setIcon(icon_pause) # 重新开始计时,包括1秒定时器和总时间计时器 self.timer.start(1000) self.elapsed_time.restart() # 把自身状态标记为非暂停状态 self.paused = False else: # 暂停计时 icon_play = QIcon() icon_play.addPixmap( QPixmap(":/icons/icons/ic_play_arrow_black_48dp.png"), QIcon.Normal, QIcon.Off, ) self.question_panel.ui.pushButtonPause.setIcon(icon_play) self.totaltime += self.elapsed_time.elapsed() self.timer.stop() self.paused = True def closeEvent(self, event): self.totaltime += self.elapsed_time.elapsed() # 关闭前更新答题总时间 self.about_close.emit()