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(bv_funcs::DEPTH_CHANGE type,
334 Paragraph const & par,
337 if (par.layout()->labeltype == LABEL_BIBLIO)
339 int const depth = par.params().depth();
340 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
342 if (type == bv_funcs::DEC_DEPTH && depth > 0)
351 bool LyXText::changeDepthAllowed(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
353 BOOST_ASSERT(this == cur.text());
354 ParagraphList::iterator beg, end;
355 getSelectionSpan(cur, *this, beg, end);
357 if (beg != paragraphs().begin())
358 max_depth = boost::prior(beg)->getMaxDepthAfter();
360 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
361 if (::changeDepthAllowed(type, *pit, max_depth))
363 max_depth = pit->getMaxDepthAfter();
369 void LyXText::changeDepth(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
371 BOOST_ASSERT(this == cur.text());
372 ParagraphList::iterator beg, end;
373 getSelectionSpan(cur, *this, beg, end);
374 recordUndoSelection(cur);
377 if (beg != paragraphs().begin())
378 max_depth = boost::prior(beg)->getMaxDepthAfter();
380 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
381 if (::changeDepthAllowed(type, *pit, max_depth)) {
382 int const depth = pit->params().depth();
383 if (type == bv_funcs::INC_DEPTH)
384 pit->params().depth(depth + 1);
386 pit->params().depth(depth - 1);
388 max_depth = pit->getMaxDepthAfter();
390 // this handles the counter labels, and also fixes up
391 // depth values for follow-on (child) paragraphs
396 // set font over selection and make a total rebreak of those paragraphs
397 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
399 BOOST_ASSERT(this == cur.text());
400 // if there is no selection just set the current_font
401 if (!cur.selection()) {
402 // Determine basis font
404 ParagraphList::iterator pit = getPar(cur.par());
405 if (cur.pos() < pit->beginOfBody())
406 layoutfont = getLabelFont(pit);
408 layoutfont = getLayoutFont(pit);
410 // Update current font
411 real_current_font.update(font,
412 bv()->buffer()->params().language,
415 // Reduce to implicit settings
416 current_font = real_current_font;
417 current_font.reduce(layoutfont);
418 // And resolve it completely
419 real_current_font.realize(layoutfont);
424 // Ok, we have a selection.
425 recordUndoSelection(cur);
427 ParagraphList::iterator beg = getPar(cur.selBegin().par());
428 ParagraphList::iterator end = getPar(cur.selEnd().par());
430 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
431 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
433 BufferParams const & params = bv()->buffer()->params();
435 for (; pos != posend; ++pos) {
436 LyXFont f = getFont(pos.pit(), pos.pos());
437 f.update(font, params.language, toggleall);
438 setCharFont(pos.pit(), pos.pos(), f);
441 redoParagraphs(beg, ++end);
445 // the cursor set functions have a special mechanism. When they
446 // realize you left an empty paragraph, they will delete it.
448 void LyXText::cursorHome(LCursor & cur)
450 BOOST_ASSERT(this == cur.text());
451 setCursor(cur, cur.par(), cur.textRow().pos());
455 void LyXText::cursorEnd(LCursor & cur)
457 BOOST_ASSERT(this == cur.text());
458 // if not on the last row of the par, put the cursor before
460 pos_type const end = cur.textRow().endpos();
461 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
465 void LyXText::cursorTop(LCursor & cur)
467 BOOST_ASSERT(this == cur.text());
468 setCursor(cur, 0, 0);
472 void LyXText::cursorBottom(LCursor & cur)
474 BOOST_ASSERT(this == cur.text());
475 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
479 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
481 BOOST_ASSERT(this == cur.text());
482 // If the mask is completely neutral, tell user
483 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
484 // Could only happen with user style
485 cur.message(_("No font change defined. "
486 "Use Character under the Layout menu to define font change."));
490 // Try implicit word selection
491 // If there is a change in the language the implicit word selection
493 CursorSlice resetCursor = cur.top();
494 bool implicitSelection =
495 font.language() == ignore_language
496 && font.number() == LyXFont::IGNORE
497 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
500 setFont(cur, font, toggleall);
502 // Implicit selections are cleared afterwards
503 // and cursor is set to the original position.
504 if (implicitSelection) {
505 cur.clearSelection();
506 cur.top() = resetCursor;
512 string LyXText::getStringToIndex(LCursor & cur)
514 BOOST_ASSERT(this == cur.text());
515 // Try implicit word selection
516 // If there is a change in the language the implicit word selection
518 CursorSlice const reset_cursor = cur.top();
519 bool const implicitSelection =
520 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
523 if (!cur.selection())
524 cur.message(_("Nothing to index!"));
525 else if (cur.selBegin().par() != cur.selEnd().par())
526 cur.message(_("Cannot index more than one paragraph!"));
528 idxstring = cur.selectionAsString(false);
530 // Reset cursors to their original position.
531 cur.top() = reset_cursor;
534 // Clear the implicit selection.
535 if (implicitSelection)
536 cur.clearSelection();
542 // the DTP switches for paragraphs(). LyX will store them in the first
543 // physical paragraph. When a paragraph is broken, the top settings rest,
544 // the bottom settings are given to the new one. So I can make sure,
545 // they do not duplicate themself and you cannot play dirty tricks with
548 void LyXText::setParagraph(LCursor & cur,
549 Spacing const & spacing, LyXAlignment align,
550 string const & labelwidthstring, bool noindent)
552 BOOST_ASSERT(cur.text());
553 // make sure that the depth behind the selection are restored, too
554 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
555 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
557 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
558 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
560 for (--pit; pit != beg; ++pit) {
561 ParagraphParameters & params = pit->params();
562 params.spacing(spacing);
564 // does the layout allow the new alignment?
565 LyXLayout_ptr const & layout = pit->layout();
567 if (align == LYX_ALIGN_LAYOUT)
568 align = layout->align;
569 if (align & layout->alignpossible) {
570 if (align == layout->align)
571 params.align(LYX_ALIGN_LAYOUT);
575 pit->setLabelWidthString(labelwidthstring);
576 params.noindent(noindent);
579 redoParagraphs(getPar(cur.selBegin()), undopit);
583 string expandLabel(LyXTextClass const & textclass,
584 LyXLayout_ptr const & layout, bool appendix)
586 string fmt = appendix ?
587 layout->labelstring_appendix() : layout->labelstring();
589 // handle 'inherited level parts' in 'fmt',
590 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
591 size_t const i = fmt.find('@', 0);
592 if (i != string::npos) {
593 size_t const j = fmt.find('@', i + 1);
594 if (j != string::npos) {
595 string parent(fmt, i + 1, j - i - 1);
596 string label = expandLabel(textclass, textclass[parent], appendix);
597 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
601 return textclass.counters().counterLabel(fmt);
607 void incrementItemDepth(ParagraphList::iterator pit,
608 ParagraphList::iterator first_pit)
610 int const cur_labeltype = pit->layout()->labeltype;
612 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
615 int const cur_depth = pit->getDepth();
617 ParagraphList::iterator prev_pit = boost::prior(pit);
619 int const prev_depth = prev_pit->getDepth();
620 int const prev_labeltype = prev_pit->layout()->labeltype;
621 if (prev_depth == 0 && cur_depth > 0) {
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 + 1;
631 } else if (prev_depth == cur_depth) {
632 if (prev_labeltype == cur_labeltype) {
633 pit->itemdepth = prev_pit->itemdepth;
637 if (prev_pit == first_pit)
645 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
646 ParagraphList::iterator firstpit,
652 int const cur_depth = pit->getDepth();
653 ParagraphList::iterator prev_pit = boost::prior(pit);
655 int const prev_depth = prev_pit->getDepth();
656 int const prev_labeltype = prev_pit->layout()->labeltype;
657 if (prev_depth <= cur_depth) {
658 if (prev_labeltype != LABEL_ENUMERATE) {
659 switch (pit->itemdepth) {
661 counters.reset("enumi");
663 counters.reset("enumii");
665 counters.reset("enumiii");
667 counters.reset("enumiv");
673 if (prev_pit == firstpit)
683 // set the counter of a paragraph. This includes the labels
684 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
686 BufferParams const & bufparams = buf.params();
687 LyXTextClass const & textclass = bufparams.getLyXTextClass();
688 LyXLayout_ptr const & layout = pit->layout();
689 ParagraphList::iterator first_pit = paragraphs().begin();
690 Counters & counters = textclass.counters();
695 if (pit == first_pit) {
696 pit->params().appendix(pit->params().startOfAppendix());
698 pit->params().appendix(boost::prior(pit)->params().appendix());
699 if (!pit->params().appendix() &&
700 pit->params().startOfAppendix()) {
701 pit->params().appendix(true);
702 textclass.counters().reset();
705 // Maybe we have to increment the item depth.
706 incrementItemDepth(pit, first_pit);
709 // erase what was there before
710 pit->params().labelString(string());
712 if (layout->margintype == MARGIN_MANUAL) {
713 if (pit->params().labelWidthString().empty())
714 pit->setLabelWidthString(layout->labelstring());
716 pit->setLabelWidthString(string());
719 // is it a layout that has an automatic label?
720 if (layout->labeltype == LABEL_COUNTER) {
721 BufferParams const & bufparams = buf.params();
722 LyXTextClass const & textclass = bufparams.getLyXTextClass();
723 counters.step(layout->counter);
724 string label = expandLabel(textclass, layout, pit->params().appendix());
725 pit->params().labelString(label);
726 } else if (layout->labeltype == LABEL_ITEMIZE) {
727 // At some point of time we should do something more
728 // clever here, like:
729 // pit->params().labelString(
730 // bufparams.user_defined_bullet(pit->itemdepth).getText());
731 // for now, use a simple hardcoded label
733 switch (pit->itemdepth) {
748 pit->params().labelString(itemlabel);
749 } else if (layout->labeltype == LABEL_ENUMERATE) {
750 // Maybe we have to reset the enumeration counter.
751 resetEnumCounterIfNeeded(pit, first_pit, counters);
754 // Yes I know this is a really, really! bad solution
756 string enumcounter = "enum";
758 switch (pit->itemdepth) {
770 // not a valid enumdepth...
774 counters.step(enumcounter);
776 pit->params().labelString(counters.enumLabel(enumcounter));
777 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
778 counters.step("bibitem");
779 int number = counters.value("bibitem");
780 if (pit->bibitem()) {
781 pit->bibitem()->setCounter(number);
782 pit->params().labelString(layout->labelstring());
784 // In biblio should't be following counters but...
786 string s = buf.B_(layout->labelstring());
789 if (layout->labeltype == LABEL_SENSITIVE) {
790 ParagraphList::iterator end = paragraphs().end();
791 ParagraphList::iterator tmppit = pit;
794 while (tmppit != end && tmppit->inInset()
795 // the single '=' is intended below
796 && (in = tmppit->inInset()->owner()))
798 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
799 in->lyxCode() == InsetBase::WRAP_CODE) {
803 Paragraph const * owner = &ownerPar(buf, in);
805 for ( ; tmppit != end; ++tmppit)
806 if (&*tmppit == owner)
814 if (in->lyxCode() == InsetBase::FLOAT_CODE)
815 type = static_cast<InsetFloat*>(in)->params().type;
816 else if (in->lyxCode() == InsetBase::WRAP_CODE)
817 type = static_cast<InsetWrap*>(in)->params().type;
821 Floating const & fl = textclass.floats().getType(type);
823 counters.step(fl.type());
825 // Doesn't work... yet.
826 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
828 // par->SetLayout(0);
829 // s = layout->labelstring;
830 s = _("Senseless: ");
833 pit->params().labelString(s);
839 // Updates all counters.
840 void LyXText::updateCounters()
843 bv()->buffer()->params().getLyXTextClass().counters().reset();
845 bool update_pos = false;
847 ParagraphList::iterator beg = paragraphs().begin();
848 ParagraphList::iterator end = paragraphs().end();
849 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
850 string const oldLabel = pit->params().labelString();
853 maxdepth = boost::prior(pit)->getMaxDepthAfter();
855 if (pit->params().depth() > maxdepth)
856 pit->params().depth(maxdepth);
858 // setCounter can potentially change the labelString.
859 setCounter(*bv()->buffer(), pit);
860 string const & newLabel = pit->params().labelString();
861 if (oldLabel != newLabel) {
862 redoParagraphInternal(pit);
868 updateParPositions();
872 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
874 BOOST_ASSERT(this == cur.text());
876 cur.paragraph().insertInset(cur.pos(), inset);
881 void LyXText::cutSelection(LCursor & cur, bool doclear, bool realcut)
883 BOOST_ASSERT(this == cur.text());
884 // Stuff what we got on the clipboard. Even if there is no selection.
886 // There is a problem with having the stuffing here in that the
887 // larger the selection the slower LyX will get. This can be
888 // solved by running the line below only when the selection has
889 // finished. The solution used currently just works, to make it
890 // faster we need to be more clever and probably also have more
891 // calls to stuffClipboard. (Lgb)
892 bv()->stuffClipboard(cur.selectionAsString(true));
894 // This doesn't make sense, if there is no selection
895 if (!cur.selection())
898 // OK, we have a selection. This is always between cur.selBegin()
901 // make sure that the depth behind the selection are restored, too
902 recordUndoSelection(cur);
903 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
904 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
905 ParagraphList::iterator undopit = undoSpan(endpit);
907 int endpos = cur.selEnd().pos();
909 BufferParams const & bufparams = bv()->buffer()->params();
910 boost::tie(endpit, endpos) = realcut ?
911 CutAndPaste::cutSelection(bufparams,
914 cur.selBegin().pos(), endpos,
917 : CutAndPaste::eraseSelection(bufparams,
920 cur.selBegin().pos(), endpos,
922 // sometimes necessary
924 begpit->stripLeadingSpaces();
926 redoParagraphs(begpit, undopit);
927 // cutSelection can invalidate the cursor so we need to set
929 // we prefer the end for when tracking changes
931 cur.par() = parOffset(endpit);
933 // need a valid cursor. (Lgb)
934 cur.clearSelection();
939 void LyXText::copySelection(LCursor & cur)
941 BOOST_ASSERT(this == cur.text());
942 // stuff the selection onto the X clipboard, from an explicit copy request
943 bv()->stuffClipboard(cur.selectionAsString(true));
945 // this doesnt make sense, if there is no selection
946 if (!cur.selection())
949 // ok we have a selection. This is always between cur.selBegin()
950 // and sel_end cursor
952 // copy behind a space if there is one
953 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
954 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
955 && (cur.selBegin().par() != cur.selEnd().par()
956 || cur.selBegin().pos() < cur.selEnd().pos()))
957 ++cur.selBegin().pos();
959 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
960 getPar(cur.selEnd().par()),
961 cur.selBegin().pos(),
963 bv()->buffer()->params().textclass);
967 void LyXText::pasteSelection(LCursor & cur, size_t sel_index)
969 // this does not make sense, if there is nothing to paste
970 if (!CutAndPaste::checkPastePossible())
975 ParagraphList::iterator endpit;
980 boost::tie(ppp, endpit) =
981 CutAndPaste::pasteSelection(*bv()->buffer(),
983 getPar(cur.par()), cur.pos(),
984 bv()->buffer()->params().textclass,
986 bufferErrors(*bv()->buffer(), el);
987 bv()->showErrorList(_("Paste"));
989 redoParagraphs(getPar(cur.par()), endpit);
991 cur.clearSelection();
993 setCursor(cur, parOffset(ppp.first), ppp.second);
999 void LyXText::setSelectionRange(LCursor & cur, lyx::pos_type length)
1010 // simple replacing. The font of the first selected character is used
1011 void LyXText::replaceSelectionWithString(LCursor & cur, string const & str)
1015 // Get font setting before we cut
1016 pos_type pos = cur.selEnd().pos();
1017 LyXFont const font = getPar(cur.selBegin())
1018 ->getFontSettings(bv()->buffer()->params(),
1019 cur.selBegin().pos());
1021 // Insert the new string
1022 string::const_iterator cit = str.begin();
1023 string::const_iterator end = str.end();
1024 for (; cit != end; ++cit) {
1025 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1029 // Cut the selection
1030 cutSelection(cur, true, false);
1034 // needed to insert the selection
1035 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
1037 ParagraphList::iterator pit = getPar(cur.par());
1038 ParagraphList::iterator endpit = boost::next(pit);
1039 pos_type pos = cur.pos();
1042 // only to be sure, should not be neccessary
1043 cur.clearSelection();
1044 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1046 redoParagraphs(getPar(cur.par()), endpit);
1048 setCursor(cur, cur.par(), pos);
1053 // turn double CR to single CR, others are converted into one
1054 // blank. Then insertStringAsLines is called
1055 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
1057 string linestr = str;
1058 bool newline_inserted = false;
1060 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
1061 if (linestr[i] == '\n') {
1062 if (newline_inserted) {
1063 // we know that \r will be ignored by
1064 // insertStringAsLines. Of course, it is a dirty
1065 // trick, but it works...
1066 linestr[i - 1] = '\r';
1070 newline_inserted = true;
1072 } else if (IsPrintable(linestr[i])) {
1073 newline_inserted = false;
1076 insertStringAsLines(cur, linestr);
1080 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
1081 bool setfont, bool boundary)
1083 CursorSlice old_cursor = cur.top();
1084 setCursorIntern(cur, par, pos, setfont, boundary);
1085 return deleteEmptyParagraphMechanism(cur.top(), old_cursor);
1089 void LyXText::setCursor(CursorSlice & cur, par_type par,
1090 pos_type pos, bool boundary)
1092 BOOST_ASSERT(par != int(paragraphs().size()));
1096 cur.boundary() = boundary;
1098 // no rows, no fun...
1099 if (paragraphs().begin()->rows.empty())
1102 // now some strict checking
1103 Paragraph & para = *getPar(par);
1104 Row const & row = *para.getRow(pos);
1105 pos_type const end = row.endpos();
1107 // None of these should happen, but we're scaredy-cats
1109 lyxerr << "dont like -1" << endl;
1110 BOOST_ASSERT(false);
1113 if (pos > para.size()) {
1114 lyxerr << "dont like 1, pos: " << pos
1115 << " size: " << para.size()
1116 << " row.pos():" << row.pos()
1117 << " par: " << par << endl;
1118 BOOST_ASSERT(false);
1122 lyxerr << "dont like 2, pos: " << pos
1123 << " size: " << para.size()
1124 << " row.pos():" << row.pos()
1125 << " par: " << par << endl;
1126 // This shouldn't happen.
1127 BOOST_ASSERT(false);
1130 if (pos < row.pos()) {
1131 lyxerr << "dont like 3 please report pos:" << pos
1132 << " size: " << para.size()
1133 << " row.pos():" << row.pos()
1134 << " par: " << par << endl;
1135 BOOST_ASSERT(false);
1140 void LyXText::setCursorIntern(LCursor & cur,
1141 par_type par, pos_type pos, bool setfont, bool boundary)
1143 setCursor(cur.top(), par, pos, boundary);
1144 cur.x_target() = cursorX(cur.top());
1146 setCurrentFont(cur);
1150 void LyXText::setCurrentFont(LCursor & cur)
1152 BOOST_ASSERT(this == cur.text());
1153 pos_type pos = cur.pos();
1154 ParagraphList::iterator pit = getPar(cur.par());
1156 if (cur.boundary() && pos > 0)
1160 if (pos == cur.lastpos())
1162 else // potentional bug... BUG (Lgb)
1163 if (pit->isSeparator(pos)) {
1164 if (pos > cur.textRow().pos() &&
1165 bidi.level(pos) % 2 ==
1166 bidi.level(pos - 1) % 2)
1168 else if (pos + 1 < cur.lastpos())
1173 BufferParams const & bufparams = bv()->buffer()->params();
1174 current_font = pit->getFontSettings(bufparams, pos);
1175 real_current_font = getFont(pit, pos);
1177 if (cur.pos() == cur.lastpos()
1178 && bidi.isBoundary(*bv()->buffer(), *pit, cur.pos())
1179 && !cur.boundary()) {
1180 Language const * lang = pit->getParLanguage(bufparams);
1181 current_font.setLanguage(lang);
1182 current_font.setNumber(LyXFont::OFF);
1183 real_current_font.setLanguage(lang);
1184 real_current_font.setNumber(LyXFont::OFF);
1189 // x is an absolute screen coord
1190 // returns the column near the specified x-coordinate of the row
1191 // x is set to the real beginning of this column
1192 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1193 Row const & row, int & x, bool & boundary) const
1196 RowMetrics const r = computeRowMetrics(pit, row);
1198 pos_type vc = row.pos();
1199 pos_type end = row.endpos();
1201 LyXLayout_ptr const & layout = pit->layout();
1203 bool left_side = false;
1205 pos_type body_pos = pit->beginOfBody();
1208 double last_tmpx = tmpx;
1211 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1214 // check for empty row
1216 x = int(tmpx) + xo_;
1220 while (vc < end && tmpx <= x) {
1221 c = bidi.vis2log(vc);
1223 if (body_pos > 0 && c == body_pos - 1) {
1224 tmpx += r.label_hfill +
1225 font_metrics::width(layout->labelsep, getLabelFont(pit));
1226 if (pit->isLineSeparator(body_pos - 1))
1227 tmpx -= singleWidth(pit, body_pos - 1);
1230 if (hfillExpansion(*pit, row, c)) {
1231 tmpx += singleWidth(pit, c);
1235 tmpx += r.label_hfill;
1236 } else if (pit->isSeparator(c)) {
1237 tmpx += singleWidth(pit, c);
1239 tmpx += r.separator;
1241 tmpx += singleWidth(pit, c);
1246 if ((tmpx + last_tmpx) / 2 > x) {
1251 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1254 // This (rtl_support test) is not needed, but gives
1255 // some speedup if rtl_support == false
1256 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1258 // If lastrow is false, we don't need to compute
1259 // the value of rtl.
1260 bool const rtl = lastrow ? isRTL(*pit) : false;
1262 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1263 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1265 else if (vc == row.pos()) {
1266 c = bidi.vis2log(vc);
1267 if (bidi.level(c) % 2 == 1)
1270 c = bidi.vis2log(vc - 1);
1271 bool const rtl = (bidi.level(c) % 2 == 1);
1272 if (left_side == rtl) {
1274 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1278 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1279 if (bidi.level(end -1) % 2 == 0)
1280 tmpx -= singleWidth(pit, end - 1);
1282 tmpx += singleWidth(pit, end - 1);
1286 x = int(tmpx) + xo_;
1287 return c - row.pos();
1291 // x,y are absolute coordinates
1292 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1296 CursorSlice old_cursor = cur.top();
1297 ParagraphList::iterator pit;
1298 Row const & row = *getRowNearY(y, pit);
1299 lyxerr << "hit row at: " << row.pos() << endl;
1301 int xx = x + xo_; // getRowNearX get absolute x coords
1302 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1303 setCursor(cur, parOffset(pit), pos, true, bound);
1307 // x,y are absolute screen coordinates
1308 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1310 ParagraphList::iterator pit;
1311 Row const & row = *getRowNearY(y - yo_, pit);
1314 int xx = x; // is modified by getColumnNearX
1315 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1316 cur.par() = parOffset(pit);
1318 cur.boundary() = bound;
1320 // try to descend into nested insets
1321 InsetBase * inset = checkInsetHit(x, y);
1325 // This should be just before or just behind the
1326 // cursor position set above.
1327 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1328 || inset == pit->getInset(pos));
1329 // Make sure the cursor points to the position before
1331 if (inset == pit->getInset(pos - 1))
1333 return inset->editXY(cur, x, y);
1337 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1339 if (cur.selection())
1341 if (cur.pos() == cur.lastpos())
1343 InsetBase * inset = cur.nextInset();
1344 if (!isHighlyEditableInset(inset))
1346 inset->edit(cur, front);
1351 void LyXText::cursorLeft(LCursor & cur)
1353 if (cur.pos() != 0) {
1354 bool boundary = cur.boundary();
1355 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1356 if (!checkAndActivateInset(cur, false)) {
1357 if (false && !boundary &&
1358 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1359 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1364 if (cur.par() != 0) {
1365 // steps into the paragraph above
1366 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1)->size());
1371 void LyXText::cursorRight(LCursor & cur)
1373 if (false && cur.boundary()) {
1374 setCursor(cur, cur.par(), cur.pos(), true, false);
1378 if (cur.pos() != cur.lastpos()) {
1379 if (!checkAndActivateInset(cur, true)) {
1380 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1381 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1383 setCursor(cur, cur.par(), cur.pos(), true, true);
1388 if (cur.par() != cur.lastpar())
1389 setCursor(cur, cur.par() + 1, 0);
1393 void LyXText::cursorUp(LCursor & cur)
1395 Row const & row = cur.textRow();
1396 int x = cur.x_target();
1397 int y = cursorY(cur.top()) - row.baseline() - 1;
1398 setCursorFromCoordinates(cur, x, y);
1400 if (!cur.selection()) {
1401 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1402 if (inset_hit && isHighlyEditableInset(inset_hit))
1403 inset_hit->editXY(cur, cur.x_target(), y);
1408 void LyXText::cursorDown(LCursor & cur)
1410 Row const & row = cur.textRow();
1411 int x = cur.x_target();
1412 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1413 setCursorFromCoordinates(cur, x, y);
1415 if (!cur.selection()) {
1416 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1417 if (inset_hit && isHighlyEditableInset(inset_hit))
1418 inset_hit->editXY(cur, cur.x_target(), y);
1423 void LyXText::cursorUpParagraph(LCursor & cur)
1426 setCursor(cur, cur.par(), 0);
1427 else if (cur.par() != 0)
1428 setCursor(cur, cur.par() - 1, 0);
1432 void LyXText::cursorDownParagraph(LCursor & cur)
1434 if (cur.par() != cur.lastpar())
1435 setCursor(cur, cur.par() + 1, 0);
1437 setCursor(cur, cur.par(), cur.lastpos());
1441 // fix the cursor `cur' after a characters has been deleted at `where'
1442 // position. Called by deleteEmptyParagraphMechanism
1443 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1445 // do notheing if cursor is not in the paragraph where the
1446 // deletion occured,
1447 if (cur.par() != where.par())
1450 // if cursor position is after the deletion place update it
1451 if (cur.pos() > where.pos())
1454 // check also if we don't want to set the cursor on a spot behind the
1455 // pagragraph because we erased the last character.
1456 if (cur.pos() > cur.lastpos())
1457 cur.pos() = cur.lastpos();
1461 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice & cur,
1462 CursorSlice const & old_cursor)
1464 #warning Disabled as it crashes after the cursor data shift... (Andre)
1467 // Would be wrong to delete anything if we have a selection.
1468 //if (cur.selection())
1472 // We allow all kinds of "mumbo-jumbo" when freespacing.
1473 ParagraphList::iterator const old_pit = getPar(old_cursor.par());
1474 if (old_pit->isFreeSpacing())
1477 /* Ok I'll put some comments here about what is missing.
1478 I have fixed BackSpace (and thus Delete) to not delete
1479 double-spaces automagically. I have also changed Cut,
1480 Copy and Paste to hopefully do some sensible things.
1481 There are still some small problems that can lead to
1482 double spaces stored in the document file or space at
1483 the beginning of paragraphs(). This happens if you have
1484 the cursor between to spaces and then save. Or if you
1485 cut and paste and the selection have a space at the
1486 beginning and then save right after the paste. I am
1487 sure none of these are very hard to fix, but I will
1488 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1489 that I can get some feedback. (Lgb)
1492 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1493 // delete the LineSeparator.
1496 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1497 // delete the LineSeparator.
1500 // If the pos around the old_cursor were spaces, delete one of them.
1501 if (old_cursor.par() != cur.par() || old_cursor.pos() != cur.pos()) {
1503 // Only if the cursor has really moved
1504 if (old_cursor.pos() > 0
1505 && old_cursor.pos() < old_pit->size()
1506 && old_pit->isLineSeparator(old_cursor.pos())
1507 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1508 bool erased = old_pit->erase(old_cursor.pos() - 1);
1509 redoParagraph(old_pit);
1513 #ifdef WITH_WARNINGS
1514 #warning This will not work anymore when we have multiple views of the same buffer
1515 // In this case, we will have to correct also the cursors held by
1516 // other bufferviews. It will probably be easier to do that in a more
1517 // automated way in CursorSlice code. (JMarc 26/09/2001)
1519 // correct all cursors held by the LyXText
1520 fixCursorAfterDelete(cursor(), old_cursor);
1521 fixCursorAfterDelete(anchor(), old_cursor);
1526 // don't delete anything if this is the ONLY paragraph!
1527 if (paragraphs().size() == 1)
1530 // Do not delete empty paragraphs with keepempty set.
1531 if (old_pit->allowEmpty())
1534 // only do our magic if we changed paragraph
1535 if (old_cursor.par() == cur.par())
1538 // record if we have deleted a paragraph
1539 // we can't possibly have deleted a paragraph before this point
1540 bool deleted = false;
1542 if (old_pit->empty()
1543 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1544 // ok, we will delete something
1545 CursorSlice tmpcursor;
1549 bool selection_position_was_oldcursor_position =
1550 anchor().par() == old_cursor.par()
1551 && anchor().pos() == old_cursor.pos();
1553 tmpcursor = cursor();
1554 cursor() = old_cursor; // that undo can restore the right cursor position
1556 ParagraphList::iterator endpit = boost::next(old_pit);
1557 while (endpit != paragraphs().end() && endpit->getDepth())
1560 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1561 cursor() = tmpcursor;
1564 paragraphs().erase(old_pit);
1565 // update cursor par offset
1569 if (selection_position_was_oldcursor_position) {
1570 // correct selection
1571 bv()->resetAnchor();
1578 if (old_pit->stripLeadingSpaces()) {
1579 redoParagraph(old_pit);
1580 bv()->resetAnchor();
1587 ParagraphList & LyXText::paragraphs() const
1589 return const_cast<ParagraphList &>(paragraphs_);
1593 void LyXText::recUndo(par_type first, par_type last) const
1595 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1599 void LyXText::recUndo(par_type par) const
1601 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1605 bool LyXText::toggleInset(LCursor & cur)
1607 InsetBase * inset = cur.nextInset();
1608 // is there an editable inset at cursor position?
1609 if (!isEditableInset(inset))
1611 cur.message(inset->editMessage());
1613 // do we want to keep this?? (JMarc)
1614 if (!isHighlyEditableInset(inset))
1617 if (inset->isOpen())
1625 int defaultRowHeight()
1627 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);