Merge remote-tracking branch 'features/properpaint' into 2.3.2-staging

This makes sure that painting actually happens at paint events, making
display more fluid.
This commit is contained in:
Jean-Marc Lasgouttes 2018-02-15 14:08:16 +01:00
commit c8230ab0d0
31 changed files with 834 additions and 764 deletions

View File

@ -60,38 +60,43 @@ cursor.
* Clean-up of drawing code
The goal is to make painting with drawing disable fast enough that it
can be used after every metrics computation. Then we can separate real
drawing from metrics.
** Make SinglePar update flag useful again.
Other changes are only clean-ups.
The current code can be very expensive when moving cursor inside a
huge table, for example. We should test the flag again, although this
will probably lead to some glitches here and there.
** Set Row::changed() in a finer way
*** singleParUpdate
When the height of the current paragraph changes, there is no need for
a full screen update. Only the rows after the current one need to have
their position recomputed.
This is also true when scrolling (how to do that?)
*** redoParagraph
It should be possible to check whether the new row is the same as the
old one and keep its changed() status in this case. This would reduce
a lot the amount of stuff to redraw.
** Put labels and friends in the Row as elements
It should not be necessary to access the Paragraph object to draw.
Adding the static elements to Row is a lot of work, but worth it IMO.
** Create a unique row by paragraph and break it afterwards
This should be a performance gain (only if paragraph breaking still
shows as expensive after the rest is done)
** do not add the vertical margin of main text to first/last row
Would make code cleaner. Probably no so difficult.
** When a paragraph ends with a newline, compute correctly the height of the extra row.
** Rewrite TextMetrics::editXY, checkInsetHit using row information (getPosNearX)?
The helper version should return a Row::Element instead of an InsetTable.
** Set inset position during metrics phase
In order to do that, a no-paint drawing will be initiated after every
redoParagraph. This code path will need to be made as fast as possible.
Effect: avoid depending on actual drawing having taken place. In turn,
it will allow to do drawing on paint events, like any reasonable
application would do.
** Cleanup after complete metrics
Then the following can be done:
+ remove hack in InsetMathNest::drawSelection
+ remove painting when not inside in drawParagraph
+ remove Cursor::inCoordCache?
** Paint directly to screen
Instead of using an intermediary pixmap. I have no idea of how
difficult it will prove.
One benefit will be that subpixel aliasing will work again (#9972)
** Merging bv::updateMetrics and tm::metrics
While the full metrics computation tries hard to limit the number of
@ -101,25 +106,25 @@ insets. We should re-use the bv::updateMetrics logic:
+ transfer all the logic of bv::updateMetrics to tm.
+ Main InsetText should not be special.
The difficuly for a tall table cell for example, is that it may be
The difficulty for a tall table cell for example, is that it may be
necessary to break the whole contents to know the width of the cell.
* Description of current drawing mechanism
** Two stage drawing
** Three-stage drawing
There are two parts to drawing the work area:
There are three parts to drawing the work area:
+ the metrics phase computes the size of insets and breaks the
paragraphs into rows. It stores the dimension of insets (both
normal and math) in bv::coordCache.
+ the drawing phase draws the contents and caches the inset
positions. Since the caching of positions is useful in itself,
there is a provision for drawing "without" drawing when the only
thing we want is to cache inset positions
(Painter::setDrawingEnabled).
+ the nodraw drawing phase paints the screen (see below) with a null
painter. The only useful effect is to store the inset positions.
+ an update() signal is sent. This in turn will trigger a paint
event, and the actual screen painting will happen then.
The machinery is controlled via bv::processUpdateFlags. This method is
called at the end of bv::mouseEventDispatch and in
@ -138,25 +143,37 @@ DecorationUpdate). It triggers a recomputation of the metrics when either:
existing metrics. Note that the Update::SinglePar flag is *never*
taken into account.
If a computation of metrics has taken place, Force is removed from the
flags and ForceDraw is added instead.
It is OK to call processUptateFlags several times before an update. In
this case, the effects are cumulative.processUpdateFlags execute the
metrics-related actions, but defers the actual drawing to the next
paint event.
The screen is drawn (with appropriate update strategy), except when
update flag is Update::None.
** Metrics computation
** Metrics computation (and nodraw drawing phase)
This is triggered by bv::updateMetrics, which calls tm::redoParagraph for
all visible paragraphs. Paragraphs above or below the screen (needed
all visible paragraphs. Some Paragraphs above or below the screen (needed
for page up/down) and computed as needed.
tm::redoParagraph will call Inset::metrics for each inset. In the case
of text insets, this will invoke recursively tm::metrics, which redoes
all the paragraphs of the inset.
At the end of the function, bv::updatePosCache is called. It triggers
a repaint of the document with a NullPainter (a painter that does
nothing). This has the effect of caching all insets positions.
** Drawing the work area.
This is done in bv::draw. This method is triggered mainly by
Buffer::changed, which draws all the work areas that show the given buffer.
This is done in bv::draw. This method is triggered by a paint event,
mainly called through Buffer::changed, which draws all the work areas
that show the given buffer.
Note that, When Buffer::changed is called outside of
bv::processUpdateFlags, it is not clear whether the update strategy
@ -186,3 +203,6 @@ The action depends on the update strategy:
+ SingleParUpdate: only tries to repaint current paragraph in a way
that is not yet very clear to me.
BufferView::draw can also be called with a null painter from
BufferView::updateMetrics().

View File

@ -70,6 +70,7 @@
#include "frontends/Application.h"
#include "frontends/Delegates.h"
#include "frontends/FontMetrics.h"
#include "frontends/NullPainter.h"
#include "frontends/Painter.h"
#include "frontends/Selection.h"
@ -227,14 +228,15 @@ enum ScreenUpdateStrategy {
struct BufferView::Private
{
Private(BufferView & bv) : update_strategy_(NoScreenUpdate),
Private(BufferView & bv) : update_strategy_(FullScreenUpdate),
update_flags_(Update::Force),
wh_(0), cursor_(bv),
anchor_pit_(0), anchor_ypos_(0),
inlineCompletionUniqueChars_(0),
last_inset_(0), clickable_inset_(false),
mouse_position_cache_(),
bookmark_edit_position_(-1), gui_(0),
horiz_scroll_offset_(0)
horiz_scroll_offset_(0), repaint_caret_row_(false)
{
xsel_cache_.set = false;
}
@ -244,6 +246,8 @@ struct BufferView::Private
///
ScreenUpdateStrategy update_strategy_;
///
Update::flags update_flags_;
///
CoordCache coord_cache_;
/// Estimated average par height for scrollbar.
@ -311,6 +315,12 @@ struct BufferView::Private
/// a slice pointing to the start of the row where cursor was
/// at previous draw event
CursorSlice last_row_slice_;
/// a slice pointing to where the cursor has been drawn after the current
/// draw() call.
CursorSlice caret_slice_;
/// indicates whether the caret slice needs to be repainted in this draw() run.
bool repaint_caret_row_;
};
@ -438,79 +448,85 @@ bool BufferView::needsFitCursor() const
}
namespace {
// this is for debugging only.
string flagsAsString(Update::flags flags)
{
if (flags == Update::None)
return "None ";
return string((flags & Update::FitCursor) ? "FitCursor " : "")
+ ((flags & Update::Force) ? "Force " : "")
+ ((flags & Update::ForceDraw) ? "ForceDraw " : "")
+ ((flags & Update::SinglePar) ? "SinglePar " : "");
}
}
void BufferView::processUpdateFlags(Update::flags flags)
{
// This is close to a hot-path.
LYXERR(Debug::PAINTING, "BufferView::processUpdateFlags()"
<< "[fitcursor = " << (flags & Update::FitCursor)
<< ", forceupdate = " << (flags & Update::Force)
<< ", singlepar = " << (flags & Update::SinglePar)
<< "] buffer: " << &buffer_);
// FIXME Does this really need doing here? It's done in updateBuffer, and
// if the Buffer doesn't need updating, then do the macros?
buffer_.updateMacros();
// Now do the first drawing step if needed. This consists on updating
// the CoordCache in updateMetrics().
// The second drawing step is done in WorkArea::redraw() if needed.
// FIXME: is this still true now that Buffer::changed() is used all over?
LYXERR(Debug::PAINTING, "BufferView::processUpdateFlags( "
<< flagsAsString(flags) << ") buffer: " << &buffer_);
// Case when no explicit update is requested.
if (!flags) {
if (flags == Update::None)
return;
// SinglePar is ignored for now (this should probably change). We
// set it ourselves below, at the price of always rebreaking the
// paragraph at cursor. This can be expensive for large tables.
flags = flags & ~Update::SinglePar;
// First check whether the metrics and inset positions should be updated
if (flags & Update::Force) {
// This will update the CoordCache items and replace Force
// with ForceDraw in flags.
updateMetrics(flags);
}
// Then make sure that the screen contains the cursor if needed
if (flags & Update::FitCursor) {
if (needsFitCursor()) {
scrollToCursor(d->cursor_, false);
// Metrics have to be recomputed (maybe again)
updateMetrics(flags);
}
flags = flags & ~Update::FitCursor;
}
// Finally detect whether we can only repaint a single paragraph
if (!(flags & Update::ForceDraw)) {
if (singleParUpdate())
flags = flags | Update::SinglePar;
else
updateMetrics(flags);
}
// Add flags to the the update flags. These will be reset to None
// after the redraw is actually done
d->update_flags_ = d->update_flags_ | flags;
LYXERR(Debug::PAINTING, "Cumulative flags: " << flagsAsString(flags));
// Now compute the update strategy
// Possibly values in flag are None, Decoration, ForceDraw
LATTEST((d->update_flags_ & ~(Update::None | Update::SinglePar
| Update::Decoration | Update::ForceDraw)) == 0);
if (d->update_flags_ & Update::ForceDraw)
d->update_strategy_ = FullScreenUpdate;
else if (d->update_flags_ & Update::Decoration)
d->update_strategy_ = DecorationUpdate;
else if (d->update_flags_ & Update::SinglePar)
d->update_strategy_ = SingleParUpdate;
else {
// no need to redraw anything.
d->update_strategy_ = NoScreenUpdate;
return;
}
if (flags == Update::Decoration) {
d->update_strategy_ = DecorationUpdate;
buffer_.changed(false);
return;
}
if (flags == Update::FitCursor
|| flags == (Update::Decoration | Update::FitCursor)) {
// tell the frontend to update the screen if needed.
if (needsFitCursor()) {
showCursor();
return;
}
if (flags & Update::Decoration) {
d->update_strategy_ = DecorationUpdate;
buffer_.changed(false);
return;
}
// no screen update is needed in principle, but this
// could change if cursor row needs horizontal scrolling.
d->update_strategy_ = NoScreenUpdate;
buffer_.changed(false);
return;
}
bool const full_metrics = flags & Update::Force || !singleParUpdate();
if (full_metrics)
// We have to update the full screen metrics.
updateMetrics();
if (!(flags & Update::FitCursor)) {
// Nothing to do anymore. Trigger a redraw and return
buffer_.changed(false);
return;
}
// updateMetrics() does not update paragraph position
// This is done at draw() time. So we need a redraw!
buffer_.changed(false);
if (needsFitCursor()) {
// The cursor is off screen so ensure it is visible.
// refresh it:
showCursor();
}
updateHoveredInset();
// Trigger a redraw.
buffer_.changed(false);
}
@ -625,8 +641,7 @@ void BufferView::scrollDocView(int const value, bool update)
// If the offset is less than 2 screen height, prefer to scroll instead.
if (abs(value) <= 2 * height_) {
d->anchor_ypos_ -= value;
buffer_.changed(true);
updateHoveredInset();
processUpdateFlags(Update::Force);
return;
}
@ -823,12 +838,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
d->cursor_.setCurrentFont();
// Do not forget to reset the anchor (see #9912)
d->cursor_.resetAnchor();
// To center the screen on this new position we need the
// paragraph position which is computed at draw() time.
// So we need a redraw!
buffer_.changed(false);
if (needsFitCursor())
showCursor();
processUpdateFlags(Update::FitCursor);
}
return success;
@ -870,19 +880,15 @@ void BufferView::showCursor()
void BufferView::showCursor(DocIterator const & dit,
bool recenter, bool update)
{
if (scrollToCursor(dit, recenter) && update) {
buffer_.changed(true);
updateHoveredInset();
}
if (scrollToCursor(dit, recenter) && update)
processUpdateFlags(Update::Force);
}
void BufferView::scrollToCursor()
{
if (scrollToCursor(d->cursor_, false)) {
buffer_.changed(true);
updateHoveredInset();
}
if (scrollToCursor(d->cursor_, false))
processUpdateFlags(Update::Force);
}
@ -1708,8 +1714,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
bool const in_texted = cur.inTexted();
cur.setCursor(doc_iterator_begin(cur.buffer()));
cur.selHandle(false);
buffer_.changed(true);
updateHoveredInset();
// Force an immediate computation of metrics because we need it below
processUpdateFlags(Update::Force);
d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_,
true, act == LFUN_SCREEN_UP);
@ -1743,8 +1749,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
if (scroll_value)
scroll(scroll_step * scroll_value);
}
buffer_.changed(true);
updateHoveredInset();
dr.screenUpdate(Update::ForceDraw);
dr.forceBufferUpdate();
break;
}
@ -2638,7 +2643,7 @@ bool BufferView::singleParUpdate()
// the singlePar optimisation.
return false;
d->update_strategy_ = SingleParUpdate;
tm.updatePosCache(bottom_pit);
LYXERR(Debug::PAINTING, "\ny1: " << pm.position() - pm.ascent()
<< " y2: " << pm.position() + pm.descent()
@ -2649,6 +2654,13 @@ bool BufferView::singleParUpdate()
void BufferView::updateMetrics()
{
updateMetrics(d->update_flags_);
d->update_strategy_ = FullScreenUpdate;
}
void BufferView::updateMetrics(Update::flags & update_flags)
{
if (height_ == 0 || width_ == 0)
return;
@ -2691,6 +2703,7 @@ void BufferView::updateMetrics()
}
}
anchor_pm.setPosition(d->anchor_ypos_);
tm.updatePosCache(d->anchor_pit_);
LYXERR(Debug::PAINTING, "metrics: "
<< " anchor pit = " << d->anchor_pit_
@ -2706,6 +2719,7 @@ void BufferView::updateMetrics()
y1 -= pm.descent();
// Save the paragraph position in the cache.
pm.setPosition(y1);
tm.updatePosCache(pit1);
y1 -= pm.ascent();
}
@ -2719,6 +2733,7 @@ void BufferView::updateMetrics()
y2 += pm.ascent();
// Save the paragraph position in the cache.
pm.setPosition(y2);
tm.updatePosCache(pit2);
y2 += pm.descent();
}
@ -2730,7 +2745,11 @@ void BufferView::updateMetrics()
<< " pit1 = " << pit1
<< " pit2 = " << pit2);
d->update_strategy_ = FullScreenUpdate;
// metrics is done, full drawing is necessary now
update_flags = (update_flags & ~Update::Force) | Update::ForceDraw;
// Now update the positions of insets in the cache.
updatePosCache();
if (lyxerr.debugging(Debug::WORKAREA)) {
LYXERR(Debug::WORKAREA, "BufferView::updateMetrics");
@ -2739,6 +2758,15 @@ void BufferView::updateMetrics()
}
void BufferView::updatePosCache()
{
// this is the "nodraw" drawing stage: only set the positions of the
// insets in metrics cache.
frontend::NullPainter np;
draw(np, false);
}
void BufferView::insertLyXFile(FileName const & fname)
{
LASSERT(d->cursor_.inTexted(), return);
@ -2873,7 +2901,7 @@ bool BufferView::paragraphVisible(DocIterator const & dit) const
}
void BufferView::cursorPosAndHeight(Point & p, int & h) const
void BufferView::caretPosAndHeight(Point & p, int & h) const
{
Cursor const & cur = cursor();
Font const font = cur.real_current_font;
@ -2946,7 +2974,30 @@ void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
}
void BufferView::checkCursorScrollOffset(PainterInfo & pi)
namespace {
bool sliceInRow(CursorSlice const & cs, Text const * text, Row const & row)
{
/* The normal case is the last line. The previous line takes care
* of empty rows (e.g. empty paragraphs). Cursor boundary issues
* are taken care of when setting caret_slice_ in
* BufferView::draw.
*/
return !cs.empty() && cs.text() == text && cs.pit() == row.pit()
&& ((row.pos() == row.endpos() && row.pos() == cs.pos())
|| (row.pos() <= cs.pos() && cs.pos() < row.endpos()));
}
}
bool BufferView::needRepaint(Text const * text, Row const & row) const
{
return d->repaint_caret_row_ && sliceInRow(d->caret_slice_, text, row);
}
void BufferView::checkCursorScrollOffset()
{
CursorSlice rowSlice = d->cursor_.bottom();
TextMetrics const & tm = textMetrics(rowSlice.text());
@ -2963,35 +3014,6 @@ void BufferView::checkCursorScrollOffset(PainterInfo & pi)
// Set the row on which the cursor lives.
setCurrentRowSlice(rowSlice);
// If insets referred to by cursor are not all in the cache, the positions
// need to be recomputed.
if (!d->cursor_.inCoordCache()) {
/** FIXME: the code below adds an extraneous computation of
* inset positions, and can therefore be bad for performance
* (think for example about a very large tabular inset.
* Redawing the row where it is means redrawing the whole
* screen).
*
* The bug that this fixes is the following: assume that there
* is a very large math inset. Upon entering the inset, when
* pressing `End', the row is not scrolled and the cursor is
* not visible. The extra row computation makes sure that the
* inset positions are correctly computed and set in the
* cache. This would not happen if we did not have two-stage
* drawing.
*
* A proper fix would be to always have proper inset positions
* at this point.
*/
// Force the recomputation of inset positions
bool const drawing = pi.pain.isDrawingEnabled();
pi.pain.setDrawingEnabled(false);
// No need to care about vertical position.
RowPainter rp(pi, buffer().text(), row, -d->horiz_scroll_offset_, 0);
rp.paintText();
pi.pain.setDrawingEnabled(drawing);
}
// Current x position of the cursor in pixels
int cur_x = getPos(d->cursor_).x_;
@ -3035,30 +3057,46 @@ void BufferView::checkCursorScrollOffset(PainterInfo & pi)
}
void BufferView::draw(frontend::Painter & pain)
void BufferView::draw(frontend::Painter & pain, bool paint_caret)
{
if (height_ == 0 || width_ == 0)
return;
LYXERR(Debug::PAINTING, "\t\t*** START DRAWING ***");
LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t--- START NODRAW ---"
: "\t\t*** START DRAWING ***"));
Text & text = buffer_.text();
TextMetrics const & tm = d->text_metrics_[&text];
int const y = tm.first().second->position();
PainterInfo pi(this, pain);
/** A repaint of the previous caret row is needed if there is
* caret painted on screen and either
* 1/ a new caret has to be painted at a place different from
* the existing one;
* 2/ there is no need for a caret anymore.
*/
d->repaint_caret_row_ = !d->caret_slice_.empty() &&
((paint_caret && d->cursor_.top() != d->caret_slice_)
|| ! paint_caret);
// Check whether the row where the cursor lives needs to be scrolled.
// Update the drawing strategy if needed.
checkCursorScrollOffset(pi);
checkCursorScrollOffset();
switch (d->update_strategy_) {
case NoScreenUpdate:
// If no screen painting is actually needed, only some the different
// coordinates of insets and paragraphs needs to be updated.
// no screen painting is actually needed. In nodraw stage
// however, the different coordinates of insets and paragraphs
// needs to be updated.
LYXERR(Debug::PAINTING, "Strategy: NoScreenUpdate");
pi.full_repaint = true;
pi.pain.setDrawingEnabled(false);
tm.draw(pi, 0, y);
pi.full_repaint = false;
if (pain.isNull()) {
pi.full_repaint = true;
tm.draw(pi, 0, y);
} else if (d->repaint_caret_row_) {
pi.full_repaint = false;
tm.draw(pi, 0, y);
}
break;
case SingleParUpdate:
@ -3103,7 +3141,8 @@ void BufferView::draw(frontend::Painter & pain)
}
break;
}
LYXERR(Debug::PAINTING, "\n\t\t*** END DRAWING ***");
LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t --- END NODRAW ---"
: "\t\t *** END DRAWING ***"));
// The scrollbar needs an update.
updateScrollbar();
@ -3114,13 +3153,29 @@ void BufferView::draw(frontend::Painter & pain)
for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) {
ParagraphMetrics const & pm = tm.parMetrics(pit);
if (pm.position() + pm.descent() > 0) {
if (d->anchor_pit_ != pit
|| d->anchor_ypos_ != pm.position())
LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_
<< " anchor ypos = " << d->anchor_ypos_);
d->anchor_pit_ = pit;
d->anchor_ypos_ = pm.position();
break;
}
}
LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_
<< " anchor ypos = " << d->anchor_ypos_);
if (!pain.isNull()) {
// reset the update flags, everything has been done
d->update_flags_ = Update::None;
}
// Remember what has just been done for the next draw() step
if (paint_caret) {
d->caret_slice_ = d->cursor_.top();
if (d->caret_slice_.pos() > 0
&& (d->cursor_.boundary()
|| d->caret_slice_.pos() == d->caret_slice_.lastpos()))
--d->caret_slice_.pos();
} else
d->caret_slice_ = CursorSlice();
}

