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