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