commit 1b3ffa6627992edeb5b2f3be0cd47003e17a690d
Author: Jean-Marc Lasgouttes <lasgout...@lyx.org>
Date:   Tue Nov 24 18:35:25 2020 +0100

    Rewrite (again!) the code for caret drawing
    
    The caret geometry is now computed in BufferView as a list of shapes
    (caret, horizontal l-shape if needed, completion triangle if needed)
    kept in a variable of type CaretGeometry.
    
    The code in WorkArea.cpp only has to draw these shapes. The
    CaretWidget (which never was a widget) in GuiWorkArea.cpp is gone
    now.
    
    As a consequence, the bounding box for the cursor is known precisely
    and therefore rows should be repainted correctly now. This avoids
    caret droppings.
    
    Fixes bug #12024.
---
 src/BufferView.cpp                     |  100 +++++++++++++++++--
 src/BufferView.h                       |    5 +
 src/frontends/Makefile.am              |    1 +
 src/frontends/qt/GuiWorkArea.cpp       |  166 ++++++++++----------------------
 src/frontends/qt/GuiWorkArea_Private.h |    7 +-
 5 files changed, 150 insertions(+), 129 deletions(-)

diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index 62969cb..ea841a0 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -53,6 +53,7 @@
 #include "mathed/MathData.h"
 
 #include "frontends/alert.h"
+#include "frontends/CaretGeometry.h"
 #include "frontends/Delegates.h"
 #include "frontends/FontMetrics.h"
 #include "frontends/NullPainter.h"
@@ -288,6 +289,8 @@ struct BufferView::Private
        CursorSlice current_row_slice_;
        /// are we hovering something that we can click
        bool clickable_inset_;
+       /// shape of the caret
+       frontend::CaretGeometry caret_geometry_;
 };
 
 
@@ -3067,10 +3070,91 @@ void BufferView::caretPosAndDim(Point & p, Dimension & 
dim) const
        p = getPos(cur);
        // center fat carets horizontally
        p.x_ -= dim.wid / 2;
+       // p is top-left
        p.y_ -= dim.asc;
 }
 
 
