Merge remote-tracking branch 'features/properpaint'

Now painting the workarea is done at paint events as should be.
Explicit painting after updating metrics has been replaced by a much
lighter procedure (updatePosCache) to update the insets positions cache.

Expected benefits:
- better performance
- proper use of subpixel aliasing

The LyXRC variable use_qimage is not needed anymore and is therefore removed.
This commit is contained in:
Jean-Marc Lasgouttes 2017-09-08 17:05:13 +02:00
commit 3d590d3bd7
26 changed files with 533 additions and 559 deletions

View File

@ -67,31 +67,6 @@ drawing from metrics.
Other changes are only clean-ups.
** 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
@ -107,19 +82,19 @@ 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
@ -145,7 +120,7 @@ update flag is Update::None.
** Metrics computation
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
@ -155,8 +130,9 @@ all the paragraphs of the inset.
** 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 +162,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

@ -1772,7 +1772,7 @@ if __name__ == '__main__':
lyx_check_config = True
lyx_kpsewhich = True
outfile = 'lyxrc.defaults'
lyxrc_fileformat = 22
lyxrc_fileformat = 23
rc_entries = ''
lyx_keep_temps = False
version_suffix = ''

View File

@ -94,6 +94,9 @@
# Add pygmentize_command for the python pygments syntax highlighter
# No conversion necessary.
# Incremented to format 23, by lasgouttes
# Remove use_qimage preference
# NOTE: The format should also be updated in LYXRC.cpp and
# in configure.py.
@ -364,6 +367,20 @@ def remove_print_support(line):
# End conversions for LyX 2.1 to 2.2
####################################
#################################
# Conversions from LyX 2.3 to 2.4
def remove_use_qimage(line):
if not line.lower().startswith("\\use_qimage "):
return no_match
return (True, "")
# End conversions for LyX 2.3 to 2.4
####################################
conversions = [
[ 1, [ # there were several conversions for format 1
export_menu,
@ -392,5 +409,6 @@ conversions = [
[ 19, [remove_print_support]],
[ 20, []],
[ 21, []],
[ 22, []]
[ 22, []],
[ 23, [remove_use_qimage]]
]

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"
@ -234,7 +235,7 @@ struct BufferView::Private
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;
}
@ -311,6 +312,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_;
};
@ -2651,6 +2658,7 @@ bool BufferView::singleParUpdate()
// the singlePar optimisation.
return false;
tm.updatePosCache(bottom_pit);
d->update_strategy_ = SingleParUpdate;
LYXERR(Debug::PAINTING, "\ny1: " << pm.position() - pm.ascent()
@ -2704,6 +2712,7 @@ void BufferView::updateMetrics()
// in the first line.
}
anchor_pm.setPosition(d->anchor_ypos_);
tm.updatePosCache(d->anchor_pit_);
LYXERR(Debug::PAINTING, "metrics: "
<< " anchor pit = " << d->anchor_pit_
@ -2719,6 +2728,7 @@ void BufferView::updateMetrics()
y1 -= pm.descent();
// Save the paragraph position in the cache.
pm.setPosition(y1);
tm.updatePosCache(pit1);
y1 -= pm.ascent();
}
@ -2732,6 +2742,7 @@ void BufferView::updateMetrics()
y2 += pm.ascent();
// Save the paragraph position in the cache.
pm.setPosition(y2);
tm.updatePosCache(pit2);
y2 += pm.descent();
}
@ -2745,6 +2756,9 @@ void BufferView::updateMetrics()
d->update_strategy_ = FullScreenUpdate;
// Now update the positions of insets in the cache.
updatePosCache();
if (lyxerr.debugging(Debug::WORKAREA)) {
LYXERR(Debug::WORKAREA, "BufferView::updateMetrics");
d->coord_cache_.dump();
@ -2752,6 +2766,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);
@ -2886,7 +2909,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;
@ -2959,7 +2982,24 @@ void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
}
void BufferView::checkCursorScrollOffset(PainterInfo & pi)
namespace {
bool sliceInRow(CursorSlice const & cs, Text const * text, Row const & row)
{
return !cs.empty() && cs.text() == text && cs.pit() == row.pit()
&& 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());
@ -2976,35 +3016,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_;
@ -3048,7 +3059,7 @@ 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;
@ -3059,19 +3070,35 @@ void BufferView::draw(frontend::Painter & pain)
int const y = tm.first().second->position();
PainterInfo pi(this, pain);
CursorSlice const & bottomSlice = d->cursor_.bottom();
/** A repaint of the previous cursor row is needed if
* 1/ the caret will be painted and is is not the same than the
* already painted one;
* 2/ the caret will not be painted, but there is already one on
* screen.
*/
d->repaint_caret_row_ = (paint_caret && bottomSlice != d->caret_slice_)
|| (! paint_caret && !d->caret_slice_.empty());
// 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 = false;
if (pain.isNull()) {
pi.full_repaint = true;
pi.pain.setDrawingEnabled(false);
tm.draw(pi, 0, y);
} else if (d->repaint_caret_row_) {
pi.full_repaint = false;
tm.draw(pi, 0, y);
}
break;
case SingleParUpdate:
@ -3134,6 +3161,12 @@ void BufferView::draw(frontend::Painter & pain)
}
LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_
<< " anchor ypos = " << d->anchor_ypos_);
// Remember what has just been done for the next draw() step
if (paint_caret)
d->caret_slice_ = bottomSlice;
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;
@ -132,6 +132,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 +286,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 +307,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();
@ -367,7 +373,7 @@ private:
// 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

