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