浮动富文本编辑器
当你在编辑器里需要给某句译文加粗、变色、加注音或做竖排内横排等行内富文本修饰时,使用浮动富文本编辑器。它是一块绑定在画布当前单选文字区域旁的独立小窗口,提供正文编辑、按文字运行的样式卡片、注音输入和样式预设。这里介绍该窗口的打开、编辑与保存;每个样式的参数细节与渲染效果见样式属性,普通文本字段、查找替换与区域列表见区域列表与文本编辑,富文本规则的匹配与样式预设文件见富文本样式与预设,快捷键与焦点冲突见快捷键。
可以做什么
- 浮动富文本编辑器是
EditorView创建的独立顶层工具窗口(Qt.Tool+ 无边框),不是画布的子覆盖层,因此不会被画布 viewport 裁剪,也可以被拖到其他面板或显示器。 - 它只在“恰好单选一个区域”且菜单开关“显示富文本编辑弹窗”开启时显示;多选、点空白画布、开始拖动区域或关闭该开关时隐藏并清理绑定。
- 它编辑当前区域的三个字段:
translation(最终译文)、translation_raw(替换前译文)和translation_rich(richtext.v1文档)。文档没有独立“保存”按钮:正文与样式变化经 180ms 防抖后自动写回模型,隐藏、关闭、切换区域或正文失焦时立即刷写。 - 顶栏“菜单”中的“编辑时自动应用富文本规则”控制输入时是否增量应用富文本规则(默认开启,只加样式不改字)。
- 不覆盖:文字区域级样式参数(字号、颜色、描边、间距、角度、对齐与方向)见样式属性;属性面板的普通文本编辑、查找替换与列表同步见区域列表与文本编辑;画布工具与选区见画布工具与选区;快捷键注册与焦点优先级见快捷键。
在编辑器中操作
打开、定位与关闭浮动编辑器
- 打开编辑器并加载图片,使用选择工具在画布上点选一个文字区域;浮动窗口默认自动出现在该文本框上方(上方空间不足时移到下方),水平居中于文本框,并被限制在当前屏幕可用区域内。
- 把鼠标移到窗口边缘(约 12px 的边框)按住左键,可以把整个窗口拖到其他位置;一旦手动拖动过,自动停靠位置会被记忆,之后的画布滚动、缩放或样式区高度变化不再移动它。
- 窗口显示时不抢占画布焦点(
WA_ShowWithoutActivating):选中区域时不会调用focus_text(),Delete、A/D切图等画布快捷键仍按画布语义生效;点击正文框后才进入文字编辑。 - 以下情况会隐藏窗口:取消选择或多选、点击空白画布、开始拖动当前区域、关闭“显示富文本编辑弹窗”开关。隐藏前会先提交待发内容(见编辑到保存的写回流程)。
- 拖动区域结束后,窗口会按文本框的新位置重新选择停靠侧并恢复显示;切换到其他页面再回到编辑器页时,也会按需恢复可见窗口。
- 关闭“显示富文本编辑弹窗”后,窗口先刷写再解除绑定并隐藏;重新打开该开关时,会按当前模型选区立即重新绑定。
编辑译文正文
- 正文区是纯文本编辑框(14pt,高 120px),显示当前区域的译文正文。加载时
[BR]、<br>、【BR】与真实换行统一归一为换行;保存时换行合并回[BR]写入translation。 - 直接输入、删除或粘贴修改正文;每次修改在 180ms 防抖后自动提交,隐藏、关闭、切换区域或正文失焦时立即提交。
- 正文框启用 Qt 撤销/重做;连续的富文本写回在控制器按
merge_key合并为一次可撤销步骤。 - 若“编辑时自动应用富文本规则”开启(默认开启),每次输入后把富文本规则增量应用到文档:规则只加样式、不改字符,命中区间已带手工富文本痕迹时整段跳过。
用工具栏与样式卡片应用样式
- 工具栏是一组 8 列的开关按钮,按钮文字就是样式存储键(
B、I、U…),悬停提示与无障碍名显示翻译后的样式名。 - 未选中文字时点击样式按钮作用于全文;选中文字时只作用于选区。再次点击同一样式按钮会清除该样式(
transform子键逐个清空,保留同组其他值)。 - 样式卡片区为选中范围内每一段连续同样式文字显示一张卡片:卡片头部显示该段文字,点击头部把该段选为编辑目标;头部右侧有“保存样式”与“清除此段文字的全部样式”。
- 每张卡片按该段实际携带的样式列出属性行(键标签 + 样式名 + 控件 + 删除按钮);数值、颜色、下拉等控件就地修改并即时提交。仅样式值变化时卡片就地刷新、不重建控件,避免打断正在输入或点击的控件。
- 注音(Ruby):对选中文字点工具栏
R或卡片内的注音行,在“注音文本”输入框中输入注音,按“应用”或回车提交;切换选区、正文失焦或隐藏窗口时也会先提交未应用的注音草稿。 - 竖排内横排(TCY):点
T把选中文字包成tcy节点,再点一次解除。
管理富文本样式预设
- 右侧“富文本预设”侧边栏列出已保存的样式预设;无预设时显示“暂无已保存样式”。侧边栏可以收起/展开,宽度在展开 248px 与收起 38px 之间切换。
- 点击预设名称应用:先清除选区全部样式,再套用预设的 style/ruby/tcy。
- 卡片头部的“保存样式”用当前段落样式弹出命名框(“输入样式名称:”,默认名“富文本预设 N”);名称不能为空,重名时确认“样式“{name}”已存在,是否覆盖?”。
- 侧边栏每行提供重命名与删除按钮;删除需要确认。写配置失败时弹出“保存样式失败”错误框并回滚内存中的预设。
修改如何保存
编辑到保存的写回流程
flowchart LR
A["画布单选区域"] --> B["RichTextFloatingEditor.set_region"]
B --> C["正文输入 / 工具栏 / 样式卡片 / 注音"]
C --> D["状态机 apply_qt_contents_change / replace_document"]
D -->|"自动规则开启"| E["apply_rich_text_rules 只加样式不改字"]
D -->|"自动规则关闭"| F["180ms 防抖后 mark_document_emitted"]
E --> F
F -->|"rich_text_changed(region_index, document, plain_text)"| G["EditorController.update_translation_rich"]
G --> H["UpdateRegionCommand 合并键 region:index:translation_rich"]
H --> I["EditorModel 更新 translation / translation_raw / translation_rich"]
I -->|"regions_changed"| J["视图 _on_regions_changed_for_rich_editor"]
J -->|"跳过编辑器自身写回"| K["refresh_region_if_changed 刷新文档"]
上图是代码中的数据流:正文或样式修改先进入编辑器状态机,可选应用自动富文本规则,再经 180ms 防抖提交;控制器把整份文档写入模型并通过 merge_key 合并连续编辑;模型变化通知视图,视图跳过编辑器自己发出的写回,只按模型数据刷新文档。
写回触发时机汇总:
| 触发时机 | 行为 |
|---|---|
| 正文修改后 180ms 防抖到期 | 提交待发文档(mark_document_emitted) |
隐藏/关闭窗口(hideEvent/closeEvent) | 先提交注音草稿与正文防抖,再解除绑定 |
正文失焦(focus_lost) | flush_pending_changes 立即提交 |
| 切换区域(新的单选) | 先刷写上一区域,再绑定新区域数据 |
| 关闭“显示富文本编辑弹窗” | clear_region:先刷写再解除绑定并隐藏 |
焦点与快捷键优先级
- 浮动编辑器用
WA_ShowWithoutActivating显示:选中区域时不抢画布焦点,画布快捷键(Delete、A/D切图、1/2/3切图像编辑页、Q/W/E切当前页工具)继续按画布语义生效;点击正文框后才进入文字编辑。 - 焦点进入浮动编辑器(另一个顶层
Qt.Tool窗口)后,EditorShortcutManager检测QApplication.focusWidget()的窗口不再是编辑器主窗口,所有 context-aware 编辑器快捷键直接返回,不会用主窗口残留焦点误删画布区域。 - 正文框持焦点时,文本控件的撤销/重做、复制粘贴等由 Qt 文本控件处理;样式修改通过文档提交与控制器命令合并。
限制与注意事项
- 浮动编辑器只在单选区域时工作;多选、无选区或弹窗开关关闭时不显示。区域列表与属性面板的选区变化会通过同一模型选区驱动浮动窗口的绑定与隐藏。
- “编辑时自动应用富文本规则”只影响编辑器内的增量样式应用;规则文件本身、匹配与预览归富文本规则页。规则只加样式不改字,命中区间带手工富文本痕迹时整段跳过。
- 富文本写回与属性面板的
translation/translation_raw编辑共用同一组字段:富文本正文变化会覆盖translation与translation_raw,纯样式修改保留translation_raw;模型变化又通过regions_changed回刷编辑器文档(跳过自身写回),避免陈旧文档覆盖模型。 - 连续富文本编辑按
merge_key(region:{index}:translation_rich)合并成一次撤销步骤;正文框自身的 Qt 撤销/重做只作用于文本。 - 样式预设保存在应用配置
app.saved_rich_text_presets,不是区域数据的一部分;保存失败会回滚并弹出“保存样式失败”。 - 窗口定位是“屏幕感知”的:自动停靠只在当前屏幕可用区域内选择上方/下方;手动拖动后不再自动移动,拖动画布上的文本框后按新位置重新停靠。
