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