Hi Abdel,

please find attached a first attempt of fixing the non-uniform scrolling
of LyX on trunk (21411).

Summary of benefits:
-) scroll is uniform and predictable when using the scrollbar, independently
of the contents (text, maths, very tall maths or pictures or tables, ecc...)
-) scrolling with the wheel up and down the same number of times leads
  exactly to the same position

Summary of changes:
-) got rid of the paragraph height estimate (wh_)
-) all paragraphs' dimensions (width, height, ascent, descent) are kept
  in the ParMetricsCache
-) row metrics details and position are cached only for (at least partially)
  visible paragraphs
-) got rid of the "view_metrics_info / anchor_ref,offset_ref" redundancy
-) anchor_ref and offset_ref are now enclosed into a TextAnchor type
(with fields "pit" and "y" -- "y" is equal to the old offset_ref reversed in sign) -) scrollbar position and total height is computed by summing up all paragraphs heights (guess it would be possible to optimize here by using an incremental
  management of the current height and the total document "height")

-) moving downwards beyond the bottom border scrolls down the minimum
sufficient to keep the cursor visible again (personally, many sudden jumps
  of the anchor make me quite nervous -- just a preference)

For the moment, the optimization that redraws the single paragraph on
which the cursor types is disabled, and my plans would be to slightly rework
the BufferView <-> buffer <-> GuiWorkArea interactions. Though, before
proceeding, I'd like to have some feedback on the current direction of change(s).

Any comments by anyone is welcome.

Bye,

   T.

Index: src/ParagraphMetrics.h
===================================================================
--- src/ParagraphMetrics.h	(revisione 21411)
+++ src/ParagraphMetrics.h	(copia locale)
@@ -113,6 +113,7 @@
 	InsetDims inset_dims_;
 };
 
+
 } // namespace lyx
 
 #endif // PARAGRAPH_METRICS_H
Index: src/insets/InsetInclude.cpp
===================================================================
--- src/insets/InsetInclude.cpp	(revisione 21411)
+++ src/insets/InsetInclude.cpp	(copia locale)
@@ -277,7 +277,8 @@
 		case LISTINGS:
 			temp = listings_label_;
 		case NONE:
-			BOOST_ASSERT(false);
+			;
+			//BOOST_ASSERT(false);
 	}
 
 	temp += ": ";
Index: src/insets/InsetText.cpp
===================================================================
--- src/insets/InsetText.cpp	(revisione 21411)
+++ src/insets/InsetText.cpp	(copia locale)
@@ -199,7 +199,7 @@
 		if (drawFrame_)
 			pi.pain.rectangle(x, yframe, w, h, frameColor());
 	}
-	tm.draw(pi, x + TEXT_TO_INSET_OFFSET, y);
+	tm.draw(pi, 0, x + TEXT_TO_INSET_OFFSET, y);
 }
 
 
Index: src/BufferView.cpp
===================================================================
--- src/BufferView.cpp	(revisione 21411)
+++ src/BufferView.cpp	(copia locale)
@@ -349,20 +349,16 @@
 
 struct BufferView::Private
 {
-	Private(BufferView & bv): wh_(0), cursor_(bv),
-		multiparsel_cache_(false), anchor_ref_(0), offset_ref_(0),
-		need_centering_(false), last_inset_(0), gui_(0)
+	Private(BufferView & bv): cursor_(bv),
+		multiparsel_cache_(false), anchor_(), anchor_bottom_(),
+		last_inset_(0), gui_(0)
 	{}
 
 	///
 	ScrollbarParameters scrollbarParameters_;
 	///
-	ViewMetricsInfo metrics_info_;
-	///
 	CoordCache coord_cache_;
 
-	/// Estimated average par height for scrollbar.
-	int wh_;
 	/// this is used to handle XSelection events in the right manner.
 	struct {
 		CursorSlice cursor;
@@ -373,12 +369,10 @@
 	Cursor cursor_;
 	///
 	bool multiparsel_cache_;
-	///
-	pit_type anchor_ref_;
-	///
-	int offset_ref_;
-	///
-	bool need_centering_;
+	/// Anchor to top visible pit/offset
+	TextAnchor anchor_;
+	/// Anchor to bottom visible pit/offset
+	TextAnchor anchor_bottom_;
 
 	/// keyboard mapping object.
 	Intl intl_;
@@ -465,18 +459,33 @@
 }
 
 
-bool BufferView::fitCursor()
-{
-	if (cursorStatus(d->cursor_) == CUR_INSIDE) {
-		frontend::FontMetrics const & fm =
-			theFontMetrics(d->cursor_.getFont().fontInfo());
-		int const asc = fm.maxAscent();
-		int const des = fm.maxDescent();
-		Point const p = getPos(d->cursor_, d->cursor_.boundary());
-		if (p.y_ - asc >= 0 && p.y_ + des < height_)
-			return false;
+bool BufferView::fitCursor() {
+	return fitCursor(d->cursor_);
+}
+
+
+bool BufferView::fitCursor(DocIterator const & dit) {
+	Text * t = dit.bottom().text();
+	TextMetrics & tm = textMetrics(t);
+	pit_type cur_pit = dit.bottom().pit();
+	Point p = coordOffset(dit, dit.boundary());
+	int cur_y = p.y_; // + tm.parMetrics(cursor_.bottom().pit()).ascent();
+	LYXERR(Debug::DEBUG)
+		<< "Ensuring viewability of pit: " << cur_pit << " with p.y=" << p.y_ << ", cur_y=" << cur_y << ", height=" << height_ << std::endl;
+	CursorStatus status = cursorStatus(dit);
+	LYXERR(Debug::DEBUG)
+		<< "Cursor is: " << (status == CUR_ABOVE ? "above " : status == CUR_BELOW ? "below" : "inside") << std::endl;
+	switch (status) {
+	case CUR_INSIDE:
+		return false;
+	case CUR_ABOVE:
+		setAnchor(TextAnchor(cur_pit,  -cur_y));
+		break;
+	case CUR_BELOW:
+		int cur_height = textMetrics(dit.top().text()).parMetrics(dit.top().pit(), false, true).rows()[0].height();
+		setAnchor(TextAnchor(cur_pit, height_ - cur_y - cur_height));
+		break;
 	}
-	center();
 	return true;
 }
 
@@ -517,12 +526,12 @@
 	// Case when no explicit update is requested.
 	if (!flags) {
 		// no need to redraw anything.
-		d->metrics_info_.update_strategy = NoScreenUpdate;
+		//d->metrics_info_.update_strategy = NoScreenUpdate;
 		return;
 	}
 
 	if (flags == Update::Decoration) {
-		d->metrics_info_.update_strategy = DecorationUpdate;
+		//d->metrics_info_.update_strategy = DecorationUpdate;
 		buffer_.changed();
 		return;
 	}
@@ -537,12 +546,12 @@
 			return;
 		}
 		if (flags & Update::Decoration) {
-			d->metrics_info_.update_strategy = DecorationUpdate;
+			//d->metrics_info_.update_strategy = DecorationUpdate;
 			buffer_.changed();
 			return;
 		}
 		// no screen update is needed.
-		d->metrics_info_.update_strategy = NoScreenUpdate;
+		//d->metrics_info_.update_strategy = NoScreenUpdate;
 		return;
 	}
 
@@ -576,57 +585,22 @@
 
 void BufferView::updateScrollbar()
 {
-	Text & t = buffer_.text();
-	TextMetrics & tm = d->text_metrics_[&t];
+	TextMetrics & tm = textMetrics(&buffer_.text());
 
-	int const parsize = int(t.paragraphs().size() - 1);
-	if (d->anchor_ref_ >  parsize)  {
-		d->anchor_ref_ = parsize;
-		d->offset_ref_ = 0;
-	}
+	LYXERR(Debug::DEBUG)
+		<< "anchor_.pit=" << d->anchor_.pit << ", anchor_.y=" << d->anchor_.y << std::endl;
 
-	LYXERR(Debug::GUI)
-		<< BOOST_CURRENT_FUNCTION
-		<< " Updating scrollbar: height: " << t.paragraphs().size()
-		<< " curr par: " << d->cursor_.bottom().pit()
-		<< " default height " << defaultRowHeight() << endl;
+	int anchor_height = tm.computeHeight(0, d->anchor_.pit);
+	int doc_height = anchor_height + tm.computeHeight(d->anchor_.pit,
+			   (pit_type) buffer_.text().paragraphs().size());
 
-	// It would be better to fix the scrollbar to understand
-	// values in [0..1] and divide everything by wh
-
-	// estimated average paragraph height:
-	if (d->wh_ == 0)
-		d->wh_ = height_ / 4;
-
-	int h = tm.parMetrics(d->anchor_ref_).height();
-
-	// Normalize anchor/offset (MV):
-	while (d->offset_ref_ > h && d->anchor_ref_ < parsize) {
-		d->anchor_ref_++;
-		d->offset_ref_ -= h;
-		h = tm.parMetrics(d->anchor_ref_).height();
-	}
-	// Look at paragraph heights on-screen
-	int sumh = 0;
-	int nh = 0;
-	for (pit_type pit = d->anchor_ref_; pit <= parsize; ++pit) {
-		if (sumh > height_)
-			break;
-		int const h2 = tm.parMetrics(pit).height();
-		sumh += h2;
-		nh++;
-	}
-
-	BOOST_ASSERT(nh);
-	int const hav = sumh / nh;
-	// More realistic average paragraph height
-	if (hav > d->wh_)
-		d->wh_ = hav;
-
-	BOOST_ASSERT(h);
-	d->scrollbarParameters_.height = (parsize + 1) * d->wh_;
-	d->scrollbarParameters_.position = d->anchor_ref_ * d->wh_ + int(d->offset_ref_ * d->wh_ / float(h));
-	d->scrollbarParameters_.lineScrollHeight = int(d->wh_ * defaultRowHeight() / float(h));
+	d->scrollbarParameters_.height = doc_height - defaultRowHeight();
+	d->scrollbarParameters_.position = anchor_height - d->anchor_.y;
+	d->scrollbarParameters_.lineScrollHeight = defaultRowHeight();
+	LYXERR(Debug::DEBUG)
+		<< "New scrollbar params: height=" << d->scrollbarParameters_.height
+		<< ", pos=" << d->scrollbarParameters_.position
+		<< ", lineHeight=" << d->scrollbarParameters_.lineScrollHeight << std::endl;
 }
 
 
@@ -640,19 +614,10 @@
 {
 	LYXERR(Debug::GUI) << BOOST_CURRENT_FUNCTION
 			   << "[ value = " << value << "]" << endl;
-
-	Text & t = buffer_.text();
-	TextMetrics & tm = d->text_metrics_[&t];
-
-	float const bar = value / float(d->wh_ * t.paragraphs().size());
-
-	d->anchor_ref_ = int(bar * t.paragraphs().size());
-	if (d->anchor_ref_ >  int(t.paragraphs().size()) - 1)
-		d->anchor_ref_ = int(t.paragraphs().size()) - 1;
-
-	tm.redoParagraph(d->anchor_ref_);
-	int const h = tm.parMetrics(d->anchor_ref_).height();
-	d->offset_ref_ = int((bar * t.paragraphs().size() - d->anchor_ref_) * h);
+	// setAnchor() will fix the supplied offset/pit info
+	setAnchor(TextAnchor(pit_type(0), -value));
+	LYXERR(Debug::DEBUG)
+		<< "New anchor  : pit=" << anchor().pit << ", y=" << anchor().y << std::endl;
 	updateMetrics(false);
 	buffer_.changed();
 }
@@ -707,10 +672,12 @@
 // FIXME: This does not work within mathed!
 CursorStatus BufferView::cursorStatus(DocIterator const & dit) const
 {
+	CursorSlice const & top = dit.top();
+	TextMetrics const & tm = d->text_metrics_[top.text()];
 	Point const p = getPos(dit, dit.boundary());
-	if (p.y_ < 0)
+	if (p.y_ - tm.parMetrics(top.pit(), false, true).rows()[0].ascent() < 0)
 		return CUR_ABOVE;
-	if (p.y_ > workHeight())
+	if (p.y_ + tm.parMetrics(top.pit(), false, true).rows()[0].descent() > workHeight())
 		return CUR_BELOW;
 	return CUR_INSIDE;
 }
@@ -815,31 +782,11 @@
 }
 
 