@ -454,19 +454,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

@ -216,8 +216,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

@ -59,7 +59,7 @@ namespace {
// The format should also be updated in configure.py, and conversion code
// should be added to prefs2prefs_prefs.py.
static unsigned int const LYXRC_FILEFORMAT = 22; // ef: pygmentize_command
static unsigned int const LYXRC_FILEFORMAT = 23; // lasgouttes: remove qimage
// when adding something to this array keep it sorted!
LexerKeyword lyxrcTags[] = {
@ -195,7 +195,6 @@ LexerKeyword lyxrcTags[] = {
{ "\\use_converter_needauth_forbidden", LyXRC::RC_USE_CONVERTER_NEEDAUTH_FORBIDDEN },
{ "\\use_lastfilepos", LyXRC::RC_USELASTFILEPOS },
{ "\\use_pixmap_cache", LyXRC::RC_USE_PIXMAP_CACHE },
{ "\\use_qimage", LyXRC::RC_USE_QIMAGE },
// compatibility with versions older than 1.4.0 only
{ "\\use_system_colors", LyXRC::RC_USE_SYSTEM_COLORS },
{ "\\use_system_theme_icons", LyXRC::RC_USE_SYSTEM_THEME_ICONS },
@ -327,7 +326,6 @@ void LyXRC::setDefaults()
use_system_colors = false;
use_tooltip = true;
use_pixmap_cache = false;
use_qimage = true;
converter_cache_maxage = 6 * 30 * 24 * 3600; // 6 months
user_name = to_utf8(support::user_name());
user_email = to_utf8(support::user_email());
@ -880,9 +878,6 @@ LyXRC::ReturnValues LyXRC::read(Lexer & lexrc, bool check_format)
case RC_USE_PIXMAP_CACHE:
lexrc >> use_pixmap_cache;
break;
case RC_USE_QIMAGE:
lexrc >> use_qimage;
break;
case RC_SPELLCHECKER:
lexrc >> spellchecker;
break;
@ -2434,15 +2429,6 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c
if (tag != RC_LAST)
break;
// fall through
case RC_USE_QIMAGE:
if (ignore_system_lyxrc ||
use_qimage != system_lyxrc.use_qimage) {
os << "\\use_qimage "
<< convert<string>(use_qimage)
<< '\n';
}
if (tag != RC_LAST)
break;
os << "\n#\n"
<< "# LANGUAGE SUPPORT SECTION ##########################\n"
@ -3016,7 +3002,6 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new)
case LyXRC::RC_USE_SYSTEM_COLORS:
case LyXRC::RC_USE_TOOLTIP:
case LyXRC::RC_USE_PIXMAP_CACHE:
case LyXRC::RC_USE_QIMAGE:
case LyXRC::RC_USE_SYSTEM_THEME_ICONS:
case LyXRC::RC_VIEWDVI_PAPEROPTION:
case LyXRC::RC_SINGLE_CLOSE_TAB_BUTTON:

View File

@ -174,7 +174,6 @@ public:
RC_USE_SYSTEM_COLORS,
RC_USE_TOOLTIP,
RC_USE_PIXMAP_CACHE,
RC_USE_QIMAGE,
RC_USE_SYSTEM_THEME_ICONS,
RC_VIEWDVI_PAPEROPTION,
RC_VIEWER,
@ -334,8 +333,6 @@ public:
bool use_system_colors;
/// Use pixmap cache?
bool use_pixmap_cache;
/// Use QImage backend?
bool use_qimage;
/// Spellchecker engine: aspell, hunspell, etc
std::string spellchecker;
/// Alternate language for spellchecker

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

@ -576,7 +576,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,6 +43,7 @@
#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"
#include "frontends/NullPainter.h"
#include "support/debug.h"
#include "support/lassert.h"
@ -198,6 +199,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;
@ -1220,6 +1229,7 @@ void TextMetrics::newParMetricsDown()
redoParagraph(pit);
par_metrics_[pit].setPosition(last.second.position()
+ last.second.descent() + par_metrics_[pit].ascent());
updatePosCache(pit);
}
@ -1234,6 +1244,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
@ -1803,8 +1814,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];
@ -1856,17 +1867,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;
}
@ -1888,13 +1893,16 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
// Row signature; has row changed since last paint?
row.setCrc(pm.computeRowSignature(row, *bv_));
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) {
@ -1925,7 +1933,7 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
<< " row_selection=" << row.selection()
<< " full_repaint=" << pi.full_repaint
<< " row_has_changed=" << row_has_changed
<< " drawingEnabled=" << pi.pain.isDrawingEnabled());
<< " null painter=" << pi.pain.isNull());
}
// Backup full_repaint status and force full repaint
@ -1953,8 +1961,6 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
// Restore full_repaint status.
pi.full_repaint = tmp;
}
// 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

