├── Editor.pro ├── Editor.pro.user ├── README.md ├── edit.cpp ├── edit.h ├── finddlg.cpp ├── finddlg.h ├── finddlg.ui ├── main.cpp ├── myresorces.qrc ├── rsc ├── Editor.rc ├── bold.png ├── close.png ├── color.png ├── copy.png ├── cut.png ├── find.png ├── help.png ├── i.png ├── icon.ico ├── icon.png ├── just.png ├── left.png ├── middle.png ├── new.png ├── open.png ├── paste.png ├── redo.png ├── right.png ├── save.png ├── saveas.png ├── selectall.png ├── size.png ├── underline.png └── undo.png ├── texteditor.cpp ├── texteditor.h └── texteditor.ui /Editor.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2019-06-19T20:36:47 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = Editor 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | CONFIG += c++11 26 | 27 | SOURCES += \ 28 | edit.cpp \ 29 | finddlg.cpp \ 30 | main.cpp \ 31 | texteditor.cpp 32 | 33 | HEADERS += \ 34 | edit.h \ 35 | finddlg.h \ 36 | texteditor.h 37 | 38 | FORMS += \ 39 | finddlg.ui \ 40 | texteditor.ui 41 | 42 | # Default rules for deployment. 43 | qnx: target.path = /tmp/$${TARGET}/bin 44 | else: unix:!android: target.path = /opt/$${TARGET}/bin 45 | !isEmpty(target.path): INSTALLS += target 46 | 47 | RESOURCES += \ 48 | myresorces.qrc 49 | 50 | RC_FILE += rsc/Editor.rc 51 | -------------------------------------------------------------------------------- /Editor.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {72502559-df52-40c9-853e-6b584b20abbf} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | -fno-delayed-template-parsing 60 | 61 | true 62 | 63 | 64 | 65 | ProjectExplorer.Project.Target.0 66 | 67 | Desktop Qt 5.12.3 MinGW 64-bit 68 | Desktop Qt 5.12.3 MinGW 64-bit 69 | qt.qt5.5123.win64_mingw73_kit 70 | 0 71 | 0 72 | 0 73 | 74 | E:/Downloads/MobileFile/editor/theNewOne 75 | 76 | 77 | true 78 | qmake 79 | 80 | QtProjectManager.QMakeBuildStep 81 | true 82 | 83 | false 84 | false 85 | false 86 | 87 | 88 | true 89 | Make 90 | 91 | Qt4ProjectManager.MakeStep 92 | 93 | false 94 | 95 | 96 | false 97 | 98 | 2 99 | Build 100 | 101 | ProjectExplorer.BuildSteps.Build 102 | 103 | 104 | 105 | true 106 | Make 107 | 108 | Qt4ProjectManager.MakeStep 109 | 110 | true 111 | clean 112 | 113 | false 114 | 115 | 1 116 | Clean 117 | 118 | ProjectExplorer.BuildSteps.Clean 119 | 120 | 2 121 | false 122 | 123 | Debug 124 | Debug 125 | Qt4ProjectManager.Qt4BuildConfiguration 126 | 2 127 | true 128 | 129 | 130 | E:/Downloads/MobileFile/editor/build-Editor-Desktop_Qt_5_12_3_MinGW_64_bit-Release 131 | 132 | 133 | true 134 | qmake 135 | 136 | QtProjectManager.QMakeBuildStep 137 | false 138 | 139 | false 140 | false 141 | true 142 | 143 | 144 | true 145 | Make 146 | 147 | Qt4ProjectManager.MakeStep 148 | 149 | false 150 | 151 | 152 | false 153 | 154 | 2 155 | Build 156 | 157 | ProjectExplorer.BuildSteps.Build 158 | 159 | 160 | 161 | true 162 | Make 163 | 164 | Qt4ProjectManager.MakeStep 165 | 166 | true 167 | clean 168 | 169 | false 170 | 171 | 1 172 | Clean 173 | 174 | ProjectExplorer.BuildSteps.Clean 175 | 176 | 2 177 | false 178 | 179 | Release 180 | Release 181 | Qt4ProjectManager.Qt4BuildConfiguration 182 | 0 183 | true 184 | 185 | 186 | E:/Downloads/MobileFile/editor/build-Editor-Desktop_Qt_5_12_3_MinGW_64_bit-Profile 187 | 188 | 189 | true 190 | qmake 191 | 192 | QtProjectManager.QMakeBuildStep 193 | true 194 | 195 | false 196 | true 197 | true 198 | 199 | 200 | true 201 | Make 202 | 203 | Qt4ProjectManager.MakeStep 204 | 205 | false 206 | 207 | 208 | false 209 | 210 | 2 211 | Build 212 | 213 | ProjectExplorer.BuildSteps.Build 214 | 215 | 216 | 217 | true 218 | Make 219 | 220 | Qt4ProjectManager.MakeStep 221 | 222 | true 223 | clean 224 | 225 | false 226 | 227 | 1 228 | Clean 229 | 230 | ProjectExplorer.BuildSteps.Clean 231 | 232 | 2 233 | false 234 | 235 | Profile 236 | Profile 237 | Qt4ProjectManager.Qt4BuildConfiguration 238 | 0 239 | true 240 | 241 | 3 242 | 243 | 244 | 0 245 | 部署 246 | 247 | ProjectExplorer.BuildSteps.Deploy 248 | 249 | 1 250 | Deploy Configuration 251 | 252 | ProjectExplorer.DefaultDeployConfiguration 253 | 254 | 1 255 | 256 | 257 | dwarf 258 | 259 | cpu-cycles 260 | 261 | 262 | 250 263 | -F 264 | true 265 | 4096 266 | false 267 | false 268 | 1000 269 | 270 | true 271 | 272 | false 273 | false 274 | false 275 | false 276 | true 277 | 0.01 278 | 10 279 | true 280 | kcachegrind 281 | 1 282 | 25 283 | 284 | 1 285 | true 286 | false 287 | true 288 | valgrind 289 | 290 | 0 291 | 1 292 | 2 293 | 3 294 | 4 295 | 5 296 | 6 297 | 7 298 | 8 299 | 9 300 | 10 301 | 11 302 | 12 303 | 13 304 | 14 305 | 306 | 2 307 | 308 | Editor 309 | Editor2 310 | Qt4ProjectManager.Qt4RunConfiguration:E:/Downloads/MobileFile/editor/theNewOne/Editor.pro 311 | 312 | 3768 313 | false 314 | true 315 | true 316 | false 317 | false 318 | true 319 | 320 | E:/Downloads/MobileFile/editor/theNewOne 321 | 322 | 1 323 | 324 | 325 | 326 | ProjectExplorer.Project.TargetCount 327 | 1 328 | 329 | 330 | ProjectExplorer.Project.Updater.FileVersion 331 | 21 332 | 333 | 334 | Version 335 | 21 336 | 337 | 338 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Visitors](https://visitor-badge.laobi.icu/badge?page_id=aspxcor.visitor) 2 | # 基于Qt5的文本编辑器 3 | 4 | ## 项目特性 5 | * 易于使用的UI界面 6 | * 有好的用户功能交互 7 | * 易于理解的代码,整齐的代码风格 8 | * 对撤销和重做设计了数据结构而非调用Qt自带接口 9 | * 实现了对Qt一部分原生功能的优化 10 | ## 项目描述 11 | > 本项目打算设计一个简易文本编辑器,能够实现基本的文本处理工作,同时附带一些高级功能。**我们主要的实现平台在windows操作系统上。**为加强用户体验,我们采用全图形界面设计,方便程序与用户交互。 12 | > 13 | 14 | > **配置环境** 15 | > 16 | > 编译器:vs2017 msvc2017 qt5.12 17 | > 18 | > 19 | 20 | #### Qt5.12.3安装 21 | 22 | [官网](http://download.qt.io/archive/qt/5.12/5.12.3/)下载qt5.12.3 23 | 24 | 选择`qt-opensource-windows-x86-5.12.3.exe`下载(windows系统)。一路安装,一般不会有什么问题。**注意选择编译器的时候选择msvc2017。** 25 | 26 | 安装成功后,添加环境变量`D:\QT\5.12.3\msvc2017_64\bin` 。 27 | 28 | **有个要注意的地方是,qt creator编译一遍生成新的一个文件夹,如果有导入照片之类的东西,要把资源文件放在生成的那个文件夹里面,不能放在.pro在的文件夹里面,不然找不到。**当然使用qt原生函数的时候可以用.qrc(xml写成的小数据库)资源管理器导入需要的文件,在编译时这些资源文件会被自动转换成二进制编码。 29 | 30 | 如果想使用vs编译qt,那就在vs中点击工具->扩展和更新->联机,搜索qt,下载第一个,如图: 31 | 32 | 下载完成后,重启安装,然后选择状态栏上面的qt vs tools->qt options,点击add,添加你的qt位置如`D:\QT\5.12.3\msvc2017_64`,以便vs编译。 33 | 34 | ### IDE介绍 35 | 36 | #### QT Creator 4.9.0(Community) 37 | 38 | **Qt Creator** 是一款跨平台的集成开发环境,特别针对Qt开发者,是QtSDK组成的一部分,可运行于Windows, Linux/X11及Mac OS X等桌面操作系统,允许开发者为多桌面环境及移动设备平台创建应用程序。它包括一个可视化调试工具和集成的 GUI 版面和外形设计师。这个编辑器的功能包括语法高亮度显示和自动完成。在 Windows,默认安装它可以使用 MinGW 或 MSVC。从源代码编译时,也可以使用 cdb。 39 | 40 | Qt Creator的**代码编辑器**用于辅助创建,编辑,浏览代码,具有对C++及QML语言完整的表达式检查,代码补全,上下文关联,键入代码时的行间错误即时指示等功能: 41 | 42 | - 使用C ++,QML和ECMAscript支持的代码编辑器 43 | - 快速代码导航工具 44 | - 语法突出显示和代码完成 45 | - 您键入时的静态代码检查和样式提示 46 | - 支持源代码重构 47 | - 上下文相关帮助 48 | - 代码折叠 49 | - 括号匹配和括号选择模式 50 | 51 | Qt Creator 提供两个集成的可视化编辑器,**Qt Designer** 与 **Qt Quick Designer**。 52 | 53 | **调试器**方面,C ++的可视化调试器能够解释许多Qt类的结构,从而增加了提示Qt各种类和对象的能力。另外,Qt Creator以清晰简洁的方式显示GDB的原始信息。 54 | 55 | - 中断程序执行。 56 | - 逐行执行程序或按指令执行。 57 | - 设置断点。 58 | - 检查调用堆栈内容以及本地和全局变量。 59 | 60 | #### Microsoft Visual Studio 2017 61 | 62 | **Microsoft Visual Studio**(简称**VS**)是微软公司的开发工具包系列产品。VS是一个基本完整的开发工具集,它包括了整个软件生命周期中所需要的大部分工具,如UML工具、代码管控工具等等。 63 | 64 | **Visual Studio 2017**是微软于2017年3月8日正式推出的新版本,是迄今为止 最具生产力 的 Visual Studio 版本。 65 | 66 | 它有以下的一些特性,可以帮助我们在处理大型第三方库(如QT,OpenCV)时有着良好的体验,方便了我们在写这个图形应用程序时,面对大量的函数不知道用法时尴尬的处境。 67 | 68 | - **IntelliSense功能**,可以帮助我们在键入函数名时,自动提示这个函数的形参和相应的用法介绍。IntelliSense 随着键入描述 API,并使用自动完成功能以提高速度和精确度。 探索新 API 的速度更快,因为可以通过分类缩小值的范围。 69 | - **在上下文中导航。**在使用QT这样大型代码库时,进行某个特定函数的查找可能会很困难。 Visual Studio 提供“速览定义”和改进的“定位”功能(方便轻松筛选掉不需要的项,并选择仅查找一种类型的项),有助于更轻松地进行导航,快速定位代码上下文或起始标记。同时,通过解决方案资源管理器中的嵌入式对象浏览方式,可以轻松查看代码的对象结构,并快速搜索解决方案中的文件。 70 | - **CodeLens 功能。**我们可以无需离开代码即可通过 CodeLens 快速了解其调用结构并导航至相关函数。 这可以使得整体结构一目了然,在使用C++这样的OOP语言时,多级继承的类和成员可以方便的检索和查找,极大地方便了我们写代码。 71 | - **查找错误列表中的所有问题。**错误列表可通过“一站式”方式导航并更正解决方案中与代码相关的问题,无论这些问题源自何处,从编译和生成到代码分析全部涵盖在内。使用高级“筛选”专注于问题,导航至该问题并进行修复。在错误上单击“代码链接”或按 F1 键以搜索 Web 内容帮助解决您的问题。 72 | 73 | ### Complier介绍 74 | 75 | #### MSVC 2017 76 | 77 | **Microsoft Visual C++2017**(简称Visual C++、MSVC、VC++或VC)是微软公司的C++开发工具,具有集成开发环境,可提供编辑C语言,C++以及C++/CLI等编程语言。VC++集成了便利的除错工具,特别是集成了微软Windows视窗操作系统应用程序接口(Windows API)、三维动画DirectX API,Microsoft .NET框架。目前最新的版本是Microsoft Visual C++ 2017。 78 | 79 | Visual C++以拥有语法高亮、IntelliSense(智能提示)以及高级调试功能而著称。比如,它允许我们进行远程调试,单步执行等。**还有允许我们在调试期间重新编译被修改的代码,而不必重新启动正在调试的程序。**其编译及创建预编译头文件(stdafx.h)、最小重建功能及累加链接(link)著称。这些特征明显缩短程序编辑、编译及链接的时间花费,在大型软件项目上尤其显著。 80 | 81 | ### 库 82 | 83 | #### Qt 5.12.3 84 | 85 | Qt是一个跨平台的桌面,广泛用于开发GUI程序,这种情况下又被称为部件工具箱。也可用于开发非GUI程序,比如控制台工具和服务器。嵌入式和移动应用程序开发框架。支持的平台包括Linux,OS X,Windows,VxWorks,QNX,Android,iOS,BlackBerry,Sailfish OS等。 86 | 87 | Qt本身*不是*一种编程语言。这是一个用C ++编写的框架。预处理器MOC(元对象编译器)用于扩展具有诸如信号和插槽之类特征的C ++语言。在编译之前,MOC解析用Qt-C ++编写的源文件,并从中生成符合标准的C ++源文件。因此,框架本身和使用它的应用程序/库可以通过任何符合标准的C ++编译器(如Clang,GCC,ICC,MinGW和MSVC)进行编译。 88 | 89 | Qt的开发始于1990年,由挪威程序员Eirik Chambe-Eng和Haavard Nord开发。他们的公司Trolltech公司出售Qt许可证并提供支持,多年来经历了多次收购。今天,前奇趣科技被命名为Qt公司,是Digia公司的全资子公司。总部位于芬兰。尽管Qt公司是Qt背后的主要驱动力,Qt现在是由一个更大的联盟Qt项目开发的。它由全球许多公司和个人组成,遵循精英管理模式。 90 | 91 | ## 功能目标 92 | 93 | 简单文本编辑器的设计是基于Microsoft Visual Studio 2017开发的一款小型的软件,主要的设计是为了满足普通用户对文本文档进行一般的简单操作,通过Qt可视化编程环境,直接生成一个友好的用户操作界面,通过这个窗口,用户可以对文本进行如下的基本操作。 94 | 95 | ### 文件 96 | 97 | ① 新建:创建一篇空白文档,从“工具栏”或“文件下拉菜单”中创建 98 | 99 | ② 打开:打开文本(.txt)文件,从“工具栏”或“文件下拉菜单”中打开;或将文本(.txt)文件拖入程序界面打开或应用程序图标上打开;或右键单击文本文件,在“打开方式”中选择本程序打开 100 | 101 | ③ 保存:保存文档,从“工具栏”或“文件下拉菜单”中打开 102 | 103 | ④ 另存为:保存文件副本,在不同位置或以不同文件名保存文档,从“工具栏”或“文件下拉菜单”中另存 104 | 105 | ### 编辑 106 | 107 | ① 撤销:撤销前一步所进行的操作,从“编辑下拉菜单”或“右键菜单”中撤销 108 | 109 | ② 删除:删除当前选定字符(串),从“编辑下拉菜单”或“右键菜单”中删除 110 | 111 | ③ 剪切:复制并删除选定字符(串),从“编辑下拉菜单”或“右键菜单”中剪切 112 | 113 | ④ 复制:复制选定字符(串),“编辑下拉菜单”或“右键菜单”中复制 114 | 115 | ⑤ 粘贴:对粘贴内容进行粘贴,从“编辑下拉菜单”或“右键菜单”中粘贴 116 | 117 | ⑥ 全选:对文本编辑框中文本全部选定,从“编辑下拉菜单”或“右键菜单”中全 118 | 119 | ⑦ 查找/替换:输入查找内容(和替换内容),可从光标位置逐个查找(或替换)相应内容,也可一次性全部替换掉相应内容,从“工具栏”或“应用下拉菜单”中执行 120 | 121 | ### 格式 122 | 123 | ① 字体设置:设置字体、字形及字的大小,从“工具栏”或“应用下拉菜单 ”中设置字体 124 | 125 | ② 颜色设置:设置字的颜色 126 | 127 | ## 基本实现思路 128 | 129 | > 文本编辑器软件的实现分为前端和后端两部分,前端程序员负责直接和用户交互的部分,利用**Qt5.12**实现便于用户操作的可视化图形界面,精确地获取并理解用户的需求,调用相关的后端功能;而后端程序员使用**自定义的函数及部分Qt接口**完成对文字的处理部分,设计一个便于使用、功能强大的文字处理函数库接口,供前端调用。 130 | 131 | ### 文本编辑器工作流程 132 | 133 | ```flow 134 | st=>start: Start 135 | e=>end 136 | i1=>inputoutput: Load text 137 | i2=>inputoutput: Get user reqiurement 138 | op1=>operation: Call the corresponding function to process the text 139 | op2=>operation: Show text 140 | cond1=>condition: Exit? 141 | o1=>inputoutput: Save text 142 | 143 | st->i1->i2->op1->op2->cond1 144 | cond1(no)->i2 145 | cond1(yes)->o1->e 146 | ``` 147 | 148 | 149 | ### 前端编写 150 | 151 | 152 | - 完成主要的软件架构以及业务逻辑 153 | 154 | - 设计美观且便于使用的GUI 155 | 156 | - 完成载入和保存文本的功能 157 | 158 | - 获取用户的操作信息,调用后端编写的功能函数处理文档 159 | 160 | 其中GUI可利用**Qt**框架自带的**Qt Designer**,该工具能够直观、便捷地设计出美观的GUI,搭配Qt强大的文档查阅工具**Qt Assistant**,相信能够较为简便地设计出简洁优雅的用户图形界面。在本次软件的编写中,前端人员负责主要的软件架构,这是由于前端程序员对整个软件的功能以及业务逻辑更加了解,同时也能使得前端和后端分离,降低编写过程中的合作难度。后端程序员只需关注功能的实现,而前端程序员只要关心界面的设计以及与用户的互动。 161 | 162 | ### 后端编写 163 | 164 | 由于Qt自带了字符串存储类`QString`、堆栈类`QStack`,因此我们在实现预期功能时主要采用这些类,并在开发过程中基于这些类进行进一步开发。 165 | 166 | 与文本处理有关的后端接口函数由负责后端的程序员编写,并为前端程序员的需要进行调用 167 | 168 | 根据实现功能的要求,负责的后端的人使用Qt编写相对应的文本处理函数,并封装在一个接口文件中。接口文件要求: 169 | 170 | - 有充分的注释,包括函数功能、参数说明和用法示例等 171 | - 为了保证代码风格一致,函数名使用大写的驼峰命名法 172 | - 涉及图像输入输出的函数,控制传入参数类型与输出参数类型符合前端开发需求 173 | - 接口文件应只包含相应的函数声明,不包括static函数的声明 174 | 175 | ### 时间线 176 | 177 | | 日期 | 任务 | 178 | | :--------- | :------------------------- | 179 | | 2019.06.03 | 代码风格讨论及确定 | 180 | | 2019.06.06 | 功能函数接口的确认 | 181 | | 2019.06.08 | 后端代码的编写、调试及测试 | 182 | | 2019.06.13 | 前端代码的编写、调试及测试 | 183 | | 2019.06.16 | 整体代码debug | 184 | | 2019.06.20 | 规划并完成UI设计 | 185 | | 2019.06.24 | 项目文档 | 186 | 187 | ### 注意事项 188 | 189 | - 在实现功能函数之前,后端程序员需要和前端程序员有充分的交流,并确认函数的接口原型,避免产生接口冲突的情况 190 | 191 | - 采用统一的代码风格,由全组人员讨论后制定,不得擅自更改,如果有分歧则少数服从多数 192 | 193 | - 代码要有充分的注释,选择有意义的变量名 194 | 195 | - 两位后端程序员要先自行测试自己编写的功能函数,通过后相互交换测试,确认没有Bug后方可交给前端程序员 196 | 197 | - 保证按照时间线完成相应任务,不得拖延 198 | 199 | ## 程序涉及的类的说明 200 | 201 | > 我们根据需求封装了edit类、TextEditor类、finddialog等,下面对系统中各个模块进行说明。 202 | 203 | ### edit类——文本编辑框模块 204 | 205 | 在`edit.h`中被定义,继承了`QTextEdit`类,主要目的是为了重载控制右键菜单的函数,实现对原先默认右键菜单的修改。(最终由于时间原因我们只屏蔽了原有右键菜单,没有实现新的预期功能) 206 | 207 | 此外,通过继承`QTextEdit`类,还可以安装事件过滤器,实现对`QTextEdit`类缺省快捷对应动作的重定义。(最终由于时间原因我们未完成此部分) 208 | 209 | ### TextEditor类——文本编辑器界面框架 210 | 211 | 在这个类中我们定义了文本编辑器界面框架,实现了其主要的若干功能 212 | 213 | ### finddlg类——查找功能模块的实现 214 | 215 | 实现了查找与替换功能 216 | 217 | ## 系统设计难点及解决 218 | 219 | > 在设计过程中,我们遇到了很多困难,但我们最终得到了逐一的解决,下面我们简要展开我们遇到的主要困难和主要解决方法 220 | 221 | * Undo栈和Redo栈的设计 222 | 223 | 撤销与重做过程目的在于实现一个新的数据结构,用于存储每次文本框中的内容更新时存储当前文本编辑器中的内容,在执行撤销操作时,读取保存的最近一次文本编辑框中的内容并加载到文本框中,并将当下文本编辑器中显示的内容再次保存,以便于下一次执行重做功能时再次恢复之前的文本框中的内容。 224 | 225 | 为此,我们容易探索出,构建两个堆栈用于保存数据信息的方法符合我们的需求,具体来说,我们需要构建一个UndoStack和一个RedoStack,而在Qt中自带了这样的模板类QStack,因此我们建立两个存储QString类型信息的QStack类变量strUndo、strRedo,用于存储每次改变字符后的当前字符串。此外在测试的过程中,为了修复系统接口与我们自定义的函数接口之间的兼容性问题,我们又定义了几个标记变量,用于标记是否使用了撤销功能、是否进入过某个特殊函数等。具体的细节将在下面的代码部分展开描述。 226 | 227 | * 字体格式和段落格式 228 | 229 | 字体格式和段落格式的设定主要是调用qt控件setFontWeight,setFontItalic等函数来实现,获取光标的选定内容利用QTextCursor类,自己设计函数使得字体格式能应用于光标选中内容。段落格式中由于菜单栏无法设置单选,需要在槽函数中添加代码使得单选实现。 230 | 231 | * 剪贴板设计思路 232 | 233 | 在TextEditor类中创建一个QString成员变量作为剪贴板,用于存储复制剪切操作中的字符串,在构造函数中将其初始化为空字符串。执行复制和剪切时将选中文本存入变量,粘贴时将原本选中内容删除并插入剪贴板内容。 234 | 235 | * 字体格式识别 236 | 237 | 对于字体加粗等设定最初只能根据按键的状态判定下一步操作为加粗或还原,无法实现word中对于光标选中内容字体状态的识别并据此更改菜单栏状态的功能。因此另外设计一个函数用于修改菜单栏按键的状态,同时用一个connect函数将光标选中的消息与之关联,从而实现一般文本编辑器中加粗的效果。 238 | 239 | * 中文读取与保存 240 | 241 | 我们注意到,windows自带的文本编辑器打开txt文件输入汉字后保存,文件编码格式是GB2312,因此在我们的文本编辑器中也采用这一编码格式读取文档。 242 | 243 | * 查找与替换的实现 244 | 245 | Qt中文本的存储方式是以QString的方式存储,因此只需要调用QString的indexOf方法,就可以找到相应的下标。在主界面中点击查找后,会新建一个findDlg的实例并将当前的文档中存储的内容传给findDlg类。此后findDlg只需要在这一传来的值中查找就可以了。 246 | 247 | 查找需要有三个基本按键(从头查找、下一个、上一个),一个对话框供用户输入查找内容,还有一个用于显示查找结果的对话框。当用户点击从头查找时,程序会从头查找待查找内容,这一点的实现比较简单。 248 | 249 | 但是要做到上一个和下一个的功能就不是很容易。我用到了qt中的堆栈容器、`QStack`,新建了一个用于存储当前查找到的内容的位置的堆栈。当点击从头查找或下一个时会把当前查找到的内容的位置压入堆栈,而当点击上一个时会把之前压入堆栈的内容弹出。这样就实现了查找上一个和下一个的功能。 250 | 251 | 在查找到内容之后还需要将找到的内容高亮处理,查找是在findDlg类中进行的,而高亮显示是在TextEditor类中实现的。因此需要用到qt信号与槽的机制,每当查找一次,fingDlg就会向TextEditor发送一个消息,包含当前搜索到的字符串的下标位置和字符串长度。 252 | 253 | 在点击替换按钮后,fingDlg也会向TextEditor发送一个消息,此消息包含替换后的字符串。在TextEditor收到消息后,只需要用replace方法就可以实现替换QString中的字符串。 254 | 255 | * 判断文档是否被保存 256 | 257 | 调用系统接口`ui->textEdit->document()->isModified()`就可以判断文档是否已经被保存。 258 | 259 | 一个关键的实现细节是,我注意到当我们的撤销和重做功能在程序中被使用时,我们的文档保存标记isModified()会出现错误,系统会认为此时的文档经过了保存,然而事实上这时的文档并没有真的被保存。注意到这个细节后,我们增加了一个bool变量undoIsUsed用于表示是否使用过撤销功能,增加了新的判断逻辑:当使用过撤销功能且Undo栈不为空时,即使`isModified()`指示文件已被保存,我们依然认为文档并没有保存。在增加了这一个修正逻辑后,我们的程序可以正确的判断文档的保存情况。 260 | 261 | ## 开发需求的代码实现 262 | 263 | ### 文件新建与打开 264 | 265 | ```c++ 266 | void TextEditor::newFile() 267 | { 268 | if (maybeSave()) { 269 | isUntitled = true; 270 | curFile = tr("未命名.txt"); 271 | setWindowTitle(curFile); 272 | ui->textEdit->clear(); 273 | ui->textEdit->setVisible(true); 274 | } 275 | resetStack(); 276 | strUndo.push(ui->textEdit->toPlainText()); 277 | } 278 | 279 | ``` 280 | 281 | 新建文档时如果没有保存之前的文档会提示是否保存,而其中resetStack**()**函数目的在于实现每次打开文档前的堆栈重置与清空,此外,这个函数将重新恢复undoIsUsed标记为初始值0,代表新的文档尚未使用过撤销功能。strUndo**.**push**(**ui**->**textEdit**->**toPlainText**())**实现了将加载的新的空白文档先压入堆栈,以期在将来重做时恢复到初始状态的目的。 282 | 283 | 在打开文档时,相关函数如下: 284 | 285 | ```c++ 286 | bool TextEditor::loadFile(const QString &fileName) 287 | { 288 | //resetStack(); 289 | QFile file(fileName); // 新建QFile对象 290 | if (!file.open(QFile::ReadOnly | QFile::Text)) { 291 | QMessageBox::warning(this, tr("多文档编辑器"), 292 | tr("无法读取文件 %1:\n%2.") 293 | .arg(fileName).arg(file.errorString())); 294 | return false; // 只读方式打开文件,出错则提示,并返回false 295 | } 296 | QTextStream in(&file); // 新建文本流对象 297 | QApplication::setOverrideCursor(Qt::WaitCursor); 298 | ui->textEdit->setPlainText(in.readAll()); 299 | resetStack(); 300 | strUndo.push(ui->textEdit->toPlainText()); 301 | QApplication::restoreOverrideCursor(); 302 | // 设置当前文件 303 | curFile = QFileInfo(fileName).canonicalFilePath(); 304 | setWindowTitle(curFile); 305 | ui->textEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); 306 | return true; 307 | } 308 | 309 | ``` 310 | 311 | 根据上述展示可以看出,程序实现逻辑是首先判断是否可读取文档,若不可读取就报错并返回,若可以读取就正常读取文档并加载,并将光标移动到文档末尾。 312 | 313 | ### 撤销与重做 314 | 315 | 撤销与重做过程目的在于实现一个新的数据结构,用于存储每次文本框中的内容更新时存储当前文本编辑器中的内容,在执行撤销操作时,读取保存的最近一次文本编辑框中的内容并加载到文本框中,并将当下文本编辑器中显示的内容再次保存,以便于下一次执行重做功能时再次恢复之前的文本框中的内容。 316 | 317 | 为此,我们容易探索出,构建两个堆栈用于保存数据信息的方法符合我们的需求,具体来说,我们需要构建一个UndoStack和一个RedoStack,而在Qt中自带了这样的模板类QStack,因此我们建立两个存储QString类型信息的QStack类变量strUndo、strRedo,具体代码实现如下: 318 | 319 | ```c++ 320 | //texteditor.h节选 321 | namespace Ui { 322 | class TextEditor; 323 | } 324 | class TextEditor : public QMainWindow 325 | { 326 | Q_OBJECT 327 | public: 328 | QStack strUndo; 329 | QStack strRedo; 330 | void refreshStack(); 331 | void resetStack(); 332 | private slots: 333 | void on_action_Undo_triggered(); 334 | void on_action_Y_triggered(); 335 | void on_textEdit_textChanged(); 336 | private: 337 | bool isUndo=0; 338 | bool isRedo=0; 339 | bool isLoadFile=0; 340 | }; 341 | //texteditor.cpp节选 342 | void TextEditor::on_action_Undo_triggered() 343 | { 344 | isUndo=1; 345 | if(strUndo.size()>1) 346 | { 347 | strRedo.push(strUndo.pop()); 348 | ui->textEdit->setPlainText(strUndo.pop()); 349 | strRedo.push(ui->textEdit->toPlainText()); 350 | ui->textEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); 351 | } 352 | isUndo=0; 353 | } 354 | void TextEditor::on_action_Y_triggered() 355 | { 356 | isRedo=1; 357 | if(strRedo.size()>1) 358 | { 359 | strUndo.push(strRedo.pop()); 360 | ui->textEdit->setPlainText(strRedo.pop()); 361 | strUndo.push(ui->textEdit->toPlainText()); 362 | ui->textEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); 363 | } 364 | isRedo=0; 365 | } 366 | void TextEditor::on_textEdit_textChanged() 367 | { 368 | //文本框更新时更新堆栈 369 | if((!isUndo)&&(!isRedo)&&(!isLoadFile)) 370 | { 371 | strUndo.push(ui->textEdit->toPlainText()); 372 | } 373 | } 374 | void TextEditor::resetStack() 375 | //实现每次打开新文件时的重置堆栈 376 | { 377 | strUndo.clear(); 378 | strRedo.clear(); 379 | } 380 | 381 | ``` 382 | 383 | 此外,为了实现在启动后第一次加载的信息能够被压入strUndo堆栈,也为了实现在打开、新建等过程中将第一次加载的字符串压入堆栈,我们需要重写之前搭建好的其它几个函数如下,以实现压栈过程中的一系列细节: 384 | 385 | ```c++ 386 | //texteditor.cpp节选 387 | TextEditor::TextEditor(QWidget *parent) : 388 | QMainWindow(parent), 389 | ui(new Ui::TextEditor) 390 | { 391 | ui->setupUi(this); 392 | // 初始化文件为未保存状态 393 | isUntitled = true; 394 | // 初始化文件名为"未命名.txt" 395 | curFile = tr("未命名.txt"); 396 | // 初始化窗口标题为文件名 397 | setWindowTitle(curFile); 398 | strUndo.push(ui->textEdit->toPlainText()); 399 | connect(ui->textEdit,SIGNAL(textChanged(QString)),this,SLOT(on_textEdit_textChanged)); 400 | } 401 | bool TextEditor::loadFile(const QString &fileName) 402 | { 403 | //resetStack(); 404 | QFile file(fileName); // 新建QFile对象 405 | if (!file.open(QFile::ReadOnly | QFile::Text)) { 406 | QMessageBox::warning(this, tr("多文档编辑器"), 407 | tr("无法读取文件 %1:\n%2.") 408 | .arg(fileName).arg(file.errorString())); 409 | return false; // 只读方式打开文件,出错则提示,并返回false 410 | } 411 | 412 | QTextStream in(&file); // 新建文本流对象 413 | QApplication::setOverrideCursor(Qt::WaitCursor); 414 | // 读取文件的全部文本内容,并添加到编辑器中 415 | isLoadFile=1; 416 | resetStack(); 417 | ui->textEdit->setPlainText(in.readAll()); 418 | QApplication::restoreOverrideCursor(); 419 | 420 | // 设置当前文件 421 | curFile = QFileInfo(fileName).canonicalFilePath(); 422 | setWindowTitle(curFile); 423 | // strRedo.push(ui->textEdit->toPlainText()); 424 | 425 | strRedo.push(ui->textEdit->toPlainText()); 426 | return true; 427 | } 428 | 429 | ``` 430 | 431 | ### 查找与替换 432 | 433 | #### 从头查找 434 | 435 | ```c++ 436 | void FindDlg::on_findButton_clicked() 437 | { 438 | find(); 439 | //emit sendQString(QString::number(lastIndex)); 440 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 441 | } 442 | 443 | bool FindDlg::find() 444 | { 445 | //获取当前文本内容 446 | QString str = nowText; 447 | //获取当前需要查找的字符串 448 | QString toBeFound = ui->findEdit->text(); 449 | int index = str.indexOf(toBeFound); 450 | if (index == -1) 451 | { 452 | ui->resultEdit->setText("没有找到"+toBeFound); 453 | return false; 454 | } 455 | else 456 | { 457 | ui->resultEdit->setText(QString::number(index)); 458 | //保存上一次查找结果 459 | lastIndex = index; 460 | stk.push(index); 461 | return true; 462 | } 463 | } 464 | 465 | ``` 466 | 467 | #### 查找下一个 468 | 469 | ```c++ 470 | void FindDlg::on_nextButton_clicked() 471 | { 472 | if (stk.isEmpty()) 473 | { 474 | find(); 475 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 476 | } 477 | else 478 | { 479 | findNext(); 480 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 481 | } 482 | } 483 | 484 | bool FindDlg::findNext() 485 | { 486 | //获取当前文本内容 487 | QString str = nowText; 488 | //获取当前需要查找的字符串 489 | QString toBeFound = ui->findEdit->text(); 490 | //循环从上一次搜索到的地方开始 491 | int i = lastIndex; 492 | //上一次搜到的结果 493 | for (; i<=str.length(); i++) 494 | { 495 | if(lastIndex != str.indexOf(toBeFound, i)) 496 | { 497 | break; 498 | } 499 | } 500 | int index = str.indexOf(toBeFound, i); 501 | if (index == -1) 502 | { 503 | ui->resultEdit->setText("没有找到"+toBeFound); 504 | return false; 505 | } 506 | else 507 | { 508 | ui->resultEdit->setText(QString::number(index)); 509 | lastIndex = index; 510 | stk.push(index); 511 | return true; 512 | } 513 | } 514 | ``` 515 | 516 | 查找到最后一个,继续按测试,会显示没有找到测试,但最后一个测试的高亮还在,这是一个小bug目前还没有解决。 517 | 518 | 此外在查找完之后关闭对话框,高亮仍会显示,这一bug目前也还没有解决 519 | 520 | #### 查找上一个 521 | 522 | ```c++ 523 | void FindDlg::on_previousButton_clicked() 524 | { 525 | findPrevious(); 526 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 527 | } 528 | 529 | bool FindDlg::findPrevious() 530 | { 531 | if (!stk.isEmpty()) 532 | { 533 | stk.pop(); 534 | } 535 | if (stk.isEmpty()) 536 | { 537 | ui->resultEdit->setText("已经是第一个"); 538 | return false; 539 | } 540 | else 541 | { 542 | lastIndex = stk.top(); 543 | ui->resultEdit->setText(QString::number(lastIndex)); 544 | return true; 545 | } 546 | } 547 | 548 | ``` 549 | 550 | 目前还有一个小bug,由于是用堆栈的形式实现的查找上一个和下一个,因此在查找下一个之后再从头查找,这样查找上一个的功能就会返回之前的位置,出错。 551 | 552 | #### 查找后高亮的实现 553 | 554 | ```c++ 555 | void TextEditor::receiveIndex(int index, int length) 556 | { 557 | replaceIndex = index; 558 | replaceLength = length; 559 | 560 | //先把之前的格式清除 561 | QTextCursor cursor = ui->textEdit->textCursor(); //获取当前光标 562 | cursor.setPosition(lastIndex); //定位到下标index的位置 563 | cursor.setPosition(lastIndex+lastLength, QTextCursor::KeepAnchor); //文本选择范围[index,index + length] 564 | //选中完成 565 | QTextCharFormat fmt;//定义突出显示 566 | fmt.setBackground(Qt::white);//定义显示的格式 567 | cursor.mergeCharFormat(fmt);//显示 568 | 569 | //再显示当前的格式 570 | //QTextCursor cursor = ui->textEdit->textCursor(); //获取当前光标 571 | cursor.setPosition(index); //定位到下标index的位置 572 | cursor.setPosition(index+length, QTextCursor::KeepAnchor); //文本选择范围[index,index + length] 573 | //选中完成 574 | //QTextCharFormat fmt;//定义突出显示 575 | fmt.setBackground(Qt::yellow);//定义显示的格式 576 | cursor.mergeCharFormat(fmt);//显示 577 | 578 | lastIndex = index; 579 | lastLength = length; 580 | } 581 | 582 | ``` 583 | 584 | #### 替换的实现 585 | 586 | ```c++ 587 | void TextEditor::receiveQString(QString replaceStr) 588 | { 589 | ui->textEdit->setPlainText(((ui->textEdit->toPlainText()).replace(replaceIndex, replaceLength, replaceStr))); 590 | } 591 | 592 | ``` 593 | 594 | ### 保存与另存为 595 | 596 | ```c++ 597 | bool TextEditor::save() 598 | { 599 | if (isUntitled) { 600 | return saveAs(); 601 | } else { 602 | return saveFile(curFile); 603 | } 604 | } 605 | bool TextEditor::saveFile(const QString &fileName) 606 | { 607 | QFile file(fileName); 608 | 609 | if (!file.open(QFile::WriteOnly | QFile::Text)) { 610 | 611 | // %1和%2分别对应后面arg两个参数,/n起换行的作用 612 | QMessageBox::warning(this, tr("多文档编辑器"), 613 | tr("无法写入文件 %1:/n %2") 614 | .arg(fileName).arg(file.errorString())); 615 | return false; 616 | } 617 | QTextStream out(&file); 618 | // 鼠标指针变为等待状态 619 | QApplication::setOverrideCursor(Qt::WaitCursor); 620 | out << ui->textEdit->toPlainText(); 621 | // 鼠标指针恢复原来的状态 622 | QApplication::restoreOverrideCursor(); 623 | isUntitled = false; 624 | // 获得文件的标准路径 625 | curFile = QFileInfo(fileName).canonicalFilePath(); 626 | setWindowTitle(curFile); 627 | return true; 628 | } 629 | bool TextEditor::saveAs() 630 | { 631 | QString fileName = QFileDialog::getSaveFileName(this, 632 | tr("另存为"),curFile); 633 | if (fileName.isEmpty()) return false; 634 | return saveFile(fileName); 635 | } 636 | 637 | 638 | ``` 639 | 640 | ### TextEdit类的继承 641 | 642 | 为了实现重写系统自带右键菜单与屏蔽系统自带快捷键的目的,我们继承了TextEdit类,其中文本编辑框的右键菜单是一个QTextEdit类中的函数contextMenuEvent,为了重写这个函数,我们需要继承这个类,并在继承类中重载这个函数。此外,在默认的文本编辑框中自带了Ctrl+Z、Ctrl+Y等快捷键,这些快捷键会指向系统自带的接口而非我们定义的Undo/Redo函数,为了屏蔽系统自带的快捷键,我们同样需要继承QTextEdit类并在派生类中通过installEventFilter安装事件过滤器,从而过滤掉原先QTextEdit类中默认的快捷键事件。 643 | 644 | 为了实现对右键菜单的重写,我们新建了edit类,下面是其头文件和cpp文件的实例代码(由于时间有限,我们最终没有完成这部分工作,因而以下代码仅为展示实现这样功能需要的大致代码框架,这代表了我们继承类来解决问题的思想,但我们最终由于时间原因未能完成这部分工作)。 645 | 646 | ```c++ 647 | //edit.h节选 648 | #ifndef EDIT_H 649 | #define EDIT_H 650 | 651 | #include 652 | 653 | class Edit : public QTextEdit 654 | { 655 | Q_OBJECT 656 | public: 657 | explicit Edit(QWidget *parent = nullptr); 658 | 659 | signals: 660 | 661 | public slots: 662 | 663 | protected: 664 | void contextMenuEvent(QContextMenuEvent* e); 665 | }; 666 | 667 | #endif // EDIT_H 668 | 669 | //edit.cpp节选 670 | #include "edit.h" 671 | #include 672 | #include 673 | #include "texteditor.h" 674 | Edit::Edit(QWidget *parent) : QTextEdit(parent) 675 | { 676 | 677 | } 678 | void Edit::contextMenuEvent(QContextMenuEvent *e) 679 | //重定义右键菜单 680 | { 681 | QMenu* menu = new QMenu; 682 | QAction* undo =menu->addAction("撤销",this,SLOT(Undo())); 683 | undo->setEnabled(document()->isUndoAvailable()); 684 | menu->exec(e->globalPos()); 685 | delete menu; 686 | } 687 | 688 | ``` 689 | 690 | ### 全选的实现 691 | 692 | ```c++ 693 | //texteditor.h节选 694 | namespace Ui { 695 | class TextEditor; 696 | } 697 | class TextEditor : public QMainWindow 698 | { 699 | Q_OBJECT 700 | private slots: 701 | void on_action_selectAll_triggered(); 702 | }; 703 | //texteditor.cpp节选 704 | void TextEditor::on_action_selectAll_triggered() 705 | { 706 | ui->textEdit->selectAll(); 707 | } 708 | 709 | ``` 710 | 711 | ### 字体设置 712 | 713 | ```c++ 714 | void TextEditor:: mergeformat(const QTextCharFormat &fmt) 715 | { 716 | /*设置光标的选区,使格式作用于选区内的字符,若没有选区则作用于光标所在处的字符*/ 717 | QTextCursor cursor =ui->textEdit->textCursor(); 718 | if ( !cursor.hasSelection() ) 719 | cursor.select( QTextCursor::WordUnderCursor ); 720 | cursor.mergeCharFormat( fmt ); 721 | 722 | ui->textEdit->mergeCurrentCharFormat( fmt ); 723 | 724 | } 725 | void TextEditor::textBold()//加粗 726 | { 727 | QTextCharFormat fmt; 728 | fmt.setFontWeight( ui->action_Bold->isChecked() ? QFont::Bold : QFont::Normal );//根据按键状态判断加粗或还原 729 | mergeformat( fmt ); 730 | } 731 | 732 | void TextEditor::textItalic()//倾斜 733 | { 734 | QTextCharFormat fmt; 735 | //根据按键状态判断倾斜或还原 736 | fmt.setFontItalic( ui->action_Italic->isChecked() ); 737 | mergeformat( fmt ); 738 | } 739 | 740 | void TextEditor::textUnderline()//下划线 741 | { 742 | QTextCharFormat fmt; 743 | //根据按键状态判断下划线或还原 744 | fmt.setFontUnderline( ui->action_Underline->isChecked() ); 745 | mergeformat( fmt ); 746 | } 747 | void TextEditor::textCurrentFormatChanged(const QTextCharFormat &fmt) 748 | {// 当光标所在处字符格式有变化,工具栏做出相应改变 749 | ui->action_Bold->setChecked( fmt.font().bold() ); 750 | ui->action_Italic->setChecked( fmt.fontItalic() ); 751 | ui->action_Underline->setChecked( fmt.fontUnderline() ); 752 | } 753 | TextEditor::TextEditor(QWidget *parent) : 754 | QMainWindow(parent), 755 | ui(new Ui::TextEditor) 756 | { 757 | //在构造函数中增加connect函数使得选中的字体格式变化时能够调用上述函数 758 | connect(ui->textEdit, &QTextEdit::currentCharFormatChanged, this, &TextEditor::textCurrentFormatChanged); 759 | } 760 | 761 | ``` 762 | 763 | ### 段落格式排版 764 | 765 | ```c++ 766 | void TextEditor::on_action_Left_triggered() 767 | {//左对齐 768 | ui->textEdit->setAlignment( Qt::AlignLeft ); 769 | if(ui->action_Left->isChecked())//设置成单选 770 | { 771 | ui->action_Right->setChecked(false); 772 | ui->action_Center->setChecked(false); 773 | } 774 | else ui->action_Left->setChecked(true); 775 | } 776 | 777 | void TextEditor::on_action_Right_triggered() 778 | {//右对齐 779 | ui->textEdit->setAlignment( Qt::AlignRight ); 780 | if(ui->action_Right->isChecked())//设置成单选 781 | { 782 | ui->action_Left->setChecked(false); 783 | ui->action_Center->setChecked(false); 784 | ui->action_Just->setChecked(false); 785 | } 786 | else ui->action_Right->setChecked(true); 787 | } 788 | 789 | void TextEditor::on_action_Center_triggered() 790 | {//居中 791 | ui->textEdit->setAlignment( Qt::AlignCenter ); 792 | if(ui->action_Center->isChecked())//设置成单选 793 | { 794 | ui->action_Just->setChecked(false); 795 | ui->action_Right->setChecked(false); 796 | ui->action_Left->setChecked(false); 797 | } 798 | else ui->action_Center->setChecked(true); 799 | } 800 | void TextEditor::on_action_Just_triggered() 801 | {//两端对齐 802 | ui->textEdit->setAlignment( Qt::AlignJustify ); 803 | if(ui->action_Center->isChecked())//设置成单选 804 | { 805 | ui->action_Center->setChecked(false); 806 | ui->action_Right->setChecked(false); 807 | ui->action_Left->setChecked(false); 808 | } 809 | else ui->action_Just->setChecked(true); 810 | } 811 | 812 | ``` 813 | 814 | ### 具体字体和颜色 815 | 816 | ```c++ 817 | #include 818 | #include 819 | void TextEditor::textColor() 820 | { 821 | QColor c =QColorDialog::getColor(Qt::red, this );//调用颜色对话框 822 | if ( c.isValid() ) 823 | { 824 | QTextCharFormat fmt; 825 | fmt.setForeground( c ); 826 | mergeformat( fmt ); 827 | } 828 | } 829 | 830 | void TextEditor::textFont() 831 | { 832 | bool ok; 833 | QFont f = QFontDialog::getFont(&ok);//调用字体对话框 834 | if(ok){ 835 | QTextCharFormat fmt; 836 | fmt.setFont(f ); 837 | mergeformat( fmt ); 838 | } 839 | } 840 | c 841 | ``` 842 | 843 | ### 剪贴板的实现 844 | 845 | 构造一个QString成员变量用于存储选中的字符串信息,实现剪贴板功能,但是缺点在于无法存储字体信息 846 | 847 | ```c++ 848 | void TextEditor::textCopy() 849 | { 850 | /*设置光标的选区, 851 | 将选中的字符存入剪贴板*/ 852 | pasteBoard=""; 853 | QTextCursor cursor =ui->textEdit->textCursor(); 854 | pasteBoard=cursor.selectedText(); 855 | } 856 | 857 | void TextEditor::textCut() 858 | { 859 | /*设置光标的选区, 860 | 将选中的字符存入剪贴板,同时删除选中内容*/ 861 | QTextCursor cursor =ui->textEdit->textCursor(); 862 | pasteBoard=cursor.selectedText(); 863 | cursor.removeSelectedText(); 864 | } 865 | 866 | void TextEditor::textPaste() 867 | { 868 | /*删除当前选中内容,插入剪贴板文本*/ 869 | QTextCursor cursor =ui->textEdit->textCursor(); 870 | cursor.removeSelectedText(); 871 | cursor.insertText(pasteBoard); 872 | } 873 | 874 | ``` 875 | 876 | 877 | 878 | ## 测试报告 879 | 880 | * 在查找结束后的高亮,也会被计入Undo与Redo堆栈导致撤销出现错误,我们认为可以引入新的变量和函数来记录这一变化,进而解决这一问题,但由于时间原因,没能实现这一部分功能; 881 | 882 | * 在对字体进行格式设置后,系统会认为文本内容发生了变化,进而将文本错误的压入堆栈,导致了后续撤销出现错误,我认为同样可以通过引入新的变量记录这一变化过程来解决,但由于时间原因,我们没能实现这个功能; 883 | 884 | * 由于字体设置后文本被保存成了txt格式,因而文本中的字体格式信息会丢失,我们没有找到用txt格式保存字体的合理方式(或许用类似markdown的标记方式可以储存字体格式,但我们认为功能过于复杂,可能并不好实现,需要借助现成的第三方库实现markdown类似功能)。 885 | 886 | * 按下Ctrl+Z和Ctrl+Y后,系统会调用自带的撤销与重做接口而不会使用我们定义的借口,通过在继承出来的edit类中加入事件过滤器可以解决此问题,但由于时间原因,我们没有完成此功能。 887 | * 查找和替换中,由于是用堆栈的形式实现的查找上一个和下一个,因此在查找下一个之后再从头查找,这样查找上一个的功能就会返回之前的位置,出错。 888 | * 查找和替换中,查找到最后一个,继续查找,会显示没有找到字符,但最后一个字符试的高亮还在,这是一个小bug目前还没有解决。此外在查找完之后关闭对话框,高亮仍会显示,这一bug目前也还没有解决,但我们提出了引入新变量记录这个变化,并在结束查找后根据记录恢复初始状态的方法解决,但由于时间原因未实现。 889 | 890 | ## 程序使用说明 891 | 892 | ### 文件 893 | 894 | ① 新建:创建一篇空白文档,从“工具栏”或“文件下拉菜单”中创建 895 | 896 | ② 打开:打开文本(.txt)文件,从“工具栏”或“文件下拉菜单”中打开;或将文本(.txt)文件拖入程序界面打开或应用程序图标上打开;或右键单击文本文件,在“打开方式”中选择本程序打开 897 | 898 | ③ 保存:保存文档,从“工具栏”或“文件下拉菜单”中打开 899 | 900 | ④ 另存为:保存文件副本,在不同位置或以不同文件名保存文档,从“工具栏”或“文件下拉菜单”中另存 901 | 902 | ### 编辑 903 | 904 | ① 撤销:撤销前一步所进行的操作,从“编辑下拉菜单”中撤销 905 | 906 | ② 重做:重做前一步所撤销的操作,从“编辑下拉菜单”中撤销 907 | 908 | ③ 剪切:复制并删除选定字符(串),从“编辑下拉菜单”中剪切 909 | 910 | ④ 复制:复制选定字符(串),“编辑下拉菜单”中复制 911 | 912 | ⑤ 粘贴:对粘贴内容进行粘贴,从“编辑下拉菜单”中粘贴 913 | 914 | ⑥ 全选:对文本编辑框中文本全部选定,从“编辑下拉菜单”中全选 915 | 916 | ⑦ 查找/替换:输入查找内容(和替换内容),可从光标位置逐个查找(或替换)相应内容,也可一次性全部替换掉相应内容,从“工具栏”或“应用下拉菜单”中执行 917 | 918 | ### 格式 919 | 920 | ① 字体设置:设置字体、字形及字的大小,从“工具栏”或“应用下拉菜单 ”中设置字体 921 | 922 | ② 颜色设置:设置字的颜色 923 | 924 | ③ 段落设置:设置段落格式 925 | -------------------------------------------------------------------------------- /edit.cpp: -------------------------------------------------------------------------------- 1 | #include "edit.h" 2 | #include 3 | #include 4 | #include "texteditor.h" 5 | Edit::Edit(QWidget *parent) : QTextEdit(parent) 6 | { 7 | 8 | } 9 | void Edit::contextMenuEvent(QContextMenuEvent *e) 10 | //重定义右键菜单 11 | { 12 | // QMenu* menu = new QMenu; 13 | // QAction* undo =menu->addAction("撤销",this,SLOT(Undo())); 14 | // undo->setEnabled(document()->isUndoAvailable()); 15 | // menu->exec(e->globalPos()); 16 | // delete menu; 17 | } 18 | -------------------------------------------------------------------------------- /edit.h: -------------------------------------------------------------------------------- 1 | #ifndef EDIT_H 2 | #define EDIT_H 3 | 4 | #include 5 | 6 | class Edit : public QTextEdit 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit Edit(QWidget *parent = nullptr); 11 | 12 | signals: 13 | 14 | public slots: 15 | 16 | protected: 17 | void contextMenuEvent(QContextMenuEvent* e); 18 | }; 19 | 20 | #endif // EDIT_H 21 | -------------------------------------------------------------------------------- /finddlg.cpp: -------------------------------------------------------------------------------- 1 | #include "finddlg.h" 2 | #include "ui_finddlg.h" 3 | 4 | FindDlg::FindDlg(QWidget *parent) : 5 | QDialog(parent), 6 | ui(new Ui::FindDlg) 7 | { 8 | ui->setupUi(this); 9 | setWindowTitle(tr("查找/替换 - TextEditor")); 10 | } 11 | 12 | FindDlg::~FindDlg() 13 | { 14 | delete ui; 15 | } 16 | 17 | /******************************* 18 | * 查找函数,用于从头查找 19 | * 进入查找对话框后,不可更改原文本内容 20 | * 21 | ******************************/ 22 | bool FindDlg::find() 23 | { 24 | //获取当前文本内容 25 | QString str = nowText; 26 | //获取当前需要查找的字符串 27 | QString toBeFound = ui->findEdit->text(); 28 | int index = str.indexOf(toBeFound); 29 | if (index == -1) 30 | { 31 | ui->resultEdit->setText("没有找到"+toBeFound); 32 | return false; 33 | } 34 | else 35 | { 36 | ui->resultEdit->setText(QString::number(index)); 37 | //保存上一次查找结果 38 | lastIndex = index; 39 | stk.push(index); 40 | return true; 41 | } 42 | } 43 | 44 | /******************************* 45 | * 查找函数,用于查找下一个 46 | * 进入查找对话框后,不可更改原文本内容 47 | * 48 | ******************************/ 49 | bool FindDlg::findNext() 50 | { 51 | //获取当前文本内容 52 | QString str = nowText; 53 | //获取当前需要查找的字符串 54 | QString toBeFound = ui->findEdit->text(); 55 | //循环从上一次搜索到的地方开始 56 | int i = lastIndex; 57 | //上一次搜到的结果 58 | for (; i<=str.length(); i++) 59 | { 60 | if(lastIndex != str.indexOf(toBeFound, i)) 61 | { 62 | break; 63 | } 64 | } 65 | int index = str.indexOf(toBeFound, i); 66 | if (index == -1) 67 | { 68 | ui->resultEdit->setText("没有找到"+toBeFound); 69 | return false; 70 | } 71 | else 72 | { 73 | ui->resultEdit->setText(QString::number(index)); 74 | lastIndex = index; 75 | stk.push(index); 76 | return true; 77 | } 78 | } 79 | 80 | bool FindDlg::findPrevious() 81 | { 82 | if (!stk.isEmpty()) 83 | { 84 | stk.pop(); 85 | } 86 | if (stk.isEmpty()) 87 | { 88 | ui->resultEdit->setText("已经是第一个"); 89 | return false; 90 | } 91 | else 92 | { 93 | lastIndex = stk.top(); 94 | ui->resultEdit->setText(QString::number(lastIndex)); 95 | return true; 96 | } 97 | } 98 | 99 | bool FindDlg::replace() 100 | { 101 | emit sendQString(ui->replaceEdit->text());//传递替换成的值 102 | return true; 103 | } 104 | 105 | /********************** 106 | * 查找按钮槽 107 | **********************/ 108 | void FindDlg::on_findButton_clicked() 109 | { 110 | find(); 111 | //emit sendQString(QString::number(lastIndex)); 112 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 113 | } 114 | /*************************** 115 | * 查找下一个按钮槽 116 | ***************************/ 117 | void FindDlg::on_nextButton_clicked() 118 | { 119 | if (stk.isEmpty()) 120 | { 121 | find(); 122 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 123 | } 124 | else 125 | { 126 | findNext(); 127 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 128 | } 129 | } 130 | /*************************** 131 | * 查找上一个按钮槽 132 | ***************************/ 133 | void FindDlg::on_previousButton_clicked() 134 | { 135 | findPrevious(); 136 | emit sendIndex(lastIndex, ui->findEdit->text().length());//传递查找到的下标 137 | } 138 | 139 | void FindDlg::on_replaceButton_clicked() 140 | { 141 | replace(); 142 | } 143 | -------------------------------------------------------------------------------- /finddlg.h: -------------------------------------------------------------------------------- 1 | #ifndef FINDDLG_H 2 | #define FINDDLG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class FindDlg; 9 | } 10 | 11 | class FindDlg : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit FindDlg(QWidget *parent = nullptr); 17 | ~FindDlg(); 18 | bool find(); 19 | bool findNext(); 20 | bool findPrevious(); 21 | bool replace(); 22 | //当前文本框存的值 23 | QString nowText; 24 | //上次查到的值 25 | int lastIndex; 26 | //堆栈用于存储已经找到的字符的位置 27 | QStack stk; 28 | 29 | signals: 30 | void sendIndex(int, int); 31 | void sendQString(QString); 32 | 33 | private slots: 34 | void on_findButton_clicked(); 35 | 36 | void on_nextButton_clicked(); 37 | 38 | void on_previousButton_clicked(); 39 | 40 | void on_replaceButton_clicked(); 41 | 42 | private: 43 | Ui::FindDlg *ui; 44 | }; 45 | 46 | #endif // FINDDLG_H 47 | -------------------------------------------------------------------------------- /finddlg.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FindDlg 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 250 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Dialog 21 | 22 | 23 | 24 | 25 | 20 26 | 0 27 | 91 28 | 31 29 | 30 | 31 | 32 | 查找内容 33 | 34 | 35 | 36 | 37 | 38 | 20 39 | 30 40 | 351 41 | 31 42 | 43 | 44 | 45 | 46 | 47 | 48 | 20 49 | 130 50 | 151 51 | 21 52 | 53 | 54 | 55 | 待查找字符所在位置 56 | 57 | 58 | 59 | 60 | 61 | 20 62 | 150 63 | 351 64 | 31 65 | 66 | 67 | 68 | ArrowCursor 69 | 70 | 71 | Qt::PreventContextMenu 72 | 73 | 74 | 75 | 76 | 77 | true 78 | 79 | 80 | 81 | 82 | 83 | 200 84 | 190 85 | 81 86 | 41 87 | 88 | 89 | 90 | 上一个 91 | 92 | 93 | 94 | 95 | 96 | 20 97 | 190 98 | 81 99 | 41 100 | 101 | 102 | 103 | 从头查找 104 | 105 | 106 | 107 | 108 | 109 | 290 110 | 190 111 | 81 112 | 41 113 | 114 | 115 | 116 | 下一个 117 | 118 | 119 | 120 | 121 | 122 | 20 123 | 90 124 | 351 125 | 31 126 | 127 | 128 | 129 | 130 | 131 | 132 | 20 133 | 60 134 | 91 135 | 31 136 | 137 | 138 | 139 | 替换内容 140 | 141 | 142 | 143 | 144 | 145 | 110 146 | 190 147 | 81 148 | 41 149 | 150 | 151 | 152 | 替换 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "texteditor.h" 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | TextEditor w; 9 | w.show(); 10 | 11 | return a.exec(); 12 | } 13 | -------------------------------------------------------------------------------- /myresorces.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | rsc/icon.ico 4 | rsc/Editor.rc 5 | rsc/bold.png 6 | rsc/just.png 7 | rsc/left.png 8 | rsc/middle.png 9 | rsc/right.png 10 | rsc/save.png 11 | rsc/size.png 12 | rsc/underline.png 13 | rsc/redo.png 14 | rsc/undo.png 15 | rsc/copy.png 16 | rsc/cut.png 17 | rsc/paste.png 18 | rsc/new.png 19 | rsc/find.png 20 | rsc/i.png 21 | rsc/help.png 22 | rsc/icon.png 23 | rsc/open.png 24 | rsc/close.png 25 | rsc/saveas.png 26 | rsc/color.png 27 | rsc/selectall.png 28 | 29 | 30 | -------------------------------------------------------------------------------- /rsc/Editor.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icon.ico" -------------------------------------------------------------------------------- /rsc/bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/bold.png -------------------------------------------------------------------------------- /rsc/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/close.png -------------------------------------------------------------------------------- /rsc/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/color.png -------------------------------------------------------------------------------- /rsc/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/copy.png -------------------------------------------------------------------------------- /rsc/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/cut.png -------------------------------------------------------------------------------- /rsc/find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/find.png -------------------------------------------------------------------------------- /rsc/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/help.png -------------------------------------------------------------------------------- /rsc/i.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/i.png -------------------------------------------------------------------------------- /rsc/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/icon.ico -------------------------------------------------------------------------------- /rsc/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/icon.png -------------------------------------------------------------------------------- /rsc/just.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/just.png -------------------------------------------------------------------------------- /rsc/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/left.png -------------------------------------------------------------------------------- /rsc/middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/middle.png -------------------------------------------------------------------------------- /rsc/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/new.png -------------------------------------------------------------------------------- /rsc/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/open.png -------------------------------------------------------------------------------- /rsc/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/paste.png -------------------------------------------------------------------------------- /rsc/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/redo.png -------------------------------------------------------------------------------- /rsc/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/right.png -------------------------------------------------------------------------------- /rsc/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/save.png -------------------------------------------------------------------------------- /rsc/saveas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/saveas.png -------------------------------------------------------------------------------- /rsc/selectall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/selectall.png -------------------------------------------------------------------------------- /rsc/size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/size.png -------------------------------------------------------------------------------- /rsc/underline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/underline.png -------------------------------------------------------------------------------- /rsc/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspxcor/Text-Editor-With-Qt/4a5d6685fcebb8301b311977a7d61958bab6e423/rsc/undo.png -------------------------------------------------------------------------------- /texteditor.cpp: -------------------------------------------------------------------------------- 1 | #include "texteditor.h" 2 | #include "ui_texteditor.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "finddlg.h" 11 | #include 12 | #include 13 | TextEditor::TextEditor(QWidget *parent) : 14 | QMainWindow(parent), 15 | ui(new Ui::TextEditor) 16 | { 17 | ui->setupUi(this); 18 | // 初始化文件为未保存状态 19 | isUntitled = true; 20 | // 初始化文件名为"未命名.txt" 21 | curFile = tr("未命名.txt"); 22 | // 初始化窗口标题为文件名 23 | setWindowTitle(curFile + tr(" - TextEditor")); 24 | strUndo.push(ui->textEdit->toPlainText()); 25 | connect(ui->textEdit,SIGNAL(textChanged(QString)),this,SLOT(on_textEdit_textChanged)); 26 | } 27 | 28 | TextEditor::~TextEditor() 29 | { 30 | delete ui; 31 | } 32 | 33 | void TextEditor::newFile() 34 | { 35 | 36 | // strUndo.push(ui->textEdit->toPlainText()); 37 | if (maybeSave()) { 38 | isUntitled = true; 39 | curFile = tr("未命名.txt"); 40 | setWindowTitle(curFile); 41 | ui->textEdit->clear(); 42 | ui->textEdit->setVisible(true); 43 | // strUndo.push(ui->textEdit->toPlainText()); 44 | } 45 | resetStack(); 46 | strUndo.push(ui->textEdit->toPlainText()); 47 | } 48 | 49 | bool TextEditor::maybeSave() 50 | { 51 | // 如果文档被更改了 52 | if ((ui->textEdit->document()->isModified())||((undoIsUsed)&&(!strUndo.isEmpty()))) { 53 | // 自定义一个警告对话框 54 | QMessageBox box; 55 | box.setWindowTitle(tr("警告")); 56 | box.setIcon(QMessageBox::Warning); 57 | box.setText(curFile + tr(" 尚未保存,是否保存?")); 58 | QPushButton *yesBtn = box.addButton(tr("是(&Y)"), 59 | QMessageBox::YesRole); 60 | box.addButton(tr("否(&N)"), QMessageBox::NoRole); 61 | QPushButton *cancelBut = box.addButton(tr("取消"), 62 | QMessageBox::RejectRole); 63 | box.exec(); 64 | if (box.clickedButton() == yesBtn) 65 | return save(); 66 | else if (box.clickedButton() == cancelBut) 67 | return false; 68 | } 69 | // 如果文档没有被更改,则直接返回true 70 | return true; 71 | } 72 | 73 | bool TextEditor::save() 74 | { 75 | if (isUntitled) { 76 | return saveAs(); 77 | } else { 78 | return saveFile(curFile); 79 | } 80 | } 81 | 82 | bool TextEditor::saveAs() 83 | { 84 | QString fileName = QFileDialog::getSaveFileName(this, 85 | tr("另存为"),curFile); 86 | if (fileName.isEmpty()) return false; 87 | return saveFile(fileName); 88 | } 89 | void TextEditor::resetStack() 90 | //实现每次打开新文件时的重置堆栈 91 | { 92 | strUndo.clear(); 93 | strRedo.clear(); 94 | undoIsUsed=0; 95 | } 96 | 97 | bool TextEditor::saveFile(const QString &fileName) 98 | { 99 | QFile file(fileName); 100 | 101 | if (!file.open(QFile::WriteOnly | QFile::Text)) { 102 | 103 | // %1和%2分别对应后面arg两个参数,/n起换行的作用 104 | QMessageBox::warning(this, tr("多文档编辑器"), 105 | tr("无法写入文件 %1:/n %2") 106 | .arg(fileName).arg(file.errorString())); 107 | return false; 108 | } 109 | QTextStream out(&file); 110 | // 鼠标指针变为等待状态 111 | QApplication::setOverrideCursor(Qt::WaitCursor); 112 | out << ui->textEdit->toPlainText(); 113 | // 鼠标指针恢复原来的状态 114 | QApplication::restoreOverrideCursor(); 115 | isUntitled = false; 116 | // 获得文件的标准路径 117 | curFile = QFileInfo(fileName).canonicalFilePath(); 118 | setWindowTitle(curFile); 119 | return true; 120 | } 121 | 122 | bool TextEditor::loadFile(const QString &fileName) 123 | { 124 | //resetStack(); 125 | QFile file(fileName); // 新建QFile对象 126 | if (!file.open(QFile::ReadOnly | QFile::Text)) { 127 | QMessageBox::warning(this, tr("多文档编辑器"), 128 | tr("无法读取文件 %1:\n%2.") 129 | .arg(fileName).arg(file.errorString())); 130 | return false; // 只读方式打开文件,出错则提示,并返回false 131 | } 132 | 133 | QTextStream in(&file); // 新建文本流对象 134 | QApplication::setOverrideCursor(Qt::WaitCursor); 135 | // 读取文件的全部文本内容,并添加到编辑器中 136 | // isLoadFile=1; 137 | // resetStack(); 138 | ui->textEdit->setPlainText(in.readAll()); 139 | resetStack(); 140 | strUndo.push(ui->textEdit->toPlainText()); 141 | QApplication::restoreOverrideCursor(); 142 | 143 | // 设置当前文件 144 | curFile = QFileInfo(fileName).canonicalFilePath(); 145 | setWindowTitle(curFile + tr(" - TextEditor")); 146 | // strRedo.push(ui->textEdit->toPlainText()); 147 | 148 | // strRedo.push(ui->textEdit->toPlainText()); 149 | ui->textEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); 150 | return true; 151 | } 152 | 153 | void TextEditor::receiveIndex(int index, int length) 154 | { 155 | replaceIndex = index; 156 | replaceLength = length; 157 | 158 | //先把之前的格式清除 159 | QTextCursor cursor = ui->textEdit->textCursor(); //获取当前光标 160 | cursor.setPosition(lastIndex); //定位到下标index的位置 161 | cursor.setPosition(lastIndex+lastLength, QTextCursor::KeepAnchor); //文本选择范围[index,index + length] 162 | //选中完成 163 | QTextCharFormat fmt;//定义突出显示 164 | fmt.setBackground(Qt::white);//定义显示的格式 165 | cursor.mergeCharFormat(fmt);//显示 166 | 167 | //再显示当前的格式 168 | //QTextCursor cursor = ui->textEdit->textCursor(); //获取当前光标 169 | cursor.setPosition(index); //定位到下标index的位置 170 | cursor.setPosition(index+length, QTextCursor::KeepAnchor); //文本选择范围[index,index + length] 171 | //选中完成 172 | //QTextCharFormat fmt;//定义突出显示 173 | fmt.setBackground(Qt::yellow);//定义显示的格式 174 | cursor.mergeCharFormat(fmt);//显示 175 | 176 | lastIndex = index; 177 | lastLength = length; 178 | } 179 | 180 | void TextEditor::receiveQString(QString replaceStr) 181 | { 182 | ui->textEdit->setPlainText(((ui->textEdit->toPlainText()).replace(replaceIndex, replaceLength, replaceStr))); 183 | } 184 | 185 | 186 | void TextEditor::on_action_New_triggered() 187 | { 188 | newFile(); 189 | } 190 | 191 | 192 | void TextEditor::on_action_Save_triggered() 193 | { 194 | save(); 195 | } 196 | 197 | 198 | void TextEditor::on_action_SavaAs_triggered() 199 | { 200 | saveAs(); 201 | } 202 | void TextEditor::on_action_Undo_triggered() 203 | { 204 | //测试用 205 | // isUndo=1; 206 | // ui->textEdit->setPlainText(strSave.pop()); 207 | // Sleep(2000); 208 | // if(strSave.isEmpty()) 209 | // ui->textEdit->setPlainText("in undo:empty"); 210 | // else { 211 | // ui->textEdit->setPlainText("in undo:not empty"); 212 | // } 213 | // isUndo=0; 214 | //QString test=strSave.pop(); 215 | //QMessageBox::information(this,tr("hello"),tr(strSave.pop())); 216 | undoIsUsed=1; 217 | isUndo=1; 218 | if(strUndo.size()>1) 219 | { 220 | strRedo.push(strUndo.pop()); 221 | ui->textEdit->setPlainText(strUndo.pop()); 222 | strRedo.push(ui->textEdit->toPlainText()); 223 | ui->textEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); 224 | } 225 | isUndo=0; 226 | } 227 | void TextEditor::on_action_Y_triggered() 228 | { 229 | isRedo=1; 230 | if(strRedo.size()>1) 231 | { 232 | strUndo.push(strRedo.pop()); 233 | ui->textEdit->setPlainText(strRedo.pop()); 234 | strUndo.push(ui->textEdit->toPlainText()); 235 | ui->textEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); 236 | } 237 | isRedo=0; 238 | } 239 | void TextEditor::on_action_Open_triggered() 240 | { 241 | // resetStack(); 242 | // isLoadFile=1; 243 | if (maybeSave()) { 244 | 245 | QString fileName = QFileDialog::getOpenFileName(this); 246 | 247 | // 如果文件名不为空,则加载文件 248 | if (!fileName.isEmpty()) { 249 | loadFile(fileName); 250 | ui->textEdit->setVisible(true); 251 | } 252 | } 253 | } 254 | 255 | 256 | void TextEditor::on_action_Find_triggered() 257 | { 258 | //QWidget *new_win = new QWidget(); 259 | //(*new_win).show(); 260 | f = new FindDlg(); 261 | f->nowText = ui->textEdit->toPlainText(); 262 | //连接 263 | connect(f,SIGNAL(sendIndex(int, int)),this,SLOT(receiveIndex(int, int))); 264 | connect(f,SIGNAL(sendQString(QString)),this,SLOT(receiveQString(QString))); 265 | //显示子对话框 266 | f->show(); 267 | //QMessageBox::information(this,tr("hello"),tr("Mars")); 268 | } 269 | 270 | void TextEditor:: mergeformat(const QTextCharFormat &fmt) 271 | {/*设置光标的选区,使格式作用于选区内的字符,若没有选区则作用于光标所在处的字符*/ 272 | QTextCursor cursor =ui->textEdit->textCursor(); 273 | 274 | if ( !cursor.hasSelection() ) 275 | 276 | cursor.select( QTextCursor::WordUnderCursor ); 277 | 278 | cursor.mergeCharFormat( fmt ); 279 | 280 | ui->textEdit->mergeCurrentCharFormat( fmt ); 281 | 282 | } 283 | 284 | void TextEditor::textBold() 285 | { 286 | QTextCharFormat fmt; 287 | 288 | fmt.setFontWeight( ui->action_Bold->isChecked() ? QFont::Bold : QFont::Normal ); 289 | 290 | mergeformat( fmt ); 291 | } 292 | 293 | void TextEditor::textItalic() 294 | { 295 | QTextCharFormat fmt; 296 | 297 | fmt.setFontItalic( ui->action_Italic->isChecked() ); 298 | 299 | mergeformat( fmt ); 300 | } 301 | 302 | void TextEditor::textUnderline() 303 | { 304 | QTextCharFormat fmt; 305 | 306 | fmt.setFontUnderline( ui->action_Underline->isChecked() ); 307 | 308 | mergeformat( fmt ); 309 | } 310 | 311 | void TextEditor::textColor() 312 | { 313 | QColor c =QColorDialog::getColor(Qt::red, this );//调用颜色对话框 314 | if ( c.isValid() ) 315 | { 316 | QTextCharFormat fmt; 317 | 318 | fmt.setForeground( c ); 319 | 320 | mergeformat( fmt ); 321 | } 322 | } 323 | 324 | void TextEditor::textFont() 325 | { 326 | bool ok; 327 | QFont f = QFontDialog::getFont(&ok);//调用字体对话框 328 | if(ok){ 329 | QTextCharFormat fmt; 330 | 331 | fmt.setFont(f ); 332 | 333 | mergeformat( fmt ); 334 | } 335 | } 336 | 337 | 338 | void TextEditor::textCopy() 339 | { 340 | /*设置光标的选区, 341 | 将选中的字符存入剪贴板*/ 342 | pasteBoard=""; 343 | QTextCursor cursor =ui->textEdit->textCursor(); 344 | 345 | pasteBoard=cursor.selectedText(); 346 | } 347 | 348 | void TextEditor::textCut() 349 | { 350 | /*设置光标的选区, 351 | 将选中的字符存入剪贴板,同时删除选中内容*/ 352 | QTextCursor cursor =ui->textEdit->textCursor(); 353 | 354 | pasteBoard=cursor.selectedText(); 355 | 356 | cursor.removeSelectedText(); 357 | } 358 | 359 | void TextEditor::textPaste() 360 | { 361 | /*删除当前选中内容,插入剪贴板文本*/ 362 | QTextCursor cursor =ui->textEdit->textCursor(); 363 | 364 | cursor.removeSelectedText(); 365 | 366 | cursor.insertText(pasteBoard); 367 | } 368 | 369 | 370 | void TextEditor::textCurrentFormatChanged(const QTextCharFormat &fmt) 371 | {// 当光标所在处字符格式有变化,工具栏做出相应改变 372 | ui->action_Bold->setChecked( fmt.font().bold() ); 373 | 374 | ui->action_Italic->setChecked( fmt.fontItalic() ); 375 | 376 | ui->action_Underline->setChecked( fmt.fontUnderline() ); 377 | } 378 | 379 | 380 | void TextEditor::on_action_Bold_triggered() 381 | { 382 | textBold(); 383 | } 384 | 385 | void TextEditor::on_action_Italic_triggered() 386 | { 387 | textItalic(); 388 | } 389 | 390 | void TextEditor::on_action_Underline_triggered() 391 | { 392 | textUnderline(); 393 | } 394 | 395 | void TextEditor::on_action_Left_triggered() 396 | {//左对齐 397 | ui->textEdit->setAlignment( Qt::AlignLeft ); 398 | if(ui->action_Left->isChecked())//设置成单选 399 | { 400 | ui->action_Right->setChecked(false); 401 | ui->action_Center->setChecked(false); 402 | } 403 | else ui->action_Left->setChecked(true); 404 | } 405 | 406 | void TextEditor::on_action_Right_triggered() 407 | {//右对齐 408 | ui->textEdit->setAlignment( Qt::AlignRight ); 409 | if(ui->action_Right->isChecked())//设置成单选 410 | { 411 | ui->action_Left->setChecked(false); 412 | ui->action_Center->setChecked(false); 413 | ui->action_Just->setChecked(false); 414 | } 415 | else ui->action_Right->setChecked(true); 416 | } 417 | 418 | void TextEditor::on_action_Center_triggered() 419 | {//居中 420 | ui->textEdit->setAlignment( Qt::AlignCenter ); 421 | if(ui->action_Center->isChecked())//设置成单选 422 | { 423 | ui->action_Just->setChecked(false); 424 | ui->action_Right->setChecked(false); 425 | ui->action_Left->setChecked(false); 426 | } 427 | else ui->action_Center->setChecked(true); 428 | } 429 | 430 | void TextEditor::on_action_Color_triggered() 431 | { 432 | textColor(); 433 | } 434 | 435 | void TextEditor::on_action_Font_triggered() 436 | {//字体设置 437 | textFont(); 438 | } 439 | 440 | void TextEditor::on_action_Just_triggered() 441 | {//两端对齐 442 | ui->textEdit->setAlignment( Qt::AlignJustify ); 443 | if(ui->action_Center->isChecked())//设置成单选 444 | { 445 | ui->action_Center->setChecked(false); 446 | ui->action_Right->setChecked(false); 447 | ui->action_Left->setChecked(false); 448 | } 449 | else ui->action_Just->setChecked(true); 450 | } 451 | void TextEditor::on_textEdit_textChanged() 452 | { 453 | //文本框更新时更新堆栈 454 | 455 | //测试代码 456 | //if(!isUndo) 457 | // strSave.push(ui->textEdit->toPlainText()); 458 | // if(!strSave.isEmpty()&&!isUndo) 459 | // { 460 | // QMessageBox::information(this,tr("hello"),tr("textChanged")); 461 | // } 462 | //strSave.push("1234"); 463 | // strRedo.push(ui->textEdit->toPlainText()); 464 | // printf("%d",undoIsUsed); 465 | if((!isUndo)&&(!isRedo)&&(!isLoadFile)) 466 | { 467 | strUndo.push(ui->textEdit->toPlainText()); 468 | } 469 | } 470 | 471 | void TextEditor::on_action_selectAll_triggered() 472 | { 473 | ui->textEdit->selectAll(); 474 | } 475 | void TextEditor::on_action_Copy_triggered() 476 | { 477 | textCopy(); 478 | 479 | } 480 | 481 | void TextEditor::on_action_Paste_triggered() 482 | { 483 | textPaste(); 484 | } 485 | 486 | void TextEditor::on_action_Cut_triggered() 487 | { 488 | textCut(); 489 | } 490 | -------------------------------------------------------------------------------- /texteditor.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTEDITOR_H 2 | #define TEXTEDITOR_H 3 | 4 | #include 5 | #include "finddlg.h" 6 | #include 7 | #include "edit.h" 8 | namespace Ui { 9 | class TextEditor; 10 | } 11 | 12 | class TextEditor : public QMainWindow 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit TextEditor(QWidget *parent = nullptr); 18 | ~TextEditor(); 19 | void newFile(); // 新建操作 20 | bool maybeSave(); // 判断是否需要保存 21 | bool save(); // 保存操作 22 | bool saveAs(); // 另存为操作 23 | bool saveFile(const QString &fileName); // 保存文件 24 | bool loadFile(const QString &fileName); // 加载文件 25 | int replaceIndex;//替换文本下标 26 | int replaceLength;//替换文本长度 27 | int lastIndex;//上一个查找到的文本下标 28 | int lastLength;//上一个查找到的文本的长度 29 | void mergeformat(const QTextCharFormat &fmt); 30 | void textBold();//加粗 31 | void textItalic();//斜体 32 | void textUnderline();//下划线 33 | void textColor(); 34 | void textCurrentFormatChanged(const QTextCharFormat & fmt);//根据光标处格式改变菜单栏 35 | void textFont(); 36 | QStack strUndo; 37 | QStack strRedo; 38 | void refreshStack(); 39 | void resetStack(); 40 | void textCopy(); 41 | void textCut(); 42 | void textPaste(); 43 | QString pasteBoard; 44 | //void createPdf(); 45 | public slots: 46 | void on_action_Undo_triggered(); 47 | private slots: 48 | void on_action_New_triggered(); 49 | 50 | void on_action_Save_triggered(); 51 | 52 | void on_action_SavaAs_triggered(); 53 | 54 | void on_action_Open_triggered(); 55 | 56 | 57 | void on_action_Find_triggered(); 58 | 59 | void receiveIndex(int index, int length); //接受替换信号的槽函数 60 | 61 | void receiveQString(QString replaceStr); 62 | 63 | void on_action_Bold_triggered(); 64 | 65 | void on_action_Italic_triggered(); 66 | 67 | void on_action_Underline_triggered(); 68 | 69 | void on_action_Left_triggered(); 70 | 71 | void on_action_Right_triggered(); 72 | 73 | void on_action_Center_triggered(); 74 | 75 | 76 | 77 | void on_action_Color_triggered(); 78 | 79 | void on_action_Font_triggered(); 80 | void on_action_Just_triggered(); 81 | void on_action_Y_triggered(); 82 | void on_textEdit_textChanged(); 83 | void on_action_selectAll_triggered(); 84 | void on_action_Copy_triggered(); 85 | 86 | void on_action_Paste_triggered(); 87 | 88 | void on_action_Cut_triggered(); 89 | 90 | private: 91 | Edit edit; 92 | Ui::TextEditor *ui; 93 | // 为真表示文件没有保存过,为假表示文件已经被保存过了 94 | bool isUntitled; 95 | // 保存当前文件的路径 96 | QString curFile; 97 | // 查找 98 | FindDlg *f; // 查找子窗体 99 | bool isUndo=0; 100 | bool isRedo=0; 101 | bool isLoadFile=0; 102 | bool undoIsUsed=0; 103 | }; 104 | 105 | #endif // TEXTEDITOR_H 106 | -------------------------------------------------------------------------------- /texteditor.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TextEditor 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1000 10 | 600 11 | 12 | 13 | 14 | TextEditor 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | toolBar 26 | 27 | 28 | TopToolBarArea 29 | 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 0 60 | 0 61 | 1000 62 | 26 63 | 64 | 65 | 66 | 67 | 文件(&F) 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 编辑(&E) 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 格式(&O) 92 | 93 | 94 | 95 | 字体(&F) 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 段落(&P) 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | :/myImages/rsc/new.png:/myImages/rsc/new.png 123 | 124 | 125 | 新建(&N) 126 | 127 | 128 | 129 | 130 | 131 | :/myImages/rsc/open.png:/myImages/rsc/open.png 132 | 133 | 134 | 打开(&O) 135 | 136 | 137 | 138 | 139 | 140 | :/myImages/rsc/close.png:/myImages/rsc/close.png 141 | 142 | 143 | 关闭(&C) 144 | 145 | 146 | 147 | 148 | 149 | :/myImages/rsc/save.png:/myImages/rsc/save.png 150 | 151 | 152 | 保存(&S) 153 | 154 | 155 | Ctrl+S 156 | 157 | 158 | 159 | 160 | 161 | :/myImages/rsc/saveas.png:/myImages/rsc/saveas.png 162 | 163 | 164 | 另存为(&A) 165 | 166 | 167 | 168 | 169 | 170 | :/myImages/rsc/close.png:/myImages/rsc/close.png 171 | 172 | 173 | 退出(&X) 174 | 175 | 176 | 177 | 178 | false 179 | 180 | 181 | 182 | :/myImages/rsc/undo.png:/myImages/rsc/undo.png 183 | 184 | 185 | 撤销(&Z) 186 | 187 | 188 | Ctrl+Z 189 | 190 | 191 | 192 | 193 | 194 | :/myImages/rsc/cut.png:/myImages/rsc/cut.png 195 | 196 | 197 | 剪切(&X) 198 | 199 | 200 | Ctrl+X 201 | 202 | 203 | 204 | 205 | 206 | :/myImages/rsc/copy.png:/myImages/rsc/copy.png 207 | 208 | 209 | 复制(&C) 210 | 211 | 212 | Ctrl+C 213 | 214 | 215 | 216 | 217 | 218 | :/myImages/rsc/paste.png:/myImages/rsc/paste.png 219 | 220 | 221 | 粘贴(&V) 222 | 223 | 224 | Ctrl+V 225 | 226 | 227 | 228 | 229 | 230 | :/myImages/rsc/find.png:/myImages/rsc/find.png 231 | 232 | 233 | 查找/替换(&F) 234 | 235 | 236 | Ctrl+F 237 | 238 | 239 | 240 | 241 | 242 | :/myImages/rsc/help.png:/myImages/rsc/help.png 243 | 244 | 245 | 关于 246 | 247 | 248 | 249 | 250 | true 251 | 252 | 253 | false 254 | 255 | 256 | 257 | :/myImages/rsc/bold.png:/myImages/rsc/bold.png 258 | 259 | 260 | 加粗(&B) 261 | 262 | 263 | Ctrl+B 264 | 265 | 266 | 267 | 268 | true 269 | 270 | 271 | false 272 | 273 | 274 | 275 | :/myImages/rsc/i.png:/myImages/rsc/i.png 276 | 277 | 278 | 倾斜(&I) 279 | 280 | 281 | Ctrl+I 282 | 283 | 284 | 285 | 286 | true 287 | 288 | 289 | 290 | :/myImages/rsc/underline.png:/myImages/rsc/underline.png 291 | 292 | 293 | 下划线(U) 294 | 295 | 296 | Ctrl+U 297 | 298 | 299 | 300 | 301 | true 302 | 303 | 304 | true 305 | 306 | 307 | 308 | :/myImages/rsc/left.png:/myImages/rsc/left.png 309 | 310 | 311 | 左对齐 312 | 313 | 314 | 315 | 316 | true 317 | 318 | 319 | 320 | :/myImages/rsc/right.png:/myImages/rsc/right.png 321 | 322 | 323 | 右对齐 324 | 325 | 326 | 327 | 328 | true 329 | 330 | 331 | 332 | :/myImages/rsc/middle.png:/myImages/rsc/middle.png 333 | 334 | 335 | 居中 336 | 337 | 338 | 339 | 340 | 341 | :/myImages/rsc/color.png:/myImages/rsc/color.png 342 | 343 | 344 | 颜色 345 | 346 | 347 | 348 | 349 | 350 | :/myImages/rsc/size.png:/myImages/rsc/size.png 351 | 352 | 353 | 设置字体 354 | 355 | 356 | 357 | 358 | true 359 | 360 | 361 | 362 | :/myImages/rsc/just.png:/myImages/rsc/just.png 363 | 364 | 365 | 两端对齐 366 | 367 | 368 | 369 | 370 | 371 | :/myImages/rsc/redo.png:/myImages/rsc/redo.png 372 | 373 | 374 | 重做(&Y) 375 | 376 | 377 | 重做(Y) 378 | 379 | 380 | Ctrl+Y 381 | 382 | 383 | 384 | 385 | 386 | :/myImages/rsc/selectall.png:/myImages/rsc/selectall.png 387 | 388 | 389 | 全选(&A) 390 | 391 | 392 | 全选(A) 393 | 394 | 395 | Ctrl+A 396 | 397 | 398 | 399 | 400 | 401 | 402 | Edit 403 | QTextEdit 404 |
edit.h
405 |
406 |
407 | 408 | 409 | 410 | 411 |
412 | --------------------------------------------------------------------------------