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