View File

@ -42,10 +42,10 @@ class FuncStatus;
class Intl;
class Inset;
class Length;
class PainterInfo;
class ParIterator;
class ParagraphMetrics;
class Point;
class Row;
class TexRow;
class Text;
class TextMetrics;
@ -120,11 +120,12 @@ public:
/// \return true if the BufferView is at the bottom of the document.
bool isBottomScreen() const;
/// perform pending metrics updates.
/** \c Update::FitCursor means first to do a FitCursor, and to
/// Add \p flags to current update flags and trigger an update.
/* If this method is invoked several times before the update
* actually takes place, the effect is cumulative.
* \c Update::FitCursor means first to do a FitCursor, and to
* force an update if screen position changes.
* \c Update::Force means to force an update in any case.
* \retval true if a screen redraw is needed
*/
void processUpdateFlags(Update::flags flags);
@ -132,6 +133,9 @@ public:
/// Only to be called with good y coordinates (after a bv::metrics)
bool needsFitCursor() const;
/// returns true if this row needs to be repainted (to erase caret)
bool needRepaint(Text const * text, Row const & row) const;
// Returns the amount of horizontal scrolling applied to the
// top-level row where the cursor lies
int horizScrollOffset() const;
@ -283,6 +287,10 @@ public:
/// update the internal \c ViewMetricsInfo.
void updateMetrics();
// this is the "nodraw" drawing stage: only set the positions of the
// insets in metrics cache.
void updatePosCache();
///
TextMetrics const & textMetrics(Text const * t) const;
TextMetrics & textMetrics(Text const * t);
@ -300,12 +308,11 @@ public:
bool paragraphVisible(DocIterator const & dit) const;
/// is the cursor currently visible in the view
bool cursorInView(Point const & p, int h) const;
/// get the position and height of the cursor
void cursorPosAndHeight(Point & p, int & h) const;
/// get the position and height of the caret
void caretPosAndHeight(Point & p, int & h) const;
///
void draw(frontend::Painter & pain);
void draw(frontend::Painter & pain, bool paint_caret);
/// get this view's keyboard map handler.
Intl & getIntl();
@ -361,13 +368,15 @@ private:
/// Update current paragraph metrics.
/// \return true if no further update is needed.
bool singleParUpdate();
/// do the work for the public updateMetrics()
void updateMetrics(Update::flags & update_flags);
// Set the row on which the cursor lives.
void setCurrentRowSlice(CursorSlice const & rowSlice);
// Check whether the row where the cursor lives needs to be scrolled.
// Update the drawing strategy if needed.
void checkCursorScrollOffset(PainterInfo & pi);
void checkCursorScrollOffset();
/// The minimal size of the document that is visible. Used
/// when it is allowed to scroll below the document.

