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