]> git.lyx.org Git - lyx.git/blob - src/text2.C
5de76d10df533e98f8079780622d7c07a8b35565
[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         int const xo = bv.coordCache().get(this, pit).x_;
761         x -= xo;
762         int max_witdh = tm.maxWidth(); 
763         RowMetrics const r = tm.computeRowMetrics(pit, row);
764         Paragraph const & par = pars_[pit];
765
766         pos_type vc = row.pos();
767         pos_type end = row.endpos();
768         pos_type c = 0;
769         LyXLayout_ptr const & layout = par.layout();
770
771         bool left_side = false;
772
773         pos_type body_pos = par.beginOfBody();
774
775         double tmpx = r.x;
776         double last_tmpx = tmpx;
777
778         if (body_pos > 0 &&
779             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
780                 body_pos = 0;
781
782         // check for empty row
783         if (vc == end) {
784                 x = int(tmpx) + xo;
785                 return 0;
786         }
787
788         frontend::FontMetrics const & fm 
789                 = theFontMetrics(getLabelFont(buffer, par));
790
791         while (vc < end && tmpx <= x) {
792                 c = bidi.vis2log(vc);
793                 last_tmpx = tmpx;
794                 if (body_pos > 0 && c == body_pos - 1) {
795                         // FIXME UNICODE
796                         docstring const lsep = from_utf8(layout->labelsep);
797                         tmpx += r.label_hfill + fm.width(lsep);
798                         if (par.isLineSeparator(body_pos - 1))
799                                 tmpx -= singleWidth(buffer, par, body_pos - 1);
800                 }
801
802                 if (par.hfillExpansion(row, c)) {
803                         tmpx += singleWidth(buffer, par, c);
804                         if (c >= body_pos)
805                                 tmpx += r.hfill;
806                         else
807                                 tmpx += r.label_hfill;
808                 } else if (par.isSeparator(c)) {
809                         tmpx += singleWidth(buffer, par, c);
810                         if (c >= body_pos)
811                                 tmpx += r.separator;
812                 } else {
813                         tmpx += singleWidth(buffer, par, c);
814                 }
815                 ++vc;
816         }
817
818         if ((tmpx + last_tmpx) / 2 > x) {
819                 tmpx = last_tmpx;
820                 left_side = true;
821         }
822
823         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
824
825         boundary = false;
826         // This (rtl_support test) is not needed, but gives
827         // some speedup if rtl_support == false
828         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
829
830         // If lastrow is false, we don't need to compute
831         // the value of rtl.
832         bool const rtl = lastrow ? isRTL(buffer, par) : false;
833         if (lastrow &&
834             ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
835              (!rtl && !left_side && vc == end  && x > tmpx + 5)))
836                 c = end;
837         else if (vc == row.pos()) {
838                 c = bidi.vis2log(vc);
839                 if (bidi.level(c) % 2 == 1)
840                         ++c;
841         } else {
842                 c = bidi.vis2log(vc - 1);
843                 bool const rtl = (bidi.level(c) % 2 == 1);
844                 if (left_side == rtl) {
845                         ++c;
846                         boundary = bidi.isBoundary(buffer, par, c);
847                 }
848         }
849
850 // I believe this code is not needed anymore (Jug 20050717)
851 #if 0
852         // The following code is necessary because the cursor position past
853         // the last char in a row is logically equivalent to that before
854         // the first char in the next row. That's why insets causing row
855         // divisions -- Newline and display-style insets -- must be treated
856         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
857         // Newline inset, air gap below:
858         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
859                 if (bidi.level(end -1) % 2 == 0)
860                         tmpx -= singleWidth(buffer, par, end - 1);
861                 else
862                         tmpx += singleWidth(buffer, par, end - 1);
863                 c = end - 1;
864         }
865
866         // Air gap above display inset:
867         if (row.pos() < end && c >= end && end < par.size()
868             && par.isInset(end) && par.getInset(end)->display()) {
869                 c = end - 1;
870         }
871         // Air gap below display inset:
872         if (row.pos() < end && c >= end && par.isInset(end - 1)
873             && par.getInset(end - 1)->display()) {
874                 c = end - 1;
875         }
876 #endif
877
878         x = int(tmpx) + xo;
879         pos_type const col = c - row.pos();
880
881         if (!c || end == par.size())
882                 return col;
883
884         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
885                 boundary = true;
886                 return col;
887         }
888
889         return min(col, end - 1 - row.pos());
890 }
891
892
893 // y is screen coordinate
894 pit_type LyXText::getPitNearY(BufferView & bv, int y)
895 {
896         BOOST_ASSERT(!paragraphs().empty());
897         BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
898         CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
899         lyxerr[Debug::DEBUG]
900                 << BOOST_CURRENT_FUNCTION
901                 << ": y: " << y << " cache size: " << cc.size()
902                 << endl;
903
904         // look for highest numbered paragraph with y coordinate less than given y
905         pit_type pit = 0;
906         int yy = -1;
907         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
908         CoordCache::InnerParPosCache::const_iterator et = cc.end();
909         CoordCache::InnerParPosCache::const_iterator last = et; last--;
910
911         TextMetrics & tm = bv.textMetrics(this);
912         ParagraphMetrics const & pm = tm.parMetrics(it->first);
913         int max_width = tm.maxWidth();
914
915         // If we are off-screen (before the visible part)
916         if (y < 0
917                 // and even before the first paragraph in the cache.
918                 && y < it->second.y_ - int(pm.ascent())) {
919                 //  and we are not at the first paragraph in the inset.
920                 if (it->first == 0)
921                         return 0;
922                 // then this is the paragraph we are looking for.
923                 pit = it->first - 1;
924                 // rebreak it and update the CoordCache.
925                 tm.redoParagraph(pit);
926                 bv.coordCache().parPos()[this][pit] =
927                         Point(0, it->second.y_ - pm.descent());
928                 return pit;
929         }
930
931         ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
932
933         // If we are off-screen (after the visible part)
934         if (y > bv.workHeight()
935                 // and even after the first paragraph in the cache.
936                 && y >= last->second.y_ + int(pm_last.descent())) {
937                 pit = last->first + 1;
938                 //  and we are not at the last paragraph in the inset.
939                 if (pit == int(pars_.size()))
940                         return last->first;
941                 // then this is the paragraph we are looking for.
942                 // rebreak it and update the CoordCache.
943                 tm.redoParagraph(pit);
944                 bv.coordCache().parPos()[this][pit] =
945                         Point(0, last->second.y_ + pm_last.ascent());
946                 return pit;
947         }
948
949         for (; it != et; ++it) {
950                 lyxerr[Debug::DEBUG]
951                         << BOOST_CURRENT_FUNCTION
952                         << "  examining: pit: " << it->first
953                         << " y: " << it->second.y_
954                         << endl;
955
956                 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
957
958                 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
959                         pit = it->first;
960                         yy = it->second.y_;
961                 }
962         }
963
964         lyxerr[Debug::DEBUG]
965                 << BOOST_CURRENT_FUNCTION
966                 << ": found best y: " << yy << " for pit: " << pit
967                 << endl;
968
969         return pit;
970 }
971
972
973 Row const & LyXText::getRowNearY(BufferView const & bv, int y, pit_type pit) const
974 {
975         Paragraph const & par = pars_[pit];
976         ParagraphMetrics const & pm = bv.parMetrics(this, pit);
977
978         int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
979         BOOST_ASSERT(!pm.rows().empty());
980         RowList::const_iterator rit = pm.rows().begin();
981         RowList::const_iterator const rlast = boost::prior(pm.rows().end());
982         for (; rit != rlast; yy += rit->height(), ++rit)
983                 if (yy + rit->height() > y)
984                         break;
985         return *rit;
986 }
987
988
989 // x,y are absolute screen coordinates
990 // sets cursor recursively descending into nested editable insets
991 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
992 {
993         if (lyxerr.debugging(Debug::WORKAREA)) {
994                 lyxerr << "LyXText::editXY(cur, " << x << ", " << y << ")" << std::endl;
995                 cur.bv().coordCache().dump();
996         }
997         pit_type pit = getPitNearY(cur.bv(), y);
998         BOOST_ASSERT(pit != -1);
999
1000         Row const & row = getRowNearY(cur.bv(), y, pit);
1001         bool bound = false;
1002
1003         TextMetrics const & tm = cur.bv().textMetrics(this);
1004         ParagraphMetrics const & pm = tm.parMetrics(pit);
1005         Buffer const & buffer = cur.buffer();
1006         int right_margin = tm.rightMargin(pm);
1007         int xx = x; // is modified by getColumnNearX
1008         pos_type const pos = row.pos()
1009                 + getColumnNearX(cur.bv(), right_margin, pit, row, xx, bound);
1010         cur.pit() = pit;
1011         cur.pos() = pos;
1012         cur.boundary(bound);
1013         cur.x_target() = x;
1014
1015         // try to descend into nested insets
1016         InsetBase * inset = checkInsetHit(cur.bv(), x, y);
1017         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1018         if (!inset) {
1019                 // Either we deconst editXY or better we move current_font
1020                 // and real_current_font to LCursor
1021                 setCurrentFont(cur);
1022                 return 0;
1023         }
1024
1025         InsetBase * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
1026         //InsetBase * insetBehind = pars_[pit].getInset(pos);
1027
1028         // This should be just before or just behind the
1029         // cursor position set above.
1030         BOOST_ASSERT((pos != 0 && inset == insetBefore)
1031                 || inset == pars_[pit].getInset(pos));
1032
1033         // Make sure the cursor points to the position before
1034         // this inset.
1035         if (inset == insetBefore)
1036                 --cur.pos();
1037
1038         // Try to descend recursively inside the inset.
1039         inset = inset->editXY(cur, x, y);
1040
1041         if (cur.top().text() == this)
1042                 setCurrentFont(cur);
1043         return inset;
1044 }
1045
1046
1047 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1048 {
1049         if (cur.selection())
1050                 return false;
1051         if (cur.pos() == cur.lastpos())
1052                 return false;
1053         InsetBase * inset = cur.nextInset();
1054         if (!isHighlyEditableInset(inset))
1055                 return false;
1056         inset->edit(cur, front);
1057         return true;
1058 }
1059
1060
1061 bool LyXText::cursorLeft(LCursor & cur)
1062 {
1063         // Tell BufferView to test for FitCursor in any case!
1064         cur.updateFlags(Update::FitCursor);
1065
1066         if (!cur.boundary() && cur.pos() > 0 &&
1067             cur.textRow().pos() == cur.pos() &&
1068             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1069             !cur.paragraph().isNewline(cur.pos()-1)) {
1070                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1071         }
1072         if (cur.pos() != 0) {
1073                 bool boundary = cur.boundary();
1074                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1075                 if (!checkAndActivateInset(cur, false)) {
1076                         if (false && !boundary &&
1077                             bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1078                                 updateNeeded |=
1079                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1080                 }
1081                 return updateNeeded;
1082         }
1083
1084         if (cur.pit() != 0) {
1085                 // Steps into the paragraph above
1086                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1087         }
1088         return false;
1089 }
1090
1091
1092 bool LyXText::cursorRight(LCursor & cur)
1093 {
1094         // Tell BufferView to test for FitCursor in any case!
1095         cur.updateFlags(Update::FitCursor);
1096
1097         if (cur.pos() != cur.lastpos()) {
1098                 if (cur.boundary())
1099                         return setCursor(cur, cur.pit(), cur.pos(),
1100                                          true, false);
1101
1102                 bool updateNeeded = false;
1103                 if (!checkAndActivateInset(cur, true)) {
1104                         if (cur.textRow().endpos() == cur.pos() + 1 &&
1105                             cur.textRow().endpos() != cur.lastpos() &&
1106                             !cur.paragraph().isLineSeparator(cur.pos()) &&
1107                             !cur.paragraph().isNewline(cur.pos())) {
1108                                 cur.boundary(true);
1109                         }
1110                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1111                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1112                                                      cur.pos()))
1113                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1114                 }
1115                 return updateNeeded;
1116         }
1117
1118         if (cur.pit() != cur.lastpit())
1119                 return setCursor(cur, cur.pit() + 1, 0);
1120         return false;
1121 }
1122
1123
1124 bool LyXText::cursorUp(LCursor & cur)
1125 {
1126         // Tell BufferView to test for FitCursor in any case!
1127         cur.updateFlags(Update::FitCursor);
1128
1129         Paragraph const & par = cur.paragraph();
1130         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
1131
1132         int row;
1133         int const x = cur.targetX();
1134
1135         if (cur.pos() && cur.boundary())
1136                 row = pm.pos2row(cur.pos()-1);
1137         else
1138                 row = pm.pos2row(cur.pos());
1139
1140         if (!cur.selection()) {
1141                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1142                 LCursor old = cur;
1143                 // Go to middle of previous row. 16 found to work OK;
1144                 // 12 = top/bottom margin of display math
1145                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1146                 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1147                 cur.clearSelection();
1148
1149                 // This happens when you move out of an inset.
1150                 // And to give the DEPM the possibility of doing
1151                 // something we must provide it with two different
1152                 // cursors. (Lgb)
1153                 LCursor dummy = cur;
1154                 if (dummy == old)
1155                         ++dummy.pos();
1156
1157                 cur.bv().checkDepm(dummy, old);
1158         }
1159
1160         bool updateNeeded = false;
1161
1162         if (row > 0) {
1163                 updateNeeded |= setCursor(cur, cur.pit(),
1164                         x2pos(cur.bv(), cur.pit(), row - 1, x));
1165         } else if (cur.pit() > 0) {
1166                 --cur.pit();
1167                 //cannot use 'par' now
1168                 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1169                 updateNeeded |= setCursor(cur, cur.pit(),
1170                         x2pos(cur.bv(), cur.pit(), pmcur.rows().size() - 1, x));
1171         }
1172
1173         cur.x_target() = x;
1174
1175         return updateNeeded;
1176 }
1177
1178
1179 bool LyXText::cursorDown(LCursor & cur)
1180 {
1181         // Tell BufferView to test for FitCursor in any case!
1182         cur.updateFlags(Update::FitCursor);
1183
1184         Paragraph const & par = cur.paragraph();
1185         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
1186
1187         int row;
1188         int const x = cur.targetX();
1189
1190         if (cur.pos() && cur.boundary())
1191                 row = pm.pos2row(cur.pos()-1);
1192         else
1193                 row = pm.pos2row(cur.pos());
1194
1195         if (!cur.selection()) {
1196                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1197                 LCursor old = cur;
1198                 // To middle of next row
1199                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1200                 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1201                 cur.clearSelection();
1202
1203                 // This happens when you move out of an inset.
1204                 // And to give the DEPM the possibility of doing
1205                 // something we must provide it with two different
1206                 // cursors. (Lgb)
1207                 LCursor dummy = cur;
1208                 if (dummy == old)
1209                         ++dummy.pos();
1210                 
1211                 bool const changed = cur.bv().checkDepm(dummy, old);
1212
1213                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1214                 if (changed)
1215                         cur = dummy;
1216
1217                 return false;
1218         }
1219
1220         bool updateNeeded = false;
1221
1222         if (row + 1 < int(pm.rows().size())) {
1223                 updateNeeded |= setCursor(cur, cur.pit(),
1224                         x2pos(cur.bv(), cur.pit(), row + 1, x));
1225         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1226                 ++cur.pit();
1227                 updateNeeded |= setCursor(cur, cur.pit(),
1228                         x2pos(cur.bv(), cur.pit(), 0, x));
1229         }
1230
1231         cur.x_target() = x;
1232
1233         return updateNeeded;
1234 }
1235
1236
1237 bool LyXText::cursorUpParagraph(LCursor & cur)
1238 {
1239         bool updated = false;
1240         if (cur.pos() > 0)
1241                 updated = setCursor(cur, cur.pit(), 0);
1242         else if (cur.pit() != 0)
1243                 updated = setCursor(cur, cur.pit() - 1, 0);
1244         return updated;
1245 }
1246
1247
1248 bool LyXText::cursorDownParagraph(LCursor & cur)
1249 {
1250         bool updated = false;
1251         if (cur.pit() != cur.lastpit())
1252                 updated = setCursor(cur, cur.pit() + 1, 0);
1253         else
1254                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1255         return updated;
1256 }
1257
1258
1259 // fix the cursor `cur' after a characters has been deleted at `where'
1260 // position. Called by deleteEmptyParagraphMechanism
1261 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1262 {
1263         // Do nothing if cursor is not in the paragraph where the
1264         // deletion occured,
1265         if (cur.pit() != where.pit())
1266                 return;
1267
1268         // If cursor position is after the deletion place update it
1269         if (cur.pos() > where.pos())
1270                 --cur.pos();
1271
1272         // Check also if we don't want to set the cursor on a spot behind the
1273         // pagragraph because we erased the last character.
1274         if (cur.pos() > cur.lastpos())
1275                 cur.pos() = cur.lastpos();
1276 }
1277
1278
1279 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur,
1280                 LCursor & old, bool & need_anchor_change)
1281 {
1282         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1283         // old should point to us
1284         BOOST_ASSERT(old.text() == this);
1285
1286         Paragraph & oldpar = old.paragraph();
1287
1288         // We allow all kinds of "mumbo-jumbo" when freespacing.
1289         if (oldpar.isFreeSpacing())
1290                 return false;
1291
1292         /* Ok I'll put some comments here about what is missing.
1293            There are still some small problems that can lead to
1294            double spaces stored in the document file or space at
1295            the beginning of paragraphs(). This happens if you have
1296            the cursor between to spaces and then save. Or if you
1297            cut and paste and the selection have a space at the
1298            beginning and then save right after the paste. (Lgb)
1299         */
1300
1301         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1302         // delete the LineSeparator.
1303         // MISSING
1304
1305         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1306         // delete the LineSeparator.
1307         // MISSING
1308
1309         bool const same_inset = &old.inset() == &cur.inset();
1310         bool const same_par = same_inset && old.pit() == cur.pit();
1311         bool const same_par_pos = same_par && old.pos() == cur.pos();
1312
1313         // If the chars around the old cursor were spaces, delete one of them.
1314         if (!same_par_pos) {
1315                 // Only if the cursor has really moved.
1316                 if (old.pos() > 0
1317                     && old.pos() < oldpar.size()
1318                     && oldpar.isLineSeparator(old.pos())
1319                     && oldpar.isLineSeparator(old.pos() - 1)
1320                     && !oldpar.isDeleted(old.pos() - 1)) {
1321                         oldpar.eraseChar(old.pos() - 1, false); // do not track changes in DEPM
1322 #ifdef WITH_WARNINGS
1323 #warning This will not work anymore when we have multiple views of the same buffer
1324 // In this case, we will have to correct also the cursors held by
1325 // other bufferviews. It will probably be easier to do that in a more
1326 // automated way in CursorSlice code. (JMarc 26/09/2001)
1327 #endif
1328                         // correct all cursor parts
1329                         if (same_par) {
1330                                 fixCursorAfterDelete(cur.top(), old.top());
1331                                 need_anchor_change = true;
1332                         }
1333                         return true;
1334                 }
1335         }
1336
1337         // only do our magic if we changed paragraph
1338         if (same_par)
1339                 return false;
1340
1341         // don't delete anything if this is the ONLY paragraph!
1342         if (old.lastpit() == 0)
1343                 return false;
1344
1345         // Do not delete empty paragraphs with keepempty set.
1346         if (oldpar.allowEmpty())
1347                 return false;
1348
1349         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1350                 // Delete old par.
1351                 recordUndo(old, Undo::ATOMIC,
1352                            max(old.pit() - 1, pit_type(0)),
1353                            min(old.pit() + 1, old.lastpit()));
1354                 ParagraphList & plist = old.text()->paragraphs();
1355                 plist.erase(boost::next(plist.begin(), old.pit()));
1356
1357                 // see #warning above
1358                 if (cur.depth() >= old.depth()) {
1359                         CursorSlice & curslice = cur[old.depth() - 1];
1360                         if (&curslice.inset() == &old.inset()
1361                             && curslice.pit() > old.pit()) {
1362                                 --curslice.pit();
1363                                 // since a paragraph has been deleted, all the
1364                                 // insets after `old' have been copied and
1365                                 // their address has changed. Therefore we
1366                                 // need to `regenerate' cur. (JMarc)
1367                                 cur.updateInsets(&(cur.bottom().inset()));
1368                                 need_anchor_change = true;
1369                         }
1370                 }
1371                 return true;
1372         }
1373
1374         if (oldpar.stripLeadingSpaces())
1375                 need_anchor_change = true;
1376
1377         return false;
1378 }
1379
1380
1381 void LyXText::recUndo(LCursor & cur, pit_type first, pit_type last) const
1382 {
1383         recordUndo(cur, Undo::ATOMIC, first, last);
1384 }
1385
1386
1387 void LyXText::recUndo(LCursor & cur, pit_type par) const
1388 {
1389         recordUndo(cur, Undo::ATOMIC, par, par);
1390 }
1391
1392 } // namespace lyx