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