You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Curriculum_Design/src/ui/quote_floating_widget.py

439 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
每日谏言悬浮窗口
"""
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QFrame, QGraphicsDropShadowEffect)
from PyQt5.QtCore import Qt, QPoint, pyqtSignal
from PyQt5.QtGui import QFont, QColor
class QuoteFloatingWidget(QWidget):
"""每日谏言悬浮窗口"""
# 定义信号
closed = pyqtSignal() # 窗口关闭信号
refresh_requested = pyqtSignal() # 刷新请求信号
insert_requested = pyqtSignal(str) # 插入请求信号,传递要插入的文本
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool)
self.setAttribute(Qt.WA_TranslucentBackground)
# 初始化变量
self.is_dragging = False
self.drag_position = QPoint()
# 设置默认谏言数据
self.quote_data = {
"quote": "书山有路勤为径,学海无涯苦作舟。",
"author": "韩愈",
"source": "《古今贤文·劝学篇》"
}
# 初始化UI
self.init_ui()
self.setup_styles()
self.apply_theme(is_dark=True) # 默认使用深色主题
def init_ui(self):
"""初始化UI"""
# 主框架
self.main_frame = QFrame(self)
self.main_frame.setObjectName("mainFrame")
self.main_frame.setFixedSize(360, 200) # 设置窗口大小
main_layout = QVBoxLayout(self.main_frame)
main_layout.setContentsMargins(12, 12, 12, 12)
main_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")
self.close_btn.clicked.connect(self.close_window)
title_layout.addWidget(self.close_btn)
main_layout.addLayout(title_layout)
# 分隔线
separator = QFrame()
separator.setObjectName("separator")
separator.setFixedHeight(1)
main_layout.addWidget(separator)
# 谏言内容区域
content_layout = QVBoxLayout()
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.setSpacing(6)
# 谏言文本
self.quote_label = QLabel()
self.quote_label.setObjectName("quoteLabel")
self.quote_label.setWordWrap(True)
self.quote_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
content_layout.addWidget(self.quote_label)
# 作者信息
self.author_label = QLabel()
self.author_label.setObjectName("authorLabel")
self.author_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
content_layout.addWidget(self.author_label)
main_layout.addLayout(content_layout)
main_layout.addStretch()
# 底部按钮区域
bottom_layout = QHBoxLayout()
bottom_layout.setContentsMargins(0, 0, 0, 0)
bottom_layout.setSpacing(8)
# 刷新按钮
self.refresh_btn = QPushButton("换一句")
self.refresh_btn.setObjectName("refreshButton")
self.refresh_btn.clicked.connect(self.on_refresh_clicked)
bottom_layout.addWidget(self.refresh_btn)
bottom_layout.addStretch()
# 插入按钮
self.insert_btn = QPushButton("插入")
self.insert_btn.setObjectName("insertButton")
self.insert_btn.clicked.connect(self.on_insert_clicked)
bottom_layout.addWidget(self.insert_btn)
main_layout.addLayout(bottom_layout)
# 设置主布局
outer_layout = QVBoxLayout(self)
outer_layout.setContentsMargins(0, 0, 0, 0)
outer_layout.addWidget(self.main_frame)
# 更新显示
self.update_quote()
def setup_styles(self):
"""设置样式"""
pass # 样式将在apply_theme中设置
def apply_theme(self, is_dark=True):
"""应用主题"""
if is_dark:
# 深色主题配色
colors = {
'surface': '#2d2d2d',
'border': '#444444',
'text': '#ffffff',
'text_secondary': '#cccccc',
'accent': '#4CAF50',
'accent_hover': '#45a049',
'button_hover': '#555555',
'error': '#f44336'
}
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.3);
}}
QLabel {{
color: {colors['text']};
background-color: transparent;
padding: 4px 6px;
margin: 2px;
}}
QLabel#quoteLabel {{
color: {colors['text']};
font-size: 14px;
font-weight: 500;
padding: 6px 8px;
margin: 3px;
}}
QLabel#authorLabel {{
color: {colors['text_secondary']};
font-size: 12px;
font-style: italic;
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#refreshButton, QPushButton#insertButton {{
background-color: {colors['accent']};
color: white;
border: none;
border-radius: 6px;
padding: 6px 16px;
font-size: 11px;
font-weight: 500;
}}
QPushButton#refreshButton:hover, QPushButton#insertButton:hover {{
background-color: {colors['accent_hover']};
}}
""")
else:
# 浅色主题配色
colors = {
'surface': '#ffffff',
'border': '#dddddd',
'text': '#333333',
'text_secondary': '#666666',
'accent': '#4CAF50',
'accent_hover': '#45a049',
'button_hover': '#f0f0f0',
'error': '#f44336'
}
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#quoteLabel {{
color: {colors['text']};
font-size: 14px;
font-weight: 500;
padding: 6px 8px;
margin: 3px;
}}
QLabel#authorLabel {{
color: {colors['text_secondary']};
font-size: 12px;
font-style: italic;
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#refreshButton, QPushButton#insertButton {{
background-color: {colors['accent']};
color: white;
border: none;
border-radius: 6px;
padding: 6px 16px;
font-size: 11px;
font-weight: 500;
}}
QPushButton#refreshButton:hover, QPushButton#insertButton:hover {{
background-color: {colors['accent_hover']};
}}
""")
def update_quote(self, quote_data=None):
"""更新谏言显示"""
if quote_data:
self.quote_data = quote_data
else:
# 如果没有提供数据,使用默认数据
if not hasattr(self, 'quote_data'):
self.quote_data = {
"quote": "书山有路勤为径,学海无涯苦作舟。",
"author": "韩愈",
"source": "《古今贤文·劝学篇》"
}
# 更新显示
self.quote_label.setText(self.quote_data["quote"])
author_info = f"{self.quote_data['author']}"
if self.quote_data.get("source"):
author_info += f"{self.quote_data['source']}"
self.author_label.setText(author_info)
def on_refresh_clicked(self):
"""刷新按钮点击事件"""
# 发送刷新请求信号
self.refresh_requested.emit()
# 同时直接获取新的内容并更新显示
self.fetch_and_update_quote()
def on_insert_clicked(self):
"""插入按钮点击事件"""
# 发送插入请求信号,传递完整的诗句信息
quote = self.quote_data.get("quote", "")
author = self.quote_data.get("author", "佚名")
source = self.quote_data.get("source", "")
# 构造完整的诗句文本
if source:
full_quote_text = f"{quote} —— {author}{source}"
else:
full_quote_text = f"{quote} —— {author}"
if quote:
self.insert_requested.emit(full_quote_text)
def fetch_and_update_quote(self):
"""获取新的谏言内容并更新显示"""
try:
# 尝试获取古诗词
import requests
import random
try:
# 使用古诗词·一言API
response = requests.get("https://v1.jinrishici.com/all.json", timeout=5, verify=False)
if response.status_code == 200:
data = response.json()
content = data.get('content', '')
author = data.get('author', '佚名')
origin = data.get('origin', '')
if content:
quote_data = {
"quote": content,
"author": author,
"source": origin
}
self.update_quote(quote_data)
return
except Exception as e:
print(f"获取古诗词失败: {e}")
# 如果古诗词获取失败使用备用API
try:
# 使用每日一言API
response = requests.get("https://api.nxvav.cn/api/yiyan?json=true", timeout=5, verify=False)
if response.status_code == 200:
data = response.json()
yiyan = data.get('yiyan', '')
nick = data.get('nick', '佚名')
if yiyan:
quote_data = {
"quote": yiyan,
"author": nick,
"source": ""
}
self.update_quote(quote_data)
return
except Exception as e:
print(f"获取每日一言失败: {e}")
# 如果API都失败使用预设内容
quotes = [
{"quote": "学而时习之,不亦说乎?", "author": "孔子", "source": "《论语》"},
{"quote": "千里之行,始于足下。", "author": "老子", "source": "《道德经》"},
{"quote": "天行健,君子以自强不息。", "author": "佚名", "source": "《周易》"},
{"quote": "书山有路勤为径,学海无涯苦作舟。", "author": "韩愈", "source": "《古今贤文·劝学篇》"},
{"quote": "山重水复疑无路,柳暗花明又一村。", "author": "陆游", "source": "《游山西村》"}
]
# 随机选择一个名言
new_quote = random.choice(quotes)
self.update_quote(new_quote)
except Exception as e:
print(f"获取新谏言失败: {e}")
# 出错时显示默认内容
default_quote = {
"quote": "书山有路勤为径,学海无涯苦作舟。",
"author": "韩愈",
"source": "《古今贤文·劝学篇》"
}
self.update_quote(default_quote)
def close_window(self):
"""关闭窗口 - 只是隐藏而不是销毁"""
try:
self.closed.emit()
self.hide() # 隐藏窗口而不是销毁
except Exception as e:
print(f"Error in close_window: {e}")
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 main():
"""测试函数"""
app = QApplication(sys.argv)
# 创建并显示窗口
widget = QuoteFloatingWidget()
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()