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