#include "ColorCache.h"
#include "FontLoader.h"
+#include "GuiApplication.h"
+#include "GuiCompleter.h"
+#include "GuiKeySymbol.h"
+#include "GuiPainter.h"
+#include "GuiView.h"
#include "Menus.h"
+#include "qt_helpers.h"
#include "Buffer.h"
#include "BufferList.h"
#include "Cursor.h"
#include "Font.h"
#include "FuncRequest.h"
-#include "GuiApplication.h"
-#include "GuiCompleter.h"
-#include "GuiKeySymbol.h"
-#include "GuiPainter.h"
-#include "GuiView.h"
#include "KeySymbol.h"
#include "Language.h"
#include "LyX.h"
#include "LyXRC.h"
#include "LyXVC.h"
-#include "qt_helpers.h"
#include "Text.h"
#include "TextMetrics.h"
+#include "Undo.h"
#include "version.h"
#include "graphics/GraphicsImage.h"
#include "support/convert.h"
#include "support/debug.h"
-#include "support/gettext.h"
-#include "support/FileName.h"
#include "support/lassert.h"
#include "support/TempFile.h"
#include <QMenu>
#include <QPainter>
#include <QPalette>
-#include <QPixmapCache>
#include <QScrollBar>
#include <QStyleOption>
#include <QStylePainter>
#include <QToolTip>
#include <QMenuBar>
-#include "support/bind.h"
-
#include <cmath>
#include <iostream>
namespace frontend {
-class CursorWidget {
+class CaretWidget {
public:
- CursorWidget() : rtl_(false), l_shape_(false), completable_(false),
- show_(false), x_(0), cursor_width_(0)
- {
- recomputeWidth();
- }
+ CaretWidget() : rtl_(false), l_shape_(false), completable_(false),
+ x_(0), caret_width_(0)
+ {}
void draw(QPainter & painter)
{
- if (!show_ || !rect_.isValid())
+ if (!rect_.isValid())
return;
int y = rect_.top();
int bot = rect_.bottom();
// draw vertical line
- painter.fillRect(x_, y, cursor_width_, rect_.height(), color_);
+ painter.fillRect(x_, y, caret_width_, rect_.height(), color_);
// draw RTL/LTR indication
painter.setPen(color_);
if (rtl_)
painter.drawLine(x_, bot, x_ - l, bot);
else
- painter.drawLine(x_, bot, x_ + cursor_width_ + r, bot);
+ painter.drawLine(x_, bot, x_ + caret_width_ + r, bot);
}
// draw completion triangle
painter.drawLine(x_ - 1, m - d, x_ - 1 - d, m);
painter.drawLine(x_ - 1, m + d, x_ - 1 - d, m);
} else {
- painter.drawLine(x_ + cursor_width_, m - d, x_ + cursor_width_ + d, m);
- painter.drawLine(x_ + cursor_width_, m + d, x_ + cursor_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);
}
}
}
r = max(r, TabIndicatorWidth);
}
- // compute overall rectangle
- rect_ = QRect(x - l, y, cursor_width_ + r + l, h);
- }
+ //FIXME: LyXRC::cursor_width should be caret_width
+ caret_width_ = lyxrc.cursor_width
+ ? lyxrc.cursor_width
+ : 1 + int((lyxrc.currentZoom + 50) / 200.0);
- void show(bool set_show = true) { show_ = set_show; }
- void hide() { show_ = false; }
- int cursorWidth() const { return cursor_width_; }
- void recomputeWidth() {
- cursor_width_ = lyxrc.cursor_width
- ? lyxrc.cursor_width
- : 1 + int((lyxrc.zoom + 50) / 200.0);
+ // compute overall rectangle
+ rect_ = QRect(x - l, y, caret_width_ + r + l, h);
}
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_;
/// triangle to show that a completion is available
bool completable_;
///
- bool show_;
- ///
QColor color_;
/// rectangle, possibly with l_shape and completion triangle
QRect rect_;
/// x position (were the vertical line is drawn)
int x_;
-
- int cursor_width_;
+ /// the width of the vertical blinking bar
+ int caret_width_;
};
GuiWorkArea::Private::Private(GuiWorkArea * parent)
-: p(parent), screen_(0), buffer_view_(0), read_only_(false), lyx_view_(0),
-cursor_visible_(false), cursor_(0),
-need_resize_(false), schedule_redraw_(false), preedit_lines_(1),
-pixel_ratio_(1.0),
-completer_(new GuiCompleter(p, p)), dialog_mode_(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)
+{
+ int const time = QApplication::cursorFlashTime() / 2;
+ if (time > 0) {
+ caret_timeout_.setInterval(time);
+ caret_timeout_.start();
+ } else {
+ // let's initialize this just to be safe
+ caret_timeout_.setInterval(500);
+ }
+}
+
+
+GuiWorkArea::Private::~Private()
{
+ // If something is wrong with the buffer, we can ignore it safely
+ try {
+ buffer_view_->buffer().workAreaManager().remove(p);
+ } catch(...) {}
+ delete buffer_view_;
+ delete caret_;
+ // Completer has a QObject parent and is thus automatically destroyed.
+ // See #4758.
+ // delete completer_;
}
GuiWorkArea::GuiWorkArea(QWidget * /* w */)
-: d(new Private(this))
+: 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);
double GuiWorkArea::pixelRatio() const
{
#if QT_VERSION >= 0x050000
- return devicePixelRatio();
+ return qt_scale_factor * devicePixelRatio();
#else
return 1.0;
#endif
void GuiWorkArea::init()
{
// Setup the signals
- connect(&d->cursor_timeout_, SIGNAL(timeout()),
- this, SLOT(toggleCursor()));
+ connect(&d->caret_timeout_, SIGNAL(timeout()),
+ this, SLOT(toggleCaret()));
- int const time = QApplication::cursorFlashTime() / 2;
- if (time > 0) {
- d->cursor_timeout_.setInterval(time);
- d->cursor_timeout_.start();
- } else {
- // let's initialize this just to be safe
- d->cursor_timeout_.setInterval(500);
- }
+ // This connection is closed at the same time as this is destroyed.
+ d->synthetic_mouse_event_.timeout.timeout.connect([this](){
+ 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());
- d->cursor_ = new frontend::CursorWidget();
- d->cursor_->hide();
+ d->caret_ = new frontend::CaretWidget();
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setAcceptDrops(true);
setFrameStyle(QFrame::NoFrame);
updateWindowTitle();
- viewport()->setAutoFillBackground(false);
- // We don't need double-buffering nor SystemBackground on
- // the viewport because we have our own backing pixmap.
- viewport()->setAttribute(Qt::WA_NoSystemBackground);
-
- setFocusPolicy(Qt::StrongFocus);
-
- d->setCursorShape(Qt::IBeamCursor);
+ d->updateCursorShape();
- d->synthetic_mouse_event_.timeout.timeout.connect(
- bind(&GuiWorkArea::generateSyntheticMouseEvent,
- this));
+ // we paint our own background
+ viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
- // Initialize the vertical Scroll Bar
- QObject::connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
- this, SLOT(scrollTo(int)));
+ setFocusPolicy(Qt::StrongFocus);
LYXERR(Debug::GUI, "viewport width: " << viewport()->width()
<< " viewport height: " << viewport()->height());
// Enables input methods for asian languages.
// Must be set when creating custom text editing widgets.
setAttribute(Qt::WA_InputMethodEnabled, true);
-
- d->dialog_mode_ = false;
}
GuiWorkArea::~GuiWorkArea()
{
- d->buffer_view_->buffer().workAreaManager().remove(this);
- delete d->screen_;
- delete d->buffer_view_;
- delete d->cursor_;
- // Completer has a QObject parent and is thus automatically destroyed.
- // See #4758.
- // delete completer_;
delete d;
}
-Qt::CursorShape GuiWorkArea::cursorShape() const
-{
- return viewport()->cursor().shape();
-}
-
-
-void GuiWorkArea::Private::setCursorShape(Qt::CursorShape shape)
-{
- p->viewport()->setCursor(shape);
-}
-
-
void GuiWorkArea::Private::updateCursorShape()
{
- setCursorShape(buffer_view_->clickableInset()
- ? Qt::PointingHandCursor : Qt::IBeamCursor);
+ bool const clickable = buffer_view_ && buffer_view_->clickableInset();
+ p->viewport()->setCursor(clickable ? Qt::PointingHandCursor
+ : Qt::IBeamCursor);
}
if (buffer.text().paragraphs().size() > 4)
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
QTimer::singleShot(50, this, SLOT(fixVerticalScrollBar()));
+ Q_EMIT bufferViewChanged();
}
}
-void GuiWorkArea::stopBlinkingCursor()
+void GuiWorkArea::stopBlinkingCaret()
{
- d->cursor_timeout_.stop();
- d->hideCursor();
+ d->caret_timeout_.stop();
+ d->hideCaret();
}
-void GuiWorkArea::startBlinkingCursor()
+void GuiWorkArea::startBlinkingCaret()
{
// do not show the cursor if the view is busy
if (view().busy())
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->showCursor();
+ d->showCaret();
//we're not supposed to cache this value.
int const time = QApplication::cursorFlashTime() / 2;
if (time <= 0)
return;
- d->cursor_timeout_.setInterval(time);
- d->cursor_timeout_.start();
+ d->caret_timeout_.setInterval(time);
+ d->caret_timeout_.start();
}
-void GuiWorkArea::redraw(bool update_metrics)
+void GuiWorkArea::toggleCaret()
+{
+ if (d->caret_visible_)
+ d->hideCaret();
+ else
+ d->showCaret();
+}
+
+
+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->cursor_visible_) {
- d->hideCursor();
- d->showCursor();
- }
+ d->updateCaretGeometry();
LYXERR(Debug::WORKAREA, "WorkArea::redraw screen");
- d->updateScreen();
- update(0, 0, viewport()->width(), viewport()->height());
+ 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 cursor...
- // the cursor gets restarted in GuiView::restartCursor()
- stopBlinkingCursor();
+ // we better stop the blinking caret...
+ // 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->stopBlinkingCursor();
+ p->stopBlinkingCaret();
buffer_view_->mouseEventDispatch(cmd);
// FIXME: let GuiView take care of those.
lyx_view_->clearMessage();
- // Show the cursor immediately after any operation
- p->startBlinkingCursor();
+ // Show the caret immediately after any operation
+ p->startBlinkingCaret();
}
updateCursorShape();
{
// WARNING: Please don't put any code that will trigger a repaint here!
// We are already inside a paint event.
- p->stopBlinkingCursor();
+ p->stopBlinkingCaret();
// Warn our container (GuiView).
p->busy(true);
Point point;
int h = 0;
- buffer_view_->cursorPosAndHeight(point, h);
- bool const cursor_in_view = buffer_view_->cursorInView(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 (cursor_in_view)
+ if (caret_in_view)
buffer_view_->scrollToCursor();
- updateScreen();
+ 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(startBlinkingCursor()));
+ QTimer::singleShot(50, p, SLOT(startBlinkingCaret()));
}
-void GuiWorkArea::Private::showCursor()
+void GuiWorkArea::Private::updateCaretGeometry()
{
- if (cursor_visible_)
- return;
-
- Point p;
+ Point point;
int h = 0;
- buffer_view_->cursorPosAndHeight(p, h);
- if (!buffer_view_->cursorInView(p, h))
+ buffer_view_->caretPosAndHeight(point, h);
+ if (!buffer_view_->cursorInView(point, h))
return;
// RTL or not RTL
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()
&& !completer_->popupVisible()
&& !completer_->inlineVisible();
- cursor_visible_ = true;
- cursor_->recomputeWidth();
+ 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.
- p.x_ -= buffer_view_->horizScrollOffset();
+ point.x_ -= buffer_view_->horizScrollOffset();
- showCursor(p.x_, p.y_, h, l_shape, isrtl, completable);
+ caret_->update(point.x_, point.y_, h, l_shape, isrtl, completable);
}
-void GuiWorkArea::Private::hideCursor()
+void GuiWorkArea::Private::showCaret()
{
- if (!cursor_visible_)
+ if (caret_visible_)
return;
- cursor_visible_ = false;
- removeCursor();
+ updateCaretGeometry();
+ p->viewport()->update(caret_->rect());
}
-void GuiWorkArea::toggleCursor()
+void GuiWorkArea::Private::hideCaret()
{
- if (d->cursor_visible_)
- d->hideCursor();
- else
- d->showCursor();
+ if (!caret_visible_)
+ return;
+
+ caret_visible_ = false;
+ //if (!qApp->focusWidget())
+ p->viewport()->update(caret_->rect());
}
void GuiWorkArea::Private::updateScrollbar()
{
+ // Prevent setRange() and setSliderPosition from causing recursive calls via
+ // the signal valueChanged. (#10311)
+ QObject::disconnect(p->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ p, SLOT(scrollTo(int)));
ScrollbarParameters const & scroll_ = buffer_view_->scrollbarParameters();
- // Block signals to prevent setRange() and setSliderPosition from causing
- // recursive calls via the signal valueChanged. (#10311)
- QSignalBlocker blocker(p->verticalScrollBar());
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)),
+ p, SLOT(scrollTo(int)));
}
void GuiWorkArea::scrollTo(int value)
{
- stopBlinkingCursor();
+ stopBlinkingCaret();
d->buffer_view_->scrollDocView(value, true);
if (lyxrc.cursor_follows_scrollbar) {
// FIXME: let GuiView take care of those.
d->lyx_view_->updateLayoutList();
}
- // Show the cursor immediately after any operation.
- startBlinkingCursor();
+ // Show the caret immediately after any operation.
+ startBlinkingCaret();
// FIXME QT5
#ifdef Q_WS_X11
QApplication::syncX();
}
name = d->buffer_view_->contextMenu(pos.x(), pos.y());
}
-
+
if (name.empty()) {
- QAbstractScrollArea::contextMenuEvent(e);
+ e->accept();
return;
}
// always show mnemonics when the keyboard is used to show the context menu
bool const keyboard = (e->reason() == QContextMenuEvent::Keyboard);
QMenu * menu = guiApp->menus().menu(toqstr(name), *d->lyx_view_, keyboard);
if (!menu) {
- QAbstractScrollArea::contextMenuEvent(e);
+ e->accept();
return;
}
// Position the menu to the right.
d->lyx_view_->currentWorkArea()->bufferView().buffer().updateBuffer();
}
- startBlinkingCursor();
+ startBlinkingCaret();
QAbstractScrollArea::focusInEvent(e);
}
void GuiWorkArea::focusOutEvent(QFocusEvent * e)
{
LYXERR(Debug::DEBUG, "GuiWorkArea::focusOutEvent(): " << this << endl);
- stopBlinkingCursor();
+ stopBlinkingCaret();
QAbstractScrollArea::focusOutEvent(e);
}
// in the first place.
return;
}
-
+
d->synthetic_mouse_event_.restart_timeout = true;
d->synthetic_mouse_event_.timeout.start();
// Fall through to handle this event...
if (up || down) {
int dist = up ? -e_y : e_y - wh;
time = max(min(200, 250000 / (dist * dist)), 1) ;
-
+
if (time < 40) {
step = 80000 / (time * time);
time = 40;
// In which paragraph do we have to set the cursor ?
Cursor & cur = d->buffer_view_->cursor();
- // FIXME: we don't know howto handle math.
+ // FIXME: we don't know how to handle math.
Text * text = cur.text();
if (!text)
return;
TextMetrics const & tm = d->buffer_view_->textMetrics(text);
+ // Quit gracefully if there are no metrics, since otherwise next
+ // line would crash (bug #10324).
+ // This situation seems related to a (not yet understood) timing problem.
+ if (tm.empty())
+ return;
+
pair<pit_type, const ParagraphMetrics *> pp = up ? tm.first() : tm.last();
ParagraphMetrics const & pm = *pp.second;
pit_type const pit = pp.first;
}
+// 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::Private::update(int x, int y, int w, int h)
-{
- p->viewport()->update(x, y, w, h);
-}
-
-
-void GuiWorkArea::paintEvent(QPaintEvent * ev)
+void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
{
- QRectF const rc = ev->rect();
- // LYXERR(Debug::PAINTING, "paintEvent begin: x: " << rc.x()
- // << " y: " << rc.y() << " w: " << rc.width() << " h: " << rc.height());
-
- if (d->needResize()) {
- d->resetScreen();
- d->resizeBufferView();
- if (d->cursor_visible_) {
- d->hideCursor();
- d->showCursor();
- }
- }
-
- QPainter pain(viewport());
- double const pr = pixelRatio();
- QRectF const rcs = QRectF(rc.x() * pr, rc.y() * pr, rc.width() * pr, rc.height() * pr);
-
- if (lyxrc.use_qimage) {
- QImage const & image = static_cast<QImage const &>(*d->screen_);
- pain.drawImage(rc, image, rcs);
- } else {
- QPixmap const & pixmap = static_cast<QPixmap const &>(*d->screen_);
- pain.drawPixmap(rc, pixmap, rcs);
- }
- d->cursor_->draw(pain);
- ev->accept();
-}
-
-
-void GuiWorkArea::Private::updateScreen()
-{
- GuiPainter pain(screen_, p->pixelRatio());
- buffer_view_->draw(pain);
-}
-
-
-void GuiWorkArea::Private::showCursor(int x, int y, int h,
- bool l_shape, bool rtl, bool 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 showCursor().
- buffer_view_->resize(p->viewport()->width(), p->viewport()->height());
- updateScreen();
- updateScrollbar();
- p->viewport()->update(QRect(0, 0, p->viewport()->width(), p->viewport()->height()));
- schedule_redraw_ = false;
- // Show the cursor immediately after the update.
- hideCursor();
- p->toggleCursor();
+ if (preedit_string_.empty())
return;
- }
-
- cursor_->update(x, y, h, l_shape, rtl, completable);
- cursor_->show();
- p->viewport()->update(cursor_->rect());
-}
-
-void GuiWorkArea::Private::removeCursor()
-{
- cursor_->hide();
- //if (!qApp->focusWidget())
- p->viewport()->update(cursor_->rect());
-}
-
-
-void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
-{
- QString const & commit_string = e->commitString();
- docstring const & preedit_string
- = qstring_to_ucs4(e->preeditString());
-
- if (!commit_string.isEmpty()) {
-
- LYXERR(Debug::KEY, "preeditString: " << e->preeditString()
- << " commitString: " << e->commitString());
-
- int key = 0;
-
- // FIXME Iwami 04/01/07: we should take care also of UTF16 surrogates here.
- for (int i = 0; i != commit_string.size(); ++i) {
- QKeyEvent ev(QEvent::KeyPress, key, Qt::NoModifier, commit_string[i]);
- keyPressEvent(&ev);
- }
- }
-
- // Hide the cursor during the kana-kanji transformation.
- if (preedit_string.empty())
- startBlinkingCursor();
- else
- stopBlinkingCursor();
-
- // last_width : for checking if last preedit string was/wasn't empty.
- // FIXME THREAD
- // We could have more than one work area, right?
- static bool last_width = false;
- if (!last_width && preedit_string.empty()) {
- // if last_width is last length of preedit string.
- e->accept();
- return;
- }
-
- GuiPainter pain(d->screen_, pixelRatio());
- d->buffer_view_->updateMetrics();
- d->buffer_view_->draw(pain);
- FontInfo font = d->buffer_view_->cursor().getFont().fontInfo();
+ // FIXME: shall we use real_current_font here? (see #10478)
+ FontInfo const font = buffer_view_->cursor().getFont().fontInfo();
FontMetrics const & fm = theFontMetrics(font);
- int height = fm.maxHeight();
- int cur_x = d->cursor_->rect().left();
- int cur_y = d->cursor_->rect().bottom();
-
- // redraw area of preedit string.
- update(0, cur_y - height, viewport()->width(),
- (height + 1) * d->preedit_lines_);
-
- if (preedit_string.empty()) {
- last_width = false;
- d->preedit_lines_ = 1;
- e->accept();
- return;
- }
- last_width = true;
-
- // att : stores an IM attribute.
- QList<QInputMethodEvent::Attribute> const & att = e->attributes();
+ int const height = fm.maxHeight();
+ int cur_x = caret_->rect().left();
+ int cur_y = caret_->rect().bottom();
// get attributes of input method cursor.
// cursor_pos : cursor position in preedit string.
size_t cursor_pos = 0;
bool cursor_is_visible = false;
- for (int i = 0; i != att.size(); ++i) {
- if (att.at(i).type == QInputMethodEvent::Cursor) {
- cursor_pos = att.at(i).start;
- cursor_is_visible = att.at(i).length != 0;
+ for (auto const & attr : preedit_attr_) {
+ if (attr.type == QInputMethodEvent::Cursor) {
+ cursor_pos = attr.start;
+ cursor_is_visible = attr.length != 0;
break;
}
}
- size_t preedit_length = preedit_string.length();
+ size_t const preedit_length = preedit_string_.length();
// get position of selection in input method.
// FIXME: isn't there a way to do this simplier?
// rLength : selected string length in IM.
size_t rLength = 0;
if (cursor_pos < preedit_length) {
- for (int i = 0; i != att.size(); ++i) {
- if (att.at(i).type == QInputMethodEvent::TextFormat) {
- if (att.at(i).start <= int(cursor_pos)
- && int(cursor_pos) < att.at(i).start + att.at(i).length) {
- rStart = att.at(i).start;
- rLength = att.at(i).length;
+ for (auto const & attr : preedit_attr_) {
+ if (attr.type == QInputMethodEvent::TextFormat) {
+ if (attr.start <= int(cursor_pos)
+ && int(cursor_pos) < attr.start + attr.length) {
+ rStart = attr.start;
+ rLength = attr.length;
if (!cursor_is_visible)
cursor_pos += rLength;
break;
rLength = 0;
}
- int const right_margin = d->buffer_view_->rightMargin();
+ int const right_margin = buffer_view_->rightMargin();
Painter::preedit_style ps;
// Most often there would be only one line:
- d->preedit_lines_ = 1;
+ preedit_lines_ = 1;
for (size_t pos = 0; pos != preedit_length; ++pos) {
- char_type const typed_char = preedit_string[pos];
+ char_type const typed_char = preedit_string_[pos];
// reset preedit string style
ps = Painter::preedit_default;
// if we reached the right extremity of the screen, go to next line.
- if (cur_x + fm.width(typed_char) > viewport()->width() - right_margin) {
+ if (cur_x + fm.width(typed_char) > p->viewport()->width() - right_margin) {
cur_x = right_margin;
cur_y += height + 1;
- ++d->preedit_lines_;
+ ++preedit_lines_;
}
// preedit strings are displayed with dashed underline
// and partial strings are displayed white on black indicating
// draw one character and update cur_x.
cur_x += pain.preeditText(cur_x, cur_y, typed_char, font, ps);
}
+}
+
+
+void GuiWorkArea::paintEvent(QPaintEvent * ev)
+{
+ // LYXERR(Debug::PAINTING, "paintEvent begin: x: " << rc.x()
+ // << " y: " << rc.y() << " w: " << rc.width() << " h: " << rc.height());
+
+ if (d->need_resize_ || pixelRatio() != d->last_pixel_ratio_) {
+ d->resetScreen();
+ d->resizeBufferView();
+ }
+
+ d->last_pixel_ratio_ = pixelRatio();
+
+ GuiPainter pain(d->screenDevice(), pixelRatio());
+
+ d->buffer_view_->draw(pain, d->caret_visible_);
+
+ // The preedit text, if needed
+ d->paintPreeditText(pain);
+
+ // and the caret
+ if (d->caret_visible_)
+ d->caret_->draw(pain);
+
+ d->updateScreen(ev->rect());
+
+ ev->accept();
+}
+
+
+void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
+{
+ LYXERR(Debug::KEY, "preeditString: " << e->preeditString()
+ << " commitString: " << e->commitString());
+
+ // 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()));
+ d->buffer_view_->updateMetrics();
+ d->buffer_view_->cursor().endUndoGroup();
+ viewport()->update();
+ }
+
+ // Hide the caret during the test transformation.
+ if (e->preeditString().isEmpty())
+ startBlinkingCaret();
+ else
+ stopBlinkingCaret();
+
+ if (d->preedit_string_.empty() && e->preeditString().isEmpty()) {
+ // Nothing to do
+ e->accept();
+ return;
+ }
- // update the preedit string screen area.
- update(0, cur_y - d->preedit_lines_*height, viewport()->width(),
+ // The preedit text and its attributes will be used in paintPreeditText
+ d->preedit_string_ = qstring_to_ucs4(e->preeditString());
+ d->preedit_attr_ = e->attributes();
+
+
+ // redraw area of preedit string.
+ int height = d->caret_->rect().height();
+ int cur_y = d->caret_->rect().bottom();
+ viewport()->update(0, cur_y - height, viewport()->width(),
(height + 1) * d->preedit_lines_);
+ if (d->preedit_string_.empty()) {
+ d->preedit_lines_ = 1;
+ e->accept();
+ return;
+ }
+
// Don't forget to accept the event!
e->accept();
}
// this is the CJK-specific composition window position and
// the context menu position when the menu key is pressed.
case Qt::ImMicroFocus:
- cur_r = d->cursor_->rect();
+ cur_r = d->caret_->rect();
if (d->preedit_lines_ != 1)
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::updateWindowTitle()
{
- docstring maximize_title;
- docstring minimize_title;
-
- Buffer const & buf = d->buffer_view_->buffer();
- FileName const file_name = buf.fileName();
- if (!file_name.empty()) {
- maximize_title = file_name.displayName(130);
- minimize_title = from_utf8(file_name.onlyFileName());
- if (buf.lyxvc().inUse()) {
- if (buf.lyxvc().locking())
- maximize_title += _(" (version control, locking)");
- else
- maximize_title += _(" (version control)");
- }
- if (!buf.isClean()) {
- maximize_title += _(" (changed)");
- minimize_title += char_type('*');
- }
- if (buf.isReadonly())
- maximize_title += _(" (read only)");
- }
-
- QString const new_title = toqstr(maximize_title);
- if (new_title != windowTitle()) {
- QWidget::setWindowTitle(new_title);
- QWidget::setWindowIconText(toqstr(minimize_title));
- titleChanged(this);
+ Buffer const & buf = bufferView().buffer();
+ if (buf.fileName() != d->file_name_
+ || buf.params().shell_escape != d->shell_escape_
+ || buf.hasReadonlyFlag() != d->read_only_
+ || buf.lyxvc().vcstatus() != d->vc_status_
+ || buf.isClean() != d->clean_
+ || buf.notifiesExternalModification() != d->externally_modified_) {
+ d->file_name_ = buf.fileName();
+ d->shell_escape_ = buf.params().shell_escape;
+ d->read_only_ = buf.hasReadonlyFlag();
+ d->vc_status_ = buf.lyxvc().vcstatus();
+ d->clean_ = buf.isClean();
+ d->externally_modified_ = buf.notifiesExternalModification();
+ Q_EMIT titleChanged(this);
}
}
}
-void GuiWorkArea::scheduleRedraw()
-{
- d->schedule_redraw_ = true;
-}
-
-
bool GuiWorkArea::inDialogMode() const
{
return d->dialog_mode_;
void EmbeddedWorkArea::disable()
{
- stopBlinkingCursor();
+ stopBlinkingCaret();
if (view().currentWorkArea() != this)
return;
// No problem if currentMainWorkArea() is 0 (setCurrentWorkArea()
if (tabBar()->isVisible()) {
QTabWidget::paintEvent(event);
} else {
- // Prevent the selected tab to influence the
+ // Prevent the selected tab to influence the
// painting of the frame of the tab widget.
// This is needed for gtk style in Qt.
QStylePainter p(this);
}
-GuiWorkArea * TabWorkArea::currentWorkArea()
+GuiWorkAreaContainer * TabWorkArea::widget(int index) const
{
- if (count() == 0)
- return 0;
+ QWidget * w = QTabWidget::widget(index);
+ if (!w)
+ return nullptr;
+ GuiWorkAreaContainer * wac = dynamic_cast<GuiWorkAreaContainer *>(w);
+ LATTEST(wac);
+ return wac;
+}
- GuiWorkArea * wa = dynamic_cast<GuiWorkArea *>(currentWidget());
- LATTEST(wa);
- return wa;
+
+GuiWorkAreaContainer * TabWorkArea::currentWidget() const
+{
+ return widget(currentIndex());
}
-GuiWorkArea * TabWorkArea::workArea(int index)
+GuiWorkArea * TabWorkArea::workArea(int index) const
{
- return dynamic_cast<GuiWorkArea *>(widget(index));
+ GuiWorkAreaContainer * w = widget(index);
+ if (!w)
+ return nullptr;
+ return w->workArea();
}
-GuiWorkArea * TabWorkArea::workArea(Buffer & buffer)
+GuiWorkArea * TabWorkArea::currentWorkArea() const
{
- // FIXME: this method doesn't work if we have more than work area
+ return workArea(currentIndex());
+}
+
+
+GuiWorkArea * TabWorkArea::workArea(Buffer & buffer) const
+{
+ // FIXME: this method doesn't work if we have more than one work area
// showing the same buffer.
for (int i = 0; i != count(); ++i) {
GuiWorkArea * wa = workArea(i);
void TabWorkArea::closeAll()
{
while (count()) {
- GuiWorkArea * wa = workArea(0);
- LASSERT(wa, return);
+ QWidget * wac = widget(0);
+ LASSERT(wac, return);
removeTab(0);
- delete wa;
+ delete wac;
}
}
+int TabWorkArea::indexOfWorkArea(GuiWorkArea * w) const
+{
+ for (int index = 0; index < count(); ++index)
+ if (workArea(index) == w)
+ return index;
+ return -1;
+}
+
+
bool TabWorkArea::setCurrentWorkArea(GuiWorkArea * work_area)
{
LASSERT(work_area, return false);
- int index = indexOf(work_area);
+ int index = indexOfWorkArea(work_area);
if (index == -1)
return false;
GuiWorkArea * TabWorkArea::addWorkArea(Buffer & buffer, GuiView & view)
{
GuiWorkArea * wa = new GuiWorkArea(buffer, view);
+ GuiWorkAreaContainer * wac = new GuiWorkAreaContainer(wa);
wa->setUpdatesEnabled(false);
// Hide tabbar if there's no tab (avoid a resize and a flashing tabbar
// when hiding it again below).
if (!(currentWorkArea() && currentWorkArea()->isFullScreen()))
showBar(count() > 0);
- addTab(wa, wa->windowTitle());
+ addTab(wac, wa->windowTitle());
QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
this, SLOT(updateTabTexts()));
if (currentWorkArea() && currentWorkArea()->isFullScreen())
bool TabWorkArea::removeWorkArea(GuiWorkArea * work_area)
{
LASSERT(work_area, return false);
- int index = indexOf(work_area);
+ int index = indexOfWorkArea(work_area);
if (index == -1)
return false;
work_area->setUpdatesEnabled(false);
+ QWidget * wac = widget(index);
removeTab(index);
- delete work_area;
+ delete wac;
if (count()) {
// make sure the next work area is enabled.
GuiWorkArea * wa = workArea(i);
LASSERT(wa, return);
wa->setUpdatesEnabled(true);
- wa->redraw(true);
+ wa->scheduleRedraw(true);
wa->setFocus();
///
currentWorkAreaChanged(wa);
class DisplayPath {
public:
/// make vector happy
- // coverity[UNINIT_CTOR]
- DisplayPath() {}
+ DisplayPath() : tab_(-1), dottedPrefix_(false) {}
///
DisplayPath(int tab, FileName const & filename)
: tab_(tab)
continue;
}
- // we found a non-atomic segment segStart <= sit <= it < next.
+ // We found a non-atomic segment
+ // We know that segStart <= it < next <= paths.end().
+ // The assertion below tells coverity about it.
+ LATTEST(segStart != paths.end());
+ QString dspString = segStart->forecastPathString();
+ LYXERR(Debug::GUI, "first forecast found for "
+ << segStart->abs() << " => " << dspString);
+ It sit = segStart;
+ ++sit;
// Shift path segments and hope for the best
// that it makes the path more unique.
somethingChanged = true;
- It sit = segStart;
- // this is ok for the reason mentioned in the previous comment.
- // coverity[INVALIDATE_ITERATOR]
- QString dspString = sit->forecastPathString();
- LYXERR(Debug::GUI, "first forecast found for "
- << sit->abs() << " => " << dspString);
- ++sit;
bool moreUnique = false;
for (; sit != next; ++sit) {
if (sit->forecastPathString() != dspString) {
if (!buf.fileName().empty() && !buf.isClean())
tab_text += "*";
QString tab_tooltip = it->abs();
- if (buf.isReadonly()) {
+ if (buf.hasReadonlyFlag()) {
setTabIcon(tab_index, QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")));
- tab_tooltip = qt_("%1 (read only)").arg(it->abs());
+ tab_tooltip = qt_("%1 (read only)").arg(tab_tooltip);
} else
setTabIcon(tab_index, QIcon());
+ if (buf.notifiesExternalModification()) {
+ QString const warn = qt_("%1 (modified externally)");
+ tab_tooltip = warn.arg(tab_tooltip);
+ tab_text += QChar(0x26a0);
+ }
setTabText(tab_index, tab_text);
setTabToolTip(tab_index, tab_tooltip);
}
}
+GuiWorkAreaContainer::GuiWorkAreaContainer(GuiWorkArea * wa, QWidget * parent)
+ : QWidget(parent), wa_(wa)
+{
+ LASSERT(wa, return);
+ Ui::WorkAreaUi::setupUi(this);
+ layout()->addWidget(wa);
+ connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
+ this, SLOT(updateDisplay()));
+ connect(reloadPB, SIGNAL(clicked()), this, SLOT(reload()));
+ connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore()));
+ setMessageColour({notificationFrame}, {reloadPB, ignorePB});
+ updateDisplay();
+}
+
+
+void GuiWorkAreaContainer::updateDisplay()
+{
+ Buffer const & buf = wa_->bufferView().buffer();
+ notificationFrame->setHidden(!buf.notifiesExternalModification());
+ QString const label = qt_("<b>The file %1 changed on disk.</b>")
+ .arg(toqstr(buf.fileName().displayName()));
+ externalModificationLabel->setText(label);
+}
+
+
+void GuiWorkAreaContainer::dispatch(FuncRequest f) const
+{
+ lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
+ wa_->bufferView().buffer().absFileName()));
+ lyx::dispatch(f);
+}
+
+
+void GuiWorkAreaContainer::reload() const
+{
+ dispatch(FuncRequest(LFUN_BUFFER_RELOAD));
+}
+
+
+void GuiWorkAreaContainer::ignore() const
+{
+ dispatch(FuncRequest(LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR));
+}
+
+
+void GuiWorkAreaContainer::mouseDoubleClickEvent(QMouseEvent * event)
+{
+ // prevent TabWorkArea from opening a new buffer on double click
+ event->accept();
+}
+
+
} // namespace frontend
} // namespace lyx