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