* qt_helpers.cpp:
[lyx.git] / src / Text2.cpp
blobed2f3fd0bac72effaee670e743a8597f014cc4b2
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 "insets/InsetCollapsable.h"
51 #include "mathed/InsetMathHull.h"
53 #include "support/lassert.h"
54 #include "support/debug.h"
55 #include "support/gettext.h"
56 #include "support/textutils.h"
58 #include <boost/next_prior.hpp>
60 #include <sstream>
62 using namespace std;
64 namespace lyx {
66 Text::Text()
67 : autoBreakRows_(false)
71 bool Text::isMainText(Buffer const & buffer) const
73 return &buffer.text() == this;
77 // Note that this is supposed to return a fully realized font.
78 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
80 Layout const & layout = pars_[pit].layout();
82 if (!pars_[pit].getDepth()) {
83 FontInfo lf = layout.resfont;
84 // In case the default family has been customized
85 if (layout.font.family() == INHERIT_FAMILY)
86 lf.setFamily(buffer.params().getFont().fontInfo().family());
87 // FIXME
88 // It ought to be possible here just to use Inset::getLayout() and skip
89 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
90 // now, because Inset::getLayout() will return a default-constructed
91 // InsetLayout, and that e.g. sets the foreground color to red. So we
92 // need to do some work to make that possible.
93 InsetCollapsable const * icp = pars_[pit].inInset().asInsetCollapsable();
94 if (!icp)
95 return lf;
96 FontInfo icf = icp->getLayout().font();
97 icf.realize(lf);
98 return icf;
101 FontInfo font = layout.font;
102 // Realize with the fonts of lesser depth.
103 //font.realize(outerFont(pit, paragraphs()));
104 font.realize(buffer.params().getFont().fontInfo());
106 return font;
110 // Note that this is supposed to return a fully realized font.
111 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
113 Layout const & layout = par.layout();
115 if (!par.getDepth()) {
116 FontInfo lf = layout.reslabelfont;
117 // In case the default family has been customized
118 if (layout.labelfont.family() == INHERIT_FAMILY)
119 lf.setFamily(buffer.params().getFont().fontInfo().family());
120 return lf;
123 FontInfo font = layout.labelfont;
124 // Realize with the fonts of lesser depth.
125 font.realize(buffer.params().getFont().fontInfo());
127 return font;
131 void Text::setCharFont(Buffer const & buffer, pit_type pit,
132 pos_type pos, Font const & fnt, Font const & display_font)
134 Font font = fnt;
135 Layout const & layout = pars_[pit].layout();
137 // Get concrete layout font to reduce against
138 FontInfo layoutfont;
140 if (pos < pars_[pit].beginOfBody())
141 layoutfont = layout.labelfont;
142 else
143 layoutfont = layout.font;
145 // Realize against environment font information
146 if (pars_[pit].getDepth()) {
147 pit_type tp = pit;
148 while (!layoutfont.resolved() &&
149 tp != pit_type(paragraphs().size()) &&
150 pars_[tp].getDepth()) {
151 tp = outerHook(tp, paragraphs());
152 if (tp != pit_type(paragraphs().size()))
153 layoutfont.realize(pars_[tp].layout().font);
157 // Inside inset, apply the inset's font attributes if any
158 // (charstyle!)
159 if (!isMainText(buffer))
160 layoutfont.realize(display_font.fontInfo());
162 layoutfont.realize(buffer.params().getFont().fontInfo());
164 // Now, reduce font against full layout font
165 font.fontInfo().reduce(layoutfont);
167 pars_[pit].setFont(pos, font);
171 void Text::setInsetFont(BufferView const & bv, pit_type pit,
172 pos_type pos, Font const & font, bool toggleall)
174 Inset * const inset = pars_[pit].getInset(pos);
175 LASSERT(inset && inset->noFontChange(), /**/);
177 CursorSlice::idx_type endidx = inset->nargs();
178 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
179 Text * text = cs.text();
180 if (text) {
181 // last position of the cell
182 CursorSlice cellend = cs;
183 cellend.pit() = cellend.lastpit();
184 cellend.pos() = cellend.lastpos();
185 text->setFont(bv, cs, cellend, font, toggleall);
191 // return past-the-last paragraph influenced by a layout change on pit
192 pit_type Text::undoSpan(pit_type pit)
194 pit_type const end = paragraphs().size();
195 pit_type nextpit = pit + 1;
196 if (nextpit == end)
197 return nextpit;
198 //because of parindents
199 if (!pars_[pit].getDepth())
200 return boost::next(nextpit);
201 //because of depth constrains
202 for (; nextpit != end; ++pit, ++nextpit) {
203 if (!pars_[pit].getDepth())
204 break;
206 return nextpit;
210 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
211 docstring const & layout)
213 LASSERT(start != end, /**/);
215 BufferParams const & bp = buffer.params();
216 Layout const & lyxlayout = bp.documentClass()[layout];
218 for (pit_type pit = start; pit != end; ++pit) {
219 Paragraph & par = pars_[pit];
220 par.applyLayout(lyxlayout);
221 if (lyxlayout.margintype == MARGIN_MANUAL)
222 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
227 // set layout over selection and make a total rebreak of those paragraphs
228 void Text::setLayout(Cursor & cur, docstring const & layout)
230 LASSERT(this == cur.text(), /**/);
232 pit_type start = cur.selBegin().pit();
233 pit_type end = cur.selEnd().pit() + 1;
234 pit_type undopit = undoSpan(end - 1);
235 recUndo(cur, start, undopit - 1);
236 setLayout(*cur.buffer(), start, end, layout);
237 cur.buffer()->updateLabels();
241 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
242 Paragraph const & par, int max_depth)
244 if (par.layout().labeltype == LABEL_BIBLIO)
245 return false;
246 int const depth = par.params().depth();
247 if (type == Text::INC_DEPTH && depth < max_depth)
248 return true;
249 if (type == Text::DEC_DEPTH && depth > 0)
250 return true;
251 return false;
255 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
257 LASSERT(this == cur.text(), /**/);
258 // this happens when selecting several cells in tabular (bug 2630)
259 if (cur.selBegin().idx() != cur.selEnd().idx())
260 return false;
262 pit_type const beg = cur.selBegin().pit();
263 pit_type const end = cur.selEnd().pit() + 1;
264 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
266 for (pit_type pit = beg; pit != end; ++pit) {
267 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
268 return true;
269 max_depth = pars_[pit].getMaxDepthAfter();
271 return false;
275 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
277 LASSERT(this == cur.text(), /**/);
278 pit_type const beg = cur.selBegin().pit();
279 pit_type const end = cur.selEnd().pit() + 1;
280 cur.recordUndoSelection();
281 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
283 for (pit_type pit = beg; pit != end; ++pit) {
284 Paragraph & par = pars_[pit];
285 if (lyx::changeDepthAllowed(type, par, max_depth)) {
286 int const depth = par.params().depth();
287 if (type == INC_DEPTH)
288 par.params().depth(depth + 1);
289 else
290 par.params().depth(depth - 1);
292 max_depth = par.getMaxDepthAfter();
294 // this handles the counter labels, and also fixes up
295 // depth values for follow-on (child) paragraphs
296 cur.buffer()->updateLabels();
300 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
302 LASSERT(this == cur.text(), /**/);
303 // Set the current_font
304 // Determine basis font
305 FontInfo layoutfont;
306 pit_type pit = cur.pit();
307 if (cur.pos() < pars_[pit].beginOfBody())
308 layoutfont = labelFont(*cur.buffer(), pars_[pit]);
309 else
310 layoutfont = layoutFont(*cur.buffer(), pit);
312 // Update current font
313 cur.real_current_font.update(font,
314 cur.buffer()->params().language,
315 toggleall);
317 // Reduce to implicit settings
318 cur.current_font = cur.real_current_font;
319 cur.current_font.fontInfo().reduce(layoutfont);
320 // And resolve it completely
321 cur.real_current_font.fontInfo().realize(layoutfont);
323 // if there is no selection that's all we need to do
324 if (!cur.selection())
325 return;
327 // Ok, we have a selection.
328 cur.recordUndoSelection();
330 setFont(cur.bv(), cur.selectionBegin().top(),
331 cur.selectionEnd().top(), font, toggleall);
335 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
336 CursorSlice const & end, Font const & font,
337 bool toggleall)
339 Buffer const & buffer = bv.buffer();
341 // Don't use forwardChar here as ditend might have
342 // pos() == lastpos() and forwardChar would miss it.
343 // Can't use forwardPos either as this descends into
344 // nested insets.
345 Language const * language = buffer.params().language;
346 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
347 if (dit.pos() == dit.lastpos())
348 continue;
349 pit_type const pit = dit.pit();
350 pos_type const pos = dit.pos();
351 Inset * inset = pars_[pit].getInset(pos);
352 if (inset && inset->noFontChange()) {
353 // We need to propagate the font change to all
354 // text cells of the inset (bug 1973).
355 // FIXME: This should change, see documentation
356 // of noFontChange in Inset.h
357 setInsetFont(bv, pit, pos, font, toggleall);
359 TextMetrics const & tm = bv.textMetrics(this);
360 Font f = tm.displayFont(pit, pos);
361 f.update(font, language, toggleall);
362 setCharFont(buffer, pit, pos, f, tm.font_);
367 bool Text::cursorTop(Cursor & cur)
369 LASSERT(this == cur.text(), /**/);
370 return setCursor(cur, 0, 0);
374 bool Text::cursorBottom(Cursor & cur)
376 LASSERT(this == cur.text(), /**/);
377 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
381 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
383 LASSERT(this == cur.text(), /**/);
384 // If the mask is completely neutral, tell user
385 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
386 // Could only happen with user style
387 cur.message(_("No font change defined."));
388 return;
391 // Try implicit word selection
392 // If there is a change in the language the implicit word selection
393 // is disabled.
394 CursorSlice const resetCursor = cur.top();
395 bool const implicitSelection =
396 font.language() == ignore_language
397 && font.fontInfo().number() == FONT_IGNORE
398 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
400 // Set font
401 setFont(cur, font, toggleall);
403 // Implicit selections are cleared afterwards
404 // and cursor is set to the original position.
405 if (implicitSelection) {
406 cur.clearSelection();
407 cur.top() = resetCursor;
408 cur.resetAnchor();
413 docstring Text::getStringToIndex(Cursor const & cur)
415 LASSERT(this == cur.text(), /**/);
417 if (cur.selection())
418 return cur.selectionAsString(false);
420 // Try implicit word selection. If there is a change
421 // in the language the implicit word selection is
422 // disabled.
423 Cursor tmpcur = cur;
424 selectWord(tmpcur, PREVIOUS_WORD);
426 if (!tmpcur.selection())
427 cur.message(_("Nothing to index!"));
428 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
429 cur.message(_("Cannot index more than one paragraph!"));
430 else
431 return tmpcur.selectionAsString(false);
433 return docstring();
437 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
439 LASSERT(cur.text(), /**/);
440 // make sure that the depth behind the selection are restored, too
441 pit_type undopit = undoSpan(cur.selEnd().pit());
442 recUndo(cur, cur.selBegin().pit(), undopit - 1);
444 //FIXME UNICODE
445 string const argument = to_utf8(arg);
446 depth_type priordepth = -1;
447 Layout priorlayout;
448 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
449 pit <= end; ++pit) {
450 Paragraph & par = pars_[pit];
451 ParagraphParameters params = par.params();
452 params.read(argument, merge);
453 // Changes to label width string apply to all paragraphs
454 // with same layout in a sequence.
455 // Do this only once for a selected range of paragraphs
456 // of the same layout and depth.
457 if (par.getDepth() != priordepth || par.layout() != priorlayout)
458 setLabelWidthStringToSequence(pit, pars_,
459 params.labelWidthString());
460 par.params().apply(params, par.layout());
461 priordepth = par.getDepth();
462 priorlayout = par.layout();
467 //FIXME This is a little redundant now, but it's probably worth keeping,
468 //especially if we're going to go away from using serialization internally
469 //quite so much.
470 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
472 LASSERT(cur.text(), /**/);
473 // make sure that the depth behind the selection are restored, too
474 pit_type undopit = undoSpan(cur.selEnd().pit());
475 recUndo(cur, cur.selBegin().pit(), undopit - 1);
477 depth_type priordepth = -1;
478 Layout priorlayout;
479 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
480 pit <= end; ++pit) {
481 Paragraph & par = pars_[pit];
482 // Changes to label width string apply to all paragraphs
483 // with same layout in a sequence.
484 // Do this only once for a selected range of paragraphs
485 // of the same layout and depth.
486 if (par.getDepth() != priordepth || par.layout() != priorlayout)
487 setLabelWidthStringToSequence(pit, pars_,
488 par.params().labelWidthString());
489 par.params().apply(p, par.layout());
490 priordepth = par.getDepth();
491 priorlayout = par.layout();
496 // this really should just insert the inset and not move the cursor.
497 void Text::insertInset(Cursor & cur, Inset * inset)
499 LASSERT(this == cur.text(), /**/);
500 LASSERT(inset, /**/);
501 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
502 Change(cur.buffer()->params().trackChanges
503 ? Change::INSERTED : Change::UNCHANGED));
507 // needed to insert the selection
508 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
510 cur.buffer()->insertStringAsLines(pars_, cur.pit(), cur.pos(),
511 cur.current_font, str, autoBreakRows_);
515 // turn double CR to single CR, others are converted into one
516 // blank. Then insertStringAsLines is called
517 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
519 docstring linestr = str;
520 bool newline_inserted = false;
522 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
523 if (linestr[i] == '\n') {
524 if (newline_inserted) {
525 // we know that \r will be ignored by
526 // insertStringAsLines. Of course, it is a dirty
527 // trick, but it works...
528 linestr[i - 1] = '\r';
529 linestr[i] = '\n';
530 } else {
531 linestr[i] = ' ';
532 newline_inserted = true;
534 } else if (isPrintable(linestr[i])) {
535 newline_inserted = false;
538 insertStringAsLines(cur, linestr);
542 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
543 bool setfont, bool boundary)
545 TextMetrics const & tm = cur.bv().textMetrics(this);
546 bool const update_needed = !tm.contains(par);
547 Cursor old = cur;
548 setCursorIntern(cur, par, pos, setfont, boundary);
549 return cur.bv().checkDepm(cur, old) || update_needed;
553 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
555 LASSERT(par != int(paragraphs().size()), /**/);
556 cur.pit() = par;
557 cur.pos() = pos;
559 // now some strict checking
560 Paragraph & para = getPar(par);
562 // None of these should happen, but we're scaredy-cats
563 if (pos < 0) {
564 lyxerr << "dont like -1" << endl;
565 LASSERT(false, /**/);
568 if (pos > para.size()) {
569 lyxerr << "dont like 1, pos: " << pos
570 << " size: " << para.size()
571 << " par: " << par << endl;
572 LASSERT(false, /**/);
577 void Text::setCursorIntern(Cursor & cur,
578 pit_type par, pos_type pos, bool setfont, bool boundary)
580 LASSERT(this == cur.text(), /**/);
581 cur.boundary(boundary);
582 setCursor(cur.top(), par, pos);
583 if (setfont)
584 cur.setCurrentFont();
588 bool Text::checkAndActivateInset(Cursor & cur, bool front)
590 if (cur.selection())
591 return false;
592 if (front && cur.pos() == cur.lastpos())
593 return false;
594 if (!front && cur.pos() == 0)
595 return false;
596 Inset * inset = front ? cur.nextInset() : cur.prevInset();
597 if (!inset || !inset->editable())
598 return false;
600 * Apparently, when entering an inset we are expected to be positioned
601 * *before* it in the containing paragraph, regardless of the direction
602 * from which we are entering. Otherwise, cursor placement goes awry,
603 * and when we exit from the beginning, we'll be placed *after* the
604 * inset.
606 if (!front)
607 --cur.pos();
608 inset->edit(cur, front);
609 return true;
613 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
615 if (cur.selection())
616 return false;
617 if (cur.pos() == -1)
618 return false;
619 if (cur.pos() == cur.lastpos())
620 return false;
621 Paragraph & par = cur.paragraph();
622 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
623 if (!inset || !inset->editable())
624 return false;
625 inset->edit(cur, movingForward,
626 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
627 return true;
631 bool Text::cursorBackward(Cursor & cur)
633 // Tell BufferView to test for FitCursor in any case!
634 cur.updateFlags(Update::FitCursor);
636 // not at paragraph start?
637 if (cur.pos() > 0) {
638 // if on right side of boundary (i.e. not at paragraph end, but line end)
639 // -> skip it, i.e. set boundary to true, i.e. go only logically left
640 // there are some exceptions to ignore this: lineseps, newlines, spaces
641 #if 0
642 // some effectless debug code to see the values in the debugger
643 bool bound = cur.boundary();
644 int rowpos = cur.textRow().pos();
645 int pos = cur.pos();
646 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
647 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
648 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
649 #endif
650 if (!cur.boundary() &&
651 cur.textRow().pos() == cur.pos() &&
652 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
653 !cur.paragraph().isNewline(cur.pos() - 1) &&
654 !cur.paragraph().isSeparator(cur.pos() - 1)) {
655 return setCursor(cur, cur.pit(), cur.pos(), true, true);
658 // go left and try to enter inset
659 if (checkAndActivateInset(cur, false))
660 return false;
662 // normal character left
663 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
666 // move to the previous paragraph or do nothing
667 if (cur.pit() > 0)
668 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
669 return false;
673 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
675 Cursor temp_cur = cur;
676 temp_cur.posVisLeft(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::cursorVisRight(Cursor & cur, bool skip_inset)
688 Cursor temp_cur = cur;
689 temp_cur.posVisRight(skip_inset);
690 if (temp_cur.depth() > cur.depth()) {
691 cur = temp_cur;
692 return false;
694 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
695 true, temp_cur.boundary());
699 bool Text::cursorForward(Cursor & cur)
701 // Tell BufferView to test for FitCursor in any case!
702 cur.updateFlags(Update::FitCursor);
704 // not at paragraph end?
705 if (cur.pos() != cur.lastpos()) {
706 // in front of editable inset, i.e. jump into it?
707 if (checkAndActivateInset(cur, true))
708 return false;
710 TextMetrics const & tm = cur.bv().textMetrics(this);
711 // if left of boundary -> just jump to right side
712 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
713 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
714 return setCursor(cur, cur.pit(), cur.pos(), true, false);
716 // next position is left of boundary,
717 // but go to next line for special cases like space, newline, linesep
718 #if 0
719 // some effectless debug code to see the values in the debugger
720 int endpos = cur.textRow().endpos();
721 int lastpos = cur.lastpos();
722 int pos = cur.pos();
723 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
724 bool newline = cur.paragraph().isNewline(cur.pos());
725 bool sep = cur.paragraph().isSeparator(cur.pos());
726 if (cur.pos() != cur.lastpos()) {
727 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
728 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
729 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
731 #endif
732 if (cur.textRow().endpos() == cur.pos() + 1 &&
733 cur.textRow().endpos() != cur.lastpos() &&
734 !cur.paragraph().isNewline(cur.pos()) &&
735 !cur.paragraph().isLineSeparator(cur.pos()) &&
736 !cur.paragraph().isSeparator(cur.pos())) {
737 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
740 // in front of RTL boundary? Stay on this side of the boundary because:
741 // ab|cDDEEFFghi -> abc|DDEEFFghi
742 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
743 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
745 // move right
746 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
749 // move to next paragraph
750 if (cur.pit() != cur.lastpit())
751 return setCursor(cur, cur.pit() + 1, 0, true, false);
752 return false;
756 bool Text::cursorUpParagraph(Cursor & cur)
758 bool updated = false;
759 if (cur.pos() > 0)
760 updated = setCursor(cur, cur.pit(), 0);
761 else if (cur.pit() != 0)
762 updated = setCursor(cur, cur.pit() - 1, 0);
763 return updated;
767 bool Text::cursorDownParagraph(Cursor & cur)
769 bool updated = false;
770 if (cur.pit() != cur.lastpit())
771 updated = setCursor(cur, cur.pit() + 1, 0);
772 else
773 updated = setCursor(cur, cur.pit(), cur.lastpos());
774 return updated;
778 // fix the cursor `cur' after a characters has been deleted at `where'
779 // position. Called by deleteEmptyParagraphMechanism
780 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
782 // Do nothing if cursor is not in the paragraph where the
783 // deletion occured,
784 if (cur.pit() != where.pit())
785 return;
787 // If cursor position is after the deletion place update it
788 if (cur.pos() > where.pos())
789 --cur.pos();
791 // Check also if we don't want to set the cursor on a spot behind the
792 // pagragraph because we erased the last character.
793 if (cur.pos() > cur.lastpos())
794 cur.pos() = cur.lastpos();
798 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
799 Cursor & old, bool & need_anchor_change)
801 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
803 Paragraph & oldpar = old.paragraph();
805 // We allow all kinds of "mumbo-jumbo" when freespacing.
806 if (oldpar.isFreeSpacing())
807 return false;
809 /* Ok I'll put some comments here about what is missing.
810 There are still some small problems that can lead to
811 double spaces stored in the document file or space at
812 the beginning of paragraphs(). This happens if you have
813 the cursor between to spaces and then save. Or if you
814 cut and paste and the selection have a space at the
815 beginning and then save right after the paste. (Lgb)
818 // If old.pos() == 0 and old.pos()(1) == LineSeparator
819 // delete the LineSeparator.
820 // MISSING
822 // If old.pos() == 1 and old.pos()(0) == LineSeparator
823 // delete the LineSeparator.
824 // MISSING
826 // Find a common inset and the corresponding depth.
827 size_t depth = 0;
828 for (; depth < cur.depth(); ++depth)
829 if (&old.inset() == &cur[depth].inset())
830 break;
832 // Whether a common inset is found and whether the cursor is still in
833 // the same paragraph (possibly nested).
834 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
835 bool const same_par_pos = depth == cur.depth() - 1 && same_par
836 && old.pos() == cur[depth].pos();
838 // If the chars around the old cursor were spaces, delete one of them.
839 if (!same_par_pos) {
840 // Only if the cursor has really moved.
841 if (old.pos() > 0
842 && old.pos() < oldpar.size()
843 && oldpar.isLineSeparator(old.pos())
844 && oldpar.isLineSeparator(old.pos() - 1)
845 && !oldpar.isDeleted(old.pos() - 1)
846 && !oldpar.isDeleted(old.pos())) {
847 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
848 // FIXME: This will not work anymore when we have multiple views of the same buffer
849 // In this case, we will have to correct also the cursors held by
850 // other bufferviews. It will probably be easier to do that in a more
851 // automated way in CursorSlice code. (JMarc 26/09/2001)
852 // correct all cursor parts
853 if (same_par) {
854 fixCursorAfterDelete(cur[depth], old.top());
855 need_anchor_change = true;
857 return true;
861 // only do our magic if we changed paragraph
862 if (same_par)
863 return false;
865 // don't delete anything if this is the ONLY paragraph!
866 if (old.lastpit() == 0)
867 return false;
869 // Do not delete empty paragraphs with keepempty set.
870 if (oldpar.allowEmpty())
871 return false;
873 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
874 // Delete old par.
875 old.recordUndo(ATOMIC_UNDO,
876 max(old.pit() - 1, pit_type(0)),
877 min(old.pit() + 1, old.lastpit()));
878 ParagraphList & plist = old.text()->paragraphs();
879 bool const soa = oldpar.params().startOfAppendix();
880 plist.erase(boost::next(plist.begin(), old.pit()));
881 // do not lose start of appendix marker (bug 4212)
882 if (soa && old.pit() < pit_type(plist.size()))
883 plist[old.pit()].params().startOfAppendix(true);
885 // see #warning (FIXME?) above
886 if (cur.depth() >= old.depth()) {
887 CursorSlice & curslice = cur[old.depth() - 1];
888 if (&curslice.inset() == &old.inset()
889 && curslice.pit() > old.pit()) {
890 --curslice.pit();
891 // since a paragraph has been deleted, all the
892 // insets after `old' have been copied and
893 // their address has changed. Therefore we
894 // need to `regenerate' cur. (JMarc)
895 cur.updateInsets(&(cur.bottom().inset()));
896 need_anchor_change = true;
899 return true;
902 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
903 need_anchor_change = true;
904 // We return true here because the Paragraph contents changed and
905 // we need a redraw before further action is processed.
906 return true;
909 return false;
913 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
915 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
917 for (pit_type pit = first; pit <= last; ++pit) {
918 Paragraph & par = pars_[pit];
920 // We allow all kinds of "mumbo-jumbo" when freespacing.
921 if (par.isFreeSpacing())
922 continue;
924 for (pos_type pos = 1; pos < par.size(); ++pos) {
925 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
926 && !par.isDeleted(pos - 1)) {
927 if (par.eraseChar(pos - 1, trackChanges)) {
928 --pos;
933 // don't delete anything if this is the only remaining paragraph within the given range
934 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
935 if (first == last)
936 continue;
938 // don't delete empty paragraphs with keepempty set
939 if (par.allowEmpty())
940 continue;
942 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
943 pars_.erase(boost::next(pars_.begin(), pit));
944 --pit;
945 --last;
946 continue;
949 par.stripLeadingSpaces(trackChanges);
954 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
956 cur.recordUndo(ATOMIC_UNDO, first, last);
960 void Text::recUndo(Cursor & cur, pit_type par) const
962 cur.recordUndo(ATOMIC_UNDO, par, par);
965 } // namespace lyx