+void BufferView::buildCaretGeometry(bool complet)
+{
+       Point p;
+       Dimension dim;
+       caretPosAndDim(p, dim);
+
+       Cursor const & cur = d->cursor_;
+       Font const & realfont = cur.real_current_font;
+       frontend::FontMetrics const & fm = theFontMetrics(realfont.fontInfo());
+       bool const isrtl = realfont.isVisibleRightToLeft();
+       int const dir = isrtl ? -1 : 1;
+
+       frontend::CaretGeometry & cg = d->caret_geometry_;
+       cg.shapes.clear();
+
+       // The caret itself, slanted for italics in text edit mode except
+       // for selections because the selection rect does not slant
+       bool const slant = fm.italic() && cur.inTexted() && !cur.selection();
+       double const slope = slant ? fm.italicSlope() : 0;
+       cg.shapes.push_back(
+               {{iround(p.x_ + dim.asc * slope), p.y_},
+                {iround(p.x_ - dim.des * slope), p.y_ + dim.height()},
+                {iround(p.x_ + dir * dim.wid - dim.des * slope), p.y_ + 
dim.height()},
+                {iround(p.x_ + dir * dim.wid + dim.asc * slope), p.y_}}
+               );
+
+       // The language indicator _| (if needed)
+       Language const * doclang = buffer().params().language;
+       if (!((realfont.language() == doclang && isrtl == 
doclang->rightToLeft())
+                 || realfont.language() == latex_language)) {
+               int const lx = dim.height() / 3;
+               int const xx = iround(p.x_ - dim.des * slope);
+               int const yy = p.y_ + dim.height();
+               cg.shapes.push_back(
+                       {{xx, yy - dim.wid},
+                        {xx + dir * (dim.wid + lx - 1), yy - dim.wid},
+                        {xx + dir * (dim.wid + lx - 1), yy},
+                        {xx, yy}}
+                       );
+       }
+
+       // The completion triangle |> (if needed)
+       if (complet) {
+               int const m = p.y_ + dim.height() / 2;
+               int const d = dim.height() / 8;
+               // offset for slanted carret
+               int const sx = iround((dim.asc - (dim.height() / 2 - d)) * 
slope);
+               // starting position x
+               int const xx = p.x_ + dir * dim.wid + sx;
+               cg.shapes.push_back(
+                       {{xx, m - d},
+                        {xx + dir * d, m},
+                        {xx, m + d},
+                        {xx, m + d - dim.wid},
+                        {xx + dir * d - dim.wid, m},
+                        {xx, m - d + dim.wid}}
+                       );
+       }
+
+       // compute extremal x values
+       cg.left = 1000000;
+       cg.right = -1000000;
+       cg.top = 1000000;
+       cg.bottom = -1000000;
+       for (auto const & shape : cg.shapes)
+               for (Point const & p : shape) {
+                       cg.left = min(cg.left, p.x_);
+                       cg.right = max(cg.right, p.x_);
+                       cg.top = min(cg.top, p.y_);
+                       cg.bottom = max(cg.bottom, p.y_);
+               }
+}
+
+
+frontend::CaretGeometry const &  BufferView::caretGeometry() const
+{
+       return d->caret_geometry_;
+}
+
+
 bool BufferView::caretInView() const
 {
        if (!paragraphVisible(cursor()))
@@ -3296,19 +3380,13 @@ void BufferView::draw(frontend::Painter & pain, bool 
paint_caret)
         */
        if (paint_caret) {
                Cursor cur(d->cursor_);
-               Point p;
-               Dimension dim;
-               caretPosAndDim(p, dim);
                while (cur.depth() > 1) {
-                       if (cur.inTexted()) {
-                               TextMetrics const & tm = 
textMetrics(cur.text());
-                               if (p.x_ >= tm.origin().x_
-                                       && p.x_ + dim.width() <= tm.origin().x_ 
+ tm.dim().width())
-                                       break;
-                       } else {
-                               // in mathed
+                       if (!cur.inTexted())
+                               break;
+                       TextMetrics const & tm = textMetrics(cur.text());
+                       if (d->caret_geometry_.left >= tm.origin().x_
+                               && d->caret_geometry_.right <= tm.origin().x_ + 
tm.dim().width())
                                break;
-                       }
                        cur.pop();
                }
                cur.textRow().changed(true);
diff --git a/src/BufferView.h b/src/BufferView.h
index 74e29c6..ca7b20b 100644
--- a/src/BufferView.h
+++ b/src/BufferView.h
@@ -26,6 +26,7 @@ namespace lyx {
 
 namespace support { class FileName; }
 
+namespace frontend { class CaretGeometry; }
 namespace frontend { class Painter; }
 namespace frontend { class GuiBufferViewDelegate; }
 
@@ -311,6 +312,10 @@ public:
        bool caretInView() const;
        /// get the position and height of the caret
        void caretPosAndDim(Point & p, Dimension & dim) const;
+       /// compute the shape of the caret
+       void buildCaretGeometry(bool complet);
+       /// the shape of the caret
+       frontend::CaretGeometry const & caretGeometry() const;
 
        ///
        void draw(frontend::Painter & pain, bool paint_caret);
diff --git a/src/frontends/Makefile.am b/src/frontends/Makefile.am
index ab9454c..89a354a 100644
--- a/src/frontends/Makefile.am
+++ b/src/frontends/Makefile.am
@@ -12,6 +12,7 @@ AM_CPPFLAGS += -I$(srcdir)/.. \
 liblyxfrontends_a_SOURCES = \
        alert.h \
        Application.h \
+       CaretGeometry.h \
        FontLoader.h \
        FontMetrics.h \
        Delegates.h \
diff --git a/src/frontends/qt/GuiWorkArea.cpp b/src/frontends/qt/GuiWorkArea.cpp
index df0eda5..99e88bb 100644
--- a/src/frontends/qt/GuiWorkArea.cpp
+++ b/src/frontends/qt/GuiWorkArea.cpp
@@ -15,7 +15,6 @@
 #include "GuiWorkArea_Private.h"
 
 #include "ColorCache.h"
-#include "FontLoader.h"
 #include "GuiApplication.h"
 #include "GuiCompleter.h"
 #include "GuiKeySymbol.h"
@@ -33,7 +32,6 @@
 #include "Font.h"
 #include "FuncRequest.h"
 #include "KeySymbol.h"
-#include "Language.h"
 #include "LyX.h"
 #include "LyXRC.h"
 #include "LyXVC.h"
@@ -51,6 +49,8 @@
 #include "support/TempFile.h"
 
 #include "frontends/Application.h"
+#include "frontends/CaretGeometry.h"
+
 #include "frontends/FontMetrics.h"
 #include "frontends/WorkAreaManager.h"
 
@@ -125,100 +125,6 @@ mouse_button::state q_motion_state(Qt::MouseButtons state)
 
 namespace frontend {
 
-class CaretWidget {
-public:
-       CaretWidget() : dir(1), l_shape(false), completable(false),
-                                       x(0), y(0), slope(0)
-       {}
-
-       /* Draw the caret. Parameter \c horiz_offset is not 0 when there
-        * has been horizontal scrolling in current row
-        */
-       void draw(QPainter & painter, int horiz_offset)
-       {
-               if (dim.empty())
-                       return;
-               // correction for horizontal scrolling
-               int const xx = x - horiz_offset;
-               int const lx = dim.height() / 3;
-
-               // draw caret box
-               painter.setPen(color);
-               QPainterPath path;
-               path.moveTo(xx + dim.asc * slope, y);
-               path.lineTo(xx - dim.des * slope, y + dim.height());
-               path.lineTo(xx + dir * dim.wid - dim.des * slope, y + 
dim.height());
-               path.lineTo(xx + dir * dim.wid + dim.asc * slope, y);
-               painter.setRenderHint(QPainter::Antialiasing, true);
-               painter.fillPath(path, color);
-               painter.setRenderHint(QPainter::Antialiasing, false);
-
-               // draw RTL/LTR indication
-               if (l_shape)
-                       painter.fillRect(xx - dim.des * slope,
-                                        y + dim.height() - dim.wid + 1,
-                                        dir * (dim.wid + lx - 1), dim.wid, 
color);
-
-               // draw completion triangle
-               if (completable) {
-                       int const m = y + dim.height() / 2;
-                       int const d = dim.height() / 8;
-                       // offset for slanted carret
-                       int const sx = (dim.asc - (dim.height() / 2 - d)) * 
slope;
-                       painter.setPen(QPen(color, dim.width()));
-                       painter.drawLine(xx + dir * dim.wid + sx, m - d,
-                                        xx + dir * (dim.wid + d) + sx, m);
-                       painter.drawLine(xx + dir * dim.wid + sx, m + d,
-                                        xx + dir * (dim.wid + d) + sx, m);
-               }
-       }
-
-       void update(BufferView const * bv, bool complet) {
-               // Cursor size and position
-               Point point;
-               bv->caretPosAndDim(point, dim);
-               x = point.x_;
-               y = point.y_;
-               completable = complet;
-
-               Cursor const & cur = bv->cursor();
-               Font const & realfont = cur.real_current_font;
-               FontMetrics const & fm = theFontMetrics(realfont.fontInfo());
-               BufferParams const & bp = bv->buffer().params();
-               bool const samelang = realfont.language() == bp.language;
-               bool const isrtl = realfont.isVisibleRightToLeft();
-               dir = isrtl ? -1 : 1;
-               // special shape
-               l_shape = (!samelang || isrtl != bp.language->rightToLeft())
-                       && realfont.language() != latex_language;
-
-               // use slanted caret for italics in text edit mode
-               // except for selections because the selection rect does not 
slant
-               bool const slant = fm.italic() && cur.inTexted() && 
!cur.selection();
-               slope = slant ? fm.italicSlope() : 0;
-
-               color = guiApp->colorCache().get(Color_cursor);
-       }
-
-       /// text direction (1 for LtR, -1 for RtL)
-       int dir;
-       /// indication for language change
-       bool l_shape;
-       /// triangle to show that a completion is available
-       bool completable;
-       ///
-       QColor color;
-       /// dimension uf base caret
-       Dimension dim;
-       /// x position (were the vertical line is drawn)
-       int x;
-       /// y position (the top of the caret)
-       int y;
-       /// the slope for drawing slanted caret
-       double slope;
-};
-
-
 // This is a 'heartbeat' generating synthetic mouse move events when the
 // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s
 SyntheticMouseEvent::SyntheticMouseEvent()
@@ -227,7 +133,7 @@ SyntheticMouseEvent::SyntheticMouseEvent()
 
 
 GuiWorkArea::Private::Private(GuiWorkArea * parent)
-: p(parent), buffer_view_(nullptr), lyx_view_(nullptr), caret_(nullptr),
+: p(parent), buffer_view_(nullptr), lyx_view_(nullptr),
   caret_visible_(false), need_resize_(false), preedit_lines_(1),
   last_pixel_ratio_(1.0), completer_(new GuiCompleter(p, p)),
   dialog_mode_(false), shell_escape_(false), read_only_(false),
@@ -266,7 +172,6 @@ GuiWorkArea::Private::~Private()
                buffer_view_->buffer().workAreaManager().remove(p);
        } catch(...) {}
        delete buffer_view_;
-       delete caret_;
        // Completer has a QObject parent and is thus automatically destroyed.
        // See #4758.
        // delete completer_;
@@ -316,7 +221,6 @@ void GuiWorkArea::init()
        // With Qt4.5 a mouse event will happen before the first paint event
        // so make sure that the buffer view has an up to date metrics.
        d->buffer_view_->resize(viewport()->width(), viewport()->height());
-       d->caret_ = new frontend::CaretWidget();
 
        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        setAcceptDrops(true);
@@ -606,7 +510,7 @@ void GuiWorkArea::Private::resetCaret()
                && !completer_->popupVisible()
                && !completer_->inlineVisible();
 
-       caret_->update(buffer_view_, completable);
+       buffer_view_->buildCaretGeometry(completable);
 
        needs_caret_geometry_update_ = true;
        caret_visible_ = true;
@@ -647,6 +551,33 @@ void GuiWorkArea::Private::hideCaret()
 }
 
 
+/* Draw the caret. Parameter \c horiz_offset is not 0 when there
+ * has been horizontal scrolling in current row
+ */
+void GuiWorkArea::Private::drawCaret(QPainter & painter, int horiz_offset) 
const
+{
+       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 & p : shape) {
+                       if (first) {
+                               path.moveTo(p.x_ - horiz_offset, p.y_);
+                               first = false;
+                       } else
+                               path.lineTo(p.x_ - horiz_offset, p.y_);
+               }
+               painter.fillPath(path, color);
+       }
+       painter.setRenderHint(QPainter::Antialiasing, false);
+}
+
+
 void GuiWorkArea::Private::updateScrollbar()
 {
        // Prevent setRange() and setSliderPosition from causing recursive 
calls via
@@ -1194,9 +1125,11 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & 
pain)
        // FIXME: shall we use real_current_font here? (see #10478)
        FontInfo const font = buffer_view_->cursor().getFont().fontInfo();
        FontMetrics const & fm = theFontMetrics(font);
