commit c2d2c2ca14fb89f976abfa6e9f2ea1fa413d7b67
Author: Koji Yokota <[email protected]>
Date:   Tue May 12 19:13:46 2026 +0900

    Change the reference point of the preedit caret
    
    The coordinates of the preedit caret is now relative to rows broken by
    TextMetrics::breakParagraph(), instead of the cursor coordinates.
    This significantly simplifies the calculation of the caret coordinates.
---
 src/MetricsInfo.cpp                    |   2 +-
 src/MetricsInfo.h                      |   2 +-
 src/TextMetrics.cpp                    |   4 +-
 src/frontends/InputMethod.h            |   6 +-
 src/frontends/NullPainter.h            |   4 +-
 src/frontends/Painter.h                |   4 +-
 src/frontends/qt/GuiInputMethod.cpp    | 289 ++++++++++++++++-----------------
 src/frontends/qt/GuiInputMethod.h      |  33 ++--
 src/frontends/qt/GuiPainter.cpp        |  10 +-
 src/frontends/qt/GuiPainter.h          |   9 +-
 src/frontends/qt/GuiWorkArea.cpp       |  54 ++++--
 src/frontends/qt/GuiWorkArea_Private.h |   4 +-
 src/mathed/InsetMathChar.cpp           |   2 +-
 src/mathed/InsetMathChar.h             |   4 +-
 14 files changed, 230 insertions(+), 197 deletions(-)

