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