Add missing include, due to André LASSERT change
[lyx.git] / src / Text2.cpp
blob27fe087c109159ca389500175e9b16980c65c9ed
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/InsetEnvironment.h"
51 #include "mathed/InsetMathHull.h"
53 #include "support/assert.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 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
79 Layout const & layout = pars_[pit].layout();
81 if (!pars_[pit].getDepth()) {
82 FontInfo lf = layout.resfont;
83 // In case the default family has been customized
84 if (layout.font.family() == INHERIT_FAMILY)
85 lf.setFamily(buffer.params().getFont().fontInfo().family());
86 return lf;
89 FontInfo font = layout.font;
90 // Realize with the fonts of lesser depth.
91 //font.realize(outerFont(pit, paragraphs()));
92 font.realize(buffer.params().getFont().fontInfo());
94 return font;
98 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
100 Layout const & layout = par.layout();
102 if (!par.getDepth()) {
103 FontInfo lf = layout.reslabelfont;
104 // In case the default family has been customized
105 if (layout.labelfont.family() == INHERIT_FAMILY)
106 lf.setFamily(buffer.params().getFont().fontInfo().family());
107 return lf;
110 FontInfo font = layout.labelfont;
111 // Realize with the fonts of lesser depth.
112 font.realize(buffer.params().getFont().fontInfo());
114 return font;
118 void Text::setCharFont(Buffer const & buffer, pit_type pit,
119 pos_type pos, Font const & fnt, Font const & display_font)
121 Font font = fnt;
122 Layout const & layout = pars_[pit].layout();
124 // Get concrete layout font to reduce against
125 FontInfo layoutfont;
127 if (pos < pars_[pit].beginOfBody())
128 layoutfont = layout.labelfont;
129 else
130 layoutfont = layout.font;
132 // Realize against environment font information
133 if (pars_[pit].getDepth()) {
134 pit_type tp = pit;
135 while (!layoutfont.resolved() &&
136 tp != pit_type(paragraphs().size()) &&
137 pars_[tp].getDepth()) {
138 tp = outerHook(tp, paragraphs());
139 if (tp != pit_type(paragraphs().size()))
140 layoutfont.realize(pars_[tp].layout().font);
144 // Inside inset, apply the inset's font attributes if any
145 // (charstyle!)
146 if (!isMainText(buffer))
147 layoutfont.realize(display_font.fontInfo());
149 layoutfont.realize(buffer.params().getFont().fontInfo());
151 // Now, reduce font against full layout font
152 font.fontInfo().reduce(layoutfont);
154 pars_[pit].setFont(pos, font);
158 void Text::setInsetFont(BufferView const & bv, pit_type pit,
159 pos_type pos, Font const & font, bool toggleall)
161 Inset * const inset = pars_[pit].getInset(pos);
162 LASSERT(inset && inset->noFontChange(), /**/);
164 CursorSlice::idx_type endidx = inset->nargs();
165 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
166 Text * text = cs.text();
167 if (text) {
168 // last position of the cell
169 CursorSlice cellend = cs;
170 cellend.pit() = cellend.lastpit();
171 cellend.pos() = cellend.lastpos();
172 text->setFont(bv, cs, cellend, font, toggleall);
178 // return past-the-last paragraph influenced by a layout change on pit
179 pit_type Text::undoSpan(pit_type pit)
181 pit_type end = paragraphs().size();
182 pit_type nextpit = pit + 1;
183 if (nextpit == end)
184 return nextpit;
185 //because of parindents
186 if (!pars_[pit].getDepth())
187 return boost::next(nextpit);
188 //because of depth constrains
189 for (; nextpit != end; ++pit, ++nextpit) {
190 if (!pars_[pit].getDepth())
191 break;
193 return nextpit;
197 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
198 docstring const & layout)
200 LASSERT(start != end, /**/);
202 BufferParams const & bufparams = buffer.params();
203 Layout const & lyxlayout = bufparams.documentClass()[layout];
205 for (pit_type pit = start; pit != end; ++pit) {
206 Paragraph & par = pars_[pit];
207 par.applyLayout(lyxlayout);
208 if (lyxlayout.margintype == MARGIN_MANUAL)
209 par.setLabelWidthString(par.translateIfPossible(
210 lyxlayout.labelstring(), buffer.params()));
215 // set layout over selection and make a total rebreak of those paragraphs
216 void Text::setLayout(Cursor & cur, docstring const & layout)
218 LASSERT(this == cur.text(), /**/);
219 // special handling of new environment insets
220 BufferView & bv = cur.bv();
221 BufferParams const & params = bv.buffer().params();
222 Layout const & lyxlayout = params.documentClass()[layout];
223 if (lyxlayout.is_environment) {
224 // move everything in a new environment inset
225 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
226 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
227 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
228 lyx::dispatch(FuncRequest(LFUN_CUT));
229 Inset * inset = new InsetEnvironment(bv.buffer(), layout);
230 insertInset(cur, inset);
231 //inset->edit(cur, true);
232 //lyx::dispatch(FuncRequest(LFUN_PASTE));
233 return;
236 pit_type start = cur.selBegin().pit();
237 pit_type end = cur.selEnd().pit() + 1;
238 pit_type undopit = undoSpan(end - 1);
239 recUndo(cur, start, undopit - 1);
240 setLayout(cur.buffer(), start, end, layout);
241 updateLabels(cur.buffer());
245 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
246 Paragraph const & par, int max_depth)
248 if (par.layout().labeltype == LABEL_BIBLIO)
249 return false;
250 int const depth = par.params().depth();
251 if (type == Text::INC_DEPTH && depth < max_depth)
252 return true;
253 if (type == Text::DEC_DEPTH && depth > 0)
254 return true;
255 return false;
259 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
261 LASSERT(this == cur.text(), /**/);
262 // this happens when selecting several cells in tabular (bug 2630)
263 if (cur.selBegin().idx() != cur.selEnd().idx())
264 return false;
266 pit_type const beg = cur.selBegin().pit();
267 pit_type const end = cur.selEnd().pit() + 1;
268 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
270 for (pit_type pit = beg; pit != end; ++pit) {
271 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
272 return true;
273 max_depth = pars_[pit].getMaxDepthAfter();
275 return false;
279 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
281 LASSERT(this == cur.text(), /**/);
282 pit_type const beg = cur.selBegin().pit();
283 pit_type const end = cur.selEnd().pit() + 1;
284 cur.recordUndoSelection();
285 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
287 for (pit_type pit = beg; pit != end; ++pit) {
288 Paragraph & par = pars_[pit];
289 if (lyx::changeDepthAllowed(type, par, max_depth)) {
290 int const depth = par.params().depth();
291 if (type == INC_DEPTH)
292 par.params().depth(depth + 1);
293 else
294 par.params().depth(depth - 1);
296 max_depth = par.getMaxDepthAfter();
298 // this handles the counter labels, and also fixes up
299 // depth values for follow-on (child) paragraphs
300 updateLabels(cur.buffer());
304 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
306 LASSERT(this == cur.text(), /**/);
307 // Set the current_font
308 // Determine basis font
309 FontInfo layoutfont;
310 pit_type pit = cur.pit();
311 if (cur.pos() < pars_[pit].beginOfBody())
312 layoutfont = labelFont(cur.buffer(), pars_[pit]);
313 else
314 layoutfont = layoutFont(cur.buffer(), pit);
316 // Update current font
317 cur.real_current_font.update(font,
318 cur.buffer().params().language,
319 toggleall);
321 // Reduce to implicit settings
322 cur.current_font = cur.real_current_font;
323 cur.current_font.fontInfo().reduce(layoutfont);
324 // And resolve it completely
325 cur.real_current_font.fontInfo().realize(layoutfont);
327 // if there is no selection that's all we need to do
328 if (!cur.selection())
329 return;
331 // Ok, we have a selection.
332 cur.recordUndoSelection();
334 setFont(cur.bv(), cur.selectionBegin().top(),
335 cur.selectionEnd().top(), font, toggleall);
339 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
340 CursorSlice const & end, Font const & font,
341 bool toggleall)
343 Buffer const & buffer = bv.buffer();
345 // Don't use forwardChar here as ditend might have
346 // pos() == lastpos() and forwardChar would miss it.
347 // Can't use forwardPos either as this descends into
348 // nested insets.
349 Language const * language = buffer.params().language;
350 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
351 if (dit.pos() == dit.lastpos())
352 continue;
353 pit_type const pit = dit.pit();
354 pos_type const pos = dit.pos();
355 Inset * inset = pars_[pit].getInset(pos);
356 if (inset && inset->noFontChange()) {
357 // We need to propagate the font change to all
358 // text cells of the inset (bug 1973).
359 // FIXME: This should change, see documentation
360 // of noFontChange in Inset.h
361 setInsetFont(bv, pit, pos, font, toggleall);
363 TextMetrics const & tm = bv.textMetrics(this);
364 Font f = tm.displayFont(pit, pos);
365 f.update(font, language, toggleall);
366 setCharFont(buffer, pit, pos, f, tm.font_);
371 bool Text::cursorTop(Cursor & cur)
373 LASSERT(this == cur.text(), /**/);
374 return setCursor(cur, 0, 0);
378 bool Text::cursorBottom(Cursor & cur)
380 LASSERT(this == cur.text(), /**/);
381 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
385 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
387 LASSERT(this == cur.text(), /**/);
388 // If the mask is completely neutral, tell user
389 if (font.fontInfo() == ignore_font &&
390 (font.language() == 0 || font.language() == ignore_language)) {
391 // Could only happen with user style
392 cur.message(_("No font change defined."));
393 return;
396 // Try implicit word selection
397 // If there is a change in the language the implicit word selection
398 // is disabled.
399 CursorSlice resetCursor = cur.top();
400 bool implicitSelection =
401 font.language() == ignore_language
402 && font.fontInfo().number() == FONT_IGNORE
403 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
405 // Set font
406 setFont(cur, font, toggleall);
408 // Implicit selections are cleared afterwards
409 // and cursor is set to the original position.
410 if (implicitSelection) {
411 cur.clearSelection();
412 cur.top() = resetCursor;
413 cur.resetAnchor();
418 docstring Text::getStringToIndex(Cursor const & cur)
420 LASSERT(this == cur.text(), /**/);
422 if (cur.selection())
423 return cur.selectionAsString(false);
425 // Try implicit word selection. If there is a change
426 // in the language the implicit word selection is
427 // disabled.
428 Cursor tmpcur = cur;
429 selectWord(tmpcur, PREVIOUS_WORD);
431 if (!tmpcur.selection())
432 cur.message(_("Nothing to index!"));
433 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
434 cur.message(_("Cannot index more than one paragraph!"));
435 else
436 return tmpcur.selectionAsString(false);
438 return docstring();
442 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
444 LASSERT(cur.text(), /**/);
445 // make sure that the depth behind the selection are restored, too
446 pit_type undopit = undoSpan(cur.selEnd().pit());
447 recUndo(cur, cur.selBegin().pit(), undopit - 1);
449 //FIXME UNICODE
450 string const argument = to_utf8(arg);
451 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
452 pit <= end; ++pit) {
453 Paragraph & par = pars_[pit];
454 ParagraphParameters params = par.params();
455 params.read(argument, merge);
456 par.params().apply(params, par.layout());
461 //FIXME This is a little redundant now, but it's probably worth keeping,
462 //especially if we're going to go away from using serialization internally
463 //quite so much.
464 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
466 LASSERT(cur.text(), /**/);
467 // make sure that the depth behind the selection are restored, too
468 pit_type undopit = undoSpan(cur.selEnd().pit());
469 recUndo(cur, cur.selBegin().pit(), undopit - 1);
471 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
472 pit <= end; ++pit) {
473 Paragraph & par = pars_[pit];
474 par.params().apply(p, par.layout());
479 // this really should just insert the inset and not move the cursor.
480 void Text::insertInset(Cursor & cur, Inset * inset)
482 LASSERT(this == cur.text(), /**/);
483 LASSERT(inset, /**/);
484 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
485 Change(cur.buffer().params().trackChanges
486 ? Change::INSERTED : Change::UNCHANGED));
490 // needed to insert the selection
491 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
493 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
494 cur.current_font, str, autoBreakRows_);
498 // turn double CR to single CR, others are converted into one
499 // blank. Then insertStringAsLines is called
500 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
502 docstring linestr = str;
503 bool newline_inserted = false;
505 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
506 if (linestr[i] == '\n') {
507 if (newline_inserted) {
508 // we know that \r will be ignored by
509 // insertStringAsLines. Of course, it is a dirty
510 // trick, but it works...
511 linestr[i - 1] = '\r';
512 linestr[i] = '\n';
513 } else {
514 linestr[i] = ' ';
515 newline_inserted = true;
517 } else if (isPrintable(linestr[i])) {
518 newline_inserted = false;
521 insertStringAsLines(cur, linestr);
525 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
526 bool setfont, bool boundary)
528 TextMetrics const & tm = cur.bv().textMetrics(this);
529 bool const update_needed = !tm.contains(par);
530 Cursor old = cur;
531 setCursorIntern(cur, par, pos, setfont, boundary);
532 return cur.bv().checkDepm(cur, old) || update_needed;
536 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
538 LASSERT(par != int(paragraphs().size()), /**/);
539 cur.pit() = par;
540 cur.pos() = pos;
542 // now some strict checking
543 Paragraph & para = getPar(par);
545 // None of these should happen, but we're scaredy-cats
546 if (pos < 0) {
547 lyxerr << "dont like -1" << endl;
548 LASSERT(false, /**/);
551 if (pos > para.size()) {
552 lyxerr << "dont like 1, pos: " << pos
553 << " size: " << para.size()
554 << " par: " << par << endl;
555 LASSERT(false, /**/);
560 void Text::setCursorIntern(Cursor & cur,
561 pit_type par, pos_type pos, bool setfont, bool boundary)
563 LASSERT(this == cur.text(), /**/);
564 cur.boundary(boundary);
565 setCursor(cur.top(), par, pos);
566 if (setfont)
567 cur.setCurrentFont();
571 bool Text::checkAndActivateInset(Cursor & cur, bool front)
573 if (cur.selection())
574 return false;
575 if (front && cur.pos() == cur.lastpos())
576 return false;
577 if (!front && cur.pos() == 0)
578 return false;
579 Inset * inset = front ? cur.nextInset() : cur.prevInset();
580 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
581 return false;
583 * Apparently, when entering an inset we are expected to be positioned
584 * *before* it in the containing paragraph, regardless of the direction
585 * from which we are entering. Otherwise, cursor placement goes awry,
586 * and when we exit from the beginning, we'll be placed *after* the
587 * inset.
589 if (!front)
590 --cur.pos();
591 inset->edit(cur, front);
592 return true;
596 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
598 if (cur.selection())
599 return false;
600 if (cur.pos() == -1)
601 return false;
602 if (cur.pos() == cur.lastpos())
603 return false;
604 Paragraph & par = cur.paragraph();
605 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
606 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
607 return false;
608 inset->edit(cur, movingForward,
609 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
610 return true;
614 bool Text::cursorBackward(Cursor & cur)
616 // Tell BufferView to test for FitCursor in any case!
617 cur.updateFlags(Update::FitCursor);
619 // not at paragraph start?
620 if (cur.pos() > 0) {
621 // if on right side of boundary (i.e. not at paragraph end, but line end)
622 // -> skip it, i.e. set boundary to true, i.e. go only logically left
623 // there are some exceptions to ignore this: lineseps, newlines, spaces
624 #if 0
625 // some effectless debug code to see the values in the debugger
626 bool bound = cur.boundary();
627 int rowpos = cur.textRow().pos();
628 int pos = cur.pos();
629 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
630 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
631 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
632 #endif
633 if (!cur.boundary() &&
634 cur.textRow().pos() == cur.pos() &&
635 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
636 !cur.paragraph().isNewline(cur.pos() - 1) &&
637 !cur.paragraph().isSeparator(cur.pos() - 1)) {
638 return setCursor(cur, cur.pit(), cur.pos(), true, true);
641 // go left and try to enter inset
642 if (checkAndActivateInset(cur, false))
643 return false;
645 // normal character left
646 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
649 // move to the previous paragraph or do nothing
650 if (cur.pit() > 0)
651 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
652 return false;
656 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
658 pit_type new_pit = cur.pit(); // the paragraph to which we will move
659 pos_type new_pos; // the position we will move to
660 bool new_boundary; // will we move to a boundary position?
661 pos_type left_pos; // position visually left of current cursor
662 pos_type right_pos; // position visually right of current cursor
663 bool new_pos_is_RTL; // is new position we're moving to RTL?
665 cur.getSurroundingPos(left_pos, right_pos);
667 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
669 // Are we at an inset?
670 Cursor temp_cur = cur;
671 temp_cur.pos() = left_pos;
672 temp_cur.boundary(false);
673 if (!skip_inset &&
674 checkAndActivateInsetVisual(temp_cur, left_pos >= cur.pos(), true)) {
675 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
676 cur = temp_cur; // set the real cursor to new position inside inset!
677 return false;
680 // Are we already at leftmost pos in row?
681 if (cur.text()->empty() || left_pos == -1) {
683 Cursor new_cur = cur;
684 if (!new_cur.posVisToNewRow(true)) {
685 LYXERR(Debug::RTL, "not moving!");
686 return false;
689 // we actually move the cursor at the end of this function, for now
690 // just keep track of the new position...
691 new_pit = new_cur.pit();
692 new_pos = new_cur.pos();
693 new_boundary = new_cur.boundary();
695 LYXERR(Debug::RTL, "left edge, moving: " << int(new_pit) << ","
696 << int(new_pos) << "," << (new_boundary ? 1 : 0));
699 // normal movement to the left
700 else {
701 // Recall, if the cursor is at position 'x', that means *before*
702 // the character at position 'x'. In RTL, "before" means "to the
703 // right of", in LTR, "to the left of". So currently our situation
704 // is this: the position to our left is 'left_pos' (i.e., we're
705 // currently to the right of 'left_pos'). In order to move to the
706 // left, it depends whether or not the character at 'left_pos' is RTL.
707 new_pos_is_RTL = cur.paragraph().getFontSettings(
708 cur.bv().buffer().params(), left_pos).isVisibleRightToLeft();
709 // If the character at 'left_pos' *is* RTL, then in order to move to
710 // the left of it, we need to be *after* 'left_pos', i.e., move to
711 // position 'left_pos' + 1.
712 if (new_pos_is_RTL) {
713 new_pos = left_pos + 1;
714 // set the boundary to true in two situations:
715 if (
716 // 1. if new_pos is now lastpos (which means that we're moving left
717 // to the end of an RTL chunk which is at the end of an LTR
718 // paragraph);
719 new_pos == cur.lastpos()
720 // 2. if the position *after* left_pos is not RTL (we want to be
721 // *after* left_pos, not before left_pos + 1!)
722 || !cur.paragraph().getFontSettings(cur.bv().buffer().params(),
723 new_pos).isVisibleRightToLeft()
725 new_boundary = true;
726 else // set the boundary to false
727 new_boundary = false;
729 // Otherwise (if the character at position 'left_pos' is LTR), then
730 // moving to the left of it is as easy as setting the new position
731 // to 'left_pos'.
732 else {
733 new_pos = left_pos;
734 new_boundary = false;
739 LYXERR(Debug::RTL, "moving to: " << new_pos
740 << (new_boundary ? " (boundary)" : ""));
742 return setCursor(cur, new_pit, new_pos, true, new_boundary);
746 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
748 pit_type new_pit = cur.pit(); // the paragraph to which we will move
749 pos_type new_pos; // the position we will move to
750 bool new_boundary; // will we move to a boundary position?
751 pos_type left_pos; // position visually left of current cursor
752 pos_type right_pos; // position visually right of current cursor
753 bool new_pos_is_RTL; // is new position we're moving to RTL?
755 cur.getSurroundingPos(left_pos, right_pos);
757 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
759 // Are we at an inset?
760 Cursor temp_cur = cur;
761 temp_cur.pos() = right_pos;
762 temp_cur.boundary(false);
763 if (!skip_inset &&
764 checkAndActivateInsetVisual(temp_cur, right_pos >= cur.pos(), false)) {
765 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
766 cur = temp_cur; // set the real cursor to new position inside inset!
767 return false;
770 // Are we already at rightmost pos in row?
771 if (cur.text()->empty() || right_pos == -1) {
773 Cursor new_cur = cur;
774 if (!new_cur.posVisToNewRow(false)) {
775 LYXERR(Debug::RTL, "not moving!");
776 return false;
779 // we actually move the cursor at the end of this function, for now
780 // just keep track of the new position...
781 new_pit = new_cur.pit();
782 new_pos = new_cur.pos();
783 new_boundary = new_cur.boundary();
785 LYXERR(Debug::RTL, "right edge, moving: " << int(new_pit) << ","
786 << int(new_pos) << "," << (new_boundary ? 1 : 0));
789 // normal movement to the right
790 else {
791 // Recall, if the cursor is at position 'x', that means *before*
792 // the character at position 'x'. In RTL, "before" means "to the
793 // right of", in LTR, "to the left of". So currently our situation
794 // is this: the position to our right is 'right_pos' (i.e., we're
795 // currently to the left of 'right_pos'). In order to move to the
796 // right, it depends whether or not the character at 'right_pos' is RTL.
797 new_pos_is_RTL = cur.paragraph().getFontSettings(
798 cur.bv().buffer().params(), right_pos).isVisibleRightToLeft();
799 // If the character at 'right_pos' *is* LTR, then in order to move to
800 // the right of it, we need to be *after* 'right_pos', i.e., move to
801 // position 'right_pos' + 1.
802 if (!new_pos_is_RTL) {
803 new_pos = right_pos + 1;
804 // set the boundary to true in two situations:
805 if (
806 // 1. if new_pos is now lastpos (which means that we're moving
807 // right to the end of an LTR chunk which is at the end of an
808 // RTL paragraph);
809 new_pos == cur.lastpos()
810 // 2. if the position *after* right_pos is RTL (we want to be
811 // *after* right_pos, not before right_pos + 1!)
812 || cur.paragraph().getFontSettings(cur.bv().buffer().params(),
813 new_pos).isVisibleRightToLeft()
815 new_boundary = true;
816 else // set the boundary to false
817 new_boundary = false;
819 // Otherwise (if the character at position 'right_pos' is RTL), then
820 // moving to the right of it is as easy as setting the new position
821 // to 'right_pos'.
822 else {
823 new_pos = right_pos;
824 new_boundary = false;
829 LYXERR(Debug::RTL, "moving to: " << new_pos
830 << (new_boundary ? " (boundary)" : ""));
832 return setCursor(cur, new_pit, new_pos, true, new_boundary);
836 bool Text::cursorForward(Cursor & cur)
838 // Tell BufferView to test for FitCursor in any case!
839 cur.updateFlags(Update::FitCursor);
841 // not at paragraph end?
842 if (cur.pos() != cur.lastpos()) {
843 // in front of editable inset, i.e. jump into it?
844 if (checkAndActivateInset(cur, true))
845 return false;
847 TextMetrics const & tm = cur.bv().textMetrics(this);
848 // if left of boundary -> just jump to right side
849 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
850 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
851 return setCursor(cur, cur.pit(), cur.pos(), true, false);
853 // next position is left of boundary,
854 // but go to next line for special cases like space, newline, linesep
855 #if 0
856 // some effectless debug code to see the values in the debugger
857 int endpos = cur.textRow().endpos();
858 int lastpos = cur.lastpos();
859 int pos = cur.pos();
860 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
861 bool newline = cur.paragraph().isNewline(cur.pos());
862 bool sep = cur.paragraph().isSeparator(cur.pos());
863 if (cur.pos() != cur.lastpos()) {
864 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
865 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
866 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
868 #endif
869 if (cur.textRow().endpos() == cur.pos() + 1 &&
870 cur.textRow().endpos() != cur.lastpos() &&
871 !cur.paragraph().isNewline(cur.pos()) &&
872 !cur.paragraph().isLineSeparator(cur.pos()) &&
873 !cur.paragraph().isSeparator(cur.pos())) {
874 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
877 // in front of RTL boundary? Stay on this side of the boundary because:
878 // ab|cDDEEFFghi -> abc|DDEEFFghi
879 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
880 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
882 // move right
883 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
886 // move to next paragraph
887 if (cur.pit() != cur.lastpit())
888 return setCursor(cur, cur.pit() + 1, 0, true, false);
889 return false;
893 bool Text::cursorUpParagraph(Cursor & cur)
895 bool updated = false;
896 if (cur.pos() > 0)
897 updated = setCursor(cur, cur.pit(), 0);
898 else if (cur.pit() != 0)
899 updated = setCursor(cur, cur.pit() - 1, 0);
900 return updated;
904 bool Text::cursorDownParagraph(Cursor & cur)
906 bool updated = false;
907 if (cur.pit() != cur.lastpit())
908 updated = setCursor(cur, cur.pit() + 1, 0);
909 else
910 updated = setCursor(cur, cur.pit(), cur.lastpos());
911 return updated;
915 // fix the cursor `cur' after a characters has been deleted at `where'
916 // position. Called by deleteEmptyParagraphMechanism
917 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
919 // Do nothing if cursor is not in the paragraph where the
920 // deletion occured,
921 if (cur.pit() != where.pit())
922 return;
924 // If cursor position is after the deletion place update it
925 if (cur.pos() > where.pos())
926 --cur.pos();
928 // Check also if we don't want to set the cursor on a spot behind the
929 // pagragraph because we erased the last character.
930 if (cur.pos() > cur.lastpos())
931 cur.pos() = cur.lastpos();
935 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
936 Cursor & old, bool & need_anchor_change)
938 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
940 Paragraph & oldpar = old.paragraph();
942 // We allow all kinds of "mumbo-jumbo" when freespacing.
943 if (oldpar.isFreeSpacing())
944 return false;
946 /* Ok I'll put some comments here about what is missing.
947 There are still some small problems that can lead to
948 double spaces stored in the document file or space at
949 the beginning of paragraphs(). This happens if you have
950 the cursor between to spaces and then save. Or if you
951 cut and paste and the selection have a space at the
952 beginning and then save right after the paste. (Lgb)
955 // If old.pos() == 0 and old.pos()(1) == LineSeparator
956 // delete the LineSeparator.
957 // MISSING
959 // If old.pos() == 1 and old.pos()(0) == LineSeparator
960 // delete the LineSeparator.
961 // MISSING
963 bool const same_inset = &old.inset() == &cur.inset();
964 bool const same_par = same_inset && old.pit() == cur.pit();
965 bool const same_par_pos = same_par && old.pos() == cur.pos();
967 // If the chars around the old cursor were spaces, delete one of them.
968 if (!same_par_pos) {
969 // Only if the cursor has really moved.
970 if (old.pos() > 0
971 && old.pos() < oldpar.size()
972 && oldpar.isLineSeparator(old.pos())
973 && oldpar.isLineSeparator(old.pos() - 1)
974 && !oldpar.isDeleted(old.pos() - 1)
975 && !oldpar.isDeleted(old.pos())) {
976 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
977 // FIXME: This will not work anymore when we have multiple views of the same buffer
978 // In this case, we will have to correct also the cursors held by
979 // other bufferviews. It will probably be easier to do that in a more
980 // automated way in CursorSlice code. (JMarc 26/09/2001)
981 // correct all cursor parts
982 if (same_par) {
983 fixCursorAfterDelete(cur.top(), old.top());
984 need_anchor_change = true;
986 return true;
990 // only do our magic if we changed paragraph
991 if (same_par)
992 return false;
994 // don't delete anything if this is the ONLY paragraph!
995 if (old.lastpit() == 0)
996 return false;
998 // Do not delete empty paragraphs with keepempty set.
999 if (oldpar.allowEmpty())
1000 return false;
1002 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1003 // Delete old par.
1004 old.recordUndo(ATOMIC_UNDO,
1005 max(old.pit() - 1, pit_type(0)),
1006 min(old.pit() + 1, old.lastpit()));
1007 ParagraphList & plist = old.text()->paragraphs();
1008 bool const soa = oldpar.params().startOfAppendix();
1009 plist.erase(boost::next(plist.begin(), old.pit()));
1010 // do not lose start of appendix marker (bug 4212)
1011 if (soa && old.pit() < pit_type(plist.size()))
1012 plist[old.pit()].params().startOfAppendix(true);
1014 // see #warning (FIXME?) above
1015 if (cur.depth() >= old.depth()) {
1016 CursorSlice & curslice = cur[old.depth() - 1];
1017 if (&curslice.inset() == &old.inset()
1018 && curslice.pit() > old.pit()) {
1019 --curslice.pit();
1020 // since a paragraph has been deleted, all the
1021 // insets after `old' have been copied and
1022 // their address has changed. Therefore we
1023 // need to `regenerate' cur. (JMarc)
1024 cur.updateInsets(&(cur.bottom().inset()));
1025 need_anchor_change = true;
1028 return true;
1031 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1032 need_anchor_change = true;
1033 // We return true here because the Paragraph contents changed and
1034 // we need a redraw before further action is processed.
1035 return true;
1038 return false;
1042 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1044 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
1046 for (pit_type pit = first; pit <= last; ++pit) {
1047 Paragraph & par = pars_[pit];
1049 // We allow all kinds of "mumbo-jumbo" when freespacing.
1050 if (par.isFreeSpacing())
1051 continue;
1053 for (pos_type pos = 1; pos < par.size(); ++pos) {
1054 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1055 && !par.isDeleted(pos - 1)) {
1056 if (par.eraseChar(pos - 1, trackChanges)) {
1057 --pos;
1062 // don't delete anything if this is the only remaining paragraph within the given range
1063 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1064 if (first == last)
1065 continue;
1067 // don't delete empty paragraphs with keepempty set
1068 if (par.allowEmpty())
1069 continue;
1071 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1072 pars_.erase(boost::next(pars_.begin(), pit));
1073 --pit;
1074 --last;
1075 continue;
1078 par.stripLeadingSpaces(trackChanges);
1083 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1085 cur.recordUndo(ATOMIC_UNDO, first, last);
1089 void Text::recUndo(Cursor & cur, pit_type par) const
1091 cur.recordUndo(ATOMIC_UNDO, par, par);
1094 } // namespace lyx