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(ParagraphList::iterator start,
273 ParagraphList::iterator end,
274 string const & layout)
276 ParagraphList::iterator undopit = end;
277 ParagraphList::iterator pars_end = paragraphs().end();
279 while (undopit != pars_end && undopit->getDepth())
281 //because of parindets etc
282 if (undopit != pars_end)
284 recUndo(parOffset(start), parOffset(undopit) - 1);
286 BufferParams const & bufparams = bv()->buffer()->params();
287 LyXLayout_ptr const & lyxlayout =
288 bufparams.getLyXTextClass()[layout];
290 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
291 pit->applyLayout(lyxlayout);
292 makeFontEntriesLayoutSpecific(bufparams, *pit);
293 if (lyxlayout->margintype == MARGIN_MANUAL)
294 pit->setLabelWidthString(lyxlayout->labelstring());
301 // set layout over selection and make a total rebreak of those paragraphs
302 void LyXText::setLayout(string const & layout)
304 // special handling of new environment insets
305 BufferParams const & params = bv()->buffer()->params();
306 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
307 if (lyxlayout->is_environment) {
308 // move everything in a new environment inset
309 lyxerr << "setting layout " << layout << endl;
310 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
311 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
312 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
313 InsetOld * inset = new InsetEnvironment(params, layout);
314 if (bv()->insertInset(inset)) {
316 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
322 ParagraphList::iterator start = getPar(selStart().par());
323 ParagraphList::iterator end = boost::next(getPar(selEnd().par()));
324 ParagraphList::iterator endpit = setLayout(start, end, layout);
326 redoParagraphs(start, endpit);
334 void getSelectionSpan(LyXText & text,
335 ParagraphList::iterator & beg,
336 ParagraphList::iterator & end)
338 if (!text.selection.set()) {
339 beg = text.cursorPar();
340 end = boost::next(beg);
342 beg = text.getPar(text.selStart());
343 end = boost::next(text.getPar(text.selEnd()));
348 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
349 Paragraph const & par,
352 if (par.layout()->labeltype == LABEL_BIBLIO)
354 int const depth = par.params().depth();
355 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
357 if (type == bv_funcs::DEC_DEPTH && depth > 0)
366 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
368 ParagraphList::iterator beg, end;
369 getSelectionSpan(*this, beg, end);
371 if (beg != paragraphs().begin())
372 max_depth = boost::prior(beg)->getMaxDepthAfter();
374 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
375 if (::changeDepthAllowed(type, *pit, max_depth))
377 max_depth = pit->getMaxDepthAfter();
383 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
385 ParagraphList::iterator beg, end;
386 getSelectionSpan(*this, beg, end);
388 recUndo(parOffset(beg), parOffset(end) - 1);
391 if (beg != paragraphs().begin())
392 max_depth = boost::prior(beg)->getMaxDepthAfter();
394 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
395 if (::changeDepthAllowed(type, *pit, max_depth)) {
396 int const depth = pit->params().depth();
397 if (type == bv_funcs::INC_DEPTH)
398 pit->params().depth(depth + 1);
400 pit->params().depth(depth - 1);
402 max_depth = pit->getMaxDepthAfter();
404 // this handles the counter labels, and also fixes up
405 // depth values for follow-on (child) paragraphs
410 // set font over selection and make a total rebreak of those paragraphs
411 void LyXText::setFont(LyXFont const & font, bool toggleall)
413 // if there is no selection just set the current_font
414 if (!selection.set()) {
415 // Determine basis font
417 if (cursor.pos() < cursorPar()->beginOfBody())
418 layoutfont = getLabelFont(cursorPar());
420 layoutfont = getLayoutFont(cursorPar());
422 // Update current font
423 real_current_font.update(font,
424 bv()->buffer()->params().language,
427 // Reduce to implicit settings
428 current_font = real_current_font;
429 current_font.reduce(layoutfont);
430 // And resolve it completely
431 real_current_font.realize(layoutfont);
436 // ok we have a selection.
437 recUndo(selStart().par(), selEnd().par());
440 ParagraphList::iterator beg = getPar(selStart().par());
441 ParagraphList::iterator end = getPar(selEnd().par());
443 PosIterator pos(¶graphs(), beg, selStart().pos());
444 PosIterator posend(¶graphs(), end, selEnd().pos());
446 BufferParams const & params = bv()->buffer()->params();
448 for (; pos != posend; ++pos) {
449 LyXFont f = getFont(pos.pit(), pos.pos());
450 f.update(font, params.language, toggleall);
451 setCharFont(pos.pit(), pos.pos(), f);
456 redoParagraphs(beg, ++end);
460 // important for the screen
463 // the cursor set functions have a special mechanism. When they
464 // realize, that you left an empty paragraph, they will delete it.
466 // need the selection cursor:
467 void LyXText::setSelection()
469 TextCursor::setSelection();
473 void LyXText::clearSelection()
475 TextCursor::clearSelection();
477 // reset this in the bv()!
478 if (bv() && bv()->text())
483 void LyXText::cursorHome()
485 ParagraphList::iterator cpit = cursorPar();
486 setCursor(cpit, cpit->getRow(cursor.pos())->pos());
490 void LyXText::cursorEnd()
492 ParagraphList::iterator cpit = cursorPar();
493 pos_type end = cpit->getRow(cursor.pos())->endpos();
494 // if not on the last row of the par, put the cursor before
496 setCursor(cpit, end == cpit->size() ? end : end - 1);
500 void LyXText::cursorTop()
502 setCursor(paragraphs().begin(), 0);
506 void LyXText::cursorBottom()
508 ParagraphList::iterator lastpit =
509 boost::prior(paragraphs().end());
510 setCursor(lastpit, lastpit->size());
514 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
516 // If the mask is completely neutral, tell user
517 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
518 // Could only happen with user style
519 bv()->owner()->message(_("No font change defined. "
520 "Use Character under the Layout menu to define font change."));
524 // Try implicit word selection
525 // If there is a change in the language the implicit word selection
527 LyXCursor resetCursor = cursor;
528 bool implicitSelection =
529 font.language() == ignore_language
530 && font.number() == LyXFont::IGNORE
531 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
534 setFont(font, toggleall);
536 // Implicit selections are cleared afterwards
537 //and cursor is set to the original position.
538 if (implicitSelection) {
540 cursor = resetCursor;
541 selection.cursor = cursor;
546 string LyXText::getStringToIndex()
548 // Try implicit word selection
549 // If there is a change in the language the implicit word selection
551 LyXCursor const reset_cursor = cursor;
552 bool const implicitSelection =
553 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
556 if (!selection.set())
557 bv()->owner()->message(_("Nothing to index!"));
558 else if (selStart().par() != selEnd().par())
559 bv()->owner()->message(_("Cannot index more than one paragraph!"));
561 idxstring = selectionAsString(*bv()->buffer(), false);
563 // Reset cursors to their original position.
564 cursor = reset_cursor;
565 selection.cursor = cursor;
567 // Clear the implicit selection.
568 if (implicitSelection)
575 // the DTP switches for paragraphs(). LyX will store them in the first
576 // physical paragraph. When a paragraph is broken, the top settings rest,
577 // the bottom settings are given to the new one. So I can make sure,
578 // they do not duplicate themself and you cannot play dirty tricks with
581 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
582 string const & labelwidthstring, bool noindent)
584 // make sure that the depth behind the selection are restored, too
585 ParagraphList::iterator endpit = boost::next(getPar(selEnd()));
586 ParagraphList::iterator pars_end = paragraphs().end();
588 while (endpit != pars_end && endpit->getDepth())
590 // because of parindents etc.
591 if (endpit != pars_end)
594 recUndo(selStart().par(), parOffset(endpit) - 1);
596 ParagraphList::reverse_iterator pit(getPar(selEnd().par()));
597 ParagraphList::reverse_iterator beg(getPar(selStart().par()));
599 for (--pit; pit != beg; ++pit) {
600 ParagraphParameters & params = pit->params();
601 params.spacing(spacing);
603 // does the layout allow the new alignment?
604 LyXLayout_ptr const & layout = pit->layout();
606 if (align == LYX_ALIGN_LAYOUT)
607 align = layout->align;
608 if (align & layout->alignpossible) {
609 if (align == layout->align)
610 params.align(LYX_ALIGN_LAYOUT);
614 pit->setLabelWidthString(labelwidthstring);
615 params.noindent(noindent);
618 redoParagraphs(getPar(selStart()), endpit);
622 string expandLabel(LyXTextClass const & textclass,
623 LyXLayout_ptr const & layout, bool appendix)
625 string fmt = appendix ?
626 layout->labelstring_appendix() : layout->labelstring();
628 // handle 'inherited level parts' in 'fmt',
629 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
630 size_t const i = fmt.find('@', 0);
631 if (i != string::npos) {
632 size_t const j = fmt.find('@', i + 1);
633 if (j != string::npos) {
634 string parent(fmt, i + 1, j - i - 1);
635 string label = expandLabel(textclass, textclass[parent], appendix);
636 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
640 return textclass.counters().counterLabel(fmt);
646 void incrementItemDepth(ParagraphList::iterator pit,
647 ParagraphList::iterator first_pit)
649 int const cur_labeltype = pit->layout()->labeltype;
651 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
654 int const cur_depth = pit->getDepth();
656 ParagraphList::iterator prev_pit = boost::prior(pit);
658 int const prev_depth = prev_pit->getDepth();
659 int const prev_labeltype = prev_pit->layout()->labeltype;
660 if (prev_depth == 0 && cur_depth > 0) {
661 if (prev_labeltype == cur_labeltype) {
662 pit->itemdepth = prev_pit->itemdepth + 1;
665 } else if (prev_depth < cur_depth) {
666 if (prev_labeltype == cur_labeltype) {
667 pit->itemdepth = prev_pit->itemdepth + 1;
670 } else if (prev_depth == cur_depth) {
671 if (prev_labeltype == cur_labeltype) {
672 pit->itemdepth = prev_pit->itemdepth;
676 if (prev_pit == first_pit)
684 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
685 ParagraphList::iterator firstpit,
691 int const cur_depth = pit->getDepth();
692 ParagraphList::iterator prev_pit = boost::prior(pit);
694 int const prev_depth = prev_pit->getDepth();
695 int const prev_labeltype = prev_pit->layout()->labeltype;
696 if (prev_depth <= cur_depth) {
697 if (prev_labeltype != LABEL_ENUMERATE) {
698 switch (pit->itemdepth) {
700 counters.reset("enumi");
702 counters.reset("enumii");
704 counters.reset("enumiii");
706 counters.reset("enumiv");
712 if (prev_pit == firstpit)
722 // set the counter of a paragraph. This includes the labels
723 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
725 BufferParams const & bufparams = buf.params();
726 LyXTextClass const & textclass = bufparams.getLyXTextClass();
727 LyXLayout_ptr const & layout = pit->layout();
728 ParagraphList::iterator first_pit = paragraphs().begin();
729 Counters & counters = textclass.counters();
734 if (pit == first_pit) {
735 pit->params().appendix(pit->params().startOfAppendix());
737 pit->params().appendix(boost::prior(pit)->params().appendix());
738 if (!pit->params().appendix() &&
739 pit->params().startOfAppendix()) {
740 pit->params().appendix(true);
741 textclass.counters().reset();
744 // Maybe we have to increment the item depth.
745 incrementItemDepth(pit, first_pit);
748 // erase what was there before
749 pit->params().labelString(string());
751 if (layout->margintype == MARGIN_MANUAL) {
752 if (pit->params().labelWidthString().empty())
753 pit->setLabelWidthString(layout->labelstring());
755 pit->setLabelWidthString(string());
758 // is it a layout that has an automatic label?
759 if (layout->labeltype == LABEL_COUNTER) {
760 BufferParams const & bufparams = buf.params();
761 LyXTextClass const & textclass = bufparams.getLyXTextClass();
762 counters.step(layout->counter);
763 string label = expandLabel(textclass, layout, pit->params().appendix());
764 pit->params().labelString(label);
765 } else if (layout->labeltype == LABEL_ITEMIZE) {
766 // At some point of time we should do something more
767 // clever here, like:
768 // pit->params().labelString(
769 // bufparams.user_defined_bullet(pit->itemdepth).getText());
770 // for now, use a simple hardcoded label
772 switch (pit->itemdepth) {
787 pit->params().labelString(itemlabel);
788 } else if (layout->labeltype == LABEL_ENUMERATE) {
789 // Maybe we have to reset the enumeration counter.
790 resetEnumCounterIfNeeded(pit, first_pit, counters);
793 // Yes I know this is a really, really! bad solution
795 string enumcounter = "enum";
797 switch (pit->itemdepth) {
809 // not a valid enumdepth...
813 counters.step(enumcounter);
815 pit->params().labelString(counters.enumLabel(enumcounter));
816 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
817 counters.step("bibitem");
818 int number = counters.value("bibitem");
819 if (pit->bibitem()) {
820 pit->bibitem()->setCounter(number);
821 pit->params().labelString(layout->labelstring());
823 // In biblio should't be following counters but...
825 string s = buf.B_(layout->labelstring());
828 if (layout->labeltype == LABEL_SENSITIVE) {
829 ParagraphList::iterator end = paragraphs().end();
830 ParagraphList::iterator tmppit = pit;
833 while (tmppit != end && tmppit->inInset()
834 // the single '=' is intended below
835 && (in = tmppit->inInset()->owner()))
837 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
838 in->lyxCode() == InsetOld::WRAP_CODE) {
842 Paragraph const * owner = &ownerPar(buf, in);
844 for ( ; tmppit != end; ++tmppit)
845 if (&*tmppit == owner)
853 if (in->lyxCode() == InsetOld::FLOAT_CODE)
854 type = static_cast<InsetFloat*>(in)->params().type;
855 else if (in->lyxCode() == InsetOld::WRAP_CODE)
856 type = static_cast<InsetWrap*>(in)->params().type;
860 Floating const & fl = textclass.floats().getType(type);
862 counters.step(fl.type());
864 // Doesn't work... yet.
865 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
867 // par->SetLayout(0);
868 // s = layout->labelstring;
869 s = _("Senseless: ");
872 pit->params().labelString(s);
878 // Updates all counters.
879 void LyXText::updateCounters()
882 bv()->buffer()->params().getLyXTextClass().counters().reset();
884 bool update_pos = false;
886 ParagraphList::iterator beg = paragraphs().begin();
887 ParagraphList::iterator end = paragraphs().end();
888 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
889 string const oldLabel = pit->params().labelString();
892 maxdepth = boost::prior(pit)->getMaxDepthAfter();
894 if (pit->params().depth() > maxdepth)
895 pit->params().depth(maxdepth);
897 // setCounter can potentially change the labelString.
898 setCounter(*bv()->buffer(), pit);
899 string const & newLabel = pit->params().labelString();
900 if (oldLabel != newLabel) {
901 redoParagraphInternal(pit);
907 updateParPositions();
911 void LyXText::insertInset(InsetOld * inset)
913 if (!cursorPar()->insetAllowed(inset->lyxCode()))
916 recUndo(cursor.par());
918 cursorPar()->insertInset(cursor.pos(), inset);
919 // Just to rebreak and refresh correctly.
920 // The character will not be inserted a second time
921 insertChar(Paragraph::META_INSET);
922 // If we enter a highly editable inset the cursor should be before
923 // the inset. After an undo LyX tries to call inset->edit(...)
924 // and fails if the cursor is behind the inset and getInset
925 // does not return the inset!
926 if (isHighlyEditableInset(inset))
933 void LyXText::cutSelection(bool doclear, bool realcut)
935 // Stuff what we got on the clipboard. Even if there is no selection.
937 // There is a problem with having the stuffing here in that the
938 // larger the selection the slower LyX will get. This can be
939 // solved by running the line below only when the selection has
940 // finished. The solution used currently just works, to make it
941 // faster we need to be more clever and probably also have more
942 // calls to stuffClipboard. (Lgb)
943 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
945 // This doesn't make sense, if there is no selection
946 if (!selection.set())
949 // OK, we have a selection. This is always between selStart()
952 // make sure that the depth behind the selection are restored, too
953 ParagraphList::iterator begpit = getPar(selStart().par());
954 ParagraphList::iterator endpit = getPar(selEnd().par());
955 ParagraphList::iterator undopit = boost::next(endpit);
956 ParagraphList::iterator pars_end = paragraphs().end();
958 while (undopit != pars_end && undopit->getDepth())
960 //because of parindents etc.
961 if (undopit != pars_end)
963 recUndo(selStart().par(), parOffset(undopit) - 1);
965 int endpos = selEnd().pos();
967 BufferParams const & bufparams = bv()->buffer()->params();
968 boost::tie(endpit, endpos) = realcut ?
969 CutAndPaste::cutSelection(bufparams,
972 selStart().pos(), endpos,
975 : CutAndPaste::eraseSelection(bufparams,
978 selStart().pos(), endpos,
980 // sometimes necessary
982 begpit->stripLeadingSpaces();
984 redoParagraphs(begpit, undopit);
985 // cutSelection can invalidate the cursor so we need to set
987 // we prefer the end for when tracking changes
989 cursor.par(parOffset(endpit));
991 // need a valid cursor. (Lgb)
997 void LyXText::copySelection()
999 // stuff the selection onto the X clipboard, from an explicit copy request
1000 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1002 // this doesnt make sense, if there is no selection
1003 if (!selection.set())
1006 // ok we have a selection. This is always between selStart()
1007 // and sel_end cursor
1009 // copy behind a space if there is one
1010 while (getPar(selStart())->size() > selStart().pos()
1011 && getPar(selStart())->isLineSeparator(selStart().pos())
1012 && (selStart().par() != selEnd().par()
1013 || selStart().pos() < selEnd().pos()))
1014 selStart().pos(selStart().pos() + 1);
1016 CutAndPaste::copySelection(getPar(selStart().par()),
1017 getPar(selEnd().par()),
1020 bv()->buffer()->params().textclass);
1024 void LyXText::pasteSelection(size_t sel_index)
1026 // this does not make sense, if there is nothing to paste
1027 if (!CutAndPaste::checkPastePossible())
1030 recUndo(cursor.par());
1032 ParagraphList::iterator endpit;
1037 boost::tie(ppp, endpit) =
1038 CutAndPaste::pasteSelection(*bv()->buffer(),
1040 cursorPar(), cursor.pos(),
1041 bv()->buffer()->params().textclass,
1043 bufferErrors(*bv()->buffer(), el);
1044 bv()->showErrorList(_("Paste"));
1046 redoParagraphs(cursorPar(), endpit);
1050 selection.cursor = cursor;
1051 setCursor(ppp.first, ppp.second);
1057 void LyXText::setSelectionRange(lyx::pos_type length)
1062 selection.cursor = cursor;
1069 // simple replacing. The font of the first selected character is used
1070 void LyXText::replaceSelectionWithString(string const & str)
1072 recUndo(cursor.par());
1075 // Get font setting before we cut
1076 pos_type pos = selEnd().pos();
1077 LyXFont const font = getPar(selStart())
1078 ->getFontSettings(bv()->buffer()->params(),
1081 // Insert the new string
1082 string::const_iterator cit = str.begin();
1083 string::const_iterator end = str.end();
1084 for (; cit != end; ++cit) {
1085 getPar(selEnd())->insertChar(pos, (*cit), font);
1089 // Cut the selection
1090 cutSelection(true, false);
1096 // needed to insert the selection
1097 void LyXText::insertStringAsLines(string const & str)
1099 ParagraphList::iterator pit = cursorPar();
1100 pos_type pos = cursor.pos();
1101 ParagraphList::iterator endpit = boost::next(cursorPar());
1103 recUndo(cursor.par());
1105 // only to be sure, should not be neccessary
1108 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1110 redoParagraphs(cursorPar(), endpit);
1111 selection.cursor = cursor;
1112 setCursor(pit, pos);
1117 // turn double CR to single CR, others are converted into one
1118 // blank. Then insertStringAsLines is called
1119 void LyXText::insertStringAsParagraphs(string const & str)
1121 string linestr(str);
1122 bool newline_inserted = false;
1123 string::size_type const siz = linestr.length();
1125 for (string::size_type i = 0; i < siz; ++i) {
1126 if (linestr[i] == '\n') {
1127 if (newline_inserted) {
1128 // we know that \r will be ignored by
1129 // insertStringAsLines. Of course, it is a dirty
1130 // trick, but it works...
1131 linestr[i - 1] = '\r';
1135 newline_inserted = true;
1137 } else if (IsPrintable(linestr[i])) {
1138 newline_inserted = false;
1141 insertStringAsLines(linestr);
1145 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1147 setCursor(parOffset(pit), pos);
1151 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1154 LyXCursor old_cursor = cursor;
1155 setCursorIntern(par, pos, setfont, boundary);
1156 return deleteEmptyParagraphMechanism(old_cursor);
1160 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1161 pos_type pos, bool boundary)
1163 BOOST_ASSERT(par != int(paragraphs().size()));
1167 cur.boundary(boundary);
1169 // no rows, no fun...
1170 if (paragraphs().begin()->rows.empty())
1173 // get the cursor y position in text
1175 ParagraphList::iterator pit = getPar(par);
1176 Row const & row = *pit->getRow(pos);
1177 pos_type const end = row.endpos();
1179 // None of these should happen, but we're scaredy-cats
1181 lyxerr << "dont like -1" << endl;
1184 BOOST_ASSERT(false);
1185 } else if (pos > pit->size()) {
1186 lyxerr << "dont like 1, pos: " << pos
1187 << " size: " << pit->size()
1188 << " row.pos():" << row.pos()
1189 << " paroffset: " << par << endl;
1192 BOOST_ASSERT(false);
1193 } else if (pos > end) {
1194 lyxerr << "dont like 2 please report" << endl;
1195 // This shouldn't happen.
1198 BOOST_ASSERT(false);
1199 } else if (pos < row.pos()) {
1200 lyxerr << "dont like 3 please report pos:" << pos
1201 << " size: " << pit->size()
1202 << " row.pos():" << row.pos()
1203 << " paroffset: " << par << endl;
1206 BOOST_ASSERT(false);
1211 void LyXText::setCursorIntern(paroffset_type par,
1212 pos_type pos, bool setfont, bool boundary)
1214 setCursor(cursor, par, pos, boundary);
1215 bv()->x_target(cursorX() + xo_);
1221 void LyXText::setCurrentFont()
1223 pos_type pos = cursor.pos();
1224 ParagraphList::iterator pit = cursorPar();
1226 if (cursor.boundary() && pos > 0)
1230 if (pos == pit->size())
1232 else // potentional bug... BUG (Lgb)
1233 if (pit->isSeparator(pos)) {
1234 if (pos > pit->getRow(pos)->pos() &&
1235 bidi.level(pos) % 2 ==
1236 bidi.level(pos - 1) % 2)
1238 else if (pos + 1 < pit->size())
1243 BufferParams const & bufparams = bv()->buffer()->params();
1244 current_font = pit->getFontSettings(bufparams, pos);
1245 real_current_font = getFont(pit, pos);
1247 if (cursor.pos() == pit->size() &&
1248 bidi.isBoundary(*bv()->buffer(), *pit, cursor.pos()) &&
1249 !cursor.boundary()) {
1250 Language const * lang =
1251 pit->getParLanguage(bufparams);
1252 current_font.setLanguage(lang);
1253 current_font.setNumber(LyXFont::OFF);
1254 real_current_font.setLanguage(lang);
1255 real_current_font.setNumber(LyXFont::OFF);
1260 // returns the column near the specified x-coordinate of the row
1261 // x is set to the real beginning of this column
1262 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1263 Row const & row, int & x, bool & boundary) const
1265 double tmpx = row.x();
1266 double fill_separator = row.fill_separator();
1267 double fill_hfill = row.fill_hfill();
1268 double fill_label_hfill = row.fill_label_hfill();
1270 pos_type vc = row.pos();
1271 pos_type end = row.endpos();
1273 LyXLayout_ptr const & layout = pit->layout();
1275 bool left_side = false;
1277 pos_type body_pos = pit->beginOfBody();
1278 double last_tmpx = tmpx;
1281 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1284 // check for empty row
1290 while (vc < end && tmpx <= x) {
1291 c = bidi.vis2log(vc);
1293 if (body_pos > 0 && c == body_pos - 1) {
1294 tmpx += fill_label_hfill +
1295 font_metrics::width(layout->labelsep, getLabelFont(pit));
1296 if (pit->isLineSeparator(body_pos - 1))
1297 tmpx -= singleWidth(pit, body_pos - 1);
1300 if (hfillExpansion(*pit, row, c)) {
1301 tmpx += singleWidth(pit, c);
1305 tmpx += fill_label_hfill;
1306 } else if (pit->isSeparator(c)) {
1307 tmpx += singleWidth(pit, c);
1309 tmpx += fill_separator;
1311 tmpx += singleWidth(pit, c);
1316 if ((tmpx + last_tmpx) / 2 > x) {
1321 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1324 // This (rtl_support test) is not needed, but gives
1325 // some speedup if rtl_support == false
1326 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1328 // If lastrow is false, we don't need to compute
1329 // the value of rtl.
1330 bool const rtl = (lastrow)
1331 ? pit->isRightToLeftPar(bv()->buffer()->params())
1334 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1335 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1337 else if (vc == row.pos()) {
1338 c = bidi.vis2log(vc);
1339 if (bidi.level(c) % 2 == 1)
1342 c = bidi.vis2log(vc - 1);
1343 bool const rtl = (bidi.level(c) % 2 == 1);
1344 if (left_side == rtl) {
1346 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1350 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1351 if (bidi.level(end -1) % 2 == 0)
1352 tmpx -= singleWidth(pit, end - 1);
1354 tmpx += singleWidth(pit, end - 1);
1364 void LyXText::setCursorFromCoordinates(int x, int y)
1366 LyXCursor old_cursor = cursor;
1367 setCursorFromCoordinates(cursor, x, y);
1369 deleteEmptyParagraphMechanism(old_cursor);
1373 // x,y are coordinates relative to this LyXText
1374 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1376 ParagraphList::iterator pit;
1377 Row const & row = *getRowNearY(y, pit);
1379 pos_type const column = getColumnNearX(pit, row, x, bound);
1380 cur.par(parOffset(pit));
1381 cur.pos(row.pos() + column);
1382 cur.boundary(bound);
1386 bool LyXText::checkAndActivateInset(bool front)
1388 if (cursor.pos() == cursorPar()->size())
1390 InsetOld * inset = cursorPar()->getInset(cursor.pos());
1391 if (!isHighlyEditableInset(inset))
1393 inset->edit(bv(), front);
1398 DispatchResult LyXText::moveRight()
1400 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1401 return moveLeftIntern(false, true, false);
1403 return moveRightIntern(true, true, false);
1407 DispatchResult LyXText::moveLeft()
1409 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1410 return moveRightIntern(true, true, false);
1412 return moveLeftIntern(false, true, false);
1416 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1418 ParagraphList::iterator c_par = cursorPar();
1419 if (boost::next(c_par) == paragraphs().end()
1420 && cursor.pos() >= c_par->size())
1421 return DispatchResult(false, FINISHED_RIGHT);
1422 if (activate_inset && checkAndActivateInset(front))
1423 return DispatchResult(true, true);
1427 return DispatchResult(true);
1431 DispatchResult LyXText::moveLeftIntern(bool front,
1432 bool activate_inset, bool selecting)
1434 if (cursor.par() == 0 && cursor.pos() <= 0)
1435 return DispatchResult(false, FINISHED);
1439 if (activate_inset && checkAndActivateInset(front))
1440 return DispatchResult(true, true);
1441 return DispatchResult(true);
1445 DispatchResult LyXText::moveUp()
1447 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1448 return DispatchResult(false, FINISHED_UP);
1451 return DispatchResult(true);
1455 DispatchResult LyXText::moveDown()
1457 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1458 return DispatchResult(false, FINISHED_DOWN);
1461 return DispatchResult(true);
1465 bool LyXText::cursorLeft(bool internal)
1467 if (cursor.pos() > 0) {
1468 bool boundary = cursor.boundary();
1469 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1470 if (!internal && !boundary &&
1471 bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1))
1472 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1476 if (cursor.par() != 0) {
1477 // steps into the paragraph above
1478 setCursor(cursor.par() - 1, boost::prior(cursorPar())->size());
1486 bool LyXText::cursorRight(bool internal)
1488 if (!internal && cursor.boundary()) {
1489 setCursor(cursor.par(), cursor.pos(), true, false);
1493 if (cursor.pos() != cursorPar()->size()) {
1494 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1495 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1497 setCursor(cursor.par(), cursor.pos(), true, true);
1501 if (cursor.par() + 1 != int(paragraphs().size())) {
1502 setCursor(cursor.par() + 1, 0);
1510 void LyXText::cursorUp(bool selecting)
1512 Row const & row = *cursorRow();
1513 int x = bv()->x_target() - xo_;
1514 int y = cursorY() - row.baseline() - 1;
1515 setCursorFromCoordinates(x, y);
1518 int y_abs = y + yo_ - bv()->top_y();
1519 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1520 if (inset_hit && isHighlyEditableInset(inset_hit))
1521 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1526 void LyXText::cursorDown(bool selecting)
1528 Row const & row = *cursorRow();
1529 int x = bv()->x_target() - xo_;
1530 int y = cursorY() - row.baseline() + row.height() + 1;
1531 setCursorFromCoordinates(x, y);
1534 int y_abs = y + yo_ - bv()->top_y();
1535 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1536 if (inset_hit && isHighlyEditableInset(inset_hit))
1537 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1542 void LyXText::cursorUpParagraph()
1544 ParagraphList::iterator cpit = cursorPar();
1545 if (cursor.pos() > 0)
1547 else if (cpit != paragraphs().begin())
1548 setCursor(boost::prior(cpit), 0);
1552 void LyXText::cursorDownParagraph()
1554 ParagraphList::iterator pit = cursorPar();
1555 ParagraphList::iterator next_pit = boost::next(pit);
1557 if (next_pit != paragraphs().end())
1558 setCursor(next_pit, 0);
1560 setCursor(pit, pit->size());
1564 // fix the cursor `cur' after a characters has been deleted at `where'
1565 // position. Called by deleteEmptyParagraphMechanism
1566 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1568 // if cursor is not in the paragraph where the delete occured,
1570 if (cur.par() != where.par())
1573 // if cursor position is after the place where the delete occured,
1575 if (cur.pos() > where.pos())
1576 cur.pos(cur.pos()-1);
1578 // check also if we don't want to set the cursor on a spot behind the
1579 // pagragraph because we erased the last character.
1580 if (cur.pos() > getPar(cur)->size())
1581 cur.pos(getPar(cur)->size());
1585 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1587 // Would be wrong to delete anything if we have a selection.
1588 if (selection.set())
1591 // Don't do anything if the cursor is invalid
1592 if (old_cursor.par() == -1)
1595 // We allow all kinds of "mumbo-jumbo" when freespacing.
1596 ParagraphList::iterator const old_pit = getPar(old_cursor);
1597 if (old_pit->isFreeSpacing())
1600 /* Ok I'll put some comments here about what is missing.
1601 I have fixed BackSpace (and thus Delete) to not delete
1602 double-spaces automagically. I have also changed Cut,
1603 Copy and Paste to hopefully do some sensible things.
1604 There are still some small problems that can lead to
1605 double spaces stored in the document file or space at
1606 the beginning of paragraphs(). This happens if you have
1607 the cursor between to spaces and then save. Or if you
1608 cut and paste and the selection have a space at the
1609 beginning and then save right after the paste. I am
1610 sure none of these are very hard to fix, but I will
1611 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1612 that I can get some feedback. (Lgb)
1615 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1616 // delete the LineSeparator.
1619 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1620 // delete the LineSeparator.
1623 // If the pos around the old_cursor were spaces, delete one of them.
1624 if (old_cursor.par() != cursor.par()
1625 || old_cursor.pos() != cursor.pos()) {
1627 // Only if the cursor has really moved
1628 if (old_cursor.pos() > 0
1629 && old_cursor.pos() < old_pit->size()
1630 && old_pit->isLineSeparator(old_cursor.pos())
1631 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1632 bool erased = old_pit->erase(old_cursor.pos() - 1);
1633 redoParagraph(old_pit);
1637 #ifdef WITH_WARNINGS
1638 #warning This will not work anymore when we have multiple views of the same buffer
1639 // In this case, we will have to correct also the cursors held by
1640 // other bufferviews. It will probably be easier to do that in a more
1641 // automated way in LyXCursor code. (JMarc 26/09/2001)
1643 // correct all cursors held by the LyXText
1644 fixCursorAfterDelete(cursor, old_cursor);
1645 fixCursorAfterDelete(selection.cursor, old_cursor);
1650 // don't delete anything if this is the ONLY paragraph!
1651 if (paragraphs().size() == 1)
1654 // Do not delete empty paragraphs with keepempty set.
1655 if (old_pit->allowEmpty())
1658 // only do our magic if we changed paragraph
1659 if (old_cursor.par() == cursor.par())
1662 // record if we have deleted a paragraph
1663 // we can't possibly have deleted a paragraph before this point
1664 bool deleted = false;
1666 if (old_pit->empty()
1667 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1668 // ok, we will delete something
1669 LyXCursor tmpcursor;
1673 bool selection_position_was_oldcursor_position =
1674 selection.cursor.par() == old_cursor.par()
1675 && selection.cursor.pos() == old_cursor.pos();
1678 cursor = old_cursor; // that undo can restore the right cursor position
1680 ParagraphList::iterator endpit = boost::next(old_pit);
1681 while (endpit != paragraphs().end() && endpit->getDepth())
1684 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1688 ParagraphList::iterator tmppit = cursorPar();
1690 paragraphs().erase(old_pit);
1691 // update cursor par offset
1692 cursor.par(parOffset(tmppit));
1695 if (selection_position_was_oldcursor_position) {
1696 // correct selection
1697 selection.cursor = cursor;
1704 if (old_pit->stripLeadingSpaces()) {
1705 redoParagraph(old_pit);
1706 selection.cursor = cursor;
1712 ParagraphList & LyXText::paragraphs() const
1714 return const_cast<ParagraphList &>(paragraphs_);
1718 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1720 recordUndo(Undo::ATOMIC, this, first, last);
1724 void LyXText::recUndo(lyx::paroffset_type par) const
1726 recordUndo(Undo::ATOMIC, this, par, par);
1730 bool LyXText::isInInset() const
1736 int defaultRowHeight()
1738 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);