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
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
30 #include "bufferview_funcs.h"
33 #include "CoordCache.h"
35 #include "CutAndPaste.h"
37 #include "DispatchResult.h"
38 #include "ErrorList.h"
39 #include "FuncRequest.h"
46 #include "Paragraph.h"
47 #include "TextMetrics.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "ParIterator.h"
52 #include "ServerSocket.h"
56 #include "frontends/FontMetrics.h"
58 #include "insets/InsetEnvironment.h"
60 #include "mathed/InsetMathHull.h"
62 #include "support/textutils.h"
64 #include <boost/current_function.hpp>
72 using std::ostringstream;
76 using std::istringstream;
79 : current_font(Font::ALL_INHERIT),
80 background_color_(Color::background),
85 bool Text::isMainText(Buffer const & buffer) const
87 return &buffer.text() == this;
91 // Gets the fully instantiated font at a given position in a paragraph
92 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
93 // The difference is that this one is used for displaying, and thus we
94 // are allowed to make cosmetic improvements. For instance make footnotes
96 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
97 pos_type const pos) const
99 BOOST_ASSERT(pos >= 0);
101 LayoutPtr const & layout = par.layout();
103 BufferParams const & params = buffer.params();
104 pos_type const body_pos = par.beginOfBody();
106 // We specialize the 95% common case:
107 if (!par.getDepth()) {
108 Font f = par.getFontSettings(params, pos);
109 if (!isMainText(buffer))
110 applyOuterFont(buffer, f);
113 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
114 lf = layout->labelfont;
115 rlf = layout->reslabelfont;
118 rlf = layout->resfont;
120 // In case the default family has been customized
121 if (lf.family() == Font::INHERIT_FAMILY)
122 rlf.setFamily(params.getFont().family());
123 return f.realize(rlf);
126 // The uncommon case need not be optimized as much
129 layoutfont = layout->labelfont;
131 layoutfont = layout->font;
133 Font font = par.getFontSettings(params, pos);
134 font.realize(layoutfont);
136 if (!isMainText(buffer))
137 applyOuterFont(buffer, font);
139 // Find the pit value belonging to paragraph. This will not break
140 // even if pars_ would not be a vector anymore.
141 // Performance appears acceptable.
143 pit_type pit = pars_.size();
144 for (pit_type it = 0; it < pit; ++it)
145 if (&pars_[it] == &par) {
149 // Realize against environment font information
150 // NOTE: the cast to pit_type should be removed when pit_type
151 // changes to a unsigned integer.
152 if (pit < pit_type(pars_.size()))
153 font.realize(outerFont(pit, pars_));
155 // Realize with the fonts of lesser depth.
156 font.realize(params.getFont());
161 // There are currently two font mechanisms in LyX:
162 // 1. The font attributes in a lyxtext, and
163 // 2. The inset-specific font properties, defined in an inset's
164 // metrics() and draw() methods and handed down the inset chain through
165 // the pi/mi parameters, and stored locally in a lyxtext in font_.
166 // This is where the two are integrated in the final fully realized
168 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
170 lf.reduce(buffer.params().getFont());
172 lf.setLanguage(font.language());
177 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
179 LayoutPtr const & layout = pars_[pit].layout();
181 if (!pars_[pit].getDepth()) {
182 Font lf = layout->resfont;
183 // In case the default family has been customized
184 if (layout->font.family() == Font::INHERIT_FAMILY)
185 lf.setFamily(buffer.params().getFont().family());
189 Font font = layout->font;
190 // Realize with the fonts of lesser depth.
191 //font.realize(outerFont(pit, paragraphs()));
192 font.realize(buffer.params().getFont());
198 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
200 LayoutPtr const & layout = par.layout();
202 if (!par.getDepth()) {
203 Font lf = layout->reslabelfont;
204 // In case the default family has been customized
205 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
206 lf.setFamily(buffer.params().getFont().family());
210 Font font = layout->labelfont;
211 // Realize with the fonts of lesser depth.
212 font.realize(buffer.params().getFont());
218 void Text::setCharFont(Buffer const & buffer, pit_type pit,
219 pos_type pos, Font const & fnt)
222 LayoutPtr const & layout = pars_[pit].layout();
224 // Get concrete layout font to reduce against
227 if (pos < pars_[pit].beginOfBody())
228 layoutfont = layout->labelfont;
230 layoutfont = layout->font;
232 // Realize against environment font information
233 if (pars_[pit].getDepth()) {
235 while (!layoutfont.resolved() &&
236 tp != pit_type(paragraphs().size()) &&
237 pars_[tp].getDepth()) {
238 tp = outerHook(tp, paragraphs());
239 if (tp != pit_type(paragraphs().size()))
240 layoutfont.realize(pars_[tp].layout()->font);
244 // Inside inset, apply the inset's font attributes if any
246 if (!isMainText(buffer))
247 layoutfont.realize(font_);
249 layoutfont.realize(buffer.params().getFont());
251 // Now, reduce font against full layout font
252 font.reduce(layoutfont);
254 pars_[pit].setFont(pos, font);
258 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
259 pos_type pos, Font const & font, bool toggleall)
261 BOOST_ASSERT(pars_[pit].isInset(pos) &&
262 pars_[pit].getInset(pos)->noFontChange());
264 Inset * const inset = pars_[pit].getInset(pos);
265 CursorSlice::idx_type endidx = inset->nargs();
266 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
267 Text * text = cs.text();
269 // last position of the cell
270 CursorSlice cellend = cs;
271 cellend.pit() = cellend.lastpit();
272 cellend.pos() = cellend.lastpos();
273 text->setFont(buffer, cs, cellend, font, toggleall);
279 // return past-the-last paragraph influenced by a layout change on pit
280 pit_type Text::undoSpan(pit_type pit)
282 pit_type end = paragraphs().size();
283 pit_type nextpit = pit + 1;
286 //because of parindents
287 if (!pars_[pit].getDepth())
288 return boost::next(nextpit);
289 //because of depth constrains
290 for (; nextpit != end; ++pit, ++nextpit) {
291 if (!pars_[pit].getDepth())
298 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
299 docstring const & layout)
301 BOOST_ASSERT(start != end);
303 BufferParams const & bufparams = buffer.params();
304 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
306 for (pit_type pit = start; pit != end; ++pit) {
307 Paragraph & par = pars_[pit];
308 par.applyLayout(lyxlayout);
309 if (lyxlayout->margintype == MARGIN_MANUAL)
310 par.setLabelWidthString(par.translateIfPossible(
311 lyxlayout->labelstring(), buffer.params()));
316 // set layout over selection and make a total rebreak of those paragraphs
317 void Text::setLayout(Cursor & cur, docstring const & layout)
319 BOOST_ASSERT(this == cur.text());
320 // special handling of new environment insets
321 BufferView & bv = cur.bv();
322 BufferParams const & params = bv.buffer().params();
323 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
324 if (lyxlayout->is_environment) {
325 // move everything in a new environment inset
326 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
327 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
328 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
329 lyx::dispatch(FuncRequest(LFUN_CUT));
330 Inset * inset = new InsetEnvironment(params, layout);
331 insertInset(cur, inset);
332 //inset->edit(cur, true);
333 //lyx::dispatch(FuncRequest(LFUN_PASTE));
337 pit_type start = cur.selBegin().pit();
338 pit_type end = cur.selEnd().pit() + 1;
339 pit_type undopit = undoSpan(end - 1);
340 recUndo(cur, start, undopit - 1);
341 setLayout(cur.buffer(), start, end, layout);
342 updateLabels(cur.buffer());
346 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
347 Paragraph const & par, int max_depth)
349 if (par.layout()->labeltype == LABEL_BIBLIO)
351 int const depth = par.params().depth();
352 if (type == Text::INC_DEPTH && depth < max_depth)
354 if (type == Text::DEC_DEPTH && depth > 0)
360 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
362 BOOST_ASSERT(this == cur.text());
363 // this happens when selecting several cells in tabular (bug 2630)
364 if (cur.selBegin().idx() != cur.selEnd().idx())
367 pit_type const beg = cur.selBegin().pit();
368 pit_type const end = cur.selEnd().pit() + 1;
369 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
371 for (pit_type pit = beg; pit != end; ++pit) {
372 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
374 max_depth = pars_[pit].getMaxDepthAfter();
380 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
382 BOOST_ASSERT(this == cur.text());
383 pit_type const beg = cur.selBegin().pit();
384 pit_type const end = cur.selEnd().pit() + 1;
385 recordUndoSelection(cur);
386 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
388 for (pit_type pit = beg; pit != end; ++pit) {
389 Paragraph & par = pars_[pit];
390 if (lyx::changeDepthAllowed(type, par, max_depth)) {
391 int const depth = par.params().depth();
392 if (type == INC_DEPTH)
393 par.params().depth(depth + 1);
395 par.params().depth(depth - 1);
397 max_depth = par.getMaxDepthAfter();
399 // this handles the counter labels, and also fixes up
400 // depth values for follow-on (child) paragraphs
401 updateLabels(cur.buffer());
405 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
407 BOOST_ASSERT(this == cur.text());
408 // Set the current_font
409 // Determine basis font
411 pit_type pit = cur.pit();
412 if (cur.pos() < pars_[pit].beginOfBody())
413 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
415 layoutfont = getLayoutFont(cur.buffer(), pit);
417 // Update current font
418 real_current_font.update(font,
419 cur.buffer().params().language,
422 // Reduce to implicit settings
423 current_font = real_current_font;
424 current_font.reduce(layoutfont);
425 // And resolve it completely
426 real_current_font.realize(layoutfont);
428 // if there is no selection that's all we need to do
429 if (!cur.selection())
432 // Ok, we have a selection.
433 recordUndoSelection(cur);
435 setFont(cur.buffer(), cur.selectionBegin().top(),
436 cur.selectionEnd().top(), font, toggleall);
440 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
441 CursorSlice const & end, Font const & font,
444 // Don't use forwardChar here as ditend might have
445 // pos() == lastpos() and forwardChar would miss it.
446 // Can't use forwardPos either as this descends into
448 Language const * language = buffer.params().language;
449 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
450 if (dit.pos() != dit.lastpos()) {
451 pit_type const pit = dit.pit();
452 pos_type const pos = dit.pos();
453 if (pars_[pit].isInset(pos) &&
454 pars_[pit].getInset(pos)->noFontChange())
455 // We need to propagate the font change to all
456 // text cells of the inset (bug 1973).
457 // FIXME: This should change, see documentation
458 // of noFontChange in Inset.h
459 setInsetFont(buffer, pit, pos, font, toggleall);
460 Font f = getFont(buffer, dit.paragraph(), pos);
461 f.update(font, language, toggleall);
462 setCharFont(buffer, pit, pos, f);
468 bool Text::cursorTop(Cursor & cur)
470 BOOST_ASSERT(this == cur.text());
471 return setCursor(cur, 0, 0);
475 bool Text::cursorBottom(Cursor & cur)
477 BOOST_ASSERT(this == cur.text());
478 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
482 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
484 BOOST_ASSERT(this == cur.text());
485 // If the mask is completely neutral, tell user
486 if (font == Font(Font::ALL_IGNORE)) {
487 // Could only happen with user style
488 cur.message(_("No font change defined."));
492 // Try implicit word selection
493 // If there is a change in the language the implicit word selection
495 CursorSlice resetCursor = cur.top();
496 bool implicitSelection =
497 font.language() == ignore_language
498 && font.number() == Font::IGNORE
499 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
502 setFont(cur, font, toggleall);
504 // Implicit selections are cleared afterwards
505 // and cursor is set to the original position.
506 if (implicitSelection) {
507 cur.clearSelection();
508 cur.top() = resetCursor;
514 docstring Text::getStringToIndex(Cursor const & cur)
516 BOOST_ASSERT(this == cur.text());
520 idxstring = cur.selectionAsString(false);
522 // Try implicit word selection. If there is a change
523 // in the language the implicit word selection is
526 selectWord(tmpcur, PREVIOUS_WORD);
528 if (!tmpcur.selection())
529 cur.message(_("Nothing to index!"));
530 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
531 cur.message(_("Cannot index more than one paragraph!"));
533 idxstring = tmpcur.selectionAsString(false);
540 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
542 BOOST_ASSERT(cur.text());
543 // make sure that the depth behind the selection are restored, too
544 pit_type undopit = undoSpan(cur.selEnd().pit());
545 recUndo(cur, cur.selBegin().pit(), undopit - 1);
547 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
549 Paragraph & par = pars_[pit];
550 ParagraphParameters params = par.params();
551 params.read(to_utf8(arg), merge);
552 Layout const & layout = *(par.layout());
553 par.params().apply(params, layout);
558 //FIXME This is a little redundant now, but it's probably worth keeping,
559 //especially if we're going to go away from using serialization internally
561 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
563 BOOST_ASSERT(cur.text());
564 // make sure that the depth behind the selection are restored, too
565 pit_type undopit = undoSpan(cur.selEnd().pit());
566 recUndo(cur, cur.selBegin().pit(), undopit - 1);
568 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
570 Paragraph & par = pars_[pit];
571 Layout const & layout = *(par.layout());
572 par.params().apply(p, layout);
577 // this really should just insert the inset and not move the cursor.
578 void Text::insertInset(Cursor & cur, Inset * inset)
580 BOOST_ASSERT(this == cur.text());
582 cur.paragraph().insertInset(cur.pos(), inset, current_font,
583 Change(cur.buffer().params().trackChanges ?
584 Change::INSERTED : Change::UNCHANGED));
588 // needed to insert the selection
589 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
591 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
592 current_font, str, autoBreakRows_);
596 // turn double CR to single CR, others are converted into one
597 // blank. Then insertStringAsLines is called
598 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
600 docstring linestr = str;
601 bool newline_inserted = false;
603 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
604 if (linestr[i] == '\n') {
605 if (newline_inserted) {
606 // we know that \r will be ignored by
607 // insertStringAsLines. Of course, it is a dirty
608 // trick, but it works...
609 linestr[i - 1] = '\r';
613 newline_inserted = true;
615 } else if (isPrintable(linestr[i])) {
616 newline_inserted = false;
619 insertStringAsLines(cur, linestr);
623 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
624 bool setfont, bool boundary)
627 setCursorIntern(cur, par, pos, setfont, boundary);
628 return cur.bv().checkDepm(cur, old);
632 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
634 BOOST_ASSERT(par != int(paragraphs().size()));
638 // now some strict checking
639 Paragraph & para = getPar(par);
641 // None of these should happen, but we're scaredy-cats
643 lyxerr << "dont like -1" << endl;
647 if (pos > para.size()) {
648 lyxerr << "dont like 1, pos: " << pos
649 << " size: " << para.size()
650 << " par: " << par << endl;
656 void Text::setCursorIntern(Cursor & cur,
657 pit_type par, pos_type pos, bool setfont, bool boundary)
659 BOOST_ASSERT(this == cur.text());
660 cur.boundary(boundary);
661 setCursor(cur.top(), par, pos);
667 void Text::setCurrentFont(Cursor & cur)
669 BOOST_ASSERT(this == cur.text());
670 pos_type pos = cur.pos();
671 Paragraph & par = cur.paragraph();
673 // are we behind previous char in fact? -> go to that char
674 if (pos > 0 && cur.boundary())
677 // find position to take the font from
679 // paragraph end? -> font of last char
680 if (pos == cur.lastpos())
682 // on space? -> look at the words in front of space
683 else if (pos > 0 && par.isSeparator(pos)) {
684 // abc| def -> font of c
685 // abc |[WERBEH], i.e. boundary==true -> font of c
686 // abc [WERBEH]| def, font of the space
687 if (!isRTLBoundary(cur.buffer(), par, pos))
693 BufferParams const & bufparams = cur.buffer().params();
694 current_font = par.getFontSettings(bufparams, pos);
695 real_current_font = getFont(cur.buffer(), par, pos);
697 // special case for paragraph end
698 if (cur.pos() == cur.lastpos()
699 && isRTLBoundary(cur.buffer(), par, cur.pos())
700 && !cur.boundary()) {
701 Language const * lang = par.getParLanguage(bufparams);
702 current_font.setLanguage(lang);
703 current_font.setNumber(Font::OFF);
704 real_current_font.setLanguage(lang);
705 real_current_font.setNumber(Font::OFF);
710 bool Text::checkAndActivateInset(Cursor & cur, bool front)
714 if (front && cur.pos() == cur.lastpos())
716 if (!front && cur.pos() == 0)
718 Inset * inset = front ? cur.nextInset() : cur.prevInset();
719 if (!isHighlyEditableInset(inset))
722 * Apparently, when entering an inset we are expected to be positioned
723 * *before* it in the containing paragraph, regardless of the direction
724 * from which we are entering. Otherwise, cursor placement goes awry,
725 * and when we exit from the beginning, we'll be placed *after* the
730 inset->edit(cur, front);
735 bool Text::cursorLeft(Cursor & cur)
737 // Tell BufferView to test for FitCursor in any case!
738 cur.updateFlags(Update::FitCursor);
740 // not at paragraph start?
742 // if on right side of boundary (i.e. not at paragraph end, but line end)
743 // -> skip it, i.e. set boundary to true, i.e. go only logically left
744 // there are some exceptions to ignore this: lineseps, newlines, spaces
746 // some effectless debug code to see the values in the debugger
747 bool bound = cur.boundary();
748 int rowpos = cur.textRow().pos();
750 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
751 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
752 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
754 if (!cur.boundary() &&
755 cur.textRow().pos() == cur.pos() &&
756 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
757 !cur.paragraph().isNewline(cur.pos() - 1) &&
758 !cur.paragraph().isSeparator(cur.pos() - 1)) {
759 return setCursor(cur, cur.pit(), cur.pos(), true, true);
762 // go left and try to enter inset
763 if (checkAndActivateInset(cur, false))
766 // normal character left
767 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
770 // move to the previous paragraph or do nothing
772 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
777 bool Text::cursorRight(Cursor & cur)
779 // Tell BufferView to test for FitCursor in any case!
780 cur.updateFlags(Update::FitCursor);
782 // not at paragraph end?
783 if (cur.pos() != cur.lastpos()) {
784 // in front of editable inset, i.e. jump into it?
785 if (checkAndActivateInset(cur, true))
788 // if left of boundary -> just jump to right side
789 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
790 if (cur.boundary() &&
791 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
792 return setCursor(cur, cur.pit(), cur.pos(), true, false);
794 // next position is left of boundary,
795 // but go to next line for special cases like space, newline, linesep
797 // some effectless debug code to see the values in the debugger
798 int endpos = cur.textRow().endpos();
799 int lastpos = cur.lastpos();
801 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
802 bool newline = cur.paragraph().isNewline(cur.pos());
803 bool sep = cur.paragraph().isSeparator(cur.pos());
804 if (cur.pos() != cur.lastpos()) {
805 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
806 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
807 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
810 if (cur.textRow().endpos() == cur.pos() + 1 &&
811 cur.textRow().endpos() != cur.lastpos() &&
812 !cur.paragraph().isNewline(cur.pos()) &&
813 !cur.paragraph().isLineSeparator(cur.pos()) &&
814 !cur.paragraph().isSeparator(cur.pos())) {
815 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
818 // in front of RTL boundary? Stay on this side of the boundary because:
819 // ab|cDDEEFFghi -> abc|DDEEFFghi
820 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
821 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
824 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
827 // move to next paragraph
828 if (cur.pit() != cur.lastpit())
829 return setCursor(cur, cur.pit() + 1, 0);
834 bool Text::cursorUpParagraph(Cursor & cur)
836 bool updated = false;
838 updated = setCursor(cur, cur.pit(), 0);
839 else if (cur.pit() != 0)
840 updated = setCursor(cur, cur.pit() - 1, 0);
845 bool Text::cursorDownParagraph(Cursor & cur)
847 bool updated = false;
848 if (cur.pit() != cur.lastpit())
849 updated = setCursor(cur, cur.pit() + 1, 0);
851 updated = setCursor(cur, cur.pit(), cur.lastpos());
856 // fix the cursor `cur' after a characters has been deleted at `where'
857 // position. Called by deleteEmptyParagraphMechanism
858 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
860 // Do nothing if cursor is not in the paragraph where the
862 if (cur.pit() != where.pit())
865 // If cursor position is after the deletion place update it
866 if (cur.pos() > where.pos())
869 // Check also if we don't want to set the cursor on a spot behind the
870 // pagragraph because we erased the last character.
871 if (cur.pos() > cur.lastpos())
872 cur.pos() = cur.lastpos();
876 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
877 Cursor & old, bool & need_anchor_change)
879 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
881 Paragraph & oldpar = old.paragraph();
883 // We allow all kinds of "mumbo-jumbo" when freespacing.
884 if (oldpar.isFreeSpacing())
887 /* Ok I'll put some comments here about what is missing.
888 There are still some small problems that can lead to
889 double spaces stored in the document file or space at
890 the beginning of paragraphs(). This happens if you have
891 the cursor between to spaces and then save. Or if you
892 cut and paste and the selection have a space at the
893 beginning and then save right after the paste. (Lgb)
896 // If old.pos() == 0 and old.pos()(1) == LineSeparator
897 // delete the LineSeparator.
900 // If old.pos() == 1 and old.pos()(0) == LineSeparator
901 // delete the LineSeparator.
904 bool const same_inset = &old.inset() == &cur.inset();
905 bool const same_par = same_inset && old.pit() == cur.pit();
906 bool const same_par_pos = same_par && old.pos() == cur.pos();
908 // If the chars around the old cursor were spaces, delete one of them.
910 // Only if the cursor has really moved.
912 && old.pos() < oldpar.size()
913 && oldpar.isLineSeparator(old.pos())
914 && oldpar.isLineSeparator(old.pos() - 1)
915 && !oldpar.isDeleted(old.pos() - 1)
916 && !oldpar.isDeleted(old.pos())) {
917 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
918 // FIXME: This will not work anymore when we have multiple views of the same buffer
919 // In this case, we will have to correct also the cursors held by
920 // other bufferviews. It will probably be easier to do that in a more
921 // automated way in CursorSlice code. (JMarc 26/09/2001)
922 // correct all cursor parts
924 fixCursorAfterDelete(cur.top(), old.top());
925 need_anchor_change = true;
931 // only do our magic if we changed paragraph
935 // don't delete anything if this is the ONLY paragraph!
936 if (old.lastpit() == 0)
939 // Do not delete empty paragraphs with keepempty set.
940 if (oldpar.allowEmpty())
943 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
945 recordUndo(old, Undo::ATOMIC,
946 max(old.pit() - 1, pit_type(0)),
947 min(old.pit() + 1, old.lastpit()));
948 ParagraphList & plist = old.text()->paragraphs();
949 plist.erase(boost::next(plist.begin(), old.pit()));
951 // see #warning (FIXME?) above
952 if (cur.depth() >= old.depth()) {
953 CursorSlice & curslice = cur[old.depth() - 1];
954 if (&curslice.inset() == &old.inset()
955 && curslice.pit() > old.pit()) {
957 // since a paragraph has been deleted, all the
958 // insets after `old' have been copied and
959 // their address has changed. Therefore we
960 // need to `regenerate' cur. (JMarc)
961 cur.updateInsets(&(cur.bottom().inset()));
962 need_anchor_change = true;
968 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
969 need_anchor_change = true;
970 // We return true here because the Paragraph contents changed and
971 // we need a redraw before further action is processed.
979 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
981 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
983 for (pit_type pit = first; pit <= last; ++pit) {
984 Paragraph & par = pars_[pit];
986 // We allow all kinds of "mumbo-jumbo" when freespacing.
987 if (par.isFreeSpacing())
990 for (pos_type pos = 1; pos < par.size(); ++pos) {
991 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
992 && !par.isDeleted(pos - 1)) {
993 if (par.eraseChar(pos - 1, trackChanges)) {
999 // don't delete anything if this is the only remaining paragraph within the given range
1000 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1004 // don't delete empty paragraphs with keepempty set
1005 if (par.allowEmpty())
1008 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1009 pars_.erase(boost::next(pars_.begin(), pit));
1015 par.stripLeadingSpaces(trackChanges);
1020 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1022 recordUndo(cur, Undo::ATOMIC, first, last);
1026 void Text::recUndo(Cursor & cur, pit_type par) const
1028 recordUndo(cur, Undo::ATOMIC, par, par);