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