From b78dbb59996c6274834ba18f2a7f3c670dc4e11e Mon Sep 17 00:00:00 2001 From: Stefan Schimanski Date: Mon, 25 Feb 2008 01:56:30 +0000 Subject: [PATCH] * special faster algorithms for sorted completion lists git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@23212 a592a061-630c-0410-9148-cb99ea01b6c8 --- src/frontends/qt4/GuiCompleter.cpp | 105 +- src/mathed/InsetMathNest.cpp.orig | 1864 ++++++++++++++++++++++++++++ 2 files changed, 1949 insertions(+), 20 deletions(-) create mode 100644 src/mathed/InsetMathNest.cpp.orig diff --git a/src/frontends/qt4/GuiCompleter.cpp b/src/frontends/qt4/GuiCompleter.cpp index b50b472f34..cc27d33e20 100644 --- a/src/frontends/qt4/GuiCompleter.cpp +++ b/src/frontends/qt4/GuiCompleter.cpp @@ -176,6 +176,7 @@ GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent) listView->setSelectionMode(QAbstractItemView::SingleSelection); listView->header()->hide(); listView->setIndentation(0); + listView->setUniformRowHeights(true); setPopup(listView); popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(this)); rtlItemDelegate_ = new RtlItemDelegate(this); @@ -604,14 +605,41 @@ void GuiCompleter::setCurrentCompletion(QString const & s) return; } - // iterate through list until the s is found - // FIXME: there must be a better way than this iteration + // find old selection in model size_t i; - for (i = 0; i < n; ++i) { - QString const & is - = model.data(model.index(i, 0), Qt::EditRole).toString(); - if (is == s) - break; + if (modelSorting() == QCompleter::UnsortedModel) { + // In unsorted models, iterate through list until the s is found + for (i = 0; i < n; ++i) { + QString const & is + = model.data(model.index(i, 0), Qt::EditRole).toString(); + if (is == s) + break; + } + } else { + // In sorted models, do binary search for s. + i = 0; + size_t r = n - 1; + do { + size_t mid = (r + i) / 2; + QString const & mids + = model.data(model.index(mid, 0), + Qt::EditRole).toString(); + + // left or right? + // FIXME: is this really the same order that the docstring + // from the CompletionList has? + int c = s.compare(mids, Qt::CaseSensitive); + if (c == 0) { + i = mid; + break; + } else if (c > 0) + // middle is not far enough + i = mid + 1; + else + // middle is too far + r = mid - 1; + + } while (r - i > 0 && i < n); } // select the first if none was found @@ -624,25 +652,62 @@ void GuiCompleter::setCurrentCompletion(QString const & s) } -docstring GuiCompleter::longestUniqueCompletion() const { +size_t commonPrefix(QString const & s1, QString const & s2) +{ + // find common prefix + size_t j; + size_t n1 = s1.length(); + size_t n2 = s2.length(); + for (j = 0; j < n1 && j < n2; ++j) { + if (s1.at(j) != s2.at(j)) + break; + } + return j; +} + + +docstring GuiCompleter::longestUniqueCompletion() const +{ QAbstractItemModel const & model = *popup()->model(); QString s = currentCompletion(); size_t n = model.rowCount(); - // iterate through the completions and cut off where s differs - for (size_t i = 0; i < n && s.length() > 0; ++i) { - QString const & is - = model.data(model.index(i, 0), Qt::EditRole).toString(); + if (modelSorting() == QCompleter::UnsortedModel) { + // For unsorted model we cannot do more than iteration. + // Iterate through the completions and cut off where s differs + for (size_t i = 0; i < n && s.length() > 0; ++i) { + QString const & is + = model.data(model.index(i, 0), Qt::EditRole).toString(); - // find common prefix - size_t j; - size_t isn = is.length(); - size_t sn = s.length(); - for (j = 0; j < isn && j < sn; ++j) { - if (s.at(j) != is.at(j)) - break; + s = s.left(commonPrefix(is, s)); + } + } else { + // For sorted models we can do binary search multiple times, + // each time to find the first string which has s not as prefix. + size_t i = 0; + while (i < n && s.length() > 0) { + // find first string that does not have s as prefix + // via binary search in [i,n-1] + size_t r = n - 1; + do { + // get common prefix with the middle string + size_t mid = (r + i) / 2; + QString const & mids + = model.data(model.index(mid, 0), + Qt::EditRole).toString(); + int sn = s.length(); + s = commonPrefix(mids, s); + + // left or right? + if (s.length() == sn) { + // middle is not far enough + i = mid + 1; + } else { + // middle is maybe too far + r = mid; + } + } while (r - i > 0 && i < n); } - s = s.left(j); } return from_utf8(fromqstr(s)); diff --git a/src/mathed/InsetMathNest.cpp.orig b/src/mathed/InsetMathNest.cpp.orig new file mode 100644 index 0000000000..30e82c5b67 --- /dev/null +++ b/src/mathed/InsetMathNest.cpp.orig @@ -0,0 +1,1864 @@ +/** + * \file InsetMathNest.cpp + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author André Pönitz + * + * Full author contact details are available in file CREDITS. + */ + +#include + +#include "InsetMathNest.h" + +#include "InsetMathArray.h" +#include "InsetMathBig.h" +#include "InsetMathBox.h" +#include "InsetMathBrace.h" +#include "InsetMathColor.h" +#include "InsetMathComment.h" +#include "InsetMathDelim.h" +#include "InsetMathHull.h" +#include "InsetMathRef.h" +#include "InsetMathScript.h" +#include "InsetMathSpace.h" +#include "InsetMathSymbol.h" +#include "InsetMathUnknown.h" +#include "MathData.h" +#include "MathFactory.h" +#include "MathMacro.h" +#include "MathMacroArgument.h" +#include "MathParser.h" +#include "MathStream.h" +#include "MathSupport.h" + +#include "Bidi.h" +#include "Buffer.h" +#include "BufferView.h" +#include "CoordCache.h" +#include "Cursor.h" +#include "CutAndPaste.h" +#include "DispatchResult.h" +#include "FuncRequest.h" +#include "FuncStatus.h" +#include "LyXFunc.h" +#include "LyXRC.h" +#include "OutputParams.h" +#include "Text.h" + +#include "frontends/Clipboard.h" +#include "frontends/Painter.h" +#include "frontends/Selection.h" + +#include "support/debug.h" +#include "support/gettext.h" +#include "support/lstrings.h" +#include "support/textutils.h" +#include "support/docstream.h" + +#include +#include +#include + +using namespace std; +using namespace lyx::support; + +namespace lyx { + +using cap::copySelection; +using cap::grabAndEraseSelection; +using cap::cutSelection; +using cap::replaceSelection; +using cap::selClearOrDel; + + +InsetMathNest::InsetMathNest(idx_type nargs) + : cells_(nargs), lock_(false), mouse_hover_(false) +{} + + +InsetMathNest::InsetMathNest(InsetMathNest const & inset) + : InsetMath(inset), cells_(inset.cells_), lock_(inset.lock_), + mouse_hover_(false) +{} + + +InsetMathNest & InsetMathNest::operator=(InsetMathNest const & inset) +{ + cells_ = inset.cells_; + lock_ = inset.lock_; + mouse_hover_ = false; + InsetMath::operator=(inset); + return *this; +} + + +InsetMath::idx_type InsetMathNest::nargs() const +{ + return cells_.size(); +} + + +void InsetMathNest::cursorPos(BufferView const & bv, + CursorSlice const & sl, bool /*boundary*/, + int & x, int & y) const +{ +// FIXME: This is a hack. Ideally, the coord cache should not store +// absolute positions, but relative ones. This would mean to call +// setXY() not in MathData::draw(), but in the parent insets' draw() +// with the correctly adjusted x,y values. But this means that we'd have +// to touch all (math)inset's draw() methods. Right now, we'll store +// absolute value, and make them here relative, only to make them +// absolute again when actually drawing the cursor. What a mess. + BOOST_ASSERT(&sl.inset() == this); + MathData const & ar = sl.cell(); + CoordCache const & coord_cache = bv.coordCache(); + if (!coord_cache.getArrays().has(&ar)) { + // this can (semi-)legally happen if we just created this cell + // and it never has been drawn before. So don't ASSERT. + //lyxerr << "no cached data for array " << &ar << endl; + x = 0; + y = 0; + return; + } + Point const pt = coord_cache.getArrays().xy(&ar); + if (!coord_cache.getInsets().has(this)) { + // same as above + //lyxerr << "no cached data for inset " << this << endl; + x = 0; + y = 0; + return; + } + Point const pt2 = coord_cache.getInsets().xy(this); + //lyxerr << "retrieving position cache for MathData " + // << pt.x_ << ' ' << pt.y_ << endl; + x = pt.x_ - pt2.x_ + ar.pos2x(&bv, sl.pos()); + y = pt.y_ - pt2.y_; +// lyxerr << "pt.y_ : " << pt.y_ << " pt2_.y_ : " << pt2.y_ +// << " asc: " << ascent() << " des: " << descent() +// << " ar.asc: " << ar.ascent() << " ar.des: " << ar.descent() << endl; + // move cursor visually into empty cells ("blue rectangles"); + if (ar.empty()) + x += 2; +} + + +void InsetMathNest::metrics(MetricsInfo const & mi) const +{ + MetricsInfo m = mi; + for (idx_type i = 0, n = nargs(); i != n; ++i) { + Dimension dim; + cell(i).metrics(m, dim); + } +} + + +bool InsetMathNest::idxNext(Cursor & cur) const +{ + BOOST_ASSERT(&cur.inset() == this); + if (cur.idx() == cur.lastidx()) + return false; + ++cur.idx(); + cur.pos() = 0; + return true; +} + + +bool InsetMathNest::idxForward(Cursor & cur) const +{ + return idxNext(cur); +} + + +bool InsetMathNest::idxPrev(Cursor & cur) const +{ + BOOST_ASSERT(&cur.inset() == this); + if (cur.idx() == 0) + return false; + --cur.idx(); + cur.pos() = cur.lastpos(); + return true; +} + + +bool InsetMathNest::idxBackward(Cursor & cur) const +{ + return idxPrev(cur); +} + + +bool InsetMathNest::idxFirst(Cursor & cur) const +{ + BOOST_ASSERT(&cur.inset() == this); + if (nargs() == 0) + return false; + cur.idx() = 0; + cur.pos() = 0; + return true; +} + + +bool InsetMathNest::idxLast(Cursor & cur) const +{ + BOOST_ASSERT(&cur.inset() == this); + if (nargs() == 0) + return false; + cur.idx() = cur.lastidx(); + cur.pos() = cur.lastpos(); + return true; +} + + +void InsetMathNest::dump() const +{ + odocstringstream oss; + WriteStream os(oss); + os << "---------------------------------------------\n"; + write(os); + os << "\n"; + for (idx_type i = 0, n = nargs(); i != n; ++i) + os << cell(i) << "\n"; + os << "---------------------------------------------\n"; + lyxerr << to_utf8(oss.str()); +} + + +void InsetMathNest::draw(PainterInfo & pi, int x, int y) const +{ +#if 0 + if (lock_) + pi.pain.fillRectangle(x, y - ascent(), width(), height(), + Color_mathlockbg); +#endif + setPosCache(pi, x, y); +} + + +void InsetMathNest::drawSelection(PainterInfo & pi, int x, int y) const +{ + BufferView & bv = *pi.base.bv; + // this should use the x/y values given, not the cached values + Cursor & cur = bv.cursor(); + if (!cur.selection()) + return; + if (&cur.inset() != this) + return; + + // FIXME: hack to get position cache warm + bool const original_drawing_state = pi.pain.isDrawingEnabled(); + pi.pain.setDrawingEnabled(false); + draw(pi, x, y); + pi.pain.setDrawingEnabled(original_drawing_state); + + CursorSlice s1 = cur.selBegin(); + CursorSlice s2 = cur.selEnd(); + + //lyxerr << "InsetMathNest::drawing selection: " + // << " s1: " << s1 << " s2: " << s2 << endl; + if (s1.idx() == s2.idx()) { + MathData const & c = cell(s1.idx()); + Geometry const & g = bv.coordCache().getArrays().geometry(&c); + int x1 = g.pos.x_ + c.pos2x(pi.base.bv, s1.pos()); + int y1 = g.pos.y_ - g.dim.ascent(); + int x2 = g.pos.x_ + c.pos2x(pi.base.bv, s2.pos()); + int y2 = g.pos.y_ + g.dim.descent(); + pi.pain.fillRectangle(x1, y1, x2 - x1, y2 - y1, Color_selection); + //lyxerr << "InsetMathNest::drawing selection 3: " + // << " x1: " << x1 << " x2: " << x2 + // << " y1: " << y1 << " y2: " << y2 << endl; + } else { + for (idx_type i = 0; i < nargs(); ++i) { + if (idxBetween(i, s1.idx(), s2.idx())) { + MathData const & c = cell(i); + Geometry const & g = bv.coordCache().getArrays().geometry(&c); + int x1 = g.pos.x_; + int y1 = g.pos.y_ - g.dim.ascent(); + int x2 = g.pos.x_ + g.dim.width(); + int y2 = g.pos.y_ + g.dim.descent(); + pi.pain.fillRectangle(x1, y1, x2 - x1, y2 - y1, Color_selection); + } + } + } +} + + +void InsetMathNest::validate(LaTeXFeatures & features) const +{ + for (idx_type i = 0; i < nargs(); ++i) + cell(i).validate(features); +} + + +void InsetMathNest::replace(ReplaceData & rep) +{ + for (idx_type i = 0; i < nargs(); ++i) + cell(i).replace(rep); +} + + +bool InsetMathNest::contains(MathData const & ar) const +{ + for (idx_type i = 0; i < nargs(); ++i) + if (cell(i).contains(ar)) + return true; + return false; +} + + +bool InsetMathNest::lock() const +{ + return lock_; +} + + +void InsetMathNest::lock(bool l) +{ + lock_ = l; +} + + +bool InsetMathNest::isActive() const +{ + return nargs() > 0; +} + + +MathData InsetMathNest::glue() const +{ + MathData ar; + for (size_t i = 0; i < nargs(); ++i) + ar.append(cell(i)); + return ar; +} + + +void InsetMathNest::write(WriteStream & os) const +{ + os << '\\' << name().c_str(); + for (size_t i = 0; i < nargs(); ++i) + os << '{' << cell(i) << '}'; + if (nargs() == 0) + os.pendingSpace(true); + if (lock_ && !os.latex()) { + os << "\\lyxlock"; + os.pendingSpace(true); + } +} + + +void InsetMathNest::normalize(NormalStream & os) const +{ + os << '[' << name().c_str(); + for (size_t i = 0; i < nargs(); ++i) + os << ' ' << cell(i); + os << ']'; +} + + +int InsetMathNest::latex(Buffer const &, odocstream & os, + OutputParams const & runparams) const +{ + WriteStream wi(os, runparams.moving_arg, true); + write(wi); + return wi.line(); +} + + +bool InsetMathNest::setMouseHover(bool mouse_hover) +{ + mouse_hover_ = mouse_hover; + return true; +} + + +bool InsetMathNest::notifyCursorLeaves(Cursor & /*cur*/) +{ + // FIXME: look here +#if 0 + MathData & ar = cur.cell(); + // remove base-only "scripts" + for (pos_type i = 0; i + 1 < ar.size(); ++i) { + InsetMathScript * p = operator[](i).nucleus()->asScriptInset(); + if (p && p->nargs() == 1) { + MathData ar = p->nuc(); + erase(i); + insert(i, ar); + cur.adjust(i, ar.size() - 1); + } + } + + // glue adjacent font insets of the same kind + for (pos_type i = 0; i + 1 < size(); ++i) { + InsetMathFont * p = operator[](i).nucleus()->asFontInset(); + InsetMathFont const * q = operator[](i + 1)->asFontInset(); + if (p && q && p->name() == q->name()) { + p->cell(0).append(q->cell(0)); + erase(i + 1); + cur.adjust(i, -1); + } + } +#endif + return false; +} + + +void InsetMathNest::handleFont + (Cursor & cur, docstring const & arg, char const * const font) +{ + handleFont(cur, arg, from_ascii(font)); +} + + +void InsetMathNest::handleFont + (Cursor & cur, docstring const & arg, docstring const & font) +{ + // this whole function is a hack and won't work for incremental font + // changes... + + if (cur.inset().asInsetMath()->name() == font) { + cur.recordUndoInset(); + cur.handleFont(to_utf8(font)); + } else { + cur.recordUndo(); + cur.handleNest(createInsetMath(font)); + cur.insert(arg); + } +} + + +void InsetMathNest::handleFont2(Cursor & cur, docstring const & arg) +{ + cur.recordUndo(); + Font font; + bool b; + font.fromString(to_utf8(arg), b); + if (font.fontInfo().color() != Color_inherit) { + MathAtom at = MathAtom(new InsetMathColor(true, font.fontInfo().color())); + cur.handleNest(at, 0); + } +} + + +void InsetMathNest::doDispatch(Cursor & cur, FuncRequest & cmd) +{ + //lyxerr << "InsetMathNest: request: " << cmd << endl; + //CursorSlice sl = cur.current(); + + switch (cmd.action) { + + case LFUN_PASTE: { + cur.recordUndo(); + cur.message(_("Paste")); + replaceSelection(cur); + docstring topaste; + if (cmd.argument().empty() && !theClipboard().isInternal()) + topaste = theClipboard().getAsText(); + else { + size_t n = 0; + idocstringstream is(cmd.argument()); + is >> n; + topaste = cap::getSelection(cur.buffer(), n); + } + cur.niceInsert(topaste); + cur.clearSelection(); // bug 393 + cur.finishUndo(); + break; + } + + case LFUN_CUT: + cur.recordUndo(); + cutSelection(cur, true, true); + cur.message(_("Cut")); + // Prevent stale position >= size crash + // Probably not necessary anymore, see eraseSelection (gb 2005-10-09) + cur.normalize(); + break; + + case LFUN_COPY: + copySelection(cur); + cur.message(_("Copy")); + break; + + case LFUN_MOUSE_PRESS: + lfunMousePress(cur, cmd); + break; + + case LFUN_MOUSE_MOTION: + lfunMouseMotion(cur, cmd); + break; + + case LFUN_MOUSE_RELEASE: + lfunMouseRelease(cur, cmd); + break; + + case LFUN_FINISHED_LEFT: // in math, left is backwards + case LFUN_FINISHED_BACKWARD: + cur.bv().cursor() = cur; + break; + + case LFUN_FINISHED_RIGHT: // in math, right is forward + case LFUN_FINISHED_FORWARD: + ++cur.pos(); + cur.bv().cursor() = cur; + break; + + case LFUN_CHAR_RIGHT: + case LFUN_CHAR_LEFT: + case LFUN_CHAR_BACKWARD: + case LFUN_CHAR_FORWARD: + cur.updateFlags(Update::Decoration | Update::FitCursor); + case LFUN_CHAR_RIGHT_SELECT: + case LFUN_CHAR_LEFT_SELECT: + case LFUN_CHAR_BACKWARD_SELECT: + case LFUN_CHAR_FORWARD_SELECT: { + // are we in a selection? + bool select = (cmd.action == LFUN_CHAR_RIGHT_SELECT + || cmd.action == LFUN_CHAR_LEFT_SELECT + || cmd.action == LFUN_CHAR_BACKWARD_SELECT + || cmd.action == LFUN_CHAR_FORWARD_SELECT); + // are we moving forward or backwards? + // If the command was RIGHT or LEFT, then whether we're moving forward + // or backwards depends on the cursor movement mode (logical or visual): + // * in visual mode, since math is always LTR, right -> forward, + // left -> backwards + // * in logical mode, the mapping is determined by the + // reverseDirectionNeeded() function + + bool forward; + kb_action finish_lfun; + + if (cmd.action == LFUN_CHAR_FORWARD + || cmd.action == LFUN_CHAR_FORWARD_SELECT) { + forward = true; + finish_lfun = LFUN_FINISHED_FORWARD; + } + else if (cmd.action == LFUN_CHAR_BACKWARD + || cmd.action == LFUN_CHAR_BACKWARD_SELECT) { + forward = false; + finish_lfun = LFUN_FINISHED_BACKWARD; + } + else { + bool right = (cmd.action == LFUN_CHAR_RIGHT_SELECT + || cmd.action == LFUN_CHAR_RIGHT); + if (lyxrc.visual_cursor || !reverseDirectionNeeded(cur)) + forward = right; + else + forward = !right; + + if (right) + finish_lfun = LFUN_FINISHED_RIGHT; + else + finish_lfun = LFUN_FINISHED_LEFT; + } + // Now that we know exactly what we want to do, let's do it! + cur.selHandle(select); + cur.autocorrect() = false; + cur.clearTargetX(); + cur.macroModeClose(); + // try moving forward or backwards as necessary... + if (!(forward ? cursorMathForward(cur) : cursorMathBackward(cur))) { + // ... and if movement failed, then finish forward or backwards + // as necessary + cmd = FuncRequest(finish_lfun); + cur.undispatched(); + } + break; + } + + case LFUN_DOWN: + case LFUN_UP: + cur.updateFlags(Update::Decoration | Update::FitCursor); + case LFUN_DOWN_SELECT: + case LFUN_UP_SELECT: { + // close active macro + if (cur.inMacroMode()) { + cur.macroModeClose(); + break; + } + + // stop/start the selection + bool select = cmd.action == LFUN_DOWN_SELECT || + cmd.action == LFUN_UP_SELECT; + cur.selHandle(select); + + // go up/down + bool up = cmd.action == LFUN_UP || cmd.action == LFUN_UP_SELECT; + bool successful = cur.upDownInMath(up); + if (successful) { + // notify left insets and give them chance to set update flags + lyx::notifyCursorLeaves(cur.beforeDispatchCursor(), cur); + cur.fixIfBroken(); + break; + } + + if (cur.fixIfBroken()) + // FIXME: Something bad happened. We pass the corrected Cursor + // instead of letting things go worse. + break; + + // We did not manage to move the cursor. + cur.undispatched(); + break; + } + + case LFUN_MOUSE_DOUBLE: + case LFUN_MOUSE_TRIPLE: + case LFUN_WORD_SELECT: + cur.pos() = 0; + cur.idx() = 0; + cur.resetAnchor(); + cur.selection() = true; + cur.pos() = cur.lastpos(); + cur.idx() = cur.lastidx(); + break; + + case LFUN_PARAGRAPH_UP: + case LFUN_PARAGRAPH_DOWN: + cur.updateFlags(Update::Decoration | Update::FitCursor); + case LFUN_PARAGRAPH_UP_SELECT: + case LFUN_PARAGRAPH_DOWN_SELECT: + break; + + case LFUN_LINE_BEGIN: + case LFUN_WORD_BACKWARD: + case LFUN_WORD_LEFT: + cur.updateFlags(Update::Decoration | Update::FitCursor); + case LFUN_LINE_BEGIN_SELECT: + case LFUN_WORD_BACKWARD_SELECT: + case LFUN_WORD_LEFT_SELECT: + cur.selHandle(cmd.action == LFUN_WORD_BACKWARD_SELECT || + cmd.action == LFUN_WORD_LEFT_SELECT || + cmd.action == LFUN_LINE_BEGIN_SELECT); + cur.macroModeClose(); + if (cur.pos() != 0) { + cur.pos() = 0; + } else if (cur.col() != 0) { + cur.idx() -= cur.col(); + cur.pos() = 0; + } else if (cur.idx() != 0) { + cur.idx() = 0; + cur.pos() = 0; + } else { + cmd = FuncRequest(LFUN_FINISHED_BACKWARD); + cur.undispatched(); + } + break; + + case LFUN_WORD_FORWARD: + case LFUN_WORD_RIGHT: + case LFUN_LINE_END: + cur.updateFlags(Update::Decoration | Update::FitCursor); + case LFUN_WORD_FORWARD_SELECT: + case LFUN_WORD_RIGHT_SELECT: + case LFUN_LINE_END_SELECT: + cur.selHandle(cmd.action == LFUN_WORD_FORWARD_SELECT || + cmd.action == LFUN_WORD_RIGHT_SELECT || + cmd.action == LFUN_LINE_END_SELECT); + cur.macroModeClose(); + cur.clearTargetX(); + if (cur.pos() != cur.lastpos()) { + cur.pos() = cur.lastpos(); + } else if (ncols() && (cur.col() != cur.lastcol())) { + cur.idx() = cur.idx() - cur.col() + cur.lastcol(); + cur.pos() = cur.lastpos(); + } else if (cur.idx() != cur.lastidx()) { + cur.idx() = cur.lastidx(); + cur.pos() = cur.lastpos(); + } else { + cmd = FuncRequest(LFUN_FINISHED_FORWARD); + cur.undispatched(); + } + break; + + case LFUN_SCREEN_UP_SELECT: + cmd = FuncRequest(LFUN_FINISHED_BACKWARD); + cur.undispatched(); + break; + + case LFUN_SCREEN_DOWN_SELECT: + cmd = FuncRequest(LFUN_FINISHED_FORWARD); + cur.undispatched(); + break; + + case LFUN_CELL_FORWARD: + cur.updateFlags(Update::Decoration | Update::FitCursor); + cur.inset().idxNext(cur); + break; + + case LFUN_CELL_BACKWARD: + cur.updateFlags(Update::Decoration | Update::FitCursor); + cur.inset().idxPrev(cur); + break; + + case LFUN_WORD_DELETE_BACKWARD: + case LFUN_CHAR_DELETE_BACKWARD: + if (cur.pos() == 0) + // May affect external cell: + cur.recordUndoInset(); + else + cur.recordUndo(); + // if the inset can not be removed from within, delete it + if (!cur.backspace()) { + FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD); + cur.innerText()->dispatch(cur, cmd); + } + break; + + case LFUN_WORD_DELETE_FORWARD: + case LFUN_CHAR_DELETE_FORWARD: + if (cur.pos() == cur.lastpos()) + // May affect external cell: + cur.recordUndoInset(); + else + cur.recordUndo(); + // if the inset can not be removed from within, delete it + if (!cur.erase()) { + FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD); + cur.innerText()->dispatch(cur, cmd); + } + break; + + case LFUN_ESCAPE: + if (cur.selection()) + cur.clearSelection(); + else { + cmd = FuncRequest(LFUN_FINISHED_FORWARD); + cur.undispatched(); + } + break; + + // 'Locks' the math inset. A 'locked' math inset behaves as a unit + // that is traversed by a single /. + case LFUN_INSET_TOGGLE: + cur.recordUndo(); + lock(!lock()); + cur.popForward(); + break; + + case LFUN_SELF_INSERT: + if (cmd.argument().size() != 1) { + cur.recordUndo(); + docstring const arg = cmd.argument(); + if (!interpretString(cur, arg)) + cur.insert(arg); + break; + } + // Don't record undo steps if we are in macro mode and + // cmd.argument is the next character of the macro name. + // Otherwise we'll get an invalid cursor if we undo after + // the macro was finished and the macro is a known command, + // e.g. sqrt. Cursor::macroModeClose replaces in this case + // the InsetMathUnknown with name "frac" by an empty + // InsetMathFrac -> a pos value > 0 is invalid. + // A side effect is that an undo before the macro is finished + // undoes the complete macro, not only the last character. + if (!cur.inMacroMode()) + cur.recordUndo(); + + // spacial handling of space. If we insert an inset + // via macro mode, we want to put the cursor inside it + // if relevant. Think typing "\frac". + if (cmd.argument()[0] == ' ' + && cur.inMacroMode() && cur.macroName() != "\\" + && cur.macroModeClose()) { + MathAtom const atom = cur.prevAtom(); + if (atom->asNestInset() && atom->isActive()) { + cur.posBackward(); + cur.pushBackward(*cur.nextInset()); + } + } else if (!interpretChar(cur, cmd.argument()[0])) { + cmd = FuncRequest(LFUN_FINISHED_FORWARD); + cur.undispatched(); + } + break; + + //case LFUN_SERVER_GET_XY: + // sprintf(dispatch_buffer, "%d %d",); + // break; + + case LFUN_SERVER_SET_XY: { + lyxerr << "LFUN_SERVER_SET_XY broken!" << endl; + int x = 0; + int y = 0; + istringstream is(to_utf8(cmd.argument())); + is >> x >> y; + cur.setScreenPos(x, y); + break; + } + + // Special casing for superscript in case of LyX handling + // dead-keys: + case LFUN_ACCENT_CIRCUMFLEX: + if (cmd.argument().empty()) { + // do superscript if LyX handles + // deadkeys + cur.recordUndo(); + script(cur, true, grabAndEraseSelection(cur)); + } + break; + + case LFUN_ACCENT_UMLAUT: + case LFUN_ACCENT_ACUTE: + case LFUN_ACCENT_GRAVE: + case LFUN_ACCENT_BREVE: + case LFUN_ACCENT_DOT: + case LFUN_ACCENT_MACRON: + case LFUN_ACCENT_CARON: + case LFUN_ACCENT_TILDE: + case LFUN_ACCENT_CEDILLA: + case LFUN_ACCENT_CIRCLE: + case LFUN_ACCENT_UNDERDOT: + case LFUN_ACCENT_TIE: + case LFUN_ACCENT_OGONEK: + case LFUN_ACCENT_HUNGARIAN_UMLAUT: + break; + + // Math fonts + case LFUN_FONT_FREE_APPLY: + case LFUN_FONT_FREE_UPDATE: + handleFont2(cur, cmd.argument()); + break; + + case LFUN_FONT_BOLD: + if (currentMode() == TEXT_MODE) + handleFont(cur, cmd.argument(), "textbf"); + else + handleFont(cur, cmd.argument(), "mathbf"); + break; + case LFUN_FONT_SANS: + if (currentMode() == TEXT_MODE) + handleFont(cur, cmd.argument(), "textsf"); + else + handleFont(cur, cmd.argument(), "mathsf"); + break; + case LFUN_FONT_EMPH: + if (currentMode() == TEXT_MODE) + handleFont(cur, cmd.argument(), "emph"); + else + handleFont(cur, cmd.argument(), "mathcal"); + break; + case LFUN_FONT_ROMAN: + if (currentMode() == TEXT_MODE) + handleFont(cur, cmd.argument(), "textrm"); + else + handleFont(cur, cmd.argument(), "mathrm"); + break; + case LFUN_FONT_TYPEWRITER: + if (currentMode() == TEXT_MODE) + handleFont(cur, cmd.argument(), "texttt"); + else + handleFont(cur, cmd.argument(), "mathtt"); + break; + case LFUN_FONT_FRAK: + handleFont(cur, cmd.argument(), "mathfrak"); + break; + case LFUN_FONT_ITAL: + if (currentMode() == TEXT_MODE) + handleFont(cur, cmd.argument(), "textit"); + else + handleFont(cur, cmd.argument(), "mathit"); + break; + case LFUN_FONT_NOUN: + if (currentMode() == TEXT_MODE) + // FIXME: should be "noun" + handleFont(cur, cmd.argument(), "textsc"); + else + handleFont(cur, cmd.argument(), "mathbb"); + break; + /* + case LFUN_FONT_FREE_APPLY: + handleFont(cur, cmd.argument(), "textrm"); + break; + */ + case LFUN_FONT_DEFAULT: + handleFont(cur, cmd.argument(), "textnormal"); + break; + + case LFUN_MATH_MODE: { +#if 1 + // ignore math-mode on when already in math mode + if (currentMode() == Inset::MATH_MODE && cmd.argument() == "on") + break; + cur.macroModeClose(); + docstring const save_selection = grabAndEraseSelection(cur); + selClearOrDel(cur); + //cur.plainInsert(MathAtom(new InsetMathMBox(cur.bv()))); + cur.plainInsert(MathAtom(new InsetMathBox(from_ascii("mbox")))); + cur.posBackward(); + cur.pushBackward(*cur.nextInset()); + cur.niceInsert(save_selection); +#else + if (currentMode() == Inset::TEXT_MODE) { + cur.niceInsert(MathAtom(new InsetMathHull("simple"))); + cur.message(_("create new math text environment ($...$)")); + } else { + handleFont(cur, cmd.argument(), "textrm"); + cur.message(_("entered math text mode (textrm)")); + } +#endif + break; + } + + case LFUN_MATH_SIZE: +#if 0 + cur.recordUndo(); + cur.setSize(arg); +#endif + break; + + case LFUN_MATH_MATRIX: { + cur.recordUndo(); + unsigned int m = 1; + unsigned int n = 1; + docstring v_align; + docstring h_align; + idocstringstream is(cmd.argument()); + is >> m >> n >> v_align >> h_align; + if (m < 1) + m = 1; + if (n < 1) + n = 1; + v_align += 'c'; + cur.niceInsert( + MathAtom(new InsetMathArray(from_ascii("array"), m, n, (char)v_align[0], h_align))); + break; + } + + case LFUN_MATH_DELIM: { + docstring ls; + docstring rs = split(cmd.argument(), ls, ' '); + // Reasonable default values + if (ls.empty()) + ls = '('; + if (rs.empty()) + rs = ')'; + cur.recordUndo(); + cur.handleNest(MathAtom(new InsetMathDelim(ls, rs))); + break; + } + + case LFUN_MATH_BIGDELIM: { + docstring const lname = from_utf8(cmd.getArg(0)); + docstring const ldelim = from_utf8(cmd.getArg(1)); + docstring const rname = from_utf8(cmd.getArg(2)); + docstring const rdelim = from_utf8(cmd.getArg(3)); + latexkeys const * l = in_word_set(lname); + bool const have_l = l && l->inset == "big" && + InsetMathBig::isBigInsetDelim(ldelim); + l = in_word_set(rname); + bool const have_r = l && l->inset == "big" && + InsetMathBig::isBigInsetDelim(rdelim); + // We mimic LFUN_MATH_DELIM in case we have an empty left + // or right delimiter. + if (have_l || have_r) { + cur.recordUndo(); + docstring const selection = grabAndEraseSelection(cur); + selClearOrDel(cur); + if (have_l) + cur.insert(MathAtom(new InsetMathBig(lname, + ldelim))); + cur.niceInsert(selection); + if (have_r) + cur.insert(MathAtom(new InsetMathBig(rname, + rdelim))); + } + // Don't call cur.undispatched() if we did nothing, this would + // lead to infinite recursion via Text::dispatch(). + break; + } + + case LFUN_SPACE_INSERT: + case LFUN_MATH_SPACE: + cur.recordUndo(); + cur.insert(MathAtom(new InsetMathSpace(from_ascii(",")))); + break; + + case LFUN_ERT_INSERT: + // interpret this as if a backslash was typed + cur.recordUndo(); + interpretChar(cur, '\\'); + break; + + case LFUN_MATH_SUBSCRIPT: + // interpret this as if a _ was typed + cur.recordUndo(); + interpretChar(cur, '_'); + break; + + case LFUN_MATH_SUPERSCRIPT: + // interpret this as if a ^ was typed + cur.recordUndo(); + interpretChar(cur, '^'); + break; + + case LFUN_MATH_MACRO_FOLD: + case LFUN_MATH_MACRO_UNFOLD: { + Cursor it = cur; + bool fold = cmd.action == LFUN_MATH_MACRO_FOLD; + bool found = findMacroToFoldUnfold(it, fold); + if (found) { + MathMacro * macro = it.nextInset()->asInsetMath()->asMacro(); + cur.recordUndoInset(); + if (fold) + macro->fold(cur); + else + macro->unfold(cur); + } + break; + } + + case LFUN_QUOTE_INSERT: + // interpret this as if a straight " was typed + cur.recordUndo(); + interpretChar(cur, '\"'); + break; + +// FIXME: We probably should swap parts of "math-insert" and "self-insert" +// handling such that "self-insert" works on "arbitrary stuff" too, and +// math-insert only handles special math things like "matrix". + case LFUN_MATH_INSERT: { + cur.recordUndo(); + if (cmd.argument() == "^" || cmd.argument() == "_") { + interpretChar(cur, cmd.argument()[0]); + } else + cur.niceInsert(cmd.argument()); + break; + } + + case LFUN_DIALOG_SHOW_NEW_INSET: { + docstring const & name = cmd.argument(); + string data; + if (name == "ref") { + InsetMathRef tmp(name); + data = tmp.createDialogStr(to_utf8(name)); + } + cur.bv().showDialog(to_utf8(name), data); + break; + } + + case LFUN_INSET_INSERT: { + MathData ar; + if (createInsetMath_fromDialogStr(cmd.argument(), ar)) { + cur.recordUndo(); + cur.insert(ar); + } else + cur.undispatched(); + break; + } + case LFUN_INSET_DISSOLVE: + if (!asHullInset()) { + cur.recordUndoInset(); + cur.pullArg(); + } + break; + + default: + InsetMath::doDispatch(cur, cmd); + break; + } +} + + +bool InsetMathNest::findMacroToFoldUnfold(Cursor & it, bool fold) const { + // look for macro to open/close, but stay in mathed + for (; !it.empty(); it.pop_back()) { + + // go backward through the current cell + Inset * inset = it.nextInset(); + while (inset && inset->asInsetMath()) { + MathMacro * macro = inset->asInsetMath()->asMacro(); + if (macro) { + // found the an macro to open/close? + if (macro->folded() != fold) + return true; + + // Wrong folding state. + // If this was the first we see in this slice, look further left, + // otherwise go up. + if (inset != it.nextInset()) + break; + } + + // go up if this was the left most position + if (it.pos() == 0) + break; + + // go left + it.pos()--; + inset = it.nextInset(); + } + } + + return false; +} + + +bool InsetMathNest::getStatus(Cursor & cur, FuncRequest const & cmd, + FuncStatus & flag) const +{ + // the font related toggles + //string tc = "mathnormal"; + bool ret = true; + string const arg = to_utf8(cmd.argument()); + switch (cmd.action) { + case LFUN_TABULAR_FEATURE: + flag.enabled(false); + break; +#if 0 + case LFUN_TABULAR_FEATURE: + // FIXME: check temporarily disabled + // valign code + char align = mathcursor::valign(); + if (align == '\0') { + enable = false; + break; + } + if (cmd.argument().empty()) { + flag.clear(); + break; + } + if (!contains("tcb", cmd.argument()[0])) { + enable = false; + break; + } + flag.setOnOff(cmd.argument()[0] == align); + break; +#endif + /// We have to handle them since 1.4 blocks all unhandled actions + case LFUN_FONT_ITAL: + case LFUN_FONT_BOLD: + case LFUN_FONT_SANS: + case LFUN_FONT_EMPH: + case LFUN_FONT_TYPEWRITER: + case LFUN_FONT_NOUN: + case LFUN_FONT_ROMAN: + case LFUN_FONT_DEFAULT: + flag.enabled(true); + break; + case LFUN_MATH_MUTATE: + //flag.setOnOff(mathcursor::formula()->hullType() == to_utf8(cmd.argument())); + flag.setOnOff(false); + break; + + // we just need to be in math mode to enable that + case LFUN_MATH_SIZE: + case LFUN_MATH_SPACE: + case LFUN_MATH_LIMITS: + case LFUN_MATH_EXTERN: + flag.enabled(true); + break; + + case LFUN_FONT_FRAK: + flag.enabled(currentMode() != TEXT_MODE); + break; + + case LFUN_MATH_INSERT: { + bool const textarg = + arg == "\\textbf" || arg == "\\textsf" || + arg == "\\textrm" || arg == "\\textmd" || + arg == "\\textit" || arg == "\\textsc" || + arg == "\\textsl" || arg == "\\textup" || + arg == "\\texttt" || arg == "\\textbb" || + arg == "\\textnormal"; + flag.enabled(currentMode() != TEXT_MODE || textarg); + break; + } + + case LFUN_MATH_MATRIX: + flag.enabled(currentMode() == MATH_MODE); + break; + + case LFUN_INSET_INSERT: { + // Don't test createMathInset_fromDialogStr(), since + // getStatus is not called with a valid reference and the + // dialog would not be applyable. + string const name = cmd.getArg(0); + flag.enabled(name == "ref"); + break; + } + + case LFUN_MATH_DELIM: + case LFUN_MATH_BIGDELIM: + // Don't do this with multi-cell selections + flag.enabled(cur.selBegin().idx() == cur.selEnd().idx()); + break; + + case LFUN_MATH_MACRO_FOLD: + case LFUN_MATH_MACRO_UNFOLD: { + Cursor it = cur; + bool found = findMacroToFoldUnfold(it, cmd.action == LFUN_MATH_MACRO_FOLD); + flag.enabled(found); + break; + } + + case LFUN_SPECIALCHAR_INSERT: + // FIXME: These would probably make sense in math-text mode + flag.enabled(false); + break; + + case LFUN_INSET_DISSOLVE: + flag.enabled(!asHullInset()); + break; + + default: + ret = false; + break; + } + return ret; +} + + +void InsetMathNest::edit(Cursor & cur, bool front, EntryDirection entry_from) +{ + cur.push(*this); + bool enter_front = (entry_from == Inset::ENTRY_DIRECTION_RIGHT || + (entry_from == Inset::ENTRY_DIRECTION_IGNORE && front)); + cur.idx() = enter_front ? 0 : cur.lastidx(); + cur.pos() = enter_front ? 0 : cur.lastpos(); + cur.resetAnchor(); + //lyxerr << "InsetMathNest::edit, cur:\n" << cur << endl; +} + + +Inset * InsetMathNest::editXY(Cursor & cur, int x, int y) +{ + int idx_min = 0; + int dist_min = 1000000; + for (idx_type i = 0, n = nargs(); i != n; ++i) { + int const d = cell(i).dist(cur.bv(), x, y); + if (d < dist_min) { + dist_min = d; + idx_min = i; + } + } + MathData & ar = cell(idx_min); + cur.push(*this); + cur.idx() = idx_min; + cur.pos() = ar.x2pos(&cur.bv(), x - ar.xo(cur.bv())); + + //lyxerr << "found cell : " << idx_min << " pos: " << cur.pos() << endl; + if (dist_min == 0) { + // hit inside cell + for (pos_type i = 0, n = ar.size(); i < n; ++i) + if (ar[i]->covers(cur.bv(), x, y)) + return ar[i].nucleus()->editXY(cur, x, y); + } + return this; +} + + +void InsetMathNest::lfunMousePress(Cursor & cur, FuncRequest & cmd) +{ + //lyxerr << "## lfunMousePress: buttons: " << cmd.button() << endl; + BufferView & bv = cur.bv(); + bool do_selection = cmd.button() == mouse_button::button1 + && cmd.argument() == "region-select"; + bv.mouseSetCursor(cur, do_selection); + if (cmd.button() == mouse_button::button1) { + //lyxerr << "## lfunMousePress: setting cursor to: " << cur << endl; + // Update the cursor update flags as needed: + // + // Update::Decoration: tells to update the decoration + // (visual box corners that define + // the inset)/ + // Update::FitCursor: adjust the screen to the cursor + // position if needed + // cur.result().update(): don't overwrite previously set flags. + cur.updateFlags(Update::Decoration | Update::FitCursor + | cur.result().update()); + } else if (cmd.button() == mouse_button::button2) { + if (cap::selection()) { + // See comment in Text::dispatch why we do this + cap::copySelectionToStack(); + cmd = FuncRequest(LFUN_PASTE, "0"); + doDispatch(bv.cursor(), cmd); + } else { + MathData ar; + asArray(theSelection().get(), ar); + bv.cursor().insert(ar); + } + } +} + + +void InsetMathNest::lfunMouseMotion(Cursor & cur, FuncRequest & cmd) +{ + // only select with button 1 + if (cmd.button() == mouse_button::button1) { + Cursor & bvcur = cur.bv().cursor(); + if (bvcur.anchor_.hasPart(cur)) { + //lyxerr << "## lfunMouseMotion: cursor: " << cur << endl; + bvcur.setCursor(cur); + bvcur.selection() = true; + //lyxerr << "MOTION " << bvcur << endl; + } else + cur.undispatched(); + } +} + + +void InsetMathNest::lfunMouseRelease(Cursor & cur, FuncRequest & cmd) +{ + //lyxerr << "## lfunMouseRelease: buttons: " << cmd.button() << endl; + + if (cmd.button() == mouse_button::button1) { + if (!cur.selection()) + cur.noUpdate(); + else { + Cursor & bvcur = cur.bv().cursor(); + bvcur.selection() = true; + } + return; + } + + cur.undispatched(); +} + + +bool InsetMathNest::interpretChar(Cursor & cur, char_type c) +{ + //lyxerr << "interpret 2: '" << c << "'" << endl; + docstring save_selection; + if (c == '^' || c == '_') + save_selection = grabAndEraseSelection(cur); + + cur.clearTargetX(); + + // handle macroMode + if (cur.inMacroMode()) { + docstring name = cur.macroName(); + + /// are we currently typing '#1' or '#2' or...? + if (name == "\\#") { + cur.backspace(); + int n = c - '0'; + if (n >= 1 && n <= 9) + cur.insert(new MathMacroArgument(n)); + return true; + } + + if (isAlphaASCII(c)) { + cur.activeMacro()->setName(name + docstring(1, c)); + return true; + } + + // handle 'special char' macros + if (name == "\\") { + // remove the '\\' + if (c == '\\') { + cur.backspace(); + if (currentMode() == InsetMath::TEXT_MODE) + cur.niceInsert(createInsetMath("textbackslash")); + else + cur.niceInsert(createInsetMath("backslash")); + } else if (c == '{') { + cur.backspace(); + cur.niceInsert(MathAtom(new InsetMathBrace)); + } else if (c == '%') { + cur.backspace(); + cur.niceInsert(MathAtom(new InsetMathComment)); + } else if (c == '#') { + BOOST_ASSERT(cur.activeMacro()); + cur.activeMacro()->setName(name + docstring(1, c)); + } else { + cur.backspace(); + cur.niceInsert(createInsetMath(docstring(1, c))); + } + return true; + } + + // One character big delimiters. The others are handled in + // interpretString(). + latexkeys const * l = in_word_set(name.substr(1)); + if (name[0] == '\\' && l && l->inset == "big") { + docstring delim; + switch (c) { + case '{': + delim = from_ascii("\\{"); + break; + case '}': + delim = from_ascii("\\}"); + break; + default: + delim = docstring(1, c); + break; + } + if (InsetMathBig::isBigInsetDelim(delim)) { + // name + delim ared a valid InsetMathBig. + // We can't use cur.macroModeClose() because + // it does not handle delim. + InsetMathUnknown * p = cur.activeMacro(); + p->finalize(); + --cur.pos(); + cur.cell().erase(cur.pos()); + cur.plainInsert(MathAtom( + new InsetMathBig(name.substr(1), delim))); + return true; + } + } + + // leave macro mode and try again if necessary + cur.macroModeClose(); + if (c == '{') + cur.niceInsert(MathAtom(new InsetMathBrace)); + else if (c != ' ') + interpretChar(cur, c); + return true; + } + + // This is annoying as one has to press far too often. + // Disable it. + +#if 0 + // leave autocorrect mode if necessary + if (autocorrect() && c == ' ') { + autocorrect() = false; + return true; + } +#endif + + // just clear selection on pressing the space bar + if (cur.selection() && c == ' ') { + cur.selection() = false; + return true; + } + + selClearOrDel(cur); + + if (c == '\\') { + //lyxerr << "starting with macro" << endl; + cur.insert(MathAtom(new InsetMathUnknown(from_ascii("\\"), false))); + return true; + } + + if (c == '\n') { + if (currentMode() == InsetMath::TEXT_MODE) + cur.insert(c); + return true; + } + + if (c == ' ') { + if (currentMode() == InsetMath::TEXT_MODE) { + // insert spaces in text mode, + // but suppress direct insertion of two spaces in a row + // the still allows typing 'a' and deleting the 'a', but + // it is better than nothing... + if (!cur.pos() != 0 || cur.prevAtom()->getChar() != ' ') { + cur.insert(c); + // FIXME: we have to enable full redraw here because of the + // visual box corners that define the inset. If we know for + // sure that we stay within the same cell we can optimize for + // that using: + //cur.updateFlags(Update::SinglePar | Update::FitCursor); + } + return true; + } + if (cur.pos() != 0 && cur.prevAtom()->asSpaceInset()) { + cur.prevAtom().nucleus()->asSpaceInset()->incSpace(); + // FIXME: we have to enable full redraw here because of the + // visual box corners that define the inset. If we know for + // sure that we stay within the same cell we can optimize for + // that using: + //cur.updateFlags(Update::SinglePar | Update::FitCursor); + return true; + } + + if (cur.popForward()) { + // FIXME: we have to enable full redraw here because of the + // visual box corners that define the inset. If we know for + // sure that we stay within the same cell we can optimize for + // that using: + //cur.updateFlags(Update::FitCursor); + return true; + } + + // if we are at the very end, leave the formula + return cur.pos() != cur.lastpos(); + } + + // These shouldn't work in text mode: + if (currentMode() != InsetMath::TEXT_MODE) { + if (c == '_') { + script(cur, false, save_selection); + return true; + } + if (c == '^') { + script(cur, true, save_selection); + return true; + } + if (c == '~') { + cur.niceInsert(createInsetMath("sim")); + return true; + } + } + + if (c == '{' || c == '}' || c == '&' || c == '$' || c == '#' || + c == '%' || c == '_' || c == '^') { + cur.niceInsert(createInsetMath(docstring(1, c))); + return true; + } + + + // try auto-correction + //if (autocorrect() && hasPrevAtom() && math_autocorrect(prevAtom(), c)) + // return true; + + // no special circumstances, so insert the character without any fuss + cur.insert(c); + cur.autocorrect() = true; + return true; +} + + +bool InsetMathNest::interpretString(Cursor & cur, docstring const & str) +{ + // Create a InsetMathBig from cur.cell()[cur.pos() - 1] and t if + // possible + if (!cur.empty() && cur.pos() > 0 && + cur.cell()[cur.pos() - 1]->asUnknownInset()) { + if (InsetMathBig::isBigInsetDelim(str)) { + docstring prev = asString(cur.cell()[cur.pos() - 1]); + if (prev[0] == '\\') { + prev = prev.substr(1); + latexkeys const * l = in_word_set(prev); + if (l && l->inset == "big") { + cur.cell()[cur.pos() - 1] = + MathAtom(new InsetMathBig(prev, str)); + return true; + } + } + } + } + return false; +} + + +bool InsetMathNest::script(Cursor & cur, bool up, + docstring const & save_selection) +{ + // Hack to get \^ and \_ working + //lyxerr << "handling script: up: " << up << endl; + if (cur.inMacroMode() && cur.macroName() == "\\") { + if (up) + cur.niceInsert(createInsetMath("mathcircumflex")); + else + interpretChar(cur, '_'); + return true; + } + + cur.macroModeClose(); + if (asScriptInset() && cur.idx() == 0) { + // we are in a nucleus of a script inset, move to _our_ script + InsetMathScript * inset = asScriptInset(); + //lyxerr << " going to cell " << inset->idxOfScript(up) << endl; + inset->ensure(up); + cur.idx() = inset->idxOfScript(up); + cur.pos() = 0; + } else if (cur.pos() != 0 && cur.prevAtom()->asScriptInset()) { + --cur.pos(); + InsetMathScript * inset = cur.nextAtom().nucleus()->asScriptInset(); + cur.push(*inset); + inset->ensure(up); + cur.idx() = inset->idxOfScript(up); + cur.pos() = cur.lastpos(); + } else { + // convert the thing to our left to a scriptinset or create a new + // one if in the very first position of the array + if (cur.pos() == 0) { + //lyxerr << "new scriptinset" << endl; + cur.insert(new InsetMathScript(up)); + } else { + //lyxerr << "converting prev atom " << endl; + cur.prevAtom() = MathAtom(new InsetMathScript(cur.prevAtom(), up)); + } + --cur.pos(); + InsetMathScript * inset = cur.nextAtom().nucleus()->asScriptInset(); + // See comment in MathParser.cpp for special handling of {}-bases + + cur.push(*inset); + cur.idx() = 1; + cur.pos() = 0; + } + //lyxerr << "inserting selection 1:\n" << save_selection << endl; + cur.niceInsert(save_selection); + cur.resetAnchor(); + //lyxerr << "inserting selection 2:\n" << save_selection << endl; + return true; +} + + +bool InsetMathNest::completionSupported(Cursor const & cur) const +{ + return cur.inMacroMode(); +} + + +bool InsetMathNest::inlineCompletionSupported(Cursor const & cur) const +{ + return cur.inMacroMode(); +} + + +bool InsetMathNest::automaticInlineCompletion() const +{ + return lyxrc.completion_inline_math; +} + + +bool InsetMathNest::automaticPopupCompletion() const +{ + return lyxrc.completion_popup_math; +} + + +Inset::CompletionListPtr InsetMathNest::completionList(Cursor const & cur) const +{ + if (!cur.inMacroMode()) + return CompletionListPtr(); + + return CompletionListPtr(new MathCompletionList(cur)); +} + + +docstring InsetMathNest::completionPrefix(Cursor const & cur) const +{ + if (!cur.inMacroMode()) + return docstring(); + + return cur.activeMacro()->name(); +} + + +bool InsetMathNest::insertCompletion(Cursor & cur, docstring const & s, + bool finished) +{ + if (!cur.inMacroMode()) + return false; + + // append completion to active macro + InsetMathUnknown * inset = cur.activeMacro(); + inset->setName(inset->name() + s); + + // finish macro + if (finished) { +#if 0 + // FIXME: this creates duplicates in the completion popup + // which looks ugly. Moreover the changes the list lengths + // which seems to + confuse the popup as well. + MathCompletionList::addToFavorites(inset->name()); +#endif + lyx::dispatch(FuncRequest(LFUN_SELF_INSERT, " ")); + } + + return true; +} + + +void InsetMathNest::completionPosAndDim(Cursor const & cur, int & x, int & y, + Dimension & dim) const +{ + Inset const * inset = cur.activeMacro(); + if (!inset) + return; + + // get inset dimensions + dim = cur.bv().coordCache().insets().dim(inset); + // FIXME: these 3 are no accurate, but should depend on the font. + // Now the popup jumps down if you enter a char with descent > 0. + dim.des += 3; + dim.asc += 3; + + // and position + Point xy + = cur.bv().coordCache().insets().xy(inset); + x = xy.x_; + y = xy.y_; +} + + +bool InsetMathNest::cursorMathForward(Cursor & cur) +{ + if (cur.pos() != cur.lastpos() && cur.openable(cur.nextAtom())) { + cur.pushBackward(*cur.nextAtom().nucleus()); + cur.inset().idxFirst(cur); + return true; + } + if (cur.posForward() || idxForward(cur) || cur.selection()) + return true; + // try to pop forwards --- but don't pop out of math! leave that to + // the FINISH lfuns + int s = cur.depth() - 2; + if (s >= 0 && cur[s].inset().asInsetMath()) + return cur.popForward(); + return false; +} + + +bool InsetMathNest::cursorMathBackward(Cursor & cur) +{ + if (cur.pos() != 0 && cur.openable(cur.prevAtom())) { + cur.posBackward(); + cur.push(*cur.nextAtom().nucleus()); + cur.inset().idxLast(cur); + return true; + } + if (cur.posBackward() || idxBackward(cur) || cur.selection()) + return true; + // try to pop backwards --- but don't pop out of math! leave that to + // the FINISH lfuns + int s = cur.depth() - 2; + if (s >= 0 && cur[s].inset().asInsetMath()) + return cur.popBackward(); + return false; +} + + +//////////////////////////////////////////////////////////////////// + +MathCompletionList::MathCompletionList(Cursor const & cur) +{ + // fill it with macros from the buffer + Buffer::MacroNameSet macros; + cur.buffer().listMacroNames(macros); + Buffer::MacroNameSet::const_iterator it; + for (it = macros.begin(); it != macros.end(); ++it) { + if (cur.buffer().getMacro(*it, cur, false)) + locals.push_back("\\" + *it); + } + sort(locals.begin(), locals.end()); + + if (globals.size() > 0) + return; + + // fill in global macros + macros.clear(); + MacroTable::globalMacros().getMacroNames(macros); + lyxerr << "Globals completion macros: "; + for (it = macros.begin(); it != macros.end(); ++it) { + lyxerr << "\\" + *it << " "; + globals.push_back("\\" + *it); + } + lyxerr << std::endl; + + // fill in global commands + globals.push_back(from_ascii("\\boxed")); + globals.push_back(from_ascii("\\fbox")); + globals.push_back(from_ascii("\\framebox")); + globals.push_back(from_ascii("\\makebox")); + globals.push_back(from_ascii("\\kern")); + globals.push_back(from_ascii("\\xrightarrow")); + globals.push_back(from_ascii("\\xleftarrow")); + globals.push_back(from_ascii("\\split")); + globals.push_back(from_ascii("\\gathered")); + globals.push_back(from_ascii("\\aligned")); + globals.push_back(from_ascii("\\alignedat")); + globals.push_back(from_ascii("\\cases")); + globals.push_back(from_ascii("\\substack")); + globals.push_back(from_ascii("\\subarray")); + globals.push_back(from_ascii("\\array")); + globals.push_back(from_ascii("\\sqrt")); + globals.push_back(from_ascii("\\root")); + globals.push_back(from_ascii("\\tabular")); + globals.push_back(from_ascii("\\stackrel")); + globals.push_back(from_ascii("\\binom")); + globals.push_back(from_ascii("\\choose")); + globals.push_back(from_ascii("\\choose")); + globals.push_back(from_ascii("\\frac")); + globals.push_back(from_ascii("\\over")); + globals.push_back(from_ascii("\\nicefrac")); + globals.push_back(from_ascii("\\unitfrac")); + globals.push_back(from_ascii("\\unitfracthree")); + globals.push_back(from_ascii("\\unitone")); + globals.push_back(from_ascii("\\unittwo")); + globals.push_back(from_ascii("\\infer")); + globals.push_back(from_ascii("\\atop")); + globals.push_back(from_ascii("\\lefteqn")); + globals.push_back(from_ascii("\\boldsymbol")); + globals.push_back(from_ascii("\\color")); + globals.push_back(from_ascii("\\normalcolor")); + globals.push_back(from_ascii("\\textcolor")); + globals.push_back(from_ascii("\\dfrac")); + globals.push_back(from_ascii("\\tfrac")); + globals.push_back(from_ascii("\\dbinom")); + globals.push_back(from_ascii("\\tbinom")); + globals.push_back(from_ascii("\\hphantom")); + globals.push_back(from_ascii("\\phantom")); + globals.push_back(from_ascii("\\vphantom")); + WordList const & words = mathedWordList(); + WordList::const_iterator it2; + lyxerr << "Globals completion commands: "; + for (it2 = words.begin(); it2 != words.end(); ++it2) { + globals.push_back("\\" + (*it2).first); + lyxerr << "\\" + (*it2).first << " "; + } + lyxerr << std::endl; + sort(globals.begin(), globals.end()); +} + + +MathCompletionList::~MathCompletionList() +{ +} + + +size_type MathCompletionList::size() const +{ + return favorites.size() + locals.size() + globals.size(); +} + + +docstring MathCompletionList::data(size_t idx) const +{ + size_t fsize = favorites.size(); + size_t lsize = locals.size(); + if (idx >= fsize + lsize) + return globals[idx - lsize - fsize]; + else if (idx >= fsize) + return locals[idx - fsize]; + else + return favorites[idx]; +} + + +std::string MathCompletionList::icon(size_t idx) const +{ + // get the latex command + docstring cmd; + size_t fsize = favorites.size(); + size_t lsize = locals.size(); + if (idx >= fsize + lsize) + cmd = globals[idx - lsize - fsize]; + else if (idx >= fsize) + cmd = locals[idx - fsize]; + else + cmd = favorites[idx]; + + // get the icon resource name by stripping the backslash + return "images/math/" + to_utf8(cmd.substr(1)) + ".png"; +} + + + +void MathCompletionList::addToFavorites(docstring const & completion) +{ + // remove old occurrence + std::deque::iterator it; + for (it = favorites.begin(); it != favorites.end(); ++it) { + if (*it == completion) { + favorites.erase(it); + break; + } + } + + // put it to the front + favorites.push_front(completion); + favorites.resize(min(int(favorites.size()), 10)); +} + + +std::vector MathCompletionList::globals; +std::deque MathCompletionList::favorites; + +} // namespace lyx