+ ParagraphMetrics const & pm = par_metrics_[pit];
+
+ int yy = pm.position() - pm.ascent();
+ BOOST_ASSERT(!pm.rows().empty());
+ RowList::const_iterator rit = pm.rows().begin();
+ RowList::const_iterator rlast = pm.rows().end();
+ --rlast;
+ for (; rit != rlast; yy += rit->height(), ++rit)
+ if (yy + rit->height() > y)
+ break;
+ return *rit;
+}
+
+
+// x,y are absolute screen coordinates
+// sets cursor recursively descending into nested editable insets
+Inset * TextMetrics::editXY(Cursor & cur, int x, int y)
+{
+ if (lyxerr.debugging(Debug::WORKAREA)) {
+ LYXERR0("TextMetrics::editXY(cur, " << x << ", " << y << ")");
+ cur.bv().coordCache().dump();
+ }
+ pit_type pit = getPitNearY(y);
+ BOOST_ASSERT(pit != -1);
+
+ Row const & row = getRowNearY(y, pit);
+ bool bound = false;
+
+ int xx = x; // is modified by getColumnNearX
+ pos_type const pos = row.pos()
+ + getColumnNearX(pit, row, xx, bound);
+ cur.pit() = pit;
+ cur.pos() = pos;
+ cur.boundary(bound);
+ cur.setTargetX(x);
+
+ // try to descend into nested insets
+ Inset * inset = checkInsetHit(x, y);
+ //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
+ if (!inset) {
+ // Either we deconst editXY or better we move current_font
+ // and real_current_font to Cursor
+ // FIXME: what is needed now that current_font and real_current_font
+ // are transferred?
+ cur.setCurrentFont();
+ return 0;
+ }
+
+ ParagraphList const & pars = text_->paragraphs();
+ 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
+ // cursor position set above.
+ BOOST_ASSERT((pos != 0 && inset == insetBefore)
+ || inset == pars[pit].getInset(pos));
+
+ // Make sure the cursor points to the position before
+ // this inset.
+ if (inset == insetBefore) {
+ --cur.pos();
+ cur.boundary(false);
+ }
+
+ // Try to descend recursively inside the inset.
+ inset = inset->editXY(cur, x, y);
+
+ if (cur.top().text() == text_)
+ cur.setCurrentFont();
+ return inset;
+}
+
+
+void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const y)
+{
+ BOOST_ASSERT(text_ == cur.text());
+ pit_type pit = getPitNearY(y);
+
+ ParagraphMetrics const & pm = par_metrics_[pit];
+
+ int yy = pm.position() - pm.ascent();
+ LYXERR(Debug::DEBUG, "x: " << x << " y: " << y <<
+ " pit: " << pit << " yy: " << yy);
+
+ int r = 0;
+ BOOST_ASSERT(pm.rows().size());
+ for (; r < int(pm.rows().size()) - 1; ++r) {
+ Row const & row = pm.rows()[r];
+ if (int(yy + row.height()) > y)
+ break;
+ yy += row.height();
+ }
+
+ Row const & row = pm.rows()[r];
+
+ LYXERR(Debug::DEBUG, "row " << r << " from pos: " << row.pos());
+
+ bool bound = false;
+ int xx = x;
+ pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
+
+ LYXERR(Debug::DEBUG, "setting cursor pit: " << pit << " pos: " << pos);
+
+ text_->setCursor(cur, pit, pos, true, bound);
+ // remember new position.
+ cur.setTargetX();
+}
+
+
+//takes screen x,y coordinates
+Inset * TextMetrics::checkInsetHit(int x, int y)
+{
+ pit_type pit = getPitNearY(y);
+ BOOST_ASSERT(pit != -1);
+
+ Paragraph const & par = text_->paragraphs()[pit];
+ ParagraphMetrics const & pm = par_metrics_[pit];
+
+ LYXERR(Debug::DEBUG, "x: " << x << " y: " << y << " pit: " << pit);
+
+ InsetList::const_iterator iit = par.insetList().begin();
+ InsetList::const_iterator iend = par.insetList().end();
+ for (; iit != iend; ++iit) {
+ Inset * inset = iit->inset;
+
+ LYXERR(Debug::DEBUG, "examining inset " << inset);
+
+ if (!bv_->coordCache().getInsets().has(inset)) {
+ LYXERR(Debug::DEBUG, "inset has no cached position");
+ return 0;
+ }
+
+ Dimension const & dim = pm.insetDimension(inset);
+ Point p = bv_->coordCache().getInsets().xy(inset);
+
+ LYXERR(Debug::DEBUG, "xo: " << p.x_ << "..." << p.x_ + dim.wid
+ << " yo: " << p.y_ - dim.asc << "..." << p.y_ + dim.des);
+
+ if (x >= p.x_
+ && x <= p.x_ + dim.wid
+ && y >= p.y_ - dim.asc
+ && y <= p.y_ + dim.des) {
+ LYXERR(Debug::DEBUG, "Hit inset: " << inset);
+ return inset;
+ }
+ }
+
+ LYXERR(Debug::DEBUG, "No inset hit. ");
+ return 0;
+}
+
+
+int TextMetrics::cursorX(CursorSlice const & sl,
+ bool boundary) const
+{
+ BOOST_ASSERT(sl.text() == text_);
+ pit_type const pit = sl.pit();
+ Paragraph const & par = text_->paragraphs()[pit];
+ ParagraphMetrics const & pm = par_metrics_[pit];
+ if (pm.rows().empty())
+ return 0;
+
+ pos_type ppos = sl.pos();
+ // Correct position in front of big insets
+ bool const boundary_correction = ppos != 0 && boundary;
+ if (boundary_correction)
+ --ppos;
+
+ Row const & row = pm.getRow(sl.pos(), boundary);
+
+ pos_type cursor_vpos = 0;
+
+ Buffer const & buffer = bv_->buffer();
+ double x = row.x;
+ Bidi bidi;
+ bidi.computeTables(par, buffer, row);
+
+ 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());