]> git.lyx.org Git - features.git/blob - src/mathed/math_cursor.C
da3f51fa1f2f23541b546bfbde1bfbf15a4ebe6a
[features.git] / src / mathed / math_cursor.C
1 /**
2  * \file math_cursor.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author André Pönitz
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "math_cursor.h"
15 #include "BufferView.h"
16 #include "cursor.h"
17 #include "debug.h"
18 #include "dispatchresult.h"
19 #include "formulabase.h"
20 #include "funcrequest.h"
21 #include "lyxrc.h"
22 #include "math_braceinset.h"
23 #include "math_commentinset.h"
24 #include "math_charinset.h"
25 #include "math_factory.h"
26 #include "math_gridinset.h"
27 #include "math_macroarg.h"
28 #include "math_macrotemplate.h"
29 #include "math_mathmlstream.h"
30 #include "math_scriptinset.h"
31 #include "math_spaceinset.h"
32 #include "math_support.h"
33 #include "math_unknowninset.h"
34
35 #include "support/limited_stack.h"
36 #include "support/std_sstream.h"
37
38 #include <boost/assert.hpp>
39
40 //#define FILEDEBUG 1
41
42 using std::string;
43 using std::endl;
44 #ifndef CXX_GLOBAL_CSTD
45 using std::isalpha;
46 #endif
47 using std::min;
48 using std::swap;
49 using std::ostringstream;
50
51
52 // matheds own cut buffer
53 limited_stack<string> theCutBuffer;
54
55
56 MathCursor::MathCursor(BufferView * bv, InsetFormulaBase * formula, bool front)
57         :       formula_(formula), autocorrect_(false), selection_(false)
58 {
59         front ? first(bv->fullCursor()) : last(bv->fullCursor());
60 }
61
62
63 MathCursor::~MathCursor()
64 {
65         // ensure that 'notifyCursorLeave' is called
66         //while (popLeft())
67         //      ;
68 }
69
70
71 void MathCursor::push(LCursor & cur, MathAtom & t)
72 {
73         cur.push(t.nucleus());
74 }
75
76
77 void MathCursor::pushLeft(LCursor & cur, MathAtom & t)
78 {
79         //lyxerr << "Entering atom " << t << " left" << endl;
80         push(cur, t);
81         t->idxFirst(cur);
82 }
83
84
85 void MathCursor::pushRight(LCursor & cur, MathAtom & t)
86 {
87         //lyxerr << "Entering atom " << t << " right" << endl;
88         cur.posLeft();
89         push(cur, t);
90         t->idxLast(cur);
91 }
92
93
94 bool MathCursor::popLeft(LCursor & cur)
95 {
96         //lyxerr << "Leaving atom to the left" << endl;
97         if (cur.depth() <= 1) {
98                 if (cur.depth() == 1)
99                         cur.inset()->asMathInset()->notifyCursorLeaves(cur.idx());
100                 return false;
101         }
102         cur.inset()->asMathInset()->notifyCursorLeaves(cur.idx());
103         cur.pop();
104         return true;
105 }
106
107
108 bool MathCursor::popRight(LCursor & cur)
109 {
110         //lyxerr << "Leaving atom "; bv.inset->asMathInset()->write(cerr, false); cerr << " right" << endl;
111         if (cur.depth() <= 1) {
112                 if (cur.depth() == 1)
113                         cur.inset()->asMathInset()->notifyCursorLeaves(cur.idx());
114                 return false;
115         }
116         cur.inset()->asMathInset()->notifyCursorLeaves(cur.idx());
117         cur.pop();
118         cur.posRight();
119         return true;
120 }
121
122
123
124 #if FILEDEBUG
125         void MathCursor::dump(char const * what) const
126         {
127                 lyxerr << "MC: " << what << endl;
128                 lyxerr << " Cursor: " << cur.depth() << endl;
129                 for (unsigned i = 0; i < cur.depth(); ++i)
130                         lyxerr << "    i: " << i << ' ' << Cursor_[i] << endl;
131                 lyxerr << " Anchor: " << Anchor_.size() << endl;
132                 for (unsigned i = 0; i < Anchor_.size(); ++i)
133                         lyxerr << "    i: " << i << ' ' << Anchor_[i] << endl;
134                 lyxerr  << " sel: " << selection_ << endl;
135         }
136 #else
137         void MathCursor::dump(char const *) const {}
138 #endif
139
140
141 bool MathCursor::isInside(MathInset const *) const
142 {
143 #warning FIXME
144 /*
145         for (unsigned i = 0; i < cur.depth(); ++i)
146                 if (Cursor_[i].asMathInset() == p)
147                         return true;
148 */
149         return false;
150 }
151
152
153 bool MathCursor::openable(MathAtom const & t, bool sel) const
154 {
155         if (!t->isActive())
156                 return false;
157
158         if (t->lock())
159                 return false;
160
161 #warning FIXME
162 #if 0
163         if (sel) {
164                 // we can't move into anything new during selection
165                 if (cur.depth() == Anchor_.size())
166                         return false;
167                 if (t.operator->() != Anchor_[cur.depth()].asMathInset())
168                         return false;
169         }
170 #else
171         if (sel)
172                 return false;
173 #endif
174
175         return true;
176 }
177
178
179 bool MathCursor::inNucleus(LCursor & cur) const
180 {
181         return cur.inset()->asMathInset()->asScriptInset() && cur.idx() == 2;
182 }
183
184
185 bool MathCursor::left(LCursor & cur, bool sel)
186 {
187         dump("Left 1");
188         autocorrect_ = false;
189         cur.bv().x_target(-1); // "no target"
190         if (inMacroMode(cur)) {
191                 macroModeClose(cur);
192                 return true;
193         }
194         selHandle(cur, sel);
195
196         if (cur.pos() != 0 && openable(cur.prevAtom(), sel)) {
197                 pushRight(cur, cur.prevAtom());
198                 return true;
199         }
200
201         return cur.posLeft() || idxLeft(cur) || popLeft(cur) || selection_;
202 }
203
204
205 bool MathCursor::right(LCursor & cur, bool sel)
206 {
207         dump("Right 1");
208         autocorrect_ = false;
209         cur.bv().x_target(-1); // "no target"
210         if (inMacroMode(cur)) {
211                 macroModeClose(cur);
212                 return true;
213         }
214         selHandle(cur, sel);
215
216         if (cur.pos() != cur.lastpos() && openable(cur.nextAtom(), sel)) {
217                 pushLeft(cur, cur.nextAtom());
218                 return true;
219         }
220
221         return cur.posRight() || idxRight(cur) || popRight(cur) || selection_;
222 }
223
224
225 void MathCursor::first(LCursor & cur)
226 {
227 #warning FIXME
228         //Cursor_.clear();
229         push(cur, formula_->par());
230         cur.inset()->asMathInset()->idxFirst(cur);
231         cur.resetAnchor();
232 }
233
234
235 void MathCursor::last(LCursor & cur)
236 {
237 #warning FIXME
238         //Cursor_.clear();
239         push(cur, formula_->par());
240         cur.inset()->asMathInset()->idxLast(cur);
241         cur.resetAnchor();
242 }
243
244
245 bool positionable(CursorBase const & cursor, CursorBase const & anchor)
246 {
247         // avoid deeper nested insets when selecting
248         if (cursor.size() > anchor.size())
249                 return false;
250
251         // anchor might be deeper, should have same path then
252         for (CursorBase::size_type i = 0; i < cursor.size(); ++i)
253                 if (cursor[i].asMathInset() != anchor[i].asMathInset())
254                         return false;
255
256         // position should be ok.
257         return true;
258 }
259
260
261 void MathCursor::setScreenPos(LCursor & cur, int x, int y)
262 {
263         dump("setScreenPos 1");
264         bool res = bruteFind(cur, x, y,
265                 formula()->xlow(), formula()->xhigh(),
266                 formula()->ylow(), formula()->yhigh());
267         if (!res) {
268                 // this can happen on creation of "math-display"
269                 dump("setScreenPos 1.5");
270                 first(cur);
271         }
272         cur.bv().x_target(-1); // "no target"
273         dump("setScreenPos 2");
274 }
275
276
277
278 bool MathCursor::home(LCursor & cur, bool sel)
279 {
280         dump("home 1");
281         autocorrect_ = false;
282         selHandle(cur, sel);
283         macroModeClose(cur);
284         if (!cur.inset()->asMathInset()->idxHome(cur))
285                 return popLeft(cur);
286         dump("home 2");
287         cur.bv().x_target(-1); // "no target"
288         return true;
289 }
290
291
292 bool MathCursor::end(LCursor & cur, bool sel)
293 {
294         dump("end 1");
295         autocorrect_ = false;
296         selHandle(cur, sel);
297         macroModeClose(cur);
298         if (!cur.inset()->asMathInset()->idxEnd(cur))
299                 return popRight(cur);
300         dump("end 2");
301         cur.bv().x_target(-1); // "no target"
302         return true;
303 }
304
305
306 void MathCursor::plainErase(LCursor & cur)
307 {
308         cur.cell().erase(cur.pos());
309 }
310
311
312 void MathCursor::markInsert(LCursor & cur)
313 {
314         //lyxerr << "inserting mark" << endl;
315         cur.cell().insert(cur.pos(), MathAtom(new MathCharInset(0)));
316 }
317
318
319 void MathCursor::markErase(LCursor & cur)
320 {
321         //lyxerr << "deleting mark" << endl;
322         cur.cell().erase(cur.pos());
323 }
324
325
326 void MathCursor::plainInsert(LCursor & cur, MathAtom const & t)
327 {
328         dump("plainInsert");
329         cur.cell().insert(cur.pos(), t);
330         ++cur.pos();
331 }
332
333
334 void MathCursor::insert2(LCursor & cur, string const & str)
335 {
336         MathArray ar;
337         asArray(str, ar);
338         insert(cur, ar);
339 }
340
341
342 void MathCursor::insert(LCursor & cur, string const & str)
343 {
344         //lyxerr << "inserting '" << str << "'" << endl;
345         selClearOrDel(cur);
346         for (string::const_iterator it = str.begin(); it != str.end(); ++it)
347                 plainInsert(cur, MathAtom(new MathCharInset(*it)));
348 }
349
350
351 void MathCursor::insert(LCursor & cur, char c)
352 {
353         //lyxerr << "inserting '" << c << "'" << endl;
354         selClearOrDel(cur);
355         plainInsert(cur, MathAtom(new MathCharInset(c)));
356 }
357
358
359 void MathCursor::insert(LCursor & cur, MathAtom const & t)
360 {
361         macroModeClose(cur);
362         selClearOrDel(cur);
363         plainInsert(cur, t);
364 }
365
366
367 void MathCursor::niceInsert(LCursor & cur, string const & t)
368 {
369         MathArray ar;
370         asArray(t, ar);
371         if (ar.size() == 1)
372                 niceInsert(cur, ar[0]);
373         else
374                 insert(cur, ar);
375 }
376
377
378 void MathCursor::niceInsert(LCursor & cur, MathAtom const & t)
379 {
380         macroModeClose(cur);
381         string safe = grabAndEraseSelection(cur);
382         plainInsert(cur, t);
383         // enter the new inset and move the contents of the selection if possible
384         if (t->isActive()) {
385                 cur.posLeft();
386                 pushLeft(cur, cur.nextAtom());
387                 paste(cur, safe);
388         }
389 }
390
391
392 void MathCursor::insert(LCursor & cur, MathArray const & ar)
393 {
394         macroModeClose(cur);
395         if (selection_)
396                 eraseSelection(cur);
397         cur.cell().insert(cur.pos(), ar);
398         cur.pos() += ar.size();
399 }
400
401
402 void MathCursor::paste(LCursor & cur, string const & data)
403 {
404         dispatch(cur, FuncRequest(LFUN_PASTE, data));
405 }
406
407
408 bool MathCursor::backspace(LCursor & cur)
409 {
410         autocorrect_ = false;
411
412         if (selection_) {
413                 selDel(cur);
414                 return true;
415         }
416
417         if (cur.pos() == 0) {
418                 if (cur.inset()->asMathInset()->nargs() == 1 &&
419                           cur.depth() == 1 &&
420                           cur.lastpos() == 0)
421                         return false;
422                 pullArg(cur);
423                 return true;
424         }
425
426         if (inMacroMode(cur)) {
427                 MathUnknownInset * p = activeMacro(cur);
428                 if (p->name().size() > 1) {
429                         p->setName(p->name().substr(0, p->name().size() - 1));
430                         return true;
431                 }
432         }
433
434         if (cur.pos() != 0 && cur.prevAtom()->nargs() > 0) {
435                 // let's require two backspaces for 'big stuff' and
436                 // highlight on the first
437                 left(cur, true);
438         } else {
439                 --cur.pos();
440                 plainErase(cur);
441         }
442         return true;
443 }
444
445
446 bool MathCursor::erase(LCursor & cur)
447 {
448         autocorrect_ = false;
449         if (inMacroMode(cur))
450                 return true;
451
452         if (selection_) {
453                 selDel(cur);
454                 return true;
455         }
456
457         // delete empty cells if possible
458 #warning FIXME
459         //if (cur.cell().empty() && cur.inset()->idxDelete(cur.idx()))
460         //              return true;
461
462         // special behaviour when in last position of cell
463         if (cur.pos() == cur.lastpos()) {
464                 bool one_cell = cur.inset()->asMathInset()->nargs() == 1;
465                 if (one_cell && cur.depth() == 1 && cur.lastpos() == 0)
466                         return false;
467                 // remove markup
468                 if (one_cell)
469                         pullArg(cur);
470                 else
471                         cur.inset()->asMathInset()->idxGlue(cur.idx());
472                 return true;
473         }
474
475         if (cur.pos() != cur.lastpos() && cur.nextAtom()->nargs() > 0)
476                 right(cur, true);
477         else
478                 plainErase(cur);
479
480         return true;
481 }
482
483
484 bool MathCursor::up(LCursor & cur, bool sel)
485 {
486         dump("up 1");
487         macroModeClose(cur);
488         selHandle(cur, sel);
489 #warning FIXME
490 #if 0
491         CursorBase save = Cursor_;
492         if (goUpDown(true))
493                 return true;
494         Cursor_ = save;
495 #endif
496         autocorrect_ = false;
497         return selection_;
498 }
499
500
501 bool MathCursor::down(LCursor & cur, bool sel)
502 {
503         dump("down 1");
504         macroModeClose(cur);
505         selHandle(cur, sel);
506 #warning FIXME
507 #if 0
508         CursorBase save = Cursor_;
509         if (goUpDown(false))
510                 return true;
511         Cursor_ = save;
512 #endif
513         autocorrect_ = false;
514         return selection_;
515 }
516
517
518 void MathCursor::macroModeClose(LCursor & cur)
519 {
520         if (!inMacroMode(cur))
521                 return;
522         MathUnknownInset * p = activeMacro(cur);
523         p->finalize();
524         string s = p->name();
525         --cur.pos();
526         cur.cell().erase(cur.pos());
527
528         // do nothing if the macro name is empty
529         if (s == "\\")
530                 return;
531
532         string const name = s.substr(1);
533
534         // prevent entering of recursive macros
535         if (formula()->lyxCode() == InsetOld::MATHMACRO_CODE
536                         && formula()->getInsetName() == name)
537                 lyxerr << "can't enter recursive macro" << endl;
538
539         niceInsert(cur, createMathInset(name));
540 }
541
542
543 string MathCursor::macroName(LCursor & cur) const
544 {
545         return inMacroMode(cur) ? activeMacro(cur)->name() : string();
546 }
547
548
549 void MathCursor::selClear(LCursor & cur)
550 {
551         cur.resetAnchor();
552         cur.bv().clearSelection();
553 }
554
555
556 void MathCursor::selCopy(LCursor & cur)
557 {
558         dump("selCopy");
559         if (selection_) {
560                 theCutBuffer.push(grabSelection(cur));
561                 selection_ = false;
562         } else {
563                 //theCutBuffer.erase();
564         }
565 }
566
567
568 void MathCursor::selCut(LCursor & cur)
569 {
570         dump("selCut");
571         theCutBuffer.push(grabAndEraseSelection(cur));
572 }
573
574
575 void MathCursor::selDel(LCursor & cur)
576 {
577         dump("selDel");
578         if (selection_) {
579                 eraseSelection(cur);
580                 selection_ = false;
581         }
582 }
583
584
585 void MathCursor::selPaste(LCursor & cur, size_t n)
586 {
587         dump("selPaste");
588         selClearOrDel(cur);
589         if (n < theCutBuffer.size())
590                 paste(cur, theCutBuffer[n]);
591         //grabSelection(cur);
592         selection_ = false;
593 }
594
595
596 void MathCursor::selHandle(LCursor & cur, bool sel)
597 {
598         if (sel == selection_)
599                 return;
600         //clear();
601         cur.resetAnchor();
602         selection_ = sel;
603 }
604
605
606 void MathCursor::selStart(LCursor & cur)
607 {
608         dump("selStart 1");
609         //clear();
610         cur.resetAnchor();
611         selection_ = true;
612         dump("selStart 2");
613 }
614
615
616 void MathCursor::selClearOrDel(LCursor & cur)
617 {
618         if (lyxrc.auto_region_delete)
619                 selDel(cur);
620         else
621                 selection_ = false;
622 }
623
624
625 void MathCursor::drawSelection(PainterInfo & pi) const
626 {
627         if (!selection_)
628                 return;
629         CursorSlice i1;
630         CursorSlice i2;
631         getSelection(pi.base.bv->fullCursor(), i1, i2);
632         i1.asMathInset()->drawSelection(pi, i1.idx_, i1.pos_, i2.idx_, i2.pos_);
633 }
634
635
636 void MathCursor::handleNest(LCursor & cur, MathAtom const & a, int c)
637 {
638         MathAtom at = a;
639         asArray(grabAndEraseSelection(cur), at.nucleus()->cell(c));
640         insert(cur, at);
641         pushRight(cur, cur.prevAtom());
642 }
643
644
645 void MathCursor::getScreenPos(LCursor & cur, int & x, int & y) const
646 {
647         cur.inset()->asMathInset()->getScreenPos(cur.idx(), cur.pos(), x, y);
648 }
649
650
651 int MathCursor::targetX(LCursor & cur) const
652 {
653         if (cur.bv().x_target() != -1)
654                 return cur.bv().x_target();
655         int x = 0;
656         int y = 0;
657         getScreenPos(cur, x, y);
658         return x;
659 }
660
661
662 InsetFormulaBase * MathCursor::formula() const
663 {
664         return formula_;
665 }
666
667
668 void MathCursor::adjust(LCursor & cur, pos_type from, difference_type diff)
669 {       
670         if (cur.pos() > from)
671                 cur.pos() += diff;
672 #warning FIXME
673 #if 0
674         if (Anchor_.back().pos_ > from)
675                 Anchor_.back().pos_ += diff;
676         // just to be on the safe side
677         // theoretically unecessary
678 #endif
679         normalize(cur);
680 }
681
682
683 bool MathCursor::inMacroMode(LCursor & cur) const
684 {
685         if (!cur.pos() != 0)
686                 return false;
687         MathUnknownInset const * p = cur.prevAtom()->asUnknownInset();
688         return p && !p->final();
689 }
690
691
692 MathUnknownInset * MathCursor::activeMacro(LCursor & cur)
693 {
694         return inMacroMode(cur) ? cur.prevAtom().nucleus()->asUnknownInset() : 0;
695 }
696
697
698 MathUnknownInset const * MathCursor::activeMacro(LCursor & cur) const
699 {
700         return inMacroMode(cur) ? cur.prevAtom()->asUnknownInset() : 0;
701 }
702
703
704 bool MathCursor::inMacroArgMode(LCursor & cur) const
705 {
706         return cur.pos() > 0 && cur.prevAtom()->getChar() == '#';
707 }
708
709
710 bool MathCursor::selection() const
711 {
712         return selection_;
713 }
714
715
716 MathGridInset *
717 MathCursor::enclosingGrid(LCursor & cur, MathCursor::idx_type & idx) const
718 {
719         for (MathInset::difference_type i = cur.depth() - 1; i >= 0; --i) {
720                 MathInset * m = cur.cursor_[i].inset()->asMathInset();
721                 if (!m)
722                         return 0;
723                 MathGridInset * p = m->asGridInset();
724                 if (p) {
725                         idx = cur.cursor_[i].idx_;
726                         return p;
727                 }
728         }
729         return 0;
730 }
731
732
733 void MathCursor::popToHere(LCursor & cur, MathInset const * p)
734 {
735         while (cur.depth() && cur.inset()->asMathInset() != p)
736                 cur.pop();
737 }
738
739
740 void MathCursor::popToEnclosingGrid(LCursor & cur)
741 {
742         while (cur.depth() && !cur.inset()->asMathInset()->asGridInset())
743                 cur.pop();
744 }
745
746
747 void MathCursor::popToEnclosingHull(LCursor & cur)
748 {
749         while (cur.depth() && !cur.inset()->asMathInset()->asGridInset())
750                 cur.pop();
751 }
752
753
754 void MathCursor::pullArg(LCursor & cur)
755 {
756         dump("pullarg");
757         MathArray ar = cur.cell();
758         if (popLeft(cur)) {
759                 plainErase(cur);
760                 cur.cell().insert(cur.pos(), ar);
761                 cur.resetAnchor();
762         } else {
763                 formula()->mutateToText();
764         }
765 }
766
767
768 void MathCursor::touch()
769 {
770 #warning
771 #if 0
772         CursorBase::const_iterator it = Cursor_.begin();
773         CursorBase::const_iterator et = Cursor_.end();
774         for ( ; it != et; ++it)
775                 it->cell().touch();
776 #endif
777 }
778
779
780 void MathCursor::normalize(LCursor & cur)
781 {
782         if (cur.idx() >= cur.nargs()) {
783                 lyxerr << "this should not really happen - 1: "
784                        << cur.idx() << ' ' << cur.nargs()
785                        << " in: " << cur.inset() << endl;
786                 dump("error 2");
787         }
788         cur.idx() = min(cur.idx(), cur.nargs() - 1);
789
790         if (cur.pos() > cur.lastpos()) {
791                 lyxerr << "this should not really happen - 2: "
792                         << cur.pos() << ' ' << cur.lastpos() <<  " in idx: " << cur.idx()
793                        << " in atom: '";
794                 WriteStream wi(lyxerr, false, true);
795                 cur.inset()->asMathInset()->write(wi);
796                 lyxerr << endl;
797                 dump("error 4");
798         }
799         cur.pos() = min(cur.pos(), cur.lastpos());
800 }
801
802
803 void MathCursor::idxNext(LCursor & cur)
804 {
805         cur.inset()->asMathInset()->idxNext(cur);
806 }
807
808
809 void MathCursor::idxPrev(LCursor & cur)
810 {
811         cur.inset()->asMathInset()->idxPrev(cur);
812 }
813
814
815 char MathCursor::valign(LCursor & cur) const
816 {
817         idx_type idx;
818         MathGridInset * p = enclosingGrid(cur, idx);
819         return p ? p->valign() : '\0';
820 }
821
822
823 char MathCursor::halign(LCursor & cur) const
824 {
825         idx_type idx;
826         MathGridInset * p = enclosingGrid(cur, idx);
827         return p ? p->halign(idx % p->ncols()) : '\0';
828 }
829
830
831 void MathCursor::getSelection(LCursor & cur,
832         CursorSlice & i1, CursorSlice & i2) const
833 {
834         CursorSlice anc = normalAnchor(cur);
835         if (anc < cur.top()) {
836                 i1 = anc;
837                 i2 = cur.top();
838         } else {
839                 i1 = cur.top();
840                 i2 = anc;
841         }
842 }
843
844
845 bool MathCursor::goUpDown(LCursor & cur, bool up)
846 {
847         // Be warned: The 'logic' implemented in this function is highly fragile.
848         // A distance of one pixel or a '<' vs '<=' _really_ matters.
849         // So fiddle around with it only if you know what you are doing!
850   int xo = 0;
851         int yo = 0;
852         getScreenPos(cur, xo, yo);
853
854         // check if we had something else in mind, if not, this is the future goal
855         if (cur.bv().x_target() == -1)
856                 cur.bv().x_target(xo);
857         else
858                 xo = cur.bv().x_target();
859
860         // try neigbouring script insets
861         if (!selection()) {
862                 // try left
863                 if (cur.pos() != 0) {
864                         MathScriptInset const * p = cur.prevAtom()->asScriptInset();
865                         if (p && p->has(up)) {
866                                 --cur.pos();
867                                 push(cur, cur.nextAtom());
868                                 cur.idx() = up; // the superscript has index 1
869                                 cur.pos() = cur.lastpos();
870                                 //lyxerr << "updown: handled by scriptinset to the left" << endl;
871                                 return true;
872                         }
873                 }
874
875                 // try right
876                 if (cur.pos() != cur.lastpos()) {
877                         MathScriptInset const * p = cur.nextAtom()->asScriptInset();
878                         if (p && p->has(up)) {
879                                 push(cur, cur.nextAtom());
880                                 cur.idx() = up;
881                                 cur.pos() = 0;
882                                 //lyxerr << "updown: handled by scriptinset to the right" << endl;
883                                 return true;
884                         }
885                 }
886         }
887
888         // try current cell for e.g. text insets
889         if (cur.inset()->asMathInset()->idxUpDown2(cur, up, cur.bv().x_target()))
890                 return true;
891
892         //xarray().boundingBox(xlow, xhigh, ylow, yhigh);
893         //if (up)
894         //      yhigh = yo - 4;
895         //else
896         //      ylow = yo + 4;
897         //if (bruteFind(xo, yo, xlow, xhigh, ylow, yhigh)) {
898         //      lyxerr << "updown: handled by brute find in the same cell" << endl;
899         //      return true;
900         //}
901
902         // try to find an inset that knows better then we
903         while (1) {
904                 //lyxerr << "updown: We are in " << inset() << " idx: " << idx() << endl;
905                 // ask inset first
906                 if (cur.inset()->asMathInset()->idxUpDown(cur, up, cur.bv().x_target())) {
907                         // try to find best position within this inset
908                         if (!selection())
909                                 bruteFind2(cur, xo, yo);
910                         return true;
911                 }
912
913                 // no such inset found, just take something "above"
914                 //lyxerr << "updown: handled by strange case" << endl;
915                 if (!popLeft(cur))
916                         return
917                                 bruteFind(cur, xo, yo,
918                                         formula()->xlow(),
919                                         formula()->xhigh(),
920                                         up ? formula()->ylow() : yo + 4,
921                                         up ? yo - 4 : formula()->yhigh()
922                                 );
923
924                 // any improvement so far?
925                 int xnew, ynew;
926                 getScreenPos(cur, xnew, ynew);
927                 if (up ? ynew < yo : ynew > yo)
928                         return true;
929         }
930 }
931
932
933 bool MathCursor::bruteFind
934         (LCursor & cur, int x, int y, int xlow, int xhigh, int ylow, int yhigh)
935 {
936         CursorBase best_cursor;
937         double best_dist = 1e10;
938
939         CursorBase it = ibegin(formula()->par().nucleus());
940         CursorBase et = iend(formula()->par().nucleus());
941         while (1) {
942                 // avoid invalid nesting when selecting
943                 if (!selection_ || positionable(it, cur.anchor_)) {
944                         int xo, yo;
945                         it.back().getScreenPos(xo, yo);
946                         if (xlow <= xo && xo <= xhigh && ylow <= yo && yo <= yhigh) {
947                                 double d = (x - xo) * (x - xo) + (y - yo) * (y - yo);
948                                 //lyxerr << "x: " << x << " y: " << y << " d: " << endl;
949                                 // '<=' in order to take the last possible position
950                                 // this is important for clicking behind \sum in e.g. '\sum_i a'
951                                 if (d <= best_dist) {
952                                         best_dist   = d;
953                                         best_cursor = it;
954                                 }
955                         }
956                 }
957
958                 if (it == et)
959                         break;
960                 increment(it);
961         }
962
963         if (best_dist < 1e10)
964                 cur.cursor_ = best_cursor;
965         return best_dist < 1e10;
966 }
967
968
969 void MathCursor::bruteFind2(LCursor & cur, int x, int y)
970 {
971         double best_dist = 1e10;
972
973         CursorBase it = cur.cursor_;
974         it.back().pos(0);
975         CursorBase et = cur.cursor_;
976         int n = et.back().asMathInset()->cell(et.back().idx_).size();
977         et.back().pos(n);
978         for (int i = 0; ; ++i) {
979                 int xo, yo;
980                 it.back().getScreenPos(xo, yo);
981                 double d = (x - xo) * (x - xo) + (y - yo) * (y - yo);
982                 // '<=' in order to take the last possible position
983                 // this is important for clicking behind \sum in e.g. '\sum_i a'
984                 lyxerr << "i: " << i << " d: " << d << " best: " << best_dist << endl;
985                 if (d <= best_dist) {
986                         best_dist = d;
987                         cur.cursor_ = it;
988                 }
989                 if (it == et)
990                         break;
991                 increment(it);
992         }
993 }
994
995
996 bool MathCursor::idxLineLast(LCursor & cur)
997 {
998         cur.idx() -= cur.idx() % cur.ncols();
999         cur.idx() += cur.ncols() - 1;
1000         cur.pos() = cur.lastpos();
1001         return true;
1002 }
1003
1004
1005 bool MathCursor::idxLeft(LCursor & cur)
1006 {
1007         return cur.inset()->asMathInset()->idxLeft(cur);
1008 }
1009
1010
1011 bool MathCursor::idxRight(LCursor & cur)
1012 {
1013         return cur.inset()->asMathInset()->idxRight(cur);
1014 }
1015
1016
1017 bool MathCursor::script(LCursor & cur, bool up)
1018 {
1019         // Hack to get \\^ and \\_ working
1020         if (inMacroMode(cur) && macroName(cur) == "\\") {
1021                 if (up)
1022                         niceInsert(cur, createMathInset("mathcircumflex"));
1023                 else
1024                         interpret(cur, '_');
1025                 return true;
1026         }
1027
1028         macroModeClose(cur);
1029         string safe = grabAndEraseSelection(cur);
1030         if (inNucleus(cur)) {
1031                 // we are in a nucleus of a script inset, move to _our_ script
1032                 cur.inset()->asMathInset()->asScriptInset()->ensure(up);
1033                 cur.idx() = up;
1034                 cur.pos() = 0;
1035         } else if (cur.pos() != 0 && cur.prevAtom()->asScriptInset()) {
1036                 cur.prevAtom().nucleus()->asScriptInset()->ensure(up);
1037                 pushRight(cur, cur.prevAtom());
1038                 cur.idx() = up;
1039                 cur.pos() = cur.lastpos();
1040         } else if (cur.pos() != 0) {
1041                 --cur.pos();
1042                 cur.cell()[cur.pos()]
1043                         = MathAtom(new MathScriptInset(cur.nextAtom(), up));
1044                 pushLeft(cur, cur.nextAtom());
1045                 cur.idx() = up;
1046                 cur.pos() = 0;
1047         } else {
1048                 plainInsert(cur, MathAtom(new MathScriptInset(up)));
1049                 cur.prevAtom().nucleus()->asScriptInset()->ensure(up);
1050                 pushRight(cur, cur.prevAtom());
1051                 cur.idx() = up;
1052                 cur.pos() = 0;
1053         }
1054         paste(cur, safe);
1055         dump("1");
1056         return true;
1057 }
1058
1059
1060 bool MathCursor::interpret(LCursor & cur, char c)
1061 {
1062         //lyxerr << "interpret 2: '" << c << "'" << endl;
1063         cur.bv().x_target(-1); // "no target"
1064         if (inMacroArgMode(cur)) {
1065                 --cur.pos();
1066                 plainErase(cur);
1067                 int n = c - '0';
1068                 MathMacroTemplate const * p = formula()->par()->asMacroTemplate();
1069                 if (p && 1 <= n && n <= p->numargs())
1070                         insert(cur, MathAtom(new MathMacroArgument(c - '0')));
1071                 else {
1072                         insert(cur, createMathInset("#"));
1073                         interpret(cur, c); // try again
1074                 }
1075                 return true;
1076         }
1077
1078         // handle macroMode
1079         if (inMacroMode(cur)) {
1080                 string name = macroName(cur);
1081                 //lyxerr << "interpret name: '" << name << "'" << endl;
1082
1083                 if (isalpha(c)) {
1084                         activeMacro(cur)->setName(activeMacro(cur)->name() + c);
1085                         return true;
1086                 }
1087
1088                 // handle 'special char' macros
1089                 if (name == "\\") {
1090                         // remove the '\\'
1091                         backspace(cur);
1092                         if (c == '\\') {
1093                                 if (currentMode(cur) == MathInset::TEXT_MODE)
1094                                         niceInsert(cur, createMathInset("textbackslash"));
1095                                 else
1096                                         niceInsert(cur, createMathInset("backslash"));
1097                         } else if (c == '{') {
1098                                 niceInsert(cur, MathAtom(new MathBraceInset));
1099                         } else {
1100                                 niceInsert(cur, createMathInset(string(1, c)));
1101                         }
1102                         return true;
1103                 }
1104
1105                 // leave macro mode and try again if necessary
1106                 macroModeClose(cur);
1107                 if (c == '{')
1108                         niceInsert(cur, MathAtom(new MathBraceInset));
1109                 else if (c != ' ')
1110                         interpret(cur, c);
1111                 return true;
1112         }
1113
1114         // This is annoying as one has to press <space> far too often.
1115         // Disable it.
1116
1117         if (0) {
1118                 // leave autocorrect mode if necessary
1119                 if (autocorrect_ && c == ' ') {
1120                         autocorrect_ = false;
1121                         return true;
1122                 }
1123         }
1124
1125         // just clear selection on pressing the space bar
1126         if (selection_ && c == ' ') {
1127                 selection_ = false;
1128                 return true;
1129         }
1130
1131         selClearOrDel(cur);
1132
1133         if (c == '\\') {
1134                 //lyxerr << "starting with macro" << endl;
1135                 insert(cur, MathAtom(new MathUnknownInset("\\", false)));
1136                 return true;
1137         }
1138
1139         if (c == '\n') {
1140                 if (currentMode(cur) == MathInset::TEXT_MODE)
1141                         insert(cur, c);
1142                 return true;
1143         }
1144
1145         if (c == ' ') {
1146                 if (currentMode(cur) == MathInset::TEXT_MODE) {
1147                         // insert spaces in text mode,
1148                         // but suppress direct insertion of two spaces in a row
1149                         // the still allows typing  '<space>a<space>' and deleting the 'a', but
1150                         // it is better than nothing...
1151                         if (!cur.pos() != 0 || cur.prevAtom()->getChar() != ' ')
1152                                 insert(cur, c);
1153                         return true;
1154                 }
1155                 if (cur.pos() != 0 && cur.prevAtom()->asSpaceInset()) {
1156                         cur.prevAtom().nucleus()->asSpaceInset()->incSpace();
1157                         return true;
1158                 }
1159                 if (popRight(cur))
1160                         return true;
1161                 // if are at the very end, leave the formula
1162                 return cur.pos() != cur.lastpos();
1163         }
1164
1165         if (c == '_') {
1166                 script(cur, false);
1167                 return true;
1168         }
1169
1170         if (c == '^') {
1171                 script(cur, true);
1172                 return true;
1173         }
1174
1175         if (c == '{' || c == '}' || c == '#' || c == '&' || c == '$') {
1176                 niceInsert(cur, createMathInset(string(1, c)));
1177                 return true;
1178         }
1179
1180         if (c == '%') {
1181                 niceInsert(cur, MathAtom(new MathCommentInset));
1182                 return true;
1183         }
1184
1185         // try auto-correction
1186         //if (autocorrect_ && hasPrevAtom() && math_autocorrect(prevAtom(), c))
1187         //      return true;
1188
1189         // no special circumstances, so insert the character without any fuss
1190         insert(cur, c);
1191         autocorrect_ = true;
1192         return true;
1193 }
1194
1195
1196 void MathCursor::setSelection
1197         (LCursor & cur, CursorBase const & where, size_t n)
1198 {
1199         selection_ = true;
1200         cur.cursor_ = where;
1201         cur.anchor_ = where;
1202         cur.pos() += n;
1203 }
1204
1205
1206 void MathCursor::insetToggle(LCursor & cur)
1207 {
1208         if (cur.pos() != cur.lastpos()) {
1209                 // toggle previous inset ...
1210                 cur.nextAtom().nucleus()->lock(!cur.nextAtom()->lock());
1211         } else if (popLeft(cur) && cur.pos() != cur.lastpos()) {
1212                 // ... or enclosing inset if we are in the last inset position
1213                 cur.nextAtom().nucleus()->lock(!cur.nextAtom()->lock());
1214                 cur.posRight();
1215         }
1216 }
1217
1218
1219 string MathCursor::info(LCursor & cur) const
1220 {
1221         ostringstream os;
1222         os << "Math editor mode.  ";
1223         for (int i = 0, n = cur.depth(); i < n; ++i) {
1224                 cur.cursor_[i].asMathInset()->infoize(os);
1225                 os << "  ";
1226         }
1227         if (cur.pos() != 0)
1228                 cur.prevAtom()->infoize2(os);
1229         os << "                    ";
1230         return os.str();
1231 }
1232
1233
1234 namespace {
1235
1236 void region(CursorSlice const & i1, CursorSlice const & i2,
1237         MathInset::row_type & r1, MathInset::row_type & r2,
1238         MathInset::col_type & c1, MathInset::col_type & c2)
1239 {
1240         MathInset * p = i1.asMathInset();
1241         c1 = p->col(i1.idx_);
1242         c2 = p->col(i2.idx_);
1243         if (c1 > c2)
1244                 swap(c1, c2);
1245         r1 = p->row(i1.idx_);
1246         r2 = p->row(i2.idx_);
1247         if (r1 > r2)
1248                 swap(r1, r2);
1249 }
1250
1251 }
1252
1253
1254 string MathCursor::grabSelection(LCursor & cur) const
1255 {
1256         if (!selection_)
1257                 return string();
1258
1259         CursorSlice i1;
1260         CursorSlice i2;
1261         getSelection(cur, i1, i2);
1262
1263         if (i1.idx_ == i2.idx_) {
1264                 MathArray::const_iterator it = i1.cell().begin();
1265                 return asString(MathArray(it + i1.pos_, it + i2.pos_));
1266         }
1267
1268         row_type r1, r2;
1269         col_type c1, c2;
1270         region(i1, i2, r1, r2, c1, c2);
1271
1272         string data;
1273         for (row_type row = r1; row <= r2; ++row) {
1274                 if (row > r1)
1275                         data += "\\\\";
1276                 for (col_type col = c1; col <= c2; ++col) {
1277                         if (col > c1)
1278                                 data += '&';
1279                         data += asString(i1.asMathInset()->cell(i1.asMathInset()->index(row, col)));
1280                 }
1281         }
1282         return data;
1283 }
1284
1285
1286 void MathCursor::eraseSelection(LCursor & cur)
1287 {
1288         CursorSlice i1;
1289         CursorSlice i2;
1290         getSelection(cur, i1, i2);
1291         if (i1.idx_ == i2.idx_)
1292                 i1.cell().erase(i1.pos_, i2.pos_);
1293         else {
1294                 MathInset * p = i1.asMathInset();
1295                 row_type r1, r2;
1296                 col_type c1, c2;
1297                 region(i1, i2, r1, r2, c1, c2);
1298                 for (row_type row = r1; row <= r2; ++row)
1299                         for (col_type col = c1; col <= c2; ++col)
1300                                 p->cell(p->index(row, col)).clear();
1301         }
1302         cur.top() = i1;
1303 }
1304
1305
1306 string MathCursor::grabAndEraseSelection(LCursor & cur)
1307 {
1308         if (!selection_)
1309                 return string();
1310         string res = grabSelection(cur);
1311         eraseSelection(cur);
1312         selection_ = false;
1313         return res;
1314 }
1315
1316
1317 CursorSlice MathCursor::normalAnchor(LCursor & cur) const
1318 {
1319 #warning FIXME
1320 #if 0
1321         if (Anchor_.size() < cur.depth()) {
1322                 cur.resetAnchor();
1323                 lyxerr << "unusual Anchor size" << endl;
1324         }
1325         //lyx::BOOST_ASSERT(Anchor_.size() >= cursor.cur.depth());
1326         // use Anchor on the same level as Cursor
1327         CursorSlice normal = Anchor_[cur.depth() - 1];
1328         if (cur.depth() < Anchor_.size() && !(normal < cursor())) {
1329                 // anchor is behind cursor -> move anchor behind the inset
1330                 ++normal.pos_;
1331         }
1332         return normal;
1333 #else
1334         return cur.top();
1335 #endif
1336 }
1337
1338
1339 DispatchResult MathCursor::dispatch(LCursor &, FuncRequest const & cmd)
1340 {
1341         // mouse clicks are somewhat special
1342         // check
1343         switch (cmd.action) {
1344                 case LFUN_MOUSE_PRESS:
1345                 case LFUN_MOUSE_MOTION:
1346                 case LFUN_MOUSE_RELEASE:
1347                 case LFUN_MOUSE_DOUBLE: {
1348 /*
1349                         CursorSlice & pos = Cursor_.back();
1350                         int x = 0;
1351                         int y = 0;
1352                         getScreenPos(x, y);
1353                         if (x < cmd.x && hasPrevAtom()) {
1354                                 DispatchResult const res =
1355                                         prevAtom().nucleus()->dispatch(cur, cmd);
1356                                 if (res.dispatched())
1357                                         return res;
1358                         }
1359                         if (x > cmd.x && hasNextAtom()) {
1360                                 DispatchResult const res =
1361                                         nextAtom().nucleus()->dispatch(cur, cmd);
1362                                 if (res.dispatched())
1363                                         return res;
1364                         }
1365 */
1366                 }
1367                 default:
1368                         break;
1369         }
1370
1371 /*
1372         for (int i = cur.depth() - 1; i >= 0; --i) {
1373                 CursorBase tmp = cur->cursor_;
1374                 CursorSlice & pos = tmp.back()
1375                 DispatchResult const res = pos.asMathInset()->dispatch(cur, cmd);
1376                 if (res.dispatched()) {
1377                         if (res.val() == FINISHED) {
1378                                 if (i + 1 < Cursor_.size())
1379                                         Cursor_.erase(Cursor_.begin() + i + 1, Cursor_.end());
1380                                 selClear();
1381                         }
1382                         return res;
1383                 }
1384         }
1385 */
1386         return DispatchResult(false);
1387 }
1388
1389
1390 MathInset::mode_type MathCursor::currentMode(LCursor &) const
1391 {
1392 #if 0
1393         for (int i = Cursor_.size() - 1; i >= 0; --i) {
1394                 MathInset::mode_type res = Cursor_[i].asMathInset()->currentMode();
1395                 if (res != MathInset::UNDECIDED_MODE)
1396                         return res;
1397         }
1398 #endif
1399         return MathInset::UNDECIDED_MODE;
1400 }
1401
1402
1403 void MathCursor::handleFont(LCursor & cur, string const & font)
1404 {
1405         string safe;
1406         if (selection()) {
1407                 macroModeClose(cur);
1408                 safe = grabAndEraseSelection(cur);
1409         }
1410
1411         if (cur.lastpos() != 0) {
1412                 // something left in the cell
1413                 if (cur.pos() == 0) {
1414                         // cursor in first position
1415                         popLeft(cur);
1416                 } else if (cur.pos() == cur.lastpos()) {
1417                         // cursor in last position
1418                         popRight(cur);
1419                 } else {
1420                         // cursor in between. split cell
1421                         MathArray::iterator bt = cur.cell().begin();
1422                         MathAtom at = createMathInset(font);
1423                         at.nucleus()->cell(0) = MathArray(bt, bt + cur.pos());
1424                         cur.cell().erase(bt, bt + cur.pos());
1425                         popLeft(cur);
1426                         plainInsert(cur, at);
1427                 }
1428         } else {
1429                 // nothing left in the cell
1430                 pullArg(cur);
1431                 plainErase(cur);
1432         }
1433         insert(cur, safe);
1434 }
1435
1436
1437 void releaseMathCursor(BufferView & bv)
1438 {
1439         if (mathcursor) {
1440                 InsetFormulaBase * f = mathcursor->formula();
1441                 delete mathcursor;
1442                 mathcursor = 0;
1443                 f->insetUnlock(bv);
1444         }
1445 }