Python编写文本编辑器
安装模块
pip install pyqt6 chardet markdown-it-py
import os
import sys
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QAction, QIcon, QFont
from PyQt6.QtWidgets import QApplication, QMainWindow, QTextEdit, QVBoxLayout, QWidget, QFileDialog, QTextBrowser, QPushButton, QHBoxLayout, QMenuBar, QDialog, QLabel, QMessageBox, QMenu, QFontDialog
from markdown_it import MarkdownIt
class AboutDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("关于本软件")
self.setGeometry(100, 100, 400, 200)
layout = QVBoxLayout()
label = QLabel("刘小猪·编辑器")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(label)
version_label = QLabel("版本号: 2.0")
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(version_label)
author_label = QLabel("作者: 刘小猪")
author_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(author_label)
license_label = QLabel("无需许可")
license_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(license_label)
close_button = QPushButton("关闭")
close_button.clicked.connect(self.close)
layout.addWidget(close_button)
self.setLayout(layout)
class MarkdownEditor(QMainWindow):
def __init__(self):
super().__init__()
self.current_file = ""
self.init_ui()
def init_ui(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QHBoxLayout()
self.text_edit = QTextEdit(self)
self.text_edit.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) # 设置自定义上下文菜单策略
self.text_edit.customContextMenuRequested.connect(self.show_custom_context_menu) # 连接自定义上下文菜单的槽函数
self.layout.addWidget(self.text_edit)
self.preview_browser = QTextBrowser(self)
self.layout.addWidget(self.preview_browser)
self.preview_browser.hide() # 初始时隐藏预览框
self.central_widget.setLayout(self.layout)
self.create_actions()
self.create_menu_bar()
self.setWindowTitle("刘小猪·编辑器")
self.setGeometry(100, 100, 800, 600)
# 创建一个定时器,用于实时更新预览
self.preview_timer = QTimer(self)
self.preview_timer.setInterval(1000) # 1秒钟更新一次
self.preview_timer.timeout.connect(self.preview_markdown)
self.preview_timer.start()
self.is_saved = True # 初始化为已保存
self.original_text = ""
self.saved_after_modification = True # 初始化为已保存
self.text_modified = False # 添加一个标志,用于跟踪文本是否已经修改过
self.text_edit.textChanged.connect(self.text_changed) # 连接文本修改事件
def create_actions(self):
self.open_action = QAction("打开", self)
self.open_action.setShortcut("Ctrl+O") # 使用Ctrl+O打开文件
self.open_action.triggered.connect(self.open_file)
self.save_action = QAction("保存", self)
self.save_action.setShortcut("Ctrl+S") # 使用Ctrl+S保存文件
self.save_action.triggered.connect(self.save_file)
self.preview_action = QAction("预览", self)
self.preview_action.setShortcut("Ctrl+P") # 使用Ctrl+P切换预览
self.preview_action.triggered.connect(self.toggle_preview)
self.about_action = QAction("关于", self)
self.about_action.setShortcut("Ctrl+H") # 使用Ctrl+H显示关于对话框
self.about_action.triggered.connect(self.show_about_dialog)
self.font_action = QAction("修改字体", self)
self.font_action.setShortcut("Ctrl+F") # 使用Ctrl+F修改字体
self.font_action.triggered.connect(self.choose_font)
def create_menu_bar(self):
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu("文件")
file_menu.addAction(self.open_action)
file_menu.addAction(self.save_action)
view_menu = menu_bar.addMenu("查看")
view_menu.addAction(self.preview_action)
edit_menu = menu_bar.addMenu("编辑")
edit_menu.addAction(self.font_action)
help_menu = menu_bar.addMenu("帮助")
help_menu.addAction(self.about_action)
def save_file(self):
if self.current_file:
text = self.text_edit.toPlainText()
with open(self.current_file, "w", encoding="utf-8") as file:
file.write(text)
self.original_text = text # Update the original text
self.is_saved = True # File is saved
self.saved_after_modification = True # Reset the flag
self.text_modified = False # 重置文本修改标志为False
else:
file_name, _ = QFileDialog.getSaveFileName(self, "保存文件", "", "Markdown 文件 (*.md);;文本文件 (*.txt)")
if file_name:
text = self.text_edit.toPlainText()
with open(file_name, "w", encoding="utf-8") as file:
file.write(text)
self.is_saved = True # 文件已保存
self.current_file = file_name
file_base_name = os.path.basename(file_name)
self.setWindowTitle(f"刘小猪·编辑器 - {file_base_name}")
self.text_modified = False # 重置文本修改标志为False
def open_file(self):
if self.text_modified:
reply = QMessageBox(self)
reply.setWindowTitle('确认打开')
reply.setText('<center>是否保存当前文件?</center>') # Center-align the text
reply.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel)
Yes_button = reply.button(QMessageBox.StandardButton.Yes)
Yes_button.setText('保存')
No_button = reply.button(QMessageBox.StandardButton.No)
No_button.setText('不保存')
Cancel_button = reply.button(QMessageBox.StandardButton.Cancel)
Cancel_button.setText('取消')
result = reply.exec()
if result == QMessageBox.StandardButton.Yes:
self.save_file()
elif result == QMessageBox.StandardButton.Cancel:
return
file_name, _ = QFileDialog.getOpenFileName(self, "打开文件", "", "Markdown 文件 (*.md);;文本文件 (*.txt);;所以文件(*.*)")
if file_name:
self.current_file = file_name
file_base_name = os.path.basename(file_name)
self.setWindowTitle(f"刘小猪·编辑器 - {file_base_name}")
try:
with open(file_name, "r", encoding="utf-8") as file:
text = file.read()
except UnicodeDecodeError:
with open(file_name, "r") as file:
text = file.read()
self.text_edit.setPlainText(text)
self.original_text = text
self.is_saved = True
self.text_modified = False # 重置文本修改标志为False
def toggle_preview(self):
if self.preview_browser.isHidden():
self.preview_markdown()
self.preview_browser.show()
else:
self.preview_browser.hide()
def preview_markdown(self):
markdown_text = self.text_edit.toPlainText()
scroll_position = self.preview_browser.verticalScrollBar().value()
html = MarkdownIt().render(markdown_text)
self.preview_browser.setHtml(html)
self.preview_browser.verticalScrollBar().setValue(scroll_position)
self.is_saved = False # 文件未保存
def show_about_dialog(self):
about_dialog = AboutDialog()
main_window_rect = self.geometry()
about_dialog_width = about_dialog.width()
about_dialog_height = about_dialog.height()
about_dialog.setGeometry(
main_window_rect.x() + (main_window_rect.width() - about_dialog_width) // 2,
main_window_rect.y() + (main_window_rect.height() - about_dialog_height) // 2,
about_dialog_width,
about_dialog_height
)
about_dialog.exec()
def show_custom_context_menu(self, pos):
menu = QMenu(self)
copy_action = QAction("复制", self)
copy_action.setShortcut("Ctrl+C") # 使用Ctrl+C复制
menu.addAction(copy_action)
cut_action = QAction("剪切", self)
cut_action.setShortcut("Ctrl+X") # 使用Ctrl+X剪切
menu.addAction(cut_action)
paste_action = QAction("粘贴", self)
paste_action.setShortcut("Ctrl+V") # 使用Ctrl+V粘贴
menu.addAction(paste_action)
undo_action = QAction("撤销", self)
undo_action.setShortcut("Ctrl+Z") # 使用Ctrl+Z撤销
menu.addAction(undo_action)
select_all_action = QAction("全选", self)
select_all_action.setShortcut("Ctrl+A") # 使用Ctrl+A全选
menu.addAction(select_all_action)
font_action = QAction("修改字体", self)
font_action.setShortcut("Ctrl+F") # 使用Ctrl+F修改字体,与菜单项保持一致
menu.addAction(font_action)
action = menu.exec(self.text_edit.mapToGlobal(pos))
if action == copy_action:
self.text_edit.copy()
elif action == cut_action:
self.text_edit.cut()
elif action == paste_action:
self.text_edit.paste()
elif action == undo_action:
self.text_edit.undo()
elif action == font_action:
self.choose_font()
elif action == select_all_action:
self.text_edit.selectAll()
def choose_font(self):
font, ok = QFontDialog.getFont(self.text_edit.font(), self)
if ok:
self.text_edit.setFont(font)
def text_changed(self):
# 这个方法用于检测文本是否发生了更改
if self.text_edit.toPlainText() != self.original_text:
self.text_modified = True
else:
self.text_modified = False
def closeEvent(self, event):
if self.text_modified:
reply = QMessageBox(self)
reply.setWindowTitle('确认关闭')
reply.setText('<center>是否保存当前文件?</center>')
reply.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel)
Yes_button = reply.button(QMessageBox.StandardButton.Yes)
Yes_button.setText('保存')
No_button = reply.button(QMessageBox.StandardButton.No)
No_button.setText('不保存')
Cancel_button = reply.button(QMessageBox.StandardButton.Cancel)
Cancel_button.setText('取消')
result = reply.exec()
if result == QMessageBox.StandardButton.Yes:
self.save_file()
event.accept()
elif result == QMessageBox.StandardButton.No:
event.accept()
else:
event.ignore()
else:
event.accept()
def main():
app = QApplication(sys.argv)
app.setWindowIcon(QIcon("编辑器.ico"))
editor = MarkdownEditor()
editor.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()