-void BufferView::updateOffsetRef()
-{
-	// No need to update d->offset_ref_ in this case.
-	if (!d->need_centering_)
-		return;
-
-	// We are not properly started yet, delay until resizing is
-	// done.
-	if (height_ == 0)
-		return;
-
-	CursorSlice & bot = d->cursor_.bottom();
-	TextMetrics & tm = d->text_metrics_[bot.text()];
-	ParagraphMetrics const & pm = tm.parMetrics(bot.pit());
-	int y = coordOffset(d->cursor_, d->cursor_.boundary()).y_;
-	d->offset_ref_ = y + pm.ascent() - height_ / 2;
-
-	d->need_centering_ = false;
-}
-
-
 void BufferView::center()
 {
-	d->anchor_ref_ = d->cursor_.bottom().pit();
-	d->need_centering_ = true;
+	pit_type cur_pit = d->cursor_.bottom().pit();
+	int cur_y = coordOffset(d->cursor_, d->cursor_.boundary()).y_;
+	setAnchor(TextAnchor(cur_pit, -cur_y + height_/2));
 }
 
 
@@ -1342,7 +1289,8 @@
 	case LFUN_SCREEN_UP:
 	case LFUN_SCREEN_DOWN: {
 		Point p = getPos(cur, cur.boundary());
-		if (p.y_ < 0 || p.y_ > height_) {
+		if (cursorStatus(cur) != CUR_INSIDE)
+		{
 			// The cursor is off-screen so recenter before proceeding.
 			center();
 			updateMetrics(false);
@@ -1351,7 +1299,7 @@
 			buffer_.changed();
 			p = getPos(cur, cur.boundary());
 		}
-		scroll(cmd.action == LFUN_SCREEN_UP? - height_ : height_);
+		scroll(cmd.action == LFUN_SCREEN_UP ? - height_ : height_);
 		cur.reset(buffer_.inset());
 		d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_);
 		//FIXME: what to do with cur.x_target()?
@@ -1503,24 +1451,6 @@
 		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 << endl;
-
 		// This event (moving without mouse click) is not passed further.
 		// This should be changed if it is further utilized.
 		buffer_.changed();
@@ -1556,6 +1486,7 @@
 }
 
 
+/// Positive values scroll down, negative ones scroll up.
 void BufferView::scroll(int y)
 {
 	if (y > 0)
@@ -1567,49 +1498,13 @@
 
 void BufferView::scrollDown(int offset)
 {
-	Text * text = &buffer_.text();
-	TextMetrics & tm = d->text_metrics_[text];
-	int ymax = height_ + offset;
-	while (true) {
-		std::pair<pit_type, ParagraphMetrics const *> last = tm.last();
-		int bottom_pos = last.second->position() + last.second->descent();
-		if (last.first + 1 == int(text->paragraphs().size())) {
-			if (bottom_pos <= height_)
-				return;
-			offset = min(offset, bottom_pos - height_);
-			break;
-		}
-		if (bottom_pos > ymax)
-			break;
-		tm.newParMetricsDown();
-	}
-	d->offset_ref_ += offset;
-	updateMetrics(false);
-	buffer_.changed();
+	setAnchor(TextAnchor(anchor().pit, anchor().y - offset));
 }
 
 
 void BufferView::scrollUp(int offset)
 {
-	Text * text = &buffer_.text();
-	TextMetrics & tm = d->text_metrics_[text];
-	int ymin = - offset;
-	while (true) {
-		std::pair<pit_type, ParagraphMetrics const *> first = tm.first();
-		int top_pos = first.second->position() - first.second->ascent();
-		if (first.first == 0) {
-			if (top_pos >= 0)
-				return;
-			offset = min(offset, - top_pos);
-			break;
-		}
-		if (top_pos < ymin)
-			break;
-		tm.newParMetricsUp();
-	}
-	d->offset_ref_ -= offset;
-	updateMetrics(false);
-	buffer_.changed();
+	setAnchor(TextAnchor(anchor().pit, anchor().y + offset));
 }
 
 
@@ -1660,12 +1555,19 @@
 
 
 ParagraphMetrics const & BufferView::parMetrics(Text const * t,
-		pit_type pit) const
+		pit_type pit, bool redo) const
 {
-	return textMetrics(t).parMetrics(pit);
+	return textMetrics(t).parMetrics(pit, redo);
 }
 
 
