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(LCursor & cur)
446 setCursor(cur.par(), cur.textRow().pos());
450 void LyXText::cursorEnd(LCursor & cur)
452 // if not on the last row of the par, put the cursor before
454 pos_type const end = cur.textRow().endpos();
455 setCursor(cur.par(), end == cur.lastpos() ? end : end - 1);
459 void LyXText::cursorTop(LCursor &)
465 void LyXText::cursorBottom(LCursor & cur)
467 setCursor(cur.lastpar(), boost::prior(paragraphs().end())->size());
471 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
473 // If the mask is completely neutral, tell user
474 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
475 // Could only happen with user style
476 bv()->owner()->message(_("No font change defined. "
477 "Use Character under the Layout menu to define font change."));
481 // Try implicit word selection
482 // If there is a change in the language the implicit word selection
484 CursorSlice resetCursor = cursor();
485 bool implicitSelection =
486 font.language() == ignore_language
487 && font.number() == LyXFont::IGNORE
488 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
491 setFont(font, toggleall);
493 // Implicit selections are cleared afterwards
494 //and cursor is set to the original position.
495 if (implicitSelection) {
496 bv()->cursor().clearSelection();
497 cursor() = resetCursor;
498 bv()->cursor().resetAnchor();
503 string LyXText::getStringToIndex()
505 LCursor & cur = bv()->cursor();
506 // Try implicit word selection
507 // If there is a change in the language the implicit word selection
509 CursorSlice const reset_cursor = cursor();
510 bool const implicitSelection =
511 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
514 if (!cur.selection())
515 bv()->owner()->message(_("Nothing to index!"));
516 else if (cur.selBegin().par() != cur.selEnd().par())
517 bv()->owner()->message(_("Cannot index more than one paragraph!"));
519 idxstring = cur.selectionAsString(false);
521 // Reset cursors to their original position.
522 cursor() = reset_cursor;
525 // Clear the implicit selection.
526 if (implicitSelection)
527 cur.clearSelection();
533 // the DTP switches for paragraphs(). LyX will store them in the first
534 // physical paragraph. When a paragraph is broken, the top settings rest,
535 // the bottom settings are given to the new one. So I can make sure,
536 // they do not duplicate themself and you cannot play dirty tricks with
539 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
540 string const & labelwidthstring, bool noindent)
542 LCursor & cur = bv()->cursor();
543 // make sure that the depth behind the selection are restored, too
544 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
545 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
547 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
548 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
550 for (--pit; pit != beg; ++pit) {
551 ParagraphParameters & params = pit->params();
552 params.spacing(spacing);
554 // does the layout allow the new alignment?
555 LyXLayout_ptr const & layout = pit->layout();
557 if (align == LYX_ALIGN_LAYOUT)
558 align = layout->align;
559 if (align & layout->alignpossible) {
560 if (align == layout->align)
561 params.align(LYX_ALIGN_LAYOUT);
565 pit->setLabelWidthString(labelwidthstring);
566 params.noindent(noindent);
569 redoParagraphs(getPar(cur.selBegin()), undopit);
573 string expandLabel(LyXTextClass const & textclass,
574 LyXLayout_ptr const & layout, bool appendix)
576 string fmt = appendix ?
577 layout->labelstring_appendix() : layout->labelstring();
579 // handle 'inherited level parts' in 'fmt',
580 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
581 size_t const i = fmt.find('@', 0);
582 if (i != string::npos) {
583 size_t const j = fmt.find('@', i + 1);
584 if (j != string::npos) {
585 string parent(fmt, i + 1, j - i - 1);
586 string label = expandLabel(textclass, textclass[parent], appendix);
587 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
591 return textclass.counters().counterLabel(fmt);
597 void incrementItemDepth(ParagraphList::iterator pit,
598 ParagraphList::iterator first_pit)
600 int const cur_labeltype = pit->layout()->labeltype;
602 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
605 int const cur_depth = pit->getDepth();
607 ParagraphList::iterator prev_pit = boost::prior(pit);
609 int const prev_depth = prev_pit->getDepth();
610 int const prev_labeltype = prev_pit->layout()->labeltype;
611 if (prev_depth == 0 && cur_depth > 0) {
612 if (prev_labeltype == cur_labeltype) {
613 pit->itemdepth = prev_pit->itemdepth + 1;
616 } else if (prev_depth < cur_depth) {
617 if (prev_labeltype == cur_labeltype) {
618 pit->itemdepth = prev_pit->itemdepth + 1;
621 } else if (prev_depth == cur_depth) {
622 if (prev_labeltype == cur_labeltype) {
623 pit->itemdepth = prev_pit->itemdepth;
627 if (prev_pit == first_pit)
635 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
636 ParagraphList::iterator firstpit,
642 int const cur_depth = pit->getDepth();
643 ParagraphList::iterator prev_pit = boost::prior(pit);
645 int const prev_depth = prev_pit->getDepth();
646 int const prev_labeltype = prev_pit->layout()->labeltype;
647 if (prev_depth <= cur_depth) {
648 if (prev_labeltype != LABEL_ENUMERATE) {
649 switch (pit->itemdepth) {
651 counters.reset("enumi");
653 counters.reset("enumii");
655 counters.reset("enumiii");
657 counters.reset("enumiv");
663 if (prev_pit == firstpit)
673 // set the counter of a paragraph. This includes the labels
674 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
676 BufferParams const & bufparams = buf.params();
677 LyXTextClass const & textclass = bufparams.getLyXTextClass();
678 LyXLayout_ptr const & layout = pit->layout();
679 ParagraphList::iterator first_pit = paragraphs().begin();
680 Counters & counters = textclass.counters();
685 if (pit == first_pit) {
686 pit->params().appendix(pit->params().startOfAppendix());
688 pit->params().appendix(boost::prior(pit)->params().appendix());
689 if (!pit->params().appendix() &&
690 pit->params().startOfAppendix()) {
691 pit->params().appendix(true);
692 textclass.counters().reset();
695 // Maybe we have to increment the item depth.
696 incrementItemDepth(pit, first_pit);
699 // erase what was there before
700 pit->params().labelString(string());
702 if (layout->margintype == MARGIN_MANUAL) {
703 if (pit->params().labelWidthString().empty())
704 pit->setLabelWidthString(layout->labelstring());
706 pit->setLabelWidthString(string());
709 // is it a layout that has an automatic label?
710 if (layout->labeltype == LABEL_COUNTER) {
711 BufferParams const & bufparams = buf.params();
712 LyXTextClass const & textclass = bufparams.getLyXTextClass();
713 counters.step(layout->counter);
714 string label = expandLabel(textclass, layout, pit->params().appendix());
715 pit->params().labelString(label);
716 } else if (layout->labeltype == LABEL_ITEMIZE) {
717 // At some point of time we should do something more
718 // clever here, like:
719 // pit->params().labelString(
720 // bufparams.user_defined_bullet(pit->itemdepth).getText());
721 // for now, use a simple hardcoded label
723 switch (pit->itemdepth) {
738 pit->params().labelString(itemlabel);
739 } else if (layout->labeltype == LABEL_ENUMERATE) {
740 // Maybe we have to reset the enumeration counter.
741 resetEnumCounterIfNeeded(pit, first_pit, counters);
744 // Yes I know this is a really, really! bad solution
746 string enumcounter = "enum";
748 switch (pit->itemdepth) {
760 // not a valid enumdepth...
764 counters.step(enumcounter);
766 pit->params().labelString(counters.enumLabel(enumcounter));
767 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
768 counters.step("bibitem");
769 int number = counters.value("bibitem");
770 if (pit->bibitem()) {
771 pit->bibitem()->setCounter(number);
772 pit->params().labelString(layout->labelstring());
774 // In biblio should't be following counters but...
776 string s = buf.B_(layout->labelstring());
779 if (layout->labeltype == LABEL_SENSITIVE) {
780 ParagraphList::iterator end = paragraphs().end();
781 ParagraphList::iterator tmppit = pit;
784 while (tmppit != end && tmppit->inInset()
785 // the single '=' is intended below
786 && (in = tmppit->inInset()->owner()))
788 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
789 in->lyxCode() == InsetBase::WRAP_CODE) {
793 Paragraph const * owner = &ownerPar(buf, in);
795 for ( ; tmppit != end; ++tmppit)
796 if (&*tmppit == owner)
804 if (in->lyxCode() == InsetBase::FLOAT_CODE)
805 type = static_cast<InsetFloat*>(in)->params().type;
806 else if (in->lyxCode() == InsetBase::WRAP_CODE)
807 type = static_cast<InsetWrap*>(in)->params().type;
811 Floating const & fl = textclass.floats().getType(type);
813 counters.step(fl.type());
815 // Doesn't work... yet.
816 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
818 // par->SetLayout(0);
819 // s = layout->labelstring;
820 s = _("Senseless: ");
823 pit->params().labelString(s);
829 // Updates all counters.
830 void LyXText::updateCounters()
833 bv()->buffer()->params().getLyXTextClass().counters().reset();
835 bool update_pos = false;
837 ParagraphList::iterator beg = paragraphs().begin();
838 ParagraphList::iterator end = paragraphs().end();
839 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
840 string const oldLabel = pit->params().labelString();
843 maxdepth = boost::prior(pit)->getMaxDepthAfter();
845 if (pit->params().depth() > maxdepth)
846 pit->params().depth(maxdepth);
848 // setCounter can potentially change the labelString.
849 setCounter(*bv()->buffer(), pit);
850 string const & newLabel = pit->params().labelString();
851 if (oldLabel != newLabel) {
852 redoParagraphInternal(pit);
858 updateParPositions();
862 void LyXText::insertInset(InsetBase * inset)
864 if (!cursorPar()->insetAllowed(inset->lyxCode()))
867 recUndo(cursor().par());
869 cursorPar()->insertInset(cursor().pos(), inset);
870 // Just to rebreak and refresh correctly.
871 // The character will not be inserted a second time
872 insertChar(Paragraph::META_INSET);
873 // If we enter a highly editable inset the cursor should be before
874 // the inset. After an undo LyX tries to call inset->edit(...)
875 // and fails if the cursor is behind the inset and getInset
876 // does not return the inset!
877 if (isHighlyEditableInset(inset))
878 cursorLeft(bv()->cursor(), true);
884 void LyXText::cutSelection(bool doclear, bool realcut)
886 LCursor & cur = bv()->cursor();
887 // Stuff what we got on the clipboard. Even if there is no selection.
889 // There is a problem with having the stuffing here in that the
890 // larger the selection the slower LyX will get. This can be
891 // solved by running the line below only when the selection has
892 // finished. The solution used currently just works, to make it
893 // faster we need to be more clever and probably also have more
894 // calls to stuffClipboard. (Lgb)
895 bv()->stuffClipboard(cur.selectionAsString(true));
897 // This doesn't make sense, if there is no selection
898 if (!cur.selection())
901 // OK, we have a selection. This is always between cur.selBegin()
904 // make sure that the depth behind the selection are restored, too
905 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
906 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
907 ParagraphList::iterator undopit = undoSpan(endpit);
908 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
910 int endpos = cur.selEnd().pos();
912 BufferParams const & bufparams = bv()->buffer()->params();
913 boost::tie(endpit, endpos) = realcut ?
914 CutAndPaste::cutSelection(bufparams,
917 cur.selBegin().pos(), endpos,
920 : CutAndPaste::eraseSelection(bufparams,
923 cur.selBegin().pos(), endpos,
925 // sometimes necessary
927 begpit->stripLeadingSpaces();
929 redoParagraphs(begpit, undopit);
930 // cutSelection can invalidate the cursor so we need to set
932 // we prefer the end for when tracking changes
933 cursor().pos(endpos);
934 cursor().par(parOffset(endpit));
936 // need a valid cursor. (Lgb)
937 cur.clearSelection();
942 void LyXText::copySelection()
944 LCursor & cur = bv()->cursor();
945 // stuff the selection onto the X clipboard, from an explicit copy request
946 bv()->stuffClipboard(cur.selectionAsString(true));
948 // this doesnt make sense, if there is no selection
949 if (!cur.selection())
952 // ok we have a selection. This is always between cur.selBegin()
953 // and sel_end cursor
955 // copy behind a space if there is one
956 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
957 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
958 && (cur.selBegin().par() != cur.selEnd().par()
959 || cur.selBegin().pos() < cur.selEnd().pos()))
960 cur.selBegin().pos(cur.selBegin().pos() + 1);
962 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
963 getPar(cur.selEnd().par()),
964 cur.selBegin().pos(),
966 bv()->buffer()->params().textclass);
970 void LyXText::pasteSelection(size_t sel_index)
972 LCursor & cur = bv()->cursor();
973 // this does not make sense, if there is nothing to paste
974 if (!CutAndPaste::checkPastePossible())
977 recUndo(cursor().par());
979 ParagraphList::iterator endpit;
984 boost::tie(ppp, endpit) =
985 CutAndPaste::pasteSelection(*bv()->buffer(),
987 cursorPar(), cursor().pos(),
988 bv()->buffer()->params().textclass,
990 bufferErrors(*bv()->buffer(), el);
991 bv()->showErrorList(_("Paste"));
993 redoParagraphs(cursorPar(), endpit);
995 cur.clearSelection();
997 setCursor(parOffset(ppp.first), ppp.second);
1003 void LyXText::setSelectionRange(lyx::pos_type length)
1008 LCursor & cur = bv()->cursor();
1011 cursorRight(cur, true);
1016 // simple replacing. The font of the first selected character is used
1017 void LyXText::replaceSelectionWithString(string const & str)
1019 LCursor & cur = bv()->cursor();
1023 // Get font setting before we cut
1024 pos_type pos = cur.selEnd().pos();
1025 LyXFont const font = getPar(cur.selBegin())
1026 ->getFontSettings(bv()->buffer()->params(),
1027 cur.selBegin().pos());
1029 // Insert the new string
1030 string::const_iterator cit = str.begin();
1031 string::const_iterator end = str.end();
1032 for (; cit != end; ++cit) {
1033 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1037 // Cut the selection
1038 cutSelection(true, false);
1044 // needed to insert the selection
1045 void LyXText::insertStringAsLines(string const & str)
1047 LCursor & cur = bv()->cursor();
1048 ParagraphList::iterator pit = cursorPar();
1049 pos_type pos = cursor().pos();
1050 ParagraphList::iterator endpit = boost::next(cursorPar());
1051 recordUndo(cur, Undo::ATOMIC);
1053 // only to be sure, should not be neccessary
1054 cur.clearSelection();
1055 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1057 redoParagraphs(cursorPar(), endpit);
1059 setCursor(parOffset(pit), pos);
1064 // turn double CR to single CR, others are converted into one
1065 // blank. Then insertStringAsLines is called
1066 void LyXText::insertStringAsParagraphs(string const & str)
1068 string linestr(str);
1069 bool newline_inserted = false;
1070 string::size_type const siz = linestr.length();
1072 for (string::size_type i = 0; i < siz; ++i) {
1073 if (linestr[i] == '\n') {
1074 if (newline_inserted) {
1075 // we know that \r will be ignored by
1076 // insertStringAsLines. Of course, it is a dirty
1077 // trick, but it works...
1078 linestr[i - 1] = '\r';
1082 newline_inserted = true;
1084 } else if (IsPrintable(linestr[i])) {
1085 newline_inserted = false;
1088 insertStringAsLines(linestr);
1092 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1095 CursorSlice old_cursor = cursor();
1096 setCursorIntern(par, pos, setfont, boundary);
1097 return deleteEmptyParagraphMechanism(old_cursor);
1101 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1102 pos_type pos, bool boundary)
1104 BOOST_ASSERT(par != int(paragraphs().size()));
1108 cur.boundary(boundary);
1110 // no rows, no fun...
1111 if (paragraphs().begin()->rows.empty())
1114 // now some strict checking
1115 Paragraph & para = *getPar(par);
1116 Row const & row = *para.getRow(pos);
1117 pos_type const end = row.endpos();
1119 // None of these should happen, but we're scaredy-cats
1121 lyxerr << "dont like -1" << endl;
1124 BOOST_ASSERT(false);
1125 } else if (pos > para.size()) {
1126 lyxerr << "dont like 1, pos: " << pos
1127 << " size: " << para.size()
1128 << " row.pos():" << row.pos()
1129 << " paroffset: " << par << endl;
1132 BOOST_ASSERT(false);
1133 } else if (pos > end) {
1134 lyxerr << "dont like 2 please report" << endl;
1135 // This shouldn't happen.
1138 BOOST_ASSERT(false);
1139 } else if (pos < row.pos()) {
1140 lyxerr << "dont like 3 please report pos:" << pos
1141 << " size: " << para.size()
1142 << " row.pos():" << row.pos()
1143 << " paroffset: " << par << endl;
1146 BOOST_ASSERT(false);
1151 void LyXText::setCursorIntern(paroffset_type par,
1152 pos_type pos, bool setfont, bool boundary)
1154 setCursor(cursor(), par, pos, boundary);
1155 bv()->cursor().x_target() = cursorX(cursor());
1161 void LyXText::setCurrentFont()
1163 LCursor & cur = bv()->cursor();
1164 pos_type pos = cur.pos();
1165 ParagraphList::iterator pit = cursorPar();
1167 if (cursor().boundary() && pos > 0)
1171 if (pos == pit->size())
1173 else // potentional bug... BUG (Lgb)
1174 if (pit->isSeparator(pos)) {
1175 if (pos > pit->getRow(pos)->pos() &&
1176 bidi.level(pos) % 2 ==
1177 bidi.level(pos - 1) % 2)
1179 else if (pos + 1 < pit->size())
1184 BufferParams const & bufparams = bv()->buffer()->params();
1185 current_font = pit->getFontSettings(bufparams, pos);
1186 real_current_font = getFont(pit, pos);
1188 if (cursor().pos() == pit->size() &&
1189 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1190 !cursor().boundary()) {
1191 Language const * lang =
1192 pit->getParLanguage(bufparams);
1193 current_font.setLanguage(lang);
1194 current_font.setNumber(LyXFont::OFF);
1195 real_current_font.setLanguage(lang);
1196 real_current_font.setNumber(LyXFont::OFF);
1200 // x is an absolute screen coord
1201 // returns the column near the specified x-coordinate of the row
1202 // x is set to the real beginning of this column
1203 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1204 Row const & row, int & x, bool & boundary) const
1207 double tmpx = row.x();
1208 double fill_separator = row.fill_separator();
1209 double fill_hfill = row.fill_hfill();
1210 double fill_label_hfill = row.fill_label_hfill();
1212 pos_type vc = row.pos();
1213 pos_type end = row.endpos();
1215 LyXLayout_ptr const & layout = pit->layout();
1217 bool left_side = false;
1219 pos_type body_pos = pit->beginOfBody();
1220 double last_tmpx = tmpx;
1223 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1226 // check for empty row
1228 x = int(tmpx) + xo_;
1232 while (vc < end && tmpx <= x) {
1233 c = bidi.vis2log(vc);
1235 if (body_pos > 0 && c == body_pos - 1) {
1236 tmpx += fill_label_hfill +
1237 font_metrics::width(layout->labelsep, getLabelFont(pit));
1238 if (pit->isLineSeparator(body_pos - 1))
1239 tmpx -= singleWidth(pit, body_pos - 1);
1242 if (hfillExpansion(*pit, row, c)) {
1243 tmpx += singleWidth(pit, c);
1247 tmpx += fill_label_hfill;
1248 } else if (pit->isSeparator(c)) {
1249 tmpx += singleWidth(pit, c);
1251 tmpx += fill_separator;
1253 tmpx += singleWidth(pit, c);
1258 if ((tmpx + last_tmpx) / 2 > x) {
1263 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1266 // This (rtl_support test) is not needed, but gives
1267 // some speedup if rtl_support == false
1268 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1270 // If lastrow is false, we don't need to compute
1271 // the value of rtl.
1272 bool const rtl = lastrow
1273 ? pit->isRightToLeftPar(bv()->buffer()->params())
1276 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1277 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1279 else if (vc == row.pos()) {
1280 c = bidi.vis2log(vc);
1281 if (bidi.level(c) % 2 == 1)
1284 c = bidi.vis2log(vc - 1);
1285 bool const rtl = (bidi.level(c) % 2 == 1);
1286 if (left_side == rtl) {
1288 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1292 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1293 if (bidi.level(end -1) % 2 == 0)
1294 tmpx -= singleWidth(pit, end - 1);
1296 tmpx += singleWidth(pit, end - 1);
1300 x = int(tmpx) + xo_;
1301 return c - row.pos();
1305 void LyXText::setCursorFromCoordinates(int x, int y)
1307 CursorSlice old_cursor = cursor();
1308 setCursorFromCoordinates(cursor(), x, y);
1310 deleteEmptyParagraphMechanism(old_cursor);
1314 // x,y are coordinates relative to this LyXText
1315 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1317 ParagraphList::iterator pit;
1318 Row const & row = *getRowNearY(y, pit);
1320 int xx = x + xo_; // getRowNearX get absolute x coords
1321 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1322 cur.par() = parOffset(pit);
1324 cur.boundary() = bound;
1328 // x,y are absolute screen coordinates
1329 void LyXText::edit(LCursor & cur, int x, int y)
1331 ParagraphList::iterator pit;
1332 Row const & row = *getRowNearY(y - yo_, pit);
1335 int xx = x; // is modified by getColumnNearX
1336 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1337 cur.par() = parOffset(pit);
1339 cur.boundary() = bound;
1341 // try to descend into nested insets
1342 InsetBase * inset = checkInsetHit(x, y);
1344 // This should be just before or just behind the
1345 // cursor position set above.
1346 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1347 || inset == pit->getInset(pos));
1348 // Make sure the cursor points to the position before
1350 if (inset == pit->getInset(pos - 1))
1352 inset->edit(cur, x, y);
1357 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1359 if (cur.pos() == cur.lastpos())
1361 InsetBase * inset = cur.nextInset();
1362 if (!isHighlyEditableInset(inset))
1364 inset->edit(cur, front);
1369 DispatchResult LyXText::moveRight(LCursor & cur)
1371 if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1372 return moveLeftIntern(cur, false, true, false);
1374 return moveRightIntern(cur, true, true, false);
1378 DispatchResult LyXText::moveLeft(LCursor & cur)
1380 if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1381 return moveRightIntern(cur, true, true, false);
1383 return moveLeftIntern(cur, false, true, false);
1387 DispatchResult LyXText::moveRightIntern(LCursor & cur,
1388 bool front, bool activate_inset, bool selecting)
1390 if (cur.par() == cur.lastpar() && cur.pos() == cur.lastpos())
1391 return DispatchResult(false, FINISHED_RIGHT);
1392 if (activate_inset && checkAndActivateInset(cur, front))
1393 return DispatchResult(true, true);
1394 cursorRight(cur, true);
1396 cur.clearSelection();
1397 return DispatchResult(true);
1401 DispatchResult LyXText::moveLeftIntern(LCursor & cur,
1402 bool front, bool activate_inset, bool selecting)
1404 if (cur.par() == 0 && cur.pos() == 0)
1405 return DispatchResult(false, FINISHED);
1406 cursorLeft(cur, true);
1408 cur.clearSelection();
1409 if (activate_inset && checkAndActivateInset(cur, front))
1410 return DispatchResult(true, true);
1411 return DispatchResult(true);
1415 DispatchResult LyXText::moveUp(LCursor & cur)
1417 if (cur.par() == 0 && cur.row() == 0)
1418 return DispatchResult(false, FINISHED_UP);
1419 cursorUp(cur, false);
1420 cur.clearSelection();
1421 return DispatchResult(true);
1425 DispatchResult LyXText::moveDown(LCursor & cur)
1427 if (cur.par() == cur.lastpar() && cur.textRow().endpos() == cur.lastpos())
1428 return DispatchResult(false, FINISHED_DOWN);
1429 cursorDown(cur, false);
1430 cur.clearSelection();
1431 return DispatchResult(true);
1435 bool LyXText::cursorLeft(LCursor & cur, bool internal)
1437 if (cur.pos() != 0) {
1438 bool boundary = cur.boundary();
1439 setCursor(cur.par(), cur.pos() - 1, true, false);
1440 if (!internal && !boundary &&
1441 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1442 setCursor(cur.par(), cur.pos() + 1, true, true);
1446 if (cur.par() != 0) {
1447 // steps into the paragraph above
1448 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1456 bool LyXText::cursorRight(LCursor & cur, bool internal)
1458 if (!internal && cur.boundary()) {
1459 setCursor(cur.par(), cur.pos(), true, false);
1463 if (cur.pos() != cur.lastpos()) {
1464 setCursor(cur.par(), cur.pos() + 1, true, false);
1465 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1467 setCursor(cur.par(), cur.pos(), true, true);
1471 if (cur.par() != cur.lastpar()) {
1472 setCursor(cur.par() + 1, 0);
1480 void LyXText::cursorUp(LCursor & cur, bool selecting)
1482 Row const & row = *cursorRow();
1483 int x = cur.x_target();
1484 int y = cursorY(cur.current()) - row.baseline() - 1;
1485 setCursorFromCoordinates(x, y);
1488 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1489 if (inset_hit && isHighlyEditableInset(inset_hit))
1490 inset_hit->edit(cur, cur.x_target(), y);
1495 void LyXText::cursorDown(LCursor & cur, bool selecting)
1497 Row const & row = cur.textRow();
1498 int x = cur.x_target();
1499 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1500 setCursorFromCoordinates(cur.current(), 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::cursorUpParagraph(LCursor & cur)
1513 setCursor(cur.par(), 0);
1514 else if (cur.par() != 0)
1515 setCursor(cur.par() - 1, 0);
1519 void LyXText::cursorDownParagraph(LCursor & cur)
1521 if (cur.par() != cur.lastpar())
1522 setCursor(cur.par() + 1, 0);
1524 setCursor(cur.par(), cur.lastpos());
1528 // fix the cursor `cur' after a characters has been deleted at `where'
1529 // position. Called by deleteEmptyParagraphMechanism
1530 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1532 // if cursor is not in the paragraph where the delete occured,
1534 if (cur.par() != where.par())
1537 // if cursor position is after the place where the delete occured,
1539 if (cur.pos() > where.pos())
1542 // check also if we don't want to set the cursor on a spot behind the
1543 // pagragraph because we erased the last character.
1544 if (cur.pos() > cur.lastpos())
1545 cur.pos() = cur.lastpos();
1549 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1551 #warning Disabled as it crashes after the cursor data shift... (Andre)
1554 // Would be wrong to delete anything if we have a selection.
1555 if (bv()->cursor().selection())
1558 // Don't do anything if the cursor is invalid
1559 if (old_cursor.par() == -1)
1563 // We allow all kinds of "mumbo-jumbo" when freespacing.
1564 ParagraphList::iterator const old_pit = getPar(old_cursor);
1565 if (old_pit->isFreeSpacing())
1568 /* Ok I'll put some comments here about what is missing.
1569 I have fixed BackSpace (and thus Delete) to not delete
1570 double-spaces automagically. I have also changed Cut,
1571 Copy and Paste to hopefully do some sensible things.
1572 There are still some small problems that can lead to
1573 double spaces stored in the document file or space at
1574 the beginning of paragraphs(). This happens if you have
1575 the cursor between to spaces and then save. Or if you
1576 cut and paste and the selection have a space at the
1577 beginning and then save right after the paste. I am
1578 sure none of these are very hard to fix, but I will
1579 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1580 that I can get some feedback. (Lgb)
1583 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1584 // delete the LineSeparator.
1587 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1588 // delete the LineSeparator.
1591 // If the pos around the old_cursor were spaces, delete one of them.
1592 if (old_cursor.par() != cursor().par()
1593 || old_cursor.pos() != cursor().pos()) {
1595 // Only if the cursor has really moved
1596 if (old_cursor.pos() > 0
1597 && old_cursor.pos() < old_pit->size()
1598 && old_pit->isLineSeparator(old_cursor.pos())
1599 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1600 bool erased = old_pit->erase(old_cursor.pos() - 1);
1601 redoParagraph(old_pit);
1605 #ifdef WITH_WARNINGS
1606 #warning This will not work anymore when we have multiple views of the same buffer
1607 // In this case, we will have to correct also the cursors held by
1608 // other bufferviews. It will probably be easier to do that in a more
1609 // automated way in CursorSlice code. (JMarc 26/09/2001)
1611 // correct all cursors held by the LyXText
1612 fixCursorAfterDelete(cursor(), old_cursor);
1613 fixCursorAfterDelete(anchor(), old_cursor);
1618 // don't delete anything if this is the ONLY paragraph!
1619 if (paragraphs().size() == 1)
1622 // Do not delete empty paragraphs with keepempty set.
1623 if (old_pit->allowEmpty())
1626 // only do our magic if we changed paragraph
1627 if (old_cursor.par() == cursor().par())
1630 // record if we have deleted a paragraph
1631 // we can't possibly have deleted a paragraph before this point
1632 bool deleted = false;
1634 if (old_pit->empty()
1635 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1636 // ok, we will delete something
1637 CursorSlice tmpcursor;
1641 bool selection_position_was_oldcursor_position =
1642 anchor().par() == old_cursor.par()
1643 && anchor().pos() == old_cursor.pos();
1645 tmpcursor = cursor();
1646 cursor() = old_cursor; // that undo can restore the right cursor position
1648 ParagraphList::iterator endpit = boost::next(old_pit);
1649 while (endpit != paragraphs().end() && endpit->getDepth())
1652 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1653 cursor() = tmpcursor;
1656 ParagraphList::iterator tmppit = cursorPar();
1658 paragraphs().erase(old_pit);
1659 // update cursor par offset
1660 cursor().par(parOffset(tmppit));
1663 if (selection_position_was_oldcursor_position) {
1664 // correct selection
1665 bv()->resetAnchor();
1672 if (old_pit->stripLeadingSpaces()) {
1673 redoParagraph(old_pit);
1674 bv()->resetAnchor();
1681 ParagraphList & LyXText::paragraphs() const
1683 return const_cast<ParagraphList &>(paragraphs_);
1687 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1689 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1693 void LyXText::recUndo(lyx::paroffset_type par) const
1695 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1699 bool LyXText::isInInset() const
1705 bool LyXText::toggleInset()
1707 InsetBase * inset = bv()->cursor().nextInset();
1708 // is there an editable inset at cursor position?
1709 if (!isEditableInset(inset))
1711 //bv()->owner()->message(inset->editMessage());
1713 // do we want to keep this?? (JMarc)
1714 if (!isHighlyEditableInset(inset))
1715 recUndo(cursor().par());
1717 if (inset->isOpen())
1725 int defaultRowHeight()
1727 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);