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);
216 InsetBase * LyXText::getInset() const
218 ParagraphList::iterator pit = cursorPar();
219 pos_type const pos = cursor().pos();
221 if (pos < pit->size() && pit->isInset(pos)) {
222 return pit->getInset(pos);
228 bool LyXText::toggleInset()
230 InsetBase * inset = getInset();
231 // is there an editable inset at cursor position?
232 if (!isEditableInset(inset))
234 //bv()->owner()->message(inset->editMessage());
236 // do we want to keep this?? (JMarc)
237 if (!isHighlyEditableInset(inset))
238 recUndo(cursor().par());
249 // Asger is not sure we want to do this...
250 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
253 LyXLayout_ptr const & layout = par.layout();
254 pos_type const psize = par.size();
257 for (pos_type pos = 0; pos < psize; ++pos) {
258 if (pos < par.beginOfBody())
259 layoutfont = layout->labelfont;
261 layoutfont = layout->font;
263 LyXFont tmpfont = par.getFontSettings(params, pos);
264 tmpfont.reduce(layoutfont);
265 par.setFont(pos, tmpfont);
270 // return past-the-last paragraph influenced by a layout change on pit
271 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
273 ParagraphList::iterator end = paragraphs().end();
274 ParagraphList::iterator nextpit = boost::next(pit);
277 //because of parindents
278 if (!pit->getDepth())
279 return boost::next(nextpit);
280 //because of depth constrains
281 for (; nextpit != end; ++pit, ++nextpit) {
282 if (!pit->getDepth())
289 ParagraphList::iterator
290 LyXText::setLayout(ParagraphList::iterator start,
291 ParagraphList::iterator end,
292 string const & layout)
294 BOOST_ASSERT(start != end);
295 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
296 recUndo(parOffset(start), parOffset(undopit) - 1);
298 BufferParams const & bufparams = bv()->buffer()->params();
299 LyXLayout_ptr const & lyxlayout =
300 bufparams.getLyXTextClass()[layout];
302 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
303 pit->applyLayout(lyxlayout);
304 makeFontEntriesLayoutSpecific(bufparams, *pit);
305 if (lyxlayout->margintype == MARGIN_MANUAL)
306 pit->setLabelWidthString(lyxlayout->labelstring());
313 // set layout over selection and make a total rebreak of those paragraphs
314 void LyXText::setLayout(string const & layout)
316 // special handling of new environment insets
317 BufferParams const & params = bv()->buffer()->params();
318 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
319 if (lyxlayout->is_environment) {
320 // move everything in a new environment inset
321 lyxerr << "setting layout " << layout << endl;
322 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
323 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
324 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
325 InsetBase * inset = new InsetEnvironment(params, layout);
326 if (bv()->insertInset(inset)) {
328 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
334 ParagraphList::iterator start =
335 getPar(bv()->cursor().selBegin().par());
336 ParagraphList::iterator end =
337 boost::next(getPar(bv()->cursor().selEnd().par()));
338 ParagraphList::iterator endpit = setLayout(start, end, layout);
340 redoParagraphs(start, endpit);
348 void getSelectionSpan(LyXText & text,
349 ParagraphList::iterator & beg,
350 ParagraphList::iterator & end)
352 if (!text.bv()->cursor().selection()) {
353 beg = text.cursorPar();
354 end = boost::next(beg);
356 beg = text.getPar(text.bv()->cursor().selBegin());
357 end = boost::next(text.getPar(text.bv()->cursor().selEnd()));
362 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
363 Paragraph const & par,
366 if (par.layout()->labeltype == LABEL_BIBLIO)
368 int const depth = par.params().depth();
369 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
371 if (type == bv_funcs::DEC_DEPTH && depth > 0)
380 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
382 ParagraphList::iterator beg, end;
383 getSelectionSpan(*this, beg, end);
385 if (beg != paragraphs().begin())
386 max_depth = boost::prior(beg)->getMaxDepthAfter();
388 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
389 if (::changeDepthAllowed(type, *pit, max_depth))
391 max_depth = pit->getMaxDepthAfter();
397 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
399 ParagraphList::iterator beg, end;
400 getSelectionSpan(*this, beg, end);
402 recUndo(parOffset(beg), parOffset(end) - 1);
405 if (beg != paragraphs().begin())
406 max_depth = boost::prior(beg)->getMaxDepthAfter();
408 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
409 if (::changeDepthAllowed(type, *pit, max_depth)) {
410 int const depth = pit->params().depth();
411 if (type == bv_funcs::INC_DEPTH)
412 pit->params().depth(depth + 1);
414 pit->params().depth(depth - 1);
416 max_depth = pit->getMaxDepthAfter();
418 // this handles the counter labels, and also fixes up
419 // depth values for follow-on (child) paragraphs
424 // set font over selection and make a total rebreak of those paragraphs
425 void LyXText::setFont(LyXFont const & font, bool toggleall)
427 LCursor & cur = bv()->cursor();
428 // if there is no selection just set the current_font
429 if (!cur.selection()) {
430 // Determine basis font
432 if (cursor().pos() < cursorPar()->beginOfBody())
433 layoutfont = getLabelFont(cursorPar());
435 layoutfont = getLayoutFont(cursorPar());
437 // Update current font
438 real_current_font.update(font,
439 bv()->buffer()->params().language,
442 // Reduce to implicit settings
443 current_font = real_current_font;
444 current_font.reduce(layoutfont);
445 // And resolve it completely
446 real_current_font.realize(layoutfont);
451 // ok we have a selection.
452 recUndo(cur.selBegin().par(), cur.selEnd().par());
455 ParagraphList::iterator beg = getPar(cur.selBegin().par());
456 ParagraphList::iterator end = getPar(cur.selEnd().par());
458 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
459 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
461 BufferParams const & params = bv()->buffer()->params();
463 for (; pos != posend; ++pos) {
464 LyXFont f = getFont(pos.pit(), pos.pos());
465 f.update(font, params.language, toggleall);
466 setCharFont(pos.pit(), pos.pos(), f);
471 redoParagraphs(beg, ++end);
475 // the cursor set functions have a special mechanism. When they
476 // realize you left an empty paragraph, they will delete it.
478 void LyXText::cursorHome()
480 ParagraphList::iterator cpit = cursorPar();
481 setCursor(cpit, cpit->getRow(cursor().pos())->pos());
485 void LyXText::cursorEnd()
487 ParagraphList::iterator cpit = cursorPar();
488 pos_type end = cpit->getRow(cursor().pos())->endpos();
489 // if not on the last row of the par, put the cursor before
491 setCursor(cpit, end == cpit->size() ? end : end - 1);
495 void LyXText::cursorTop()
497 setCursor(paragraphs().begin(), 0);
501 void LyXText::cursorBottom()
503 ParagraphList::iterator lastpit =
504 boost::prior(paragraphs().end());
505 setCursor(lastpit, lastpit->size());
509 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
511 // If the mask is completely neutral, tell user
512 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
513 // Could only happen with user style
514 bv()->owner()->message(_("No font change defined. "
515 "Use Character under the Layout menu to define font change."));
519 // Try implicit word selection
520 // If there is a change in the language the implicit word selection
522 CursorSlice resetCursor = cursor();
523 bool implicitSelection =
524 font.language() == ignore_language
525 && font.number() == LyXFont::IGNORE
526 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
529 setFont(font, toggleall);
531 // Implicit selections are cleared afterwards
532 //and cursor is set to the original position.
533 if (implicitSelection) {
534 bv()->cursor().clearSelection();
535 cursor() = resetCursor;
536 bv()->cursor().resetAnchor();
541 string LyXText::getStringToIndex()
543 LCursor & cur = bv()->cursor();
544 // Try implicit word selection
545 // If there is a change in the language the implicit word selection
547 CursorSlice const reset_cursor = cursor();
548 bool const implicitSelection =
549 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
552 if (!cur.selection())
553 bv()->owner()->message(_("Nothing to index!"));
554 else if (cur.selBegin().par() != cur.selEnd().par())
555 bv()->owner()->message(_("Cannot index more than one paragraph!"));
557 idxstring = selectionAsString(*bv()->buffer(), false);
559 // Reset cursors to their original position.
560 cursor() = reset_cursor;
563 // Clear the implicit selection.
564 if (implicitSelection)
565 cur.clearSelection();
571 // the DTP switches for paragraphs(). LyX will store them in the first
572 // physical paragraph. When a paragraph is broken, the top settings rest,
573 // the bottom settings are given to the new one. So I can make sure,
574 // they do not duplicate themself and you cannot play dirty tricks with
577 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
578 string const & labelwidthstring, bool noindent)
580 LCursor & cur = bv()->cursor();
581 // make sure that the depth behind the selection are restored, too
582 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
583 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
585 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
586 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
588 for (--pit; pit != beg; ++pit) {
589 ParagraphParameters & params = pit->params();
590 params.spacing(spacing);
592 // does the layout allow the new alignment?
593 LyXLayout_ptr const & layout = pit->layout();
595 if (align == LYX_ALIGN_LAYOUT)
596 align = layout->align;
597 if (align & layout->alignpossible) {
598 if (align == layout->align)
599 params.align(LYX_ALIGN_LAYOUT);
603 pit->setLabelWidthString(labelwidthstring);
604 params.noindent(noindent);
607 redoParagraphs(getPar(cur.selBegin()), undopit);
611 string expandLabel(LyXTextClass const & textclass,
612 LyXLayout_ptr const & layout, bool appendix)
614 string fmt = appendix ?
615 layout->labelstring_appendix() : layout->labelstring();
617 // handle 'inherited level parts' in 'fmt',
618 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
619 size_t const i = fmt.find('@', 0);
620 if (i != string::npos) {
621 size_t const j = fmt.find('@', i + 1);
622 if (j != string::npos) {
623 string parent(fmt, i + 1, j - i - 1);
624 string label = expandLabel(textclass, textclass[parent], appendix);
625 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
629 return textclass.counters().counterLabel(fmt);
635 void incrementItemDepth(ParagraphList::iterator pit,
636 ParagraphList::iterator first_pit)
638 int const cur_labeltype = pit->layout()->labeltype;
640 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
643 int const cur_depth = pit->getDepth();
645 ParagraphList::iterator prev_pit = boost::prior(pit);
647 int const prev_depth = prev_pit->getDepth();
648 int const prev_labeltype = prev_pit->layout()->labeltype;
649 if (prev_depth == 0 && cur_depth > 0) {
650 if (prev_labeltype == cur_labeltype) {
651 pit->itemdepth = prev_pit->itemdepth + 1;
654 } else if (prev_depth < cur_depth) {
655 if (prev_labeltype == cur_labeltype) {
656 pit->itemdepth = prev_pit->itemdepth + 1;
659 } else if (prev_depth == cur_depth) {
660 if (prev_labeltype == cur_labeltype) {
661 pit->itemdepth = prev_pit->itemdepth;
665 if (prev_pit == first_pit)
673 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
674 ParagraphList::iterator firstpit,
680 int const cur_depth = pit->getDepth();
681 ParagraphList::iterator prev_pit = boost::prior(pit);
683 int const prev_depth = prev_pit->getDepth();
684 int const prev_labeltype = prev_pit->layout()->labeltype;
685 if (prev_depth <= cur_depth) {
686 if (prev_labeltype != LABEL_ENUMERATE) {
687 switch (pit->itemdepth) {
689 counters.reset("enumi");
691 counters.reset("enumii");
693 counters.reset("enumiii");
695 counters.reset("enumiv");
701 if (prev_pit == firstpit)
711 // set the counter of a paragraph. This includes the labels
712 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
714 BufferParams const & bufparams = buf.params();
715 LyXTextClass const & textclass = bufparams.getLyXTextClass();
716 LyXLayout_ptr const & layout = pit->layout();
717 ParagraphList::iterator first_pit = paragraphs().begin();
718 Counters & counters = textclass.counters();
723 if (pit == first_pit) {
724 pit->params().appendix(pit->params().startOfAppendix());
726 pit->params().appendix(boost::prior(pit)->params().appendix());
727 if (!pit->params().appendix() &&
728 pit->params().startOfAppendix()) {
729 pit->params().appendix(true);
730 textclass.counters().reset();
733 // Maybe we have to increment the item depth.
734 incrementItemDepth(pit, first_pit);
737 // erase what was there before
738 pit->params().labelString(string());
740 if (layout->margintype == MARGIN_MANUAL) {
741 if (pit->params().labelWidthString().empty())
742 pit->setLabelWidthString(layout->labelstring());
744 pit->setLabelWidthString(string());
747 // is it a layout that has an automatic label?
748 if (layout->labeltype == LABEL_COUNTER) {
749 BufferParams const & bufparams = buf.params();
750 LyXTextClass const & textclass = bufparams.getLyXTextClass();
751 counters.step(layout->counter);
752 string label = expandLabel(textclass, layout, pit->params().appendix());
753 pit->params().labelString(label);
754 } else if (layout->labeltype == LABEL_ITEMIZE) {
755 // At some point of time we should do something more
756 // clever here, like:
757 // pit->params().labelString(
758 // bufparams.user_defined_bullet(pit->itemdepth).getText());
759 // for now, use a simple hardcoded label
761 switch (pit->itemdepth) {
776 pit->params().labelString(itemlabel);
777 } else if (layout->labeltype == LABEL_ENUMERATE) {
778 // Maybe we have to reset the enumeration counter.
779 resetEnumCounterIfNeeded(pit, first_pit, counters);
782 // Yes I know this is a really, really! bad solution
784 string enumcounter = "enum";
786 switch (pit->itemdepth) {
798 // not a valid enumdepth...
802 counters.step(enumcounter);
804 pit->params().labelString(counters.enumLabel(enumcounter));
805 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
806 counters.step("bibitem");
807 int number = counters.value("bibitem");
808 if (pit->bibitem()) {
809 pit->bibitem()->setCounter(number);
810 pit->params().labelString(layout->labelstring());
812 // In biblio should't be following counters but...
814 string s = buf.B_(layout->labelstring());
817 if (layout->labeltype == LABEL_SENSITIVE) {
818 ParagraphList::iterator end = paragraphs().end();
819 ParagraphList::iterator tmppit = pit;
822 while (tmppit != end && tmppit->inInset()
823 // the single '=' is intended below
824 && (in = tmppit->inInset()->owner()))
826 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
827 in->lyxCode() == InsetBase::WRAP_CODE) {
831 Paragraph const * owner = &ownerPar(buf, in);
833 for ( ; tmppit != end; ++tmppit)
834 if (&*tmppit == owner)
842 if (in->lyxCode() == InsetBase::FLOAT_CODE)
843 type = static_cast<InsetFloat*>(in)->params().type;
844 else if (in->lyxCode() == InsetBase::WRAP_CODE)
845 type = static_cast<InsetWrap*>(in)->params().type;
849 Floating const & fl = textclass.floats().getType(type);
851 counters.step(fl.type());
853 // Doesn't work... yet.
854 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
856 // par->SetLayout(0);
857 // s = layout->labelstring;
858 s = _("Senseless: ");
861 pit->params().labelString(s);
867 // Updates all counters.
868 void LyXText::updateCounters()
871 bv()->buffer()->params().getLyXTextClass().counters().reset();
873 bool update_pos = false;
875 ParagraphList::iterator beg = paragraphs().begin();
876 ParagraphList::iterator end = paragraphs().end();
877 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
878 string const oldLabel = pit->params().labelString();
881 maxdepth = boost::prior(pit)->getMaxDepthAfter();
883 if (pit->params().depth() > maxdepth)
884 pit->params().depth(maxdepth);
886 // setCounter can potentially change the labelString.
887 setCounter(*bv()->buffer(), pit);
888 string const & newLabel = pit->params().labelString();
889 if (oldLabel != newLabel) {
890 redoParagraphInternal(pit);
896 updateParPositions();
900 void LyXText::insertInset(InsetBase * inset)
902 if (!cursorPar()->insetAllowed(inset->lyxCode()))
905 recUndo(cursor().par());
907 cursorPar()->insertInset(cursor().pos(), inset);
908 // Just to rebreak and refresh correctly.
909 // The character will not be inserted a second time
910 insertChar(Paragraph::META_INSET);
911 // If we enter a highly editable inset the cursor should be before
912 // the inset. After an undo LyX tries to call inset->edit(...)
913 // and fails if the cursor is behind the inset and getInset
914 // does not return the inset!
915 if (isHighlyEditableInset(inset))
922 void LyXText::cutSelection(bool doclear, bool realcut)
924 LCursor & cur = bv()->cursor();
925 // Stuff what we got on the clipboard. Even if there is no selection.
927 // There is a problem with having the stuffing here in that the
928 // larger the selection the slower LyX will get. This can be
929 // solved by running the line below only when the selection has
930 // finished. The solution used currently just works, to make it
931 // faster we need to be more clever and probably also have more
932 // calls to stuffClipboard. (Lgb)
933 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
935 // This doesn't make sense, if there is no selection
936 if (!cur.selection())
939 // OK, we have a selection. This is always between cur.selBegin()
942 // make sure that the depth behind the selection are restored, too
943 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
944 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
945 ParagraphList::iterator undopit = undoSpan(endpit);
946 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
948 int endpos = cur.selEnd().pos();
950 BufferParams const & bufparams = bv()->buffer()->params();
951 boost::tie(endpit, endpos) = realcut ?
952 CutAndPaste::cutSelection(bufparams,
955 cur.selBegin().pos(), endpos,
958 : CutAndPaste::eraseSelection(bufparams,
961 cur.selBegin().pos(), endpos,
963 // sometimes necessary
965 begpit->stripLeadingSpaces();
967 redoParagraphs(begpit, undopit);
968 // cutSelection can invalidate the cursor so we need to set
970 // we prefer the end for when tracking changes
971 cursor().pos(endpos);
972 cursor().par(parOffset(endpit));
974 // need a valid cursor. (Lgb)
975 cur.clearSelection();
980 void LyXText::copySelection()
982 LCursor & cur = bv()->cursor();
983 // stuff the selection onto the X clipboard, from an explicit copy request
984 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
986 // this doesnt make sense, if there is no selection
987 if (!cur.selection())
990 // ok we have a selection. This is always between cur.selBegin()
991 // and sel_end cursor
993 // copy behind a space if there is one
994 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
995 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
996 && (cur.selBegin().par() != cur.selEnd().par()
997 || cur.selBegin().pos() < cur.selEnd().pos()))
998 cur.selBegin().pos(cur.selBegin().pos() + 1);
1000 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
1001 getPar(cur.selEnd().par()),
1002 cur.selBegin().pos(),
1004 bv()->buffer()->params().textclass);
1008 void LyXText::pasteSelection(size_t sel_index)
1010 LCursor & cur = bv()->cursor();
1011 // this does not make sense, if there is nothing to paste
1012 if (!CutAndPaste::checkPastePossible())
1015 recUndo(cursor().par());
1017 ParagraphList::iterator endpit;
1022 boost::tie(ppp, endpit) =
1023 CutAndPaste::pasteSelection(*bv()->buffer(),
1025 cursorPar(), cursor().pos(),
1026 bv()->buffer()->params().textclass,
1028 bufferErrors(*bv()->buffer(), el);
1029 bv()->showErrorList(_("Paste"));
1031 redoParagraphs(cursorPar(), endpit);
1033 cur.clearSelection();
1035 setCursor(ppp.first, ppp.second);
1041 void LyXText::setSelectionRange(lyx::pos_type length)
1046 LCursor & cur = bv()->cursor();
1054 // simple replacing. The font of the first selected character is used
1055 void LyXText::replaceSelectionWithString(string const & str)
1057 LCursor & cur = bv()->cursor();
1061 // Get font setting before we cut
1062 pos_type pos = cur.selEnd().pos();
1063 LyXFont const font = getPar(cur.selBegin())
1064 ->getFontSettings(bv()->buffer()->params(),
1065 cur.selBegin().pos());
1067 // Insert the new string
1068 string::const_iterator cit = str.begin();
1069 string::const_iterator end = str.end();
1070 for (; cit != end; ++cit) {
1071 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1075 // Cut the selection
1076 cutSelection(true, false);
1082 // needed to insert the selection
1083 void LyXText::insertStringAsLines(string const & str)
1085 LCursor & cur = bv()->cursor();
1086 ParagraphList::iterator pit = cursorPar();
1087 pos_type pos = cursor().pos();
1088 ParagraphList::iterator endpit = boost::next(cursorPar());
1090 recUndo(cursor().par());
1092 // only to be sure, should not be neccessary
1093 cur.clearSelection();
1094 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1096 redoParagraphs(cursorPar(), endpit);
1098 setCursor(pit, pos);
1103 // turn double CR to single CR, others are converted into one
1104 // blank. Then insertStringAsLines is called
1105 void LyXText::insertStringAsParagraphs(string const & str)
1107 string linestr(str);
1108 bool newline_inserted = false;
1109 string::size_type const siz = linestr.length();
1111 for (string::size_type i = 0; i < siz; ++i) {
1112 if (linestr[i] == '\n') {
1113 if (newline_inserted) {
1114 // we know that \r will be ignored by
1115 // insertStringAsLines. Of course, it is a dirty
1116 // trick, but it works...
1117 linestr[i - 1] = '\r';
1121 newline_inserted = true;
1123 } else if (IsPrintable(linestr[i])) {
1124 newline_inserted = false;
1127 insertStringAsLines(linestr);
1131 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1133 setCursor(parOffset(pit), pos);
1137 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1140 CursorSlice old_cursor = cursor();
1141 setCursorIntern(par, pos, setfont, boundary);
1142 return deleteEmptyParagraphMechanism(old_cursor);
1146 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1147 pos_type pos, bool boundary)
1149 BOOST_ASSERT(par != int(paragraphs().size()));
1153 cur.boundary(boundary);
1155 // no rows, no fun...
1156 if (paragraphs().begin()->rows.empty())
1159 // now some strict checking
1160 Paragraph & para = *getPar(par);
1161 Row const & row = *para.getRow(pos);
1162 pos_type const end = row.endpos();
1164 // None of these should happen, but we're scaredy-cats
1166 lyxerr << "dont like -1" << endl;
1169 BOOST_ASSERT(false);
1170 } else if (pos > para.size()) {
1171 lyxerr << "dont like 1, pos: " << pos
1172 << " size: " << para.size()
1173 << " row.pos():" << row.pos()
1174 << " paroffset: " << par << endl;
1177 BOOST_ASSERT(false);
1178 } else if (pos > end) {
1179 lyxerr << "dont like 2 please report" << endl;
1180 // This shouldn't happen.
1183 BOOST_ASSERT(false);
1184 } else if (pos < row.pos()) {
1185 lyxerr << "dont like 3 please report pos:" << pos
1186 << " size: " << para.size()
1187 << " row.pos():" << row.pos()
1188 << " paroffset: " << par << endl;
1191 BOOST_ASSERT(false);
1196 void LyXText::setCursorIntern(paroffset_type par,
1197 pos_type pos, bool setfont, bool boundary)
1199 setCursor(cursor(), par, pos, boundary);
1200 bv()->cursor().x_target() = cursorX(cursor());
1206 void LyXText::setCurrentFont()
1208 LCursor & cur = bv()->cursor();
1209 pos_type pos = cur.pos();
1210 ParagraphList::iterator pit = cursorPar();
1212 if (cursor().boundary() && pos > 0)
1216 if (pos == pit->size())
1218 else // potentional bug... BUG (Lgb)
1219 if (pit->isSeparator(pos)) {
1220 if (pos > pit->getRow(pos)->pos() &&
1221 bidi.level(pos) % 2 ==
1222 bidi.level(pos - 1) % 2)
1224 else if (pos + 1 < pit->size())
1229 BufferParams const & bufparams = bv()->buffer()->params();
1230 current_font = pit->getFontSettings(bufparams, pos);
1231 real_current_font = getFont(pit, pos);
1233 if (cursor().pos() == pit->size() &&
1234 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1235 !cursor().boundary()) {
1236 Language const * lang =
1237 pit->getParLanguage(bufparams);
1238 current_font.setLanguage(lang);
1239 current_font.setNumber(LyXFont::OFF);
1240 real_current_font.setLanguage(lang);
1241 real_current_font.setNumber(LyXFont::OFF);
1246 // returns the column near the specified x-coordinate of the row
1247 // x is set to the real beginning of this column
1248 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1249 Row const & row, int & x, bool & boundary) const
1252 double tmpx = row.x();
1253 double fill_separator = row.fill_separator();
1254 double fill_hfill = row.fill_hfill();
1255 double fill_label_hfill = row.fill_label_hfill();
1257 pos_type vc = row.pos();
1258 pos_type end = row.endpos();
1260 LyXLayout_ptr const & layout = pit->layout();
1262 bool left_side = false;
1264 pos_type body_pos = pit->beginOfBody();
1265 double last_tmpx = tmpx;
1268 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1271 // check for empty row
1273 x = int(tmpx) + xo_;
1277 while (vc < end && tmpx <= x) {
1278 c = bidi.vis2log(vc);
1280 if (body_pos > 0 && c == body_pos - 1) {
1281 tmpx += fill_label_hfill +
1282 font_metrics::width(layout->labelsep, getLabelFont(pit));
1283 if (pit->isLineSeparator(body_pos - 1))
1284 tmpx -= singleWidth(pit, body_pos - 1);
1287 if (hfillExpansion(*pit, row, c)) {
1288 tmpx += singleWidth(pit, c);
1292 tmpx += fill_label_hfill;
1293 } else if (pit->isSeparator(c)) {
1294 tmpx += singleWidth(pit, c);
1296 tmpx += fill_separator;
1298 tmpx += singleWidth(pit, c);
1303 if ((tmpx + last_tmpx) / 2 > x) {
1308 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1311 // This (rtl_support test) is not needed, but gives
1312 // some speedup if rtl_support == false
1313 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1315 // If lastrow is false, we don't need to compute
1316 // the value of rtl.
1317 bool const rtl = lastrow
1318 ? pit->isRightToLeftPar(bv()->buffer()->params())
1321 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1322 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1324 else if (vc == row.pos()) {
1325 c = bidi.vis2log(vc);
1326 if (bidi.level(c) % 2 == 1)
1329 c = bidi.vis2log(vc - 1);
1330 bool const rtl = (bidi.level(c) % 2 == 1);
1331 if (left_side == rtl) {
1333 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1337 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1338 if (bidi.level(end -1) % 2 == 0)
1339 tmpx -= singleWidth(pit, end - 1);
1341 tmpx += singleWidth(pit, end - 1);
1345 x = int(tmpx) + xo_;
1346 return c - row.pos();
1350 void LyXText::setCursorFromCoordinates(int x, int y)
1352 CursorSlice old_cursor = cursor();
1353 setCursorFromCoordinates(cursor(), x, y);
1355 deleteEmptyParagraphMechanism(old_cursor);
1359 // x,y are coordinates relative to this LyXText
1360 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1362 ParagraphList::iterator pit;
1363 Row const & row = *getRowNearY(y, pit);
1365 pos_type const pos = row.pos() + getColumnNearX(pit, row, x, bound);
1366 cur.par() = parOffset(pit);
1368 cur.boundary() = bound;
1372 // x,y are absolute screen coordinates
1373 void LyXText::edit(LCursor & cur, int x, int y)
1375 int xx = x; // is modified by getColumnNearX
1376 ParagraphList::iterator pit;
1377 Row const & row = *getRowNearY(y, pit);
1379 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1380 cur.par() = parOffset(pit);
1382 cur.boundary() = bound;
1384 // try to descend into nested insets
1385 InsetBase * inset = checkInsetHit(x, y);
1387 // This should be just before or just behind the cursor position
1389 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1390 || inset == pit->getInset(pos));
1391 // Make sure the cursor points to the position before this inset.
1392 if (inset == pit->getInset(pos - 1))
1394 inset->edit(cur, x, y);
1399 bool LyXText::checkAndActivateInset(bool front)
1401 if (cursor().pos() == cursorPar()->size())
1403 InsetBase * inset = cursorPar()->getInset(cursor().pos());
1404 if (!isHighlyEditableInset(inset))
1406 inset->edit(bv()->cursor(), front);
1411 DispatchResult LyXText::moveRight()
1413 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1414 return moveLeftIntern(false, true, false);
1416 return moveRightIntern(true, true, false);
1420 DispatchResult LyXText::moveLeft()
1422 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1423 return moveRightIntern(true, true, false);
1425 return moveLeftIntern(false, true, false);
1429 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1431 ParagraphList::iterator c_par = cursorPar();
1432 if (boost::next(c_par) == paragraphs().end()
1433 && cursor().pos() >= c_par->size())
1434 return DispatchResult(false, FINISHED_RIGHT);
1435 if (activate_inset && checkAndActivateInset(front))
1436 return DispatchResult(true, true);
1439 bv()->cursor().clearSelection();
1440 return DispatchResult(true);
1444 DispatchResult LyXText::moveLeftIntern(bool front,
1445 bool activate_inset, bool selecting)
1447 if (cursor().par() == 0 && cursor().pos() <= 0)
1448 return DispatchResult(false, FINISHED);
1451 bv()->cursor().clearSelection();
1452 if (activate_inset && checkAndActivateInset(front))
1453 return DispatchResult(true, true);
1454 return DispatchResult(true);
1458 DispatchResult LyXText::moveUp()
1460 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1461 return DispatchResult(false, FINISHED_UP);
1463 bv()->cursor().clearSelection();
1464 return DispatchResult(true);
1468 DispatchResult LyXText::moveDown()
1470 LCursor & cur = bv()->cursor();
1471 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1472 return DispatchResult(false, FINISHED_DOWN);
1474 cur.clearSelection();
1475 return DispatchResult(true);
1479 bool LyXText::cursorLeft(bool internal)
1481 LCursor & cur = bv()->cursor();
1482 if (cur.pos() > 0) {
1483 bool boundary = cur.boundary();
1484 setCursor(cur.par(), cur.pos() - 1, true, false);
1485 if (!internal && !boundary &&
1486 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1487 setCursor(cur.par(), cur.pos() + 1, true, true);
1491 if (cur.par() != 0) {
1492 // steps into the paragraph above
1493 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1501 bool LyXText::cursorRight(bool internal)
1503 LCursor & cur = bv()->cursor();
1504 if (!internal && cur.boundary()) {
1505 setCursor(cur.par(), cur.pos(), true, false);
1509 if (cur.pos() != cur.lastpos()) {
1510 setCursor(cur.par(), cur.pos() + 1, true, false);
1511 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1513 setCursor(cur.par(), cur.pos(), true, true);
1517 if (cur.par() + 1 != int(paragraphs().size())) {
1518 setCursor(cur.par() + 1, 0);
1526 void LyXText::cursorUp(bool selecting)
1528 LCursor & cur = bv()->cursor();
1529 Row const & row = *cursorRow();
1530 int x = cur.x_target();
1531 int y = cursorY(cur.current()) - row.baseline() - 1;
1532 setCursorFromCoordinates(x, y);
1535 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1536 if (inset_hit && isHighlyEditableInset(inset_hit))
1537 inset_hit->edit(cur, cur.x_target(), y);
1542 void LyXText::cursorDown(bool selecting)
1544 LCursor & cur = bv()->cursor();
1545 Row const & row = *cursorRow();
1546 int x = cur.x_target();
1547 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1548 setCursorFromCoordinates(x, y);
1551 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1552 if (inset_hit && isHighlyEditableInset(inset_hit))
1553 inset_hit->edit(cur, cur.x_target(), y);
1558 void LyXText::cursorUpParagraph()
1560 ParagraphList::iterator cpit = cursorPar();
1561 if (cursor().pos() > 0)
1563 else if (cpit != paragraphs().begin())
1564 setCursor(boost::prior(cpit), 0);
1568 void LyXText::cursorDownParagraph()
1570 ParagraphList::iterator pit = cursorPar();
1571 ParagraphList::iterator next_pit = boost::next(pit);
1573 if (next_pit != paragraphs().end())
1574 setCursor(next_pit, 0);
1576 setCursor(pit, pit->size());
1580 // fix the cursor `cur' after a characters has been deleted at `where'
1581 // position. Called by deleteEmptyParagraphMechanism
1582 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1584 // if cursor is not in the paragraph where the delete occured,
1586 if (cur.par() != where.par())
1589 // if cursor position is after the place where the delete occured,
1591 if (cur.pos() > where.pos())
1592 cur.pos(cur.pos()-1);
1594 // check also if we don't want to set the cursor on a spot behind the
1595 // pagragraph because we erased the last character.
1596 if (cur.pos() > cur.lastpos())
1597 cur.pos() = cur.lastpos();
1601 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1603 #warning Disabled as it crashes after the cursor data shift... (Andre)
1606 // Would be wrong to delete anything if we have a selection.
1607 if (bv()->cursor().selection())
1610 // Don't do anything if the cursor is invalid
1611 if (old_cursor.par() == -1)
1615 // We allow all kinds of "mumbo-jumbo" when freespacing.
1616 ParagraphList::iterator const old_pit = getPar(old_cursor);
1617 if (old_pit->isFreeSpacing())
1620 /* Ok I'll put some comments here about what is missing.
1621 I have fixed BackSpace (and thus Delete) to not delete
1622 double-spaces automagically. I have also changed Cut,
1623 Copy and Paste to hopefully do some sensible things.
1624 There are still some small problems that can lead to
1625 double spaces stored in the document file or space at
1626 the beginning of paragraphs(). This happens if you have
1627 the cursor between to spaces and then save. Or if you
1628 cut and paste and the selection have a space at the
1629 beginning and then save right after the paste. I am
1630 sure none of these are very hard to fix, but I will
1631 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1632 that I can get some feedback. (Lgb)
1635 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1636 // delete the LineSeparator.
1639 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1640 // delete the LineSeparator.
1643 // If the pos around the old_cursor were spaces, delete one of them.
1644 if (old_cursor.par() != cursor().par()
1645 || old_cursor.pos() != cursor().pos()) {
1647 // Only if the cursor has really moved
1648 if (old_cursor.pos() > 0
1649 && old_cursor.pos() < old_pit->size()
1650 && old_pit->isLineSeparator(old_cursor.pos())
1651 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1652 bool erased = old_pit->erase(old_cursor.pos() - 1);
1653 redoParagraph(old_pit);
1657 #ifdef WITH_WARNINGS
1658 #warning This will not work anymore when we have multiple views of the same buffer
1659 // In this case, we will have to correct also the cursors held by
1660 // other bufferviews. It will probably be easier to do that in a more
1661 // automated way in CursorSlice code. (JMarc 26/09/2001)
1663 // correct all cursors held by the LyXText
1664 fixCursorAfterDelete(cursor(), old_cursor);
1665 fixCursorAfterDelete(anchor(), old_cursor);
1670 // don't delete anything if this is the ONLY paragraph!
1671 if (paragraphs().size() == 1)
1674 // Do not delete empty paragraphs with keepempty set.
1675 if (old_pit->allowEmpty())
1678 // only do our magic if we changed paragraph
1679 if (old_cursor.par() == cursor().par())
1682 // record if we have deleted a paragraph
1683 // we can't possibly have deleted a paragraph before this point
1684 bool deleted = false;
1686 if (old_pit->empty()
1687 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1688 // ok, we will delete something
1689 CursorSlice tmpcursor;
1693 bool selection_position_was_oldcursor_position =
1694 anchor().par() == old_cursor.par()
1695 && anchor().pos() == old_cursor.pos();
1697 tmpcursor = cursor();
1698 cursor() = old_cursor; // that undo can restore the right cursor position
1700 ParagraphList::iterator endpit = boost::next(old_pit);
1701 while (endpit != paragraphs().end() && endpit->getDepth())
1704 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1705 cursor() = tmpcursor;
1708 ParagraphList::iterator tmppit = cursorPar();
1710 paragraphs().erase(old_pit);
1711 // update cursor par offset
1712 cursor().par(parOffset(tmppit));
1715 if (selection_position_was_oldcursor_position) {
1716 // correct selection
1717 bv()->resetAnchor();
1724 if (old_pit->stripLeadingSpaces()) {
1725 redoParagraph(old_pit);
1726 bv()->resetAnchor();
1733 ParagraphList & LyXText::paragraphs() const
1735 return const_cast<ParagraphList &>(paragraphs_);
1739 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1741 recordUndo(Undo::ATOMIC, this, first, last);
1745 void LyXText::recUndo(lyx::paroffset_type par) const
1747 recordUndo(Undo::ATOMIC, this, par, par);
1751 bool LyXText::isInInset() const
1757 int defaultRowHeight()
1759 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);