]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiWorkArea.cpp
Use <cstdint> instead of <boost/cstdint.hpp>
[lyx.git] / src / frontends / qt4 / GuiWorkArea.cpp
index 3d169d02ee134a612c8303fc95b49f0068bae242..001056df72abb1bdd551b007ae039982d010c59a 100644 (file)
@@ -150,9 +150,9 @@ public:
                painter.setPen(color_);
                if (l_shape_) {
                        if (rtl_)
-                               painter.drawLine(x_, bot, x_ - l, bot);
+                               painter.drawLine(x_, bot, x_ - l + 1, bot);
                        else
-                               painter.drawLine(x_, bot, x_ + caret_width_ + r, bot);
+                               painter.drawLine(x_, bot, x_ + caret_width_ + r - 1, bot);
                }
 
                // draw completion triangle
@@ -270,12 +270,14 @@ GuiWorkArea::Private::~Private()
 GuiWorkArea::GuiWorkArea(QWidget * /* w */)
 : d(new Private(this))
 {
+       new CompressorProxy(this); // not a leak
 }
 
 
 GuiWorkArea::GuiWorkArea(Buffer & buffer, GuiView & gv)
 : d(new Private(this))
 {
+       new CompressorProxy(this); // not a leak
        setGuiView(gv);
        buffer.params().display_pixel_ratio = theGuiApp()->pixelRatio();
        setBuffer(buffer);
@@ -637,7 +639,7 @@ void GuiWorkArea::Private::showCaret()
                return;
 
        updateCaretGeometry();
-       p->viewport()->update(caret_->rect());
+       p->viewport()->update();
 }
 
 
@@ -648,7 +650,7 @@ void GuiWorkArea::Private::hideCaret()
 
        caret_visible_ = false;
        //if (!qApp->focusWidget())
-               p->viewport()->update(caret_->rect());
+               p->viewport()->update();
 }
 
 
@@ -658,10 +660,10 @@ void GuiWorkArea::Private::updateScrollbar()
        // the signal valueChanged. (#10311)
        QObject::disconnect(p->verticalScrollBar(), SIGNAL(valueChanged(int)),
                            p, SLOT(scrollTo(int)));
-       ScrollbarParameters const & scroll_ = buffer_view_->scrollbarParameters();
-       p->verticalScrollBar()->setRange(scroll_.min, scroll_.max);
-       p->verticalScrollBar()->setPageStep(scroll_.page_step);
-       p->verticalScrollBar()->setSingleStep(scroll_.single_step);
+       ScrollbarParameters const & scroll = buffer_view_->scrollbarParameters();
+       p->verticalScrollBar()->setRange(scroll.min, scroll.max);
+       p->verticalScrollBar()->setPageStep(scroll.page_step);
+       p->verticalScrollBar()->setSingleStep(scroll.single_step);
        p->verticalScrollBar()->setSliderPosition(0);
        // Connect to the vertical scroll bar
        QObject::connect(p->verticalScrollBar(), SIGNAL(valueChanged(int)),
@@ -898,7 +900,23 @@ 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())) {
+               ev->accept();
+               return;
+       }
+       double const delta = aDelta.y() / 120.0;
+#endif
+
        bool zoom = false;
        switch (lyxrc.scroll_wheel_zoom) {
        case LyXRC::SCROLL_WHEEL_ZOOM_CTRL:
@@ -1033,6 +1051,39 @@ void GuiWorkArea::generateSyntheticMouseEvent()
 }
 
 
+// CompressorProxy adapted from Kuba Ober https://stackoverflow.com/a/21006207
+CompressorProxy::CompressorProxy(GuiWorkArea * wa) : QObject(wa), flag_(false)
+{
+       qRegisterMetaType<KeySymbol>("KeySymbol");
+       qRegisterMetaType<KeyModifier>("KeyModifier");
+       connect(wa, SIGNAL(compressKeySym(KeySymbol, KeyModifier, bool)),
+               this, SLOT(slot(KeySymbol, KeyModifier, bool)),
+               Qt::QueuedConnection);
+       connect(this, SIGNAL(signal(KeySymbol, KeyModifier)),
+               wa, SLOT(processKeySym(KeySymbol, KeyModifier)));
+}
+
+
+bool CompressorProxy::emitCheck(bool isAutoRepeat)
+{
+       flag_ = true;
+       if (isAutoRepeat)
+               QCoreApplication::sendPostedEvents(this, QEvent::MetaCall); // recurse
+       bool result = flag_;
+       flag_ = false;
+       return result;
+}
+
+
+void CompressorProxy::slot(KeySymbol sym, KeyModifier mod, bool isAutoRepeat)
+{
+       if (emitCheck(isAutoRepeat))
+               Q_EMIT signal(sym, mod);
+       else
+               LYXERR(Debug::KEY, "system is busy: autoRepeat key event ignored");
+}
+
+
 void GuiWorkArea::keyPressEvent(QKeyEvent * ev)
 {
        // this is also called for ShortcutOverride events. In this case, one must
@@ -1040,13 +1091,16 @@ void GuiWorkArea::keyPressEvent(QKeyEvent * ev)
        bool const act = (ev->type() != QEvent::ShortcutOverride);
 
        // Do not process here some keys if dialog_mode_ is set
-       if (d->dialog_mode_
+       bool const for_dialog_mode = d->dialog_mode_
                && (ev->modifiers() == Qt::NoModifier
                    || ev->modifiers() == Qt::ShiftModifier)
                && (ev->key() == Qt::Key_Escape
                    || ev->key() == Qt::Key_Enter
-                   || ev->key() == Qt::Key_Return)
-           ) {
+                   || ev->key() == Qt::Key_Return);
+       // also do not use autoRepeat to input shortcuts
+       bool const autoRepeat = ev->isAutoRepeat();
+
+       if (for_dialog_mode || (!act && autoRepeat)) {
                ev->ignore();
                return;
        }
@@ -1063,51 +1117,31 @@ void GuiWorkArea::keyPressEvent(QKeyEvent * ev)
                }
        }
 
