* de.po: sync with branch.
[lyx.git] / src / Text2.cpp
blob99ec8e66c720289fee945d0b00b329862f0122c7
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.
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
18 * Full author contact details are available in file CREDITS.
21 #include <config.h>
23 #include "Text.h"
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 "ParagraphParameters.h"
44 #include "TextClass.h"
45 #include "TextMetrics.h"
46 #include "VSpace.h"
48 #include "insets/InsetCollapsable.h"
50 #include "mathed/InsetMathHull.h"
52 #include "support/lassert.h"
53 #include "support/debug.h"
54 #include "support/gettext.h"
55 #include "support/textutils.h"
57 #include <boost/next_prior.hpp>
59 #include <sstream>
61 using namespace std;
63 namespace lyx {
65 bool Text::isMainText() const
67 return &owner_->buffer().text() == this;
71 // Note that this is supposed to return a fully realized font.
72 FontInfo Text::layoutFont(pit_type const pit) const
74 Layout const & layout = pars_[pit].layout();
76 if (!pars_[pit].getDepth()) {
77 FontInfo lf = layout.resfont;
78 // In case the default family has been customized
79 if (layout.font.family() == INHERIT_FAMILY)
80 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
81 // FIXME
82 // It ought to be possible here just to use Inset::getLayout() and skip
83 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
84 // now, because Inset::getLayout() will return a default-constructed
85 // InsetLayout, and that e.g. sets the foreground color to red. So we
86 // need to do some work to make that possible.
87 InsetCollapsable const * icp = owner_->asInsetCollapsable();
88 if (!icp)
89 return lf;
90 FontInfo icf = icp->getLayout().font();
91 icf.realize(lf);
92 return icf;
95 FontInfo font = layout.font;
96 // Realize with the fonts of lesser depth.
97 //font.realize(outerFont(pit));
98 font.realize(owner_->buffer().params().getFont().fontInfo());
100 return font;
104 // Note that this is supposed to return a fully realized font.
105 FontInfo Text::labelFont(Paragraph const & par) const
107 Buffer const & buffer = owner_->buffer();
108 Layout const & layout = par.layout();
110 if (!par.getDepth()) {
111 FontInfo lf = layout.reslabelfont;
112 // In case the default family has been customized
113 if (layout.labelfont.family() == INHERIT_FAMILY)
114 lf.setFamily(buffer.params().getFont().fontInfo().family());
115 return lf;
118 FontInfo font = layout.labelfont;
119 // Realize with the fonts of lesser depth.
120 font.realize(buffer.params().getFont().fontInfo());
122 return font;
126 void Text::setCharFont(pit_type pit,
127 pos_type pos, Font const & fnt, Font const & display_font)
129 Buffer const & buffer = owner_->buffer();
130 Font font = fnt;
131 Layout const & layout = pars_[pit].layout();
133 // Get concrete layout font to reduce against
134 FontInfo layoutfont;
136 if (pos < pars_[pit].beginOfBody())
137 layoutfont = layout.labelfont;
138 else
139 layoutfont = layout.font;
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);
148 if (tp != pit_type(paragraphs().size()))
149 layoutfont.realize(pars_[tp].layout().font);
153 // Inside inset, apply the inset's font attributes if any
154 // (charstyle!)
155 if (!isMainText())
156 layoutfont.realize(display_font.fontInfo());
158 layoutfont.realize(buffer.params().getFont().fontInfo());
160 // Now, reduce font against full layout font
161 font.fontInfo().reduce(layoutfont);
163 pars_[pit].setFont(pos, font);
167 void Text::setInsetFont(BufferView const & bv, pit_type pit,
168 pos_type pos, Font const & font, bool toggleall)
170 Inset * const inset = pars_[pit].getInset(pos);
171 LASSERT(inset && inset->noFontChange(), /**/);
173 CursorSlice::idx_type endidx = inset->nargs();
174 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
175 Text * text = cs.text();
176 if (text) {
177 // last position of the cell
178 CursorSlice cellend = cs;
179 cellend.pit() = cellend.lastpit();
180 cellend.pos() = cellend.lastpos();
181 text->setFont(bv, cs, cellend, font, toggleall);
187 // return past-the-last paragraph influenced by a layout change on pit
188 pit_type Text::undoSpan(pit_type pit)
190 pit_type const end = paragraphs().size();
191 pit_type nextpit = pit + 1;
192 if (nextpit == end)
193 return nextpit;
194 //because of parindents
195 if (!pars_[pit].getDepth())
196 return boost::next(nextpit);
197 //because of depth constrains
198 for (; nextpit != end; ++pit, ++nextpit) {
199 if (!pars_[pit].getDepth())
200 break;
202 return nextpit;
206 void Text::setLayout(pit_type start, pit_type end,
207 docstring const & layout)
209 LASSERT(start != end, /**/);
211 Buffer const & buffer = owner_->buffer();
212 BufferParams const & bp = buffer.params();
213 Layout const & lyxlayout = bp.documentClass()[layout];
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.expandLabel(lyxlayout, bp));
224 // set layout over selection and make a total rebreak of those paragraphs
225 void Text::setLayout(Cursor & cur, docstring const & layout)
227 LASSERT(this == cur.text(), /**/);
229 pit_type start = cur.selBegin().pit();
230 pit_type end = cur.selEnd().pit() + 1;
231 pit_type undopit = undoSpan(end - 1);
232 recUndo(cur, start, undopit - 1);
233 setLayout(start, end, layout);
234 cur.buffer()->updateLabels();
238 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
239 Paragraph const & par, int max_depth)
241 if (par.layout().labeltype == LABEL_BIBLIO)
242 return false;
243 int const depth = par.params().depth();
244 if (type == Text::INC_DEPTH && depth < max_depth)
245 return true;
246 if (type == Text::DEC_DEPTH && depth > 0)
247 return true;
248 return false;
252 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
254 LASSERT(this == cur.text(), /**/);
255 // this happens when selecting several cells in tabular (bug 2630)
256 if (cur.selBegin().idx() != cur.selEnd().idx())
257 return false;
259 pit_type const beg = cur.selBegin().pit();
260 pit_type const end = cur.selEnd().pit() + 1;
261 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
263 for (pit_type pit = beg; pit != end; ++pit) {
264 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
265 return true;
266 max_depth = pars_[pit].getMaxDepthAfter();
268 return false;
272 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
274 LASSERT(this == cur.text(), /**/);
275 pit_type const beg = cur.selBegin().pit();
276 pit_type const end = cur.selEnd().pit() + 1;
277 cur.recordUndoSelection();
278 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
280 for (pit_type pit = beg; pit != end; ++pit) {
281 Paragraph & par = pars_[pit];
282 if (lyx::changeDepthAllowed(type, par, max_depth)) {
283 int const depth = par.params().depth();
284 if (type == INC_DEPTH)
285 par.params().depth(depth + 1);
286 else
287 par.params().depth(depth - 1);
289 max_depth = par.getMaxDepthAfter();
291 // this handles the counter labels, and also fixes up
292 // depth values for follow-on (child) paragraphs
293 cur.buffer()->updateLabels();
297 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
299 LASSERT(this == cur.text(), /**/);
300 // Set the current_font
301 // Determine basis font
302 FontInfo layoutfont;
303 pit_type pit = cur.pit();
304 if (cur.pos() < pars_[pit].beginOfBody())
305 layoutfont = labelFont(pars_[pit]);
306 else
307 layoutfont = layoutFont(pit);
309 // Update current font
310 cur.real_current_font.update(font,
311 cur.buffer()->params().language,
312 toggleall);
314 // Reduce to implicit settings
315 cur.current_font = cur.real_current_font;
316 cur.current_font.fontInfo().reduce(layoutfont);
317 // And resolve it completely
318 cur.real_current_font.fontInfo().realize(layoutfont);
320 // if there is no selection that's all we need to do
321 if (!cur.selection())
322 return;
324 // Ok, we have a selection.
325 cur.recordUndoSelection();
327 setFont(cur.bv(), cur.selectionBegin().top(),
328 cur.selectionEnd().top(), font, toggleall);
332 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
333 CursorSlice const & end, Font const & font,
334 bool toggleall)
336 Buffer const & buffer = bv.buffer();
338 // Don't use forwardChar here as ditend might have
339 // pos() == lastpos() and forwardChar would miss it.
340 // Can't use forwardPos either as this descends into
341 // nested insets.
342 Language const * language = buffer.params().language;
343 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
344 if (dit.pos() == dit.lastpos())
345 continue;
346 pit_type const pit = dit.pit();
347 pos_type const pos = dit.pos();
348 Inset * inset = pars_[pit].getInset(pos);
349 if (inset && inset->noFontChange()) {
350 // We need to propagate the font change to all
351 // text cells of the inset (bug 1973).
352 // FIXME: This should change, see documentation
353 // of noFontChange in Inset.h
354 setInsetFont(bv, pit, pos, font, toggleall);
356 TextMetrics const & tm = bv.textMetrics(this);
357 Font f = tm.displayFont(pit, pos);
358 f.update(font, language, toggleall);
359 setCharFont(pit, pos, f, tm.font_);
364 bool Text::cursorTop(Cursor & cur)
366 LASSERT(this == cur.text(), /**/);
367 return setCursor(cur, 0, 0);
371 bool Text::cursorBottom(Cursor & cur)
373 LASSERT(this == cur.text(), /**/);
374 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
378 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
380 LASSERT(this == cur.text(), /**/);
381 // If the mask is completely neutral, tell user
382 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
383 // Could only happen with user style
384 cur.message(_("No font change defined."));
385 return;
388 // Try implicit word selection
389 // If there is a change in the language the implicit word selection
390 // is disabled.
391 CursorSlice const resetCursor = cur.top();
392 bool const implicitSelection =
393 font.language() == ignore_language
394 && font.fontInfo().number() == FONT_IGNORE
395 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
397 // Set font
398 setFont(cur, font, toggleall);
400 // Implicit selections are cleared afterwards
401 // and cursor is set to the original position.
402 if (implicitSelection) {
403 cur.clearSelection();
404 cur.top() = resetCursor;
405 cur.resetAnchor();
410 docstring Text::getStringToIndex(Cursor const & cur)
412 LASSERT(this == cur.text(), /**/);
414 if (cur.selection())
415 return cur.selectionAsString(false);
417 // Try implicit word selection. If there is a change
418 // in the language the implicit word selection is
419 // disabled.
420 Cursor tmpcur = cur;
421 selectWord(tmpcur, PREVIOUS_WORD);
423 if (!tmpcur.selection())
424 cur.message(_("Nothing to index!"));
425 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
426 cur.message(_("Cannot index more than one paragraph!"));
427 else
428 return tmpcur.selectionAsString(false);
430 return docstring();
434 void Text::setLabelWidthStringToSequence(pit_type const par_offset,
435 docstring const & s)
437 pit_type offset = par_offset;
438 // Find first of same layout in sequence
439 while (!isFirstInSequence(offset)) {
440 offset = depthHook(offset, pars_[offset].getDepth());
443 // now apply label width string to every par
444 // in sequence
445 pit_type const end = pars_.size();
446 depth_type const depth = pars_[offset].getDepth();
447 Layout const & layout = pars_[offset].layout();
448 for (pit_type pit = offset; pit != end; ++pit) {
449 while (pars_[pit].getDepth() > depth)
450 ++pit;
451 if (pars_[pit].getDepth() < depth)
452 return;
453 if (pars_[pit].layout() != layout)
454 return;
455 pars_[pit].setLabelWidthString(s);
460 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
462 LASSERT(cur.text(), /**/);
463 // make sure that the depth behind the selection are restored, too
464 pit_type undopit = undoSpan(cur.selEnd().pit());
465 recUndo(cur, cur.selBegin().pit(), undopit - 1);
467 //FIXME UNICODE
468 string const argument = to_utf8(arg);
469 depth_type priordepth = -1;
470 Layout priorlayout;
471 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
472 pit <= end; ++pit) {
473 Paragraph & par = pars_[pit];
474 ParagraphParameters params = par.params();
475 params.read(argument, merge);
476 // Changes to label width string apply to all paragraphs
477 // with same layout in a sequence.
478 // Do this only once for a selected range of paragraphs
479 // of the same layout and depth.
480 if (par.getDepth() != priordepth || par.layout() != priorlayout)
481 setLabelWidthStringToSequence(pit, params.labelWidthString());
482 par.params().apply(params, par.layout());
483 priordepth = par.getDepth();
484 priorlayout = par.layout();
489 //FIXME This is a little redundant now, but it's probably worth keeping,
490 //especially if we're going to go away from using serialization internally
491 //quite so much.
492 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
494 LASSERT(cur.text(), /**/);
495 // make sure that the depth behind the selection are restored, too
496 pit_type undopit = undoSpan(cur.selEnd().pit());
497 recUndo(cur, cur.selBegin().pit(), undopit - 1);
499 depth_type priordepth = -1;
500 Layout priorlayout;
501 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
502 pit <= end; ++pit) {
503 Paragraph & par = pars_[pit];
504 // Changes to label width string apply to all paragraphs
505 // with same layout in a sequence.
506 // Do this only once for a selected range of paragraphs
507 // of the same layout and depth.
508 if (par.getDepth() != priordepth || par.layout() != priorlayout)
509 setLabelWidthStringToSequence(pit,
510 par.params().labelWidthString());
511 par.params().apply(p, par.layout());
512 priordepth = par.getDepth();
513 priorlayout = par.layout();
518 // this really should just insert the inset and not move the cursor.
519 void Text::insertInset(Cursor & cur, Inset * inset)
521 LASSERT(this == cur.text(), /**/);
522 LASSERT(inset, /**/);
523 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
524 Change(cur.buffer()->params().trackChanges
525 ? Change::INSERTED : Change::UNCHANGED));
529 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
530 bool setfont, bool boundary)
532 TextMetrics const & tm = cur.bv().textMetrics(this);
533 bool const update_needed = !tm.contains(par);
534 Cursor old = cur;
535 setCursorIntern(cur, par, pos, setfont, boundary);
536 return cur.bv().checkDepm(cur, old) || update_needed;
540 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
542 LASSERT(par != int(paragraphs().size()), /**/);
543 cur.pit() = par;
544 cur.pos() = pos;
546 // now some strict checking
547 Paragraph & para = getPar(par);
549 // None of these should happen, but we're scaredy-cats
550 if (pos < 0) {
551 lyxerr << "dont like -1" << endl;
552 LASSERT(false, /**/);
555 if (pos > para.size()) {
556 lyxerr << "dont like 1, pos: " << pos
557 << " size: " << para.size()
558 << " par: " << par << endl;
559 LASSERT(false, /**/);
564 void Text::setCursorIntern(Cursor & cur,
565 pit_type par, pos_type pos, bool setfont, bool boundary)
567 LASSERT(this == cur.text(), /**/);
568 cur.boundary(boundary);
569 setCursor(cur.top(), par, pos);
570 if (setfont)
571 cur.setCurrentFont();
575 bool Text::checkAndActivateInset(Cursor & cur, bool front)
577 if (cur.selection())
578 return false;
579 if (front && cur.pos() == cur.lastpos())
580 return false;
581 if (!front && cur.pos() == 0)
582 return false;
583 Inset * inset = front ? cur.nextInset() : cur.prevInset();
584 if (!inset || !inset->editable())
585 return false;
587 * Apparently, when entering an inset we are expected to be positioned
588 * *before* it in the containing paragraph, regardless of the direction
589 * from which we are entering. Otherwise, cursor placement goes awry,
590 * and when we exit from the beginning, we'll be placed *after* the
591 * inset.
593 if (!front)
594 --cur.pos();
595 inset->edit(cur, front);
596 return true;
600 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
602 if (cur.selection())
603 return false;
604 if (cur.pos() == -1)
605 return false;
606 if (cur.pos() == cur.lastpos())
607 return false;
608 Paragraph & par = cur.paragraph();
609 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
610 if (!inset || !inset->editable())
611 return false;
612 inset->edit(cur, movingForward,
613 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
614 return true;
618 bool Text::cursorBackward(Cursor & cur)
620 // Tell BufferView to test for FitCursor in any case!
621 cur.updateFlags(Update::FitCursor);
623 // not at paragraph start?
624 if (cur.pos() > 0) {
625 // if on right side of boundary (i.e. not at paragraph end, but line end)
626 // -> skip it, i.e. set boundary to true, i.e. go only logically left
627 // there are some exceptions to ignore this: lineseps, newlines, spaces
628 #if 0
629 // some effectless debug code to see the values in the debugger
630 bool bound = cur.boundary();
631 int rowpos = cur.textRow().pos();
632 int pos = cur.pos();
633 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
634 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
635 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
636 #endif
637 if (!cur.boundary() &&
638 cur.textRow().pos() == cur.pos() &&
639 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
640 !cur.paragraph().isNewline(cur.pos() - 1) &&
641 !cur.paragraph().isSeparator(cur.pos() - 1)) {
642 return setCursor(cur, cur.pit(), cur.pos(), true, true);
645 // go left and try to enter inset
646 if (checkAndActivateInset(cur, false))
647 return false;
649 // normal character left
650 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
653 // move to the previous paragraph or do nothing
654 if (cur.pit() > 0)
655 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
656 return false;
660 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
662 Cursor temp_cur = cur;
663 temp_cur.posVisLeft(skip_inset);
664 if (temp_cur.depth() > cur.depth()) {
665 cur = temp_cur;
666 return false;
668 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
669 true, temp_cur.boundary());
673 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
675 Cursor temp_cur = cur;
676 temp_cur.posVisRight(skip_inset);
677 if (temp_cur.depth() > cur.depth()) {
678 cur = temp_cur;
679 return false;
681 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
682 true, temp_cur.boundary());
686 bool Text::cursorForward(Cursor & cur)
688 // Tell BufferView to test for FitCursor in any case!
689 cur.updateFlags(Update::FitCursor);
691 // not at paragraph end?
692 if (cur.pos() != cur.lastpos()) {
693 // in front of editable inset, i.e. jump into it?
694 if (checkAndActivateInset(cur, true))
695 return false;
697 TextMetrics const & tm = cur.bv().textMetrics(this);
698 // if left of boundary -> just jump to right side
699 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
700 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
701 return setCursor(cur, cur.pit(), cur.pos(), true, false);
703 // next position is left of boundary,
704 // but go to next line for special cases like space, newline, linesep
705 #if 0
706 // some effectless debug code to see the values in the debugger
707 int endpos = cur.textRow().endpos();
708 int lastpos = cur.lastpos();
709 int pos = cur.pos();
710 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
711 bool newline = cur.paragraph().isNewline(cur.pos());
712 bool sep = cur.paragraph().isSeparator(cur.pos());
713 if (cur.pos() != cur.lastpos()) {
714 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
715 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
716 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
718 #endif
719 if (cur.textRow().endpos() == cur.pos() + 1 &&
720 cur.textRow().endpos() != cur.lastpos() &&
721 !cur.paragraph().isNewline(cur.pos()) &&
722 !cur.paragraph().isLineSeparator(cur.pos()) &&
723 !cur.paragraph().isSeparator(cur.pos())) {
724 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
727 // in front of RTL boundary? Stay on this side of the boundary because:
728 // ab|cDDEEFFghi -> abc|DDEEFFghi
729 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
730 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
732 // move right
733 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
736 // move to next paragraph
737 if (cur.pit() != cur.lastpit())
738 return setCursor(cur, cur.pit() + 1, 0, true, false);
739 return false;
743 bool Text::cursorUpParagraph(Cursor & cur)
745 bool updated = false;
746 if (cur.pos() > 0)
747 updated = setCursor(cur, cur.pit(), 0);
748 else if (cur.pit() != 0)
749 updated = setCursor(cur, cur.pit() - 1, 0);
750 return updated;
754 bool Text::cursorDownParagraph(Cursor & cur)
756 bool updated = false;
757 if (cur.pit() != cur.lastpit())
758 updated = setCursor(cur, cur.pit() + 1, 0);
759 else
760 updated = setCursor(cur, cur.pit(), cur.lastpos());
761 return updated;
765 // fix the cursor `cur' after a characters has been deleted at `where'
766 // position. Called by deleteEmptyParagraphMechanism
767 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
769 // Do nothing if cursor is not in the paragraph where the
770 // deletion occured,
771 if (cur.pit() != where.pit())
772 return;
774 // If cursor position is after the deletion place update it
775 if (cur.pos() > where.pos())
776 --cur.pos();
778 // Check also if we don't want to set the cursor on a spot behind the
779 // pagragraph because we erased the last character.
780 if (cur.pos() > cur.lastpos())
781 cur.pos() = cur.lastpos();
785 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
786 Cursor & old, bool & need_anchor_change)
788 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
790 Paragraph & oldpar = old.paragraph();
792 // We allow all kinds of "mumbo-jumbo" when freespacing.
793 if (oldpar.isFreeSpacing())
794 return false;
796 /* Ok I'll put some comments here about what is missing.
797 There are still some small problems that can lead to
798 double spaces stored in the document file or space at
799 the beginning of paragraphs(). This happens if you have
800 the cursor between to spaces and then save. Or if you
801 cut and paste and the selection have a space at the
802 beginning and then save right after the paste. (Lgb)
805 // If old.pos() == 0 and old.pos()(1) == LineSeparator
806 // delete the LineSeparator.
807 // MISSING
809 // If old.pos() == 1 and old.pos()(0) == LineSeparator
810 // delete the LineSeparator.
811 // MISSING
813 // Find a common inset and the corresponding depth.
814 size_t depth = 0;
815 for (; depth < cur.depth(); ++depth)
816 if (&old.inset() == &cur[depth].inset())
817 break;
819 // Whether a common inset is found and whether the cursor is still in
820 // the same paragraph (possibly nested).
821 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
822 bool const same_par_pos = depth == cur.depth() - 1 && same_par
823 && old.pos() == cur[depth].pos();
825 // If the chars around the old cursor were spaces, delete one of them.
826 if (!same_par_pos) {
827 // Only if the cursor has really moved.
828 if (old.pos() > 0
829 && old.pos() < oldpar.size()
830 && oldpar.isLineSeparator(old.pos())
831 && oldpar.isLineSeparator(old.pos() - 1)
832 && !oldpar.isDeleted(old.pos() - 1)
833 && !oldpar.isDeleted(old.pos())) {
834 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
835 // FIXME: This will not work anymore when we have multiple views of the same buffer
836 // In this case, we will have to correct also the cursors held by
837 // other bufferviews. It will probably be easier to do that in a more
838 // automated way in CursorSlice code. (JMarc 26/09/2001)
839 // correct all cursor parts
840 if (same_par) {
841 fixCursorAfterDelete(cur[depth], old.top());
842 need_anchor_change = true;
844 return true;
848 // only do our magic if we changed paragraph
849 if (same_par)
850 return false;
852 // don't delete anything if this is the ONLY paragraph!
853 if (old.lastpit() == 0)
854 return false;
856 // Do not delete empty paragraphs with keepempty set.
857 if (oldpar.allowEmpty())
858 return false;
860 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
861 // Delete old par.
862 old.recordUndo(ATOMIC_UNDO,
863 max(old.pit() - 1, pit_type(0)),
864 min(old.pit() + 1, old.lastpit()));
865 ParagraphList & plist = old.text()->paragraphs();
866 bool const soa = oldpar.params().startOfAppendix();
867 plist.erase(boost::next(plist.begin(), old.pit()));
868 // do not lose start of appendix marker (bug 4212)
869 if (soa && old.pit() < pit_type(plist.size()))
870 plist[old.pit()].params().startOfAppendix(true);
872 // see #warning (FIXME?) above
873 if (cur.depth() >= old.depth()) {
874 CursorSlice & curslice = cur[old.depth() - 1];
875 if (&curslice.inset() == &old.inset()
876 && curslice.pit() > old.pit()) {
877 --curslice.pit();
878 // since a paragraph has been deleted, all the
879 // insets after `old' have been copied and
880 // their address has changed. Therefore we
881 // need to `regenerate' cur. (JMarc)
882 cur.updateInsets(&(cur.bottom().inset()));
883 need_anchor_change = true;
886 return true;
889 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
890 need_anchor_change = true;
891 // We return true here because the Paragraph contents changed and
892 // we need a redraw before further action is processed.
893 return true;
896 return false;
900 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
902 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
904 for (pit_type pit = first; pit <= last; ++pit) {
905 Paragraph & par = pars_[pit];
907 // We allow all kinds of "mumbo-jumbo" when freespacing.
908 if (par.isFreeSpacing())
909 continue;
911 for (pos_type pos = 1; pos < par.size(); ++pos) {
912 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
913 && !par.isDeleted(pos - 1)) {
914 if (par.eraseChar(pos - 1, trackChanges)) {
915 --pos;
920 // don't delete anything if this is the only remaining paragraph within the given range
921 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
922 if (first == last)
923 continue;
925 // don't delete empty paragraphs with keepempty set
926 if (par.allowEmpty())
927 continue;
929 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
930 pars_.erase(boost::next(pars_.begin(), pit));
931 --pit;
932 --last;
933 continue;
936 par.stripLeadingSpaces(trackChanges);
941 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
943 cur.recordUndo(ATOMIC_UNDO, first, last);
947 void Text::recUndo(Cursor & cur, pit_type par) const
949 cur.recordUndo(ATOMIC_UNDO, par, par);
952 } // namespace lyx