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);
97 bv->cursor().resetAnchor();
103 bool LyXText::isMainText() const
105 return &bv()->buffer()->text() == this;
109 // Gets the fully instantiated font at a given position in a paragraph
110 // Basically the same routine as Paragraph::getFont() in paragraph.C.
111 // The difference is that this one is used for displaying, and thus we
112 // are allowed to make cosmetic improvements. For instance make footnotes
114 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
116 BOOST_ASSERT(pos >= 0);
118 LyXLayout_ptr const & layout = pit->layout();
120 BufferParams const & params = bv()->buffer()->params();
121 pos_type const body_pos = pit->beginOfBody();
123 // We specialize the 95% common case:
124 if (!pit->getDepth()) {
125 LyXFont f = pit->getFontSettings(params, pos);
128 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
129 return f.realize(layout->reslabelfont);
131 return f.realize(layout->resfont);
134 // The uncommon case need not be optimized as much
137 layoutfont = layout->labelfont;
139 layoutfont = layout->font;
141 LyXFont font = pit->getFontSettings(params, pos);
142 font.realize(layoutfont);
147 // Realize with the fonts of lesser depth.
148 //font.realize(outerFont(pit, paragraphs()));
149 font.realize(defaultfont_);
155 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
157 LyXLayout_ptr const & layout = pit->layout();
159 if (!pit->getDepth())
160 return layout->resfont;
162 LyXFont font = layout->font;
163 // Realize with the fonts of lesser depth.
164 //font.realize(outerFont(pit, paragraphs()));
165 font.realize(defaultfont_);
171 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
173 LyXLayout_ptr const & layout = pit->layout();
175 if (!pit->getDepth())
176 return layout->reslabelfont;
178 LyXFont font = layout->labelfont;
179 // Realize with the fonts of lesser depth.
180 font.realize(outerFont(pit, paragraphs()));
181 font.realize(defaultfont_);
187 void LyXText::setCharFont(
188 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
191 LyXLayout_ptr const & layout = pit->layout();
193 // Get concrete layout font to reduce against
196 if (pos < pit->beginOfBody())
197 layoutfont = layout->labelfont;
199 layoutfont = layout->font;
201 // Realize against environment font information
202 if (pit->getDepth()) {
203 ParagraphList::iterator tp = pit;
204 while (!layoutfont.resolved() &&
205 tp != paragraphs().end() &&
207 tp = outerHook(tp, paragraphs());
208 if (tp != paragraphs().end())
209 layoutfont.realize(tp->layout()->font);
213 layoutfont.realize(defaultfont_);
215 // Now, reduce font against full layout font
216 font.reduce(layoutfont);
218 pit->setFont(pos, font);
223 // Asger is not sure we want to do this...
224 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
227 LyXLayout_ptr const & layout = par.layout();
228 pos_type const psize = par.size();
231 for (pos_type pos = 0; pos < psize; ++pos) {
232 if (pos < par.beginOfBody())
233 layoutfont = layout->labelfont;
235 layoutfont = layout->font;
237 LyXFont tmpfont = par.getFontSettings(params, pos);
238 tmpfont.reduce(layoutfont);
239 par.setFont(pos, tmpfont);
244 // return past-the-last paragraph influenced by a layout change on pit
245 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
247 ParagraphList::iterator end = paragraphs().end();
248 ParagraphList::iterator nextpit = boost::next(pit);
251 //because of parindents
252 if (!pit->getDepth())
253 return boost::next(nextpit);
254 //because of depth constrains
255 for (; nextpit != end; ++pit, ++nextpit) {
256 if (!pit->getDepth())
263 ParagraphList::iterator
264 LyXText::setLayout(ParagraphList::iterator start,
265 ParagraphList::iterator end,
266 string const & layout)
268 BOOST_ASSERT(start != end);
269 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
270 recUndo(parOffset(start), parOffset(undopit) - 1);
272 BufferParams const & bufparams = bv()->buffer()->params();
273 LyXLayout_ptr const & lyxlayout =
274 bufparams.getLyXTextClass()[layout];
276 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
277 pit->applyLayout(lyxlayout);
278 makeFontEntriesLayoutSpecific(bufparams, *pit);
279 if (lyxlayout->margintype == MARGIN_MANUAL)
280 pit->setLabelWidthString(lyxlayout->labelstring());
287 // set layout over selection and make a total rebreak of those paragraphs
288 void LyXText::setLayout(LCursor & cur, string const & layout)
290 BOOST_ASSERT(this == cur.text());
291 // special handling of new environment insets
292 BufferParams const & params = bv()->buffer()->params();
293 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
294 if (lyxlayout->is_environment) {
295 // move everything in a new environment inset
296 lyxerr << "setting layout " << layout << endl;
297 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
298 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
299 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
300 InsetBase * inset = new InsetEnvironment(params, layout);
301 insertInset(cur, inset);
302 //inset->edit(cur, true);
303 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
307 ParagraphList::iterator start = getPar(cur.selBegin().par());
308 ParagraphList::iterator end = boost::next(getPar(cur.selEnd().par()));
309 ParagraphList::iterator endpit = setLayout(start, end, layout);
310 redoParagraphs(start, endpit);
318 void getSelectionSpan(LCursor & cur, LyXText & text,
319 ParagraphList::iterator & beg,
320 ParagraphList::iterator & end)
322 if (!cur.selection()) {
323 beg = text.getPar(cur.par());
324 end = boost::next(beg);
326 beg = text.getPar(cur.selBegin());
327 end = boost::next(text.getPar(cur.selEnd()));
332 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
333 Paragraph const & par,
336 if (par.layout()->labeltype == LABEL_BIBLIO)
338 int const depth = par.params().depth();
339 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
341 if (type == bv_funcs::DEC_DEPTH && depth > 0)
350 bool LyXText::changeDepthAllowed(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
352 BOOST_ASSERT(this == cur.text());
353 ParagraphList::iterator beg, end;
354 getSelectionSpan(cur, *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, bv_funcs::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 == bv_funcs::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 = cursor().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 cur.par() = parOffset(pit);
1304 cur.boundary() = bound;
1305 deleteEmptyParagraphMechanism(cur.top(), old_cursor);
1309 // x,y are absolute screen coordinates
1310 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1312 ParagraphList::iterator pit;
1313 Row const & row = *getRowNearY(y - yo_, pit);
1316 int xx = x; // is modified by getColumnNearX
1317 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1318 cur.par() = parOffset(pit);
1320 cur.boundary() = bound;
1322 // try to descend into nested insets
1323 InsetBase * inset = checkInsetHit(x, y);
1327 // This should be just before or just behind the
1328 // cursor position set above.
1329 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1330 || inset == pit->getInset(pos));
1331 // Make sure the cursor points to the position before
1333 if (inset == pit->getInset(pos - 1))
1335 return inset->editXY(cur, x, y);
1339 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1341 if (cur.selection())
1343 if (cur.pos() == cur.lastpos())
1345 InsetBase * inset = cur.nextInset();
1346 if (!isHighlyEditableInset(inset))
1348 inset->edit(cur, front);
1353 void LyXText::cursorLeft(LCursor & cur)
1355 if (cur.pos() != 0) {
1356 bool boundary = cur.boundary();
1357 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1358 if (!checkAndActivateInset(cur, false)) {
1359 if (false && !boundary &&
1360 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1361 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1366 if (cur.par() != 0) {
1367 // steps into the paragraph above
1368 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1)->size());
1373 void LyXText::cursorRight(LCursor & cur)
1375 if (false && cur.boundary()) {
1376 setCursor(cur, cur.par(), cur.pos(), true, false);
1380 if (cur.pos() != cur.lastpos()) {
1381 if (!checkAndActivateInset(cur, true)) {
1382 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1383 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1385 setCursor(cur, cur.par(), cur.pos(), true, true);
1390 if (cur.par() != cur.lastpar())
1391 setCursor(cur, cur.par() + 1, 0);
1395 void LyXText::cursorUp(LCursor & cur)
1397 Row const & row = cur.textRow();
1398 int x = cur.x_target();
1399 int y = cursorY(cur.top()) - row.baseline() - 1;
1400 setCursorFromCoordinates(cur, x, y);
1402 if (!cur.selection()) {
1403 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1404 if (inset_hit && isHighlyEditableInset(inset_hit))
1405 inset_hit->editXY(cur, cur.x_target(), y);
1410 void LyXText::cursorDown(LCursor & cur)
1412 Row const & row = cur.textRow();
1413 int x = cur.x_target();
1414 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1415 setCursorFromCoordinates(cur, x, y);
1417 if (!cur.selection()) {
1418 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1419 if (inset_hit && isHighlyEditableInset(inset_hit))
1420 inset_hit->editXY(cur, cur.x_target(), y);
1425 void LyXText::cursorUpParagraph(LCursor & cur)
1428 setCursor(cur, cur.par(), 0);
1429 else if (cur.par() != 0)
1430 setCursor(cur, cur.par() - 1, 0);
1434 void LyXText::cursorDownParagraph(LCursor & cur)
1436 if (cur.par() != cur.lastpar())
1437 setCursor(cur, cur.par() + 1, 0);
1439 setCursor(cur, cur.par(), cur.lastpos());
1443 // fix the cursor `cur' after a characters has been deleted at `where'
1444 // position. Called by deleteEmptyParagraphMechanism
1445 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1447 // do notheing if cursor is not in the paragraph where the
1448 // deletion occured,
1449 if (cur.par() != where.par())
1452 // if cursor position is after the deletion place update it
1453 if (cur.pos() > where.pos())
1456 // check also if we don't want to set the cursor on a spot behind the
1457 // pagragraph because we erased the last character.
1458 if (cur.pos() > cur.lastpos())
1459 cur.pos() = cur.lastpos();
1463 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice & cur,
1464 CursorSlice const & old_cursor)
1466 #warning Disabled as it crashes after the cursor data shift... (Andre)
1469 // Would be wrong to delete anything if we have a selection.
1470 //if (cur.selection())
1474 // We allow all kinds of "mumbo-jumbo" when freespacing.
1475 ParagraphList::iterator const old_pit = getPar(old_cursor.par());
1476 if (old_pit->isFreeSpacing())
1479 /* Ok I'll put some comments here about what is missing.
1480 I have fixed BackSpace (and thus Delete) to not delete
1481 double-spaces automagically. I have also changed Cut,
1482 Copy and Paste to hopefully do some sensible things.
1483 There are still some small problems that can lead to
1484 double spaces stored in the document file or space at
1485 the beginning of paragraphs(). This happens if you have
1486 the cursor between to spaces and then save. Or if you
1487 cut and paste and the selection have a space at the
1488 beginning and then save right after the paste. I am
1489 sure none of these are very hard to fix, but I will
1490 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1491 that I can get some feedback. (Lgb)
1494 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1495 // delete the LineSeparator.
1498 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1499 // delete the LineSeparator.
1502 // If the pos around the old_cursor were spaces, delete one of them.
1503 if (old_cursor.par() != cur.par() || old_cursor.pos() != cur.pos()) {
1505 // Only if the cursor has really moved
1506 if (old_cursor.pos() > 0
1507 && old_cursor.pos() < old_pit->size()
1508 && old_pit->isLineSeparator(old_cursor.pos())
1509 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1510 bool erased = old_pit->erase(old_cursor.pos() - 1);
1511 redoParagraph(old_pit);
1515 #ifdef WITH_WARNINGS
1516 #warning This will not work anymore when we have multiple views of the same buffer
1517 // In this case, we will have to correct also the cursors held by
1518 // other bufferviews. It will probably be easier to do that in a more
1519 // automated way in CursorSlice code. (JMarc 26/09/2001)
1521 // correct all cursors held by the LyXText
1522 fixCursorAfterDelete(cursor(), old_cursor);
1523 fixCursorAfterDelete(anchor(), old_cursor);
1528 // don't delete anything if this is the ONLY paragraph!
1529 if (paragraphs().size() == 1)
1532 // Do not delete empty paragraphs with keepempty set.
1533 if (old_pit->allowEmpty())
1536 // only do our magic if we changed paragraph
1537 if (old_cursor.par() == cur.par())
1540 // record if we have deleted a paragraph
1541 // we can't possibly have deleted a paragraph before this point
1542 bool deleted = false;
1544 if (old_pit->empty()
1545 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1546 // ok, we will delete something
1547 CursorSlice tmpcursor;
1551 bool selection_position_was_oldcursor_position =
1552 anchor().par() == old_cursor.par()
1553 && anchor().pos() == old_cursor.pos();
1555 tmpcursor = cursor();
1556 cursor() = old_cursor; // that undo can restore the right cursor position
1558 ParagraphList::iterator endpit = boost::next(old_pit);
1559 while (endpit != paragraphs().end() && endpit->getDepth())
1562 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1563 cursor() = tmpcursor;
1566 paragraphs().erase(old_pit);
1567 // update cursor par offset
1571 if (selection_position_was_oldcursor_position) {
1572 // correct selection
1573 bv()->resetAnchor();
1580 if (old_pit->stripLeadingSpaces()) {
1581 redoParagraph(old_pit);
1582 bv()->resetAnchor();
1589 ParagraphList & LyXText::paragraphs() const
1591 return const_cast<ParagraphList &>(paragraphs_);
1595 void LyXText::recUndo(par_type first, par_type last) const
1597 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1601 void LyXText::recUndo(par_type par) const
1603 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1607 bool LyXText::toggleInset(LCursor & cur)
1609 InsetBase * inset = cur.nextInset();
1610 // is there an editable inset at cursor position?
1611 if (!isEditableInset(inset))
1613 cur.message(inset->editMessage());
1615 // do we want to keep this?? (JMarc)
1616 if (!isHighlyEditableInset(inset))
1619 if (inset->isOpen())
1627 int defaultRowHeight()
1629 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);