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