diff --git a/TeXmacs/misc/pixmaps/liii-night/20x20/mode/left-align.svg b/TeXmacs/misc/pixmaps/liii-night/20x20/mode/left-align.svg new file mode 100644 index 0000000000..002d21b022 --- /dev/null +++ b/TeXmacs/misc/pixmaps/liii-night/20x20/mode/left-align.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/pixmaps/liii-night/20x20/mode/middle-align.svg b/TeXmacs/misc/pixmaps/liii-night/20x20/mode/middle-align.svg new file mode 100644 index 0000000000..7c55d0603b --- /dev/null +++ b/TeXmacs/misc/pixmaps/liii-night/20x20/mode/middle-align.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/pixmaps/liii-night/20x20/mode/right-align.svg b/TeXmacs/misc/pixmaps/liii-night/20x20/mode/right-align.svg new file mode 100644 index 0000000000..a06c661af7 --- /dev/null +++ b/TeXmacs/misc/pixmaps/liii-night/20x20/mode/right-align.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/pixmaps/liii/20x20/mode/left-align.svg b/TeXmacs/misc/pixmaps/liii/20x20/mode/left-align.svg new file mode 100644 index 0000000000..e63297a38a --- /dev/null +++ b/TeXmacs/misc/pixmaps/liii/20x20/mode/left-align.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/pixmaps/liii/20x20/mode/middle-align.svg b/TeXmacs/misc/pixmaps/liii/20x20/mode/middle-align.svg new file mode 100644 index 0000000000..1ffc2c05b6 --- /dev/null +++ b/TeXmacs/misc/pixmaps/liii/20x20/mode/middle-align.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/pixmaps/liii/20x20/mode/right-align.svg b/TeXmacs/misc/pixmaps/liii/20x20/mode/right-align.svg new file mode 100644 index 0000000000..4cf62a85d8 --- /dev/null +++ b/TeXmacs/misc/pixmaps/liii/20x20/mode/right-align.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/themes/liii-night.css b/TeXmacs/misc/themes/liii-night.css index 5f41f33eec..abb2220ff7 100644 --- a/TeXmacs/misc/themes/liii-night.css +++ b/TeXmacs/misc/themes/liii-night.css @@ -873,3 +873,28 @@ QWidget#centralWidget QWidget { background: #202020; color: #ffffff; } + +/*文本工具栏窗口样式*/ +QWidget#text_toolbar { + background: #333333; + border: none; + border-radius: 8px; +} + +/*文本工具栏按钮样式*/ +QToolButton#text-toolbar-button { + background-color: transparent; + border: none; +} + +/*文本工具栏按钮悬停样式*/ +QToolButton#text-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.1); + border: none; +} + +/*文本工具栏按钮按下样式*/ +QToolButton#text-toolbar-button:pressed { + background-color: rgba(255, 255, 255, 0.2); + border: none; +} diff --git a/TeXmacs/misc/themes/liii.css b/TeXmacs/misc/themes/liii.css index 823770835e..109400fb2d 100644 --- a/TeXmacs/misc/themes/liii.css +++ b/TeXmacs/misc/themes/liii.css @@ -850,3 +850,28 @@ QWidget#auxiliary_container { #guestNotificationCloseButton:pressed { background-color: rgba(0, 0, 0, 0.2); } + +/*文本工具栏窗口样式*/ +QWidget#text_toolbar { + background: #ffffff; + border: none; + border-radius: 8px; +} + +/*文本工具栏按钮样式*/ +QToolButton#text-toolbar-button { + background-color: transparent; + border: none; +} + +/*文本工具栏按钮悬停样式*/ +QToolButton#text-toolbar-button:hover { + background-color: rgba(128, 128, 128, 0.3); + border: none; +} + +/*文本工具栏按钮按下样式*/ +QToolButton#text-toolbar-button:pressed { + background-color: rgba(128, 128, 128, 0.5); + border: none; +} diff --git a/TeXmacs/progs/generic/text-toolbar.scm b/TeXmacs/progs/generic/text-toolbar.scm new file mode 100644 index 0000000000..38bc329bb0 --- /dev/null +++ b/TeXmacs/progs/generic/text-toolbar.scm @@ -0,0 +1,38 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : text-toolbar.scm +;; DESCRIPTION : text selection toolbar icons +;; 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 . +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(texmacs-module (generic text-toolbar) + (:use (generic format-edit) + (generic format-menu) + (generic generic-edit))) + +(menu-bind text-toolbar-icons + ((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.svg") "Marked text") + (mark-text)) + (=> (balloon (icon "tm_color.xpm") "Select a foreground color") + (link color-menu)) + (=> (balloon (icon "tm_section.xpm") "chapter::menu") + (link chapter-menu)) + (=> (balloon (icon "tm_theorem.xpm") "enunciation") + (link enunciation-menu)) + ((balloon (icon "left-align.xpm") "left aligned") + (make-line-with "par-mode" "left")) + ((balloon (icon "middle-align.xpm") "center") + (make-line-with "par-mode" "center")) + ((balloon (icon "right-align.xpm") "right aligned") + (make-line-with "par-mode" "right"))) diff --git a/devel/201_63.md b/devel/201_63.md new file mode 100644 index 0000000000..f4147d353a --- /dev/null +++ b/devel/201_63.md @@ -0,0 +1,8 @@ +# [201_63] 文字选中悬浮窗口初步实现 + +## 如何测试 +1. 输入一段文字通过键盘或鼠标选中,会出现悬浮窗口 +2. 依次点击悬浮窗口按钮,实现的功能和模式工具栏一致 +3. 窗口位置的优先级应该是选区顶部>选区底部>窗口中央 +4. 切换标签页不会导致原标签页的文本选中窗口失效 +5. 单独选中任意数学公式环境该悬浮窗口都不出现,选中公式文本混合内容悬浮窗口出现 diff --git a/src/Edit/Interface/edit_interface.cpp b/src/Edit/Interface/edit_interface.cpp index 019c84c6e2..afb6eb3d45 100644 --- a/src/Edit/Interface/edit_interface.cpp +++ b/src/Edit/Interface/edit_interface.cpp @@ -990,6 +990,8 @@ edit_interface_rep::apply_changes () { selection_rects= rs; invalidate (selection_rects); } + // 选区改变后更新文本工具栏 + update_text_toolbar (); } // cout << "Handling alternative selection\n"; diff --git a/src/Edit/Interface/edit_interface.hpp b/src/Edit/Interface/edit_interface.hpp index 2f99a1ee0d..f9e6524f19 100644 --- a/src/Edit/Interface/edit_interface.hpp +++ b/src/Edit/Interface/edit_interface.hpp @@ -237,6 +237,13 @@ 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_text_toolbar (); + rectangle get_text_selection_rect (); + void show_text_toolbar (rectangle selr, double magf, int scroll_x, + int scroll_y, int canvas_x, int canvas_y); + void hide_text_toolbar (); + bool is_point_in_text_toolbar (SI x, SI y); + void update_text_toolbar (); /* the footer */ tree compute_text_footer (tree st); diff --git a/src/Edit/Interface/edit_keyboard.cpp b/src/Edit/Interface/edit_keyboard.cpp index 7997903d43..7bad5e2b22 100644 --- a/src/Edit/Interface/edit_keyboard.cpp +++ b/src/Edit/Interface/edit_keyboard.cpp @@ -487,6 +487,8 @@ edit_interface_rep::handle_keypress (string key_u8, time_t t) { if (!is_nil (focus_ids) && got_focus) call ("link-follow-ids", object (focus_ids), object ("focus")); notify_change (THE_DECORATIONS); + // 键盘事件后更新文本工具栏显示状态 + update_text_toolbar (); end_editing (); // time_t t2= texmacs_time (); // if (t2 - t1 >= 10) cout << "handle_keypress took " << t2-t1 << "ms\n"; diff --git a/src/Edit/Interface/edit_mouse.cpp b/src/Edit/Interface/edit_mouse.cpp index 7f9b9feefa..25008be80e 100644 --- a/src/Edit/Interface/edit_mouse.cpp +++ b/src/Edit/Interface/edit_mouse.cpp @@ -21,6 +21,7 @@ #include "path.hpp" #include "qapplication.h" #include "qnamespace.h" +#include "qt_simple_widget.hpp" #include "scheme.hpp" #include "sys_utils.hpp" #include "tm_buffer.hpp" @@ -780,10 +781,14 @@ 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_text_toolbar (); } else { set_cursor_style ("normal"); hide_image_popup (); + + // 检查是否应该显示文本工具栏 + update_text_toolbar (); } if (type == "move") mouse_message ("move", x, y); @@ -879,10 +884,14 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t, if (type == "press-up") mouse_scroll (x, y, true); if (type == "press-down") mouse_scroll (x, y, false); - if ((type == "press-left") || (type == "release-left") || - (type == "end-drag-left") || (type == "press-middle") || - (type == "press-right")) + if ((type == "press-left") || (type == "press-middle") || + (type == "press-right")) { + // 当用户点击其他地方(不在文本工具栏内)时,隐藏文本工具栏 + if (!is_point_in_text_toolbar (x, y)) { + hide_text_toolbar (); + } notify_change (THE_DECORATIONS); + } if (type == "wheel" && N (data) == 2) eval ("(wheel-event " * as_string (data[0]) * " " * as_string (data[1]) * @@ -1014,3 +1023,113 @@ edit_interface_rep::handle_mouse (string kind, SI x, SI y, int m, time_t t, } handle_exceptions (); } + +/****************************************************************************** + * Text toolbar support + ******************************************************************************/ + +bool +edit_interface_rep::should_show_text_toolbar () { + if (as_bool (call ("in-math?")) || as_bool (call ("in-prog?")) || + as_bool (call ("in-code?")) || as_bool (call ("in-verbatim?"))) + return false; + // 检查是否有活动的文本选区 + if (!selection_active_any ()) return false; + + // 检查选区是否非空 + tree sel_tree= selection_get (); + if (is_atomic (sel_tree) && as_string (sel_tree) == "") return false; + + return true; +} + +rectangle +edit_interface_rep::get_text_selection_rect () { + rectangle sel_rect; + + if (selection_active_any () && !is_nil (selection_rects)) { + // 使用现有的选区矩形 + sel_rect= least_upper_bound (selection_rects); + } + else if (selection_active_any ()) { + // 如果没有选区矩形,但选区存在,计算一个默认矩形 + path p1, p2; + selection_get (p1, p2); + if (p1 != p2) { + selection sel= search_selection (p1, p2); + if (!is_nil (sel->rs)) { + sel_rect= least_upper_bound (sel->rs); + } + else { + // 如果选区矩形为空,使用光标位置创建一个最小矩形 + cursor cu= get_cursor (); + sel_rect = rectangle (cu->ox - 10 * pixel, cu->oy - 5 * pixel, + cu->ox + 10 * pixel, cu->oy + 5 * pixel); + } + } + } + + return sel_rect; +} + +void +edit_interface_rep::show_text_toolbar (rectangle selr, double magf, + int scroll_x, int scroll_y, int canvas_x, + int canvas_y) { + // 通过qt_simple_widget显示文本工具栏 + // this指针实际上是edit_interface_rep,它继承自editor_rep,而editor_rep继承自simple_widget_rep + // 在Qt环境下,simple_widget_rep实际上是qt_simple_widget_rep + qt_simple_widget_rep* qsw= static_cast (this); + qsw->show_text_toolbar (selr, magf, scroll_x, scroll_y, canvas_x, canvas_y); +} + +void +edit_interface_rep::hide_text_toolbar () { + // 通过qt_simple_widget隐藏文本工具栏 + qt_simple_widget_rep* qsw= static_cast (this); + qsw->hide_text_toolbar (); +} + +bool +edit_interface_rep::is_point_in_text_toolbar (SI x, SI y) { + // 通过qt_simple_widget检查点是否在文本工具栏内 + qt_simple_widget_rep* qsw= static_cast (this); + return qsw->is_point_in_text_toolbar (x, y); +} + +void +edit_interface_rep::update_text_toolbar () { + if (left_dragging) { + hide_text_toolbar (); + return; + } + // 检查是否应该显示文本工具栏 + if (should_show_text_toolbar ()) { + rectangle text_selr= get_text_selection_rect (); + // 检查矩形是否有效(非零面积) + // 注意:rectangle 不是 list 类型,不能使用 is_nil + // 我们检查矩形坐标是否有效 + if (text_selr->x1 < text_selr->x2 && text_selr->y1 < text_selr->y2) { + update_visible (); + SI sel_x1= min (text_selr->x1, text_selr->x2); + SI sel_x2= max (text_selr->x1, text_selr->x2); + SI sel_y1= min (text_selr->y1, text_selr->y2); + SI sel_y2= max (text_selr->y1, text_selr->y2); + bool sel_in_view= + !(sel_x2 < vx1 || sel_x1 > vx2 || sel_y2 < vy1 || sel_y1 > vy2); + if (!sel_in_view) { + hide_text_toolbar (); + return; + } + show_text_toolbar (text_selr, magf, get_scroll_x (), get_scroll_y (), + get_canvas_x (), get_canvas_y ()); + } + else { + // 即使矩形无效,也尝试显示工具栏(例如单个字符选区) + hide_text_toolbar (); + } + } + else { + hide_text_toolbar (); + } +} diff --git a/src/Plugins/Qt/QTMTextToolbar.cpp b/src/Plugins/Qt/QTMTextToolbar.cpp new file mode 100644 index 0000000000..18f2242c4b --- /dev/null +++ b/src/Plugins/Qt/QTMTextToolbar.cpp @@ -0,0 +1,321 @@ +/****************************************************************************** + * MODULE : QTMTextToolbar.cpp + * DESCRIPTION: Text selection toolbar popup widget implementation + * COPYRIGHT : (C) 2025 + ******************************************************************************* + * 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 "QTMTextToolbar.hpp" +#include "QTMStyle.hpp" +#include "bitmap_font.hpp" +#include "moebius/data/scheme.hpp" +#include "object_l5.hpp" +#include "qt_renderer.hpp" +#include "qt_utilities.hpp" +#include "scheme.hpp" +#include "server.hpp" +#include "tm_ostream.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 悬浮工具栏创建函数 +QTMTextToolbar::QTMTextToolbar (QWidget* parent, qt_simple_widget_rep* owner) + : QWidget (parent), owner (owner), layout (nullptr), + cached_selection_mid_x (0), cached_selection_mid_y (0), + cached_scroll_x (0), cached_scroll_y (0), cached_canvas_x (0), + cached_canvas_y (0), cached_magf (0.0), painted (false), + painted_count (0) { + Q_INIT_RESOURCE (images); + setObjectName ("text_toolbar"); + setWindowFlags (Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + setAttribute (Qt::WA_ShowWithoutActivating); + setMouseTracking (true); + setFocusPolicy (Qt::NoFocus); + layout= new QHBoxLayout (this); + layout->setContentsMargins (0, 0, 0, 0); + layout->setSizeConstraint (QLayout::SetMinimumSize); + layout->setSpacing (1); + setLayout (layout); + + // 添加阴影效果 + QGraphicsDropShadowEffect* effect= new QGraphicsDropShadowEffect (this); + effect->setBlurRadius (40); + effect->setOffset (0, 4); + effect->setColor (QColor (0, 0, 0, 120)); + this->setGraphicsEffect (effect); + + rebuildButtonsFromScheme (); +} + +QTMTextToolbar::~QTMTextToolbar () {} + +void +QTMTextToolbar::clearButtons () { + if (!layout) return; + QLayoutItem* item= nullptr; + while ((item= layout->takeAt (0)) != nullptr) { + if (QWidget* w= item->widget ()) { + w->setParent (nullptr); + delete w; + } + else if (QLayout* l= item->layout ()) { + delete l; + } + delete item; + } +} + +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* list= text_toolbar_widget->get_qactionlist (); + if (!list) return; + + clearButtons (); + + 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 (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 (); +} + +void +QTMTextToolbar::showTextToolbar (qt_renderer_rep* ren, rectangle selr, + double magf, int scroll_x, int scroll_y, + int canvas_x, int canvas_y) { + cachePosition (selr, magf, scroll_x, scroll_y, canvas_x, canvas_y); + if (!selectionInView ()) { + hide (); + return; + } + updatePosition (ren); + show (); + raise (); +} + +void +QTMTextToolbar::updatePosition (qt_renderer_rep* ren) { + if (!selectionInView ()) { + hide (); + return; + } + int x, y; + getCachedPosition (ren, x, y); + move (x, y); +} + +void +QTMTextToolbar::scrollBy (int x, int y) { + cached_scroll_x-= (int) (x / cached_magf); + cached_scroll_y-= (int) (y / cached_magf); +} + +void +QTMTextToolbar::cachePosition (rectangle selr, double magf, int scroll_x, + int scroll_y, int canvas_x, int canvas_y) { + cached_rect = selr; + cached_magf = magf; + cached_scroll_x= scroll_x; + cached_scroll_y= scroll_y; + cached_canvas_x= canvas_x; + cached_canvas_y= canvas_y; + + // 计算选区中心位置 + cached_selection_mid_x= (selr->x1 + selr->x2) * 0.5; + cached_selection_mid_y= + selr->y2; // 使用选区底部位置,使工具栏显示在选中文字正上方 +} + +void +QTMTextToolbar::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 sel_top_logic = (selr->y1 > selr->y2) ? selr->y1 : selr->y2; + double sel_bottom_logic= (selr->y1 > selr->y2) ? selr->y2 : selr->y1; + + // 使用公式计算QT坐标 + double cx_px= + ((cx_logic - cached_scroll_x) * cached_magf + cached_canvas_x) * inv_unit; + double top_px= -(sel_top_logic - cached_scroll_y) * cached_magf * inv_unit; + double bottom_px= + -(sel_bottom_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; + bottom_px+= blank_top; + + const int above_y= + int (std::round (top_px - cached_height - 10)); // 在选区顶部上方显示 + const int below_y= + int (std::round (bottom_px + 10)); // 如果上面空间不够,显示在选区下方 + + x= int (std::round (cx_px - cached_width * 0.5)); + y= above_y; + + // 确保工具栏在视口内 + if (owner && owner->scrollarea () && owner->scrollarea ()->viewport ()) { + int vp_w= owner->scrollarea ()->viewport ()->width (); + int vp_h= owner->scrollarea ()->viewport ()->height (); + + const bool above_fits= (above_y >= 0) && (above_y + cached_height <= vp_h); + const bool below_fits= (below_y >= 0) && (below_y + cached_height <= vp_h); + + if (above_fits) y= above_y; + else if (below_fits) y= below_y; + else { + x= std::max (0, (vp_w - cached_width) / 2); + y= std::max (0, (vp_h - cached_height) / 2); + } + + if (x < 0) x= 0; + if (x + cached_width > vp_w) x= vp_w - cached_width; + if (y < 0) y= 0; + if (y + cached_height > vp_h) y= vp_h - cached_height; + } + else { + if (y < 0) y= below_y; + } +} + +bool +QTMTextToolbar::selectionInView () const { + if (!owner || !owner->scrollarea () || !owner->scrollarea ()->viewport ()) + return true; + + rectangle selr = cached_rect; + double inv_unit= 1.0 / 256.0; + + double x1_px= + ((selr->x1 - cached_scroll_x) * cached_magf + cached_canvas_x) * inv_unit; + double x2_px= + ((selr->x2 - cached_scroll_x) * cached_magf + cached_canvas_x) * inv_unit; + double y1_px= -(selr->y1 - cached_scroll_y) * cached_magf * inv_unit; + double y2_px= -(selr->y2 - cached_scroll_y) * cached_magf * inv_unit; + + double blank_top= 0.0; + if (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; + } + y1_px+= blank_top; + y2_px+= blank_top; + + double left = std::min (x1_px, x2_px); + double right = std::max (x1_px, x2_px); + double top = std::min (y1_px, y2_px); + double bottom= std::max (y1_px, y2_px); + + int vp_w= owner->scrollarea ()->viewport ()->width (); + int vp_h= owner->scrollarea ()->viewport ()->height (); + + if (right < 0.0 || left > vp_w) return false; + if (bottom < 0.0 || top > vp_h) return false; + return true; +} + +void +QTMTextToolbar::autoSize () { + // 根据DPI和缩放因子自动调整大小 + QScreen* Screen= QGuiApplication::primaryScreen (); + const double Dpi = Screen ? Screen->logicalDotsPerInch () : 96.0; + const double Scale = Dpi / 96.0; + const double totalScale= + Scale * cached_magf * 12.0; // 原始3.0倍,扩大4倍后为12.0倍 + int btn_size; + +#if defined(Q_OS_MAC) + btn_size= int (50 * totalScale); +#else + btn_size= int (40 * totalScale); +#endif + + if (cached_magf <= 0.16) { + btn_size= 25; + } + + // 设置按钮大小 + QSize icon_size (btn_size, btn_size); + QSize fixed_size (btn_size + 32, + btn_size + 32); // 内边距也扩大4倍 (8 * 4.0 = 32) + const QList buttons= + findChildren (QString (), Qt::FindChildrenRecursively); + for (QToolButton* button : buttons) { + if (!button) continue; + if (button->objectName ().isEmpty ()) + button->setObjectName ("text-toolbar-button"); + button->setIconSize (icon_size); + button->setFixedSize (fixed_size); + } + + // 调整窗口大小 + adjustSize (); + cached_width = width (); + cached_height= height (); +} + +bool +QTMTextToolbar::eventFilter (QObject* obj, QEvent* event) { + // 处理事件过滤 + return QWidget::eventFilter (obj, event); +} diff --git a/src/Plugins/Qt/QTMTextToolbar.hpp b/src/Plugins/Qt/QTMTextToolbar.hpp new file mode 100644 index 0000000000..d60e76c1ba --- /dev/null +++ b/src/Plugins/Qt/QTMTextToolbar.hpp @@ -0,0 +1,62 @@ +/****************************************************************************** + * MODULE : QTMTextToolbar.hpp + * DESCRIPTION: Text selection toolbar popup widget + * COPYRIGHT : (C) 2025 + ******************************************************************************* + * 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_TEXT_TOOLBAR_HPP +#define QT_TEXT_TOOLBAR_HPP + +#include "qt_simple_widget.hpp" +#include "rectangles.hpp" + +#include +#include +#include +#include +#include + +class QTMTextToolbar : public QWidget { +protected: + qt_simple_widget_rep* owner; + QHBoxLayout* layout; + QGraphicsDropShadowEffect* effect; + int cached_selection_mid_x; + int cached_selection_mid_y; + rectangle cached_rect; + int cached_scroll_x; // 页面滚动位置x + int cached_scroll_y; // 页面滚动位置y + int cached_canvas_x; + int cached_canvas_y; + int cached_width; + int cached_height; + double cached_magf; // 缩放因子 + bool painted; + int painted_count; + qt_widget text_toolbar_widget; + +public: + QTMTextToolbar (QWidget* parent, qt_simple_widget_rep* owner); + ~QTMTextToolbar (); + + void showTextToolbar (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 autoSize (); + +protected: + 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); + bool selectionInView () const; + void rebuildButtonsFromScheme (); + void clearButtons (); + bool eventFilter (QObject* obj, QEvent* event) override; +}; + +#endif // QT_TEXT_TOOLBAR_HPP diff --git a/src/Plugins/Qt/QTMWidget.cpp b/src/Plugins/Qt/QTMWidget.cpp index 78580a9568..65bf212611 100644 --- a/src/Plugins/Qt/QTMWidget.cpp +++ b/src/Plugins/Qt/QTMWidget.cpp @@ -14,6 +14,7 @@ #include "array.hpp" #include "boot.hpp" #include "converter.hpp" +#include "edit_interface.hpp" #include "object_l5.hpp" #include "preferences.hpp" #include "qt_gui.hpp" @@ -156,6 +157,11 @@ 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_text_toolbar_by (dx, dy); + if (edit_interface_rep* ed= + dynamic_cast (tm_widget ())) { + ed->update_text_toolbar (); + } } void diff --git a/src/Plugins/Qt/qt_simple_widget.cpp b/src/Plugins/Qt/qt_simple_widget.cpp index 3774278a9e..1c90e6cb48 100644 --- a/src/Plugins/Qt/qt_simple_widget.cpp +++ b/src/Plugins/Qt/qt_simple_widget.cpp @@ -21,6 +21,7 @@ #include "QTMMathCompletionPopup.hpp" #include "QTMMenuHelper.hpp" #include "QTMStyle.hpp" +#include "QTMTextToolbar.hpp" #include "QTMWidget.hpp" #include #include @@ -46,6 +47,7 @@ qt_simple_widget_rep::~qt_simple_widget_rep () { if (backingPixmap != NULL) delete backingPixmap; #endif if (completionPopUp != nullptr) delete completionPopUp; + if (textToolbar != nullptr) delete textToolbar; } QWidget* @@ -789,4 +791,67 @@ 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 +} + +/****************************************************************************** + * Text toolbar support + ******************************************************************************/ + +void +qt_simple_widget_rep::ensure_text_toolbar () { + if (!canvas ()) return; + if (textToolbar) { + if (textToolbar->parent () != canvas ()) { + textToolbar->setParent (canvas ()); + } + return; + } + textToolbar= new QTMTextToolbar (canvas (), this); + if (is_empty (tm_style_sheet)) { + textToolbar->setStyle (qtmstyle ()); + } +} + +void +qt_simple_widget_rep::show_text_toolbar (rectangle selr, double magf, + int scroll_x, int scroll_y, + int canvas_x, int canvas_y) { + ensure_text_toolbar (); + qt_renderer_rep* ren= the_qt_renderer (); + textToolbar->showTextToolbar (ren, selr, magf, scroll_x, scroll_y, canvas_x, + canvas_y); +} + +void +qt_simple_widget_rep::hide_text_toolbar () { + if (textToolbar) { + textToolbar->hide (); + } +} + +void +qt_simple_widget_rep::scroll_text_toolbar_by (SI x, SI y) { + if (textToolbar) { + QPoint qp (x, y); + coord2 p= from_qpoint (qp); + textToolbar->scrollBy (p.x1, p.x2); + qt_renderer_rep* ren= the_qt_renderer (); + textToolbar->updatePosition (ren); + } +} + +bool +qt_simple_widget_rep::is_point_in_text_toolbar (SI x, SI y) { + if (!textToolbar) return false; + + // 将逻辑坐标转换为像素坐标 + double inv_unit= 1.0 / 256.0; + int px = int (std::round (x * inv_unit)); + int py = int (std::round (y * inv_unit)); + + // 获取工具栏的几何位置 + QRect toolbarRect= textToolbar->geometry (); + + // 检查点是否在工具栏内 + return toolbarRect.contains (px, py); +} diff --git a/src/Plugins/Qt/qt_simple_widget.hpp b/src/Plugins/Qt/qt_simple_widget.hpp index 99fb02d199..55c217229f 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 QTMTextToolbar; /*! A widget containing a TeXmacs canvas. @@ -121,6 +122,14 @@ class qt_simple_widget_rep : public qt_widget_rep { void hide_image_popup (); void scroll_image_popup_by (SI x, SI y); + ////////////////////// Text toolbar support + void ensure_text_toolbar (); + void show_text_toolbar (rectangle selr, double magf, int scroll_x, + int scroll_y, int canvas_x, int canvas_y); + void hide_text_toolbar (); + void scroll_text_toolbar_by (SI x, SI y); + bool is_point_in_text_toolbar (SI x, SI y); + ////////////////////// backing store management static void repaint_all (); // called by qt_gui_rep::update() @@ -131,6 +140,7 @@ class qt_simple_widget_rep : public qt_widget_rep { QPointer completionPopUp; QPointer mathCompletionPopUp; QPointer imagePopUp; + QPointer textToolbar; #ifdef USE_MUPDF_RENDERER double bs_zoomf; picture backing_store;