+ParagraphMetrics const & BufferView::parMetrics(Text const * t,
+						pit_type pit, bool redo, bool keep_rows) const
+{
+	return textMetrics(t).parMetrics(pit, redo, keep_rows);
+}
+
+
 int BufferView::workHeight() const
 {
 	return height_;
@@ -1792,18 +1694,24 @@
 }
 
 
-pit_type BufferView::anchor_ref() const
+TextAnchor const & BufferView::anchor() const
 {
-	return d->anchor_ref_;
+	return d->anchor_;
 }
 
 
-ViewMetricsInfo const & BufferView::viewMetricsInfo()
+TextAnchor const & BufferView::anchor_bottom() const
 {
-	return d->metrics_info_;
+	return d->anchor_bottom_;
 }
 
 
+TextAnchor & BufferView::anchor_bottom()
+{
+	return d->anchor_bottom_;
+}
+
+
 bool BufferView::singleParUpdate()
 {
 	Text & buftext = buffer_.text();
@@ -1822,23 +1730,14 @@
 		// 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());
-	LYXERR(Debug::PAINTING)
-		<< BOOST_CURRENT_FUNCTION
-		<< "\ny1: " << y1
-		<< " y2: " << y2
-		<< " pit: " << bottom_pit
-		<< " singlepar: 1"
-		<< endl;
 	return true;
 }
 
 
 void BufferView::updateMetrics(bool singlepar)
 {
+	LYXERR(Debug::DEBUG)
+		<< "Updating metrics..."  << std::endl;
 	if (singlepar && singleParUpdate())
 		// No need to update the full screen metrics.
 		return;
@@ -1846,22 +1745,20 @@
 	Text & buftext = buffer_.text();
 	pit_type const npit = int(buftext.paragraphs().size());
 
-	if (d->anchor_ref_ > int(npit - 1)) {
-		d->anchor_ref_ = int(npit - 1);
-		d->offset_ref_ = 0;
+	if (d->anchor_.pit > pit_type(npit - 1)) {
+		setAnchor(TextAnchor(npit - 1, 0));
 	}
 
 	// Clear out the position cache in case of full screen redraw,
 	d->coord_cache_.clear();
 
+	TextMetrics & tm = textMetrics(&buftext);
 	// Clear out paragraph metrics to avoid having invalid metrics
 	// in the cache from paragraphs not relayouted below
 	// The complete text metrics will be redone.
-	d->text_metrics_.clear();
+	tm.clearRows(d->anchor_.pit, d->anchor_bottom_.pit);
 
-	TextMetrics & tm = textMetrics(&buftext);
-
-	pit_type const pit = d->anchor_ref_;
+	pit_type const pit = d->anchor_.pit;
 	int pit1 = pit;
 	int pit2 = pit;
 
@@ -1869,9 +1766,10 @@
 	tm.redoParagraph(pit);
 
 	// Take care of anchor offset if case a recentering is needed.
-	updateOffsetRef();
+	//updateOffsetRef();
 
-	int y0 = tm.parMetrics(pit).ascent() - d->offset_ref_;
+	int y0 = d->anchor_.y + tm.parMetrics(pit).ascent();
+	tm.setPosition(pit, y0);
 
 	// Redo paragraphs above anchor if necessary.
 	int y1 = y0;
@@ -1880,20 +1778,20 @@
 		--pit1;
 		tm.redoParagraph(pit1);
 		y1 -= tm.parMetrics(pit1).descent();
+		tm.setPosition(pit1, y1);
 	}
 
 	// Take care of ascent of first line
 	y1 -= tm.parMetrics(pit1).ascent();
 
 	// Normalize anchor for next time
-	d->anchor_ref_ = pit1;
-	d->offset_ref_ = -y1;
+	d->anchor_ = TextAnchor(pit1, y1);
 
 	// Grey at the beginning is ugly
 	if (pit1 == 0 && y1 > 0) {
 		y0 -= y1;
 		y1 = 0;
-		d->anchor_ref_ = 0;
+		d->anchor_ = TextAnchor(0, 0);
 	}
 
 	// Redo paragraphs below the anchor if necessary.
@@ -1903,6 +1801,7 @@
 		++pit2;
 		tm.redoParagraph(pit2);
 		y2 += tm.parMetrics(pit2).ascent();
+		tm.setPosition(pit2, y2);
 	}
 
 	// Take care of descent of last line
@@ -1918,9 +1817,12 @@
 		<< " singlepar: 0"
 		<< endl;
 
-	d->metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2,
-		FullScreenUpdate, npit);
+	BOOST_ASSERT(pit2 < npit);
 
+	d->anchor_bottom_ = TextAnchor(pit2, y2);
+/* 	d->metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2, */
+/* 		FullScreenUpdate, npit); */
+
 	if (lyxerr.debugging(Debug::WORKAREA)) {
 		LYXERR(Debug::WORKAREA) << "BufferView::updateMetrics" << endl;
 		d->coord_cache_.dump();
@@ -2049,7 +1951,11 @@
 	// Add contribution of initial rows of outermost paragraph
 	CursorSlice const & sl = dit[0];
 	TextMetrics const & tm = textMetrics(sl.text());
-	ParagraphMetrics const & pm = tm.parMetrics(sl.pit());
+	LYXERR(Debug::DEBUG)
+		<< "sl.pit()=" << sl.pit() << ", sl.text().pars().size=" << sl.text()->paragraphs().size() << std::endl;
+	ParagraphMetrics const & pm = tm.parMetrics(sl.pit(), false, true);
+	if (pm.rows().empty())
+		ParagraphMetrics const & pm1 = tm.parMetrics(sl.pit(), false, true);
 	BOOST_ASSERT(!pm.rows().empty());
 	y -= pm.rows()[0].ascent();
 #if 1
@@ -2087,6 +1993,7 @@
 }
 
 
+/// Returns the on-screen position of the cursor baseline, i.e. ascent is included
 Point BufferView::getPos(DocIterator const & dit, bool boundary) const
 {
 	CursorSlice const & bot = dit.bottom();
@@ -2095,7 +2002,7 @@
 		return Point(-1, -1);
 
 	Point p = coordOffset(dit, boundary); // offset from outer paragraph
-	p.y_ += tm.parMetrics(bot.pit()).position();
+	p.y_ += tm.parPosition(bot.pit());
 	return p;
 }
 
@@ -2107,36 +2014,36 @@
 	// FIXME: We should also distinguish DecorationUpdate to avoid text
 	// drawing if possible. This is not possible to do easily right now
 	// because of the single backing pixmap.
-	pi.full_repaint = d->metrics_info_.update_strategy != SingleParUpdate;
+	pi.full_repaint = true; // d->metrics_info_.update_strategy != SingleParUpdate;
 
 	if (pi.full_repaint)
 		// Clear background (if not delegated to rows)
