]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt/GuiWorkArea.cpp
Introduce new helpers ParagraphMetrics::top/bottom
[lyx.git] / src / frontends / qt / GuiWorkArea.cpp
index 802fbc71b70d8867c6d5ae7284f1aa791b430121..7999dceda9fb6fb765d3b77a312c78f6ad847dee 100644 (file)
@@ -39,7 +39,6 @@
 #include "LyXVC.h"
 #include "Text.h"
 #include "TextMetrics.h"
-#include "Undo.h"
 #include "version.h"
 
 #include "support/convert.h"
 #include "frontends/WorkAreaManager.h"
 
 #include <QContextMenuEvent>
-#if (QT_VERSION < 0x050000)
-#include <QInputContext>
-#endif
 #include <QDrag>
 #include <QHelpEvent>
+#include <QInputMethod>
 #ifdef Q_OS_MAC
 #include <QProxyStyle>
 #endif
@@ -182,10 +179,10 @@ GuiWorkArea::GuiWorkArea(Buffer & buffer, GuiView & gv)
 
 double GuiWorkArea::pixelRatio() const
 {
-#if QT_VERSION >= 0x050000
-       return qt_scale_factor * devicePixelRatio();
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
+       return devicePixelRatioF();
 #else
-       return 1.0;
+       return devicePixelRatio();
 #endif
 }
 
@@ -202,7 +199,7 @@ void GuiWorkArea::init()
                });
 
        d->resetScreen();
-       // With Qt4.5 a mouse event will happen before the first paint event
+       // 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());
 
@@ -226,6 +223,8 @@ void GuiWorkArea::init()
        // Enables input methods for asian languages.
        // Must be set when creating custom text editing widgets.
        setAttribute(Qt::WA_InputMethodEnabled, true);
+       // obtain transformation information to reset it when LyX gets refocus
+       d->im_item_trans_ = d->im_->inputItemTransform();
 }
 
 
@@ -318,8 +317,7 @@ void GuiWorkArea::startBlinkingCaret()
 
        // 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())
+       if (!d->buffer_view_->busy() && !d->buffer_view_->caretInView())
                return;
 
        d->showCaret();
@@ -483,19 +481,11 @@ void GuiWorkArea::Private::resizeBufferView()
 
 void GuiWorkArea::Private::resetCaret()
 {
-       // Don't start blinking if the cursor isn't on screen.
-       if (!buffer_view_->caretInView())
+       // Don't start blinking if the cursor isn't on screen or the window
+       // does not have focus
+       if (!buffer_view_->caretInView() || !p->hasFocus())
                return;
 
-       // completion indicator
-       Cursor const & cur = buffer_view_->cursor();
-       bool const completable = cur.inset().showCompletionCursor()
-               && completer_->completionAvailable()
-               && !completer_->popupVisible()
-               && !completer_->inlineVisible();
-
-       buffer_view_->buildCaretGeometry(completable);
-
        needs_caret_geometry_update_ = true;
        caret_visible_ = true;
 }
@@ -505,10 +495,17 @@ void GuiWorkArea::Private::updateCaretGeometry()
 {
        // 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())
+       if (buffer_view_->busy() || !buffer_view_->caretInView())
                return;
 
+       // completion indicator
+       Cursor const & cur = buffer_view_->cursor();
+       bool const completable = cur.inset().showCompletionCursor()
+               && completer_->completionAvailable()
+               && !completer_->popupVisible()
+               && !completer_->inlineVisible();
+
+       buffer_view_->buildCaretGeometry(completable);
 
        needs_caret_geometry_update_ = false;
 }
@@ -591,10 +588,6 @@ void GuiWorkArea::scrollTo(int value)
        }
        // Show the caret immediately after any operation.
        startBlinkingCaret();
-       // FIXME QT5
-#ifdef Q_WS_X11
-       QApplication::syncX();
-#endif
 }
 
 
