]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
refactoring visual cursor movement functions
[lyx.git] / src / Text2.cpp
1 /**
2  * \file text2.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Alfredo Braunstein
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Allan Rae
14  * \author Stefan Schimanski
15  * \author Dekel Tsur
16  * \author Jürgen Vigna
17  *
18  * Full author contact details are available in file CREDITS.
19  */
20
21 #include <config.h>
22
23 #include "Text.h"
24
25 #include "Bidi.h"
26 #include "Buffer.h"
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
31 #include "Changes.h"
32 #include "Cursor.h"
33 #include "CutAndPaste.h"
34 #include "DispatchResult.h"
35 #include "ErrorList.h"
36 #include "FuncRequest.h"
37 #include "Language.h"
38 #include "Layout.h"
39 #include "Lexer.h"
40 #include "LyXFunc.h"
41 #include "LyXRC.h"
42 #include "Paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "TextClass.h"
46 #include "TextMetrics.h"
47 #include "VSpace.h"
48
49 #include "insets/InsetEnvironment.h"
50
51 #include "mathed/InsetMathHull.h"
52
53 #include "support/lassert.h"
54 #include "support/debug.h"
55 #include "support/gettext.h"
56 #include "support/textutils.h"
57
58 #include <boost/next_prior.hpp>
59
60 #include <sstream>
61
62 using namespace std;
63
64 namespace lyx {
65
66 Text::Text()
67         : autoBreakRows_(false)
68 {}
69
70
71 bool Text::isMainText(Buffer const & buffer) const
72 {
73         return &buffer.text() == this;
74 }
75
76
77 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
78 {
79         Layout const & layout = pars_[pit].layout();
80
81         if (!pars_[pit].getDepth())  {
82                 FontInfo lf = layout.resfont;
83                 // In case the default family has been customized
84                 if (layout.font.family() == INHERIT_FAMILY)
85                         lf.setFamily(buffer.params().getFont().fontInfo().family());
86                 return lf;
87         }
88
89         FontInfo font = layout.font;
90         // Realize with the fonts of lesser depth.
91         //font.realize(outerFont(pit, paragraphs()));
92         font.realize(buffer.params().getFont().fontInfo());
93
94         return font;
95 }
96
97
98 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
99 {
100         Layout const & layout = par.layout();
101
102         if (!par.getDepth()) {
103                 FontInfo lf = layout.reslabelfont;
104                 // In case the default family has been customized
105                 if (layout.labelfont.family() == INHERIT_FAMILY)
106                         lf.setFamily(buffer.params().getFont().fontInfo().family());
107                 return lf;
108         }
109
110         FontInfo font = layout.labelfont;
111         // Realize with the fonts of lesser depth.
112         font.realize(buffer.params().getFont().fontInfo());
113
114         return font;
115 }
116
117
118 void Text::setCharFont(Buffer const & buffer, pit_type pit,
119                 pos_type pos, Font const & fnt, Font const & display_font)
120 {
121         Font font = fnt;
122         Layout const & layout = pars_[pit].layout();
123
124         // Get concrete layout font to reduce against
125         FontInfo layoutfont;
126
127         if (pos < pars_[pit].beginOfBody())
128                 layoutfont = layout.labelfont;
129         else
130                 layoutfont = layout.font;
131
132         // Realize against environment font information
133         if (pars_[pit].getDepth()) {
134                 pit_type tp = pit;
135                 while (!layoutfont.resolved() &&
136                        tp != pit_type(paragraphs().size()) &&
137                        pars_[tp].getDepth()) {
138                         tp = outerHook(tp, paragraphs());
139                         if (tp != pit_type(paragraphs().size()))
140                                 layoutfont.realize(pars_[tp].layout().font);
141                 }
142         }
143
144         // Inside inset, apply the inset's font attributes if any
145         // (charstyle!)
146         if (!isMainText(buffer))
147                 layoutfont.realize(display_font.fontInfo());
148
149         layoutfont.realize(buffer.params().getFont().fontInfo());
150
151         // Now, reduce font against full layout font
152         font.fontInfo().reduce(layoutfont);
153
154         pars_[pit].setFont(pos, font);
155 }
156
157
158 void Text::setInsetFont(BufferView const & bv, pit_type pit,
159                 pos_type pos, Font const & font, bool toggleall)
160 {
161         Inset * const inset = pars_[pit].getInset(pos);
162         LASSERT(inset && inset->noFontChange(), /**/);
163
164         CursorSlice::idx_type endidx = inset->nargs();
165         for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
166                 Text * text = cs.text();
167                 if (text) {
168                         // last position of the cell
169                         CursorSlice cellend = cs;
170                         cellend.pit() = cellend.lastpit();
171                         cellend.pos() = cellend.lastpos();
172                         text->setFont(bv, cs, cellend, font, toggleall);
173                 }
174         }
175 }
176
177
178 // return past-the-last paragraph influenced by a layout change on pit
179 pit_type Text::undoSpan(pit_type pit)
180 {
181         pit_type end = paragraphs().size();
182         pit_type nextpit = pit + 1;
183         if (nextpit == end)
184                 return nextpit;
185         //because of parindents
186         if (!pars_[pit].getDepth())
187                 return boost::next(nextpit);
188         //because of depth constrains
189         for (; nextpit != end; ++pit, ++nextpit) {
190                 if (!pars_[pit].getDepth())
191                         break;
192         }
193         return nextpit;
194 }
195
196
197 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
198                 docstring const & layout)
199 {
200         LASSERT(start != end, /**/);
201
202         BufferParams const & bufparams = buffer.params();
203         Layout const & lyxlayout = bufparams.documentClass()[layout];
204
205         for (pit_type pit = start; pit != end; ++pit) {
206                 Paragraph & par = pars_[pit];
207                 par.applyLayout(lyxlayout);
208                 if (lyxlayout.margintype == MARGIN_MANUAL)
209                         par.setLabelWidthString(par.translateIfPossible(
210                                 lyxlayout.labelstring(), buffer.params()));
211         }
212 }
213
214
215 // set layout over selection and make a total rebreak of those paragraphs
216 void Text::setLayout(Cursor & cur, docstring const & layout)
217 {
218         LASSERT(this == cur.text(), /**/);
219         // special handling of new environment insets
220         BufferView & bv = cur.bv();
221         BufferParams const & params = bv.buffer().params();
222         Layout const & lyxlayout = params.documentClass()[layout];
223         if (lyxlayout.is_environment) {
224                 // move everything in a new environment inset
225                 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
226                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
227                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
228                 lyx::dispatch(FuncRequest(LFUN_CUT));
229                 Inset * inset = new InsetEnvironment(bv.buffer(), layout);
230                 insertInset(cur, inset);
231                 //inset->edit(cur, true);
232                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
233                 return;
234         }
235
236         pit_type start = cur.selBegin().pit();
237         pit_type end = cur.selEnd().pit() + 1;
238         pit_type undopit = undoSpan(end - 1);
239         recUndo(cur, start, undopit - 1);
240         setLayout(cur.buffer(), start, end, layout);
241         updateLabels(cur.buffer());
242 }
243
244
245 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
246                         Paragraph const & par, int max_depth)
247 {
248         if (par.layout().labeltype == LABEL_BIBLIO)
249                 return false;
250         int const depth = par.params().depth();
251         if (type == Text::INC_DEPTH && depth < max_depth)
252                 return true;
253         if (type == Text::DEC_DEPTH && depth > 0)
254                 return true;
255         return false;
256 }
257
258
259 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
260 {
261         LASSERT(this == cur.text(), /**/);
262         // this happens when selecting several cells in tabular (bug 2630)
263         if (cur.selBegin().idx() != cur.selEnd().idx())
264                 return false;
265
266         pit_type const beg = cur.selBegin().pit();
267         pit_type const end = cur.selEnd().pit() + 1;
268         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
269
270         for (pit_type pit = beg; pit != end; ++pit) {
271                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
272                         return true;
273                 max_depth = pars_[pit].getMaxDepthAfter();
274         }
275         return false;
276 }
277
278
279 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
280 {
281         LASSERT(this == cur.text(), /**/);
282         pit_type const beg = cur.selBegin().pit();
283         pit_type const end = cur.selEnd().pit() + 1;
284         cur.recordUndoSelection();
285         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
286
287         for (pit_type pit = beg; pit != end; ++pit) {
288                 Paragraph & par = pars_[pit];
289                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
290                         int const depth = par.params().depth();
291                         if (type == INC_DEPTH)
292                                 par.params().depth(depth + 1);
293                         else
294                                 par.params().depth(depth - 1);
295                 }
296                 max_depth = par.getMaxDepthAfter();
297         }
298         // this handles the counter labels, and also fixes up
299         // depth values for follow-on (child) paragraphs
300         updateLabels(cur.buffer());
301 }
302
303
304 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
305 {
306         LASSERT(this == cur.text(), /**/);
307         // Set the current_font
308         // Determine basis font
309         FontInfo layoutfont;
310         pit_type pit = cur.pit();
311         if (cur.pos() < pars_[pit].beginOfBody())
312                 layoutfont = labelFont(cur.buffer(), pars_[pit]);
313         else
314                 layoutfont = layoutFont(cur.buffer(), pit);
315
316         // Update current font
317         cur.real_current_font.update(font,
318                                         cur.buffer().params().language,
319                                         toggleall);
320
321         // Reduce to implicit settings
322         cur.current_font = cur.real_current_font;
323         cur.current_font.fontInfo().reduce(layoutfont);
324         // And resolve it completely
325         cur.real_current_font.fontInfo().realize(layoutfont);
326
327         // if there is no selection that's all we need to do
328         if (!cur.selection())
329                 return;
330
331         // Ok, we have a selection.
332         cur.recordUndoSelection();
333
334         setFont(cur.bv(), cur.selectionBegin().top(), 
335                 cur.selectionEnd().top(), font, toggleall);
336 }
337
338
339 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
340                 CursorSlice const & end, Font const & font,
341                 bool toggleall)
342 {
343         Buffer const & buffer = bv.buffer();
344
345         // Don't use forwardChar here as ditend might have
346         // pos() == lastpos() and forwardChar would miss it.
347         // Can't use forwardPos either as this descends into
348         // nested insets.
349         Language const * language = buffer.params().language;
350         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
351                 if (dit.pos() == dit.lastpos())
352                         continue;
353                 pit_type const pit = dit.pit();
354                 pos_type const pos = dit.pos();
355                 Inset * inset = pars_[pit].getInset(pos);
356                 if (inset && inset->noFontChange()) {
357                         // We need to propagate the font change to all
358                         // text cells of the inset (bug 1973).
359                         // FIXME: This should change, see documentation
360                         // of noFontChange in Inset.h
361                         setInsetFont(bv, pit, pos, font, toggleall);
362                 }
363                 TextMetrics const & tm = bv.textMetrics(this);
364                 Font f = tm.displayFont(pit, pos);
365                 f.update(font, language, toggleall);
366                 setCharFont(buffer, pit, pos, f, tm.font_);
367         }
368 }
369
370
371 bool Text::cursorTop(Cursor & cur)
372 {
373         LASSERT(this == cur.text(), /**/);
374         return setCursor(cur, 0, 0);
375 }
376
377
378 bool Text::cursorBottom(Cursor & cur)
379 {
380         LASSERT(this == cur.text(), /**/);
381         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
382 }
383
384
385 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
386 {
387         LASSERT(this == cur.text(), /**/);
388         // If the mask is completely neutral, tell user
389         if (font.fontInfo() == ignore_font && 
390                 (font.language() == 0 || font.language() == ignore_language)) {
391                 // Could only happen with user style
392                 cur.message(_("No font change defined."));
393                 return;
394         }
395
396         // Try implicit word selection
397         // If there is a change in the language the implicit word selection
398         // is disabled.
399         CursorSlice resetCursor = cur.top();
400         bool implicitSelection =
401                 font.language() == ignore_language
402                 && font.fontInfo().number() == FONT_IGNORE
403                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
404
405         // Set font
406         setFont(cur, font, toggleall);
407
408         // Implicit selections are cleared afterwards
409         // and cursor is set to the original position.
410         if (implicitSelection) {
411                 cur.clearSelection();
412                 cur.top() = resetCursor;
413                 cur.resetAnchor();
414         }
415 }
416
417
418 docstring Text::getStringToIndex(Cursor const & cur)
419 {
420         LASSERT(this == cur.text(), /**/);
421
422         if (cur.selection())
423                 return cur.selectionAsString(false);
424
425         // Try implicit word selection. If there is a change
426         // in the language the implicit word selection is
427         // disabled.
428         Cursor tmpcur = cur;
429         selectWord(tmpcur, PREVIOUS_WORD);
430
431         if (!tmpcur.selection())
432                 cur.message(_("Nothing to index!"));
433         else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
434                 cur.message(_("Cannot index more than one paragraph!"));
435         else
436                 return tmpcur.selectionAsString(false);
437         
438         return docstring();
439 }
440
441
442 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge) 
443 {
444         LASSERT(cur.text(), /**/);
445         // make sure that the depth behind the selection are restored, too
446         pit_type undopit = undoSpan(cur.selEnd().pit());
447         recUndo(cur, cur.selBegin().pit(), undopit - 1);
448
449         //FIXME UNICODE
450         string const argument = to_utf8(arg);
451         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
452              pit <= end; ++pit) {
453                 Paragraph & par = pars_[pit];
454                 ParagraphParameters params = par.params();
455                 params.read(argument, merge);
456                 par.params().apply(params, par.layout());
457         }
458 }
459
460
461 //FIXME This is a little redundant now, but it's probably worth keeping,
462 //especially if we're going to go away from using serialization internally
463 //quite so much.
464 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p) 
465 {
466         LASSERT(cur.text(), /**/);
467         // make sure that the depth behind the selection are restored, too
468         pit_type undopit = undoSpan(cur.selEnd().pit());
469         recUndo(cur, cur.selBegin().pit(), undopit - 1);
470
471         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
472              pit <= end; ++pit) {
473                 Paragraph & par = pars_[pit];
474                 par.params().apply(p, par.layout());
475         }       
476 }
477
478
479 // this really should just insert the inset and not move the cursor.
480 void Text::insertInset(Cursor & cur, Inset * inset)
481 {
482         LASSERT(this == cur.text(), /**/);
483         LASSERT(inset, /**/);
484         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
485                 Change(cur.buffer().params().trackChanges
486                 ? Change::INSERTED : Change::UNCHANGED));
487 }
488
489
490 // needed to insert the selection
491 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
492 {
493         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
494                 cur.current_font, str, autoBreakRows_);
495 }
496
497
498 // turn double CR to single CR, others are converted into one
499 // blank. Then insertStringAsLines is called
500 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
501 {
502         docstring linestr = str;
503         bool newline_inserted = false;
504
505         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
506                 if (linestr[i] == '\n') {
507                         if (newline_inserted) {
508                                 // we know that \r will be ignored by
509                                 // insertStringAsLines. Of course, it is a dirty
510                                 // trick, but it works...
511                                 linestr[i - 1] = '\r';
512                                 linestr[i] = '\n';
513                         } else {
514                                 linestr[i] = ' ';
515                                 newline_inserted = true;
516                         }
517                 } else if (isPrintable(linestr[i])) {
518                         newline_inserted = false;
519                 }
520         }
521         insertStringAsLines(cur, linestr);
522 }
523
524
525 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
526                         bool setfont, bool boundary)
527 {
528         TextMetrics const & tm = cur.bv().textMetrics(this);
529         bool const update_needed = !tm.contains(par);
530         Cursor old = cur;
531         setCursorIntern(cur, par, pos, setfont, boundary);
532         return cur.bv().checkDepm(cur, old) || update_needed;
533 }
534
535
536 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
537 {
538         LASSERT(par != int(paragraphs().size()), /**/);
539         cur.pit() = par;
540         cur.pos() = pos;
541
542         // now some strict checking
543         Paragraph & para = getPar(par);
544
545         // None of these should happen, but we're scaredy-cats
546         if (pos < 0) {
547                 lyxerr << "dont like -1" << endl;
548                 LASSERT(false, /**/);
549         }
550
551         if (pos > para.size()) {
552                 lyxerr << "dont like 1, pos: " << pos
553                        << " size: " << para.size()
554                        << " par: " << par << endl;
555                 LASSERT(false, /**/);
556         }
557 }
558
559
560 void Text::setCursorIntern(Cursor & cur,
561                               pit_type par, pos_type pos, bool setfont, bool boundary)
562 {
563         LASSERT(this == cur.text(), /**/);
564         cur.boundary(boundary);
565         setCursor(cur.top(), par, pos);
566         if (setfont)
567                 cur.setCurrentFont();
568 }
569
570
571 bool Text::checkAndActivateInset(Cursor & cur, bool front)
572 {
573         if (cur.selection())
574                 return false;
575         if (front && cur.pos() == cur.lastpos())
576                 return false;
577         if (!front && cur.pos() == 0)
578                 return false;
579         Inset * inset = front ? cur.nextInset() : cur.prevInset();
580         if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
581                 return false;
582         /*
583          * Apparently, when entering an inset we are expected to be positioned
584          * *before* it in the containing paragraph, regardless of the direction
585          * from which we are entering. Otherwise, cursor placement goes awry,
586          * and when we exit from the beginning, we'll be placed *after* the
587          * inset.
588          */
589         if (!front)
590                 --cur.pos();
591         inset->edit(cur, front);
592         return true;
593 }
594
595
596 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
597 {
598         if (cur.selection())
599                 return false;
600         if (cur.pos() == -1)
601                 return false;
602         if (cur.pos() == cur.lastpos())
603                 return false;
604         Paragraph & par = cur.paragraph();
605         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
606         if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
607                 return false;
608         inset->edit(cur, movingForward, 
609                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
610         return true;
611 }
612
613
614 bool Text::cursorBackward(Cursor & cur)
615 {
616         // Tell BufferView to test for FitCursor in any case!
617         cur.updateFlags(Update::FitCursor);
618
619         // not at paragraph start?
620         if (cur.pos() > 0) {
621                 // if on right side of boundary (i.e. not at paragraph end, but line end)
622                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
623                 // there are some exceptions to ignore this: lineseps, newlines, spaces
624 #if 0
625                 // some effectless debug code to see the values in the debugger
626                 bool bound = cur.boundary();
627                 int rowpos = cur.textRow().pos();
628                 int pos = cur.pos();
629                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
630                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
631                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
632 #endif
633                 if (!cur.boundary() &&
634                                 cur.textRow().pos() == cur.pos() &&
635                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
636                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
637                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
638                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
639                 }
640                 
641                 // go left and try to enter inset
642                 if (checkAndActivateInset(cur, false))
643                         return false;
644                 
645                 // normal character left
646                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
647         }
648
649         // move to the previous paragraph or do nothing
650         if (cur.pit() > 0)
651                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
652         return false;
653 }
654
655
656 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
657 {
658         Cursor temp_cur = cur;
659         temp_cur.posVisLeft(skip_inset);
660         if (temp_cur.depth() > cur.depth()) {
661                 cur = temp_cur;
662                 return false;
663         }
664         return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
665                 true, temp_cur.boundary());
666 }
667
668
669 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
670 {
671         Cursor temp_cur = cur;
672         temp_cur.posVisRight(skip_inset);
673         if (temp_cur.depth() > cur.depth()) {
674                 cur = temp_cur;
675                 return false;
676         }
677         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
678                 true, temp_cur.boundary());
679 }
680
681
682 bool Text::cursorForward(Cursor & cur)
683 {
684         // Tell BufferView to test for FitCursor in any case!
685         cur.updateFlags(Update::FitCursor);
686
687         // not at paragraph end?
688         if (cur.pos() != cur.lastpos()) {
689                 // in front of editable inset, i.e. jump into it?
690                 if (checkAndActivateInset(cur, true))
691                         return false;
692
693                 TextMetrics const & tm = cur.bv().textMetrics(this);
694                 // if left of boundary -> just jump to right side
695                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
696                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
697                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
698
699                 // next position is left of boundary, 
700                 // but go to next line for special cases like space, newline, linesep
701 #if 0
702                 // some effectless debug code to see the values in the debugger
703                 int endpos = cur.textRow().endpos();
704                 int lastpos = cur.lastpos();
705                 int pos = cur.pos();
706                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
707                 bool newline = cur.paragraph().isNewline(cur.pos());
708                 bool sep = cur.paragraph().isSeparator(cur.pos());
709                 if (cur.pos() != cur.lastpos()) {
710                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
711                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
712                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
713                 }
714 #endif
715                 if (cur.textRow().endpos() == cur.pos() + 1 &&
716                     cur.textRow().endpos() != cur.lastpos() &&
717                                 !cur.paragraph().isNewline(cur.pos()) &&
718                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
719                                 !cur.paragraph().isSeparator(cur.pos())) {
720                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
721                 }
722                 
723                 // in front of RTL boundary? Stay on this side of the boundary because:
724                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
725                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
726                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
727                 
728                 // move right
729                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
730         }
731
732         // move to next paragraph
733         if (cur.pit() != cur.lastpit())
734                 return setCursor(cur, cur.pit() + 1, 0, true, false);
735         return false;
736 }
737
738
739 bool Text::cursorUpParagraph(Cursor & cur)
740 {
741         bool updated = false;
742         if (cur.pos() > 0)
743                 updated = setCursor(cur, cur.pit(), 0);
744         else if (cur.pit() != 0)
745                 updated = setCursor(cur, cur.pit() - 1, 0);
746         return updated;
747 }
748
749
750 bool Text::cursorDownParagraph(Cursor & cur)
751 {
752         bool updated = false;
753         if (cur.pit() != cur.lastpit())
754                 updated = setCursor(cur, cur.pit() + 1, 0);
755         else
756                 updated = setCursor(cur, cur.pit(), cur.lastpos());
757         return updated;
758 }
759
760
761 // fix the cursor `cur' after a characters has been deleted at `where'
762 // position. Called by deleteEmptyParagraphMechanism
763 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
764 {
765         // Do nothing if cursor is not in the paragraph where the
766         // deletion occured,
767         if (cur.pit() != where.pit())
768                 return;
769
770         // If cursor position is after the deletion place update it
771         if (cur.pos() > where.pos())
772                 --cur.pos();
773
774         // Check also if we don't want to set the cursor on a spot behind the
775         // pagragraph because we erased the last character.
776         if (cur.pos() > cur.lastpos())
777                 cur.pos() = cur.lastpos();
778 }
779
780
781 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
782                 Cursor & old, bool & need_anchor_change)
783 {
784         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
785
786         Paragraph & oldpar = old.paragraph();
787
788         // We allow all kinds of "mumbo-jumbo" when freespacing.
789         if (oldpar.isFreeSpacing())
790                 return false;
791
792         /* Ok I'll put some comments here about what is missing.
793            There are still some small problems that can lead to
794            double spaces stored in the document file or space at
795            the beginning of paragraphs(). This happens if you have
796            the cursor between to spaces and then save. Or if you
797            cut and paste and the selection have a space at the
798            beginning and then save right after the paste. (Lgb)
799         */
800
801         // If old.pos() == 0 and old.pos()(1) == LineSeparator
802         // delete the LineSeparator.
803         // MISSING
804
805         // If old.pos() == 1 and old.pos()(0) == LineSeparator
806         // delete the LineSeparator.
807         // MISSING
808
809         bool const same_inset = &old.inset() == &cur.inset();
810         bool const same_par = same_inset && old.pit() == cur.pit();
811         bool const same_par_pos = same_par && old.pos() == cur.pos();
812
813         // If the chars around the old cursor were spaces, delete one of them.
814         if (!same_par_pos) {
815                 // Only if the cursor has really moved.
816                 if (old.pos() > 0
817                     && old.pos() < oldpar.size()
818                     && oldpar.isLineSeparator(old.pos())
819                     && oldpar.isLineSeparator(old.pos() - 1)
820                     && !oldpar.isDeleted(old.pos() - 1)
821                     && !oldpar.isDeleted(old.pos())) {
822                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
823 // FIXME: This will not work anymore when we have multiple views of the same buffer
824 // In this case, we will have to correct also the cursors held by
825 // other bufferviews. It will probably be easier to do that in a more
826 // automated way in CursorSlice code. (JMarc 26/09/2001)
827                         // correct all cursor parts
828                         if (same_par) {
829                                 fixCursorAfterDelete(cur.top(), old.top());
830                                 need_anchor_change = true;
831                         }
832                         return true;
833                 }
834         }
835
836         // only do our magic if we changed paragraph
837         if (same_par)
838                 return false;
839
840         // don't delete anything if this is the ONLY paragraph!
841         if (old.lastpit() == 0)
842                 return false;
843
844         // Do not delete empty paragraphs with keepempty set.
845         if (oldpar.allowEmpty())
846                 return false;
847
848         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
849                 // Delete old par.
850                 old.recordUndo(ATOMIC_UNDO,
851                            max(old.pit() - 1, pit_type(0)),
852                            min(old.pit() + 1, old.lastpit()));
853                 ParagraphList & plist = old.text()->paragraphs();
854                 bool const soa = oldpar.params().startOfAppendix();
855                 plist.erase(boost::next(plist.begin(), old.pit()));
856                 // do not lose start of appendix marker (bug 4212)
857                 if (soa && old.pit() < pit_type(plist.size()))
858                         plist[old.pit()].params().startOfAppendix(true);
859
860                 // see #warning (FIXME?) above 
861                 if (cur.depth() >= old.depth()) {
862                         CursorSlice & curslice = cur[old.depth() - 1];
863                         if (&curslice.inset() == &old.inset()
864                             && curslice.pit() > old.pit()) {
865                                 --curslice.pit();
866                                 // since a paragraph has been deleted, all the
867                                 // insets after `old' have been copied and
868                                 // their address has changed. Therefore we
869                                 // need to `regenerate' cur. (JMarc)
870                                 cur.updateInsets(&(cur.bottom().inset()));
871                                 need_anchor_change = true;
872                         }
873                 }
874                 return true;
875         }
876
877         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
878                 need_anchor_change = true;
879                 // We return true here because the Paragraph contents changed and
880                 // we need a redraw before further action is processed.
881                 return true;
882         }
883
884         return false;
885 }
886
887
888 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
889 {
890         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
891
892         for (pit_type pit = first; pit <= last; ++pit) {
893                 Paragraph & par = pars_[pit];
894
895                 // We allow all kinds of "mumbo-jumbo" when freespacing.
896                 if (par.isFreeSpacing())
897                         continue;
898
899                 for (pos_type pos = 1; pos < par.size(); ++pos) {
900                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
901                             && !par.isDeleted(pos - 1)) {
902                                 if (par.eraseChar(pos - 1, trackChanges)) {
903                                         --pos;
904                                 }
905                         }
906                 }
907
908                 // don't delete anything if this is the only remaining paragraph within the given range
909                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
910                 if (first == last)
911                         continue;
912
913                 // don't delete empty paragraphs with keepempty set
914                 if (par.allowEmpty())
915                         continue;
916
917                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
918                         pars_.erase(boost::next(pars_.begin(), pit));
919                         --pit;
920                         --last;
921                         continue;
922                 }
923
924                 par.stripLeadingSpaces(trackChanges);
925         }
926 }
927
928
929 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
930 {
931         cur.recordUndo(ATOMIC_UNDO, first, last);
932 }
933
934
935 void Text::recUndo(Cursor & cur, pit_type par) const
936 {
937         cur.recordUndo(ATOMIC_UNDO, par, par);
938 }
939
940 } // namespace lyx