-		pain.fillRectangle(0, d->metrics_info_.y1, width_,
-			d->metrics_info_.y2 - d->metrics_info_.y1,
+		pain.fillRectangle(0, d->anchor_.y, width_,
+			d->anchor_bottom_.y - d->anchor_.y,
 			buffer_.inset().backgroundColor());
 
 	LYXERR(Debug::PAINTING) << "\t\t*** START DRAWING ***" << endl;
 	Text & text = buffer_.text();
 	TextMetrics const & tm = d->text_metrics_[&text];
-	int y = d->metrics_info_.y1 + tm.parMetrics(d->metrics_info_.p1).ascent();
-	if (!pi.full_repaint)
-		tm.drawParagraph(pi, d->metrics_info_.p1, 0, y);
-	else
-		tm.draw(pi, 0, y);
+	int y = d->anchor_.y + tm.parMetrics(d->anchor_.pit).ascent();
+// 	if (!pi.full_repaint)
+// 		tm.drawParagraph(pi, d->anchor_.pit, 0, y);
+// 	else
+		tm.draw(pi, d->anchor_.pit, 0, y);
 	LYXERR(Debug::PAINTING) << "\n\t\t*** END DRAWING  ***" << endl;
 
 	// and grey out above (should not happen later)
 //	lyxerr << "par ascent: " << text.getPar(d->metrics_info_.p1).ascent() << endl;
-	if (d->metrics_info_.y1 > 0
-		&& d->metrics_info_.update_strategy == FullScreenUpdate)
-		pain.fillRectangle(0, 0, width_, d->metrics_info_.y1, Color_bottomarea);
+	if (d->anchor_.y > 0
+	    /* && d->metrics_info_.update_strategy == FullScreenUpdate*/)
+		pain.fillRectangle(0, 0, width_, d->anchor_.y, Color_bottomarea);
 
 	// and possibly grey out below
 //	lyxerr << "par descent: " << text.getPar(d->metrics_info_.p1).ascent() << endl;
-	if (d->metrics_info_.y2 < height_
-		&& d->metrics_info_.update_strategy == FullScreenUpdate)
-		pain.fillRectangle(0, d->metrics_info_.y2, width_,
-			height_ - d->metrics_info_.y2, Color_bottomarea);
+	if (d->anchor_bottom_.y < height_
+	    /*&& d->metrics_info_.update_strategy == FullScreenUpdate*/)
+		pain.fillRectangle(0, d->anchor_bottom_.y, width_,
+			height_ - d->anchor_bottom_.y, Color_bottomarea);
 }
 
 
@@ -2278,4 +2185,25 @@
 		cur.innerText()->insertStringAsLines(cur, tmpstr);
 }
 
+
+void BufferView::fixAnchor() {
+	textMetrics(&buffer_.text()).fixAnchor(d->anchor_);
+}
+
+
+/// Every view movement should call this method
+void BufferView::setAnchor(TextAnchor const & ref) {
+	d->anchor_ = ref;
+	LYXERR(Debug::DEBUG)
+		<< "Set anchor to:   pit=" << d->anchor_.pit << ", y=" << d->anchor_.y << std::endl;
+	fixAnchor();
+	LYXERR(Debug::DEBUG)
+		<< "Fixed anchor to: pit=" << d->anchor_.pit << ", y=" << d->anchor_.y << std::endl;
+	BOOST_ASSERT(d->anchor_.y > -textMetrics(&buffer_.text()).parMetrics(d->anchor_.pit).height());
+	textMetrics(&buffer_.text()).redoScreen(d->anchor_.pit, d->anchor_.y, workHeight(), d->anchor_bottom_.pit, d->anchor_bottom_.y);
+	// Emit redraw signal, so WorkArea will actually issue a draw() request, followed by a paintEvent event.
+	buffer_.changed();
+}
+
+
 } // namespace lyx
Index: src/TextMetrics.h
===================================================================
--- src/TextMetrics.h	(revisione 21411)
+++ src/TextMetrics.h	(copia locale)
@@ -28,6 +28,16 @@
 class MetricsInfo;
 class Text;
 
