[LyX/master] Change the reference point of the preedit caret

Koji Yokota <[email protected]> Tue, 12 May 2026 10:17:44 +0000
Newsgroups gmane.editors.lyx.cvs
Message-ID <[email protected]>
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