]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Revert 23154.
[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::getLayoutFont(Buffer const & buffer, pit_type const pit) const
77 {
78         LayoutPtr 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::getLabelFont(Buffer const & buffer, Paragraph const & par) const
98 {
99         LayoutPtr 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         LayoutPtr 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         LayoutPtr const & lyxlayout = bufparams.getTextClass()[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         LayoutPtr const & lyxlayout = params.getTextClass()[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(params, 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 = getLabelFont(cur.buffer(), pars_[pit]);
312         else
313                 layoutfont = getLayoutFont(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.getDisplayFont(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                 Layout const & layout = *(par.layout());
474                 par.params().apply(p, layout);
475         }       
476 }
477
478
479 // this really should just insert the inset and not move the cursor.
480 void Text::insertInset(Cursor & cur, Inset * inset)
481 {
482         BOOST_ASSERT(this == cur.text());
483         BOOST_ASSERT(inset);
484         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
485                 Change(cur.buffer().params().trackChanges
486                 ? Change::INSERTED : Change::UNCHANGED));
487 }
488
489
490 // needed to insert the selection
491 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
492 {
493         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
494                 cur.current_font, str, autoBreakRows_);
495 }
496
497
498 // turn double CR to single CR, others are converted into one
499 // blank. Then insertStringAsLines is called
500 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
501 {
502         docstring linestr = str;
503         bool newline_inserted = false;
504
505         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
506                 if (linestr[i] == '\n') {
507                         if (newline_inserted) {
508                                 // we know that \r will be ignored by
509                                 // insertStringAsLines. Of course, it is a dirty
510                                 // trick, but it works...
511                                 linestr[i - 1] = '\r';
512                                 linestr[i] = '\n';
513                         } else {
514                                 linestr[i] = ' ';
515                                 newline_inserted = true;
516                         }
517                 } else if (isPrintable(linestr[i])) {
518                         newline_inserted = false;
519                 }
520         }
521         insertStringAsLines(cur, linestr);
522 }
523
524
525 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
526                         bool setfont, bool boundary)
527 {
528         TextMetrics const & tm = cur.bv().textMetrics(this);
529         bool const update_needed = !tm.has(par);
530         Cursor old = cur;
531         setCursorIntern(cur, par, pos, setfont, boundary);
532         return cur.bv().checkDepm(cur, old) || update_needed;
533 }
534
535
536 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
537 {
538         BOOST_ASSERT(par != int(paragraphs().size()));
539         cur.pit() = par;
540         cur.pos() = pos;
541
542         // now some strict checking
543         Paragraph & para = getPar(par);
544
545         // None of these should happen, but we're scaredy-cats
546         if (pos < 0) {
547                 lyxerr << "dont like -1" << endl;
548                 BOOST_ASSERT(false);
549         }
550
551         if (pos > para.size()) {
552                 lyxerr << "dont like 1, pos: " << pos
553                        << " size: " << para.size()
554                        << " par: " << par << endl;
555                 BOOST_ASSERT(false);
556         }
557 }
558
559
560 void Text::setCursorIntern(Cursor & cur,
561                               pit_type par, pos_type pos, bool setfont, bool boundary)
562 {
563         BOOST_ASSERT(this == cur.text());
564         cur.boundary(boundary);
565         setCursor(cur.top(), par, pos);
566         if (setfont)
567                 cur.setCurrentFont();
568 }
569
570
571 bool Text::checkAndActivateInset(Cursor & cur, bool front)
572 {
573         if (cur.selection())
574                 return false;
575         if (front && cur.pos() == cur.lastpos())
576                 return false;
577         if (!front && cur.pos() == 0)
578                 return false;
579         Inset * inset = front ? cur.nextInset() : cur.prevInset();
580         if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
581                 return false;
582         /*
583          * Apparently, when entering an inset we are expected to be positioned
584          * *before* it in the containing paragraph, regardless of the direction
585          * from which we are entering. Otherwise, cursor placement goes awry,
586          * and when we exit from the beginning, we'll be placed *after* the
587          * inset.
588          */
589         if (!front)
590                 --cur.pos();
591         inset->edit(cur, front);
592         return true;
593 }
594
595
596 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
597 {
598         if (cur.selection())
599                 return false;
600         if (cur.pos() == -1)
601                 return false;
602         if (cur.pos() == cur.lastpos())
603                 return false;
604         Paragraph & par = cur.paragraph();
605         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
606         if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
607                 return false;
608         inset->edit(cur, movingForward, 
609                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
610         return true;
611 }
612
613
614 bool Text::cursorBackward(Cursor & cur)
615 {
616         // Tell BufferView to test for FitCursor in any case!
617         cur.updateFlags(Update::FitCursor);
618
619         // not at paragraph start?
620         if (cur.pos() > 0) {
621                 // if on right side of boundary (i.e. not at paragraph end, but line end)
622                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
623                 // there are some exceptions to ignore this: lineseps, newlines, spaces
624 #if 0
625                 // some effectless debug code to see the values in the debugger
626                 bool bound = cur.boundary();
627                 int rowpos = cur.textRow().pos();
628                 int pos = cur.pos();
629                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
630                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
631                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
632 #endif
633                 if (!cur.boundary() &&
634                                 cur.textRow().pos() == cur.pos() &&
635                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
636                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
637                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
638                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
639                 }
640                 
641                 // go left and try to enter inset
642                 if (checkAndActivateInset(cur, false))
643                         return false;
644                 
645                 // normal character left
646                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
647         }
648
649         // move to the previous paragraph or do nothing
650         if (cur.pit() > 0)
651                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
652         return false;
653 }
654
655
656 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
657 {
658         pit_type new_pit = cur.pit(); // the paragraph to which we will move
659         pos_type new_pos; // the position we will move to
660         bool new_boundary; // will we move to a boundary position?
661         pos_type left_pos; // position visually left of current cursor
662         pos_type right_pos; // position visually right of current cursor
663         bool new_pos_is_RTL; // is new position we're moving to RTL?
664
665         cur.getSurroundingPos(left_pos, right_pos);
666
667         LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
668
669         // Are we at an inset?
670         Cursor temp_cur = cur;
671         temp_cur.pos() = left_pos;
672         temp_cur.boundary(false);
673         if (!skip_inset && 
674                 checkAndActivateInsetVisual(temp_cur, left_pos >= cur.pos(), true)) {
675                 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
676                 cur = temp_cur; // set the real cursor to new position inside inset!
677                 return false;
678         }
679
680         // Are we already at leftmost pos in row?
681         if (left_pos == -1) {
682                 
683                 Cursor new_cur = cur;
684                 if (!new_cur.posVisToNewRow(true)) {
685                         LYXERR(Debug::RTL, "not moving!");
686                         return false;
687                 }
688                 
689                 // we actually move the cursor at the end of this function, for now 
690                 // just keep track of the new position...
691                 new_pit = new_cur.pit();
692                 new_pos = new_cur.pos();
693                 new_boundary = new_cur.boundary();
694
695                 LYXERR(Debug::RTL, "left edge, moving: " << int(new_pit) << "," 
696                         << int(new_pos) << "," << (new_boundary ? 1 : 0));
697
698         }
699         // normal movement to the left
700         else {
701                 // Recall, if the cursor is at position 'x', that means *before* 
702                 // the character at position 'x'. In RTL, "before" means "to the 
703                 // right of", in LTR, "to the left of". So currently our situation
704                 // is this: the position to our left is 'left_pos' (i.e., we're 
705                 // currently to the right of 'left_pos'). In order to move to the 
706                 // left, it depends whether or not the character at 'left_pos' is RTL.
707                 new_pos_is_RTL = cur.paragraph().getFontSettings(
708                         cur.bv().buffer().params(), left_pos).isVisibleRightToLeft();
709                 // If the character at 'left_pos' *is* RTL, then in order to move to
710                 // the left of it, we need to be *after* 'left_pos', i.e., move to
711                 // position 'left_pos' + 1.
712                 if (new_pos_is_RTL) {
713                         new_pos = left_pos + 1;
714                         // set the boundary to true in two situations:
715                         if (
716                         // 1. if new_pos is now lastpos (which means that we're moving left
717                         // to the end of an RTL chunk which is at the end of an LTR 
718                         // paragraph);
719                                 new_pos == cur.lastpos()
720                         // 2. if the position *after* left_pos is not RTL (we want to be 
721                         // *after* left_pos, not before left_pos + 1!)
722                                 || !cur.paragraph().getFontSettings(cur.bv().buffer().params(),
723                                                 new_pos).isVisibleRightToLeft()
724                         )
725                                 new_boundary = true;
726                         else // set the boundary to false
727                                 new_boundary = false;
728                 }
729                 // Otherwise (if the character at position 'left_pos' is LTR), then
730                 // moving to the left of it is as easy as setting the new position
731                 // to 'left_pos'.
732                 else {
733                         new_pos = left_pos;
734                         new_boundary = false;
735                 }
736         
737         }
738
739         LYXERR(Debug::RTL, "moving to: " << new_pos 
740                 << (new_boundary ? " (boundary)" : ""));
741
742         return setCursor(cur, new_pit, new_pos, true, new_boundary);
743 }
744
745
746 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
747 {
748         pit_type new_pit = cur.pit(); // the paragraph to which we will move
749         pos_type new_pos; // the position we will move to
750         bool new_boundary; // will we move to a boundary position?
751         pos_type left_pos; // position visually left of current cursor
752         pos_type right_pos; // position visually right of current cursor
753         bool new_pos_is_RTL; // is new position we're moving to RTL?
754
755         cur.getSurroundingPos(left_pos, right_pos);
756
757         LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
758
759         // Are we at an inset?
760         Cursor temp_cur = cur;
761         temp_cur.pos() = right_pos;
762         temp_cur.boundary(false);
763         if (!skip_inset &&
764                 checkAndActivateInsetVisual(temp_cur, right_pos >= cur.pos(), false)) {
765                 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
766                 cur = temp_cur; // set the real cursor to new position inside inset!
767                 return false;
768         }
769
770         // Are we already at rightmost pos in row?
771         if (right_pos == -1) {
772                 
773                 Cursor new_cur = cur;
774                 if (!new_cur.posVisToNewRow(false)) {
775                         LYXERR(Debug::RTL, "not moving!");
776                         return false;
777                 }
778                 
779                 // we actually move the cursor at the end of this function, for now 
780                 // just keep track of the new position...
781                 new_pit = new_cur.pit();
782                 new_pos = new_cur.pos();
783                 new_boundary = new_cur.boundary();
784
785                 LYXERR(Debug::RTL, "right edge, moving: " << int(new_pit) << "," 
786                         << int(new_pos) << "," << (new_boundary ? 1 : 0));
787
788         }
789         // normal movement to the right
790         else {
791                 // Recall, if the cursor is at position 'x', that means *before* 
792                 // the character at position 'x'. In RTL, "before" means "to the 
793                 // right of", in LTR, "to the left of". So currently our situation
794                 // is this: the position to our right is 'right_pos' (i.e., we're 
795                 // currently to the left of 'right_pos'). In order to move to the 
796                 // right, it depends whether or not the character at 'right_pos' is RTL.
797                 new_pos_is_RTL = cur.paragraph().getFontSettings(
798                         cur.bv().buffer().params(), right_pos).isVisibleRightToLeft();
799                 // If the character at 'right_pos' *is* LTR, then in order to move to
800                 // the right of it, we need to be *after* 'right_pos', i.e., move to
801                 // position 'right_pos' + 1.
802                 if (!new_pos_is_RTL) {
803                         new_pos = right_pos + 1;
804                         // set the boundary to true in two situations:
805                         if (
806                         // 1. if new_pos is now lastpos (which means that we're moving 
807                         // right to the end of an LTR chunk which is at the end of an
808                         // RTL paragraph);
809                                 new_pos == cur.lastpos()
810                         // 2. if the position *after* right_pos is RTL (we want to be 
811                         // *after* right_pos, not before right_pos + 1!)
812                                 || cur.paragraph().getFontSettings(cur.bv().buffer().params(),
813                                                 new_pos).isVisibleRightToLeft()
814                         )
815                                 new_boundary = true;
816                         else // set the boundary to false
817                                 new_boundary = false;
818                 }
819                 // Otherwise (if the character at position 'right_pos' is RTL), then
820                 // moving to the right of it is as easy as setting the new position
821                 // to 'right_pos'.
822                 else {
823                         new_pos = right_pos;
824                         new_boundary = false;
825                 }
826         
827         }
828
829         LYXERR(Debug::RTL, "moving to: " << new_pos 
830                 << (new_boundary ? " (boundary)" : ""));
831
832         return setCursor(cur, new_pit, new_pos, true, new_boundary);
833 }
834
835
836 bool Text::cursorForward(Cursor & cur)
837 {
838         // Tell BufferView to test for FitCursor in any case!
839         cur.updateFlags(Update::FitCursor);
840
841         // not at paragraph end?
842         if (cur.pos() != cur.lastpos()) {
843                 // in front of editable inset, i.e. jump into it?
844                 if (checkAndActivateInset(cur, true))
845                         return false;
846
847                 TextMetrics const & tm = cur.bv().textMetrics(this);
848                 // if left of boundary -> just jump to right side
849                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
850                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
851                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
852
853                 // next position is left of boundary, 
854                 // but go to next line for special cases like space, newline, linesep
855 #if 0
856                 // some effectless debug code to see the values in the debugger
857                 int endpos = cur.textRow().endpos();
858                 int lastpos = cur.lastpos();
859                 int pos = cur.pos();
860                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
861                 bool newline = cur.paragraph().isNewline(cur.pos());
862                 bool sep = cur.paragraph().isSeparator(cur.pos());
863                 if (cur.pos() != cur.lastpos()) {
864                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
865                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
866                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
867                 }
868 #endif
869                 if (cur.textRow().endpos() == cur.pos() + 1 &&
870                     cur.textRow().endpos() != cur.lastpos() &&
871                                 !cur.paragraph().isNewline(cur.pos()) &&
872                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
873                                 !cur.paragraph().isSeparator(cur.pos())) {
874                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
875                 }
876                 
877                 // in front of RTL boundary? Stay on this side of the boundary because:
878                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
879                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
880                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
881                 
882                 // move right
883                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
884         }
885
886         // move to next paragraph
887         if (cur.pit() != cur.lastpit())
888                 return setCursor(cur, cur.pit() + 1, 0, true, false);
889         return false;
890 }
891
892
893 bool Text::cursorUpParagraph(Cursor & cur)
894 {
895         bool updated = false;
896         if (cur.pos() > 0)
897                 updated = setCursor(cur, cur.pit(), 0);
898         else if (cur.pit() != 0)
899                 updated = setCursor(cur, cur.pit() - 1, 0);
900         return updated;
901 }
902
903
904 bool Text::cursorDownParagraph(Cursor & cur)
905 {
906         bool updated = false;
907         if (cur.pit() != cur.lastpit())
908                 updated = setCursor(cur, cur.pit() + 1, 0);
909         else
910                 updated = setCursor(cur, cur.pit(), cur.lastpos());
911         return updated;
912 }
913
914
915 // fix the cursor `cur' after a characters has been deleted at `where'
916 // position. Called by deleteEmptyParagraphMechanism
917 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
918 {
919         // Do nothing if cursor is not in the paragraph where the
920         // deletion occured,
921         if (cur.pit() != where.pit())
922                 return;
923
924         // If cursor position is after the deletion place update it
925         if (cur.pos() > where.pos())
926                 --cur.pos();
927
928         // Check also if we don't want to set the cursor on a spot behind the
929         // pagragraph because we erased the last character.
930         if (cur.pos() > cur.lastpos())
931                 cur.pos() = cur.lastpos();
932 }
933
934
935 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
936                 Cursor & old, bool & need_anchor_change)
937 {
938         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
939
940         Paragraph & oldpar = old.paragraph();
941
942         // We allow all kinds of "mumbo-jumbo" when freespacing.
943         if (oldpar.isFreeSpacing())
944                 return false;
945
946         /* Ok I'll put some comments here about what is missing.
947            There are still some small problems that can lead to
948            double spaces stored in the document file or space at
949            the beginning of paragraphs(). This happens if you have
950            the cursor between to spaces and then save. Or if you
951            cut and paste and the selection have a space at the
952            beginning and then save right after the paste. (Lgb)
953         */
954
955         // If old.pos() == 0 and old.pos()(1) == LineSeparator
956         // delete the LineSeparator.
957         // MISSING
958
959         // If old.pos() == 1 and old.pos()(0) == LineSeparator
960         // delete the LineSeparator.
961         // MISSING
962
963         bool const same_inset = &old.inset() == &cur.inset();
964         bool const same_par = same_inset && old.pit() == cur.pit();
965         bool const same_par_pos = same_par && old.pos() == cur.pos();
966
967         // If the chars around the old cursor were spaces, delete one of them.
968         if (!same_par_pos) {
969                 // Only if the cursor has really moved.
970                 if (old.pos() > 0
971                     && old.pos() < oldpar.size()
972                     && oldpar.isLineSeparator(old.pos())
973                     && oldpar.isLineSeparator(old.pos() - 1)
974                     && !oldpar.isDeleted(old.pos() - 1)
975                     && !oldpar.isDeleted(old.pos())) {
976                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
977 // FIXME: This will not work anymore when we have multiple views of the same buffer
978 // In this case, we will have to correct also the cursors held by
979 // other bufferviews. It will probably be easier to do that in a more
980 // automated way in CursorSlice code. (JMarc 26/09/2001)
981                         // correct all cursor parts
982                         if (same_par) {
983                                 fixCursorAfterDelete(cur.top(), old.top());
984                                 need_anchor_change = true;
985                         }
986                         return true;
987                 }
988         }
989
990         // only do our magic if we changed paragraph
991         if (same_par)
992                 return false;
993
994         // don't delete anything if this is the ONLY paragraph!
995         if (old.lastpit() == 0)
996                 return false;
997
998         // Do not delete empty paragraphs with keepempty set.
999         if (oldpar.allowEmpty())
1000                 return false;
1001
1002         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1003                 // Delete old par.
1004                 old.recordUndo(ATOMIC_UNDO,
1005                            max(old.pit() - 1, pit_type(0)),
1006                            min(old.pit() + 1, old.lastpit()));
1007                 ParagraphList & plist = old.text()->paragraphs();
1008                 bool const soa = oldpar.params().startOfAppendix();
1009                 plist.erase(boost::next(plist.begin(), old.pit()));
1010                 // do not lose start of appendix marker (bug 4212)
1011                 if (soa && old.pit() < pit_type(plist.size()))
1012                         plist[old.pit()].params().startOfAppendix(true);
1013
1014                 // see #warning (FIXME?) above 
1015                 if (cur.depth() >= old.depth()) {
1016                         CursorSlice & curslice = cur[old.depth() - 1];
1017                         if (&curslice.inset() == &old.inset()
1018                             && curslice.pit() > old.pit()) {
1019                                 --curslice.pit();
1020                                 // since a paragraph has been deleted, all the
1021                                 // insets after `old' have been copied and
1022                                 // their address has changed. Therefore we
1023                                 // need to `regenerate' cur. (JMarc)
1024                                 cur.updateInsets(&(cur.bottom().inset()));
1025                                 need_anchor_change = true;
1026                         }
1027                 }
1028                 return true;
1029         }
1030
1031         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1032                 need_anchor_change = true;
1033                 // We return true here because the Paragraph contents changed and
1034                 // we need a redraw before further action is processed.
1035                 return true;
1036         }
1037
1038         return false;
1039 }
1040
1041
1042 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1043 {
1044         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1045
1046         for (pit_type pit = first; pit <= last; ++pit) {
1047                 Paragraph & par = pars_[pit];
1048
1049                 // We allow all kinds of "mumbo-jumbo" when freespacing.
1050                 if (par.isFreeSpacing())
1051                         continue;
1052
1053                 for (pos_type pos = 1; pos < par.size(); ++pos) {
1054                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1055                             && !par.isDeleted(pos - 1)) {
1056                                 if (par.eraseChar(pos - 1, trackChanges)) {
1057                                         --pos;
1058                                 }
1059                         }
1060                 }
1061
1062                 // don't delete anything if this is the only remaining paragraph within the given range
1063                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1064                 if (first == last)
1065                         continue;
1066
1067                 // don't delete empty paragraphs with keepempty set
1068                 if (par.allowEmpty())
1069                         continue;
1070
1071                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1072                         pars_.erase(boost::next(pars_.begin(), pit));
1073                         --pit;
1074                         --last;
1075                         continue;
1076                 }
1077
1078                 par.stripLeadingSpaces(trackChanges);
1079         }
1080 }
1081
1082
1083 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1084 {
1085         cur.recordUndo(ATOMIC_UNDO, first, last);
1086 }
1087
1088
1089 void Text::recUndo(Cursor & cur, pit_type par) const
1090 {
1091         cur.recordUndo(ATOMIC_UNDO, par, par);
1092 }
1093
1094 } // namespace lyx