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