2026-01-21 18:21:56 +08:00
|
|
|
|
import sys
|
2026-01-21 21:55:34 +08:00
|
|
|
|
import os
|
|
|
|
|
|
from PySide6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
|
|
|
|
|
|
QPushButton, QLabel, QDialog, QFormLayout,
|
|
|
|
|
|
QSpinBox, QCheckBox, QSlider, QGraphicsOpacityEffect)
|
|
|
|
|
|
from PySide6.QtCore import Qt, QTimer, QPoint, QPropertyAnimation, QEasingCurve, QSequentialAnimationGroup, QVariantAnimation
|
|
|
|
|
|
from PySide6.QtGui import QFont, QColor, QMouseEvent
|
|
|
|
|
|
from PySide6.QtMultimedia import QSoundEffect
|
2026-01-21 18:21:56 +08:00
|
|
|
|
from PySide6.QtCore import QUrl
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigDialog(QDialog):
|
2026-01-21 21:55:34 +08:00
|
|
|
|
def __init__(self, parent=None, config=None):
|
2026-01-21 18:21:56 +08:00
|
|
|
|
super().__init__(parent)
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.setWindowTitle("配置")
|
|
|
|
|
|
self.config = config
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
2026-01-22 10:14:19 +08:00
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
|
QDialog {
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
QLabel {
|
|
|
|
|
|
color: #333333;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
QSpinBox, QCheckBox {
|
|
|
|
|
|
padding: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
2026-01-21 21:55:34 +08:00
|
|
|
|
layout = QFormLayout(self)
|
|
|
|
|
|
|
|
|
|
|
|
# 自定义倒计时
|
|
|
|
|
|
self.custom_time = QSpinBox()
|
|
|
|
|
|
self.custom_time.setRange(1, 3600)
|
|
|
|
|
|
self.custom_time.setValue(self.config['duration'])
|
|
|
|
|
|
|
|
|
|
|
|
btn_start = QPushButton("开始倒计时")
|
|
|
|
|
|
btn_start.setStyleSheet("background-color: #27ae60; color: white;")
|
|
|
|
|
|
btn_start.clicked.connect(self.start_countdown)
|
|
|
|
|
|
|
|
|
|
|
|
time_layout = QHBoxLayout()
|
|
|
|
|
|
time_layout.addWidget(self.custom_time)
|
|
|
|
|
|
time_layout.addWidget(btn_start)
|
|
|
|
|
|
time_widget = QWidget()
|
|
|
|
|
|
time_widget.setLayout(time_layout)
|
|
|
|
|
|
layout.addRow("自定义秒数:", time_widget)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
# 提前告警
|
|
|
|
|
|
self.alarm_offset = QSpinBox()
|
|
|
|
|
|
self.alarm_offset.setRange(0, 300)
|
|
|
|
|
|
self.alarm_offset.setValue(self.config['alarm_offset'])
|
|
|
|
|
|
layout.addRow("提前告警(秒):", self.alarm_offset)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
# 窗口置顶
|
|
|
|
|
|
self.stay_on_top = QCheckBox()
|
|
|
|
|
|
self.stay_on_top.setChecked(self.config['stay_on_top'])
|
|
|
|
|
|
layout.addRow("窗口置顶:", self.stay_on_top)
|
|
|
|
|
|
|
|
|
|
|
|
# 透明度
|
|
|
|
|
|
self.opacity = QSlider(Qt.Horizontal)
|
|
|
|
|
|
self.opacity.setRange(10, 100)
|
|
|
|
|
|
self.opacity.setValue(int(self.config['opacity'] * 100))
|
|
|
|
|
|
layout.addRow("透明度:", self.opacity)
|
|
|
|
|
|
|
|
|
|
|
|
# 微缩窗口位置
|
|
|
|
|
|
self.right_margin = QSpinBox()
|
|
|
|
|
|
self.right_margin.setRange(0, 1000)
|
|
|
|
|
|
self.right_margin.setValue(self.config['right_margin'])
|
|
|
|
|
|
layout.addRow("距离右边缘(像素):", self.right_margin)
|
|
|
|
|
|
|
|
|
|
|
|
self.top_margin = QSpinBox()
|
|
|
|
|
|
self.top_margin.setRange(0, 1000)
|
|
|
|
|
|
self.top_margin.setValue(self.config['top_margin'])
|
|
|
|
|
|
layout.addRow("距离上边缘(像素):", self.top_margin)
|
|
|
|
|
|
|
|
|
|
|
|
# 按钮组
|
|
|
|
|
|
btn_test = QPushButton("测试告警音")
|
|
|
|
|
|
btn_test.clicked.connect(self.parent().play_alarm)
|
|
|
|
|
|
layout.addRow(btn_test)
|
|
|
|
|
|
|
|
|
|
|
|
btn_exit = QPushButton("退出程序")
|
|
|
|
|
|
btn_exit.setStyleSheet("background-color: #ff4d4f; color: white;")
|
|
|
|
|
|
btn_exit.clicked.connect(QApplication.instance().quit)
|
|
|
|
|
|
layout.addRow(btn_exit)
|
|
|
|
|
|
|
|
|
|
|
|
save_btn = QPushButton("确认并保存")
|
|
|
|
|
|
save_btn.clicked.connect(self.save_and_close)
|
|
|
|
|
|
layout.addRow(save_btn)
|
|
|
|
|
|
|
|
|
|
|
|
def save_and_close(self):
|
|
|
|
|
|
self.config['duration'] = self.custom_time.value()
|
|
|
|
|
|
self.config['alarm_offset'] = self.alarm_offset.value()
|
|
|
|
|
|
self.config['stay_on_top'] = self.stay_on_top.isChecked()
|
|
|
|
|
|
self.config['opacity'] = self.opacity.value() / 100.0
|
|
|
|
|
|
self.config['right_margin'] = self.right_margin.value()
|
|
|
|
|
|
self.config['top_margin'] = self.top_margin.value()
|
|
|
|
|
|
self.accept()
|
|
|
|
|
|
|
|
|
|
|
|
def start_countdown(self):
|
|
|
|
|
|
self.save_and_close()
|
|
|
|
|
|
self.parent().start_countdown(self.custom_time.value())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TimerApp(QWidget):
|
2026-01-21 18:21:56 +08:00
|
|
|
|
def __init__(self):
|
|
|
|
|
|
super().__init__()
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.config = {
|
2026-01-22 11:23:15 +08:00
|
|
|
|
'duration': 240,
|
2026-01-21 21:55:34 +08:00
|
|
|
|
'alarm_offset': 30,
|
|
|
|
|
|
'stay_on_top': True,
|
|
|
|
|
|
'opacity': 1.0,
|
2026-01-22 13:49:51 +08:00
|
|
|
|
'right_margin': 40, # 距离右边缘
|
|
|
|
|
|
'top_margin': 30 # 距离上边缘
|
2026-01-21 21:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
self.remaining_time = 0
|
2026-01-21 18:21:56 +08:00
|
|
|
|
self.is_running = False
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.state = "NORMAL" # NORMAL or MINI
|
|
|
|
|
|
self.drag_position = QPoint()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
|
|
|
|
|
self.init_ui()
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.setup_timers()
|
|
|
|
|
|
self.setup_audio()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
self.apply_config()
|
2026-01-21 21:55:34 +08:00
|
|
|
|
|
2026-01-21 18:21:56 +08:00
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
self.setWindowFlags(Qt.FramelessWindowHint)
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.setAttribute(Qt.WA_TranslucentBackground)
|
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
|
QWidget#MainFrame {
|
2026-01-22 11:23:15 +08:00
|
|
|
|
background-color: #e8f4f8;
|
2026-01-21 21:55:34 +08:00
|
|
|
|
border-radius: 15px;
|
2026-01-22 11:23:15 +08:00
|
|
|
|
border: 2px solid #b0d4e3;
|
2026-01-21 18:21:56 +08:00
|
|
|
|
}
|
2026-01-22 11:23:15 +08:00
|
|
|
|
QLabel { color: #2c3e50; font-family: 'Segoe UI', Arial; }
|
2026-01-21 18:21:56 +08:00
|
|
|
|
QPushButton {
|
2026-01-22 11:23:15 +08:00
|
|
|
|
background-color: #5dade2;
|
2026-01-21 18:21:56 +08:00
|
|
|
|
color: white;
|
2026-01-21 21:55:34 +08:00
|
|
|
|
border-radius: 5px;
|
|
|
|
|
|
padding: 8px;
|
2026-01-21 18:21:56 +08:00
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
2026-01-22 11:23:15 +08:00
|
|
|
|
QPushButton:hover { background-color: #3498db; }
|
2026-01-21 18:21:56 +08:00
|
|
|
|
""")
|
2026-01-21 21:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
self.main_layout = QVBoxLayout(self)
|
|
|
|
|
|
self.frame = QWidget()
|
|
|
|
|
|
self.frame.setObjectName("MainFrame")
|
|
|
|
|
|
self.main_layout.addWidget(self.frame)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.content_layout = QVBoxLayout(self.frame)
|
|
|
|
|
|
|
|
|
|
|
|
# 标题标签
|
|
|
|
|
|
self.label_title = QLabel("述职倒计时")
|
|
|
|
|
|
self.label_title.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.label_title.setFont(QFont("Microsoft YaHei", 24, QFont.Bold))
|
|
|
|
|
|
self.content_layout.addWidget(self.label_title)
|
|
|
|
|
|
|
|
|
|
|
|
# 倒计时显示
|
|
|
|
|
|
self.label_time = QLabel("00:00")
|
|
|
|
|
|
self.label_time.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.label_time.setFont(QFont("Consolas", 60, QFont.Bold))
|
|
|
|
|
|
self.label_time.setMinimumSize(120, 40) # 设置最小尺寸
|
|
|
|
|
|
self.label_time.setMaximumSize(480, 180) # 设置最大尺寸
|
|
|
|
|
|
self.content_layout.addWidget(self.label_time)
|
|
|
|
|
|
|
|
|
|
|
|
# 按钮区域
|
|
|
|
|
|
self.btn_area = QWidget()
|
|
|
|
|
|
self.btn_layout = QHBoxLayout(self.btn_area)
|
2026-01-22 11:23:15 +08:00
|
|
|
|
self.btn_4m = QPushButton("4分钟")
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.btn_other = QPushButton("其它")
|
|
|
|
|
|
|
2026-01-22 11:23:15 +08:00
|
|
|
|
self.btn_4m.clicked.connect(lambda: self.start_countdown(240))
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.btn_other.clicked.connect(self.open_config)
|
|
|
|
|
|
|
2026-01-22 11:23:15 +08:00
|
|
|
|
self.btn_layout.addWidget(self.btn_4m)
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.btn_layout.addWidget(self.btn_other)
|
|
|
|
|
|
self.content_layout.addWidget(self.btn_area)
|
|
|
|
|
|
|
|
|
|
|
|
self.set_normal_state()
|
|
|
|
|
|
|
|
|
|
|
|
def setup_timers(self):
|
|
|
|
|
|
self.timer = QTimer(self)
|
|
|
|
|
|
self.timer.timeout.connect(self.update_countdown)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.mini_timer = QTimer(self)
|
|
|
|
|
|
self.mini_timer.setSingleShot(True)
|
|
|
|
|
|
self.mini_timer.timeout.connect(self.set_mini_state)
|
|
|
|
|
|
|
|
|
|
|
|
def setup_audio(self):
|
|
|
|
|
|
self.sound = QSoundEffect()
|
2026-01-22 10:14:19 +08:00
|
|
|
|
|
|
|
|
|
|
# 优先从外部目录加载音频文件
|
2026-01-21 21:55:34 +08:00
|
|
|
|
if getattr(sys, 'frozen', False):
|
2026-01-22 10:14:19 +08:00
|
|
|
|
# exe所在目录
|
|
|
|
|
|
external_path = os.path.dirname(sys.executable)
|
2026-01-21 21:55:34 +08:00
|
|
|
|
else:
|
2026-01-22 10:14:19 +08:00
|
|
|
|
# 当前工作目录
|
|
|
|
|
|
external_path = os.getcwd()
|
|
|
|
|
|
|
|
|
|
|
|
alarm_file = os.path.join(external_path, "alarm.wav")
|
|
|
|
|
|
if not os.path.exists(alarm_file):
|
|
|
|
|
|
# 外部没有则从打包的临时目录加载
|
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
|
alarm_file = os.path.join(sys._MEIPASS, "alarm.wav")
|
|
|
|
|
|
else:
|
|
|
|
|
|
alarm_file = os.path.join(os.path.dirname(__file__), "alarm.wav")
|
|
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
if os.path.exists(alarm_file):
|
|
|
|
|
|
self.sound.setSource(QUrl.fromLocalFile(alarm_file))
|
|
|
|
|
|
self.sound.setLoopCount(3)
|
|
|
|
|
|
self.sound.setVolume(0.8)
|
|
|
|
|
|
|
|
|
|
|
|
def apply_config(self):
|
|
|
|
|
|
if self.config['stay_on_top']:
|
|
|
|
|
|
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.setWindowOpacity(self.config['opacity'])
|
|
|
|
|
|
self.show()
|
|
|
|
|
|
|
|
|
|
|
|
def set_normal_state(self):
|
|
|
|
|
|
self.state = "NORMAL"
|
|
|
|
|
|
# 解除固定尺寸限制
|
|
|
|
|
|
self.setMinimumSize(0, 0)
|
|
|
|
|
|
self.setMaximumSize(16777215, 16777215)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.resize(500, 300)
|
|
|
|
|
|
self.btn_area.show()
|
|
|
|
|
|
self.label_title.show()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
# 恢复布局边距
|
|
|
|
|
|
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
|
|
self.content_layout.setContentsMargins(10, 10, 10, 10)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.label_time.setFont(QFont("Consolas", 60, QFont.Bold))
|
|
|
|
|
|
self.label_time.setFixedSize(480, 180)
|
|
|
|
|
|
self.center_on_screen()
|
|
|
|
|
|
|
|
|
|
|
|
def set_mini_state(self):
|
|
|
|
|
|
if not self.is_running:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.state = "MINI"
|
|
|
|
|
|
self.btn_area.hide()
|
|
|
|
|
|
self.label_title.hide()
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 关键:将布局边距设为0,否则控件会被挤出窗口
|
|
|
|
|
|
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
self.content_layout.setContentsMargins(5, 5, 5, 5) # 留一点小边距即可
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 关键:先设置内部控件尺寸
|
|
|
|
|
|
self.label_time.setFont(QFont("Consolas", 18, QFont.Bold))
|
|
|
|
|
|
self.label_time.setFixedSize(110, 40)
|
|
|
|
|
|
|
|
|
|
|
|
# 3. 关键:强制设置整个窗口的固定大小
|
|
|
|
|
|
self.setFixedSize(120, 50)
|
|
|
|
|
|
|
|
|
|
|
|
# 定位
|
|
|
|
|
|
screen = QApplication.primaryScreen()
|
|
|
|
|
|
screen_geometry = screen.availableGeometry()
|
|
|
|
|
|
x = screen_geometry.width() - self.width() - self.config['right_margin']
|
|
|
|
|
|
y = self.config['top_margin']
|
|
|
|
|
|
self.move(x, y)
|
2026-01-22 14:17:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 将焦点还给上一个进程
|
|
|
|
|
|
self.return_focus_to_previous_window()
|
2026-01-21 21:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
def center_on_screen(self):
|
|
|
|
|
|
screen = QApplication.primaryScreen()
|
|
|
|
|
|
screen_geometry = screen.availableGeometry()
|
|
|
|
|
|
x = (screen_geometry.width() - self.width()) // 2
|
|
|
|
|
|
y = (screen_geometry.height() - self.height()) // 2
|
|
|
|
|
|
self.move(x, y)
|
|
|
|
|
|
|
|
|
|
|
|
def mousePressEvent(self, event: QMouseEvent):
|
|
|
|
|
|
if event.button() == Qt.LeftButton:
|
|
|
|
|
|
self.drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
|
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
|
|
|
|
def mouseMoveEvent(self, event: QMouseEvent):
|
|
|
|
|
|
if event.buttons() == Qt.LeftButton and self.drag_position:
|
|
|
|
|
|
self.move(event.globalPosition().toPoint() - self.drag_position)
|
|
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
|
|
|
|
def mouseDoubleClickEvent(self, event: QMouseEvent):
|
|
|
|
|
|
if self.state == "MINI":
|
|
|
|
|
|
self.pause_countdown()
|
|
|
|
|
|
self.set_normal_state()
|
|
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
|
|
|
|
def start_countdown(self, seconds):
|
|
|
|
|
|
self.remaining_time = seconds
|
|
|
|
|
|
self.update_label()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.timer.stop()
|
|
|
|
|
|
self.mini_timer.stop()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
if hasattr(self, 'color_anim') and self.color_anim is not None:
|
|
|
|
|
|
self.color_anim.stop()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-22 11:23:15 +08:00
|
|
|
|
self.label_time.setStyleSheet("color: #2c3e50;")
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
|
|
|
|
|
self.is_running = True
|
|
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.mini_timer.timeout.disconnect()
|
|
|
|
|
|
self.mini_timer.timeout.connect(self.start_timer)
|
|
|
|
|
|
self.mini_timer.start(2000)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.set_mini_state()
|
|
|
|
|
|
|
|
|
|
|
|
def start_timer(self):
|
|
|
|
|
|
# 开始倒计时
|
2026-01-21 18:21:56 +08:00
|
|
|
|
self.timer.start(1000)
|
2026-01-21 21:55:34 +08:00
|
|
|
|
|
2026-01-21 18:21:56 +08:00
|
|
|
|
def pause_countdown(self):
|
|
|
|
|
|
self.timer.stop()
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.mini_timer.stop()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
self.is_running = False
|
2026-01-21 21:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
def update_countdown(self):
|
|
|
|
|
|
if self.remaining_time > 0:
|
|
|
|
|
|
self.remaining_time -= 1
|
|
|
|
|
|
self.update_label()
|
|
|
|
|
|
if self.remaining_time == self.config['alarm_offset']:
|
|
|
|
|
|
self.play_alarm()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
else:
|
|
|
|
|
|
self.timer.stop()
|
|
|
|
|
|
self.is_running = False
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.show_finished_anim()
|
|
|
|
|
|
|
|
|
|
|
|
def update_label(self):
|
|
|
|
|
|
m, s = divmod(self.remaining_time, 60)
|
|
|
|
|
|
self.label_time.setText(f"{m:02d}:{s:02d}")
|
|
|
|
|
|
|
|
|
|
|
|
def play_alarm(self):
|
|
|
|
|
|
if hasattr(self, 'sound') and self.sound.source().isValid():
|
|
|
|
|
|
self.sound.play()
|
|
|
|
|
|
|
|
|
|
|
|
def show_finished_anim(self):
|
|
|
|
|
|
self.label_time.setText("时间已到")
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
if self.label_time.graphicsEffect():
|
|
|
|
|
|
self.label_time.graphicsEffect().deleteLater()
|
|
|
|
|
|
|
|
|
|
|
|
if self.state == "MINI":
|
|
|
|
|
|
self.color_anim = QVariantAnimation(self)
|
|
|
|
|
|
self.color_anim.setDuration(2000)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.color_anim.setStartValue(QColor("#ff0000"))
|
|
|
|
|
|
self.color_anim.setKeyValueAt(0.5, QColor("#ffff00"))
|
|
|
|
|
|
self.color_anim.setEndValue(QColor("#ff0000"))
|
|
|
|
|
|
|
|
|
|
|
|
def update_style(color):
|
|
|
|
|
|
self.label_time.setStyleSheet(f"color: {color.name()};")
|
|
|
|
|
|
|
|
|
|
|
|
self.color_anim.valueChanged.connect(update_style)
|
|
|
|
|
|
self.color_anim.setLoopCount(-1)
|
|
|
|
|
|
self.color_anim.start()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
|
|
|
|
|
else:
|
2026-01-21 21:55:34 +08:00
|
|
|
|
jump_offset = -20
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.jump_anim = QPropertyAnimation(self.label_time, b"pos")
|
|
|
|
|
|
self.jump_anim.setDuration(200)
|
|
|
|
|
|
curr_pos = self.label_time.pos()
|
|
|
|
|
|
self.jump_anim.setKeyValueAt(0, curr_pos)
|
|
|
|
|
|
self.jump_anim.setKeyValueAt(0.5, curr_pos + QPoint(0, jump_offset))
|
|
|
|
|
|
self.jump_anim.setKeyValueAt(1, curr_pos)
|
|
|
|
|
|
self.jump_anim.setEasingCurve(QEasingCurve.OutBounce)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
opacity_effect = QGraphicsOpacityEffect(self.label_time)
|
|
|
|
|
|
self.label_time.setGraphicsEffect(opacity_effect)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.flash_anim = QPropertyAnimation(opacity_effect, b"opacity")
|
|
|
|
|
|
self.flash_anim.setDuration(1000)
|
|
|
|
|
|
self.flash_anim.setStartValue(1.0)
|
|
|
|
|
|
self.flash_anim.setEndValue(0.3)
|
|
|
|
|
|
self.flash_anim.setLoopCount(-1)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
self.anim_group = QSequentialAnimationGroup()
|
|
|
|
|
|
self.anim_group.addAnimation(self.jump_anim)
|
|
|
|
|
|
self.anim_group.addAnimation(self.flash_anim)
|
|
|
|
|
|
self.anim_group.start()
|
|
|
|
|
|
|
2026-01-22 14:17:09 +08:00
|
|
|
|
def return_focus_to_previous_window(self):
|
|
|
|
|
|
# 清除当前窗口的焦点
|
|
|
|
|
|
self.clearFocus()
|
|
|
|
|
|
|
|
|
|
|
|
# 将窗口置于其他窗口下方
|
|
|
|
|
|
self.lower()
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试激活其他窗口
|
|
|
|
|
|
app = QApplication.instance()
|
|
|
|
|
|
windows = app.topLevelWindows()
|
|
|
|
|
|
|
|
|
|
|
|
# 找到不是当前窗口的其他窗口
|
|
|
|
|
|
for window in windows:
|
|
|
|
|
|
if window != self.windowHandle() and window.isVisible():
|
|
|
|
|
|
# 尝试激活其他窗口
|
|
|
|
|
|
window.requestActivate()
|
|
|
|
|
|
break
|
|
|
|
|
|
|
2026-01-21 18:21:56 +08:00
|
|
|
|
def open_config(self):
|
2026-01-21 21:55:34 +08:00
|
|
|
|
dialog = ConfigDialog(self, self.config)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
if dialog.exec() == QDialog.Accepted:
|
|
|
|
|
|
self.apply_config()
|
2026-01-21 21:55:34 +08:00
|
|
|
|
custom_seconds = dialog.custom_time.value()
|
|
|
|
|
|
if custom_seconds != self.config['duration']:
|
|
|
|
|
|
self.start_countdown(custom_seconds)
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
app = QApplication(sys.argv)
|
2026-01-21 21:55:34 +08:00
|
|
|
|
app.setApplicationName("述职计时器")
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
# 设置窗口图标
|
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
|
base_path = sys._MEIPASS
|
|
|
|
|
|
else:
|
|
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
|
|
icon_file = os.path.join(base_path, "Timer.ico")
|
|
|
|
|
|
if os.path.exists(icon_file):
|
|
|
|
|
|
from PySide6.QtGui import QIcon
|
|
|
|
|
|
app.setWindowIcon(QIcon(icon_file))
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
2026-01-21 21:55:34 +08:00
|
|
|
|
timer_app = TimerApp()
|
|
|
|
|
|
timer_app.show()
|
2026-01-21 18:21:56 +08:00
|
|
|
|
|
|
|
|
|
|
sys.exit(app.exec())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2026-01-21 21:55:34 +08:00
|
|
|
|
main()
|