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