Prepare ANNOUNCE and NEWS for rc2
[lyx.git] / src / Text2.cpp
blob4a0c1dd3eb27b9b7393d0f4d6e75e6fd77ad2c15
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 "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "TextClass.h"
46 #include "TextMetrics.h"
47 #include "VSpace.h"
49 #include "mathed/InsetMathHull.h"
51 #include "support/lassert.h"
52 #include "support/debug.h"
53 #include "support/gettext.h"
54 #include "support/textutils.h"
56 #include <boost/next_prior.hpp>
58 #include <sstream>
60 using namespace std;
62 namespace lyx {
64 Text::Text()
65 : autoBreakRows_(false)
69 bool Text::isMainText(Buffer const & buffer) const
71 return &buffer.text() == this;
75 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
77 Layout const & layout = pars_[pit].layout();
79 if (!pars_[pit].getDepth()) {
80 FontInfo lf = layout.resfont;
81 // In case the default family has been customized
82 if (layout.font.family() == INHERIT_FAMILY)
83 lf.setFamily(buffer.params().getFont().fontInfo().family());
84 return lf;
87 FontInfo font = layout.font;
88 // Realize with the fonts of lesser depth.
89 //font.realize(outerFont(pit, paragraphs()));
90 font.realize(buffer.params().getFont().fontInfo());
92 return font;
96 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
98 Layout const & layout = par.layout();
100 if (!par.getDepth()) {
101 FontInfo lf = layout.reslabelfont;
102 // In case the default family has been customized
103 if (layout.labelfont.family() == INHERIT_FAMILY)
104 lf.setFamily(buffer.params().getFont().fontInfo().family());
105 return lf;
108 FontInfo font = layout.labelfont;
109 // Realize with the fonts of lesser depth.
110 font.realize(buffer.params().getFont().fontInfo());
112 return font;
116 void Text::setCharFont(Buffer const & buffer, pit_type pit,
117 pos_type pos, Font const & fnt, Font const & display_font)
119 Font font = fnt;
120 Layout const & layout = pars_[pit].layout();
122 // Get concrete layout font to reduce against
123 FontInfo layoutfont;
125 if (pos < pars_[pit].beginOfBody())
126 layoutfont = layout.labelfont;
127 else
128 layoutfont = layout.font;
130 // Realize against environment font information
131 if (pars_[pit].getDepth()) {
132 pit_type tp = pit;
133 while (!layoutfont.resolved() &&
134 tp != pit_type(paragraphs().size()) &&
135 pars_[tp].getDepth()) {
136 tp = outerHook(tp, paragraphs());
137 if (tp != pit_type(paragraphs().size()))
138 layoutfont.realize(pars_[tp].layout().font);
142 // Inside inset, apply the inset's font attributes if any
143 // (charstyle!)
144 if (!isMainText(buffer))
145 layoutfont.realize(display_font.fontInfo());
147 layoutfont.realize(buffer.params().getFont().fontInfo());
149 // Now, reduce font against full layout font
150 font.fontInfo().reduce(layoutfont);
152 pars_[pit].setFont(pos, font);
156 void Text::setInsetFont(BufferView const & bv, pit_type pit,
157 pos_type pos, Font const & font, bool toggleall)
159 Inset * const inset = pars_[pit].getInset(pos);
160 LASSERT(inset && inset->noFontChange(), /**/);
162 CursorSlice::idx_type endidx = inset->nargs();
163 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
164 Text * text = cs.text();
165 if (text) {
166 // last position of the cell
167 CursorSlice cellend = cs;
168 cellend.pit() = cellend.lastpit();
169 cellend.pos() = cellend.lastpos();
170 text->setFont(bv, cs, cellend, font, toggleall);
176 // return past-the-last paragraph influenced by a layout change on pit
177 pit_type Text::undoSpan(pit_type pit)
179 pit_type end = paragraphs().size();
180 pit_type nextpit = pit + 1;
181 if (nextpit == end)
182 return nextpit;
183 //because of parindents
184 if (!pars_[pit].getDepth())
185 return boost::next(nextpit);
186 //because of depth constrains
187 for (; nextpit != end; ++pit, ++nextpit) {
188 if (!pars_[pit].getDepth())
189 break;
191 return nextpit;
195 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
196 docstring const & layout)
198 LASSERT(start != end, /**/);
200 BufferParams const & bufparams = buffer.params();
201 Layout const & lyxlayout = bufparams.documentClass()[layout];
203 for (pit_type pit = start; pit != end; ++pit) {
204 Paragraph & par = pars_[pit];
205 par.applyLayout(lyxlayout);
206 if (lyxlayout.margintype == MARGIN_MANUAL)
207 par.setLabelWidthString(par.translateIfPossible(
208 lyxlayout.labelstring(), buffer.params()));
213 // set layout over selection and make a total rebreak of those paragraphs
214 void Text::setLayout(Cursor & cur, docstring const & layout)
216 LASSERT(this == cur.text(), /**/);
218 pit_type start = cur.selBegin().pit();
219 pit_type end = cur.selEnd().pit() + 1;
220 pit_type undopit = undoSpan(end - 1);
221 recUndo(cur, start, undopit - 1);
222 setLayout(cur.buffer(), start, end, layout);
223 updateLabels(cur.buffer());
227 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
228 Paragraph const & par, int max_depth)
230 if (par.layout().labeltype == LABEL_BIBLIO)
231 return false;
232 int const depth = par.params().depth();
233 if (type == Text::INC_DEPTH && depth < max_depth)
234 return true;
235 if (type == Text::DEC_DEPTH && depth > 0)
236 return true;
237 return false;
241 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
243 LASSERT(this == cur.text(), /**/);
244 // this happens when selecting several cells in tabular (bug 2630)
245 if (cur.selBegin().idx() != cur.selEnd().idx())
246 return false;
248 pit_type const beg = cur.selBegin().pit();
249 pit_type const end = cur.selEnd().pit() + 1;
250 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
252 for (pit_type pit = beg; pit != end; ++pit) {
253 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
254 return true;
255 max_depth = pars_[pit].getMaxDepthAfter();
257 return false;
261 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
263 LASSERT(this == cur.text(), /**/);
264 pit_type const beg = cur.selBegin().pit();
265 pit_type const end = cur.selEnd().pit() + 1;
266 cur.recordUndoSelection();
267 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
269 for (pit_type pit = beg; pit != end; ++pit) {
270 Paragraph & par = pars_[pit];
271 if (lyx::changeDepthAllowed(type, par, max_depth)) {
272 int const depth = par.params().depth();
273 if (type == INC_DEPTH)
274 par.params().depth(depth + 1);
275 else
276 par.params().depth(depth - 1);
278 max_depth = par.getMaxDepthAfter();
280 // this handles the counter labels, and also fixes up
281 // depth values for follow-on (child) paragraphs
282 updateLabels(cur.buffer());
286 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
288 LASSERT(this == cur.text(), /**/);
289 // Set the current_font
290 // Determine basis font
291 FontInfo layoutfont;
292 pit_type pit = cur.pit();
293 if (cur.pos() < pars_[pit].beginOfBody())
294 layoutfont = labelFont(cur.buffer(), pars_[pit]);
295 else
296 layoutfont = layoutFont(cur.buffer(), pit);
298 // Update current font
299 cur.real_current_font.update(font,
300 cur.buffer().params().language,
301 toggleall);
303 // Reduce to implicit settings
304 cur.current_font = cur.real_current_font;
305 cur.current_font.fontInfo().reduce(layoutfont);
306 // And resolve it completely
307 cur.real_current_font.fontInfo().realize(layoutfont);
309 // if there is no selection that's all we need to do
310 if (!cur.selection())
311 return;
313 // Ok, we have a selection.
314 cur.recordUndoSelection();
316 setFont(cur.bv(), cur.selectionBegin().top(),
317 cur.selectionEnd().top(), font, toggleall);
321 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
322 CursorSlice const & end, Font const & font,
323 bool toggleall)
325 Buffer const & buffer = bv.buffer();
327 // Don't use forwardChar here as ditend might have
328 // pos() == lastpos() and forwardChar would miss it.
329 // Can't use forwardPos either as this descends into
330 // nested insets.
331 Language const * language = buffer.params().language;
332 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
333 if (dit.pos() == dit.lastpos())
334 continue;
335 pit_type const pit = dit.pit();
336 pos_type const pos = dit.pos();
337 Inset * inset = pars_[pit].getInset(pos);
338 if (inset && inset->noFontChange()) {
339 // We need to propagate the font change to all
340 // text cells of the inset (bug 1973).
341 // FIXME: This should change, see documentation
342 // of noFontChange in Inset.h
343 setInsetFont(bv, pit, pos, font, toggleall);
345 TextMetrics const & tm = bv.textMetrics(this);
346 Font f = tm.displayFont(pit, pos);
347 f.update(font, language, toggleall);
348 setCharFont(buffer, pit, pos, f, tm.font_);
353 bool Text::cursorTop(Cursor & cur)
355 LASSERT(this == cur.text(), /**/);
356 return setCursor(cur, 0, 0);
360 bool Text::cursorBottom(Cursor & cur)
362 LASSERT(this == cur.text(), /**/);
363 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
367 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
369 LASSERT(this == cur.text(), /**/);
370 // If the mask is completely neutral, tell user
371 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
372 // Could only happen with user style
373 cur.message(_("No font change defined."));
374 return;
377 // Try implicit word selection
378 // If there is a change in the language the implicit word selection
379 // is disabled.
380 CursorSlice resetCursor = cur.top();
381 bool implicitSelection =
382 font.language() == ignore_language
383 && font.fontInfo().number() == FONT_IGNORE
384 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
386 // Set font
387 setFont(cur, font, toggleall);
389 // Implicit selections are cleared afterwards
390 // and cursor is set to the original position.
391 if (implicitSelection) {
392 cur.clearSelection();
393 cur.top() = resetCursor;
394 cur.resetAnchor();
399 docstring Text::getStringToIndex(Cursor const & cur)
401 LASSERT(this == cur.text(), /**/);
403 if (cur.selection())
404 return cur.selectionAsString(false);
406 // Try implicit word selection. If there is a change
407 // in the language the implicit word selection is
408 // disabled.
409 Cursor tmpcur = cur;
410 selectWord(tmpcur, PREVIOUS_WORD);
412 if (!tmpcur.selection())
413 cur.message(_("Nothing to index!"));
414 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
415 cur.message(_("Cannot index more than one paragraph!"));
416 else
417 return tmpcur.selectionAsString(false);
419 return docstring();
423 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
425 LASSERT(cur.text(), /**/);
426 // make sure that the depth behind the selection are restored, too
427 pit_type undopit = undoSpan(cur.selEnd().pit());
428 recUndo(cur, cur.selBegin().pit(), undopit - 1);
430 //FIXME UNICODE
431 string const argument = to_utf8(arg);
432 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
433 pit <= end; ++pit) {
434 Paragraph & par = pars_[pit];
435 ParagraphParameters params = par.params();
436 params.read(argument, merge);
437 par.params().apply(params, par.layout());
442 //FIXME This is a little redundant now, but it's probably worth keeping,
443 //especially if we're going to go away from using serialization internally
444 //quite so much.
445 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
447 LASSERT(cur.text(), /**/);
448 // make sure that the depth behind the selection are restored, too
449 pit_type undopit = undoSpan(cur.selEnd().pit());
450 recUndo(cur, cur.selBegin().pit(), undopit - 1);
452 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
453 pit <= end; ++pit) {
454 Paragraph & par = pars_[pit];
455 par.params().apply(p, par.layout());
460 // this really should just insert the inset and not move the cursor.
461 void Text::insertInset(Cursor & cur, Inset * inset)
463 LASSERT(this == cur.text(), /**/);
464 LASSERT(inset, /**/);
465 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
466 Change(cur.buffer().params().trackChanges
467 ? Change::INSERTED : Change::UNCHANGED));
471 // needed to insert the selection
472 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
474 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
475 cur.current_font, str, autoBreakRows_);
479 // turn double CR to single CR, others are converted into one
480 // blank. Then insertStringAsLines is called
481 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
483 docstring linestr = str;
484 bool newline_inserted = false;
486 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
487 if (linestr[i] == '\n') {
488 if (newline_inserted) {
489 // we know that \r will be ignored by
490 // insertStringAsLines. Of course, it is a dirty
491 // trick, but it works...
492 linestr[i - 1] = '\r';
493 linestr[i] = '\n';
494 } else {
495 linestr[i] = ' ';
496 newline_inserted = true;
498 } else if (isPrintable(linestr[i])) {
499 newline_inserted = false;
502 insertStringAsLines(cur, linestr);
506 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
507 bool setfont, bool boundary)
509 TextMetrics const & tm = cur.bv().textMetrics(this);
510 bool const update_needed = !tm.contains(par);
511 Cursor old = cur;
512 setCursorIntern(cur, par, pos, setfont, boundary);
513 return cur.bv().checkDepm(cur, old) || update_needed;
517 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
519 LASSERT(par != int(paragraphs().size()), /**/);
520 cur.pit() = par;
521 cur.pos() = pos;
523 // now some strict checking
524 Paragraph & para = getPar(par);
526 // None of these should happen, but we're scaredy-cats
527 if (pos < 0) {
528 lyxerr << "dont like -1" << endl;
529 LASSERT(false, /**/);
532 if (pos > para.size()) {
533 lyxerr << "dont like 1, pos: " << pos
534 << " size: " << para.size()
535 << " par: " << par << endl;
536 LASSERT(false, /**/);
541 void Text::setCursorIntern(Cursor & cur,
542 pit_type par, pos_type pos, bool setfont, bool boundary)
544 LASSERT(this == cur.text(), /**/);
545 cur.boundary(boundary);
546 setCursor(cur.top(), par, pos);
547 if (setfont)
548 cur.setCurrentFont();
552 bool Text::checkAndActivateInset(Cursor & cur, bool front)
554 if (cur.selection())
555 return false;
556 if (front && cur.pos() == cur.lastpos())
557 return false;
558 if (!front && cur.pos() == 0)
559 return false;
560 Inset * inset = front ? cur.nextInset() : cur.prevInset();
561 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
562 return false;
564 * Apparently, when entering an inset we are expected to be positioned
565 * *before* it in the containing paragraph, regardless of the direction
566 * from which we are entering. Otherwise, cursor placement goes awry,
567 * and when we exit from the beginning, we'll be placed *after* the
568 * inset.
570 if (!front)
571 --cur.pos();
572 inset->edit(cur, front);
573 return true;
577 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
579 if (cur.selection())
580 return false;
581 if (cur.pos() == -1)
582 return false;
583 if (cur.pos() == cur.lastpos())
584 return false;
585 Paragraph & par = cur.paragraph();
586 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
587 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
588 return false;
589 inset->edit(cur, movingForward,
590 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
591 return true;
595 bool Text::cursorBackward(Cursor & cur)
597 // Tell BufferView to test for FitCursor in any case!
598 cur.updateFlags(Update::FitCursor);
600 // not at paragraph start?
601 if (cur.pos() > 0) {
602 // if on right side of boundary (i.e. not at paragraph end, but line end)
603 // -> skip it, i.e. set boundary to true, i.e. go only logically left
604 // there are some exceptions to ignore this: lineseps, newlines, spaces
605 #if 0
606 // some effectless debug code to see the values in the debugger
607 bool bound = cur.boundary();
608 int rowpos = cur.textRow().pos();
609 int pos = cur.pos();
610 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
611 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
612 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
613 #endif
614 if (!cur.boundary() &&
615 cur.textRow().pos() == cur.pos() &&
616 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
617 !cur.paragraph().isNewline(cur.pos() - 1) &&
618 !cur.paragraph().isSeparator(cur.pos() - 1)) {
619 return setCursor(cur, cur.pit(), cur.pos(), true, true);
622 // go left and try to enter inset
623 if (checkAndActivateInset(cur, false))
624 return false;
626 // normal character left
627 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
630 // move to the previous paragraph or do nothing
631 if (cur.pit() > 0)
632 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
633 return false;
637 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
639 Cursor temp_cur = cur;
640 temp_cur.posVisLeft(skip_inset);
641 if (temp_cur.depth() > cur.depth()) {
642 cur = temp_cur;
643 return false;
645 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
646 true, temp_cur.boundary());
650 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
652 Cursor temp_cur = cur;
653 temp_cur.posVisRight(skip_inset);
654 if (temp_cur.depth() > cur.depth()) {
655 cur = temp_cur;
656 return false;
658 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
659 true, temp_cur.boundary());
663 bool Text::cursorForward(Cursor & cur)
665 // Tell BufferView to test for FitCursor in any case!
666 cur.updateFlags(Update::FitCursor);
668 // not at paragraph end?
669 if (cur.pos() != cur.lastpos()) {
670 // in front of editable inset, i.e. jump into it?
671 if (checkAndActivateInset(cur, true))
672 return false;
674 TextMetrics const & tm = cur.bv().textMetrics(this);
675 // if left of boundary -> just jump to right side
676 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
677 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
678 return setCursor(cur, cur.pit(), cur.pos(), true, false);
680 // next position is left of boundary,
681 // but go to next line for special cases like space, newline, linesep
682 #if 0
683 // some effectless debug code to see the values in the debugger
684 int endpos = cur.textRow().endpos();
685 int lastpos = cur.lastpos();
686 int pos = cur.pos();
687 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
688 bool newline = cur.paragraph().isNewline(cur.pos());
689 bool sep = cur.paragraph().isSeparator(cur.pos());
690 if (cur.pos() != cur.lastpos()) {
691 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
692 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
693 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
695 #endif
696 if (cur.textRow().endpos() == cur.pos() + 1 &&
697 cur.textRow().endpos() != cur.lastpos() &&
698 !cur.paragraph().isNewline(cur.pos()) &&
699 !cur.paragraph().isLineSeparator(cur.pos()) &&
700 !cur.paragraph().isSeparator(cur.pos())) {
701 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
704 // in front of RTL boundary? Stay on this side of the boundary because:
705 // ab|cDDEEFFghi -> abc|DDEEFFghi
706 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
707 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
709 // move right
710 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
713 // move to next paragraph
714 if (cur.pit() != cur.lastpit())
715 return setCursor(cur, cur.pit() + 1, 0, true, false);
716 return false;
720 bool Text::cursorUpParagraph(Cursor & cur)
722 bool updated = false;
723 if (cur.pos() > 0)
724 updated = setCursor(cur, cur.pit(), 0);
725 else if (cur.pit() != 0)
726 updated = setCursor(cur, cur.pit() - 1, 0);
727 return updated;
731 bool Text::cursorDownParagraph(Cursor & cur)
733 bool updated = false;
734 if (cur.pit() != cur.lastpit())
735 updated = setCursor(cur, cur.pit() + 1, 0);
736 else
737 updated = setCursor(cur, cur.pit(), cur.lastpos());
738 return updated;
742 // fix the cursor `cur' after a characters has been deleted at `where'
743 // position. Called by deleteEmptyParagraphMechanism
744 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
746 // Do nothing if cursor is not in the paragraph where the
747 // deletion occured,
748 if (cur.pit() != where.pit())
749 return;
751 // If cursor position is after the deletion place update it
752 if (cur.pos() > where.pos())
753 --cur.pos();
755 // Check also if we don't want to set the cursor on a spot behind the
756 // pagragraph because we erased the last character.
757 if (cur.pos() > cur.lastpos())
758 cur.pos() = cur.lastpos();
762 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
763 Cursor & old, bool & need_anchor_change)
765 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
767 Paragraph & oldpar = old.paragraph();
769 // We allow all kinds of "mumbo-jumbo" when freespacing.
770 if (oldpar.isFreeSpacing())
771 return false;
773 /* Ok I'll put some comments here about what is missing.
774 There are still some small problems that can lead to
775 double spaces stored in the document file or space at
776 the beginning of paragraphs(). This happens if you have
777 the cursor between to spaces and then save. Or if you
778 cut and paste and the selection have a space at the
779 beginning and then save right after the paste. (Lgb)
782 // If old.pos() == 0 and old.pos()(1) == LineSeparator
783 // delete the LineSeparator.
784 // MISSING
786 // If old.pos() == 1 and old.pos()(0) == LineSeparator
787 // delete the LineSeparator.
788 // MISSING
790 bool const same_inset = &old.inset() == &cur.inset();
791 bool const same_par = same_inset && old.pit() == cur.pit();
792 bool const same_par_pos = same_par && old.pos() == cur.pos();
794 // If the chars around the old cursor were spaces, delete one of them.
795 if (!same_par_pos) {
796 // Only if the cursor has really moved.
797 if (old.pos() > 0
798 && old.pos() < oldpar.size()
799 && oldpar.isLineSeparator(old.pos())
800 && oldpar.isLineSeparator(old.pos() - 1)
801 && !oldpar.isDeleted(old.pos() - 1)
802 && !oldpar.isDeleted(old.pos())) {
803 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
804 // FIXME: This will not work anymore when we have multiple views of the same buffer
805 // In this case, we will have to correct also the cursors held by
806 // other bufferviews. It will probably be easier to do that in a more
807 // automated way in CursorSlice code. (JMarc 26/09/2001)
808 // correct all cursor parts
809 if (same_par) {
810 fixCursorAfterDelete(cur.top(), old.top());
811 need_anchor_change = true;
813 return true;
817 // only do our magic if we changed paragraph
818 if (same_par)
819 return false;
821 // don't delete anything if this is the ONLY paragraph!
822 if (old.lastpit() == 0)
823 return false;
825 // Do not delete empty paragraphs with keepempty set.
826 if (oldpar.allowEmpty())
827 return false;
829 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
830 // Delete old par.
831 old.recordUndo(ATOMIC_UNDO,
832 max(old.pit() - 1, pit_type(0)),
833 min(old.pit() + 1, old.lastpit()));
834 ParagraphList & plist = old.text()->paragraphs();
835 bool const soa = oldpar.params().startOfAppendix();
836 plist.erase(boost::next(plist.begin(), old.pit()));
837 // do not lose start of appendix marker (bug 4212)
838 if (soa && old.pit() < pit_type(plist.size()))
839 plist[old.pit()].params().startOfAppendix(true);
841 // see #warning (FIXME?) above
842 if (cur.depth() >= old.depth()) {
843 CursorSlice & curslice = cur[old.depth() - 1];
844 if (&curslice.inset() == &old.inset()
845 && curslice.pit() > old.pit()) {
846 --curslice.pit();
847 // since a paragraph has been deleted, all the
848 // insets after `old' have been copied and
849 // their address has changed. Therefore we
850 // need to `regenerate' cur. (JMarc)
851 cur.updateInsets(&(cur.bottom().inset()));
852 need_anchor_change = true;
855 return true;
858 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
859 need_anchor_change = true;
860 // We return true here because the Paragraph contents changed and
861 // we need a redraw before further action is processed.
862 return true;
865 return false;
869 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
871 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
873 for (pit_type pit = first; pit <= last; ++pit) {
874 Paragraph & par = pars_[pit];
876 // We allow all kinds of "mumbo-jumbo" when freespacing.
877 if (par.isFreeSpacing())
878 continue;
880 for (pos_type pos = 1; pos < par.size(); ++pos) {
881 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
882 && !par.isDeleted(pos - 1)) {
883 if (par.eraseChar(pos - 1, trackChanges)) {
884 --pos;
889 // don't delete anything if this is the only remaining paragraph within the given range
890 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
891 if (first == last)
892 continue;
894 // don't delete empty paragraphs with keepempty set
895 if (par.allowEmpty())
896 continue;
898 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
899 pars_.erase(boost::next(pars_.begin(), pit));
900 --pit;
901 --last;
902 continue;
905 par.stripLeadingSpaces(trackChanges);
910 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
912 cur.recordUndo(ATOMIC_UNDO, first, last);
916 void Text::recUndo(Cursor & cur, pit_type par) const
918 cur.recordUndo(ATOMIC_UNDO, par, par);
921 } // namespace lyx