UI-fix #24

Merged
p9o3yklam merged 3 commits from main into huangjiunyuna 4 months ago

@ -33,18 +33,8 @@ class FileParser:
if not FileParser.validate_file_path(file_path):
raise ValueError(f"Invalid file path: {file_path}")
# 导入工具函数来检测编码
try:
from src.utils.helper_functions import Utils
except ImportError:
# 如果无法导入,使用默认方法检测编码
import chardet
with open(file_path, 'rb') as f:
raw_data = f.read(1024)
encoding = chardet.detect(raw_data)['encoding'] or 'utf-8'
else:
# 使用工具函数检测编码
encoding = Utils.detect_encoding(file_path)
# 尝试多种编码方式读取文件
encoding = FileParser.detect_file_encoding(file_path)
# 读取文件内容
with open(file_path, 'r', encoding=encoding, errors='ignore') as f:
@ -55,6 +45,47 @@ class FileParser:
return content
@staticmethod
def detect_file_encoding(file_path: str) -> str:
"""检测文件编码"""
# 首先尝试UTF-8
try:
with open(file_path, 'r', encoding='utf-8') as f:
f.read()
return 'utf-8'
except UnicodeDecodeError:
pass
# 尝试GBK中文Windows常用
try:
with open(file_path, 'r', encoding='gbk') as f:
f.read()
return 'gbk'
except UnicodeDecodeError:
pass
# 尝试GB2312
try:
with open(file_path, 'r', encoding='gb2312') as f:
f.read()
return 'gb2312'
except UnicodeDecodeError:
pass
# 尝试使用chardet如果可用
try:
import chardet
with open(file_path, 'rb') as f:
raw_data = f.read(1024)
result = chardet.detect(raw_data)
if result and result['encoding']:
return result['encoding']
except ImportError:
pass
# 默认返回UTF-8
return 'utf-8'
@staticmethod
def parse_docx(file_path: str) -> str:

@ -20,30 +20,25 @@ elif os.path.exists(venv_qt_plugins_path):
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from src.main_window import MainWindow
from word_main_window import WordStyleMainWindow
# 设置高DPI支持必须在QApplication创建之前
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
def main():
"""
应用程序主入口点
- 创建QApplication实例
- 设置应用程序属性
- 创建MainWindow实例
- 显示窗口
- 启动事件循环
- 返回退出码
"""
"""应用程序入口函数 - Word风格版本"""
try:
# 创建QApplication实例
app = QApplication(sys.argv)
# 设置应用程序样式为Windows风格更接近Word界面
app.setStyle('WindowsVista')
# 设置应用程序属性
app.setApplicationName("隐私学习软件")
app.setApplicationVersion("0.1.0")
app.setOrganizationName("MagicWord Team")
app.setApplicationName("MagicWord")
app.setApplicationVersion("2.0")
app.setOrganizationName("MagicWord")
# 设置窗口图标(如果存在)
icon_path = os.path.join(project_root, 'resources', 'icons', 'app_icon.png')
@ -53,9 +48,9 @@ def main():
# 使用默认图标
app.setWindowIcon(QIcon())
# 创建主窗口
window = MainWindow()
window.show()
# 创建Word风格的主窗口
main_window = WordStyleMainWindow()
main_window.show()
# 启动事件循环并返回退出码
exit_code = app.exec_()

@ -17,13 +17,13 @@ class NetworkService:
# 实现天气信息获取逻辑
# 1. 获取用户IP地址
try:
ip_response = self.session.get("https://httpbin.org/ip", timeout=5)
ip_response = self.session.get("https://httpbin.org/ip", timeout=5, verify=False)
ip_data = ip_response.json()
ip = ip_data.get("origin", "")
# 2. 根据IP获取地理位置
# 注意这里使用免费的IP地理位置API实际应用中可能需要更精确的服务
location_response = self.session.get(f"http://ip-api.com/json/{ip}", timeout=5)
location_response = self.session.get(f"http://ip-api.com/json/{ip}", timeout=5, verify=False)
location_data = location_response.json()
if location_data.get("status") != "success":
@ -36,7 +36,7 @@ class NetworkService:
# 在实际应用中需要设置有效的API密钥
if self.api_key:
weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn"
weather_response = self.session.get(weather_url, timeout=5)
weather_response = self.session.get(weather_url, timeout=5, verify=False)
weather_data = weather_response.json()
# 4. 解析并格式化数据
@ -68,8 +68,8 @@ class NetworkService:
# 实现每日一句获取逻辑
# 1. 调用名言API
try:
# 使用一个免费的名言API
response = self.session.get("https://api.quotable.io/random", timeout=5)
# 使用一个免费的名言API禁用SSL验证以避免证书问题
response = self.session.get("https://api.quotable.io/random", timeout=5, verify=False)
# 2. 解析返回的名言数据
if response.status_code == 200:

