X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt%2FGuiWorkArea.cpp;h=02e03aaaeb780565d741ffd284345b3c09a852e9;hb=070270fdc719edcd0ae49a182acf58583038a765;hp=2500afe4db9621f46ceeca772b6c5c20b1987e5a;hpb=e99436b529fc83a5a89e3ec053f977117c339082;p=features.git diff --git a/src/frontends/qt/GuiWorkArea.cpp b/src/frontends/qt/GuiWorkArea.cpp index 2500afe4db..02e03aaaeb 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" @@ -42,15 +40,14 @@ #include "Undo.h" #include "version.h" -#include "graphics/GraphicsImage.h" -#include "graphics/GraphicsLoader.h" - #include "support/convert.h" #include "support/debug.h" #include "support/lassert.h" #include "support/TempFile.h" #include "frontends/Application.h" +#include "frontends/CaretGeometry.h" + #include "frontends/FontMetrics.h" #include "frontends/WorkAreaManager.h" @@ -79,8 +76,6 @@ #include #include -int const TabIndicatorWidth = 3; - #undef KeyPress #undef NoModifier @@ -98,7 +93,7 @@ static mouse_button::state q_button_state(Qt::MouseButton button) case Qt::LeftButton: b = mouse_button::button1; break; - case Qt::MidButton: + case Qt::MiddleButton: b = mouse_button::button2; break; case Qt::RightButton: @@ -117,7 +112,7 @@ mouse_button::state q_motion_state(Qt::MouseButtons state) mouse_button::state b = mouse_button::none; if (state & Qt::LeftButton) b |= mouse_button::button1; - if (state & Qt::MidButton) + if (state & Qt::MiddleButton) b |= mouse_button::button2; if (state & Qt::RightButton) b |= mouse_button::button3; @@ -127,110 +122,6 @@ mouse_button::state q_motion_state(Qt::MouseButtons state) namespace frontend { -class CaretWidget { -public: - CaretWidget() : rtl_(false), l_shape_(false), completable_(false), - x_(0), caret_width_(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 (!rect_.isValid()) - return; - - int const x = x_ - horiz_offset; - int const y = rect_.top(); - int const l = x_ - rect_.left(); - int const r = rect_.right() - x_; - int const bot = rect_.bottom(); - - // draw vertical line - painter.fillRect(x, y, caret_width_, rect_.height(), color_); - - // draw RTL/LTR indication - painter.setPen(color_); - if (l_shape_) { - if (rtl_) - painter.drawLine(x, bot, x - l + 1, bot); - else - painter.drawLine(x, bot, x + caret_width_ + r - 1, bot); - } - - // draw completion triangle - if (completable_) { - int m = y + rect_.height() / 2; - int d = TabIndicatorWidth - 1; - if (rtl_) { - painter.drawLine(x - 1, m - d, x - 1 - d, m); - painter.drawLine(x - 1, m + d, x - 1 - d, m); - } else { - painter.drawLine(x + caret_width_, m - d, x + caret_width_ + d, m); - painter.drawLine(x + caret_width_, m + d, x + caret_width_ + d, m); - } - } - } - - void update(int x, int y, int h, bool l_shape, - bool rtl, bool completable) - { - color_ = guiApp->colorCache().get(Color_cursor); - l_shape_ = l_shape; - rtl_ = rtl; - completable_ = completable; - x_ = x; - - // extension to left and right - int l = 0; - int r = 0; - - // RTL/LTR indication - if (l_shape_) { - if (rtl) - l += h / 3; - else - r += h / 3; - } - - // completion triangle - if (completable_) { - if (rtl) - l = max(l, TabIndicatorWidth); - else - r = max(r, TabIndicatorWidth); - } - - //FIXME: LyXRC::cursor_width should be caret_width - caret_width_ = lyxrc.cursor_width - ? lyxrc.cursor_width - : 1 + int((lyxrc.currentZoom + 50) / 200.0); - - // compute overall rectangle - rect_ = QRect(x - l, y, caret_width_ + r + l, h); - } - - QRect const & rect() { return rect_; } - -private: - /// caret is in RTL or LTR text - bool rtl_; - /// indication for RTL or LTR - bool l_shape_; - /// triangle to show that a completion is available - bool completable_; - /// - QColor color_; - /// rectangle, possibly with l_shape and completion triangle - QRect rect_; - /// x position (were the vertical line is drawn) - int x_; - /// the width of the vertical blinking bar - int caret_width_; -}; - - // 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() @@ -239,26 +130,15 @@ SyntheticMouseEvent::SyntheticMouseEvent() GuiWorkArea::Private::Private(GuiWorkArea * parent) -: p(parent), buffer_view_(0), lyx_view_(0), caret_(0), - 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), - clean_(true), externally_modified_(false) -{ -/* Qt on macOS and Wayland does not respect the - * Qt::WA_OpaquePaintEvent attribute and resets the widget backing - * store at each update. Therefore, we use our own backing store in - * these two cases. */ -#if QT_VERSION >= 0x050000 + : p(parent), completer_(new GuiCompleter(p, p)) +{ + /* Qt on macOS and Wayland does not respect the + * Qt::WA_OpaquePaintEvent attribute and resets the widget backing + * store at each update. Therefore, we use our own backing store + * in these two cases. + */ use_backingstore_ = guiApp->platformName() == "cocoa" || guiApp->platformName().contains("wayland"); -#else -# ifdef Q_OS_MAC - use_backingstore_ = true; -# else - use_backingstore_ = false; -# endif -#endif int const time = QApplication::cursorFlashTime() / 2; if (time > 0) { @@ -278,7 +158,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_; @@ -328,7 +207,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); @@ -380,7 +258,7 @@ void GuiWorkArea::setBuffer(Buffer & buffer) buffer.workAreaManager().add(this); // HACK: Prevents an additional redraw when the scrollbar pops up - // which regularily happens on documents with more than one page. + // which regularly happens on documents with more than one page. // The policy should be set to "Qt::ScrollBarAsNeeded" soon. // Since we have no geometry information yet, we assume that // a document needs a scrollbar if there is more then four @@ -408,12 +286,9 @@ void GuiWorkArea::close() void GuiWorkArea::setFullScreen(bool full_screen) { d->buffer_view_->setFullScreen(full_screen); - setFrameStyle(QFrame::NoFrame); - if (full_screen) { - setFrameStyle(QFrame::NoFrame); - if (lyxrc.full_screen_scrollbar) - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - } else + if (full_screen && lyxrc.full_screen_scrollbar) + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + else setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } @@ -439,6 +314,16 @@ void GuiWorkArea::stopBlinkingCaret() void GuiWorkArea::startBlinkingCaret() { + // do not show the cursor if the view is busy + if (view().busy()) + return; + + // Don't start blinking if the cursor isn't on screen, unless we + // are not ready to know whether the cursor is on screen. + if (!d->buffer_view_->buffer().undo().activeUndoGroup() + && !d->buffer_view_->caretInView()) + return; + d->showCaret(); // Avoid blinking when debugging PAINTING, since it creates too much noise @@ -480,7 +365,7 @@ void GuiWorkArea::scheduleRedraw(bool update_metrics) // update caret position, because otherwise it has to wait until // the blinking interval is over - d->updateCaretGeometry(); + d->resetCaret(); LYXERR(Debug::WORKAREA, "WorkArea::redraw screen"); viewport()->update(); @@ -580,12 +465,12 @@ void GuiWorkArea::Private::resizeBufferView() buffer_view_->resize(p->viewport()->width(), p->viewport()->height()); if (caret_in_view) buffer_view_->scrollToCursor(); - updateCaretGeometry(); + resetCaret(); // Update scrollbars which might have changed due different // BufferView dimension. This is especially important when the // BufferView goes from zero-size to the real-size for the first time, - // as the scrollbar paramters are then set for the first time. + // as the scrollbar parameters are then set for the first time. updateScrollbar(); need_resize_ = false; @@ -598,64 +483,87 @@ void GuiWorkArea::Private::resizeBufferView() } -void GuiWorkArea::Private::updateCaretGeometry() +void GuiWorkArea::Private::resetCaret() { + // Don't start blinking if the cursor isn't on screen. if (!buffer_view_->caretInView()) return; - Point point; - int h = 0; - buffer_view_->caretPosAndHeight(point, h); - - // RTL or not RTL - bool l_shape = false; - Font const & realfont = buffer_view_->cursor().real_current_font; - BufferParams const & bp = buffer_view_->buffer().params(); - bool const samelang = realfont.language() == bp.language; - bool const isrtl = realfont.isVisibleRightToLeft(); - - if (!samelang || isrtl != bp.language->rightToLeft()) - l_shape = true; - - // The ERT language hack needs fixing up - if (realfont.language() == latex_language) - l_shape = false; - - // show caret on screen - Cursor & cur = buffer_view_->cursor(); - bool completable = cur.inset().showCompletionCursor() + // completion indicator + Cursor const & cur = buffer_view_->cursor(); + bool const completable = cur.inset().showCompletionCursor() && completer_->completionAvailable() && !completer_->popupVisible() && !completer_->inlineVisible(); - caret_visible_ = true; - caret_->update(point.x_, point.y_, h, l_shape, isrtl, completable); + buffer_view_->buildCaretGeometry(completable); + + needs_caret_geometry_update_ = true; + caret_visible_ = true; } -void GuiWorkArea::Private::showCaret(bool show) +void GuiWorkArea::Private::updateCaretGeometry() { - if (caret_visible_ == show) - return; - caret_visible_ = show; - - /** - * Do not trigger the painting machinery if either - * 1. the view is busy (no updates at all) - * 2. The we are not ready because document is being modified (see bug #11763) - * 3. The caret is outside of screen anyway. - */ - if (p->view().busy() - || buffer_view_->buffer().undo().activeUndoGroup() + // we cannot update geometry if not ready and we do not need to if + // caret is not in view. + if (buffer_view_->buffer().undo().activeUndoGroup() || !buffer_view_->caretInView()) return; + + needs_caret_geometry_update_ = false; +} + + +void GuiWorkArea::Private::showCaret() +{ if (caret_visible_) - updateCaretGeometry(); + return; + + resetCaret(); p->viewport()->update(); } +void GuiWorkArea::Private::hideCaret() +{ + if (!caret_visible_) + return; + + caret_visible_ = false; + //if (!qApp->focusWidget()) + p->viewport()->update(); +} + + +/* 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 @@ -841,6 +749,10 @@ void GuiWorkArea::mouseReleaseEvent(QMouseEvent * e) FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(), q_button_state(e->button()), q_key_state(e->modifiers())); +#if (QT_VERSION > QT_VERSION_CHECK(5,10,1) && \ + QT_VERSION < QT_VERSION_CHECK(5,15,1)) + d->synthetic_mouse_event_.cmd = cmd; // QtBug QAbstractScrollArea::mouseMoveEvent +#endif d->dispatch(cmd); e->accept(); } @@ -848,6 +760,21 @@ void GuiWorkArea::mouseReleaseEvent(QMouseEvent * e) void GuiWorkArea::mouseMoveEvent(QMouseEvent * e) { +#if (QT_VERSION > QT_VERSION_CHECK(5,10,1) && \ + QT_VERSION < QT_VERSION_CHECK(5,15,1)) + // cancel the event if the coordinates didn't change, this is due to QtBug + // QAbstractScrollArea::mouseMoveEvent, the event is triggered falsely when quickly + // double tapping a touchpad. To test: try to select a word by quickly double tapping + // on a touchpad while hovering the cursor over that word in the work area. + // This bug does not occur on Qt versions 5.10.1 and below. Only Windows seems to be affected. + // ML thread: https://www.mail-archive.com/lyx-devel@lists.lyx.org/msg211699.html + // Qt bugtracker: https://bugreports.qt.io/browse/QTBUG-85431 + // Bug was fixed in Qt 5.15.1 + if (e->x() == d->synthetic_mouse_event_.cmd.x() && // QtBug QAbstractScrollArea::mouseMoveEvent + e->y() == d->synthetic_mouse_event_.cmd.y()) // QtBug QAbstractScrollArea::mouseMoveEvent + return; // QtBug QAbstractScrollArea::mouseMoveEvent +#endif + // we kill the triple click if we move doubleClickTimeout(); FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(), @@ -1049,7 +976,6 @@ void GuiWorkArea::generateSyntheticMouseEvent() cur.boundary(bound); d->buffer_view_->buffer().changed(false); - return; } @@ -1185,9 +1111,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_->rect().left(); - int cur_y = caret_->rect().bottom(); + 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. @@ -1195,7 +1123,7 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain) bool cursor_is_visible = false; for (auto const & attr : preedit_attr_) { if (attr.type == QInputMethodEvent::Cursor) { - cursor_pos = attr.start; + cursor_pos = size_t(attr.start); cursor_is_visible = attr.length != 0; break; } @@ -1204,7 +1132,7 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain) size_t const preedit_length = preedit_string_.length(); // get position of selection in input method. - // FIXME: isn't there a way to do this simplier? + // FIXME: isn't there a simpler way to do this? // rStart : cursor position in selected string in IM. size_t rStart = 0; // rLength : selected string length in IM. @@ -1214,8 +1142,8 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain) if (attr.type == QInputMethodEvent::TextFormat) { if (attr.start <= int(cursor_pos) && int(cursor_pos) < attr.start + attr.length) { - rStart = attr.start; - rLength = attr.length; + rStart = size_t(attr.start); + rLength = size_t(attr.length); if (!cursor_is_visible) cursor_pos += rLength; break; @@ -1240,7 +1168,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 @@ -1332,8 +1260,14 @@ void GuiWorkArea::paintEvent(QPaintEvent * ev) d->paintPreeditText(pain); // and the caret - if (d->caret_visible_) - d->caret_->draw(pain, d->buffer_view_->horizScrollOffset()); + // 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->caret_visible_) { + if (d->needs_caret_geometry_update_) + d->updateCaretGeometry(); + d->drawCaret(pain, d->buffer_view_->horizScrollOffset()); + } d->updateScreen(ev->rect()); @@ -1353,7 +1287,7 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e) FuncRequest::KEYBOARD); dispatch(cmd); // FIXME: this is supposed to remove traces from preedit - // string. Can we avoid calling it explicitely? + // string. Can we avoid calling it explicitly? d->buffer_view_->updateMetrics(); } @@ -1375,10 +1309,11 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e) // redraw area of preedit string. - int height = d->caret_->rect().height(); - int cur_y = d->caret_->rect().bottom(); - viewport()->update(0, cur_y - height, 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; @@ -1393,20 +1328,21 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e) QVariant GuiWorkArea::inputMethodQuery(Qt::InputMethodQuery query) const { - QRect cur_r(0, 0, 0, 0); switch (query) { // this is the CJK-specific composition window position and // the context menu position when the menu key is pressed. - case Qt::ImMicroFocus: - cur_r = d->caret_->rect(); - if (d->preedit_lines_ != 1) - cur_r.moveLeft(10); - cur_r.moveBottom(cur_r.bottom() - + cur_r.height() * (d->preedit_lines_ - 1)); - // return lower right of caret in LyX. - return cur_r; - default: - return QWidget::inputMethodQuery(query); +#if (QT_VERSION < 0x050000) + case Qt::ImMicroFocus: { +#else + case Qt::ImCursorRectangle: { +#endif + 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); } } @@ -1613,7 +1549,7 @@ TabWorkArea::TabWorkArea(QWidget * parent) void TabWorkArea::mousePressEvent(QMouseEvent *me) { - if (me->button() == Qt::MidButton) + if (me->button() == Qt::MiddleButton) midpressed_tab_ = tabBar()->tabAt(me->pos()); else QTabWidget::mousePressEvent(me); @@ -1622,7 +1558,7 @@ void TabWorkArea::mousePressEvent(QMouseEvent *me) void TabWorkArea::mouseReleaseEvent(QMouseEvent *me) { - if (me->button() == Qt::MidButton) { + if (me->button() == Qt::MiddleButton) { int const midreleased_tab = tabBar()->tabAt(me->pos()); if (midpressed_tab_ == midreleased_tab && posIsTab(me->pos())) closeTab(midreleased_tab); @@ -1741,11 +1677,11 @@ GuiWorkArea * TabWorkArea::workArea(Buffer & buffer) const // showing the same buffer. for (int i = 0; i != count(); ++i) { GuiWorkArea * wa = workArea(i); - LASSERT(wa, return 0); + LASSERT(wa, return nullptr); if (&wa->bufferView().buffer() == &buffer) return wa; } - return 0; + return nullptr; } @@ -2000,7 +1936,7 @@ bool operator==(DisplayPath const & a, DisplayPath const & b) void TabWorkArea::updateTabTexts() { - size_t n = count(); + int const n = count(); if (n == 0) return; std::list paths; @@ -2008,7 +1944,7 @@ void TabWorkArea::updateTabTexts() // collect full names first: path into postfix, empty prefix and // filename without extension - for (size_t i = 0; i < n; ++i) { + for (int i = 0; i < n; ++i) { GuiWorkArea * i_wa = workArea(i); FileName const fn = i_wa->bufferView().buffer().fileName(); paths.push_back(DisplayPath(i, fn)); @@ -2182,7 +2118,7 @@ void GuiWorkAreaContainer::updateDisplay() } -void GuiWorkAreaContainer::dispatch(FuncRequest f) const +void GuiWorkAreaContainer::dispatch(FuncRequest const & f) const { lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH, wa_->bufferView().buffer().absFileName()));