diff --git a/pyqt5_文本编辑器.py b/pyqt5_文本编辑器.py new file mode 100644 index 0000000..c552299 --- /dev/null +++ b/pyqt5_文本编辑器.py @@ -0,0 +1,342 @@ +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.Qt import * + +import os +import sys + +FONT_SIZES = [7, 8, 9, 10, 11, 12, 13, 14, 18, 24, 36, 48, 64, 72, 96, 144, 288] +HTML_EXTENSIONS = [".htm", ".html"] + + +# 文件路径分割成文件名和扩展名两部分 +def splitext(p): + return os.path.splitext(p)[1].lower() + + +class MainWindow(QMainWindow): + def __init__(self, *args, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + + layout = QVBoxLayout() + self.editor = QTextEdit() + self.setWindowIcon(QIcon("D:/python_study/python课程设计/Typora/icon/img.png")) + + self.editor.setAutoFormatting(QTextEdit.AutoAll) + self.editor.selectionChanged.connect(self.update_format) + + font = QFont("Times", 12) + self.editor.setFont(font) + self.editor.setFontPointSize(12) + self.path = None + layout.addWidget(self.editor) + + container = QWidget() + container.setLayout(layout) + self.setCentralWidget(container) + + 文件工具栏 = QToolBar("文件") + 文件工具栏.setIconSize(QSize(20, 20)) + self.addToolBar(文件工具栏) + 文件菜单 = self.menuBar().addMenu("&文件") + + 打开_action = QAction( + QIcon("D:/python_study/python课程设计/Typora/icon/打开文件.png"), + "打开文件", + self, + ) + 打开_action.triggered.connect(self.file_open) + 文件菜单.addAction(打开_action) + 文件工具栏.addAction(打开_action) + + 保存_action = QAction( + QIcon(os.path.join("Typora/icon", "保存文件.png")), "保存", self + ) + 保存_action.triggered.connect(self.file_save) + 文件菜单.addAction(保存_action) + 文件工具栏.addAction(保存_action) + + 另存为_action = QAction( + QIcon(os.path.join("Typora/icon", "另存为.png")), "另存为", self + ) + 另存为_action.triggered.connect(self.file_saveas) + 文件菜单.addAction(另存为_action) + 文件工具栏.addAction(另存为_action) + + 编辑工具栏 = QToolBar("编辑") + 编辑工具栏.setIconSize(QSize(20, 20)) + self.addToolBar(编辑工具栏) + 编辑菜单 = self.menuBar().addMenu("&编辑") + + 撤回_action = QAction( + QIcon(os.path.join("Typora/icon", "撤回.png")), "撤回", self + ) + + 撤回_action.triggered.connect(self.editor.undo) + 编辑菜单.addAction(撤回_action) + + 重做_action = QAction( + QIcon(os.path.join("Typora/icon", "重做.png")), "重做", self + ) + + 重做_action.triggered.connect(self.editor.redo) + 编辑工具栏.addAction(重做_action) + 编辑菜单.addAction(重做_action) + + 编辑菜单.addSeparator() + + 剪切_action = QAction( + QIcon(os.path.join("Typora/icon", "剪切.png")), "剪切", self + ) + + 剪切_action.setShortcut(QKeySequence.Cut) + 剪切_action.triggered.connect(self.editor.cut) + 编辑工具栏.addAction(剪切_action) + 编辑菜单.addAction(剪切_action) + + 复制_action = QAction( + QIcon(os.path.join("Typora/icon", "复制.png")), "复制", self + ) + + 剪切_action.setShortcut(QKeySequence.Copy) + 复制_action.triggered.connect(self.editor.copy) + 编辑工具栏.addAction(复制_action) + 编辑菜单.addAction(复制_action) + + 粘贴_action = QAction( + QIcon(os.path.join("Typora/icon", "粘贴.png")), + "粘帖", + self, + ) + + 剪切_action.setShortcut(QKeySequence.Paste) + 粘贴_action.triggered.connect(self.editor.paste) + 编辑工具栏.addAction(粘贴_action) + 编辑菜单.addAction(粘贴_action) + + 全选_action = QAction( + QIcon(os.path.join("Typora/icon", "selection-input.png")), "全选", self + ) + + 剪切_action.setShortcut(QKeySequence.SelectAll) + 全选_action.triggered.connect(self.editor.selectAll) + 编辑菜单.addAction(全选_action) + + 编辑菜单.addSeparator() + + 换行 = QAction( + QIcon(os.path.join("Typora/icon", "arrow-continue.png")), "自动换行", self + ) + + 换行.setCheckable(True) + 换行.setChecked(True) + 换行.triggered.connect(self.edit_toggle_wrap) + 编辑菜单.addAction(换行) + + 格式工具栏 = QToolBar("格式") + 格式工具栏.setIconSize(QSize(20, 20)) + self.addToolBar(格式工具栏) + 格式菜单 = self.menuBar().addMenu("&格式") + + self.字体 = QFontComboBox() + self.字体.currentFontChanged.connect(self.editor.setCurrentFont) + 格式工具栏.addWidget(self.字体) + + self.字体大小 = QComboBox() + self.字体大小.addItems([str(s) for s in FONT_SIZES]) + self.字体大小.currentIndexChanged[str].connect( + lambda s: self.editor.setFontPointSize(float(s)) + ) + 格式工具栏.addWidget(self.字体大小) + + self.加粗 = QAction( + QIcon(os.path.join("Typora/icon", "字体加粗.png")), "加粗", self + ) + + self.加粗.setShortcut(QKeySequence.Bold) + self.加粗.setCheckable(True) + self.加粗.toggled.connect( + lambda x: self.editor.setFontWeight(QFont.Bold if x else QFont.Normal) + ) + 格式工具栏.addAction(self.加粗) + 格式菜单.addAction(self.加粗) + + self.斜体 = QAction( + QIcon(os.path.join("Typora/icon", "字体斜体.png")), "斜体", self + ) + + self.斜体.setShortcut(QKeySequence.Italic) + self.斜体.setCheckable(True) + self.斜体.toggled.connect(self.editor.setFontItalic) + 格式工具栏.addAction(self.斜体) + 格式菜单.addAction(self.斜体) + + self.下划线 = QAction( + QIcon(os.path.join("Typora/icon", "字体下划线.png")), "下划线", self + ) + + self.下划线.setShortcut(QKeySequence.Underline) + self.下划线.setCheckable(True) + self.下划线.toggled.connect(self.editor.setFontUnderline) + 格式工具栏.addAction(self.下划线) + 格式菜单.addAction(self.下划线) + + 格式菜单.addSeparator() + + self.左对齐_action = QAction( + QIcon(os.path.join("Typora/icon", "左对齐.png")), "靠左对齐", self + ) + self.左对齐_action.setCheckable(True) + self.左对齐_action.triggered.connect( + lambda: self.editor.setAlignment(Qt.AlignLeft) + ) + 格式工具栏.addAction(self.左对齐_action) + 格式菜单.addAction(self.左对齐_action) + + self.居中_action = QAction( + QIcon(os.path.join("Typora/icon", "居中对齐.png")), "居中对齐", self + ) + self.居中_action.setCheckable(True) + self.居中_action.triggered.connect( + lambda: self.editor.setAlignment(Qt.AlignCenter) + ) + 格式工具栏.addAction(self.居中_action) + 格式菜单.addAction(self.居中_action) + + self.右对齐_action = QAction( + QIcon(os.path.join("Typora/icon", "右对齐.png")), "靠右对齐", self + ) + + self.右对齐_action.setCheckable(True) + self.右对齐_action.triggered.connect( + lambda: self.editor.setAlignment(Qt.AlignRight) + ) + 格式工具栏.addAction(self.右对齐_action) + 格式菜单.addAction(self.右对齐_action) + + self.左右对齐_action = QAction( + QIcon(os.path.join("Typora/icon", "左右对齐.png")), "左右对齐", self + ) + self.左右对齐_action.setCheckable(True) + self.左右对齐_action.triggered.connect( + lambda: self.editor.setAlignment(Qt.AlignJustify) + ) + 格式工具栏.addAction(self.左右对齐_action) + 格式菜单.addAction(self.左右对齐_action) + + # 单选框 + 对齐_group = QActionGroup(self) + 对齐_group.setExclusive(True) + 对齐_group.addAction(self.左对齐_action) + 对齐_group.addAction(self.居中_action) + 对齐_group.addAction(self.右对齐_action) + 对齐_group.addAction(self.左右对齐_action) + + 格式菜单.addSeparator() + + self._format_actions = [ + self.字体, + self.字体大小, + self.加粗, + self.斜体, + self.下划线, + ] + self.update_format() + self.update_title() + self.show() + + def block_signals(self, objects, b): + for o in objects: + o.blockSignals(b) + + def update_format(self): + # 设置信号阻塞 + self.block_signals(self._format_actions, True) + self.字体.setCurrentFont(self.editor.currentFont()) + self.字体大小.setCurrentText(str(int(self.editor.fontPointSize()))) + self.斜体.setChecked(self.editor.fontItalic()) + self.下划线.setChecked(self.editor.fontUnderline()) + self.加粗.setChecked(self.editor.fontWeight() == QFont.Bold) + self.左对齐_action.setChecked(self.editor.alignment() == Qt.AlignLeft) + self.居中_action.setChecked(self.editor.alignment() == Qt.AlignCenter) + self.右对齐_action.setChecked(self.editor.alignment() == Qt.AlignRight) + self.左右对齐_action.setChecked(self.editor.alignment() == Qt.AlignJustify) + self.block_signals(self._format_actions, False) + + def dialog_critical(self, s): + dlg = QMessageBox(self) + dlg.setText(s) + dlg.setIcon(QMessageBox.Critical) + dlg.show() + + def file_open(self): + path, _ = QFileDialog.getOpenFileName( + self, + "打开文件", + "", + "HTML documents (*.html);Text documents (*.txt);All files (*.*)", + ) + try: + with open(path, "r+") as f: + text = f.read() + except Exception as e: + self.dialog_critical(str(e)) + else: + self.path = path + self.editor.setText(text) + self.update_title() + + def file_save(self): + if self.path is None: + return self.file_saveas() + text = ( + self.editor.toHtml() + if splitext(self.path) in HTML_EXTENSIONS + else self.editor.toPlainText() + ) + try: + with open(self.path, "w") as f: + f.write(text) + except Exception as e: + self.dialog_critical(str(e)) + + def file_saveas(self): + path, _ = QFileDialog.getSaveFileName( + self, + "保存文件", + "", + "HTML documents (*.html);Text documents (*.txt);All files (*.*)", + ) + if not path: + return + text = ( + self.editor.toHtml() + if splitext(path) in HTML_EXTENSIONS + else self.editor.toPlainText() # 获取文本 + ) + try: + with open(path, "w") as f: + f.write(text) + except Exception as e: + self.dialog_critical(str(e)) + else: + self.path = path + self.update_title() + + def update_title(self): + self.setWindowTitle( + "%s - 文本编辑器" + % (os.path.basename(self.path) if self.path else "Untitled") + ) + + def edit_toggle_wrap(self): + self.editor.setLineWrapMode(1 if self.editor.lineWrapMode() == 0 else 0) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + app.setApplicationName("文本编辑器") + window = MainWindow() + window.resize(1300, 750) + app.exec_()