+/// Represents the vertical position of a paragraph (top border, not baseline y) on the screen,
+/// relative to the view top.
+/// @TODO Adapt methods in TextMetrics to manage anchors through TextAnchor instances instead of separate params.
+class TextAnchor {
+public:
+	TextAnchor(pit_type pit = 0, int y = 0) : pit(pit), y(y) {  }
+	pit_type pit;
+	int y;
+};
+
 /// A map from a Text to the map of paragraphs metrics
 class TextMetrics
 {
@@ -36,19 +46,23 @@
 	TextMetrics() : text_(0) {}
 	/// The only useful constructor.
 	TextMetrics(BufferView *, Text *);
-	
+
 	///
 	bool has(pit_type pit) const;
-	///
+	/// If metrics not in cache (or cached object has no info), then compute entire paragraph metrics, then only keep dimensions and discard row details
 	ParagraphMetrics const & parMetrics(pit_type) const;
-	///
-	std::pair<pit_type, ParagraphMetrics const *> first() const;
-	///
-	std::pair<pit_type, ParagraphMetrics const *> last() const;
+	ParagraphMetrics & parMetrics(pit_type);
+// 	///
+// 	std::pair<pit_type, ParagraphMetrics const *> first() const;
+// 	///
+// 	std::pair<pit_type, ParagraphMetrics const *> last() const;
 
-	///
+	/// Return y on-screen position.
 	int parPosition(pit_type pit) const;
 
+	/// Store a paragraph position in the position cahce
+	void setPosition(pit_type pit, int y);
+
 	///
 	Dimension const & dimension() const { return dim_; }
 	///
@@ -58,10 +72,10 @@
 	/// compute text metrics.
 	bool metrics(MetricsInfo & mi, Dimension & dim, int min_width = 0);
 
-	///
-	void newParMetricsDown();
-	///
-	void newParMetricsUp();
+// 	/// Add a paragraph 
+// 	void newParMetricsDown();
+// 	///
+// 	void newParMetricsUp();
 
 	/// Gets the fully instantiated font at a given position in a paragraph
 	/// Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
@@ -94,8 +108,10 @@
 	/// \retval true if a full screen redraw is needed.
 	/// \retval false if a single paragraph redraw is enough.
 	bool redoParagraph(pit_type const pit);
+	/// Similar to findPitForward, but also sets position of paragraphs in the cache.
+	void redoScreen(pit_type pit, int y, int delta_y, pit_type & out_pit, int & out_pit_y);
 	/// Clear cache of paragraph metrics
-	void clear() { par_metrics_.clear(); }
+	void clearRows(pit_type pit_from, pit_type pit_to);
 
 	///
 	int ascent() const { return dim_.asc; }
@@ -119,15 +135,27 @@
 	 * the cursor and when creating a visible row */
 	void computeRowMetrics(pit_type pit, Row & row, int width) const;
 
-	///
-	void draw(PainterInfo & pi, int x, int y) const;
-	
+	/// Draw at (x,y) paragraphs from pit up to end of either screen or text.
+	void draw(PainterInfo & pi, pit_type pit, int x, int y) const;
+
 	void drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const;
 
+	/// Fix a TextAnchor object.
+	void fixAnchor(TextAnchor & anchor);
+
+	/// Return height of [pit, pit2[ text (pit included, pit2 excluded).
+	int computeHeight(pit_type pit, pit_type pit2);
+
+	/// Cache for ParagraphMetrics for entire document.
+	/// @note Only visible paragraphs have row-specific metrics details.
+	ParagraphMetrics & parMetrics(pit_type, bool redo_paragraph, bool keep_rows) const;
+	ParagraphMetrics & parMetrics(pit_type, bool redo_paragraph) const;
+
 private:
-	///
-	ParagraphMetrics & parMetrics(pit_type, bool redo_paragraph);
 
+	void checkParagraphsSize();
+
+	int parHeight(pit_type pit);
 	/// draw textselection.
 	/// FIXME: simplify to just to single row painting.
 	void drawSelection(PainterInfo & pi,
@@ -169,6 +197,9 @@
 		DocIterator const & beg, DocIterator const & end, 
 		bool drawOnBegMargin, bool drawOnEndMargin) const;
 
+	void findPitForward(pit_type pit, int delta_y, pit_type & out_pit, int & out_pit_y);
+	void findPitBackward(pit_type pit, int delta_y, pit_type & out_pit, int & out_pit_y);
+
 // Temporary public:
 public:
 	/// returns the column near the specified x-coordinate of the row.
@@ -259,7 +290,7 @@
 
 	bool main_text_;
 	/// A map from paragraph index number to paragraph metrics
-	typedef std::map<pit_type, ParagraphMetrics> ParMetricsCache;
+	typedef std::vector<ParagraphMetrics> ParMetricsCache;
 	///
 	mutable ParMetricsCache par_metrics_;
 	Dimension dim_;
Index: src/TextMetrics.cpp
===================================================================
--- src/TextMetrics.cpp	(revisione 21411)
+++ src/TextMetrics.cpp	(copia locale)
@@ -133,57 +133,99 @@
 
 bool TextMetrics::has(pit_type pit) const
 {
-	return par_metrics_.find(pit) != par_metrics_.end();
+	return pit < pit_type(par_metrics_.size());
 }
 
 
 ParagraphMetrics const & TextMetrics::parMetrics(pit_type pit) const
 {
-	return const_cast<TextMetrics *>(this)->parMetrics(pit, true);
+	return const_cast<TextMetrics *>(this)->parMetrics(pit, false);
 }
 
 
+ParagraphMetrics & TextMetrics::parMetrics(pit_type pit)
+{
+	return parMetrics(pit, false);
+}
 
-pair<pit_type, ParagraphMetrics const *> TextMetrics::first() const
+
+/* pair<pit_type, ParagraphMetrics const *> TextMetrics::first() const */
+/* { */
+/* 	ParMetricsCache::const_iterator it = par_metrics_.begin(); */
+/* 	return make_pair(it->first, &it->second); */
+/* } */
+
+
+/* pair<pit_type, ParagraphMetrics const *> TextMetrics::last() const */
+/* { */
+/* 	ParMetricsCache::const_reverse_iterator it = par_metrics_.rbegin(); */
+/* 	return make_pair(it->first, &it->second); */
+/* } */
+
+
+void TextMetrics::checkParagraphsSize() {
+	if (text_ == 0)
+		return;
+	if (text_->paragraphs().size() != par_metrics_.size()) {
+		par_metrics_.resize(text_->paragraphs().size());
+		/// @todo Move this loop in a background activity
+		for (pit_type p = 0; p < pit_type(par_metrics_.size()); ++p)
+			parMetrics(p, true, false);
+	}
+	if (&bv_->buffer().text() == text_
+	    && bv_->anchor_bottom().pit >= (pit_type) text_->paragraphs().size())
+		bv_->anchor_bottom() = TextAnchor(text_->paragraphs().size() - 1, par_metrics_[text_->paragraphs().size() - 1].height());
+}
+
+
+ParagraphMetrics & TextMetrics::parMetrics(pit_type pit, bool redo) const
 {
-	ParMetricsCache::const_iterator it = par_metrics_.begin();
-	return make_pair(it->first, &it->second);
+	const_cast<TextMetrics *>(this)->checkParagraphsSize();
+	if (redo || (par_metrics_[pit].height() == 0))
+		const_cast<TextMetrics *>(this)->redoParagraph(pit);
+	return par_metrics_[pit];
 }
 
 
-pair<pit_type, ParagraphMetrics const *> TextMetrics::last() const
+/// Force keeping or discarding rows according to keep_rows parameter.
+ParagraphMetrics & TextMetrics::parMetrics(pit_type pit, bool redo, bool keep_rows) const
 {
-	ParMetricsCache::const_reverse_iterator it = par_metrics_.rbegin();
-	return make_pair(it->first, &it->second);
+	const_cast<TextMetrics *>(this)->checkParagraphsSize();
+	redo |= (keep_rows && par_metrics_[pit].rows().empty());
+	ParagraphMetrics & pm = parMetrics(pit, redo);
+	if (! keep_rows)
+		par_metrics_[pit].rows().clear();
+	return pm;
 }
 
 
-ParagraphMetrics & TextMetrics::parMetrics(pit_type pit, bool redo)
+void TextMetrics::setPosition(pit_type pit, int y)
 {
-	ParMetricsCache::iterator pmc_it = par_metrics_.find(pit);
-	if (pmc_it == par_metrics_.end()) {
-		pmc_it = par_metrics_.insert(
-			make_pair(pit, ParagraphMetrics(text_->getPar(pit)))).first;
-	}
-	if (pmc_it->second.rows().empty() && redo)
-		redoParagraph(pit);
-	return pmc_it->second;
+	parMetrics(pit).setPosition(y);
 }
 
 
+void TextMetrics::clearRows(pit_type pit_from, pit_type pit_to) {
+  for (pit_type pit = pit_from; pit <= pit_to && pit_to < (pit_type) text_->paragraphs().size(); ++pit)
+		parMetrics(pit, false, false).rows().clear();
+}
+
+
+/// @todo remove loop, and use info stored in setPosition()
 int TextMetrics::parPosition(pit_type pit) const
 {
-	if (pit < par_metrics_.begin()->first)
-		return -1000000;
-	if (pit > par_metrics_.rbegin()->first)
-		return +1000000;
-
-	return par_metrics_[pit].position();
+	return parMetrics(pit).position();
+//	int pos = bv_->anchor().y;
+//	for (pit_type p = bv_->anchor().pit; p < pit; ++p)
+//		pos += parMetrics(p).height();
+//	pos += parMetrics(pit).ascent();
+//	return pos;
 }
 
 
 bool TextMetrics::metrics(MetricsInfo & mi, Dimension & dim, int min_width)
 {
+	checkParagraphsSize();
 	BOOST_ASSERT(mi.base.textwidth);
 	max_width_ = mi.base.textwidth;
 	// backup old dimension.
@@ -349,11 +391,12 @@
 
 bool TextMetrics::redoParagraph(pit_type const pit)
 {
+	checkParagraphsSize();
 	Paragraph & par = text_->getPar(pit);
 	// IMPORTANT NOTE: We pass 'false' explicitely in order to not call
 	// redoParagraph() recursively inside parMetrics.
-	Dimension old_dim = parMetrics(pit, false).dim();
 	ParagraphMetrics & pm = par_metrics_[pit];
+	Dimension old_dim = pm.dim();
 	pm.reset(par);
 
 	Buffer & buffer = bv_->buffer();
@@ -376,6 +419,8 @@
 			cursor.posLeft();
 	}
 
+	max_width_ = bv_->workWidth();
+
 	// Optimisation: this is used in the next two loops
 	// so better to calculate that once here.
 	int const right_margin = rightMargin(pm);
@@ -454,6 +499,9 @@
 	do {
 		Dimension dim;
 		pos_type end = rowBreakPoint(width, pit, first);
+		LYXERR(Debug::DEBUG)
+			<< "Found break point: max_width_=" << max_width_ << ", rm=" << right_margin
+			<< ", width=" << width << ", pit=" << pit << ", first=" << first << ": end=" << end << std::endl;
 		if (row_index || end < par.size())
 			// If there is more than one row, expand the text to 
 			// the full allowable width. This setting here is needed
@@ -481,6 +529,8 @@
 
 		pm.dim().wid = std::max(pm.dim().wid, dim.wid);
 		pm.dim().des += dim.height();
+		LYXERR(Debug::DEBUG)
+			<< "first=" << first << ", par.size()=" << par.size() << std::endl;
 	} while (first < par.size());
 
 	if (row_index < pm.rows().size())
@@ -513,6 +563,29 @@
 }
 
 
+/** Similar to findPitForward, but also sets position of paragraphs in the cache.
+ ** Return the bottom (even partially) viewable pit, along with the y coordinate of its bottom border.
+ **/
+void TextMetrics::redoScreen(pit_type pit, int y, int delta_y, pit_type & out_pit, int & out_pit_y) {
+	out_pit = pit;
+	out_pit_y = y;
+	while (out_pit < (pit_type) text_->paragraphs().size()) {
+		ParagraphMetrics & pm = const_cast<ParagraphMetrics &>(parMetrics(out_pit, true));
+		out_pit_y += pm.ascent();
+		// Save the paragraph position in the cache.
+		setPosition(out_pit, out_pit_y);
+		out_pit_y += pm.descent();
+
+		if (out_pit_y >= delta_y)
+			break;
+
+		++out_pit;
+	}
+	if (out_pit == (pit_type) text_->paragraphs().size())
+		out_pit = (pit_type) text_->paragraphs().size() - 1;
+}
+
+
 void TextMetrics::computeRowMetrics(pit_type const pit,
 		Row & row, int width) const
 {
@@ -847,6 +920,8 @@
 Dimension TextMetrics::rowHeight(pit_type const pit, pos_type const first,
 		pos_type const end) const
 {
+	LYXERR(Debug::DEBUG)
+		<< "rowHeight(): pit=" << pit << ", first=" << first << ", end=" << end << std::endl;
 	Paragraph const & par = text_->getPar(pit);
 	// get the maximum ascent and the maximum descent
 	double layoutasc = 0;
@@ -1048,8 +1123,9 @@
 	/// x Paragraph coordinate is always 0 for main text anyway.
 	int const xo = origin_.x_;
 	x -= xo;
+	BOOST_ASSERT(pit < (pit_type) text_->paragraphs().size());
 	Paragraph const & par = text_->getPar(pit);
-	ParagraphMetrics const & pm = par_metrics_[pit];
+	ParagraphMetrics const & pm = parMetrics(pit, false, true);
 	Bidi bidi;
 	bidi.computeTables(par, buffer, row);
 
@@ -1192,36 +1268,39 @@
 }
 
 
-void TextMetrics::newParMetricsDown()
-{
-	pair<pit_type, ParagraphMetrics> const & last = *par_metrics_.rbegin();
-	pit_type const pit = last.first + 1;
-	if (pit == int(text_->paragraphs().size()))
-		return;
+//void TextMetrics::newParMetricsDown()
+//{
+//	pair<pit_type, ParagraphMetrics> const & last = *par_metrics_.rbegin();
+//	pit_type const pit = last.first + 1;
+//	if (pit == int(text_->paragraphs().size()))
+//		return;
+//
+//	// do it and update its position.
+//	redoParagraph(pit);
+//	par_metrics_[pit].setPosition(last.second.position()
+//		+ last.second.descent());
+//}
+//
+//
+//void TextMetrics::newParMetricsUp()
+//{
+//	pair<pit_type, ParagraphMetrics> const & first = *par_metrics_.begin();
+//	if (first.first == 0)
+//		return;
+//
+//	pit_type const pit = first.first - 1;
+//	// do it and update its position.
+//	redoParagraph(pit);
+//	par_metrics_[pit].setPosition(first.second.position()
+//		- first.second.ascent());
+//}
 
-	// do it and update its position.
-	redoParagraph(pit);
-	par_metrics_[pit].setPosition(last.second.position()
-		+ last.second.descent());
-}
 
-
-void TextMetrics::newParMetricsUp()
-{
-	pair<pit_type, ParagraphMetrics> const & first = *par_metrics_.begin();
-	if (first.first == 0)
-		return;
-
-	pit_type const pit = first.first - 1;
-	// do it and update its position.
-	redoParagraph(pit);
-	par_metrics_[pit].setPosition(first.second.position()
-		- first.second.ascent());
-}
-
 // y is screen coordinate
 pit_type TextMetrics::getPitNearY(int y)
 {
+	checkParagraphsSize();
+
 	BOOST_ASSERT(!text_->paragraphs().empty());
 	LYXERR(Debug::DEBUG)
 		<< BOOST_CURRENT_FUNCTION
@@ -1229,59 +1308,68 @@
 		<< endl;
 
 	// look for highest numbered paragraph with y coordinate less than given y
-	pit_type pit = 0;
-	int yy = -1;
+	pit_type pit;
 	ParMetricsCache::const_iterator it = par_metrics_.begin();
 	ParMetricsCache::const_iterator et = par_metrics_.end();
 	ParMetricsCache::const_iterator last = et; last--;
 
-	ParagraphMetrics const & pm = it->second;
+	pit_type pit_top, pit_bottom;
+	if (text_ == &bv_->buffer().text()) {
+		pit_top = bv_->anchor().pit;
+		pit_bottom = bv_->anchor_bottom().pit;
+	} else {
+		pit_top = 0;
+		pit_bottom = text_->paragraphs().size() - 1;
+	}
 
+	ParagraphMetrics const & pm = parMetrics(pit_top);
+
 	// If we are off-screen (before the visible part)
 	if (y < 0
-		// and even before the first paragraph in the cache.
-		&& y < it->second.position() - int(pm.ascent())) {
+	    // and even before the first paragraph in the cache.
+	    && y < parPosition(pit_top) - int(pm.ascent())) {
 		//  and we are not at the first paragraph in the inset.
-		if (it->first == 0)
+		if (pit_top == 0)
 			return 0;
 		// then this is the paragraph we are looking for.
-		pit = it->first - 1;
+		pit = pit_top - 1;
 		// rebreak it and update the CoordCache.
 		redoParagraph(pit);
-		par_metrics_[pit].setPosition(it->second.position() - pm.descent());
+		setPosition(pit, parPosition(pit_top) - pm.descent());
 		return pit;
 	}
 
-	ParagraphMetrics const & pm_last = par_metrics_[last->first];
+	ParagraphMetrics const & pm_last = parMetrics(pit_bottom);
 
 	// If we are off-screen (after the visible part)
 	if (y > bv_->workHeight()
 		// and even after the first paragraph in the cache.
-		&& y >= last->second.position() + int(pm_last.descent())) {
-		pit = last->first + 1;
+	    && y >= parPosition(pit_bottom) + int(pm_last.descent())) {
+		pit = pit_bottom + 1;
 		//  and we are not at the last paragraph in the inset.
 		if (pit == int(text_->paragraphs().size()))
-			return last->first;
+			return pit_bottom;
 		// then this is the paragraph we are looking for.
 		// rebreak it and update the CoordCache.
 		redoParagraph(pit);
-		par_metrics_[pit].setPosition(last->second.position() + pm_last.ascent());
+		int bottom_y = parPosition(pit_bottom);
+		int bottom_desc = pm_last.descent();
+		int new_asc =  parMetrics(pit, true, true).ascent();
+		setPosition(pit, bottom_y + bottom_desc + new_asc);
 		return pit;
 	}
 
-	for (; it != et; ++it) {
+	int yy = parPosition(pit_top) - par_metrics_[pit_top].ascent();
+	for (pit = pit_top; pit < pit_bottom; ++pit) {
 		LYXERR(Debug::DEBUG)
 			<< BOOST_CURRENT_FUNCTION
-			<< "  examining: pit: " << it->first
-			<< " y: " << it->second.position()
+			<< "  examining: pit: " << pit
+			<< " y: " << yy
 			<< endl;
-
-		ParagraphMetrics const & pm = par_metrics_[it->first];
-
-		if (it->first >= pit && int(it->second.position()) - int(pm.ascent()) <= y) {
-			pit = it->first;
-			yy = it->second.position();
-		}
+		ParagraphMetrics const & pm = par_metrics_[pit];
+		yy += pm.height();
+		if (yy > y)
+			break;
 	}
 
 	LYXERR(Debug::DEBUG)
@@ -1289,15 +1377,17 @@
 		<< ": found best y: " << yy << " for pit: " << pit
 		<< endl;
 
+	BOOST_ASSERT(pit < (pit_type) text_->paragraphs().size());
+
 	return pit;
 }
 
 
 Row const & TextMetrics::getRowNearY(int y, pit_type pit) const
 {
-	ParagraphMetrics const & pm = par_metrics_[pit];
+	ParagraphMetrics const & pm = parMetrics(pit, false, true);
 
-	int yy = pm.position() - pm.ascent();
+	int yy = parPosition(pit) - pm.ascent();
 	BOOST_ASSERT(!pm.rows().empty());
 	RowList::const_iterator rit = pm.rows().begin();
 	RowList::const_iterator rlast = pm.rows().end();
@@ -1314,13 +1404,18 @@
 Inset * TextMetrics::editXY(Cursor & cur, int x, int y)
 {
 	if (lyxerr.debugging(Debug::WORKAREA)) {
-		lyxerr << "TextMetrics::editXY(cur, " << x << ", " << y << ")" << std::endl;
+		LYXERR(Debug::WORKAREA)
+			<< "TextMetrics::editXY(cur, " << x << ", " << y << ")" << std::endl;
 		cur.bv().coordCache().dump();
 	}
 	pit_type pit = getPitNearY(y);
-	BOOST_ASSERT(pit != -1);
+	BOOST_ASSERT(pit != -1 && pit < (pit_type) text_->paragraphs().size());
+	LYXERR(Debug::DEBUG)
+		<< "Found pit=" << pit << " near y=" << y << std::endl;
 
 	Row const & row = getRowNearY(y, pit);
+	LYXERR(Debug::DEBUG)
+		<< "Found row with pos=" << row.pos() << " near y=" << y << " and pit=" << pit << std::endl;
 	bool bound = false;
 
 	int xx = x; // is modified by getColumnNearX
@@ -1344,7 +1439,7 @@
 	}
 
 	ParagraphList const & pars = text_->paragraphs();
-	Inset const * insetBefore = pos? pars[pit].getInset(pos - 1): 0;
+	Inset const * insetBefore = pos ? pars[pit].getInset(pos - 1) : 0;
 	//Inset * insetBehind = pars[pit].getInset(pos);
 
 	// This should be just before or just behind the
@@ -1368,6 +1463,33 @@
 }
 
 
+int TextMetrics::parHeight(pit_type pit) {
+	return parMetrics(pit).height();
+}
+
+
+/** Fix a TextAnchor object.
+ **
+ ** Useful because you can just increase/decrease the Y coordinate and call fixAnchor(),
+ ** that takes care of checking bounds and increasing/decreasing also the par iterator,
+ ** if needed.
+ **/
+void TextMetrics::fixAnchor(TextAnchor & anchor) {
+	pit_type & anchor_ref_ = anchor.pit;
+	int & offset_ref_ = anchor.y;
+	while (anchor_ref_ < int(text_->paragraphs().size() - 1) && offset_ref_ <= -parHeight(anchor_ref_)) {
+	  offset_ref_ += parHeight(anchor_ref_);
+	  ++anchor_ref_;
+	}
+	offset_ref_ = std::max(offset_ref_, -parHeight(anchor_ref_) + 1);
+	while (anchor_ref_ > 0 && offset_ref_ > 0) {
+	  --anchor_ref_;
+	  offset_ref_ -= parHeight(anchor_ref_);
+	}
+	offset_ref_ = std::min(offset_ref_, 0);
+}
+
+
 void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const y)
 {
 	BOOST_ASSERT(text_ == cur.text());
@@ -1375,7 +1497,7 @@
 
 	ParagraphMetrics const & pm = par_metrics_[pit];
 
-	int yy = pm.position() - pm.ascent();
+	int yy = parPosition(pit) - pm.ascent();
 	LYXERR(Debug::DEBUG)
 		<< BOOST_CURRENT_FUNCTION
 		<< ": x: " << x
@@ -1420,7 +1542,7 @@
 Inset * TextMetrics::checkInsetHit(int x, int y)
 {
 	pit_type pit = getPitNearY(y);
-	BOOST_ASSERT(pit != -1);
+	BOOST_ASSERT(pit != -1 && pit < (pit_type) text_->paragraphs().size());
 
 	Paragraph const & par = text_->paragraphs()[pit];
 	ParagraphMetrics const & pm = par_metrics_[pit];
@@ -1642,7 +1764,7 @@
 bool TextMetrics::cursorHome(Cursor & cur)
 {
 	BOOST_ASSERT(text_ == cur.text());
-	ParagraphMetrics const & pm = par_metrics_[cur.pit()];
+	ParagraphMetrics const & pm = parMetrics(cur.pit(), false, true);
 	Row const & row = pm.getRow(cur.pos(),cur.boundary());
 	return text_->setCursor(cur, cur.pit(), row.pos());
 }
@@ -1887,7 +2009,8 @@
 
 
 // only used for inset right now. should also be used for main text
-void TextMetrics::draw(PainterInfo & pi, int x, int y) const
+// @param y Is the baseline y, thus it includes the ascent
+void TextMetrics::draw(PainterInfo & pi, pit_type pit, int x, int y) const
 {
 	if (par_metrics_.empty())
 		return;
@@ -1895,23 +2018,23 @@
 	origin_.x_ = x;
 	origin_.y_ = y;
 
-	ParMetricsCache::iterator it = par_metrics_.begin();
-	ParMetricsCache::iterator const pm_end = par_metrics_.end();
-	y -= it->second.ascent();
-	for (; it != pm_end; ++it) {
-		ParagraphMetrics const & pmi = it->second;
+	y -= parMetrics(pit).ascent();
+	for (; y < bv_->workHeight() && pit < (pit_type) text_->paragraphs().size(); ++pit) {
+		ParagraphMetrics const & pmi = parMetrics(pit, true, true);
 		y += pmi.ascent();
-		pit_type const pit = it->first;
 		// Save the paragraph position in the cache.
-		it->second.setPosition(y);
+		const_cast<TextMetrics *>(this)->setPosition(pit, y);
 		drawParagraph(pi, pit, x, y);
 		y += pmi.descent();
 	}
 }
 
 
+/// y already includes the paragraph ascent
 void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const
 {
+	const_cast<TextMetrics *>(this)->checkParagraphsSize();
+
 //	lyxerr << "  paintPar: pit: " << pit << " at y: " << y << endl;
 	int const ww = bv_->workHeight();
 
@@ -2007,8 +2130,8 @@
 void TextMetrics::drawSelection(PainterInfo & pi,
 	DocIterator const & beg, DocIterator const & end, int x) const
 {
-	ParagraphMetrics const & pm1 = parMetrics(beg.pit());
-	ParagraphMetrics const & pm2 = parMetrics(end.pit());
+	ParagraphMetrics const & pm1 = parMetrics(beg.pit(), false, true);
+	ParagraphMetrics const & pm2 = parMetrics(end.pit(), false, true);
 	Row const & row1 = pm1.getRow(beg.pos(), beg.boundary());
 	Row const & row2 = pm2.getRow(end.pos(), end.boundary());
 
@@ -2152,4 +2275,44 @@
 	return int(theFontMetrics(sane_font).maxHeight() *  1.2);
 }
 
+/// Return height of [pit, pit2[ text (pit included, pit2 excluded).
+int TextMetrics::computeHeight(pit_type pit, pit_type pit2) {
+	int delta_y = 0;
+	for (; pit != pit2; ++pit)
+		delta_y += parMetrics(pit).height();
+	return delta_y;
+}
+
+
+/** Find the paragraph after (or equal to) pit that contains the delta_y coordinate relative to the start
+ ** of pit.
+ ** Output also the y coordinate of the start of the returned paragraph (relative to the start of pit).
+ **/
+void TextMetrics::findPitForward(pit_type pit, int delta_y, pit_type & out_pit, int & out_pit_y) {
+	out_pit = pit;
+	out_pit_y = 0;
+	while (out_pit < int(text_->paragraphs().size() - 1) && out_pit_y + parMetrics(out_pit).height() <= delta_y) {
+		out_pit_y += parMetrics(out_pit).height();
+		++out_pit;
+	}
+}
+
+
+/// Find the paragraph before pit that contains the -delta_y coordinate relative to the start of pit
+/// Output also the y coordinate of the start of the returned paragraph (relative to the start of pit).
+///
+/// \note The height of pit is not considered in this computation.
+void TextMetrics::findPitBackward(pit_type pit, int delta_y, pit_type & out_pit, int & out_pit_y) {
+	out_pit = pit;
+	out_pit_y = 0;
+	if (out_pit == 0)
+		return;
+	do {
+		--out_pit;
+		redoParagraph(out_pit);
+		out_pit_y += parMetrics(out_pit).height();
+	}  while (out_pit > 0 && out_pit_y < delta_y);
+}
+
+
 } // namespace lyx
Index: src/ParagraphMetrics.cpp
===================================================================
--- src/ParagraphMetrics.cpp	(revisione 21411)
+++ src/ParagraphMetrics.cpp	(copia locale)
@@ -92,7 +92,7 @@
 {
 	par_ = &par;
 	dim_ = Dimension();
-	//position_ = -1;
+	position_ = -1;
 }
 
 
Index: src/frontends/WorkArea.h
===================================================================
--- src/frontends/WorkArea.h	(revisione 21411)
+++ src/frontends/WorkArea.h	(copia locale)
@@ -136,6 +136,9 @@
 
 	///
 	Timeout cursor_timeout_;
+
+	/// 
+	boost::signals::connection redrawcon;
 };
 
 } // namespace frontend
Index: src/frontends/qt4/GuiView.cpp
===================================================================
--- src/frontends/qt4/GuiView.cpp	(revisione 21411)
+++ src/frontends/qt4/GuiView.cpp	(copia locale)
@@ -815,6 +815,7 @@
 		d.stack_widget_->setCurrentWidget(d.tab_widget_);
 	// Hide tabbar if there's only one tab.
 	d.tab_widget_->showBar(d.tab_widget_->count() > 1);
+	wa->scheduleRedraw();
 	return wa;
 }
 
Index: src/frontends/WorkArea.cpp
===================================================================
--- src/frontends/WorkArea.cpp	(revisione 21411)
+++ src/frontends/WorkArea.cpp	(copia locale)
@@ -33,6 +33,7 @@
 #include "LyXFunc.h"
 #include "LyXRC.h"
 #include "MetricsInfo.h"
+#include "TextMetrics.h"
 
 #include "gettext.h"
 #include "support/ForkedcallsController.h"
@@ -73,12 +74,15 @@
 	timecon = cursor_timeout_.timeout
 		.connect(boost::bind(&WorkArea::toggleCursor, this));
 
+	redrawcon = buffer_view_->redraw.connect(boost::bind(&WorkArea::scheduleRedraw, this));
+
 	cursor_timeout_.start();
 }
 
 
 WorkArea::~WorkArea()
 {
+	redrawcon.disconnect();
 	buffer_view_->buffer().workAreaManager().remove(this);
 	delete buffer_view_;
 }
@@ -144,12 +148,12 @@
 		showCursor();
 	}
 	
