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