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