x_(0), caret_width_(0)
{}
- void draw(QPainter & painter)
+ /* 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 y = rect_.top();
- int l = x_ - rect_.left();
- int r = rect_.right() - x_;
- int bot = rect_.bottom();
+ 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_);
+ 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, bot);
+ painter.drawLine(x, bot, x - l, bot);
else
- painter.drawLine(x_, bot, x_ + caret_width_ + r, bot);
+ painter.drawLine(x, bot, x + caret_width_ + r, bot);
}
// draw completion triangle
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);
+ 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);
+ painter.drawLine(x + caret_width_, m - d, x + caret_width_ + d, m);
+ painter.drawLine(x + caret_width_, m + d, x + caret_width_ + d, m);
}
}
}
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)
-{
+: 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
+ 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) {
caret_timeout_.setInterval(time);
if (view().busy())
return;
- Point p;
- int h = 0;
- d->buffer_view_->caretPosAndHeight(p, h);
- // Don't start blinking if the cursor isn't on screen.
- if (!d->buffer_view_->cursorInView(p, h))
- return;
+ // we cannot update geometry if not ready and we do not need to if
+ // caret is not in view.
+ if (!d->buffer_view_->buffer().undo().activeUndoGroup()) {
+ Point p;
+ int h = 0;
+ d->buffer_view_->caretPosAndHeight(p, h);
+ // Don't start blinking if the cursor isn't on screen.
+ if (!d->buffer_view_->cursorInView(p, h))
+ return;
+ }
d->showCaret();
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())
+ return;
+
Point point;
int h = 0;
buffer_view_->caretPosAndHeight(point, h);
&& !completer_->inlineVisible();
caret_visible_ = true;
- //int cur_x = buffer_view_->getPos(cur).x_;
- // We may have decided to slide the cursor row so that caret
- // is visible.
- point.x_ -= buffer_view_->horizScrollOffset();
-
caret_->update(point.x_, point.y_, h, l_shape, isrtl, completable);
}
return;
updateCaretGeometry();
- p->viewport()->update(caret_->rect());
+ p->viewport()->update();
}
caret_visible_ = false;
//if (!qApp->focusWidget())
- p->viewport()->update(caret_->rect());
+ p->viewport()->update();
}
{
// 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:
}
+void GuiWorkArea::Private::resetScreen()
+{
+ if (use_backingstore_) {
+ int const pr = p->pixelRatio();
+ screen_ = QImage(static_cast<int>(pr * p->viewport()->width()),
+ static_cast<int>(pr * p->viewport()->height()),
+ QImage::Format_ARGB32_Premultiplied);
+# if QT_VERSION >= 0x050000
+ screen_.setDevicePixelRatio(pr);
+# endif
+ }
+}
+
+
+QPaintDevice * GuiWorkArea::Private::screenDevice()
+{
+ if (use_backingstore_)
+ return &screen_;
+ else
+ return p->viewport();
+}
+
+
+void GuiWorkArea::Private::updateScreen(QRectF const & rc)
+{
+ if (use_backingstore_) {
+ QPainter qpain(p->viewport());
+ double const pr = p->pixelRatio();
+ QRectF const rcs = QRectF(rc.x() * pr, rc.y() * pr,
+ rc.width() * pr, rc.height() * pr);
+ qpain.drawImage(rc, screen_, rcs);
+ }
+}
+
+
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());
// and the caret
if (d->caret_visible_)
- d->caret_->draw(pain);
+ d->caret_->draw(pain, d->buffer_view_->horizScrollOffset());
d->updateScreen(ev->rect());
// 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.
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);
}
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;
}
-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)
{