]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Upon exiting an inset, set the font back to what it was when entering.
[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 "Color.h"
32 #include "Cursor.h"
33 #include "CutAndPaste.h"
34 #include "debug.h"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
38 #include "gettext.h"
39 #include "Language.h"
40 #include "Lexer.h"
41 #include "LyXFunc.h"
42 #include "LyXRC.h"
43 #include "Paragraph.h"
44 #include "paragraph_funcs.h"
45 #include "ParagraphParameters.h"
46 #include "ParIterator.h"
47 #include "Server.h"
48 #include "ServerSocket.h"
49 #include "Undo.h"
50 #include "VSpace.h"
51
52 #include "frontends/FontMetrics.h"
53
54 #include "insets/InsetEnvironment.h"
55
56 #include "mathed/InsetMathHull.h"
57
58 #include "support/textutils.h"
59
60 #include <boost/current_function.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 Font 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                 Font lf = layout->resfont;
90                 // In case the default family has been customized
91                 if (layout->font.family() == Font::INHERIT_FAMILY)
92                         lf.setFamily(buffer.params().getFont().family());
93                 return lf;
94         }
95
96         Font font = layout->font;
97         // Realize with the fonts of lesser depth.
98         //font.realize(outerFont(pit, paragraphs()));
99         font.realize(buffer.params().getFont());
100
101         return font;
102 }
103
104
105 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
106 {
107         LayoutPtr const & layout = par.layout();
108
109         if (!par.getDepth()) {
110                 Font lf = layout->reslabelfont;
111                 // In case the default family has been customized
112                 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
113                         lf.setFamily(buffer.params().getFont().family());
114                 return lf;
115         }
116
117         Font font = layout->labelfont;
118         // Realize with the fonts of lesser depth.
119         font.realize(buffer.params().getFont());
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         Font 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);
155
156         layoutfont.realize(buffer.params().getFont());
157
158         // Now, reduce font against full layout font
159         font.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) << endl;
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         recordUndoSelection(cur);
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         Font 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.reduce(layoutfont);
332         // And resolve it completely
333         cur.real_current_font.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         recordUndoSelection(cur);
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 == Font(Font::ALL_IGNORE)) {
397                 // Could only happen with user style
398                 cur.message(_("No font change defined."));
399                 return;
400         }
401
402         // Try implicit word selection
403         // If there is a change in the language the implicit word selection
404         // is disabled.
405         CursorSlice resetCursor = cur.top();
406         bool implicitSelection =
407                 font.language() == ignore_language
408                 && font.number() == Font::IGNORE
409                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
410
411         // Set font
412         setFont(cur, font, toggleall);
413
414         // Implicit selections are cleared afterwards
415         // and cursor is set to the original position.
416         if (implicitSelection) {
417                 cur.clearSelection();
418                 cur.top() = resetCursor;
419                 cur.resetAnchor();
420         }
421 }
422
423
424 docstring Text::getStringToIndex(Cursor const & cur)
425 {
426         BOOST_ASSERT(this == cur.text());
427
428         docstring idxstring;
429         if (cur.selection())
430                 idxstring = cur.selectionAsString(false);
431         else {
432                 // Try implicit word selection. If there is a change
433                 // in the language the implicit word selection is
434                 // disabled.
435                 Cursor tmpcur = cur;
436                 selectWord(tmpcur, PREVIOUS_WORD);
437
438                 if (!tmpcur.selection())
439                         cur.message(_("Nothing to index!"));
440                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
441                         cur.message(_("Cannot index more than one paragraph!"));
442                 else
443                         idxstring = tmpcur.selectionAsString(false);
444         }
445
446         return idxstring;
447 }
448
449
450 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge) 
451 {
452         BOOST_ASSERT(cur.text());
453         // make sure that the depth behind the selection are restored, too
454         pit_type undopit = undoSpan(cur.selEnd().pit());
455         recUndo(cur, cur.selBegin().pit(), undopit - 1);
456
457         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
458              pit <= end; ++pit) {
459                 Paragraph & par = pars_[pit];
460                 ParagraphParameters params = par.params();
461                 params.read(to_utf8(arg), merge);
462                 Layout const & layout = *(par.layout());
463                 par.params().apply(params, layout);
464         }
465 }
466
467
468 //FIXME This is a little redundant now, but it's probably worth keeping,
469 //especially if we're going to go away from using serialization internally
470 //quite so much.
471 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p) 
472 {
473         BOOST_ASSERT(cur.text());
474         // make sure that the depth behind the selection are restored, too
475         pit_type undopit = undoSpan(cur.selEnd().pit());
476         recUndo(cur, cur.selBegin().pit(), undopit - 1);
477
478         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
479              pit <= end; ++pit) {
480                 Paragraph & par = pars_[pit];
481                 Layout const & layout = *(par.layout());
482                 par.params().apply(p, layout);
483         }       
484 }
485
486
487 // this really should just insert the inset and not move the cursor.
488 void Text::insertInset(Cursor & cur, Inset * inset)
489 {
490         BOOST_ASSERT(this == cur.text());
491         BOOST_ASSERT(inset);
492         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
493                                     Change(cur.buffer().params().trackChanges ?
494                                            Change::INSERTED : Change::UNCHANGED));
495 }
496
497
498 // needed to insert the selection
499 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
500 {
501         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
502                                          cur.current_font, str, autoBreakRows_);
503 }
504
505
506 // turn double CR to single CR, others are converted into one
507 // blank. Then insertStringAsLines is called
508 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
509 {
510         docstring linestr = str;
511         bool newline_inserted = false;
512
513         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
514                 if (linestr[i] == '\n') {
515                         if (newline_inserted) {
516                                 // we know that \r will be ignored by
517                                 // insertStringAsLines. Of course, it is a dirty
518                                 // trick, but it works...
519                                 linestr[i - 1] = '\r';
520                                 linestr[i] = '\n';
521                         } else {
522                                 linestr[i] = ' ';
523                                 newline_inserted = true;
524                         }
525                 } else if (isPrintable(linestr[i])) {
526                         newline_inserted = false;
527                 }
528         }
529         insertStringAsLines(cur, linestr);
530 }
531
532
533 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
534                         bool setfont, bool boundary)
535 {
536         Cursor old = cur;
537         setCursorIntern(cur, par, pos, setfont, boundary);
538         return cur.bv().checkDepm(cur, old);
539 }
540
541
542 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
543 {
544         BOOST_ASSERT(par != int(paragraphs().size()));
545         cur.pit() = par;
546         cur.pos() = pos;
547
548         // now some strict checking
549         Paragraph & para = getPar(par);
550
551         // None of these should happen, but we're scaredy-cats
552         if (pos < 0) {
553                 lyxerr << "dont like -1" << endl;
554                 BOOST_ASSERT(false);
555         }
556
557         if (pos > para.size()) {
558                 lyxerr << "dont like 1, pos: " << pos
559                        << " size: " << para.size()
560                        << " par: " << par << endl;
561                 BOOST_ASSERT(false);
562         }
563 }
564
565
566 void Text::setCursorIntern(Cursor & cur,
567                               pit_type par, pos_type pos, bool setfont, bool boundary)
568 {
569         BOOST_ASSERT(this == cur.text());
570         cur.boundary(boundary);
571         setCursor(cur.top(), par, pos);
572         if (setfont)
573                 cur.setCurrentFont();
574 }
575
576
577 bool Text::checkAndActivateInset(Cursor & cur, bool front)
578 {
579         if (cur.selection())
580                 return false;
581         if (front && cur.pos() == cur.lastpos())
582                 return false;
583         if (!front && cur.pos() == 0)
584                 return false;
585         Inset * inset = front ? cur.nextInset() : cur.prevInset();
586         if (!isHighlyEditableInset(inset))
587                 return false;
588         /*
589          * Apparently, when entering an inset we are expected to be positioned
590          * *before* it in the containing paragraph, regardless of the direction
591          * from which we are entering. Otherwise, cursor placement goes awry,
592          * and when we exit from the beginning, we'll be placed *after* the
593          * inset.
594          */
595         if (!front)
596                 --cur.pos();
597         inset->edit(cur, front);
598         return true;
599 }
600
601
602 bool Text::cursorLeft(Cursor & cur)
603 {
604         // Tell BufferView to test for FitCursor in any case!
605         cur.updateFlags(Update::FitCursor);
606
607         // not at paragraph start?
608         if (cur.pos() > 0) {
609                 // if on right side of boundary (i.e. not at paragraph end, but line end)
610                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
611                 // there are some exceptions to ignore this: lineseps, newlines, spaces
612 #if 0
613                 // some effectless debug code to see the values in the debugger
614                 bool bound = cur.boundary();
615                 int rowpos = cur.textRow().pos();
616                 int pos = cur.pos();
617                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
618                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
619                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
620 #endif
621                 if (!cur.boundary() &&
622                                 cur.textRow().pos() == cur.pos() &&
623                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
624                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
625                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
626                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
627                 }
628                 
629                 // go left and try to enter inset
630                 if (checkAndActivateInset(cur, false))
631                         return false;
632                 
633                 // normal character left
634                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
635         }
636
637         // move to the previous paragraph or do nothing
638         if (cur.pit() > 0)
639                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
640         return false;
641 }
642
643
644 bool Text::cursorRight(Cursor & cur)
645 {
646         // Tell BufferView to test for FitCursor in any case!
647         cur.updateFlags(Update::FitCursor);
648
649         // not at paragraph end?
650         if (cur.pos() != cur.lastpos()) {
651                 // in front of editable inset, i.e. jump into it?
652                 if (checkAndActivateInset(cur, true))
653                         return false;
654
655                 TextMetrics const & tm = cur.bv().textMetrics(this);
656                 // if left of boundary -> just jump to right side
657                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
658                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
659                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
660
661                 // next position is left of boundary, 
662                 // but go to next line for special cases like space, newline, linesep
663 #if 0
664                 // some effectless debug code to see the values in the debugger
665                 int endpos = cur.textRow().endpos();
666                 int lastpos = cur.lastpos();
667                 int pos = cur.pos();
668                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
669                 bool newline = cur.paragraph().isNewline(cur.pos());
670                 bool sep = cur.paragraph().isSeparator(cur.pos());
671                 if (cur.pos() != cur.lastpos()) {
672                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
673                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
674                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
675                 }
676 #endif
677                 if (cur.textRow().endpos() == cur.pos() + 1 &&
678                     cur.textRow().endpos() != cur.lastpos() &&
679                                 !cur.paragraph().isNewline(cur.pos()) &&
680                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
681                                 !cur.paragraph().isSeparator(cur.pos())) {
682                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
683                 }
684                 
685                 // in front of RTL boundary? Stay on this side of the boundary because:
686                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
687                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
688                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
689                 
690                 // move right
691                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
692         }
693
694         // move to next paragraph
695         if (cur.pit() != cur.lastpit())
696                 return setCursor(cur, cur.pit() + 1, 0, true, false);
697         return false;
698 }
699
700
701 bool Text::cursorUpParagraph(Cursor & cur)
702 {
703         bool updated = false;
704         if (cur.pos() > 0)
705                 updated = setCursor(cur, cur.pit(), 0);
706         else if (cur.pit() != 0)
707                 updated = setCursor(cur, cur.pit() - 1, 0);
708         return updated;
709 }
710
711
712 bool Text::cursorDownParagraph(Cursor & cur)
713 {
714         bool updated = false;
715         if (cur.pit() != cur.lastpit())
716                 updated = setCursor(cur, cur.pit() + 1, 0);
717         else
718                 updated = setCursor(cur, cur.pit(), cur.lastpos());
719         return updated;
720 }
721
722
723 // fix the cursor `cur' after a characters has been deleted at `where'
724 // position. Called by deleteEmptyParagraphMechanism
725 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
726 {
727         // Do nothing if cursor is not in the paragraph where the
728         // deletion occured,
729         if (cur.pit() != where.pit())
730                 return;
731
732         // If cursor position is after the deletion place update it
733         if (cur.pos() > where.pos())
734                 --cur.pos();
735
736         // Check also if we don't want to set the cursor on a spot behind the
737         // pagragraph because we erased the last character.
738         if (cur.pos() > cur.lastpos())
739                 cur.pos() = cur.lastpos();
740 }
741
742
743 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
744                 Cursor & old, bool & need_anchor_change)
745 {
746         //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
747
748         Paragraph & oldpar = old.paragraph();
749
750         // We allow all kinds of "mumbo-jumbo" when freespacing.
751         if (oldpar.isFreeSpacing())
752                 return false;
753
754         /* Ok I'll put some comments here about what is missing.
755            There are still some small problems that can lead to
756            double spaces stored in the document file or space at
757            the beginning of paragraphs(). This happens if you have
758            the cursor between to spaces and then save. Or if you
759            cut and paste and the selection have a space at the
760            beginning and then save right after the paste. (Lgb)
761         */
762
763         // If old.pos() == 0 and old.pos()(1) == LineSeparator
764         // delete the LineSeparator.
765         // MISSING
766
767         // If old.pos() == 1 and old.pos()(0) == LineSeparator
768         // delete the LineSeparator.
769         // MISSING
770
771         bool const same_inset = &old.inset() == &cur.inset();
772         bool const same_par = same_inset && old.pit() == cur.pit();
773         bool const same_par_pos = same_par && old.pos() == cur.pos();
774
775         // If the chars around the old cursor were spaces, delete one of them.
776         if (!same_par_pos) {
777                 // Only if the cursor has really moved.
778                 if (old.pos() > 0
779                     && old.pos() < oldpar.size()
780                     && oldpar.isLineSeparator(old.pos())
781                     && oldpar.isLineSeparator(old.pos() - 1)
782                     && !oldpar.isDeleted(old.pos() - 1)
783                     && !oldpar.isDeleted(old.pos())) {
784                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
785 // FIXME: This will not work anymore when we have multiple views of the same buffer
786 // In this case, we will have to correct also the cursors held by
787 // other bufferviews. It will probably be easier to do that in a more
788 // automated way in CursorSlice code. (JMarc 26/09/2001)
789                         // correct all cursor parts
790                         if (same_par) {
791                                 fixCursorAfterDelete(cur.top(), old.top());
792                                 need_anchor_change = true;
793                         }
794                         return true;
795                 }
796         }
797
798         // only do our magic if we changed paragraph
799         if (same_par)
800                 return false;
801
802         // don't delete anything if this is the ONLY paragraph!
803         if (old.lastpit() == 0)
804                 return false;
805
806         // Do not delete empty paragraphs with keepempty set.
807         if (oldpar.allowEmpty())
808                 return false;
809
810         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
811                 // Delete old par.
812                 recordUndo(old, Undo::ATOMIC,
813                            max(old.pit() - 1, pit_type(0)),
814                            min(old.pit() + 1, old.lastpit()));
815                 ParagraphList & plist = old.text()->paragraphs();
816                 bool const soa = oldpar.params().startOfAppendix();
817                 plist.erase(boost::next(plist.begin(), old.pit()));
818                 // do not lose start of appendix marker (bug 4212)
819                 if (soa)
820                         plist[old.pit()].params().startOfAppendix(true);
821
822                 // see #warning (FIXME?) above 
823                 if (cur.depth() >= old.depth()) {
824                         CursorSlice & curslice = cur[old.depth() - 1];
825                         if (&curslice.inset() == &old.inset()
826                             && curslice.pit() > old.pit()) {
827                                 --curslice.pit();
828                                 // since a paragraph has been deleted, all the
829                                 // insets after `old' have been copied and
830                                 // their address has changed. Therefore we
831                                 // need to `regenerate' cur. (JMarc)
832                                 cur.updateInsets(&(cur.bottom().inset()));
833                                 need_anchor_change = true;
834                         }
835                 }
836                 return true;
837         }
838
839         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
840                 need_anchor_change = true;
841                 // We return true here because the Paragraph contents changed and
842                 // we need a redraw before further action is processed.
843                 return true;
844         }
845
846         return false;
847 }
848
849
850 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
851 {
852         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
853
854         for (pit_type pit = first; pit <= last; ++pit) {
855                 Paragraph & par = pars_[pit];
856
857                 // We allow all kinds of "mumbo-jumbo" when freespacing.
858                 if (par.isFreeSpacing())
859                         continue;
860
861                 for (pos_type pos = 1; pos < par.size(); ++pos) {
862                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
863                             && !par.isDeleted(pos - 1)) {
864                                 if (par.eraseChar(pos - 1, trackChanges)) {
865                                         --pos;
866                                 }
867                         }
868                 }
869
870                 // don't delete anything if this is the only remaining paragraph within the given range
871                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
872                 if (first == last)
873                         continue;
874
875                 // don't delete empty paragraphs with keepempty set
876                 if (par.allowEmpty())
877                         continue;
878
879                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
880                         pars_.erase(boost::next(pars_.begin(), pit));
881                         --pit;
882                         --last;
883                         continue;
884                 }
885
886                 par.stripLeadingSpaces(trackChanges);
887         }
888 }
889
890
891 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
892 {
893         recordUndo(cur, Undo::ATOMIC, first, last);
894 }
895
896
897 void Text::recUndo(Cursor & cur, pit_type par) const
898 {
899         recordUndo(cur, Undo::ATOMIC, par, par);
900 }
901
902 } // namespace lyx