Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion TeXmacs/progs/generic/generic-kbd.scm
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,9 @@
;; ("emacs:prefix 0" (delete-window))
;; ("emacs:prefix 1" (delete-other-windows))
;; ("emacs:prefix 2" (split-window-vertically))
;; ("emacs:prefix 3" (split-window-horizontally))
("emacs:prefix 3" (split-window-horizontally))
("emacs:prefix 0" (unsplit-window))
("emacs:prefix o" (other-pane))
;; ("emacs:prefix d" (dired))
;; ("emacs:prefix f" (set-fill-column))
;; ("emacs:prefix i" (interactive insert-buffer))
Expand Down
4 changes: 4 additions & 0 deletions TeXmacs/progs/texmacs/menus/view-menu.scm
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
("200%" (change-zoom-factor 2.0))
---
("Other" (interactive other-zoom-factor)))
(-> "Split"
("Split horizontally" (split-window-horizontally))
("Unsplit" (unsplit-window))
("Other pane" (other-pane)))

("Snap to pages" (toggle-snap-to-pages))
---
Expand Down
174 changes: 174 additions & 0 deletions devel/0153.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# [0153] 分屏功能:共享菜单栏/工具栏的多文档同时编辑

## 1 相关文档
- [dddd.md](dddd.md) - 任务文档模板
- [new_view.cpp](../src/Texmacs/Data/new_view.cpp) - 视图与窗口管理
- [qt_tm_widget.hpp](../src/Plugins/Qt/qt_tm_widget.hpp) - 主窗口 Qt 部件
- [qt_tm_widget.cpp](../src/Plugins/Qt/qt_tm_widget.cpp) - 主窗口实现
- [qt_ui_element.cpp](../src/Plugins/Qt/qt_ui_element.cpp) - UI 元素(含 QSplitter)
- [edit_interface.cpp](../src/Edit/Interface/edit_interface.cpp) - 编辑器 resume/suspend

## 2 任务相关的代码文件
- `src/Plugins/Qt/qt_tm_widget.hpp`
- `src/Plugins/Qt/qt_tm_widget.cpp`
- `src/Texmacs/Data/new_view.cpp`
- `src/Texmacs/Window/tm_frame.cpp`
- `src/Edit/Interface/edit_interface.cpp`
- `src/Plugins/Qt/qt_ui_element.cpp`

## 3 如何测试

### 3.1 确定性测试(单元测试)
```bash
# 构建主项目
xmake b stem
# 运行 Qt 相关测试
xmake b qt_test 2>/dev/null || true
```

### 3.2 非确定性测试(文档验证)
```bash
# 启动 Mogan,验证以下场景:
# 1. 菜单栏「视图」->「分屏」->「左右分屏」
# 2. 左 pane 打开 tmu 文档,右 pane 打开另一份 tmu 文档
# 3. 左 pane 打开 tmu,右 pane 打开 PDF(通过预览或拖拽)
# 4. 焦点切换时菜单栏和工具栏状态正确更新
# 5. ESC 或菜单可退出分屏模式
```

## 4 如何提交

提交前执行以下最少步骤:
```bash
xmake b stem
# 手动运行验证分屏功能正常
```

## 5 What

实现主窗口内的分屏(Split Screen)功能:

1. 一个主窗口内可同时显示两个 pane(左/右),通过 `QSplitter` 分隔。
2. 每个 pane 可独立承载编辑器视图(`main_widget`)、PDF 阅读器(`PDFReaderWidget`)或启动页。
3. 菜单栏和工具栏保持在窗口级别共享,不随 pane 数量翻倍。
4. 焦点切换到某个 pane 时,该 pane 对应的文档/视图成为「当前活动视图」,菜单和工具栏状态随之更新。
5. 支持以下典型布局:
- 左编辑器 + 右编辑器
- 左编辑器 + 右 PDF
- 左 PDF + 右编辑器
6. 提供 Scheme 命令和菜单项来开启/关闭分屏、调整分屏比例。

## 6 Why

用户需要同时参考或编辑两份文档,或一边编辑 tmu 一边查看 PDF 预览。当前每个窗口只能显示单一内容(编辑器/PDF/启动页三选一),必须开两个独立窗口,无法共享菜单栏和工具栏,体验割裂。分屏功能可提升多文档协作效率。

## 7 How

### 7.1 架构分析

当前 `qt_tm_widget_rep` 的 central widget 是一个 `QVBoxLayout`,按顺序堆叠:
```
centralWidget (QVBoxLayout)
├── notificationContainer
├── main_widget / pdfViewerWidget / startupContentWidget / chatContentWidget
```

`sync_startup_tab_mode()` / `sync_chat_tab_mode()` / `SLOT_FILE` 触发的逻辑通过 `hide_widget_from_layout` / `show_widget_in_layout` 在布局中切换显示哪个 widget。