-       int const height = fm.maxHeight();
-       int cur_x = caret_->x;
-       int cur_y = caret_->y + height;
+       Point point;
+       Dimension dim;
+       buffer_view_->caretPosAndDim(point, dim);
+       int cur_x = point.x_;
+       int cur_y = point.y_ + dim.height();
 
        // get attributes of input method cursor.
        // cursor_pos : cursor position in preedit string.
@@ -1249,7 +1182,7 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & 
pain)
                // if we reached the right extremity of the screen, go to next 
line.
                if (cur_x + fm.width(typed_char) > p->viewport()->width() - 
right_margin) {
                        cur_x = right_margin;
-                       cur_y += height + 1;
+                       cur_y += dim.height() + 1;
                        ++preedit_lines_;
                }
                // preedit strings are displayed with dashed underline
@@ -1347,7 +1280,7 @@ void GuiWorkArea::paintEvent(QPaintEvent * ev)
        if (d->caret_visible_) {
                if (d->needs_caret_geometry_update_)
                        d->updateCaretGeometry();
-               d->caret_->draw(pain, d->buffer_view_->horizScrollOffset());
+               d->drawCaret(pain, d->buffer_view_->horizScrollOffset());
        }
 
        d->updateScreen(ev->rect());
@@ -1390,10 +1323,11 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * 
e)
 
 
        // redraw area of preedit string.
