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