|
|
|
|
@ -16,13 +16,13 @@ from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
|
|
|
QMessageBox, QSplitter, QTabWidget, QStatusBar,
|
|
|
|
|
QToolBar, QPushButton, QLabel, QFrame, QApplication,
|
|
|
|
|
QDesktopWidget, QStyleFactory, QStyle, QGroupBox, QProgressBar,
|
|
|
|
|
QScrollArea, QTextBrowser)
|
|
|
|
|
QScrollArea, QTextBrowser, QFontComboBox, QComboBox, QColorDialog)
|
|
|
|
|
from PyQt5.QtCore import (Qt, QTimer, pyqtSignal, QThread, QObject, QUrl,
|
|
|
|
|
QSettings, QPoint, QSize, QEvent, QPropertyAnimation,
|
|
|
|
|
QEasingCurve, QRect)
|
|
|
|
|
from PyQt5.QtGui import (QFont, QPalette, QColor, QIcon, QKeySequence,
|
|
|
|
|
QTextCursor, QTextCharFormat, QPainter, QPixmap,
|
|
|
|
|
QFontDatabase, QSyntaxHighlighter, QTextDocument)
|
|
|
|
|
QFontDatabase, QSyntaxHighlighter, QTextDocument, QFontInfo)
|
|
|
|
|
|
|
|
|
|
# 导入MagicWord现有功能
|
|
|
|
|
from services.network_service import NetworkService
|
|
|
|
|
@ -30,6 +30,7 @@ from file_manager.file_operations import FileManager
|
|
|
|
|
from learning_mode_window import LearningModeWindow
|
|
|
|
|
from ui.theme_manager import ThemeManager
|
|
|
|
|
from ui.ai_chat_panel import AIChatPanel
|
|
|
|
|
from ui.snake_game import SnakeGameWindow
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MarkTextEditor(QPlainTextEdit):
|
|
|
|
|
@ -44,6 +45,97 @@ class MarkTextEditor(QPlainTextEdit):
|
|
|
|
|
self.current_file_path = None
|
|
|
|
|
self.is_modified = False
|
|
|
|
|
|
|
|
|
|
def apply_format(self, format_type: str, value=None):
|
|
|
|
|
"""应用文本格式"""
|
|
|
|
|
cursor = self.textCursor()
|
|
|
|
|
if not cursor.hasSelection():
|
|
|
|
|
# 如果没有选中文本,设置默认格式供后续输入使用
|
|
|
|
|
char_format = self.currentCharFormat()
|
|
|
|
|
|
|
|
|
|
if format_type == "bold":
|
|
|
|
|
char_format.setFontWeight(QFont.Bold if value else QFont.Normal)
|
|
|
|
|
elif format_type == "italic":
|
|
|
|
|
char_format.setFontItalic(value)
|
|
|
|
|
elif format_type == "underline":
|
|
|
|
|
char_format.setFontUnderline(value)
|
|
|
|
|
elif format_type == "font_family":
|
|
|
|
|
char_format.setFontFamily(value)
|
|
|
|
|
elif format_type == "font_size":
|
|
|
|
|
char_format.setFontPointSize(value)
|
|
|
|
|
elif format_type == "color":
|
|
|
|
|
char_format.setForeground(QColor(value))
|
|
|
|
|
elif format_type == "background_color":
|
|
|
|
|
char_format.setBackground(QColor(value))
|
|
|
|
|
|
|
|
|
|
self.setCurrentCharFormat(char_format)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 如果有选中文本,直接修改选中文本的格式
|
|
|
|
|
char_format = cursor.charFormat()
|
|
|
|
|
|
|
|
|
|
if format_type == "bold":
|
|
|
|
|
char_format.setFontWeight(QFont.Bold if value else QFont.Normal)
|
|
|
|
|
elif format_type == "italic":
|
|
|
|
|
char_format.setFontItalic(value)
|
|
|
|
|
elif format_type == "underline":
|
|
|
|
|
char_format.setFontUnderline(value)
|
|
|
|
|
elif format_type == "font_family":
|
|
|
|
|
char_format.setFontFamily(value)
|
|
|
|
|
elif format_type == "font_size":
|
|
|
|
|
char_format.setFontPointSize(value)
|
|
|
|
|
elif format_type == "color":
|
|
|
|
|
char_format.setForeground(QColor(value))
|
|
|
|
|
elif format_type == "background_color":
|
|
|
|
|
char_format.setBackground(QColor(value))
|
|
|
|
|
elif format_type == "strikethrough":
|
|
|
|
|
char_format.setFontStrikeOut(value)
|
|
|
|
|
elif format_type == "superscript":
|
|
|
|
|
char_format.setVerticalAlignment(QTextCharFormat.AlignSuperScript if value else QTextCharFormat.AlignNormal)
|
|
|
|
|
elif format_type == "subscript":
|
|
|
|
|
char_format.setVerticalAlignment(QTextCharFormat.AlignSubScript if value else QTextCharFormat.AlignNormal)
|
|
|
|
|
|
|
|
|
|
cursor.setCharFormat(char_format)
|
|
|
|
|
|
|
|
|
|
def apply_paragraph_format(self, format_type: str, value=None):
|
|
|
|
|
"""应用段落格式"""
|
|
|
|
|
cursor = self.textCursor()
|
|
|
|
|
block_format = cursor.blockFormat()
|
|
|
|
|
|
|
|
|
|
if format_type == "alignment":
|
|
|
|
|
block_format.setAlignment(value)
|
|
|
|
|
elif format_type == "line_spacing":
|
|
|
|
|
block_format.setLineHeight(value, QTextBlockFormat.ProportionalHeight)
|
|
|
|
|
elif format_type == "top_margin":
|
|
|
|
|
block_format.setTopMargin(value)
|
|
|
|
|
elif format_type == "bottom_margin":
|
|
|
|
|
block_format.setBottomMargin(value)
|
|
|
|
|
elif format_type == "left_margin":
|
|
|
|
|
block_format.setLeftMargin(value)
|
|
|
|
|
elif format_type == "right_margin":
|
|
|
|
|
block_format.setRightMargin(value)
|
|
|
|
|
elif format_type == "indent":
|
|
|
|
|
block_format.setIndent(value)
|
|
|
|
|
|
|
|
|
|
cursor.setBlockFormat(block_format)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_format_info(self):
|
|
|
|
|
"""获取当前光标位置的格式信息"""
|
|
|
|
|
cursor = self.textCursor()
|
|
|
|
|
char_format = cursor.charFormat()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"font_family": char_format.fontFamily(),
|
|
|
|
|
"font_size": char_format.fontPointSize(),
|
|
|
|
|
"bold": char_format.fontWeight() == QFont.Bold,
|
|
|
|
|
"italic": char_format.fontItalic(),
|
|
|
|
|
"underline": char_format.fontUnderline(),
|
|
|
|
|
"strikethrough": char_format.fontStrikeOut(),
|
|
|
|
|
"color": char_format.foreground().color().name(),
|
|
|
|
|
"background_color": char_format.background().color().name()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def setup_editor(self):
|
|
|
|
|
"""设置编辑器样式和功能 - 深色主题"""
|
|
|
|
|
# 设置字体 - 使用等宽字体,支持多平台
|
|
|
|
|
@ -280,11 +372,13 @@ class MarkTextSideBar(QWidget):
|
|
|
|
|
self.quote_btn = QPushButton("📖 每日名言")
|
|
|
|
|
self.insert_weather_btn = QPushButton("🌈 插入天气")
|
|
|
|
|
self.insert_quote_btn = QPushButton("✨ 插入名言")
|
|
|
|
|
self.snake_game_btn = QPushButton("🐍 贪吃蛇游戏")
|
|
|
|
|
|
|
|
|
|
tools_layout.addWidget(self.weather_btn)
|
|
|
|
|
tools_layout.addWidget(self.quote_btn)
|
|
|
|
|
tools_layout.addWidget(self.insert_weather_btn)
|
|
|
|
|
tools_layout.addWidget(self.insert_quote_btn)
|
|
|
|
|
tools_layout.addWidget(self.snake_game_btn)
|
|
|
|
|
|
|
|
|
|
tools_group.setLayout(tools_layout)
|
|
|
|
|
layout.addWidget(tools_group)
|
|
|
|
|
@ -306,6 +400,7 @@ class MarkTextSideBar(QWidget):
|
|
|
|
|
self.quote_btn.clicked.connect(self.parent.show_quote_info)
|
|
|
|
|
self.insert_weather_btn.clicked.connect(self.parent.insert_weather_to_editor)
|
|
|
|
|
self.insert_quote_btn.clicked.connect(self.parent.insert_quote_to_editor)
|
|
|
|
|
self.snake_game_btn.clicked.connect(self.parent.open_snake_game)
|
|
|
|
|
|
|
|
|
|
# 设置固定宽度
|
|
|
|
|
self.setFixedWidth(220)
|
|
|
|
|
@ -746,6 +841,33 @@ class MarkTextMainWindow(QMainWindow):
|
|
|
|
|
select_all_action.triggered.connect(self.editor_select_all)
|
|
|
|
|
edit_menu.addAction(select_all_action)
|
|
|
|
|
|
|
|
|
|
edit_menu.addSeparator()
|
|
|
|
|
|
|
|
|
|
# 格式子菜单
|
|
|
|
|
format_menu = edit_menu.addMenu('格式(&F)')
|
|
|
|
|
|
|
|
|
|
italic_action = QAction('斜体(&I)', self)
|
|
|
|
|
italic_action.setShortcut('Ctrl+I')
|
|
|
|
|
italic_action.triggered.connect(self.on_italic_clicked)
|
|
|
|
|
format_menu.addAction(italic_action)
|
|
|
|
|
|
|
|
|
|
underline_action = QAction('下划线(&U)', self)
|
|
|
|
|
underline_action.setShortcut('Ctrl+U')
|
|
|
|
|
underline_action.triggered.connect(self.on_underline_clicked)
|
|
|
|
|
format_menu.addAction(underline_action)
|
|
|
|
|
|
|
|
|
|
format_menu.addSeparator()
|
|
|
|
|
|
|
|
|
|
color_action = QAction('字体颜色(&C)...', self)
|
|
|
|
|
color_action.triggered.connect(self.on_color_clicked)
|
|
|
|
|
format_menu.addAction(color_action)
|
|
|
|
|
|
|
|
|
|
bg_color_action = QAction('背景颜色(&G)...', self)
|
|
|
|
|
bg_color_action.triggered.connect(self.on_background_color_clicked)
|
|
|
|
|
format_menu.addAction(bg_color_action)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 工具菜单
|
|
|
|
|
tools_menu = menubar.addMenu('工具(&T)')
|
|
|
|
|
tools_menu.setStyleSheet("""
|
|
|
|
|
@ -861,20 +983,77 @@ class MarkTextMainWindow(QMainWindow):
|
|
|
|
|
QToolBar QToolButton:pressed {
|
|
|
|
|
background-color: #1f6feb;
|
|
|
|
|
}
|
|
|
|
|
QToolBar QComboBox {
|
|
|
|
|
background-color: #30363d;
|
|
|
|
|
color: #c9d1d9;
|
|
|
|
|
border: 1px solid #484f58;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
padding: 5px;
|
|
|
|
|
min-width: 80px;
|
|
|
|
|
}
|
|
|
|
|
QToolBar QComboBox::drop-down {
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
QToolBar QComboBox::down-arrow {
|
|
|
|
|
image: none;
|
|
|
|
|
border-left: 5px solid transparent;
|
|
|
|
|
border-right: 5px solid transparent;
|
|
|
|
|
border-top: 5px solid #c9d1d9;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 添加常用工具按钮
|
|
|
|
|
# 文件操作按钮
|
|
|
|
|
toolbar.addAction("新建", self.new_file)
|
|
|
|
|
toolbar.addAction("打开", self.open_file)
|
|
|
|
|
toolbar.addAction("保存", self.save_file)
|
|
|
|
|
toolbar.addSeparator()
|
|
|
|
|
|
|
|
|
|
# 编辑操作按钮
|
|
|
|
|
toolbar.addAction("撤销", lambda: self.get_current_editor().undo() if self.get_current_editor() else None)
|
|
|
|
|
toolbar.addAction("重做", lambda: self.get_current_editor().redo() if self.get_current_editor() else None)
|
|
|
|
|
toolbar.addSeparator()
|
|
|
|
|
|
|
|
|
|
# 剪切复制粘贴
|
|
|
|
|
toolbar.addAction("剪切", lambda: self.get_current_editor().cut() if self.get_current_editor() else None)
|
|
|
|
|
toolbar.addAction("复制", lambda: self.get_current_editor().copy() if self.get_current_editor() else None)
|
|
|
|
|
toolbar.addAction("粘贴", lambda: self.get_current_editor().paste() if self.get_current_editor() else None)
|
|
|
|
|
toolbar.addSeparator()
|
|
|
|
|
|
|
|
|
|
# 字体选择
|
|
|
|
|
self.font_combo = QFontComboBox()
|
|
|
|
|
self.font_combo.setCurrentFont(QFont("Consolas"))
|
|
|
|
|
self.font_combo.currentFontChanged.connect(self.on_font_changed)
|
|
|
|
|
toolbar.addWidget(self.font_combo)
|
|
|
|
|
|
|
|
|
|
# 字体大小选择
|
|
|
|
|
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.setCurrentText("14")
|
|
|
|
|
self.font_size_combo.currentTextChanged.connect(self.on_font_size_changed)
|
|
|
|
|
toolbar.addWidget(self.font_size_combo)
|
|
|
|
|
|
|
|
|
|
toolbar.addSeparator()
|
|
|
|
|
|
|
|
|
|
# 格式按钮
|
|
|
|
|
italic_btn = QAction("斜体", self)
|
|
|
|
|
italic_btn.setCheckable(True)
|
|
|
|
|
italic_btn.triggered.connect(self.on_italic_clicked)
|
|
|
|
|
toolbar.addAction(italic_btn)
|
|
|
|
|
|
|
|
|
|
underline_btn = QAction("下划线", self)
|
|
|
|
|
underline_btn.setCheckable(True)
|
|
|
|
|
underline_btn.triggered.connect(self.on_underline_clicked)
|
|
|
|
|
toolbar.addAction(underline_btn)
|
|
|
|
|
|
|
|
|
|
color_btn = QAction("颜色", self)
|
|
|
|
|
color_btn.triggered.connect(self.on_color_clicked)
|
|
|
|
|
toolbar.addAction(color_btn)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toolbar.addSeparator()
|
|
|
|
|
|
|
|
|
|
# 学习模式相关
|
|
|
|
|
toolbar.addAction("学习模式", self.switch_to_learning_mode)
|
|
|
|
|
toolbar.addAction("导入文件", self.import_file)
|
|
|
|
|
toolbar.addSeparator()
|
|
|
|
|
@ -1323,25 +1502,86 @@ class MarkTextMainWindow(QMainWindow):
|
|
|
|
|
try:
|
|
|
|
|
weather_info = self.network_service.get_weather_info()
|
|
|
|
|
if weather_info:
|
|
|
|
|
weather_text = f"\n\n🌤 今日天气: {weather_info['city']} {weather_info['temperature']}°C, {weather_info['description']}\n"
|
|
|
|
|
weather_text += f"湿度: {weather_info['humidity']}% | 风速: {weather_info['wind_speed']}m/s\n\n"
|
|
|
|
|
# 基础天气信息
|
|
|
|
|
weather_text = f"\n【天气信息】\n{weather_info['city']}: {weather_info['temperature']}°C, {weather_info['description']}"
|
|
|
|
|
weather_text += f"\n湿度: {weather_info['humidity']}%"
|
|
|
|
|
weather_text += f"\n风速: {weather_info['wind_speed']}m/s"
|
|
|
|
|
|
|
|
|
|
# 添加生活提示
|
|
|
|
|
lifetips = weather_info.get('lifetips', [])
|
|
|
|
|
if lifetips:
|
|
|
|
|
weather_text += "🌟 生活提示:\n"
|
|
|
|
|
weather_text += "\n\n🌟 生活提示:"
|
|
|
|
|
for tip in lifetips:
|
|
|
|
|
weather_text += f"• {tip}\n"
|
|
|
|
|
weather_text += "\n"
|
|
|
|
|
weather_text += f"\n• {tip}"
|
|
|
|
|
|
|
|
|
|
weather_text += "\n"
|
|
|
|
|
|
|
|
|
|
cursor = editor.textCursor()
|
|
|
|
|
cursor.insertText(weather_text)
|
|
|
|
|
self.statusBar().showMessage("天气信息已插入到文档", 2000)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", "无法获取天气信息")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.critical(self, "错误", f"插入天气信息失败: {str(e)}")
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", "请先打开一个文档")
|
|
|
|
|
|
|
|
|
|
# 文本格式化功能函数
|
|
|
|
|
def on_font_changed(self, font):
|
|
|
|
|
"""字体选择变化"""
|
|
|
|
|
editor = self.get_current_editor()
|
|
|
|
|
if editor:
|
|
|
|
|
editor.apply_format("font_family", font.family())
|
|
|
|
|
|
|
|
|
|
def on_font_size_changed(self, size_text):
|
|
|
|
|
"""字体大小选择变化"""
|
|
|
|
|
editor = self.get_current_editor()
|
|
|
|
|
if editor:
|
|
|
|
|
try:
|
|
|
|
|
size = int(size_text)
|
|
|
|
|
editor.apply_format("font_size", size)
|
|
|
|
|
except ValueError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def on_italic_clicked(self):
|
|
|
|
|
"""斜体按钮点击"""
|
|
|
|
|
editor = self.get_current_editor()
|
|
|
|
|
if editor:
|
|
|
|
|
current_format = editor.get_format_info()
|
|
|
|
|
editor.apply_format("italic", not current_format["italic"])
|
|
|
|
|
|
|
|
|
|
def on_underline_clicked(self):
|
|
|
|
|
"""下划线按钮点击"""
|
|
|
|
|
editor = self.get_current_editor()
|
|
|
|
|
if editor:
|
|
|
|
|
current_format = editor.get_format_info()
|
|
|
|
|
editor.apply_format("underline", not current_format["underline"])
|
|
|
|
|
|
|
|
|
|
def on_color_clicked(self):
|
|
|
|
|
"""颜色按钮点击"""
|
|
|
|
|
editor = self.get_current_editor()
|
|
|
|
|
if editor:
|
|
|
|
|
color = QColorDialog.getColor()
|
|
|
|
|
if color.isValid():
|
|
|
|
|
editor.apply_format("color", color.name())
|
|
|
|
|
|
|
|
|
|
def on_background_color_clicked(self):
|
|
|
|
|
"""背景颜色按钮点击"""
|
|
|
|
|
editor = self.get_current_editor()
|
|
|
|
|
if editor:
|
|
|
|
|
color = QColorDialog.getColor()
|
|
|
|
|
if color.isValid():
|
|
|
|
|
editor.apply_format("background_color", color.name())
|
|
|
|
|
|
|
|
|
|
def open_snake_game(self):
|
|
|
|
|
"""打开贪吃蛇游戏"""
|
|
|
|
|
try:
|
|
|
|
|
self.snake_game_window = SnakeGameWindow(self)
|
|
|
|
|
self.snake_game_window.show()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.critical(self, "错误", f"无法打开贪吃蛇游戏: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def insert_quote_to_editor(self):
|
|
|
|
|
"""将名言插入到编辑器 - 优化错误处理"""
|
|
|
|
|
|