mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-09-20 06:49:56 +00:00
Simplify single par drawing:
* ParagraphMetrics::computeRowSignature(): Integrate row's dimensions and selection status in the row signature. * TextMetrics::drawParagraph(): compute the row signature here and rely on that to decide if a redraw is needed or not. * BufferView::Private: get rid of the ViewMetricsInfo member. Just keep the ScreenUpdateStrategy. * BufferView::draw(): full screen update even for singlePar case because the row signature will detect if something needs to be redrawn. * Text3.cpp: get rid of hack following architecture update. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@21650 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
parent
fd34bd3914
commit
926abae753
@ -210,6 +210,13 @@ void gotoInset(BufferView * bv, InsetCode code, bool same_content)
|
||||
/// A map from a Text to the associated text metrics
|
||||
typedef std::map<Text const *, TextMetrics> TextMetricsCache;
|
||||
|
||||
enum ScreenUpdateStrategy {
|
||||
NoScreenUpdate,
|
||||
SingleParUpdate,
|
||||
FullScreenUpdate,
|
||||
DecorationUpdate
|
||||
};
|
||||
|
||||
} // anon namespace
|
||||
|
||||
|
||||
@ -229,7 +236,7 @@ struct BufferView::Private
|
||||
///
|
||||
ScrollbarParameters scrollbarParameters_;
|
||||
///
|
||||
ViewMetricsInfo metrics_info_;
|
||||
ScreenUpdateStrategy update_strategy_;
|
||||
///
|
||||
CoordCache coord_cache_;
|
||||
|
||||
@ -375,12 +382,12 @@ void BufferView::processUpdateFlags(Update::flags flags)
|
||||
// Case when no explicit update is requested.
|
||||
if (!flags) {
|
||||
// no need to redraw anything.
|
||||
d->metrics_info_.update_strategy = NoScreenUpdate;
|
||||
d->update_strategy_ = NoScreenUpdate;
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags == Update::Decoration) {
|
||||
d->metrics_info_.update_strategy = DecorationUpdate;
|
||||
d->update_strategy_ = DecorationUpdate;
|
||||
buffer_.changed();
|
||||
return;
|
||||
}
|
||||
@ -395,12 +402,12 @@ void BufferView::processUpdateFlags(Update::flags flags)
|
||||
return;
|
||||
}
|
||||
if (flags & Update::Decoration) {
|
||||
d->metrics_info_.update_strategy = DecorationUpdate;
|
||||
d->update_strategy_ = DecorationUpdate;
|
||||
buffer_.changed();
|
||||
return;
|
||||
}
|
||||
// no screen update is needed.
|
||||
d->metrics_info_.update_strategy = NoScreenUpdate;
|
||||
d->update_strategy_ = NoScreenUpdate;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1176,7 +1183,7 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd)
|
||||
//FIXME: updateMetrics() does not update paragraph position
|
||||
// This is done at draw() time. So we need a redraw!
|
||||
// But no screen update is needed.
|
||||
d->metrics_info_.update_strategy = NoScreenUpdate;
|
||||
d->update_strategy_ = NoScreenUpdate;
|
||||
buffer_.changed();
|
||||
p = getPos(cur, cur.boundary());
|
||||
}
|
||||
@ -1205,7 +1212,7 @@ Update::flags BufferView::dispatch(FuncRequest const & cmd)
|
||||
}
|
||||
// FIXME: we need to do a redraw again because of the selection
|
||||
// But no screen update is needed.
|
||||
d->metrics_info_.update_strategy = NoScreenUpdate;
|
||||
d->update_strategy_ = NoScreenUpdate;
|
||||
buffer_.changed();
|
||||
updateFlags = Update::Force | Update::FitCursor;
|
||||
break;
|
||||
@ -1334,23 +1341,10 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
|
||||
if (!need_redraw)
|
||||
return;
|
||||
|
||||
// if last metrics update was in singlepar mode, WorkArea::redraw() will
|
||||
// not expose the button for redraw. We adjust here the metrics dimension
|
||||
// to enable a full redraw in any case as this is not costly.
|
||||
TextMetrics & tm = d->text_metrics_[&buffer_.text()];
|
||||
std::pair<pit_type, ParagraphMetrics const *> firstpm = tm.first();
|
||||
std::pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
|
||||
int y1 = firstpm.second->position() - firstpm.second->ascent();
|
||||
int y2 = lastpm.second->position() + lastpm.second->descent();
|
||||
d->metrics_info_ = ViewMetricsInfo(firstpm.first, lastpm.first, y1, y2,
|
||||
FullScreenUpdate, buffer_.text().paragraphs().size());
|
||||
// Reinitialize anchor to first pit.
|
||||
d->anchor_ref_ = firstpm.first;
|
||||
d->offset_ref_ = -y1;
|
||||
LYXERR(Debug::PAINTING,
|
||||
"Mouse hover detected at: (" << cmd.x << ", " << cmd.y << ")"
|
||||
<< "\nTriggering redraw: y1: " << y1 << " y2: " << y2
|
||||
<< " pit1: " << firstpm.first << " pit2: " << lastpm.first);
|
||||
LYXERR(Debug::PAINTING, "Mouse hover detected at: ("
|
||||
<< cmd.x << ", " << cmd.y << ")");
|
||||
|
||||
d->update_strategy_ = DecorationUpdate;
|
||||
|
||||
// This event (moving without mouse click) is not passed further.
|
||||
// This should be changed if it is further utilized.
|
||||
@ -1629,12 +1623,6 @@ pit_type BufferView::anchor_ref() const
|
||||
}
|
||||
|
||||
|
||||
ViewMetricsInfo const & BufferView::viewMetricsInfo()
|
||||
{
|
||||
return d->metrics_info_;
|
||||
}
|
||||
|
||||
|
||||
bool BufferView::singleParUpdate()
|
||||
{
|
||||
Text & buftext = buffer_.text();
|
||||
@ -1653,13 +1641,11 @@ bool BufferView::singleParUpdate()
|
||||
// the singlePar optimisation.
|
||||
return false;
|
||||
|
||||
int y1 = pm.position() - pm.ascent();
|
||||
int y2 = pm.position() + pm.descent();
|
||||
d->metrics_info_ = ViewMetricsInfo(bottom_pit, bottom_pit, y1, y2,
|
||||
SingleParUpdate, buftext.paragraphs().size());
|
||||
d->update_strategy_ = SingleParUpdate;
|
||||
|
||||
LYXERR(Debug::PAINTING, BOOST_CURRENT_FUNCTION
|
||||
<< "\ny1: " << y1
|
||||
<< " y2: " << y2
|
||||
<< "\ny1: " << pm.position() - pm.ascent()
|
||||
<< " y2: " << pm.position() + pm.descent()
|
||||
<< " pit: " << bottom_pit
|
||||
<< " singlepar: 1");
|
||||
return true;
|
||||
@ -1741,8 +1727,7 @@ void BufferView::updateMetrics()
|
||||
<< " npit: " << npit
|
||||
<< " singlepar: 0");
|
||||
|
||||
d->metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2,
|
||||
FullScreenUpdate, npit);
|
||||
d->update_strategy_ = FullScreenUpdate;
|
||||
|
||||
if (lyxerr.debugging(Debug::WORKAREA)) {
|
||||
LYXERR(Debug::WORKAREA, "BufferView::updateMetrics");
|
||||
@ -1928,11 +1913,10 @@ void BufferView::draw(frontend::Painter & pain)
|
||||
LYXERR(Debug::PAINTING, "\t\t*** START DRAWING ***");
|
||||
Text & text = buffer_.text();
|
||||
TextMetrics const & tm = d->text_metrics_[&text];
|
||||
int const y = d->metrics_info_.y1
|
||||
+ tm.parMetrics(d->metrics_info_.p1).ascent();
|
||||
int const y = - d->offset_ref_ + tm.parMetrics(d->anchor_ref_).ascent();
|
||||
PainterInfo pi(this, pain);
|
||||
|
||||
switch (d->metrics_info_.update_strategy) {
|
||||
switch (d->update_strategy_) {
|
||||
|
||||
case NoScreenUpdate:
|
||||
// If no screen painting is actually needed, only some the different
|
||||
@ -1943,9 +1927,11 @@ void BufferView::draw(frontend::Painter & pain)
|
||||
break;
|
||||
|
||||
case SingleParUpdate:
|
||||
// Only the current outermost paragraph will be redrawn.
|
||||
pi.full_repaint = false;
|
||||
tm.drawParagraph(pi, d->metrics_info_.p1, 0, y);
|
||||
// In general, only the current row of the outermost paragraph
|
||||
// will be redrawn. Particular cases where selection spans
|
||||
// multiple paragraph are correctly detected in TextMetrics.
|
||||
tm.draw(pi, 0, y);
|
||||
break;
|
||||
|
||||
case DecorationUpdate:
|
||||
@ -1957,21 +1943,16 @@ void BufferView::draw(frontend::Painter & pain)
|
||||
// The whole screen, including insets, will be refreshed.
|
||||
pi.full_repaint = true;
|
||||
|
||||
// Clear background (if not delegated to rows)
|
||||
pain.fillRectangle(0, d->metrics_info_.y1, width_,
|
||||
d->metrics_info_.y2 - d->metrics_info_.y1,
|
||||
// Clear background.
|
||||
pain.fillRectangle(0, 0, width_, height_,
|
||||
buffer_.inset().backgroundColor());
|
||||
tm.draw(pi, 0, y);
|
||||
|
||||
// and grey out above (should not happen later)
|
||||
if (d->metrics_info_.y1 > 0)
|
||||
pain.fillRectangle(0, 0, width_,
|
||||
d->metrics_info_.y1, Color_bottomarea);
|
||||
|
||||
// and possibly grey out below
|
||||
if (d->metrics_info_.y2 < height_)
|
||||
pain.fillRectangle(0, d->metrics_info_.y2, width_,
|
||||
height_ - d->metrics_info_.y2, Color_bottomarea);
|
||||
std::pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
|
||||
int const y2 = lastpm.second->position() + lastpm.second->descent();
|
||||
if (y2 < height_)
|
||||
pain.fillRectangle(0, y2, width_, height_ - y2, Color_bottomarea);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,6 @@ class ParagraphMetrics;
|
||||
class Point;
|
||||
class Text;
|
||||
class TextMetrics;
|
||||
class ViewMetricsInfo;
|
||||
|
||||
enum CursorStatus {
|
||||
CUR_INSIDE,
|
||||
@ -195,11 +194,6 @@ public:
|
||||
void putSelectionAt(DocIterator const & cur,
|
||||
int length, bool backwards);
|
||||
|
||||
/// return the internal \c ViewMetricsInfo.
|
||||
/// This is used specifically by the \c Workrea.
|
||||
/// \sa WorkArea
|
||||
/// \sa ViewMetricsInfo
|
||||
ViewMetricsInfo const & viewMetricsInfo();
|
||||
/// update the internal \c ViewMetricsInfo.
|
||||
void updateMetrics();
|
||||
|
||||
|
@ -112,38 +112,9 @@ public:
|
||||
|
||||
class TextMetricsInfo {};
|
||||
|
||||
enum ScreenUpdateStrategy {
|
||||
NoScreenUpdate,
|
||||
SingleParUpdate,
|
||||
FullScreenUpdate,
|
||||
DecorationUpdate
|
||||
};
|
||||
|
||||
class ViewMetricsInfo
|
||||
{
|
||||
public:
|
||||
ViewMetricsInfo()
|
||||
: p1(0), p2(0), y1(0), y2(0),
|
||||
update_strategy(FullScreenUpdate), size(0)
|
||||
{}
|
||||
ViewMetricsInfo(pit_type p1, pit_type p2, int y1, int y2,
|
||||
ScreenUpdateStrategy updatestrategy, pit_type size)
|
||||
: p1(p1), p2(p2), y1(y1), y2(y2),
|
||||
update_strategy(updatestrategy), size(size)
|
||||
{}
|
||||
|
||||
pit_type p1;
|
||||
pit_type p2;
|
||||
int y1;
|
||||
int y2;
|
||||
ScreenUpdateStrategy update_strategy;
|
||||
pit_type size;
|
||||
};
|
||||
|
||||
|
||||
// Generic base for temporarily changing things.
|
||||
// The original state gets restored when the Changer is destructed.
|
||||
|
||||
/// Generic base for temporarily changing things.
|
||||
/// The original state gets restored when the Changer is destructed.
|
||||
template <class Struct, class Temp = Struct>
|
||||
class Changer {
|
||||
public:
|
||||
|
@ -97,8 +97,8 @@ void ParagraphMetrics::reset(Paragraph const & par)
|
||||
}
|
||||
|
||||
|
||||
void ParagraphMetrics::computeRowSignature(Row & row,
|
||||
BufferParams const & bparams)
|
||||
size_t ParagraphMetrics::computeRowSignature(Row const & row,
|
||||
BufferParams const & bparams) const
|
||||
{
|
||||
boost::crc_32_type crc;
|
||||
for (pos_type i = row.pos(); i < row.endpos(); ++i) {
|
||||
@ -110,7 +110,12 @@ void ParagraphMetrics::computeRowSignature(Row & row,
|
||||
crc.process_bytes(b, 1);
|
||||
}
|
||||
}
|
||||
row.setCrc(crc.checksum());
|
||||
|
||||
Dimension const & d = row.dimension();
|
||||
char_type const b[] = { row.sel_beg, row.sel_end, d.wid, d.asc, d.des};
|
||||
crc.process_bytes(b, 5);
|
||||
|
||||
return crc.checksum();
|
||||
}
|
||||
|
||||
|
||||
|
@ -87,7 +87,7 @@ public:
|
||||
bool hfillExpansion(Row const & row, pos_type pos) const;
|
||||
|
||||
///
|
||||
void computeRowSignature(Row &, BufferParams const & bparams);
|
||||
size_t computeRowSignature(Row const &, BufferParams const & bparams) const;
|
||||
|
||||
///
|
||||
int position() const { return position_; }
|
||||
|
15
src/Row.cpp
15
src/Row.cpp
@ -35,16 +35,15 @@ Row::Row(pos_type pos)
|
||||
{}
|
||||
|
||||
|
||||
void Row::setCrc(size_type crc)
|
||||
void Row::setCrc(size_type crc) const
|
||||
{
|
||||
changed_ |= crc != crc_;
|
||||
changed_ = crc != crc_;
|
||||
crc_ = crc;
|
||||
}
|
||||
|
||||
|
||||
void Row::setDimension(Dimension const & dim)
|
||||
{
|
||||
changed_ |= dim != dim_;
|
||||
dim_ = dim;
|
||||
}
|
||||
|
||||
@ -63,7 +62,6 @@ void Row::endpos(pos_type p)
|
||||
|
||||
void Row::setSelection(pos_type beg, pos_type end)
|
||||
{
|
||||
pos_type sel_beg_b = sel_beg;
|
||||
if (pos_ >= beg && pos_ <= end)
|
||||
sel_beg = pos_;
|
||||
else if (beg > pos_ && beg <= end_)
|
||||
@ -71,21 +69,12 @@ void Row::setSelection(pos_type beg, pos_type end)
|
||||
else
|
||||
sel_beg = -1;
|
||||
|
||||
pos_type sel_end_b = sel_end;
|
||||
if (end_ >= beg && end_ <= end)
|
||||
sel_end = end_;
|
||||
else if (end < end_ && end >= pos_)
|
||||
sel_end = end;
|
||||
else
|
||||
sel_end = -1;
|
||||
/*
|
||||
&& ((rit->pos() >= beg.pos() && rit->pos() <= end.pos())
|
||||
|| (rit->endpos() >= beg.pos() && rit->endpos() <= end.pos())
|
||||
|| (beg.pos() >= rit->pos() && beg.pos() <= rit->endpos())
|
||||
|| (end.pos() >= rit->pos() && end.pos() <= rit->endpos()));
|
||||
*/
|
||||
changed_ |= sel_beg_b != sel_beg;
|
||||
changed_ |= sel_end_b != sel_end;
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ public:
|
||||
///
|
||||
void setChanged(bool c) { changed_ = c; }
|
||||
///
|
||||
void setCrc(size_type crc);
|
||||
void setCrc(size_type crc) const;
|
||||
///
|
||||
void setSelection(pos_type sel_beg, pos_type sel_end);
|
||||
|
||||
@ -80,9 +80,9 @@ public:
|
||||
pos_type sel_end;
|
||||
private:
|
||||
/// has the Row appearance changed since last drawing?
|
||||
bool changed_;
|
||||
mutable bool changed_;
|
||||
/// CRC of row contents.
|
||||
size_type crc_;
|
||||
mutable size_type crc_;
|
||||
/// first pos covered by this row
|
||||
pos_type pos_;
|
||||
/// one behind last pos covered by this row
|
||||
|
@ -1203,17 +1203,8 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
|
||||
// "auto_region_delete", which defaults to
|
||||
// true (on).
|
||||
|
||||
if (lyxrc.auto_region_delete && cur.selection()) {
|
||||
pit_type const begpit = cur.selBegin().pit();
|
||||
pit_type const endpit = cur.selEnd().pit();
|
||||
if (lyxrc.auto_region_delete && cur.selection())
|
||||
cutSelection(cur, false, false);
|
||||
// When a selection spans multiple paragraphs, the metrics update
|
||||
// mechanism sometimes fail to detect that a full update is needed.
|
||||
// In this case, we force the full update:
|
||||
// (see http://bugzilla.lyx.org/show_bug.cgi?id=4317)
|
||||
if (isMainText(cur.bv().buffer()) && begpit != endpit)
|
||||
cur.updateFlags(Update::Force);
|
||||
}
|
||||
|
||||
cur.clearSelection();
|
||||
Font const old_font = cur.real_current_font;
|
||||
|
@ -359,7 +359,6 @@ bool TextMetrics::redoParagraph(pit_type const pit)
|
||||
pm.reset(par);
|
||||
|
||||
Buffer & buffer = bv_->buffer();
|
||||
BufferParams const & bparams = buffer.params();
|
||||
main_text_ = (text_ == &buffer.text());
|
||||
bool changed = false;
|
||||
|
||||
@ -477,7 +476,6 @@ bool TextMetrics::redoParagraph(pit_type const pit)
|
||||
row.setDimension(dim);
|
||||
int const max_row_width = max(dim_.wid, dim.wid);
|
||||
computeRowMetrics(pit, row, max_row_width);
|
||||
pm.computeRowSignature(row, bparams);
|
||||
first = end;
|
||||
++row_index;
|
||||
|
||||
@ -502,7 +500,6 @@ bool TextMetrics::redoParagraph(pit_type const pit)
|
||||
row.setDimension(dim);
|
||||
int const max_row_width = max(dim_.wid, dim.wid);
|
||||
computeRowMetrics(pit, row, max_row_width);
|
||||
pm.computeRowSignature(row, bparams);
|
||||
pm.dim().des += dim.height();
|
||||
}
|
||||
|
||||
@ -1862,7 +1859,6 @@ int TextMetrics::singleWidth(pit_type pit, pos_type pos) const
|
||||
}
|
||||
|
||||
|
||||
// only used for inset right now. should also be used for main text
|
||||
void TextMetrics::draw(PainterInfo & pi, int x, int y) const
|
||||
{
|
||||
if (par_metrics_.empty())
|
||||
@ -1888,6 +1884,7 @@ void TextMetrics::draw(PainterInfo & pi, int x, int y) const
|
||||
|
||||
void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const
|
||||
{
|
||||
BufferParams const & bparams = bv_->buffer().params();
|
||||
ParagraphMetrics const & pm = par_metrics_[pit];
|
||||
if (pm.rows().empty())
|
||||
return;
|
||||
@ -1910,26 +1907,27 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co
|
||||
RowPainter rp(pi, *text_, pit, row, bidi, x, y);
|
||||
|
||||
// Row signature; has row changed since last paint?
|
||||
row.setCrc(pm.computeRowSignature(row, bparams));
|
||||
bool row_has_changed = row.changed();
|
||||
|
||||
bool row_selection = row.sel_beg != -1 && row.sel_end != -1;
|
||||
|
||||
if (!row_selection && !pi.full_repaint && !row_has_changed) {
|
||||
// Paint the only the insets if the text itself is
|
||||
// Don't paint the row if a full repaint has not been requested
|
||||
// or if it has not changed.
|
||||
if (!pi.full_repaint && !row_has_changed) {
|
||||
// Paint only the insets if the text itself is
|
||||
// unchanged.
|
||||
rp.paintOnlyInsets();
|
||||
y += row.descent();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Paint the row if a full repaint has been requested or it has
|
||||
// changed.
|
||||
// Clear background of this row
|
||||
// (if paragraph background was not cleared)
|
||||
if (row_selection || (!pi.full_repaint && row_has_changed)) {
|
||||
// Clear background of this row if paragraph background was not
|
||||
// already cleared because of a full repaint.
|
||||
if (!pi.full_repaint && row_has_changed) {
|
||||
pi.pain.fillRectangle(x, y - row.ascent(),
|
||||
width(), row.height(), pi.background_color);
|
||||
}
|
||||
|
||||
bool row_selection = row.sel_beg != -1 && row.sel_end != -1;
|
||||
if (row_selection) {
|
||||
DocIterator beg = bv_->cursor().selectionBegin();
|
||||
DocIterator end = bv_->cursor().selectionEnd();
|
||||
@ -1944,13 +1942,14 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co
|
||||
|
||||
// Instrumentation for testing row cache (see also
|
||||
// 12 lines lower):
|
||||
if (lyxerr.debugging(Debug::PAINTING)) {
|
||||
if (text_->isMainText(bv_->buffer()))
|
||||
LYXERR(Debug::PAINTING, "\n{" << inside <<
|
||||
pi.full_repaint << row_has_changed << "}");
|
||||
else
|
||||
LYXERR(Debug::PAINTING, "[" << inside <<
|
||||
pi.full_repaint << row_has_changed << "]");
|
||||
if (lyxerr.debugging(Debug::PAINTING) && inside
|
||||
&& (row_selection || pi.full_repaint || row_has_changed)) {
|
||||
std::string const foreword = text_->isMainText(bv_->buffer()) ?
|
||||
"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);
|
||||
}
|
||||
|
||||
// Backup full_repaint status and force full repaint
|
||||
|
@ -320,18 +320,9 @@ void GuiWorkArea::redraw()
|
||||
showCursor();
|
||||
}
|
||||
|
||||
ViewMetricsInfo const & vi = buffer_view_->viewMetricsInfo();
|
||||
|
||||
LYXERR(Debug::WORKAREA, "WorkArea::redraw screen");
|
||||
|
||||
int const ymin = std::max(vi.y1, 0);
|
||||
int const ymax = vi.p2 < vi.size - 1 ? vi.y2 : viewport()->height();
|
||||
|
||||
updateScreen();
|
||||
update(0, ymin, viewport()->width(), ymax - ymin);
|
||||
|
||||
//LYXERR(Debug::WORKAREA, " ymin = " << ymin << " width() = " << width()
|
||||
// << " ymax-ymin = " << ymax-ymin);
|
||||
update(0, 0, viewport()->width(), viewport()->height());
|
||||
|
||||
if (lyxerr.debugging(Debug::WORKAREA))
|
||||
buffer_view_->coordCache().dump();
|
||||
|
@ -31,7 +31,6 @@ class ParagraphMetrics;
|
||||
class Row;
|
||||
class Text;
|
||||
class TextMetrics;
|
||||
class ViewMetricsInfo;
|
||||
|
||||
namespace frontend { class Painter; }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user