菜单和工具栏(`menuToolBar`, `mainToolBar`, `modeToolBar`, `focusToolBar`, `userToolBar`)是 `qt_tm_widget_rep` 的成员,**已经是窗口级别**,天然共享。

全局 `the_view`(`src/Texmacs/Data/new_view.cpp:121`)跟踪唯一当前活动视图。`attach_view()` / `window_set_view()` 负责将视图绑定到窗口。`resume()` 触发菜单刷新,`suspend()` 清除焦点。

### 7.2 实现步骤

#### Step 1: 引入 `QSplitter` 和双 pane 结构

将 central widget 的单一内容区域改为可切换的两种模式:
- **单 pane 模式**:保持现有行为(向后兼容)。
- **分屏模式**:central widget 内使用 `QSplitter`,左右各一个 `QWidget* pane`。

每个 pane 内部仍使用 `QVBoxLayout`,可独立容纳:
- 编辑器 `main_widget`
- PDF 阅读器 `pdfViewerWidget`
- 启动页 `startupContentWidget`
- 聊天页 `chatContentWidget`

> 注意:不要为 pane 创建新的窗口类,而是将 pane 作为一个轻量的 `QWidget` 容器,由 `qt_tm_widget_rep` 统一管理其内容切换。

#### Step 2: 为每个 pane 独立管理内容状态

当前 `qt_tm_widget_rep` 使用全局标志(`startupTabMode`, `pdfTabMode`, `chatTabMode`)决定 central widget 显示什么。分屏模式下需要为**每个 pane**维护:
- 当前 pane 显示的内容类型(editor / pdf / startup / chat)
- 当前 pane 绑定的视图(`url`)或 PDF 路径
- 焦点状态

在 `qt_tm_widget_rep` 中新增:
```cpp
struct PaneState {
enum Type { Editor, Pdf, Startup, Chat } type;
url view; // 编辑器视图
QString pdfPath; // PDF 路径
};
PaneState leftPane;
PaneState rightPane;
QWidget* leftPaneWidget; // pane 容器
QWidget* rightPaneWidget; // pane 容器
bool splitMode;
QSplitter* splitter;
```

#### Step 3: 焦点切换与 `the_view` 更新

当前 `the_view` 是全局单例,菜单/工具栏更新通过 `resume()` -> `SERVER(menu_main(...))` 触发。

分屏时,用户点击左/右 pane 需要:
1. 接收焦点的 pane 将其绑定的视图设为 `the_view`(调用 `set_current_view()`)。
2. 调用该视图的 `resume()` 刷新菜单和工具栏。
3. 失去焦点的 pane 调用其旧视图的 `suspend()`。

实现方式:在 pane 容器上安装 `eventFilter`,监听 `QEvent::FocusIn` / `QEvent::MouseButtonPress`,激活时调用 `window_set_view()` 或等效逻辑。

#### Step 4: 改造 `sync_*_tab_mode()` 系列函数

将现有的 `sync_startup_tab_mode()` 等函数从操作全局 central widget 布局,改为操作**指定 pane** 的布局。

例如:
```cpp
void sync_pane_content(QWidget* pane, PaneState& state);
```

#### Step 5: 提供 Scheme 接口和菜单

在 Scheme 层暴露命令:
```scheme
(split-window-horizontally)
(unsplit-window)
(other-pane)
```

菜单栏「视图」下新增子菜单:
- 分屏 -> 左右分屏
- 分屏 -> 取消分屏
- 分屏 -> 切换焦点到另一 pane

#### Step 6: 打开文档时的路由逻辑

当用户通过菜单或快捷键在新窗口/新标签打开文档时,需要判断:
- 分屏模式下,是否在当前非活动 pane 打开?
- 提供「在另一 pane 打开」选项。

初期可先保持简单:分屏后,新打开的文档替换当前活动 pane 的内容;用户手动切换焦点到另一 pane 后再打开文档。

### 7.3 关键修改点

1. **`qt_tm_widget_rep` 新增分屏相关成员和方法**(`qt_tm_widget.hpp` / `.cpp`)
2. **`sync_startup_tab_mode()` 等改造为 pane 级别操作**(`qt_tm_widget.cpp`)
3. **焦点事件处理,更新 `the_view`**(`qt_tm_widget.cpp`)
4. **Scheme 绑定新增分屏命令**(新增或修改 `src/Scheme/` 相关文件)
5. **菜单定义文件新增「分屏」子菜单**(`TeXmacs/progs/` 下的菜单定义)

### 7.4 兼容性

- 默认关闭分屏模式,不影响现有单 pane 行为。
- 退出分屏时恢复当前活动 pane 的内容到单 pane 模式。
- `QSplitter` 的 handle 允许用户拖拽调整比例。
Loading
Loading