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
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);
QRect const & rect() { return rect_; }
private:
- /// cursor is in RTL or LTR text
+ /// caret is in RTL or LTR text
bool rtl_;
/// indication for RTL or LTR
bool l_shape_;
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)
{
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);
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());
Point p;
int h = 0;
- d->buffer_view_->cursorPosAndHeight(p, h);
+ 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();
- //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();
+ }
}
}
-void GuiWorkArea::redraw(bool update_metrics)
+void GuiWorkArea::scheduleRedraw(bool update_metrics)
{
if (!isVisible())
// No need to redraw in this case.
d->buffer_view_->cursor().fixIfBroken();
}
- // update cursor position, because otherwise it has to wait until
+ // 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();
// In order to avoid bad surprise in the middle of an operation,
// we better stop the blinking caret...
- // the cursor gets restarted in GuiView::restartCaret()
+ // the caret gets restarted in GuiView::restartCaret()
stopBlinkingCaret();
guiApp->processKeySym(key, mod);
}
cmd.action() != LFUN_MOUSE_MOTION || cmd.button() != mouse_button::none;
// In order to avoid bad surprise in the middle of an operation, we better stop
- // the blinking cursor.
+ // the blinking caret.
if (notJustMovingTheMouse)
p->stopBlinkingCaret();
// FIXME: let GuiView take care of those.
lyx_view_->clearMessage();
- // Show the cursor immediately after any operation
+ // Show the caret immediately after any operation
p->startBlinkingCaret();
}
Point point;
int h = 0;
- buffer_view_->cursorPosAndHeight(point, h);
+ buffer_view_->caretPosAndHeight(point, h);
bool const caret_in_view = buffer_view_->cursorInView(point, h);
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
need_resize_ = false;
p->busy(false);
- // Eventually, restart the cursor after the resize event.
+ // Eventually, restart the caret after the resize event.
// We might be resizing even if the focus is on another widget so we only
- // restart the cursor if we have the focus.
+ // restart the caret if we have the focus.
if (p->hasFocus())
QTimer::singleShot(50, p, SLOT(startBlinkingCaret()));
}
-void GuiWorkArea::Private::showCaret()
+void GuiWorkArea::Private::updateCaretGeometry()
{
- if (caret_visible_)
- return;
-
Point point;
int h = 0;
- buffer_view_->cursorPosAndHeight(point, h);
+ buffer_view_->caretPosAndHeight(point, h);
if (!buffer_view_->cursorInView(point, h))
return;
if (realfont.language() == latex_language)
l_shape = false;
- // show cursor on screen
+ // show caret on screen
Cursor & cur = buffer_view_->cursor();
bool completable = cur.inset().showCompletionCursor()
&& completer_->completionAvailable()
caret_visible_ = true;
//int cur_x = buffer_view_->getPos(cur).x_;
- // We may have decided to slide the cursor row so that cursor
+ // 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);
+}
- 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;
- }
- p->viewport()->update(caret_->rect());
+ updateCaretGeometry();
+ p->viewport()->update();
}
caret_visible_ = false;
//if (!qApp->focusWidget())
- p->viewport()->update(caret_->rect());
+ p->viewport()->update();
}
// 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)),
// FIXME: let GuiView take care of those.
d->lyx_view_->updateLayoutList();
}
- // Show the cursor immediately after any operation.
+ // Show the caret immediately after any operation.
startBlinkingCaret();
// FIXME QT5
#ifdef Q_WS_X11
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();
}
{
// 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:
}
+// 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
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;
}
}
}
- // 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();
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());
- 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
// and the caret
if (d->caret_visible_)
d->caret_->draw(pain);
+
+ d->updateScreen(ev->rect());
+
ev->accept();
}
// 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 cursor during the test transformation.
+ // Hide the caret during the test transformation.
if (e->preeditString().isEmpty())
startBlinkingCaret();
else
cur_r.moveLeft(10);
cur_r.moveBottom(cur_r.bottom()
+ cur_r.height() * (d->preedit_lines_ - 1));
- // return lower right of cursor in LyX.
+ // return lower right of caret in LyX.
return cur_r;
default:
return QWidget::inputMethodQuery(query);
}
-void GuiWorkArea::scheduleRedraw()
-{
- d->schedule_redraw_ = true;
-}
-
-
bool GuiWorkArea::inDialogMode() const
{
return d->dialog_mode_;
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);
}
GuiWorkArea * wa = workArea(i);
LASSERT(wa, return);
wa->setUpdatesEnabled(true);
- wa->redraw(true);
+ wa->scheduleRedraw(true);
wa->setFocus();
///
currentWorkAreaChanged(wa);
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();
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;
}
-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)
{