@ -0,0 +1,371 @@
# word_style_ui.py
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QTabWidget, QFrame, QTextEdit,
QToolButton, QMenuBar, QStatusBar, QGroupBox,
QComboBox, QSpinBox, QFontComboBox, QToolBar)
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont, QIcon, QPalette, QColor
class WordRibbonTab(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
layout = QHBoxLayout()
layout.setSpacing(10)
layout.setContentsMargins(10, 5, 10, 5)
self.setLayout(layout)
class WordRibbon(QFrame):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
self.setFrameStyle(QFrame.NoFrame)
self.setFixedHeight(120)
# 主布局
main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 标签栏
self.tab_bar = QWidget()
self.tab_bar.setFixedHeight(25)
self.tab_bar.setStyleSheet("""
QWidget {
background-color: #f3f2f1;
border-bottom: 1px solid #e1e1e1;
}
""")
tab_layout = QHBoxLayout()
tab_layout.setContentsMargins(10, 0, 0, 0)
tab_layout.setSpacing(0)
# 创建标签按钮
self.tabs = {}
tab_names = ['文件', '开始', '插入', '设计', '布局', '引用', '邮件', '审阅', '视图', '帮助']
for name in tab_names:
tab_btn = QPushButton(name)
tab_btn.setFlat(True)
tab_btn.setFixedHeight(25)
tab_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
padding: 5px 15px;
font-size: 12px;
color: #333333;
}
QPushButton:hover {
background-color: #e1e1e1;
}
QPushButton:checked {
background-color: #ffffff;
border: 1px solid #d0d0d0;
border-bottom: 1px solid #ffffff;
}
""")
self.tabs[name] = tab_btn
tab_layout.addWidget(tab_btn)
self.tab_bar.setLayout(tab_layout)
# 功能区
self.ribbon_area = QFrame()
self.ribbon_area.setStyleSheet("""
QFrame {
background-color: #ffffff;
border: 1px solid #d0d0d0;
border-top: none;
}
""")
ribbon_layout = QHBoxLayout()
ribbon_layout.setContentsMargins(10, 5, 10, 5)
ribbon_layout.setSpacing(15)
# 开始标签的内容(最常用的功能)
self.setup_home_tab(ribbon_layout)
self.ribbon_area.setLayout(ribbon_layout)
main_layout.addWidget(self.tab_bar)
main_layout.addWidget(self.ribbon_area)
self.setLayout(main_layout)
# 设置默认选中的标签
self.tabs['开始'].setChecked(True)
def setup_home_tab(self, layout):
"""设置开始标签的功能区内容"""
# 剪贴板组
clipboard_group = self.create_ribbon_group("剪贴板")
paste_btn = self.create_ribbon_button("粘贴", "Ctrl+V", "paste")
cut_btn = self.create_ribbon_button("剪切", "Ctrl+X", "cut")
copy_btn = self.create_ribbon_button("复制", "Ctrl+C", "copy")
clipboard_layout = QVBoxLayout()
clipboard_layout.addWidget(paste_btn)
small_btn_layout = QHBoxLayout()
small_btn_layout.addWidget(cut_btn)
small_btn_layout.addWidget(copy_btn)
clipboard_layout.addLayout(small_btn_layout)
clipboard_group.setLayout(clipboard_layout)
layout.addWidget(clipboard_group)
# 字体组
font_group = self.create_ribbon_group("字体")
# 字体选择
font_layout = QHBoxLayout()
self.font_combo = QFontComboBox()
self.font_combo.setFixedWidth(120)
self.font_size_combo = QComboBox()
self.font_size_combo.addItems(['8', '9', '10', '11', '12', '14', '16', '18', '20', '22', '24', '26', '28', '36', '48', '72'])
self.font_size_combo.setFixedWidth(50)
self.font_size_combo.setCurrentText('12')
font_layout.addWidget(self.font_combo)
font_layout.addWidget(self.font_size_combo)
# 字体样式按钮
font_style_layout = QHBoxLayout()
self.bold_btn = self.create_toggle_button("B", "bold")
self.italic_btn = self.create_toggle_button("I", "italic")
self.underline_btn = self.create_toggle_button("U", "underline")
font_style_layout.addWidget(self.bold_btn)
font_style_layout.addWidget(self.italic_btn)
font_style_layout.addWidget(self.underline_btn)
font_main_layout = QVBoxLayout()
font_main_layout.addLayout(font_layout)
font_main_layout.addLayout(font_style_layout)
font_group.setLayout(font_main_layout)
layout.addWidget(font_group)
# 段落组
paragraph_group = self.create_ribbon_group("段落")
# 对齐方式
align_layout = QHBoxLayout()
self.align_left_btn = self.create_toggle_button("左对齐", "align_left")
self.align_center_btn = self.create_toggle_button("居中", "align_center")
self.align_right_btn = self.create_toggle_button("右对齐", "align_right")
self.align_justify_btn = self.create_toggle_button("两端对齐", "align_justify")
align_layout.addWidget(self.align_left_btn)
align_layout.addWidget(self.align_center_btn)
align_layout.addWidget(self.align_right_btn)
align_layout.addWidget(self.align_justify_btn)
paragraph_layout = QVBoxLayout()
paragraph_layout.addLayout(align_layout)
paragraph_group.setLayout(paragraph_layout)
layout.addWidget(paragraph_group)
# 样式组
styles_group = self.create_ribbon_group("样式")
styles_layout = QVBoxLayout()
styles_layout.addWidget(QLabel("样式"))
styles_group.setLayout(styles_layout)
layout.addWidget(styles_group)
# 编辑组
editing_group = self.create_ribbon_group("编辑")
find_btn = self.create_ribbon_button("查找", "Ctrl+F", "find")
replace_btn = self.create_ribbon_button("替换", "Ctrl+H", "replace")
editing_layout = QVBoxLayout()
editing_layout.addWidget(find_btn)
editing_layout.addWidget(replace_btn)
editing_group.setLayout(editing_layout)
layout.addWidget(editing_group)
layout.addStretch()
def create_ribbon_group(self, title):
"""创建功能区组"""
group = QGroupBox(title)
group.setStyleSheet("""
QGroupBox {
font-size: 11px;
font-weight: normal;
color: #333333;
border: 1px solid #e1e1e1;
border-radius: 0px;
margin-top: 5px;
padding-top: 5px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
""")
return group
def create_ribbon_button(self, text, shortcut, icon_name):
"""创建功能区按钮"""
btn = QToolButton()
btn.setText(text)
btn.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
btn.setFixedSize(60, 60)
btn.setStyleSheet("""
QToolButton {
border: 1px solid transparent;
border-radius: 3px;
background-color: transparent;
font-size: 11px;
color: #333333;
}
QToolButton:hover {
background-color: #f0f0f0;
border: 1px solid #d0d0d0;
}
QToolButton:pressed {
background-color: #e1e1e1;
border: 1px solid #c0c0c0;
}
""")
return btn
def create_toggle_button(self, text, icon_name):
"""创建切换按钮"""
btn = QToolButton()
btn.setText(text)
btn.setCheckable(True)
btn.setToolButtonStyle(Qt.ToolButtonTextOnly)
btn.setFixedSize(30, 25)
btn.setStyleSheet("""
QToolButton {
border: 1px solid #d0d0d0;
border-radius: 2px;
background-color: transparent;
font-size: 12px;
font-weight: bold;
color: #333333;
}
QToolButton:hover {
background-color: #f0f0f0;
}
QToolButton:checked {
background-color: #e1e1e1;
border: 1px solid #c0c0c0;
}
""")
return btn
class WordStatusBar(QStatusBar):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
"""设置状态栏"""
self.setStyleSheet("""
QStatusBar {
background-color: #f3f2f1;
border-top: 1px solid #d0d0d0;
font-size: 11px;
color: #333333;
}
""")
# 添加状态栏项目
self.page_label = QLabel("第 1 页,共 1 页")
self.words_label = QLabel("字数: 0")
self.language_label = QLabel("中文(中国)")
self.input_mode_label = QLabel("插入")
self.addPermanentWidget(self.page_label)
self.addPermanentWidget(self.words_label)
self.addPermanentWidget(self.language_label)
self.addPermanentWidget(self.input_mode_label)
class WordTextEdit(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
"""设置文本编辑区域样式"""
self.setStyleSheet("""
QTextEdit {
background-color: #ffffff;
border: 1px solid #d0d0d0;
font-family: 'Calibri', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 12pt;
color: #000000;
padding: 20px;
line-height: 1.5;
}
""")
# 设置页面边距和背景
self.setViewportMargins(50, 50, 50, 50)
# 设置默认字体
font = QFont("Calibri", 12)
font.setStyleStrategy(QFont.PreferAntialias)
self.setFont(font)
# 启用自动换行
self.setLineWrapMode(QTextEdit.WidgetWidth)
# 设置光标宽度
self.setCursorWidth(2)
class WordStyleToolBar(QToolBar):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
"""设置快速访问工具栏"""
self.setFixedHeight(30)
self.setStyleSheet("""
QToolBar {
background-color: #f3f2f1;
border-bottom: 1px solid #d0d0d0;
spacing: 5px;
}
""")
# 快速访问按钮
save_btn = self.create_quick_button("保存", "Ctrl+S")
undo_btn = self.create_quick_button("撤销", "Ctrl+Z")
redo_btn = self.create_quick_button("重做", "Ctrl+Y")
self.addWidget(save_btn)
self.addWidget(undo_btn)
self.addWidget(redo_btn)
self.addSeparator()
def create_quick_button(self, text, shortcut):
"""创建快速访问按钮"""
btn = QToolButton()
btn.setText(text)
btn.setToolButtonStyle(Qt.ToolButtonTextOnly)
btn.setFixedSize(40, 25)
btn.setStyleSheet("""
QToolButton {
border: none;
background-color: transparent;
font-size: 11px;
color: #333333;
}
QToolButton:hover {
background-color: #e1e1e1;
}
""")
return btn

@ -0,0 +1,655 @@
# word_main_window.py
import sys
import os
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QTextEdit, QLabel, QSplitter, QFrame, QMenuBar,
QAction, QFileDialog, QMessageBox, QApplication)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect
from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat
from ui.word_style_ui import (WordRibbon, WordStatusBar, WordTextEdit,
WordStyleToolBar)
from services.network_service import NetworkService
from typing_logic import TypingLogic
from file_parser import FileParser
class WeatherFetchThread(QThread):
weather_fetched = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.network_service = NetworkService()
def run(self):
try:
weather_data = self.network_service.get_weather()
self.weather_fetched.emit(weather_data)
except Exception as e:
self.weather_fetched.emit({'error': str(e)})
class QuoteFetchThread(QThread):
quote_fetched = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.network_service = NetworkService()
def run(self):
try:
quote_data = self.network_service.get_daily_quote()
self.quote_fetched.emit(quote_data)
except Exception as e:
self.quote_fetched.emit({'error': str(e)})
class WordStyleMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.current_file_path = None
self.is_modified = False
self.typing_logic = None
self.is_loading_file = False # 添加文件加载标志
self.imported_content = "" # 存储导入的完整内容
self.displayed_chars = 0 # 已显示的字符数
self.setup_ui()
self.network_service = NetworkService()
# 设置窗口属性
self.setWindowTitle("文档1 - MagicWord")
self.setGeometry(100, 100, 1200, 800)
# 设置应用程序图标
self.set_window_icon()
# 初始化UI
self.setup_ui()
# 初始化网络服务
self.init_network_services()
# 初始化打字逻辑
self.init_typing_logic()
# 连接信号和槽
self.connect_signals()
def set_window_icon(self):
"""设置窗口图标"""
# 创建简单的Word风格图标
icon = QIcon()
pixmap = QPixmap(32, 32)
pixmap.fill(QColor("#2B579A"))
icon.addPixmap(pixmap)
self.setWindowIcon(icon)
def setup_ui(self):
"""设置Word风格的UI界面"""
# 创建菜单栏
self.create_menu_bar()
# 创建快速访问工具栏
self.quick_toolbar = WordStyleToolBar()
self.addToolBar(Qt.TopToolBarArea, self.quick_toolbar)
# 创建Ribbon功能区
self.ribbon = WordRibbon()
# 创建中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 添加Ribbon
main_layout.addWidget(self.ribbon)
# 创建文档编辑区域
self.create_document_area(main_layout)
# 创建状态栏
self.status_bar = WordStatusBar()
self.setStatusBar(self.status_bar)
central_widget.setLayout(main_layout)
# 设置样式
self.setStyleSheet("""
QMainWindow {
background-color: #f3f2f1;
}
""")
def create_menu_bar(self):
"""创建菜单栏"""
menubar = self.menuBar()
menubar.setStyleSheet("""
QMenuBar {
background-color: #f3f2f1;
border-bottom: 1px solid #d0d0d0;
font-size: 12px;
color: #333333;
}
QMenuBar::item:selected {
background-color: #e1e1e1;
}
""")
# 文件菜单
file_menu = menubar.addMenu('文件(F)')
file_menu.setStyleSheet("""
QMenu {
background-color: #ffffff;
border: 1px solid #d0d0d0;
font-size: 12px;
}
QMenu::item:selected {
background-color: #e1e1e1;
}
""")
# 新建
new_action = QAction('新建(N)', self)
new_action.setShortcut('Ctrl+N')
new_action.triggered.connect(self.new_document)
file_menu.addAction(new_action)
# 打开
open_action = QAction('打开(O)...', self)
open_action.setShortcut('Ctrl+O')
open_action.triggered.connect(self.open_file)
file_menu.addAction(open_action)
# 保存
save_action = QAction('保存(S)', self)
save_action.setShortcut('Ctrl+S')
save_action.triggered.connect(self.save_file)
file_menu.addAction(save_action)
# 另存为
save_as_action = QAction('另存为(A)...', self)
save_as_action.triggered.connect(self.save_as_file)
file_menu.addAction(save_as_action)
file_menu.addSeparator()
# 退出
exit_action = QAction('退出(X)', self)
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 编辑菜单
edit_menu = menubar.addMenu('编辑(E)')
# 撤销
undo_action = QAction('撤销(U)', self)
undo_action.setShortcut('Ctrl+Z')
undo_action.triggered.connect(self.undo)
edit_menu.addAction(undo_action)
# 重做
redo_action = QAction('重做(R)', self)
redo_action.setShortcut('Ctrl+Y')
redo_action.triggered.connect(self.redo)
edit_menu.addAction(redo_action)
edit_menu.addSeparator()
# 剪切
cut_action = QAction('剪切(T)', self)
cut_action.setShortcut('Ctrl+X')
cut_action.triggered.connect(self.cut)
edit_menu.addAction(cut_action)
# 复制
copy_action = QAction('复制(C)', self)
copy_action.setShortcut('Ctrl+C')
copy_action.triggered.connect(self.copy)
edit_menu.addAction(copy_action)
# 粘贴
paste_action = QAction('粘贴(P)', self)
paste_action.setShortcut('Ctrl+V')
paste_action.triggered.connect(self.paste)
edit_menu.addAction(paste_action)
# 视图菜单
view_menu = menubar.addMenu('视图(V)')
# 阅读视图
read_view_action = QAction('阅读视图', self)
read_view_action.triggered.connect(self.toggle_reading_view)
view_menu.addAction(read_view_action)
# 打印布局
print_layout_action = QAction('打印布局', self)
print_layout_action.setChecked(True)
print_layout_action.triggered.connect(self.toggle_print_layout)
view_menu.addAction(print_layout_action)
# 帮助菜单
help_menu = menubar.addMenu('帮助(H)')
# 关于
about_action = QAction('关于 MagicWord', self)
about_action.triggered.connect(self.show_about)
help_menu.addAction(about_action)
def create_document_area(self, main_layout):
"""创建文档编辑区域"""
# 创建滚动区域
from PyQt5.QtWidgets import QScrollArea
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll_area.setStyleSheet("""
QScrollArea {
background-color: #e1e1e1;
border: none;
}
QScrollArea QWidget {
background-color: #e1e1e1;
}
""")
# 创建文档容器
document_container = QWidget()
document_layout = QVBoxLayout()
document_layout.setContentsMargins(50, 50, 50, 50)
# 创建文本编辑区域使用Word风格的文本编辑器
self.text_edit = WordTextEdit()
self.text_edit.setMinimumHeight(600)
self.text_edit.setStyleSheet("""
QTextEdit {
background-color: #ffffff;
border: 1px solid #d0d0d0;
border-radius: 0px;
font-family: 'Calibri', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 12pt;
color: #000000;
padding: 40px;
line-height: 1.5;
}
""")
# 设置默认文档内容
self.text_edit.setPlainText("在此输入您的内容...")
document_layout.addWidget(self.text_edit)
document_container.setLayout(document_layout)
scroll_area.setWidget(document_container)
main_layout.addWidget(scroll_area)
def init_network_services(self):
"""初始化网络服务"""
# 获取天气信息
self.weather_thread = WeatherFetchThread()
self.weather_thread.weather_fetched.connect(self.update_weather_display)
self.weather_thread.start()
# 获取每日名言
self.quote_thread = QuoteFetchThread()
self.quote_thread.quote_fetched.connect(self.update_quote_display)
self.quote_thread.start()
def init_typing_logic(self):
"""初始化打字逻辑"""
# 使用默认内容初始化打字逻辑
default_content = "欢迎使用MagicWord隐私学习软件\n\n这是一个仿Microsoft Word界面的学习工具。"
self.typing_logic = TypingLogic(default_content)
self.typing_logic.reset()
def connect_signals(self):
"""连接信号和槽"""
# 文本变化信号
self.text_edit.textChanged.connect(self.on_text_changed)
# Ribbon按钮信号
if hasattr(self.ribbon, 'tabs'):
for tab_name, tab_btn in self.ribbon.tabs.items():
tab_btn.clicked.connect(lambda checked, name=tab_name: self.on_tab_changed(name))
def on_text_changed(self):
"""文本变化处理 - 逐步显示模式"""
# 如果正在加载文件,跳过处理
if self.is_loading_file:
return
# 如果有导入的内容,实现逐步显示
if self.imported_content and self.typing_logic:
current_text = self.text_edit.toPlainText()
# 计算应该显示的字符数(用户输入多少个字符就显示多少个)
input_length = len(current_text)
# 如果用户输入长度超过了导入内容长度,限制在导入内容长度内
if input_length > len(self.imported_content):
input_length = len(self.imported_content)
# 如果显示的字符数需要更新
if input_length != self.displayed_chars:
self.displayed_chars = input_length
# 获取应该显示的文本部分
display_text = self.imported_content[:self.displayed_chars]
# 临时禁用文本变化信号,避免递归
self.text_edit.textChanged.disconnect(self.on_text_changed)
# 更新文本编辑器内容
self.text_edit.setPlainText(display_text)
# 重新连接文本变化信号
self.text_edit.textChanged.connect(self.on_text_changed)
# 将光标移动到末尾
cursor = self.text_edit.textCursor()
cursor.movePosition(cursor.End)
self.text_edit.setTextCursor(cursor)
# 更新打字逻辑(只检查已显示的部分)
if display_text:
result = self.typing_logic.check_input(display_text)
self.typing_logic.update_position(display_text)
# 错误处理
if not result['correct'] and display_text:
expected_char = result.get('expected', '')
self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000)
self.highlight_next_char(result['position'], expected_char)
# 更新统计信息
stats = self.typing_logic.get_statistics()
self.update_status_bar(stats)
# 检查是否完成
if self.displayed_chars >= len(self.imported_content):
self.on_lesson_complete()
return
# 更新状态栏显示进度
progress_percentage = (self.displayed_chars / len(self.imported_content)) * 100
self.status_bar.showMessage(f"逐步显示进度: {progress_percentage:.1f}% ({self.displayed_chars}/{len(self.imported_content)})", 2000)
# 标记文档为已修改
if not self.is_modified:
self.is_modified = True
self.update_window_title()
def on_tab_changed(self, tab_name):
"""标签切换处理"""
# 更新标签状态
for name, btn in self.ribbon.tabs.items():
btn.setChecked(name == tab_name)
# 这里可以根据不同标签切换不同的功能区内容
print(f"切换到标签: {tab_name}")
def update_weather_display(self, weather_data):
"""更新天气显示"""
if 'error' in weather_data:
self.status_bar.showMessage(f"天气信息获取失败: {weather_data['error']}", 3000)
else:
temp = weather_data.get('temperature', 'N/A')
desc = weather_data.get('description', 'N/A')
self.status_bar.showMessage(f"天气: {desc}, {temp}°C", 5000)
def update_quote_display(self, quote_data):
"""更新名言显示"""
if 'error' not in quote_data:
content = quote_data.get('content', '获取名言失败')
author = quote_data.get('author', '未知')
self.status_bar.showMessage(f"每日名言: {content} - {author}", 10000)
def update_status_bar(self, stats):
"""更新状态栏统计信息"""
if stats:
# 获取打字统计信息
total_chars = stats.get('total_chars', 0)
typed_chars = stats.get('typed_chars', 0)
error_count = stats.get('error_count', 0)
accuracy_rate = stats.get('accuracy_rate', 0)
# 计算进度百分比
progress_text = ""
if total_chars > 0:
progress_percentage = (typed_chars / total_chars) * 100
progress_text = f"进度: {progress_percentage:.1f}%"
# 计算准确率
accuracy_text = f"准确率: {accuracy_rate:.1%}"
# 显示统计信息
status_text = f"{progress_text} | {accuracy_text} | 已输入: {typed_chars}/{total_chars} | 错误: {error_count}"
self.status_bar.showMessage(status_text, 0) # 0表示不自动消失
# 更新字数统计标签(如果存在)
if hasattr(self.status_bar, 'words_label'):
self.status_bar.words_label.setText(f"总字数: {total_chars}")
# 更新进度标签(如果存在)
if hasattr(self.status_bar, 'progress_label'):
self.status_bar.progress_label.setText(f"进度: {progress_percentage:.1f}%" if total_chars > 0 else "进度: 0%")
def update_window_title(self):
"""更新窗口标题"""
file_name = "文档1"
if self.current_file_path:
file_name = os.path.basename(self.current_file_path)
modified = "*" if self.is_modified else ""
self.setWindowTitle(f"{file_name}{modified} - MagicWord")
def new_document(self):
"""新建文档"""
self.text_edit.clear()
self.current_file_path = None
self.is_modified = False
self.update_window_title()
if self.typing_logic:
self.typing_logic.reset()
def open_file(self):
"""打开文件并设置为打字学习内容 - 逐步显示模式"""
file_path, _ = QFileDialog.getOpenFileName(
self, "打开文件", "",
"文档文件 (*.docx *.txt *.pdf);;所有文件 (*.*)"
)
if file_path:
try:
# 解析文件
parser = FileParser()
content = parser.parse_file(file_path)
if content:
# 设置文件加载标志
self.is_loading_file = True
# 存储完整内容但不立即显示
self.imported_content = content
self.displayed_chars = 0
# 设置学习内容到打字逻辑
if self.typing_logic:
self.typing_logic.reset(content) # 重置打字状态并设置新内容
# 清空文本编辑器,准备逐步显示
self.text_edit.clear()
# 清除文件加载标志
self.is_loading_file = False
# 设置当前文件路径
self.current_file_path = file_path
self.is_modified = False
self.update_window_title()
# 更新状态栏
self.status_bar.showMessage(f"已打开学习文件: {os.path.basename(file_path)},开始打字逐步显示学习内容!", 5000)
# 更新字数统计
if hasattr(self.status_bar, 'words_label'):
self.status_bar.words_label.setText(f"总字数: {len(content)}")
else:
QMessageBox.warning(self, "警告", "无法读取文件内容或文件为空")
except Exception as e:
# 确保在异常情况下也清除标志
self.is_loading_file = False
QMessageBox.critical(self, "错误", f"打开文件失败: {str(e)}")
print(f"文件打开错误详情: {e}") # 调试信息
def save_file(self):
"""保存文件"""
if self.current_file_path:
try:
with open(self.current_file_path, 'w', encoding='utf-8') as f:
f.write(self.text_edit.toPlainText())
self.is_modified = False
self.update_window_title()
self.status_bar.showMessage("文件已保存", 3000)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
else:
self.save_as_file()
def save_as_file(self):
"""另存为"""
file_path, _ = QFileDialog.getSaveFileName(
self, "另存为", "", "文本文档 (*.txt);;所有文件 (*.*)"
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(self.text_edit.toPlainText())
self.current_file_path = file_path
self.is_modified = False
self.update_window_title()
self.status_bar.showMessage(f"已保存: {os.path.basename(file_path)}", 3000)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
def undo(self):
"""撤销"""
self.text_edit.undo()
def redo(self):
"""重做"""
self.text_edit.redo()
def cut(self):
"""剪切"""
self.text_edit.cut()
def copy(self):
"""复制"""
self.text_edit.copy()
def paste(self):
"""粘贴"""
self.text_edit.paste()
def toggle_reading_view(self):
"""切换阅读视图"""
# 这里可以实现阅读视图的逻辑
self.status_bar.showMessage("阅读视图功能开发中...", 3000)
def toggle_print_layout(self):
"""切换打印布局"""
# 这里可以实现打印布局的逻辑
self.status_bar.showMessage("打印布局功能开发中...", 3000)
def show_about(self):
"""显示关于对话框"""
QMessageBox.about(
self, "关于 MagicWord",
"MagicWord - 隐私学习软件\n\n"
"版本: 2.0\n"
"基于 Microsoft Word 界面设计\n\n"
"功能特色:\n"
"• 仿Word界面设计\n"
"• 隐私学习模式\n"
"• 多格式文档支持\n"
"• 实时进度跟踪\n"
"• 天气和名言显示"
)
def highlight_next_char(self, position, expected_char):
"""高亮显示下一个期望字符"""
if position < len(self.typing_logic.learning_content):
# 获取当前光标位置
cursor = self.text_edit.textCursor()
cursor.setPosition(position)
# 选择下一个字符
cursor.movePosition(cursor.Right, cursor.KeepAnchor, 1)
# 设置高亮格式
format = QTextCharFormat()
format.setBackground(QColor(255, 255, 0, 128)) # 黄色半透明背景
format.setForeground(QColor(255, 0, 0)) # 红色文字
format.setFontWeight(QFont.Bold)
# 应用格式
cursor.setCharFormat(format)
def on_lesson_complete(self):
"""课程完成处理"""
stats = self.typing_logic.get_statistics()
QMessageBox.information(
self, "恭喜",
"恭喜完成本课程学习!\n\n"
f"准确率: {stats['accuracy_rate']*100:.1f}%\n"
f"总字符数: {stats['total_chars']}\n"
f"错误次数: {stats['error_count']}"
)
# 重置状态
if self.typing_logic:
self.typing_logic.reset()
def closeEvent(self, event):
"""关闭事件处理"""
if self.is_modified:
reply = QMessageBox.question(
self, "确认退出",
"文档已修改,是否保存更改?",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
)
if reply == QMessageBox.Save:
self.save_file()
event.accept()
elif reply == QMessageBox.Discard:
event.accept()
else:
event.ignore()
else:
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
# 设置应用程序样式
app.setStyle('Windows')
# 创建并显示主窗口
window = WordStyleMainWindow()
window.show()
sys.exit(app.exec_())
Loading…
Cancel
Save