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
58 changes: 58 additions & 0 deletions devel/1297.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# [1297] 另存为对话框:stem/tmu 互相可选

## 1 相关文档
- [1279.md](1279.md) - 另存为引导到继任格式(.ts 默认 .stem,.tm 默认 .tmu),本任务在其过滤器基础上扩展
- [1131.md](1131.md) - stem 格式的引入
- CLAUDE.md - 开发规范

## 2 任务相关的代码文件
- src/Plugins/Qt/qt_chooser_widget.cpp - 另存为对话框过滤器列表(本次修改)
- src/Plugins/Qt/qt_chooser_widget.hpp - 另存为决策纯函数声明(本次修改)
- src/Plugins/ImGui/im_chooser_widget.cpp - ImGui 选择器 action_save_as 后缀列表(本次修改)
- tests/Plugins/Qt/qt_chooser_filters_test.cpp - 决策纯函数单元测试(本次修改)

## 3 需求

1279 之后另存为对话框按当前 buffer 格式收窄过滤器:

- stem buffer(或 .ts 文件)另存为:只有 `STEM files (*.stem)` 与 `TS files (*.ts)`;
- tmu/tm buffer 另存为:只有 `TMU files (*.tmu)` 与 `TM files (*.tm)`。

两个方向互不可达。本任务要求:

- stem 格式的 save_as 还需要有 tmu 格式;
- tmu 格式的 save_as 还需要有 stem 格式。

## 4 方案

- stem 方向:`chooser_stem_save_as_filters()`(原 `chooser_style_filters` 改名)
追加第三项 `TMU files (*.tmu)`。默认仍是首个过滤器 stem;用户切到 tmu
过滤器时,选中后 `defaultSuffix` 由所选过滤器解析为 `tmu`,
`chooser_normalize_suffix` 把最终文件名规范为 `.tmu`(沿用 1279 的既有路径)。
- tmu 方向:`set_type` 的 `action_save_as` 分支在 `TM files (*.tm)` 后追加
`STEM files (*.stem)`;`chooser_save_as_target` 覆盖 `.tmu`(返回 "tmu",
不改写建议名),与 stem 方向对称地经 `chooser_normalize_suffix` 把最终
文件名规范为所选过滤器的后缀——切到 STEM 过滤器即使不改建议名也落
`.stem`,切到 TM 过滤器落 `.tm`(顺带消除原有的 `foo.tmu.tm` 双后缀)。
默认后缀仍是 `tmu`。
- ImGui 选择器 `action_save_as` 的 extensions 追加 `stem`(桌面端为 stub、
WASM 另存为不走过滤器,仅保持与 Qt 一致)。

保存路径本身无需改动:scheme 侧 `has-faithful-format?` 已同时包含
`stem`/`tmu`,`save-buffer-as` 按目标文件后缀落盘对应格式。

## 5 如何测试

### 5.1 单元测试

```bash
xmake b qt_chooser_filters_test
TEXMACS_PATH=$(pwd)/TeXmacs xmake r qt_chooser_filters_test
```

### 5.2 手动测试

