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