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 bv()->insertInset(inset);
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 =
470 boost::prior(paragraphs().end());
471 setCursor(lastpit, lastpit->size());
475 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
477 // If the mask is completely neutral, tell user
478 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
479 // Could only happen with user style
480 bv()->owner()->message(_("No font change defined. "
481 "Use Character under the Layout menu to define font change."));
485 // Try implicit word selection
486 // If there is a change in the language the implicit word selection
488 CursorSlice resetCursor = cursor();
489 bool implicitSelection =
490 font.language() == ignore_language
491 && font.number() == LyXFont::IGNORE
492 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
495 setFont(font, toggleall);
497 // Implicit selections are cleared afterwards
498 //and cursor is set to the original position.
499 if (implicitSelection) {
500 bv()->cursor().clearSelection();
501 cursor() = resetCursor;
502 bv()->cursor().resetAnchor();
507 string LyXText::getStringToIndex()
509 LCursor & cur = bv()->cursor();
510 // Try implicit word selection
511 // If there is a change in the language the implicit word selection
513 CursorSlice const reset_cursor = cursor();
514 bool const implicitSelection =
515 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
518 if (!cur.selection())
519 bv()->owner()->message(_("Nothing to index!"));
520 else if (cur.selBegin().par() != cur.selEnd().par())
521 bv()->owner()->message(_("Cannot index more than one paragraph!"));
523 idxstring = selectionAsString(*bv()->buffer(), false);
525 // Reset cursors to their original position.
526 cursor() = reset_cursor;
529 // Clear the implicit selection.
530 if (implicitSelection)
531 cur.clearSelection();
537 // the DTP switches for paragraphs(). LyX will store them in the first
538 // physical paragraph. When a paragraph is broken, the top settings rest,
539 // the bottom settings are given to the new one. So I can make sure,
540 // they do not duplicate themself and you cannot play dirty tricks with
543 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
544 string const & labelwidthstring, bool noindent)
546 LCursor & cur = bv()->cursor();
547 // make sure that the depth behind the selection are restored, too
548 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
549 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
551 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
552 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
554 for (--pit; pit != beg; ++pit) {
555 ParagraphParameters & params = pit->params();
556 params.spacing(spacing);
558 // does the layout allow the new alignment?
559 LyXLayout_ptr const & layout = pit->layout();
561 if (align == LYX_ALIGN_LAYOUT)
562 align = layout->align;
563 if (align & layout->alignpossible) {
564 if (align == layout->align)
565 params.align(LYX_ALIGN_LAYOUT);
569 pit->setLabelWidthString(labelwidthstring);
570 params.noindent(noindent);
573 redoParagraphs(getPar(cur.selBegin()), undopit);
577 string expandLabel(LyXTextClass const & textclass,
578 LyXLayout_ptr const & layout, bool appendix)
580 string fmt = appendix ?
581 layout->labelstring_appendix() : layout->labelstring();
583 // handle 'inherited level parts' in 'fmt',
584 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
585 size_t const i = fmt.find('@', 0);
586 if (i != string::npos) {
587 size_t const j = fmt.find('@', i + 1);
588 if (j != string::npos) {
589 string parent(fmt, i + 1, j - i - 1);
590 string label = expandLabel(textclass, textclass[parent], appendix);
591 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
595 return textclass.counters().counterLabel(fmt);
601 void incrementItemDepth(ParagraphList::iterator pit,
602 ParagraphList::iterator first_pit)
604 int const cur_labeltype = pit->layout()->labeltype;
606 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
609 int const cur_depth = pit->getDepth();
611 ParagraphList::iterator prev_pit = boost::prior(pit);
613 int const prev_depth = prev_pit->getDepth();
614 int const prev_labeltype = prev_pit->layout()->labeltype;
615 if (prev_depth == 0 && cur_depth > 0) {
616 if (prev_labeltype == cur_labeltype) {
617 pit->itemdepth = prev_pit->itemdepth + 1;
620 } else if (prev_depth < cur_depth) {
621 if (prev_labeltype == cur_labeltype) {
622 pit->itemdepth = prev_pit->itemdepth + 1;
625 } else if (prev_depth == cur_depth) {
626 if (prev_labeltype == cur_labeltype) {
627 pit->itemdepth = prev_pit->itemdepth;
631 if (prev_pit == first_pit)
639 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
640 ParagraphList::iterator firstpit,
646 int const cur_depth = pit->getDepth();
647 ParagraphList::iterator prev_pit = boost::prior(pit);
649 int const prev_depth = prev_pit->getDepth();
650 int const prev_labeltype = prev_pit->layout()->labeltype;
651 if (prev_depth <= cur_depth) {
652 if (prev_labeltype != LABEL_ENUMERATE) {
653 switch (pit->itemdepth) {
655 counters.reset("enumi");
657 counters.reset("enumii");
659 counters.reset("enumiii");
661 counters.reset("enumiv");
667 if (prev_pit == firstpit)
677 // set the counter of a paragraph. This includes the labels
678 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
680 BufferParams const & bufparams = buf.params();
681 LyXTextClass const & textclass = bufparams.getLyXTextClass();
682 LyXLayout_ptr const & layout = pit->layout();
683 ParagraphList::iterator first_pit = paragraphs().begin();
684 Counters & counters = textclass.counters();
689 if (pit == first_pit) {
690 pit->params().appendix(pit->params().startOfAppendix());
692 pit->params().appendix(boost::prior(pit)->params().appendix());
693 if (!pit->params().appendix() &&
694 pit->params().startOfAppendix()) {
695 pit->params().appendix(true);
696 textclass.counters().reset();
699 // Maybe we have to increment the item depth.
700 incrementItemDepth(pit, first_pit);
703 // erase what was there before
704 pit->params().labelString(string());
706 if (layout->margintype == MARGIN_MANUAL) {
707 if (pit->params().labelWidthString().empty())
708 pit->setLabelWidthString(layout->labelstring());
710 pit->setLabelWidthString(string());
713 // is it a layout that has an automatic label?
714 if (layout->labeltype == LABEL_COUNTER) {
715 BufferParams const & bufparams = buf.params();
716 LyXTextClass const & textclass = bufparams.getLyXTextClass();
717 counters.step(layout->counter);
718 string label = expandLabel(textclass, layout, pit->params().appendix());
719 pit->params().labelString(label);
720 } else if (layout->labeltype == LABEL_ITEMIZE) {
721 // At some point of time we should do something more
722 // clever here, like:
723 // pit->params().labelString(
724 // bufparams.user_defined_bullet(pit->itemdepth).getText());
725 // for now, use a simple hardcoded label
727 switch (pit->itemdepth) {
742 pit->params().labelString(itemlabel);
743 } else if (layout->labeltype == LABEL_ENUMERATE) {
744 // Maybe we have to reset the enumeration counter.
745 resetEnumCounterIfNeeded(pit, first_pit, counters);
748 // Yes I know this is a really, really! bad solution
750 string enumcounter = "enum";
752 switch (pit->itemdepth) {
764 // not a valid enumdepth...
768 counters.step(enumcounter);
770 pit->params().labelString(counters.enumLabel(enumcounter));
771 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
772 counters.step("bibitem");
773 int number = counters.value("bibitem");
774 if (pit->bibitem()) {
775 pit->bibitem()->setCounter(number);
776 pit->params().labelString(layout->labelstring());
778 // In biblio should't be following counters but...
780 string s = buf.B_(layout->labelstring());
783 if (layout->labeltype == LABEL_SENSITIVE) {
784 ParagraphList::iterator end = paragraphs().end();
785 ParagraphList::iterator tmppit = pit;
788 while (tmppit != end && tmppit->inInset()
789 // the single '=' is intended below
790 && (in = tmppit->inInset()->owner()))
792 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
793 in->lyxCode() == InsetBase::WRAP_CODE) {
797 Paragraph const * owner = &ownerPar(buf, in);
799 for ( ; tmppit != end; ++tmppit)
800 if (&*tmppit == owner)
808 if (in->lyxCode() == InsetBase::FLOAT_CODE)
809 type = static_cast<InsetFloat*>(in)->params().type;
810 else if (in->lyxCode() == InsetBase::WRAP_CODE)
811 type = static_cast<InsetWrap*>(in)->params().type;
815 Floating const & fl = textclass.floats().getType(type);
817 counters.step(fl.type());
819 // Doesn't work... yet.
820 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
822 // par->SetLayout(0);
823 // s = layout->labelstring;
824 s = _("Senseless: ");
827 pit->params().labelString(s);
833 // Updates all counters.
834 void LyXText::updateCounters()
837 bv()->buffer()->params().getLyXTextClass().counters().reset();
839 bool update_pos = false;
841 ParagraphList::iterator beg = paragraphs().begin();
842 ParagraphList::iterator end = paragraphs().end();
843 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
844 string const oldLabel = pit->params().labelString();
847 maxdepth = boost::prior(pit)->getMaxDepthAfter();
849 if (pit->params().depth() > maxdepth)
850 pit->params().depth(maxdepth);
852 // setCounter can potentially change the labelString.
853 setCounter(*bv()->buffer(), pit);
854 string const & newLabel = pit->params().labelString();
855 if (oldLabel != newLabel) {
856 redoParagraphInternal(pit);
862 updateParPositions();
866 void LyXText::insertInset(InsetBase * inset)
868 if (!cursorPar()->insetAllowed(inset->lyxCode()))
871 recUndo(cursor().par());
873 cursorPar()->insertInset(cursor().pos(), inset);
874 // Just to rebreak and refresh correctly.
875 // The character will not be inserted a second time
876 insertChar(Paragraph::META_INSET);
877 // If we enter a highly editable inset the cursor should be before
878 // the inset. After an undo LyX tries to call inset->edit(...)
879 // and fails if the cursor is behind the inset and getInset
880 // does not return the inset!
881 if (isHighlyEditableInset(inset))
888 void LyXText::cutSelection(bool doclear, bool realcut)
890 LCursor & cur = bv()->cursor();
891 // Stuff what we got on the clipboard. Even if there is no selection.
893 // There is a problem with having the stuffing here in that the
894 // larger the selection the slower LyX will get. This can be
895 // solved by running the line below only when the selection has
896 // finished. The solution used currently just works, to make it
897 // faster we need to be more clever and probably also have more
898 // calls to stuffClipboard. (Lgb)
899 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
901 // This doesn't make sense, if there is no selection
902 if (!cur.selection())
905 // OK, we have a selection. This is always between cur.selBegin()
908 // make sure that the depth behind the selection are restored, too
909 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
910 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
911 ParagraphList::iterator undopit = undoSpan(endpit);
912 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
914 int endpos = cur.selEnd().pos();
916 BufferParams const & bufparams = bv()->buffer()->params();
917 boost::tie(endpit, endpos) = realcut ?
918 CutAndPaste::cutSelection(bufparams,
921 cur.selBegin().pos(), endpos,
924 : CutAndPaste::eraseSelection(bufparams,
927 cur.selBegin().pos(), endpos,
929 // sometimes necessary
931 begpit->stripLeadingSpaces();
933 redoParagraphs(begpit, undopit);
934 // cutSelection can invalidate the cursor so we need to set
936 // we prefer the end for when tracking changes
937 cursor().pos(endpos);
938 cursor().par(parOffset(endpit));
940 // need a valid cursor. (Lgb)
941 cur.clearSelection();
946 void LyXText::copySelection()
948 LCursor & cur = bv()->cursor();
949 // stuff the selection onto the X clipboard, from an explicit copy request
950 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
952 // this doesnt make sense, if there is no selection
953 if (!cur.selection())
956 // ok we have a selection. This is always between cur.selBegin()
957 // and sel_end cursor
959 // copy behind a space if there is one
960 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
961 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
962 && (cur.selBegin().par() != cur.selEnd().par()
963 || cur.selBegin().pos() < cur.selEnd().pos()))
964 cur.selBegin().pos(cur.selBegin().pos() + 1);
966 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
967 getPar(cur.selEnd().par()),
968 cur.selBegin().pos(),
970 bv()->buffer()->params().textclass);
974 void LyXText::pasteSelection(size_t sel_index)
976 LCursor & cur = bv()->cursor();
977 // this does not make sense, if there is nothing to paste
978 if (!CutAndPaste::checkPastePossible())
981 recUndo(cursor().par());
983 ParagraphList::iterator endpit;
988 boost::tie(ppp, endpit) =
989 CutAndPaste::pasteSelection(*bv()->buffer(),
991 cursorPar(), cursor().pos(),
992 bv()->buffer()->params().textclass,
994 bufferErrors(*bv()->buffer(), el);
995 bv()->showErrorList(_("Paste"));
997 redoParagraphs(cursorPar(), endpit);
999 cur.clearSelection();
1001 setCursor(ppp.first, ppp.second);
1007 void LyXText::setSelectionRange(lyx::pos_type length)
1012 LCursor & cur = bv()->cursor();
1020 // simple replacing. The font of the first selected character is used
1021 void LyXText::replaceSelectionWithString(string const & str)
1023 LCursor & cur = bv()->cursor();
1027 // Get font setting before we cut
1028 pos_type pos = cur.selEnd().pos();
1029 LyXFont const font = getPar(cur.selBegin())
1030 ->getFontSettings(bv()->buffer()->params(),
1031 cur.selBegin().pos());
1033 // Insert the new string
1034 string::const_iterator cit = str.begin();
1035 string::const_iterator end = str.end();
1036 for (; cit != end; ++cit) {
1037 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1041 // Cut the selection
1042 cutSelection(true, false);
1048 // needed to insert the selection
1049 void LyXText::insertStringAsLines(string const & str)
1051 LCursor & cur = bv()->cursor();
1052 ParagraphList::iterator pit = cursorPar();
1053 pos_type pos = cursor().pos();
1054 ParagraphList::iterator endpit = boost::next(cursorPar());
1055 recordUndo(cur, Undo::ATOMIC);
1057 // only to be sure, should not be neccessary
1058 cur.clearSelection();
1059 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1061 redoParagraphs(cursorPar(), endpit);
1063 setCursor(pit, pos);
1068 // turn double CR to single CR, others are converted into one
1069 // blank. Then insertStringAsLines is called
1070 void LyXText::insertStringAsParagraphs(string const & str)
1072 string linestr(str);
1073 bool newline_inserted = false;
1074 string::size_type const siz = linestr.length();
1076 for (string::size_type i = 0; i < siz; ++i) {
1077 if (linestr[i] == '\n') {
1078 if (newline_inserted) {
1079 // we know that \r will be ignored by
1080 // insertStringAsLines. Of course, it is a dirty
1081 // trick, but it works...
1082 linestr[i - 1] = '\r';
1086 newline_inserted = true;
1088 } else if (IsPrintable(linestr[i])) {
1089 newline_inserted = false;
1092 insertStringAsLines(linestr);
1096 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1098 setCursor(parOffset(pit), pos);
1102 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1105 CursorSlice old_cursor = cursor();
1106 setCursorIntern(par, pos, setfont, boundary);
1107 return deleteEmptyParagraphMechanism(old_cursor);
1111 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1112 pos_type pos, bool boundary)
1114 BOOST_ASSERT(par != int(paragraphs().size()));
1118 cur.boundary(boundary);
1120 // no rows, no fun...
1121 if (paragraphs().begin()->rows.empty())
1124 // now some strict checking
1125 Paragraph & para = *getPar(par);
1126 Row const & row = *para.getRow(pos);
1127 pos_type const end = row.endpos();
1129 // None of these should happen, but we're scaredy-cats
1131 lyxerr << "dont like -1" << endl;
1134 BOOST_ASSERT(false);
1135 } else if (pos > para.size()) {
1136 lyxerr << "dont like 1, pos: " << pos
1137 << " size: " << para.size()
1138 << " row.pos():" << row.pos()
1139 << " paroffset: " << par << endl;
1142 BOOST_ASSERT(false);
1143 } else if (pos > end) {
1144 lyxerr << "dont like 2 please report" << endl;
1145 // This shouldn't happen.
1148 BOOST_ASSERT(false);
1149 } else if (pos < row.pos()) {
1150 lyxerr << "dont like 3 please report pos:" << pos
1151 << " size: " << para.size()
1152 << " row.pos():" << row.pos()
1153 << " paroffset: " << par << endl;
1156 BOOST_ASSERT(false);
1161 void LyXText::setCursorIntern(paroffset_type par,
1162 pos_type pos, bool setfont, bool boundary)
1164 setCursor(cursor(), par, pos, boundary);
1165 bv()->cursor().x_target() = cursorX(cursor());
1171 void LyXText::setCurrentFont()
1173 LCursor & cur = bv()->cursor();
1174 pos_type pos = cur.pos();
1175 ParagraphList::iterator pit = cursorPar();
1177 if (cursor().boundary() && pos > 0)
1181 if (pos == pit->size())
1183 else // potentional bug... BUG (Lgb)
1184 if (pit->isSeparator(pos)) {
1185 if (pos > pit->getRow(pos)->pos() &&
1186 bidi.level(pos) % 2 ==
1187 bidi.level(pos - 1) % 2)
1189 else if (pos + 1 < pit->size())
1194 BufferParams const & bufparams = bv()->buffer()->params();
1195 current_font = pit->getFontSettings(bufparams, pos);
1196 real_current_font = getFont(pit, pos);
1198 if (cursor().pos() == pit->size() &&
1199 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1200 !cursor().boundary()) {
1201 Language const * lang =
1202 pit->getParLanguage(bufparams);
1203 current_font.setLanguage(lang);
1204 current_font.setNumber(LyXFont::OFF);
1205 real_current_font.setLanguage(lang);
1206 real_current_font.setNumber(LyXFont::OFF);
1211 // returns the column near the specified x-coordinate of the row
1212 // x is set to the real beginning of this column
1213 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1214 Row const & row, int & x, bool & boundary) const
1217 double tmpx = row.x();
1218 double fill_separator = row.fill_separator();
1219 double fill_hfill = row.fill_hfill();
1220 double fill_label_hfill = row.fill_label_hfill();
1222 pos_type vc = row.pos();
1223 pos_type end = row.endpos();
1225 LyXLayout_ptr const & layout = pit->layout();
1227 bool left_side = false;
1229 pos_type body_pos = pit->beginOfBody();
1230 double last_tmpx = tmpx;
1233 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1236 // check for empty row
1238 x = int(tmpx) + xo_;
1242 while (vc < end && tmpx <= x) {
1243 c = bidi.vis2log(vc);
1245 if (body_pos > 0 && c == body_pos - 1) {
1246 tmpx += fill_label_hfill +
1247 font_metrics::width(layout->labelsep, getLabelFont(pit));
1248 if (pit->isLineSeparator(body_pos - 1))
1249 tmpx -= singleWidth(pit, body_pos - 1);
1252 if (hfillExpansion(*pit, row, c)) {
1253 tmpx += singleWidth(pit, c);
1257 tmpx += fill_label_hfill;
1258 } else if (pit->isSeparator(c)) {
1259 tmpx += singleWidth(pit, c);
1261 tmpx += fill_separator;
1263 tmpx += singleWidth(pit, c);
1268 if ((tmpx + last_tmpx) / 2 > x) {
1273 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1276 // This (rtl_support test) is not needed, but gives
1277 // some speedup if rtl_support == false
1278 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1280 // If lastrow is false, we don't need to compute
1281 // the value of rtl.
1282 bool const rtl = lastrow
1283 ? pit->isRightToLeftPar(bv()->buffer()->params())
1286 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1287 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1289 else if (vc == row.pos()) {
1290 c = bidi.vis2log(vc);
1291 if (bidi.level(c) % 2 == 1)
1294 c = bidi.vis2log(vc - 1);
1295 bool const rtl = (bidi.level(c) % 2 == 1);
1296 if (left_side == rtl) {
1298 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1302 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1303 if (bidi.level(end -1) % 2 == 0)
1304 tmpx -= singleWidth(pit, end - 1);
1306 tmpx += singleWidth(pit, end - 1);
1310 x = int(tmpx) + xo_;
1311 return c - row.pos();
1315 void LyXText::setCursorFromCoordinates(int x, int y)
1317 CursorSlice old_cursor = cursor();
1318 setCursorFromCoordinates(cursor(), x, y);
1320 deleteEmptyParagraphMechanism(old_cursor);
1324 // x,y are coordinates relative to this LyXText
1325 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1327 ParagraphList::iterator pit;
1328 Row const & row = *getRowNearY(y, pit);
1330 pos_type const pos = row.pos() + getColumnNearX(pit, row, x, 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 int xx = x; // is modified by getColumnNearX
1341 ParagraphList::iterator pit;
1342 Row const & row = *getRowNearY(y, pit);
1344 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1345 cur.par() = parOffset(pit);
1347 cur.boundary() = bound;
1349 // try to descend into nested insets
1350 InsetBase * inset = checkInsetHit(x, y);
1352 // This should be just before or just behind the cursor position
1354 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1355 || inset == pit->getInset(pos));
1356 // Make sure the cursor points to the position before this inset.
1357 if (inset == pit->getInset(pos - 1))
1359 inset->edit(cur, x, y);
1364 bool LyXText::checkAndActivateInset(bool front)
1366 if (cursor().pos() == cursorPar()->size())
1368 InsetBase * inset = cursorPar()->getInset(cursor().pos());
1369 if (!isHighlyEditableInset(inset))
1371 inset->edit(bv()->cursor(), front);
1376 DispatchResult LyXText::moveRight()
1378 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1379 return moveLeftIntern(false, true, false);
1381 return moveRightIntern(true, true, false);
1385 DispatchResult LyXText::moveLeft()
1387 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1388 return moveRightIntern(true, true, false);
1390 return moveLeftIntern(false, true, false);
1394 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1396 ParagraphList::iterator c_par = cursorPar();
1397 if (boost::next(c_par) == paragraphs().end()
1398 && cursor().pos() >= c_par->size())
1399 return DispatchResult(false, FINISHED_RIGHT);
1400 if (activate_inset && checkAndActivateInset(front))
1401 return DispatchResult(true, true);
1404 bv()->cursor().clearSelection();
1405 return DispatchResult(true);
1409 DispatchResult LyXText::moveLeftIntern(bool front,
1410 bool activate_inset, bool selecting)
1412 if (cursor().par() == 0 && cursor().pos() <= 0)
1413 return DispatchResult(false, FINISHED);
1416 bv()->cursor().clearSelection();
1417 if (activate_inset && checkAndActivateInset(front))
1418 return DispatchResult(true, true);
1419 return DispatchResult(true);
1423 DispatchResult LyXText::moveUp()
1425 LCursor & cur = bv()->cursor();
1426 if (cur.par() == 0 && cursorRow() == firstRow())
1427 return DispatchResult(false, FINISHED_UP);
1429 cur.clearSelection();
1430 return DispatchResult(true);
1434 DispatchResult LyXText::moveDown()
1436 LCursor & cur = bv()->cursor();
1437 if (cur.par() == cur.lastpar() && cursorRow() == lastRow())
1438 return DispatchResult(false, FINISHED_DOWN);
1440 cur.clearSelection();
1441 return DispatchResult(true);
1445 bool LyXText::cursorLeft(bool internal)
1447 LCursor & cur = bv()->cursor();
1448 if (cur.pos() > 0) {
1449 bool boundary = cur.boundary();
1450 setCursor(cur.par(), cur.pos() - 1, true, false);
1451 if (!internal && !boundary &&
1452 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1453 setCursor(cur.par(), cur.pos() + 1, true, true);
1457 if (cur.par() != 0) {
1458 // steps into the paragraph above
1459 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1467 bool LyXText::cursorRight(bool internal)
1469 LCursor & cur = bv()->cursor();
1470 if (!internal && cur.boundary()) {
1471 setCursor(cur.par(), cur.pos(), true, false);
1475 if (cur.pos() != cur.lastpos()) {
1476 setCursor(cur.par(), cur.pos() + 1, true, false);
1477 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1479 setCursor(cur.par(), cur.pos(), true, true);
1483 if (cur.par() + 1 != int(paragraphs().size())) {
1484 setCursor(cur.par() + 1, 0);
1492 void LyXText::cursorUp(bool selecting)
1494 LCursor & cur = bv()->cursor();
1495 Row const & row = *cursorRow();
1496 int x = cur.x_target();
1497 int y = cursorY(cur.current()) - row.baseline() - 1;
1498 setCursorFromCoordinates(x, y);
1501 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1502 if (inset_hit && isHighlyEditableInset(inset_hit))
1503 inset_hit->edit(cur, cur.x_target(), y);
1508 void LyXText::cursorDown(bool selecting)
1510 LCursor & cur = bv()->cursor();
1511 Row const & row = *cursorRow();
1512 int x = cur.x_target();
1513 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1514 setCursorFromCoordinates(x, y);
1517 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1518 if (inset_hit && isHighlyEditableInset(inset_hit))
1519 inset_hit->edit(cur, cur.x_target(), y);
1524 void LyXText::cursorUpParagraph()
1526 ParagraphList::iterator cpit = cursorPar();
1527 if (cursor().pos() > 0)
1529 else if (cpit != paragraphs().begin())
1530 setCursor(boost::prior(cpit), 0);
1534 void LyXText::cursorDownParagraph()
1536 ParagraphList::iterator pit = cursorPar();
1537 ParagraphList::iterator next_pit = boost::next(pit);
1539 if (next_pit != paragraphs().end())
1540 setCursor(next_pit, 0);
1542 setCursor(pit, pit->size());
1546 // fix the cursor `cur' after a characters has been deleted at `where'
1547 // position. Called by deleteEmptyParagraphMechanism
1548 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1550 // if cursor is not in the paragraph where the delete occured,
1552 if (cur.par() != where.par())
1555 // if cursor position is after the place where the delete occured,
1557 if (cur.pos() > where.pos())
1558 cur.pos(cur.pos()-1);
1560 // check also if we don't want to set the cursor on a spot behind the
1561 // pagragraph because we erased the last character.
1562 if (cur.pos() > cur.lastpos())
1563 cur.pos() = cur.lastpos();
1567 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1569 #warning Disabled as it crashes after the cursor data shift... (Andre)
1572 // Would be wrong to delete anything if we have a selection.
1573 if (bv()->cursor().selection())
1576 // Don't do anything if the cursor is invalid
1577 if (old_cursor.par() == -1)
1581 // We allow all kinds of "mumbo-jumbo" when freespacing.
1582 ParagraphList::iterator const old_pit = getPar(old_cursor);
1583 if (old_pit->isFreeSpacing())
1586 /* Ok I'll put some comments here about what is missing.
1587 I have fixed BackSpace (and thus Delete) to not delete
1588 double-spaces automagically. I have also changed Cut,
1589 Copy and Paste to hopefully do some sensible things.
1590 There are still some small problems that can lead to
1591 double spaces stored in the document file or space at
1592 the beginning of paragraphs(). This happens if you have
1593 the cursor between to spaces and then save. Or if you
1594 cut and paste and the selection have a space at the
1595 beginning and then save right after the paste. I am
1596 sure none of these are very hard to fix, but I will
1597 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1598 that I can get some feedback. (Lgb)
1601 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1602 // delete the LineSeparator.
1605 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1606 // delete the LineSeparator.
1609 // If the pos around the old_cursor were spaces, delete one of them.
1610 if (old_cursor.par() != cursor().par()
1611 || old_cursor.pos() != cursor().pos()) {
1613 // Only if the cursor has really moved
1614 if (old_cursor.pos() > 0
1615 && old_cursor.pos() < old_pit->size()
1616 && old_pit->isLineSeparator(old_cursor.pos())
1617 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1618 bool erased = old_pit->erase(old_cursor.pos() - 1);
1619 redoParagraph(old_pit);
1623 #ifdef WITH_WARNINGS
1624 #warning This will not work anymore when we have multiple views of the same buffer
1625 // In this case, we will have to correct also the cursors held by
1626 // other bufferviews. It will probably be easier to do that in a more
1627 // automated way in CursorSlice code. (JMarc 26/09/2001)
1629 // correct all cursors held by the LyXText
1630 fixCursorAfterDelete(cursor(), old_cursor);
1631 fixCursorAfterDelete(anchor(), old_cursor);
1636 // don't delete anything if this is the ONLY paragraph!
1637 if (paragraphs().size() == 1)
1640 // Do not delete empty paragraphs with keepempty set.
1641 if (old_pit->allowEmpty())
1644 // only do our magic if we changed paragraph
1645 if (old_cursor.par() == cursor().par())
1648 // record if we have deleted a paragraph
1649 // we can't possibly have deleted a paragraph before this point
1650 bool deleted = false;
1652 if (old_pit->empty()
1653 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1654 // ok, we will delete something
1655 CursorSlice tmpcursor;
1659 bool selection_position_was_oldcursor_position =
1660 anchor().par() == old_cursor.par()
1661 && anchor().pos() == old_cursor.pos();
1663 tmpcursor = cursor();
1664 cursor() = old_cursor; // that undo can restore the right cursor position
1666 ParagraphList::iterator endpit = boost::next(old_pit);
1667 while (endpit != paragraphs().end() && endpit->getDepth())
1670 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1671 cursor() = tmpcursor;
1674 ParagraphList::iterator tmppit = cursorPar();
1676 paragraphs().erase(old_pit);
1677 // update cursor par offset
1678 cursor().par(parOffset(tmppit));
1681 if (selection_position_was_oldcursor_position) {
1682 // correct selection
1683 bv()->resetAnchor();
1690 if (old_pit->stripLeadingSpaces()) {
1691 redoParagraph(old_pit);
1692 bv()->resetAnchor();
1699 ParagraphList & LyXText::paragraphs() const
1701 return const_cast<ParagraphList &>(paragraphs_);
1705 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1707 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1711 void LyXText::recUndo(lyx::paroffset_type par) const
1713 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1717 bool LyXText::isInInset() const
1723 bool LyXText::toggleInset()
1725 InsetBase * inset = bv()->cursor().nextInset();
1726 // is there an editable inset at cursor position?
1727 if (!isEditableInset(inset))
1729 //bv()->owner()->message(inset->editMessage());
1731 // do we want to keep this?? (JMarc)
1732 if (!isHighlyEditableInset(inset))
1733 recUndo(cursor().par());
1735 if (inset->isOpen())
1743 int defaultRowHeight()
1745 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);