+ pos_type const row_pos = row.pos();
+ pos_type const end = row.endpos();
+ // Spaces at logical line breaks in bidi text must be skipped during
+ // cursor positioning. However, they may appear visually in the middle
+ // of a row; they must be skipped, wherever they are...
+ // * logically "abc_[HEBREW_\nHEBREW]"
+ // * visually "abc_[_WERBEH\nWERBEH]"
+ pos_type skipped_sep_vpos = -1;
+
+ if (end <= row_pos)
+ cursor_vpos = row_pos;
+ else if (ppos >= end)
+ cursor_vpos = text_->isRTL(buffer, par) ? row_pos : end;
+ else if (ppos > row_pos && ppos >= end)
+ // Place cursor after char at (logical) position pos - 1
+ cursor_vpos = (bidi.level(ppos - 1) % 2 == 0)
+ ? bidi.log2vis(ppos - 1) + 1 : bidi.log2vis(ppos - 1);
+ else
+ // Place cursor before char at (logical) position ppos
+ cursor_vpos = (bidi.level(ppos) % 2 == 0)
+ ? bidi.log2vis(ppos) : bidi.log2vis(ppos) + 1;
+
+ pos_type body_pos = par.beginOfBody();
+ if (body_pos > 0 &&
+ (body_pos > end || !par.isLineSeparator(body_pos - 1)))
+ body_pos = 0;
+
+ // Use font span to speed things up, see below
+ FontSpan font_span;
+ Font font;
+
+ // If the last logical character is a separator, skip it, unless
+ // it's in the last row of a paragraph; see skipped_sep_vpos declaration
+ if (end > 0 && end < par.size() && par.isSeparator(end - 1))
+ skipped_sep_vpos = bidi.log2vis(end - 1);
+
+ for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) {
+ // Skip the separator which is at the logical end of the row
+ if (vpos == skipped_sep_vpos)
+ continue;
+ pos_type pos = bidi.vis2log(vpos);
+ if (body_pos > 0 && pos == body_pos - 1) {
+ FontMetrics const & labelfm = theFontMetrics(
+ text_->getLabelFont(buffer, par));
+ x += row.label_hfill + labelfm.width(par.layout()->labelsep);
+ if (par.isLineSeparator(body_pos - 1))
+ x -= singleWidth(pit, body_pos - 1);
+ }
+
+ // Use font span to speed things up, see above
+ if (pos < font_span.first || pos > font_span.last) {
+ font_span = par.fontSpan(pos);
+ font = getDisplayFont(pit, pos);
+ }
+
+ x += pm.singleWidth(pos, font);
+
+ if (par.isSeparator(pos) && pos >= body_pos)
+ x += row.separator;
+ }
+
+ // see correction above
+ if (boundary_correction) {
+ if (isRTL(sl, boundary))
+ x -= singleWidth(pit, ppos);
+ else
+ x += singleWidth(pit, ppos);
+ }
+
+ return int(x);
+}
+
+
+int TextMetrics::cursorY(CursorSlice const & sl, bool boundary) const
+{
+ //lyxerr << "TextMetrics::cursorY: boundary: " << boundary << endl;
+ ParagraphMetrics const & pm = par_metrics_[sl.pit()];
+ if (pm.rows().empty())
+ return 0;
+
+ int h = 0;
+ h -= par_metrics_[0].rows()[0].ascent();
+ for (pit_type pit = 0; pit < sl.pit(); ++pit) {
+ h += par_metrics_[pit].height();
+ }
+ int pos = sl.pos();
+ if (pos && boundary)
+ --pos;
+ size_t const rend = pm.pos2row(pos);
+ for (size_t rit = 0; rit != rend; ++rit)
+ h += pm.rows()[rit].height();
+ h += pm.rows()[rend].ascent();
+ return h;
+}
+
+
+void TextMetrics::cursorPrevious(Cursor & cur)
+{
+ pos_type cpos = cur.pos();
+ pit_type cpar = cur.pit();
+
+ int x = cur.x_target();
+ setCursorFromCoordinates(cur, x, 0);
+ cur.dispatch(FuncRequest(cur.selection()? LFUN_UP_SELECT: LFUN_UP));
+
+ if (cpar == cur.pit() && cpos == cur.pos())
+ // we have a row which is taller than the workarea. The
+ // simplest solution is to move to the previous row instead.
+ cur.dispatch(FuncRequest(cur.selection()? LFUN_UP_SELECT: LFUN_UP));
+
+ cur.finishUndo();
+ cur.updateFlags(Update::Force | Update::FitCursor);
+}
+
+
+void TextMetrics::cursorNext(Cursor & cur)
+{
+ pos_type cpos = cur.pos();
+ pit_type cpar = cur.pit();
+
+ int x = cur.x_target();
+ setCursorFromCoordinates(cur, x, cur.bv().workHeight() - 1);
+ cur.dispatch(FuncRequest(cur.selection()? LFUN_DOWN_SELECT: LFUN_DOWN));
+
+ if (cpar == cur.pit() && cpos == cur.pos())
+ // we have a row which is taller than the workarea. The
+ // simplest solution is to move to the next row instead.
+ cur.dispatch(
+ FuncRequest(cur.selection()? LFUN_DOWN_SELECT: LFUN_DOWN));
+
+ cur.finishUndo();
+ cur.updateFlags(Update::Force | Update::FitCursor);
+}
+
+
+// the cursor set functions have a special mechanism. When they
+// realize you left an empty paragraph, they will delete it.
+
+bool TextMetrics::cursorHome(Cursor & cur)
+{
+ BOOST_ASSERT(text_ == cur.text());
+ ParagraphMetrics const & pm = par_metrics_[cur.pit()];
+ Row const & row = pm.getRow(cur.pos(),cur.boundary());
+ return text_->setCursor(cur, cur.pit(), row.pos());
+}
+
+
+bool TextMetrics::cursorEnd(Cursor & cur)
+{
+ BOOST_ASSERT(text_ == cur.text());
+ // if not on the last row of the par, put the cursor before
+ // the final space exept if I have a spanning inset or one string
+ // is so long that we force a break.
+ pos_type end = cur.textRow().endpos();
+ if (end == 0)
+ // empty text, end-1 is no valid position
+ return false;
+ bool boundary = false;
+ if (end != cur.lastpos()) {
+ if (!cur.paragraph().isLineSeparator(end-1)
+ && !cur.paragraph().isNewline(end-1))
+ boundary = true;
+ else
+ --end;
+ }
+ return text_->setCursor(cur, cur.pit(), end, true, boundary);
+}
+
+
+void TextMetrics::deleteLineForward(Cursor & cur)
+{
+ BOOST_ASSERT(text_ == cur.text());
+ if (cur.lastpos() == 0) {
+ // Paragraph is empty, so we just go forward
+ text_->cursorForward(cur);
+ } else {
+ cur.resetAnchor();
+ cur.selection() = true; // to avoid deletion
+ cursorEnd(cur);
+ cur.setSelection();
+ // What is this test for ??? (JMarc)
+ if (!cur.selection())
+ text_->deleteWordForward(cur);
+ else
+ cap::cutSelection(cur, true, false);
+ cur.checkBufferStructure();
+ }
+}
+
+
+bool TextMetrics::isLastRow(pit_type pit, Row const & row) const
+{
+ ParagraphList const & pars = text_->paragraphs();
+ return row.endpos() >= pars[pit].size()
+ && pit + 1 == pit_type(pars.size());
+}
+
+
+bool TextMetrics::isFirstRow(pit_type pit, Row const & row) const
+{
+ return row.pos() == 0 && pit == 0;
+}
+
+
+int TextMetrics::leftMargin(int max_width, pit_type pit) const
+{
+ BOOST_ASSERT(pit >= 0);
+ BOOST_ASSERT(pit < int(text_->paragraphs().size()));
+ return leftMargin(max_width, pit, text_->paragraphs()[pit].size());
+}
+
+
+int TextMetrics::leftMargin(int max_width,
+ pit_type const pit, pos_type const pos) const
+{
+ ParagraphList const & pars = text_->paragraphs();
+
+ BOOST_ASSERT(pit >= 0);
+ BOOST_ASSERT(pit < int(pars.size()));
+ Paragraph const & par = pars[pit];
+ BOOST_ASSERT(pos >= 0);
+ BOOST_ASSERT(pos <= par.size());
+ Buffer const & buffer = bv_->buffer();
+ //lyxerr << "TextMetrics::leftMargin: pit: " << pit << " pos: " << pos << endl;
+ TextClass const & tclass = buffer.params().getTextClass();
+ LayoutPtr const & layout = par.layout();
+
+ docstring parindent = layout->parindent;
+
+ int l_margin = 0;
+
+ if (text_->isMainText(buffer))
+ l_margin += bv_->leftMargin();
+
+ l_margin += theFontMetrics(buffer.params().getFont()).signedWidth(
+ tclass.leftmargin());
+
+ if (par.getDepth() != 0) {
+ // find the next level paragraph
+ pit_type newpar = outerHook(pit, pars);
+ if (newpar != pit_type(pars.size())) {
+ if (pars[newpar].layout()->isEnvironment()) {
+ l_margin = leftMargin(max_width, newpar);
+ }
+ //FIXME Should this check for emptyLayout() as well?
+ if (par.layout() == tclass.defaultLayout()) {
+ if (pars[newpar].params().noindent())
+ parindent.erase();