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), textwidth_(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)
93 current_font = getFont(beg, 0);
95 redoParagraphs(beg, end);
96 bv->cursor().resetAnchor();
102 // Gets the fully instantiated font at a given position in a paragraph
103 // Basically the same routine as Paragraph::getFont() in paragraph.C.
104 // The difference is that this one is used for displaying, and thus we
105 // are allowed to make cosmetic improvements. For instance make footnotes
107 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
109 BOOST_ASSERT(pos >= 0);
111 LyXLayout_ptr const & layout = pit->layout();
113 BufferParams const & params = bv()->buffer()->params();
114 pos_type const body_pos = pit->beginOfBody();
116 // We specialize the 95% common case:
117 if (!pit->getDepth()) {
118 LyXFont f = pit->getFontSettings(params, pos);
121 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
122 return f.realize(layout->reslabelfont);
124 return f.realize(layout->resfont);
127 // The uncommon case need not be optimized as much
130 layoutfont = layout->labelfont;
132 layoutfont = layout->font;
134 LyXFont font = pit->getFontSettings(params, pos);
135 font.realize(layoutfont);
140 // Realize with the fonts of lesser depth.
141 //font.realize(outerFont(pit, paragraphs()));
142 font.realize(defaultfont_);
148 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
150 LyXLayout_ptr const & layout = pit->layout();
152 if (!pit->getDepth())
153 return layout->resfont;
155 LyXFont font = layout->font;
156 // Realize with the fonts of lesser depth.
157 //font.realize(outerFont(pit, paragraphs()));
158 font.realize(defaultfont_);
164 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
166 LyXLayout_ptr const & layout = pit->layout();
168 if (!pit->getDepth())
169 return layout->reslabelfont;
171 LyXFont font = layout->labelfont;
172 // Realize with the fonts of lesser depth.
173 font.realize(outerFont(pit, paragraphs()));
174 font.realize(defaultfont_);
180 void LyXText::setCharFont(
181 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
184 LyXLayout_ptr const & layout = pit->layout();
186 // Get concrete layout font to reduce against
189 if (pos < pit->beginOfBody())
190 layoutfont = layout->labelfont;
192 layoutfont = layout->font;
194 // Realize against environment font information
195 if (pit->getDepth()) {
196 ParagraphList::iterator tp = pit;
197 while (!layoutfont.resolved() &&
198 tp != paragraphs().end() &&
200 tp = outerHook(tp, paragraphs());
201 if (tp != paragraphs().end())
202 layoutfont.realize(tp->layout()->font);
206 layoutfont.realize(defaultfont_);
208 // Now, reduce font against full layout font
209 font.reduce(layoutfont);
211 pit->setFont(pos, font);
216 // Asger is not sure we want to do this...
217 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
220 LyXLayout_ptr const & layout = par.layout();
221 pos_type const psize = par.size();
224 for (pos_type pos = 0; pos < psize; ++pos) {
225 if (pos < par.beginOfBody())
226 layoutfont = layout->labelfont;
228 layoutfont = layout->font;
230 LyXFont tmpfont = par.getFontSettings(params, pos);
231 tmpfont.reduce(layoutfont);
232 par.setFont(pos, tmpfont);
237 // return past-the-last paragraph influenced by a layout change on pit
238 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
240 ParagraphList::iterator end = paragraphs().end();
241 ParagraphList::iterator nextpit = boost::next(pit);
244 //because of parindents
245 if (!pit->getDepth())
246 return boost::next(nextpit);
247 //because of depth constrains
248 for (; nextpit != end; ++pit, ++nextpit) {
249 if (!pit->getDepth())
256 ParagraphList::iterator
257 LyXText::setLayout(ParagraphList::iterator start,
258 ParagraphList::iterator end,
259 string const & layout)
261 BOOST_ASSERT(start != end);
262 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
263 recUndo(parOffset(start), parOffset(undopit) - 1);
265 BufferParams const & bufparams = bv()->buffer()->params();
266 LyXLayout_ptr const & lyxlayout =
267 bufparams.getLyXTextClass()[layout];
269 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
270 pit->applyLayout(lyxlayout);
271 makeFontEntriesLayoutSpecific(bufparams, *pit);
272 if (lyxlayout->margintype == MARGIN_MANUAL)
273 pit->setLabelWidthString(lyxlayout->labelstring());
280 // set layout over selection and make a total rebreak of those paragraphs
281 void LyXText::setLayout(LCursor & cur, string const & layout)
283 BOOST_ASSERT(this == cur.text());
284 // special handling of new environment insets
285 BufferParams const & params = bv()->buffer()->params();
286 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
287 if (lyxlayout->is_environment) {
288 // move everything in a new environment inset
289 lyxerr << "setting layout " << layout << endl;
290 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
291 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
292 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
293 InsetBase * inset = new InsetEnvironment(params, layout);
294 insertInset(cur, inset);
296 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
300 ParagraphList::iterator start = getPar(cur.selBegin().par());
301 ParagraphList::iterator end = boost::next(getPar(cur.selEnd().par()));
302 ParagraphList::iterator endpit = setLayout(start, end, layout);
303 redoParagraphs(start, endpit);
311 void getSelectionSpan(LCursor & cur, LyXText & text,
312 ParagraphList::iterator & beg,
313 ParagraphList::iterator & end)
315 if (!cur.selection()) {
316 beg = text.getPar(cur.par());
317 end = boost::next(beg);
319 beg = text.getPar(cur.selBegin());
320 end = boost::next(text.getPar(cur.selEnd()));
325 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
326 Paragraph const & par,
329 if (par.layout()->labeltype == LABEL_BIBLIO)
331 int const depth = par.params().depth();
332 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
334 if (type == bv_funcs::DEC_DEPTH && depth > 0)
343 bool LyXText::changeDepthAllowed(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
345 BOOST_ASSERT(this == cur.text());
346 ParagraphList::iterator beg, end;
347 getSelectionSpan(cur, *this, beg, end);
349 if (beg != paragraphs().begin())
350 max_depth = boost::prior(beg)->getMaxDepthAfter();
352 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
353 if (::changeDepthAllowed(type, *pit, max_depth))
355 max_depth = pit->getMaxDepthAfter();
361 void LyXText::changeDepth(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
363 BOOST_ASSERT(this == cur.text());
364 ParagraphList::iterator beg, end;
365 getSelectionSpan(cur, *this, beg, end);
366 recordUndoSelection(cur);
369 if (beg != paragraphs().begin())
370 max_depth = boost::prior(beg)->getMaxDepthAfter();
372 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
373 if (::changeDepthAllowed(type, *pit, max_depth)) {
374 int const depth = pit->params().depth();
375 if (type == bv_funcs::INC_DEPTH)
376 pit->params().depth(depth + 1);
378 pit->params().depth(depth - 1);
380 max_depth = pit->getMaxDepthAfter();
382 // this handles the counter labels, and also fixes up
383 // depth values for follow-on (child) paragraphs
388 // set font over selection and make a total rebreak of those paragraphs
389 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
391 BOOST_ASSERT(this == cur.text());
392 // if there is no selection just set the current_font
393 if (!cur.selection()) {
394 // Determine basis font
396 ParagraphList::iterator pit = getPar(cur.par());
397 if (cur.pos() < pit->beginOfBody())
398 layoutfont = getLabelFont(pit);
400 layoutfont = getLayoutFont(pit);
402 // Update current font
403 real_current_font.update(font,
404 bv()->buffer()->params().language,
407 // Reduce to implicit settings
408 current_font = real_current_font;
409 current_font.reduce(layoutfont);
410 // And resolve it completely
411 real_current_font.realize(layoutfont);
416 // ok we have a selection.
417 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);
436 redoParagraphs(beg, ++end);
440 // the cursor set functions have a special mechanism. When they
441 // realize you left an empty paragraph, they will delete it.
443 void LyXText::cursorHome(LCursor & cur)
445 BOOST_ASSERT(this == cur.text());
446 setCursor(cur, cur.par(), cur.textRow().pos());
450 void LyXText::cursorEnd(LCursor & cur)
452 BOOST_ASSERT(this == cur.text());
453 // if not on the last row of the par, put the cursor before
455 pos_type const end = cur.textRow().endpos();
456 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
460 void LyXText::cursorTop(LCursor & cur)
462 BOOST_ASSERT(this == cur.text());
463 setCursor(cur, 0, 0);
467 void LyXText::cursorBottom(LCursor & cur)
469 BOOST_ASSERT(this == cur.text());
470 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
474 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
476 BOOST_ASSERT(this == cur.text());
477 // If the mask is completely neutral, tell user
478 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
479 // Could only happen with user style
480 cur.message(_("No font change defined. "
481 "Use Character under the Layout menu to define font change."));
485 // Try implicit word selection
486 // If there is a change in the language the implicit word selection
488 CursorSlice resetCursor = cur.current();
489 bool implicitSelection =
490 font.language() == ignore_language
491 && font.number() == LyXFont::IGNORE
492 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
495 setFont(cur, font, toggleall);
497 // Implicit selections are cleared afterwards
498 // and cursor is set to the original position.
499 if (implicitSelection) {
500 cur.clearSelection();
501 cur.current() = resetCursor;
507 string LyXText::getStringToIndex(LCursor & cur)
509 BOOST_ASSERT(this == cur.text());
510 // Try implicit word selection
511 // If there is a change in the language the implicit word selection
513 CursorSlice const reset_cursor = cur.current();
514 bool const implicitSelection =
515 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
518 if (!cur.selection())
519 cur.message(_("Nothing to index!"));
520 else if (cur.selBegin().par() != cur.selEnd().par())
521 cur.message(_("Cannot index more than one paragraph!"));
523 idxstring = cur.selectionAsString(false);
525 // Reset cursors to their original position.
526 cur.current() = reset_cursor;
529 // Clear the implicit selection.
530 if (implicitSelection)
531 cur.clearSelection();
537 // the DTP switches for paragraphs(). LyX will store them in the first
538 // physical paragraph. When a paragraph is broken, the top settings rest,
539 // the bottom settings are given to the new one. So I can make sure,
540 // they do not duplicate themself and you cannot play dirty tricks with
543 void LyXText::setParagraph(LCursor & cur,
544 Spacing const & spacing, LyXAlignment align,
545 string const & labelwidthstring, bool noindent)
547 BOOST_ASSERT(cur.text());
548 // make sure that the depth behind the selection are restored, too
549 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
550 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
552 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
553 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
555 for (--pit; pit != beg; ++pit) {
556 ParagraphParameters & params = pit->params();
557 params.spacing(spacing);
559 // does the layout allow the new alignment?
560 LyXLayout_ptr const & layout = pit->layout();
562 if (align == LYX_ALIGN_LAYOUT)
563 align = layout->align;
564 if (align & layout->alignpossible) {
565 if (align == layout->align)
566 params.align(LYX_ALIGN_LAYOUT);
570 pit->setLabelWidthString(labelwidthstring);
571 params.noindent(noindent);
574 redoParagraphs(getPar(cur.selBegin()), undopit);
578 string expandLabel(LyXTextClass const & textclass,
579 LyXLayout_ptr const & layout, bool appendix)
581 string fmt = appendix ?
582 layout->labelstring_appendix() : layout->labelstring();
584 // handle 'inherited level parts' in 'fmt',
585 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
586 size_t const i = fmt.find('@', 0);
587 if (i != string::npos) {
588 size_t const j = fmt.find('@', i + 1);
589 if (j != string::npos) {
590 string parent(fmt, i + 1, j - i - 1);
591 string label = expandLabel(textclass, textclass[parent], appendix);
592 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
596 return textclass.counters().counterLabel(fmt);
602 void incrementItemDepth(ParagraphList::iterator pit,
603 ParagraphList::iterator first_pit)
605 int const cur_labeltype = pit->layout()->labeltype;
607 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
610 int const cur_depth = pit->getDepth();
612 ParagraphList::iterator prev_pit = boost::prior(pit);
614 int const prev_depth = prev_pit->getDepth();
615 int const prev_labeltype = prev_pit->layout()->labeltype;
616 if (prev_depth == 0 && cur_depth > 0) {
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 + 1;
626 } else if (prev_depth == cur_depth) {
627 if (prev_labeltype == cur_labeltype) {
628 pit->itemdepth = prev_pit->itemdepth;
632 if (prev_pit == first_pit)
640 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
641 ParagraphList::iterator firstpit,
647 int const cur_depth = pit->getDepth();
648 ParagraphList::iterator prev_pit = boost::prior(pit);
650 int const prev_depth = prev_pit->getDepth();
651 int const prev_labeltype = prev_pit->layout()->labeltype;
652 if (prev_depth <= cur_depth) {
653 if (prev_labeltype != LABEL_ENUMERATE) {
654 switch (pit->itemdepth) {
656 counters.reset("enumi");
658 counters.reset("enumii");
660 counters.reset("enumiii");
662 counters.reset("enumiv");
668 if (prev_pit == firstpit)
678 // set the counter of a paragraph. This includes the labels
679 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
681 BufferParams const & bufparams = buf.params();
682 LyXTextClass const & textclass = bufparams.getLyXTextClass();
683 LyXLayout_ptr const & layout = pit->layout();
684 ParagraphList::iterator first_pit = paragraphs().begin();
685 Counters & counters = textclass.counters();
690 if (pit == first_pit) {
691 pit->params().appendix(pit->params().startOfAppendix());
693 pit->params().appendix(boost::prior(pit)->params().appendix());
694 if (!pit->params().appendix() &&
695 pit->params().startOfAppendix()) {
696 pit->params().appendix(true);
697 textclass.counters().reset();
700 // Maybe we have to increment the item depth.
701 incrementItemDepth(pit, first_pit);
704 // erase what was there before
705 pit->params().labelString(string());
707 if (layout->margintype == MARGIN_MANUAL) {
708 if (pit->params().labelWidthString().empty())
709 pit->setLabelWidthString(layout->labelstring());
711 pit->setLabelWidthString(string());
714 // is it a layout that has an automatic label?
715 if (layout->labeltype == LABEL_COUNTER) {
716 BufferParams const & bufparams = buf.params();
717 LyXTextClass const & textclass = bufparams.getLyXTextClass();
718 counters.step(layout->counter);
719 string label = expandLabel(textclass, layout, pit->params().appendix());
720 pit->params().labelString(label);
721 } else if (layout->labeltype == LABEL_ITEMIZE) {
722 // At some point of time we should do something more
723 // clever here, like:
724 // pit->params().labelString(
725 // bufparams.user_defined_bullet(pit->itemdepth).getText());
726 // for now, use a simple hardcoded label
728 switch (pit->itemdepth) {
743 pit->params().labelString(itemlabel);
744 } else if (layout->labeltype == LABEL_ENUMERATE) {
745 // Maybe we have to reset the enumeration counter.
746 resetEnumCounterIfNeeded(pit, first_pit, counters);
749 // Yes I know this is a really, really! bad solution
751 string enumcounter = "enum";
753 switch (pit->itemdepth) {
765 // not a valid enumdepth...
769 counters.step(enumcounter);
771 pit->params().labelString(counters.enumLabel(enumcounter));
772 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
773 counters.step("bibitem");
774 int number = counters.value("bibitem");
775 if (pit->bibitem()) {
776 pit->bibitem()->setCounter(number);
777 pit->params().labelString(layout->labelstring());
779 // In biblio should't be following counters but...
781 string s = buf.B_(layout->labelstring());
784 if (layout->labeltype == LABEL_SENSITIVE) {
785 ParagraphList::iterator end = paragraphs().end();
786 ParagraphList::iterator tmppit = pit;
789 while (tmppit != end && tmppit->inInset()
790 // the single '=' is intended below
791 && (in = tmppit->inInset()->owner()))
793 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
794 in->lyxCode() == InsetBase::WRAP_CODE) {
798 Paragraph const * owner = &ownerPar(buf, in);
800 for ( ; tmppit != end; ++tmppit)
801 if (&*tmppit == owner)
809 if (in->lyxCode() == InsetBase::FLOAT_CODE)
810 type = static_cast<InsetFloat*>(in)->params().type;
811 else if (in->lyxCode() == InsetBase::WRAP_CODE)
812 type = static_cast<InsetWrap*>(in)->params().type;
816 Floating const & fl = textclass.floats().getType(type);
818 counters.step(fl.type());
820 // Doesn't work... yet.
821 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
823 // par->SetLayout(0);
824 // s = layout->labelstring;
825 s = _("Senseless: ");
828 pit->params().labelString(s);
834 // Updates all counters.
835 void LyXText::updateCounters()
838 bv()->buffer()->params().getLyXTextClass().counters().reset();
840 bool update_pos = false;
842 ParagraphList::iterator beg = paragraphs().begin();
843 ParagraphList::iterator end = paragraphs().end();
844 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
845 string const oldLabel = pit->params().labelString();
848 maxdepth = boost::prior(pit)->getMaxDepthAfter();
850 if (pit->params().depth() > maxdepth)
851 pit->params().depth(maxdepth);
853 // setCounter can potentially change the labelString.
854 setCounter(*bv()->buffer(), pit);
855 string const & newLabel = pit->params().labelString();
856 if (oldLabel != newLabel) {
857 redoParagraphInternal(pit);
863 updateParPositions();
867 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
869 BOOST_ASSERT(this == cur.text());
872 cur.paragraph().insertInset(cur.pos(), inset);
873 // Just to rebreak and refresh correctly.
874 // The character will not be inserted a second time
875 insertChar(cur, Paragraph::META_INSET);
876 // If we enter a highly editable inset the cursor should be before
877 // the inset. After an undo LyX tries to call inset->edit(...)
878 // and fails if the cursor is behind the inset and getInset
879 // does not return the inset!
880 if (isHighlyEditableInset(inset))
881 cursorLeft(cur, true);
886 void LyXText::cutSelection(LCursor & cur, bool doclear, bool realcut)
888 BOOST_ASSERT(this == cur.text());
889 // Stuff what we got on the clipboard. Even if there is no selection.
891 // There is a problem with having the stuffing here in that the
892 // larger the selection the slower LyX will get. This can be
893 // solved by running the line below only when the selection has
894 // finished. The solution used currently just works, to make it
895 // faster we need to be more clever and probably also have more
896 // calls to stuffClipboard. (Lgb)
897 bv()->stuffClipboard(cur.selectionAsString(true));
899 // This doesn't make sense, if there is no selection
900 if (!cur.selection())
903 // OK, we have a selection. This is always between cur.selBegin()
906 // make sure that the depth behind the selection are restored, too
907 recordUndoSelection(cur);
908 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
909 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
910 ParagraphList::iterator undopit = undoSpan(endpit);
912 int endpos = cur.selEnd().pos();
914 BufferParams const & bufparams = bv()->buffer()->params();
915 boost::tie(endpit, endpos) = realcut ?
916 CutAndPaste::cutSelection(bufparams,
919 cur.selBegin().pos(), endpos,
922 : CutAndPaste::eraseSelection(bufparams,
925 cur.selBegin().pos(), endpos,
927 // sometimes necessary
929 begpit->stripLeadingSpaces();
931 redoParagraphs(begpit, undopit);
932 // cutSelection can invalidate the cursor so we need to set
934 // we prefer the end for when tracking changes
936 cur.par() = parOffset(endpit);
938 // need a valid cursor. (Lgb)
939 cur.clearSelection();
944 void LyXText::copySelection(LCursor & cur)
946 BOOST_ASSERT(this == cur.text());
947 // stuff the selection onto the X clipboard, from an explicit copy request
948 bv()->stuffClipboard(cur.selectionAsString(true));
950 // this doesnt make sense, if there is no selection
951 if (!cur.selection())
954 // ok we have a selection. This is always between cur.selBegin()
955 // and sel_end cursor
957 // copy behind a space if there is one
958 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
959 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
960 && (cur.selBegin().par() != cur.selEnd().par()
961 || cur.selBegin().pos() < cur.selEnd().pos()))
962 cur.selBegin().pos(cur.selBegin().pos() + 1);
964 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
965 getPar(cur.selEnd().par()),
966 cur.selBegin().pos(),
968 bv()->buffer()->params().textclass);
972 void LyXText::pasteSelection(LCursor & cur, size_t sel_index)
974 // this does not make sense, if there is nothing to paste
975 if (!CutAndPaste::checkPastePossible())
980 ParagraphList::iterator endpit;
985 boost::tie(ppp, endpit) =
986 CutAndPaste::pasteSelection(*bv()->buffer(),
988 getPar(cur.par()), cur.pos(),
989 bv()->buffer()->params().textclass,
991 bufferErrors(*bv()->buffer(), el);
992 bv()->showErrorList(_("Paste"));
994 redoParagraphs(getPar(cur.par()), endpit);
996 cur.clearSelection();
998 setCursor(cur, parOffset(ppp.first), ppp.second);
1004 void LyXText::setSelectionRange(LCursor & cur, lyx::pos_type length)
1010 cursorRight(cur, true);
1015 // simple replacing. The font of the first selected character is used
1016 void LyXText::replaceSelectionWithString(LCursor & cur, string const & str)
1021 // Get font setting before we cut
1022 pos_type pos = cur.selEnd().pos();
1023 LyXFont const font = getPar(cur.selBegin())
1024 ->getFontSettings(bv()->buffer()->params(),
1025 cur.selBegin().pos());
1027 // Insert the new string
1028 string::const_iterator cit = str.begin();
1029 string::const_iterator end = str.end();
1030 for (; cit != end; ++cit) {
1031 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1035 // Cut the selection
1036 cutSelection(cur, true, false);
1041 // needed to insert the selection
1042 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
1044 ParagraphList::iterator pit = getPar(cur.par());
1045 ParagraphList::iterator endpit = boost::next(pit);
1046 pos_type pos = cursor().pos();
1049 // only to be sure, should not be neccessary
1050 cur.clearSelection();
1051 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1053 redoParagraphs(getPar(cur.par()), endpit);
1055 setCursor(cur, cur.par(), pos);
1060 // turn double CR to single CR, others are converted into one
1061 // blank. Then insertStringAsLines is called
1062 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
1064 string linestr = str;
1065 bool newline_inserted = false;
1067 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
1068 if (linestr[i] == '\n') {
1069 if (newline_inserted) {
1070 // we know that \r will be ignored by
1071 // insertStringAsLines. Of course, it is a dirty
1072 // trick, but it works...
1073 linestr[i - 1] = '\r';
1077 newline_inserted = true;
1079 } else if (IsPrintable(linestr[i])) {
1080 newline_inserted = false;
1083 insertStringAsLines(cur, linestr);
1087 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
1088 bool setfont, bool boundary)
1090 CursorSlice old_cursor = cur.current();
1091 setCursorIntern(cur, par, pos, setfont, boundary);
1092 return deleteEmptyParagraphMechanism(old_cursor);
1096 void LyXText::setCursor(CursorSlice & cur, par_type par,
1097 pos_type pos, bool boundary)
1099 BOOST_ASSERT(par != int(paragraphs().size()));
1103 cur.boundary(boundary);
1105 // no rows, no fun...
1106 if (paragraphs().begin()->rows.empty())
1109 // now some strict checking
1110 Paragraph & para = *getPar(par);
1111 Row const & row = *para.getRow(pos);
1112 pos_type const end = row.endpos();
1114 // None of these should happen, but we're scaredy-cats
1116 lyxerr << "dont like -1" << endl;
1119 BOOST_ASSERT(false);
1120 } else if (pos > para.size()) {
1121 lyxerr << "dont like 1, pos: " << pos
1122 << " size: " << para.size()
1123 << " row.pos():" << row.pos()
1124 << " par: " << par << endl;
1127 BOOST_ASSERT(false);
1128 } else if (pos > end) {
1129 lyxerr << "dont like 2 please report" << endl;
1130 // This shouldn't happen.
1133 BOOST_ASSERT(false);
1134 } else if (pos < row.pos()) {
1135 lyxerr << "dont like 3 please report pos:" << pos
1136 << " size: " << para.size()
1137 << " row.pos():" << row.pos()
1138 << " par: " << par << endl;
1141 BOOST_ASSERT(false);
1146 void LyXText::setCursorIntern(LCursor & cur,
1147 par_type par, pos_type pos, bool setfont, bool boundary)
1149 setCursor(cur.current(), par, pos, boundary);
1150 cur.x_target() = cursorX(cur.current());
1152 setCurrentFont(cur);
1156 void LyXText::setCurrentFont(LCursor & cur)
1158 BOOST_ASSERT(this == cur.text());
1159 pos_type pos = cur.pos();
1160 ParagraphList::iterator pit = getPar(cur.par());
1162 if (cur.boundary() && pos > 0)
1166 if (pos == cur.lastpos())
1168 else // potentional bug... BUG (Lgb)
1169 if (pit->isSeparator(pos)) {
1170 if (pos > cur.textRow().pos() &&
1171 bidi.level(pos) % 2 ==
1172 bidi.level(pos - 1) % 2)
1174 else if (pos + 1 < cur.lastpos())
1179 BufferParams const & bufparams = bv()->buffer()->params();
1180 current_font = pit->getFontSettings(bufparams, pos);
1181 real_current_font = getFont(pit, pos);
1183 if (cur.pos() == cur.lastpos()
1184 && bidi.isBoundary(*bv()->buffer(), *pit, cur.pos())
1185 && !cur.boundary()) {
1186 Language const * lang = pit->getParLanguage(bufparams);
1187 current_font.setLanguage(lang);
1188 current_font.setNumber(LyXFont::OFF);
1189 real_current_font.setLanguage(lang);
1190 real_current_font.setNumber(LyXFont::OFF);
1195 // x is an absolute screen coord
1196 // returns the column near the specified x-coordinate of the row
1197 // x is set to the real beginning of this column
1198 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1199 Row const & row, int & x, bool & boundary) const
1202 double tmpx = row.x();
1203 double fill_separator = row.fill_separator();
1204 double fill_hfill = row.fill_hfill();
1205 double fill_label_hfill = row.fill_label_hfill();
1207 pos_type vc = row.pos();
1208 pos_type end = row.endpos();
1210 LyXLayout_ptr const & layout = pit->layout();
1212 bool left_side = false;
1214 pos_type body_pos = pit->beginOfBody();
1215 double last_tmpx = tmpx;
1218 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1221 // check for empty row
1223 x = int(tmpx) + xo_;
1227 while (vc < end && tmpx <= x) {
1228 c = bidi.vis2log(vc);
1230 if (body_pos > 0 && c == body_pos - 1) {
1231 tmpx += fill_label_hfill +
1232 font_metrics::width(layout->labelsep, getLabelFont(pit));
1233 if (pit->isLineSeparator(body_pos - 1))
1234 tmpx -= singleWidth(pit, body_pos - 1);
1237 if (hfillExpansion(*pit, row, c)) {
1238 tmpx += singleWidth(pit, c);
1242 tmpx += fill_label_hfill;
1243 } else if (pit->isSeparator(c)) {
1244 tmpx += singleWidth(pit, c);
1246 tmpx += fill_separator;
1248 tmpx += singleWidth(pit, c);
1253 if ((tmpx + last_tmpx) / 2 > x) {
1258 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1261 // This (rtl_support test) is not needed, but gives
1262 // some speedup if rtl_support == false
1263 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1265 // If lastrow is false, we don't need to compute
1266 // the value of rtl.
1267 bool const rtl = lastrow
1268 ? pit->isRightToLeftPar(bv()->buffer()->params())
1271 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1272 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1274 else if (vc == row.pos()) {
1275 c = bidi.vis2log(vc);
1276 if (bidi.level(c) % 2 == 1)
1279 c = bidi.vis2log(vc - 1);
1280 bool const rtl = (bidi.level(c) % 2 == 1);
1281 if (left_side == rtl) {
1283 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1287 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1288 if (bidi.level(end -1) % 2 == 0)
1289 tmpx -= singleWidth(pit, end - 1);
1291 tmpx += singleWidth(pit, end - 1);
1295 x = int(tmpx) + xo_;
1296 return c - row.pos();
1300 // x,y are coordinates relative to this LyXText
1301 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1303 CursorSlice old_cursor = cur;
1304 ParagraphList::iterator pit;
1305 Row const & row = *getRowNearY(y, pit);
1307 int xx = x + xo_; // getRowNearX get absolute x coords
1308 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1309 cur.par() = parOffset(pit);
1311 cur.boundary() = bound;
1312 deleteEmptyParagraphMechanism(old_cursor);
1316 // x,y are absolute screen coordinates
1317 void LyXText::edit(LCursor & cur, int x, int y)
1319 ParagraphList::iterator pit;
1320 Row const & row = *getRowNearY(y - yo_, pit);
1323 int xx = x; // is modified by getColumnNearX
1324 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1325 cur.par() = parOffset(pit);
1327 cur.boundary() = bound;
1329 // try to descend into nested insets
1330 InsetBase * inset = checkInsetHit(x, y);
1332 // This should be just before or just behind the
1333 // cursor position set above.
1334 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1335 || inset == pit->getInset(pos));
1336 // Make sure the cursor points to the position before
1338 if (inset == pit->getInset(pos - 1))
1340 inset->edit(cur, x, y);
1345 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1347 if (cur.pos() == cur.lastpos())
1349 InsetBase * inset = cur.nextInset();
1350 if (!isHighlyEditableInset(inset))
1352 inset->edit(cur, front);
1357 DispatchResult LyXText::moveRight(LCursor & cur)
1359 if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1360 return moveLeftIntern(cur, false, true, false);
1362 return moveRightIntern(cur, true, true, false);
1366 DispatchResult LyXText::moveLeft(LCursor & cur)
1368 if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1369 return moveRightIntern(cur, true, true, false);
1371 return moveLeftIntern(cur, false, true, false);
1375 DispatchResult LyXText::moveRightIntern(LCursor & cur,
1376 bool front, bool activate_inset, bool selecting)
1378 if (cur.par() == cur.lastpar() && cur.pos() == cur.lastpos())
1379 return DispatchResult(false, FINISHED_RIGHT);
1380 if (activate_inset && checkAndActivateInset(cur, front))
1381 return DispatchResult(true, true);
1382 cursorRight(cur, true);
1384 cur.clearSelection();
1385 return DispatchResult(true);
1389 DispatchResult LyXText::moveLeftIntern(LCursor & cur,
1390 bool front, bool activate_inset, bool selecting)
1392 if (cur.par() == 0 && cur.pos() == 0)
1393 return DispatchResult(false, FINISHED);
1394 cursorLeft(cur, true);
1396 cur.clearSelection();
1397 if (activate_inset && checkAndActivateInset(cur, front))
1398 return DispatchResult(true, true);
1399 return DispatchResult(true);
1403 DispatchResult LyXText::moveUp(LCursor & cur)
1405 if (cur.par() == 0 && cur.crow() == 0)
1406 return DispatchResult(false, FINISHED_UP);
1407 cursorUp(cur, false);
1408 cur.clearSelection();
1409 return DispatchResult(true);
1413 DispatchResult LyXText::moveDown(LCursor & cur)
1415 if (cur.par() == cur.lastpar() && cur.textRow().endpos() == cur.lastpos())
1416 return DispatchResult(false, FINISHED_DOWN);
1417 cursorDown(cur, false);
1418 cur.clearSelection();
1419 return DispatchResult(true);
1423 bool LyXText::cursorLeft(LCursor & cur, bool internal)
1425 if (cur.pos() != 0) {
1426 bool boundary = cur.boundary();
1427 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1428 if (!internal && !boundary &&
1429 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1430 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1434 if (cur.par() != 0) {
1435 // steps into the paragraph above
1436 setCursor(cur, cur.par() - 1, boost::prior(cursorPar())->size());
1444 bool LyXText::cursorRight(LCursor & cur, bool internal)
1446 if (!internal && cur.boundary()) {
1447 setCursor(cur, cur.par(), cur.pos(), true, false);
1451 if (cur.pos() != cur.lastpos()) {
1452 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1453 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1455 setCursor(cur, cur.par(), cur.pos(), true, true);
1459 if (cur.par() != cur.lastpar()) {
1460 setCursor(cur, cur.par() + 1, 0);
1468 void LyXText::cursorUp(LCursor & cur, bool selecting)
1470 Row const & row = cur.textRow();
1471 int x = cur.x_target();
1472 int y = cursorY(cur.current()) - row.baseline() - 1;
1473 setCursorFromCoordinates(cur.current(), x - xo_, y - yo_);
1476 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1477 if (inset_hit && isHighlyEditableInset(inset_hit))
1478 inset_hit->edit(cur, cur.x_target(), y);
1483 void LyXText::cursorDown(LCursor & cur, bool selecting)
1485 Row const & row = cur.textRow();
1486 int x = cur.x_target();
1487 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1488 setCursorFromCoordinates(cur.current(), x - xo_, y - yo_);
1491 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1492 if (inset_hit && isHighlyEditableInset(inset_hit))
1493 inset_hit->edit(cur, cur.x_target(), y);
1498 void LyXText::cursorUpParagraph(LCursor & cur)
1501 setCursor(cur, cur.par(), 0);
1502 else if (cur.par() != 0)
1503 setCursor(cur, cur.par() - 1, 0);
1507 void LyXText::cursorDownParagraph(LCursor & cur)
1509 if (cur.par() != cur.lastpar())
1510 setCursor(cur, cur.par() + 1, 0);
1512 setCursor(cur, cur.par(), cur.lastpos());
1516 // fix the cursor `cur' after a characters has been deleted at `where'
1517 // position. Called by deleteEmptyParagraphMechanism
1518 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1520 // if cursor is not in the paragraph where the delete occured,
1522 if (cur.par() != where.par())
1525 // if cursor position is after the place where the delete occured,
1527 if (cur.pos() > where.pos())
1530 // check also if we don't want to set the cursor on a spot behind the
1531 // pagragraph because we erased the last character.
1532 if (cur.pos() > cur.lastpos())
1533 cur.pos() = cur.lastpos();
1537 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1539 #warning Disabled as it crashes after the cursor data shift... (Andre)
1542 // Would be wrong to delete anything if we have a selection.
1543 if (bv()->cursor().selection())
1546 // Don't do anything if the cursor is invalid
1547 if (old_cursor.par() == -1)
1551 // We allow all kinds of "mumbo-jumbo" when freespacing.
1552 ParagraphList::iterator const old_pit = getPar(old_cursor);
1553 if (old_pit->isFreeSpacing())
1556 /* Ok I'll put some comments here about what is missing.
1557 I have fixed BackSpace (and thus Delete) to not delete
1558 double-spaces automagically. I have also changed Cut,
1559 Copy and Paste to hopefully do some sensible things.
1560 There are still some small problems that can lead to
1561 double spaces stored in the document file or space at
1562 the beginning of paragraphs(). This happens if you have
1563 the cursor between to spaces and then save. Or if you
1564 cut and paste and the selection have a space at the
1565 beginning and then save right after the paste. I am
1566 sure none of these are very hard to fix, but I will
1567 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1568 that I can get some feedback. (Lgb)
1571 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1572 // delete the LineSeparator.
1575 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1576 // delete the LineSeparator.
1579 // If the pos around the old_cursor were spaces, delete one of them.
1580 if (old_cursor.par() != cursor().par()
1581 || old_cursor.pos() != cursor().pos()) {
1583 // Only if the cursor has really moved
1584 if (old_cursor.pos() > 0
1585 && old_cursor.pos() < old_pit->size()
1586 && old_pit->isLineSeparator(old_cursor.pos())
1587 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1588 bool erased = old_pit->erase(old_cursor.pos() - 1);
1589 redoParagraph(old_pit);
1593 #ifdef WITH_WARNINGS
1594 #warning This will not work anymore when we have multiple views of the same buffer
1595 // In this case, we will have to correct also the cursors held by
1596 // other bufferviews. It will probably be easier to do that in a more
1597 // automated way in CursorSlice code. (JMarc 26/09/2001)
1599 // correct all cursors held by the LyXText
1600 fixCursorAfterDelete(cursor(), old_cursor);
1601 fixCursorAfterDelete(anchor(), old_cursor);
1606 // don't delete anything if this is the ONLY paragraph!
1607 if (paragraphs().size() == 1)
1610 // Do not delete empty paragraphs with keepempty set.
1611 if (old_pit->allowEmpty())
1614 // only do our magic if we changed paragraph
1615 if (old_cursor.par() == cursor().par())
1618 // record if we have deleted a paragraph
1619 // we can't possibly have deleted a paragraph before this point
1620 bool deleted = false;
1622 if (old_pit->empty()
1623 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1624 // ok, we will delete something
1625 CursorSlice tmpcursor;
1629 bool selection_position_was_oldcursor_position =
1630 anchor().par() == old_cursor.par()
1631 && anchor().pos() == old_cursor.pos();
1633 tmpcursor = cursor();
1634 cursor() = old_cursor; // that undo can restore the right cursor position
1636 ParagraphList::iterator endpit = boost::next(old_pit);
1637 while (endpit != paragraphs().end() && endpit->getDepth())
1640 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1641 cursor() = tmpcursor;
1644 ParagraphList::iterator tmppit = cursorPar();
1646 paragraphs().erase(old_pit);
1647 // update cursor par offset
1648 cursor().par(parOffset(tmppit));
1651 if (selection_position_was_oldcursor_position) {
1652 // correct selection
1653 bv()->resetAnchor();
1660 if (old_pit->stripLeadingSpaces()) {
1661 redoParagraph(old_pit);
1662 bv()->resetAnchor();
1669 ParagraphList & LyXText::paragraphs() const
1671 return const_cast<ParagraphList &>(paragraphs_);
1675 void LyXText::recUndo(par_type first, par_type last) const
1677 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1681 void LyXText::recUndo(par_type par) const
1683 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1687 bool LyXText::isInInset() const
1693 bool LyXText::toggleInset(LCursor & cur)
1695 InsetBase * inset = cur.nextInset();
1696 // is there an editable inset at cursor position?
1697 if (!isEditableInset(inset))
1699 cur.message(inset->editMessage());
1701 // do we want to keep this?? (JMarc)
1702 if (!isHighlyEditableInset(inset))
1705 if (inset->isOpen())
1713 int defaultRowHeight()
1715 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);