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