]> git.lyx.org Git - features.git/blob - src/mathed/math_cursor.C
move things around
[features.git] / src / mathed / math_cursor.C
1 /*
2  *  File:        math_cursor.C
3  *  Purpose:     Interaction for mathed
4  *  Author:      Alejandro Aguilar Sierra <asierra@servidor.unam.mx>
5  *  Created:     January 1996
6  *  Description: Math interaction for a WYSIWYG math editor.
7  *
8  *  Dependencies: Xlib, XForms
9  *
10  *  Copyright: 1996, Alejandro Aguilar Sierra
11  *
12  *   Version: 0.8beta, Math & Lyx project.
13  *
14  *   You are free to use and modify this code under the terms of
15  *   the GNU General Public Licence version 2 or later.
16  */
17
18 #include <config.h>
19 #include <lyxrc.h>
20
21 #ifdef __GNUG__
22 #pragma implementation
23 #endif
24
25 #include "support/lstrings.h"
26 #include "support/LAssert.h"
27 #include "BufferView.h"
28 #include "debug.h"
29 #include "LColor.h"
30 #include "frontends/Painter.h"
31 #include "math_cursor.h"
32 #include "formulabase.h"
33 #include "math_autocorrect.h"
34 #include "math_arrayinset.h"
35 #include "math_braceinset.h"
36 #include "math_casesinset.h"
37 #include "math_charinset.h"
38 #include "math_extern.h"
39 #include "math_factory.h"
40 #include "math_hullinset.h"
41 #include "math_iterator.h"
42 #include "math_macroarg.h"
43 #include "math_macrotemplate.h"
44 #include "math_mathmlstream.h"
45 #include "math_parser.h"
46 #include "math_replace.h"
47 #include "math_scriptinset.h"
48 #include "math_spaceinset.h"
49 #include "math_support.h"
50 #include "math_unknowninset.h"
51
52 #include <algorithm>
53 #include <cctype>
54
55 //#define FILEDEBUG 1
56
57 using std::endl;
58 using std::min;
59 using std::max;
60 using std::swap;
61 using std::vector;
62 using std::ostringstream;
63 using std::isalpha;
64
65
66 // matheds own cut buffer
67 MathGridInset theCutBuffer = MathGridInset(1, 1);
68
69
70 MathCursor::MathCursor(InsetFormulaBase * formula, bool front)
71         :       formula_(formula), autocorrect_(false), selection_(false)
72 {
73         front ? first() : last();
74         Anchor_ = Cursor_;
75 }
76
77
78 MathCursor::~MathCursor()
79 {
80   // ensure that 'notifyCursorLeave' is called
81   while (popLeft())
82     ;
83 }   
84
85
86 void MathCursor::push(MathAtom & t)
87 {
88         Cursor_.push_back(MathCursorPos(t.nucleus()));
89 }
90
91
92 void MathCursor::pushLeft(MathAtom & t)
93 {
94         //cerr << "Entering atom "; t->write(cerr, false); cerr << " left\n";
95         push(t);
96         t->idxFirst(idx(), pos());
97 }
98
99
100 void MathCursor::pushRight(MathAtom & t)
101 {
102         //cerr << "Entering atom "; t->write(cerr, false); cerr << " right\n";
103         posLeft();
104         push(t);
105         t->idxLast(idx(), pos());
106 }
107
108
109 bool MathCursor::popLeft()
110 {
111         //cerr << "Leaving atom to the left\n";
112         if (depth() <= 1) {
113                 if (depth() == 1)
114                         par()->notifyCursorLeaves();
115                 return false;
116         }
117         par()->notifyCursorLeaves();
118         Cursor_.pop_back();
119         return true;
120 }
121
122
123 bool MathCursor::popRight()
124 {
125         //cerr << "Leaving atom "; par()->write(cerr, false); cerr << " right\n";
126         if (depth() <= 1) {
127                 if (depth() == 1)
128                         par()->notifyCursorLeaves();
129                 return false;
130         }
131         par()->notifyCursorLeaves();
132         Cursor_.pop_back();
133         posRight();
134         return true;
135 }
136
137
138
139 #if FILEDEBUG
140         void MathCursor::dump(char const * what) const
141         {
142                 lyxerr << "MC: " << what << "\n";
143                 lyxerr << " Cursor: " << depth() << "\n";
144                 for (unsigned i = 0; i < depth(); ++i)
145                         lyxerr << "    i: " << i << " " << Cursor_[i] << "\n";
146                 lyxerr << " Anchor: " << Anchor_.size() << "\n";
147                 for (unsigned i = 0; i < Anchor_.size(); ++i)
148                         lyxerr << "    i: " << i << " " << Anchor_[i] << "\n";
149                 lyxerr  << " sel: " << selection_ << "\n";
150         }
151 #else
152         void MathCursor::dump(char const *) const {}
153 #endif
154
155
156 bool MathCursor::isInside(MathInset const * p) const
157 {
158         for (unsigned i = 0; i < depth(); ++i)
159                 if (Cursor_[i].par_ == p)
160                         return true;
161         return false;
162 }
163
164
165 bool MathCursor::openable(MathAtom const & t, bool sel) const
166 {
167         if (!t->isActive())
168                 return false;
169
170         if (t->lock())
171                 return false;
172
173         if (t->asScriptInset())
174                 return false;
175
176         if (sel) {
177                 // we can't move into anything new during selection
178                 if (depth() == Anchor_.size())
179                         return false;
180                 if (t.nucleus() != Anchor_[depth()].par_)
181                         return false;
182         }
183         return true;
184 }
185
186
187 bool MathCursor::posLeft()
188 {
189         if (pos() == 0)
190                 return false;
191         --pos();
192         return true;
193 }
194
195
196 bool MathCursor::posRight()
197 {
198         if (pos() == size())
199                 return false;
200         ++pos();
201         return true;
202 }
203
204
205 bool MathCursor::left(bool sel)
206 {
207         dump("Left 1");
208         autocorrect_ = false;
209         targetx_ = -1; // "no target"
210         if (inMacroMode()) {
211                 macroModeClose();
212                 return true;
213         }
214         selHandle(sel);
215
216         if (hasPrevAtom() && openable(prevAtom(), sel)) {
217                 pushRight(prevAtom());
218                 return true;
219         }
220
221         return posLeft() || idxLeft() || popLeft() || selection_;
222 }
223
224
225 bool MathCursor::right(bool sel)
226 {
227         dump("Right 1");
228         autocorrect_ = false;
229         targetx_ = -1; // "no target"
230         if (inMacroMode()) {
231                 macroModeClose();
232                 return true;
233         }
234         selHandle(sel);
235
236         if (hasNextAtom() && openable(nextAtom(), sel)) {
237                 pushLeft(nextAtom());
238                 return true;
239         }
240
241         return posRight() || idxRight() || popRight() || selection_;
242 }
243
244
245 void MathCursor::first()
246 {
247         Cursor_.clear();
248         push(formula_->par());
249         par()->idxFirst(idx(), pos());
250 }
251
252
253 void MathCursor::last()
254 {
255         Cursor_.clear();
256         push(formula_->par());
257         par()->idxLast(idx(), pos());
258 }
259
260
261 bool positionable(MathIterator const & cursor, MathIterator const & anchor)
262 {
263         // avoid deeper nested insets when selecting
264         if (cursor.size() > anchor.size())
265                 return false;
266
267         // anchor might be deeper, should have same path then
268         for (MathIterator::size_type i = 0; i < cursor.size(); ++i)
269                 if (cursor[i].par_ != anchor[i].par_)
270                         return false;
271
272         // position should be ok.
273         return true;
274 }
275
276
277 void MathCursor::setPos(int x, int y)
278 {
279         dump("setPos 1");
280         bool res = bruteFind(x, y,
281                 formula()->xlow(), formula()->xhigh(),
282                 formula()->ylow(), formula()->yhigh());
283         if (!res) {
284                 // this ccan happen on creation of "math-display"
285                 dump("setPos 1.5");
286                 first();
287         }
288         dump("setPos 2");
289 }
290
291
292
293 bool MathCursor::home(bool sel)
294 {
295         dump("home 1");
296         autocorrect_ = false;
297         selHandle(sel);
298         macroModeClose();
299         if (!par()->idxHome(idx(), pos()))
300                 return popLeft();
301         dump("home 2");
302         return true;
303 }
304
305
306 bool MathCursor::end(bool sel)
307 {
308         dump("end 1");
309         autocorrect_ = false;
310         selHandle(sel);
311         macroModeClose();
312         if (!par()->idxEnd(idx(), pos()))
313                 return popRight();
314         dump("end 2");
315         return true;
316 }
317
318
319 void MathCursor::plainErase()
320 {
321         array().erase(pos());
322 }
323
324
325 void MathCursor::markInsert()
326 {
327         //lyxerr << "inserting mark\n";
328         array().insert(pos(), MathAtom(new MathCharInset(0)));
329 }
330
331
332 void MathCursor::markErase()
333 {
334         //lyxerr << "deleting mark\n";
335         array().erase(pos());
336 }
337
338
339 void MathCursor::plainInsert(MathAtom const & t)
340 {
341         dump("plainInsert");
342         array().insert(pos(), t);
343         ++pos();
344 }
345
346
347 void MathCursor::insert(string const & str)
348 {
349         //lyxerr << "inserting '" << str << "'\n";
350         selClearOrDel();
351         for (string::const_iterator it = str.begin(); it != str.end(); ++it)
352                 plainInsert(MathAtom(new MathCharInset(*it)));
353 }
354
355
356 void MathCursor::insert(char c)
357 {
358         //lyxerr << "inserting '" << c << "'\n";
359         selClearOrDel();
360         plainInsert(MathAtom(new MathCharInset(c)));
361 }
362
363
364 void MathCursor::insert(MathAtom const & t)
365 {
366         macroModeClose();
367         selClearOrDel();
368         plainInsert(t);
369 }
370
371
372 void MathCursor::niceInsert(MathAtom const & t)
373 {
374         macroModeClose();
375         MathGridInset safe = grabAndEraseSelection();
376         plainInsert(t);
377         // enter the new inset and move the contents of the selection if possible
378         if (t->isActive()) {
379                 posLeft();
380                 pushLeft(nextAtom());
381                 paste(safe);
382         }
383 }
384
385
386 void MathCursor::insert(MathArray const & ar)
387 {
388         macroModeClose();
389         if (selection_)
390                 eraseSelection();
391
392         array().insert(pos(), ar);
393         pos() += ar.size();
394 }
395
396
397 void MathCursor::paste(MathArray const & ar)
398 {
399         Anchor_ = Cursor_;
400         selection_ = true;
401         array().insert(pos(), ar);
402         pos() += ar.size();
403 }
404
405
406 void MathCursor::paste(MathGridInset const & data)
407 {
408         if (data.nargs() == 1) {
409                 // single cell/part of cell
410                 paste(data.cell(0));
411         } else {
412                 // mulitple cells
413                 idx_type idx; // index of upper left cell
414                 MathGridInset * p = enclosingGrid(idx);
415                 col_type const numcols = min(data.ncols(), p->ncols() - p->col(idx));
416                 row_type const numrows = min(data.nrows(), p->nrows() - p->row(idx));
417                 for (row_type row = 0; row < numrows; ++row) {
418                         for (col_type col = 0; col < numcols; ++col) {
419                                 idx_type i = p->index(row + p->row(idx), col + p->col(idx));
420                                 p->cell(i).push_back(data.cell(data.index(row, col)));
421                         }
422                         // append the left over horizontal cells to the last column
423                         idx_type i = p->index(row + p->row(idx), p->ncols() - 1);
424                         for (MathInset::col_type col = numcols; col < data.ncols(); ++col)
425                                 p->cell(i).push_back(data.cell(data.index(row, col)));
426                 }
427                 // append the left over vertical cells to the last _cell_
428                 idx_type i = p->nargs() - 1;
429                 for (row_type row = numrows; row < data.nrows(); ++row)
430                         for (col_type col = 0; col < data.ncols(); ++col)
431                                 p->cell(i).push_back(data.cell(data.index(row, col)));
432         }
433 }
434
435 void MathCursor::backspace()
436 {
437         autocorrect_ = false;
438         if (pos() == 0) {
439                 pullArg();
440                 return;
441         }
442
443         if (selection_) {
444                 selDel();
445                 return;
446         }
447
448         MathScriptInset * p = prevAtom()->asScriptInset();
449         if (p) {
450                 p->removeScript(p->hasUp());
451                 // Don't delete if there is anything left
452                 if (p->hasUp() || p->hasDown())
453                         return;
454         }
455
456         --pos();
457         plainErase();
458 }
459
460
461 void MathCursor::erase()
462 {
463         autocorrect_ = false;
464         if (inMacroMode())
465                 return;
466
467         if (selection_) {
468                 selDel();
469                 return;
470         }
471
472         // delete empty cells if possible
473         if (array().empty())
474                 if (par()->idxDelete(idx()))
475                         return;
476
477         // old behaviour when in last position of cell
478         if (pos() == size()) {
479                 par()->idxGlue(idx());
480                 return;
481         }
482
483         MathScriptInset * p = nextAtom()->asScriptInset();
484         if (p) {
485                 p->removeScript(p->hasUp());
486                 // Don't delete if there is anything left
487                 if (p->hasUp() || p->hasDown())
488                         return;
489         }
490
491         plainErase();
492 }
493
494
495 void MathCursor::delLine()
496 {
497         autocorrect_ = false;
498         macroModeClose();
499
500         if (selection_) {
501                 selDel();
502                 return;
503         }
504
505         if (par()->nrows() > 1) {
506                 // grid are the only things with more than one row...
507                 lyx::Assert(par()->asGridInset());
508                 par()->asGridInset()->delRow(hullRow());
509         }
510
511         if (idx() >= par()->nargs())
512                 idx() = par()->nargs() - 1;
513
514         if (pos() > size())
515                 pos() = size();
516 }
517
518
519 bool MathCursor::up(bool sel)
520 {
521         dump("up 1");
522         macroModeClose();
523         selHandle(sel);
524         MathIterator save = Cursor_;
525         if (goUpDown(true))
526                 return true;
527         Cursor_ = save;
528         autocorrect_ = false;
529         return selection_;
530 }
531
532
533 bool MathCursor::down(bool sel)
534 {
535         dump("down 1");
536         macroModeClose();
537         selHandle(sel);
538         MathIterator save = Cursor_;
539         if (goUpDown(false))
540                 return true;
541         Cursor_ = save;
542         autocorrect_ = false;
543         return selection_;
544 }
545
546
547 bool MathCursor::toggleLimits()
548 {
549         if (!hasNextAtom())
550                 return false;
551         MathScriptInset * t = nextAtom()->asScriptInset();
552         if (!t)
553                 return false;
554         int old = t->limits();
555         t->limits(old < 0 ? 1 : -1);
556         return old != t->limits();
557 }
558
559
560 void MathCursor::macroModeClose()
561 {
562         MathUnknownInset * p = inMacroMode();
563         if (!p)
564                 return;
565         p->finalize();
566         string s = p->name();
567         --pos();
568         array().erase(pos());
569         if (s != "\\")
570                 interpret(s);
571 }
572
573
574 string MathCursor::macroName() const
575 {
576         return inMacroMode() ? inMacroMode()->name() : string();
577 }
578
579
580 void MathCursor::selClear()
581 {
582         selection_ = false;
583 }
584
585
586 void MathCursor::selCopy()
587 {
588         dump("selCopy");
589         if (selection_) {
590                 theCutBuffer = grabSelection();
591                 selection_ = false;
592         } else {
593                 theCutBuffer = MathGridInset(1, 1);
594         }
595 }
596
597
598 void MathCursor::selCut()
599 {
600         dump("selCut");
601         theCutBuffer = grabAndEraseSelection();
602 }
603
604
605 void MathCursor::selDel()
606 {
607         dump("selDel");
608         if (selection_) {
609                 eraseSelection();
610                 selection_ = false;
611         }
612 }
613
614
615 void MathCursor::selPaste()
616 {
617         dump("selPaste");
618         selClearOrDel();
619         paste(theCutBuffer);
620         //grabSelection();
621         selection_ = false;
622 }
623
624
625 void MathCursor::selHandle(bool sel)
626 {
627         if (sel == selection_)
628                 return;
629         //clear();
630         Anchor_ = Cursor_;
631         selection_ = sel;
632 }
633
634
635 void MathCursor::selStart()
636 {
637         dump("selStart 1");
638         //clear();
639         Anchor_ = Cursor_;
640         selection_ = true;
641         dump("selStart 2");
642 }
643
644
645 void MathCursor::selClearOrDel()
646 {
647         if (lyxrc.auto_region_delete)
648                 selDel();
649         else
650                 selection_ = false;
651 }
652
653
654 void MathCursor::selGet(MathArray & ar)
655 {
656         dump("selGet");
657         if (selection_)
658                 ar = grabSelection().glue();
659 }
660
661
662
663 void MathCursor::drawSelection(MathPainterInfo & pain) const
664 {
665         if (!selection_)
666                 return;
667
668         MathCursorPos i1;
669         MathCursorPos i2;
670         getSelection(i1, i2);
671
672         if (i1.idx_ == i2.idx_) {
673                 MathXArray & c = i1.xcell();
674                 int x1 = c.xo() + c.pos2x(i1.pos_);
675                 int y1 = c.yo() - c.ascent();
676                 int x2 = c.xo() + c.pos2x(i2.pos_);
677                 int y2 = c.yo() + c.descent();
678                 pain.pain.fillRectangle(x1, y1, x2 - x1, y2 - y1, LColor::selection);
679         } else {
680                 vector<MathInset::idx_type> indices
681                         = i1.par_->idxBetween(i1.idx_, i2.idx_);
682                 for (unsigned i = 0; i < indices.size(); ++i) {
683                         MathXArray & c = i1.xcell(indices[i]);
684                         int x1 = c.xo();
685                         int y1 = c.yo() - c.ascent();
686                         int x2 = c.xo() + c.width();
687                         int y2 = c.yo() + c.descent();
688                         pain.pain.fillRectangle(x1, y1, x2 - x1, y2 - y1, LColor::selection);
689                 }
690         }
691
692 #if 0
693         // draw anchor if different from selection boundary
694         MathCursorPos anc = Anchor_.back();
695         if (anc != i1 && anc != i2) {
696                 MathXArray & c = anc.xcell();
697                 int x  = c.xo() + c.pos2x(anc.pos_);
698                 int y1 = c.yo() - c.ascent();
699                 int y2 = c.yo() + c.descent();
700                 pain.line(x, y1, x, y2, LColor::math);
701         }
702 #endif
703 }
704
705
706 void MathCursor::handleNest(MathAtom const & at)
707 {
708         at->cell(0) = grabAndEraseSelection().glue();
709         insert(at);
710         pushRight(prevAtom());
711 }
712
713
714 void MathCursor::getPos(int & x, int & y)
715 {
716         par()->getPos(idx(), pos(), x, y);
717 }
718
719
720 MathInset * MathCursor::par() const
721 {
722         return cursor().par_;
723 }
724
725
726 InsetFormulaBase * MathCursor::formula() const
727 {
728         return formula_;
729 }
730
731
732 MathCursor::idx_type MathCursor::idx() const
733 {
734         return cursor().idx_;
735 }
736
737
738 MathCursor::idx_type & MathCursor::idx()
739 {
740         return cursor().idx_;
741 }
742
743
744 MathCursor::pos_type MathCursor::pos() const
745 {
746         return cursor().pos_;
747 }
748
749
750 MathCursor::pos_type & MathCursor::pos()
751 {
752         return cursor().pos_;
753 }
754
755
756 MathUnknownInset * MathCursor::inMacroMode() const
757 {
758         if (!hasPrevAtom())
759                 return 0;
760         MathUnknownInset * p = prevAtom()->asUnknownInset();
761         return (p && !p->final()) ? p : 0;
762 }
763
764
765 bool MathCursor::inMacroArgMode() const
766 {
767         return pos() > 0 && prevAtom()->getChar() == '#';
768 }
769
770
771 bool MathCursor::selection() const
772 {
773         return selection_;
774 }
775
776
777 MathGridInset * MathCursor::enclosingGrid(MathCursor::idx_type & idx) const
778 {
779         for (MathInset::difference_type i = depth() - 1; i >= 0; --i) {
780                 MathGridInset * p = Cursor_[i].par_->asGridInset();
781                 if (p) {
782                         idx = Cursor_[i].idx_;
783                         return p;
784                 }
785         }
786         return 0;
787 }
788
789
790 MathHullInset * MathCursor::enclosingHull(MathCursor::idx_type & idx) const
791 {
792         for (MathInset::difference_type i = depth() - 1; i >= 0; --i) {
793                 MathHullInset * p = Cursor_[i].par_->asHullInset();
794                 if (p) {
795                         idx = Cursor_[i].idx_;
796                         return p;
797                 }
798         }
799         return 0;
800 }
801
802
803 void MathCursor::popToEnclosingGrid()
804 {
805         while (depth() && !Cursor_.back().par_->asGridInset())
806                 Cursor_.pop_back();
807 }
808
809
810 void MathCursor::popToEnclosingHull()
811 {
812         while (depth() && !Cursor_.back().par_->asHullInset())
813                 Cursor_.pop_back();
814 }
815
816
817 void MathCursor::pullArg()
818 {
819         dump("pullarg");
820         MathArray a = array();
821
822         MathScriptInset const * p = par()->asScriptInset();
823         if (p) {
824                 // special handling for scripts
825                 const bool up = p->hasUp();
826                 popLeft();
827                 MathScriptInset * q = nextAtom()->asScriptInset();
828                 if (q)
829                         q->removeScript(up);
830                 ++pos();
831                 array().insert(pos(), a);
832                 return;
833         }
834
835         if (popLeft()) {
836                 plainErase();
837                 array().insert(pos(), a);
838                 Anchor_ = Cursor_;
839         } else {
840                 formula()->mutateToText();
841         }
842 }
843
844
845 void MathCursor::touch()
846 {
847         MathIterator::const_iterator it = Cursor_.begin();
848         MathIterator::const_iterator et = Cursor_.end();
849         for ( ; it != et; ++it)
850                 it->xcell().touch();
851 }
852
853
854 void MathCursor::normalize()
855 {
856         if (idx() >= par()->nargs()) {
857                 lyxerr << "this should not really happen - 1: "
858                        << idx() << " " << par()->nargs() << "\n";
859                 dump("error 2");
860         }
861         idx() = min(idx(), par()->nargs() - 1);
862
863         if (pos() > size()) {
864                 lyxerr << "this should not really happen - 2: "
865                         << pos() << " " << size() <<  " in idx: " << idx()
866                         << " in atom: '";
867                 WriteStream wi(lyxerr, false, true);
868                 par()->write(wi);
869                 lyxerr << "\n";
870                 dump("error 4");
871         }
872         pos() = min(pos(), size());
873
874         // remove empty scripts if possible
875         if (1) {
876                 for (pos_type i = 0; i < size(); ++i) {
877                         MathScriptInset * p = array()[i]->asScriptInset();
878                         if (p) {
879                                 p->removeEmptyScripts();
880                                 //if (p->empty())
881                                 //      array().erase(i);
882                         }
883                 }
884         }
885
886         // fix again position
887         pos() = min(pos(), size());
888 }
889
890
891 MathCursor::size_type MathCursor::size() const
892 {
893         return array().size();
894 }
895
896
897 MathCursor::col_type MathCursor::hullCol() const
898 {
899         idx_type idx = 0;
900         MathHullInset * p = enclosingHull(idx);
901         return p->col(idx);
902 }
903
904
905 MathCursor::row_type MathCursor::hullRow() const
906 {
907         idx_type idx = 0;
908         MathHullInset * p = enclosingHull(idx);
909         return p->row(idx);
910 }
911
912
913 bool MathCursor::hasPrevAtom() const
914 {
915         return pos() > 0;
916 }
917
918
919 bool MathCursor::hasNextAtom() const
920 {
921         return pos() < size();
922 }
923
924
925 MathAtom const & MathCursor::prevAtom() const
926 {
927         lyx::Assert(pos() > 0);
928         return array()[pos() - 1];
929 }
930
931
932 MathAtom & MathCursor::prevAtom()
933 {
934         lyx::Assert(pos() > 0);
935         return array()[pos() - 1];
936 }
937
938
939 MathAtom const & MathCursor::nextAtom() const
940 {
941         lyx::Assert(pos() < size());
942         return array()[pos()];
943 }
944
945
946 MathAtom & MathCursor::nextAtom()
947 {
948         lyx::Assert(pos() < size());
949         return array()[pos()];
950 }
951
952
953 MathArray & MathCursor::array() const
954 {
955         static MathArray dummy;
956
957         if (idx() >= par()->nargs()) {
958                 lyxerr << "############  idx_ " << idx() << " not valid\n";
959                 return dummy;
960         }
961
962         if (depth() == 0) {
963                 lyxerr << "############  depth() == 0 not valid\n";
964                 return dummy;
965         }
966
967         return cursor().cell();
968 }
969
970
971 MathXArray & MathCursor::xarray() const
972 {
973         static MathXArray dummy;
974
975         if (depth() == 0) {
976                 lyxerr << "############  depth() == 0 not valid\n";
977                 return dummy;
978         }
979
980         return cursor().xcell();
981 }
982
983
984 void MathCursor::idxNext()
985 {
986         par()->idxNext(idx(), pos());
987 }
988
989
990 void MathCursor::idxPrev()
991 {
992         par()->idxPrev(idx(), pos());
993 }
994
995
996 void MathCursor::splitCell()
997 {
998         if (idx() + 1 == par()->nargs())
999                 return;
1000         MathArray ar = array();
1001         ar.erase(0, pos());
1002         array().erase(pos(), size());
1003         ++idx();
1004         pos() = 0;
1005         array().insert(0, ar);
1006 }
1007
1008
1009 void MathCursor::breakLine()
1010 {
1011         // leave inner cells
1012         while (popRight())
1013                 ;
1014
1015         idx_type dummy;
1016         MathHullInset * p = enclosingHull(dummy);
1017         if (!p)
1018                 return;
1019
1020         if (p->getType() == "simple" || p->getType() == "equation") {
1021                 p->mutate("eqnarray");
1022                 idx() = 1;
1023                 pos() = 0;
1024         } else {
1025                 p->addRow(hullRow());
1026
1027                 // split line
1028                 const row_type r = hullRow();
1029                 for (col_type c = hullCol() + 1; c < p->ncols(); ++c)
1030                         p->cell(p->index(r, c)).swap(p->cell(p->index(r + 1, c)));
1031
1032                 // split cell
1033                 splitCell();
1034                 p->cell(idx()).swap(p->cell(idx() + p->ncols() - 1));
1035         }
1036 }
1037
1038
1039 //void MathCursor::readLine(MathArray & ar) const
1040 //{
1041 //      idx_type base = row() * par()->ncols();
1042 //      for (idx_type off = 0; off < par()->ncols(); ++off)
1043 //              ar.push_back(par()->cell(base + off));
1044 //}
1045
1046
1047 char MathCursor::valign() const
1048 {
1049         idx_type idx;
1050         MathGridInset * p = enclosingGrid(idx);
1051         return p ? p->valign() : '\0';
1052 }
1053
1054
1055 char MathCursor::halign() const
1056 {
1057         idx_type idx;
1058         MathGridInset * p = enclosingGrid(idx);
1059         return p ? p->halign(idx % p->ncols()) : '\0';
1060 }
1061
1062
1063 void MathCursor::getSelection(MathCursorPos & i1, MathCursorPos & i2) const
1064 {
1065         MathCursorPos anc = normalAnchor();
1066         if (anc < cursor()) {
1067                 i1 = anc;
1068                 i2 = cursor();
1069         } else {
1070                 i1 = cursor();
1071                 i2 = anc;
1072         }
1073 }
1074
1075
1076 MathCursorPos & MathCursor::cursor()
1077 {
1078         lyx::Assert(depth());
1079         return Cursor_.back();
1080 }
1081
1082
1083 MathCursorPos const & MathCursor::cursor() const
1084 {
1085         lyx::Assert(depth());
1086         return Cursor_.back();
1087 }
1088
1089
1090 bool MathCursor::goUpDown(bool up)
1091 {
1092         // Be warned: The 'logic' implemented in this function is highly fragile.
1093         // A distance of one pixel or a '<' vs '<=' _really_ matters.
1094         // So fiddle around with it only if you know what you are doing!
1095         int xlow, xhigh, ylow, yhigh;
1096
1097   int xo, yo;
1098         getPos(xo, yo);
1099
1100         // check if we had something else in mind, if not, this is the future goal
1101         if (targetx_ == -1)
1102                 targetx_ = xo;
1103         else
1104                 xo = targetx_;
1105
1106         // try neigbouring script insets
1107         // try left
1108         if (hasPrevAtom()) {
1109                 MathScriptInset * p = prevAtom()->asScriptInset();
1110                 if (p && p->has(up)) {
1111                         --pos();
1112                         push(nextAtom());
1113                         idx() = up; // the superscript has index 1
1114                         pos() = size();
1115                         ///lyxerr << "updown: handled by scriptinset to the left\n";
1116                         return true;
1117                 }
1118         }
1119
1120         // try right
1121         if (hasNextAtom()) {
1122                 MathScriptInset * p = nextAtom()->asScriptInset();
1123                 if (p && p->has(up)) {
1124                         push(nextAtom());
1125                         idx() = up;
1126                         pos() = 0;
1127                         ///lyxerr << "updown: handled by scriptinset to the right\n";
1128                         return true;
1129                 }
1130         }
1131
1132         // try current cell
1133         //xarray().boundingBox(xlow, xhigh, ylow, yhigh);
1134         //if (up)
1135         //      yhigh = yo - 4;
1136         //else
1137         //      ylow = yo + 4;
1138         //if (bruteFind(xo, yo, xlow, xhigh, ylow, yhigh)) {
1139         //      lyxerr << "updown: handled by brute find in the same cell\n";
1140         //      return true;
1141         //}
1142
1143         // try to find an inset that knows better then we
1144         while (1) {
1145                 ///lyxerr << "updown: We are in " << *par() << " idx: " << idx() << '\n';
1146                 // ask inset first
1147                 if (par()->idxUpDown(idx(), pos(), up)) {
1148 #ifdef WITH_WARNINGS
1149 #warning this code should be moved to individual insets that handle this
1150 #endif
1151                         // position might have changed, so re-compute it
1152                         getPos(xo, yo);
1153                         // we found a cell that thinks it has something "below" us.
1154                         //lyxerr << "updown: found inset that handles UpDown\n";
1155                         xarray().boundingBox(xlow, xhigh, ylow, yhigh);
1156                         // project (xo,yo) onto proper box
1157                         //lyxerr << "\n   xo: " << xo << " yo: " << yo
1158                         //       << "\n   xlow: " << xlow << " ylow: " << ylow
1159                         //       << "\n   xhigh: " << xhigh << " yhigh: " << yhigh;
1160                         xo = min(max(xo, xlow), xhigh);
1161                         yo = min(max(yo, ylow), yhigh);
1162                         //lyxerr << "\n   xo2: " << xo << " yo2: " << yo << "\n";
1163                         bruteFind(xo, yo, xlow, xhigh, ylow, yhigh);
1164                         //lyxerr << "updown: handled by final brute find\n";
1165                         return true;
1166                 }
1167
1168                 // leave inset
1169                 if (!popLeft()) {
1170                         // no such inset found, just take something "above"
1171                         ///lyxerr << "updown: handled by strange case\n";
1172                         return
1173                                 bruteFind(xo, yo,
1174                                         formula()->xlow(),
1175                                         formula()->xhigh(),
1176                                         up ? formula()->ylow() : yo + 4,
1177                                         up ? yo - 4 : formula()->yhigh()
1178                                 );
1179                 }
1180
1181                 // any improvement so far?
1182                 int xnew, ynew;
1183                 getPos(xnew, ynew);
1184                 if (up ? ynew < yo : ynew > yo)
1185                         return true;
1186         }
1187 }
1188
1189
1190 bool MathCursor::bruteFind
1191         (int x, int y, int xlow, int xhigh, int ylow, int yhigh)
1192 {
1193         MathIterator best_cursor;
1194         double best_dist = 1e10;
1195
1196         MathIterator it = ibegin(formula()->par().nucleus());
1197         MathIterator et = iend(formula()->par().nucleus());
1198         while (1) {
1199                 // avoid invalid nesting when selecting
1200                 if (!selection_ || positionable(it, Anchor_)) {
1201                         int xo, yo;
1202                         it.back().getPos(xo, yo);
1203                         if (xlow <= xo && xo <= xhigh && ylow <= yo && yo <= yhigh) {
1204                                 double d = (x - xo) * (x - xo) + (y - yo) * (y - yo);
1205                                 // '<=' in order to take the last possible position
1206                                 // this is important for clicking behind \sum in e.g. '\sum_i a'
1207                                 if (d <= best_dist) {
1208                                         best_dist   = d;
1209                                         best_cursor = it;
1210                                 }
1211                         }
1212                 }
1213
1214                 if (it == et)
1215                         break;
1216                 ++it;
1217         }
1218
1219         if (best_dist < 1e10)
1220                 Cursor_ = best_cursor;
1221         return best_dist < 1e10;
1222 }
1223
1224
1225 bool MathCursor::idxLineFirst()
1226 {
1227         idx() -= idx() % par()->ncols();
1228         pos() = 0;
1229         return true;
1230 }
1231
1232
1233 bool MathCursor::idxLineLast()
1234 {
1235         idx() -= idx() % par()->ncols();
1236         idx() += par()->ncols() - 1;
1237         pos() = size();
1238         return true;
1239 }
1240
1241 bool MathCursor::idxLeft()
1242 {
1243         return par()->idxLeft(idx(), pos());
1244 }
1245
1246
1247 bool MathCursor::idxRight()
1248 {
1249         return par()->idxRight(idx(), pos());
1250 }
1251
1252
1253 bool MathCursor::interpret(string const & s)
1254 {
1255         //lyxerr << "interpret 1: '" << s << "'\n";
1256         if (s.empty())
1257                 return true;
1258
1259         //lyxerr << "char: '" << s[0] << "'  int: " << int(s[0]) << endl;
1260         //owner_->getIntl()->getTransManager().TranslateAndInsert(s[0], lt);
1261         //lyxerr << "trans: '" << s[0] << "'  int: " << int(s[0]) << endl;
1262
1263         if (s.size() >= 5 && s.substr(0, 5) == "cases") {
1264                 unsigned int n = 1;
1265                 istringstream is(s.substr(5).c_str());
1266                 is >> n;
1267                 n = max(1u, n);
1268                 niceInsert(MathAtom(new MathCasesInset(n)));
1269                 return true;
1270         }
1271
1272         if (s.size() >= 6 && s.substr(0, 6) == "matrix") {
1273                 unsigned int m = 1;
1274                 unsigned int n = 1;
1275                 string v_align;
1276                 string h_align;
1277                 istringstream is(s.substr(6).c_str());
1278                 is >> m >> n >> v_align >> h_align;
1279                 m = max(1u, m);
1280                 n = max(1u, n);
1281                 v_align += 'c';
1282                 niceInsert(MathAtom(new MathArrayInset("array", m, n, v_align[0], h_align)));
1283                 return true;
1284         }
1285
1286         if (s.size() >= 7 && s.substr(0, 7) == "replace") {
1287                 ReplaceData rep;
1288                 istringstream is(s.substr(7).c_str());
1289                 string from, to;
1290                 is >> from >> to;
1291                 mathed_parse_cell(rep.from, from);
1292                 mathed_parse_cell(rep.to, to);
1293                 lyxerr << "replacing '" << from << "' with '" << to << "'\n";
1294                 par()->replace(rep);
1295                 return true;
1296         }
1297
1298         string name = s.substr(1);
1299
1300         if (name == "over" || name == "choose" || name == "atop") {
1301                 MathArray ar = array();
1302                 MathAtom t(createMathInset(name));
1303                 t->asNestInset()->cell(0).swap(array());
1304                 pos() = 0;
1305                 niceInsert(t);
1306                 popRight();
1307                 left();
1308                 return true;
1309         }
1310
1311         // prevent entering of recursive macros
1312         if (formula()->lyxCode() == Inset::MATHMACRO_CODE
1313                 && formula()->getInsetName() == name)
1314         {
1315                 lyxerr << "can't enter recursive macro\n";
1316                 return true;
1317         }
1318
1319         niceInsert(createMathInset(name));
1320         return true;
1321 }
1322
1323
1324 bool MathCursor::script(bool up)
1325 {
1326         // Hack to get \\^ and \\_ working
1327         if (inMacroMode() && macroName() == "\\") {
1328                 if (up)
1329                         interpret("\\mathcircumflex");
1330                 else
1331                         interpret('_');
1332                 return true;
1333         }
1334
1335         macroModeClose();
1336         MathGridInset safe = grabAndEraseSelection();
1337         if (hasPrevAtom() && prevAtom()->asScriptInset()) {
1338                 prevAtom()->asScriptInset()->ensure(up);
1339                 pushRight(prevAtom());
1340                 idx() = up;
1341                 pos() = size();
1342         } else if (hasNextAtom() && nextAtom()->asScriptInset()) {
1343                 nextAtom()->asScriptInset()->ensure(up);
1344                 pushLeft(nextAtom());
1345                 idx() = up;
1346                 pos() = 0;
1347         } else {
1348                 plainInsert(MathAtom(new MathScriptInset(up)));
1349                 prevAtom()->asScriptInset()->ensure(up);
1350                 pushRight(prevAtom());
1351                 idx() = up;
1352                 pos() = 0;
1353         }
1354         paste(safe);
1355         dump("1");
1356         return true;
1357 }
1358
1359
1360 bool MathCursor::interpret(char c)
1361 {
1362         //lyxerr << "interpret 2: '" << c << "'\n";
1363         targetx_ = -1; // "no target"
1364         if (inMacroArgMode()) {
1365                 --pos();
1366                 plainErase();
1367                 int n = c - '0';
1368                 MathMacroTemplate * p = formula()->par()->asMacroTemplate();
1369                 if (p && 1 <= n && n <= p->numargs())
1370                         insert(MathAtom(new MathMacroArgument(c - '0')));
1371                 else {
1372                         insert(createMathInset("#"));
1373                         interpret(c); // try again
1374                 }
1375                 return true;
1376         }
1377
1378         // handle macroMode
1379         if (inMacroMode()) {
1380                 string name = macroName();
1381                 //lyxerr << "interpret name: '" << name << "'\n";
1382
1383                 if (name.empty() && c == '\\') {
1384                         backspace();
1385                         interpret("\\backslash");
1386                         return true;
1387                 }
1388
1389                 if (isalpha(c)) {
1390                         inMacroMode()->name() += c;
1391                         return true;
1392                 }
1393
1394                 // handle 'special char' macros
1395                 if (name == "\\") {
1396                         // remove the '\\'
1397                         backspace();
1398                         if (c == '\\')
1399                                 interpret("\\backslash");
1400                         else
1401                                 interpret(string("\\") + c);
1402                         return true;
1403                 }
1404
1405                 // leave macro mode and try again if necessary
1406                 macroModeClose();
1407                 if (c != ' ')
1408                         interpret(c);
1409                 return true;
1410         }
1411
1412         // This is annoying as one has to press <space> far too often.
1413         // Disable it.
1414
1415         if (0) {
1416                 // leave autocorrect mode if necessary
1417                 if (autocorrect_ && c == ' ') {
1418                         autocorrect_ = false;
1419                         return true;
1420                 }
1421         }
1422
1423         // just clear selection on pressing the space bar
1424         if (selection_ && c == ' ') {
1425                 selection_ = false;
1426                 return true;
1427         }
1428
1429         selClearOrDel();
1430
1431         if (c == '\\') {
1432                 //lyxerr << "starting with macro\n";
1433                 insert(MathAtom(new MathUnknownInset("\\", false)));
1434                 return true;
1435         }
1436
1437         if (c == ' ') {
1438                 if (currentMode() == MathInset::TEXT_MODE) {
1439                         // insert spaces in text mode,
1440                         // but suppress direct insertion of two spaces in a row
1441                         // the still allows typing  '<space>a<space>' and deleting the 'a', but
1442                         // it is better than nothing...
1443                         if (!hasPrevAtom() || prevAtom()->getChar() != ' ')
1444                                 insert(c);
1445                         return true;
1446                 }
1447                 if (hasPrevAtom() && prevAtom()->asSpaceInset()) {
1448                         prevAtom()->asSpaceInset()->incSpace();
1449                         return true;
1450                 }
1451                 if (popRight())
1452                         return true;
1453                 // if are at the very end, leave the formula
1454                 return pos() != size();
1455         }
1456
1457         if (c == '#') {
1458                 insert(c); // LM_TC_TEX;
1459                 return true;
1460         }
1461
1462 /*
1463         if (c == '{' || c == '}', c)) {
1464                 insert(c); // LM_TC_TEX;
1465                 return true;
1466         }
1467 */
1468
1469         if (c == '{') {
1470                 niceInsert(MathAtom(new MathBraceInset));
1471                 return true;
1472         }
1473
1474         if (c == '}') {
1475                 return true;
1476         }
1477
1478         if (c == '$') {
1479                 insert(createMathInset("$"));
1480                 return true;
1481         }
1482
1483         if (c == '%') {
1484                 insert(createMathInset("%"));
1485                 return true;
1486         }
1487
1488 /*
1489         if (isalpha(c) && lastcode_ == LM_TC_GREEK) {
1490                 insert(c, LM_TC_VAR);
1491                 return true;
1492         }
1493
1494         if (isalpha(c) && lastcode_ == LM_TC_GREEK1) {
1495                 insert(c, LM_TC_VAR);
1496                 lastcode_ = LM_TC_VAR;
1497                 return true;
1498         }
1499
1500         if (c == '\\') {
1501                 insert(c, LM_TC_TEX);
1502                 //bv->owner()->message(_("TeX mode"));
1503                 return true;
1504         }
1505 */
1506
1507         // try auto-correction
1508         //if (autocorrect_ && hasPrevAtom() && math_autocorrect(prevAtom(), c))
1509         //      return true;
1510
1511         // no special circumstances, so insert the character without any fuss
1512         insert(c);
1513         autocorrect_ = true;
1514         return true;
1515 }
1516
1517
1518 void MathCursor::setSelection(MathIterator const & where, size_type n)
1519 {
1520         selection_ = true;
1521         Anchor_ = where;
1522         Cursor_ = where;
1523         cursor().pos_ += n;
1524 }
1525
1526
1527 void MathCursor::insetToggle()
1528 {
1529         if (hasNextAtom()) {
1530                 // toggle next inset ...
1531                 nextAtom()->lock(!nextAtom()->lock());
1532         } else if (popLeft() && hasNextAtom()) {
1533                 // ... or enclosing inset if we are in the last inset position
1534                 nextAtom()->lock(!nextAtom()->lock());
1535                 posRight();
1536         }
1537 }
1538
1539
1540 string MathCursor::info() const
1541 {
1542         ostringstream os;
1543         os << "Math editor mode.  ";
1544         for (int i = 0, n = depth(); i < n; ++i) {
1545                 Cursor_[i].par_->infoize(os);
1546                 os << "  ";
1547         }
1548         if (hasPrevAtom())
1549                 if (prevAtom()->asSymbolInset() || prevAtom()->asScriptInset())
1550                         prevAtom()->infoize(os);
1551         os << "                    ";
1552         return os.str().c_str(); // .c_str() needed for lyxstring
1553 }
1554
1555
1556 unsigned MathCursor::depth() const
1557 {
1558         return Cursor_.size();
1559 }
1560
1561
1562
1563
1564 namespace {
1565
1566 void region(MathCursorPos const & i1, MathCursorPos const & i2,
1567         MathInset::row_type & r1, MathInset::row_type & r2,
1568         MathInset::col_type & c1, MathInset::col_type & c2)
1569 {
1570         MathInset * p = i1.par_;
1571         c1 = p->col(i1.idx_);
1572         c2 = p->col(i2.idx_);
1573         if (c1 > c2)
1574                 swap(c1, c2);
1575         r1 = p->row(i1.idx_);
1576         r2 = p->row(i2.idx_);
1577         if (r1 > r2)
1578                 swap(r1, r2);
1579 }
1580
1581 }
1582
1583
1584 MathGridInset MathCursor::grabSelection() const
1585 {
1586         if (!selection_)
1587                 return MathGridInset();
1588         MathCursorPos i1;
1589         MathCursorPos i2;
1590         getSelection(i1, i2);
1591         // shouldn't we assert on i1.par_ == i2.par_?
1592         if (i1.idx_ == i2.idx_) {
1593                 MathGridInset data(1, 1);
1594                 data.cell(0) = MathArray(i1.cell(), i1.pos_, i2.pos_);
1595                 return data;
1596         }
1597         row_type r1, r2;
1598         col_type c1, c2;
1599         region(i1, i2, r1, r2, c1, c2);
1600         MathGridInset data(c2 - c1 + 1, r2 - r1 + 1);
1601         for (row_type row = 0; row < data.nrows(); ++row)
1602                 for (col_type col = 0; col < data.ncols(); ++col) {
1603                         idx_type i = i1.par_->index(row + r1, col + c1);
1604                         data.cell(data.index(row, col)) = i1.par_->cell(i);
1605                 }
1606         return data;
1607 }
1608
1609
1610 void MathCursor::eraseSelection()
1611 {
1612         MathCursorPos i1;
1613         MathCursorPos i2;
1614         getSelection(i1, i2);
1615         if (i1.idx_ == i2.idx_)
1616                 i1.cell().erase(i1.pos_, i2.pos_);
1617         else {
1618                 MathInset * p = i1.par_;
1619                 row_type r1, r2;
1620                 col_type c1, c2;
1621                 region(i1, i2, r1, r2, c1, c2);
1622                 for (row_type row = r1; row <= r2; ++row)
1623                         for (col_type col = c1; col <= c2; ++col)
1624                                 p->cell(p->index(row, col)).erase();
1625         }
1626         cursor() = i1;
1627 }
1628
1629
1630 MathGridInset MathCursor::grabAndEraseSelection()
1631 {
1632         if (!selection_)
1633                 return MathGridInset();
1634         MathGridInset res = grabSelection();
1635         eraseSelection();
1636         selection_ = false;
1637         return res;
1638 }
1639
1640
1641 MathCursorPos MathCursor::normalAnchor() const
1642 {
1643         if (Anchor_.size() < depth()) {
1644                 Anchor_ = Cursor_;
1645                 lyxerr << "unusual Anchor size\n";
1646         }
1647         //lyx::Assert(Anchor_.size() >= cursor.depth());
1648         // use Anchor on the same level as Cursor
1649         MathCursorPos normal = Anchor_[depth() - 1];
1650         if (depth() < Anchor_.size() && !(normal < cursor())) {
1651                 // anchor is behind cursor -> move anchor behind the inset
1652                 ++normal.pos_;
1653         }
1654         return normal;
1655 }
1656
1657
1658
1659 void MathCursor::handleExtern(const string & arg)
1660 {
1661         string lang;
1662         string extra;
1663         istringstream iss(arg.c_str());
1664         iss >> lang >> extra;
1665         if (extra.empty())
1666                 extra = "noextra";
1667
1668
1669         if (selection()) {
1670                 MathArray ar;
1671                 selGet(ar);
1672                 lyxerr << "use selection: " << ar << "\n";
1673                 insert(pipeThroughExtern(lang, extra, ar));
1674                 return;
1675         }
1676
1677         MathArray eq;
1678         eq.push_back(MathAtom(new MathCharInset('=')));
1679
1680         popToEnclosingHull();
1681
1682         idx_type idx = 0;
1683         MathHullInset * hull = enclosingHull(idx);
1684         lyx::Assert(hull);
1685         idxLineFirst();
1686
1687         if (hull->getType() == "simple") {
1688                 MathArray::size_type pos = cursor().cell().find_last(eq);
1689                 MathArray ar;
1690                 if (pos == size()) {
1691                         ar = array();
1692                         lyxerr << "use whole cell: " << ar << "\n";
1693                 } else {
1694                         ar = MathArray(array(), pos + 1, size());
1695                         lyxerr << "use partial cell form pos: " << pos << "\n";
1696                 }
1697                 end();
1698                 insert(eq);
1699                 insert(pipeThroughExtern(lang, extra, ar));
1700                 return;
1701         }
1702
1703         if (hull->getType() == "equation") {
1704                 lyxerr << "use equation inset\n";
1705                 hull->mutate("eqnarray");
1706                 MathArray & ar = cursor().cell();
1707                 lyxerr << "use cell: " << ar << "\n";
1708                 idxRight();
1709                 cursor().cell() = eq;
1710                 idxRight();
1711                 cursor().cell() = pipeThroughExtern(lang, extra, ar);
1712                 idxLineLast();
1713                 return;
1714         }
1715
1716         {
1717                 lyxerr << "use eqnarray\n";
1718                 idxLineLast();
1719                 MathArray ar = cursor().cell();
1720                 lyxerr << "use cell: " << ar << "\n";
1721                 breakLine();
1722                 idxRight();
1723                 cursor().cell() = eq;
1724                 idxRight();
1725                 cursor().cell() = pipeThroughExtern(lang, extra, ar);
1726                 idxLineLast();
1727         }
1728
1729 }
1730
1731
1732 int MathCursor::dispatch(string const & cmd)
1733 {
1734         // try to dispatch to adajcent items if they are not editable
1735         // actually, this should only happen for mouse clicks...
1736         if (hasNextAtom() && !openable(nextAtom(), false))
1737                 if (int res = nextAtom()->dispatch(cmd, 0, 0))
1738                         return res;
1739         if (hasPrevAtom() && !openable(prevAtom(), false))
1740                 if (int res = prevAtom()->dispatch(cmd, 0, 0))
1741                         return res;
1742
1743         for (int i = Cursor_.size() - 1; i >= 0; --i) {
1744                 MathCursorPos & pos = Cursor_[i];
1745                 if (int res = pos.par_->dispatch(cmd, pos.idx_, pos.pos_))
1746                         return res;
1747         }
1748         return 0;
1749 }
1750
1751
1752 MathInset::mode_type MathCursor::currentMode() const
1753 {
1754         for (int i = Cursor_.size() - 1; i >= 0; --i) {
1755                 MathInset::mode_type res = Cursor_[i].par_->currentMode();
1756                 if (res != MathInset::UNDECIDED_MODE)
1757                         return res;
1758         }
1759         return MathInset::UNDECIDED_MODE;
1760 }
1761
1762
1763 void releaseMathCursor(BufferView * bv)
1764 {
1765         if (!mathcursor)
1766                 return;
1767         mathcursor->formula()->hideInsetCursor(bv);
1768         delete mathcursor;
1769         mathcursor = 0;
1770 }