2 * \file InsetMathNest.C
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * Full author contact details are available in file CREDITS.
13 #include "InsetMathNest.h"
15 #include "InsetMathArray.h"
16 #include "InsetMathBig.h"
17 #include "InsetMathBox.h"
18 #include "InsetMathBrace.h"
19 #include "InsetMathColor.h"
20 #include "InsetMathComment.h"
22 #include "InsetMathDelim.h"
23 #include "MathFactory.h"
24 #include "InsetMathHull.h"
25 #include "MathStream.h"
26 #include "MathMacroArgument.h"
27 //#include "InsetMathMBox.h"
28 #include "MathParser.h"
29 #include "InsetMathScript.h"
30 #include "InsetMathSpace.h"
31 #include "InsetMathSymbol.h"
32 #include "MathSupport.h"
33 #include "InsetMathUnknown.h"
34 #include "InsetMathRef.h"
36 #include "BufferView.h"
37 #include "CutAndPaste.h"
38 #include "FuncStatus.h"
40 #include "bufferview_funcs.h"
41 #include "coordcache.h"
44 #include "dispatchresult.h"
45 #include "funcrequest.h"
48 #include "outputparams.h"
51 #include "support/lstrings.h"
53 #include "frontends/Clipboard.h"
54 #include "frontends/Painter.h"
55 #include "frontends/Selection.h"
57 #include "funcrequest.h"
58 #include "lyxserver.h"
59 #include "lyxsocket.h"
66 using cap::copySelection;
67 using cap::grabAndEraseSelection;
68 using cap::cutSelection;
69 using cap::replaceSelection;
70 using cap::selClearOrDel;
74 using std::istringstream;
77 InsetMathNest::InsetMathNest(idx_type nargs)
78 : cells_(nargs), lock_(false)
82 InsetMath::idx_type InsetMathNest::nargs() const
88 MathArray & InsetMathNest::cell(idx_type i)
94 MathArray const & InsetMathNest::cell(idx_type i) const
100 void InsetMathNest::cursorPos(BufferView const & bv,
101 CursorSlice const & sl, bool /*boundary*/,
102 int & x, int & y) const
104 // FIXME: This is a hack. Ideally, the coord cache should not store
105 // absolute positions, but relative ones. This would mean to call
106 // setXY() not in MathArray::draw(), but in the parent insets' draw()
107 // with the correctly adjusted x,y values. But this means that we'd have
108 // to touch all (math)inset's draw() methods. Right now, we'll store
109 // absolute value, and make them here relative, only to make them
110 // absolute again when actually drawing the cursor. What a mess.
111 BOOST_ASSERT(ptr_cmp(&sl.inset(), this));
112 MathArray const & ar = sl.cell();
113 CoordCache const & coord_cache = bv.coordCache();
114 if (!coord_cache.getArrays().has(&ar)) {
115 // this can (semi-)legally happen if we just created this cell
116 // and it never has been drawn before. So don't ASSERT.
117 //lyxerr << "no cached data for array " << &ar << endl;
122 Point const pt = coord_cache.getArrays().xy(&ar);
123 if (!coord_cache.getInsets().has(this)) {
125 //lyxerr << "no cached data for inset " << this << endl;
130 Point const pt2 = coord_cache.getInsets().xy(this);
131 //lyxerr << "retrieving position cache for MathArray "
132 // << pt.x_ << ' ' << pt.y_ << std::endl;
133 x = pt.x_ - pt2.x_ + ar.pos2x(sl.pos());
135 // lyxerr << "pt.y_ : " << pt.y_ << " pt2_.y_ : " << pt2.y_
136 // << " asc: " << ascent() << " des: " << descent()
137 // << " ar.asc: " << ar.ascent() << " ar.des: " << ar.descent() << endl;
138 // move cursor visually into empty cells ("blue rectangles");
144 void InsetMathNest::metrics(MetricsInfo const & mi) const
147 for (idx_type i = 0, n = nargs(); i != n; ++i)
152 bool InsetMathNest::idxNext(LCursor & cur) const
154 BOOST_ASSERT(ptr_cmp(&cur.inset(), this));
155 if (cur.idx() == cur.lastidx())
163 bool InsetMathNest::idxRight(LCursor & cur) const
169 bool InsetMathNest::idxPrev(LCursor & cur) const
171 BOOST_ASSERT(ptr_cmp(&cur.inset(), this));
175 cur.pos() = cur.lastpos();
180 bool InsetMathNest::idxLeft(LCursor & cur) const
186 bool InsetMathNest::idxFirst(LCursor & cur) const
188 BOOST_ASSERT(ptr_cmp(&cur.inset(), this));
197 bool InsetMathNest::idxLast(LCursor & cur) const
199 BOOST_ASSERT(ptr_cmp(&cur.inset(), this));
202 cur.idx() = cur.lastidx();
203 cur.pos() = cur.lastpos();
208 void InsetMathNest::dump() const
210 odocstringstream oss;
212 os << "---------------------------------------------\n";
215 for (idx_type i = 0, n = nargs(); i != n; ++i)
216 os << cell(i) << "\n";
217 os << "---------------------------------------------\n";
218 lyxerr << to_utf8(oss.str());
222 void InsetMathNest::draw(PainterInfo & pi, int x, int y) const
226 pi.pain.fillRectangle(x, y - ascent(), width(), height(),
229 setPosCache(pi, x, y);
233 void InsetMathNest::drawSelection(PainterInfo & pi, int x, int y) const
235 BufferView & bv = *pi.base.bv;
236 // this should use the x/y values given, not the cached values
237 LCursor & cur = bv.cursor();
238 if (!cur.selection())
240 if (!ptr_cmp(&cur.inset(), this))
243 // FIXME: hack to get position cache warm
244 pi.pain.setDrawingEnabled(false);
246 pi.pain.setDrawingEnabled(true);
248 CursorSlice s1 = cur.selBegin();
249 CursorSlice s2 = cur.selEnd();
251 //lyxerr << "InsetMathNest::drawing selection: "
252 // << " s1: " << s1 << " s2: " << s2 << endl;
253 if (s1.idx() == s2.idx()) {
254 MathArray const & c = cell(s1.idx());
255 int x1 = c.xo(bv) + c.pos2x(s1.pos());
256 int y1 = c.yo(bv) - c.ascent();
257 int x2 = c.xo(bv) + c.pos2x(s2.pos());
258 int y2 = c.yo(bv) + c.descent();
259 pi.pain.fillRectangle(x1, y1, x2 - x1, y2 - y1, LColor::selection);
260 //lyxerr << "InsetMathNest::drawing selection 3: "
261 // << " x1: " << x1 << " x2: " << x2
262 // << " y1: " << y1 << " y2: " << y2 << endl;
264 for (idx_type i = 0; i < nargs(); ++i) {
265 if (idxBetween(i, s1.idx(), s2.idx())) {
266 MathArray const & c = cell(i);
268 int y1 = c.yo(bv) - c.ascent();
269 int x2 = c.xo(bv) + c.width();
270 int y2 = c.yo(bv) + c.descent();
271 pi.pain.fillRectangle(x1, y1, x2 - x1, y2 - y1, LColor::selection);
278 void InsetMathNest::validate(LaTeXFeatures & features) const
280 for (idx_type i = 0; i < nargs(); ++i)
281 cell(i).validate(features);
285 void InsetMathNest::replace(ReplaceData & rep)
287 for (idx_type i = 0; i < nargs(); ++i)
288 cell(i).replace(rep);
292 bool InsetMathNest::contains(MathArray const & ar) const
294 for (idx_type i = 0; i < nargs(); ++i)
295 if (cell(i).contains(ar))
301 bool InsetMathNest::lock() const
307 void InsetMathNest::lock(bool l)
313 bool InsetMathNest::isActive() const
319 MathArray InsetMathNest::glue() const
322 for (size_t i = 0; i < nargs(); ++i)
328 void InsetMathNest::write(WriteStream & os) const
330 os << '\\' << name().c_str();
331 for (size_t i = 0; i < nargs(); ++i)
332 os << '{' << cell(i) << '}';
334 os.pendingSpace(true);
335 if (lock_ && !os.latex()) {
337 os.pendingSpace(true);
342 void InsetMathNest::normalize(NormalStream & os) const
344 os << '[' << name().c_str();
345 for (size_t i = 0; i < nargs(); ++i)
346 os << ' ' << cell(i);
351 int InsetMathNest::latex(Buffer const &, odocstream & os,
352 OutputParams const & runparams) const
354 WriteStream wi(os, runparams.moving_arg, true);
360 bool InsetMathNest::notifyCursorLeaves(LCursor & /*cur*/)
366 MathArray & ar = cur.cell();
367 // remove base-only "scripts"
368 for (pos_type i = 0; i + 1 < ar.size(); ++i) {
369 InsetMathScript * p = operator[](i).nucleus()->asScriptInset();
370 if (p && p->nargs() == 1) {
371 MathArray ar = p->nuc();
374 cur.adjust(i, ar.size() - 1);
378 // glue adjacent font insets of the same kind
379 for (pos_type i = 0; i + 1 < size(); ++i) {
380 InsetMathFont * p = operator[](i).nucleus()->asFontInset();
381 InsetMathFont const * q = operator[](i + 1)->asFontInset();
382 if (p && q && p->name() == q->name()) {
383 p->cell(0).append(q->cell(0));
393 void InsetMathNest::handleFont
394 (LCursor & cur, docstring const & arg, char const * const font)
396 handleFont(cur, arg, from_ascii(font));
400 void InsetMathNest::handleFont
401 (LCursor & cur, docstring const & arg, docstring const & font)
403 // this whole function is a hack and won't work for incremental font
406 if (cur.inset().asInsetMath()->name() == font) {
407 recordUndoInset(cur, Undo::ATOMIC);
408 cur.handleFont(to_utf8(font));
410 recordUndo(cur, Undo::ATOMIC);
411 cur.handleNest(createInsetMath(font));
417 void InsetMathNest::handleFont2(LCursor & cur, docstring const & arg)
419 recordUndo(cur, Undo::ATOMIC);
422 bv_funcs::string2font(to_utf8(arg), font, b);
423 if (font.color() != LColor::inherit) {
424 MathAtom at = MathAtom(new InsetMathColor(true, font.color()));
425 cur.handleNest(at, 0);
430 void InsetMathNest::doDispatch(LCursor & cur, FuncRequest & cmd)
432 //lyxerr << "InsetMathNest: request: " << cmd << std::endl;
433 //CursorSlice sl = cur.current();
435 switch (cmd.action) {
439 cur.message(_("Paste"));
440 replaceSelection(cur);
442 if (cmd.argument().empty() && !theClipboard().isInternal())
443 topaste = theClipboard().getAsText();
446 idocstringstream is(cmd.argument());
448 topaste = cap::getSelection(cur.buffer(), n);
450 cur.niceInsert(topaste);
451 cur.clearSelection(); // bug 393
452 cur.bv().switchKeyMap();
459 cutSelection(cur, true, true);
460 cur.message(_("Cut"));
461 // Prevent stale position >= size crash
462 // Probably not necessary anymore, see eraseSelection (gb 2005-10-09)
468 cur.message(_("Copy"));
471 case LFUN_MOUSE_PRESS:
472 lfunMousePress(cur, cmd);
475 case LFUN_MOUSE_MOTION:
476 lfunMouseMotion(cur, cmd);
479 case LFUN_MOUSE_RELEASE:
480 lfunMouseRelease(cur, cmd);
483 case LFUN_FINISHED_LEFT:
484 cur.bv().cursor() = cur;
487 case LFUN_FINISHED_RIGHT:
489 cur.bv().cursor() = cur;
492 case LFUN_FINISHED_UP:
493 cur.bv().cursor() = cur;
496 case LFUN_FINISHED_DOWN:
498 cur.bv().cursor() = cur;
501 case LFUN_CHAR_FORWARD:
502 cur.updateFlags(Update::Decoration | Update::FitCursor);
503 case LFUN_CHAR_FORWARD_SELECT:
504 cur.selHandle(cmd.action == LFUN_CHAR_FORWARD_SELECT);
505 cur.autocorrect() = false;
507 cur.macroModeClose();
508 if (cur.pos() != cur.lastpos() && cur.openable(cur.nextAtom())) {
509 cur.pushLeft(*cur.nextAtom().nucleus());
510 cur.inset().idxFirst(cur);
511 } else if (cur.posRight() || idxRight(cur)
512 || cur.popRight() || cur.selection())
515 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
520 case LFUN_CHAR_BACKWARD:
521 cur.updateFlags(Update::Decoration | Update::FitCursor);
522 case LFUN_CHAR_BACKWARD_SELECT:
523 cur.selHandle(cmd.action == LFUN_CHAR_BACKWARD_SELECT);
524 cur.autocorrect() = false;
526 cur.macroModeClose();
527 if (cur.pos() != 0 && cur.openable(cur.prevAtom())) {
529 cur.push(*cur.nextAtom().nucleus());
530 cur.inset().idxLast(cur);
531 } else if (cur.posLeft() || idxLeft(cur)
532 || cur.popLeft() || cur.selection())
535 cmd = FuncRequest(LFUN_FINISHED_LEFT);
541 cur.updateFlags(Update::Decoration | Update::FitCursor);
543 // FIXME Tried to use clearTargetX and macroModeClose, crashed on cur.up()
544 if (cur.inMacroMode()) {
546 cur.macroModeClose();
549 cur.selHandle(cmd.action == LFUN_UP_SELECT);
551 cmd = FuncRequest(LFUN_FINISHED_UP);
554 // fixes bug 1598. Please check!
559 cur.updateFlags(Update::Decoration | Update::FitCursor);
560 case LFUN_DOWN_SELECT:
561 if (cur.inMacroMode()) {
562 cur.macroModeClose();
565 cur.selHandle(cmd.action == LFUN_DOWN_SELECT);
567 cmd = FuncRequest(LFUN_FINISHED_DOWN);
570 // fixes bug 1598. Please check!
574 case LFUN_MOUSE_DOUBLE:
575 case LFUN_MOUSE_TRIPLE:
576 case LFUN_WORD_SELECT:
580 cur.selection() = true;
581 cur.pos() = cur.lastpos();
582 cur.idx() = cur.lastidx();
583 cap::saveSelection(cur);
586 case LFUN_PARAGRAPH_UP:
587 case LFUN_PARAGRAPH_DOWN:
588 cur.updateFlags(Update::Decoration | Update::FitCursor);
589 case LFUN_PARAGRAPH_UP_SELECT:
590 case LFUN_PARAGRAPH_DOWN_SELECT:
593 case LFUN_LINE_BEGIN:
594 case LFUN_WORD_BACKWARD:
595 cur.updateFlags(Update::Decoration | Update::FitCursor);
596 case LFUN_LINE_BEGIN_SELECT:
597 case LFUN_WORD_BACKWARD_SELECT:
598 cur.selHandle(cmd.action == LFUN_WORD_BACKWARD_SELECT ||
599 cmd.action == LFUN_LINE_BEGIN_SELECT);
600 cur.macroModeClose();
601 if (cur.pos() != 0) {
603 } else if (cur.col() != 0) {
604 cur.idx() -= cur.col();
606 } else if (cur.idx() != 0) {
610 cmd = FuncRequest(LFUN_FINISHED_LEFT);
615 case LFUN_WORD_FORWARD:
617 cur.updateFlags(Update::Decoration | Update::FitCursor);
618 case LFUN_WORD_FORWARD_SELECT:
619 case LFUN_LINE_END_SELECT:
620 cur.selHandle(cmd.action == LFUN_WORD_FORWARD_SELECT ||
621 cmd.action == LFUN_LINE_END_SELECT);
622 cur.macroModeClose();
624 if (cur.pos() != cur.lastpos()) {
625 cur.pos() = cur.lastpos();
626 } else if (ncols() && (cur.col() != cur.lastcol())) {
627 cur.idx() = cur.idx() - cur.col() + cur.lastcol();
628 cur.pos() = cur.lastpos();
629 } else if (cur.idx() != cur.lastidx()) {
630 cur.idx() = cur.lastidx();
631 cur.pos() = cur.lastpos();
633 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
638 case LFUN_SCREEN_UP_SELECT:
640 cmd = FuncRequest(LFUN_FINISHED_LEFT);
644 case LFUN_SCREEN_DOWN_SELECT:
645 case LFUN_SCREEN_DOWN:
646 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
650 case LFUN_CELL_FORWARD:
651 cur.updateFlags(Update::Decoration | Update::FitCursor);
652 cur.inset().idxNext(cur);
655 case LFUN_CELL_BACKWARD:
656 cur.updateFlags(Update::Decoration | Update::FitCursor);
657 cur.inset().idxPrev(cur);
660 case LFUN_WORD_DELETE_BACKWARD:
661 case LFUN_CHAR_DELETE_BACKWARD:
663 // May affect external cell:
664 recordUndoInset(cur, Undo::ATOMIC);
666 recordUndo(cur, Undo::ATOMIC);
667 // if the inset can not be removed from within, delete it
668 if (!cur.backspace()) {
669 FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD);
670 cur.innerText()->dispatch(cur, cmd);
674 case LFUN_WORD_DELETE_FORWARD:
675 case LFUN_CHAR_DELETE_FORWARD:
676 if (cur.pos() == cur.lastpos())
677 // May affect external cell:
678 recordUndoInset(cur, Undo::ATOMIC);
680 recordUndo(cur, Undo::ATOMIC);
681 // if the inset can not be removed from within, delete it
683 FuncRequest cmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD);
684 cur.innerText()->dispatch(cur, cmd);
690 cur.clearSelection();
692 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
697 case LFUN_INSET_TOGGLE:
703 case LFUN_SELF_INSERT:
704 if (cmd.argument().size() != 1) {
706 docstring const arg = cmd.argument();
707 if (!interpretString(cur, arg))
711 // Don't record undo steps if we are in macro mode and
712 // cmd.argument is the next character of the macro name.
713 // Otherwise we'll get an invalid cursor if we undo after
714 // the macro was finished and the macro is a known command,
715 // e.g. sqrt. LCursor::macroModeClose replaces in this case
716 // the InsetMathUnknown with name "frac" by an empty
717 // InsetMathFrac -> a pos value > 0 is invalid.
718 // A side effect is that an undo before the macro is finished
719 // undoes the complete macro, not only the last character.
720 if (!cur.inMacroMode())
723 // spacial handling of space. If we insert an inset
724 // via macro mode, we want to put the cursor inside it
725 // if relevant. Think typing "\frac<space>".
726 if (cmd.argument()[0] == ' '
727 && cur.inMacroMode() && cur.macroName() != "\\"
728 && cur.macroModeClose()) {
729 MathAtom const atom = cur.prevAtom();
730 if (atom->asNestInset() && atom->nargs() > 0) {
732 cur.pushLeft(*cur.nextInset());
734 } else if (!interpretChar(cur, cmd.argument()[0])) {
735 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
740 //case LFUN_SERVER_GET_XY:
741 // sprintf(dispatch_buffer, "%d %d",);
744 case LFUN_SERVER_SET_XY: {
745 lyxerr << "LFUN_SERVER_SET_XY broken!" << endl;
748 istringstream is(to_utf8(cmd.argument()));
750 cur.setScreenPos(x, y);
754 // Special casing for superscript in case of LyX handling
756 case LFUN_ACCENT_CIRCUMFLEX:
757 if (cmd.argument().empty()) {
758 // do superscript if LyX handles
760 recordUndo(cur, Undo::ATOMIC);
761 script(cur, true, grabAndEraseSelection(cur));
765 case LFUN_ACCENT_UMLAUT:
766 case LFUN_ACCENT_ACUTE:
767 case LFUN_ACCENT_GRAVE:
768 case LFUN_ACCENT_BREVE:
769 case LFUN_ACCENT_DOT:
770 case LFUN_ACCENT_MACRON:
771 case LFUN_ACCENT_CARON:
772 case LFUN_ACCENT_TILDE:
773 case LFUN_ACCENT_CEDILLA:
774 case LFUN_ACCENT_CIRCLE:
775 case LFUN_ACCENT_UNDERDOT:
776 case LFUN_ACCENT_TIE:
777 case LFUN_ACCENT_OGONEK:
778 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
782 case LFUN_FONT_FREE_APPLY:
783 case LFUN_FONT_FREE_UPDATE:
784 handleFont2(cur, cmd.argument());
788 if (currentMode() == TEXT_MODE)
789 handleFont(cur, cmd.argument(), "textbf");
791 handleFont(cur, cmd.argument(), "mathbf");
794 if (currentMode() == TEXT_MODE)
795 handleFont(cur, cmd.argument(), "textsf");
797 handleFont(cur, cmd.argument(), "mathsf");
800 if (currentMode() == TEXT_MODE)
801 handleFont(cur, cmd.argument(), "emph");
803 handleFont(cur, cmd.argument(), "mathcal");
805 case LFUN_FONT_ROMAN:
806 if (currentMode() == TEXT_MODE)
807 handleFont(cur, cmd.argument(), "textrm");
809 handleFont(cur, cmd.argument(), "mathrm");
812 if (currentMode() == TEXT_MODE)
813 handleFont(cur, cmd.argument(), "texttt");
815 handleFont(cur, cmd.argument(), "mathtt");
818 handleFont(cur, cmd.argument(), "mathfrak");
821 if (currentMode() == TEXT_MODE)
822 handleFont(cur, cmd.argument(), "textit");
824 handleFont(cur, cmd.argument(), "mathit");
827 if (currentMode() == TEXT_MODE)
828 // FIXME: should be "noun"
829 handleFont(cur, cmd.argument(), "textsc");
831 handleFont(cur, cmd.argument(), "mathbb");
834 case LFUN_FONT_FREE_APPLY:
835 handleFont(cur, cmd.argument(), "textrm");
838 case LFUN_FONT_DEFAULT:
839 handleFont(cur, cmd.argument(), "textnormal");
842 case LFUN_MATH_MODE: {
844 // ignore math-mode on when already in math mode
845 if (currentMode() == InsetBase::MATH_MODE && cmd.argument() == "on")
847 cur.macroModeClose();
848 docstring const save_selection = grabAndEraseSelection(cur);
850 //cur.plainInsert(MathAtom(new InsetMathMBox(cur.bv())));
851 cur.plainInsert(MathAtom(new InsetMathBox(from_ascii("mbox"))));
853 cur.pushLeft(*cur.nextInset());
854 cur.niceInsert(save_selection);
856 if (currentMode() == InsetBase::TEXT_MODE) {
857 cur.niceInsert(MathAtom(new InsetMathHull("simple")));
858 cur.message(_("create new math text environment ($...$)"));
860 handleFont(cur, cmd.argument(), "textrm");
861 cur.message(_("entered math text mode (textrm)"));
874 case LFUN_MATH_MATRIX: {
875 recordUndo(cur, Undo::ATOMIC);
880 idocstringstream is(cmd.argument());
881 is >> m >> n >> v_align >> h_align;
888 MathAtom(new InsetMathArray(from_ascii("array"), m, n, (char)v_align[0], h_align)));
892 case LFUN_MATH_DELIM: {
894 docstring rs = support::split(cmd.argument(), ls, ' ');
895 // Reasonable default values
900 recordUndo(cur, Undo::ATOMIC);
901 cur.handleNest(MathAtom(new InsetMathDelim(ls, rs)));
905 case LFUN_MATH_BIGDELIM: {
906 docstring const lname = from_utf8(cmd.getArg(0));
907 docstring const ldelim = from_utf8(cmd.getArg(1));
908 docstring const rname = from_utf8(cmd.getArg(2));
909 docstring const rdelim = from_utf8(cmd.getArg(3));
910 latexkeys const * l = in_word_set(lname);
911 bool const have_l = l && l->inset == "big" &&
912 InsetMathBig::isBigInsetDelim(ldelim);
913 l = in_word_set(rname);
914 bool const have_r = l && l->inset == "big" &&
915 InsetMathBig::isBigInsetDelim(rdelim);
916 // We mimic LFUN_MATH_DELIM in case we have an empty left
917 // or right delimiter.
918 if (have_l || have_r) {
919 recordUndo(cur, Undo::ATOMIC);
920 docstring const selection = grabAndEraseSelection(cur);
923 cur.insert(MathAtom(new InsetMathBig(lname,
925 cur.niceInsert(selection);
927 cur.insert(MathAtom(new InsetMathBig(rname,
930 // Don't call cur.undispatched() if we did nothing, this would
931 // lead to infinite recursion via LyXText::dispatch().
935 case LFUN_SPACE_INSERT:
936 case LFUN_MATH_SPACE:
937 recordUndo(cur, Undo::ATOMIC);
938 cur.insert(MathAtom(new InsetMathSpace(from_ascii(","))));
941 case LFUN_ERT_INSERT:
942 // interpret this as if a backslash was typed
943 recordUndo(cur, Undo::ATOMIC);
944 interpretChar(cur, '\\');
947 case LFUN_MATH_SUBSCRIPT:
948 // interpret this as if a _ was typed
949 recordUndo(cur, Undo::ATOMIC);
950 interpretChar(cur, '_');
953 case LFUN_MATH_SUPERSCRIPT:
954 // interpret this as if a ^ was typed
955 recordUndo(cur, Undo::ATOMIC);
956 interpretChar(cur, '^');
959 case LFUN_QUOTE_INSERT:
960 // interpret this as if a straight " was typed
961 recordUndo(cur, Undo::ATOMIC);
962 interpretChar(cur, '\"');
965 // FIXME: We probably should swap parts of "math-insert" and "self-insert"
966 // handling such that "self-insert" works on "arbitrary stuff" too, and
967 // math-insert only handles special math things like "matrix".
968 case LFUN_MATH_INSERT: {
969 recordUndo(cur, Undo::ATOMIC);
970 if (cmd.argument() == "^" || cmd.argument() == "_") {
971 interpretChar(cur, cmd.argument()[0]);
973 cur.niceInsert(cmd.argument());
977 case LFUN_DIALOG_SHOW_NEW_INSET: {
978 docstring const & name = cmd.argument();
982 data = tmp.createDialogStr(to_utf8(name));
984 cur.bv().showInsetDialog(to_utf8(name), data, 0);
988 case LFUN_INSET_INSERT: {
990 if (createInsetMath_fromDialogStr(cmd.argument(), ar)) {
999 InsetMathDim::doDispatch(cur, cmd);
1005 bool InsetMathNest::getStatus(LCursor & cur, FuncRequest const & cmd,
1006 FuncStatus & flag) const
1008 // the font related toggles
1009 //string tc = "mathnormal";
1011 string const arg = to_utf8(cmd.argument());
1012 switch (cmd.action) {
1013 case LFUN_TABULAR_FEATURE:
1014 flag.enabled(false);
1017 case LFUN_TABULAR_FEATURE:
1018 // FIXME: check temporarily disabled
1020 char align = mathcursor::valign();
1021 if (align == '\0') {
1025 if (cmd.argument().empty()) {
1029 if (!contains("tcb", cmd.argument()[0])) {
1033 flag.setOnOff(cmd.argument()[0] == align);
1036 /// We have to handle them since 1.4 blocks all unhandled actions
1037 case LFUN_FONT_ITAL:
1038 case LFUN_FONT_BOLD:
1039 case LFUN_FONT_SANS:
1040 case LFUN_FONT_EMPH:
1041 case LFUN_FONT_CODE:
1042 case LFUN_FONT_NOUN:
1043 case LFUN_FONT_ROMAN:
1044 case LFUN_FONT_DEFAULT:
1047 case LFUN_MATH_MUTATE:
1048 //flag.setOnOff(mathcursor::formula()->hullType() == to_utf8(cmd.argument()));
1049 flag.setOnOff(false);
1052 // we just need to be in math mode to enable that
1053 case LFUN_MATH_SIZE:
1054 case LFUN_MATH_SPACE:
1055 case LFUN_MATH_LIMITS:
1056 case LFUN_MATH_NONUMBER:
1057 case LFUN_MATH_NUMBER:
1058 case LFUN_MATH_EXTERN:
1062 case LFUN_FONT_FRAK:
1063 flag.enabled(currentMode() != TEXT_MODE);
1066 case LFUN_MATH_INSERT: {
1067 bool const textarg =
1068 arg == "\\textbf" || arg == "\\textsf" ||
1069 arg == "\\textrm" || arg == "\\textmd" ||
1070 arg == "\\textit" || arg == "\\textsc" ||
1071 arg == "\\textsl" || arg == "\\textup" ||
1072 arg == "\\texttt" || arg == "\\textbb" ||
1073 arg == "\\textnormal";
1074 flag.enabled(currentMode() != TEXT_MODE || textarg);
1078 case LFUN_MATH_MATRIX:
1079 flag.enabled(currentMode() == MATH_MODE);
1082 case LFUN_INSET_INSERT: {
1083 // Don't test createMathInset_fromDialogStr(), since
1084 // getStatus is not called with a valid reference and the
1085 // dialog would not be applyable.
1086 string const name = cmd.getArg(0);
1087 flag.enabled(name == "ref");
1091 case LFUN_MATH_DELIM:
1092 case LFUN_MATH_BIGDELIM:
1093 // Don't do this with multi-cell selections
1094 flag.enabled(cur.selBegin().idx() == cur.selEnd().idx());
1105 void InsetMathNest::edit(LCursor & cur, bool left)
1108 cur.idx() = left ? 0 : cur.lastidx();
1109 cur.pos() = left ? 0 : cur.lastpos();
1111 //lyxerr << "InsetMathNest::edit, cur:\n" << cur << endl;
1115 InsetBase * InsetMathNest::editXY(LCursor & cur, int x, int y)
1118 int dist_min = 1000000;
1119 for (idx_type i = 0, n = nargs(); i != n; ++i) {
1120 int const d = cell(i).dist(cur.bv(), x, y);
1126 MathArray & ar = cell(idx_min);
1128 cur.idx() = idx_min;
1129 cur.pos() = ar.x2pos(x - ar.xo(cur.bv()));
1131 //lyxerr << "found cell : " << idx_min << " pos: " << cur.pos() << endl;
1132 if (dist_min == 0) {
1134 for (pos_type i = 0, n = ar.size(); i < n; ++i)
1135 if (ar[i]->covers(cur.bv(), x, y))
1136 return ar[i].nucleus()->editXY(cur, x, y);
1142 void InsetMathNest::lfunMousePress(LCursor & cur, FuncRequest & cmd)
1144 //lyxerr << "## lfunMousePress: buttons: " << cmd.button() << endl;
1145 BufferView & bv = cur.bv();
1146 if (cmd.button() == mouse_button::button1) {
1147 //lyxerr << "## lfunMousePress: setting cursor to: " << cur << endl;
1148 bv.mouseSetCursor(cur);
1149 // FIXME: we have to enable full redraw here because of the
1150 // visual box corners that define the inset.
1151 cur.updateFlags(Update::Decoration | Update::FitCursor);
1152 } else if (cmd.button() == mouse_button::button2) {
1154 if (cap::selection()) {
1155 // See comment in LyXText::dispatch why we do this
1156 cap::copySelectionToStack();
1157 cmd = FuncRequest(LFUN_PASTE, "0");
1158 doDispatch(cur, cmd);
1160 asArray(theSelection().get(), ar);
1163 bv.mouseSetCursor(cur);
1168 void InsetMathNest::lfunMouseMotion(LCursor & cur, FuncRequest & cmd)
1170 // only select with button 1
1171 if (cmd.button() == mouse_button::button1) {
1172 LCursor & bvcur = cur.bv().cursor();
1173 if (bvcur.anchor_.hasPart(cur)) {
1174 //lyxerr << "## lfunMouseMotion: cursor: " << cur << endl;
1175 bvcur.setCursor(cur);
1176 bvcur.selection() = true;
1177 //lyxerr << "MOTION " << bvcur << endl;
1184 void InsetMathNest::lfunMouseRelease(LCursor & cur, FuncRequest & cmd)
1186 //lyxerr << "## lfunMouseRelease: buttons: " << cmd.button() << endl;
1188 if (cmd.button() == mouse_button::button1) {
1189 if (!cur.selection())
1192 LCursor & bvcur = cur.bv().cursor();
1193 bvcur.selection() = true;
1194 cap::saveSelection(bvcur);
1199 if (cmd.button() == mouse_button::button3) {
1200 // try to dispatch to enclosed insets first
1201 cur.bv().showDialog("mathpanel");
1209 bool InsetMathNest::interpretChar(LCursor & cur, char_type c)
1211 //lyxerr << "interpret 2: '" << c << "'" << endl;
1212 docstring save_selection;
1213 if (c == '^' || c == '_')
1214 save_selection = grabAndEraseSelection(cur);
1219 if (cur.inMacroMode()) {
1220 docstring name = cur.macroName();
1222 /// are we currently typing '#1' or '#2' or...?
1223 if (name == "\\#") {
1226 if (n >= 1 && n <= 9)
1227 cur.insert(new MathMacroArgument(n));
1232 cur.activeMacro()->setName(name + docstring(1, c));
1236 // handle 'special char' macros
1241 if (currentMode() == InsetMath::TEXT_MODE)
1242 cur.niceInsert(createInsetMath("textbackslash"));
1244 cur.niceInsert(createInsetMath("backslash"));
1245 } else if (c == '{') {
1247 cur.niceInsert(MathAtom(new InsetMathBrace));
1248 } else if (c == '%') {
1250 cur.niceInsert(MathAtom(new InsetMathComment));
1251 } else if (c == '#') {
1252 BOOST_ASSERT(cur.activeMacro());
1253 cur.activeMacro()->setName(name + docstring(1, c));
1256 cur.niceInsert(createInsetMath(docstring(1, c)));
1261 // One character big delimiters. The others are handled in
1262 // interpretString().
1263 latexkeys const * l = in_word_set(name.substr(1));
1264 if (name[0] == '\\' && l && l->inset == "big") {
1268 delim = from_ascii("\\{");
1271 delim = from_ascii("\\}");
1274 delim = docstring(1, c);
1277 if (InsetMathBig::isBigInsetDelim(delim)) {
1278 // name + delim ared a valid InsetMathBig.
1279 // We can't use cur.macroModeClose() because
1280 // it does not handle delim.
1281 InsetMathUnknown * p = cur.activeMacro();
1284 cur.cell().erase(cur.pos());
1285 cur.plainInsert(MathAtom(
1286 new InsetMathBig(name.substr(1), delim)));
1291 // leave macro mode and try again if necessary
1292 cur.macroModeClose();
1294 cur.niceInsert(MathAtom(new InsetMathBrace));
1296 interpretChar(cur, c);
1300 // This is annoying as one has to press <space> far too often.
1304 // leave autocorrect mode if necessary
1305 if (autocorrect() && c == ' ') {
1306 autocorrect() = false;
1311 // just clear selection on pressing the space bar
1312 if (cur.selection() && c == ' ') {
1313 cur.selection() = false;
1320 //lyxerr << "starting with macro" << endl;
1321 cur.insert(MathAtom(new InsetMathUnknown(from_ascii("\\"), false)));
1326 if (currentMode() == InsetMath::TEXT_MODE)
1332 if (currentMode() == InsetMath::TEXT_MODE) {
1333 // insert spaces in text mode,
1334 // but suppress direct insertion of two spaces in a row
1335 // the still allows typing '<space>a<space>' and deleting the 'a', but
1336 // it is better than nothing...
1337 if (!cur.pos() != 0 || cur.prevAtom()->getChar() != ' ') {
1339 // FIXME: we have to enable full redraw here because of the
1340 // visual box corners that define the inset. If we know for
1341 // sure that we stay within the same cell we can optimize for
1343 //cur.updateFlags(Update::SinglePar | Update::FitCursor);
1347 if (cur.pos() != 0 && cur.prevAtom()->asSpaceInset()) {
1348 cur.prevAtom().nucleus()->asSpaceInset()->incSpace();
1349 // FIXME: we have to enable full redraw here because of the
1350 // visual box corners that define the inset. If we know for
1351 // sure that we stay within the same cell we can optimize for
1353 //cur.updateFlags(Update::SinglePar | Update::FitCursor);
1357 if (cur.popRight()) {
1358 // FIXME: we have to enable full redraw here because of the
1359 // visual box corners that define the inset. If we know for
1360 // sure that we stay within the same cell we can optimize for
1362 //cur.updateFlags(Update::FitCursor);
1366 // if we are at the very end, leave the formula
1367 return cur.pos() != cur.lastpos();
1370 // These shouldn't work in text mode:
1371 if (currentMode() != InsetMath::TEXT_MODE) {
1373 script(cur, false, save_selection);
1377 script(cur, true, save_selection);
1381 cur.niceInsert(createInsetMath("sim"));
1386 if (c == '{' || c == '}' || c == '&' || c == '$' || c == '#' ||
1387 c == '%' || c == '_' || c == '^') {
1388 cur.niceInsert(createInsetMath(docstring(1, c)));
1393 // try auto-correction
1394 //if (autocorrect() && hasPrevAtom() && math_autocorrect(prevAtom(), c))
1397 // no special circumstances, so insert the character without any fuss
1399 cur.autocorrect() = true;
1404 bool InsetMathNest::interpretString(LCursor & cur, docstring const & str)
1406 // Create a InsetMathBig from cur.cell()[cur.pos() - 1] and t if
1408 if (!cur.empty() && cur.pos() > 0 &&
1409 cur.cell()[cur.pos() - 1]->asUnknownInset()) {
1410 if (InsetMathBig::isBigInsetDelim(str)) {
1411 docstring prev = asString(cur.cell()[cur.pos() - 1]);
1412 if (prev[0] == '\\') {
1413 prev = prev.substr(1);
1414 latexkeys const * l = in_word_set(prev);
1415 if (l && l->inset == "big") {
1416 cur.cell()[cur.pos() - 1] =
1417 MathAtom(new InsetMathBig(prev, str));
1427 bool InsetMathNest::script(LCursor & cur, bool up,
1428 docstring const & save_selection)
1430 // Hack to get \^ and \_ working
1431 //lyxerr << "handling script: up: " << up << endl;
1432 if (cur.inMacroMode() && cur.macroName() == "\\") {
1434 cur.niceInsert(createInsetMath("mathcircumflex"));
1436 interpretChar(cur, '_');
1440 cur.macroModeClose();
1441 if (asScriptInset() && cur.idx() == 0) {
1442 // we are in a nucleus of a script inset, move to _our_ script
1443 InsetMathScript * inset = asScriptInset();
1444 //lyxerr << " going to cell " << inset->idxOfScript(up) << endl;
1446 cur.idx() = inset->idxOfScript(up);
1448 } else if (cur.pos() != 0 && cur.prevAtom()->asScriptInset()) {
1450 InsetMathScript * inset = cur.nextAtom().nucleus()->asScriptInset();
1453 cur.idx() = inset->idxOfScript(up);
1454 cur.pos() = cur.lastpos();
1456 // convert the thing to our left to a scriptinset or create a new
1457 // one if in the very first position of the array
1458 if (cur.pos() == 0) {
1459 //lyxerr << "new scriptinset" << endl;
1460 cur.insert(new InsetMathScript(up));
1462 //lyxerr << "converting prev atom " << endl;
1463 cur.prevAtom() = MathAtom(new InsetMathScript(cur.prevAtom(), up));
1466 InsetMathScript * inset = cur.nextAtom().nucleus()->asScriptInset();
1467 // See comment in MathParser.C for special handling of {}-bases
1473 //lyxerr << "inserting selection 1:\n" << save_selection << endl;
1474 cur.niceInsert(save_selection);
1476 //lyxerr << "inserting selection 2:\n" << save_selection << endl;