]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
0d4358d8f342ad0c68da7288865510db563c5bc7
[lyx.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 "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
30 #include "Bullet.h"
31 #include "Color.h"
32 #include "Cursor.h"
33 #include "CutAndPaste.h"
34 #include "debug.h"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
38 #include "gettext.h"
39 #include "Language.h"
40 #include "Lexer.h"
41 #include "LyXFunc.h"
42 #include "LyXRC.h"
43 #include "Paragraph.h"
44 #include "paragraph_funcs.h"
45 #include "ParagraphParameters.h"
46 #include "ParIterator.h"
47 #include "Server.h"
48 #include "ServerSocket.h"
49 #include "Undo.h"
50 #include "VSpace.h"
51
52 #include "frontends/FontMetrics.h"
53
54 #include "insets/InsetEnvironment.h"
55
56 #include "mathed/InsetMathHull.h"
57
58 #include "support/textutils.h"
59
60 #include <boost/current_function.hpp>
61
62 #include <sstream>
63
64 using std::endl;
65 using std::ostringstream;
66 using std::string;
67 using std::max;
68 using std::min;
69 using std::istringstream;
70
71 namespace lyx {
72
73 Text::Text()
74         : autoBreakRows_(false)
75 {}
76
77
78 bool Text::isMainText(Buffer const & buffer) const
79 {
80         return &buffer.text() == this;
81 }
82
83
84 // Gets the fully instantiated font at a given position in a paragraph
85 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
86 // The difference is that this one is used for displaying, and thus we
87 // are allowed to make cosmetic improvements. For instance make footnotes
88 // smaller. (Asger)
89 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
90                 pos_type const pos) const
91 {
92         BOOST_ASSERT(pos >= 0);
93
94         LayoutPtr const & layout = par.layout();
95         // FIXME: broken?
96         BufferParams const & params = buffer.params();
97         pos_type const body_pos = par.beginOfBody();
98
99         // We specialize the 95% common case:
100         if (!par.getDepth()) {
101                 Font f = par.getFontSettings(params, pos);
102                 if (!isMainText(buffer))
103                         applyOuterFont(buffer, f);
104                 Font lf;
105                 Font rlf;
106                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
107                         lf = layout->labelfont;
108                         rlf = layout->reslabelfont;
109                 } else {
110                         lf = layout->font;
111                         rlf = layout->resfont;
112                 }
113                 // In case the default family has been customized
114                 if (lf.family() == Font::INHERIT_FAMILY)
115                         rlf.setFamily(params.getFont().family());
116                 return f.realize(rlf);
117         }
118
119         // The uncommon case need not be optimized as much
120         Font layoutfont;
121         if (pos < body_pos)
122                 layoutfont = layout->labelfont;
123         else
124                 layoutfont = layout->font;
125
126         Font font = par.getFontSettings(params, pos);
127         font.realize(layoutfont);
128
129         if (!isMainText(buffer))
130                 applyOuterFont(buffer, font);
131
132         // Find the pit value belonging to paragraph. This will not break
133         // even if pars_ would not be a vector anymore.
134         // Performance appears acceptable.
135
136         pit_type pit = pars_.size();
137         for (pit_type it = 0; it < pit; ++it)
138                 if (&pars_[it] == &par) {
139                         pit = it;
140                         break;
141                 }
142         // Realize against environment font information
143         // NOTE: the cast to pit_type should be removed when pit_type
144         // changes to a unsigned integer.
145         if (pit < pit_type(pars_.size()))
146                 font.realize(outerFont(pit, pars_));
147
148         // Realize with the fonts of lesser depth.
149         font.realize(params.getFont());
150
151         return font;
152 }
153
154 // There are currently two font mechanisms in LyX:
155 // 1. The font attributes in a lyxtext, and
156 // 2. The inset-specific font properties, defined in an inset's
157 // metrics() and draw() methods and handed down the inset chain through
158 // the pi/mi parameters, and stored locally in a lyxtext in font_.
159 // This is where the two are integrated in the final fully realized
160 // font.
161 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
162         Font lf(font_);
163         lf.reduce(buffer.params().getFont());
164         lf.realize(font);
165         lf.setLanguage(font.language());
166         font = lf;
167 }
168
169
170 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
171 {
172         LayoutPtr const & layout = pars_[pit].layout();
173
174         if (!pars_[pit].getDepth())  {
175                 Font lf = layout->resfont;
176                 // In case the default family has been customized
177                 if (layout->font.family() == Font::INHERIT_FAMILY)
178                         lf.setFamily(buffer.params().getFont().family());
179                 return lf;
180         }
181
182         Font font = layout->font;
183         // Realize with the fonts of lesser depth.
184         //font.realize(outerFont(pit, paragraphs()));
185         font.realize(buffer.params().getFont());
186
187         return font;
188 }
189
190
191 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
192 {
193         LayoutPtr const & layout = par.layout();
194
195         if (!par.getDepth()) {
196                 Font lf = layout->reslabelfont;
197                 // In case the default family has been customized
198                 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
199                         lf.setFamily(buffer.params().getFont().family());
200                 return lf;
201         }
202
203         Font font = layout->labelfont;
204         // Realize with the fonts of lesser depth.
205         font.realize(buffer.params().getFont());
206
207         return font;
208 }
209
210
211 void Text::setCharFont(Buffer const & buffer, pit_type pit,
212                 pos_type pos, Font const & fnt)
213 {
214         Font font = fnt;
215         LayoutPtr const & layout = pars_[pit].layout();
216
217         // Get concrete layout font to reduce against
218         Font layoutfont;
219
220         if (pos < pars_[pit].beginOfBody())
221                 layoutfont = layout->labelfont;
222         else
223                 layoutfont = layout->font;
224
225         // Realize against environment font information
226         if (pars_[pit].getDepth()) {
227                 pit_type tp = pit;
228                 while (!layoutfont.resolved() &&
229                        tp != pit_type(paragraphs().size()) &&
230                        pars_[tp].getDepth()) {
231                         tp = outerHook(tp, paragraphs());
232                         if (tp != pit_type(paragraphs().size()))
233                                 layoutfont.realize(pars_[tp].layout()->font);
234                 }
235         }
236
237         // Inside inset, apply the inset's font attributes if any
238         // (charstyle!)
239         if (!isMainText(buffer))
240                 layoutfont.realize(font_);
241
242         layoutfont.realize(buffer.params().getFont());
243
244         // Now, reduce font against full layout font
245         font.reduce(layoutfont);
246
247         pars_[pit].setFont(pos, font);
248 }
249
250
251 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
252                 pos_type pos, Font const & font, bool toggleall)
253 {
254         BOOST_ASSERT(pars_[pit].isInset(pos) &&
255                      pars_[pit].getInset(pos)->noFontChange());
256
257         Inset * const inset = pars_[pit].getInset(pos);
258         CursorSlice::idx_type endidx = inset->nargs();
259         for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
260                 Text * text = cs.text();
261                 if (text) {
262                         // last position of the cell
263                         CursorSlice cellend = cs;
264                         cellend.pit() = cellend.lastpit();
265                         cellend.pos() = cellend.lastpos();
266                         text->setFont(buffer, cs, cellend, font, toggleall);
267                 }
268         }
269 }
270
271
272 // return past-the-last paragraph influenced by a layout change on pit
273 pit_type Text::undoSpan(pit_type pit)
274 {
275         pit_type end = paragraphs().size();
276         pit_type nextpit = pit + 1;
277         if (nextpit == end)
278                 return nextpit;
279         //because of parindents
280         if (!pars_[pit].getDepth())
281                 return boost::next(nextpit);
282         //because of depth constrains
283         for (; nextpit != end; ++pit, ++nextpit) {
284                 if (!pars_[pit].getDepth())
285                         break;
286         }
287         return nextpit;
288 }
289
290
291 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
292                 docstring const & layout)
293 {
294         BOOST_ASSERT(start != end);
295
296         BufferParams const & bufparams = buffer.params();
297         LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
298
299         for (pit_type pit = start; pit != end; ++pit) {
300                 Paragraph & par = pars_[pit];
301                 par.applyLayout(lyxlayout);
302                 if (lyxlayout->margintype == MARGIN_MANUAL)
303                         par.setLabelWidthString(par.translateIfPossible(
304                                 lyxlayout->labelstring(), buffer.params()));
305         }
306 }
307
308
309 // set layout over selection and make a total rebreak of those paragraphs
310 void Text::setLayout(Cursor & cur, docstring const & layout)
311 {
312         BOOST_ASSERT(this == cur.text());
313         // special handling of new environment insets
314         BufferView & bv = cur.bv();
315         BufferParams const & params = bv.buffer().params();
316         LayoutPtr const & lyxlayout = params.getTextClass()[layout];
317         if (lyxlayout->is_environment) {
318                 // move everything in a new environment inset
319                 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
320                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
321                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
322                 lyx::dispatch(FuncRequest(LFUN_CUT));
323                 Inset * inset = new InsetEnvironment(params, layout);
324                 insertInset(cur, inset);
325                 //inset->edit(cur, true);
326                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
327                 return;
328         }
329
330         pit_type start = cur.selBegin().pit();
331         pit_type end = cur.selEnd().pit() + 1;
332         pit_type undopit = undoSpan(end - 1);
333         recUndo(cur, start, undopit - 1);
334         setLayout(cur.buffer(), start, end, layout);
335         updateLabels(cur.buffer());
336 }
337
338
339 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
340                         Paragraph const & par, int max_depth)
341 {
342         if (par.layout()->labeltype == LABEL_BIBLIO)
343                 return false;
344         int const depth = par.params().depth();
345         if (type == Text::INC_DEPTH && depth < max_depth)
346                 return true;
347         if (type == Text::DEC_DEPTH && depth > 0)
348                 return true;
349         return false;
350 }
351
352
353 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
354 {
355         BOOST_ASSERT(this == cur.text());
356         // this happens when selecting several cells in tabular (bug 2630)
357         if (cur.selBegin().idx() != cur.selEnd().idx())
358                 return false;
359
360         pit_type const beg = cur.selBegin().pit();
361         pit_type const end = cur.selEnd().pit() + 1;
362         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
363
364         for (pit_type pit = beg; pit != end; ++pit) {
365                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
366                         return true;
367                 max_depth = pars_[pit].getMaxDepthAfter();
368         }
369         return false;
370 }
371
372
373 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
374 {
375         BOOST_ASSERT(this == cur.text());
376         pit_type const beg = cur.selBegin().pit();
377         pit_type const end = cur.selEnd().pit() + 1;
378         recordUndoSelection(cur);
379         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
380
381         for (pit_type pit = beg; pit != end; ++pit) {
382                 Paragraph & par = pars_[pit];
383                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
384                         int const depth = par.params().depth();
385                         if (type == INC_DEPTH)
386                                 par.params().depth(depth + 1);
387                         else
388                                 par.params().depth(depth - 1);
389                 }
390                 max_depth = par.getMaxDepthAfter();
391         }
392         // this handles the counter labels, and also fixes up
393         // depth values for follow-on (child) paragraphs
394         updateLabels(cur.buffer());
395 }
396
397
398 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
399 {
400         BOOST_ASSERT(this == cur.text());
401         // Set the current_font
402         // Determine basis font
403         Font layoutfont;
404         pit_type pit = cur.pit();
405         if (cur.pos() < pars_[pit].beginOfBody())
406                 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
407         else
408                 layoutfont = getLayoutFont(cur.buffer(), pit);
409
410         // Update current font
411         cur.real_current_font.update(font,
412                                         cur.buffer().params().language,
413                                         toggleall);
414
415         // Reduce to implicit settings
416         cur.current_font = cur.real_current_font;
417         cur.current_font.reduce(layoutfont);
418         // And resolve it completely
419         cur.real_current_font.realize(layoutfont);
420
421         // if there is no selection that's all we need to do
422         if (!cur.selection())
423                 return;
424
425         // Ok, we have a selection.
426         recordUndoSelection(cur);
427
428         setFont(cur.buffer(), cur.selectionBegin().top(), 
429                 cur.selectionEnd().top(), font, toggleall);
430 }
431
432
433 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
434                 CursorSlice const & end, Font const & font,
435                 bool toggleall)
436 {
437         // Don't use forwardChar here as ditend might have
438         // pos() == lastpos() and forwardChar would miss it.
439         // Can't use forwardPos either as this descends into
440         // nested insets.
441         Language const * language = buffer.params().language;
442         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
443                 if (dit.pos() != dit.lastpos()) {
444                         pit_type const pit = dit.pit();
445                         pos_type const pos = dit.pos();
446                         if (pars_[pit].isInset(pos) &&
447                             pars_[pit].getInset(pos)->noFontChange())
448                                 // We need to propagate the font change to all
449                                 // text cells of the inset (bug 1973).
450                                 // FIXME: This should change, see documentation
451                                 // of noFontChange in Inset.h
452                                 setInsetFont(buffer, pit, pos, font, toggleall);
453                         Font f = getFont(buffer, dit.paragraph(), pos);
454                         f.update(font, language, toggleall);
455                         setCharFont(buffer, pit, pos, f);
456                 }
457         }
458 }
459
460
461 bool Text::cursorTop(Cursor & cur)
462 {
463         BOOST_ASSERT(this == cur.text());
464         return setCursor(cur, 0, 0);
465 }
466
467
468 bool Text::cursorBottom(Cursor & cur)
469 {
470         BOOST_ASSERT(this == cur.text());
471         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
472 }
473
474
475 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
476 {
477         BOOST_ASSERT(this == cur.text());
478         // If the mask is completely neutral, tell user
479         if (font == Font(Font::ALL_IGNORE)) {
480                 // Could only happen with user style
481                 cur.message(_("No font change defined."));
482                 return;
483         }
484
485         // Try implicit word selection
486         // If there is a change in the language the implicit word selection
487         // is disabled.
488         CursorSlice resetCursor = cur.top();
489         bool implicitSelection =
490                 font.language() == ignore_language
491                 && font.number() == Font::IGNORE
492                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
493
494         // Set font
495         setFont(cur, font, toggleall);
496
497         // Implicit selections are cleared afterwards
498         // and cursor is set to the original position.
499         if (implicitSelection) {
500                 cur.clearSelection();
501                 cur.top() = resetCursor;
502                 cur.resetAnchor();
503         }
504 }
505
506
507 docstring Text::getStringToIndex(Cursor const & cur)
508 {
509         BOOST_ASSERT(this == cur.text());
510
511         docstring idxstring;
512         if (cur.selection())
513                 idxstring = cur.selectionAsString(false);
514         else {
515                 // Try implicit word selection. If there is a change
516                 // in the language the implicit word selection is
517                 // disabled.
518                 Cursor tmpcur = cur;
519                 selectWord(tmpcur, PREVIOUS_WORD);
520
521                 if (!tmpcur.selection())
522                         cur.message(_("Nothing to index!"));
523                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
524                         cur.message(_("Cannot index more than one paragraph!"));
525                 else
526                         idxstring = tmpcur.selectionAsString(false);
527         }
528
529         return idxstring;
530 }
531
532
533 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge) 
534 {
535         BOOST_ASSERT(cur.text());
536         // make sure that the depth behind the selection are restored, too
537         pit_type undopit = undoSpan(cur.selEnd().pit());
538         recUndo(cur, cur.selBegin().pit(), undopit - 1);
539
540         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
541              pit <= end; ++pit) {
542                 Paragraph & par = pars_[pit];
543                 ParagraphParameters params = par.params();
544                 params.read(to_utf8(arg), merge);
545                 Layout const & layout = *(par.layout());
546                 par.params().apply(params, layout);
547         }
548 }
549
550
551 //FIXME This is a little redundant now, but it's probably worth keeping,
552 //especially if we're going to go away from using serialization internally
553 //quite so much.
554 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p) 
555 {
556         BOOST_ASSERT(cur.text());
557         // make sure that the depth behind the selection are restored, too
558         pit_type undopit = undoSpan(cur.selEnd().pit());
559         recUndo(cur, cur.selBegin().pit(), undopit - 1);
560
561         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
562              pit <= end; ++pit) {
563                 Paragraph & par = pars_[pit];
564                 Layout const & layout = *(par.layout());
565                 par.params().apply(p, layout);
566         }       
567 }
568
569
570 // this really should just insert the inset and not move the cursor.
571 void Text::insertInset(Cursor & cur, Inset * inset)
572 {
573         BOOST_ASSERT(this == cur.text());
574         BOOST_ASSERT(inset);
575         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
576                                     Change(cur.buffer().params().trackChanges ?
577                                            Change::INSERTED : Change::UNCHANGED));
578 }
579
580
581 // needed to insert the selection
582 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
583 {
584         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
585                                          cur.current_font, str, autoBreakRows_);
586 }
587
588
589 // turn double CR to single CR, others are converted into one
590 // blank. Then insertStringAsLines is called
591 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
592 {
593         docstring linestr = str;
594         bool newline_inserted = false;
595
596         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
597                 if (linestr[i] == '\n') {
598                         if (newline_inserted) {
599                                 // we know that \r will be ignored by
600                                 // insertStringAsLines. Of course, it is a dirty
601                                 // trick, but it works...
602                                 linestr[i - 1] = '\r';
603                                 linestr[i] = '\n';
604                         } else {
605                                 linestr[i] = ' ';
606                                 newline_inserted = true;
607                         }
608                 } else if (isPrintable(linestr[i])) {
609                         newline_inserted = false;
610                 }
611         }
612         insertStringAsLines(cur, linestr);
613 }
614
615
616 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
617                         bool setfont, bool boundary)
618 {
619         Cursor old = cur;
620         setCursorIntern(cur, par, pos, setfont, boundary);
621         return cur.bv().checkDepm(cur, old);
622 }
623
624
625 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
626 {
627         BOOST_ASSERT(par != int(paragraphs().size()));
628         cur.pit() = par;
629         cur.pos() = pos;
630
631         // now some strict checking
632         Paragraph & para = getPar(par);
633
634         // None of these should happen, but we're scaredy-cats
635         if (pos < 0) {
636                 lyxerr << "dont like -1" << endl;
637                 BOOST_ASSERT(false);
638         }
639
640         if (pos > para.size()) {
641                 lyxerr << "dont like 1, pos: " << pos
642                        << " size: " << para.size()
643                        << " par: " << par << endl;
644                 BOOST_ASSERT(false);
645         }
646 }
647
648
649 void Text::setCursorIntern(Cursor & cur,
650                               pit_type par, pos_type pos, bool setfont, bool boundary)
651 {
652         BOOST_ASSERT(this == cur.text());
653         cur.boundary(boundary);
654         setCursor(cur.top(), par, pos);
655         if (setfont)
656                 cur.setCurrentFont();
657 }
658
659
660 bool Text::checkAndActivateInset(Cursor & cur, bool front)
661 {
662         if (cur.selection())
663                 return false;
664         if (front && cur.pos() == cur.lastpos())
665                 return false;
666         if (!front && cur.pos() == 0)
667                 return false;
668         Inset * inset = front ? cur.nextInset() : cur.prevInset();
669         if (!isHighlyEditableInset(inset))
670                 return false;
671         /*
672          * Apparently, when entering an inset we are expected to be positioned
673          * *before* it in the containing paragraph, regardless of the direction
674          * from which we are entering. Otherwise, cursor placement goes awry,
675          * and when we exit from the beginning, we'll be placed *after* the
676          * inset.
677          */
678         if (!front)
679                 --cur.pos();
680         inset->edit(cur, front);
681         return true;
682 }
683
684
685 bool Text::cursorLeft(Cursor & cur)
686 {
687         // Tell BufferView to test for FitCursor in any case!
688         cur.updateFlags(Update::FitCursor);
689
690         // not at paragraph start?
691         if (cur.pos() > 0) {
692                 // if on right side of boundary (i.e. not at paragraph end, but line end)
693                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
694                 // there are some exceptions to ignore this: lineseps, newlines, spaces
695 #if 0
696                 // some effectless debug code to see the values in the debugger
697                 bool bound = cur.boundary();
698                 int rowpos = cur.textRow().pos();
699                 int pos = cur.pos();
700                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
701                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
702                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
703 #endif
704                 if (!cur.boundary() &&
705                                 cur.textRow().pos() == cur.pos() &&
706                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
707                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
708                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
709                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
710                 }
711                 
712                 // go left and try to enter inset
713                 if (checkAndActivateInset(cur, false))
714                         return false;
715                 
716                 // normal character left
717                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
718         }
719
720         // move to the previous paragraph or do nothing
721         if (cur.pit() > 0)
722                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
723         return false;
724 }
725
726
727 bool Text::cursorRight(Cursor & cur)
728 {
729         // Tell BufferView to test for FitCursor in any case!
730         cur.updateFlags(Update::FitCursor);
731
732         // not at paragraph end?
733         if (cur.pos() != cur.lastpos()) {
734                 // in front of editable inset, i.e. jump into it?
735                 if (checkAndActivateInset(cur, true))
736                         return false;
737
738                 // if left of boundary -> just jump to right side
739           // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
740           if (cur.boundary() && 
741                                 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
742                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
743
744                 // next position is left of boundary, 
745                 // but go to next line for special cases like space, newline, linesep
746 #if 0
747                 // some effectless debug code to see the values in the debugger
748                 int endpos = cur.textRow().endpos();
749                 int lastpos = cur.lastpos();
750                 int pos = cur.pos();
751                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
752                 bool newline = cur.paragraph().isNewline(cur.pos());
753                 bool sep = cur.paragraph().isSeparator(cur.pos());
754                 if (cur.pos() != cur.lastpos()) {
755                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
756                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
757                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
758                 }
759 #endif
760                 if (cur.textRow().endpos() == cur.pos() + 1 &&
761                     cur.textRow().endpos() != cur.lastpos() &&
762                                 !cur.paragraph().isNewline(cur.pos()) &&
763                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
764                                 !cur.paragraph().isSeparator(cur.pos())) {
765                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
766                 }
767                 
768                 // in front of RTL boundary? Stay on this side of the boundary because:
769                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
770                 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
771                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
772                 
773                 // move right
774                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
775         }
776
777         // move to next paragraph
778         if (cur.pit() != cur.lastpit())
779                 return setCursor(cur, cur.pit() + 1, 0);
780         return false;
781 }
782
783
784 bool Text::cursorUpParagraph(Cursor & cur)
785 {
786         bool updated = false;
787         if (cur.pos() > 0)
788                 updated = setCursor(cur, cur.pit(), 0);
789         else if (cur.pit() != 0)
790                 updated = setCursor(cur, cur.pit() - 1, 0);
791         return updated;
792 }
793
794
795 bool Text::cursorDownParagraph(Cursor & cur)
796 {
797         bool updated = false;
798         if (cur.pit() != cur.lastpit())
799                 updated = setCursor(cur, cur.pit() + 1, 0);
800         else
801                 updated = setCursor(cur, cur.pit(), cur.lastpos());
802         return updated;
803 }
804
805
806 // fix the cursor `cur' after a characters has been deleted at `where'
807 // position. Called by deleteEmptyParagraphMechanism
808 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
809 {
810         // Do nothing if cursor is not in the paragraph where the
811         // deletion occured,
812         if (cur.pit() != where.pit())
813                 return;
814
815         // If cursor position is after the deletion place update it
816         if (cur.pos() > where.pos())
817                 --cur.pos();
818
819         // Check also if we don't want to set the cursor on a spot behind the
820         // pagragraph because we erased the last character.
821         if (cur.pos() > cur.lastpos())
822                 cur.pos() = cur.lastpos();
823 }
824
825
826 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
827                 Cursor & old, bool & need_anchor_change)
828 {
829         //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
830
831         Paragraph & oldpar = old.paragraph();
832
833         // We allow all kinds of "mumbo-jumbo" when freespacing.
834         if (oldpar.isFreeSpacing())
835                 return false;
836
837         /* Ok I'll put some comments here about what is missing.
838            There are still some small problems that can lead to
839            double spaces stored in the document file or space at
840            the beginning of paragraphs(). This happens if you have
841            the cursor between to spaces and then save. Or if you
842            cut and paste and the selection have a space at the
843            beginning and then save right after the paste. (Lgb)
844         */
845
846         // If old.pos() == 0 and old.pos()(1) == LineSeparator
847         // delete the LineSeparator.
848         // MISSING
849
850         // If old.pos() == 1 and old.pos()(0) == LineSeparator
851         // delete the LineSeparator.
852         // MISSING
853
854         bool const same_inset = &old.inset() == &cur.inset();
855         bool const same_par = same_inset && old.pit() == cur.pit();
856         bool const same_par_pos = same_par && old.pos() == cur.pos();
857
858         // If the chars around the old cursor were spaces, delete one of them.
859         if (!same_par_pos) {
860                 // Only if the cursor has really moved.
861                 if (old.pos() > 0
862                     && old.pos() < oldpar.size()
863                     && oldpar.isLineSeparator(old.pos())
864                     && oldpar.isLineSeparator(old.pos() - 1)
865                     && !oldpar.isDeleted(old.pos() - 1)
866                     && !oldpar.isDeleted(old.pos())) {
867                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
868 // FIXME: This will not work anymore when we have multiple views of the same buffer
869 // In this case, we will have to correct also the cursors held by
870 // other bufferviews. It will probably be easier to do that in a more
871 // automated way in CursorSlice code. (JMarc 26/09/2001)
872                         // correct all cursor parts
873                         if (same_par) {
874                                 fixCursorAfterDelete(cur.top(), old.top());
875                                 need_anchor_change = true;
876                         }
877                         return true;
878                 }
879         }
880
881         // only do our magic if we changed paragraph
882         if (same_par)
883                 return false;
884
885         // don't delete anything if this is the ONLY paragraph!
886         if (old.lastpit() == 0)
887                 return false;
888
889         // Do not delete empty paragraphs with keepempty set.
890         if (oldpar.allowEmpty())
891                 return false;
892
893         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
894                 // Delete old par.
895                 recordUndo(old, Undo::ATOMIC,
896                            max(old.pit() - 1, pit_type(0)),
897                            min(old.pit() + 1, old.lastpit()));
898                 ParagraphList & plist = old.text()->paragraphs();
899                 plist.erase(boost::next(plist.begin(), old.pit()));
900
901                 // see #warning (FIXME?) above 
902                 if (cur.depth() >= old.depth()) {
903                         CursorSlice & curslice = cur[old.depth() - 1];
904                         if (&curslice.inset() == &old.inset()
905                             && curslice.pit() > old.pit()) {
906                                 --curslice.pit();
907                                 // since a paragraph has been deleted, all the
908                                 // insets after `old' have been copied and
909                                 // their address has changed. Therefore we
910                                 // need to `regenerate' cur. (JMarc)
911                                 cur.updateInsets(&(cur.bottom().inset()));
912                                 need_anchor_change = true;
913                         }
914                 }
915                 return true;
916         }
917
918         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
919                 need_anchor_change = true;
920                 // We return true here because the Paragraph contents changed and
921                 // we need a redraw before further action is processed.
922                 return true;
923         }
924
925         return false;
926 }
927
928
929 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
930 {
931         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
932
933         for (pit_type pit = first; pit <= last; ++pit) {
934                 Paragraph & par = pars_[pit];
935
936                 // We allow all kinds of "mumbo-jumbo" when freespacing.
937                 if (par.isFreeSpacing())
938                         continue;
939
940                 for (pos_type pos = 1; pos < par.size(); ++pos) {
941                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
942                             && !par.isDeleted(pos - 1)) {
943                                 if (par.eraseChar(pos - 1, trackChanges)) {
944                                         --pos;
945                                 }
946                         }
947                 }
948
949                 // don't delete anything if this is the only remaining paragraph within the given range
950                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
951                 if (first == last)
952                         continue;
953
954                 // don't delete empty paragraphs with keepempty set
955                 if (par.allowEmpty())
956                         continue;
957
958                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
959                         pars_.erase(boost::next(pars_.begin(), pit));
960                         --pit;
961                         --last;
962                         continue;
963                 }
964
965                 par.stripLeadingSpaces(trackChanges);
966         }
967 }
968
969
970 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
971 {
972         recordUndo(cur, Undo::ATOMIC, first, last);
973 }
974
975
976 void Text::recUndo(Cursor & cur, pit_type par) const
977 {
978         recordUndo(cur, Undo::ATOMIC, par, par);
979 }
980
981 } // namespace lyx