@ -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_; }

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

@ -122,7 +122,6 @@
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <QX11Info>
#undef CursorShape
#undef None
#elif defined(QPA_XCB)
#include <xcb/xcb.h>
@ -1439,7 +1438,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 _()
@ -2158,7 +2157,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;
}
@ -2218,7 +2217,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.
@ -2240,7 +2239,7 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
{
if (current_view_) {
current_view_->message(_("Unknown function."));
current_view_->restartCursor();
current_view_->restartCaret();
}
return;
}
@ -2255,7 +2254,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

@ -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

@ -4351,13 +4351,13 @@ Buffer const * GuiView::updateInset(Inset const * inset)
}
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();
@ -4426,7 +4426,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),
: p(parent), buffer_view_(0), lyx_view_(0),
caret_(0), caret_visible_(false),
need_resize_(false), schedule_redraw_(false), preedit_lines_(1),
pixel_ratio_(1.0),
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_;
}
@ -285,24 +296,18 @@ 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);
@ -311,63 +316,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);
}
@ -434,14 +409,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())
@ -449,19 +424,28 @@ 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::toggleCaret()
{
if (d->caret_visible_)
d->hideCaret();
else
d->showCaret();
}
@ -481,16 +465,15 @@ 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();
if (d->caret_visible_) {
d->hideCaret();
d->showCaret();
}
LYXERR(Debug::WORKAREA, "WorkArea::redraw screen");
d->updateScreen();
update(0, 0, viewport()->width(), viewport()->height());
viewport()->update();
/// \warning: scrollbar updating *must* be done after the BufferView is drawn
/// because \c BufferView::updateScrollbar() is called in \c BufferView::draw().
@ -524,9 +507,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);
}
@ -544,9 +527,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);
@ -566,8 +549,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();
@ -578,18 +561,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();
p->viewport()->update();
// Update scrollbars which might have changed due different
// BufferView dimension. This is especially important when the
@ -599,23 +582,23 @@ 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::showCaret()
{
if (cursor_visible_)
if (caret_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
@ -632,40 +615,45 @@ 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);
if (schedule_redraw_) {
// This happens when a graphic conversion is finished. As we don't know
// the size of the new graphics, it's better the update everything.
// We can't use redraw() here because this would trigger a infinite
// recursive loop with showCaret().
buffer_view_->resize(p->viewport()->width(), p->viewport()->height());
p->viewport()->update();
updateScrollbar();
schedule_redraw_ = false;
return;
}
p->viewport()->update(caret_->rect());
}
void GuiWorkArea::Private::hideCursor()
void GuiWorkArea::Private::hideCaret()
{
if (!cursor_visible_)
if (!caret_visible_)
return;
cursor_visible_ = false;
removeCursor();
}
void GuiWorkArea::toggleCursor()
{
if (d->cursor_visible_)
d->hideCursor();
else
d->showCursor();
caret_visible_ = false;
//if (!qApp->focusWidget())
p->viewport()->update(caret_->rect());
}
@ -688,7 +676,7 @@ void GuiWorkArea::Private::updateScrollbar()
void GuiWorkArea::scrollTo(int value)
{
stopBlinkingCursor();
stopBlinkingCaret();
d->buffer_view_->scrollDocView(value, true);
if (lyxrc.cursor_follows_scrollbar) {
@ -696,8 +684,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();
@ -803,7 +791,7 @@ void GuiWorkArea::focusInEvent(QFocusEvent * e)
d->lyx_view_->currentWorkArea()->bufferView().buffer().updateBuffer();
}
startBlinkingCursor();
startBlinkingCaret();
QAbstractScrollArea::focusInEvent(e);
}
@ -811,7 +799,7 @@ void GuiWorkArea::focusInEvent(QFocusEvent * e)
void GuiWorkArea::focusOutEvent(QFocusEvent * e)
{
LYXERR(Debug::DEBUG, "GuiWorkArea::focusOutEvent(): " << this << endl);
stopBlinkingCursor();
stopBlinkingCaret();
QAbstractScrollArea::focusOutEvent(e);
}
@ -1158,157 +1146,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?
@ -1317,12 +1179,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;
@ -1335,20 +1197,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
@ -1367,11 +1229,78 @@ 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->needResize()) {
d->resizeBufferView();
if (d->caret_visible_) {
d->hideCaret();
d->showCaret();
}
}
GuiPainter pain(viewport(), 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);
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();
}
@ -1384,12 +1313,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);
@ -1511,7 +1440,7 @@ QSize EmbeddedWorkArea::sizeHint () const
void EmbeddedWorkArea::disable()
{
stopBlinkingCursor();
stopBlinkingCaret();
if (view().currentWorkArea() != this)
return;
// No problem if currentMainWorkArea() is 0 (setCurrentWorkArea()

View File

@ -26,10 +26,6 @@ class QDropEvent;
class QToolButton;
class QWidget;
#ifdef CursorShape
#undef CursorShape
#endif
namespace lyx {
class Buffer;
@ -83,8 +79,6 @@ public:
///
GuiCompleter & completer();
Qt::CursorShape cursorShape() const;
/// Return the GuiView this workArea belongs to
GuiView const & view() const;
GuiView & view();
@ -94,9 +88,9 @@ public:
public Q_SLOTS:
///
void stopBlinkingCursor();
void stopBlinkingCaret();
///
void startBlinkingCursor();
void startBlinkingCaret();
Q_SIGNALS:
///
@ -115,8 +109,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,30 +13,13 @@
#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
#endif
namespace lyx {
class Buffer;
@ -44,6 +27,7 @@ class Buffer;
namespace frontend {
class GuiCompleter;
class GuiPainter;
class GuiView;
class GuiWorkArea;
@ -86,92 +70,68 @@ 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();
/// hide the visible caret, if it is visible
void hideCaret();
/// show the caret if it is not visible
void showCaret();
/// 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);
void paintPreeditText(GuiPainter & pain);
bool needResize() const {
return need_resize_ || p->pixelRatio() != pixel_ratio_;
}
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_);
#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;
}
}
///
GuiWorkArea * p;
///
QPaintDevice * screen_;
///
BufferView * buffer_view_;
///
GuiView * lyx_view_;
/// is the cursor currently displayed
bool cursor_visible_;
///
QTimer cursor_timeout_;
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.

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()) {
|| !parent_.cell(parent_.displayIdx()).empty()
|| pi.pain.isNull())
InsetLabelBox::draw(pi, x, y);
} else {
bool enabled = pi.pain.isDrawingEnabled();
pi.pain.setDrawingEnabled(false);
InsetLabelBox::draw(pi, x, y);
pi.pain.setDrawingEnabled(enabled);
}
}