]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
284be9f2be317ec521345a2014a7fcb0c7b8a686
[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 "Layout.h"
41 #include "Lexer.h"
42 #include "LyXFunc.h"
43 #include "LyXRC.h"
44 #include "Paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "ParIterator.h"
48 #include "Server.h"
49 #include "ServerSocket.h"
50 #include "Undo.h"
51 #include "VSpace.h"
52
53 #include "frontends/FontMetrics.h"
54
55 #include "insets/InsetEnvironment.h"
56
57 #include "mathed/InsetMathHull.h"
58
59 #include "support/textutils.h"
60
61 #include <boost/current_function.hpp>
62
63 #include <sstream>
64
65 using std::endl;
66 using std::ostringstream;
67 using std::string;
68 using std::max;
69 using std::min;
70 using std::istringstream;
71
72 namespace lyx {
73
74 Text::Text()
75         : autoBreakRows_(false)
76 {}
77
78
79 bool Text::isMainText(Buffer const & buffer) const
80 {
81         return &buffer.text() == this;
82 }
83
84
85 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
86 {
87         LayoutPtr const & layout = pars_[pit].layout();
88
89         if (!pars_[pit].getDepth())  {
90                 Font lf = layout->resfont;
91                 // In case the default family has been customized
92                 if (layout->font.family() == Font::INHERIT_FAMILY)
93                         lf.setFamily(buffer.params().getFont().family());
94                 return lf;
95         }
96
97         Font font = layout->font;
98         // Realize with the fonts of lesser depth.
99         //font.realize(outerFont(pit, paragraphs()));
100         font.realize(buffer.params().getFont());
101
102         return font;
103 }
104
105
106 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
107 {
108         LayoutPtr const & layout = par.layout();
109
110         if (!par.getDepth()) {
111                 Font lf = layout->reslabelfont;
112                 // In case the default family has been customized
113                 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
114                         lf.setFamily(buffer.params().getFont().family());
115                 return lf;
116         }
117
118         Font font = layout->labelfont;
119         // Realize with the fonts of lesser depth.
120         font.realize(buffer.params().getFont());
121
122         return font;
123 }
124
125
126 void Text::setCharFont(Buffer const & buffer, pit_type pit,
127                 pos_type pos, Font const & fnt, Font const & display_font)
128 {
129         Font font = fnt;
130         LayoutPtr const & layout = pars_[pit].layout();
131
132         // Get concrete layout font to reduce against
133         Font layoutfont;
134
135         if (pos < pars_[pit].beginOfBody())
136                 layoutfont = layout->labelfont;
137         else
138                 layoutfont = layout->font;
139
140         // Realize against environment font information
141         if (pars_[pit].getDepth()) {
142                 pit_type tp = pit;
143                 while (!layoutfont.resolved() &&
144                        tp != pit_type(paragraphs().size()) &&
145                        pars_[tp].getDepth()) {
146                         tp = outerHook(tp, paragraphs());
147                         if (tp != pit_type(paragraphs().size()))
148                                 layoutfont.realize(pars_[tp].layout()->font);
149                 }
150         }
151
152         // Inside inset, apply the inset's font attributes if any
153         // (charstyle!)
154         if (!isMainText(buffer))
155                 layoutfont.realize(display_font);
156
157         layoutfont.realize(buffer.params().getFont());
158
159         // Now, reduce font against full layout font
160         font.reduce(layoutfont);
161
162         pars_[pit].setFont(pos, font);
163 }
164
165
166 void Text::setInsetFont(BufferView const & bv, pit_type pit,
167                 pos_type pos, Font const & font, bool toggleall)
168 {
169         BOOST_ASSERT(pars_[pit].isInset(pos) &&
170                      pars_[pit].getInset(pos)->noFontChange());
171
172         Inset * const inset = pars_[pit].getInset(pos);
173         CursorSlice::idx_type endidx = inset->nargs();
174         for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
175                 Text * text = cs.text();
176                 if (text) {
177                         // last position of the cell
178                         CursorSlice cellend = cs;
179                         cellend.pit() = cellend.lastpit();
180                         cellend.pos() = cellend.lastpos();
181                         text->setFont(bv, cs, cellend, font, toggleall);
182                 }
183         }
184 }
185
186
187 // return past-the-last paragraph influenced by a layout change on pit
188 pit_type Text::undoSpan(pit_type pit)
189 {
190         pit_type end = paragraphs().size();
191         pit_type nextpit = pit + 1;
192         if (nextpit == end)
193                 return nextpit;
194         //because of parindents
195         if (!pars_[pit].getDepth())
196                 return boost::next(nextpit);
197         //because of depth constrains
198         for (; nextpit != end; ++pit, ++nextpit) {
199                 if (!pars_[pit].getDepth())
200                         break;
201         }
202         return nextpit;
203 }
204
205
206 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
207                 docstring const & layout)
208 {
209         BOOST_ASSERT(start != end);
210
211         BufferParams const & bufparams = buffer.params();
212         LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
213
214         for (pit_type pit = start; pit != end; ++pit) {
215                 Paragraph & par = pars_[pit];
216                 par.applyLayout(lyxlayout);
217                 if (lyxlayout->margintype == MARGIN_MANUAL)
218                         par.setLabelWidthString(par.translateIfPossible(
219                                 lyxlayout->labelstring(), buffer.params()));
220         }
221 }
222
223
224 // set layout over selection and make a total rebreak of those paragraphs
225 void Text::setLayout(Cursor & cur, docstring const & layout)
226 {
227         BOOST_ASSERT(this == cur.text());
228         // special handling of new environment insets
229         BufferView & bv = cur.bv();
230         BufferParams const & params = bv.buffer().params();
231         LayoutPtr const & lyxlayout = params.getTextClass()[layout];
232         if (lyxlayout->is_environment) {
233                 // move everything in a new environment inset
234                 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
235                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
236                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
237                 lyx::dispatch(FuncRequest(LFUN_CUT));
238                 Inset * inset = new InsetEnvironment(params, layout);
239                 insertInset(cur, inset);
240                 //inset->edit(cur, true);
241                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
242                 return;
243         }
244
245         pit_type start = cur.selBegin().pit();
246         pit_type end = cur.selEnd().pit() + 1;
247         pit_type undopit = undoSpan(end - 1);
248         recUndo(cur, start, undopit - 1);
249         setLayout(cur.buffer(), start, end, layout);
250         updateLabels(cur.buffer());
251 }
252
253
254 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
255                         Paragraph const & par, int max_depth)
256 {
257         if (par.layout()->labeltype == LABEL_BIBLIO)
258                 return false;
259         int const depth = par.params().depth();
260         if (type == Text::INC_DEPTH && depth < max_depth)
261                 return true;
262         if (type == Text::DEC_DEPTH && depth > 0)
263                 return true;
264         return false;
265 }
266
267
268 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
269 {
270         BOOST_ASSERT(this == cur.text());
271         // this happens when selecting several cells in tabular (bug 2630)
272         if (cur.selBegin().idx() != cur.selEnd().idx())
273                 return false;
274
275         pit_type const beg = cur.selBegin().pit();
276         pit_type const end = cur.selEnd().pit() + 1;
277         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
278
279         for (pit_type pit = beg; pit != end; ++pit) {
280                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
281                         return true;
282                 max_depth = pars_[pit].getMaxDepthAfter();
283         }
284         return false;
285 }
286
287
288 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
289 {
290         BOOST_ASSERT(this == cur.text());
291         pit_type const beg = cur.selBegin().pit();
292         pit_type const end = cur.selEnd().pit() + 1;
293         recordUndoSelection(cur);
294         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
295
296         for (pit_type pit = beg; pit != end; ++pit) {
297                 Paragraph & par = pars_[pit];
298                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
299                         int const depth = par.params().depth();
300                         if (type == INC_DEPTH)
301                                 par.params().depth(depth + 1);
302                         else
303                                 par.params().depth(depth - 1);
304                 }
305                 max_depth = par.getMaxDepthAfter();
306         }
307         // this handles the counter labels, and also fixes up
308         // depth values for follow-on (child) paragraphs
309         updateLabels(cur.buffer());
310 }
311
312
313 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
314 {
315         BOOST_ASSERT(this == cur.text());
316         // Set the current_font
317         // Determine basis font
318         Font layoutfont;
319         pit_type pit = cur.pit();
320         if (cur.pos() < pars_[pit].beginOfBody())
321                 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
322         else
323                 layoutfont = getLayoutFont(cur.buffer(), pit);
324
325         // Update current font
326         cur.real_current_font.update(font,
327                                         cur.buffer().params().language,
328                                         toggleall);
329
330         // Reduce to implicit settings
331         cur.current_font = cur.real_current_font;
332         cur.current_font.reduce(layoutfont);
333         // And resolve it completely
334         cur.real_current_font.realize(layoutfont);
335
336         // if there is no selection that's all we need to do
337         if (!cur.selection())
338                 return;
339
340         // Ok, we have a selection.
341         recordUndoSelection(cur);
342
343         setFont(cur.bv(), cur.selectionBegin().top(), 
344                 cur.selectionEnd().top(), font, toggleall);
345 }
346
347
348 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
349                 CursorSlice const & end, Font const & font,
350                 bool toggleall)
351 {
352         Buffer const & buffer = bv.buffer();
353
354         // Don't use forwardChar here as ditend might have
355         // pos() == lastpos() and forwardChar would miss it.
356         // Can't use forwardPos either as this descends into
357         // nested insets.
358         Language const * language = buffer.params().language;
359         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
360                 if (dit.pos() != dit.lastpos()) {
361                         pit_type const pit = dit.pit();
362                         pos_type const pos = dit.pos();
363                         if (pars_[pit].isInset(pos) &&
364                             pars_[pit].getInset(pos)->noFontChange())
365                                 // We need to propagate the font change to all
366                                 // text cells of the inset (bug 1973).
367                                 // FIXME: This should change, see documentation
368                                 // of noFontChange in Inset.h
369                                 setInsetFont(bv, pit, pos, font, toggleall);
370                         TextMetrics const & tm = bv.textMetrics(this);
371                         Font f = tm.getDisplayFont(pit, pos);
372                         f.update(font, language, toggleall);
373                         setCharFont(buffer, pit, pos, f, tm.font_);
374                 }
375         }
376 }
377
378
379 bool Text::cursorTop(Cursor & cur)
380 {
381         BOOST_ASSERT(this == cur.text());
382         return setCursor(cur, 0, 0);
383 }
384
385
386 bool Text::cursorBottom(Cursor & cur)
387 {
388         BOOST_ASSERT(this == cur.text());
389         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
390 }
391
392
393 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
394 {
395         BOOST_ASSERT(this == cur.text());
396         // If the mask is completely neutral, tell user
397         if (font == Font(Font::ALL_IGNORE)) {
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.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 (!isHighlyEditableInset(inset))
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::cursorLeft(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::cursorRight(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 << endl;
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                 recordUndo(old, Undo::ATOMIC,
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)
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         recordUndo(cur, Undo::ATOMIC, first, last);
895 }
896
897
898 void Text::recUndo(Cursor & cur, pit_type par) const
899 {
900         recordUndo(cur, Undo::ATOMIC, par, par);
901 }
902
903 } // namespace lyx