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);
296 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
300 ParagraphList::iterator start =
301 getPar(bv()->cursor().selBegin().par());
302 ParagraphList::iterator end =
303 boost::next(getPar(bv()->cursor().selEnd().par()));
304 ParagraphList::iterator endpit = setLayout(start, end, layout);
306 redoParagraphs(start, endpit);
314 void getSelectionSpan(LyXText & text,
315 ParagraphList::iterator & beg,
316 ParagraphList::iterator & end)
318 if (!text.bv()->cursor().selection()) {
319 beg = text.cursorPar();
320 end = boost::next(beg);
322 beg = text.getPar(text.bv()->cursor().selBegin());
323 end = boost::next(text.getPar(text.bv()->cursor().selEnd()));
328 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
329 Paragraph const & par,
332 if (par.layout()->labeltype == LABEL_BIBLIO)
334 int const depth = par.params().depth();
335 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
337 if (type == bv_funcs::DEC_DEPTH && depth > 0)
346 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
348 ParagraphList::iterator beg, end;
349 getSelectionSpan(*this, beg, end);
351 if (beg != paragraphs().begin())
352 max_depth = boost::prior(beg)->getMaxDepthAfter();
354 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
355 if (::changeDepthAllowed(type, *pit, max_depth))
357 max_depth = pit->getMaxDepthAfter();
363 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
365 ParagraphList::iterator beg, end;
366 getSelectionSpan(*this, beg, end);
368 recUndo(parOffset(beg), parOffset(end) - 1);
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)) {
376 int const depth = pit->params().depth();
377 if (type == bv_funcs::INC_DEPTH)
378 pit->params().depth(depth + 1);
380 pit->params().depth(depth - 1);
382 max_depth = pit->getMaxDepthAfter();
384 // this handles the counter labels, and also fixes up
385 // depth values for follow-on (child) paragraphs
390 // set font over selection and make a total rebreak of those paragraphs
391 void LyXText::setFont(LyXFont const & font, bool toggleall)
393 LCursor & cur = bv()->cursor();
394 // if there is no selection just set the current_font
395 if (!cur.selection()) {
396 // Determine basis font
398 if (cursor().pos() < cursorPar()->beginOfBody())
399 layoutfont = getLabelFont(cursorPar());
401 layoutfont = getLayoutFont(cursorPar());
403 // Update current font
404 real_current_font.update(font,
405 bv()->buffer()->params().language,
408 // Reduce to implicit settings
409 current_font = real_current_font;
410 current_font.reduce(layoutfont);
411 // And resolve it completely
412 real_current_font.realize(layoutfont);
417 // ok we have a selection.
418 recUndo(cur.selBegin().par(), cur.selEnd().par());
421 ParagraphList::iterator beg = getPar(cur.selBegin().par());
422 ParagraphList::iterator end = getPar(cur.selEnd().par());
424 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
425 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
427 BufferParams const & params = bv()->buffer()->params();
429 for (; pos != posend; ++pos) {
430 LyXFont f = getFont(pos.pit(), pos.pos());
431 f.update(font, params.language, toggleall);
432 setCharFont(pos.pit(), pos.pos(), f);
437 redoParagraphs(beg, ++end);
441 // the cursor set functions have a special mechanism. When they
442 // realize you left an empty paragraph, they will delete it.
444 void LyXText::cursorHome()
446 ParagraphList::iterator cpit = cursorPar();
447 setCursor(cpit, cpit->getRow(cursor().pos())->pos());
451 void LyXText::cursorEnd()
453 ParagraphList::iterator cpit = cursorPar();
454 pos_type end = cpit->getRow(cursor().pos())->endpos();
455 // if not on the last row of the par, put the cursor before
457 setCursor(cpit, end == cpit->size() ? end : end - 1);
461 void LyXText::cursorTop()
463 setCursor(paragraphs().begin(), 0);
467 void LyXText::cursorBottom()
469 ParagraphList::iterator lastpit = boost::prior(paragraphs().end());
470 setCursor(lastpit, lastpit->size());
474 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
476 // If the mask is completely neutral, tell user
477 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
478 // Could only happen with user style
479 bv()->owner()->message(_("No font change defined. "
480 "Use Character under the Layout menu to define font change."));
484 // Try implicit word selection
485 // If there is a change in the language the implicit word selection
487 CursorSlice resetCursor = cursor();
488 bool implicitSelection =
489 font.language() == ignore_language
490 && font.number() == LyXFont::IGNORE
491 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
494 setFont(font, toggleall);
496 // Implicit selections are cleared afterwards
497 //and cursor is set to the original position.
498 if (implicitSelection) {
499 bv()->cursor().clearSelection();
500 cursor() = resetCursor;
501 bv()->cursor().resetAnchor();
506 string LyXText::getStringToIndex()
508 LCursor & cur = bv()->cursor();
509 // Try implicit word selection
510 // If there is a change in the language the implicit word selection
512 CursorSlice const reset_cursor = cursor();
513 bool const implicitSelection =
514 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
517 if (!cur.selection())
518 bv()->owner()->message(_("Nothing to index!"));
519 else if (cur.selBegin().par() != cur.selEnd().par())
520 bv()->owner()->message(_("Cannot index more than one paragraph!"));
522 idxstring = cur.selectionAsString(false);
524 // Reset cursors to their original position.
525 cursor() = reset_cursor;
528 // Clear the implicit selection.
529 if (implicitSelection)
530 cur.clearSelection();
536 // the DTP switches for paragraphs(). LyX will store them in the first
537 // physical paragraph. When a paragraph is broken, the top settings rest,
538 // the bottom settings are given to the new one. So I can make sure,
539 // they do not duplicate themself and you cannot play dirty tricks with
542 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
543 string const & labelwidthstring, bool noindent)
545 LCursor & cur = bv()->cursor();
546 // make sure that the depth behind the selection are restored, too
547 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
548 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
550 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
551 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
553 for (--pit; pit != beg; ++pit) {
554 ParagraphParameters & params = pit->params();
555 params.spacing(spacing);
557 // does the layout allow the new alignment?
558 LyXLayout_ptr const & layout = pit->layout();
560 if (align == LYX_ALIGN_LAYOUT)
561 align = layout->align;
562 if (align & layout->alignpossible) {
563 if (align == layout->align)
564 params.align(LYX_ALIGN_LAYOUT);
568 pit->setLabelWidthString(labelwidthstring);
569 params.noindent(noindent);
572 redoParagraphs(getPar(cur.selBegin()), undopit);
576 string expandLabel(LyXTextClass const & textclass,
577 LyXLayout_ptr const & layout, bool appendix)
579 string fmt = appendix ?
580 layout->labelstring_appendix() : layout->labelstring();
582 // handle 'inherited level parts' in 'fmt',
583 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
584 size_t const i = fmt.find('@', 0);
585 if (i != string::npos) {
586 size_t const j = fmt.find('@', i + 1);
587 if (j != string::npos) {
588 string parent(fmt, i + 1, j - i - 1);
589 string label = expandLabel(textclass, textclass[parent], appendix);
590 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
594 return textclass.counters().counterLabel(fmt);
600 void incrementItemDepth(ParagraphList::iterator pit,
601 ParagraphList::iterator first_pit)
603 int const cur_labeltype = pit->layout()->labeltype;
605 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
608 int const cur_depth = pit->getDepth();
610 ParagraphList::iterator prev_pit = boost::prior(pit);
612 int const prev_depth = prev_pit->getDepth();
613 int const prev_labeltype = prev_pit->layout()->labeltype;
614 if (prev_depth == 0 && cur_depth > 0) {
615 if (prev_labeltype == cur_labeltype) {
616 pit->itemdepth = prev_pit->itemdepth + 1;
619 } else if (prev_depth < cur_depth) {
620 if (prev_labeltype == cur_labeltype) {
621 pit->itemdepth = prev_pit->itemdepth + 1;
624 } else if (prev_depth == cur_depth) {
625 if (prev_labeltype == cur_labeltype) {
626 pit->itemdepth = prev_pit->itemdepth;
630 if (prev_pit == first_pit)
638 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
639 ParagraphList::iterator firstpit,
645 int const cur_depth = pit->getDepth();
646 ParagraphList::iterator prev_pit = boost::prior(pit);
648 int const prev_depth = prev_pit->getDepth();
649 int const prev_labeltype = prev_pit->layout()->labeltype;
650 if (prev_depth <= cur_depth) {
651 if (prev_labeltype != LABEL_ENUMERATE) {
652 switch (pit->itemdepth) {
654 counters.reset("enumi");
656 counters.reset("enumii");
658 counters.reset("enumiii");
660 counters.reset("enumiv");
666 if (prev_pit == firstpit)
676 // set the counter of a paragraph. This includes the labels
677 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
679 BufferParams const & bufparams = buf.params();
680 LyXTextClass const & textclass = bufparams.getLyXTextClass();
681 LyXLayout_ptr const & layout = pit->layout();
682 ParagraphList::iterator first_pit = paragraphs().begin();
683 Counters & counters = textclass.counters();
688 if (pit == first_pit) {
689 pit->params().appendix(pit->params().startOfAppendix());
691 pit->params().appendix(boost::prior(pit)->params().appendix());
692 if (!pit->params().appendix() &&
693 pit->params().startOfAppendix()) {
694 pit->params().appendix(true);
695 textclass.counters().reset();
698 // Maybe we have to increment the item depth.
699 incrementItemDepth(pit, first_pit);
702 // erase what was there before
703 pit->params().labelString(string());
705 if (layout->margintype == MARGIN_MANUAL) {
706 if (pit->params().labelWidthString().empty())
707 pit->setLabelWidthString(layout->labelstring());
709 pit->setLabelWidthString(string());
712 // is it a layout that has an automatic label?
713 if (layout->labeltype == LABEL_COUNTER) {
714 BufferParams const & bufparams = buf.params();
715 LyXTextClass const & textclass = bufparams.getLyXTextClass();
716 counters.step(layout->counter);
717 string label = expandLabel(textclass, layout, pit->params().appendix());
718 pit->params().labelString(label);
719 } else if (layout->labeltype == LABEL_ITEMIZE) {
720 // At some point of time we should do something more
721 // clever here, like:
722 // pit->params().labelString(
723 // bufparams.user_defined_bullet(pit->itemdepth).getText());
724 // for now, use a simple hardcoded label
726 switch (pit->itemdepth) {
741 pit->params().labelString(itemlabel);
742 } else if (layout->labeltype == LABEL_ENUMERATE) {
743 // Maybe we have to reset the enumeration counter.
744 resetEnumCounterIfNeeded(pit, first_pit, counters);
747 // Yes I know this is a really, really! bad solution
749 string enumcounter = "enum";
751 switch (pit->itemdepth) {
763 // not a valid enumdepth...
767 counters.step(enumcounter);
769 pit->params().labelString(counters.enumLabel(enumcounter));
770 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
771 counters.step("bibitem");
772 int number = counters.value("bibitem");
773 if (pit->bibitem()) {
774 pit->bibitem()->setCounter(number);
775 pit->params().labelString(layout->labelstring());
777 // In biblio should't be following counters but...
779 string s = buf.B_(layout->labelstring());
782 if (layout->labeltype == LABEL_SENSITIVE) {
783 ParagraphList::iterator end = paragraphs().end();
784 ParagraphList::iterator tmppit = pit;
787 while (tmppit != end && tmppit->inInset()
788 // the single '=' is intended below
789 && (in = tmppit->inInset()->owner()))
791 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
792 in->lyxCode() == InsetBase::WRAP_CODE) {
796 Paragraph const * owner = &ownerPar(buf, in);
798 for ( ; tmppit != end; ++tmppit)
799 if (&*tmppit == owner)
807 if (in->lyxCode() == InsetBase::FLOAT_CODE)
808 type = static_cast<InsetFloat*>(in)->params().type;
809 else if (in->lyxCode() == InsetBase::WRAP_CODE)
810 type = static_cast<InsetWrap*>(in)->params().type;
814 Floating const & fl = textclass.floats().getType(type);
816 counters.step(fl.type());
818 // Doesn't work... yet.
819 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
821 // par->SetLayout(0);
822 // s = layout->labelstring;
823 s = _("Senseless: ");
826 pit->params().labelString(s);
832 // Updates all counters.
833 void LyXText::updateCounters()
836 bv()->buffer()->params().getLyXTextClass().counters().reset();
838 bool update_pos = false;
840 ParagraphList::iterator beg = paragraphs().begin();
841 ParagraphList::iterator end = paragraphs().end();
842 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
843 string const oldLabel = pit->params().labelString();
846 maxdepth = boost::prior(pit)->getMaxDepthAfter();
848 if (pit->params().depth() > maxdepth)
849 pit->params().depth(maxdepth);
851 // setCounter can potentially change the labelString.
852 setCounter(*bv()->buffer(), pit);
853 string const & newLabel = pit->params().labelString();
854 if (oldLabel != newLabel) {
855 redoParagraphInternal(pit);
861 updateParPositions();
865 void LyXText::insertInset(InsetBase * inset)
867 if (!cursorPar()->insetAllowed(inset->lyxCode()))
870 recUndo(cursor().par());
872 cursorPar()->insertInset(cursor().pos(), inset);
873 // Just to rebreak and refresh correctly.
874 // The character will not be inserted a second time
875 insertChar(Paragraph::META_INSET);
876 // If we enter a highly editable inset the cursor should be before
877 // the inset. After an undo LyX tries to call inset->edit(...)
878 // and fails if the cursor is behind the inset and getInset
879 // does not return the inset!
880 if (isHighlyEditableInset(inset))
887 void LyXText::cutSelection(bool doclear, bool realcut)
889 LCursor & cur = bv()->cursor();
890 // Stuff what we got on the clipboard. Even if there is no selection.
892 // There is a problem with having the stuffing here in that the
893 // larger the selection the slower LyX will get. This can be
894 // solved by running the line below only when the selection has
895 // finished. The solution used currently just works, to make it
896 // faster we need to be more clever and probably also have more
897 // calls to stuffClipboard. (Lgb)
898 bv()->stuffClipboard(cur.selectionAsString(true));
900 // This doesn't make sense, if there is no selection
901 if (!cur.selection())
904 // OK, we have a selection. This is always between cur.selBegin()
907 // make sure that the depth behind the selection are restored, too
908 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
909 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
910 ParagraphList::iterator undopit = undoSpan(endpit);
911 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
913 int endpos = cur.selEnd().pos();
915 BufferParams const & bufparams = bv()->buffer()->params();
916 boost::tie(endpit, endpos) = realcut ?
917 CutAndPaste::cutSelection(bufparams,
920 cur.selBegin().pos(), endpos,
923 : CutAndPaste::eraseSelection(bufparams,
926 cur.selBegin().pos(), endpos,
928 // sometimes necessary
930 begpit->stripLeadingSpaces();
932 redoParagraphs(begpit, undopit);
933 // cutSelection can invalidate the cursor so we need to set
935 // we prefer the end for when tracking changes
936 cursor().pos(endpos);
937 cursor().par(parOffset(endpit));
939 // need a valid cursor. (Lgb)
940 cur.clearSelection();
945 void LyXText::copySelection()
947 LCursor & cur = bv()->cursor();
948 // stuff the selection onto the X clipboard, from an explicit copy request
949 bv()->stuffClipboard(cur.selectionAsString(true));
951 // this doesnt make sense, if there is no selection
952 if (!cur.selection())
955 // ok we have a selection. This is always between cur.selBegin()
956 // and sel_end cursor
958 // copy behind a space if there is one
959 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
960 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
961 && (cur.selBegin().par() != cur.selEnd().par()
962 || cur.selBegin().pos() < cur.selEnd().pos()))
963 cur.selBegin().pos(cur.selBegin().pos() + 1);
965 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
966 getPar(cur.selEnd().par()),
967 cur.selBegin().pos(),
969 bv()->buffer()->params().textclass);
973 void LyXText::pasteSelection(size_t sel_index)
975 LCursor & cur = bv()->cursor();
976 // this does not make sense, if there is nothing to paste
977 if (!CutAndPaste::checkPastePossible())
980 recUndo(cursor().par());
982 ParagraphList::iterator endpit;
987 boost::tie(ppp, endpit) =
988 CutAndPaste::pasteSelection(*bv()->buffer(),
990 cursorPar(), cursor().pos(),
991 bv()->buffer()->params().textclass,
993 bufferErrors(*bv()->buffer(), el);
994 bv()->showErrorList(_("Paste"));
996 redoParagraphs(cursorPar(), endpit);
998 cur.clearSelection();
1000 setCursor(ppp.first, ppp.second);
1006 void LyXText::setSelectionRange(lyx::pos_type length)
1011 LCursor & cur = bv()->cursor();
1019 // simple replacing. The font of the first selected character is used
1020 void LyXText::replaceSelectionWithString(string const & str)
1022 LCursor & cur = bv()->cursor();
1026 // Get font setting before we cut
1027 pos_type pos = cur.selEnd().pos();
1028 LyXFont const font = getPar(cur.selBegin())
1029 ->getFontSettings(bv()->buffer()->params(),
1030 cur.selBegin().pos());
1032 // Insert the new string
1033 string::const_iterator cit = str.begin();
1034 string::const_iterator end = str.end();
1035 for (; cit != end; ++cit) {
1036 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1040 // Cut the selection
1041 cutSelection(true, false);
1047 // needed to insert the selection
1048 void LyXText::insertStringAsLines(string const & str)
1050 LCursor & cur = bv()->cursor();
1051 ParagraphList::iterator pit = cursorPar();
1052 pos_type pos = cursor().pos();
1053 ParagraphList::iterator endpit = boost::next(cursorPar());
1054 recordUndo(cur, Undo::ATOMIC);
1056 // only to be sure, should not be neccessary
1057 cur.clearSelection();
1058 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1060 redoParagraphs(cursorPar(), endpit);
1062 setCursor(pit, pos);
1067 // turn double CR to single CR, others are converted into one
1068 // blank. Then insertStringAsLines is called
1069 void LyXText::insertStringAsParagraphs(string const & str)
1071 string linestr(str);
1072 bool newline_inserted = false;
1073 string::size_type const siz = linestr.length();
1075 for (string::size_type i = 0; i < siz; ++i) {
1076 if (linestr[i] == '\n') {
1077 if (newline_inserted) {
1078 // we know that \r will be ignored by
1079 // insertStringAsLines. Of course, it is a dirty
1080 // trick, but it works...
1081 linestr[i - 1] = '\r';
1085 newline_inserted = true;
1087 } else if (IsPrintable(linestr[i])) {
1088 newline_inserted = false;
1091 insertStringAsLines(linestr);
1095 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1097 setCursor(parOffset(pit), pos);
1101 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1104 CursorSlice old_cursor = cursor();
1105 setCursorIntern(par, pos, setfont, boundary);
1106 return deleteEmptyParagraphMechanism(old_cursor);
1110 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1111 pos_type pos, bool boundary)
1113 BOOST_ASSERT(par != int(paragraphs().size()));
1117 cur.boundary(boundary);
1119 // no rows, no fun...
1120 if (paragraphs().begin()->rows.empty())
1123 // now some strict checking
1124 Paragraph & para = *getPar(par);
1125 Row const & row = *para.getRow(pos);
1126 pos_type const end = row.endpos();
1128 // None of these should happen, but we're scaredy-cats
1130 lyxerr << "dont like -1" << endl;
1133 BOOST_ASSERT(false);
1134 } else if (pos > para.size()) {
1135 lyxerr << "dont like 1, pos: " << pos
1136 << " size: " << para.size()
1137 << " row.pos():" << row.pos()
1138 << " paroffset: " << par << endl;
1141 BOOST_ASSERT(false);
1142 } else if (pos > end) {
1143 lyxerr << "dont like 2 please report" << endl;
1144 // This shouldn't happen.
1147 BOOST_ASSERT(false);
1148 } else if (pos < row.pos()) {
1149 lyxerr << "dont like 3 please report pos:" << pos
1150 << " size: " << para.size()
1151 << " row.pos():" << row.pos()
1152 << " paroffset: " << par << endl;
1155 BOOST_ASSERT(false);
1160 void LyXText::setCursorIntern(paroffset_type par,
1161 pos_type pos, bool setfont, bool boundary)
1163 setCursor(cursor(), par, pos, boundary);
1164 bv()->cursor().x_target() = cursorX(cursor());
1170 void LyXText::setCurrentFont()
1172 LCursor & cur = bv()->cursor();
1173 pos_type pos = cur.pos();
1174 ParagraphList::iterator pit = cursorPar();
1176 if (cursor().boundary() && pos > 0)
1180 if (pos == pit->size())
1182 else // potentional bug... BUG (Lgb)
1183 if (pit->isSeparator(pos)) {
1184 if (pos > pit->getRow(pos)->pos() &&
1185 bidi.level(pos) % 2 ==
1186 bidi.level(pos - 1) % 2)
1188 else if (pos + 1 < pit->size())
1193 BufferParams const & bufparams = bv()->buffer()->params();
1194 current_font = pit->getFontSettings(bufparams, pos);
1195 real_current_font = getFont(pit, pos);
1197 if (cursor().pos() == pit->size() &&
1198 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1199 !cursor().boundary()) {
1200 Language const * lang =
1201 pit->getParLanguage(bufparams);
1202 current_font.setLanguage(lang);
1203 current_font.setNumber(LyXFont::OFF);
1204 real_current_font.setLanguage(lang);
1205 real_current_font.setNumber(LyXFont::OFF);
1209 // x is an absolute screen coord
1210 // returns the column near the specified x-coordinate of the row
1211 // x is set to the real beginning of this column
1212 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1213 Row const & row, int & x, bool & boundary) const
1216 double tmpx = row.x();
1217 double fill_separator = row.fill_separator();
1218 double fill_hfill = row.fill_hfill();
1219 double fill_label_hfill = row.fill_label_hfill();
1221 pos_type vc = row.pos();
1222 pos_type end = row.endpos();
1224 LyXLayout_ptr const & layout = pit->layout();
1226 bool left_side = false;
1228 pos_type body_pos = pit->beginOfBody();
1229 double last_tmpx = tmpx;
1232 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1235 // check for empty row
1237 x = int(tmpx) + xo_;
1241 while (vc < end && tmpx <= x) {
1242 c = bidi.vis2log(vc);
1244 if (body_pos > 0 && c == body_pos - 1) {
1245 tmpx += fill_label_hfill +
1246 font_metrics::width(layout->labelsep, getLabelFont(pit));
1247 if (pit->isLineSeparator(body_pos - 1))
1248 tmpx -= singleWidth(pit, body_pos - 1);
1251 if (hfillExpansion(*pit, row, c)) {
1252 tmpx += singleWidth(pit, c);
1256 tmpx += fill_label_hfill;
1257 } else if (pit->isSeparator(c)) {
1258 tmpx += singleWidth(pit, c);
1260 tmpx += fill_separator;
1262 tmpx += singleWidth(pit, c);
1267 if ((tmpx + last_tmpx) / 2 > x) {
1272 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1275 // This (rtl_support test) is not needed, but gives
1276 // some speedup if rtl_support == false
1277 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1279 // If lastrow is false, we don't need to compute
1280 // the value of rtl.
1281 bool const rtl = lastrow
1282 ? pit->isRightToLeftPar(bv()->buffer()->params())
1285 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1286 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1288 else if (vc == row.pos()) {
1289 c = bidi.vis2log(vc);
1290 if (bidi.level(c) % 2 == 1)
1293 c = bidi.vis2log(vc - 1);
1294 bool const rtl = (bidi.level(c) % 2 == 1);
1295 if (left_side == rtl) {
1297 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1301 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1302 if (bidi.level(end -1) % 2 == 0)
1303 tmpx -= singleWidth(pit, end - 1);
1305 tmpx += singleWidth(pit, end - 1);
1309 x = int(tmpx) + xo_;
1310 return c - row.pos();
1314 void LyXText::setCursorFromCoordinates(int x, int y)
1316 CursorSlice old_cursor = cursor();
1317 setCursorFromCoordinates(cursor(), x, y);
1319 deleteEmptyParagraphMechanism(old_cursor);
1323 // x,y are coordinates relative to this LyXText
1324 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1326 ParagraphList::iterator pit;
1327 Row const & row = *getRowNearY(y, pit);
1329 int xx = x + xo_; // getRowNearX get absolute x coords
1330 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1331 cur.par() = parOffset(pit);
1333 cur.boundary() = bound;
1337 // x,y are absolute screen coordinates
1338 void LyXText::edit(LCursor & cur, int x, int y)
1340 ParagraphList::iterator pit;
1341 Row const & row = *getRowNearY(y - yo_, pit);
1344 int xx = x; // is modified by getColumnNearX
1345 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1346 cur.par() = parOffset(pit);
1348 cur.boundary() = bound;
1350 // try to descend into nested insets
1351 InsetBase * inset = checkInsetHit(x, y);
1353 // This should be just before or just behind the
1354 // cursor position set above.
1355 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1356 || inset == pit->getInset(pos));
1357 // Make sure the cursor points to the position before
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);