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.
5 *
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
17 *
18 * Full author contact details are available in file CREDITS.
19 */
20
21 #include <config.h>
22
23 #include "Text.h"
24
25 #include "Buffer.h"
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
30 #include "Changes.h"
31 #include "Cursor.h"
32 #include "CutAndPaste.h"
33 #include "DispatchResult.h"
34 #include "ErrorList.h"
35 #include "Language.h"
36 #include "Layout.h"
37 #include "Lexer.h"
38 #include "LyX.h"
39 #include "LyXRC.h"
40 #include "Paragraph.h"
41 #include "ParagraphParameters.h"
42 #include "TextClass.h"
43 #include "TextMetrics.h"
44
45 #include "insets/InsetCollapsible.h"
46
47 #include "mathed/InsetMathHull.h"
48
49 #include "support/lassert.h"
50 #include "support/debug.h"
51 #include "support/gettext.h"
52 #include "support/lyxalgo.h"
53 #include "support/textutils.h"
54
55 #include <sstream>
56
57 using namespace std;
58
59 namespace lyx {
60
isMainText() const61 bool Text::isMainText() const
62 {
63 return &owner_->buffer().text() == this;
64 }
65
66
67 // Note that this is supposed to return a fully realized font.
layoutFont(pit_type const pit) const68 FontInfo Text::layoutFont(pit_type const pit) const
69 {
70 Layout const & layout = pars_[pit].layout();
71
72 if (!pars_[pit].getDepth()) {
73 FontInfo lf = layout.resfont;
74 // In case the default family has been customized
75 if (layout.font.family() == INHERIT_FAMILY)
76 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
77 FontInfo icf = owner_->getLayout().font();
78 icf.realize(lf);
79 return icf;
80 }
81
82 FontInfo font = layout.font;
83 // Realize with the fonts of lesser depth.
84 //font.realize(outerFont(pit));
85 font.realize(owner_->buffer().params().getFont().fontInfo());
86
87 return font;
88 }
89
90
91 // Note that this is supposed to return a fully realized font.
labelFont(Paragraph const & par) const92 FontInfo Text::labelFont(Paragraph const & par) const
93 {
94 Buffer const & buffer = owner_->buffer();
95 Layout const & layout = par.layout();
96
97 if (!par.getDepth()) {
98 FontInfo lf = layout.reslabelfont;
99 // In case the default family has been customized
100 if (layout.labelfont.family() == INHERIT_FAMILY)
101 lf.setFamily(buffer.params().getFont().fontInfo().family());
102 return lf;
103 }
104
105 FontInfo font = layout.labelfont;
106 // Realize with the fonts of lesser depth.
107 font.realize(buffer.params().getFont().fontInfo());
108
109 return font;
110 }
111
112
setCharFont(pit_type pit,pos_type pos,Font const & fnt,Font const & display_font)113 void Text::setCharFont(pit_type pit,
114 pos_type pos, Font const & fnt, Font const & display_font)
115 {
116 Buffer const & buffer = owner_->buffer();
117 Font font = fnt;
118 Layout const & layout = pars_[pit].layout();
119
120 // Get concrete layout font to reduce against
121 FontInfo layoutfont;
122
123 if (pos < pars_[pit].beginOfBody())
124 layoutfont = layout.labelfont;
125 else
126 layoutfont = layout.font;
127
128 // Realize against environment font information
129 if (pars_[pit].getDepth()) {
130 pit_type tp = pit;
131 while (!layoutfont.resolved() &&
132 tp != pit_type(paragraphs().size()) &&
133 pars_[tp].getDepth()) {
134 tp = outerHook(tp);
135 if (tp != pit_type(paragraphs().size()))
136 layoutfont.realize(pars_[tp].layout().font);
137 }
138 }
139
140 // Inside inset, apply the inset's font attributes if any
141 // (charstyle!)
142 if (!isMainText())
143 layoutfont.realize(display_font.fontInfo());
144
145 layoutfont.realize(buffer.params().getFont().fontInfo());
146
147 // Now, reduce font against full layout font
148 font.fontInfo().reduce(layoutfont);
149
150 pars_[pit].setFont(pos, font);
151 }
152
153
setInsetFont(BufferView const & bv,pit_type pit,pos_type pos,Font const & font)154 void Text::setInsetFont(BufferView const & bv, pit_type pit,
155 pos_type pos, Font const & font)
156 {
157 Inset * const inset = pars_[pit].getInset(pos);
158 LASSERT(inset && inset->resetFontEdit(), return);
159
160 CursorSlice::idx_type endidx = inset->nargs();
161 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
162 Text * text = cs.text();
163 if (text) {
164 // last position of the cell
165 CursorSlice cellend = cs;
166 cellend.pit() = cellend.lastpit();
167 cellend.pos() = cellend.lastpos();
168 text->setFont(bv, cs, cellend, font);
169 }
170 }
171 }
172
173
setLayout(pit_type start,pit_type end,docstring const & layout)174 void Text::setLayout(pit_type start, pit_type end,
175 docstring const & layout)
176 {
177 LASSERT(start != end, return);
178
179 Buffer const & buffer = owner_->buffer();
180 BufferParams const & bp = buffer.params();
181 Layout const & lyxlayout = bp.documentClass()[layout];
182
183 for (pit_type pit = start; pit != end; ++pit) {
184 Paragraph & par = pars_[pit];
185 par.applyLayout(lyxlayout);
186 if (lyxlayout.margintype == MARGIN_MANUAL)
187 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
188 }
189 }
190
191
192 // set layout over selection and make a total rebreak of those paragraphs
setLayout(Cursor & cur,docstring const & layout)193 void Text::setLayout(Cursor & cur, docstring const & layout)
194 {
195 LBUFERR(this == cur.text());
196
197 pit_type start = cur.selBegin().pit();
198 pit_type end = cur.selEnd().pit() + 1;
199 cur.recordUndoSelection();
200 setLayout(start, end, layout);
201 cur.setCurrentFont();
202 cur.forceBufferUpdate();
203 }
204
205
changeDepthAllowed(Text::DEPTH_CHANGE type,Paragraph const & par,int max_depth)206 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
207 Paragraph const & par, int max_depth)
208 {
209 int const depth = par.params().depth();
210 if (type == Text::INC_DEPTH && depth < max_depth)
211 return true;
212 if (type == Text::DEC_DEPTH && depth > 0)
213 return true;
214 return false;
215 }
216
217
changeDepthAllowed(Cursor & cur,DEPTH_CHANGE type) const218 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
219 {
220 LBUFERR(this == cur.text());
221 // this happens when selecting several cells in tabular (bug 2630)
222 if (cur.selBegin().idx() != cur.selEnd().idx())
223 return false;
224
225 pit_type const beg = cur.selBegin().pit();
226 pit_type const end = cur.selEnd().pit() + 1;
227 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
228
229 for (pit_type pit = beg; pit != end; ++pit) {
230 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
231 return true;
232 max_depth = pars_[pit].getMaxDepthAfter();
233 }
234 return false;
235 }
236
237
changeDepth(Cursor & cur,DEPTH_CHANGE type)238 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
239 {
240 LBUFERR(this == cur.text());
241 pit_type const beg = cur.selBegin().pit();
242 pit_type const end = cur.selEnd().pit() + 1;
243 cur.recordUndoSelection();
244 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
245
246 for (pit_type pit = beg; pit != end; ++pit) {
247 Paragraph & par = pars_[pit];
248 if (lyx::changeDepthAllowed(type, par, max_depth)) {
249 int const depth = par.params().depth();
250 if (type == INC_DEPTH)
251 par.params().depth(depth + 1);
252 else
253 par.params().depth(depth - 1);
254 }
255 max_depth = par.getMaxDepthAfter();
256 }
257 // this handles the counter labels, and also fixes up
258 // depth values for follow-on (child) paragraphs
259 cur.forceBufferUpdate();
260 }
261
262
setFont(Cursor & cur,Font const & font,bool toggleall)263 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
264 {
265 LASSERT(this == cur.text(), return);
266
267 // If there is a selection, record undo before the cursor font is changed.
268 if (cur.selection())
269 cur.recordUndoSelection();
270
271 // Set the current_font
272 // Determine basis font
273 FontInfo layoutfont;
274 pit_type pit = cur.pit();
275 if (cur.pos() < pars_[pit].beginOfBody())
276 layoutfont = labelFont(pars_[pit]);
277 else
278 layoutfont = layoutFont(pit);
279
280 // Update current font
281 cur.real_current_font.update(font,
282 cur.buffer()->params().language,
283 toggleall);
284
285 // Reduce to implicit settings
286 cur.current_font = cur.real_current_font;
287 cur.current_font.fontInfo().reduce(layoutfont);
288 // And resolve it completely
289 cur.real_current_font.fontInfo().realize(layoutfont);
290
291 // if there is no selection that's all we need to do
292 if (!cur.selection())
293 return;
294
295 // Ok, we have a selection.
296 Font newfont = font;
297
298 if (toggleall) {
299 // Toggling behaves as follows: We check the first character of the
300 // selection. If it's (say) got EMPH on, then we set to off; if off,
301 // then to on. With families and the like, we set it to INHERIT, if
302 // we already have it.
303 CursorSlice const & sl = cur.selBegin();
304 Text const & text = *sl.text();
305 Paragraph const & par = text.getPar(sl.pit());
306
307 // get font at the position
308 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
309 text.outerFont(sl.pit()));
310 FontInfo const & oldfi = oldfont.fontInfo();
311
312 FontInfo & newfi = newfont.fontInfo();
313
314 FontFamily newfam = newfi.family();
315 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
316 newfam == oldfi.family())
317 newfi.setFamily(INHERIT_FAMILY);
318
319 FontSeries newser = newfi.series();
320 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
321 newfi.setSeries(INHERIT_SERIES);
322
323 FontShape newshp = newfi.shape();
324 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
325 newshp == oldfi.shape())
326 newfi.setShape(INHERIT_SHAPE);
327
328 ColorCode newcol = newfi.color();
329 if (newcol != Color_none && newcol != Color_inherit
330 && newcol != Color_ignore && newcol == oldfi.color())
331 newfi.setColor(Color_none);
332
333 // ON/OFF ones
334 if (newfi.emph() == FONT_TOGGLE)
335 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
336 if (newfi.underbar() == FONT_TOGGLE)
337 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
338 if (newfi.strikeout() == FONT_TOGGLE)
339 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
340 if (newfi.xout() == FONT_TOGGLE)
341 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.uuline() == FONT_TOGGLE)
343 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.uwave() == FONT_TOGGLE)
345 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.noun() == FONT_TOGGLE)
347 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
348 if (newfi.number() == FONT_TOGGLE)
349 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
350 }
351
352 setFont(cur.bv(), cur.selectionBegin().top(),
353 cur.selectionEnd().top(), newfont);
354 }
355
356
setFont(BufferView const & bv,CursorSlice const & begin,CursorSlice const & end,Font const & font)357 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
358 CursorSlice const & end, Font const & font)
359 {
360 Buffer const & buffer = bv.buffer();
361
362 // Don't use forwardChar here as ditend might have
363 // pos() == lastpos() and forwardChar would miss it.
364 // Can't use forwardPos either as this descends into
365 // nested insets.
366 Language const * language = buffer.params().language;
367 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
368 if (dit.pos() == dit.lastpos())
369 continue;
370 pit_type const pit = dit.pit();
371 pos_type const pos = dit.pos();
372 Inset * inset = pars_[pit].getInset(pos);
373 if (inset && inset->resetFontEdit()) {
374 // We need to propagate the font change to all
375 // text cells of the inset (bugs 1973, 6919).
376 setInsetFont(bv, pit, pos, font);
377 }
378 TextMetrics const & tm = bv.textMetrics(this);
379 Font f = tm.displayFont(pit, pos);
380 f.update(font, language);
381 setCharFont(pit, pos, f, tm.font_);
382 // font change may change language...
383 // spell checker has to know that
384 pars_[pit].requestSpellCheck(pos);
385 }
386 }
387
388
cursorTop(Cursor & cur)389 bool Text::cursorTop(Cursor & cur)
390 {
391 LBUFERR(this == cur.text());
392 return setCursor(cur, 0, 0);
393 }
394
395
cursorBottom(Cursor & cur)396 bool Text::cursorBottom(Cursor & cur)
397 {
398 LBUFERR(this == cur.text());
399 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
400 }
401
402
toggleFree(Cursor & cur,Font const & font,bool toggleall)403 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
404 {
405 LBUFERR(this == cur.text());
406 // If the mask is completely neutral, tell user
407 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
408 // Could only happen with user style
409 cur.message(_("No font change defined."));
410 return;
411 }
412
413 // Try implicit word selection
414 // If there is a change in the language the implicit word selection
415 // is disabled.
416 CursorSlice const resetCursor = cur.top();
417 bool const implicitSelection =
418 font.language() == ignore_language
419 && font.fontInfo().number() == FONT_IGNORE
420 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
421
422 // Set font
423 setFont(cur, font, toggleall);
424
425 // Implicit selections are cleared afterwards
426 // and cursor is set to the original position.
427 if (implicitSelection) {
428 cur.clearSelection();
429 cur.top() = resetCursor;
430 cur.resetAnchor();
431 }
432 }
433
434
getStringToIndex(Cursor const & cur)435 docstring Text::getStringToIndex(Cursor const & cur)
436 {
437 LBUFERR(this == cur.text());
438
439 if (cur.selection())
440 return cur.selectionAsString(false);
441
442 // Try implicit word selection. If there is a change
443 // in the language the implicit word selection is
444 // disabled.
445 Cursor tmpcur = cur;
446 selectWord(tmpcur, PREVIOUS_WORD);
447
448 if (!tmpcur.selection())
449 cur.message(_("Nothing to index!"));
450 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
451 cur.message(_("Cannot index more than one paragraph!"));
452 else
453 return tmpcur.selectionAsString(false);
454
455 return docstring();
456 }
457
458
setLabelWidthStringToSequence(Cursor const & cur,docstring const & s)459 void Text::setLabelWidthStringToSequence(Cursor const & cur,
460 docstring const & s)
461 {
462 Cursor c = cur;
463 // Find first of same layout in sequence
464 while (!isFirstInSequence(c.pit())) {
465 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
466 }
467
468 // now apply label width string to every par
469 // in sequence
470 depth_type const depth = c.paragraph().getDepth();
471 Layout const & layout = c.paragraph().layout();
472 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
473 while (c.paragraph().getDepth() > depth) {
474 ++c.pit();
475 if (c.pit() > c.lastpit())
476 return;
477 }
478 if (c.paragraph().getDepth() < depth)
479 return;
480 if (c.paragraph().layout() != layout)
481 return;
482 c.recordUndo();
483 c.paragraph().setLabelWidthString(s);
484 }
485 }
486
487
setParagraphs(Cursor & cur,docstring arg,bool merge)488 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
489 {
490 LBUFERR(cur.text());
491
492 //FIXME UNICODE
493 string const argument = to_utf8(arg);
494 depth_type priordepth = -1;
495 Layout priorlayout;
496 Cursor c(cur.bv());
497 c.setCursor(cur.selectionBegin());
498 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
499 Paragraph & par = c.paragraph();
500 ParagraphParameters params = par.params();
501 params.read(argument, merge);
502 // Changes to label width string apply to all paragraphs
503 // with same layout in a sequence.
504 // Do this only once for a selected range of paragraphs
505 // of the same layout and depth.
506 c.recordUndo();
507 par.params().apply(params, par.layout());
508 if (par.getDepth() != priordepth || par.layout() != priorlayout)
509 setLabelWidthStringToSequence(c, params.labelWidthString());
510 priordepth = par.getDepth();
511 priorlayout = par.layout();
512 }
513 }
514
515
setParagraphs(Cursor & cur,ParagraphParameters const & p)516 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
517 {
518 LBUFERR(cur.text());
519
520 depth_type priordepth = -1;
521 Layout priorlayout;
522 Cursor c(cur.bv());
523 c.setCursor(cur.selectionBegin());
524 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
525 Paragraph & par = c.paragraph();
526 // Changes to label width string apply to all paragraphs
527 // with same layout in a sequence.
528 // Do this only once for a selected range of paragraphs
529 // of the same layout and depth.
530 cur.recordUndo();
531 par.params().apply(p, par.layout());
532 if (par.getDepth() != priordepth || par.layout() != priorlayout)
533 setLabelWidthStringToSequence(c,
534 par.params().labelWidthString());
535 priordepth = par.getDepth();
536 priorlayout = par.layout();
537 }
538 }
539
540
541 // this really should just insert the inset and not move the cursor.
insertInset(Cursor & cur,Inset * inset)542 void Text::insertInset(Cursor & cur, Inset * inset)
543 {
544 LBUFERR(this == cur.text());
545 LBUFERR(inset);
546 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
547 Change(cur.buffer()->params().track_changes
548 ? Change::INSERTED : Change::UNCHANGED));
549 }
550
551
setCursor(Cursor & cur,pit_type pit,pos_type pos,bool setfont,bool boundary)552 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
553 bool setfont, bool boundary)
554 {
555 TextMetrics const & tm = cur.bv().textMetrics(this);
556 bool const update_needed = !tm.contains(pit);
557 Cursor old = cur;
558 setCursorIntern(cur, pit, pos, setfont, boundary);
559 return cur.bv().checkDepm(cur, old) || update_needed;
560 }
561
562
setCursorIntern(Cursor & cur,pit_type pit,pos_type pos,bool setfont,bool boundary)563 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
564 bool setfont, bool boundary)
565 {
566 LBUFERR(this == cur.text());
567 cur.boundary(boundary);
568 cur.top().setPitPos(pit, pos);
569 if (setfont)
570 cur.setCurrentFont();
571 }
572
573
checkAndActivateInset(Cursor & cur,bool front)574 bool Text::checkAndActivateInset(Cursor & cur, bool front)
575 {
576 if (front && cur.pos() == cur.lastpos())
577 return false;
578 if (!front && cur.pos() == 0)
579 return false;
580 Inset * inset = front ? cur.nextInset() : cur.prevInset();
581 if (!inset || !inset->editable())
582 return false;
583 if (cur.selection() && cur.realAnchor().find(inset) == -1)
584 return false;
585 /*
586 * Apparently, when entering an inset we are expected to be positioned
587 * *before* it in the containing paragraph, regardless of the direction
588 * from which we are entering. Otherwise, cursor placement goes awry,
589 * and when we exit from the beginning, we'll be placed *after* the
590 * inset.
591 */
592 if (!front)
593 --cur.pos();
594 inset->edit(cur, front);
595 cur.setCurrentFont();
596 cur.boundary(false);
597 return true;
598 }
599
600
checkAndActivateInsetVisual(Cursor & cur,bool movingForward,bool movingLeft)601 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
602 {
603 if (cur.pos() == -1)
604 return false;
605 if (cur.pos() == cur.lastpos())
606 return false;
607 Paragraph & par = cur.paragraph();
608 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
609 if (!inset || !inset->editable())
610 return false;
611 if (cur.selection() && cur.realAnchor().find(inset) == -1)
612 return false;
613 inset->edit(cur, movingForward,
614 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
615 cur.setCurrentFont();
616 cur.boundary(false);
617 return true;
618 }
619
620
cursorBackward(Cursor & cur)621 bool Text::cursorBackward(Cursor & cur)
622 {
623 // Tell BufferView to test for FitCursor in any case!
624 cur.screenUpdateFlags(Update::FitCursor);
625
626 // not at paragraph start?
627 if (cur.pos() > 0) {
628 // if on right side of boundary (i.e. not at paragraph end, but line end)
629 // -> skip it, i.e. set boundary to true, i.e. go only logically left
630 // there are some exceptions to ignore this: lineseps, newlines, spaces
631 #if 0
632 // some effectless debug code to see the values in the debugger
633 bool bound = cur.boundary();
634 int rowpos = cur.textRow().pos();
635 int pos = cur.pos();
636 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
637 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
638 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
639 #endif
640 if (!cur.boundary() &&
641 cur.textRow().pos() == cur.pos() &&
642 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
643 !cur.paragraph().isNewline(cur.pos() - 1) &&
644 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
645 !cur.paragraph().isSeparator(cur.pos() - 1)) {
646 return setCursor(cur, cur.pit(), cur.pos(), true, true);
647 }
648
649 // go left and try to enter inset
650 if (checkAndActivateInset(cur, false))
651 return false;
652
653 // normal character left
654 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
655 }
656
657 // move to the previous paragraph or do nothing
658 if (cur.pit() > 0) {
659 Paragraph & par = getPar(cur.pit() - 1);
660 pos_type lastpos = par.size();
661 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
662 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
663 else
664 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
665 }
666 return false;
667 }
668
669
cursorVisLeft(Cursor & cur,bool skip_inset)670 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
671 {
672 Cursor temp_cur = cur;
673 temp_cur.posVisLeft(skip_inset);
674 if (temp_cur.depth() > cur.depth()) {
675 cur = temp_cur;
676 return false;
677 }
678 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
679 true, temp_cur.boundary());
680 }
681
682
cursorVisRight(Cursor & cur,bool skip_inset)683 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
684 {
685 Cursor temp_cur = cur;
686 temp_cur.posVisRight(skip_inset);
687 if (temp_cur.depth() > cur.depth()) {
688 cur = temp_cur;
689 return false;
690 }
691 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
692 true, temp_cur.boundary());
693 }
694
695
cursorForward(Cursor & cur)696 bool Text::cursorForward(Cursor & cur)
697 {
698 // Tell BufferView to test for FitCursor in any case!
699 cur.screenUpdateFlags(Update::FitCursor);
700
701 // not at paragraph end?
702 if (cur.pos() != cur.lastpos()) {
703 // in front of editable inset, i.e. jump into it?
704 if (checkAndActivateInset(cur, true))
705 return false;
706
707 TextMetrics const & tm = cur.bv().textMetrics(this);
708 // if left of boundary -> just jump to right side
709 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
710 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
711 return setCursor(cur, cur.pit(), cur.pos(), true, false);
712
713 // next position is left of boundary,
714 // but go to next line for special cases like space, newline, linesep
715 #if 0
716 // some effectless debug code to see the values in the debugger
717 int endpos = cur.textRow().endpos();
718 int lastpos = cur.lastpos();
719 int pos = cur.pos();
720 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
721 bool newline = cur.paragraph().isNewline(cur.pos());
722 bool sep = cur.paragraph().isSeparator(cur.pos());
723 if (cur.pos() != cur.lastpos()) {
724 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
725 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
726 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
727 }
728 #endif
729 if (cur.textRow().endpos() == cur.pos() + 1) {
730 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
731 cur.pos() + 1 == cur.lastpos() &&
732 cur.pit() != cur.lastpit()) {
733 // move to next paragraph
734 return setCursor(cur, cur.pit() + 1, 0, true, false);
735 } else if (cur.textRow().endpos() != cur.lastpos() &&
736 !cur.paragraph().isNewline(cur.pos()) &&
737 !cur.paragraph().isEnvSeparator(cur.pos()) &&
738 !cur.paragraph().isLineSeparator(cur.pos()) &&
739 !cur.paragraph().isSeparator(cur.pos())) {
740 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
741 }
742 }
743
744 // in front of RTL boundary? Stay on this side of the boundary because:
745 // ab|cDDEEFFghi -> abc|DDEEFFghi
746 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
747 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
748
749 // move right
750 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
751 }
752
753 // move to next paragraph
754 if (cur.pit() != cur.lastpit())
755 return setCursor(cur, cur.pit() + 1, 0, true, false);
756 return false;
757 }
758
759
cursorUpParagraph(Cursor & cur)760 bool Text::cursorUpParagraph(Cursor & cur)
761 {
762 bool updated = false;
763 if (cur.pos() > 0)
764 updated = setCursor(cur, cur.pit(), 0);
765 else if (cur.pit() != 0)
766 updated = setCursor(cur, cur.pit() - 1, 0);
767 return updated;
768 }
769
770
cursorDownParagraph(Cursor & cur)771 bool Text::cursorDownParagraph(Cursor & cur)
772 {
773 bool updated = false;
774 if (cur.pit() != cur.lastpit())
775 if (lyxrc.mac_like_cursor_movement)
776 if (cur.pos() == cur.lastpos())
777 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
778 else
779 updated = setCursor(cur, cur.pit(), cur.lastpos());
780 else
781 updated = setCursor(cur, cur.pit() + 1, 0);
782 else
783 updated = setCursor(cur, cur.pit(), cur.lastpos());
784 return updated;
785 }
786
787
788 namespace {
789 // fix the cursor `cur' after characters has been deleted at `where'
790 // position. Called by deleteEmptyParagraphMechanism
fixCursorAfterDelete(CursorSlice & cur,CursorSlice const & where,pos_type from,int num_chars)791 void fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where,
792 pos_type from, int num_chars)
793 {
794 // Do nothing if cursor is not in the paragraph where the
795 // deletion occurred,
796 if (cur.pit() != where.pit())
797 return;
798
799 // If cursor position is after the deletion place update it
800 if (cur.pos() > from)
801 cur.pos() = max(from + 1, cur.pos() - num_chars);
802
803 // Check also if we don't want to set the cursor on a spot behind the
804 // pagragraph because we erased the last character.
805 if (cur.pos() > cur.lastpos())
806 cur.pos() = cur.lastpos();
807 }
808
809 }
810
811
deleteEmptyParagraphMechanism(Cursor & cur,Cursor & old,bool & need_anchor_change)812 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
813 Cursor & old, bool & need_anchor_change)
814 {
815 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
816
817 Paragraph & oldpar = old.paragraph();
818
819 // We allow all kinds of "mumbo-jumbo" when freespacing.
820 if (oldpar.isFreeSpacing())
821 return false;
822
823 /* Ok I'll put some comments here about what is missing.
824 There are still some small problems that can lead to
825 double spaces stored in the document file or space at
826 the beginning of paragraphs(). This happens if you have
827 the cursor between two spaces and then save. Or if you
828 cut and paste and the selection has a space at the
829 beginning and then save right after the paste. (Lgb)
830 */
831
832 // If old.pos() == 0 and old.pos()(1) == LineSeparator
833 // delete the LineSeparator.
834 // MISSING
835
836 // If old.pos() == 1 and old.pos()(0) == LineSeparator
837 // delete the LineSeparator.
838 // MISSING
839
840 // Find a common inset and the corresponding depth.
841 size_t depth = 0;
842 for (; depth < cur.depth(); ++depth)
843 if (&old.inset() == &cur[depth].inset())
844 break;
845
846 // Whether a common inset is found and whether the cursor is still in
847 // the same paragraph (possibly nested).
848 bool const same_par = depth < cur.depth() && old.idx() == cur[depth].idx()
849 && old.pit() == cur[depth].pit();
850 bool const same_par_pos = depth == cur.depth() - 1 && same_par
851 && old.pos() == cur[depth].pos();
852
853 // If the chars around the old cursor were spaces, delete some of
854 // them, but only if the cursor has really moved.
855 if (!same_par_pos) {
856 // find range of spaces around cursors
857 int from = old.pos();
858 while (from > 0
859 && oldpar.isLineSeparator(from - 1)
860 && !oldpar.isDeleted(from - 1))
861 --from;
862 int to = old.pos();
863 while (to < oldpar.size()
864 && oldpar.isLineSeparator(to)
865 && !oldpar.isDeleted(to))
866 ++to;
867
868 // If we are not at the start of the paragraph, keep one space
869 if (from != to && from > 0) {
870 --to;
871 // if the new cursor is inside the sequence of spaces, keep one more space
872 if (same_par && cur.pos() > from && cur.pos() <= to)
873 --to;
874 }
875
876 // Remove spaces and adapt cursor.
877 if (from < to) {
878 // we need to remember what the size was because
879 // eraseChars might mark spaces as deleted instead of
880 // removing them.
881 int const oldsize = oldpar.size();
882 oldpar.eraseChars(from, to, cur.buffer()->params().track_changes);
883 // FIXME: This will not work anymore when we have multiple views of the same buffer
884 // In this case, we will have to correct also the cursors held by
885 // other bufferviews. It will probably be easier to do that in a more
886 // automated way in CursorSlice code. (JMarc 26/09/2001)
887 // correct all cursor parts
888 if (same_par) {
889 fixCursorAfterDelete(cur[depth], old.top(), from, oldsize - oldpar.size());
890 need_anchor_change = true;
891 }
892 return true;
893 }
894 }
895
896 // only do our other magic if we changed paragraph
897 if (same_par)
898 return false;
899
900 // don't delete anything if this is the ONLY paragraph!
901 if (old.lastpit() == 0)
902 return false;
903
904 // Do not delete empty paragraphs with keepempty set.
905 if (oldpar.allowEmpty())
906 return false;
907
908 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
909 // Delete old par.
910 old.recordUndo(max(old.pit() - 1, pit_type(0)),
911 min(old.pit() + 1, old.lastpit()));
912 ParagraphList & plist = old.text()->paragraphs();
913 bool const soa = oldpar.params().startOfAppendix();
914 plist.erase(lyx::next(plist.begin(), old.pit()));
915 // do not lose start of appendix marker (bug 4212)
916 if (soa && old.pit() < pit_type(plist.size()))
917 plist[old.pit()].params().startOfAppendix(true);
918
919 // see #warning (FIXME?) above
920 if (cur.depth() >= old.depth()) {
921 CursorSlice & curslice = cur[old.depth() - 1];
922 if (&curslice.inset() == &old.inset()
923 && curslice.pit() > old.pit()) {
924 --curslice.pit();
925 // since a paragraph has been deleted, all the
926 // insets after `old' have been copied and
927 // their address has changed. Therefore we
928 // need to `regenerate' cur. (JMarc)
929 cur.updateInsets(&(cur.bottom().inset()));
930 need_anchor_change = true;
931 }
932 }
933 return true;
934 }
935
936 if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
937 need_anchor_change = true;
938 // We return true here because the Paragraph contents changed and
939 // we need a redraw before further action is processed.
940 return true;
941 }
942
943 return false;
944 }
945
946
deleteEmptyParagraphMechanism(pit_type first,pit_type last,bool trackChanges)947 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
948 {
949 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
950
951 for (pit_type pit = first; pit <= last; ++pit) {
952 Paragraph & par = pars_[pit];
953
954 // We allow all kinds of "mumbo-jumbo" when freespacing.
955 if (par.isFreeSpacing())
956 continue;
957
958 pos_type from = 0;
959 while (from < par.size()) {
960 // skip non-spaces
961 while (from < par.size()
962 && (!par.isLineSeparator(from) || par.isDeleted(from)))
963 ++from;
964 // find string of spaces
965 pos_type to = from;
966 while (to < par.size()
967 && par.isLineSeparator(to) && !par.isDeleted(to))
968 ++to;
969 // empty? We are done
970 if (from == to)
971 break;
972 // if inside the line, keep one space
973 if (from > 0 && to < par.size())
974 ++from;
975 // remove the extra spaces
976 if (from < to)
977 par.eraseChars(from, to, trackChanges);
978 }
979
980 // don't delete anything if this is the only remaining paragraph
981 // within the given range. Note: Text::acceptOrRejectChanges()
982 // sets the cursor to 'first' after calling DEPM
983 if (first == last)
984 continue;
985
986 // don't delete empty paragraphs with keepempty set
987 if (par.allowEmpty())
988 continue;
989
990 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
991 pars_.erase(lyx::next(pars_.begin(), pit));
992 --pit;
993 --last;
994 continue;
995 }
996
997 par.stripLeadingSpaces(trackChanges);
998 }
999 }
1000
1001
1002 } // namespace lyx
1003