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);
420 ParagraphList::iterator beg = getPar(cur.selBegin().par());
421 ParagraphList::iterator end = getPar(cur.selEnd().par());
423 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
424 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
426 BufferParams const & params = bv()->buffer()->params();
428 for (; pos != posend; ++pos) {
429 LyXFont f = getFont(pos.pit(), pos.pos());
430 f.update(font, params.language, toggleall);
431 setCharFont(pos.pit(), pos.pos(), f);
434 redoParagraphs(beg, ++end);
438 // the cursor set functions have a special mechanism. When they
439 // realize you left an empty paragraph, they will delete it.
441 void LyXText::cursorHome(LCursor & cur)
443 BOOST_ASSERT(this == cur.text());
444 setCursor(cur, cur.par(), cur.textRow().pos());
448 void LyXText::cursorEnd(LCursor & cur)
450 BOOST_ASSERT(this == cur.text());
451 // if not on the last row of the par, put the cursor before
453 pos_type const end = cur.textRow().endpos();
454 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
458 void LyXText::cursorTop(LCursor & cur)
460 BOOST_ASSERT(this == cur.text());
461 setCursor(cur, 0, 0);
465 void LyXText::cursorBottom(LCursor & cur)
467 BOOST_ASSERT(this == cur.text());
468 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
472 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
474 BOOST_ASSERT(this == cur.text());
475 // If the mask is completely neutral, tell user
476 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
477 // Could only happen with user style
478 cur.message(_("No font change defined. "
479 "Use Character under the Layout menu to define font change."));
483 // Try implicit word selection
484 // If there is a change in the language the implicit word selection
486 CursorSlice resetCursor = cur.top();
487 bool implicitSelection =
488 font.language() == ignore_language
489 && font.number() == LyXFont::IGNORE
490 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
493 setFont(cur, font, toggleall);
495 // Implicit selections are cleared afterwards
496 // and cursor is set to the original position.
497 if (implicitSelection) {
498 cur.clearSelection();
499 cur.top() = resetCursor;
505 string LyXText::getStringToIndex(LCursor & cur)
507 BOOST_ASSERT(this == cur.text());
508 // Try implicit word selection
509 // If there is a change in the language the implicit word selection
511 CursorSlice const reset_cursor = cur.top();
512 bool const implicitSelection =
513 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
516 if (!cur.selection())
517 cur.message(_("Nothing to index!"));
518 else if (cur.selBegin().par() != cur.selEnd().par())
519 cur.message(_("Cannot index more than one paragraph!"));
521 idxstring = cur.selectionAsString(false);
523 // Reset cursors to their original position.
524 cur.top() = reset_cursor;
527 // Clear the implicit selection.
528 if (implicitSelection)
529 cur.clearSelection();
535 // the DTP switches for paragraphs(). LyX will store them in the first
536 // physical paragraph. When a paragraph is broken, the top settings rest,
537 // the bottom settings are given to the new one. So I can make sure,
538 // they do not duplicate themself and you cannot play dirty tricks with
541 void LyXText::setParagraph(LCursor & cur,
542 Spacing const & spacing, LyXAlignment align,
543 string const & labelwidthstring, bool noindent)
545 BOOST_ASSERT(cur.text());
546 // make sure that the depth behind the selection are restored, too
547 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
548 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
550 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
551 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
553 for (--pit; pit != beg; ++pit) {
554 ParagraphParameters & params = pit->params();
555 params.spacing(spacing);
557 // does the layout allow the new alignment?
558 LyXLayout_ptr const & layout = pit->layout();
560 if (align == LYX_ALIGN_LAYOUT)
561 align = layout->align;
562 if (align & layout->alignpossible) {
563 if (align == layout->align)
564 params.align(LYX_ALIGN_LAYOUT);
568 pit->setLabelWidthString(labelwidthstring);
569 params.noindent(noindent);
572 redoParagraphs(getPar(cur.selBegin()), undopit);
576 string expandLabel(LyXTextClass const & textclass,
577 LyXLayout_ptr const & layout, bool appendix)
579 string fmt = appendix ?
580 layout->labelstring_appendix() : layout->labelstring();
582 // handle 'inherited level parts' in 'fmt',
583 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
584 size_t const i = fmt.find('@', 0);
585 if (i != string::npos) {
586 size_t const j = fmt.find('@', i + 1);
587 if (j != string::npos) {
588 string parent(fmt, i + 1, j - i - 1);
589 string label = expandLabel(textclass, textclass[parent], appendix);
590 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
594 return textclass.counters().counterLabel(fmt);
600 void incrementItemDepth(ParagraphList::iterator pit,
601 ParagraphList::iterator first_pit)
603 int const cur_labeltype = pit->layout()->labeltype;
605 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
608 int const cur_depth = pit->getDepth();
610 ParagraphList::iterator prev_pit = boost::prior(pit);
612 int const prev_depth = prev_pit->getDepth();
613 int const prev_labeltype = prev_pit->layout()->labeltype;
614 if (prev_depth == 0 && cur_depth > 0) {
615 if (prev_labeltype == cur_labeltype) {
616 pit->itemdepth = prev_pit->itemdepth + 1;
619 } else if (prev_depth < cur_depth) {
620 if (prev_labeltype == cur_labeltype) {
621 pit->itemdepth = prev_pit->itemdepth + 1;
624 } else if (prev_depth == cur_depth) {
625 if (prev_labeltype == cur_labeltype) {
626 pit->itemdepth = prev_pit->itemdepth;
630 if (prev_pit == first_pit)
638 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
639 ParagraphList::iterator firstpit,
645 int const cur_depth = pit->getDepth();
646 ParagraphList::iterator prev_pit = boost::prior(pit);
648 int const prev_depth = prev_pit->getDepth();
649 int const prev_labeltype = prev_pit->layout()->labeltype;
650 if (prev_depth <= cur_depth) {
651 if (prev_labeltype != LABEL_ENUMERATE) {
652 switch (pit->itemdepth) {
654 counters.reset("enumi");
656 counters.reset("enumii");
658 counters.reset("enumiii");
660 counters.reset("enumiv");
666 if (prev_pit == firstpit)
676 // set the counter of a paragraph. This includes the labels
677 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
679 BufferParams const & bufparams = buf.params();
680 LyXTextClass const & textclass = bufparams.getLyXTextClass();
681 LyXLayout_ptr const & layout = pit->layout();
682 ParagraphList::iterator first_pit = paragraphs().begin();
683 Counters & counters = textclass.counters();
688 if (pit == first_pit) {
689 pit->params().appendix(pit->params().startOfAppendix());
691 pit->params().appendix(boost::prior(pit)->params().appendix());
692 if (!pit->params().appendix() &&
693 pit->params().startOfAppendix()) {
694 pit->params().appendix(true);
695 textclass.counters().reset();
698 // Maybe we have to increment the item depth.
699 incrementItemDepth(pit, first_pit);
702 // erase what was there before
703 pit->params().labelString(string());
705 if (layout->margintype == MARGIN_MANUAL) {
706 if (pit->params().labelWidthString().empty())
707 pit->setLabelWidthString(layout->labelstring());
709 pit->setLabelWidthString(string());
712 // is it a layout that has an automatic label?
713 if (layout->labeltype == LABEL_COUNTER) {
714 BufferParams const & bufparams = buf.params();
715 LyXTextClass const & textclass = bufparams.getLyXTextClass();
716 counters.step(layout->counter);
717 string label = expandLabel(textclass, layout, pit->params().appendix());
718 pit->params().labelString(label);
719 } else if (layout->labeltype == LABEL_ITEMIZE) {
720 // At some point of time we should do something more
721 // clever here, like:
722 // pit->params().labelString(
723 // bufparams.user_defined_bullet(pit->itemdepth).getText());
724 // for now, use a simple hardcoded label
726 switch (pit->itemdepth) {
741 pit->params().labelString(itemlabel);
742 } else if (layout->labeltype == LABEL_ENUMERATE) {
743 // Maybe we have to reset the enumeration counter.
744 resetEnumCounterIfNeeded(pit, first_pit, counters);
747 // Yes I know this is a really, really! bad solution
749 string enumcounter = "enum";
751 switch (pit->itemdepth) {
763 // not a valid enumdepth...
767 counters.step(enumcounter);
769 pit->params().labelString(counters.enumLabel(enumcounter));
770 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
771 counters.step("bibitem");
772 int number = counters.value("bibitem");
773 if (pit->bibitem()) {
774 pit->bibitem()->setCounter(number);
775 pit->params().labelString(layout->labelstring());
777 // In biblio should't be following counters but...
779 string s = buf.B_(layout->labelstring());
782 if (layout->labeltype == LABEL_SENSITIVE) {
783 ParagraphList::iterator end = paragraphs().end();
784 ParagraphList::iterator tmppit = pit;
787 while (tmppit != end && tmppit->inInset()
788 // the single '=' is intended below
789 && (in = tmppit->inInset()->owner()))
791 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
792 in->lyxCode() == InsetBase::WRAP_CODE) {
796 Paragraph const * owner = &ownerPar(buf, in);
798 for ( ; tmppit != end; ++tmppit)
799 if (&*tmppit == owner)
807 if (in->lyxCode() == InsetBase::FLOAT_CODE)
808 type = static_cast<InsetFloat*>(in)->params().type;
809 else if (in->lyxCode() == InsetBase::WRAP_CODE)
810 type = static_cast<InsetWrap*>(in)->params().type;
814 Floating const & fl = textclass.floats().getType(type);
816 counters.step(fl.type());
818 // Doesn't work... yet.
819 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
821 // par->SetLayout(0);
822 // s = layout->labelstring;
823 s = _("Senseless: ");
826 pit->params().labelString(s);
832 // Updates all counters.
833 void LyXText::updateCounters()
836 bv()->buffer()->params().getLyXTextClass().counters().reset();
838 bool update_pos = false;
840 ParagraphList::iterator beg = paragraphs().begin();
841 ParagraphList::iterator end = paragraphs().end();
842 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
843 string const oldLabel = pit->params().labelString();
846 maxdepth = boost::prior(pit)->getMaxDepthAfter();
848 if (pit->params().depth() > maxdepth)
849 pit->params().depth(maxdepth);
851 // setCounter can potentially change the labelString.
852 setCounter(*bv()->buffer(), pit);
853 string const & newLabel = pit->params().labelString();
854 if (oldLabel != newLabel) {
855 redoParagraphInternal(pit);
861 updateParPositions();
865 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
867 BOOST_ASSERT(this == cur.text());
870 cur.paragraph().insertInset(cur.pos(), inset);
871 // Just to rebreak and refresh correctly.
872 // The character will not be inserted a second time
873 insertChar(cur, Paragraph::META_INSET);
874 // If we enter a highly editable inset the cursor should be before
875 // the inset. After an undo LyX tries to call inset->edit(...)
876 // and fails if the cursor is behind the inset and getInset
877 // does not return the inset!
878 if (isHighlyEditableInset(inset))
884 void LyXText::cutSelection(LCursor & cur, bool doclear, bool realcut)
886 BOOST_ASSERT(this == cur.text());
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 recordUndoSelection(cur);
906 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
907 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
908 ParagraphList::iterator undopit = undoSpan(endpit);
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
934 cur.par() = parOffset(endpit);
936 // need a valid cursor. (Lgb)
937 cur.clearSelection();
942 void LyXText::copySelection(LCursor & cur)
944 BOOST_ASSERT(this == cur.text());
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();
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(LCursor & cur, size_t sel_index)
972 // this does not make sense, if there is nothing to paste
973 if (!CutAndPaste::checkPastePossible())
978 ParagraphList::iterator endpit;
983 boost::tie(ppp, endpit) =
984 CutAndPaste::pasteSelection(*bv()->buffer(),
986 getPar(cur.par()), cur.pos(),
987 bv()->buffer()->params().textclass,
989 bufferErrors(*bv()->buffer(), el);
990 bv()->showErrorList(_("Paste"));
992 redoParagraphs(getPar(cur.par()), endpit);
994 cur.clearSelection();
996 setCursor(cur, parOffset(ppp.first), ppp.second);
1002 void LyXText::setSelectionRange(LCursor & cur, lyx::pos_type length)
1013 // simple replacing. The font of the first selected character is used
1014 void LyXText::replaceSelectionWithString(LCursor & cur, string const & str)
1019 // Get font setting before we cut
1020 pos_type pos = cur.selEnd().pos();
1021 LyXFont const font = getPar(cur.selBegin())
1022 ->getFontSettings(bv()->buffer()->params(),
1023 cur.selBegin().pos());
1025 // Insert the new string
1026 string::const_iterator cit = str.begin();
1027 string::const_iterator end = str.end();
1028 for (; cit != end; ++cit) {
1029 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1033 // Cut the selection
1034 cutSelection(cur, true, false);
1039 // needed to insert the selection
1040 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
1042 ParagraphList::iterator pit = getPar(cur.par());
1043 ParagraphList::iterator endpit = boost::next(pit);
1044 pos_type pos = cursor().pos();
1047 // only to be sure, should not be neccessary
1048 cur.clearSelection();
1049 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1051 redoParagraphs(getPar(cur.par()), endpit);
1053 setCursor(cur, cur.par(), pos);
1058 // turn double CR to single CR, others are converted into one
1059 // blank. Then insertStringAsLines is called
1060 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
1062 string linestr = str;
1063 bool newline_inserted = false;
1065 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
1066 if (linestr[i] == '\n') {
1067 if (newline_inserted) {
1068 // we know that \r will be ignored by
1069 // insertStringAsLines. Of course, it is a dirty
1070 // trick, but it works...
1071 linestr[i - 1] = '\r';
1075 newline_inserted = true;
1077 } else if (IsPrintable(linestr[i])) {
1078 newline_inserted = false;
1081 insertStringAsLines(cur, linestr);
1085 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
1086 bool setfont, bool boundary)
1088 CursorSlice old_cursor = cur.top();
1089 setCursorIntern(cur, par, pos, setfont, boundary);
1090 return deleteEmptyParagraphMechanism(cur.top(), old_cursor);
1094 void LyXText::setCursor(CursorSlice & cur, par_type par,
1095 pos_type pos, bool boundary)
1097 BOOST_ASSERT(par != int(paragraphs().size()));
1101 cur.boundary() = boundary;
1103 // no rows, no fun...
1104 if (paragraphs().begin()->rows.empty())
1107 // now some strict checking
1108 Paragraph & para = *getPar(par);
1109 Row const & row = *para.getRow(pos);
1110 pos_type const end = row.endpos();
1112 // None of these should happen, but we're scaredy-cats
1114 lyxerr << "dont like -1" << endl;
1115 BOOST_ASSERT(false);
1118 if (pos > para.size()) {
1119 lyxerr << "dont like 1, pos: " << pos
1120 << " size: " << para.size()
1121 << " row.pos():" << row.pos()
1122 << " par: " << par << endl;
1123 BOOST_ASSERT(false);
1127 lyxerr << "dont like 2 please report" << endl;
1128 // This shouldn't happen.
1129 BOOST_ASSERT(false);
1132 if (pos < row.pos()) {
1133 lyxerr << "dont like 3 please report pos:" << pos
1134 << " size: " << para.size()
1135 << " row.pos():" << row.pos()
1136 << " par: " << par << endl;
1137 BOOST_ASSERT(false);
1142 void LyXText::setCursorIntern(LCursor & cur,
1143 par_type par, pos_type pos, bool setfont, bool boundary)
1145 setCursor(cur.top(), par, pos, boundary);
1146 cur.x_target() = cursorX(cur.top());
1148 setCurrentFont(cur);
1152 void LyXText::setCurrentFont(LCursor & cur)
1154 BOOST_ASSERT(this == cur.text());
1155 pos_type pos = cur.pos();
1156 ParagraphList::iterator pit = getPar(cur.par());
1158 if (cur.boundary() && pos > 0)
1162 if (pos == cur.lastpos())
1164 else // potentional bug... BUG (Lgb)
1165 if (pit->isSeparator(pos)) {
1166 if (pos > cur.textRow().pos() &&
1167 bidi.level(pos) % 2 ==
1168 bidi.level(pos - 1) % 2)
1170 else if (pos + 1 < cur.lastpos())
1175 BufferParams const & bufparams = bv()->buffer()->params();
1176 current_font = pit->getFontSettings(bufparams, pos);
1177 real_current_font = getFont(pit, pos);
1179 if (cur.pos() == cur.lastpos()
1180 && bidi.isBoundary(*bv()->buffer(), *pit, cur.pos())
1181 && !cur.boundary()) {
1182 Language const * lang = pit->getParLanguage(bufparams);
1183 current_font.setLanguage(lang);
1184 current_font.setNumber(LyXFont::OFF);
1185 real_current_font.setLanguage(lang);
1186 real_current_font.setNumber(LyXFont::OFF);
1191 // x is an absolute screen coord
1192 // returns the column near the specified x-coordinate of the row
1193 // x is set to the real beginning of this column
1194 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1195 Row const & row, int & x, bool & boundary) const
1198 RowMetrics const r = computeRowMetrics(pit, row);
1200 pos_type vc = row.pos();
1201 pos_type end = row.endpos();
1203 LyXLayout_ptr const & layout = pit->layout();
1205 bool left_side = false;
1207 pos_type body_pos = pit->beginOfBody();
1210 double last_tmpx = tmpx;
1213 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1216 // check for empty row
1218 x = int(tmpx) + xo_;
1222 while (vc < end && tmpx <= x) {
1223 c = bidi.vis2log(vc);
1225 if (body_pos > 0 && c == body_pos - 1) {
1226 tmpx += r.label_hfill +
1227 font_metrics::width(layout->labelsep, getLabelFont(pit));
1228 if (pit->isLineSeparator(body_pos - 1))
1229 tmpx -= singleWidth(pit, body_pos - 1);
1232 if (hfillExpansion(*pit, row, c)) {
1233 tmpx += singleWidth(pit, c);
1237 tmpx += r.label_hfill;
1238 } else if (pit->isSeparator(c)) {
1239 tmpx += singleWidth(pit, c);
1241 tmpx += r.separator;
1243 tmpx += singleWidth(pit, c);
1248 if ((tmpx + last_tmpx) / 2 > x) {
1253 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1256 // This (rtl_support test) is not needed, but gives
1257 // some speedup if rtl_support == false
1258 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1260 // If lastrow is false, we don't need to compute
1261 // the value of rtl.
1262 bool const rtl = lastrow ? isRTL(*pit) : false;
1264 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1265 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1267 else if (vc == row.pos()) {
1268 c = bidi.vis2log(vc);
1269 if (bidi.level(c) % 2 == 1)
1272 c = bidi.vis2log(vc - 1);
1273 bool const rtl = (bidi.level(c) % 2 == 1);
1274 if (left_side == rtl) {
1276 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1280 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1281 if (bidi.level(end -1) % 2 == 0)
1282 tmpx -= singleWidth(pit, end - 1);
1284 tmpx += singleWidth(pit, end - 1);
1288 x = int(tmpx) + xo_;
1289 return c - row.pos();
1293 // x,y are absolute coordinates
1294 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1298 CursorSlice old_cursor = cur.top();
1299 ParagraphList::iterator pit;
1300 Row const & row = *getRowNearY(y, pit);
1301 lyxerr << "hit row at: " << row.pos() << endl;
1303 int xx = x + xo_; // getRowNearX get absolute x coords
1304 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1305 cur.par() = parOffset(pit);
1307 cur.boundary() = bound;
1308 deleteEmptyParagraphMechanism(cur.top(), old_cursor);
1312 // x,y are absolute screen coordinates
1313 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1315 ParagraphList::iterator pit;
1316 Row const & row = *getRowNearY(y - yo_, pit);
1319 int xx = x; // is modified by getColumnNearX
1320 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1321 cur.par() = parOffset(pit);
1323 cur.boundary() = bound;
1325 // try to descend into nested insets
1326 InsetBase * inset = checkInsetHit(x, y);
1330 // This should be just before or just behind the
1331 // cursor position set above.
1332 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1333 || inset == pit->getInset(pos));
1334 // Make sure the cursor points to the position before
1336 if (inset == pit->getInset(pos - 1))
1338 return inset->editXY(cur, x, y);
1342 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1344 if (cur.selection())
1346 if (cur.pos() == cur.lastpos())
1348 InsetBase * inset = cur.nextInset();
1349 if (!isHighlyEditableInset(inset))
1351 inset->edit(cur, front);
1356 void LyXText::cursorLeft(LCursor & cur)
1358 if (cur.pos() != 0) {
1359 bool boundary = cur.boundary();
1360 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1361 if (!checkAndActivateInset(cur, false)) {
1362 if (false && !boundary &&
1363 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1364 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1369 if (cur.par() != 0) {
1370 // steps into the paragraph above
1371 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1)->size());
1376 void LyXText::cursorRight(LCursor & cur)
1378 if (false && cur.boundary()) {
1379 setCursor(cur, cur.par(), cur.pos(), true, false);
1383 if (cur.pos() != cur.lastpos()) {
1384 if (!checkAndActivateInset(cur, true)) {
1385 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1386 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1388 setCursor(cur, cur.par(), cur.pos(), true, true);
1393 if (cur.par() != cur.lastpar())
1394 setCursor(cur, cur.par() + 1, 0);
1398 void LyXText::cursorUp(LCursor & cur)
1400 Row const & row = cur.textRow();
1401 int x = cur.x_target();
1402 int y = cursorY(cur.top()) - row.baseline() - 1;
1403 setCursorFromCoordinates(cur, x, y);
1405 if (!cur.selection()) {
1406 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1407 if (inset_hit && isHighlyEditableInset(inset_hit))
1408 inset_hit->editXY(cur, cur.x_target(), y);
1413 void LyXText::cursorDown(LCursor & cur)
1415 Row const & row = cur.textRow();
1416 int x = cur.x_target();
1417 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1418 setCursorFromCoordinates(cur, x, y);
1420 if (!cur.selection()) {
1421 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1422 if (inset_hit && isHighlyEditableInset(inset_hit))
1423 inset_hit->editXY(cur, cur.x_target(), y);
1428 void LyXText::cursorUpParagraph(LCursor & cur)
1431 setCursor(cur, cur.par(), 0);
1432 else if (cur.par() != 0)
1433 setCursor(cur, cur.par() - 1, 0);
1437 void LyXText::cursorDownParagraph(LCursor & cur)
1439 if (cur.par() != cur.lastpar())
1440 setCursor(cur, cur.par() + 1, 0);
1442 setCursor(cur, cur.par(), cur.lastpos());
1446 // fix the cursor `cur' after a characters has been deleted at `where'
1447 // position. Called by deleteEmptyParagraphMechanism
1448 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1450 // do notheing if cursor is not in the paragraph where the
1451 // deletion occured,
1452 if (cur.par() != where.par())
1455 // if cursor position is after the deletion place update it
1456 if (cur.pos() > where.pos())
1459 // check also if we don't want to set the cursor on a spot behind the
1460 // pagragraph because we erased the last character.
1461 if (cur.pos() > cur.lastpos())
1462 cur.pos() = cur.lastpos();
1466 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice & cur,
1467 CursorSlice const & old_cursor)
1469 #warning Disabled as it crashes after the cursor data shift... (Andre)
1472 // Would be wrong to delete anything if we have a selection.
1473 //if (cur.selection())
1477 // We allow all kinds of "mumbo-jumbo" when freespacing.
1478 ParagraphList::iterator const old_pit = getPar(old_cursor.par());
1479 if (old_pit->isFreeSpacing())
1482 /* Ok I'll put some comments here about what is missing.
1483 I have fixed BackSpace (and thus Delete) to not delete
1484 double-spaces automagically. I have also changed Cut,
1485 Copy and Paste to hopefully do some sensible things.
1486 There are still some small problems that can lead to
1487 double spaces stored in the document file or space at
1488 the beginning of paragraphs(). This happens if you have
1489 the cursor between to spaces and then save. Or if you
1490 cut and paste and the selection have a space at the
1491 beginning and then save right after the paste. I am
1492 sure none of these are very hard to fix, but I will
1493 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1494 that I can get some feedback. (Lgb)
1497 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1498 // delete the LineSeparator.
1501 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1502 // delete the LineSeparator.
1505 // If the pos around the old_cursor were spaces, delete one of them.
1506 if (old_cursor.par() != cur.par() || old_cursor.pos() != cur.pos()) {
1508 // Only if the cursor has really moved
1509 if (old_cursor.pos() > 0
1510 && old_cursor.pos() < old_pit->size()
1511 && old_pit->isLineSeparator(old_cursor.pos())
1512 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1513 bool erased = old_pit->erase(old_cursor.pos() - 1);
1514 redoParagraph(old_pit);
1518 #ifdef WITH_WARNINGS
1519 #warning This will not work anymore when we have multiple views of the same buffer
1520 // In this case, we will have to correct also the cursors held by
1521 // other bufferviews. It will probably be easier to do that in a more
1522 // automated way in CursorSlice code. (JMarc 26/09/2001)
1524 // correct all cursors held by the LyXText
1525 fixCursorAfterDelete(cursor(), old_cursor);
1526 fixCursorAfterDelete(anchor(), old_cursor);
1531 // don't delete anything if this is the ONLY paragraph!
1532 if (paragraphs().size() == 1)
1535 // Do not delete empty paragraphs with keepempty set.
1536 if (old_pit->allowEmpty())
1539 // only do our magic if we changed paragraph
1540 if (old_cursor.par() == cur.par())
1543 // record if we have deleted a paragraph
1544 // we can't possibly have deleted a paragraph before this point
1545 bool deleted = false;
1547 if (old_pit->empty()
1548 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1549 // ok, we will delete something
1550 CursorSlice tmpcursor;
1554 bool selection_position_was_oldcursor_position =
1555 anchor().par() == old_cursor.par()
1556 && anchor().pos() == old_cursor.pos();
1558 tmpcursor = cursor();
1559 cursor() = old_cursor; // that undo can restore the right cursor position
1561 ParagraphList::iterator endpit = boost::next(old_pit);
1562 while (endpit != paragraphs().end() && endpit->getDepth())
1565 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1566 cursor() = tmpcursor;
1569 paragraphs().erase(old_pit);
1570 // update cursor par offset
1574 if (selection_position_was_oldcursor_position) {
1575 // correct selection
1576 bv()->resetAnchor();
1583 if (old_pit->stripLeadingSpaces()) {
1584 redoParagraph(old_pit);
1585 bv()->resetAnchor();
1592 ParagraphList & LyXText::paragraphs() const
1594 return const_cast<ParagraphList &>(paragraphs_);
1598 void LyXText::recUndo(par_type first, par_type last) const
1600 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1604 void LyXText::recUndo(par_type par) const
1606 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1610 bool LyXText::isInInset() const
1616 bool LyXText::toggleInset(LCursor & cur)
1618 InsetBase * inset = cur.nextInset();
1619 // is there an editable inset at cursor position?
1620 if (!isEditableInset(inset))
1622 cur.message(inset->editMessage());
1624 // do we want to keep this?? (JMarc)
1625 if (!isHighlyEditableInset(inset))
1628 if (inset->isOpen())
1636 int defaultRowHeight()
1638 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);