View File

@ -453,19 +453,6 @@ int Cursor::currentMode()
}
bool Cursor::inCoordCache() const
{
// the root inset is not in cache, but we do not need it.
if (depth() == 1)
return true;
CoordCache::Insets const & icache = bv_->coordCache().getInsets();
for (size_t i = 1 ; i < depth() ; ++i)
if (!icache.has(&(*this)[i].inset()))
return false;
return true;
}
void Cursor::getPos(int & x, int & y) const
{
Point p = bv().getPos(*this);

View File

@ -215,8 +215,6 @@ public:
/// are we entering a macro name?
bool & macromode() { return macromode_; }
/// returns true when all insets in cursor stack are in cache
bool inCoordCache() const;
/// returns x,y position
void getPos(int & x, int & y) const;
/// return logical positions between which the cursor is situated

View File

@ -130,7 +130,7 @@ public:
bool selected;
/// Whether the spell checker is enabled for the parent
bool do_spellcheck;
///
/// True when it can be assumed that the screen has been cleared
bool full_repaint;
/// Current background color
ColorCode background_color;

View File

@ -47,8 +47,6 @@
#include "support/lstrings.h"
#include "support/textutils.h"
#include <boost/crc.hpp>
#include <algorithm>
#include <list>
#include <stack>
@ -84,42 +82,6 @@ void ParagraphMetrics::reset(Paragraph const & par)
}
size_t ParagraphMetrics::computeRowSignature(Row const & row,
BufferView const & bv) const
{
boost::crc_32_type crc;
for (pos_type i = row.pos(); i < row.endpos(); ++i) {
if (par_->isInset(i)) {
Inset const * in = par_->getInset(i);
Dimension const d = in->dimension(bv);
int const b[] = { d.wid, d.asc, d.des };
crc.process_bytes(b, sizeof(b));
} else {
char_type const b[] = { par_->getChar(i) };
crc.process_bytes(b, sizeof(char_type));
}
if (bv.buffer().params().track_changes) {
Change change = par_->lookupChange(i);
char_type const b[] = { static_cast<char_type>(change.type) };
// 1 byte is enough to encode Change::Type
crc.process_bytes(b, 1);
}
}
pos_type const b1[] = { row.sel_beg, row.sel_end };
crc.process_bytes(b1, sizeof(b1));
Dimension const & d = row.dimension();
int const b2[] = { row.begin_margin_sel,
row.end_margin_sel,
d.wid, d.asc, d.des };
crc.process_bytes(b2, sizeof(b2));
crc.process_bytes(&row.separator, sizeof(row.separator));
return crc.checksum();
}
void ParagraphMetrics::setPosition(int position)
{
position_ = position;

View File

@ -85,9 +85,6 @@ public:
///
bool hfillExpansion(Row const & row, pos_type pos) const;
///
size_t computeRowSignature(Row const &, BufferView const & bv) const;
///
int position() const { return position_; }
void setPosition(int position);

View File

@ -162,19 +162,13 @@ Row::Row()
: separator(0), label_hfill(0), left_margin(0), right_margin(0),
sel_beg(-1), sel_end(-1),
begin_margin_sel(false), end_margin_sel(false),
changed_(false), crc_(0),
changed_(true),
pit_(0), pos_(0), end_(0),
right_boundary_(false), flushed_(false), rtl_(false)
right_boundary_(false), flushed_(false), rtl_(false),
changebar_(false)
{}
void Row::setCrc(size_type crc) const
{
changed_ = crc != crc_;
crc_ = crc;
}
bool Row::isMarginSelected(bool left_margin, DocIterator const & beg,
DocIterator const & end) const
{
@ -209,8 +203,8 @@ void Row::setSelectionAndMargins(DocIterator const & beg,
setSelection(beg.pos(), end.pos());
if (selection()) {
end_margin_sel = isMarginSelected(false, beg, end);
begin_margin_sel = isMarginSelected(true, beg, end);
change(end_margin_sel, isMarginSelected(false, beg, end));
change(begin_margin_sel, isMarginSelected(true, beg, end));
}
}
@ -218,18 +212,18 @@ void Row::setSelectionAndMargins(DocIterator const & beg,
void Row::setSelection(pos_type beg, pos_type end) const
{
if (pos_ >= beg && pos_ <= end)
sel_beg = pos_;
change(sel_beg, pos_);
else if (beg > pos_ && beg <= end_)
sel_beg = beg;
change(sel_beg, beg);
else
sel_beg = -1;
change(sel_beg, -1);
if (end_ >= beg && end_ <= end)
sel_end = end_;
change(sel_end,end_);
else if (end < end_ && end >= pos_)
sel_end = end;
change(sel_end, end);
else
sel_end = -1;
change(sel_end, -1);
}
@ -371,6 +365,8 @@ void Row::finalizeLast()
if (elt.final)
return;
elt.final = true;
if (elt.change.changed())
changebar_ = true;
if (elt.type == STRING) {
dim_.wid -= elt.dim.wid;

View File

@ -136,12 +136,33 @@ public:
///
Row();
/**
* Helper function: set variable \c var to value \c val, and mark
* row as changed is the values were different. This is intended
* for use when changing members of the row object.
*/
template<class T1, class T2>
void change(T1 & var, T2 const val) {
if (var != val)
changed(true);
var = val;
}
/**
* Helper function: set variable \c var to value \c val, and mark
* row as changed is the values were different. This is intended
* for use when changing members of the row object.
* This is the const version, useful for mutable members.
*/
template<class T1, class T2>
void change(T1 & var, T2 const val) const {
if (var != val)
changed(true);
var = val;
}
///
bool changed() const { return changed_; }
///
void setChanged(bool c) { changed_ = c; }
///
void setCrc(size_type crc) const;
void changed(bool c) const { changed_ = c; }
/// Set the selection begin and end.
/**
* This is const because we update the selection status only at draw()
@ -266,6 +287,11 @@ public:
void reverseRTL(bool rtl_par);
///
bool isRTL() const { return rtl_; }
///
bool needsChangeBar() const { return changebar_; }
///
void needsChangeBar(bool ncb) { changebar_ = ncb; }
/// Find row element that contains \c pos, and compute x offset.
const_iterator const findElement(pos_type pos, bool boundary, double & x) const;
@ -310,8 +336,6 @@ private:
/// has the Row appearance changed since last drawing?
mutable bool changed_;
/// CRC of row contents.
mutable size_type crc_;
/// Index of the paragraph that contains this row
pit_type pit_;
/// first pos covered by this row
@ -326,6 +350,8 @@ private:
Dimension dim_;
/// true when this row lives in a right-to-left paragraph
bool rtl_;
/// true when a changebar should be drawn in the margin
bool changebar_;
};

View File

@ -247,18 +247,6 @@ void RowPainter::paintChange(Row::Element const & e) const
void RowPainter::paintChangeBar() const
{
pos_type const start = row_.pos();
pos_type end = row_.endpos();
if (par_.size() == end) {
// this is the last row of the paragraph;
// thus, we must also consider the imaginary end-of-par character
end++;
}
if (start == end || !par_.isChanged(start, end))
return;
int const height = tm_.isLastRow(row_)
? row_.ascent()
: row_.height();
@ -576,7 +564,7 @@ void RowPainter::paintText()
paintStringAndSel(e);
// Paint the spelling marks if enabled.
if (lyxrc.spellcheck_continuously && pi_.do_spellcheck && pi_.pain.isDrawingEnabled())
if (lyxrc.spellcheck_continuously && pi_.do_spellcheck && !pi_.pain.isNull())
paintMisspelledMark(e);
break;

View File

@ -43,7 +43,9 @@
#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"
#include "frontends/NullPainter.h"
#include "support/convert.h"
#include "support/debug.h"
#include "support/lassert.h"
@ -198,6 +200,14 @@ bool TextMetrics::metrics(MetricsInfo & mi, Dimension & dim, int min_width)
}
void TextMetrics::updatePosCache(pit_type pit) const
{
frontend::NullPainter np;
PainterInfo pi(bv_, np);
drawParagraph(pi, pit, origin_.x_, par_metrics_[pit].position());
}
int TextMetrics::rightMargin(ParagraphMetrics const & pm) const
{
return text_->isMainText() ? pm.rightMargin(*bv_) : 0;
@ -459,7 +469,7 @@ bool TextMetrics::redoParagraph(pit_type const pit)
row.pit(pit);
need_new_row = breakRow(row, right_margin);
setRowHeight(row);
row.setChanged(false);
row.changed(true);
if (row_index || row.endpos() < par.size()
|| (row.right_boundary() && par.inInset().lyxCode() != CELL_CODE)) {
/* If there is more than one row or the row has been
@ -955,6 +965,10 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
row.addVirtual(end, docstring(1, char_type(0x00B6)), f, Change());
}
// Is there a end-of-paragaph change?
if (i == end && par.lookupChange(end).changed() && !need_new_row)
row.needsChangeBar(true);
// if the row is too large, try to cut at last separator. In case
// of success, reset indication that the row was broken abruptly.
int const next_width = max_width_ - leftMargin(row.pit(), row.endpos())
@ -1214,6 +1228,7 @@ void TextMetrics::newParMetricsDown()
redoParagraph(pit);
par_metrics_[pit].setPosition(last.second.position()
+ last.second.descent() + par_metrics_[pit].ascent());
updatePosCache(pit);
}
@ -1228,6 +1243,7 @@ void TextMetrics::newParMetricsUp()
redoParagraph(pit);
par_metrics_[pit].setPosition(first.second.position()
- first.second.ascent() - par_metrics_[pit].descent());
updatePosCache(pit);
}
// y is screen coordinate
@ -1797,8 +1813,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
return;
size_t const nrows = pm.rows().size();
// Use fast lane when drawing is disabled.
if (!pi.pain.isDrawingEnabled()) {
// Use fast lane in nodraw stage.
if (pi.pain.isNull()) {
for (size_t i = 0; i != nrows; ++i) {
Row const & row = pm.rows()[i];
@ -1850,17 +1866,11 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
if (i)
y += row.ascent();
RowPainter rp(pi, *text_, row, row_x, y);
// It is not needed to draw on screen if we are not inside.
bool const inside = (y + row.descent() >= 0
&& y - row.ascent() < ww);
pi.pain.setDrawingEnabled(inside);
if (!inside) {
// Paint only the insets to set inset cache correctly
// FIXME: remove paintOnlyInsets when we know that positions
// have already been set.
rp.paintOnlyInsets();
// Inset positions have already been set in nodraw stage.
y += row.descent();
continue;
}
@ -1874,27 +1884,30 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
// whether this row is the first or last and update the margins.
if (row.selection()) {
if (row.sel_beg == 0)
row.begin_margin_sel = sel_beg.pit() < pit;
row.change(row.begin_margin_sel, sel_beg.pit() < pit);
if (row.sel_end == sel_end_par.lastpos())
row.end_margin_sel = sel_end.pit() > pit;
row.change(row.end_margin_sel, sel_end.pit() > pit);
}
// Row signature; has row changed since last paint?
row.setCrc(pm.computeRowSignature(row, *bv_));
// has row changed since last paint?
bool row_has_changed = row.changed()
|| bv_->hadHorizScrollOffset(text_, pit, row.pos());
|| bv_->hadHorizScrollOffset(text_, pit, row.pos())
|| bv_->needRepaint(text_, row);
// Take this opportunity to spellcheck the row contents.
if (row_has_changed && pi.do_spellcheck && lyxrc.spellcheck_continuously) {
text_->getPar(pit).spellCheck();
}
RowPainter rp(pi, *text_, row, row_x, y);
// Don't paint the row if a full repaint has not been requested
// and if it has not changed.
if (!pi.full_repaint && !row_has_changed) {
// Paint only the insets if the text itself is
// unchanged.
rp.paintOnlyInsets();
row.changed(false);
y += row.descent();
continue;
}
@ -1905,21 +1918,28 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
LYXERR(Debug::PAINTING, "Clear rect@("
<< max(row_x, 0) << ", " << y - row.ascent() << ")="
<< width() << " x " << row.height());
pi.pain.fillRectangle(max(row_x, 0), y - row.ascent(),
width(), row.height(), pi.background_color);
// FIXME: this is a hack. We know that at least this
// amount of pixels can be cleared on right and left.
// Doing so gets rid of caret ghosts when the cursor is at
// the begining/end of row. However, it will not work if
// the caret has a ridiculous width like 6. (see ticket
// #10797)
pi.pain.fillRectangle(max(row_x, 0) - Inset::TEXT_TO_INSET_OFFSET,
y - row.ascent(),
width() + 2 * Inset::TEXT_TO_INSET_OFFSET,
row.height(), pi.background_color);
}
// Instrumentation for testing row cache (see also
// 12 lines lower):
if (lyxerr.debugging(Debug::PAINTING)
&& (row.selection() || pi.full_repaint || row_has_changed)) {
string const foreword = text_->isMainText() ?
"main text redraw " : "inset text redraw: ";
LYXERR(Debug::PAINTING, foreword << "pit=" << pit << " row=" << i
<< " row_selection=" << row.selection()
<< " full_repaint=" << pi.full_repaint
<< " row_has_changed=" << row_has_changed
<< " drawingEnabled=" << pi.pain.isDrawingEnabled());
&& (row.selection() || pi.full_repaint || row_has_changed)) {
string const foreword = text_->isMainText() ? "main text redraw "
: "inset text redraw: ";
LYXERR0(foreword << "pit=" << pit << " row=" << i
<< (row.selection() ? " row_selection": "")
<< (pi.full_repaint ? " full_repaint" : "")
<< (row_has_changed ? " row_has_changed" : ""));
}
// Backup full_repaint status and force full repaint
@ -1930,7 +1950,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
rp.paintSelection();
rp.paintAppendix();
rp.paintDepthBar();
rp.paintChangeBar();
if (row.needsChangeBar())
rp.paintChangeBar();
if (i == 0 && !row.isRTL())
rp.paintFirst();
if (i == nrows - 1 && row.isRTL())
@ -1944,11 +1965,25 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
row_x + row.right_x() > bv_->workWidth());
y += row.descent();
#if 0
// This debug code shows on screen which rows are repainted.
// FIXME: since the updates related to caret blinking restrict
// the painter to a small rectangle, the numbers are not
// updated when this happens. Change the code in
// GuiWorkArea::Private::show/hideCaret if this is important.
static int count = 0;
++count;
FontInfo fi(sane_font);
fi.setSize(FONT_SIZE_TINY);
fi.setColor(Color_red);
pi.pain.text(row_x, y, convert<docstring>(count), fi);
#endif
// Restore full_repaint status.
pi.full_repaint = tmp;
row.changed(false);
}
// Re-enable screen drawing for future use of the painter.
pi.pain.setDrawingEnabled(true);
//LYXERR(Debug::PAINTING, ".");
}

View File

@ -62,6 +62,11 @@ public:
///
void newParMetricsUp();
/// The "nodraw" drawing stage for one single paragraph: set the
/// positions of the insets contained this paragraph in metrics
/// cache. Related to BufferView::updatePosCache.
void updatePosCache(pit_type pit) const;
/// Gets the fully instantiated font at a given position in a paragraph
/// Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
/// The difference is that this one is used for displaying, and thus we

View File

@ -17,6 +17,7 @@ liblyxfrontends_a_SOURCES = \
Delegates.h \
KeyModifier.h \
KeySymbol.h \
NullPainter.h \
Painter.h \
Clipboard.h \
Selection.h \

109
src/frontends/NullPainter.h Normal file
View File

@ -0,0 +1,109 @@
// -*- C++ -*-
/**
* \file NullPainter.h
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author unknown
* \author John Levon
* \author Jean-Marc Lasgouttes
*
* Full author contact details are available in file CREDITS.
*/
#ifndef NULLPAINTER_H
#define NULLPAINTER_H
#include "Painter.h"
namespace lyx {
namespace frontend {
/**
* NullPainter - A painter instance that does nothing
*/
class NullPainter : public Painter {
public:
NullPainter() : Painter(1) {}
~NullPainter() {}
/// draw a line from point to point
void line(int, int, int, int, Color,
line_style = line_solid, int = thin_line) {}
///
void lines(int const *, int const *, int, Color,
fill_style = fill_none, line_style = line_solid,
int = thin_line) {}
///
void path(int const *, int const *, int const *, int const *,
int const *, int const *, int, Color,
fill_style = fill_none, line_style = line_solid, int = thin_line) {}
/// draw a rectangle
void rectangle(int, int, int, int, Color,
line_style = line_solid, int = thin_line) {}
/// draw a filled rectangle
void fillRectangle(int, int, int, int, Color) {}
/// draw an arc
void arc(int, int, unsigned int, unsigned int, int, int, Color) {}
/// draw a pixel
void point(int, int, Color) {}
/// draw an image from the image cache
void image(int, int, int, int, graphics::Image const &) {}
/// draw a string
void text(int, int, docstring const &, FontInfo const &) {}
/// draw a char
void text(int, int, char_type, FontInfo const &) {}
/// draw a string
void text(int, int, docstring const &, Font const &, double, double) {}
///
void text(int, int, docstring const &, Font const &,
Color, size_type, size_type, double, double) {}
/// This painter does not paint
bool isNull() const { return true; }
/// draw the underbar, strikeout, xout, uuline and uwave font attributes
void textDecoration(FontInfo const &, int, int, int) {}
/**
* Draw a string and enclose it inside a rectangle. If
* back color is specified, the background is cleared with
* the given color. If frame is specified, a thin frame is drawn
* around the text with the given color.
*/
void rectText(int, int, docstring const &,
FontInfo const &, Color, Color) {}
/// draw a string and enclose it inside a button frame
void buttonText(int, int, docstring const &,
FontInfo const &, Color, Color, int) {}
/// draw a character of a preedit string for cjk support.
int preeditText(int, int, char_type, FontInfo const &,
preedit_style) { return 0; }
/// start monochrome painting mode, i.e. map every color into [min,max]
void enterMonochromeMode(Color const &, Color const &) {}
/// leave monochrome painting mode
void leaveMonochromeMode() {}
/// draws a wavy line that can be used for underlining.
void wavyHorizontalLine(int, int, int, ColorCode) {}
};
} // namespace frontend
} // namespace lyx
#endif // NULLPAINTER_H

View File

@ -49,7 +49,7 @@ namespace frontend {
*/
class Painter {
public:
Painter(double pixel_ratio) : drawing_enabled_(true), pixel_ratio_(pixel_ratio) {}
Painter(double pixel_ratio) : pixel_ratio_(pixel_ratio) {}
static const int thin_line;
@ -147,11 +147,8 @@ public:
Color other, size_type from, size_type to,
double wordspacing, double textwidth) = 0;
void setDrawingEnabled(bool drawing_enabled)
{ drawing_enabled_ = drawing_enabled; }
/// Indicate wether real screen drawing shall be done or not.
bool isDrawingEnabled() const { return drawing_enabled_; }
// Returns true if the painter does not actually paint.
virtual bool isNull() const = 0;
double pixelRatio() const { return pixel_ratio_; }
@ -183,8 +180,6 @@ public:
/// draws a wavy line that can be used for underlining.
virtual void wavyHorizontalLine(int x, int y, int width, ColorCode col) = 0;
private:
///
bool drawing_enabled_;
/// Ratio between physical pixels and device-independent pixels
double pixel_ratio_;
};

View File

@ -36,8 +36,8 @@ public:
///
virtual ~WorkArea() {}
/// redraw the screen, without using existing pixmap
virtual void redraw(bool update_metrics) = 0;
/// Update metrics if needed and schedule a paint event
virtual void scheduleRedraw(bool update_metrics) = 0;
/// close this work area.
/// Slot for Buffer::closing signal.

View File

@ -35,7 +35,7 @@ void WorkAreaManager::remove(WorkArea * wa)
void WorkAreaManager::redrawAll(bool update_metrics)
{
for (WorkArea * wa : work_areas_)
wa->redraw(update_metrics);
wa->scheduleRedraw(update_metrics);
}

View File

@ -65,8 +65,8 @@ FindAndReplaceWidget::FindAndReplaceWidget(GuiView & view)
replace_work_area_->setFrameStyle(QFrame::StyledPanel);
// We don't want two cursors blinking.
find_work_area_->stopBlinkingCursor();
replace_work_area_->stopBlinkingCursor();
find_work_area_->stopBlinkingCaret();
replace_work_area_->stopBlinkingCaret();
}

View File

@ -1435,7 +1435,7 @@ void GuiApplication::updateCurrentView(FuncRequest const & cmd, DispatchResult &
theSelection().haveSelection(bv->cursor().selection());
// update gui
current_view_->restartCursor();
current_view_->restartCaret();
}
if (dr.needMessageUpdate()) {
// Some messages may already be translated, so we cannot use _()
@ -1629,14 +1629,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
case LFUN_SCREEN_FONT_UPDATE: {
// handle the screen font changes.
d->font_loader_.update();
// Backup current_view_
GuiView * view = current_view_;
// Set current_view_ to zero to forbid GuiWorkArea::redraw()
// to skip the refresh.
current_view_ = 0;
theBufferList().changed(false);
// Restore current_view_
current_view_ = view;
dr.screenUpdate(Update::Force | Update::FitCursor);
break;
}
@ -2151,7 +2144,7 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
if (!keysym.isOK())
LYXERR(Debug::KEY, "Empty kbd action (probably composing)");
if (current_view_)
current_view_->restartCursor();
current_view_->restartCaret();
return;
}
@ -2211,7 +2204,7 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
if (!isPrintable(encoded_last_key)) {
LYXERR(Debug::KEY, "Non-printable character! Omitting.");
if (current_view_)
current_view_->restartCursor();
current_view_->restartCaret();
return;
}
// The following modifier check is not needed on Mac.
@ -2233,7 +2226,7 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
{
if (current_view_) {
current_view_->message(_("Unknown function."));
current_view_->restartCursor();
current_view_->restartCaret();
}
return;
}
@ -2248,7 +2241,7 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
LYXERR(Debug::KEY, "Unknown Action and not isText() -- giving up");
if (current_view_) {
current_view_->message(_("Unknown function."));
current_view_->restartCursor();
current_view_->restartCaret();
}
return;
}

View File

@ -31,9 +31,9 @@
#include "insets/InsetListingsParams.h"
#include "insets/InsetInclude.h"
#include <QPushButton>
#include <QCheckBox>
#include <QLineEdit>
#include <QPushButton>
#include <utility>

View File

@ -52,10 +52,10 @@ GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
: QPainter(device), Painter(pixel_ratio),
use_pixmap_cache_(false)
{
// new QPainter has default QPen:
current_color_ = guiApp->colorCache().get(Color_black);
current_ls_ = line_solid;
current_lw_ = thin_line;
// set cache correctly
current_color_ = pen().color();
current_ls_ = pen().style() == Qt::DotLine ? line_onoffdash : line_solid;
current_lw_ = pen().width();
}
@ -171,9 +171,6 @@ void GuiPainter::leaveMonochromeMode()
void GuiPainter::point(int x, int y, Color col)
{
if (!isDrawingEnabled())
return;
setQPainterPen(computeColor(col));
drawPoint(x, y);
}
@ -184,9 +181,6 @@ void GuiPainter::line(int x1, int y1, int x2, int y2,
line_style ls,
int lw)
{
if (!isDrawingEnabled())
return;
setQPainterPen(computeColor(col), ls, lw);
bool const do_antialiasing = renderHints() & TextAntialiasing
&& x1 != x2 && y1 != y2 && ls != line_solid_aliased;
@ -202,9 +196,6 @@ void GuiPainter::lines(int const * xp, int const * yp, int np,
line_style ls,
int lw)
{
if (!isDrawingEnabled())
return;
// double the size if needed
// FIXME THREAD
static QVector<QPoint> points(32);
@ -247,9 +238,6 @@ void GuiPainter::path(int const * xp, int const * yp,
line_style ls,
int lw)
{
if (!isDrawingEnabled())
return;
QPainterPath bpath;
// This is the starting point, so its control points are meaningless
bpath.moveTo(xp[0], yp[0]);
@ -278,9 +266,6 @@ void GuiPainter::rectangle(int x, int y, int w, int h,
line_style ls,
int lw)
{
if (!isDrawingEnabled())
return;
setQPainterPen(computeColor(col), ls, lw);
drawRect(x, y, w, h);
}
@ -288,9 +273,6 @@ void GuiPainter::rectangle(int x, int y, int w, int h,
void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
{
if (!isDrawingEnabled())
return;
fillRect(x, y, w, h, guiApp->colorCache().get(col));
}
@ -298,9 +280,6 @@ void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
int a1, int a2, Color col)
{
if (!isDrawingEnabled())
return;
// LyX usings 1/64ths degree, Qt usings 1/16th
setQPainterPen(computeColor(col));
bool const do_antialiasing = renderHints() & TextAntialiasing;
@ -317,9 +296,6 @@ void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
fillRectangle(x, y, w, h, Color_graphicsbg);
if (!isDrawingEnabled())
return;
QImage const image = qlimage.image();
QRectF const drect = QRectF(x, y, w, h);
QRectF const srect = QRectF(0, 0, image.width(), image.height());
@ -391,7 +367,7 @@ void GuiPainter::text(int x, int y, docstring const & s,
double const wordspacing, double const tw)
{
//LYXERR0("text: x=" << x << ", s=" << s);
if (s.empty() || !isDrawingEnabled())
if (s.empty())
return;
/* Caution: The following ucs4 to QString conversions work for symbol fonts

View File

@ -37,6 +37,9 @@ public:
GuiPainter(QPaintDevice *, double pixel_ratio);
virtual ~GuiPainter();
/// This painter paints
virtual bool isNull() const { return false; }
/// draw a line from point to point
virtual void line(
int x1, int y1,

View File

@ -4152,7 +4152,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
// painting so we must reset it.
QPixmapCache::clear();
guiApp->fontLoader().update();
lyx::dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
dr.screenUpdate(Update::Force | Update::FitCursor);
break;
}
@ -4348,19 +4348,19 @@ Buffer const * GuiView::updateInset(Inset const * inset)
continue;
Buffer const * buffer = &(wa->bufferView().buffer());
if (inset_buffer == buffer)
wa->scheduleRedraw();
wa->scheduleRedraw(true);
}
return inset_buffer;
}
void GuiView::restartCursor()
void GuiView::restartCaret()
{
/* When we move around, or type, it's nice to be able to see
* the cursor immediately after the keypress.
* the caret immediately after the keypress.
*/
if (d.current_work_area_)
d.current_work_area_->startBlinkingCursor();
d.current_work_area_->startBlinkingCaret();
// Take this occasion to update the other GUI elements.
updateDialogs();
@ -4429,7 +4429,7 @@ void GuiView::resetDialogs()
// Now update controls with current buffer.
guiApp->setCurrentView(this);
restoreLayout();
restartCursor();
restartCaret();
}

View File

@ -116,7 +116,7 @@ public:
/// \return true if the \c FuncRequest has been dispatched.
void dispatch(FuncRequest const & cmd, DispatchResult & dr);
void restartCursor();
void restartCaret();
/// Update the completion popup and the inline completion state.
/// If \c start is true, then a new completion might be started.
/// If \c keep is true, an active completion will be kept active

View File

@ -16,7 +16,13 @@
#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"
@ -26,19 +32,14 @@
#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"
@ -46,7 +47,6 @@
#include "support/convert.h"
#include "support/debug.h"
#include "support/gettext.h"
#include "support/lassert.h"
#include "support/TempFile.h"
@ -68,7 +68,6 @@
#include <QMenu>
#include <QPainter>
#include <QPalette>
#include <QPixmapCache>
#include <QScrollBar>
#include <QStyleOption>
#include <QStylePainter>
@ -77,8 +76,6 @@
#include <QToolTip>
#include <QMenuBar>
#include "support/bind.h"
#include <cmath>
#include <iostream>
@ -130,17 +127,15 @@ mouse_button::state q_motion_state(Qt::MouseButtons state)
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();
@ -149,7 +144,7 @@ public:
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_);
@ -157,7 +152,7 @@ public:
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
@ -168,8 +163,8 @@ public:
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);
}
}
}
@ -203,38 +198,32 @@ public:
r = max(r, TabIndicatorWidth);
}
// compute overall rectangle
rect_ = QRect(x - l, y, cursor_width_ + r + l, h);
}
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
//FIXME: LyXRC::cursor_width should be caret_width
caret_width_ = lyxrc.cursor_width
? lyxrc.cursor_width
: 1 + int((lyxrc.currentZoom + 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_;
};
@ -246,13 +235,35 @@ SyntheticMouseEvent::SyntheticMouseEvent()
GuiWorkArea::Private::Private(GuiWorkArea * parent)
: p(parent), screen_(0), buffer_view_(0), lyx_view_(0),
cursor_visible_(false), cursor_(0),
need_resize_(false), schedule_redraw_(false), preedit_lines_(1),
pixel_ratio_(1.0),
: 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_;
}
@ -287,24 +298,19 @@ double GuiWorkArea::pixelRatio() const
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);
@ -313,63 +319,33 @@ void GuiWorkArea::init()
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);
d->updateCursorShape();
// we paint our own background
viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
setFocusPolicy(Qt::StrongFocus);
d->setCursorShape(Qt::IBeamCursor);
// This connection is closed at the same time as this is destroyed.
d->synthetic_mouse_event_.timeout.timeout.connect([this](){
generateSyntheticMouseEvent();
});
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()
{
// If something is wrong with the buffer, we can ignore it safely
try {
d->buffer_view_->buffer().workAreaManager().remove(this);
} catch(...) {}
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);
}
@ -436,14 +412,14 @@ BufferView const & GuiWorkArea::bufferView() const
}
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())
@ -451,23 +427,32 @@ void GuiWorkArea::startBlinkingCursor()
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.
@ -483,17 +468,14 @@ void GuiWorkArea::redraw(bool update_metrics)
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();
@ -526,9 +508,9 @@ void GuiWorkArea::processKeySym(KeySymbol const & key, KeyModifier mod)
}
// 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);
}
@ -546,9 +528,9 @@ void GuiWorkArea::Private::dispatch(FuncRequest const & cmd)
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);
@ -568,8 +550,8 @@ void GuiWorkArea::Private::dispatch(FuncRequest const & 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();
@ -580,18 +562,18 @@ void GuiWorkArea::Private::resizeBufferView()
{
// 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
@ -601,23 +583,20 @@ void GuiWorkArea::Private::resizeBufferView()
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
@ -634,40 +613,41 @@ void GuiWorkArea::Private::showCursor()
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());
}
@ -690,7 +670,7 @@ void GuiWorkArea::Private::updateScrollbar()
void GuiWorkArea::scrollTo(int value)
{
stopBlinkingCursor();
stopBlinkingCaret();
d->buffer_view_->scrollDocView(value, true);
if (lyxrc.cursor_follows_scrollbar) {
@ -698,8 +678,8 @@ void GuiWorkArea::scrollTo(int value)
// 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();
@ -805,7 +785,7 @@ void GuiWorkArea::focusInEvent(QFocusEvent * e)
d->lyx_view_->currentWorkArea()->bufferView().buffer().updateBuffer();
}
startBlinkingCursor();
startBlinkingCaret();
QAbstractScrollArea::focusInEvent(e);
}
@ -813,7 +793,7 @@ void GuiWorkArea::focusInEvent(QFocusEvent * e)
void GuiWorkArea::focusOutEvent(QFocusEvent * e)
{
LYXERR(Debug::DEBUG, "GuiWorkArea::focusOutEvent(): " << this << endl);
stopBlinkingCursor();
stopBlinkingCaret();
QAbstractScrollArea::focusOutEvent(e);
}
@ -1176,157 +1156,31 @@ void GuiWorkArea::resizeEvent(QResizeEvent * ev)
}
void GuiWorkArea::Private::update(int x, int y, int w, int h)
void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
{
p->viewport()->update(x, y, w, h);
}
void GuiWorkArea::paintEvent(QPaintEvent * ev)
{
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 && FIXME
// 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);
// FIXME: shall we use real_current_font here? (see #10478)
FontInfo font = d->buffer_view_->cursor().getFont().fontInfo();
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?
@ -1335,12 +1189,12 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
// 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;
@ -1353,20 +1207,20 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
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
@ -1385,11 +1239,81 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
// draw one character and update cur_x.
cur_x += pain.preeditText(cur_x, cur_y, typed_char, font, ps);
}
}
// update the preedit string screen area.
update(0, cur_y - d->preedit_lines_*height, viewport()->width(),
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;
}
// 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();
}
@ -1402,12 +1326,12 @@ QVariant GuiWorkArea::inputMethodQuery(Qt::InputMethodQuery query) const
// 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);
@ -1441,12 +1365,6 @@ bool GuiWorkArea::isFullScreen() const
}
void GuiWorkArea::scheduleRedraw()
{
d->schedule_redraw_ = true;
}
bool GuiWorkArea::inDialogMode() const
{
return d->dialog_mode_;
@ -1529,7 +1447,7 @@ QSize EmbeddedWorkArea::sizeHint () const
void EmbeddedWorkArea::disable()
{
stopBlinkingCursor();
stopBlinkingCaret();
if (view().currentWorkArea() != this)
return;
// No problem if currentMainWorkArea() is 0 (setCurrentWorkArea()
@ -1859,7 +1777,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);

View File

@ -27,10 +27,6 @@ class QDropEvent;
class QToolButton;
class QWidget;
#ifdef CursorShape
#undef CursorShape
#endif
namespace lyx {
class Buffer;
@ -64,13 +60,11 @@ public:
/// is GuiView in fullscreen mode?
bool isFullScreen() const;
///
void scheduleRedraw();
///
BufferView & bufferView();
///
BufferView const & bufferView() const;
///
void redraw(bool update_metrics);
void scheduleRedraw(bool update_metrics);
/// return true if the key is part of a shortcut
bool queryKeySym(KeySymbol const & key, KeyModifier mod) const;
@ -81,8 +75,6 @@ public:
///
GuiCompleter & completer();
Qt::CursorShape cursorShape() const;
/// Return the GuiView this workArea belongs to
GuiView const & view() const;
GuiView & view();
@ -95,9 +87,9 @@ public Q_SLOTS:
/// This needs to be public because it is accessed externally by GuiView.
void processKeySym(KeySymbol const & key, KeyModifier mod);
///
void stopBlinkingCursor();
void stopBlinkingCaret();
///
void startBlinkingCursor();
void startBlinkingCaret();
Q_SIGNALS:
///
@ -118,8 +110,8 @@ private Q_SLOTS:
void scrollTo(int value);
/// timer to limit triple clicks
void doubleClickTimeout();
/// toggle the cursor's visibility
void toggleCursor();
/// toggle the caret's visibility
void toggleCaret();
/// close this work area.
/// Slot for Buffer::closing signal.
void close();

View File

@ -13,28 +13,19 @@
#define WORKAREA_PRIVATE_H
#include "FuncRequest.h"
#include "LyXRC.h"
#include "support/FileName.h"
#include "support/Timeout.h"
#include <QMouseEvent>
#include <QImage>
#include <QPixmap>
#include <QTimer>
class QContextMenuEvent;
class QDragEnterEvent;
class QDropEvent;
class QKeyEvent;
class QPaintEvent;
class QResizeEvent;
class QToolButton;
class QWheelEvent;
class QWidget;
#ifdef CursorShape
#undef CursorShape
#ifdef Q_OS_MAC
/* Qt on macOS 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 this case */
#define LYX_BACKINGSTORE 1
#include <QPainter>
#endif
namespace lyx {
@ -44,6 +35,7 @@ class Buffer;
namespace frontend {
class GuiCompleter;
class GuiPainter;
class GuiView;
class GuiWorkArea;
@ -86,96 +78,104 @@ public:
/**
* Implementation of the work area (buffer view GUI)
*/
class CursorWidget;
class CaretWidget;
struct GuiWorkArea::Private
{
///
Private(GuiWorkArea *);
/// update the passed area.
void update(int x, int y, int w, int h);
///
void updateScreen();
~Private();
///
void resizeBufferView();
/// paint the cursor and store the background
void showCursor(int x, int y, int h,
bool l_shape, bool rtl, bool completable);
/// hide the cursor
void removeCursor();
///
void dispatch(FuncRequest const & cmd0);
/// hide the visible cursor, if it is visible
void hideCursor();
/// show the cursor if it is not visible
void showCursor();
/// recompute the shape and position of the caret
void updateCaretGeometry();
/// show the caret if it is not visible
void showCaret();
/// hide the caret if it is visible
void hideCaret();
/// Set the range and value of the scrollbar and connect to its valueChanged
/// signal.
void updateScrollbar();
/// Change the cursor when the mouse hovers over a clickable inset
void updateCursorShape();
///
void setCursorShape(Qt::CursorShape shape);
bool needResize() const {
return need_resize_ || p->pixelRatio() != pixel_ratio_;
void paintPreeditText(GuiPainter & pain);
void resetScreen() {
#ifdef LYX_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
#endif
}
void resetScreen()
{
delete screen_;
pixel_ratio_ = p->pixelRatio();
if (lyxrc.use_qimage) {
QImage *x =
new QImage(static_cast<int>(pixel_ratio_ * p->viewport()->width()),
static_cast<int>(pixel_ratio_ * p->viewport()->height()),
QImage::Format_ARGB32_Premultiplied);
#if QT_VERSION >= 0x050000
x->setDevicePixelRatio(pixel_ratio_);
QPaintDevice * screenDevice() {
#ifdef LYX_BACKINGSTORE
return &screen_;
#else
return p->viewport();
#endif
screen_ = x;
} else {
QPixmap *x =
new QPixmap(static_cast<int>(pixel_ratio_ * p->viewport()->width()),
static_cast<int>(pixel_ratio_ * p->viewport()->height()));
#if QT_VERSION >= 0x050000
x->setDevicePixelRatio(pixel_ratio_);
#endif
screen_ = x;
}
}
#ifdef LYX_BACKINGSTORE
void updateScreen(QRectF const & rc) {
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);
}
#else
void updateScreen(QRectF const & ) {}
#endif
///
GuiWorkArea * p;
///
QPaintDevice * screen_;
///
BufferView * buffer_view_;
///
GuiView * lyx_view_;
/// is the cursor currently displayed
bool cursor_visible_;
#ifdef LYX_BACKINGSTORE
///
QTimer cursor_timeout_;
QImage screen_;
#endif
///
CaretWidget * caret_;
/// is the caret currently displayed
bool caret_visible_;
///
QTimer caret_timeout_;
///
SyntheticMouseEvent synthetic_mouse_event_;
///
DoubleClick dc_event_;
///
CursorWidget * cursor_;
///
bool need_resize_;
///
bool schedule_redraw_;
///
/// the current preedit text of the input method
docstring preedit_string_;
/// Number of lines used by preedit text
int preedit_lines_;
/// the attributes of the preedit text
QList<QInputMethodEvent::Attribute> preedit_attr_;
/// Ratio between physical pixels and device-independent pixels
/// We save the last used value to detect changes of the
/// current pixel_ratio of the viewport.
double pixel_ratio_;
double last_pixel_ratio_;
///
GuiCompleter * completer_;

View File

@ -231,25 +231,17 @@ void InsetDisplayLabelBox::metrics(MetricsInfo & mi, Dimension & dim) const
{
InsetLabelBox::metrics(mi, dim);
if (!parent_.editing(mi.base.bv)
&& parent_.cell(parent_.displayIdx()).empty()) {
dim.wid = 0;
dim.asc = 0;
dim.des = 0;
}
&& parent_.cell(parent_.displayIdx()).empty())
dim.clear();
}
void InsetDisplayLabelBox::draw(PainterInfo & pi, int x, int y) const
{
if (parent_.editing(pi.base.bv)
|| !parent_.cell(parent_.displayIdx()).empty()) {
InsetLabelBox::draw(pi, x, y);
} else {
bool enabled = pi.pain.isDrawingEnabled();
pi.pain.setDrawingEnabled(false);
InsetLabelBox::draw(pi, x, y);
pi.pain.setDrawingEnabled(enabled);
}
|| !parent_.cell(parent_.displayIdx()).empty()
|| pi.pain.isNull())
InsetLabelBox::draw(pi, x, y);
}

View File

@ -21,13 +21,16 @@ namespace Update {
/// Recenter the screen around the cursor if is found outside the
/// visible area.
FitCursor = 1,
/// Force a full screen metrics update.
/// Force a full screen metrics update and a full draw.
Force = 2,
/// Force a full redraw (but no metrics computations)
ForceDraw = 4,
/// Try to rebreak only the current paragraph metrics.
SinglePar = 4,
/// (currently ignored!)
SinglePar = 8,
/// Only the inset decorations need to be redrawn, no text metrics
/// update is needed.
Decoration = 8
Decoration = 16
};
inline flags operator|(flags const f, flags const g)
@ -40,6 +43,11 @@ inline flags operator&(flags const f, flags const g)
return static_cast<flags>(int(f) & int(g));
}
inline flags operator~(flags const f)
{
return static_cast<flags>(~int(f));
}
} // namespace Update
} // namespace lyx

View File

@ -25,6 +25,11 @@ What's new
* USER INTERFACE
- Overhaul the document painting mechanism. Now the screen is updated
asyncronously (as all normal applications do), which makes LyX
snappier, especially on repeated events. As an added bonus, subpixel
aliasing is honored in the work area.
- Handle properly top/bottom of inset with mac-like cursor movement
(bug 10701).