#include <config.h>
#include "Buffer.h"
+#include "BufferParams.h"
#include "BufferView.h"
#include "CoordCache.h"
#include "Cursor.h"
#include "Paragraph.h"
#include "ParIterator.h"
#include "Row.h"
+#include "texstream.h"
#include "Text.h"
#include "TextMetrics.h"
#include "TocBackend.h"
#include "mathed/InsetMath.h"
#include "mathed/InsetMathBrace.h"
+#include "mathed/InsetMathEnsureMath.h"
#include "mathed/InsetMathScript.h"
#include "mathed/MacroTable.h"
#include "mathed/MathData.h"
+#include "mathed/MathFactory.h"
#include "mathed/MathMacro.h"
-#include "support/bind.h"
-
#include <sstream>
#include <limits>
#include <map>
// Find position closest to (x, y) in cell given by iter.
// Used only in mathed
-DocIterator bruteFind2(Cursor const & c, int x, int y)
+DocIterator bruteFind(Cursor const & c, int x, int y)
{
double best_dist = numeric_limits<double>::max();
DocIterator result;
DocIterator it = c;
- it.top().pos() = 0;
+ it.pos() = 0;
DocIterator et = c;
- et.top().pos() = et.top().asInsetMath()->cell(et.top().idx()).size();
+ et.pos() = et.lastpos();
for (size_t i = 0;; ++i) {
int xo;
int yo;
Inset const * inset = &it.inset();
- CoordCache const & cache = c.bv().coordCache();
+ CoordCache::Insets const & insetCache = c.bv().coordCache().getInsets();
// FIXME: in the case where the inset is not in the cache, this
// means that no part of it is visible on screen. In this case
// we don't do elaborate search and we just return the forwarded
// DocIterator at its beginning.
- if (!cache.getInsets().has(inset)) {
+ if (!insetCache.has(inset)) {
it.top().pos() = 0;
return it;
}
- Point const o = cache.getInsets().xy(inset);
+ Point const o = insetCache.xy(inset);
inset->cursorPos(c.bv(), it.top(), c.boundary(), xo, yo);
// Convert to absolute
xo += o.x_;
}
-/*
-/// moves position closest to (x, y) in given box
-bool bruteFind(Cursor & cursor,
- int x, int y, int xlow, int xhigh, int ylow, int yhigh)
-{
- LASSERT(!cursor.empty(), return false);
- Inset & inset = cursor[0].inset();
- BufferView & bv = cursor.bv();
-
- CoordCache::InnerParPosCache const & cache =
- bv.coordCache().getParPos().find(cursor.bottom().text())->second;
- // Get an iterator on the first paragraph in the cache
- DocIterator it(inset);
- it.push_back(CursorSlice(inset));
- it.pit() = cache.begin()->first;
- // Get an iterator after the last paragraph in the cache
- DocIterator et(inset);
- et.push_back(CursorSlice(inset));
- et.pit() = prev(cache.end(), 1)->first;
- if (et.pit() >= et.lastpit())
- et = doc_iterator_end(inset);
- else
- ++et.pit();
-
- double best_dist = numeric_limits<double>::max();
- DocIterator best_cursor = et;
-
- for ( ; it != et; it.forwardPos(true)) {
- // avoid invalid nesting when selecting
- if (!cursor.selection() || positionable(it, cursor.anchor_)) {
- Point p = bv.getPos(it, false);
- int xo = p.x_;
- int yo = p.y_;
- if (xlow <= xo && xo <= xhigh && ylow <= yo && yo <= yhigh) {
- double const dx = xo - x;
- double const dy = yo - y;
- double const d = dx * dx + dy * dy;
- // '<=' in order to take the last possible position
- // this is important for clicking behind \sum in e.g. '\sum_i a'
- if (d <= best_dist) {
- // lyxerr << "*" << endl;
- best_dist = d;
- best_cursor = it;
- }
- }
- }
- }
-
- if (best_cursor != et) {
- cursor.setCursor(best_cursor);
- return true;
- }
-
- return false;
-}
-*/
-
-/*
-/// moves position closest to (x, y) in given box
-bool bruteFind3(Cursor & cur, int x, int y, bool up)
-{
- BufferView & bv = cur.bv();
- int ylow = up ? 0 : y + 1;
- int yhigh = up ? y - 1 : bv.workHeight();
- int xlow = 0;
- int xhigh = bv.workWidth();
-
-// FIXME: bit more work needed to get 'from' and 'to' right.
- pit_type from = cur.bottom().pit();
- //pit_type to = cur.bottom().pit();
- //lyxerr << "Pit start: " << from << endl;
-
- //lyxerr << "bruteFind3: x: " << x << " y: " << y
- // << " xlow: " << xlow << " xhigh: " << xhigh
- // << " ylow: " << ylow << " yhigh: " << yhigh
- // << endl;
- DocIterator it = doc_iterator_begin(cur.buffer());
- it.pit() = from;
- DocIterator et = doc_iterator_end(cur.buffer());
-
- double best_dist = numeric_limits<double>::max();
- DocIterator best_cursor = et;
-
- for ( ; it != et; it.forwardPos()) {
- // avoid invalid nesting when selecting
- if (bv.cursorStatus(it) == CUR_INSIDE
- && (!cur.selection() || positionable(it, cur.realAnchor()))) {
- // If this function is ever used again, check
- // whether this is the same as "bv.getPos(it,
- // false)" with boundary = false.
- Point p = bv.getPos(it);
- int xo = p.x_;
- int yo = p.y_;
- if (xlow <= xo && xo <= xhigh && ylow <= yo && yo <= yhigh) {
- double const dx = xo - x;
- double const dy = yo - y;
- double const d = dx * dx + dy * dy;
- //lyxerr << "itx: " << xo << " ity: " << yo << " d: " << d
- // << " dx: " << dx << " dy: " << dy
- // << " idx: " << it.idx() << " pos: " << it.pos()
- // << " it:\n" << it
- // << endl;
- // '<=' in order to take the last possible position
- // this is important for clicking behind \sum in e.g. '\sum_i a'
- if (d <= best_dist) {
- //lyxerr << "*" << endl;
- best_dist = d;
- best_cursor = it;
- }
- }
- }
- }
-
- //lyxerr << "best_dist: " << best_dist << " cur:\n" << best_cursor << endl;
- if (best_cursor == et)
- return false;
- cur.setCursor(best_cursor);
- return true;
-}
-*/
} // namespace anon
}
-// this (intentionally) does neither touch anchor nor selection status
void Cursor::setCursor(DocIterator const & cur)
{
DocIterator::operator=(cur);
}
+void Cursor::setCursorSelectionTo(DocIterator dit)
+{
+ size_t i = 0;
+ // normalise dit
+ while (i < dit.depth() && i < anchor_.depth() && dit[i] == anchor_[i])
+ ++i;
+ if (i != dit.depth()) {
+ // otherwise the cursor is already normal
+ if (i == anchor_.depth())
+ // dit is a proper extension of the anchor_
+ dit.cutOff(i - 1);
+ else if (i + 1 < dit.depth()) {
+ // one has dit[i] != anchor_[i] but either dit[i-1] == anchor_[i-1]
+ // or i == 0. Remove excess.
+ dit.cutOff(i);
+ if (dit[i] > anchor_[i])
+ // place dit after the inset it was in
+ ++dit.pos();
+ }
+ }
+ setCursor(dit);
+ setSelection();
+}
+
+
void Cursor::setCursorToAnchor()
{
if (selection()) {
}
-bool Cursor::posBackward()
-{
- if (pos() == 0)
- return false;
- --pos();
- return true;
-}
-
-
-bool Cursor::posForward()
-{
- if (pos() == lastpos())
- return false;
- ++pos();
- return true;
-}
-
-
bool Cursor::posVisRight(bool skip_inset)
{
Cursor new_cur = *this; // where we will move to
right_pos = -1;
Row const & row = textRow();
- TextMetrics const & tm = bv_->textMetrics(text());
double dummy = 0;
- Row::const_iterator cit = tm.findRowElement(row, pos(), boundary(), dummy);
+ Row::const_iterator cit = row.findElement(pos(), boundary(), dummy);
// Handle the case of empty row
if (cit == row.end()) {
- if (paragraph().isRTL(buffer()->params()))
+ if (row.isRTL())
right_pos = row.pos();
else
left_pos = row.pos() - 1;
bool Cursor::posVisToNewRow(bool movingLeft)
{
- Paragraph const & par = paragraph();
- Buffer const & buf = *buffer();
Row const & row = textRow();
- bool par_is_LTR = !par.isRTL(buf.params());
+ bool par_is_LTR = !row.isRTL();
// Inside a table, determining whether to move to the next or
// previous row should be done based on the table's direction.
void Cursor::setSelection()
{
- setSelection(true);
+ selection(true);
if (idx() == normalAnchor().idx() &&
pit() == normalAnchor().pit() &&
pos() == normalAnchor().pos())
- setSelection(false);
+ selection(false);
}
void Cursor::setSelection(DocIterator const & where, int n)
{
setCursor(where);
- setSelection(true);
+ selection(true);
anchor_ = where;
pos() += n;
}
void Cursor::clearSelection()
{
- setSelection(false);
+ selection(false);
setWordSelection(false);
setMark(false);
resetAnchor();
cap::saveSelection(*this);
resetAnchor();
- setSelection(sel);
+ selection(sel);
return true;
}
} // namespace lyx
void Cursor::insert(docstring const & str)
{
- for_each(str.begin(), str.end(),
- bind(static_cast<void(Cursor::*)(char_type)>
- (&Cursor::insert), this, _1));
+ for (char_type c : str)
+ insert(c);
}
}
-bool Cursor::backspace()
+bool Cursor::backspace(bool const force)
{
if (selection()) {
cap::eraseSelection(*this);
}
}
- if (pos() != 0 && prevAtom()->nargs() > 0) {
+ if (pos() != 0 && !force && prevAtom()->confirmDeletion()) {
// let's require two backspaces for 'big stuff' and
// highlight on the first
resetAnchor();
- setSelection(true);
+ selection(true);
--pos();
} else {
--pos();
}
-bool Cursor::erase()
+bool Cursor::erase(bool const force)
{
if (inMacroMode())
return true;
}
// 'clever' UI hack: only erase large items if previously slected
- if (pos() != lastpos() && nextAtom()->nargs() > 0) {
+ if (pos() != lastpos() && !force && nextAtom()->confirmDeletion()) {
resetAnchor();
- setSelection(true);
+ selection(true);
++pos();
} else {
plainErase();
InsetMathNest * const in = inset().asInsetMath()->asNestInset();
if (in && in->interpretString(*this, s))
return true;
- MathAtom atom = buffer()->getMacro(name, *this, false) ?
- MathAtom(new MathMacro(buffer(), name)) : createInsetMath(name, buffer());
+ bool const user_macro = buffer()->getMacro(name, *this, false);
+ MathAtom atom = user_macro ? MathAtom(new MathMacro(buffer(), name))
+ : createInsetMath(name, buffer());
// try to put argument into macro, if we just inserted a macro
bool macroArg = false;
else if (atom.nucleus()->nargs() > 0)
atom.nucleus()->cell(0).append(selection);
- plainInsert(atom);
+ MathWordList const & words = mathedWordList();
+ MathWordList::const_iterator it = words.find(name);
+ bool keep_mathmode = it != words.end() && (it->second.inset == "font"
+ || it->second.inset == "oldfont"
+ || it->second.inset == "mbox");
+ bool ert_macro = !user_macro && it == words.end() && atomAsMacro;
+
+ if (in && in->currentMode() == Inset::TEXT_MODE
+ && atom.nucleus()->currentMode() == Inset::MATH_MODE
+ && name != from_ascii("ensuremath") && !ert_macro) {
+ MathAtom at(new InsetMathEnsureMath(buffer()));
+ at.nucleus()->cell(0).push_back(atom);
+ niceInsert(at);
+ posForward();
+ } else if (in && in->currentMode() == Inset::MATH_MODE
+ && atom.nucleus()->currentMode() == Inset::TEXT_MODE
+ && !keep_mathmode) {
+ MathAtom at = createInsetMath("text", buffer());
+ at.nucleus()->cell(0).push_back(atom);
+ niceInsert(at);
+ posForward();
+ } else
+ plainInsert(atom);
// finally put the macro argument behind, if needed
if (macroArg) {
<< pos() << ' ' << lastpos() << " in idx: " << idx()
<< " in atom: '";
odocstringstream os;
- TexRow texrow(false);
- otexrowstream ots(os,texrow);
+ otexrowstream ots(os);
WriteStream wi(ots, false, true, WriteStream::wsDefault);
inset().asInsetMath()->write(wi);
lyxerr << to_utf8(os.str()) << endl;
//lyxerr << "idxUpDown triggered" << endl;
// try to find best position within this inset
if (!selection())
- setCursor(bruteFind2(*this, xo, yo));
+ setCursor(bruteFind(*this, xo, yo));
return true;
}
}
+InsetMath & Cursor::nextMath()
+{
+ return *nextAtom().nucleus();
+}
+
+
+InsetMath & Cursor::prevMath()
+{
+ return *prevAtom().nucleus();
+}
+
+
+bool Cursor::mathForward(bool word)
+{
+ LASSERT(inMathed(), return false);
+ if (pos() < lastpos()) {
+ if (word) {
+ // word: skip a group of insets with same math class
+ MathClass mc = nextMath().mathClass();
+ do
+ posForward();
+ while (pos() < lastpos() && mc == nextMath().mathClass());
+ } else if (openable(nextAtom())) {
+ // single step: try to enter the next inset
+ pushBackward(nextMath());
+ inset().idxFirst(*this);
+ } else
+ posForward();
+ return true;
+ }
+ if (inset().idxForward(*this))
+ return true;
+ // try to pop forwards --- but don't pop out of math! leave that to
+ // the FINISH lfuns
+ int s = depth() - 2;
+ if (s >= 0 && operator[](s).inset().asInsetMath())
+ return popForward();
+ return false;
+}
+
+
+bool Cursor::mathBackward(bool word)
+{
+ LASSERT(inMathed(), return false);
+ if (pos() > 0) {
+ if (word) {
+ // word: skip a group of insets with same math class
+ MathClass mc = prevMath().mathClass();
+ do
+ posBackward();
+ while (pos() > 0 && mc == prevMath().mathClass());
+ } else if (openable(prevAtom())) {
+ // single step: try to enter the preceding inset
+ posBackward();
+ push(nextMath());
+ inset().idxLast(*this);
+ } else
+ posBackward();
+ return true;
+ }
+ if (inset().idxBackward(*this))
+ return true;
+ // try to pop backwards --- but don't pop out of math! leave that to
+ // the FINISH lfuns
+ int s = depth() - 2;
+ if (s >= 0 && operator[](s).inset().asInsetMath())
+ return popBackward();
+ return false;
+}
+
+
bool Cursor::atFirstOrLastRow(bool up)
{
TextMetrics const & tm = bv_->textMetrics(text());
int yo = bv().getPos(*this).y_;
Cursor old = *this;
// To next/previous row
+ // FIXME: the y position is often guessed wrongly across styles and
+ // insets, which leads to weird behaviour.
if (up)
tm.editXY(*this, xo, yo - textRow().ascent() - 1);
else
tm.editXY(*this, xo, yo + textRow().descent() + 1);
+ x_target_ = old.x_target_;
clearSelection();
// This happens when you move out of an inset.
{
if (empty())
return 0;
+ BufferParams const & bp = bv().buffer().params();
+ if (bp.useNonTeXFonts)
+ return encodings.fromLyXName("utf8-plain");
+
CursorSlice const & sl = innerTextSlice();
Text const & text = *sl.text();
- Font font = text.getPar(sl.pit()).getFont(
- bv().buffer().params(), sl.pos(), text.outerFont(sl.pit()));
+ Font font = text.getPar(sl.pit()).getFont(bp, sl.pos(),
+ text.outerFont(sl.pit()));
return font.language()->encoding();
}
// The logic here should more or less match to the
// Cursor::setCurrentFont logic, i.e. the cursor height should
// give a hint what will happen if a character is entered.
+ // FIXME: this is not the case, what about removing this method ? (see #10478).
// HACK. far from being perfect...
{
setBuffer(&bv_->buffer());
DocIterator::sanitize();
+ new_word_.sanitize();
if (selection())
anchor_.sanitize();
else
}
+bool Cursor::confirmDeletion(bool const before) const
+{
+ if (!selection()) {
+ if (Inset const * inset = before ? prevInset() : nextInset())
+ return inset->confirmDeletion();
+ } else {
+ DocIterator dit = selectionBegin();
+ CursorSlice const end = selectionEnd().top();
+ for (; dit.top() < end; dit.top().forwardPos())
+ if (Inset const * inset = dit.nextInset())
+ if (inset->confirmDeletion())
+ return true;
+ }
+ return false;
+}
+
+
+void Cursor::moveToClosestEdge(int const x, bool const edit)
+{
+ if (Inset const * inset = nextInset()) {
+ // stay in front of insets for which we want to open the dialog
+ // (e.g. InsetMathSpace).
+ if (edit && (inset->hasSettings() || !inset->contextMenuName().empty()))
+ return;
+ CoordCache::Insets const & insetCache = bv().coordCache().getInsets();
+ if (!insetCache.has(inset))
+ return;
+ int const wid = insetCache.dim(inset).wid;
+ Point p = insetCache.xy(inset);
+ if (x > p.x_ + (wid + 1) / 2)
+ posForward();
+ }
+}
+
+
} // namespace lyx