3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
36 #include "FloatList.h"
37 #include "funcrequest.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "PosIterator.h"
51 #include "frontends/font_metrics.h"
52 #include "frontends/LyXView.h"
54 #include "insets/insetbibitem.h"
55 #include "insets/insetenv.h"
56 #include "insets/insetfloat.h"
57 #include "insets/insetwrap.h"
59 #include "support/lstrings.h"
60 #include "support/textutils.h"
61 #include "support/tostr.h"
62 #include "support/std_sstream.h"
64 #include <boost/tuple/tuple.hpp>
67 using lyx::paroffset_type;
68 using lyx::support::bformat;
71 using std::ostringstream;
75 LyXText::LyXText(BufferView * bv, bool in_inset)
76 : height(0), width(0), textwidth_(bv ? bv->workWidth() : 100),
77 background_color_(LColor::background),
78 bv_owner(bv), in_inset_(in_inset), xo_(0), yo_(0)
82 void LyXText::init(BufferView * bview)
86 ParagraphList::iterator const beg = paragraphs().begin();
87 ParagraphList::iterator const end = paragraphs().end();
88 for (ParagraphList::iterator pit = beg; pit != end; ++pit)
94 current_font = getFont(beg, 0);
96 redoParagraphs(beg, end);
97 setCursorIntern(0, 0);
98 selection.cursor = cursor;
104 // Gets the fully instantiated font at a given position in a paragraph
105 // Basically the same routine as Paragraph::getFont() in paragraph.C.
106 // The difference is that this one is used for displaying, and thus we
107 // are allowed to make cosmetic improvements. For instance make footnotes
109 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
111 BOOST_ASSERT(pos >= 0);
113 LyXLayout_ptr const & layout = pit->layout();
115 BufferParams const & params = bv()->buffer()->params();
116 pos_type const body_pos = pit->beginOfBody();
118 // We specialize the 95% common case:
119 if (!pit->getDepth()) {
120 LyXFont f = pit->getFontSettings(params, pos);
123 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
124 return f.realize(layout->reslabelfont);
126 return f.realize(layout->resfont);
129 // The uncommon case need not be optimized as much
132 layoutfont = layout->labelfont;
134 layoutfont = layout->font;
136 LyXFont font = pit->getFontSettings(params, pos);
137 font.realize(layoutfont);
142 // Realize with the fonts of lesser depth.
143 //font.realize(outerFont(pit, paragraphs()));
144 font.realize(defaultfont_);
150 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
152 LyXLayout_ptr const & layout = pit->layout();
154 if (!pit->getDepth())
155 return layout->resfont;
157 LyXFont font = layout->font;
158 // Realize with the fonts of lesser depth.
159 //font.realize(outerFont(pit, paragraphs()));
160 font.realize(defaultfont_);
166 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
168 LyXLayout_ptr const & layout = pit->layout();
170 if (!pit->getDepth())
171 return layout->reslabelfont;
173 LyXFont font = layout->labelfont;
174 // Realize with the fonts of lesser depth.
175 font.realize(outerFont(pit, paragraphs()));
176 font.realize(defaultfont_);
182 void LyXText::setCharFont(
183 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
186 LyXLayout_ptr const & layout = pit->layout();
188 // Get concrete layout font to reduce against
191 if (pos < pit->beginOfBody())
192 layoutfont = layout->labelfont;
194 layoutfont = layout->font;
196 // Realize against environment font information
197 if (pit->getDepth()) {
198 ParagraphList::iterator tp = pit;
199 while (!layoutfont.resolved() &&
200 tp != paragraphs().end() &&
202 tp = outerHook(tp, paragraphs());
203 if (tp != paragraphs().end())
204 layoutfont.realize(tp->layout()->font);
208 layoutfont.realize(defaultfont_);
210 // Now, reduce font against full layout font
211 font.reduce(layoutfont);
213 pit->setFont(pos, font);
217 InsetOld * LyXText::getInset() const
219 ParagraphList::iterator pit = cursorPar();
220 pos_type const pos = cursor.pos();
222 if (pos < pit->size() && pit->isInset(pos)) {
223 return pit->getInset(pos);
229 bool LyXText::toggleInset()
231 InsetOld * inset = getInset();
232 // is there an editable inset at cursor position?
233 if (!isEditableInset(inset))
235 //bv()->owner()->message(inset->editMessage());
237 // do we want to keep this?? (JMarc)
238 if (!isHighlyEditableInset(inset))
239 recUndo(cursor.par());
250 // Asger is not sure we want to do this...
251 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
254 LyXLayout_ptr const & layout = par.layout();
255 pos_type const psize = par.size();
258 for (pos_type pos = 0; pos < psize; ++pos) {
259 if (pos < par.beginOfBody())
260 layoutfont = layout->labelfont;
262 layoutfont = layout->font;
264 LyXFont tmpfont = par.getFontSettings(params, pos);
265 tmpfont.reduce(layoutfont);
266 par.setFont(pos, tmpfont);
271 ParagraphList::iterator
272 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
273 LyXCursor & send_cur,
274 string const & layout)
276 ParagraphList::iterator endpit = boost::next(getPar(send_cur));
277 ParagraphList::iterator undoendpit = endpit;
278 ParagraphList::iterator pars_end = paragraphs().end();
280 if (endpit != pars_end && endpit->getDepth()) {
281 while (endpit != pars_end && endpit->getDepth()) {
285 } else if (endpit != pars_end) {
286 // because of parindents etc.
290 recUndo(sstart_cur.par(), parOffset(undoendpit) - 1);
292 // ok we have a selection. This is always between sstart_cur
293 // and sel_end cursor
295 ParagraphList::iterator pit = getPar(sstart_cur);
296 ParagraphList::iterator epit = boost::next(getPar(send_cur));
298 BufferParams const & bufparams = bv()->buffer()->params();
299 LyXLayout_ptr const & lyxlayout =
300 bufparams.getLyXTextClass()[layout];
303 pit->applyLayout(lyxlayout);
304 makeFontEntriesLayoutSpecific(bufparams, *pit);
305 if (lyxlayout->margintype == MARGIN_MANUAL)
306 pit->setLabelWidthString(lyxlayout->labelstring());
307 cur.par(std::distance(paragraphs().begin(), pit));
309 } while (pit != epit);
315 // set layout over selection and make a total rebreak of those paragraphs
316 void LyXText::setLayout(string const & layout)
320 // special handling of new environment insets
321 BufferParams const & params = bv()->buffer()->params();
322 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
323 if (lyxlayout->is_environment) {
324 // move everything in a new environment inset
325 lyxerr << "setting layout " << layout << endl;
326 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
327 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
328 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
329 InsetOld * inset = new InsetEnvironment(params, layout);
330 if (bv()->insertInset(inset)) {
332 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
338 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
339 selection.end, layout);
340 redoParagraphs(getPar(selection.start), endpit);
349 void getSelectionSpan(LyXText & text,
350 ParagraphList::iterator & beg,
351 ParagraphList::iterator & end)
353 if (!text.selection.set()) {
354 beg = text.cursorPar();
355 end = boost::next(beg);
357 beg = text.getPar(text.selection.start);
358 end = boost::next(text.getPar(text.selection.end));
363 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
364 Paragraph const & par,
367 if (par.layout()->labeltype == LABEL_BIBLIO)
369 int const depth = par.params().depth();
370 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
372 if (type == bv_funcs::DEC_DEPTH && depth > 0)
381 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
383 ParagraphList::iterator beg, end;
384 getSelectionSpan(*this, beg, end);
386 if (beg != paragraphs().begin())
387 max_depth = boost::prior(beg)->getMaxDepthAfter();
389 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
390 if (::changeDepthAllowed(type, *pit, max_depth))
392 max_depth = pit->getMaxDepthAfter();
398 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
400 ParagraphList::iterator beg, end;
401 getSelectionSpan(*this, beg, end);
403 recUndo(parOffset(beg), parOffset(end) - 1);
406 if (beg != paragraphs().begin())
407 max_depth = boost::prior(beg)->getMaxDepthAfter();
409 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
410 if (::changeDepthAllowed(type, *pit, max_depth)) {
411 int const depth = pit->params().depth();
412 if (type == bv_funcs::INC_DEPTH)
413 pit->params().depth(depth + 1);
415 pit->params().depth(depth - 1);
417 max_depth = pit->getMaxDepthAfter();
419 // this handles the counter labels, and also fixes up
420 // depth values for follow-on (child) paragraphs
426 // set font over selection and make a total rebreak of those paragraphs
427 void LyXText::setFont(LyXFont const & font, bool toggleall)
429 // if there is no selection just set the current_font
430 if (!selection.set()) {
431 // Determine basis font
433 if (cursor.pos() < cursorPar()->beginOfBody()) {
434 layoutfont = getLabelFont(cursorPar());
436 layoutfont = getLayoutFont(cursorPar());
438 // Update current font
439 real_current_font.update(font,
440 bv()->buffer()->params().language,
443 // Reduce to implicit settings
444 current_font = real_current_font;
445 current_font.reduce(layoutfont);
446 // And resolve it completely
447 real_current_font.realize(layoutfont);
452 // ok we have a selection.
453 recUndo(selection.start.par(), selection.end.par());
456 ParagraphList::iterator beg = getPar(selection.start.par());
457 ParagraphList::iterator end = getPar(selection.end.par());
459 PosIterator pos(¶graphs(), beg, selection.start.pos());
460 PosIterator posend(¶graphs(), end, selection.end.pos());
462 BufferParams const & params = bv()->buffer()->params();
464 for (; pos != posend; ++pos) {
465 LyXFont f = getFont(pos.pit(), pos.pos());
466 f.update(font, params.language, toggleall);
467 setCharFont(pos.pit(), pos.pos(), f);
472 redoParagraphs(beg, ++end);
477 // important for the screen
480 // the cursor set functions have a special mechanism. When they
481 // realize, that you left an empty paragraph, they will delete it.
483 // need the selection cursor:
484 void LyXText::setSelection()
486 TextCursor::setSelection();
490 void LyXText::clearSelection()
492 TextCursor::clearSelection();
494 // reset this in the bv()!
495 if (bv() && bv()->text())
496 bv()->text()->xsel_cache.set(false);
500 void LyXText::cursorHome()
502 ParagraphList::iterator cpit = cursorPar();
503 setCursor(cpit, cpit->getRow(cursor.pos())->pos());
507 void LyXText::cursorEnd()
509 ParagraphList::iterator cpit = cursorPar();
510 pos_type end = cpit->getRow(cursor.pos())->endpos();
511 // if not on the last row of the par, put the cursor before
513 setCursor(cpit, end == cpit->size() ? end : end - 1);
517 void LyXText::cursorTop()
519 setCursor(paragraphs().begin(), 0);
523 void LyXText::cursorBottom()
525 ParagraphList::iterator lastpit =
526 boost::prior(paragraphs().end());
527 setCursor(lastpit, lastpit->size());
531 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
533 // If the mask is completely neutral, tell user
534 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
535 // Could only happen with user style
536 bv()->owner()->message(_("No font change defined. "
537 "Use Character under the Layout menu to define font change."));
541 // Try implicit word selection
542 // If there is a change in the language the implicit word selection
544 LyXCursor resetCursor = cursor;
545 bool implicitSelection =
546 font.language() == ignore_language
547 && font.number() == LyXFont::IGNORE
548 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
551 setFont(font, toggleall);
553 // Implicit selections are cleared afterwards
554 //and cursor is set to the original position.
555 if (implicitSelection) {
557 cursor = resetCursor;
558 setCursor(cursorPar(), cursor.pos());
559 selection.cursor = cursor;
564 string LyXText::getStringToIndex()
566 // Try implicit word selection
567 // If there is a change in the language the implicit word selection
569 LyXCursor const reset_cursor = cursor;
570 bool const implicitSelection =
571 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
574 if (!selection.set())
575 bv()->owner()->message(_("Nothing to index!"));
576 else if (selection.start.par() != selection.end.par())
577 bv()->owner()->message(_("Cannot index more than one paragraph!"));
579 idxstring = selectionAsString(*bv()->buffer(), false);
581 // Reset cursors to their original position.
582 cursor = reset_cursor;
583 setCursor(cursorPar(), cursor.pos());
584 selection.cursor = cursor;
586 // Clear the implicit selection.
587 if (implicitSelection)
594 // the DTP switches for paragraphs(). LyX will store them in the first
595 // physical paragraph. When a paragraph is broken, the top settings rest,
596 // the bottom settings are given to the new one. So I can make sure,
597 // they do not duplicate themself and you cannot play dirty tricks with
600 void LyXText::setParagraph(
601 Spacing const & spacing,
603 string const & labelwidthstring,
607 // make sure that the depth behind the selection are restored, too
608 ParagraphList::iterator endpit = boost::next(getPar(selection.end));
609 ParagraphList::iterator undoendpit = endpit;
610 ParagraphList::iterator pars_end = paragraphs().end();
612 if (endpit != pars_end && endpit->getDepth()) {
613 while (endpit != pars_end && endpit->getDepth()) {
617 } else if (endpit != pars_end) {
618 // because of parindents etc.
622 recUndo(selection.start.par(), parOffset(undoendpit) - 1);
624 int tmppit = selection.end.par();
626 while (tmppit != selection.start.par() - 1) {
627 setCursor(tmppit, 0);
629 ParagraphList::iterator const pit = cursorPar();
630 ParagraphParameters & params = pit->params();
631 params.spacing(spacing);
633 // does the layout allow the new alignment?
634 LyXLayout_ptr const & layout = pit->layout();
636 if (align == LYX_ALIGN_LAYOUT)
637 align = layout->align;
638 if (align & layout->alignpossible) {
639 if (align == layout->align)
640 params.align(LYX_ALIGN_LAYOUT);
644 pit->setLabelWidthString(labelwidthstring);
645 params.noindent(noindent);
649 redoParagraphs(getPar(selection.start), endpit);
654 string expandLabel(LyXTextClass const & textclass,
655 LyXLayout_ptr const & layout, bool appendix)
657 string fmt = appendix ?
658 layout->labelstring_appendix() : layout->labelstring();
660 // handle 'inherited level parts' in 'fmt',
661 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
662 size_t const i = fmt.find('@', 0);
663 if (i != string::npos) {
664 size_t const j = fmt.find('@', i + 1);
665 if (j != string::npos) {
666 string parent(fmt, i + 1, j - i - 1);
667 string label = expandLabel(textclass, textclass[parent], appendix);
668 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
672 return textclass.counters().counterLabel(fmt);
678 void incrementItemDepth(ParagraphList::iterator pit,
679 ParagraphList::iterator first_pit)
681 int const cur_labeltype = pit->layout()->labeltype;
683 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
686 int const cur_depth = pit->getDepth();
688 ParagraphList::iterator prev_pit = boost::prior(pit);
690 int const prev_depth = prev_pit->getDepth();
691 int const prev_labeltype = prev_pit->layout()->labeltype;
692 if (prev_depth == 0 && cur_depth > 0) {
693 if (prev_labeltype == cur_labeltype) {
694 pit->itemdepth = prev_pit->itemdepth + 1;
697 } else if (prev_depth < cur_depth) {
698 if (prev_labeltype == cur_labeltype) {
699 pit->itemdepth = prev_pit->itemdepth + 1;
702 } else if (prev_depth == cur_depth) {
703 if (prev_labeltype == cur_labeltype) {
704 pit->itemdepth = prev_pit->itemdepth;
708 if (prev_pit == first_pit)
716 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
717 ParagraphList::iterator firstpit,
723 int const cur_depth = pit->getDepth();
724 ParagraphList::iterator prev_pit = boost::prior(pit);
726 int const prev_depth = prev_pit->getDepth();
727 int const prev_labeltype = prev_pit->layout()->labeltype;
728 if (prev_depth <= cur_depth) {
729 if (prev_labeltype != LABEL_ENUMERATE) {
730 switch (pit->itemdepth) {
732 counters.reset("enumi");
734 counters.reset("enumii");
736 counters.reset("enumiii");
738 counters.reset("enumiv");
744 if (prev_pit == firstpit)
754 // set the counter of a paragraph. This includes the labels
755 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
757 BufferParams const & bufparams = buf.params();
758 LyXTextClass const & textclass = bufparams.getLyXTextClass();
759 LyXLayout_ptr const & layout = pit->layout();
760 ParagraphList::iterator first_pit = paragraphs().begin();
761 Counters & counters = textclass.counters();
766 if (pit == first_pit) {
767 pit->params().appendix(pit->params().startOfAppendix());
769 pit->params().appendix(boost::prior(pit)->params().appendix());
770 if (!pit->params().appendix() &&
771 pit->params().startOfAppendix()) {
772 pit->params().appendix(true);
773 textclass.counters().reset();
776 // Maybe we have to increment the item depth.
777 incrementItemDepth(pit, first_pit);
780 // erase what was there before
781 pit->params().labelString(string());
783 if (layout->margintype == MARGIN_MANUAL) {
784 if (pit->params().labelWidthString().empty())
785 pit->setLabelWidthString(layout->labelstring());
787 pit->setLabelWidthString(string());
790 // is it a layout that has an automatic label?
791 if (layout->labeltype == LABEL_COUNTER) {
792 BufferParams const & bufparams = buf.params();
793 LyXTextClass const & textclass = bufparams.getLyXTextClass();
794 counters.step(layout->counter);
795 string label = expandLabel(textclass, layout, pit->params().appendix());
796 pit->params().labelString(label);
797 } else if (layout->labeltype == LABEL_ITEMIZE) {
798 // At some point of time we should do something more
799 // clever here, like:
800 // pit->params().labelString(
801 // bufparams.user_defined_bullet(pit->itemdepth).getText());
802 // for now, use a simple hardcoded label
804 switch (pit->itemdepth) {
819 pit->params().labelString(itemlabel);
820 } else if (layout->labeltype == LABEL_ENUMERATE) {
821 // Maybe we have to reset the enumeration counter.
822 resetEnumCounterIfNeeded(pit, first_pit, counters);
825 // Yes I know this is a really, really! bad solution
827 string enumcounter = "enum";
829 switch (pit->itemdepth) {
841 // not a valid enumdepth...
845 counters.step(enumcounter);
847 pit->params().labelString(counters.enumLabel(enumcounter));
848 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
849 counters.step("bibitem");
850 int number = counters.value("bibitem");
851 if (pit->bibitem()) {
852 pit->bibitem()->setCounter(number);
853 pit->params().labelString(layout->labelstring());
855 // In biblio should't be following counters but...
857 string s = buf.B_(layout->labelstring());
860 if (layout->labeltype == LABEL_SENSITIVE) {
861 ParagraphList::iterator end = paragraphs().end();
862 ParagraphList::iterator tmppit = pit;
865 while (tmppit != end && tmppit->inInset()
866 // the single '=' is intended below
867 && (in = tmppit->inInset()->owner()))
869 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
870 in->lyxCode() == InsetOld::WRAP_CODE) {
874 Paragraph const * owner = &ownerPar(buf, in);
876 for ( ; tmppit != end; ++tmppit)
877 if (&*tmppit == owner)
885 if (in->lyxCode() == InsetOld::FLOAT_CODE)
886 type = static_cast<InsetFloat*>(in)->params().type;
887 else if (in->lyxCode() == InsetOld::WRAP_CODE)
888 type = static_cast<InsetWrap*>(in)->params().type;
892 Floating const & fl = textclass.floats().getType(type);
894 counters.step(fl.type());
896 // Doesn't work... yet.
897 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
899 // par->SetLayout(0);
900 // s = layout->labelstring;
901 s = _("Senseless: ");
904 pit->params().labelString(s);
910 // Updates all counters.
911 void LyXText::updateCounters()
914 bv()->buffer()->params().getLyXTextClass().counters().reset();
916 bool update_pos = false;
918 ParagraphList::iterator beg = paragraphs().begin();
919 ParagraphList::iterator end = paragraphs().end();
920 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
921 string const oldLabel = pit->params().labelString();
924 maxdepth = boost::prior(pit)->getMaxDepthAfter();
926 if (pit->params().depth() > maxdepth)
927 pit->params().depth(maxdepth);
929 // setCounter can potentially change the labelString.
930 setCounter(*bv()->buffer(), pit);
931 string const & newLabel = pit->params().labelString();
932 if (oldLabel != newLabel) {
933 redoParagraphInternal(pit);
939 updateParPositions();
943 void LyXText::insertInset(InsetOld * inset)
945 if (!cursorPar()->insetAllowed(inset->lyxCode()))
948 recUndo(cursor.par());
950 cursorPar()->insertInset(cursor.pos(), inset);
951 // Just to rebreak and refresh correctly.
952 // The character will not be inserted a second time
953 insertChar(Paragraph::META_INSET);
954 // If we enter a highly editable inset the cursor should be before
955 // the inset. After an undo LyX tries to call inset->edit(...)
956 // and fails if the cursor is behind the inset and getInset
957 // does not return the inset!
958 if (isHighlyEditableInset(inset))
965 void LyXText::cutSelection(bool doclear, bool realcut)
967 // Stuff what we got on the clipboard. Even if there is no selection.
969 // There is a problem with having the stuffing here in that the
970 // larger the selection the slower LyX will get. This can be
971 // solved by running the line below only when the selection has
972 // finished. The solution used currently just works, to make it
973 // faster we need to be more clever and probably also have more
974 // calls to stuffClipboard. (Lgb)
975 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
977 // This doesn't make sense, if there is no selection
978 if (!selection.set())
981 // OK, we have a selection. This is always between selection.start
984 // make sure that the depth behind the selection are restored, too
985 ParagraphList::iterator endpit = boost::next(getPar(selection.end.par()));
986 ParagraphList::iterator undoendpit = endpit;
987 ParagraphList::iterator pars_end = paragraphs().end();
989 if (endpit != pars_end && endpit->getDepth()) {
990 while (endpit != pars_end && endpit->getDepth()) {
994 } else if (endpit != pars_end) {
995 // because of parindents etc.
999 recUndo(selection.start.par(), parOffset(undoendpit) - 1);
1001 endpit = getPar(selection.end.par());
1002 int endpos = selection.end.pos();
1004 BufferParams const & bufparams = bv()->buffer()->params();
1005 boost::tie(endpit, endpos) = realcut ?
1006 CutAndPaste::cutSelection(bufparams,
1008 getPar(selection.start.par()), endpit,
1009 selection.start.pos(), endpos,
1010 bufparams.textclass,
1012 : CutAndPaste::eraseSelection(bufparams,
1014 getPar(selection.start.par()), endpit,
1015 selection.start.pos(), endpos,
1017 // sometimes necessary
1019 getPar(selection.start.par())->stripLeadingSpaces();
1021 redoParagraphs(getPar(selection.start.par()), boost::next(endpit));
1022 // cutSelection can invalidate the cursor so we need to set
1024 // we prefer the end for when tracking changes
1026 cursor.par(parOffset(endpit));
1028 // need a valid cursor. (Lgb)
1031 setCursor(cursorPar(), cursor.pos());
1032 selection.cursor = cursor;
1037 void LyXText::copySelection()
1039 // stuff the selection onto the X clipboard, from an explicit copy request
1040 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1042 // this doesnt make sense, if there is no selection
1043 if (!selection.set())
1046 // ok we have a selection. This is always between selection.start
1047 // and sel_end cursor
1049 // copy behind a space if there is one
1050 while (getPar(selection.start)->size() > selection.start.pos()
1051 && getPar(selection.start)->isLineSeparator(selection.start.pos())
1052 && (selection.start.par() != selection.end.par()
1053 || selection.start.pos() < selection.end.pos()))
1054 selection.start.pos(selection.start.pos() + 1);
1056 CutAndPaste::copySelection(getPar(selection.start.par()),
1057 getPar(selection.end.par()),
1058 selection.start.pos(), selection.end.pos(),
1059 bv()->buffer()->params().textclass);
1063 void LyXText::pasteSelection(size_t sel_index)
1065 // this does not make sense, if there is nothing to paste
1066 if (!CutAndPaste::checkPastePossible())
1069 recUndo(cursor.par());
1071 ParagraphList::iterator endpit;
1076 boost::tie(ppp, endpit) =
1077 CutAndPaste::pasteSelection(*bv()->buffer(),
1079 cursorPar(), cursor.pos(),
1080 bv()->buffer()->params().textclass,
1082 bufferErrors(*bv()->buffer(), el);
1083 bv()->showErrorList(_("Paste"));
1085 redoParagraphs(cursorPar(), endpit);
1087 setCursor(cursor.par(), cursor.pos());
1090 selection.cursor = cursor;
1091 setCursor(ppp.first, ppp.second);
1097 void LyXText::setSelectionRange(lyx::pos_type length)
1102 selection.cursor = cursor;
1109 // simple replacing. The font of the first selected character is used
1110 void LyXText::replaceSelectionWithString(string const & str)
1112 recUndo(cursor.par());
1115 if (!selection.set()) { // create a dummy selection
1116 selection.end = cursor;
1117 selection.start = cursor;
1120 // Get font setting before we cut
1121 pos_type pos = selection.end.pos();
1122 LyXFont const font = getPar(selection.start)
1123 ->getFontSettings(bv()->buffer()->params(),
1124 selection.start.pos());
1126 // Insert the new string
1127 string::const_iterator cit = str.begin();
1128 string::const_iterator end = str.end();
1129 for (; cit != end; ++cit) {
1130 getPar(selection.end)->insertChar(pos, (*cit), font);
1134 // Cut the selection
1135 cutSelection(true, false);
1141 // needed to insert the selection
1142 void LyXText::insertStringAsLines(string const & str)
1144 ParagraphList::iterator pit = cursorPar();
1145 pos_type pos = cursor.pos();
1146 ParagraphList::iterator endpit = boost::next(cursorPar());
1148 recUndo(cursor.par());
1150 // only to be sure, should not be neccessary
1153 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1155 redoParagraphs(cursorPar(), endpit);
1156 setCursor(cursorPar(), cursor.pos());
1157 selection.cursor = cursor;
1158 setCursor(pit, pos);
1163 // turn double CR to single CR, others are converted into one
1164 // blank. Then insertStringAsLines is called
1165 void LyXText::insertStringAsParagraphs(string const & str)
1167 string linestr(str);
1168 bool newline_inserted = false;
1169 string::size_type const siz = linestr.length();
1171 for (string::size_type i = 0; i < siz; ++i) {
1172 if (linestr[i] == '\n') {
1173 if (newline_inserted) {
1174 // we know that \r will be ignored by
1175 // insertStringAsLines. Of course, it is a dirty
1176 // trick, but it works...
1177 linestr[i - 1] = '\r';
1181 newline_inserted = true;
1183 } else if (IsPrintable(linestr[i])) {
1184 newline_inserted = false;
1187 insertStringAsLines(linestr);
1191 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1193 setCursor(parOffset(pit), pos);
1197 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1200 LyXCursor old_cursor = cursor;
1201 setCursorIntern(par, pos, setfont, boundary);
1202 return deleteEmptyParagraphMechanism(old_cursor);
1206 void LyXText::redoCursor()
1208 setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary());
1210 if (!selection.set())
1213 LyXCursor tmpcursor = cursor;
1214 setCursor(selection.cursor.par(), selection.cursor.pos());
1215 selection.cursor = cursor;
1216 setCursor(tmpcursor.par(), tmpcursor.pos());
1221 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1222 pos_type pos, bool boundary)
1224 BOOST_ASSERT(par != int(paragraphs().size()));
1228 cur.boundary(boundary);
1230 // no rows, no fun...
1231 if (paragraphs().begin()->rows.empty())
1234 // get the cursor y position in text
1236 ParagraphList::iterator pit = getPar(par);
1237 Row const & row = *pit->getRow(pos);
1239 int y = pit->y + row.y_offset();
1241 // y is now the beginning of the cursor row
1242 y += row.baseline();
1243 // y is now the cursor baseline
1246 pos_type const end = row.endpos();
1248 // None of these should happen, but we're scaredy-cats
1250 lyxerr << "dont like -1" << endl;
1253 BOOST_ASSERT(false);
1254 } else if (pos > pit->size()) {
1255 lyxerr << "dont like 1, pos: " << pos
1256 << " size: " << pit->size()
1257 << " row.pos():" << row.pos()
1258 << " paroffset: " << par << endl;
1261 BOOST_ASSERT(false);
1262 } else if (pos > end) {
1263 lyxerr << "dont like 2 please report" << endl;
1264 // This shouldn't happen.
1267 BOOST_ASSERT(false);
1268 } else if (pos < row.pos()) {
1269 lyxerr << "dont like 3 please report pos:" << pos
1270 << " size: " << pit->size()
1271 << " row.pos():" << row.pos()
1272 << " paroffset: " << par << endl;
1275 BOOST_ASSERT(false);
1277 // now get the cursors x position
1278 cur.x(int(getCursorX(pit, row, pos, boundary)));
1282 float LyXText::getCursorX(ParagraphList::iterator pit, Row const & row,
1283 pos_type pos, bool boundary) const
1285 pos_type cursor_vpos = 0;
1287 double fill_separator = row.fill_separator();
1288 double fill_hfill = row.fill_hfill();
1289 double fill_label_hfill = row.fill_label_hfill();
1290 pos_type const row_pos = row.pos();
1291 pos_type const end = row.endpos();
1294 cursor_vpos = row_pos;
1295 else if (pos >= end && !boundary)
1296 cursor_vpos = (pit->isRightToLeftPar(bv()->buffer()->params()))
1298 else if (pos > row_pos && (pos >= end || boundary))
1299 // Place cursor after char at (logical) position pos - 1
1300 cursor_vpos = (bidi.level(pos - 1) % 2 == 0)
1301 ? bidi.log2vis(pos - 1) + 1 : bidi.log2vis(pos - 1);
1303 // Place cursor before char at (logical) position pos
1304 cursor_vpos = (bidi.level(pos) % 2 == 0)
1305 ? bidi.log2vis(pos) : bidi.log2vis(pos) + 1;
1307 pos_type body_pos = pit->beginOfBody();
1309 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1312 for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) {
1313 pos_type pos = bidi.vis2log(vpos);
1314 if (body_pos > 0 && pos == body_pos - 1) {
1315 x += fill_label_hfill
1316 + font_metrics::width(pit->layout()->labelsep,
1318 if (pit->isLineSeparator(body_pos - 1))
1319 x -= singleWidth(pit, body_pos - 1);
1322 if (hfillExpansion(*pit, row, pos)) {
1323 x += singleWidth(pit, pos);
1324 if (pos >= body_pos)
1327 x += fill_label_hfill;
1328 } else if (pit->isSeparator(pos)) {
1329 x += singleWidth(pit, pos);
1330 if (pos >= body_pos)
1331 x += fill_separator;
1333 x += singleWidth(pit, pos);
1339 void LyXText::setCursorIntern(paroffset_type par,
1340 pos_type pos, bool setfont, bool boundary)
1342 setCursor(cursor, par, pos, boundary);
1343 bv()->x_target(cursor.x() + xo_);
1349 void LyXText::setCurrentFont()
1351 pos_type pos = cursor.pos();
1352 ParagraphList::iterator pit = cursorPar();
1354 if (cursor.boundary() && pos > 0)
1358 if (pos == pit->size())
1360 else // potentional bug... BUG (Lgb)
1361 if (pit->isSeparator(pos)) {
1362 if (pos > pit->getRow(pos)->pos() &&
1363 bidi.level(pos) % 2 ==
1364 bidi.level(pos - 1) % 2)
1366 else if (pos + 1 < pit->size())
1371 BufferParams const & bufparams = bv()->buffer()->params();
1372 current_font = pit->getFontSettings(bufparams, pos);
1373 real_current_font = getFont(pit, pos);
1375 if (cursor.pos() == pit->size() &&
1376 bidi.isBoundary(*bv()->buffer(), *pit, cursor.pos()) &&
1377 !cursor.boundary()) {
1378 Language const * lang =
1379 pit->getParLanguage(bufparams);
1380 current_font.setLanguage(lang);
1381 current_font.setNumber(LyXFont::OFF);
1382 real_current_font.setLanguage(lang);
1383 real_current_font.setNumber(LyXFont::OFF);
1388 // returns the column near the specified x-coordinate of the row
1389 // x is set to the real beginning of this column
1390 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1391 Row const & row, int & x, bool & boundary) const
1393 double tmpx = row.x();
1394 double fill_separator = row.fill_separator();
1395 double fill_hfill = row.fill_hfill();
1396 double fill_label_hfill = row.fill_label_hfill();
1398 pos_type vc = row.pos();
1399 pos_type end = row.endpos();
1401 LyXLayout_ptr const & layout = pit->layout();
1403 bool left_side = false;
1405 pos_type body_pos = pit->beginOfBody();
1406 double last_tmpx = tmpx;
1409 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1412 // check for empty row
1418 while (vc < end && tmpx <= x) {
1419 c = bidi.vis2log(vc);
1421 if (body_pos > 0 && c == body_pos - 1) {
1422 tmpx += fill_label_hfill +
1423 font_metrics::width(layout->labelsep, getLabelFont(pit));
1424 if (pit->isLineSeparator(body_pos - 1))
1425 tmpx -= singleWidth(pit, body_pos - 1);
1428 if (hfillExpansion(*pit, row, c)) {
1429 tmpx += singleWidth(pit, c);
1433 tmpx += fill_label_hfill;
1434 } else if (pit->isSeparator(c)) {
1435 tmpx += singleWidth(pit, c);
1437 tmpx += fill_separator;
1439 tmpx += singleWidth(pit, c);
1444 if ((tmpx + last_tmpx) / 2 > x) {
1449 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1452 // This (rtl_support test) is not needed, but gives
1453 // some speedup if rtl_support == false
1454 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1456 // If lastrow is false, we don't need to compute
1457 // the value of rtl.
1458 bool const rtl = (lastrow)
1459 ? pit->isRightToLeftPar(bv()->buffer()->params())
1462 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1463 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1465 else if (vc == row.pos()) {
1466 c = bidi.vis2log(vc);
1467 if (bidi.level(c) % 2 == 1)
1470 c = bidi.vis2log(vc - 1);
1471 bool const rtl = (bidi.level(c) % 2 == 1);
1472 if (left_side == rtl) {
1474 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1478 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1479 if (bidi.level(end -1) % 2 == 0)
1480 tmpx -= singleWidth(pit, end - 1);
1482 tmpx += singleWidth(pit, end - 1);
1492 void LyXText::setCursorFromCoordinates(int x, int y)
1494 LyXCursor old_cursor = cursor;
1495 setCursorFromCoordinates(cursor, x, y);
1497 deleteEmptyParagraphMechanism(old_cursor);
1500 // x,y are coordinates relative to this LyXText
1501 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1503 // Get the row first.
1504 ParagraphList::iterator pit;
1505 Row const & row = *getRowNearY(y, pit);
1506 y = pit->y + row.y_offset();
1509 pos_type const column = getColumnNearX(pit, row, x, bound);
1510 cur.par(parOffset(pit));
1511 cur.pos(row.pos() + column);
1513 cur.y(y + row.baseline());
1515 cur.boundary(bound);
1519 bool LyXText::checkAndActivateInset(bool front)
1521 if (cursor.pos() == cursorPar()->size())
1523 InsetOld * inset = cursorPar()->getInset(cursor.pos());
1524 if (!isHighlyEditableInset(inset))
1526 inset->edit(bv(), front);
1531 DispatchResult LyXText::moveRight()
1533 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1534 return moveLeftIntern(false, true, false);
1536 return moveRightIntern(true, true, false);
1540 DispatchResult LyXText::moveLeft()
1542 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1543 return moveRightIntern(true, true, false);
1545 return moveLeftIntern(false, true, false);
1549 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1551 ParagraphList::iterator c_par = cursorPar();
1552 if (boost::next(c_par) == paragraphs().end()
1553 && cursor.pos() >= c_par->size())
1554 return DispatchResult(false, FINISHED_RIGHT);
1555 if (activate_inset && checkAndActivateInset(front))
1556 return DispatchResult(true, true);
1560 return DispatchResult(true);
1564 DispatchResult LyXText::moveLeftIntern(bool front,
1565 bool activate_inset, bool selecting)
1567 if (cursor.par() == 0 && cursor.pos() <= 0)
1568 return DispatchResult(false, FINISHED);
1572 if (activate_inset && checkAndActivateInset(front))
1573 return DispatchResult(true, true);
1574 return DispatchResult(true);
1578 DispatchResult LyXText::moveUp()
1580 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1581 return DispatchResult(false, FINISHED_UP);
1584 return DispatchResult(true);
1588 DispatchResult LyXText::moveDown()
1590 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1591 return DispatchResult(false, FINISHED_DOWN);
1594 return DispatchResult(true);
1598 bool LyXText::cursorLeft(bool internal)
1600 if (cursor.pos() > 0) {
1601 bool boundary = cursor.boundary();
1602 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1603 if (!internal && !boundary &&
1604 bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1))
1605 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1609 if (cursor.par() != 0) {
1610 // steps into the paragraph above
1611 setCursor(cursor.par() - 1, boost::prior(cursorPar())->size());
1619 bool LyXText::cursorRight(bool internal)
1621 if (!internal && cursor.boundary()) {
1622 setCursor(cursor.par(), cursor.pos(), true, false);
1626 if (cursor.pos() != cursorPar()->size()) {
1627 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1628 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1630 setCursor(cursor.par(), cursor.pos(), true, true);
1634 if (cursor.par() + 1 != int(paragraphs().size())) {
1635 setCursor(cursor.par() + 1, 0);
1643 void LyXText::cursorUp(bool selecting)
1645 Row const & row = *cursorRow();
1646 int x = bv()->x_target() - xo_;
1647 int y = cursor.y() - row.baseline() - 1;
1648 setCursorFromCoordinates(x, y);
1651 int y_abs = y + yo_ - bv()->top_y();
1652 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1653 if (inset_hit && isHighlyEditableInset(inset_hit))
1654 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1659 void LyXText::cursorDown(bool selecting)
1661 Row const & row = *cursorRow();
1662 int x = bv()->x_target() - xo_;
1663 int y = cursor.y() - row.baseline() + row.height() + 1;
1664 setCursorFromCoordinates(x, y);
1667 int y_abs = y + yo_ - bv()->top_y();
1668 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1669 if (inset_hit && isHighlyEditableInset(inset_hit))
1670 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1675 void LyXText::cursorUpParagraph()
1677 ParagraphList::iterator cpit = cursorPar();
1678 if (cursor.pos() > 0)
1680 else if (cpit != paragraphs().begin())
1681 setCursor(boost::prior(cpit), 0);
1685 void LyXText::cursorDownParagraph()
1687 ParagraphList::iterator pit = cursorPar();
1688 ParagraphList::iterator next_pit = boost::next(pit);
1690 if (next_pit != paragraphs().end())
1691 setCursor(next_pit, 0);
1693 setCursor(pit, pit->size());
1697 // fix the cursor `cur' after a characters has been deleted at `where'
1698 // position. Called by deleteEmptyParagraphMechanism
1699 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1701 // if cursor is not in the paragraph where the delete occured,
1703 if (cur.par() != where.par())
1706 // if cursor position is after the place where the delete occured,
1708 if (cur.pos() > where.pos())
1709 cur.pos(cur.pos()-1);
1711 // check also if we don't want to set the cursor on a spot behind the
1712 // pagragraph because we erased the last character.
1713 if (cur.pos() > getPar(cur)->size())
1714 cur.pos(getPar(cur)->size());
1716 // recompute row et al. for this cursor
1717 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1721 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1723 // Would be wrong to delete anything if we have a selection.
1724 if (selection.set())
1727 // Don't do anything if the cursor is invalid
1728 if (old_cursor.par() == -1)
1731 // We allow all kinds of "mumbo-jumbo" when freespacing.
1732 ParagraphList::iterator const old_pit = getPar(old_cursor);
1733 if (old_pit->isFreeSpacing())
1736 /* Ok I'll put some comments here about what is missing.
1737 I have fixed BackSpace (and thus Delete) to not delete
1738 double-spaces automagically. I have also changed Cut,
1739 Copy and Paste to hopefully do some sensible things.
1740 There are still some small problems that can lead to
1741 double spaces stored in the document file or space at
1742 the beginning of paragraphs(). This happens if you have
1743 the cursor between to spaces and then save. Or if you
1744 cut and paste and the selection have a space at the
1745 beginning and then save right after the paste. I am
1746 sure none of these are very hard to fix, but I will
1747 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1748 that I can get some feedback. (Lgb)
1751 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1752 // delete the LineSeparator.
1755 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1756 // delete the LineSeparator.
1759 // If the pos around the old_cursor were spaces, delete one of them.
1760 if (old_cursor.par() != cursor.par()
1761 || old_cursor.pos() != cursor.pos()) {
1763 // Only if the cursor has really moved
1764 if (old_cursor.pos() > 0
1765 && old_cursor.pos() < old_pit->size()
1766 && old_pit->isLineSeparator(old_cursor.pos())
1767 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1768 bool erased = old_pit->erase(old_cursor.pos() - 1);
1769 redoParagraph(old_pit);
1773 #ifdef WITH_WARNINGS
1774 #warning This will not work anymore when we have multiple views of the same buffer
1775 // In this case, we will have to correct also the cursors held by
1776 // other bufferviews. It will probably be easier to do that in a more
1777 // automated way in LyXCursor code. (JMarc 26/09/2001)
1779 // correct all cursors held by the LyXText
1780 fixCursorAfterDelete(cursor, old_cursor);
1781 fixCursorAfterDelete(selection.cursor, old_cursor);
1782 fixCursorAfterDelete(selection.start, old_cursor);
1783 fixCursorAfterDelete(selection.end, old_cursor);
1788 // don't delete anything if this is the ONLY paragraph!
1789 if (paragraphs().size() == 1)
1792 // Do not delete empty paragraphs with keepempty set.
1793 if (old_pit->allowEmpty())
1796 // only do our magic if we changed paragraph
1797 if (old_cursor.par() == cursor.par())
1800 // record if we have deleted a paragraph
1801 // we can't possibly have deleted a paragraph before this point
1802 bool deleted = false;
1804 if (old_pit->empty()
1805 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1806 // ok, we will delete something
1807 LyXCursor tmpcursor;
1811 bool selection_position_was_oldcursor_position =
1812 selection.cursor.par() == old_cursor.par()
1813 && selection.cursor.pos() == old_cursor.pos();
1816 cursor = old_cursor; // that undo can restore the right cursor position
1818 ParagraphList::iterator endpit = boost::next(old_pit);
1819 while (endpit != paragraphs().end() && endpit->getDepth())
1822 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1826 ParagraphList::iterator tmppit = cursorPar();
1828 paragraphs().erase(old_pit);
1829 // update cursor par offset
1830 cursor.par(parOffset(tmppit));
1834 setCursorIntern(cursor.par(), cursor.pos());
1836 if (selection_position_was_oldcursor_position) {
1837 // correct selection
1838 selection.cursor = cursor;
1845 if (old_pit->stripLeadingSpaces()) {
1846 redoParagraph(old_pit);
1848 setCursorIntern(cursor.par(), cursor.pos());
1849 selection.cursor = cursor;
1855 ParagraphList & LyXText::paragraphs() const
1857 return const_cast<ParagraphList &>(paragraphs_);
1861 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1863 recordUndo(Undo::ATOMIC, this, first, last);
1867 void LyXText::recUndo(lyx::paroffset_type par) const
1869 recordUndo(Undo::ATOMIC, this, par, par);
1873 bool LyXText::isInInset() const
1879 int defaultRowHeight()
1881 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);