]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiWorkArea.cpp
QDialogButtonBox for the remaining dialogs.
[lyx.git] / src / frontends / qt4 / GuiWorkArea.cpp
index bd3104c2509becef89527debc4aa3951f1fa410f..e6d993c2735ad8bd16eb43c394b7e91c51ee1c51 100644 (file)
@@ -237,8 +237,8 @@ SyntheticMouseEvent::SyntheticMouseEvent()
 GuiWorkArea::Private::Private(GuiWorkArea * parent)
 : p(parent), buffer_view_(0), lyx_view_(0),
   caret_(0), caret_visible_(false),
-  need_resize_(false), schedule_redraw_(false), preedit_lines_(1),
-  pixel_ratio_(1.0),
+  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)
 {
@@ -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);
@@ -304,6 +306,7 @@ void GuiWorkArea::init()
                        generateSyntheticMouseEvent();
                });
 
+       d->resetScreen();
        // 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());
@@ -431,12 +434,15 @@ void GuiWorkArea::startBlinkingCaret()
 
        d->showCaret();
 
-       //we're not supposed to cache this value.
-       int const time = QApplication::cursorFlashTime() / 2;
-       if (time <= 0)
-               return;
-       d->caret_timeout_.setInterval(time);
-       d->caret_timeout_.start();
+       // Avoid blinking when debugging PAINTING, since it creates too much noise
+       if (!lyxerr.debugging(Debug::PAINTING)) {
+               // we are not supposed to cache this value.
+               int const time = QApplication::cursorFlashTime() / 2;
+               if (time <= 0)
+                       return;
+               d->caret_timeout_.setInterval(time);
+               d->caret_timeout_.start();
+       }
 }
 
 
@@ -449,7 +455,7 @@ void GuiWorkArea::toggleCaret()
 }
 
 
-void GuiWorkArea::redraw(bool update_metrics)
+void GuiWorkArea::scheduleRedraw(bool update_metrics)
 {
        if (!isVisible())
                // No need to redraw in this case.
@@ -467,14 +473,12 @@ void GuiWorkArea::redraw(bool update_metrics)
 
        // update caret position, because otherwise it has to wait until
        // the blinking interval is over
-       if (d->caret_visible_) {
-               d->hideCaret();
-               d->showCaret();
-       }
+       d->updateCaretGeometry();
 
        LYXERR(Debug::WORKAREA, "WorkArea::redraw screen");
        viewport()->update();
 
+       /// FIXME: is this still true now that paintEvent does the actual painting?
        /// \warning: scrollbar updating *must* be done after the BufferView is drawn
        /// because \c BufferView::updateScrollbar() is called in \c BufferView::draw().
        d->updateScrollbar();
@@ -572,7 +576,7 @@ void GuiWorkArea::Private::resizeBufferView()
        buffer_view_->resize(p->viewport()->width(), p->viewport()->height());
        if (caret_in_view)
                buffer_view_->scrollToCursor();
-       p->viewport()->update();
+       updateCaretGeometry();
 
        // Update scrollbars which might have changed due different
        // BufferView dimension. This is especially important when the
@@ -590,11 +594,8 @@ void GuiWorkArea::Private::resizeBufferView()
 }
 
 
-void GuiWorkArea::Private::showCaret()
+void GuiWorkArea::Private::updateCaretGeometry()
 {
-       if (caret_visible_)
-               return;
-
        Point point;
        int h = 0;
        buffer_view_->caretPosAndHeight(point, h);
@@ -629,19 +630,15 @@ void GuiWorkArea::Private::showCaret()
        point.x_ -= buffer_view_->horizScrollOffset();
 
        caret_->update(point.x_, point.y_, h, l_shape, isrtl, completable);
+}
+
 
-       if (schedule_redraw_) {
-               // This happens when a graphic conversion is finished. As we don't know
-               // the size of the new graphics, it's better the update everything.
-               // We can't use redraw() here because this would trigger a infinite
-               // recursive loop with showCaret().
-               buffer_view_->resize(p->viewport()->width(), p->viewport()->height());
-               p->viewport()->update();
-               updateScrollbar();
-               schedule_redraw_ = false;
+void GuiWorkArea::Private::showCaret()
+{
+       if (caret_visible_)
                return;
-       }
 
+       updateCaretGeometry();
        p->viewport()->update(caret_->rect());
 }
 
@@ -663,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)),
@@ -756,7 +753,7 @@ void GuiWorkArea::contextMenuEvent(QContextMenuEvent * e)
                        if (inset && inset->asInsetMath())
                                --pos.rx();
                        else if (cur.pos() > 0) {
-                               Inset * inset = cur.paragraph().getInset(cur.pos() - 1);
+                               inset = cur.paragraph().getInset(cur.pos() - 1);
                                if (inset)
                                        ++pos.rx();
                        }
@@ -1038,6 +1035,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
@@ -1045,13 +1075,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;
        }
@@ -1068,51 +1101,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();
@@ -1234,18 +1247,30 @@ 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());
+               ev->accept();
+               return;
+       }
+
        // LYXERR(Debug::PAINTING, "paintEvent begin: x: " << rc.x()
        //      << " y: " << rc.y() << " w: " << rc.width() << " h: " << rc.height());
 
-       if (d->needResize()) {
+       if (d->need_resize_ || pixelRatio() != d->last_pixel_ratio_) {
+               d->resetScreen();
                d->resizeBufferView();
-               if (d->caret_visible_) {
-                       d->hideCaret();
-                       d->showCaret();
-               }
        }
 
-       GuiPainter pain(viewport(), pixelRatio());
+       d->last_pixel_ratio_ = pixelRatio();
+
+       GuiPainter pain(d->screenDevice(), pixelRatio());
+
        d->buffer_view_->draw(pain, d->caret_visible_);
 
        // The preedit text, if needed
@@ -1254,6 +1279,9 @@ void GuiWorkArea::paintEvent(QPaintEvent * ev)
        // and the caret
        if (d->caret_visible_)
                d->caret_->draw(pain);
+
+       d->updateScreen(ev->rect());
+
        ev->accept();
 }
 
@@ -1265,11 +1293,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.
@@ -1352,12 +1382,6 @@ bool GuiWorkArea::isFullScreen() const
 }
 
 
-void GuiWorkArea::scheduleRedraw()
-{
-       d->schedule_redraw_ = true;
-}
-
-
 bool GuiWorkArea::inDialogMode() const
 {
        return d->dialog_mode_;
@@ -1770,7 +1794,7 @@ void TabWorkArea::on_currentTabChanged(int i)
        GuiWorkArea * wa = workArea(i);
        LASSERT(wa, return);
        wa->setUpdatesEnabled(true);
-       wa->redraw(true);
+       wa->scheduleRedraw(true);
        wa->setFocus();
        ///
        currentWorkAreaChanged(wa);