-       int height = d->caret_->dim.height();
-       int cur_y = d->caret_->y;
-       viewport()->update(0, cur_y, viewport()->width(),
-               (height + 1) * d->preedit_lines_);
+       // int height = d->caret_->dim.height();
+       // int cur_y = d->caret_->y;
+       // viewport()->update(0, cur_y, viewport()->width(),
+       //      (height + 1) * d->preedit_lines_);
+       viewport()->update();
 
        if (d->preedit_string_.empty()) {
                d->preedit_lines_ = 1;
@@ -1411,12 +1345,14 @@ QVariant 
GuiWorkArea::inputMethodQuery(Qt::InputMethodQuery query) const
        switch (query) {
                // this is the CJK-specific composition window position and
                // the context menu position when the menu key is pressed.
-               case Qt::ImMicroFocus:
-                       return QRect(d->caret_->x - 10 * (d->preedit_lines_ != 
1),
-                                                d->caret_->y + 
d->caret_->dim.height() * d->preedit_lines_,
-                                                d->caret_->dim.width(), 
d->caret_->dim.height());
-               default:
-                       return QWidget::inputMethodQuery(query);
+       case Qt::ImMicroFocus: {
+               CaretGeometry const & cg = bufferView().caretGeometry();
+               return QRect(cg.left - 10 * (d->preedit_lines_ != 1),
+                            cg.top + cg.height() * d->preedit_lines_,
+                            cg.width(), cg.height());
+       }
+       default:
+               return QWidget::inputMethodQuery(query);
        }
 }
 
