From 2415e097486ee35fbf746dfc2a91af909752ff04 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Thu, 20 Nov 2025 20:58:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=8E=86=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/calendar_floating_widget.py | 482 +++++++++++++++++++++++++++++ src/ui/word_style_ui.py | 7 + src/word_main_window.py | 48 ++- 3 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 src/ui/calendar_floating_widget.py diff --git a/src/ui/calendar_floating_widget.py b/src/ui/calendar_floating_widget.py new file mode 100644 index 0000000..f8ee8ca --- /dev/null +++ b/src/ui/calendar_floating_widget.py @@ -0,0 +1,482 @@ +# -*- coding: utf-8 -*- + +""" +日历悬浮窗口模块 +提供一个可拖拽的日历悬浮窗口,用于在应用程序中显示和选择日期 +""" + +import sys +from PyQt5.QtWidgets import ( + QWidget, QCalendarWidget, QVBoxLayout, QHBoxLayout, + QPushButton, QLabel, QFrame +) +from PyQt5.QtCore import QDate, Qt, pyqtSignal, QPoint +from PyQt5.QtGui import QFont + +# 导入主题管理器 +from .theme_manager import theme_manager + + +class CalendarFloatingWidget(QWidget): + """日历悬浮窗口类""" + + # 自定义信号 + closed = pyqtSignal() # 窗口关闭信号 + date_selected = pyqtSignal(str) # 日期字符串信号,用于插入功能 + + def __init__(self, parent=None): + super().__init__(parent) + self.drag_position = None + self.is_dragging = False + self.setup_ui() + self.setup_connections() + self.setup_theme() + self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) + self.setAttribute(Qt.WA_TranslucentBackground) + + def setup_ui(self): + """设置UI界面""" + # 设置窗口属性 + self.setWindowTitle("日历") + self.setFixedSize(360, 320) # 设置窗口大小 + + # 创建主框架,用于实现圆角和阴影效果 + self.main_frame = QFrame() + self.main_frame.setObjectName("mainFrame") + + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.addWidget(self.main_frame) + + # 内容布局 + content_layout = QVBoxLayout(self.main_frame) + content_layout.setContentsMargins(10, 10, 10, 10) + content_layout.setSpacing(8) + + # 标题栏 + title_layout = QHBoxLayout() + title_layout.setContentsMargins(0, 0, 0, 0) + title_layout.setSpacing(0) + + self.title_label = QLabel("日历") + self.title_label.setFont(QFont("Arial", 12, QFont.Bold)) + title_layout.addWidget(self.title_label) + title_layout.addStretch() + # 添加一个小的固定空间,使关闭按钮向左移动 + title_layout.addSpacing(25) # 向左移动25个单位 + + # 关闭按钮 + self.close_btn = QPushButton("×") + self.close_btn.setFixedSize(20, 20) + self.close_btn.setObjectName("closeButton") + title_layout.addWidget(self.close_btn) + + content_layout.addLayout(title_layout) + + # 分隔线 + separator = QFrame() + separator.setObjectName("separator") + separator.setFixedHeight(1) + content_layout.addWidget(separator) + + # 日历控件 + self.calendar = QCalendarWidget() + self.calendar.setGridVisible(True) + self.calendar.setVerticalHeaderFormat(QCalendarWidget.NoVerticalHeader) + self.calendar.setNavigationBarVisible(True) + content_layout.addWidget(self.calendar) + + # 当前日期显示 + self.date_label = QLabel() + self.date_label.setAlignment(Qt.AlignCenter) + self.date_label.setFont(QFont("Arial", 10)) + self.date_label.setObjectName("dateLabel") + self.update_date_label() + content_layout.addWidget(self.date_label) + + # 操作按钮 + button_layout = QHBoxLayout() + button_layout.setContentsMargins(0, 0, 0, 0) + button_layout.setSpacing(6) + + self.today_btn = QPushButton("今天") + self.today_btn.setObjectName("todayButton") + button_layout.addWidget(self.today_btn) + + self.insert_btn = QPushButton("插入") + self.insert_btn.setObjectName("insertButton") + button_layout.addWidget(self.insert_btn) + + button_layout.addStretch() + + self.clear_btn = QPushButton("清除") + self.clear_btn.setObjectName("clearButton") + button_layout.addWidget(self.clear_btn) + + content_layout.addLayout(button_layout) + + def setup_connections(self): + """设置信号连接""" + self.calendar.clicked.connect(self.on_date_selected) + self.today_btn.clicked.connect(self.on_today_clicked) + self.clear_btn.clicked.connect(self.on_clear_clicked) + self.close_btn.clicked.connect(self.close_window) + self.insert_btn.clicked.connect(self.on_insert_clicked) + + def setup_theme(self): + """设置主题""" + # 连接主题切换信号 + theme_manager.theme_changed.connect(self.on_theme_changed) + + # 应用当前主题 + self.apply_theme() + + def apply_theme(self): + """应用主题样式""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + # 深色主题样式 + self.main_frame.setStyleSheet(f""" + QFrame#mainFrame {{ + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 10px; + }} + QLabel {{ + color: {colors['text']}; + background-color: transparent; + padding: 4px 6px; + margin: 2px; + }} + QLabel#dateLabel {{ + color: {colors['text_secondary']}; + font-size: 11px; + padding: 4px 6px; + margin: 2px; + }} + QFrame#separator {{ + background-color: {colors['border']}; + }} + QPushButton#closeButton {{ + background-color: rgba(255, 255, 255, 0.1); + border: none; + color: {colors['text']}; + font-size: 18px; + font-weight: bold; + border-radius: 6px; + padding: 2px 4px; + }} + QPushButton#closeButton:hover {{ + background-color: #e81123; + color: white; + border-radius: 6px; + }} + QPushButton#todayButton, QPushButton#clearButton, QPushButton#insertButton {{ + background-color: {colors['accent']}; + color: white; + border: none; + border-radius: 6px; + padding: 6px 16px; + font-size: 11px; + font-weight: 500; + }} + QPushButton#todayButton:hover, QPushButton#clearButton:hover, QPushButton#insertButton:hover {{ + background-color: {colors['accent_hover']}; + }} + """) + + # 更新日历控件样式 + self.calendar.setStyleSheet(f""" + QCalendarWidget {{ + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 4px; + }} + QCalendarWidget QToolButton {{ + height: 30px; + width: 80px; + color: {colors['text']}; + font-size: 12px; + font-weight: bold; + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 4px; + }} + QCalendarWidget QToolButton:hover {{ + background-color: {colors['surface_hover']}; + }} + QCalendarWidget QMenu {{ + width: 150px; + left: 20px; + color: {colors['text']}; + font-size: 12px; + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + }} + QCalendarWidget QSpinBox {{ + width: 80px; + font-size: 12px; + background-color: {colors['surface']}; + selection-background-color: {colors['accent']}; + selection-color: white; + border: 1px solid {colors['border']}; + border-radius: 4px; + color: {colors['text']}; + }} + QCalendarWidget QSpinBox::up-button {{ + subcontrol-origin: border; + subcontrol-position: top right; + width: 20px; + }} + QCalendarWidget QSpinBox::down-button {{ + subcontrol-origin: border; + subcontrol-position: bottom right; + width: 20px; + }} + QCalendarWidget QSpinBox::up-arrow {{ + width: 10px; + height: 10px; + }} + QCalendarWidget QSpinBox::down-arrow {{ + width: 10px; + height: 10px; + }} + QCalendarWidget QWidget {{ + alternate-background-color: {colors['surface']}; + }} + QCalendarWidget QAbstractItemView:enabled {{ + font-size: 12px; + selection-background-color: {colors['accent']}; + selection-color: white; + background-color: {colors['surface']}; + color: {colors['text']}; + }} + QCalendarWidget QWidget#qt_calendar_navigationbar {{ + background-color: {colors['surface']}; + }} + """) + else: + # 浅色主题样式 + self.main_frame.setStyleSheet(f""" + QFrame#mainFrame {{ + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + }} + QLabel {{ + color: {colors['text']}; + background-color: transparent; + padding: 4px 6px; + margin: 2px; + }} + QLabel#dateLabel {{ + color: {colors['text_secondary']}; + font-size: 11px; + padding: 4px 6px; + margin: 2px; + }} + QFrame#separator {{ + background-color: {colors['border']}; + }} + QPushButton#closeButton {{ + background-color: rgba(0, 0, 0, 0.05); + border: none; + color: {colors['text']}; + font-size: 18px; + font-weight: bold; + border-radius: 6px; + padding: 2px 4px; + }} + QPushButton#closeButton:hover {{ + background-color: #e81123; + color: white; + border-radius: 6px; + }} + QPushButton#todayButton, QPushButton#clearButton, QPushButton#insertButton {{ + background-color: {colors['accent']}; + color: white; + border: none; + border-radius: 6px; + padding: 6px 16px; + font-size: 11px; + font-weight: 500; + }} + QPushButton#todayButton:hover, QPushButton#clearButton:hover, QPushButton#insertButton:hover {{ + background-color: {colors['accent_hover']}; + }} + """) + + # 更新日历控件样式 + self.calendar.setStyleSheet(f""" + QCalendarWidget {{ + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 4px; + }} + QCalendarWidget QToolButton {{ + height: 30px; + width: 80px; + color: {colors['text']}; + font-size: 12px; + font-weight: bold; + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 4px; + }} + QCalendarWidget QToolButton:hover {{ + background-color: {colors['surface_hover']}; + }} + QCalendarWidget QMenu {{ + width: 150px; + left: 20px; + color: {colors['text']}; + font-size: 12px; + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + }} + QCalendarWidget QSpinBox {{ + width: 80px; + font-size: 12px; + background-color: {colors['surface']}; + selection-background-color: {colors['accent']}; + selection-color: white; + border: 1px solid {colors['border']}; + border-radius: 4px; + color: {colors['text']}; + }} + QCalendarWidget QSpinBox::up-button {{ + subcontrol-origin: border; + subcontrol-position: top right; + width: 20px; + }} + QCalendarWidget QSpinBox::down-button {{ + subcontrol-origin: border; + subcontrol-position: bottom right; + width: 20px; + }} + QCalendarWidget QSpinBox::up-arrow {{ + width: 10px; + height: 10px; + }} + QCalendarWidget QSpinBox::down-arrow {{ + width: 10px; + height: 10px; + }} + QCalendarWidget QWidget {{ + alternate-background-color: {colors['surface']}; + }} + QCalendarWidget QAbstractItemView:enabled {{ + font-size: 12px; + selection-background-color: {colors['accent']}; + selection-color: white; + background-color: {colors['surface']}; + color: {colors['text']}; + }} + QCalendarWidget QWidget#qt_calendar_navigationbar {{ + background-color: {colors['surface']}; + }} + """) + + def on_theme_changed(self, is_dark): + """主题切换槽函数""" + self.apply_theme() + + def mousePressEvent(self, event): + """鼠标按下事件,用于拖拽""" + if event.button() == Qt.LeftButton: + # 检查是否点击在标题栏区域 + if event.pos().y() <= 40: # 假设标题栏高度为40像素 + self.is_dragging = True + self.drag_position = event.globalPos() - self.frameGeometry().topLeft() + event.accept() + + def mouseMoveEvent(self, event): + """鼠标移动事件,用于拖拽""" + if self.is_dragging and event.buttons() == Qt.LeftButton: + self.move(event.globalPos() - self.drag_position) + event.accept() + + def mouseReleaseEvent(self, event): + """鼠标释放事件""" + self.is_dragging = False + + def on_date_selected(self, date): + """日期选择事件""" + self.update_date_label(date) + + def on_today_clicked(self): + """今天按钮点击事件""" + today = QDate.currentDate() + self.calendar.setSelectedDate(today) + self.update_date_label(today) + + def on_clear_clicked(self): + """清除按钮点击事件""" + self.calendar.setSelectedDate(QDate()) + self.date_label.setText("未选择日期") + + def on_insert_clicked(self): + """插入按钮点击事件""" + selected_date = self.calendar.selectedDate() + if selected_date.isValid(): + # 发送信号,将选中的日期传递给主窗口 + date_str = selected_date.toString("yyyy年MM月dd日 dddd") + self.date_selected.emit(date_str) + + def update_date_label(self, date=None): + """更新日期显示标签""" + if date is None: + date = self.calendar.selectedDate() + + if date.isValid(): + date_str = date.toString("yyyy年MM月dd日 (ddd)") + self.date_label.setText(f"选中日期: {date_str}") + else: + self.date_label.setText("未选择日期") + + def get_selected_date(self): + """获取选中的日期""" + return self.calendar.selectedDate() + + def set_selected_date(self, date): + """设置选中的日期""" + if isinstance(date, str): + date = QDate.fromString(date, "yyyy-MM-dd") + self.calendar.setSelectedDate(date) + self.update_date_label(date) + + def close_window(self): + """关闭窗口 - 只是隐藏而不是销毁""" + try: + self.closed.emit() + self.hide() # 隐藏窗口而不是销毁 + except Exception as e: + print(f"Error in close_window: {e}") + + +def main(): + """测试函数""" + from PyQt5.QtWidgets import QApplication + + app = QApplication(sys.argv) + + # 创建并显示窗口 + widget = CalendarFloatingWidget() + widget.show() + + # 移动到屏幕中心 + screen_geometry = app.desktop().screenGeometry() + widget.move( + (screen_geometry.width() - widget.width()) // 2, + (screen_geometry.height() - widget.height()) // 2 + ) + + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 07ad4ff..65e0525 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -711,10 +711,17 @@ class WordRibbon(QFrame): self.floating_quote_btn.setStyleSheet("QPushButton { font-size: 11px; padding: 5px; }") self.floating_quote_btn.setToolTip("切换每日谏言悬浮窗口") + # 日历悬浮窗口按钮 + self.floating_calendar_btn = QPushButton("📅 悬浮") + self.floating_calendar_btn.setFixedSize(60, 30) + self.floating_calendar_btn.setStyleSheet("QPushButton { font-size: 11px; padding: 5px; }") + self.floating_calendar_btn.setToolTip("切换日历悬浮窗口") + control_layout.addWidget(self.city_combo) control_layout.addWidget(self.refresh_weather_btn) control_layout.addWidget(self.floating_weather_btn) control_layout.addWidget(self.floating_quote_btn) + control_layout.addWidget(self.floating_calendar_btn) # 添加右侧弹性空间,确保内容居中 control_layout.addStretch() diff --git a/src/word_main_window.py b/src/word_main_window.py index 4f4851d..6a9abcc 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -21,6 +21,7 @@ from input_handler.input_processor import InputProcessor from ui.calendar_widget import CalendarWidget from ui.weather_floating_widget import WeatherFloatingWidget from ui.quote_floating_widget import QuoteFloatingWidget +from ui.calendar_floating_widget import CalendarFloatingWidget # 导入主题管理器 from ui.theme_manager import theme_manager @@ -116,13 +117,17 @@ class WordStyleMainWindow(QMainWindow): self.weather_floating_widget.closed.connect(self.on_weather_floating_closed) self.weather_floating_widget.refresh_requested.connect(self.refresh_weather) - # 初始化每日谏言悬浮窗口 self.quote_floating_widget = QuoteFloatingWidget(self) self.quote_floating_widget.hide() # 默认隐藏 self.quote_floating_widget.closed.connect(self.on_quote_floating_closed) self.quote_floating_widget.refresh_requested.connect(self.refresh_daily_quote) self.quote_floating_widget.insert_requested.connect(self.insert_quote_to_cursor) + self.calendar_floating_widget = CalendarFloatingWidget(self) + self.calendar_floating_widget.hide() # 默认隐藏 + self.calendar_floating_widget.closed.connect(self.on_calendar_floating_closed) + self.calendar_floating_widget.date_selected.connect(self.insert_date_to_cursor) + # 设置窗口属性 self.setWindowTitle("文档1 - MagicWord") self.setGeometry(100, 100, 1200, 800) @@ -149,6 +154,14 @@ class WordStyleMainWindow(QMainWindow): self.ribbon.on_refresh_weather = self.refresh_weather self.ribbon.on_city_changed = self.on_city_changed + # 连接Ribbon的悬浮窗口按钮信号 + if hasattr(self.ribbon, 'floating_weather_btn'): + self.ribbon.floating_weather_btn.clicked.connect(self.toggle_floating_weather) + if hasattr(self.ribbon, 'floating_quote_btn'): + self.ribbon.floating_quote_btn.clicked.connect(self.toggle_floating_quote) + if hasattr(self.ribbon, 'floating_calendar_btn'): + self.ribbon.floating_calendar_btn.clicked.connect(self.toggle_floating_calendar) + # 初始化时刷新天气 self.refresh_weather() @@ -723,6 +736,11 @@ class WordStyleMainWindow(QMainWindow): toggle_floating_quote_action = QAction('每日谏言悬浮窗口', self) toggle_floating_quote_action.triggered.connect(self.toggle_floating_quote) weather_menu.addAction(toggle_floating_quote_action) + + # 日历悬浮窗口切换动作 + toggle_floating_calendar_action = QAction('日历悬浮窗口', self) + toggle_floating_calendar_action.triggered.connect(self.toggle_floating_calendar) + weather_menu.addAction(toggle_floating_calendar_action) # 插入菜单 insert_menu = menubar.addMenu('插入(I)') @@ -1800,6 +1818,22 @@ class WordStyleMainWindow(QMainWindow): def on_quote_floating_closed(self): """每日谏言悬浮窗口关闭时的处理""" self.status_bar.showMessage("每日谏言悬浮窗口已关闭", 2000) + + def toggle_floating_calendar(self): + """切换日历悬浮窗口的显示/隐藏状态""" + if hasattr(self, 'calendar_floating_widget'): + if self.calendar_floating_widget.isVisible(): + self.calendar_floating_widget.hide() + self.status_bar.showMessage("日历悬浮窗口已隐藏", 2000) + else: + self.calendar_floating_widget.show() + # 确保窗口在屏幕内 + self.calendar_floating_widget.move(100, 100) + self.status_bar.showMessage("日历悬浮窗口已显示", 2000) + + def on_calendar_floating_closed(self): + """日历悬浮窗口关闭事件""" + self.status_bar.showMessage("日历悬浮窗口已关闭", 2000) def insert_quote_to_cursor(self, quote_text): """将古诗句插入到光标位置""" @@ -1814,6 +1848,18 @@ class WordStyleMainWindow(QMainWindow): # 从文本中提取诗句部分用于显示 quote_only = quote_text.split(" —— ")[0] if " —— " in quote_text else quote_text self.status_bar.showMessage(f"已插入古诗句: {quote_only}", 3000) + + def insert_date_to_cursor(self, date_str): + """在光标位置插入日期""" + try: + # 在光标位置插入日期 + cursor = self.text_edit.textCursor() + cursor.insertText(date_str) + + # 更新状态栏 + self.status_bar.showMessage(f"已插入日期: {date_str}", 2000) + except Exception as e: + print(f"插入日期时出错: {e}") def toggle_weather_tools(self, checked): """切换天气工具组显示"""