«

Python编写文本编辑器

发布于 阅读:87 教程


安装模块

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()

Python