-	ViewMetricsInfo const & vi = buffer_view_->viewMetricsInfo();
+	//ViewMetricsInfo const & vi = buffer_view_->viewMetricsInfo();
 
 	LYXERR(Debug::WORKAREA) << "WorkArea::redraw screen" << endl;
 
-	int const ymin = std::max(vi.y1, 0);
-	int const ymax = vi.p2 < vi.size - 1 ? vi.y2 : height();
+	int const ymin = std::max(buffer_view_->anchor().y, 0);
+	int const ymax = std::min(buffer_view_->anchor_bottom().y, height());
 
 	expose(0, ymin, width(), ymax - ymin);
 
Index: src/Cursor.cpp
===================================================================
--- src/Cursor.cpp	(revisione 21411)
+++ src/Cursor.cpp	(copia locale)
@@ -1222,7 +1222,7 @@
 		
 	// first get the current line
 	TextMetrics & tm = bv_->textMetrics(text());
-	ParagraphMetrics const & pm = tm.parMetrics(pit());
+	ParagraphMetrics const & pm = tm.parMetrics(pit(), false, true);
 	int row;
 	if (pos() && boundary())
 		row = pm.pos2row(pos() - 1);
@@ -1270,7 +1270,7 @@
 				top().pos() = std::min(tm.x2pos(pit(), row - 1, xo), top().lastpos());
 			} else if (pit() > 0) {
 				--pit();
-				ParagraphMetrics const & pmcur = bv_->parMetrics(text(), pit());
+				ParagraphMetrics const & pmcur = bv_->parMetrics(text(), pit(), false, true);
 				top().pos() = std::min(tm.x2pos(pit(), pmcur.rows().size() - 1, xo), top().lastpos());
 			}
 		} else {
Index: src/BufferView.h
===================================================================
--- src/BufferView.h	(revisione 21411)
+++ src/BufferView.h	(copia locale)
@@ -19,6 +19,7 @@
 
 #include "support/strfwd.h"
 #include "support/types.h"
+#include <boost/signal.hpp>
 
 namespace lyx {
 
@@ -41,6 +42,7 @@
 class Point;
 class Text;
 class TextMetrics;
+class TextAnchor;
 class ViewMetricsInfo;
 
 enum CursorStatus {
@@ -101,6 +103,7 @@
 	/// move the screen to fit the cursor.
 	/// Only to be called with good y coordinates (after a bv::metrics)
 	bool fitCursor();
+	bool fitCursor(DocIterator const & dit);
 	/// reset the scrollbar to reflect current view position.
 	void updateScrollbar();
 	/// return the Scrollbar Parameters.
@@ -117,6 +120,7 @@
 		int top_id, ///< Paragraph ID, \sa Paragraph
 		pos_type top_pos ///< Position in the \c Paragraph
 		);
+
 	/// return the current change at the cursor.
 	Change const getCurrentChange() const;
 
@@ -167,9 +171,17 @@
 	/// \sa WorkArea
 	void mouseEventDispatch(FuncRequest const & ev);
 
+	/// Set Anchor, fix it if needed, and issue a redraw() signal
+	void setAnchor(TextAnchor const & ref);
+
 	/// access to anchor.
-	pit_type anchor_ref() const;
+	TextAnchor const & anchor() const;
 
+	/// access to bottom anchor.
+	TextAnchor const & anchor_bottom() const;
+	/// access to bottom anchor.
+	TextAnchor & anchor_bottom();
+
 	///
 	CursorStatus cursorStatus(DocIterator const & dit) const;
 	/// access to full cursor.
@@ -199,7 +211,7 @@
 	/// This is used specifically by the \c Workrea.
 	/// \sa WorkArea
 	/// \sa ViewMetricsInfo
-	ViewMetricsInfo const & viewMetricsInfo();
+/* 	ViewMetricsInfo const & viewMetricsInfo(); */
 	/// update the internal \c ViewMetricsInfo.
 	/// \param singlepar indicates wether
 	void updateMetrics(bool singlepar = false);
@@ -208,7 +220,8 @@
 	TextMetrics const & textMetrics(Text const * t) const;
 	TextMetrics & textMetrics(Text const * t);
 	///
-	ParagraphMetrics const & parMetrics(Text const *, pit_type) const;
+	ParagraphMetrics const & parMetrics(Text const *, pit_type, bool redo = false) const;
+	ParagraphMetrics const & parMetrics(Text const *, pit_type, bool redo, bool keep_rows) const;
 
 	///
 	CoordCache & coordCache();
@@ -255,6 +268,12 @@
 	// Insert plain text file (if filename is empty, prompt for one)
 	void insertPlaintextFile(std::string const & fileName, bool asParagraph);
 
+	/// This signal is emitted when the entire screen needs redraw
+	boost::signal<void()> redraw;
+
+	/// This signal is emitted when only one paragraph needs redraw
+	boost::signal<void(pit_type pit)> redrawParagraph;
+
 private:
 	/// noncopyable
 	BufferView(BufferView const &);
@@ -286,8 +305,11 @@
 	///
 	void menuInsertLyXFile(std::string const & filen);
 
-	void updateOffsetRef();
+// 	void updateOffsetRef();
 
+	/// Call text_metrics_.fixAnchor on anchor.
+	void fixAnchor();
+
 	struct Private;
 	Private * const d;
 };
Index: src/Text.h
===================================================================
--- src/Text.h	(revisione 21411)
+++ src/Text.h	(copia locale)
@@ -121,9 +121,15 @@
 		FuncStatus & status) const;
 
 	/// read-only access to individual paragraph
-	Paragraph const & getPar(pit_type pit) const { return pars_[pit]; }
+	Paragraph const & getPar(pit_type pit) const {
+	  BOOST_ASSERT(pit < (pit_type) pars_.size());
+		return pars_[pit];
+	}
 	/// read-write access to individual paragraph
-	Paragraph & getPar(pit_type pit) { return pars_[pit]; }
+	Paragraph & getPar(pit_type pit) {
+		BOOST_ASSERT(pit < (pit_type) pars_.size());
+		return pars_[pit];
+	}
 	// Returns the current font and depth as a message.
 	/// FIXME: replace Cursor with DocIterator.
 	docstring currentState(Cursor & cur);

Reply via email to