]> git.lyx.org Git - lyx.git/blob - src/text2.C
Fix crash when scrolling up using the arrow keys.
[lyx.git] / src / text2.C
1 /**
2  * \file text2.C
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 Dekel Tsur
15  * \author Jürgen Vigna
16  *
17  * Full author contact details are available in file CREDITS.
18  */
19
20 #include <config.h>
21
22 #include "lyxtext.h"
23
24 #include "buffer.h"
25 #include "buffer_funcs.h"
26 #include "bufferlist.h"
27 #include "bufferparams.h"
28 #include "BufferView.h"
29 #include "bufferview_funcs.h"
30 #include "Bullet.h"
31 #include "coordcache.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 "LColor.h"
41 #include "lyxfunc.h"
42 #include "lyxrc.h"
43 #include "lyxrow.h"
44 #include "paragraph.h"
45 #include "TextMetrics.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "pariterator.h"
49 #include "lyxserver.h"
50 #include "lyxsocket.h"
51 #include "undo.h"
52 #include "vspace.h"
53
54 #include "frontends/FontMetrics.h"
55
56 #include "insets/insetenv.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
75
76 LyXText::LyXText()
77         : current_font(LyXFont::ALL_INHERIT),
78           background_color_(LColor::background),
79           autoBreakRows_(false)
80 {}
81
82
83 bool LyXText::isMainText(Buffer const & buffer) const
84 {
85         return &buffer.text() == this;
86 }
87
88
89 //takes screen x,y coordinates
90 InsetBase * LyXText::checkInsetHit(BufferView & bv, int x, int y)
91 {
92         pit_type pit = getPitNearY(bv, y);
93         BOOST_ASSERT(pit != -1);
94
95         Paragraph const & par = pars_[pit];
96
97         lyxerr[Debug::DEBUG]
98                 << BOOST_CURRENT_FUNCTION
99                 << ": x: " << x
100                 << " y: " << y
101                 << "  pit: " << pit
102                 << endl;
103         InsetList::const_iterator iit = par.insetlist.begin();
104         InsetList::const_iterator iend = par.insetlist.end();
105         for (; iit != iend; ++iit) {
106                 InsetBase * inset = iit->inset;
107 #if 1
108                 lyxerr[Debug::DEBUG]
109                         << BOOST_CURRENT_FUNCTION
110                         << ": examining inset " << inset << endl;
111
112                 if (bv.coordCache().getInsets().has(inset))
113                         lyxerr[Debug::DEBUG]
114                                 << BOOST_CURRENT_FUNCTION
115                                 << ": xo: " << inset->xo(bv) << "..."
116                                 << inset->xo(bv) + inset->width()
117                                 << " yo: " << inset->yo(bv) - inset->ascent()
118                                 << "..."
119                                 << inset->yo(bv) + inset->descent()
120                                 << endl;
121                 else
122                         lyxerr[Debug::DEBUG]
123                                 << BOOST_CURRENT_FUNCTION
124                                 << ": inset has no cached position" << endl;
125 #endif
126                 if (inset->covers(bv, x, y)) {
127                         lyxerr[Debug::DEBUG]
128                                 << BOOST_CURRENT_FUNCTION
129                                 << ": Hit inset: " << inset << endl;
130                         return inset;
131                 }
132         }
133         lyxerr[Debug::DEBUG]
134                 << BOOST_CURRENT_FUNCTION
135                 << ": No inset hit. " << endl;
136         return 0;
137 }
138
139
140
141 // Gets the fully instantiated font at a given position in a paragraph
142 // Basically the same routine as Paragraph::getFont() in paragraph.C.
143 // The difference is that this one is used for displaying, and thus we
144 // are allowed to make cosmetic improvements. For instance make footnotes
145 // smaller. (Asger)
146 LyXFont LyXText::getFont(Buffer const & buffer, Paragraph const & par,
147                 pos_type const pos) const
148 {
149         BOOST_ASSERT(pos >= 0);
150
151         LyXLayout_ptr const & layout = par.layout();
152 #ifdef WITH_WARNINGS
153 #warning broken?
154 #endif
155         BufferParams const & params = buffer.params();
156         pos_type const body_pos = par.beginOfBody();
157
158         // We specialize the 95% common case:
159         if (!par.getDepth()) {
160                 LyXFont f = par.getFontSettings(params, pos);
161                 if (!isMainText(buffer))
162                         applyOuterFont(buffer, f);
163                 LyXFont lf;
164                 LyXFont rlf;
165                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
166                         lf = layout->labelfont;
167                         rlf = layout->reslabelfont;
168                 } else {
169                         lf = layout->font;
170                         rlf = layout->resfont;
171                 }
172                 // In case the default family has been customized
173                 if (lf.family() == LyXFont::INHERIT_FAMILY)
174                         rlf.setFamily(params.getFont().family());
175                 return f.realize(rlf);
176         }
177
178         // The uncommon case need not be optimized as much
179         LyXFont layoutfont;
180         if (pos < body_pos)
181                 layoutfont = layout->labelfont;
182         else
183                 layoutfont = layout->font;
184
185         LyXFont font = par.getFontSettings(params, pos);
186         font.realize(layoutfont);
187
188         if (!isMainText(buffer))
189                 applyOuterFont(buffer, font);
190
191         // Find the pit value belonging to paragraph. This will not break
192         // even if pars_ would not be a vector anymore.
193         // Performance appears acceptable.
194
195         pit_type pit = pars_.size();
196         for (pit_type it = 0; it < pit; ++it)
197                 if (&pars_[it] == &par) {
198                         pit = it;
199                         break;
200                 }
201         // Realize against environment font information
202         // NOTE: the cast to pit_type should be removed when pit_type
203         // changes to a unsigned integer.
204         if (pit < pit_type(pars_.size()))
205                 font.realize(outerFont(pit, pars_));
206
207         // Realize with the fonts of lesser depth.
208         font.realize(params.getFont());
209
210         return font;
211 }
212
213 // There are currently two font mechanisms in LyX:
214 // 1. The font attributes in a lyxtext, and
215 // 2. The inset-specific font properties, defined in an inset's
216 // metrics() and draw() methods and handed down the inset chain through
217 // the pi/mi parameters, and stored locally in a lyxtext in font_.
218 // This is where the two are integrated in the final fully realized
219 // font.
220 void LyXText::applyOuterFont(Buffer const & buffer, LyXFont & font) const {
221         LyXFont lf(font_);
222         lf.reduce(buffer.params().getFont());
223         lf.realize(font);
224         lf.setLanguage(font.language());
225         font = lf;
226 }
227
228
229 LyXFont LyXText::getLayoutFont(Buffer const & buffer, pit_type const pit) const
230 {
231         LyXLayout_ptr const & layout = pars_[pit].layout();
232
233         if (!pars_[pit].getDepth())  {
234                 LyXFont lf = layout->resfont;
235                 // In case the default family has been customized
236                 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
237                         lf.setFamily(buffer.params().getFont().family());
238                 return lf;
239         }
240
241         LyXFont font = layout->font;
242         // Realize with the fonts of lesser depth.
243         //font.realize(outerFont(pit, paragraphs()));
244         font.realize(buffer.params().getFont());
245
246         return font;
247 }
248
249
250 LyXFont LyXText::getLabelFont(Buffer const & buffer, Paragraph const & par) const
251 {
252         LyXLayout_ptr const & layout = par.layout();
253
254         if (!par.getDepth()) {
255                 LyXFont lf = layout->reslabelfont;
256                 // In case the default family has been customized
257                 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
258                         lf.setFamily(buffer.params().getFont().family());
259                 return lf;
260         }
261
262         LyXFont font = layout->labelfont;
263         // Realize with the fonts of lesser depth.
264         font.realize(buffer.params().getFont());
265
266         return font;
267 }
268
269
270 void LyXText::setCharFont(Buffer const & buffer, pit_type pit,
271                 pos_type pos, LyXFont const & fnt)
272 {
273         LyXFont font = fnt;
274         LyXLayout_ptr const & layout = pars_[pit].layout();
275
276         // Get concrete layout font to reduce against
277         LyXFont layoutfont;
278
279         if (pos < pars_[pit].beginOfBody())
280                 layoutfont = layout->labelfont;
281         else
282                 layoutfont = layout->font;
283
284         // Realize against environment font information
285         if (pars_[pit].getDepth()) {
286                 pit_type tp = pit;
287                 while (!layoutfont.resolved() &&
288                        tp != pit_type(paragraphs().size()) &&
289                        pars_[tp].getDepth()) {
290                         tp = outerHook(tp, paragraphs());
291                         if (tp != pit_type(paragraphs().size()))
292                                 layoutfont.realize(pars_[tp].layout()->font);
293                 }
294         }
295
296         // Inside inset, apply the inset's font attributes if any
297         // (charstyle!)
298         if (!isMainText(buffer))
299                 layoutfont.realize(font_);
300
301         layoutfont.realize(buffer.params().getFont());
302
303         // Now, reduce font against full layout font
304         font.reduce(layoutfont);
305
306         pars_[pit].setFont(pos, font);
307 }
308
309
310 // return past-the-last paragraph influenced by a layout change on pit
311 pit_type LyXText::undoSpan(pit_type pit)
312 {
313         pit_type end = paragraphs().size();
314         pit_type nextpit = pit + 1;
315         if (nextpit == end)
316                 return nextpit;
317         //because of parindents
318         if (!pars_[pit].getDepth())
319                 return boost::next(nextpit);
320         //because of depth constrains
321         for (; nextpit != end; ++pit, ++nextpit) {
322                 if (!pars_[pit].getDepth())
323                         break;
324         }
325         return nextpit;
326 }
327
328
329 void LyXText::setLayout(Buffer const & buffer, pit_type start, pit_type end,
330                 string const & layout)
331 {
332         BOOST_ASSERT(start != end);
333
334         BufferParams const & bufparams = buffer.params();
335         LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
336
337         for (pit_type pit = start; pit != end; ++pit) {
338                 pars_[pit].applyLayout(lyxlayout);
339                 if (lyxlayout->margintype == MARGIN_MANUAL)
340                         pars_[pit].setLabelWidthString(buffer.translateLabel(lyxlayout->labelstring()));
341         }
342 }
343
344
345 // set layout over selection and make a total rebreak of those paragraphs
346 void LyXText::setLayout(LCursor & cur, string const & layout)
347 {
348         BOOST_ASSERT(this == cur.text());
349         // special handling of new environment insets
350         BufferView & bv = cur.bv();
351         BufferParams const & params = bv.buffer()->params();
352         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
353         if (lyxlayout->is_environment) {
354                 // move everything in a new environment inset
355                 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
356                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
357                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
358                 lyx::dispatch(FuncRequest(LFUN_CUT));
359                 InsetBase * inset = new InsetEnvironment(params, layout);
360                 insertInset(cur, inset);
361                 //inset->edit(cur, true);
362                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
363                 return;
364         }
365
366         pit_type start = cur.selBegin().pit();
367         pit_type end = cur.selEnd().pit() + 1;
368         pit_type undopit = undoSpan(end - 1);
369         recUndo(cur, start, undopit - 1);
370         setLayout(cur.buffer(), start, end, layout);
371         updateLabels(cur.buffer());
372 }
373
374
375 static bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
376                         Paragraph const & par, int max_depth)
377 {
378         if (par.layout()->labeltype == LABEL_BIBLIO)
379                 return false;
380         int const depth = par.params().depth();
381         if (type == LyXText::INC_DEPTH && depth < max_depth)
382                 return true;
383         if (type == LyXText::DEC_DEPTH && depth > 0)
384                 return true;
385         return false;
386 }
387
388
389 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
390 {
391         BOOST_ASSERT(this == cur.text());
392         // this happens when selecting several cells in tabular (bug 2630)
393         if (cur.selBegin().idx() != cur.selEnd().idx())
394                 return false;
395
396         pit_type const beg = cur.selBegin().pit();
397         pit_type const end = cur.selEnd().pit() + 1;
398         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
399
400         for (pit_type pit = beg; pit != end; ++pit) {
401                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
402                         return true;
403                 max_depth = pars_[pit].getMaxDepthAfter();
404         }
405         return false;
406 }
407
408
409 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
410 {
411         BOOST_ASSERT(this == cur.text());
412         pit_type const beg = cur.selBegin().pit();
413         pit_type const end = cur.selEnd().pit() + 1;
414         recordUndoSelection(cur);
415         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
416
417         for (pit_type pit = beg; pit != end; ++pit) {
418                 Paragraph & par = pars_[pit];
419                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
420                         int const depth = par.params().depth();
421                         if (type == INC_DEPTH)
422                                 par.params().depth(depth + 1);
423                         else
424                                 par.params().depth(depth - 1);
425                 }
426                 max_depth = par.getMaxDepthAfter();
427         }
428         // this handles the counter labels, and also fixes up
429         // depth values for follow-on (child) paragraphs
430         updateLabels(cur.buffer());
431 }
432
433
434 // set font over selection
435 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
436 {
437         BOOST_ASSERT(this == cur.text());
438         // if there is no selection just set the current_font
439         if (!cur.selection()) {
440                 // Determine basis font
441                 LyXFont layoutfont;
442                 pit_type pit = cur.pit();
443                 if (cur.pos() < pars_[pit].beginOfBody())
444                         layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
445                 else
446                         layoutfont = getLayoutFont(cur.buffer(), pit);
447
448                 // Update current font
449                 real_current_font.update(font,
450                                          cur.buffer().params().language,
451                                          toggleall);
452
453                 // Reduce to implicit settings
454                 current_font = real_current_font;
455                 current_font.reduce(layoutfont);
456                 // And resolve it completely
457                 real_current_font.realize(layoutfont);
458
459                 return;
460         }
461
462         // Ok, we have a selection.
463         recordUndoSelection(cur);
464
465         DocIterator dit = cur.selectionBegin();
466         DocIterator ditend = cur.selectionEnd();
467
468         BufferParams const & params = cur.buffer().params();
469
470         // Don't use forwardChar here as ditend might have
471         // pos() == lastpos() and forwardChar would miss it.
472         // Can't use forwardPos either as this descends into
473         // nested insets.
474         for (; dit != ditend; dit.forwardPosNoDescend()) {
475                 if (dit.pos() != dit.lastpos()) {
476                         LyXFont f = getFont(cur.buffer(), dit.paragraph(), dit.pos());
477                         f.update(font, params.language, toggleall);
478                         setCharFont(cur.buffer(), dit.pit(), dit.pos(), f);
479                 }
480         }
481 }
482
483
484 // the cursor set functions have a special mechanism. When they
485 // realize you left an empty paragraph, they will delete it.
486
487 bool LyXText::cursorHome(LCursor & cur)
488 {
489         BOOST_ASSERT(this == cur.text());
490         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
491         Row const & row = pm.getRow(cur.pos(),cur.boundary());
492         return setCursor(cur, cur.pit(), row.pos());
493 }
494
495
496 bool LyXText::cursorEnd(LCursor & cur)
497 {
498         BOOST_ASSERT(this == cur.text());
499         // if not on the last row of the par, put the cursor before
500         // the final space exept if I have a spanning inset or one string
501         // is so long that we force a break.
502         pos_type end = cur.textRow().endpos();
503         if (end == 0)
504                 // empty text, end-1 is no valid position
505                 return false;
506         bool boundary = false;
507         if (end != cur.lastpos()) {
508                 if (!cur.paragraph().isLineSeparator(end-1)
509                     && !cur.paragraph().isNewline(end-1))
510                         boundary = true;
511                 else
512                         --end;
513         }
514         return setCursor(cur, cur.pit(), end, true, boundary);
515 }
516
517
518 bool LyXText::cursorTop(LCursor & cur)
519 {
520         BOOST_ASSERT(this == cur.text());
521         return setCursor(cur, 0, 0);
522 }
523
524
525 bool LyXText::cursorBottom(LCursor & cur)
526 {
527         BOOST_ASSERT(this == cur.text());
528         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
529 }
530
531
532 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
533 {
534         BOOST_ASSERT(this == cur.text());
535         // If the mask is completely neutral, tell user
536         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
537                 // Could only happen with user style
538                 cur.message(_("No font change defined. "
539                                            "Use Character under the Layout menu to define font change."));
540                 return;
541         }
542
543         // Try implicit word selection
544         // If there is a change in the language the implicit word selection
545         // is disabled.
546         CursorSlice resetCursor = cur.top();
547         bool implicitSelection =
548                 font.language() == ignore_language
549                 && font.number() == LyXFont::IGNORE
550                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
551
552         // Set font
553         setFont(cur, font, toggleall);
554
555         // Implicit selections are cleared afterwards
556         // and cursor is set to the original position.
557         if (implicitSelection) {
558                 cur.clearSelection();
559                 cur.top() = resetCursor;
560                 cur.resetAnchor();
561         }
562 }
563
564
565 docstring LyXText::getStringToIndex(LCursor const & cur)
566 {
567         BOOST_ASSERT(this == cur.text());
568
569         docstring idxstring;
570         if (cur.selection())
571                 idxstring = cur.selectionAsString(false);
572         else {
573                 // Try implicit word selection. If there is a change
574                 // in the language the implicit word selection is
575                 // disabled.
576                 LCursor tmpcur = cur;
577                 selectWord(tmpcur, PREVIOUS_WORD);
578
579                 if (!tmpcur.selection())
580                         cur.message(_("Nothing to index!"));
581                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
582                         cur.message(_("Cannot index more than one paragraph!"));
583                 else
584                         idxstring = tmpcur.selectionAsString(false);
585         }
586
587         return idxstring;
588 }
589
590
591 void LyXText::setParagraph(LCursor & cur,
592                            Spacing const & spacing, LyXAlignment align,
593                            docstring const & labelwidthstring, bool noindent)
594 {
595         BOOST_ASSERT(cur.text());
596         // make sure that the depth behind the selection are restored, too
597         pit_type undopit = undoSpan(cur.selEnd().pit());
598         recUndo(cur, cur.selBegin().pit(), undopit - 1);
599
600         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
601              pit <= end; ++pit) {
602                 Paragraph & par = pars_[pit];
603                 ParagraphParameters & params = par.params();
604                 params.spacing(spacing);
605
606                 // does the layout allow the new alignment?
607                 LyXLayout_ptr const & layout = par.layout();
608
609                 if (align == LYX_ALIGN_LAYOUT)
610                         align = layout->align;
611                 if (align & layout->alignpossible) {
612                         if (align == layout->align)
613                                 params.align(LYX_ALIGN_LAYOUT);
614                         else
615                                 params.align(align);
616                 }
617                 par.setLabelWidthString(labelwidthstring);
618                 params.noindent(noindent);
619         }
620 }
621
622
623 // this really should just insert the inset and not move the cursor.
624 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
625 {
626         BOOST_ASSERT(this == cur.text());
627         BOOST_ASSERT(inset);
628         cur.paragraph().insertInset(cur.pos(), inset, 
629                                     Change(cur.buffer().params().trackChanges ?
630                                            Change::INSERTED : Change::UNCHANGED));
631 }
632
633
634 // needed to insert the selection
635 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
636 {
637         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
638                                          current_font, str, autoBreakRows_);
639 }
640
641
642 // turn double CR to single CR, others are converted into one
643 // blank. Then insertStringAsLines is called
644 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
645 {
646         docstring linestr = str;
647         bool newline_inserted = false;
648
649         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
650                 if (linestr[i] == '\n') {
651                         if (newline_inserted) {
652                                 // we know that \r will be ignored by
653                                 // insertStringAsLines. Of course, it is a dirty
654                                 // trick, but it works...
655                                 linestr[i - 1] = '\r';
656                                 linestr[i] = '\n';
657                         } else {
658                                 linestr[i] = ' ';
659                                 newline_inserted = true;
660                         }
661                 } else if (isPrintable(linestr[i])) {
662                         newline_inserted = false;
663                 }
664         }
665         insertStringAsLines(cur, linestr);
666 }
667
668
669 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
670                         bool setfont, bool boundary)
671 {
672         LCursor old = cur;
673         setCursorIntern(cur, par, pos, setfont, boundary);
674         return cur.bv().checkDepm(cur, old);
675 }
676
677
678 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
679 {
680         BOOST_ASSERT(par != int(paragraphs().size()));
681         cur.pit() = par;
682         cur.pos() = pos;
683
684         // now some strict checking
685         Paragraph & para = getPar(par);
686
687         // None of these should happen, but we're scaredy-cats
688         if (pos < 0) {
689                 lyxerr << "dont like -1" << endl;
690                 BOOST_ASSERT(false);
691         }
692
693         if (pos > para.size()) {
694                 lyxerr << "dont like 1, pos: " << pos
695                        << " size: " << para.size()
696                        << " par: " << par << endl;
697                 BOOST_ASSERT(false);
698         }
699 }
700
701
702 void LyXText::setCursorIntern(LCursor & cur,
703                               pit_type par, pos_type pos, bool setfont, bool boundary)
704 {
705         BOOST_ASSERT(this == cur.text());
706         cur.boundary(boundary);
707         setCursor(cur.top(), par, pos);
708         if (setfont)
709                 setCurrentFont(cur);
710 }
711
712
713 void LyXText::setCurrentFont(LCursor & cur)
714 {
715         BOOST_ASSERT(this == cur.text());
716         pos_type pos = cur.pos();
717         Paragraph & par = cur.paragraph();
718
719         if (cur.boundary() && pos > 0)
720                 --pos;
721
722         if (pos > 0) {
723                 if (pos == cur.lastpos())
724                         --pos;
725                 else // potentional bug... BUG (Lgb)
726                         if (par.isSeparator(pos)) {
727                                 if (pos > cur.textRow().pos() &&
728                                     bidi.level(pos) % 2 ==
729                                     bidi.level(pos - 1) % 2)
730                                         --pos;
731                                 else if (pos + 1 < cur.lastpos())
732                                         ++pos;
733                         }
734         }
735
736         BufferParams const & bufparams = cur.buffer().params();
737         current_font = par.getFontSettings(bufparams, pos);
738         real_current_font = getFont(cur.buffer(), par, pos);
739
740         if (cur.pos() == cur.lastpos()
741             && bidi.isBoundary(cur.buffer(), par, cur.pos())
742             && !cur.boundary()) {
743                 Language const * lang = par.getParLanguage(bufparams);
744                 current_font.setLanguage(lang);
745                 current_font.setNumber(LyXFont::OFF);
746                 real_current_font.setLanguage(lang);
747                 real_current_font.setNumber(LyXFont::OFF);
748         }
749 }
750
751
752 // x is an absolute screen coord
753 // returns the column near the specified x-coordinate of the row
754 // x is set to the real beginning of this column
755 pos_type LyXText::getColumnNearX(BufferView const & bv, int right_margin,
756                 pit_type const pit, Row const & row, int & x, bool & boundary) const
757 {
758         Buffer const & buffer = *bv.buffer();
759         TextMetrics const & tm = bv.textMetrics(this);
760
761         /// For the main LyXText, it is possible that this pit is not
762         /// yet in the CoordCache when moving cursor up.
763         /// x Paragraph coordinate is always 0 for main text anyway.
764         int const xo = isMainText(*bv.buffer())?
765                 0 : bv.coordCache().get(this, pit).x_;
766         x -= xo;
767         RowMetrics const r = tm.computeRowMetrics(pit, row);
768         Paragraph const & par = pars_[pit];
769
770         pos_type vc = row.pos();
771         pos_type end = row.endpos();
772         pos_type c = 0;
773         LyXLayout_ptr const & layout = par.layout();
774
775         bool left_side = false;
776
777         pos_type body_pos = par.beginOfBody();
778
779         double tmpx = r.x;
780         double last_tmpx = tmpx;
781
782         if (body_pos > 0 &&
783             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
784                 body_pos = 0;
785
786         // check for empty row
787         if (vc == end) {
788                 x = int(tmpx) + xo;
789                 return 0;
790         }
791
792         frontend::FontMetrics const & fm 
793                 = theFontMetrics(getLabelFont(buffer, par));
794
795         while (vc < end && tmpx <= x) {
796                 c = bidi.vis2log(vc);
797                 last_tmpx = tmpx;
798                 if (body_pos > 0 && c == body_pos - 1) {
799                         // FIXME UNICODE
800                         docstring const lsep = from_utf8(layout->labelsep);
801                         tmpx += r.label_hfill + fm.width(lsep);
802                         if (par.isLineSeparator(body_pos - 1))
803                                 tmpx -= singleWidth(buffer, par, body_pos - 1);
804                 }
805
806                 if (par.hfillExpansion(row, c)) {
807                         tmpx += singleWidth(buffer, par, c);
808                         if (c >= body_pos)
809                                 tmpx += r.hfill;
810                         else
811                                 tmpx += r.label_hfill;
812                 } else if (par.isSeparator(c)) {
813                         tmpx += singleWidth(buffer, par, c);
814                         if (c >= body_pos)
815                                 tmpx += r.separator;
816                 } else {
817                         tmpx += singleWidth(buffer, par, c);
818                 }
819                 ++vc;
820         }
821
822         if ((tmpx + last_tmpx) / 2 > x) {
823                 tmpx = last_tmpx;
824                 left_side = true;
825         }
826
827         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
828
829         boundary = false;
830         // This (rtl_support test) is not needed, but gives
831         // some speedup if rtl_support == false
832         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
833
834         // If lastrow is false, we don't need to compute
835         // the value of rtl.
836         bool const rtl = lastrow ? isRTL(buffer, par) : false;
837         if (lastrow &&
838             ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
839              (!rtl && !left_side && vc == end  && x > tmpx + 5)))
840                 c = end;
841         else if (vc == row.pos()) {
842                 c = bidi.vis2log(vc);
843                 if (bidi.level(c) % 2 == 1)
844                         ++c;
845         } else {
846                 c = bidi.vis2log(vc - 1);
847                 bool const rtl = (bidi.level(c) % 2 == 1);
848                 if (left_side == rtl) {
849                         ++c;
850                         boundary = bidi.isBoundary(buffer, par, c);
851                 }
852         }
853
854 // I believe this code is not needed anymore (Jug 20050717)
855 #if 0
856         // The following code is necessary because the cursor position past
857         // the last char in a row is logically equivalent to that before
858         // the first char in the next row. That's why insets causing row
859         // divisions -- Newline and display-style insets -- must be treated
860         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
861         // Newline inset, air gap below:
862         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
863                 if (bidi.level(end -1) % 2 == 0)
864                         tmpx -= singleWidth(buffer, par, end - 1);
865                 else
866                         tmpx += singleWidth(buffer, par, end - 1);
867                 c = end - 1;
868         }
869
870         // Air gap above display inset:
871         if (row.pos() < end && c >= end && end < par.size()
872             && par.isInset(end) && par.getInset(end)->display()) {
873                 c = end - 1;
874         }
875         // Air gap below display inset:
876         if (row.pos() < end && c >= end && par.isInset(end - 1)
877             && par.getInset(end - 1)->display()) {
878                 c = end - 1;
879         }
880 #endif
881
882         x = int(tmpx) + xo;
883         pos_type const col = c - row.pos();
884
885         if (!c || end == par.size())
886                 return col;
887
888         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
889                 boundary = true;
890                 return col;
891         }
892
893         return min(col, end - 1 - row.pos());
894 }
895
896
897 // y is screen coordinate
898 pit_type LyXText::getPitNearY(BufferView & bv, int y)
899 {
900         BOOST_ASSERT(!paragraphs().empty());
901         BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
902         CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
903         lyxerr[Debug::DEBUG]
904                 << BOOST_CURRENT_FUNCTION
905                 << ": y: " << y << " cache size: " << cc.size()
906                 << endl;
907
908         // look for highest numbered paragraph with y coordinate less than given y
909         pit_type pit = 0;
910         int yy = -1;
911         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
912         CoordCache::InnerParPosCache::const_iterator et = cc.end();
913         CoordCache::InnerParPosCache::const_iterator last = et; last--;
914
915         TextMetrics & tm = bv.textMetrics(this);
916         ParagraphMetrics const & pm = tm.parMetrics(it->first);
917
918         // If we are off-screen (before the visible part)
919         if (y < 0
920                 // and even before the first paragraph in the cache.
921                 && y < it->second.y_ - int(pm.ascent())) {
922                 //  and we are not at the first paragraph in the inset.
923                 if (it->first == 0)
924                         return 0;
925                 // then this is the paragraph we are looking for.
926                 pit = it->first - 1;
927                 // rebreak it and update the CoordCache.
928                 tm.redoParagraph(pit);
929                 bv.coordCache().parPos()[this][pit] =
930                         Point(0, it->second.y_ - pm.descent());
931                 return pit;
932         }
933
934         ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
935
936         // If we are off-screen (after the visible part)
937         if (y > bv.workHeight()
938                 // and even after the first paragraph in the cache.
939                 && y >= last->second.y_ + int(pm_last.descent())) {
940                 pit = last->first + 1;
941                 //  and we are not at the last paragraph in the inset.
942                 if (pit == int(pars_.size()))
943                         return last->first;
944                 // then this is the paragraph we are looking for.
945                 // rebreak it and update the CoordCache.
946                 tm.redoParagraph(pit);
947                 bv.coordCache().parPos()[this][pit] =
948                         Point(0, last->second.y_ + pm_last.ascent());
949                 return pit;
950         }
951
952         for (; it != et; ++it) {
953                 lyxerr[Debug::DEBUG]
954                         << BOOST_CURRENT_FUNCTION
955                         << "  examining: pit: " << it->first
956                         << " y: " << it->second.y_
957                         << endl;
958
959                 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
960
961                 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
962                         pit = it->first;
963                         yy = it->second.y_;
964                 }
965         }
966
967         lyxerr[Debug::DEBUG]
968                 << BOOST_CURRENT_FUNCTION
969                 << ": found best y: " << yy << " for pit: " << pit
970                 << endl;
971
972         return pit;
973 }
974
975
976 Row const & LyXText::getRowNearY(BufferView const & bv, int y, pit_type pit) const
977 {
978         ParagraphMetrics const & pm = bv.parMetrics(this, pit);
979
980         int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
981         BOOST_ASSERT(!pm.rows().empty());
982         RowList::const_iterator rit = pm.rows().begin();
983         RowList::const_iterator const rlast = boost::prior(pm.rows().end());
984         for (; rit != rlast; yy += rit->height(), ++rit)
985                 if (yy + rit->height() > y)
986                         break;
987         return *rit;
988 }
989
990
991 // x,y are absolute screen coordinates
992 // sets cursor recursively descending into nested editable insets
993 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
994 {
995         if (lyxerr.debugging(Debug::WORKAREA)) {
996                 lyxerr << "LyXText::editXY(cur, " << x << ", " << y << ")" << std::endl;
997                 cur.bv().coordCache().dump();
998         }
999         pit_type pit = getPitNearY(cur.bv(), y);
1000         BOOST_ASSERT(pit != -1);
1001
1002         Row const & row = getRowNearY(cur.bv(), y, pit);
1003         bool bound = false;
1004
1005         TextMetrics const & tm = cur.bv().textMetrics(this);
1006         ParagraphMetrics const & pm = tm.parMetrics(pit);
1007         int right_margin = tm.rightMargin(pm);
1008         int xx = x; // is modified by getColumnNearX
1009         pos_type const pos = row.pos()
1010                 + getColumnNearX(cur.bv(), right_margin, pit, row, xx, bound);
1011         cur.pit() = pit;
1012         cur.pos() = pos;
1013         cur.boundary(bound);
1014         cur.x_target() = x;
1015
1016         // try to descend into nested insets
1017         InsetBase * inset = checkInsetHit(cur.bv(), x, y);
1018         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1019         if (!inset) {
1020                 // Either we deconst editXY or better we move current_font
1021                 // and real_current_font to LCursor
1022                 setCurrentFont(cur);
1023                 return 0;
1024         }
1025
1026         InsetBase * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
1027         //InsetBase * insetBehind = pars_[pit].getInset(pos);
1028
1029         // This should be just before or just behind the
1030         // cursor position set above.
1031         BOOST_ASSERT((pos != 0 && inset == insetBefore)
1032                 || inset == pars_[pit].getInset(pos));
1033
1034         // Make sure the cursor points to the position before
1035         // this inset.
1036         if (inset == insetBefore)
1037                 --cur.pos();
1038
1039         // Try to descend recursively inside the inset.
1040         inset = inset->editXY(cur, x, y);
1041
1042         if (cur.top().text() == this)
1043                 setCurrentFont(cur);
1044         return inset;
1045 }
1046
1047
1048 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1049 {
1050         if (cur.selection())
1051                 return false;
1052         if (cur.pos() == cur.lastpos())
1053                 return false;
1054         InsetBase * inset = cur.nextInset();
1055         if (!isHighlyEditableInset(inset))
1056                 return false;
1057         inset->edit(cur, front);
1058         return true;
1059 }
1060
1061
1062 bool LyXText::cursorLeft(LCursor & cur)
1063 {
1064         // Tell BufferView to test for FitCursor in any case!
1065         cur.updateFlags(Update::FitCursor);
1066
1067         if (!cur.boundary() && cur.pos() > 0 &&
1068             cur.textRow().pos() == cur.pos() &&
1069             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1070             !cur.paragraph().isNewline(cur.pos()-1)) {
1071                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1072         }
1073         if (cur.pos() != 0) {
1074                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1075                 if (!checkAndActivateInset(cur, false)) {
1076                         /** FIXME: What's this cause purpose???
1077                         bool boundary = cur.boundary();
1078                         if (false && !boundary &&
1079                             bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1080                                 updateNeeded |=
1081                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1082                         */
1083                 }
1084                 return updateNeeded;
1085         }
1086
1087         if (cur.pit() != 0) {
1088                 // Steps into the paragraph above
1089                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1090         }
1091         return false;
1092 }
1093
1094
1095 bool LyXText::cursorRight(LCursor & cur)
1096 {
1097         // Tell BufferView to test for FitCursor in any case!
1098         cur.updateFlags(Update::FitCursor);
1099
1100         if (cur.pos() != cur.lastpos()) {
1101                 if (cur.boundary())
1102                         return setCursor(cur, cur.pit(), cur.pos(),
1103                                          true, false);
1104
1105                 bool updateNeeded = false;
1106                 if (!checkAndActivateInset(cur, true)) {
1107                         if (cur.textRow().endpos() == cur.pos() + 1 &&
1108                             cur.textRow().endpos() != cur.lastpos() &&
1109                             !cur.paragraph().isLineSeparator(cur.pos()) &&
1110                             !cur.paragraph().isNewline(cur.pos())) {
1111                                 cur.boundary(true);
1112                         }
1113                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1114                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1115                                                      cur.pos()))
1116                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1117                 }
1118                 return updateNeeded;
1119         }
1120
1121         if (cur.pit() != cur.lastpit())
1122                 return setCursor(cur, cur.pit() + 1, 0);
1123         return false;
1124 }
1125
1126
1127 bool LyXText::cursorUp(LCursor & cur)
1128 {
1129         // Tell BufferView to test for FitCursor in any case!
1130         cur.updateFlags(Update::FitCursor);
1131
1132         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
1133
1134         int row;
1135         int const x = cur.targetX();
1136
1137         if (cur.pos() && cur.boundary())
1138                 row = pm.pos2row(cur.pos()-1);
1139         else
1140                 row = pm.pos2row(cur.pos());
1141
1142         if (!cur.selection()) {
1143                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1144                 LCursor old = cur;
1145                 // Go to middle of previous row. 16 found to work OK;
1146                 // 12 = top/bottom margin of display math
1147                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1148                 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1149                 cur.clearSelection();
1150
1151                 // This happens when you move out of an inset.
1152                 // And to give the DEPM the possibility of doing
1153                 // something we must provide it with two different
1154                 // cursors. (Lgb)
1155                 LCursor dummy = cur;
1156                 if (dummy == old)
1157                         ++dummy.pos();
1158
1159                 cur.bv().checkDepm(dummy, old);
1160         }
1161
1162         bool updateNeeded = false;
1163
1164         if (row > 0) {
1165                 updateNeeded |= setCursor(cur, cur.pit(),
1166                         x2pos(cur.bv(), cur.pit(), row - 1, x));
1167         } else if (cur.pit() > 0) {
1168                 --cur.pit();
1169                 //cannot use 'par' now
1170                 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1171                 updateNeeded |= setCursor(cur, cur.pit(),
1172                         x2pos(cur.bv(), cur.pit(), pmcur.rows().size() - 1, x));
1173         }
1174
1175         cur.x_target() = x;
1176
1177         return updateNeeded;
1178 }
1179
1180
1181 bool LyXText::cursorDown(LCursor & cur)
1182 {
1183         // Tell BufferView to test for FitCursor in any case!
1184         cur.updateFlags(Update::FitCursor);
1185
1186         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
1187
1188         int row;
1189         int const x = cur.targetX();
1190
1191         if (cur.pos() && cur.boundary())
1192                 row = pm.pos2row(cur.pos()-1);
1193         else
1194                 row = pm.pos2row(cur.pos());
1195
1196         if (!cur.selection()) {
1197                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1198                 LCursor old = cur;
1199                 // To middle of next row
1200                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1201                 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1202                 cur.clearSelection();
1203
1204                 // This happens when you move out of an inset.
1205                 // And to give the DEPM the possibility of doing
1206                 // something we must provide it with two different
1207                 // cursors. (Lgb)
1208                 LCursor dummy = cur;
1209                 if (dummy == old)
1210                         ++dummy.pos();
1211                 
1212                 bool const changed = cur.bv().checkDepm(dummy, old);
1213
1214                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1215                 if (changed)
1216                         cur = dummy;
1217
1218                 return false;
1219         }
1220
1221         bool updateNeeded = false;
1222
1223         if (row + 1 < int(pm.rows().size())) {
1224                 updateNeeded |= setCursor(cur, cur.pit(),
1225                         x2pos(cur.bv(), cur.pit(), row + 1, x));
1226         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1227                 ++cur.pit();
1228                 updateNeeded |= setCursor(cur, cur.pit(),
1229                         x2pos(cur.bv(), cur.pit(), 0, x));
1230         }
1231
1232         cur.x_target() = x;
1233
1234         return updateNeeded;
1235 }
1236
1237
1238 bool LyXText::cursorUpParagraph(LCursor & cur)
1239 {
1240         bool updated = false;
1241         if (cur.pos() > 0)
1242                 updated = setCursor(cur, cur.pit(), 0);
1243         else if (cur.pit() != 0)
1244                 updated = setCursor(cur, cur.pit() - 1, 0);
1245         return updated;
1246 }
1247
1248
1249 bool LyXText::cursorDownParagraph(LCursor & cur)
1250 {
1251         bool updated = false;
1252         if (cur.pit() != cur.lastpit())
1253                 updated = setCursor(cur, cur.pit() + 1, 0);
1254         else
1255                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1256         return updated;
1257 }
1258
1259
1260 // fix the cursor `cur' after a characters has been deleted at `where'
1261 // position. Called by deleteEmptyParagraphMechanism
1262 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1263 {
1264         // Do nothing if cursor is not in the paragraph where the
1265         // deletion occured,
1266         if (cur.pit() != where.pit())
1267                 return;
1268
1269         // If cursor position is after the deletion place update it
1270         if (cur.pos() > where.pos())
1271                 --cur.pos();
1272
1273         // Check also if we don't want to set the cursor on a spot behind the
1274         // pagragraph because we erased the last character.
1275         if (cur.pos() > cur.lastpos())
1276                 cur.pos() = cur.lastpos();
1277 }
1278
1279
1280 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur,
1281                 LCursor & old, bool & need_anchor_change)
1282 {
1283         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1284         // old should point to us
1285         BOOST_ASSERT(old.text() == this);
1286
1287         Paragraph & oldpar = old.paragraph();
1288
1289         // We allow all kinds of "mumbo-jumbo" when freespacing.
1290         if (oldpar.isFreeSpacing())
1291                 return false;
1292
1293         /* Ok I'll put some comments here about what is missing.
1294            There are still some small problems that can lead to
1295            double spaces stored in the document file or space at
1296            the beginning of paragraphs(). This happens if you have
1297            the cursor between to spaces and then save. Or if you
1298            cut and paste and the selection have a space at the
1299            beginning and then save right after the paste. (Lgb)
1300         */
1301
1302         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1303         // delete the LineSeparator.
1304         // MISSING
1305
1306         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1307         // delete the LineSeparator.
1308         // MISSING
1309
1310         bool const same_inset = &old.inset() == &cur.inset();
1311         bool const same_par = same_inset && old.pit() == cur.pit();
1312         bool const same_par_pos = same_par && old.pos() == cur.pos();
1313
1314         // If the chars around the old cursor were spaces, delete one of them.
1315         if (!same_par_pos) {
1316                 // Only if the cursor has really moved.
1317                 if (old.pos() > 0
1318                     && old.pos() < oldpar.size()
1319                     && oldpar.isLineSeparator(old.pos())
1320                     && oldpar.isLineSeparator(old.pos() - 1)
1321                     && !oldpar.isDeleted(old.pos() - 1)) {
1322                         oldpar.eraseChar(old.pos() - 1, false); // do not track changes in DEPM
1323 #ifdef WITH_WARNINGS
1324 #warning This will not work anymore when we have multiple views of the same buffer
1325 // In this case, we will have to correct also the cursors held by
1326 // other bufferviews. It will probably be easier to do that in a more
1327 // automated way in CursorSlice code. (JMarc 26/09/2001)
1328 #endif
1329                         // correct all cursor parts
1330                         if (same_par) {
1331                                 fixCursorAfterDelete(cur.top(), old.top());
1332                                 need_anchor_change = true;
1333                         }
1334                         return true;
1335                 }
1336         }
1337
1338         // only do our magic if we changed paragraph
1339         if (same_par)
1340                 return false;
1341
1342         // don't delete anything if this is the ONLY paragraph!
1343         if (old.lastpit() == 0)
1344                 return false;
1345
1346         // Do not delete empty paragraphs with keepempty set.
1347         if (oldpar.allowEmpty())
1348                 return false;
1349
1350         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1351                 // Delete old par.
1352                 recordUndo(old, Undo::ATOMIC,
1353                            max(old.pit() - 1, pit_type(0)),
1354                            min(old.pit() + 1, old.lastpit()));
1355                 ParagraphList & plist = old.text()->paragraphs();
1356                 plist.erase(boost::next(plist.begin(), old.pit()));
1357
1358                 // see #warning above
1359                 if (cur.depth() >= old.depth()) {
1360                         CursorSlice & curslice = cur[old.depth() - 1];
1361                         if (&curslice.inset() == &old.inset()
1362                             && curslice.pit() > old.pit()) {
1363                                 --curslice.pit();
1364                                 // since a paragraph has been deleted, all the
1365                                 // insets after `old' have been copied and
1366                                 // their address has changed. Therefore we
1367                                 // need to `regenerate' cur. (JMarc)
1368                                 cur.updateInsets(&(cur.bottom().inset()));
1369                                 need_anchor_change = true;
1370                         }
1371                 }
1372                 return true;
1373         }
1374
1375         if (oldpar.stripLeadingSpaces())
1376                 need_anchor_change = true;
1377
1378         return false;
1379 }
1380
1381
1382 void LyXText::recUndo(LCursor & cur, pit_type first, pit_type last) const
1383 {
1384         recordUndo(cur, Undo::ATOMIC, first, last);
1385 }
1386
1387
1388 void LyXText::recUndo(LCursor & cur, pit_type par) const
1389 {
1390         recordUndo(cur, Undo::ATOMIC, par, par);
1391 }
1392
1393 } // namespace lyx