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