diff --git a/src/MetricsInfo.cpp b/src/MetricsInfo.cpp
index 3b4dd70f82..faca0ea183 100644
--- a/src/MetricsInfo.cpp
+++ b/src/MetricsInfo.cpp
@@ -218,7 +218,7 @@ void PainterInfo::draw(int x, int y, docstring const & str)
 
 
 void PainterInfo::draw(int x, int y, char_type c,
-                       frontend::InputMethod const * im,
+                       frontend::InputMethod * im,
                        pos_type const char_format_index, FontInfo const * f)
 {
        pain.text(x, y, c, im, char_format_index, f);
diff --git a/src/MetricsInfo.h b/src/MetricsInfo.h
index 8804f09b23..1cb4554b69 100644
--- a/src/MetricsInfo.h
+++ b/src/MetricsInfo.h
@@ -128,7 +128,7 @@ public:
        void draw(int x, int y, docstring const & str);
        /// Draw preedit char using char_format_index for format
        /// f is used to get the font size in the text mode of mathed
-       void draw(int x, int y, char_type c, frontend::InputMethod const * im,
+       void draw(int x, int y, char_type c, frontend::InputMethod * im,
                  pos_type const char_format_index, FontInfo const * f = 
nullptr);
 
        /// Determines the background color based on the
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index c7ee6769d5..b8dd127987 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -895,7 +895,7 @@ Row TextMetrics::tokenizeParagraph(pit_type const pit) const
                                // loop for differently presented segments of 
preedit string
                                // note that a sequence of preedit segments 
should have the
                                // same position since they have zero length
-                               for (size_type j=0; j < im_->segmentSize(); 
j++) {
+                               for (size_type j=0; j < im_->segmentsSize(); 
j++) {
                                        row.addPreedit(
                                            i, 
preedit.substr(im_->segmentStart(j), im_->segmentLength(j)),
                                            *fi, im_, (pos_type)j, Change());
@@ -2004,7 +2004,6 @@ void TextMetrics::drawParagraph(PainterInfo & pi, 
pit_type const pit, int const
                
theSession().bookmarks().bookmarksInPar(bv_->buffer().fileName(), pm.id());
 
        for (size_t i = 0; i != nrows; ++i) {
-
                Row const & row = pm.rows()[i];
                // Adapt to cursor row scroll offset if applicable.
                int row_x = x - bv_->horizScrollOffset(text_, pit, row.pos());
@@ -2122,7 +2121,6 @@ void TextMetrics::drawParagraph(PainterInfo & pi, 
pit_type const pit, int const
 
                row.changed(false);
        }
-
        //LYXERR(Debug::PAINTING, ".");
 }
 
diff --git a/src/frontends/InputMethod.h b/src/frontends/InputMethod.h
index 60393c138b..bb4a56da63 100644
--- a/src/frontends/InputMethod.h
+++ b/src/frontends/InputMethod.h
@@ -64,7 +64,7 @@ public:
        /// Lengths of each preedit snippet
        virtual size_type & segmentLength(size_type seg_id) const = 0;
 
-       virtual size_type segmentSize() const = 0;
+       virtual size_type segmentsSize() const = 0;
 
        /// the index in char_formats_ vector for given pos in preedit string
        virtual pos_type charFormatIndex(pos_type pos) const = 0;
@@ -83,6 +83,10 @@ public:
        virtual void enableInputMethod() = 0;
        ///
        virtual void disableInputMethod() = 0;
+       ///
+       virtual void setElemsCoordinates(int x, int y) = 0;
+       ///
+       virtual void clearElemsCoordinates() = 0;
 #ifdef Q_DEBUG
        ///
        virtual void setHint(Hint) = 0;
diff --git a/src/frontends/NullPainter.h b/src/frontends/NullPainter.h
index f3113eb65f..0397a3cd43 100644
--- a/src/frontends/NullPainter.h
+++ b/src/frontends/NullPainter.h
@@ -78,11 +78,11 @@ public:
                  Color, size_type, size_type, double, double, bool) override {}
 
        /// draw a char with input method, FontInfo is used in mathed
-       void text(int, int, char_type, InputMethod const *, pos_type const,
+       void text(int, int, char_type, InputMethod *, pos_type const,
                  FontInfo const * = nullptr, Direction const = Auto) override 
{}
 
        /// draw a string with input method, FontInfo is used in mathed
-       void text(int, int, docstring const &, InputMethod const *, pos_type 
const,
+       void text(int, int, docstring const &, InputMethod *, pos_type const,
                  FontInfo const * = nullptr,Direction const = Auto) override {}
 
        /// This painter does not paint
diff --git a/src/frontends/Painter.h b/src/frontends/Painter.h
index c9f90ecb90..3fc6adaec9 100644
--- a/src/frontends/Painter.h
+++ b/src/frontends/Painter.h
@@ -164,13 +164,13 @@ public:
 
        /// draw a char at position x, y (y is the baseline) using input method
        /// f is used to get the font size in the text mode of mathed
-       virtual void text(int, int, char_type, InputMethod const *, pos_type 
const,
+       virtual void text(int, int, char_type, InputMethod *, pos_type const,
                          FontInfo const * = nullptr, Direction const = Auto) = 
0;
 
        /// draw a string at position x, y (y is the baseline) using input 
method.
        /// f is used to get the font size in the text mode of mathed
        virtual void text(int x, int y, docstring const & str,
-                         InputMethod const * im, pos_type const 
char_format_index,
+                         InputMethod * im, pos_type const char_format_index,
                          FontInfo const * f = nullptr, Direction const dir = 
Auto) = 0;
 
        // Returns true if the painter does not actually paint.
diff --git a/src/frontends/qt/GuiInputMethod.cpp 
b/src/frontends/qt/GuiInputMethod.cpp
index 2ad68efb73..b9d8786824 100644
--- a/src/frontends/qt/GuiInputMethod.cpp
+++ b/src/frontends/qt/GuiInputMethod.cpp
@@ -75,21 +75,30 @@ struct GuiInputMethod::Private
        Rows::iterator rows_;
        size_type rows_size_;
 
+       /// position of the cursor
        pos_type cur_pos_ = 0;
+       /// the index of the cursor row
        pos_type cur_row_idx_;
+       /// the row index of the end of preedit
+       pos_type preedit_end_row_idx_;
+       /// position of the caret
        pos_type caret_pos_;
+       /// the row index and pos of the caret
+       PreeditElement caret_row_;
+       /// caret index and pos in preedit elements
+       PreeditElement caret_elem_;
 
        pos_type anchor_pos_ = 0;
        pos_type abs_pos_;
 
+       std::vector<std::array<int,2>> elems_coords_;
+
        bool real_boundary_    = false;
        bool virtual_boundary_ = false;
        bool initial_tf_entry_ = false;
 
        Point init_point_;
        Dimension cur_dim_;
-       /// pixel offset of the caret within preedit text
-       std::array<int, 2> caret_offset_ = {0, 0};
 };
 
 
@@ -242,7 +251,6 @@ void GuiInputMethod::inputMethodEvent(QInputMethodEvent* ev)
                d->cur_->undoAction();
                d->cur_->endUndoGroup();
                d->has_selection_ = false;
-               d->im_state_.preediting_ = false;
        }
 
        d->preedit_str_ = qstring_to_ucs4(ev->preeditString());
@@ -256,32 +264,13 @@ void GuiInputMethod::inputMethodEvent(QInputMethodEvent* 
ev)
        d->init_point_ =
                initializeCaretCoords(d->cur_row_idx_ + d->real_boundary_,
                                      d->real_boundary_ && 
!d->im_state_.composing_mode_);
+       setElemsCoordinates(d->init_point_.x, d->init_point_.y);
 
        // Push preedit texts into row elements, which can shift the anchor
        // point of the preedit texts in a centered or right-flushed row.
        // Check such a shift immediately below.
        updateMetrics(d->cur_);
 
-       /*
-        *          Draw caret
-        */
-
-       // set offset of the virtual preedit caret from the real position
-       d->caret_offset_ = setCaretOffset(d->caret_pos_);
-
-       // set graphical geometry of the caret
-       d->im_state_.cursor_rect_.setCoords(
-                   d->init_point_.x + d->caret_offset_[0],
-                   d->init_point_.y + d->caret_offset_[1],
-                   d->init_point_.x + d->caret_offset_[0] + 
d->cur_dim_.width(),
-                   d->init_point_.y + d->caret_offset_[1] + 
d->cur_dim_.height());
-       // while preedit exists, this is just coords of real cursor
-       d->im_state_.anchor_rect_.setCoords(
-                   d->init_point_.x,
-                   d->init_point_.y,
-                   d->init_point_.x + d->cur_dim_.width(),
-                   d->init_point_.y + d->cur_dim_.height());
-
        // if preedit string is not empty, we are still working on it
        d->im_state_.preediting_ = d->preedit_str_.empty() ? false : true;
 
@@ -722,126 +711,117 @@ void 
GuiInputMethod::setParagraphMetrics(ParagraphMetrics & pm)
        d->pm_ptr_ = &pm;
 }
 
-std::array<int,2> GuiInputMethod::setCaretOffset(pos_type caret_pos)
+void GuiInputMethod::setCaretRectangle()
 {
-       // Note that preedit elements are virtual and not counted in pos().
-       // pos: 0 1 2 3 4 5 6 7 8 | 8 8 8 8 8 8 8 8 8 8 | 9 10 11 ...
-       //      <-  non-virtual ->|<- preedit element ->|<- non-virtual
-       //
-       // This is also true for next_row_pos.
-       // On the other hand d->caret_pos_ counts preedit elements.
-
-       if (d->preedit_str_.empty() || d->cur_->reverseDirectionNeeded() ||
-               d->cur_->inMathed()) {
-               // reset shift of the virtual caret as the preedit string is 
cancelled
-               // this part is also visited right before starting preedit input
-               return {0, 0};
+       d->caret_elem_ = setCaretPreeditElement();
+       int x,y;
+       if (d->im_state_.composing_mode_) {
+               x = d->elems_coords_[d->caret_elem_.index][0] + 
preeditCaretOffset();
+               y = d->elems_coords_[d->caret_elem_.index][1];
+       } else {
+               x = d->elems_coords_[d->caret_elem_.index][0];
+               y = d->elems_coords_[d->caret_elem_.index][1];
        }
-       // when preedit cursor is available, string fonts are all common
-       // so pick up the last one
-       QTextCharFormat qtcf;
-       if (!d->style_.segments_.empty())
-               qtcf = d->style_.segments_.back().char_format_;
-       else
-               conformToSurroundingFont(qtcf);
-       QFontMetrics qfm(qtcf.font());
-       QString str_before_caret = "";
+       d->im_state_.anchor_rect_.setRect(d->elems_coords_[0][0],
+                                         d->elems_coords_[0][1],
+                                         d->cur_dim_.width(),
+                                         d->cur_dim_.height());
+       d->im_state_.cursor_rect_.setRect(x, y, d->cur_dim_.width(),
+                                         d->cur_dim_.height());
+       QGuiApplication::inputMethod()->update(Qt::ImQueryInput);
+}
 
-#if defined(Q_OS_MACOS) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QRectF GuiInputMethod::caretRectangle()
+{
        if (d->im_state_.composing_mode_)
-               str_before_caret =
-                   toqstr(d->preedit_str_.substr(0, caret_pos - d->cur_pos_));
+               return d->im_state_.cursor_rect_;
        else
-               // adjust for the reported caret position in the completion 
mode in Qt5
-               str_before_caret = toqstr(
-                           d->preedit_str_.substr(0, caret_pos - d->cur_pos_ -
-                                                  
shiftFromCaretToSegmentHead()));
-#else
-       str_before_caret =
-               toqstr(d->preedit_str_.substr(0, caret_pos - d->cur_pos_));
-#endif
-
-       // process line wrapping
-       //
-       // NOTE:
-       // preedits added on boundary goes to the beginning of the next line
-       //         -> real_boundary = true   virtual_boundary = false
-       // preedits added on boundary is appended to the same line
-       //         -> real_boundary = false   virtual_boundary = true
-
-       PreeditRow caret_row = getCaretInfo();
-       std::array<int,2> caret_offset {};
-
-       // caret_row.index doesn't decrease with virtual_boundary
-       // has multiple preedit rows
-       if (caret_row.index > d->cur_row_idx_ || d->real_boundary_) {
-               QString lastline_str;
+               return d->im_state_.anchor_rect_;
+}
 
-               if (d->real_boundary_ && caret_row.index == d->cur_row_idx_ + 
d->real_boundary_)
-                       lastline_str = str_before_caret;
-               else
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-                       lastline_str = str_before_caret.sliced(
-                           caret_row.pos - d->cur_pos_,
-                           d->cur_pos_ + str_before_caret.length() - 
caret_row.pos);
-#else
-                       lastline_str = str_before_caret.mid(
-                           caret_row.pos - d->cur_pos_,
-                           d->cur_pos_ + str_before_caret.length() - 
caret_row.pos);
-#endif
-               //
-               // calculate left margin
-               //
-               int left_margin;
-               int inset_offset;
-               if (d->cur_->depth() > 1) {
-                       left_margin = 
d->cur_->inset().leftOffset(&d->cur_->bv());
-                       inset_offset =  d->cur_->inset().xo(d->cur_->bv());
-               } else {
-                       left_margin = d->rows_[caret_row.index].left_margin;
-                       inset_offset = 0;
-               }
-#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
-               if (d->real_boundary_ && !d->im_state_.composing_mode_)
-                       caret_offset[0] = qfm.horizontalAdvance(lastline_str);
-               else
-                       caret_offset[0] = - d->init_point_.x + left_margin
-                               + inset_offset + 
qfm.horizontalAdvance(lastline_str);
-#else
-               if (d->real_boundary_ && !d->im_state_.composing_mode_)
-                       caret_offset[0] = qfm.width(lastline_str);
+GuiInputMethod::PreeditElement GuiInputMethod::setCaretPreeditElement()
+{
+       int caret_elem_idx = 0;
+       int elem_pos = -1;
+       docstring elem_str;
+
+       if (d->cur_->inMathed()) {
+               // in mathed, each char ocuppies an element
+               elem_pos = d->caret_pos_;
+               if (d->im_state_.composing_mode_)
+                       caret_elem_idx = d->caret_pos_ - d->cur_pos_ - 1;
                else
-                       caret_offset[0] = - d->init_point_.x + left_margin
-                               + inset_offset + qfm.width(lastline_str);
-#endif
+                       caret_elem_idx = d->caret_pos_ - d->cur_pos_;
        } else {
-               int left_margin_diff = d->rows_[caret_row.index].left_margin -
-                       d->rows_[d->cur_row_idx_].left_margin;
-               // the case in which the preedit caret is in the first row of 
the preedit
-#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
-               caret_offset[0] =
-                       left_margin_diff + 
qfm.horizontalAdvance(str_before_caret);
-#else
-               caret_offset[0] =
-                       left_margin_diff + qfm.width(str_before_caret);
-#endif
+               TextMetrics & tm = 
d->cur_->bv().textMetrics(d->cur_->innerText());
+               ParagraphMetrics & pm = tm.parMetrics(d->cur_->top().pit());
+
+               Rows & cur_rows = pm.rows();
+               bool break_all = false;
+               for (auto&& row : cur_rows) {
+                       for (auto&& elem : row) {
+                               if (elem.type != Row::Type::PREEDIT)
+                                       continue;
+                               // element type is preedit
+                               if (elem_pos == -1)
+                                       elem_pos = elem.pos;
+                               // note that caret is at the end of the element 
in the composing
+                               // mode whereas it is at the start in the 
completing mode
+                               if ((d->im_state_.composing_mode_ &&
+                                    elem_pos + (int)elem.str.length() >= 
d->caret_pos_) ||
+                                   (!d->im_state_.composing_mode_ && elem_pos 
>= d->caret_pos_))
+                               {
+                                       elem_str = elem.str;
+                                       break_all = true;
+                                       break;
+                               }
+                               elem_pos += elem.str.length();
+                               ++caret_elem_idx;
+                       }
+                       if (break_all)
+                               break;
+               }
        }
+       return {caret_elem_idx, elem_pos, elem_str};
+}
 
-       // vertical offset only applicable to main text
-       caret_offset[1] = 0;
-       for (pos_type i = d->cur_row_idx_ +
-                         (d->real_boundary_ && !d->im_state_.composing_mode_);
-            i < caret_row.index; ++i)
-               caret_offset[1] += d->rows_[i].descent() + 
d->rows_[i+1].ascent();
 
-       return caret_offset;
+void GuiInputMethod::setElemsCoordinates(int x, int y)
+{
+       d->elems_coords_.push_back({x,y});
 }
 
 
-// returns (x_offset, y_offset) array
-std::array<int, 2> GuiInputMethod::preeditCaretOffset() const
+void GuiInputMethod::clearElemsCoordinates()
 {
-       return d->caret_offset_;
+       d->elems_coords_.clear();
+}
+
+
+int GuiInputMethod::preeditCaretOffset()
+{
+       if (d->preedit_str_.empty() || d->cur_->reverseDirectionNeeded())
+               return 0;
+
+       // when preedit cursor is available, string fonts are all common
+       // so pick up the last one
+       QTextCharFormat qtcf;
+       if (!d->style_.segments_.empty())
+               qtcf = d->style_.segments_.back().char_format_;
+       else
+               conformToSurroundingFont(qtcf);
+       QFontMetrics qfm(qtcf.font());
+
+       docstring line;
+       if (d->cur_->inMathed())
+               // texts in math text mode cannot be accessed via preedit 
elements,
+               // so that d->caret_elem_ becomes empty.
+               // accessing it via d->preedit_str_
+               line = d->preedit_str_.substr(d->preedit_str_.length()-1, 1);
+       else
+               line = d->caret_elem_.str.substr(0, d->caret_pos_ - 
d->caret_elem_.pos);
+
+       return qfm.horizontalAdvance(toqstr(line));
 }
 
 
@@ -947,14 +927,15 @@ QVariant 
GuiInputMethod::inputMethodQuery(Qt::InputMethodQuery query)
                } else {
                        QRectF * rect_ptr;
                        if (d->im_state_.composing_mode_) {
-                               // in the editing mode, cursor_rect_ follows 
the position of
+                               // in the composing mode, cursor_rect_ follows 
the position of
                                // the virtual caret, but the drop down of 
predicted candidates
                                // wants the starting point of the preedit, so 
respond with
                                // anchor_rect_ that points the starting point 
during the
-                               // editing mode
+                               // composing mode
                                rect_ptr = &d->im_state_.anchor_rect_;
-                       } else
+                       } else {
                                rect_ptr = &d->im_state_.cursor_rect_;
+                       }
 
                        LYXERR(Debug::KEY, msg << " x:" << rect_ptr->x() <<
                               " y:" << rect_ptr->y() <<
@@ -1042,7 +1023,7 @@ QVariant 
GuiInputMethod::inputMethodQuery(Qt::InputMethodQuery query)
                                d->work_area_->viewport()->y(),
                                d->work_area_->viewport()->width(),
                                d->work_area_->viewport()->height());
-               LYXERR(Debug::KEY, msg << "(x,y,w,h) = " <<
+               LYXERR(Debug::KEY, msg << "(x,y,w,h) = (" <<
                       viewport.x() << ", " << viewport.y() << ", " <<
                       viewport.width() << ", " << viewport.height() << ")");
                return viewport;
@@ -1132,6 +1113,8 @@ pos_type GuiInputMethod::initializePositions(Cursor * cur)
        // position of the real cursor (also the start of the preedit)
        updatePosAndSurroundingText();
 
+       d->preedit_end_row_idx_ = d->pm_ptr_->getRowIndex(d->cur_pos_, false);
+
        // Note that getRowIndex(., false) gives the row index *after* preedit
        // strings since they are virtual, so it increases as preedit strings go
        // over multiple rows. To fix it at the starting point, getRowIndex(., 
true)
@@ -1206,14 +1189,13 @@ pos_type GuiInputMethod::initializePositions(Cursor * 
cur)
        LYXERR(Debug::KEY, "========== BEGIN: initializePositions ==========");
        LYXERR(Debug::KEY, "cur_row_idx   = " << cur_row_idx <<
                "\treal_boundary    = " << real_boundary);
-       LYXERR(Debug::KEY, "                      " <<
+       LYXERR(Debug::KEY, "                " <<
                "\tvirtual_boundary = " << virtual_boundary);
-       LYXERR(Debug::KEY, "max width     = " << std::dec <<
-               d->buffer_view_->textMetrics(
-                   cur->innerText()).maxWidth()
-                    - d->rows_[cur_row_idx].right_margin);
-       LYXERR(Debug::KEY, "row width     = " << std::dec <<
-                       d->rows_[cur_row_idx].width());
+       LYXERR(Debug::KEY, "preedit_end_row_idx_ = " << 
d->preedit_end_row_idx_);
+       LYXERR(Debug::KEY, "max width     = " <<
+              d->buffer_view_->textMetrics(cur->innerText()).maxWidth()
+              - d->rows_[cur_row_idx].right_margin);
+       LYXERR(Debug::KEY, "row width     = " << d->rows_[cur_row_idx].width());
        if (d->preedit_str_.empty())
                LYXERR(Debug::KEY, "wchar width   = " <<
                       horizontalAdvance(from_utf8("あ")));
@@ -1266,9 +1248,12 @@ void GuiInputMethod::updateMetrics(Cursor * cur)
 
 ParagraphMetrics * GuiInputMethod::resetParagraphMetrics(Cursor * cur)
 {
+       d->buffer_view_->updateMetrics();
+
        // paragraph metrics of the par we are in
        ParagraphMetrics * pm_ptr =
            
&cur->bv().textMetrics(cur->innerText()).parMetrics(cur->top().pit());
+
        d->rows_ = pm_ptr->rows().begin();
        d->rows_size_ = pm_ptr->rows().size();
        // ID of the row in which the current real cursor resides
@@ -1286,15 +1271,22 @@ pos_type GuiInputMethod::getCaretPos(size_type 
preedit_length)
 }
 
 
-GuiInputMethod::PreeditRow GuiInputMethod::getCaretInfo()
+GuiInputMethod::PreeditElement GuiInputMethod::getCaretInfo()
 {
-       // the virtual boundary case has the real cusor on the second row of
-       // the preedit inputs
+       PreeditElement caret_row{};
+
+       // math text doesn't break
+       if (d->cur_->inMathed()) {
+
+               caret_row.index = d->cur_row_idx_;
+               caret_row.pos = d->cur_pos_;
+
+               return caret_row;
+       }
 
        // row index of the preedit's second row in a paragraph (rows)
        const pos_type second_row_idx =
                d->cur_row_idx_ + 1 + d->real_boundary_ - d->virtual_boundary_;
-
        LASSERT(d->cur_row_idx_ < (pos_type)d->rows_size_ &&
                d->cur_row_idx_ >= 0, return {});
 
@@ -1315,8 +1307,6 @@ GuiInputMethod::PreeditRow GuiInputMethod::getCaretInfo()
             eit < d->rows_[visual_row_idx_].end(); ++eit)
                second_row_pos += eit->str.length();
 
-       PreeditRow caret_row{};
-
        // when d->real_boundary_ is true, cursor position is at the beginning 
of the
        // new line, while the caret on screen stays at the end of one line 
above
        // below is the starting point to calculate caret_row.pos
@@ -1325,8 +1315,9 @@ GuiInputMethod::PreeditRow GuiInputMethod::getCaretInfo()
        LASSERT(caret_row.pos >= 0, return {});
 
        // if the preedit caret is on the second row or later, count the second 
row
-       caret_row.index = d->caret_pos_ > second_row_pos ?
-                   second_row_idx + d->virtual_boundary_ : d->cur_row_idx_ + 
d->real_boundary_;
+       caret_row.index = (d->caret_pos_ >= second_row_pos ?
+                   second_row_idx + d->virtual_boundary_ :
+                              d->cur_row_idx_ + d->real_boundary_);
 
        // the second row exists and begins with the preedit
        if (d->cur_row_idx_ + 1 < (pos_type)d->rows_size_ &&
@@ -1334,6 +1325,8 @@ GuiInputMethod::PreeditRow GuiInputMethod::getCaretInfo()
 
                for (pos_type i = d->cur_row_idx_+1; i < 
(pos_type)d->rows_size_; i++) {
                        if (d->rows_[i].front().isPreedit()) {
+                               // accumulate element lengths to get the row 
length except for
+                               // the last row; used to judge break time
                                int row_length = 0;
                                for (const Row::Element & elm : d->rows_[i])
                                        row_length += elm.str.length();
@@ -1425,7 +1418,7 @@ size_type & GuiInputMethod::segmentLength(size_type 
seg_id) const
 }
 
 
-size_type GuiInputMethod::segmentSize() const
+size_type GuiInputMethod::segmentsSize() const
 {
        return d->style_.segments_.size();
 }
diff --git a/src/frontends/qt/GuiInputMethod.h 
b/src/frontends/qt/GuiInputMethod.h
index 66cdeea033..fe56b8a6c6 100644
--- a/src/frontends/qt/GuiInputMethod.h
+++ b/src/frontends/qt/GuiInputMethod.h
@@ -62,10 +62,12 @@ public:
                QString ruby_;
        };
 
-       /// pair of row's \p index and its first char \p pos in a paragraph
-       struct PreeditRow {
-               pos_type pos;
-               pos_type index;
+       /// preedit element's \p index and its first char \p pos in a paragraph
+       /// and string in the element
+       struct PreeditElement {
+               pos_type  index;
+               pos_type  pos;
+               docstring str;
        };
 
        struct InputMethodState {
@@ -88,21 +90,22 @@ public:
 
        /// Starting position of preedit segments
        pos_type & segmentStart(size_type seg_id) const override;
-
-       /// Lengths of each preedit segment
+       /// Length of each preedit segment
        size_type & segmentLength(size_type seg_id) const override;
-
-       size_type segmentSize() const override;
+       /// Number of segments in preedit
+       size_type segmentsSize() const override;
 
        /// Character format of the given index in the char_formats_ vector
        QTextCharFormat & charFormat(pos_type index) const;
        /// the index in char_formats_ vector for given pos in preedit string
        pos_type charFormatIndex(pos_type pos) const override;
 
-       /// Sets pixel offsets of the caret from real cursor position
-       std::array<int,2> setCaretOffset(pos_type caret_pos);
-       /// Returns pixel offsets of the caret from real cursor position
-       std::array<int,2> preeditCaretOffset() const;
+       /// Sets caret rectangle
+       void setCaretRectangle();
+       /// Returns coordinates of the preedit caret
+       QRectF caretRectangle();
+       /// Returns horizontal pixel offsets of the caret from real cursor 
position
+       int preeditCaretOffset();
        ///
        void setParagraphMetrics(ParagraphMetrics &) override;
        /// Sets surrounding text of the cursor within the paragraph
@@ -119,6 +122,8 @@ public:
        int horizontalAdvance(docstring const &);
        /// Whether the segment contains language that allows wrapping anywhere
        bool canWrapAnywhere(pos_type const) override;
+       void setElemsCoordinates(int x, int y) override;
+       void clearElemsCoordinates() override;
 Q_SIGNALS:
        void preeditProcessed(QInputMethodEvent* ev);
        void inputMethodStateChanged(Qt::InputMethodQueries);
@@ -166,8 +171,10 @@ private:
        pos_type focusedSegmentIndex();
        /// x shift factor from the caret to the selection segment's head
        int shiftFromCaretToSegmentHead();
+       /// get caret index in preedit elements
+       PreeditElement setCaretPreeditElement();
        ///
-       PreeditRow getCaretInfo();
+       PreeditElement getCaretInfo();
        /// Pick up next segment from the turnout if there is a match and return
        /// the next segment position to be filled
        /// If the second argument is given, it is merged before filling the 
segment
diff --git a/src/frontends/qt/GuiPainter.cpp b/src/frontends/qt/GuiPainter.cpp
index cf00f0257e..0383cf5b45 100644
--- a/src/frontends/qt/GuiPainter.cpp
+++ b/src/frontends/qt/GuiPainter.cpp
@@ -345,7 +345,7 @@ void GuiPainter::text(int x, int y, docstring const & s,
 }
 
 
-void GuiPainter::text(int x, int y, char_type c, InputMethod const * im,
+void GuiPainter::text(int x, int y, char_type c, InputMethod * im,
                       pos_type const char_format_index, FontInfo const * f,
                       Direction const dir)
 {
@@ -354,7 +354,7 @@ void GuiPainter::text(int x, int y, char_type c, 
InputMethod const * im,
 
 
 void GuiPainter::text(int x, int y, docstring const & s,
-                      InputMethod const * im, pos_type const char_format_index,
+                      InputMethod * im, pos_type const char_format_index,
                       FontInfo const * f, Direction const dir)
 {
        if (s.empty())
@@ -379,6 +379,12 @@ void GuiPainter::text(int x, int y, docstring const & s,
                qfont.setPointSize(fsize);
        }
        setFont(qfont);
+       QFontMetrics qfm(qfont);
+
+       // we raise the caret by its height here, not in
+       // GuiInputMethod::caretOffset() since offset is also used for
+       // determination of candidate menu coords
+       im->setElemsCoordinates(x, y - qfm.ascent());
 
        LYXERR(Debug::GUI, "Drawing preedit segment " << char_format_index <<
               ": fg = " <<
diff --git a/src/frontends/qt/GuiPainter.h b/src/frontends/qt/GuiPainter.h
index b53e4392b5..69ffcf4983 100644
--- a/src/frontends/qt/GuiPainter.h
+++ b/src/frontends/qt/GuiPainter.h
@@ -141,16 +141,15 @@ public:
        /// draw a char at position x, y (y is the baseline) using input method
        /// char_format_index specifies preedit format
        /// f is used to get the font size in the text mode of mathed
-       void text(int x, int y, char_type c, InputMethod const * im,
+       void text(int x, int y, char_type c, InputMethod * im,
                  pos_type const char_format_index, FontInfo const * f = 
nullptr,
                  Direction const dir = Auto) override;
        /// draw a string at position x, y (y is the baseline) using input 
method.
        /// char_format_index specifies preedit format
        /// f is used to get the font size in the text mode of mathed
-       void text(int x, int y, docstring const & str, InputMethod const * im,
-                 pos_type const char_format_index, FontInfo const * f = 
nullptr,
-                 Direction const dir = Auto) override;
-
+       void text(int x, int y, docstring const & str,
+                 InputMethod * im, pos_type const char_format_index,
+                 FontInfo const * f = nullptr, Direction const dir = Auto) 
override;
        ///
        void textDecoration(FontInfo const & f, int x, int y, int width) 
override;
 
diff --git a/src/frontends/qt/GuiWorkArea.cpp b/src/frontends/qt/GuiWorkArea.cpp
index bd77d0805d..9e1cbb4655 100644
--- a/src/frontends/qt/GuiWorkArea.cpp
+++ b/src/frontends/qt/GuiWorkArea.cpp
@@ -573,11 +573,29 @@ void GuiWorkArea::Private::hideCaret()
  */
 void GuiWorkArea::Private::drawCaret(QPainter & painter, int horiz_offset) 
const
 {
-       drawCaret(painter, horiz_offset, 0);
+       if (buffer_view_->caretGeometry().shapes.empty())
+               return;
+
+       QColor const color = guiApp->colorCache().get(Color_cursor);
+       painter.setPen(color);
+       painter.setRenderHint(QPainter::Antialiasing, true);
+       for (auto const & shape : buffer_view_->caretGeometry().shapes) {
+               bool first = true;
+               QPainterPath path;
+               for (Point const & pt : shape) {
+                       if (first) {
+                               path.moveTo(pt.x - horiz_offset, pt.y);
+                               first = false;
+                       } else
+                               path.lineTo(pt.x - horiz_offset, pt.y);
+               }
+               painter.fillPath(path, color);
+       }
+       painter.setRenderHint(QPainter::Antialiasing, false);
 }
 
-void GuiWorkArea::Private::drawCaret(QPainter & painter, int horiz_offset,
-                                     int vert_offset) const
+
+void GuiWorkArea::Private::drawPreeditCaret(GuiPainter & painter) const
 {
        if (buffer_view_->caretGeometry().shapes.empty())
                return;
@@ -585,15 +603,22 @@ void GuiWorkArea::Private::drawCaret(QPainter & painter, 
int horiz_offset,
        QColor const color = guiApp->colorCache().get(Color_cursor);
        painter.setPen(color);
        painter.setRenderHint(QPainter::Antialiasing, true);
+
+       int const & abs_x = im_->caretRectangle().x();
+       int const & abs_y = im_->caretRectangle().y();
+
        for (auto const & shape : buffer_view_->caretGeometry().shapes) {
                bool first = true;
                QPainterPath path;
+               int first_x, first_y;
                for (Point const & pt : shape) {
                        if (first) {
-                               path.moveTo(pt.x - horiz_offset, pt.y - 
vert_offset);
+                               path.moveTo(abs_x, abs_y);
+                               first_x = pt.x;
+                               first_y = pt.y;
                                first = false;
                        } else
-                               path.lineTo(pt.x - horiz_offset, pt.y - 
vert_offset);
+                               path.lineTo(pt.x - first_x + abs_x, pt.y - 
first_y + abs_y);
                }
                painter.fillPath(path, color);
        }
@@ -1356,22 +1381,23 @@ void GuiWorkArea::paintEvent(QPaintEvent * ev)
 
        GuiPainter pain(d->screenDevice(), pixelRatio(), 
d->lyx_view_->develMode());
 
+       d->im_->clearElemsCoordinates();
+
        d->buffer_view_->draw(pain, d->caret_visible_);
 
        // draw the caret
        // FIXME: the code would be a little bit simpler if caret geometry
        // was updated unconditionally. Some profiling is required to see
        // how expensive this is (especially when idle).
-       if ((d->im_->preeditString().empty() && d->caret_visible_) ||
-               (!d->im_->preeditString().empty() && d->im_->isCaretVisible())) 
{
-               if (d->needs_caret_geometry_update_)
-                       d->updateCaretGeometry();
-               d->drawCaret(pain,
-                            d->buffer_view_->horizScrollOffset()
-                            - d->im_->preeditCaretOffset()[0],
-                            - d->im_->preeditCaretOffset()[1]);
+       if (d->needs_caret_geometry_update_)
+               d->updateCaretGeometry();
+       if (d->im_->preeditString().empty() && d->caret_visible_) {
+               d->drawCaret(pain, d->buffer_view_->horizScrollOffset());
+       } else if (!d->im_->preeditString().empty()) {
+               d->im_->setCaretRectangle();
+               if (d->im_->isCaretVisible())
+                       d->drawPreeditCaret(pain);
        }
-
        d->updateScreen(ev->rect());
 
        ev->accept();
diff --git a/src/frontends/qt/GuiWorkArea_Private.h 
b/src/frontends/qt/GuiWorkArea_Private.h
index e82e4d6e8e..c707bd590f 100644
--- a/src/frontends/qt/GuiWorkArea_Private.h
+++ b/src/frontends/qt/GuiWorkArea_Private.h
@@ -91,8 +91,8 @@ struct GuiWorkArea::Private
         * has been horizontal scrolling in current row
         */
        void drawCaret(QPainter & painter, int horiz_offset) const;
-       /// virtual preedits can require vertical offset when contines to next 
row
-       void drawCaret(QPainter & painter, int horiz_offset, int vert_offset) 
const;
+       ///
+       void drawPreeditCaret(GuiPainter & painter) const;
        /// Set the range and value of the scrollbar and connect to its 
valueChanged
        /// signal.
        void updateScrollbar();
diff --git a/src/mathed/InsetMathChar.cpp b/src/mathed/InsetMathChar.cpp
index e32c2249ab..84c0be9a09 100644
--- a/src/mathed/InsetMathChar.cpp
+++ b/src/mathed/InsetMathChar.cpp
@@ -192,7 +192,7 @@ void InsetMathChar::draw(PainterInfo & pi, int x, int y) 
const
 
 
 void InsetMathChar::draw(PainterInfo & pi, int x, int y,
-                         frontend::InputMethod const * im,
+                         frontend::InputMethod * im,
                          pos_type const char_format_index) const
 {
        pi.draw(x, y, char_, im, char_format_index, &pi.base.font);
diff --git a/src/mathed/InsetMathChar.h b/src/mathed/InsetMathChar.h
index bd63436ded..8e424ddfda 100644
--- a/src/mathed/InsetMathChar.h
+++ b/src/mathed/InsetMathChar.h
@@ -28,8 +28,8 @@ public:
        ///
        void draw(PainterInfo & pi, int x, int y) const override;
        ///
-       void draw(PainterInfo & pi, int x, int y, frontend::InputMethod const * 
im,
-                 pos_type const char_format_index) const;
+       void draw(PainterInfo & pi, int x, int y,
+                 frontend::InputMethod * im, pos_type const char_format_index) 
const;
        ///
        void metricsT(TextMetricsInfo const & mi, Dimension & dim) const 
override;
        ///
-- 
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs

Reply via email to