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