1. 打开 `.stem` 文件,File -> Save as:过滤器为 STEM/TS/TMU 三项,默认 STEM;
切到 TMU 过滤器保存,文件名落为 `.tmu`。
2. 打开 `.tmu` 文件,File -> Save as:过滤器为 TMU/TM/STEM 三项,默认 TMU;
切到 STEM 过滤器保存(不改建议文件名),落为 `.stem`。
4 changes: 3 additions & 1 deletion src/Plugins/ImGui/im_chooser_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ im_chooser_widget_rep::set_type (const string& _type) {

if (_type == "action_save_as") {
type= _type;
extensions << string ("tmu");
// 与 Qt 的过滤器列表保持 parity;桌面端对话框为 stub、WASM 另存为不读
// extensions,该列表暂无消费者
extensions << string ("tmu") << string ("stem");
def_ext= "tmu";
return true;
}
Expand Down
27 changes: 17 additions & 10 deletions src/Plugins/Qt/qt_chooser_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ qt_chooser_widget_rep::set_type (const string& _type) {
mainNameFilter+= " (*.tmu)";
nameFilters << mainNameFilter;
nameFilters << to_qstring (translate ("TM files") * " (*.tm)");
nameFilters << to_qstring (translate ("STEM files") * " (*.stem)");
defaultSuffix= "tmu";
}
else if (_type == "action_include") {
Expand Down Expand Up @@ -297,13 +298,15 @@ chooser_save_as_target (string& file) {
file= remove_suffix (file, ".tm") * ".tmu";
return "tmu";
}
if (ends (file, ".tmu")) return "tmu";
return "";
}

QStringList
chooser_style_filters () {
chooser_stem_save_as_filters () {
return QStringList () << to_qstring (translate ("STEM files") * " (*.stem)")
<< to_qstring (translate ("TS files") * " (*.ts)");
<< to_qstring (translate ("TS files") * " (*.ts)")
<< to_qstring (translate ("TMU files") * " (*.tmu)");
}

QString
Expand All @@ -322,16 +325,19 @@ chooser_normalize_suffix (const QString& path, const QString& suffix) {
*/
void
qt_chooser_widget_rep::perform_dialog () {
// 另存为引导到继任格式:.ts 默认 .stem(仅提供 stem/ts,任务 1131/1279),
// .tm 默认 .tmu(过滤器保持 tmu/tm);建议文件名同步改为默认后缀,让默认
// 目标在弹窗时即可见;普通保存仍写回原文件
// 另存为引导到继任格式:.ts 默认 .stem(过滤器 stem/ts/tmu),.tm 默认
// .tmu(过滤器 tmu/tm/stem)(任务 1131/1279/1297);建议文件名同步改为
// 默认后缀,让默认目标在弹窗时即可见;普通保存仍写回原文件
string save_as_target; // 非空时最终文件名规范为所选过滤器的后缀
if (type == "action_save_as") {
save_as_target= chooser_save_as_target (file);
if (save_as_target == "") {
url cur_buf= get_current_buffer_safe ();
if (!is_none (cur_buf) && suffix (cur_buf) == "stem") {
save_as_target= "stem";
if (!is_none (cur_buf)) {
string buf_suffix= suffix (cur_buf);
if (buf_suffix == "stem" || buf_suffix == "tmu") {
save_as_target= buf_suffix;
}
}
}
}
Expand Down Expand Up @@ -368,10 +374,11 @@ qt_chooser_widget_rep::perform_dialog () {

#if (QT_VERSION >= 0x040400)
if (type != "directory") {
// .ts/.stem 另存为仅提供 stem/ts 两项,默认 .stem;其余情形(.tm 另存为
// tmu/tm 默认 tmu;.tmu/草稿保持首项 TMU files (*.tmu))
// .ts/.stem 另存为提供 stem/ts/tmu 三项,默认 .stem(任务 1297 追加 tmu
// 互转);其余情形(.tm 另存为 tmu/tm/stem 默认 tmu;.tmu/草稿保持首项
// TMU files (*.tmu))
if (save_as_target == "stem") {
nameFilters = chooser_style_filters ();
nameFilters = chooser_stem_save_as_filters ();
defaultSuffix= "stem";
}
dialog->setNameFilters (nameFilters);
Expand Down
12 changes: 7 additions & 5 deletions src/Plugins/Qt/qt_chooser_widget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,21 @@ class qt_chooser_widget_rep : public qt_widget_rep {
};

/*! @brief 另存为建议名改写与目标格式判定:.ts → .stem,.tm → .tmu
* (继任格式引导,任务 1279)
* (继任格式引导,任务 1279);.stem/.tmu 原样保留仅返回目标(1297)
* @param file 建议文件名,命中 .ts/.tm 时被就地改写为默认后缀
* @return 目标默认后缀("stem"/"tmu"),空串表示非引导场景;非空时最终文件名
* 需按所选过滤器后缀规范(chooser_normalize_suffix),为 "stem" 时
* 过滤器收窄为 stem/ts 两项(chooser_style_filters)
* 需按所选过滤器后缀规范(chooser_normalize_suffix);为 "stem" 时
* 过滤器替换为 stem/ts/tmu 三项(chooser_stem_save_as_filters),
* 为 "tmu" 时保持 set_type 的 tmu/tm/stem 列表
* @note 纯函数,供单测;不触发 scheme 调用
*/
string chooser_save_as_target (string& file);

/*! @brief .ts/.stem 样式另存的成对过滤器:stem(默认)在前、ts(兼容)在后
/*! @brief .ts/.stem 另存的过滤器:stem(默认)在前、ts(兼容)居中、
* tmu(互转,任务 1297)在后
* @note 纯函数,供单测;translate 在未加载字典时返回原文,不依赖 scheme
*/
QStringList chooser_style_filters ();
QStringList chooser_stem_save_as_filters ();

/*! @brief 从名称过滤器的括号内容解析首个后缀(如 "TS files (*.ts)" → "ts"),
* 无括号或无 `*.xxx` 模式时返回空串
Expand Down
22 changes: 14 additions & 8 deletions tests/Plugins/Qt/qt_chooser_filters_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,21 @@ private slots:
QVERIFY (f == "foo.stem");
}

// .tm 另存为改写为 .tmu;.stm/.tmu 不得误伤
// .tm 另存为改写为 .tmu;.stm 不得误伤
void test_rewrite_tm () {
string f= "foo.tm";
QVERIFY (chooser_save_as_target (f) == "tmu");
QVERIFY (f == "foo.tmu");
string g= "foo.stm";
QVERIFY (chooser_save_as_target (g) == "");
QVERIFY (g == "foo.stm");
string h= "foo.tmu";
QVERIFY (chooser_save_as_target (h) == "");
}

// .tmu 本就是默认格式:保持不变但属于引导场景(与 stem 方向对称,1297)
void test_rewrite_tmu () {
string f= "foo.tmu";
QVERIFY (chooser_save_as_target (f) == "tmu");
QVERIFY (f == "foo.tmu");
}

// 其余后缀与无后缀(草稿)不触发改写
Expand All @@ -59,14 +64,15 @@ private slots:
QVERIFY (chooser_save_as_target (tp) == "");
}

// 样式另存的成对过滤器:stem(默认)在前,ts 在后,无其他格式
void test_style_filters () {
QStringList filters= chooser_style_filters ();
QCOMPARE (filters.size (), 2);
// stem 另存的过滤器:stem(默认)在前,ts 居中,tmu(1297 互转)在后
void test_stem_save_as_filters () {
QStringList filters= chooser_stem_save_as_filters ();
QCOMPARE (filters.size (), 3);
QVERIFY (filters[0].contains ("*.stem"));
QVERIFY (filters[0].contains ("STEM"));
QVERIFY (!filters[0].contains ("*.tmu"));
QVERIFY (filters[1].contains ("*.ts"));
QVERIFY (filters[1].contains ("TS files"));
QVERIFY (filters[2].contains ("*.tmu"));
}

// 从过滤器括号内容解析首个后缀;非法形状返回空串
Expand Down
Loading