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