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 * bv)
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 bv->cursor().resetAnchor();
103 // Gets the fully instantiated font at a given position in a paragraph
104 // Basically the same routine as Paragraph::getFont() in paragraph.C.
105 // The difference is that this one is used for displaying, and thus we
106 // are allowed to make cosmetic improvements. For instance make footnotes
108 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
110 BOOST_ASSERT(pos >= 0);
112 LyXLayout_ptr const & layout = pit->layout();
114 BufferParams const & params = bv()->buffer()->params();
115 pos_type const body_pos = pit->beginOfBody();
117 // We specialize the 95% common case:
118 if (!pit->getDepth()) {
119 LyXFont f = pit->getFontSettings(params, pos);
122 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
123 return f.realize(layout->reslabelfont);
125 return f.realize(layout->resfont);
128 // The uncommon case need not be optimized as much
131 layoutfont = layout->labelfont;
133 layoutfont = layout->font;
135 LyXFont font = pit->getFontSettings(params, pos);
136 font.realize(layoutfont);
141 // Realize with the fonts of lesser depth.
142 //font.realize(outerFont(pit, paragraphs()));
143 font.realize(defaultfont_);
149 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
151 LyXLayout_ptr const & layout = pit->layout();
153 if (!pit->getDepth())
154 return layout->resfont;
156 LyXFont font = layout->font;
157 // Realize with the fonts of lesser depth.
158 //font.realize(outerFont(pit, paragraphs()));
159 font.realize(defaultfont_);
165 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
167 LyXLayout_ptr const & layout = pit->layout();
169 if (!pit->getDepth())
170 return layout->reslabelfont;
172 LyXFont font = layout->labelfont;
173 // Realize with the fonts of lesser depth.
174 font.realize(outerFont(pit, paragraphs()));
175 font.realize(defaultfont_);
181 void LyXText::setCharFont(
182 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
185 LyXLayout_ptr const & layout = pit->layout();
187 // Get concrete layout font to reduce against
190 if (pos < pit->beginOfBody())
191 layoutfont = layout->labelfont;
193 layoutfont = layout->font;
195 // Realize against environment font information
196 if (pit->getDepth()) {
197 ParagraphList::iterator tp = pit;
198 while (!layoutfont.resolved() &&
199 tp != paragraphs().end() &&
201 tp = outerHook(tp, paragraphs());
202 if (tp != paragraphs().end())
203 layoutfont.realize(tp->layout()->font);
207 layoutfont.realize(defaultfont_);
209 // Now, reduce font against full layout font
210 font.reduce(layoutfont);
212 pit->setFont(pos, font);
217 // Asger is not sure we want to do this...
218 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
221 LyXLayout_ptr const & layout = par.layout();
222 pos_type const psize = par.size();
225 for (pos_type pos = 0; pos < psize; ++pos) {
226 if (pos < par.beginOfBody())
227 layoutfont = layout->labelfont;
229 layoutfont = layout->font;
231 LyXFont tmpfont = par.getFontSettings(params, pos);
232 tmpfont.reduce(layoutfont);
233 par.setFont(pos, tmpfont);
238 // return past-the-last paragraph influenced by a layout change on pit
239 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
241 ParagraphList::iterator end = paragraphs().end();
242 ParagraphList::iterator nextpit = boost::next(pit);
245 //because of parindents
246 if (!pit->getDepth())
247 return boost::next(nextpit);
248 //because of depth constrains
249 for (; nextpit != end; ++pit, ++nextpit) {
250 if (!pit->getDepth())
257 ParagraphList::iterator
258 LyXText::setLayout(ParagraphList::iterator start,
259 ParagraphList::iterator end,
260 string const & layout)
262 BOOST_ASSERT(start != end);
263 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
264 recUndo(parOffset(start), parOffset(undopit) - 1);
266 BufferParams const & bufparams = bv()->buffer()->params();
267 LyXLayout_ptr const & lyxlayout =
268 bufparams.getLyXTextClass()[layout];
270 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
271 pit->applyLayout(lyxlayout);
272 makeFontEntriesLayoutSpecific(bufparams, *pit);
273 if (lyxlayout->margintype == MARGIN_MANUAL)
274 pit->setLabelWidthString(lyxlayout->labelstring());
281 // set layout over selection and make a total rebreak of those paragraphs
282 void LyXText::setLayout(string const & layout)
284 // special handling of new environment insets
285 BufferParams const & params = bv()->buffer()->params();
286 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
287 if (lyxlayout->is_environment) {
288 // move everything in a new environment inset
289 lyxerr << "setting layout " << layout << endl;
290 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
291 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
292 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
293 InsetBase * inset = new InsetEnvironment(params, layout);
294 if (bv()->insertInset(inset)) {
296 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
302 ParagraphList::iterator start =
303 getPar(bv()->cursor().selBegin().par());
304 ParagraphList::iterator end =
305 boost::next(getPar(bv()->cursor().selEnd().par()));
306 ParagraphList::iterator endpit = setLayout(start, end, layout);
308 redoParagraphs(start, endpit);
316 void getSelectionSpan(LyXText & text,
317 ParagraphList::iterator & beg,
318 ParagraphList::iterator & end)
320 if (!text.bv()->cursor().selection()) {
321 beg = text.cursorPar();
322 end = boost::next(beg);
324 beg = text.getPar(text.bv()->cursor().selBegin());
325 end = boost::next(text.getPar(text.bv()->cursor().selEnd()));
330 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
331 Paragraph const & par,
334 if (par.layout()->labeltype == LABEL_BIBLIO)
336 int const depth = par.params().depth();
337 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
339 if (type == bv_funcs::DEC_DEPTH && depth > 0)
348 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
350 ParagraphList::iterator beg, end;
351 getSelectionSpan(*this, beg, end);
353 if (beg != paragraphs().begin())
354 max_depth = boost::prior(beg)->getMaxDepthAfter();
356 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
357 if (::changeDepthAllowed(type, *pit, max_depth))
359 max_depth = pit->getMaxDepthAfter();
365 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
367 ParagraphList::iterator beg, end;
368 getSelectionSpan(*this, beg, end);
370 recUndo(parOffset(beg), parOffset(end) - 1);
373 if (beg != paragraphs().begin())
374 max_depth = boost::prior(beg)->getMaxDepthAfter();
376 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
377 if (::changeDepthAllowed(type, *pit, max_depth)) {
378 int const depth = pit->params().depth();
379 if (type == bv_funcs::INC_DEPTH)
380 pit->params().depth(depth + 1);
382 pit->params().depth(depth - 1);
384 max_depth = pit->getMaxDepthAfter();
386 // this handles the counter labels, and also fixes up
387 // depth values for follow-on (child) paragraphs
392 // set font over selection and make a total rebreak of those paragraphs
393 void LyXText::setFont(LyXFont const & font, bool toggleall)
395 LCursor & cur = bv()->cursor();
396 // if there is no selection just set the current_font
397 if (!cur.selection()) {
398 // Determine basis font
400 if (cursor().pos() < cursorPar()->beginOfBody())
401 layoutfont = getLabelFont(cursorPar());
403 layoutfont = getLayoutFont(cursorPar());
405 // Update current font
406 real_current_font.update(font,
407 bv()->buffer()->params().language,
410 // Reduce to implicit settings
411 current_font = real_current_font;
412 current_font.reduce(layoutfont);
413 // And resolve it completely
414 real_current_font.realize(layoutfont);
419 // ok we have a selection.
420 recUndo(cur.selBegin().par(), cur.selEnd().par());
423 ParagraphList::iterator beg = getPar(cur.selBegin().par());
424 ParagraphList::iterator end = getPar(cur.selEnd().par());
426 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
427 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
429 BufferParams const & params = bv()->buffer()->params();
431 for (; pos != posend; ++pos) {
432 LyXFont f = getFont(pos.pit(), pos.pos());
433 f.update(font, params.language, toggleall);
434 setCharFont(pos.pit(), pos.pos(), f);
439 redoParagraphs(beg, ++end);
443 // the cursor set functions have a special mechanism. When they
444 // realize you left an empty paragraph, they will delete it.
446 void LyXText::cursorHome()
448 ParagraphList::iterator cpit = cursorPar();
449 setCursor(cpit, cpit->getRow(cursor().pos())->pos());
453 void LyXText::cursorEnd()
455 ParagraphList::iterator cpit = cursorPar();
456 pos_type end = cpit->getRow(cursor().pos())->endpos();
457 // if not on the last row of the par, put the cursor before
459 setCursor(cpit, end == cpit->size() ? end : end - 1);
463 void LyXText::cursorTop()
465 setCursor(paragraphs().begin(), 0);
469 void LyXText::cursorBottom()
471 ParagraphList::iterator lastpit =
472 boost::prior(paragraphs().end());
473 setCursor(lastpit, lastpit->size());
477 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
479 // If the mask is completely neutral, tell user
480 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
481 // Could only happen with user style
482 bv()->owner()->message(_("No font change defined. "
483 "Use Character under the Layout menu to define font change."));
487 // Try implicit word selection
488 // If there is a change in the language the implicit word selection
490 CursorSlice resetCursor = cursor();
491 bool implicitSelection =
492 font.language() == ignore_language
493 && font.number() == LyXFont::IGNORE
494 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
497 setFont(font, toggleall);
499 // Implicit selections are cleared afterwards
500 //and cursor is set to the original position.
501 if (implicitSelection) {
502 bv()->cursor().clearSelection();
503 cursor() = resetCursor;
504 bv()->cursor().resetAnchor();
509 string LyXText::getStringToIndex()
511 LCursor & cur = bv()->cursor();
512 // Try implicit word selection
513 // If there is a change in the language the implicit word selection
515 CursorSlice const reset_cursor = cursor();
516 bool const implicitSelection =
517 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
520 if (!cur.selection())
521 bv()->owner()->message(_("Nothing to index!"));
522 else if (cur.selBegin().par() != cur.selEnd().par())
523 bv()->owner()->message(_("Cannot index more than one paragraph!"));
525 idxstring = selectionAsString(*bv()->buffer(), false);
527 // Reset cursors to their original position.
528 cursor() = reset_cursor;
531 // Clear the implicit selection.
532 if (implicitSelection)
533 cur.clearSelection();
539 // the DTP switches for paragraphs(). LyX will store them in the first
540 // physical paragraph. When a paragraph is broken, the top settings rest,
541 // the bottom settings are given to the new one. So I can make sure,
542 // they do not duplicate themself and you cannot play dirty tricks with
545 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
546 string const & labelwidthstring, bool noindent)
548 LCursor & cur = bv()->cursor();
549 // make sure that the depth behind the selection are restored, too
550 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
551 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
553 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
554 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
556 for (--pit; pit != beg; ++pit) {
557 ParagraphParameters & params = pit->params();
558 params.spacing(spacing);
560 // does the layout allow the new alignment?
561 LyXLayout_ptr const & layout = pit->layout();
563 if (align == LYX_ALIGN_LAYOUT)
564 align = layout->align;
565 if (align & layout->alignpossible) {
566 if (align == layout->align)
567 params.align(LYX_ALIGN_LAYOUT);
571 pit->setLabelWidthString(labelwidthstring);
572 params.noindent(noindent);
575 redoParagraphs(getPar(cur.selBegin()), undopit);
579 string expandLabel(LyXTextClass const & textclass,
580 LyXLayout_ptr const & layout, bool appendix)
582 string fmt = appendix ?
583 layout->labelstring_appendix() : layout->labelstring();
585 // handle 'inherited level parts' in 'fmt',
586 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
587 size_t const i = fmt.find('@', 0);
588 if (i != string::npos) {
589 size_t const j = fmt.find('@', i + 1);
590 if (j != string::npos) {
591 string parent(fmt, i + 1, j - i - 1);
592 string label = expandLabel(textclass, textclass[parent], appendix);
593 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
597 return textclass.counters().counterLabel(fmt);
603 void incrementItemDepth(ParagraphList::iterator pit,
604 ParagraphList::iterator first_pit)
606 int const cur_labeltype = pit->layout()->labeltype;
608 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
611 int const cur_depth = pit->getDepth();
613 ParagraphList::iterator prev_pit = boost::prior(pit);
615 int const prev_depth = prev_pit->getDepth();
616 int const prev_labeltype = prev_pit->layout()->labeltype;
617 if (prev_depth == 0 && cur_depth > 0) {
618 if (prev_labeltype == cur_labeltype) {
619 pit->itemdepth = prev_pit->itemdepth + 1;
622 } else if (prev_depth < cur_depth) {
623 if (prev_labeltype == cur_labeltype) {
624 pit->itemdepth = prev_pit->itemdepth + 1;
627 } else if (prev_depth == cur_depth) {
628 if (prev_labeltype == cur_labeltype) {
629 pit->itemdepth = prev_pit->itemdepth;
633 if (prev_pit == first_pit)
641 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
642 ParagraphList::iterator firstpit,
648 int const cur_depth = pit->getDepth();
649 ParagraphList::iterator prev_pit = boost::prior(pit);
651 int const prev_depth = prev_pit->getDepth();
652 int const prev_labeltype = prev_pit->layout()->labeltype;
653 if (prev_depth <= cur_depth) {
654 if (prev_labeltype != LABEL_ENUMERATE) {
655 switch (pit->itemdepth) {
657 counters.reset("enumi");
659 counters.reset("enumii");
661 counters.reset("enumiii");
663 counters.reset("enumiv");
669 if (prev_pit == firstpit)
679 // set the counter of a paragraph. This includes the labels
680 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
682 BufferParams const & bufparams = buf.params();
683 LyXTextClass const & textclass = bufparams.getLyXTextClass();
684 LyXLayout_ptr const & layout = pit->layout();
685 ParagraphList::iterator first_pit = paragraphs().begin();
686 Counters & counters = textclass.counters();
691 if (pit == first_pit) {
692 pit->params().appendix(pit->params().startOfAppendix());
694 pit->params().appendix(boost::prior(pit)->params().appendix());
695 if (!pit->params().appendix() &&
696 pit->params().startOfAppendix()) {
697 pit->params().appendix(true);
698 textclass.counters().reset();
701 // Maybe we have to increment the item depth.
702 incrementItemDepth(pit, first_pit);
705 // erase what was there before
706 pit->params().labelString(string());
708 if (layout->margintype == MARGIN_MANUAL) {
709 if (pit->params().labelWidthString().empty())
710 pit->setLabelWidthString(layout->labelstring());
712 pit->setLabelWidthString(string());
715 // is it a layout that has an automatic label?
716 if (layout->labeltype == LABEL_COUNTER) {
717 BufferParams const & bufparams = buf.params();
718 LyXTextClass const & textclass = bufparams.getLyXTextClass();
719 counters.step(layout->counter);
720 string label = expandLabel(textclass, layout, pit->params().appendix());
721 pit->params().labelString(label);
722 } else if (layout->labeltype == LABEL_ITEMIZE) {
723 // At some point of time we should do something more
724 // clever here, like:
725 // pit->params().labelString(
726 // bufparams.user_defined_bullet(pit->itemdepth).getText());
727 // for now, use a simple hardcoded label
729 switch (pit->itemdepth) {
744 pit->params().labelString(itemlabel);
745 } else if (layout->labeltype == LABEL_ENUMERATE) {
746 // Maybe we have to reset the enumeration counter.
747 resetEnumCounterIfNeeded(pit, first_pit, counters);
750 // Yes I know this is a really, really! bad solution
752 string enumcounter = "enum";
754 switch (pit->itemdepth) {
766 // not a valid enumdepth...
770 counters.step(enumcounter);
772 pit->params().labelString(counters.enumLabel(enumcounter));
773 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
774 counters.step("bibitem");
775 int number = counters.value("bibitem");
776 if (pit->bibitem()) {
777 pit->bibitem()->setCounter(number);
778 pit->params().labelString(layout->labelstring());
780 // In biblio should't be following counters but...
782 string s = buf.B_(layout->labelstring());
785 if (layout->labeltype == LABEL_SENSITIVE) {
786 ParagraphList::iterator end = paragraphs().end();
787 ParagraphList::iterator tmppit = pit;
790 while (tmppit != end && tmppit->inInset()
791 // the single '=' is intended below
792 && (in = tmppit->inInset()->owner()))
794 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
795 in->lyxCode() == InsetBase::WRAP_CODE) {
799 Paragraph const * owner = &ownerPar(buf, in);
801 for ( ; tmppit != end; ++tmppit)
802 if (&*tmppit == owner)
810 if (in->lyxCode() == InsetBase::FLOAT_CODE)
811 type = static_cast<InsetFloat*>(in)->params().type;
812 else if (in->lyxCode() == InsetBase::WRAP_CODE)
813 type = static_cast<InsetWrap*>(in)->params().type;
817 Floating const & fl = textclass.floats().getType(type);
819 counters.step(fl.type());
821 // Doesn't work... yet.
822 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
824 // par->SetLayout(0);
825 // s = layout->labelstring;
826 s = _("Senseless: ");
829 pit->params().labelString(s);
835 // Updates all counters.
836 void LyXText::updateCounters()
839 bv()->buffer()->params().getLyXTextClass().counters().reset();
841 bool update_pos = false;
843 ParagraphList::iterator beg = paragraphs().begin();
844 ParagraphList::iterator end = paragraphs().end();
845 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
846 string const oldLabel = pit->params().labelString();
849 maxdepth = boost::prior(pit)->getMaxDepthAfter();
851 if (pit->params().depth() > maxdepth)
852 pit->params().depth(maxdepth);
854 // setCounter can potentially change the labelString.
855 setCounter(*bv()->buffer(), pit);
856 string const & newLabel = pit->params().labelString();
857 if (oldLabel != newLabel) {
858 redoParagraphInternal(pit);
864 updateParPositions();
868 void LyXText::insertInset(InsetBase * inset)
870 if (!cursorPar()->insetAllowed(inset->lyxCode()))
873 recUndo(cursor().par());
875 cursorPar()->insertInset(cursor().pos(), inset);
876 // Just to rebreak and refresh correctly.
877 // The character will not be inserted a second time
878 insertChar(Paragraph::META_INSET);
879 // If we enter a highly editable inset the cursor should be before
880 // the inset. After an undo LyX tries to call inset->edit(...)
881 // and fails if the cursor is behind the inset and getInset
882 // does not return the inset!
883 if (isHighlyEditableInset(inset))
890 void LyXText::cutSelection(bool doclear, bool realcut)
892 LCursor & cur = bv()->cursor();
893 // Stuff what we got on the clipboard. Even if there is no selection.
895 // There is a problem with having the stuffing here in that the
896 // larger the selection the slower LyX will get. This can be
897 // solved by running the line below only when the selection has
898 // finished. The solution used currently just works, to make it
899 // faster we need to be more clever and probably also have more
900 // calls to stuffClipboard. (Lgb)
901 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
903 // This doesn't make sense, if there is no selection
904 if (!cur.selection())
907 // OK, we have a selection. This is always between cur.selBegin()
910 // make sure that the depth behind the selection are restored, too
911 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
912 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
913 ParagraphList::iterator undopit = undoSpan(endpit);
914 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
916 int endpos = cur.selEnd().pos();
918 BufferParams const & bufparams = bv()->buffer()->params();
919 boost::tie(endpit, endpos) = realcut ?
920 CutAndPaste::cutSelection(bufparams,
923 cur.selBegin().pos(), endpos,
926 : CutAndPaste::eraseSelection(bufparams,
929 cur.selBegin().pos(), endpos,
931 // sometimes necessary
933 begpit->stripLeadingSpaces();
935 redoParagraphs(begpit, undopit);
936 // cutSelection can invalidate the cursor so we need to set
938 // we prefer the end for when tracking changes
939 cursor().pos(endpos);
940 cursor().par(parOffset(endpit));
942 // need a valid cursor. (Lgb)
943 cur.clearSelection();
948 void LyXText::copySelection()
950 LCursor & cur = bv()->cursor();
951 // stuff the selection onto the X clipboard, from an explicit copy request
952 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
954 // this doesnt make sense, if there is no selection
955 if (!cur.selection())
958 // ok we have a selection. This is always between cur.selBegin()
959 // and sel_end cursor
961 // copy behind a space if there is one
962 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
963 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
964 && (cur.selBegin().par() != cur.selEnd().par()
965 || cur.selBegin().pos() < cur.selEnd().pos()))
966 cur.selBegin().pos(cur.selBegin().pos() + 1);
968 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
969 getPar(cur.selEnd().par()),
970 cur.selBegin().pos(),
972 bv()->buffer()->params().textclass);
976 void LyXText::pasteSelection(size_t sel_index)
978 LCursor & cur = bv()->cursor();
979 // this does not make sense, if there is nothing to paste
980 if (!CutAndPaste::checkPastePossible())
983 recUndo(cursor().par());
985 ParagraphList::iterator endpit;
990 boost::tie(ppp, endpit) =
991 CutAndPaste::pasteSelection(*bv()->buffer(),
993 cursorPar(), cursor().pos(),
994 bv()->buffer()->params().textclass,
996 bufferErrors(*bv()->buffer(), el);
997 bv()->showErrorList(_("Paste"));
999 redoParagraphs(cursorPar(), endpit);
1001 cur.clearSelection();
1003 setCursor(ppp.first, ppp.second);
1009 void LyXText::setSelectionRange(lyx::pos_type length)
1014 LCursor & cur = bv()->cursor();
1022 // simple replacing. The font of the first selected character is used
1023 void LyXText::replaceSelectionWithString(string const & str)
1025 LCursor & cur = bv()->cursor();
1029 // Get font setting before we cut
1030 pos_type pos = cur.selEnd().pos();
1031 LyXFont const font = getPar(cur.selBegin())
1032 ->getFontSettings(bv()->buffer()->params(),
1033 cur.selBegin().pos());
1035 // Insert the new string
1036 string::const_iterator cit = str.begin();
1037 string::const_iterator end = str.end();
1038 for (; cit != end; ++cit) {
1039 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1043 // Cut the selection
1044 cutSelection(true, false);
1050 // needed to insert the selection
1051 void LyXText::insertStringAsLines(string const & str)
1053 LCursor & cur = bv()->cursor();
1054 ParagraphList::iterator pit = cursorPar();
1055 pos_type pos = cursor().pos();
1056 ParagraphList::iterator endpit = boost::next(cursorPar());
1057 recordUndo(cur, Undo::ATOMIC);
1059 // only to be sure, should not be neccessary
1060 cur.clearSelection();
1061 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1063 redoParagraphs(cursorPar(), endpit);
1065 setCursor(pit, pos);
1070 // turn double CR to single CR, others are converted into one
1071 // blank. Then insertStringAsLines is called
1072 void LyXText::insertStringAsParagraphs(string const & str)
1074 string linestr(str);
1075 bool newline_inserted = false;
1076 string::size_type const siz = linestr.length();
1078 for (string::size_type i = 0; i < siz; ++i) {
1079 if (linestr[i] == '\n') {
1080 if (newline_inserted) {
1081 // we know that \r will be ignored by
1082 // insertStringAsLines. Of course, it is a dirty
1083 // trick, but it works...
1084 linestr[i - 1] = '\r';
1088 newline_inserted = true;
1090 } else if (IsPrintable(linestr[i])) {
1091 newline_inserted = false;
1094 insertStringAsLines(linestr);
1098 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1100 setCursor(parOffset(pit), pos);
1104 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1107 CursorSlice old_cursor = cursor();
1108 setCursorIntern(par, pos, setfont, boundary);
1109 return deleteEmptyParagraphMechanism(old_cursor);
1113 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1114 pos_type pos, bool boundary)
1116 BOOST_ASSERT(par != int(paragraphs().size()));
1120 cur.boundary(boundary);
1122 // no rows, no fun...
1123 if (paragraphs().begin()->rows.empty())
1126 // now some strict checking
1127 Paragraph & para = *getPar(par);
1128 Row const & row = *para.getRow(pos);
1129 pos_type const end = row.endpos();
1131 // None of these should happen, but we're scaredy-cats
1133 lyxerr << "dont like -1" << endl;
1136 BOOST_ASSERT(false);
1137 } else if (pos > para.size()) {
1138 lyxerr << "dont like 1, pos: " << pos
1139 << " size: " << para.size()
1140 << " row.pos():" << row.pos()
1141 << " paroffset: " << par << endl;
1144 BOOST_ASSERT(false);
1145 } else if (pos > end) {
1146 lyxerr << "dont like 2 please report" << endl;
1147 // This shouldn't happen.
1150 BOOST_ASSERT(false);
1151 } else if (pos < row.pos()) {
1152 lyxerr << "dont like 3 please report pos:" << pos
1153 << " size: " << para.size()
1154 << " row.pos():" << row.pos()
1155 << " paroffset: " << par << endl;
1158 BOOST_ASSERT(false);
1163 void LyXText::setCursorIntern(paroffset_type par,
1164 pos_type pos, bool setfont, bool boundary)
1166 setCursor(cursor(), par, pos, boundary);
1167 bv()->cursor().x_target() = cursorX(cursor());
1173 void LyXText::setCurrentFont()
1175 LCursor & cur = bv()->cursor();
1176 pos_type pos = cur.pos();
1177 ParagraphList::iterator pit = cursorPar();
1179 if (cursor().boundary() && pos > 0)
1183 if (pos == pit->size())
1185 else // potentional bug... BUG (Lgb)
1186 if (pit->isSeparator(pos)) {
1187 if (pos > pit->getRow(pos)->pos() &&
1188 bidi.level(pos) % 2 ==
1189 bidi.level(pos - 1) % 2)
1191 else if (pos + 1 < pit->size())
1196 BufferParams const & bufparams = bv()->buffer()->params();
1197 current_font = pit->getFontSettings(bufparams, pos);
1198 real_current_font = getFont(pit, pos);
1200 if (cursor().pos() == pit->size() &&
1201 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1202 !cursor().boundary()) {
1203 Language const * lang =
1204 pit->getParLanguage(bufparams);
1205 current_font.setLanguage(lang);
1206 current_font.setNumber(LyXFont::OFF);
1207 real_current_font.setLanguage(lang);
1208 real_current_font.setNumber(LyXFont::OFF);
1213 // returns the column near the specified x-coordinate of the row
1214 // x is set to the real beginning of this column
1215 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1216 Row const & row, int & x, bool & boundary) const
1219 double tmpx = row.x();
1220 double fill_separator = row.fill_separator();
1221 double fill_hfill = row.fill_hfill();
1222 double fill_label_hfill = row.fill_label_hfill();
1224 pos_type vc = row.pos();
1225 pos_type end = row.endpos();
1227 LyXLayout_ptr const & layout = pit->layout();
1229 bool left_side = false;
1231 pos_type body_pos = pit->beginOfBody();
1232 double last_tmpx = tmpx;
1235 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1238 // check for empty row
1240 x = int(tmpx) + xo_;
1244 while (vc < end && tmpx <= x) {
1245 c = bidi.vis2log(vc);
1247 if (body_pos > 0 && c == body_pos - 1) {
1248 tmpx += fill_label_hfill +
1249 font_metrics::width(layout->labelsep, getLabelFont(pit));
1250 if (pit->isLineSeparator(body_pos - 1))
1251 tmpx -= singleWidth(pit, body_pos - 1);
1254 if (hfillExpansion(*pit, row, c)) {
1255 tmpx += singleWidth(pit, c);
1259 tmpx += fill_label_hfill;
1260 } else if (pit->isSeparator(c)) {
1261 tmpx += singleWidth(pit, c);
1263 tmpx += fill_separator;
1265 tmpx += singleWidth(pit, c);
1270 if ((tmpx + last_tmpx) / 2 > x) {
1275 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1278 // This (rtl_support test) is not needed, but gives
1279 // some speedup if rtl_support == false
1280 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1282 // If lastrow is false, we don't need to compute
1283 // the value of rtl.
1284 bool const rtl = lastrow
1285 ? pit->isRightToLeftPar(bv()->buffer()->params())
1288 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1289 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1291 else if (vc == row.pos()) {
1292 c = bidi.vis2log(vc);
1293 if (bidi.level(c) % 2 == 1)
1296 c = bidi.vis2log(vc - 1);
1297 bool const rtl = (bidi.level(c) % 2 == 1);
1298 if (left_side == rtl) {
1300 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1304 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1305 if (bidi.level(end -1) % 2 == 0)
1306 tmpx -= singleWidth(pit, end - 1);
1308 tmpx += singleWidth(pit, end - 1);
1312 x = int(tmpx) + xo_;
1313 return c - row.pos();
1317 void LyXText::setCursorFromCoordinates(int x, int y)
1319 CursorSlice old_cursor = cursor();
1320 setCursorFromCoordinates(cursor(), x, y);
1322 deleteEmptyParagraphMechanism(old_cursor);
1326 // x,y are coordinates relative to this LyXText
1327 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1329 ParagraphList::iterator pit;
1330 Row const & row = *getRowNearY(y, pit);
1332 pos_type const pos = row.pos() + getColumnNearX(pit, row, x, bound);
1333 cur.par() = parOffset(pit);
1335 cur.boundary() = bound;
1339 // x,y are absolute screen coordinates
1340 void LyXText::edit(LCursor & cur, int x, int y)
1342 int xx = x; // is modified by getColumnNearX
1343 ParagraphList::iterator pit;
1344 Row const & row = *getRowNearY(y, pit);
1346 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1347 cur.par() = parOffset(pit);
1349 cur.boundary() = bound;
1351 // try to descend into nested insets
1352 InsetBase * inset = checkInsetHit(x, y);
1354 // This should be just before or just behind the cursor position
1356 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1357 || inset == pit->getInset(pos));
1358 // Make sure the cursor points to the position before this inset.
1359 if (inset == pit->getInset(pos - 1))
1361 inset->edit(cur, x, y);
1366 bool LyXText::checkAndActivateInset(bool front)
1368 if (cursor().pos() == cursorPar()->size())
1370 InsetBase * inset = cursorPar()->getInset(cursor().pos());
1371 if (!isHighlyEditableInset(inset))
1373 inset->edit(bv()->cursor(), front);
1378 DispatchResult LyXText::moveRight()
1380 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1381 return moveLeftIntern(false, true, false);
1383 return moveRightIntern(true, true, false);
1387 DispatchResult LyXText::moveLeft()
1389 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1390 return moveRightIntern(true, true, false);
1392 return moveLeftIntern(false, true, false);
1396 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1398 ParagraphList::iterator c_par = cursorPar();
1399 if (boost::next(c_par) == paragraphs().end()
1400 && cursor().pos() >= c_par->size())
1401 return DispatchResult(false, FINISHED_RIGHT);
1402 if (activate_inset && checkAndActivateInset(front))
1403 return DispatchResult(true, true);
1406 bv()->cursor().clearSelection();
1407 return DispatchResult(true);
1411 DispatchResult LyXText::moveLeftIntern(bool front,
1412 bool activate_inset, bool selecting)
1414 if (cursor().par() == 0 && cursor().pos() <= 0)
1415 return DispatchResult(false, FINISHED);
1418 bv()->cursor().clearSelection();
1419 if (activate_inset && checkAndActivateInset(front))
1420 return DispatchResult(true, true);
1421 return DispatchResult(true);
1425 DispatchResult LyXText::moveUp()
1427 LCursor & cur = bv()->cursor();
1428 if (cur.par() == 0 && cursorRow() == firstRow())
1429 return DispatchResult(false, FINISHED_UP);
1431 cur.clearSelection();
1432 return DispatchResult(true);
1436 DispatchResult LyXText::moveDown()
1438 LCursor & cur = bv()->cursor();
1439 if (cur.par() == cur.lastpar() && cursorRow() == lastRow())
1440 return DispatchResult(false, FINISHED_DOWN);
1442 cur.clearSelection();
1443 return DispatchResult(true);
1447 bool LyXText::cursorLeft(bool internal)
1449 LCursor & cur = bv()->cursor();
1450 if (cur.pos() > 0) {
1451 bool boundary = cur.boundary();
1452 setCursor(cur.par(), cur.pos() - 1, true, false);
1453 if (!internal && !boundary &&
1454 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1455 setCursor(cur.par(), cur.pos() + 1, true, true);
1459 if (cur.par() != 0) {
1460 // steps into the paragraph above
1461 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1469 bool LyXText::cursorRight(bool internal)
1471 LCursor & cur = bv()->cursor();
1472 if (!internal && cur.boundary()) {
1473 setCursor(cur.par(), cur.pos(), true, false);
1477 if (cur.pos() != cur.lastpos()) {
1478 setCursor(cur.par(), cur.pos() + 1, true, false);
1479 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1481 setCursor(cur.par(), cur.pos(), true, true);
1485 if (cur.par() + 1 != int(paragraphs().size())) {
1486 setCursor(cur.par() + 1, 0);
1494 void LyXText::cursorUp(bool selecting)
1496 LCursor & cur = bv()->cursor();
1497 Row const & row = *cursorRow();
1498 int x = cur.x_target();
1499 int y = cursorY(cur.current()) - row.baseline() - 1;
1500 setCursorFromCoordinates(x, y);
1503 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1504 if (inset_hit && isHighlyEditableInset(inset_hit))
1505 inset_hit->edit(cur, cur.x_target(), y);
1510 void LyXText::cursorDown(bool selecting)
1512 LCursor & cur = bv()->cursor();
1513 Row const & row = *cursorRow();
1514 int x = cur.x_target();
1515 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1516 setCursorFromCoordinates(x, y);
1519 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1520 if (inset_hit && isHighlyEditableInset(inset_hit))
1521 inset_hit->edit(cur, cur.x_target(), y);
1526 void LyXText::cursorUpParagraph()
1528 ParagraphList::iterator cpit = cursorPar();
1529 if (cursor().pos() > 0)
1531 else if (cpit != paragraphs().begin())
1532 setCursor(boost::prior(cpit), 0);
1536 void LyXText::cursorDownParagraph()
1538 ParagraphList::iterator pit = cursorPar();
1539 ParagraphList::iterator next_pit = boost::next(pit);
1541 if (next_pit != paragraphs().end())
1542 setCursor(next_pit, 0);
1544 setCursor(pit, pit->size());
1548 // fix the cursor `cur' after a characters has been deleted at `where'
1549 // position. Called by deleteEmptyParagraphMechanism
1550 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1552 // if cursor is not in the paragraph where the delete occured,
1554 if (cur.par() != where.par())
1557 // if cursor position is after the place where the delete occured,
1559 if (cur.pos() > where.pos())
1560 cur.pos(cur.pos()-1);
1562 // check also if we don't want to set the cursor on a spot behind the
1563 // pagragraph because we erased the last character.
1564 if (cur.pos() > cur.lastpos())
1565 cur.pos() = cur.lastpos();
1569 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1571 #warning Disabled as it crashes after the cursor data shift... (Andre)
1574 // Would be wrong to delete anything if we have a selection.
1575 if (bv()->cursor().selection())
1578 // Don't do anything if the cursor is invalid
1579 if (old_cursor.par() == -1)
1583 // We allow all kinds of "mumbo-jumbo" when freespacing.
1584 ParagraphList::iterator const old_pit = getPar(old_cursor);
1585 if (old_pit->isFreeSpacing())
1588 /* Ok I'll put some comments here about what is missing.
1589 I have fixed BackSpace (and thus Delete) to not delete
1590 double-spaces automagically. I have also changed Cut,
1591 Copy and Paste to hopefully do some sensible things.
1592 There are still some small problems that can lead to
1593 double spaces stored in the document file or space at
1594 the beginning of paragraphs(). This happens if you have
1595 the cursor between to spaces and then save. Or if you
1596 cut and paste and the selection have a space at the
1597 beginning and then save right after the paste. I am
1598 sure none of these are very hard to fix, but I will
1599 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1600 that I can get some feedback. (Lgb)
1603 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1604 // delete the LineSeparator.
1607 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1608 // delete the LineSeparator.
1611 // If the pos around the old_cursor were spaces, delete one of them.
1612 if (old_cursor.par() != cursor().par()
1613 || old_cursor.pos() != cursor().pos()) {
1615 // Only if the cursor has really moved
1616 if (old_cursor.pos() > 0
1617 && old_cursor.pos() < old_pit->size()
1618 && old_pit->isLineSeparator(old_cursor.pos())
1619 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1620 bool erased = old_pit->erase(old_cursor.pos() - 1);
1621 redoParagraph(old_pit);
1625 #ifdef WITH_WARNINGS
1626 #warning This will not work anymore when we have multiple views of the same buffer
1627 // In this case, we will have to correct also the cursors held by
1628 // other bufferviews. It will probably be easier to do that in a more
1629 // automated way in CursorSlice code. (JMarc 26/09/2001)
1631 // correct all cursors held by the LyXText
1632 fixCursorAfterDelete(cursor(), old_cursor);
1633 fixCursorAfterDelete(anchor(), old_cursor);
1638 // don't delete anything if this is the ONLY paragraph!
1639 if (paragraphs().size() == 1)
1642 // Do not delete empty paragraphs with keepempty set.
1643 if (old_pit->allowEmpty())
1646 // only do our magic if we changed paragraph
1647 if (old_cursor.par() == cursor().par())
1650 // record if we have deleted a paragraph
1651 // we can't possibly have deleted a paragraph before this point
1652 bool deleted = false;
1654 if (old_pit->empty()
1655 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1656 // ok, we will delete something
1657 CursorSlice tmpcursor;
1661 bool selection_position_was_oldcursor_position =
1662 anchor().par() == old_cursor.par()
1663 && anchor().pos() == old_cursor.pos();
1665 tmpcursor = cursor();
1666 cursor() = old_cursor; // that undo can restore the right cursor position
1668 ParagraphList::iterator endpit = boost::next(old_pit);
1669 while (endpit != paragraphs().end() && endpit->getDepth())
1672 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1673 cursor() = tmpcursor;
1676 ParagraphList::iterator tmppit = cursorPar();
1678 paragraphs().erase(old_pit);
1679 // update cursor par offset
1680 cursor().par(parOffset(tmppit));
1683 if (selection_position_was_oldcursor_position) {
1684 // correct selection
1685 bv()->resetAnchor();
1692 if (old_pit->stripLeadingSpaces()) {
1693 redoParagraph(old_pit);
1694 bv()->resetAnchor();
1701 ParagraphList & LyXText::paragraphs() const
1703 return const_cast<ParagraphList &>(paragraphs_);
1707 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1709 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1713 void LyXText::recUndo(lyx::paroffset_type par) const
1715 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1719 bool LyXText::isInInset() const
1725 bool LyXText::toggleInset()
1727 InsetBase * inset = bv()->cursor().nextInset();
1728 // is there an editable inset at cursor position?
1729 if (!isEditableInset(inset))
1731 //bv()->owner()->message(inset->editMessage());
1733 // do we want to keep this?? (JMarc)
1734 if (!isHighlyEditableInset(inset))
1735 recUndo(cursor().par());
1737 if (inset->isOpen())
1745 int defaultRowHeight()
1747 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);