From dffac9f6ad66b6bcec006be0195e865a40b14ae5 Mon Sep 17 00:00:00 2001 From: notfoundzzz Date: Wed, 28 Jan 2026 16:16:54 +0800 Subject: [PATCH 1/3] =?UTF-8?q?[209=5F13]=20=E6=B7=BB=E5=8A=A0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=82=AC=E6=B5=AE=E8=8F=9C=E5=8D=95=EF=BC=88=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/misc/themes/liii-night.css | 25 ++++ TeXmacs/misc/themes/liii.css | 25 ++++ TeXmacs/plugins/code/progs/prog/prog-drd.scm | 11 +- TeXmacs/progs/prog/prog-edit.scm | 9 +- TeXmacs/tests/tmu/209_13.tmu | 86 +++++++++++ devel/209_13.md | 25 ++++ src/Data/Convert/Verbatim/verbatim.cpp | 29 +++- src/Edit/Interface/edit_interface.hpp | 1 + src/Edit/Interface/edit_mouse.cpp | 70 ++++++++- src/Plugins/Qt/QTMCodePopup.cpp | 150 +++++++++++++++++++ src/Plugins/Qt/QTMCodePopup.hpp | 57 +++++++ src/Plugins/Qt/QTMWidget.cpp | 1 + src/Plugins/Qt/qt_simple_widget.cpp | 57 ++++++- src/Plugins/Qt/qt_simple_widget.hpp | 9 ++ 14 files changed, 538 insertions(+), 17 deletions(-) create mode 100644 TeXmacs/tests/tmu/209_13.tmu create mode 100644 devel/209_13.md create mode 100644 src/Plugins/Qt/QTMCodePopup.cpp create mode 100644 src/Plugins/Qt/QTMCodePopup.hpp diff --git a/TeXmacs/misc/themes/liii-night.css b/TeXmacs/misc/themes/liii-night.css index 5f41f33eec..dcefd8a357 100644 --- a/TeXmacs/misc/themes/liii-night.css +++ b/TeXmacs/misc/themes/liii-night.css @@ -734,6 +734,31 @@ QToolButton#image-align-button:checked:hover { background: rgb(133, 133, 133); } +/**************************************************************************** +* 代码悬浮菜单样式 +****************************************************************************/ + +QWidget#code_popup { + background: #2c2c2c; + border: none; + border-radius: 8px; +} + +QToolButton#code-popup-button { + background-color: transparent; + border: none; + padding: 4px 10px; + color: #ffffff; +} + +QToolButton#code-popup-button:hover { + background-color: rgba(128, 128, 128, 0.3); +} + +QToolButton#code-popup-button:pressed { + background-color: rgba(128, 128, 128, 0.5); +} + /**************************************************************************** * 分组框样式 ****************************************************************************/ diff --git a/TeXmacs/misc/themes/liii.css b/TeXmacs/misc/themes/liii.css index 823770835e..f888779984 100644 --- a/TeXmacs/misc/themes/liii.css +++ b/TeXmacs/misc/themes/liii.css @@ -718,6 +718,31 @@ QToolButton#image-align-button:checked:hover { background: rgb(180, 212, 255); } +/**************************************************************************** +* 代码悬浮菜单样式 +****************************************************************************/ + +QWidget#code_popup { + background: #ffffff; + border: none; + border-radius: 8px; +} + +QToolButton#code-popup-button { + background-color: transparent; + border: none; + padding: 4px 10px; + color: #1d1d1f; +} + +QToolButton#code-popup-button:hover { + background-color: rgba(128, 128, 128, 0.3); +} + +QToolButton#code-popup-button:pressed { + background-color: rgba(128, 128, 128, 0.5); +} + /**************************************************************************** * 分组框样式 ****************************************************************************/ diff --git a/TeXmacs/plugins/code/progs/prog/prog-drd.scm b/TeXmacs/plugins/code/progs/prog/prog-drd.scm index 58cb9f6071..bd3dad8ac6 100644 --- a/TeXmacs/plugins/code/progs/prog/prog-drd.scm +++ b/TeXmacs/plugins/code/progs/prog/prog-drd.scm @@ -19,12 +19,17 @@ (inline-code-tag) (block-code-tag)) (define-group inline-code-tag - verbatim scm cpp mmx r fortran octave - python scilab shell) + verbatim scm cpp mmx r fortran + python scilab shell + bash csv gnuplot goldfish java javascript json julia lua matlab moonbit + r7rs scala sql) (define-group block-code-tag verbatim-code scm-code cpp-code mmx-code r-code fortran-code - octave-code python-code scilab-code shell-code) + python-code scilab-code shell-code + bash-code csv-code gnuplot-code goldfish-code java-code javascript-code + json-code julia-code lua-code matlab-code moonbit-code r7rs-code + scala-code sql-code) ;; Listings (define-group listing-tag diff --git a/TeXmacs/progs/prog/prog-edit.scm b/TeXmacs/progs/prog/prog-edit.scm index adea76271b..dbc0d6d08a 100644 --- a/TeXmacs/progs/prog/prog-edit.scm +++ b/TeXmacs/progs/prog/prog-edit.scm @@ -13,7 +13,8 @@ (texmacs-module (prog prog-edit) (:use (utils library tree) - (utils library cursor))) + (utils library cursor) + (utils edit selections))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Basic routines for textual programs @@ -26,6 +27,12 @@ (dt (tree-ref ct :up))) (and (tree-atomic? ct) (tree-is? dt 'document)))) +(tm-define (code-popup-copy t) + (:synopsis "Copy code tree to clipboard") + (when (tree-select t) + (clipboard-copy-export "verbatim" "primary") + (selection-cancel))) + (tm-define (program-tree) (:synopsis "get the entire program tree") (let* ((ct (cursor-tree)) diff --git a/TeXmacs/tests/tmu/209_13.tmu b/TeXmacs/tests/tmu/209_13.tmu new file mode 100644 index 0000000000..3a39fd70e2 --- /dev/null +++ b/TeXmacs/tests/tmu/209_13.tmu @@ -0,0 +1,86 @@ +> + +> + +<\body> + C++代码块: + + <\cpp-code> + #include \stdio.h\ + + cout\\"Hello World!"; + + + C++行内代码: + + \"Hello World!";> + + C++代码清单: + + <\cpp-listing> + #include \stdio.h\ + + cout\\"Hello World!"; + + + \; + + Python代码块: + + <\python> + nums = [1, 2, 3, 4] + + for x in nums: + + \ \ \ \ print(x) + + + Python行内代码: + + <\python> + print("Hello, World!") + + \; + + + Scheme代码清单: + + <\scm-listing> + (display "Hello, World!") + + (newline) + + + \; + + Java代码块: + + <\java-code> + public class Main { + + \ \ \ \ public static void main(String[] args) { + + \ \ \ \ \ \ \ \ System.out.println("Hello, World!"); + + \ \ \ \ } + + } + + + \; + + Java行内代码: + + + + \; + + \; + + +<\initial> + <\collection> + + + + diff --git a/devel/209_13.md b/devel/209_13.md new file mode 100644 index 0000000000..ab46e819ef --- /dev/null +++ b/devel/209_13.md @@ -0,0 +1,25 @@ +# 209_13 添加代码悬浮菜单(复制) + +## 如何测试 +1. 在文档中插入块级代码(例如 Python/Cpp/Scheme/Shell)。 +2. 将鼠标悬浮到代码区域顶部,确认出现“复制”按钮。 +3. 点击“复制”,粘贴到外部编辑器应为纯文本代码。 +4. 在行内代码、代码清单环境中重复上述操作。 + +- 测试文档: TeXmacs/tests/tmu/209_13.tmu + +## 2026/01/28 代码悬浮菜单与多语言支持 +### What +- 新增代码悬浮菜单(Qt 端)与显示/隐藏/滚动同步逻辑。 +- 鼠标悬浮检测支持块级与行内代码。 +- 复制操作通过 Scheme 执行,导出为 verbatim。 +- 按钮文本与提示使用翻译表(随语言变化)。 +- C++ 侧补齐 Scheme 端支持的代码环境标签。 +- 主题中增加 code popup 样式(浅色/深色)。 + +### How +- C++:新增 `QTMCodePopup`,并在 `qt_simple_widget`/`QTMWidget` 接入显示与滚动同步。 +- C++:在 `edit_mouse.cpp` 中沿祖先路径查找代码节点,使用 `is_verbatim()` 判断。 +- Scheme:新增 `code-popup-copy`,用 `clipboard-copy-export "verbatim"` 实现纯文本复制。 +- 主题:添加 `#code_popup` 与 `#code-popup-button` 样式。 +- `is_verbatim()` 补齐 `python-code` 等标签,与 Scheme 侧定义对齐。 diff --git a/src/Data/Convert/Verbatim/verbatim.cpp b/src/Data/Convert/Verbatim/verbatim.cpp index 0aef24336a..f9c23e0abb 100644 --- a/src/Data/Convert/Verbatim/verbatim.cpp +++ b/src/Data/Convert/Verbatim/verbatim.cpp @@ -380,10 +380,29 @@ verbatim_document_to_tree (string s, bool wrap, string enc) { bool is_verbatim (tree t) { - return is_compound (t, "cpp-code") || is_compound (t, "mmx-code") || - is_compound (t, "scm-code") || is_compound (t, "shell-code") || - is_compound (t, "code") || is_compound (t, "verbatim") || - is_compound (t, "scilab-code") || is_compound (t, "scala-code") || - is_compound (t, "java-code") || is_compound (t, "latex_preview") || + return is_compound (t, "code") || is_compound (t, "verbatim") || + is_compound (t, "verbatim-code") || is_compound (t, "scm") || + is_compound (t, "cpp") || is_compound (t, "mmx") || + is_compound (t, "r") || is_compound (t, "fortran") || + is_compound (t, "python") || is_compound (t, "scilab") || + is_compound (t, "shell") || is_compound (t, "bash") || + is_compound (t, "csv") || is_compound (t, "gnuplot") || + is_compound (t, "goldfish") || is_compound (t, "java") || + is_compound (t, "javascript") || is_compound (t, "json") || + is_compound (t, "julia") || is_compound (t, "lua") || + is_compound (t, "matlab") || is_compound (t, "moonbit") || + is_compound (t, "r7rs") || is_compound (t, "scala") || + is_compound (t, "sql") || is_compound (t, "scm-code") || + is_compound (t, "cpp-code") || is_compound (t, "mmx-code") || + is_compound (t, "r-code") || is_compound (t, "fortran-code") || + is_compound (t, "python-code") || is_compound (t, "scilab-code") || + is_compound (t, "shell-code") || is_compound (t, "bash-code") || + is_compound (t, "csv-code") || is_compound (t, "gnuplot-code") || + is_compound (t, "goldfish-code") || is_compound (t, "java-code") || + is_compound (t, "javascript-code") || is_compound (t, "json-code") || + is_compound (t, "julia-code") || is_compound (t, "lua-code") || + is_compound (t, "matlab-code") || is_compound (t, "moonbit-code") || + is_compound (t, "r7rs-code") || is_compound (t, "scala-code") || + is_compound (t, "sql-code") || is_compound (t, "latex_preview") || is_compound (t, "picture-mixed"); } diff --git a/src/Edit/Interface/edit_interface.hpp b/src/Edit/Interface/edit_interface.hpp index 62b4d2aff4..0c6d8961eb 100644 --- a/src/Edit/Interface/edit_interface.hpp +++ b/src/Edit/Interface/edit_interface.hpp @@ -240,6 +240,7 @@ class edit_interface_rep : virtual public editor_rep { void update_mouse_loci (); void update_focus_loci (); bool should_show_image_popup (tree t); + bool should_show_code_popup (tree t); /* the footer */ tree compute_text_footer (tree st); diff --git a/src/Edit/Interface/edit_mouse.cpp b/src/Edit/Interface/edit_mouse.cpp index 7233cae7b3..fd64b50a10 100644 --- a/src/Edit/Interface/edit_mouse.cpp +++ b/src/Edit/Interface/edit_mouse.cpp @@ -11,6 +11,7 @@ #include "Modify/edit_table.hpp" #include "analyze.hpp" +#include "convert.hpp" #include "edit_interface.hpp" #include "link.hpp" #include "message.hpp" @@ -73,6 +74,26 @@ edit_interface_rep::should_show_image_popup (tree t) { return true; } +bool +edit_interface_rep::should_show_code_popup (tree t) { + if (is_nil (t)) return false; + path ip= obtain_ip (t); + if (is_nil (ip) || ip->item == DETACHED) return false; + return true; +} + +static bool +is_code_tree (tree t) { + if (is_compound (t, "listing") || is_compound (t, "shell-listing") || + is_compound (t, "scm-listing") || is_compound (t, "cpp-listing")) { + return true; + } + if (!is_verbatim (t)) return false; + if (is_compound (t, "latex_preview")) return false; + if (is_compound (t, "picture-mixed")) return false; + return true; +} + /****************************************************************************** * Routines for the mouse ******************************************************************************/ @@ -746,10 +767,13 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, } } bool hovering_image= false; + bool hovering_code = false; bool over_handles = false; string handle_cursor = ""; static path current_path = path (); static rectangle selr = rectangle (); + static rectangle code_selr = rectangle (); + static tree code_tree = tree (); if (type == "move") { if (!is_zero (last_image_brec)) { // already clicked on image // 检测鼠标是否在handles上 @@ -786,13 +810,34 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, path p= reverse (obtain_ip (current_tree)); selection sel= search_selection (p * start (current_tree), p * end (current_tree)); - selr= least_upper_bound (sel->rs); - if (last_x >= selr->x1 && last_y >= selr->y1 && last_x <= selr->x2 && - last_y <= selr->y2 * 0.8) { - hovering_image= true; + if (sel->valid) { + selr= least_upper_bound (sel->rs); + if (last_x >= selr->x1 && last_y >= selr->y1 && last_x <= selr->x2 && + last_y <= selr->y2 * 0.8) { + hovering_image= true; + } } } } + + path p= current_path; + while (true) { + tree t= subtree (et, p); + if (is_code_tree (t)) { + code_tree = t; + path ip = reverse (obtain_ip (t)); + selection sel= search_selection (ip * start (t), ip * end (t)); + if (!sel->valid) break; + code_selr= least_upper_bound (sel->rs); + if (last_x >= code_selr->x1 && last_y >= code_selr->y1 && + last_x <= code_selr->x2 && last_y <= code_selr->y2) { + hovering_code= true; + } + break; + } + if (p == path ()) break; + p= path_up (p); + } } if (over_handles) { if (handle_cursor != "") set_cursor_style (handle_cursor); @@ -804,8 +849,12 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, // draw table resizing handles } else set_cursor_style (hovering_table == 1 ? "size_ver" : "size_hor"); + hide_code_popup (); + } + else if (hovering_hlink) { + set_cursor_style ("pointing_hand"); + hide_code_popup (); } - else if (hovering_hlink) set_cursor_style ("pointing_hand"); else if (hovering_image) { set_cursor_style ("pointing_hand"); path path_of_image_parent= path_up (current_path); @@ -814,10 +863,21 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, show_image_popup (tree_of_image_parent, selr, magf, get_scroll_x (), get_scroll_y (), get_canvas_x (), get_canvas_y ()); } + hide_code_popup (); + } + else if (hovering_code) { + set_cursor_style ("pointing_hand"); + if (should_show_code_popup (code_tree)) { + show_code_popup (code_tree, code_selr, magf, get_scroll_x (), + get_scroll_y (), get_canvas_x (), get_canvas_y ()); + } + else hide_code_popup (); + hide_image_popup (); } else { set_cursor_style ("normal"); hide_image_popup (); + hide_code_popup (); } if (type == "move") mouse_message ("move", x, y); diff --git a/src/Plugins/Qt/QTMCodePopup.cpp b/src/Plugins/Qt/QTMCodePopup.cpp new file mode 100644 index 0000000000..b90e287e95 --- /dev/null +++ b/src/Plugins/Qt/QTMCodePopup.cpp @@ -0,0 +1,150 @@ +/****************************************************************************** + * MODULE : QTMCodePopup.cpp + * DESCRIPTION: + * COPYRIGHT : (C) 2026 + ******************************************************************************* + * 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 . + ******************************************************************************/ + +#include "QTMCodePopup.hpp" +#include "qt_renderer.hpp" +#include "qt_utilities.hpp" +#include "scheme.hpp" + +#include +#include +#include + +// 悬浮菜单创建函数 +QTMCodePopup::QTMCodePopup (QWidget* parent, qt_simple_widget_rep* owner) + : QWidget (parent), owner (owner), layout (nullptr), cached_scroll_x (0), + cached_scroll_y (0), cached_canvas_x (0), cached_canvas_y (0), + cached_width (0), cached_height (0), cached_magf (0.0), copyBtn (nullptr), + painted (false), painted_count (0) { + setObjectName ("code_popup"); + setWindowFlags (Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + setAttribute (Qt::WA_ShowWithoutActivating); + setMouseTracking (true); + setFocusPolicy (Qt::NoFocus); + + layout= new QHBoxLayout (this); + layout->setContentsMargins (6, 6, 6, 6); + layout->setSpacing (2); + setLayout (layout); + + effect= new QGraphicsDropShadowEffect (this); + effect->setBlurRadius (40); + effect->setOffset (0, 4); + effect->setColor (QColor (0, 0, 0, 120)); + this->setGraphicsEffect (effect); + + copyBtn= new QToolButton (); + copyBtn->setObjectName ("code-popup-button"); + copyBtn->setText (qt_translate ("copy")); + copyBtn->setCheckable (false); + copyBtn->setToolTip (qt_translate ("copy")); + layout->addWidget (copyBtn); + + eval ("(use-modules (prog prog-edit))"); + connect (copyBtn, &QToolButton::clicked, this, + [=] () { call ("code-popup-copy", current_tree); }); +} + +QTMCodePopup::~QTMCodePopup () {} + +void +QTMCodePopup::showCodePopup (qt_renderer_rep* ren, rectangle selr, double magf, + int scroll_x, int scroll_y, int canvas_x, + int canvas_y) { + if (painted) return; + cachePosition (selr, magf, scroll_x, scroll_y, canvas_x, canvas_y); + autoSize (); + int x, y; + getCachedPosition (ren, x, y); + move (x, y); + if (painted_count == 2) { + show (); + painted= true; + } + else { + painted_count++; + } +} + +void +QTMCodePopup::setCodeTree (tree t) { + current_tree= t; +} + +void +QTMCodePopup::scrollBy (int x, int y) { + cached_scroll_x-= (int) (x / cached_magf); + cached_scroll_y-= (int) (y / cached_magf); +} + +void +QTMCodePopup::updatePosition (qt_renderer_rep* ren) { + int pos_x, pos_y; + getCachedPosition (ren, pos_x, pos_y); + move (pos_x, pos_y); +} + +// 根据DPI缩放和缩放比例自动调整按钮大小和窗口尺寸 +void +QTMCodePopup::autoSize () { + QScreen* Screen = QGuiApplication::primaryScreen (); + const double Dpi = Screen ? Screen->logicalDotsPerInch () : 96.0; + const double Scale = Dpi / 96.0; + const int baseWidth = 92; + const int baseHeight= 36; + double totalScale= Scale * cached_magf * 3.0; + if (cached_magf <= 0.16) { + cached_width = baseWidth; + cached_height= baseHeight; + } + else { + cached_width = int (baseWidth * totalScale); + cached_height= int (baseHeight * totalScale); + } + setFixedSize (cached_width, cached_height); +} + +// 缓存菜单显示位置 +void +QTMCodePopup::cachePosition (rectangle selr, double magf, int scroll_x, + int scroll_y, int canvas_x, int canvas_y) { + cached_rect = selr; + cached_scroll_x= scroll_x; + cached_scroll_y= scroll_y; + cached_canvas_x= canvas_x; + cached_canvas_y= canvas_y; + cached_magf = magf; +} + +// 计算菜单显示位置 +void +QTMCodePopup::getCachedPosition (qt_renderer_rep* ren, int& x, int& y) { + rectangle selr = cached_rect; + double inv_unit = 1.0 / 256.0; + double cx_logic = (selr->x1 + selr->x2) * 0.5; + double top_logic= selr->y2; + + double cx_px= + ((cx_logic - cached_scroll_x) * cached_magf + cached_canvas_x) * inv_unit; + double top_px= -(top_logic - cached_scroll_y) * cached_magf * inv_unit; + + double blank_top= 0.0; + if (owner && owner->scrollarea () && owner->scrollarea ()->viewport () && + owner->scrollarea ()->surface ()) { + int vp_h = owner->scrollarea ()->viewport ()->height (); + int surf_h= owner->scrollarea ()->surface ()->height (); + if (vp_h > surf_h) blank_top= (vp_h - surf_h) * 0.5; + } + top_px+= blank_top; + + x= int (std::round (cx_px - cached_width * 0.5)); + y= int (std::round (top_px - cached_height)); + (void) ren; +} diff --git a/src/Plugins/Qt/QTMCodePopup.hpp b/src/Plugins/Qt/QTMCodePopup.hpp new file mode 100644 index 0000000000..7a46606268 --- /dev/null +++ b/src/Plugins/Qt/QTMCodePopup.hpp @@ -0,0 +1,57 @@ +/****************************************************************************** + * MODULE : QTMCodePopup.hpp + * DESCRIPTION: + * COPYRIGHT : (C) 2026 + ******************************************************************************* + * 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 . + ******************************************************************************/ + +#ifndef QT_CODE_POPUP_HPP +#define QT_CODE_POPUP_HPP + +#include "qt_simple_widget.hpp" +#include "rectangles.hpp" + +#include +#include +#include +#include + +class QTMCodePopup : public QWidget { +protected: + qt_simple_widget_rep* owner; + QHBoxLayout* layout; + QGraphicsDropShadowEffect* effect; + rectangle cached_rect; + int cached_scroll_x; + int cached_scroll_y; + int cached_canvas_x; + int cached_canvas_y; + int cached_width; + int cached_height; + double cached_magf; + tree current_tree; + QToolButton* copyBtn; + bool painted; + int painted_count; + +public: + QTMCodePopup (QWidget* parent, qt_simple_widget_rep* owner); + ~QTMCodePopup (); + + void showCodePopup (qt_renderer_rep* ren, rectangle selr, double magf, + int scroll_x, int scroll_y, int canvas_x, int canvas_y); + void updatePosition (qt_renderer_rep* ren); + void scrollBy (int x, int y); + void setCodeTree (tree t); + +protected: + void autoSize (); + void cachePosition (rectangle selr, double magf, int scroll_x, int scroll_y, + int canvas_x, int canvas_y); + void getCachedPosition (qt_renderer_rep* ren, int& x, int& y); +}; + +#endif // QT_CODE_POPUP_HPP diff --git a/src/Plugins/Qt/QTMWidget.cpp b/src/Plugins/Qt/QTMWidget.cpp index 78580a9568..c146426517 100644 --- a/src/Plugins/Qt/QTMWidget.cpp +++ b/src/Plugins/Qt/QTMWidget.cpp @@ -156,6 +156,7 @@ QTMWidget::scrollContentsBy (int dx, int dy) { tm_widget ()->scroll_completion_popup_by (dx, dy); tm_widget ()->scroll_math_completion_popup_by (dx, dy); tm_widget ()->scroll_image_popup_by (dx, dy); + tm_widget ()->scroll_code_popup_by (dx, dy); } void diff --git a/src/Plugins/Qt/qt_simple_widget.cpp b/src/Plugins/Qt/qt_simple_widget.cpp index 3774278a9e..c76ded5b6d 100644 --- a/src/Plugins/Qt/qt_simple_widget.cpp +++ b/src/Plugins/Qt/qt_simple_widget.cpp @@ -16,6 +16,7 @@ #include "qt_utilities.hpp" #include "qt_window_widget.hpp" +#include "QTMCodePopup.hpp" #include "QTMCompletionPopup.hpp" #include "QTMImagePopup.hpp" #include "QTMMathCompletionPopup.hpp" @@ -746,8 +747,10 @@ qt_simple_widget_rep::scroll_math_completion_popup_by (SI x, SI y) { } /****************************************************************************** - * Image popup support - ******************************************************************************/ + + * * Image popup support + + * ******************************************************************************/ void qt_simple_widget_rep::ensure_image_popup () { @@ -789,4 +792,52 @@ qt_simple_widget_rep::scroll_image_popup_by (SI x, SI y) { qt_renderer_rep* ren= the_qt_renderer (); imagePopUp->updatePosition (ren); } -} \ No newline at end of file +} + +/****************************************************************************** + + * * Code popup support + + * ******************************************************************************/ + +void +qt_simple_widget_rep::ensure_code_popup () { + if (!codePopUp && canvas ()) { + codePopUp= new QTMCodePopup (canvas (), this); + if (is_empty (tm_style_sheet)) { + codePopUp->setStyle (qtmstyle ()); + } + } +} + +void +qt_simple_widget_rep::show_code_popup (tree current_tree, rectangle selr, + double magf, int scroll_x, int scroll_y, + int canvas_x, int canvas_y) { + ensure_code_popup (); + codePopUp->setCodeTree (current_tree); + qt_renderer_rep* ren= the_qt_renderer (); + codePopUp->showCodePopup (ren, selr, magf, scroll_x, scroll_y, canvas_x, + canvas_y); +} + +void +qt_simple_widget_rep::hide_code_popup () { + if (codePopUp) { + codePopUp->hide (); + codePopUp->setParent (nullptr); + codePopUp->deleteLater (); + codePopUp= nullptr; + } +} + +void +qt_simple_widget_rep::scroll_code_popup_by (SI x, SI y) { + if (codePopUp) { + QPoint qp (x, y); + coord2 p= from_qpoint (qp); + codePopUp->scrollBy (p.x1, p.x2); + qt_renderer_rep* ren= the_qt_renderer (); + codePopUp->updatePosition (ren); + } +} diff --git a/src/Plugins/Qt/qt_simple_widget.hpp b/src/Plugins/Qt/qt_simple_widget.hpp index 99fb02d199..2ba7f81eb7 100644 --- a/src/Plugins/Qt/qt_simple_widget.hpp +++ b/src/Plugins/Qt/qt_simple_widget.hpp @@ -24,6 +24,7 @@ class QTMCompletionPopup; class QTMMathCompletionPopup; class QTMImagePopup; +class QTMCodePopup; /*! A widget containing a TeXmacs canvas. @@ -121,6 +122,13 @@ class qt_simple_widget_rep : public qt_widget_rep { void hide_image_popup (); void scroll_image_popup_by (SI x, SI y); + ////////////////////// Code popup support + void ensure_code_popup (); + void show_code_popup (tree current_tree, rectangle selr, double magf, + int scroll_x, int scroll_y, int canvas_x, int canvas_y); + void hide_code_popup (); + void scroll_code_popup_by (SI x, SI y); + ////////////////////// backing store management static void repaint_all (); // called by qt_gui_rep::update() @@ -131,6 +139,7 @@ class qt_simple_widget_rep : public qt_widget_rep { QPointer completionPopUp; QPointer mathCompletionPopUp; QPointer imagePopUp; + QPointer codePopUp; #ifdef USE_MUPDF_RENDERER double bs_zoomf; picture backing_store; From 38b9fcbe3ab2e19b828f836ba1c3fe133b542872 Mon Sep 17 00:00:00 2001 From: notfoundzzz Date: Thu, 29 Jan 2026 13:54:51 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=82=AC=E6=B5=AE=E8=8F=9C=E5=8D=95-=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/prog/prog-edit.scm | 109 ++++++++++++++++++++++++- TeXmacs/tests/tmu/209_13.tmu | 10 ++- devel/209_13.md | 73 +++++++++++++---- src/Data/Convert/Verbatim/verbatim.cpp | 36 ++++---- src/Edit/Interface/edit_mouse.cpp | 43 +++++++--- src/Plugins/Qt/QTMCodePopup.cpp | 73 +++++++++++++++-- src/Plugins/Qt/QTMCodePopup.hpp | 6 ++ 7 files changed, 291 insertions(+), 59 deletions(-) diff --git a/TeXmacs/progs/prog/prog-edit.scm b/TeXmacs/progs/prog/prog-edit.scm index dbc0d6d08a..bccd96ab5e 100644 --- a/TeXmacs/progs/prog/prog-edit.scm +++ b/TeXmacs/progs/prog/prog-edit.scm @@ -14,7 +14,8 @@ (texmacs-module (prog prog-edit) (:use (utils library tree) (utils library cursor) - (utils edit selections))) + (utils edit selections) + (generic document-style))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Basic routines for textual programs @@ -33,6 +34,112 @@ (clipboard-copy-export "verbatim" "primary") (selection-cancel))) +(define code-popup-inline-options + '(("verbatim" "verbatim") + ("scheme" "scm") + ("c++" "cpp") + ("r" "r") + ("python" "python") + ("shell" "shell") + ("bash" "bash") + ("csv" "csv") + ("gnuplot" "gnuplot") + ("goldfish" "goldfish-lang") + ("java" "java") + ("javascript" "javascript") + ("json" "json") + ("julia" "julia") + ("lua" "lua") + ("matlab" "matlab") + ("moonbit" "moonbit") + ("r7rs" "r7rs") + ("scala" "scala") + ("sql" "sql"))) + +(define code-popup-block-options + '(("verbatim" "verbatim-code") + ("scheme" "scm-code") + ("c++" "cpp-code") + ("r" "r-code") + ("python" "python-code") + ("shell" "shell-code") + ("bash" "bash-code") + ("csv" "csv-code") + ("gnuplot" "gnuplot-code") + ("goldfish" "goldfish-code") + ("java" "java-code") + ("javascript" "javascript-code") + ("json" "json-code") + ("julia" "julia-code") + ("lua" "lua-code") + ("matlab" "matlab-code") + ("moonbit" "moonbit-code") + ("r7rs" "r7rs-code") + ("scala" "scala-code") + ("sql" "sql-code"))) + +(define code-popup-listing-options + '(("verbatim" "listing") + ("c++" "cpp-listing") + ("scheme" "scm-listing") + ("shell" "shell-listing"))) + +(define (code-popup-tag-in? tag opts) + (cond ((null? opts) #f) + ((string=? tag (cadr (car opts))) #t) + (else (code-popup-tag-in? tag (cdr opts))))) + +(define (code-popup-tag-kind tag) + (cond ((string=? tag "code") 'block) + ((code-popup-tag-in? tag code-popup-listing-options) 'listing) + ((code-popup-tag-in? tag code-popup-block-options) 'block) + ((code-popup-tag-in? tag code-popup-inline-options) 'inline) + (else #f))) + +(tm-define (code-popup-language-current t) + (:synopsis "Return current code tag name") + (symbol->string (tree-label t))) + +(define code-popup-packages + '("bash" "csv" "gnuplot" "goldfish" "java" "javascript" "json" "julia" "lua" + "matlab" "moonbit" "octave" "python" "r" "r7rs" "scala" "sql")) + +(define (code-popup-base-tag tag) + (cond ((string-ends? tag "-code") (string-drop-right tag 5)) + ((string-ends? tag "-listing") (string-drop-right tag 8)) + ((string-ends? tag "-lang") (string-drop-right tag 5)) + (else tag))) + +(define (code-popup-ensure-package tag) + (let* ((base (code-popup-base-tag tag)) + (pack (and (in? base code-popup-packages) base))) + (when (and pack (not (has-style-package? pack))) + (add-style-package pack)))) + +(tm-define (code-popup-make tag) + (:synopsis "Make code tag with auto package") + (code-popup-ensure-package (symbol->string tag)) + (make tag)) + +(tm-define (code-popup-language-options t) + (:synopsis "Return menu options for code popup") + (let* ((tag (symbol->string (tree-label t))) + (kind (code-popup-tag-kind tag)) + (opts (cond ((eq? kind 'listing) code-popup-listing-options) + ((eq? kind 'block) code-popup-block-options) + (else code-popup-inline-options)))) + (map (lambda (it) (string-append (car it) "\t" (cadr it))) opts))) + +(tm-define (code-popup-set-language t target) + (:synopsis "Change code tag for current code tree") + (let* ((kind (code-popup-tag-kind (symbol->string (tree-label t)))) + (opts (cond ((eq? kind 'listing) code-popup-listing-options) + ((eq? kind 'block) code-popup-block-options) + (else code-popup-inline-options)))) + (when (code-popup-tag-in? target opts) + (code-popup-ensure-package target) + (tree-assign-node! t (string->symbol target))))) + (tm-define (program-tree) (:synopsis "get the entire program tree") (let* ((ct (cursor-tree)) diff --git a/TeXmacs/tests/tmu/209_13.tmu b/TeXmacs/tests/tmu/209_13.tmu index 3a39fd70e2..6ca61ab17a 100644 --- a/TeXmacs/tests/tmu/209_13.tmu +++ b/TeXmacs/tests/tmu/209_13.tmu @@ -8,7 +8,7 @@ <\cpp-code> #include \stdio.h\ - cout\\"Hello World!"; + cout\\"Hello World!" C++行内代码: @@ -23,6 +23,8 @@ cout\\"Hello World!"; + + \; Python代码块: @@ -35,7 +37,9 @@ \ \ \ \ print(x) - Python行内代码: + Python行内代码 + + \; <\python> print("Hello, World!") @@ -43,6 +47,8 @@ \; + \; + Scheme代码清单: <\scm-listing> diff --git a/devel/209_13.md b/devel/209_13.md index ab46e819ef..92d1f44e69 100644 --- a/devel/209_13.md +++ b/devel/209_13.md @@ -1,25 +1,66 @@ -# 209_13 添加代码悬浮菜单(复制) +# 209_13 添加代码悬浮菜单 -## 如何测试 -1. 在文档中插入块级代码(例如 Python/Cpp/Scheme/Shell)。 -2. 将鼠标悬浮到代码区域顶部,确认出现“复制”按钮。 -3. 点击“复制”,粘贴到外部编辑器应为纯文本代码。 -4. 在行内代码、代码清单环境中重复上述操作。 +## 如何测试 -- 测试文档: TeXmacs/tests/tmu/209_13.tmu +1. 在 Mogan 中插入: + - 块级代码(Python / C++ / Scheme / Shell 等) + - 行内代码 + - 代码清单 -## 2026/01/28 代码悬浮菜单与多语言支持 -### What -- 新增代码悬浮菜单(Qt 端)与显示/隐藏/滚动同步逻辑。 +2. 将鼠标悬浮到代码区域,右上角: + - 应显示「复制」与「语言切换」按钮。 + +3. 点击「复制」: + - 粘贴到外部编辑器,应为纯文本代码。 + +4. 使用语言菜单切换标签: + - 如 `cpp-code` → `python-code` + - 应自动添加所需宏包。 + +5. 移开鼠标: + - 菜单应延迟约 0.5s 消失。 + +### 测试文档 + +- `TeXmacs/tests/tmu/209_13.tmu` + +## 2026/01/29 代码悬浮菜单-语言切换 + +### What + +- 新增代码悬浮菜单(复制 + 语言切换),支持块级 / 行内 / 代码清单。 +- 菜单固定在代码块右上角,提升可点击性。 +- 悬浮离开后延迟约 0.5s 隐藏,避免移动过快无法点击。 +- 切换语言标签(如 `cpp-code` → `python-code`)自动补齐所需宏包。 + +### How + +- 鼠标悬浮检测与显示逻辑: + `src/Edit/Interface/edit_mouse.cpp` +- Qt 菜单组件: + `src/Plugins/Qt/QTMCodePopup.cpp` + `src/Plugins/Qt/QTMCodePopup.hpp` +- Scheme 复制与语言切换: + `TeXmacs/progs/prog/prog-edit.scm` + + +## 2026/01/28 代码悬浮菜单-复制 + +### What + +- 新增代码悬浮菜单(Qt 端)及显示 / 隐藏 / 滚动同步逻辑。 - 鼠标悬浮检测支持块级与行内代码。 -- 复制操作通过 Scheme 执行,导出为 verbatim。 -- 按钮文本与提示使用翻译表(随语言变化)。 +- 复制操作通过 Scheme 导出为 `verbatim`。 - C++ 侧补齐 Scheme 端支持的代码环境标签。 -- 主题中增加 code popup 样式(浅色/深色)。 +- 主题中增加 code popup 样式(浅色 / 深色)。 + +### How -### How -- C++:新增 `QTMCodePopup`,并在 `qt_simple_widget`/`QTMWidget` 接入显示与滚动同步。 +- C++:新增 `QTMCodePopup`,并在 `qt_simple_widget` / `QTMWidget` 接入显示与滚动同步。 - C++:在 `edit_mouse.cpp` 中沿祖先路径查找代码节点,使用 `is_verbatim()` 判断。 -- Scheme:新增 `code-popup-copy`,用 `clipboard-copy-export "verbatim"` 实现纯文本复制。 +- Scheme:新增 `code-popup-copy`,使用 + `clipboard-copy-export "verbatim"` 实现纯文本复制。 - 主题:添加 `#code_popup` 与 `#code-popup-button` 样式。 - `is_verbatim()` 补齐 `python-code` 等标签,与 Scheme 侧定义对齐。 + + diff --git a/src/Data/Convert/Verbatim/verbatim.cpp b/src/Data/Convert/Verbatim/verbatim.cpp index f9c23e0abb..9c539cb429 100644 --- a/src/Data/Convert/Verbatim/verbatim.cpp +++ b/src/Data/Convert/Verbatim/verbatim.cpp @@ -387,22 +387,22 @@ is_verbatim (tree t) { is_compound (t, "python") || is_compound (t, "scilab") || is_compound (t, "shell") || is_compound (t, "bash") || is_compound (t, "csv") || is_compound (t, "gnuplot") || - is_compound (t, "goldfish") || is_compound (t, "java") || - is_compound (t, "javascript") || is_compound (t, "json") || - is_compound (t, "julia") || is_compound (t, "lua") || - is_compound (t, "matlab") || is_compound (t, "moonbit") || - is_compound (t, "r7rs") || is_compound (t, "scala") || - is_compound (t, "sql") || is_compound (t, "scm-code") || - is_compound (t, "cpp-code") || is_compound (t, "mmx-code") || - is_compound (t, "r-code") || is_compound (t, "fortran-code") || - is_compound (t, "python-code") || is_compound (t, "scilab-code") || - is_compound (t, "shell-code") || is_compound (t, "bash-code") || - is_compound (t, "csv-code") || is_compound (t, "gnuplot-code") || - is_compound (t, "goldfish-code") || is_compound (t, "java-code") || - is_compound (t, "javascript-code") || is_compound (t, "json-code") || - is_compound (t, "julia-code") || is_compound (t, "lua-code") || - is_compound (t, "matlab-code") || is_compound (t, "moonbit-code") || - is_compound (t, "r7rs-code") || is_compound (t, "scala-code") || - is_compound (t, "sql-code") || is_compound (t, "latex_preview") || - is_compound (t, "picture-mixed"); + is_compound (t, "goldfish") || is_compound (t, "goldfish-lang") || + is_compound (t, "java") || is_compound (t, "javascript") || + is_compound (t, "json") || is_compound (t, "julia") || + is_compound (t, "lua") || is_compound (t, "matlab") || + is_compound (t, "moonbit") || is_compound (t, "r7rs") || + is_compound (t, "scala") || is_compound (t, "sql") || + is_compound (t, "scm-code") || is_compound (t, "cpp-code") || + is_compound (t, "mmx-code") || is_compound (t, "r-code") || + is_compound (t, "fortran-code") || is_compound (t, "python-code") || + is_compound (t, "scilab-code") || is_compound (t, "shell-code") || + is_compound (t, "bash-code") || is_compound (t, "csv-code") || + is_compound (t, "gnuplot-code") || is_compound (t, "goldfish-code") || + is_compound (t, "java-code") || is_compound (t, "javascript-code") || + is_compound (t, "json-code") || is_compound (t, "julia-code") || + is_compound (t, "lua-code") || is_compound (t, "matlab-code") || + is_compound (t, "moonbit-code") || is_compound (t, "r7rs-code") || + is_compound (t, "scala-code") || is_compound (t, "sql-code") || + is_compound (t, "latex_preview") || is_compound (t, "picture-mixed"); } diff --git a/src/Edit/Interface/edit_mouse.cpp b/src/Edit/Interface/edit_mouse.cpp index fd64b50a10..96e9ad15a0 100644 --- a/src/Edit/Interface/edit_mouse.cpp +++ b/src/Edit/Interface/edit_mouse.cpp @@ -766,14 +766,17 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, hovering_hlink= true; } } - bool hovering_image= false; - bool hovering_code = false; - bool over_handles = false; - string handle_cursor = ""; - static path current_path = path (); - static rectangle selr = rectangle (); - static rectangle code_selr = rectangle (); - static tree code_tree = tree (); + bool hovering_image = false; + bool hovering_code = false; + bool over_handles = false; + string handle_cursor = ""; + static path current_path = path (); + static rectangle selr = rectangle (); + static rectangle code_selr = rectangle (); + static tree code_tree = tree (); + static tree shown_code_tree = tree (); + static time_t last_image_hover_time= 0; + static time_t last_code_hover_time = 0; if (type == "move") { if (!is_zero (last_image_brec)) { // already clicked on image // 检测鼠标是否在handles上 @@ -814,7 +817,8 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, selr= least_upper_bound (sel->rs); if (last_x >= selr->x1 && last_y >= selr->y1 && last_x <= selr->x2 && last_y <= selr->y2 * 0.8) { - hovering_image= true; + hovering_image = true; + last_image_hover_time= texmacs_time (); } } } @@ -831,7 +835,8 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, code_selr= least_upper_bound (sel->rs); if (last_x >= code_selr->x1 && last_y >= code_selr->y1 && last_x <= code_selr->x2 && last_y <= code_selr->y2) { - hovering_code= true; + hovering_code = true; + last_code_hover_time= texmacs_time (); } break; } @@ -850,10 +855,12 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, } else set_cursor_style (hovering_table == 1 ? "size_ver" : "size_hor"); hide_code_popup (); + shown_code_tree= tree (); } else if (hovering_hlink) { set_cursor_style ("pointing_hand"); hide_code_popup (); + shown_code_tree= tree (); } else if (hovering_image) { set_cursor_style ("pointing_hand"); @@ -864,20 +871,30 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, get_scroll_y (), get_canvas_x (), get_canvas_y ()); } hide_code_popup (); + shown_code_tree= tree (); } else if (hovering_code) { set_cursor_style ("pointing_hand"); if (should_show_code_popup (code_tree)) { + if (!(code_tree == shown_code_tree)) hide_code_popup (); show_code_popup (code_tree, code_selr, magf, get_scroll_x (), get_scroll_y (), get_canvas_x (), get_canvas_y ()); + shown_code_tree= code_tree; + } + else { + hide_code_popup (); + shown_code_tree= tree (); } - else hide_code_popup (); hide_image_popup (); } else { set_cursor_style ("normal"); - hide_image_popup (); - hide_code_popup (); + time_t now= texmacs_time (); + if (now - last_image_hover_time >= 500) hide_image_popup (); + if (now - last_code_hover_time >= 500) { + hide_code_popup (); + shown_code_tree= tree (); + } } if (type == "move") mouse_message ("move", x, y); diff --git a/src/Plugins/Qt/QTMCodePopup.cpp b/src/Plugins/Qt/QTMCodePopup.cpp index b90e287e95..a85ce0e4a0 100644 --- a/src/Plugins/Qt/QTMCodePopup.cpp +++ b/src/Plugins/Qt/QTMCodePopup.cpp @@ -13,6 +13,7 @@ #include "qt_utilities.hpp" #include "scheme.hpp" +#include #include #include #include @@ -22,6 +23,7 @@ QTMCodePopup::QTMCodePopup (QWidget* parent, qt_simple_widget_rep* owner) : QWidget (parent), owner (owner), layout (nullptr), cached_scroll_x (0), cached_scroll_y (0), cached_canvas_x (0), cached_canvas_y (0), cached_width (0), cached_height (0), cached_magf (0.0), copyBtn (nullptr), + langBtn (nullptr), langMenu (nullptr), langGroup (nullptr), painted (false), painted_count (0) { setObjectName ("code_popup"); setWindowFlags (Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); @@ -47,6 +49,19 @@ QTMCodePopup::QTMCodePopup (QWidget* parent, qt_simple_widget_rep* owner) copyBtn->setToolTip (qt_translate ("copy")); layout->addWidget (copyBtn); + langBtn= new QToolButton (); + langBtn->setObjectName ("code-popup-button"); + langBtn->setText (qt_translate ("language")); + langBtn->setCheckable (false); + langBtn->setToolTip (qt_translate ("select a language")); + layout->addWidget (langBtn); + + langMenu = new QMenu (this); + langGroup= new QActionGroup (this); + langGroup->setExclusive (true); + langBtn->setMenu (langMenu); + langBtn->setPopupMode (QToolButton::InstantPopup); + eval ("(use-modules (prog prog-edit))"); connect (copyBtn, &QToolButton::clicked, this, [=] () { call ("code-popup-copy", current_tree); }); @@ -61,6 +76,7 @@ QTMCodePopup::showCodePopup (qt_renderer_rep* ren, rectangle selr, double magf, if (painted) return; cachePosition (selr, magf, scroll_x, scroll_y, canvas_x, canvas_y); autoSize (); + refreshLanguageMenu (); int x, y; getCachedPosition (ren, x, y); move (x, y); @@ -97,7 +113,7 @@ QTMCodePopup::autoSize () { QScreen* Screen = QGuiApplication::primaryScreen (); const double Dpi = Screen ? Screen->logicalDotsPerInch () : 96.0; const double Scale = Dpi / 96.0; - const int baseWidth = 92; + const int baseWidth = 180; const int baseHeight= 36; double totalScale= Scale * cached_magf * 3.0; if (cached_magf <= 0.16) { @@ -111,6 +127,44 @@ QTMCodePopup::autoSize () { setFixedSize (cached_width, cached_height); } +void +QTMCodePopup::refreshLanguageMenu () { + if (!langMenu || !langGroup) return; + langMenu->clear (); + if (langGroup) { + delete langGroup; + langGroup= new QActionGroup (this); + langGroup->setExclusive (true); + } + const list entries= + as_list_string (call ("code-popup-language-options", current_tree)); + const string current= + as_string (call ("code-popup-language-current", current_tree)); + for (list it= entries; !is_nil (it); it= it->next) { + string entry= it->item; + int sep = -1; + for (int i= 0; i < N (entry); ++i) { + if (entry[i] == '\t') { + sep= i; + break; + } + } + if (sep < 0) continue; + string label= entry (0, sep); + string tag = entry (sep + 1, N (entry)); + QAction* act = new QAction (qt_translate (label), langMenu); + act->setData (to_qstring (tag)); + act->setCheckable (true); + if (tag == current) act->setChecked (true); + langGroup->addAction (act); + langMenu->addAction (act); + connect (act, &QAction::triggered, this, [=] () { + string target= from_qstring (act->data ().toString ()); + call ("code-popup-set-language", current_tree, target); + }); + } +} + // 缓存菜单显示位置 void QTMCodePopup::cachePosition (rectangle selr, double magf, int scroll_x, @@ -126,13 +180,14 @@ QTMCodePopup::cachePosition (rectangle selr, double magf, int scroll_x, // 计算菜单显示位置 void QTMCodePopup::getCachedPosition (qt_renderer_rep* ren, int& x, int& y) { - rectangle selr = cached_rect; - double inv_unit = 1.0 / 256.0; - double cx_logic = (selr->x1 + selr->x2) * 0.5; - double top_logic= selr->y2; - - double cx_px= - ((cx_logic - cached_scroll_x) * cached_magf + cached_canvas_x) * inv_unit; + rectangle selr = cached_rect; + double inv_unit = 1.0 / 256.0; + double right_logic= selr->x2; + double top_logic = selr->y2; + + double right_px= + ((right_logic - cached_scroll_x) * cached_magf + cached_canvas_x) * + inv_unit; double top_px= -(top_logic - cached_scroll_y) * cached_magf * inv_unit; double blank_top= 0.0; @@ -144,7 +199,7 @@ QTMCodePopup::getCachedPosition (qt_renderer_rep* ren, int& x, int& y) { } top_px+= blank_top; - x= int (std::round (cx_px - cached_width * 0.5)); + x= int (std::round (right_px - cached_width)); y= int (std::round (top_px - cached_height)); (void) ren; } diff --git a/src/Plugins/Qt/QTMCodePopup.hpp b/src/Plugins/Qt/QTMCodePopup.hpp index 7a46606268..d19bb0abeb 100644 --- a/src/Plugins/Qt/QTMCodePopup.hpp +++ b/src/Plugins/Qt/QTMCodePopup.hpp @@ -14,8 +14,10 @@ #include "qt_simple_widget.hpp" #include "rectangles.hpp" +#include #include #include +#include #include #include @@ -34,6 +36,9 @@ class QTMCodePopup : public QWidget { double cached_magf; tree current_tree; QToolButton* copyBtn; + QToolButton* langBtn; + QMenu* langMenu; + QActionGroup* langGroup; bool painted; int painted_count; @@ -49,6 +54,7 @@ class QTMCodePopup : public QWidget { protected: void autoSize (); + void refreshLanguageMenu (); void cachePosition (rectangle selr, double magf, int scroll_x, int scroll_y, int canvas_x, int canvas_y); void getCachedPosition (qt_renderer_rep* ren, int& x, int& y); From 951a4282a14b6c14af0345355ad8441b05741917 Mon Sep 17 00:00:00 2001 From: notfoundzzz Date: Thu, 5 Feb 2026 09:36:58 +0800 Subject: [PATCH 3/3] =?UTF-8?q?[209=5F13]=20=E4=BB=A3=E7=A0=81=E6=82=AC?= =?UTF-8?q?=E6=B5=AE=E8=8F=9C=E5=8D=95=E9=AB=98=E5=BA=A6=E4=B8=8E=20packag?= =?UTF-8?q?e=20=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BD=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/prog/prog-menu.scm | 52 ++++++++++++++++--------------- devel/209_13.md | 25 ++++++++++++--- src/Edit/Interface/edit_mouse.cpp | 4 ++- src/Plugins/Qt/QTMCodePopup.cpp | 45 ++++++++++++++++---------- 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/TeXmacs/progs/prog/prog-menu.scm b/TeXmacs/progs/prog/prog-menu.scm index e27c62ab8b..312e84ed43 100644 --- a/TeXmacs/progs/prog/prog-menu.scm +++ b/TeXmacs/progs/prog/prog-menu.scm @@ -13,7 +13,8 @@ (texmacs-module (prog prog-menu) (:use (generic format-edit) - (generic insert-menu))) + (generic insert-menu) + (prog prog-edit))) (tm-menu (focus-code-icons t) (mini #t @@ -110,30 +111,31 @@ (when (not (selection-active?)) ("Tabbed" (make 'wide-tabbed))) --- + ;; 统一通过 code-popup-make 创建代码标签,确保对应语言包已自动加载。 (-> "Inline code" - ("Verbatim" (make 'verbatim)) - ("C++" (make 'cpp)) - ("Scheme" (make 'scm)) - ("Shell" (make 'shell)) - ("Goldfish" (make* 'goldfish-lang "goldfish")) - ("Scala" (make* 'scala "scala")) - ("Python" (make* 'python "python")) - ("R" (make* 'r "r")) - ("SQL" (make* 'sql "sql")) - ("Bash" (make* 'bash "bash"))) + ("Verbatim" (code-popup-make 'verbatim)) + ("C++" (code-popup-make 'cpp)) + ("Scheme" (code-popup-make 'scm)) + ("Shell" (code-popup-make 'shell)) + ("Goldfish" (code-popup-make 'goldfish-lang)) + ("Scala" (code-popup-make 'scala)) + ("Python" (code-popup-make 'python)) + ("R" (code-popup-make 'r)) + ("SQL" (code-popup-make 'sql)) + ("Bash" (code-popup-make 'bash))) (-> "Block of code" - ("Verbatim" (make 'verbatim-code)) - ("C++" (make 'cpp-code)) - ("Scheme" (make 'scm-code)) - ("Shell" (make 'shell-code)) - ("Goldfish" (make* 'goldfish-code "goldfish")) - ("Scala" (make* 'scala-code "scala")) - ("Python" (make* 'python-code "python")) - ("R" (make* 'r-code "r")) - ("SQL" (make* 'sql-code "sql")) - ("Bash" (make* 'bash-code "bash"))) + ("Verbatim" (code-popup-make 'verbatim-code)) + ("C++" (code-popup-make 'cpp-code)) + ("Scheme" (code-popup-make 'scm-code)) + ("Shell" (code-popup-make 'shell-code)) + ("Goldfish" (code-popup-make 'goldfish-code)) + ("Scala" (code-popup-make 'scala-code)) + ("Python" (code-popup-make 'python-code)) + ("R" (code-popup-make 'r-code)) + ("SQL" (code-popup-make 'sql-code)) + ("Bash" (code-popup-make 'bash-code))) (-> "Listing" - ("Verbatim" (make 'listing)) - ("C++" (make 'cpp-listing)) - ("Scheme" (make 'scm-listing)) - ("Shell" (make 'shell-listing)))) + ("Verbatim" (code-popup-make 'listing)) + ("C++" (code-popup-make 'cpp-listing)) + ("Scheme" (code-popup-make 'scm-listing)) + ("Shell" (code-popup-make 'shell-listing)))) diff --git a/devel/209_13.md b/devel/209_13.md index 92d1f44e69..68e49ab8ae 100644 --- a/devel/209_13.md +++ b/devel/209_13.md @@ -18,19 +18,38 @@ - 应自动添加所需宏包。 5. 移开鼠标: - - 菜单应延迟约 0.5s 消失。 + - 菜单应延迟约 0.25s 消失。 ### 测试文档 - `TeXmacs/tests/tmu/209_13.tmu` +## 2026/02/04 代码悬浮菜单高度与 package 自动加载修复 + +### What + +- 修复 KDE / Plasma 下代码悬浮菜单按钮高度不足,导致文本和图标被裁剪的问题。 +- 在 `code-menu` 的 Inline / Block / Listing 创建入口统一接入 `code-popup-make`。 +- 让 `code-popup-make` 在创建 code tag 前自动确保对应 package 已加载,避免“函数已定义但未生效”。 + +### How + +- Qt 侧修改 `src/Plugins/Qt/QTMCodePopup.cpp` 的 `QTMCodePopup::autoSize()`: + - 移除基于 DPI / `magf` 的外框放大逻辑; + - 仅使用按钮 `sizeHint` + 布局边距计算弹窗尺寸; + - 保留最小兜底尺寸,避免异常情况下尺寸过小。 +- Scheme 侧修改 `TeXmacs/progs/prog/prog-menu.scm`: + - 增加 `(prog prog-edit)` 依赖; + - 将代码语言项从 `make` / `make*` 改为 `code-popup-make`,统一走自动 package 确保逻辑。 + + ## 2026/01/29 代码悬浮菜单-语言切换 ### What - 新增代码悬浮菜单(复制 + 语言切换),支持块级 / 行内 / 代码清单。 - 菜单固定在代码块右上角,提升可点击性。 -- 悬浮离开后延迟约 0.5s 隐藏,避免移动过快无法点击。 +- 悬浮离开后延迟约 0.25s 隐藏,避免移动过快无法点击。 - 切换语言标签(如 `cpp-code` → `python-code`)自动补齐所需宏包。 ### How @@ -62,5 +81,3 @@ `clipboard-copy-export "verbatim"` 实现纯文本复制。 - 主题:添加 `#code_popup` 与 `#code-popup-button` 样式。 - `is_verbatim()` 补齐 `python-code` 等标签,与 Scheme 侧定义对齐。 - - diff --git a/src/Edit/Interface/edit_mouse.cpp b/src/Edit/Interface/edit_mouse.cpp index 3da6c49220..0caff6944a 100644 --- a/src/Edit/Interface/edit_mouse.cpp +++ b/src/Edit/Interface/edit_mouse.cpp @@ -780,6 +780,8 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, static tree shown_code_tree = tree (); static time_t last_image_hover_time= 0; static time_t last_code_hover_time = 0; + // 代码悬浮菜单隐藏延迟(毫秒),用于控制离开后的消失速度。 + static const int code_popup_hide_delay= 250; if (type == "move") { if (!is_zero (last_image_brec)) { // already clicked on image // 检测鼠标是否在handles上 @@ -894,7 +896,7 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, set_cursor_style ("normal"); time_t now= texmacs_time (); if (now - last_image_hover_time >= 500) hide_image_popup (); - if (now - last_code_hover_time >= 500) { + if (now - last_code_hover_time >= code_popup_hide_delay) { hide_code_popup (); shown_code_tree= tree (); } diff --git a/src/Plugins/Qt/QTMCodePopup.cpp b/src/Plugins/Qt/QTMCodePopup.cpp index a85ce0e4a0..7e7aa2180e 100644 --- a/src/Plugins/Qt/QTMCodePopup.cpp +++ b/src/Plugins/Qt/QTMCodePopup.cpp @@ -14,8 +14,8 @@ #include "scheme.hpp" #include -#include -#include +#include +#include #include // 悬浮菜单创建函数 @@ -107,22 +107,35 @@ QTMCodePopup::updatePosition (qt_renderer_rep* ren) { move (pos_x, pos_y); } -// 根据DPI缩放和缩放比例自动调整按钮大小和窗口尺寸 +// 按按钮内容与字体动态计算弹窗尺寸,避免跨平台比例失衡。 void QTMCodePopup::autoSize () { - QScreen* Screen = QGuiApplication::primaryScreen (); - const double Dpi = Screen ? Screen->logicalDotsPerInch () : 96.0; - const double Scale = Dpi / 96.0; - const int baseWidth = 180; - const int baseHeight= 36; - double totalScale= Scale * cached_magf * 3.0; - if (cached_magf <= 0.16) { - cached_width = baseWidth; - cached_height= baseHeight; - } - else { - cached_width = int (baseWidth * totalScale); - cached_height= int (baseHeight * totalScale); + const int fallbackWidth = 120; + const int fallbackHeight= 28; + cached_width = fallbackWidth; + cached_height = fallbackHeight; + if (layout && copyBtn && langBtn) { + int lm= 0, tm= 0, rm= 0, bm= 0; + layout->getContentsMargins (&lm, &tm, &rm, &bm); + const int spacing= std::max (0, layout->spacing ()); + const int btnW= + copyBtn->sizeHint ().width () + langBtn->sizeHint ().width () + spacing; + const int btnH= std::max (copyBtn->sizeHint ().height (), + langBtn->sizeHint ().height ()); + + // 动态兜底:用当前字体度量计算最小按钮尺寸,避免固定值在 Linux 上显得过大。 + QFontMetrics fmCopy (copyBtn->font ()); + QFontMetrics fmLang (langBtn->font ()); + const int copyTextW= fmCopy.boundingRect (copyBtn->text ()).width (); + const int langTextW= fmLang.boundingRect (langBtn->text ()).width (); + const int copyMinW = copyTextW + 20; + const int langMinW = langTextW + 20; + const int textMinH = std::max (fmCopy.height (), fmLang.height ()) + 10; + + const int dynamicMinW= copyMinW + langMinW + spacing + lm + rm; + const int dynamicMinH= textMinH + tm + bm; + cached_width = std::max (btnW + lm + rm, dynamicMinW); + cached_height = std::max (btnH + tm + bm, dynamicMinH); } setFixedSize (cached_width, cached_height); }