diff --git a/src/frontends/qt/GuiWorkArea_Private.h 
b/src/frontends/qt/GuiWorkArea_Private.h
index e716323..c97485a 100644
--- a/src/frontends/qt/GuiWorkArea_Private.h
+++ b/src/frontends/qt/GuiWorkArea_Private.h
@@ -68,7 +68,6 @@ public:
 /**
  * Implementation of the work area (buffer view GUI)
 */
-class CaretWidget;
 
 struct GuiWorkArea::Private
 {
@@ -91,6 +90,10 @@ struct GuiWorkArea::Private
        void showCaret();
        /// hide the caret if it is visible
        void hideCaret();
+       /* Draw the caret. Parameter \c horiz_offset is not 0 when there
+        * has been horizontal scrolling in current row
+        */
+       void drawCaret(QPainter & painter, int horiz_offset) const;
        /// Set the range and value of the scrollbar and connect to its 
valueChanged
        /// signal.
        void updateScrollbar();
@@ -118,8 +121,6 @@ struct GuiWorkArea::Private
        ///
        QImage screen_;
 
-       ///
-       CaretWidget * caret_;
        /// is the caret currently displayed
        bool caret_visible_;
        ///
-- 
lyx-cvs mailing list
lyx-cvs@lists.lyx.org
http://lists.lyx.org/mailman/listinfo/lyx-cvs

Reply via email to