-       // do nothing if there are other events
-       // (the auto repeated events come too fast)
-       // it looks like this is only needed on X11
-#if defined(Q_WS_X11) || defined(QPA_XCB)
-       // FIXME: this is a weird way to implement event compression. Also, this is
-       // broken with IBus.
-       if (act && qApp->hasPendingEvents() && ev->isAutoRepeat()) {
-               switch (ev->key()) {
-               case Qt::Key_PageDown:
-               case Qt::Key_PageUp:
-               case Qt::Key_Left:
-               case Qt::Key_Right:
-               case Qt::Key_Up:
-               case Qt::Key_Down:
-                       LYXERR(Debug::KEY, "system is busy: scroll key event ignored");
-                       ev->ignore();
-                       return;
-               }
-       }
-#endif
-
        KeyModifier const m = q_key_state(ev->modifiers());
 
-       std::string str;
-       if (m & ShiftModifier)
-               str += "Shift-";
-       if (m & ControlModifier)
-               str += "Control-";
-       if (m & AltModifier)
-               str += "Alt-";
-       if (m & MetaModifier)
-               str += "Meta-";
-
-       if (act)
+       if (act && lyxerr.debugging(Debug::KEY)) {
+               std::string str;
+               if (m & ShiftModifier)
+                       str += "Shift-";
+               if (m & ControlModifier)
+                       str += "Control-";
+               if (m & AltModifier)
+                       str += "Alt-";
+               if (m & MetaModifier)
+                       str += "Meta-";
                LYXERR(Debug::KEY, " count: " << ev->count() << " text: " << ev->text()
                       << " isAutoRepeat: " << ev->isAutoRepeat() << " key: " << ev->key()
                       << " keyState: " << str);
+       }
 
        KeySymbol sym;
        setKeySymbol(&sym, ev);
        if (sym.isOK()) {
                if (act) {
-                       processKeySym(sym, m);
+                       Q_EMIT compressKeySym(sym, m, autoRepeat);
                        ev->accept();
                } else
+                       // here, !autoRepeat, as determined at the beginning
                        ev->setAccepted(queryKeySym(sym, m));
        } else {
                ev->ignore();
@@ -1229,6 +1263,20 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
 
 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).
+               d->updateScreen(ev->rect());
+               // Ignore this paint event, but request a new one for later.
+               viewport()->update(ev->rect());
+               ev->accept();
+               return;
+       }
+
        // LYXERR(Debug::PAINTING, "paintEvent begin: x: " << rc.x()
        //      << " y: " << rc.y() << " w: " << rc.width() << " h: " << rc.height());
 
@@ -1263,11 +1311,13 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
 
        // insert the processed text in the document (handles undo)
        if (!e->commitString().isEmpty()) {
-               d->buffer_view_->cursor().beginUndoGroup();
-               d->buffer_view_->cursor().insert(qstring_to_ucs4(e->commitString()));
+               FuncRequest cmd(LFUN_SELF_INSERT,
+                               qstring_to_ucs4(e->commitString()),
+                               FuncRequest::KEYBOARD);
+               dispatch(cmd);
+               // FIXME: this is supposed to remove traces from preedit
+               // string. Can we avoid calling it explicitely?
                d->buffer_view_->updateMetrics();
-               d->buffer_view_->cursor().endUndoGroup();
-               viewport()->update();
        }
 
        // Hide the caret during the test transformation.
@@ -1506,19 +1556,19 @@ TabWorkArea::TabWorkArea(QWidget * parent)
                this, SLOT(closeCurrentBuffer()));
        setCornerWidget(closeBufferButton, Qt::TopRightCorner);
 
-       // setup drag'n'drop
-       QTabBar* tb = new DragTabBar;
-       connect(tb, SIGNAL(tabMoveRequested(int, int)),
-               this, SLOT(moveTab(int, int)));
+       // set TabBar behaviour
+       QTabBar * tb = tabBar();
+       tb->setTabsClosable(!lyxrc.single_close_tab_button);
+       tb->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab);
        tb->setElideMode(Qt::ElideNone);
