|
|
|
|
@ -226,23 +226,6 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
background-color: #f3f2f1;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def init_network_services(self):
|
|
|
|
|
"""
|
|
|
|
|
初始化网络服务:启动天气和每日一言的后台线程并连接信号
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 天气线程
|
|
|
|
|
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()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"初始化网络服务失败: {e}")
|
|
|
|
|
|
|
|
|
|
def create_menu_bar(self):
|
|
|
|
|
"""创建菜单栏"""
|
|
|
|
|
@ -412,35 +395,47 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 创建文档容器(用于多页布局)
|
|
|
|
|
# 创建文档容器
|
|
|
|
|
document_container = QWidget()
|
|
|
|
|
container_layout = QHBoxLayout()
|
|
|
|
|
container_layout.setContentsMargins(0, 40, 0, 40) # workspace vertical padding
|
|
|
|
|
container_layout.setSpacing(0)
|
|
|
|
|
|
|
|
|
|
# 中心列,垂直堆叠多个 page_frame
|
|
|
|
|
pages_column = QWidget()
|
|
|
|
|
pages_column_layout = QVBoxLayout()
|
|
|
|
|
pages_column_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
pages_column_layout.setSpacing(20) # 间隔表示页间距
|
|
|
|
|
pages_column.setLayout(pages_column_layout)
|
|
|
|
|
|
|
|
|
|
# 保存引用以便后续构建/清理页面
|
|
|
|
|
self._pages_column_layout = pages_column_layout
|
|
|
|
|
self._page_frames = []
|
|
|
|
|
|
|
|
|
|
# center the pages_column horizontally inside document_container
|
|
|
|
|
container_layout.addStretch()
|
|
|
|
|
container_layout.addWidget(pages_column)
|
|
|
|
|
container_layout.addStretch()
|
|
|
|
|
document_container.setLayout(container_layout)
|
|
|
|
|
|
|
|
|
|
# 将 document_container 放入 scroll_area 并添加到 main_layout
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 构建首个空白页面(后续会根据内容重建)
|
|
|
|
|
self.build_pages("")
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
|
"""初始化打字逻辑"""
|
|
|
|
|
@ -448,103 +443,6 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
default_content = "欢迎使用MagicWord隐私学习软件!\n\n这是一个仿Microsoft Word界面的学习工具。"
|
|
|
|
|
self.typing_logic = TypingLogic(default_content)
|
|
|
|
|
self.typing_logic.reset()
|
|
|
|
|
|
|
|
|
|
def clear_pages(self):
|
|
|
|
|
"""清理已创建的页面控件"""
|
|
|
|
|
try:
|
|
|
|
|
while self._page_frames:
|
|
|
|
|
frame = self._page_frames.pop()
|
|
|
|
|
# 从布局中移除并删除
|
|
|
|
|
self._pages_column_layout.removeWidget(frame)
|
|
|
|
|
frame.setParent(None)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def build_pages(self, full_text):
|
|
|
|
|
"""
|
|
|
|
|
根据传入文本按近似字符数分页并在界面中渲染多个 page_frame。
|
|
|
|
|
这是一个近似实现:基于字体度量估算每页可容纳字符数,然后按字符切分。
|
|
|
|
|
"""
|
|
|
|
|
from PyQt5.QtWidgets import QTextEdit, QLabel
|
|
|
|
|
from PyQt5.QtGui import QFontMetrics
|
|
|
|
|
|
|
|
|
|
# 清理旧页面
|
|
|
|
|
self.clear_pages()
|
|
|
|
|
|
|
|
|
|
# 基本页面尺寸与内边距(应与 create_document_area 中一致)
|
|
|
|
|
page_width = 816
|
|
|
|
|
page_height = 1056
|
|
|
|
|
page_margin = 72
|
|
|
|
|
|
|
|
|
|
inner_width = page_width - page_margin * 2
|
|
|
|
|
inner_height = page_height - page_margin * 2
|
|
|
|
|
|
|
|
|
|
# 使用默认字体度量进行估算
|
|
|
|
|
font = QFont("Calibri", 12)
|
|
|
|
|
fm = QFontMetrics(font)
|
|
|
|
|
avg_char_width = max(1, fm.averageCharWidth())
|
|
|
|
|
line_height = fm.lineSpacing()
|
|
|
|
|
|
|
|
|
|
chars_per_line = max(1, int(inner_width / avg_char_width))
|
|
|
|
|
lines_per_page = max(1, int(inner_height / line_height))
|
|
|
|
|
chars_per_page = chars_per_line * lines_per_page
|
|
|
|
|
|
|
|
|
|
# 防止 chars_per_page 过小
|
|
|
|
|
if chars_per_page < 200:
|
|
|
|
|
chars_per_page = 2000
|
|
|
|
|
|
|
|
|
|
# 切分文本
|
|
|
|
|
pages = []
|
|
|
|
|
if not full_text:
|
|
|
|
|
pages = [""]
|
|
|
|
|
else:
|
|
|
|
|
i = 0
|
|
|
|
|
L = len(full_text)
|
|
|
|
|
while i < L:
|
|
|
|
|
pages.append(full_text[i:i+chars_per_page])
|
|
|
|
|
i += chars_per_page
|
|
|
|
|
|
|
|
|
|
# 为每页创建 page_frame
|
|
|
|
|
for idx, page_text in enumerate(pages, start=1):
|
|
|
|
|
page_frame = QFrame()
|
|
|
|
|
page_frame.setObjectName('page_frame')
|
|
|
|
|
page_frame.setFixedWidth(page_width)
|
|
|
|
|
page_frame.setFixedHeight(page_height)
|
|
|
|
|
page_layout = QVBoxLayout()
|
|
|
|
|
page_layout.setContentsMargins(page_margin, page_margin, page_margin, page_margin)
|
|
|
|
|
page_layout.setSpacing(0)
|
|
|
|
|
|
|
|
|
|
# 文本编辑器(每页一个)
|
|
|
|
|
page_edit = QTextEdit()
|
|
|
|
|
page_edit.setPlainText(page_text)
|
|
|
|
|
page_edit.setFont(font)
|
|
|
|
|
page_edit.setStyleSheet("background-color: transparent; border: none;")
|
|
|
|
|
page_edit.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
|
|
|
page_edit.setMinimumHeight(inner_height)
|
|
|
|
|
|
|
|
|
|
page_layout.addWidget(page_edit)
|
|
|
|
|
|
|
|
|
|
# 页码标签(居中)
|
|
|
|
|
page_number_label = QLabel(f"{idx}")
|
|
|
|
|
page_number_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
page_number_label.setStyleSheet("color: #666666; font-size: 11px;")
|
|
|
|
|
page_layout.addWidget(page_number_label)
|
|
|
|
|
|
|
|
|
|
page_frame.setLayout(page_layout)
|
|
|
|
|
page_frame.setStyleSheet("QFrame#page_frame { background: #ffffff; border: 1px solid #dcdcdc; border-radius: 2px; }")
|
|
|
|
|
|
|
|
|
|
# 将 page_frame 添加到列布局
|
|
|
|
|
self._pages_column_layout.addWidget(page_frame)
|
|
|
|
|
self._page_frames.append(page_frame)
|
|
|
|
|
|
|
|
|
|
# 绑定第一个页面的编辑器为 self.text_edit(保持老逻辑)
|
|
|
|
|
try:
|
|
|
|
|
first_frame = self._page_frames[0]
|
|
|
|
|
first_edit = first_frame.findChild(QTextEdit)
|
|
|
|
|
if first_edit:
|
|
|
|
|
self.text_edit = first_edit
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def connect_signals(self):
|
|
|
|
|
"""连接信号和槽"""
|
|
|
|
|
@ -947,8 +845,7 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
|
|
|
|
|
def new_document(self):
|
|
|
|
|
"""新建文档"""
|
|
|
|
|
# 构建空白页面
|
|
|
|
|
self.build_pages("")
|
|
|
|
|
self.text_edit.clear()
|
|
|
|
|
self.current_file_path = None
|
|
|
|
|
self.is_modified = False
|
|
|
|
|
self.update_window_title()
|
|
|
|
|
@ -972,32 +869,29 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
if content:
|
|
|
|
|
# 设置文件加载标志
|
|
|
|
|
self.is_loading_file = True
|
|
|
|
|
|
|
|
|
|
# 存储完整内容
|
|
|
|
|
|
|
|
|
|
# 存储完整内容但不立即显示
|
|
|
|
|
self.imported_content = content
|
|
|
|
|
self.displayed_chars = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 设置学习内容到打字逻辑
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset(content) # 重置打字状态并设置新内容
|
|
|
|
|
|
|
|
|
|
# 按页构建并显示内容
|
|
|
|
|
try:
|
|
|
|
|
self.build_pages(content)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"分页显示失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 清空文本编辑器,准备逐步显示
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
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)}")
|
|
|
|
|
@ -1017,26 +911,10 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
try:
|
|
|
|
|
# 如果是.docx文件,创建一个基本的Word文档
|
|
|
|
|
if self.current_file_path.endswith('.docx'):
|
|
|
|
|
# dynamically import python-docx if available to avoid static import errors
|
|
|
|
|
try:
|
|
|
|
|
import importlib.util
|
|
|
|
|
spec = importlib.util.find_spec('docx')
|
|
|
|
|
if spec is not None:
|
|
|
|
|
docx = importlib.import_module('docx')
|
|
|
|
|
Document = getattr(docx, 'Document', None)
|
|
|
|
|
else:
|
|
|
|
|
Document = None
|
|
|
|
|
except Exception:
|
|
|
|
|
Document = None
|
|
|
|
|
|
|
|
|
|
if Document:
|
|
|
|
|
doc = Document()
|
|
|
|
|
doc.add_paragraph(self.text_edit.toPlainText())
|
|
|
|
|
doc.save(self.current_file_path)
|
|
|
|
|
else:
|
|
|
|
|
# python-docx not available; fallback to writing plain text
|
|
|
|
|
with open(self.current_file_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
f.write(self.text_edit.toPlainText())
|
|
|
|
|
from docx import Document
|
|
|
|
|
doc = Document()
|
|
|
|
|
doc.add_paragraph(self.text_edit.toPlainText())
|
|
|
|
|
doc.save(self.current_file_path)
|
|
|
|
|
else:
|
|
|
|
|
# 对于其他格式,保持原有逻辑
|
|
|
|
|
with open(self.current_file_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
@ -1065,26 +943,10 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
try:
|
|
|
|
|
# 如果是.docx文件,创建一个基本的Word文档
|
|
|
|
|
if file_path.endswith('.docx'):
|
|
|
|
|
# dynamically import python-docx if available to avoid static import errors
|
|
|
|
|
try:
|
|
|
|
|
import importlib.util
|
|
|
|
|
spec = importlib.util.find_spec('docx')
|
|
|
|
|
if spec is not None:
|
|
|
|
|
docx = importlib.import_module('docx')
|
|
|
|
|
Document = getattr(docx, 'Document', None)
|
|
|
|
|
else:
|
|
|
|
|
Document = None
|
|
|
|
|
except Exception:
|
|
|
|
|
Document = None
|
|
|
|
|
|
|
|
|
|
if Document:
|
|
|
|
|
doc = Document()
|
|
|
|
|
doc.add_paragraph(self.text_edit.toPlainText())
|
|
|
|
|
doc.save(file_path)
|
|
|
|
|
else:
|
|
|
|
|
# python-docx not available; save as plain text
|
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
f.write(self.text_edit.toPlainText())
|
|
|
|
|
from docx import Document
|
|
|
|
|
doc = Document()
|
|
|
|
|
doc.add_paragraph(self.text_edit.toPlainText())
|
|
|
|
|
doc.save(file_path)
|
|
|
|
|
else:
|
|
|
|
|
# 对于其他格式,保持原有逻辑
|
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
|