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::support::bformat;
70 using std::ostringstream;
74 LyXText::LyXText(BufferView * bv, bool in_inset)
75 : height_(0), width_(0), maxwidth_(bv ? bv->workWidth() : 100),
76 background_color_(LColor::background),
77 bv_owner(bv), in_inset_(in_inset), xo_(0), yo_(0)
81 void LyXText::init(BufferView * bv)
85 ParagraphList::iterator const beg = paragraphs().begin();
86 ParagraphList::iterator const end = paragraphs().end();
87 for (ParagraphList::iterator pit = beg; pit != end; ++pit)
90 maxwidth_ = bv->workWidth();
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(LCursor & cur, string const & layout)
284 BOOST_ASSERT(this == cur.text());
285 // special handling of new environment insets
286 BufferParams const & params = bv()->buffer()->params();
287 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
288 if (lyxlayout->is_environment) {
289 // move everything in a new environment inset
290 lyxerr << "setting layout " << layout << endl;
291 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
292 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
293 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
294 InsetBase * inset = new InsetEnvironment(params, layout);
295 insertInset(cur, inset);
296 //inset->edit(cur, true);
297 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
301 ParagraphList::iterator start = getPar(cur.selBegin().par());
302 ParagraphList::iterator end = boost::next(getPar(cur.selEnd().par()));
303 ParagraphList::iterator endpit = setLayout(start, end, layout);
304 redoParagraphs(start, endpit);
312 void getSelectionSpan(LCursor & cur, LyXText & text,
313 ParagraphList::iterator & beg,
314 ParagraphList::iterator & end)
316 if (!cur.selection()) {
317 beg = text.getPar(cur.par());
318 end = boost::next(beg);
320 beg = text.getPar(cur.selBegin());
321 end = boost::next(text.getPar(cur.selEnd()));
326 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
327 Paragraph const & par,
330 if (par.layout()->labeltype == LABEL_BIBLIO)
332 int const depth = par.params().depth();
333 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
335 if (type == bv_funcs::DEC_DEPTH && depth > 0)
344 bool LyXText::changeDepthAllowed(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
346 BOOST_ASSERT(this == cur.text());
347 ParagraphList::iterator beg, end;
348 getSelectionSpan(cur, *this, beg, end);
350 if (beg != paragraphs().begin())
351 max_depth = boost::prior(beg)->getMaxDepthAfter();
353 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
354 if (::changeDepthAllowed(type, *pit, max_depth))
356 max_depth = pit->getMaxDepthAfter();
362 void LyXText::changeDepth(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
364 BOOST_ASSERT(this == cur.text());
365 ParagraphList::iterator beg, end;
366 getSelectionSpan(cur, *this, beg, end);
367 recordUndoSelection(cur);
370 if (beg != paragraphs().begin())
371 max_depth = boost::prior(beg)->getMaxDepthAfter();
373 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
374 if (::changeDepthAllowed(type, *pit, max_depth)) {
375 int const depth = pit->params().depth();
376 if (type == bv_funcs::INC_DEPTH)
377 pit->params().depth(depth + 1);
379 pit->params().depth(depth - 1);
381 max_depth = pit->getMaxDepthAfter();
383 // this handles the counter labels, and also fixes up
384 // depth values for follow-on (child) paragraphs
389 // set font over selection and make a total rebreak of those paragraphs
390 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
392 BOOST_ASSERT(this == cur.text());
393 // if there is no selection just set the current_font
394 if (!cur.selection()) {
395 // Determine basis font
397 ParagraphList::iterator pit = getPar(cur.par());
398 if (cur.pos() < pit->beginOfBody())
399 layoutfont = getLabelFont(pit);
401 layoutfont = getLayoutFont(pit);
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 recordUndoSelection(cur);
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(LCursor & cur)
446 BOOST_ASSERT(this == cur.text());
447 setCursor(cur, cur.par(), cur.textRow().pos());
451 void LyXText::cursorEnd(LCursor & cur)
453 BOOST_ASSERT(this == cur.text());
454 // if not on the last row of the par, put the cursor before
456 pos_type const end = cur.textRow().endpos();
457 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
461 void LyXText::cursorTop(LCursor & cur)
463 BOOST_ASSERT(this == cur.text());
464 setCursor(cur, 0, 0);
468 void LyXText::cursorBottom(LCursor & cur)
470 BOOST_ASSERT(this == cur.text());
471 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
475 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
477 BOOST_ASSERT(this == cur.text());
478 // If the mask is completely neutral, tell user
479 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
480 // Could only happen with user style
481 cur.message(_("No font change defined. "
482 "Use Character under the Layout menu to define font change."));
486 // Try implicit word selection
487 // If there is a change in the language the implicit word selection
489 CursorSlice resetCursor = cur.current();
490 bool implicitSelection =
491 font.language() == ignore_language
492 && font.number() == LyXFont::IGNORE
493 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
496 setFont(cur, font, toggleall);
498 // Implicit selections are cleared afterwards
499 // and cursor is set to the original position.
500 if (implicitSelection) {
501 cur.clearSelection();
502 cur.current() = resetCursor;
508 string LyXText::getStringToIndex(LCursor & cur)
510 BOOST_ASSERT(this == cur.text());
511 // Try implicit word selection
512 // If there is a change in the language the implicit word selection
514 CursorSlice const reset_cursor = cur.current();
515 bool const implicitSelection =
516 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
519 if (!cur.selection())
520 cur.message(_("Nothing to index!"));
521 else if (cur.selBegin().par() != cur.selEnd().par())
522 cur.message(_("Cannot index more than one paragraph!"));
524 idxstring = cur.selectionAsString(false);
526 // Reset cursors to their original position.
527 cur.current() = reset_cursor;
530 // Clear the implicit selection.
531 if (implicitSelection)
532 cur.clearSelection();
538 // the DTP switches for paragraphs(). LyX will store them in the first
539 // physical paragraph. When a paragraph is broken, the top settings rest,
540 // the bottom settings are given to the new one. So I can make sure,
541 // they do not duplicate themself and you cannot play dirty tricks with
544 void LyXText::setParagraph(LCursor & cur,
545 Spacing const & spacing, LyXAlignment align,
546 string const & labelwidthstring, bool noindent)
548 BOOST_ASSERT(cur.text());
549 // make sure that the depth behind the selection are restored, too
550 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
551 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
553 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
554 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
556 for (--pit; pit != beg; ++pit) {
557 ParagraphParameters & params = pit->params();
558 params.spacing(spacing);
560 // does the layout allow the new alignment?
561 LyXLayout_ptr const & layout = pit->layout();
563 if (align == LYX_ALIGN_LAYOUT)
564 align = layout->align;
565 if (align & layout->alignpossible) {
566 if (align == layout->align)
567 params.align(LYX_ALIGN_LAYOUT);
571 pit->setLabelWidthString(labelwidthstring);
572 params.noindent(noindent);
575 redoParagraphs(getPar(cur.selBegin()), undopit);
579 string expandLabel(LyXTextClass const & textclass,
580 LyXLayout_ptr const & layout, bool appendix)
582 string fmt = appendix ?
583 layout->labelstring_appendix() : layout->labelstring();
585 // handle 'inherited level parts' in 'fmt',
586 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
587 size_t const i = fmt.find('@', 0);
588 if (i != string::npos) {
589 size_t const j = fmt.find('@', i + 1);
590 if (j != string::npos) {
591 string parent(fmt, i + 1, j - i - 1);
592 string label = expandLabel(textclass, textclass[parent], appendix);
593 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
597 return textclass.counters().counterLabel(fmt);
603 void incrementItemDepth(ParagraphList::iterator pit,
604 ParagraphList::iterator first_pit)
606 int const cur_labeltype = pit->layout()->labeltype;
608 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
611 int const cur_depth = pit->getDepth();
613 ParagraphList::iterator prev_pit = boost::prior(pit);
615 int const prev_depth = prev_pit->getDepth();
616 int const prev_labeltype = prev_pit->layout()->labeltype;
617 if (prev_depth == 0 && cur_depth > 0) {
618 if (prev_labeltype == cur_labeltype) {
619 pit->itemdepth = prev_pit->itemdepth + 1;
622 } else if (prev_depth < cur_depth) {
623 if (prev_labeltype == cur_labeltype) {
624 pit->itemdepth = prev_pit->itemdepth + 1;
627 } else if (prev_depth == cur_depth) {
628 if (prev_labeltype == cur_labeltype) {
629 pit->itemdepth = prev_pit->itemdepth;
633 if (prev_pit == first_pit)
641 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
642 ParagraphList::iterator firstpit,
648 int const cur_depth = pit->getDepth();
649 ParagraphList::iterator prev_pit = boost::prior(pit);
651 int const prev_depth = prev_pit->getDepth();
652 int const prev_labeltype = prev_pit->layout()->labeltype;
653 if (prev_depth <= cur_depth) {
654 if (prev_labeltype != LABEL_ENUMERATE) {
655 switch (pit->itemdepth) {
657 counters.reset("enumi");
659 counters.reset("enumii");
661 counters.reset("enumiii");
663 counters.reset("enumiv");
669 if (prev_pit == firstpit)
679 // set the counter of a paragraph. This includes the labels
680 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
682 BufferParams const & bufparams = buf.params();
683 LyXTextClass const & textclass = bufparams.getLyXTextClass();
684 LyXLayout_ptr const & layout = pit->layout();
685 ParagraphList::iterator first_pit = paragraphs().begin();
686 Counters & counters = textclass.counters();
691 if (pit == first_pit) {
692 pit->params().appendix(pit->params().startOfAppendix());
694 pit->params().appendix(boost::prior(pit)->params().appendix());
695 if (!pit->params().appendix() &&
696 pit->params().startOfAppendix()) {
697 pit->params().appendix(true);
698 textclass.counters().reset();
701 // Maybe we have to increment the item depth.
702 incrementItemDepth(pit, first_pit);
705 // erase what was there before
706 pit->params().labelString(string());
708 if (layout->margintype == MARGIN_MANUAL) {
709 if (pit->params().labelWidthString().empty())
710 pit->setLabelWidthString(layout->labelstring());
712 pit->setLabelWidthString(string());
715 // is it a layout that has an automatic label?
716 if (layout->labeltype == LABEL_COUNTER) {
717 BufferParams const & bufparams = buf.params();
718 LyXTextClass const & textclass = bufparams.getLyXTextClass();
719 counters.step(layout->counter);
720 string label = expandLabel(textclass, layout, pit->params().appendix());
721 pit->params().labelString(label);
722 } else if (layout->labeltype == LABEL_ITEMIZE) {
723 // At some point of time we should do something more
724 // clever here, like:
725 // pit->params().labelString(
726 // bufparams.user_defined_bullet(pit->itemdepth).getText());
727 // for now, use a simple hardcoded label
729 switch (pit->itemdepth) {
744 pit->params().labelString(itemlabel);
745 } else if (layout->labeltype == LABEL_ENUMERATE) {
746 // Maybe we have to reset the enumeration counter.
747 resetEnumCounterIfNeeded(pit, first_pit, counters);
750 // Yes I know this is a really, really! bad solution
752 string enumcounter = "enum";
754 switch (pit->itemdepth) {
766 // not a valid enumdepth...
770 counters.step(enumcounter);
772 pit->params().labelString(counters.enumLabel(enumcounter));
773 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
774 counters.step("bibitem");
775 int number = counters.value("bibitem");
776 if (pit->bibitem()) {
777 pit->bibitem()->setCounter(number);
778 pit->params().labelString(layout->labelstring());
780 // In biblio should't be following counters but...
782 string s = buf.B_(layout->labelstring());
785 if (layout->labeltype == LABEL_SENSITIVE) {
786 ParagraphList::iterator end = paragraphs().end();
787 ParagraphList::iterator tmppit = pit;
790 while (tmppit != end && tmppit->inInset()
791 // the single '=' is intended below
792 && (in = tmppit->inInset()->owner()))
794 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
795 in->lyxCode() == InsetBase::WRAP_CODE) {
799 Paragraph const * owner = &ownerPar(buf, in);
801 for ( ; tmppit != end; ++tmppit)
802 if (&*tmppit == owner)
810 if (in->lyxCode() == InsetBase::FLOAT_CODE)
811 type = static_cast<InsetFloat*>(in)->params().type;
812 else if (in->lyxCode() == InsetBase::WRAP_CODE)
813 type = static_cast<InsetWrap*>(in)->params().type;
817 Floating const & fl = textclass.floats().getType(type);
819 counters.step(fl.type());
821 // Doesn't work... yet.
822 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
824 // par->SetLayout(0);
825 // s = layout->labelstring;
826 s = _("Senseless: ");
829 pit->params().labelString(s);
835 // Updates all counters.
836 void LyXText::updateCounters()
839 bv()->buffer()->params().getLyXTextClass().counters().reset();
841 bool update_pos = false;
843 ParagraphList::iterator beg = paragraphs().begin();
844 ParagraphList::iterator end = paragraphs().end();
845 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
846 string const oldLabel = pit->params().labelString();
849 maxdepth = boost::prior(pit)->getMaxDepthAfter();
851 if (pit->params().depth() > maxdepth)
852 pit->params().depth(maxdepth);
854 // setCounter can potentially change the labelString.
855 setCounter(*bv()->buffer(), pit);
856 string const & newLabel = pit->params().labelString();
857 if (oldLabel != newLabel) {
858 redoParagraphInternal(pit);
864 updateParPositions();
868 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
870 BOOST_ASSERT(this == cur.text());
873 cur.paragraph().insertInset(cur.pos(), inset);
874 // Just to rebreak and refresh correctly.
875 // The character will not be inserted a second time
876 insertChar(cur, 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))
887 void LyXText::cutSelection(LCursor & cur, bool doclear, bool realcut)
889 BOOST_ASSERT(this == cur.text());
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 recordUndoSelection(cur);
909 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
910 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
911 ParagraphList::iterator undopit = undoSpan(endpit);
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
937 cur.par() = parOffset(endpit);
939 // need a valid cursor. (Lgb)
940 cur.clearSelection();
945 void LyXText::copySelection(LCursor & cur)
947 BOOST_ASSERT(this == cur.text());
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();
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(LCursor & cur, size_t sel_index)
975 // this does not make sense, if there is nothing to paste
976 if (!CutAndPaste::checkPastePossible())
981 ParagraphList::iterator endpit;
986 boost::tie(ppp, endpit) =
987 CutAndPaste::pasteSelection(*bv()->buffer(),
989 getPar(cur.par()), cur.pos(),
990 bv()->buffer()->params().textclass,
992 bufferErrors(*bv()->buffer(), el);
993 bv()->showErrorList(_("Paste"));
995 redoParagraphs(getPar(cur.par()), endpit);
997 cur.clearSelection();
999 setCursor(cur, parOffset(ppp.first), ppp.second);
1005 void LyXText::setSelectionRange(LCursor & cur, lyx::pos_type length)
1016 // simple replacing. The font of the first selected character is used
1017 void LyXText::replaceSelectionWithString(LCursor & cur, string const & str)
1022 // Get font setting before we cut
1023 pos_type pos = cur.selEnd().pos();
1024 LyXFont const font = getPar(cur.selBegin())
1025 ->getFontSettings(bv()->buffer()->params(),
1026 cur.selBegin().pos());
1028 // Insert the new string
1029 string::const_iterator cit = str.begin();
1030 string::const_iterator end = str.end();
1031 for (; cit != end; ++cit) {
1032 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1036 // Cut the selection
1037 cutSelection(cur, true, false);
1042 // needed to insert the selection
1043 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
1045 ParagraphList::iterator pit = getPar(cur.par());
1046 ParagraphList::iterator endpit = boost::next(pit);
1047 pos_type pos = cursor().pos();
1050 // only to be sure, should not be neccessary
1051 cur.clearSelection();
1052 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1054 redoParagraphs(getPar(cur.par()), endpit);
1056 setCursor(cur, cur.par(), pos);
1061 // turn double CR to single CR, others are converted into one
1062 // blank. Then insertStringAsLines is called
1063 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
1065 string linestr = str;
1066 bool newline_inserted = false;
1068 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
1069 if (linestr[i] == '\n') {
1070 if (newline_inserted) {
1071 // we know that \r will be ignored by
1072 // insertStringAsLines. Of course, it is a dirty
1073 // trick, but it works...
1074 linestr[i - 1] = '\r';
1078 newline_inserted = true;
1080 } else if (IsPrintable(linestr[i])) {
1081 newline_inserted = false;
1084 insertStringAsLines(cur, linestr);
1088 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
1089 bool setfont, bool boundary)
1091 CursorSlice old_cursor = cur.current();
1092 setCursorIntern(cur, par, pos, setfont, boundary);
1093 return deleteEmptyParagraphMechanism(cur.current(), old_cursor);
1097 void LyXText::setCursor(CursorSlice & cur, par_type par,
1098 pos_type pos, bool boundary)
1100 BOOST_ASSERT(par != int(paragraphs().size()));
1104 cur.boundary() = boundary;
1106 // no rows, no fun...
1107 if (paragraphs().begin()->rows.empty())
1110 // now some strict checking
1111 Paragraph & para = *getPar(par);
1112 Row const & row = *para.getRow(pos);
1113 pos_type const end = row.endpos();
1115 // None of these should happen, but we're scaredy-cats
1117 lyxerr << "dont like -1" << endl;
1118 BOOST_ASSERT(false);
1121 if (pos > para.size()) {
1122 lyxerr << "dont like 1, pos: " << pos
1123 << " size: " << para.size()
1124 << " row.pos():" << row.pos()
1125 << " par: " << par << endl;
1126 BOOST_ASSERT(false);
1130 lyxerr << "dont like 2 please report" << endl;
1131 // This shouldn't happen.
1132 BOOST_ASSERT(false);
1135 if (pos < row.pos()) {
1136 lyxerr << "dont like 3 please report pos:" << pos
1137 << " size: " << para.size()
1138 << " row.pos():" << row.pos()
1139 << " par: " << par << endl;
1140 BOOST_ASSERT(false);
1145 void LyXText::setCursorIntern(LCursor & cur,
1146 par_type par, pos_type pos, bool setfont, bool boundary)
1148 setCursor(cur.current(), par, pos, boundary);
1149 cur.x_target() = cursorX(cur.current());
1151 setCurrentFont(cur);
1155 void LyXText::setCurrentFont(LCursor & cur)
1157 BOOST_ASSERT(this == cur.text());
1158 pos_type pos = cur.pos();
1159 ParagraphList::iterator pit = getPar(cur.par());
1161 if (cur.boundary() && pos > 0)
1165 if (pos == cur.lastpos())
1167 else // potentional bug... BUG (Lgb)
1168 if (pit->isSeparator(pos)) {
1169 if (pos > cur.textRow().pos() &&
1170 bidi.level(pos) % 2 ==
1171 bidi.level(pos - 1) % 2)
1173 else if (pos + 1 < cur.lastpos())
1178 BufferParams const & bufparams = bv()->buffer()->params();
1179 current_font = pit->getFontSettings(bufparams, pos);
1180 real_current_font = getFont(pit, pos);
1182 if (cur.pos() == cur.lastpos()
1183 && bidi.isBoundary(*bv()->buffer(), *pit, cur.pos())
1184 && !cur.boundary()) {
1185 Language const * lang = pit->getParLanguage(bufparams);
1186 current_font.setLanguage(lang);
1187 current_font.setNumber(LyXFont::OFF);
1188 real_current_font.setLanguage(lang);
1189 real_current_font.setNumber(LyXFont::OFF);
1194 // x is an absolute screen coord
1195 // returns the column near the specified x-coordinate of the row
1196 // x is set to the real beginning of this column
1197 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1198 Row const & row, int & x, bool & boundary) const
1201 RowMetrics const r = computeRowMetrics(pit, row);
1203 pos_type vc = row.pos();
1204 pos_type end = row.endpos();
1206 LyXLayout_ptr const & layout = pit->layout();
1208 bool left_side = false;
1210 pos_type body_pos = pit->beginOfBody();
1213 double last_tmpx = tmpx;
1216 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1219 // check for empty row
1221 x = int(tmpx) + xo_;
1225 while (vc < end && tmpx <= x) {
1226 c = bidi.vis2log(vc);
1228 if (body_pos > 0 && c == body_pos - 1) {
1229 tmpx += r.label_hfill +
1230 font_metrics::width(layout->labelsep, getLabelFont(pit));
1231 if (pit->isLineSeparator(body_pos - 1))
1232 tmpx -= singleWidth(pit, body_pos - 1);
1235 if (hfillExpansion(*pit, row, c)) {
1236 tmpx += singleWidth(pit, c);
1240 tmpx += r.label_hfill;
1241 } else if (pit->isSeparator(c)) {
1242 tmpx += singleWidth(pit, c);
1244 tmpx += r.separator;
1246 tmpx += singleWidth(pit, c);
1251 if ((tmpx + last_tmpx) / 2 > x) {
1256 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1259 // This (rtl_support test) is not needed, but gives
1260 // some speedup if rtl_support == false
1261 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1263 // If lastrow is false, we don't need to compute
1264 // the value of rtl.
1265 bool const rtl = lastrow ? isRTL(*pit) : false;
1267 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1268 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1270 else if (vc == row.pos()) {
1271 c = bidi.vis2log(vc);
1272 if (bidi.level(c) % 2 == 1)
1275 c = bidi.vis2log(vc - 1);
1276 bool const rtl = (bidi.level(c) % 2 == 1);
1277 if (left_side == rtl) {
1279 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1283 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1284 if (bidi.level(end -1) % 2 == 0)
1285 tmpx -= singleWidth(pit, end - 1);
1287 tmpx += singleWidth(pit, end - 1);
1291 x = int(tmpx) + xo_;
1292 return c - row.pos();
1296 // x,y are absolute coordinates
1297 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1301 CursorSlice old_cursor = cur.current();
1302 ParagraphList::iterator pit;
1303 Row const & row = *getRowNearY(y, pit);
1304 lyxerr << "hit row at: " << row.pos() << endl;
1306 int xx = x + xo_; // getRowNearX get absolute x coords
1307 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1308 cur.par() = parOffset(pit);
1310 cur.boundary() = bound;
1311 deleteEmptyParagraphMechanism(cur.current(), old_cursor);
1315 // x,y are absolute screen coordinates
1316 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1318 ParagraphList::iterator pit;
1319 Row const & row = *getRowNearY(y - yo_, pit);
1322 int xx = x; // is modified by getColumnNearX
1323 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1324 cur.par() = parOffset(pit);
1326 cur.boundary() = bound;
1328 // try to descend into nested insets
1329 InsetBase * inset = checkInsetHit(x, y);
1333 // This should be just before or just behind the
1334 // cursor position set above.
1335 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1336 || inset == pit->getInset(pos));
1337 // Make sure the cursor points to the position before
1339 if (inset == pit->getInset(pos - 1))
1341 return inset->editXY(cur, x, y);
1345 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1347 if (cur.selection())
1349 if (cur.pos() == cur.lastpos())
1351 InsetBase * inset = cur.nextInset();
1352 if (!isHighlyEditableInset(inset))
1354 inset->edit(cur, front);
1359 void LyXText::cursorLeft(LCursor & cur)
1361 if (cur.pos() != 0) {
1362 bool boundary = cur.boundary();
1363 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1364 if (!checkAndActivateInset(cur, false)) {
1365 if (false && !boundary &&
1366 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1367 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1372 if (cur.par() != 0) {
1373 // steps into the paragraph above
1374 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1)->size());
1379 void LyXText::cursorRight(LCursor & cur)
1381 if (false && cur.boundary()) {
1382 setCursor(cur, cur.par(), cur.pos(), true, false);
1386 if (cur.pos() != cur.lastpos()) {
1387 if (!checkAndActivateInset(cur, true)) {
1388 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1389 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1391 setCursor(cur, cur.par(), cur.pos(), true, true);
1396 if (cur.par() != cur.lastpar())
1397 setCursor(cur, cur.par() + 1, 0);
1401 void LyXText::cursorUp(LCursor & cur)
1403 Row const & row = cur.textRow();
1404 int x = cur.x_target();
1405 int y = cursorY(cur.current()) - row.baseline() - 1;
1406 setCursorFromCoordinates(cur, x, y);
1408 if (!cur.selection()) {
1409 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1410 if (inset_hit && isHighlyEditableInset(inset_hit))
1411 inset_hit->editXY(cur, cur.x_target(), y);
1416 void LyXText::cursorDown(LCursor & cur)
1418 Row const & row = cur.textRow();
1419 int x = cur.x_target();
1420 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1421 setCursorFromCoordinates(cur, x, y);
1423 if (!cur.selection()) {
1424 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1425 if (inset_hit && isHighlyEditableInset(inset_hit))
1426 inset_hit->editXY(cur, cur.x_target(), y);
1431 void LyXText::cursorUpParagraph(LCursor & cur)
1434 setCursor(cur, cur.par(), 0);
1435 else if (cur.par() != 0)
1436 setCursor(cur, cur.par() - 1, 0);
1440 void LyXText::cursorDownParagraph(LCursor & cur)
1442 if (cur.par() != cur.lastpar())
1443 setCursor(cur, cur.par() + 1, 0);
1445 setCursor(cur, cur.par(), cur.lastpos());
1449 // fix the cursor `cur' after a characters has been deleted at `where'
1450 // position. Called by deleteEmptyParagraphMechanism
1451 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1453 // do notheing if cursor is not in the paragraph where the
1454 // deletion occured,
1455 if (cur.par() != where.par())
1458 // if cursor position is after the deletion place update it
1459 if (cur.pos() > where.pos())
1462 // check also if we don't want to set the cursor on a spot behind the
1463 // pagragraph because we erased the last character.
1464 if (cur.pos() > cur.lastpos())
1465 cur.pos() = cur.lastpos();
1469 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice & cur,
1470 CursorSlice const & old_cursor)
1472 #warning Disabled as it crashes after the cursor data shift... (Andre)
1475 // Would be wrong to delete anything if we have a selection.
1476 //if (cur.selection())
1480 // We allow all kinds of "mumbo-jumbo" when freespacing.
1481 ParagraphList::iterator const old_pit = getPar(old_cursor.par());
1482 if (old_pit->isFreeSpacing())
1485 /* Ok I'll put some comments here about what is missing.
1486 I have fixed BackSpace (and thus Delete) to not delete
1487 double-spaces automagically. I have also changed Cut,
1488 Copy and Paste to hopefully do some sensible things.
1489 There are still some small problems that can lead to
1490 double spaces stored in the document file or space at
1491 the beginning of paragraphs(). This happens if you have
1492 the cursor between to spaces and then save. Or if you
1493 cut and paste and the selection have a space at the
1494 beginning and then save right after the paste. I am
1495 sure none of these are very hard to fix, but I will
1496 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1497 that I can get some feedback. (Lgb)
1500 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1501 // delete the LineSeparator.
1504 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1505 // delete the LineSeparator.
1508 // If the pos around the old_cursor were spaces, delete one of them.
1509 if (old_cursor.par() != cur.par() || old_cursor.pos() != cur.pos()) {
1511 // Only if the cursor has really moved
1512 if (old_cursor.pos() > 0
1513 && old_cursor.pos() < old_pit->size()
1514 && old_pit->isLineSeparator(old_cursor.pos())
1515 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1516 bool erased = old_pit->erase(old_cursor.pos() - 1);
1517 redoParagraph(old_pit);
1521 #ifdef WITH_WARNINGS
1522 #warning This will not work anymore when we have multiple views of the same buffer
1523 // In this case, we will have to correct also the cursors held by
1524 // other bufferviews. It will probably be easier to do that in a more
1525 // automated way in CursorSlice code. (JMarc 26/09/2001)
1527 // correct all cursors held by the LyXText
1528 fixCursorAfterDelete(cursor(), old_cursor);
1529 fixCursorAfterDelete(anchor(), old_cursor);
1534 // don't delete anything if this is the ONLY paragraph!
1535 if (paragraphs().size() == 1)
1538 // Do not delete empty paragraphs with keepempty set.
1539 if (old_pit->allowEmpty())
1542 // only do our magic if we changed paragraph
1543 if (old_cursor.par() == cur.par())
1546 // record if we have deleted a paragraph
1547 // we can't possibly have deleted a paragraph before this point
1548 bool deleted = false;
1550 if (old_pit->empty()
1551 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1552 // ok, we will delete something
1553 CursorSlice tmpcursor;
1557 bool selection_position_was_oldcursor_position =
1558 anchor().par() == old_cursor.par()
1559 && anchor().pos() == old_cursor.pos();
1561 tmpcursor = cursor();
1562 cursor() = old_cursor; // that undo can restore the right cursor position
1564 ParagraphList::iterator endpit = boost::next(old_pit);
1565 while (endpit != paragraphs().end() && endpit->getDepth())
1568 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1569 cursor() = tmpcursor;
1572 paragraphs().erase(old_pit);
1573 // update cursor par offset
1577 if (selection_position_was_oldcursor_position) {
1578 // correct selection
1579 bv()->resetAnchor();
1586 if (old_pit->stripLeadingSpaces()) {
1587 redoParagraph(old_pit);
1588 bv()->resetAnchor();
1595 ParagraphList & LyXText::paragraphs() const
1597 return const_cast<ParagraphList &>(paragraphs_);
1601 void LyXText::recUndo(par_type first, par_type last) const
1603 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1607 void LyXText::recUndo(par_type par) const
1609 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1613 bool LyXText::isInInset() const
1619 bool LyXText::toggleInset(LCursor & cur)
1621 InsetBase * inset = cur.nextInset();
1622 // is there an editable inset at cursor position?
1623 if (!isEditableInset(inset))
1625 cur.message(inset->editMessage());
1627 // do we want to keep this?? (JMarc)
1628 if (!isHighlyEditableInset(inset))
1631 if (inset->isOpen())
1639 int defaultRowHeight()
1641 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);