图片 富文本 粘贴_用C++实现富文本控件(中): 撤销
用C++实现富文本控件: 撤销
本节是关于撤销重做相关实现. 项目地址: Github-RichED 本文备份地址: github
撤销重做
这就被称为UNDO/REDO之类的, 简直是'增量'的代表. 这也算富文本编辑器实现的一个难点. 虽然叫做撤销栈, 但是不完全是栈:
- 一般栈只需要栈底、栈顶指针, 撤销栈多一个操作位置
- 撤销是操作位-1, 栈顶不动, 直到达到栈底
- 重做是操作位+1, 栈顶不动, 直到达到栈顶
- 每一个操作(OP)入操作位栈, 操作位+1, 栈顶 = 操作位
具体实现中, 可以使用链表模拟栈. 然后设计上尽量使用可以直接调用free
就能释放的Trivial数据, 原因后述.
装饰操作
替换操作很常见:
- 将范围[2, +2]的'泥壕', 替换为长度为2的'你好'
- 这个按下撤销键, 会先删除'你好'再添上'泥壕', 可以简化为两个OP:
- (1). 将范围[2, +2]的'泥壕'删掉
- (2). 在位置为2的地方插入长度为2的'你好'
这样就是将现有问题转换为已知的问题: 删除和插入是最基本的操作(坐下坐下这是基本操作). 但是就有一个问题: 撤销会变成两次.
Scintilla
Scintilla是一个与本项目类似的代码编辑控件, 作者提到富文本编辑器会把格式的修改也压入栈. 这个特性让他实为烦躁, 于是出现了Scintilla.
以上两点我们可以用一个装饰的标志解决, 将后面的装饰性操作打上标志:
STACK-TOP
OP <- 独立的操作
OP +decorator <- 装饰操作
OP <- 与上面的装饰操作作为一个整体的操作
OP +decorator <- 装饰操作
OP +decorator <- 装饰操作
OP <- 与上面两个装饰操作作为一个整体的操作例子: 将'type'替换成'class ', 然后输入'if'STACK-TOP
OP: 输入' '
OP: [装饰, 词法解析器检测到关键字] 将'if'设为蓝色
OP: 插入'if'
OP: [装饰, 词法解析器检测到关键字] 将'class'设为蓝色
OP: [装饰, 实际是替换操作插入部分] 插入'class '
OP: 删除'type'
程序语言级可以用begin_op
和end_op
之类的函数包裹实现:
void demo(void) {// 替换type->classbegin_op();remove_text(type);insert_text("class ");do_parser();end_op();// 插入ifbegin_op();insert_text("if");do_parser();end_op();// 输入' 'begin_op();insert_text(" ");do_parser();end_op();
}void insert_text(const char[]) {// 具体实现// 操作记录if (op) {// 撤销栈操作以0开始// 0到下一个0, 这个前闭后开区间为一个完整的撤销栈操作// 超过0的作为装饰操作gui_op.op = op - 1;op++;}
}void begin_op(void) {op = 1;
}void end_op(void) {op = 0;
}
当然, 这是将富文本操作作为装饰的一种实现方式, 实际上还是把富文本操作记录在撤销栈上了.
使用begin_op
和end_op
之类的函数包裹的话, 还有一种实现就是完全将富文本操作排除在撤销栈操作外, 每次都由词法解析器进行富文本更新.
// 插入if
begin_op();
insert_text("if");
end_op();
do_parser();
好处是不会记录在撤销栈上, 坏处是撤销/重做后还要调用do_parser()
.
换行符
编辑器是允许中途更换换行符的, 这时候直接使用撤销栈会造成报道上的偏差.解决方法有:
- 修改换行符时修改撤销栈上所有OP
- 不直接保存绝对长度信息(总偏移), 而是相对信息(行号+行内偏移)
这里选择了后者, 方便全局编写
平平无奇撤销栈
撤销栈储存的OP必须是平平无奇的(trivial), 主要有两个特点:
- 没有析构操作, 或者说唯一的析构操作就是释放空间(
std::free()
) - 核心数据没有指针, 全部是偏移量
这样的好处有:
(1) 无需担心对象所有权. 内联对象, 特别是图片是相当消耗储存空间. 不可能显示保存一份, 撤销栈保存一份. 所以撤销栈上图片保存的仅仅是一种简略信息的引用. 比如引用计数什么的.
不过引用计数之类的会有析构之类的操作, 作为历史保存不太合适: 明明文档中不存在, 但是因为撤销栈存在引用就必须储存在内存中. 所以干脆平凡化: 储存简略信息, 比如uri
字符串.
(2) 上面是没有析构操作的好处, 这里是没有指针的好处: 储存偏移量几乎可以不加修改地直接将二进制信息存储在硬盘上, 即可以将撤销栈完整地、简单地存储下来.
(3) 上面这一点可能不太吸引人, 但是可以作为二进制文件编码, 将富文本以二进制形式直接储存下来.
富文本怎么保存? xml? rtf? 这些都是与其他程序交互时使用! 并且还要涉及到解析和标准什么的(光rtf解析就够吃一壶了). 自己用的, 直接储存撤销栈然后重现就行: 完毕后将全部字符串删掉, 将撤销栈最后一步保存下来(当然只是为了方便说明, 实际直接记录就行).
即: 超简单的序列化(Serialization)的实现.
当然, 没有指针的话, 空间的消耗将是最小的, 也算是一大优点. 以及也可以用于相同编辑框数据交换的类型, 例如操作系统的下的复制粘贴. 平平无奇+无指针, 强, 无敌.
大概具体的实现
富文本插入:
- 撤销: 插入的数据删除, 需要数据: 起点与重点
- 重做: 将插入的富文本插入回去, 需要数据: 起点, 富文本数据
富文本删除:
- 撤销: 将插入的富文本插入回去, 需要数据: 起点, 富文本数据
- 重做: 插入的数据删除, 需要数据: 起点与重点
内联对象插入:
- 撤销: 将插入的数据删除, 需要数据: 起点(长度假定为1)
- 重做: 将插入的内联对象插入回去, 需要数据: 起点, 内联对象
内联对象删除:
- 撤销: 将插入的内联对象插入回去, 需要数据: 起点, 内联对象
- 重做: 将插入的数据删除, 需要数据: 起点(长度假定为1)
可以看出插入与删除完全就是相反的, 我们将对象进行分解:
- 内联对象的插入与替换
- 富属性的修改与回退
- 纯文本的插入
- 范围删除
- 无视
请注意, 这里内联对象的'替换'并不是真正的意义上的替换. 不会对文本长度造成影响, 完全是为撤销服务的. 所以自己就称之为升阶(Rank-Up), 需要调用RankUpMagic
完成替换.
平平无奇的实现
- 装饰操作
- 平平无奇
综合之前所述的平平无奇撤销栈操作, 没有析构步骤, 以及'装饰物'. 算法如下:
删除:
- 删除操作是不分对象的
- 将范围内特殊对象按顺序记录, 装饰OP +1
- 将范围内记录所有富属性, 装饰OP +1
- 将范围内记录所有纯文本, 装饰OP +1
- 范围删除
即:
- 内联对象撤销: 内联对象的的升阶
- 内联对象重做: 无视
- 富属性撤销: 富属性的修改
- 富属性重做: 无视
- 纯文本撤销: 纯文本的插入
- 纯文本重做: 范围删除
装饰OP的范围需要给出一个大概范围, 这里可以看出最大是+3. 之所以需要判断范围, 是因为有限撤销栈的实现:
- 完整记录OP后(调用
end_op()
)检查数量 - 如果OP数量超过范围, 获取撤销栈(队列)末尾OP
- 遍历到第二个OP的开头(用装饰OP判断)
- 如果 当
前撤销栈长度
减去第一个OP的长度
, 如果大于等于范围 - 将第一个完整的OP释放掉, 第二个OP作为新的第一个OP
插入对象
由于插入只能插入一种, 所以后面将会很简单. 插入内联对象则是:
- 装饰OP +1
- 撤销: 范围删除
- 重做: 内联对象的插入
插入文本
由于只能插入纯文本(富属性单独设置), 插入是比较简单的.
- 装饰OP +1
- 撤销: 范围删除
- 重做: 文本的插入
富属性修改
同样, 修改只能将范围内修改为同一个属性, 和删除的差不多.
- 装饰OP +1
- 撤销: 富属性的回退
- 重做: 富属性的修改
替换
之前提到了, 直接组合就行.
附加变化的实现
一般地, 如果连续按退格键, 撤销后会回到第一次退格前的位置. 所以针对输入单个字符和退格删除的实现, 我们需要检查一下是否可以与目前的OP进行合并.
具体实现中, 主动检查有点繁琐, 采用被动检查的方式. 好处就是降低耦合, 坏处就是效率会稍微低一点. 不过由于这些是GUI输入, 不会太频繁, 就如所谓了.
插入符号的变化
上面没有讨论插入符的更新, 这个只需要保存就行. 于是出现了一些专门为撤销栈实现的方法:
图片 富文本 粘贴_用C++实现富文本控件(中): 撤销相关推荐
- 安卓imageView加载MYSQL图片_Android调用相机拍摄照片并显示到 ImageView控件中
在前面的一篇文章中曾介绍过简单的开启相机照相功能,详见 Android简单调用相机Camera功能,实现打开照相功能 ,这一次就会将前面拍摄的照片显示到ImageView中,形成一个完整的效果 看实例 ...
- python 选择文件对话框插件_[ PyQt入门教程 ] PyQt5基本控件使用:消息弹出、用户输入、文件/目录选择对话框...
本文主要介绍PyQt界面实现中常用的消息弹出对话框.提供用户输入的输入框.打开文件获取文件/目录路径的文件对话框.学习这三种控件前,先想一下它们使用的主要场景: 1.消息弹出对话框.程序遇到问题需要退 ...
- WPF 把图片分割成两份自动翻页 WpfFlipPageControl:CtrlBook 书控件
原文:WPF 把图片分割成两份自动翻页 WpfFlipPageControl:CtrlBook 书控件 版权声明:本文为博主原创文章,需要转载尽管转载. https://blog.csdn.net/z ...
- 格式化显示在Label控件中的金额格式文本 (2)
通过Label控件的Text属性可以设置控件内显示的文本,而通过对象的ToString方法可以格式化字符串显示的格式.本实例设定在Label控件中金额的显示格式,运行结果如图1所示. <?XML ...
- VB.net实现从ListView控件中异地获取文本内容源代码
对于一些VB6工程代码来说,需要完成从VB到VB.net的转换,在转换过程中需要注意很多内容,利用VB.net的直接转换功能很少能完全成功,需要我们付出很大的努力. 下面的VB.net代码实现从其他应 ...
- MFC 对话框Picture Control(图片控件)中静态和动态显示Bmp图片
最近有同学问我如何实现MFC基于对话框在图片控件中加载图片?其实使用MFC显示图片的方法各种各样,但是还是有些同学不知道怎样显示.以前在<数字图像处理>课程中完成的软件都是基于单文档的程序 ...
- easyui数据表格显示复选框_【Excel技巧】使用控件一键切换实现单位元和万元随意显示...
工欲善其事,必先利其器.职场上亦是如此.Excel报表想要做得完美,首先肯定Excel要精通. 做一份Excel报表,如果涉及到金额,当金额比较大,单位到底是用元还是万元,经常是大家纠结的一个问题.我 ...
- Qt 实现QT控件中的QLabel显示图片并自适应显示
一.需求 实现QT控件中的QLabel显示图片,并自适应显示. 二.代码 QImage Image; Image.load(":/image/image/logo.jpg"); Q ...
- 【MFC】动态加载Picture Control控件中的图片
[MFC]动态加载Picture Control控件中的图片 前言 方法1:CBrush 方法2:SetBitmap 参考链接 方法3:重写MyPictureControl 前言 在MFC窗体中,我们 ...
最新文章
- composer更新_深入学习Composer原理(四)
- Java教程:SpringBoot常用配置
- 设计模式的征途—2.简单工厂(Simple Factory)模式
- java开发展望怎么写_Java开发趋势:2019年展望
- Windows下Subversion配置管理员指南
- JSON Schema
- Apple分区总体布局结构
- 洛谷——P1116 车厢重组
- c语言程序设计身高体重测评系统,C语言程序设计验.doc
- 为串联机械臂写一个ROS控制器
- ATX电源的工作原理
- SVCHOST启动服务实战
- C++STL算法equal(15)
- 微信小程序开屏动画组件封装以及使用示例
- Excel2019关闭时无响应
- 2009世界500强排名(2009年07月08日)
- 递归门控卷积HorNet(gn_conv)阅读笔记
- 河师大计算机学院宿舍情况哪,河师大环境学院“惊现”7个学霸宿舍
- 聊一聊IT培训机构的那些事!
- 总结——STL 常用数据结构及用法
热门文章
- MEF董事、中国电信云计算中心主任赵慧玲:MEF第三类网络
- canvas绘制字体-属性设置2
- JavaGUI版本销售管理系统
- Servlet Request
- 读《大学之路》有感②
- 异步 HttpContext.Current实现取值的方法(解决异步Application,Session,Cache...等失效的问题)...
- 教你10分钟搭建酷炫的个人博客
- k邻近算法(KNN)实例
- 【推荐】2016年不得不读的九本好书
- 九度OJ1005题 一直WA??