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