|
|
|
|
@ -0,0 +1,415 @@
|
|
|
|
|
#!/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() # 刷新请求信号
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
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 {{
|
|
|
|
|
background-color: {colors['accent']};
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 6px 16px;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}}
|
|
|
|
|
QPushButton#refreshButton: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 {{
|
|
|
|
|
background-color: {colors['accent']};
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 6px 16px;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}}
|
|
|
|
|
QPushButton#refreshButton: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 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()
|