-       setTabBar(tb);
-
+       // allow dragging tabs
+       tb->setMovable(true);
        // make us responsible for the context menu of the tabbar
        tb->setContextMenuPolicy(Qt::CustomContextMenu);
        connect(tb, SIGNAL(customContextMenuRequested(const QPoint &)),
-               this, SLOT(showContextMenu(const QPoint &)));
+               this, SLOT(showContextMenu(const QPoint &)));
        connect(tb, SIGNAL(tabCloseRequested(int)),
-               this, SLOT(closeTab(int)));
+               this, SLOT(closeTab(int)));
 
        setUsesScrollButtons(true);
 }
@@ -1821,9 +1871,12 @@ public:
        DisplayPath(int tab, FileName const & filename)
                : tab_(tab)
        {
+               // Recode URL encoded chars via fromPercentEncoding()
                filename_ = (filename.extension() == "lyx") ?
-                       toqstr(filename.onlyFileNameWithoutExt())
-                       : toqstr(filename.onlyFileName());
+                       QString(QByteArray::fromPercentEncoding(
+                                       toqstr(filename.onlyFileNameWithoutExt()).toUtf8()))
+                       : QString(QByteArray::fromPercentEncoding(
+                                         toqstr(filename.onlyFileName()).toUtf8()));
                postfix_ = toqstr(filename.absoluteFilePath()).
                        split("/", QString::SkipEmptyParts);
                postfix_.pop_back();
@@ -2033,16 +2086,23 @@ void TabWorkArea::updateTabTexts()
 void TabWorkArea::showContextMenu(const QPoint & pos)
 {
        // which tab?
-       clicked_tab_ = static_cast<DragTabBar *>(tabBar())->tabAt(pos);
+       clicked_tab_ = tabBar()->tabAt(pos);
        if (clicked_tab_ == -1)
                return;
 
+       GuiWorkArea * wa = workArea(clicked_tab_);
+       LASSERT(wa, return);
+
        // show tab popup
        QMenu popup;
        popup.addAction(QIcon(getPixmap("images/", "hidetab", "svgz,png")),
                qt_("Hide tab"), this, SLOT(hideCurrentTab()));
-       popup.addAction(QIcon(getPixmap("images/", "closetab", "svgz,png")),
-               qt_("Close tab"), this, SLOT(closeCurrentBuffer()));
+
+       // 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()));
        popup.exec(tabBar()->mapToGlobal(pos));
 
        clicked_tab_ = -1;
@@ -2062,84 +2122,6 @@ void TabWorkArea::moveTab(int fromIndex, int toIndex)
 }
 
 
-DragTabBar::DragTabBar(QWidget* parent)
-       : QTabBar(parent)
-{
-       setAcceptDrops(true);
-       setTabsClosable(!lyxrc.single_close_tab_button);
-}
-
-
-void DragTabBar::mousePressEvent(QMouseEvent * event)
-{
-       if (event->button() == Qt::LeftButton)
-               dragStartPos_ = event->pos();
-       QTabBar::mousePressEvent(event);
-}
-
-
-void DragTabBar::mouseMoveEvent(QMouseEvent * event)
-{
-       // If the left button isn't pressed anymore then return
-       if (!(event->buttons() & Qt::LeftButton))
-               return;
-
-       // If the distance is too small then return
-       if ((event->pos() - dragStartPos_).manhattanLength()
-           < QApplication::startDragDistance())
-               return;
-
-       // did we hit something after all?
-       int tab = tabAt(dragStartPos_);
-       if (tab == -1)
-               return;
-
-       // simulate button release to remove highlight from button
-       int i = currentIndex();
-       QMouseEvent me(QEvent::MouseButtonRelease, dragStartPos_,
-               event->button(), event->buttons(), 0);
-       QTabBar::mouseReleaseEvent(&me);
-       setCurrentIndex(i);
-
-       // initiate Drag
-       QDrag * drag = new QDrag(this);
-       QMimeData * mimeData = new QMimeData;
-       // a crude way to distinguish tab-reodering drops from other ones
-       mimeData->setData("action", "tab-reordering") ;
-       drag->setMimeData(mimeData);
-
-       // get tab pixmap as cursor
-       QRect r = tabRect(tab);
-       QPixmap pixmap(r.size());
-       render(&pixmap, - r.topLeft());
-       drag->setPixmap(pixmap);
-       drag->exec();
-}
-
-
-void DragTabBar::dragEnterEvent(QDragEnterEvent * event)
-{
-       // Only accept if it's an tab-reordering request
-       QMimeData const * m = event->mimeData();
-       QStringList formats = m->formats();
-       if (formats.contains("action")
-           && m->data("action") == "tab-reordering")
-               event->acceptProposedAction();
-}
-
-
-void DragTabBar::dropEvent(QDropEvent * event)
-{
-       int fromIndex = tabAt(dragStartPos_);
-       int toIndex = tabAt(event->pos());
-
-       // Tell interested objects that
-       if (fromIndex != toIndex)
-               tabMoveRequested(fromIndex, toIndex);
-       event->acceptProposedAction();
-}
-
-
 GuiWorkAreaContainer::GuiWorkAreaContainer(GuiWorkArea * wa, QWidget * parent)
        : QWidget(parent), wa_(wa)
 {