@@ -666,6 +659,10 @@ void GuiWorkArea::contextMenuEvent(QContextMenuEvent * e)
                                        ++pos.rx();
                        }
                }
+               if (e->reason() == QContextMenuEvent::Keyboard)
+                       // Subtract the top margin, see #12811
+                       pos.setY(pos.y() - d->buffer_view_->topMargin());
+
                name = d->buffer_view_->contextMenu(pos.x(), pos.y());
        }
 
@@ -724,10 +721,6 @@ void GuiWorkArea::mousePressEvent(QMouseEvent * e)
                return;
        }
 
-#if (QT_VERSION < 0x050000) && !defined(__HAIKU__)
-       inputContext()->reset();
-#endif
-
 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
        FuncRequest const cmd(LFUN_MOUSE_PRESS, e->position().x(), e->position().y(),
 #else
@@ -860,13 +853,6 @@ void GuiWorkArea::wheelEvent(QWheelEvent * ev)
        // Wheel rotation by one notch results in a delta() of 120 (see
        // documentation of QWheelEvent)
        // But first we have to ignore horizontal scroll events.
-#if QT_VERSION < 0x050000
-       if (ev->orientation() == Qt::Horizontal) {
-               ev->accept();
-               return;
-       }
-       double const delta = ev->delta() / 120.0;
-#else
        QPoint const aDelta = ev->angleDelta();
        // skip horizontal wheel event
        if (abs(aDelta.x()) > abs(aDelta.y())) {
@@ -874,7 +860,6 @@ void GuiWorkArea::wheelEvent(QWheelEvent * ev)
                return;
        }
        double const delta = aDelta.y() / 120.0;
-#endif
 
        bool zoom = false;
        switch (lyxrc.scroll_wheel_zoom) {
@@ -986,7 +971,7 @@ void GuiWorkArea::generateSyntheticMouseEvent()
        // Find the row at which we set the cursor.
        RowList::const_iterator rit = pm.rows().begin();
        RowList::const_iterator rlast = pm.rows().end();
-       int yy = pm.position() - pm.ascent();
+       int yy = pm.top();
        for (--rlast; rit != rlast; ++rit) {
                int h = rit->height();
                if ((up && yy + h > 0)
@@ -1148,8 +1133,18 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
        Point point;
        Dimension dim;
        buffer_view_->caretPosAndDim(point, dim);
-       int cur_x = point.x_;
+       int cur_x = point.x_ - dim.width();
        int cur_y = point.y_ + dim.height();
+       // lower margin of the preedit area to separate the candidate window
+       // report to IM the height of preedit rectangle larger than the actual by
+       // preedit_lower_margin so that the conversion suggestion window does not
+       // hide the underline of the preedit text
+       int preedit_lower_margin = 3;
+       // reset item transformation since it gets wrong after the item get
+       // lost and regain focus.
+       im_->setInputItemTransform(im_item_trans_);
+       // force fulldraw to remove previous paint remaining on screen
+       buffer_view_->processUpdateFlags(Update::ForceDraw);
 
        // get attributes of input method cursor.
        // cursor_pos : cursor position in preedit string.
@@ -1190,7 +1185,8 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
                rLength = 0;
        }
 
-       int const right_margin = buffer_view_->rightMargin();
+       int const text_width = p->viewport()->width() - buffer_view_->rightMargin()
+                       - buffer_view_->leftMargin();
        Painter::preedit_style ps;
        // Most often there would be only one line:
        preedit_lines_ = 1;
@@ -1200,8 +1196,8 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
                ps = Painter::preedit_default;
 
                // 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;
+               if (cur_x + fm.width(typed_char) > p->viewport()->width() - buffer_view_->rightMargin()) {
+                       cur_x = buffer_view_->leftMargin();
                        cur_y += dim.height() + 1;
                        ++preedit_lines_;
                }
@@ -1212,8 +1208,9 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
                // FIXME: should be put out of the loop.
                if (pos >= rStart
                        && pos < rStart + rLength
-                       && !(cursor_pos < rLength && rLength == preedit_length))
+                       && !(cursor_pos < rLength && rLength == preedit_length)) {
                        ps = Painter::preedit_selecting;
+               }
 
                if (pos == cursor_pos
                        && (cursor_pos < rLength && rLength == preedit_length))
@@ -1222,19 +1219,60 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
                // draw one character and update cur_x.
                cur_x += pain.preeditText(cur_x, cur_y, typed_char, font, ps);
        }
+
+       // the selection candidate window follows the position of Qt::ImCursorRectangle
+       // so we set it here in preparation for InputMethodQuery.
+       if(rLength > 0) {
+               // candidate selection mode
+               div_t mod_cur, mod_anc;
+               // width of preedit string in pixels
+               int preedit_width = fm.width(preedit_string_);
+               // calculate for modular so that its remainder and quotient become horizontal
+               // and vertical adjustment factor respectively
+               // FIXME: divider should be (text_width - font width of the end-of-line character)
+               //        however, since it's very rare that preedit becomes so long that it goes over
+               //        more than two lines, current error of position is more or less negligible
+               mod_cur = div(text_width + cur_x - (preedit_width - fm.width(preedit_string_.substr(0, rStart))),
+                               text_width);
+               mod_anc = div(text_width + cur_x - (preedit_width - fm.width(preedit_string_.substr(0, rStart + rLength))),
+                               text_width);
+               // Obtain cursor and anchor rectangles to bind starting and ending points of selection.
+               // Since the remainder of div moves from positive to negative as the line becomes longer
+               // while its quotient repeats zero twice, we need to take it into account by conditioning.
+               if (mod_cur.rem >= 0)
+                       im_cursor_rect_ = QRectF(mod_cur.rem,
+                                       (cur_y - dim.height()) + (dim.height() + 1) * (mod_cur.quot - 1), 1,
+                                       dim.height() + preedit_lower_margin);
+               else
+                       im_cursor_rect_ = QRectF(text_width + mod_cur.rem,
+                                       (cur_y - dim.height()) + (dim.height() + 1) * (mod_cur.quot - 2), 1,
+                                       dim.height() + preedit_lower_margin);
+               if (mod_anc.rem >= 0)
+                       im_anchor_rect_ = QRectF(mod_anc.rem,
+                                       point.y_ + (dim.height() + 1) * (mod_anc.quot - 1), 1,
+                                       dim.height() + preedit_lower_margin );
+               else
+                       im_anchor_rect_ = QRectF(text_width + mod_anc.rem,
+                                       point.y_ + (dim.height() + 1) * (mod_anc.quot - 2), 1,
+                                       dim.height() + preedit_lower_margin );
+       } else {
+               im_cursor_rect_ =       QRectF(point.x_, point.y_, 1, dim.height() + preedit_lower_margin);
+               im_anchor_rect_ = im_cursor_rect_;
+       }
+       // Urge platform input method to make inputMethodQuery to check the values
+       // set above
+       im_->update(Qt::ImQueryInput);
 }
 
 
 void GuiWorkArea::Private::resetScreen()
 {
        if (use_backingstore_) {
-               int const pr = p->pixelRatio();
-               screen_ = QImage(pr * p->viewport()->width(),
-                                pr * p->viewport()->height(),
+               double const pr = p->pixelRatio();
+               screen_ = QImage(int(pr * p->viewport()->width()),
+                                int(pr * p->viewport()->height()),
                                 QImage::Format_ARGB32_Premultiplied);
-#  if QT_VERSION >= 0x050000
                screen_.setDevicePixelRatio(pr);
-#  endif
        }
 }
 
@@ -1265,10 +1303,11 @@ void GuiWorkArea::paintEvent(QPaintEvent * ev)
        // Do not trigger the painting machinery if we are not ready (see
        // bug #10989). The second test triggers when in the middle of a
        // dispatch operation.
-       if (view().busy() || d->buffer_view_->buffer().undo().activeUndoGroup()) {
-               // Since macOS has turned the screen black at this point, our
-               // backing store has to be copied to screen (this is a no-op
-               // except on macOS).
+       if (view().busy() || d->buffer_view_->busy()) {
+               // Since the screen may have turned black at this point, our
+               // backing store has to be copied to screen. This is a no-op
+               // except when our drawing strategy is "backingstore" (macOS,
+               // Wayland, or set in prefs).
                d->updateScreen(ev->rect());
                // Ignore this paint event, but request a new one for later.
                viewport()->update(ev->rect());
@@ -1375,19 +1414,20 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
 
 QVariant GuiWorkArea::inputMethodQuery(Qt::InputMethodQuery query) const
 {
+       LYXERR(Debug::INFO, "incoming InputMethodQuery Value: 0x" << std::hex << query);
        switch (query) {
-               // this is the CJK-specific composition window position and
-               // the context menu position when the menu key is pressed.
-#if (QT_VERSION < 0x050000)
-       case Qt::ImMicroFocus: {
-#else
+       // this is the CJK-specific composition window position and
+       // the context menu position when the menu key is pressed.
        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());
+               return QVariant(d->im_cursor_rect_);
+               break;
+       }
+#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
+       case Qt::ImAnchorRectangle: {
+               return QVariant(d->im_anchor_rect_);
+               break;
        }
+#endif
        default:
                return QWidget::inputMethodQuery(query);
        }
@@ -1530,6 +1570,9 @@ TabWorkArea::TabWorkArea(QWidget * parent)
 
        QObject::connect(this, SIGNAL(currentChanged(int)),
                this, SLOT(on_currentTabChanged(int)));
+       // Fix for #11835
+       QObject::connect(this, SIGNAL(tabBarClicked(int)),
+               this, SLOT(on_currentTabChanged(int)));
 
        closeBufferButton = new QToolButton(this);
        closeBufferButton->setPalette(pal);
@@ -1595,11 +1638,7 @@ void TabWorkArea::paintEvent(QPaintEvent * event)
                // painting of the frame of the tab widget.
                // This is needed for gtk style in Qt.
                QStylePainter p(this);
-#if QT_VERSION < 0x050000
-               QStyleOptionTabWidgetFrameV2 opt;
-#else
                QStyleOptionTabWidgetFrame opt;
-#endif
                initStyleOption(&opt);
                opt.rect = style()->subElementRect(QStyle::SE_TabWidgetTabPane,
                        &opt, this);
@@ -2111,13 +2150,13 @@ void TabWorkArea::showContextMenu(const QPoint & pos)
        // show tab popup
        QMenu popup;
        popup.addAction(QIcon(getPixmap("images/", "hidetab", "svgz,png")),
-               qt_("Hide tab"), this, SLOT(hideCurrentTab()));
+               qt_("&Hide Tab"), this, SLOT(hideCurrentTab()));
 
        // we want to show the 'close' option only if this is not a child buffer.
        Buffer const & buf = wa->bufferView().buffer();
        if (!buf.parent())
                popup.addAction(QIcon(getPixmap("images/", "closetab", "svgz,png")),
-                       qt_("Close tab"), this, SLOT(closeCurrentBuffer()));
+                       qt_("&Close Tab"), this, SLOT(closeCurrentBuffer()));
        popup.exec(tabBar()->mapToGlobal(pos));
 
        clicked_tab_ = -1;
@@ -2147,7 +2186,8 @@ GuiWorkAreaContainer::GuiWorkAreaContainer(GuiWorkArea * wa, QWidget * parent)
                this, SLOT(updateDisplay()));
        connect(reloadPB, SIGNAL(clicked()), this, SLOT(reload()));
        connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore()));
-       setMessageColour({notificationFrame}, {reloadPB, ignorePB});
+       setMessageColour({notificationFrame, externalModificationLabel},
+                        {reloadPB, ignorePB});
        updateDisplay();
 }