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