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