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)
75 : width_(0), maxwidth_(bv ? bv->workWidth() : 100), height_(0),
76 background_color_(LColor::background),
77 bv_owner(bv), 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);
98 bv->cursor().resetAnchor();
104 bool LyXText::isMainText() const
106 return &bv()->buffer()->text() == this;
110 // Gets the fully instantiated font at a given position in a paragraph
111 // Basically the same routine as Paragraph::getFont() in paragraph.C.
112 // The difference is that this one is used for displaying, and thus we
113 // are allowed to make cosmetic improvements. For instance make footnotes
115 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
117 BOOST_ASSERT(pos >= 0);
119 LyXLayout_ptr const & layout = pit->layout();
121 BufferParams const & params = bv()->buffer()->params();
122 pos_type const body_pos = pit->beginOfBody();
124 // We specialize the 95% common case:
125 if (!pit->getDepth()) {
126 LyXFont f = pit->getFontSettings(params, pos);
129 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
130 return f.realize(layout->reslabelfont);
132 return f.realize(layout->resfont);
135 // The uncommon case need not be optimized as much
138 layoutfont = layout->labelfont;
140 layoutfont = layout->font;
142 LyXFont font = pit->getFontSettings(params, pos);
143 font.realize(layoutfont);
148 // Realize with the fonts of lesser depth.
149 //font.realize(outerFont(pit, paragraphs()));
150 font.realize(defaultfont_);
156 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
158 LyXLayout_ptr const & layout = pit->layout();
160 if (!pit->getDepth())
161 return layout->resfont;
163 LyXFont font = layout->font;
164 // Realize with the fonts of lesser depth.
165 //font.realize(outerFont(pit, paragraphs()));
166 font.realize(defaultfont_);
172 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
174 LyXLayout_ptr const & layout = pit->layout();
176 if (!pit->getDepth())
177 return layout->reslabelfont;
179 LyXFont font = layout->labelfont;
180 // Realize with the fonts of lesser depth.
181 font.realize(outerFont(pit, paragraphs()));
182 font.realize(defaultfont_);
188 void LyXText::setCharFont(
189 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
192 LyXLayout_ptr const & layout = pit->layout();
194 // Get concrete layout font to reduce against
197 if (pos < pit->beginOfBody())
198 layoutfont = layout->labelfont;
200 layoutfont = layout->font;
202 // Realize against environment font information
203 if (pit->getDepth()) {
204 ParagraphList::iterator tp = pit;
205 while (!layoutfont.resolved() &&
206 tp != paragraphs().end() &&
208 tp = outerHook(tp, paragraphs());
209 if (tp != paragraphs().end())
210 layoutfont.realize(tp->layout()->font);
214 layoutfont.realize(defaultfont_);
216 // Now, reduce font against full layout font
217 font.reduce(layoutfont);
219 pit->setFont(pos, font);
224 // Asger is not sure we want to do this...
225 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
228 LyXLayout_ptr const & layout = par.layout();
229 pos_type const psize = par.size();
232 for (pos_type pos = 0; pos < psize; ++pos) {
233 if (pos < par.beginOfBody())
234 layoutfont = layout->labelfont;
236 layoutfont = layout->font;
238 LyXFont tmpfont = par.getFontSettings(params, pos);
239 tmpfont.reduce(layoutfont);
240 par.setFont(pos, tmpfont);
245 // return past-the-last paragraph influenced by a layout change on pit
246 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
248 ParagraphList::iterator end = paragraphs().end();
249 ParagraphList::iterator nextpit = boost::next(pit);
252 //because of parindents
253 if (!pit->getDepth())
254 return boost::next(nextpit);
255 //because of depth constrains
256 for (; nextpit != end; ++pit, ++nextpit) {
257 if (!pit->getDepth())
264 ParagraphList::iterator
265 LyXText::setLayout(ParagraphList::iterator start,
266 ParagraphList::iterator end,
267 string const & layout)
269 BOOST_ASSERT(start != end);
270 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
271 recUndo(parOffset(start), parOffset(undopit) - 1);
273 BufferParams const & bufparams = bv()->buffer()->params();
274 LyXLayout_ptr const & lyxlayout =
275 bufparams.getLyXTextClass()[layout];
277 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
278 pit->applyLayout(lyxlayout);
279 makeFontEntriesLayoutSpecific(bufparams, *pit);
280 if (lyxlayout->margintype == MARGIN_MANUAL)
281 pit->setLabelWidthString(lyxlayout->labelstring());
288 // set layout over selection and make a total rebreak of those paragraphs
289 void LyXText::setLayout(LCursor & cur, string const & layout)
291 BOOST_ASSERT(this == cur.text());
292 // special handling of new environment insets
293 BufferParams const & params = bv()->buffer()->params();
294 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
295 if (lyxlayout->is_environment) {
296 // move everything in a new environment inset
297 lyxerr << "setting layout " << layout << endl;
298 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
299 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
300 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
301 InsetBase * inset = new InsetEnvironment(params, layout);
302 insertInset(cur, inset);
303 //inset->edit(cur, true);
304 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
308 ParagraphList::iterator start = getPar(cur.selBegin().par());
309 ParagraphList::iterator end = boost::next(getPar(cur.selEnd().par()));
310 ParagraphList::iterator endpit = setLayout(start, end, layout);
311 redoParagraphs(start, endpit);
319 void getSelectionSpan(LCursor & cur, LyXText & text,
320 ParagraphList::iterator & beg,
321 ParagraphList::iterator & end)
323 if (!cur.selection()) {
324 beg = text.getPar(cur.par());
325 end = boost::next(beg);
327 beg = text.getPar(cur.selBegin());
328 end = boost::next(text.getPar(cur.selEnd()));
333 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
334 Paragraph const & par, int max_depth)
336 if (par.layout()->labeltype == LABEL_BIBLIO)
338 int const depth = par.params().depth();
339 if (type == LyXText::INC_DEPTH && depth < max_depth)
341 if (type == LyXText::DEC_DEPTH && depth > 0)
350 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
352 BOOST_ASSERT(this == cur.text());
353 ParagraphList::iterator beg, end;
354 getSelectionSpan(cur, const_cast<LyXText&>(*this), beg, end);
356 if (beg != paragraphs().begin())
357 max_depth = boost::prior(beg)->getMaxDepthAfter();
359 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
360 if (::changeDepthAllowed(type, *pit, max_depth))
362 max_depth = pit->getMaxDepthAfter();
368 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
370 BOOST_ASSERT(this == cur.text());
371 ParagraphList::iterator beg, end;
372 getSelectionSpan(cur, *this, beg, end);
373 recordUndoSelection(cur);
376 if (beg != paragraphs().begin())
377 max_depth = boost::prior(beg)->getMaxDepthAfter();
379 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
380 if (::changeDepthAllowed(type, *pit, max_depth)) {
381 int const depth = pit->params().depth();
382 if (type == INC_DEPTH)
383 pit->params().depth(depth + 1);
385 pit->params().depth(depth - 1);
387 max_depth = pit->getMaxDepthAfter();
389 // this handles the counter labels, and also fixes up
390 // depth values for follow-on (child) paragraphs
395 // set font over selection and make a total rebreak of those paragraphs
396 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
398 BOOST_ASSERT(this == cur.text());
399 // if there is no selection just set the current_font
400 if (!cur.selection()) {
401 // Determine basis font
403 ParagraphList::iterator pit = getPar(cur.par());
404 if (cur.pos() < pit->beginOfBody())
405 layoutfont = getLabelFont(pit);
407 layoutfont = getLayoutFont(pit);
409 // Update current font
410 real_current_font.update(font,
411 bv()->buffer()->params().language,
414 // Reduce to implicit settings
415 current_font = real_current_font;
416 current_font.reduce(layoutfont);
417 // And resolve it completely
418 real_current_font.realize(layoutfont);
423 // Ok, we have a selection.
424 recordUndoSelection(cur);
426 ParagraphList::iterator beg = getPar(cur.selBegin().par());
427 ParagraphList::iterator end = getPar(cur.selEnd().par());
429 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
430 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
432 BufferParams const & params = bv()->buffer()->params();
434 for (; pos != posend; ++pos) {
435 LyXFont f = getFont(pos.pit(), pos.pos());
436 f.update(font, params.language, toggleall);
437 setCharFont(pos.pit(), pos.pos(), f);
440 redoParagraphs(beg, ++end);
444 // the cursor set functions have a special mechanism. When they
445 // realize you left an empty paragraph, they will delete it.
447 void LyXText::cursorHome(LCursor & cur)
449 BOOST_ASSERT(this == cur.text());
450 setCursor(cur, cur.par(), cur.textRow().pos());
454 void LyXText::cursorEnd(LCursor & cur)
456 BOOST_ASSERT(this == cur.text());
457 // if not on the last row of the par, put the cursor before
459 pos_type const end = cur.textRow().endpos();
460 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
464 void LyXText::cursorTop(LCursor & cur)
466 BOOST_ASSERT(this == cur.text());
467 setCursor(cur, 0, 0);
471 void LyXText::cursorBottom(LCursor & cur)
473 BOOST_ASSERT(this == cur.text());
474 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
478 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
480 BOOST_ASSERT(this == cur.text());
481 // If the mask is completely neutral, tell user
482 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
483 // Could only happen with user style
484 cur.message(_("No font change defined. "
485 "Use Character under the Layout menu to define font change."));
489 // Try implicit word selection
490 // If there is a change in the language the implicit word selection
492 CursorSlice resetCursor = cur.top();
493 bool implicitSelection =
494 font.language() == ignore_language
495 && font.number() == LyXFont::IGNORE
496 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
499 setFont(cur, font, toggleall);
501 // Implicit selections are cleared afterwards
502 // and cursor is set to the original position.
503 if (implicitSelection) {
504 cur.clearSelection();
505 cur.top() = resetCursor;
511 string LyXText::getStringToIndex(LCursor & cur)
513 BOOST_ASSERT(this == cur.text());
514 // Try implicit word selection
515 // If there is a change in the language the implicit word selection
517 CursorSlice const reset_cursor = cur.top();
518 bool const implicitSelection =
519 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
522 if (!cur.selection())
523 cur.message(_("Nothing to index!"));
524 else if (cur.selBegin().par() != cur.selEnd().par())
525 cur.message(_("Cannot index more than one paragraph!"));
527 idxstring = cur.selectionAsString(false);
529 // Reset cursors to their original position.
530 cur.top() = reset_cursor;
533 // Clear the implicit selection.
534 if (implicitSelection)
535 cur.clearSelection();
541 // the DTP switches for paragraphs(). LyX will store them in the first
542 // physical paragraph. When a paragraph is broken, the top settings rest,
543 // the bottom settings are given to the new one. So I can make sure,
544 // they do not duplicate themself and you cannot play dirty tricks with
547 void LyXText::setParagraph(LCursor & cur,
548 Spacing const & spacing, LyXAlignment align,
549 string const & labelwidthstring, bool noindent)
551 BOOST_ASSERT(cur.text());
552 // make sure that the depth behind the selection are restored, too
553 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
554 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
556 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
557 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
559 for (--pit; pit != beg; ++pit) {
560 ParagraphParameters & params = pit->params();
561 params.spacing(spacing);
563 // does the layout allow the new alignment?
564 LyXLayout_ptr const & layout = pit->layout();
566 if (align == LYX_ALIGN_LAYOUT)
567 align = layout->align;
568 if (align & layout->alignpossible) {
569 if (align == layout->align)
570 params.align(LYX_ALIGN_LAYOUT);
574 pit->setLabelWidthString(labelwidthstring);
575 params.noindent(noindent);
578 redoParagraphs(getPar(cur.selBegin()), undopit);
582 string expandLabel(LyXTextClass const & textclass,
583 LyXLayout_ptr const & layout, bool appendix)
585 string fmt = appendix ?
586 layout->labelstring_appendix() : layout->labelstring();
588 // handle 'inherited level parts' in 'fmt',
589 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
590 size_t const i = fmt.find('@', 0);
591 if (i != string::npos) {
592 size_t const j = fmt.find('@', i + 1);
593 if (j != string::npos) {
594 string parent(fmt, i + 1, j - i - 1);
595 string label = expandLabel(textclass, textclass[parent], appendix);
596 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
600 return textclass.counters().counterLabel(fmt);
606 void incrementItemDepth(ParagraphList::iterator pit,
607 ParagraphList::iterator first_pit)
609 int const cur_labeltype = pit->layout()->labeltype;
611 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
614 int const cur_depth = pit->getDepth();
616 ParagraphList::iterator prev_pit = boost::prior(pit);
618 int const prev_depth = prev_pit->getDepth();
619 int const prev_labeltype = prev_pit->layout()->labeltype;
620 if (prev_depth == 0 && cur_depth > 0) {
621 if (prev_labeltype == cur_labeltype) {
622 pit->itemdepth = prev_pit->itemdepth + 1;
625 } else if (prev_depth < cur_depth) {
626 if (prev_labeltype == cur_labeltype) {
627 pit->itemdepth = prev_pit->itemdepth + 1;
630 } else if (prev_depth == cur_depth) {
631 if (prev_labeltype == cur_labeltype) {
632 pit->itemdepth = prev_pit->itemdepth;
636 if (prev_pit == first_pit)
644 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
645 ParagraphList::iterator firstpit,
651 int const cur_depth = pit->getDepth();
652 ParagraphList::iterator prev_pit = boost::prior(pit);
654 int const prev_depth = prev_pit->getDepth();
655 int const prev_labeltype = prev_pit->layout()->labeltype;
656 if (prev_depth <= cur_depth) {
657 if (prev_labeltype != LABEL_ENUMERATE) {
658 switch (pit->itemdepth) {
660 counters.reset("enumi");
662 counters.reset("enumii");
664 counters.reset("enumiii");
666 counters.reset("enumiv");
672 if (prev_pit == firstpit)
682 // set the counter of a paragraph. This includes the labels
683 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
685 BufferParams const & bufparams = buf.params();
686 LyXTextClass const & textclass = bufparams.getLyXTextClass();
687 LyXLayout_ptr const & layout = pit->layout();
688 ParagraphList::iterator first_pit = paragraphs().begin();
689 Counters & counters = textclass.counters();
694 if (pit == first_pit) {
695 pit->params().appendix(pit->params().startOfAppendix());
697 pit->params().appendix(boost::prior(pit)->params().appendix());
698 if (!pit->params().appendix() &&
699 pit->params().startOfAppendix()) {
700 pit->params().appendix(true);
701 textclass.counters().reset();
704 // Maybe we have to increment the item depth.
705 incrementItemDepth(pit, first_pit);
708 // erase what was there before
709 pit->params().labelString(string());
711 if (layout->margintype == MARGIN_MANUAL) {
712 if (pit->params().labelWidthString().empty())
713 pit->setLabelWidthString(layout->labelstring());
715 pit->setLabelWidthString(string());
718 // is it a layout that has an automatic label?
719 if (layout->labeltype == LABEL_COUNTER) {
720 BufferParams const & bufparams = buf.params();
721 LyXTextClass const & textclass = bufparams.getLyXTextClass();
722 counters.step(layout->counter);
723 string label = expandLabel(textclass, layout, pit->params().appendix());
724 pit->params().labelString(label);
725 } else if (layout->labeltype == LABEL_ITEMIZE) {
726 // At some point of time we should do something more
727 // clever here, like:
728 // pit->params().labelString(
729 // bufparams.user_defined_bullet(pit->itemdepth).getText());
730 // for now, use a simple hardcoded label
732 switch (pit->itemdepth) {
747 pit->params().labelString(itemlabel);
748 } else if (layout->labeltype == LABEL_ENUMERATE) {
749 // Maybe we have to reset the enumeration counter.
750 resetEnumCounterIfNeeded(pit, first_pit, counters);
753 // Yes I know this is a really, really! bad solution
755 string enumcounter = "enum";
757 switch (pit->itemdepth) {
769 // not a valid enumdepth...
773 counters.step(enumcounter);
775 pit->params().labelString(counters.enumLabel(enumcounter));
776 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
777 counters.step("bibitem");
778 int number = counters.value("bibitem");
779 if (pit->bibitem()) {
780 pit->bibitem()->setCounter(number);
781 pit->params().labelString(layout->labelstring());
783 // In biblio should't be following counters but...
785 string s = buf.B_(layout->labelstring());
788 if (layout->labeltype == LABEL_SENSITIVE) {
789 ParagraphList::iterator end = paragraphs().end();
790 ParagraphList::iterator tmppit = pit;
793 while (tmppit != end && tmppit->inInset()
794 // the single '=' is intended below
795 && (in = tmppit->inInset()->owner()))
797 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
798 in->lyxCode() == InsetBase::WRAP_CODE) {
802 Paragraph const * owner = &ownerPar(buf, in);
804 for ( ; tmppit != end; ++tmppit)
805 if (&*tmppit == owner)
813 if (in->lyxCode() == InsetBase::FLOAT_CODE)
814 type = static_cast<InsetFloat*>(in)->params().type;
815 else if (in->lyxCode() == InsetBase::WRAP_CODE)
816 type = static_cast<InsetWrap*>(in)->params().type;
820 Floating const & fl = textclass.floats().getType(type);
822 counters.step(fl.type());
824 // Doesn't work... yet.
825 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
827 // par->SetLayout(0);
828 // s = layout->labelstring;
829 s = _("Senseless: ");
832 pit->params().labelString(s);
838 // Updates all counters.
839 void LyXText::updateCounters()
842 bv()->buffer()->params().getLyXTextClass().counters().reset();
844 bool update_pos = false;
846 ParagraphList::iterator beg = paragraphs().begin();
847 ParagraphList::iterator end = paragraphs().end();
848 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
849 string const oldLabel = pit->params().labelString();
852 maxdepth = boost::prior(pit)->getMaxDepthAfter();
854 if (pit->params().depth() > maxdepth)
855 pit->params().depth(maxdepth);
857 // setCounter can potentially change the labelString.
858 setCounter(*bv()->buffer(), pit);
859 string const & newLabel = pit->params().labelString();
860 if (oldLabel != newLabel) {
861 redoParagraphInternal(pit);
867 updateParPositions();
871 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
873 BOOST_ASSERT(this == cur.text());
875 cur.paragraph().insertInset(cur.pos(), inset);
880 void LyXText::cutSelection(LCursor & cur, bool doclear, bool realcut)
882 BOOST_ASSERT(this == cur.text());
883 // Stuff what we got on the clipboard. Even if there is no selection.
885 // There is a problem with having the stuffing here in that the
886 // larger the selection the slower LyX will get. This can be
887 // solved by running the line below only when the selection has
888 // finished. The solution used currently just works, to make it
889 // faster we need to be more clever and probably also have more
890 // calls to stuffClipboard. (Lgb)
891 bv()->stuffClipboard(cur.selectionAsString(true));
893 // This doesn't make sense, if there is no selection
894 if (!cur.selection())
897 // OK, we have a selection. This is always between cur.selBegin()
900 // make sure that the depth behind the selection are restored, too
901 recordUndoSelection(cur);
902 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
903 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
904 ParagraphList::iterator undopit = undoSpan(endpit);
906 int endpos = cur.selEnd().pos();
908 BufferParams const & bufparams = bv()->buffer()->params();
909 boost::tie(endpit, endpos) = realcut ?
910 CutAndPaste::cutSelection(bufparams,
913 cur.selBegin().pos(), endpos,
916 : CutAndPaste::eraseSelection(bufparams,
919 cur.selBegin().pos(), endpos,
921 // sometimes necessary
923 begpit->stripLeadingSpaces();
925 redoParagraphs(begpit, undopit);
926 // cutSelection can invalidate the cursor so we need to set
928 // we prefer the end for when tracking changes
930 cur.par() = parOffset(endpit);
932 // need a valid cursor. (Lgb)
933 cur.clearSelection();
938 void LyXText::copySelection(LCursor & cur)
940 BOOST_ASSERT(this == cur.text());
941 // stuff the selection onto the X clipboard, from an explicit copy request
942 bv()->stuffClipboard(cur.selectionAsString(true));
944 // this doesnt make sense, if there is no selection
945 if (!cur.selection())
948 // ok we have a selection. This is always between cur.selBegin()
949 // and sel_end cursor
951 // copy behind a space if there is one
952 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
953 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
954 && (cur.selBegin().par() != cur.selEnd().par()
955 || cur.selBegin().pos() < cur.selEnd().pos()))
956 ++cur.selBegin().pos();
958 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
959 getPar(cur.selEnd().par()),
960 cur.selBegin().pos(),
962 bv()->buffer()->params().textclass);
966 void LyXText::pasteSelection(LCursor & cur, size_t sel_index)
968 // this does not make sense, if there is nothing to paste
969 if (!CutAndPaste::checkPastePossible())
974 ParagraphList::iterator endpit;
979 boost::tie(ppp, endpit) =
980 CutAndPaste::pasteSelection(*bv()->buffer(),
982 getPar(cur.par()), cur.pos(),
983 bv()->buffer()->params().textclass,
985 bufferErrors(*bv()->buffer(), el);
986 bv()->showErrorList(_("Paste"));
988 redoParagraphs(getPar(cur.par()), endpit);
990 cur.clearSelection();
992 setCursor(cur, parOffset(ppp.first), ppp.second);
998 void LyXText::setSelectionRange(LCursor & cur, lyx::pos_type length)
1009 // simple replacing. The font of the first selected character is used
1010 void LyXText::replaceSelectionWithString(LCursor & cur, string const & str)
1014 // Get font setting before we cut
1015 pos_type pos = cur.selEnd().pos();
1016 LyXFont const font = getPar(cur.selBegin())
1017 ->getFontSettings(bv()->buffer()->params(),
1018 cur.selBegin().pos());
1020 // Insert the new string
1021 string::const_iterator cit = str.begin();
1022 string::const_iterator end = str.end();
1023 for (; cit != end; ++cit) {
1024 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1028 // Cut the selection
1029 cutSelection(cur, true, false);
1033 // needed to insert the selection
1034 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
1036 ParagraphList::iterator pit = getPar(cur.par());
1037 ParagraphList::iterator endpit = boost::next(pit);
1038 pos_type pos = cur.pos();
1041 // only to be sure, should not be neccessary
1042 cur.clearSelection();
1043 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1045 redoParagraphs(getPar(cur.par()), endpit);
1047 setCursor(cur, cur.par(), pos);
1052 // turn double CR to single CR, others are converted into one
1053 // blank. Then insertStringAsLines is called
1054 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
1056 string linestr = str;
1057 bool newline_inserted = false;
1059 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
1060 if (linestr[i] == '\n') {
1061 if (newline_inserted) {
1062 // we know that \r will be ignored by
1063 // insertStringAsLines. Of course, it is a dirty
1064 // trick, but it works...
1065 linestr[i - 1] = '\r';
1069 newline_inserted = true;
1071 } else if (IsPrintable(linestr[i])) {
1072 newline_inserted = false;
1075 insertStringAsLines(cur, linestr);
1079 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
1080 bool setfont, bool boundary)
1082 CursorSlice old_cursor = cur.top();
1083 setCursorIntern(cur, par, pos, setfont, boundary);
1084 return deleteEmptyParagraphMechanism(cur.top(), old_cursor);
1088 void LyXText::setCursor(CursorSlice & cur, par_type par,
1089 pos_type pos, bool boundary)
1091 BOOST_ASSERT(par != int(paragraphs().size()));
1095 cur.boundary() = boundary;
1097 // no rows, no fun...
1098 if (paragraphs().begin()->rows.empty())
1101 // now some strict checking
1102 Paragraph & para = *getPar(par);
1103 Row const & row = *para.getRow(pos);
1104 pos_type const end = row.endpos();
1106 // None of these should happen, but we're scaredy-cats
1108 lyxerr << "dont like -1" << endl;
1109 BOOST_ASSERT(false);
1112 if (pos > para.size()) {
1113 lyxerr << "dont like 1, pos: " << pos
1114 << " size: " << para.size()
1115 << " row.pos():" << row.pos()
1116 << " par: " << par << endl;
1117 BOOST_ASSERT(false);
1121 lyxerr << "dont like 2, pos: " << pos
1122 << " size: " << para.size()
1123 << " row.pos():" << row.pos()
1124 << " par: " << par << endl;
1125 // This shouldn't happen.
1126 BOOST_ASSERT(false);
1129 if (pos < row.pos()) {
1130 lyxerr << "dont like 3 please report pos:" << pos
1131 << " size: " << para.size()
1132 << " row.pos():" << row.pos()
1133 << " par: " << par << endl;
1134 BOOST_ASSERT(false);
1139 void LyXText::setCursorIntern(LCursor & cur,
1140 par_type par, pos_type pos, bool setfont, bool boundary)
1142 setCursor(cur.top(), par, pos, boundary);
1143 cur.x_target() = cursorX(cur.top());
1145 setCurrentFont(cur);
1149 void LyXText::setCurrentFont(LCursor & cur)
1151 BOOST_ASSERT(this == cur.text());
1152 pos_type pos = cur.pos();
1153 ParagraphList::iterator pit = getPar(cur.par());
1155 if (cur.boundary() && pos > 0)
1159 if (pos == cur.lastpos())
1161 else // potentional bug... BUG (Lgb)
1162 if (pit->isSeparator(pos)) {
1163 if (pos > cur.textRow().pos() &&
1164 bidi.level(pos) % 2 ==
1165 bidi.level(pos - 1) % 2)
1167 else if (pos + 1 < cur.lastpos())
1172 BufferParams const & bufparams = bv()->buffer()->params();
1173 current_font = pit->getFontSettings(bufparams, pos);
1174 real_current_font = getFont(pit, pos);
1176 if (cur.pos() == cur.lastpos()
1177 && bidi.isBoundary(*bv()->buffer(), *pit, cur.pos())
1178 && !cur.boundary()) {
1179 Language const * lang = pit->getParLanguage(bufparams);
1180 current_font.setLanguage(lang);
1181 current_font.setNumber(LyXFont::OFF);
1182 real_current_font.setLanguage(lang);
1183 real_current_font.setNumber(LyXFont::OFF);
1188 // x is an absolute screen coord
1189 // returns the column near the specified x-coordinate of the row
1190 // x is set to the real beginning of this column
1191 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1192 Row const & row, int & x, bool & boundary) const
1195 RowMetrics const r = computeRowMetrics(pit, row);
1197 pos_type vc = row.pos();
1198 pos_type end = row.endpos();
1200 LyXLayout_ptr const & layout = pit->layout();
1202 bool left_side = false;
1204 pos_type body_pos = pit->beginOfBody();
1207 double last_tmpx = tmpx;
1210 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1213 // check for empty row
1215 x = int(tmpx) + xo_;
1219 while (vc < end && tmpx <= x) {
1220 c = bidi.vis2log(vc);
1222 if (body_pos > 0 && c == body_pos - 1) {
1223 tmpx += r.label_hfill +
1224 font_metrics::width(layout->labelsep, getLabelFont(pit));
1225 if (pit->isLineSeparator(body_pos - 1))
1226 tmpx -= singleWidth(pit, body_pos - 1);
1229 if (hfillExpansion(*pit, row, c)) {
1230 tmpx += singleWidth(pit, c);
1234 tmpx += r.label_hfill;
1235 } else if (pit->isSeparator(c)) {
1236 tmpx += singleWidth(pit, c);
1238 tmpx += r.separator;
1240 tmpx += singleWidth(pit, c);
1245 if ((tmpx + last_tmpx) / 2 > x) {
1250 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1253 // This (rtl_support test) is not needed, but gives
1254 // some speedup if rtl_support == false
1255 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1257 // If lastrow is false, we don't need to compute
1258 // the value of rtl.
1259 bool const rtl = lastrow ? isRTL(*pit) : false;
1261 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1262 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1264 else if (vc == row.pos()) {
1265 c = bidi.vis2log(vc);
1266 if (bidi.level(c) % 2 == 1)
1269 c = bidi.vis2log(vc - 1);
1270 bool const rtl = (bidi.level(c) % 2 == 1);
1271 if (left_side == rtl) {
1273 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1277 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1278 if (bidi.level(end -1) % 2 == 0)
1279 tmpx -= singleWidth(pit, end - 1);
1281 tmpx += singleWidth(pit, end - 1);
1285 x = int(tmpx) + xo_;
1286 return c - row.pos();
1290 // x,y are absolute coordinates
1291 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1295 CursorSlice old_cursor = cur.top();
1296 ParagraphList::iterator pit;
1297 Row const & row = *getRowNearY(y, pit);
1298 lyxerr << "hit row at: " << row.pos() << endl;
1300 int xx = x + xo_; // getRowNearX get absolute x coords
1301 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1302 setCursor(cur, parOffset(pit), pos, true, bound);
1306 // x,y are absolute screen coordinates
1307 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1309 ParagraphList::iterator pit;
1310 Row const & row = *getRowNearY(y - yo_, pit);
1313 int xx = x; // is modified by getColumnNearX
1314 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1315 cur.par() = parOffset(pit);
1317 cur.boundary() = bound;
1319 // try to descend into nested insets
1320 InsetBase * inset = checkInsetHit(x, y);
1324 // This should be just before or just behind the
1325 // cursor position set above.
1326 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1327 || inset == pit->getInset(pos));
1328 // Make sure the cursor points to the position before
1330 if (inset == pit->getInset(pos - 1))
1332 return inset->editXY(cur, x, y);
1336 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1338 if (cur.selection())
1340 if (cur.pos() == cur.lastpos())
1342 InsetBase * inset = cur.nextInset();
1343 if (!isHighlyEditableInset(inset))
1345 inset->edit(cur, front);
1350 void LyXText::cursorLeft(LCursor & cur)
1352 if (cur.pos() != 0) {
1353 bool boundary = cur.boundary();
1354 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1355 if (!checkAndActivateInset(cur, false)) {
1356 if (false && !boundary &&
1357 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1358 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1363 if (cur.par() != 0) {
1364 // steps into the paragraph above
1365 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1)->size());
1370 void LyXText::cursorRight(LCursor & cur)
1372 if (false && cur.boundary()) {
1373 setCursor(cur, cur.par(), cur.pos(), true, false);
1377 if (cur.pos() != cur.lastpos()) {
1378 if (!checkAndActivateInset(cur, true)) {
1379 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1380 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1382 setCursor(cur, cur.par(), cur.pos(), true, true);
1387 if (cur.par() != cur.lastpar())
1388 setCursor(cur, cur.par() + 1, 0);
1392 void LyXText::cursorUp(LCursor & cur)
1394 Row const & row = cur.textRow();
1395 int x = cur.x_target();
1396 int y = cursorY(cur.top()) - row.baseline() - 1;
1397 setCursorFromCoordinates(cur, x, y);
1399 if (!cur.selection()) {
1400 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1401 if (inset_hit && isHighlyEditableInset(inset_hit))
1402 inset_hit->editXY(cur, cur.x_target(), y);
1407 void LyXText::cursorDown(LCursor & cur)
1409 Row const & row = cur.textRow();
1410 int x = cur.x_target();
1411 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1412 setCursorFromCoordinates(cur, x, y);
1414 if (!cur.selection()) {
1415 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1416 if (inset_hit && isHighlyEditableInset(inset_hit))
1417 inset_hit->editXY(cur, cur.x_target(), y);
1422 void LyXText::cursorUpParagraph(LCursor & cur)
1425 setCursor(cur, cur.par(), 0);
1426 else if (cur.par() != 0)
1427 setCursor(cur, cur.par() - 1, 0);
1431 void LyXText::cursorDownParagraph(LCursor & cur)
1433 if (cur.par() != cur.lastpar())
1434 setCursor(cur, cur.par() + 1, 0);
1436 setCursor(cur, cur.par(), cur.lastpos());
1440 // fix the cursor `cur' after a characters has been deleted at `where'
1441 // position. Called by deleteEmptyParagraphMechanism
1442 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1444 // do notheing if cursor is not in the paragraph where the
1445 // deletion occured,
1446 if (cur.par() != where.par())
1449 // if cursor position is after the deletion place update it
1450 if (cur.pos() > where.pos())
1453 // check also if we don't want to set the cursor on a spot behind the
1454 // pagragraph because we erased the last character.
1455 if (cur.pos() > cur.lastpos())
1456 cur.pos() = cur.lastpos();
1460 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice & cur,
1461 CursorSlice const & old_cursor)
1463 #warning Disabled as it crashes after the cursor data shift... (Andre)
1466 // Would be wrong to delete anything if we have a selection.
1467 //if (cur.selection())
1471 // We allow all kinds of "mumbo-jumbo" when freespacing.
1472 ParagraphList::iterator const old_pit = getPar(old_cursor.par());
1473 if (old_pit->isFreeSpacing())
1476 /* Ok I'll put some comments here about what is missing.
1477 I have fixed BackSpace (and thus Delete) to not delete
1478 double-spaces automagically. I have also changed Cut,
1479 Copy and Paste to hopefully do some sensible things.
1480 There are still some small problems that can lead to
1481 double spaces stored in the document file or space at
1482 the beginning of paragraphs(). This happens if you have
1483 the cursor between to spaces and then save. Or if you
1484 cut and paste and the selection have a space at the
1485 beginning and then save right after the paste. I am
1486 sure none of these are very hard to fix, but I will
1487 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1488 that I can get some feedback. (Lgb)
1491 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1492 // delete the LineSeparator.
1495 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1496 // delete the LineSeparator.
1499 // If the pos around the old_cursor were spaces, delete one of them.
1500 if (old_cursor.par() != cur.par() || old_cursor.pos() != cur.pos()) {
1502 // Only if the cursor has really moved
1503 if (old_cursor.pos() > 0
1504 && old_cursor.pos() < old_pit->size()
1505 && old_pit->isLineSeparator(old_cursor.pos())
1506 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1507 bool erased = old_pit->erase(old_cursor.pos() - 1);
1508 redoParagraph(old_pit);
1512 #ifdef WITH_WARNINGS
1513 #warning This will not work anymore when we have multiple views of the same buffer
1514 // In this case, we will have to correct also the cursors held by
1515 // other bufferviews. It will probably be easier to do that in a more
1516 // automated way in CursorSlice code. (JMarc 26/09/2001)
1518 // correct all cursors held by the LyXText
1519 fixCursorAfterDelete(cursor(), old_cursor);
1520 fixCursorAfterDelete(anchor(), old_cursor);
1525 // don't delete anything if this is the ONLY paragraph!
1526 if (paragraphs().size() == 1)
1529 // Do not delete empty paragraphs with keepempty set.
1530 if (old_pit->allowEmpty())
1533 // only do our magic if we changed paragraph
1534 if (old_cursor.par() == cur.par())
1537 // record if we have deleted a paragraph
1538 // we can't possibly have deleted a paragraph before this point
1539 bool deleted = false;
1541 if (old_pit->empty()
1542 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1543 // ok, we will delete something
1544 CursorSlice tmpcursor;
1548 bool selection_position_was_oldcursor_position =
1549 anchor().par() == old_cursor.par()
1550 && anchor().pos() == old_cursor.pos();
1552 tmpcursor = cursor();
1553 cursor() = old_cursor; // that undo can restore the right cursor position
1555 ParagraphList::iterator endpit = boost::next(old_pit);
1556 while (endpit != paragraphs().end() && endpit->getDepth())
1559 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1560 cursor() = tmpcursor;
1563 paragraphs().erase(old_pit);
1564 // update cursor par offset
1568 if (selection_position_was_oldcursor_position) {
1569 // correct selection
1570 bv()->resetAnchor();
1577 if (old_pit->stripLeadingSpaces()) {
1578 redoParagraph(old_pit);
1579 bv()->resetAnchor();
1586 ParagraphList & LyXText::paragraphs() const
1588 return const_cast<ParagraphList &>(paragraphs_);
1592 void LyXText::recUndo(par_type first, par_type last) const
1594 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1598 void LyXText::recUndo(par_type par) const
1600 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1604 bool LyXText::toggleInset(LCursor & cur)
1606 InsetBase * inset = cur.nextInset();
1607 // is there an editable inset at cursor position?
1608 if (!isEditableInset(inset))
1610 cur.message(inset->editMessage());
1612 // do we want to keep this?? (JMarc)
1613 if (!isHighlyEditableInset(inset))
1616 if (inset->isOpen())
1624 int defaultRowHeight()
1626 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);