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