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