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
40 changes: 40 additions & 0 deletions TeXmacs/progs/generic/text-toolbar.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; MODULE : text-toolbar.scm
;; DESCRIPTION : text selection toolbar icons
;; COPYRIGHT : (C) 2026 Jie Chen
;; Yifan Lu
;;
;; This software falls under the GNU general public license version 3 or later.
;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
;; in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(texmacs-module (generic text-toolbar)
(:use (generic format-edit)
(generic format-menu)
(generic generic-edit)))

(menu-bind text-toolbar-icons
(=> (balloon (icon "tm_section.xpm") "chapter::menu")
(link chapter-menu))
(=> (balloon (icon "tm_theorem.xpm") "enunciation")
(link enunciation-menu))
((balloon (icon "tm_bold.xpm") "Write bold text")
(toggle-bold))
((balloon (icon "tm_italic.xpm") "Write italic text")
(toggle-italic))
((balloon (icon "tm_underline.xpm") "Write underline")
(toggle-underlined))
((balloon (icon "tm_marked.xpm") "Marked text")
(mark-text))
(=> (balloon (icon "tm_color.xpm") "Select a foreground color")
(link color-menu))
((balloon (icon "tm_cell_left.xpm") "left aligned")
(make 'padded-left-aligned))
((balloon (icon "tm_cell_center.xpm") "center")
(make 'padded-center))
((balloon (icon "tm_cell_right.xpm") "right aligned")
(make 'padded-right-aligned)))
180 changes: 180 additions & 0 deletions TeXmacs/tests/201_63.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; MODULE : 201_63.scm
;; DESCRIPTION : Unit tests for text toolbar functionality
;; COPYRIGHT : (C) 2026 Yuki Lu
;;
;; This software falls under the GNU general public license version 3 or later.
;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
;; in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(import (liii check))

(check-set-mode! 'report-failed)

(tm-define (test_201_63)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helper functions for testing
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; 模拟缓存时间检查
(define (cache-still-valid? last-check current-time)
(< (- current-time last-check) 100))

;; 模拟缓存失效判断
(define (should-invalidate-cache? last-check current-time)
(>= (- current-time last-check) 100))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Cache mechanism tests
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; 缓存有效期内应该返回true
(check (cache-still-valid? 1000 1099) => #t)

;; 缓存过期边界(正好100ms)应该返回false
(check (cache-still-valid? 1000 1100) => #f)

;; 缓存过期后应该返回false
(check (cache-still-valid? 1000 1101) => #f)

;; 缓存失效判断:有效期内
(check (should-invalidate-cache? 1000 1099) => #f)

;; 缓存失效判断:正好过期
(check (should-invalidate-cache? 1000 1100) => #t)

;; 缓存失效判断:已过期
(check (should-invalidate-cache? 1000 1200) => #t)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Rectangle validity tests
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (rectangle-valid? x1 y1 x2 y2)
(and (< x1 x2) (< y1 y2)))

;; 有效矩形检测
(check (rectangle-valid? 100 200 300 400) => #t)

;; 零宽度矩形应该无效
(check (rectangle-valid? 100 200 100 400) => #f)

;; 零高度矩形应该无效
(check (rectangle-valid? 100 200 300 200) => #f)

;; 负宽度矩形应该无效
(check (rectangle-valid? 300 200 100 400) => #f)

;; 负高度矩形应该无效
(check (rectangle-valid? 100 400 300 200) => #f)

;; 最小有效矩形(1x1)
(check (rectangle-valid? 0 0 1 1) => #t)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Coordinate conversion tests (simulating SI to pixel conversion)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define INV_UNIT (/ 1.0 256))

(define (si->pixel si-coord)
(inexact->exact (round (* si-coord INV_UNIT))))

;; 坐标转换:2560 -> 10
(check (si->pixel 2560) => 10)

;; 坐标转换:5120 -> 20
(check (si->pixel 5120) => 20)

;; 坐标转换:0 -> 0
(check (si->pixel 0) => 0)

;; 坐标转换:255 -> 1(四舍五入)
(check (si->pixel 255) => 1)

;; 坐标转换:256 -> 1(正好1单位)
(check (si->pixel 256) => 1)

;; 坐标转换:257 -> 1(略大于1)
(check (si->pixel 257) => 1)

;; 坐标转换:大数值1000000
(check (si->pixel 1000000) => 3906)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Mode checking simulation tests
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (should-show-toolbar? in-math? in-prog? in-code? in-verbatim?
has-selection? selection-empty?)
(and (not in-math?)
(not in-prog?)
(not in-code?)
(not in-verbatim?)
has-selection?
(not selection-empty?)))

;; 普通文本选区:应该显示
(check (should-show-toolbar? #f #f #f #f #t #f) => #t)

;; 数学模式中:不应该显示
(check (should-show-toolbar? #t #f #f #f #t #f) => #f)

;; 编程模式中:不应该显示
(check (should-show-toolbar? #f #t #f #f #t #f) => #f)

;; 代码模式中:不应该显示
(check (should-show-toolbar? #f #f #t #f #t #f) => #f)

;; 原文模式中:不应该显示
(check (should-show-toolbar? #f #f #f #t #t #f) => #f)

;; 无选区时:不应该显示
(check (should-show-toolbar? #f #f #f #f #f #f) => #f)

;; 空选区时:不应该显示
(check (should-show-toolbar? #f #f #f #f #t #t) => #f)

;; 数学模式 + 无选区:不应该显示
(check (should-show-toolbar? #t #f #f #f #f #f) => #f)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Viewport intersection tests
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (selection-in-view? sel-x1 sel-y1 sel-x2 sel-y2
view-x1 view-y1 view-x2 view-y2)
(not (or (< sel-x2 view-x1)
(> sel-x1 view-x2)
(< sel-y2 view-y1)
(> sel-y1 view-y2))))

;; 选区完全在视图内
(check (selection-in-view? 100 100 200 200 0 0 500 500) => #t)

;; 选区部分在视图内(左侧)
(check (selection-in-view? -50 100 50 200 0 0 500 500) => #t)

;; 选区部分在视图内(右侧)
(check (selection-in-view? 450 100 550 200 0 0 500 500) => #t)

;; 选区完全在视图左侧
(check (selection-in-view? -100 100 -50 200 0 0 500 500) => #f)

;; 选区完全在视图右侧
(check (selection-in-view? 550 100 600 200 0 0 500 500) => #f)

;; 选区完全在视图上方
(check (selection-in-view? 100 -100 200 -50 0 0 500 500) => #f)

;; 选区完全在视图下方
(check (selection-in-view? 100 550 200 600 0 0 500 500) => #f)

;; 选区正好接触视图边界(应该算在视图内)
(check (selection-in-view? 0 0 100 100 0 0 500 500) => #t)

(check-report))
22 changes: 17 additions & 5 deletions devel/201_63.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,20 @@
- 快速移动鼠标,观察工具栏是否稳定(100ms缓存)
- 选区变化后,观察是否及时更新

4. **按键功能测试**
- 测试按键行为是否和图标一致

### 自动测试
```bash
# 编译测试
xmake b text_toolbar_test

# 运行测试
xmake r text_toolbar_test
#### cpp
```bash
# 运行脚本
./bin/test_only text_toolbar_test
```
#### scheme
```bash
# 运行脚本
./bin/test_only 201_63
```

**测试覆盖**:
Expand All @@ -49,6 +56,11 @@ xmake r text_toolbar_test
- 有效选区矩形计算
- 坐标转换一致性

## 2026/3/13 通过 scheme 加入具体的按钮
### What
- 添加功能按键
- 优化部分代码
- 补充scheme测试,优化cpp测试

## 2026/3/6 性能优化与代码清理
### What
Expand Down
3 changes: 3 additions & 0 deletions src/Edit/Interface/edit_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,9 @@ edit_interface_rep::notify_change (int change) {
needs_update ();
if ((change & (THE_TREE | THE_SELECTION | THE_CURSOR)) != 0)
manual_focus_set (path (), (change & THE_TREE) != 0);
// 选区变化时,使文本工具栏缓存失效
// 输入字符时选区变化会触发 THE_SELECTION,进而隐藏工具栏
if ((change & THE_SELECTION) != 0) invalidate_text_toolbar_cache ();
}

bool
Expand Down
47 changes: 44 additions & 3 deletions src/Plugins/Qt/QTMTextToolbar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,51 @@ QTMTextToolbar::clearButtons () {

void
QTMTextToolbar::rebuildButtonsFromScheme () {
eval ("(use-modules (generic text-toolbar))");
object menu= eval ("'(horizontal (link text-toolbar-icons))");
object obj = call ("make-menu-widget", menu, 0);
if (!is_widget (obj)) return;

text_toolbar_widget = concrete (as_widget (obj));
QList<QAction*>* list= text_toolbar_widget->get_qactionlist ();
if (!list) return;

clearButtons ();
QLabel* placeholder= new QLabel ("文本工具栏", this);
placeholder->setAlignment (Qt::AlignCenter);
layout->addWidget (placeholder);

for (int i= 0; i < list->count (); ++i) {
QAction* action= list->at (i);
if (!action) continue;

if (action->isSeparator ()) {
QFrame* sep= new QFrame (this);
sep->setFrameShape (QFrame::VLine);
sep->setFrameShadow (QFrame::Plain);
sep->setFixedWidth (1);
sep->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Expanding);
layout->addWidget (sep);
continue;
}

if (action->text ().isNull () && action->icon ().isNull ()) {
layout->addSpacing (8);
continue;
}

if (QWidgetAction* wa= qobject_cast<QWidgetAction*> (action)) {
QWidget* w= wa->requestWidget (this);
if (w) layout->addWidget (w);
continue;
}

QToolButton* button= new QToolButton (this);
button->setObjectName ("text-toolbar-button");
button->setAutoRaise (true);
button->setDefaultAction (action);
button->setPopupMode (QToolButton::InstantPopup);
if (tm_style_sheet == "") button->setStyle (qtmstyle ());
layout->addWidget (button);
}

autoSize ();
}

Expand Down
Loading
Loading