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