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::paroffset_type;
68 using lyx::support::bformat;
71 using std::ostringstream;
75 LyXText::LyXText(BufferView * bv, bool in_inset)
76 : height(0), width(0), textwidth_(bv ? bv->workWidth() : 100),
77 background_color_(LColor::background),
78 bv_owner(bv), in_inset_(in_inset), xo_(0), yo_(0)
82 void LyXText::init(BufferView * bview)
86 ParagraphList::iterator const beg = paragraphs().begin();
87 ParagraphList::iterator const end = paragraphs().end();
88 for (ParagraphList::iterator pit = beg; pit != end; ++pit)
94 current_font = getFont(beg, 0);
96 redoParagraphs(beg, end);
97 setCursorIntern(0, 0);
98 bv()->cursor().resetAnchor();
104 // Gets the fully instantiated font at a given position in a paragraph
105 // Basically the same routine as Paragraph::getFont() in paragraph.C.
106 // The difference is that this one is used for displaying, and thus we
107 // are allowed to make cosmetic improvements. For instance make footnotes
109 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
111 BOOST_ASSERT(pos >= 0);
113 LyXLayout_ptr const & layout = pit->layout();
115 BufferParams const & params = bv()->buffer()->params();
116 pos_type const body_pos = pit->beginOfBody();
118 // We specialize the 95% common case:
119 if (!pit->getDepth()) {
120 LyXFont f = pit->getFontSettings(params, pos);
123 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
124 return f.realize(layout->reslabelfont);
126 return f.realize(layout->resfont);
129 // The uncommon case need not be optimized as much
132 layoutfont = layout->labelfont;
134 layoutfont = layout->font;
136 LyXFont font = pit->getFontSettings(params, pos);
137 font.realize(layoutfont);
142 // Realize with the fonts of lesser depth.
143 //font.realize(outerFont(pit, paragraphs()));
144 font.realize(defaultfont_);
150 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
152 LyXLayout_ptr const & layout = pit->layout();
154 if (!pit->getDepth())
155 return layout->resfont;
157 LyXFont font = layout->font;
158 // Realize with the fonts of lesser depth.
159 //font.realize(outerFont(pit, paragraphs()));
160 font.realize(defaultfont_);
166 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
168 LyXLayout_ptr const & layout = pit->layout();
170 if (!pit->getDepth())
171 return layout->reslabelfont;
173 LyXFont font = layout->labelfont;
174 // Realize with the fonts of lesser depth.
175 font.realize(outerFont(pit, paragraphs()));
176 font.realize(defaultfont_);
182 void LyXText::setCharFont(
183 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
186 LyXLayout_ptr const & layout = pit->layout();
188 // Get concrete layout font to reduce against
191 if (pos < pit->beginOfBody())
192 layoutfont = layout->labelfont;
194 layoutfont = layout->font;
196 // Realize against environment font information
197 if (pit->getDepth()) {
198 ParagraphList::iterator tp = pit;
199 while (!layoutfont.resolved() &&
200 tp != paragraphs().end() &&
202 tp = outerHook(tp, paragraphs());
203 if (tp != paragraphs().end())
204 layoutfont.realize(tp->layout()->font);
208 layoutfont.realize(defaultfont_);
210 // Now, reduce font against full layout font
211 font.reduce(layoutfont);
213 pit->setFont(pos, font);
217 InsetBase * LyXText::getInset() const
219 ParagraphList::iterator pit = cursorPar();
220 pos_type const pos = cursor().pos();
222 if (pos < pit->size() && pit->isInset(pos)) {
223 return pit->getInset(pos);
229 bool LyXText::toggleInset()
231 InsetBase * inset = getInset();
232 // is there an editable inset at cursor position?
233 if (!isEditableInset(inset))
235 //bv()->owner()->message(inset->editMessage());
237 // do we want to keep this?? (JMarc)
238 if (!isHighlyEditableInset(inset))
239 recUndo(cursor().par());
250 // Asger is not sure we want to do this...
251 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
254 LyXLayout_ptr const & layout = par.layout();
255 pos_type const psize = par.size();
258 for (pos_type pos = 0; pos < psize; ++pos) {
259 if (pos < par.beginOfBody())
260 layoutfont = layout->labelfont;
262 layoutfont = layout->font;
264 LyXFont tmpfont = par.getFontSettings(params, pos);
265 tmpfont.reduce(layoutfont);
266 par.setFont(pos, tmpfont);
271 // return past-the-last paragraph influenced by a layout change on pit
272 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
274 ParagraphList::iterator end = paragraphs().end();
275 ParagraphList::iterator nextpit = boost::next(pit);
278 //because of parindents
279 if (!pit->getDepth())
280 return boost::next(nextpit);
281 //because of depth constrains
282 for (; nextpit != end; ++pit, ++nextpit) {
283 if (!pit->getDepth())
290 ParagraphList::iterator
291 LyXText::setLayout(ParagraphList::iterator start,
292 ParagraphList::iterator end,
293 string const & layout)
295 BOOST_ASSERT(start != end);
296 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
297 recUndo(parOffset(start), parOffset(undopit) - 1);
299 BufferParams const & bufparams = bv()->buffer()->params();
300 LyXLayout_ptr const & lyxlayout =
301 bufparams.getLyXTextClass()[layout];
303 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
304 pit->applyLayout(lyxlayout);
305 makeFontEntriesLayoutSpecific(bufparams, *pit);
306 if (lyxlayout->margintype == MARGIN_MANUAL)
307 pit->setLabelWidthString(lyxlayout->labelstring());
314 // set layout over selection and make a total rebreak of those paragraphs
315 void LyXText::setLayout(string const & layout)
317 // special handling of new environment insets
318 BufferParams const & params = bv()->buffer()->params();
319 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
320 if (lyxlayout->is_environment) {
321 // move everything in a new environment inset
322 lyxerr << "setting layout " << layout << endl;
323 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
324 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
325 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
326 InsetBase * inset = new InsetEnvironment(params, layout);
327 if (bv()->insertInset(inset)) {
329 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
335 ParagraphList::iterator start =
336 getPar(bv()->cursor().selBegin().par());
337 ParagraphList::iterator end =
338 boost::next(getPar(bv()->cursor().selEnd().par()));
339 ParagraphList::iterator endpit = setLayout(start, end, layout);
341 redoParagraphs(start, endpit);
349 void getSelectionSpan(LyXText & text,
350 ParagraphList::iterator & beg,
351 ParagraphList::iterator & end)
353 if (!text.bv()->cursor().selection()) {
354 beg = text.cursorPar();
355 end = boost::next(beg);
357 beg = text.getPar(text.bv()->cursor().selBegin());
358 end = boost::next(text.getPar(text.bv()->cursor().selEnd()));
363 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
364 Paragraph const & par,
367 if (par.layout()->labeltype == LABEL_BIBLIO)
369 int const depth = par.params().depth();
370 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
372 if (type == bv_funcs::DEC_DEPTH && depth > 0)
381 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
383 ParagraphList::iterator beg, end;
384 getSelectionSpan(*this, beg, end);
386 if (beg != paragraphs().begin())
387 max_depth = boost::prior(beg)->getMaxDepthAfter();
389 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
390 if (::changeDepthAllowed(type, *pit, max_depth))
392 max_depth = pit->getMaxDepthAfter();
398 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
400 ParagraphList::iterator beg, end;
401 getSelectionSpan(*this, beg, end);
403 recUndo(parOffset(beg), parOffset(end) - 1);
406 if (beg != paragraphs().begin())
407 max_depth = boost::prior(beg)->getMaxDepthAfter();
409 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
410 if (::changeDepthAllowed(type, *pit, max_depth)) {
411 int const depth = pit->params().depth();
412 if (type == bv_funcs::INC_DEPTH)
413 pit->params().depth(depth + 1);
415 pit->params().depth(depth - 1);
417 max_depth = pit->getMaxDepthAfter();
419 // this handles the counter labels, and also fixes up
420 // depth values for follow-on (child) paragraphs
425 // set font over selection and make a total rebreak of those paragraphs
426 void LyXText::setFont(LyXFont const & font, bool toggleall)
428 LCursor & cur = bv()->cursor();
429 // if there is no selection just set the current_font
430 if (!cur.selection()) {
431 // Determine basis font
433 if (cursor().pos() < cursorPar()->beginOfBody())
434 layoutfont = getLabelFont(cursorPar());
436 layoutfont = getLayoutFont(cursorPar());
438 // Update current font
439 real_current_font.update(font,
440 bv()->buffer()->params().language,
443 // Reduce to implicit settings
444 current_font = real_current_font;
445 current_font.reduce(layoutfont);
446 // And resolve it completely
447 real_current_font.realize(layoutfont);
452 // ok we have a selection.
453 recUndo(cur.selBegin().par(), cur.selEnd().par());
456 ParagraphList::iterator beg = getPar(cur.selBegin().par());
457 ParagraphList::iterator end = getPar(cur.selEnd().par());
459 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
460 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
462 BufferParams const & params = bv()->buffer()->params();
464 for (; pos != posend; ++pos) {
465 LyXFont f = getFont(pos.pit(), pos.pos());
466 f.update(font, params.language, toggleall);
467 setCharFont(pos.pit(), pos.pos(), f);
472 redoParagraphs(beg, ++end);
476 // the cursor set functions have a special mechanism. When they
477 // realize you left an empty paragraph, they will delete it.
479 void LyXText::cursorHome()
481 ParagraphList::iterator cpit = cursorPar();
482 setCursor(cpit, cpit->getRow(cursor().pos())->pos());
486 void LyXText::cursorEnd()
488 ParagraphList::iterator cpit = cursorPar();
489 pos_type end = cpit->getRow(cursor().pos())->endpos();
490 // if not on the last row of the par, put the cursor before
492 setCursor(cpit, end == cpit->size() ? end : end - 1);
496 void LyXText::cursorTop()
498 setCursor(paragraphs().begin(), 0);
502 void LyXText::cursorBottom()
504 ParagraphList::iterator lastpit =
505 boost::prior(paragraphs().end());
506 setCursor(lastpit, lastpit->size());
510 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
512 // If the mask is completely neutral, tell user
513 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
514 // Could only happen with user style
515 bv()->owner()->message(_("No font change defined. "
516 "Use Character under the Layout menu to define font change."));
520 // Try implicit word selection
521 // If there is a change in the language the implicit word selection
523 CursorSlice resetCursor = cursor();
524 bool implicitSelection =
525 font.language() == ignore_language
526 && font.number() == LyXFont::IGNORE
527 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
530 setFont(font, toggleall);
532 // Implicit selections are cleared afterwards
533 //and cursor is set to the original position.
534 if (implicitSelection) {
535 bv()->cursor().clearSelection();
536 cursor() = resetCursor;
537 bv()->cursor().resetAnchor();
542 string LyXText::getStringToIndex()
544 LCursor & cur = bv()->cursor();
545 // Try implicit word selection
546 // If there is a change in the language the implicit word selection
548 CursorSlice const reset_cursor = cursor();
549 bool const implicitSelection =
550 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
553 if (!cur.selection())
554 bv()->owner()->message(_("Nothing to index!"));
555 else if (cur.selBegin().par() != cur.selEnd().par())
556 bv()->owner()->message(_("Cannot index more than one paragraph!"));
558 idxstring = selectionAsString(*bv()->buffer(), false);
560 // Reset cursors to their original position.
561 cursor() = reset_cursor;
564 // Clear the implicit selection.
565 if (implicitSelection)
566 cur.clearSelection();
572 // the DTP switches for paragraphs(). LyX will store them in the first
573 // physical paragraph. When a paragraph is broken, the top settings rest,
574 // the bottom settings are given to the new one. So I can make sure,
575 // they do not duplicate themself and you cannot play dirty tricks with
578 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
579 string const & labelwidthstring, bool noindent)
581 LCursor & cur = bv()->cursor();
582 // make sure that the depth behind the selection are restored, too
583 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
584 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
586 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
587 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
589 for (--pit; pit != beg; ++pit) {
590 ParagraphParameters & params = pit->params();
591 params.spacing(spacing);
593 // does the layout allow the new alignment?
594 LyXLayout_ptr const & layout = pit->layout();
596 if (align == LYX_ALIGN_LAYOUT)
597 align = layout->align;
598 if (align & layout->alignpossible) {
599 if (align == layout->align)
600 params.align(LYX_ALIGN_LAYOUT);
604 pit->setLabelWidthString(labelwidthstring);
605 params.noindent(noindent);
608 redoParagraphs(getPar(cur.selBegin()), undopit);
612 string expandLabel(LyXTextClass const & textclass,
613 LyXLayout_ptr const & layout, bool appendix)
615 string fmt = appendix ?
616 layout->labelstring_appendix() : layout->labelstring();
618 // handle 'inherited level parts' in 'fmt',
619 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
620 size_t const i = fmt.find('@', 0);
621 if (i != string::npos) {
622 size_t const j = fmt.find('@', i + 1);
623 if (j != string::npos) {
624 string parent(fmt, i + 1, j - i - 1);
625 string label = expandLabel(textclass, textclass[parent], appendix);
626 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
630 return textclass.counters().counterLabel(fmt);
636 void incrementItemDepth(ParagraphList::iterator pit,
637 ParagraphList::iterator first_pit)
639 int const cur_labeltype = pit->layout()->labeltype;
641 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
644 int const cur_depth = pit->getDepth();
646 ParagraphList::iterator prev_pit = boost::prior(pit);
648 int const prev_depth = prev_pit->getDepth();
649 int const prev_labeltype = prev_pit->layout()->labeltype;
650 if (prev_depth == 0 && cur_depth > 0) {
651 if (prev_labeltype == cur_labeltype) {
652 pit->itemdepth = prev_pit->itemdepth + 1;
655 } else if (prev_depth < cur_depth) {
656 if (prev_labeltype == cur_labeltype) {
657 pit->itemdepth = prev_pit->itemdepth + 1;
660 } else if (prev_depth == cur_depth) {
661 if (prev_labeltype == cur_labeltype) {
662 pit->itemdepth = prev_pit->itemdepth;
666 if (prev_pit == first_pit)
674 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
675 ParagraphList::iterator firstpit,
681 int const cur_depth = pit->getDepth();
682 ParagraphList::iterator prev_pit = boost::prior(pit);
684 int const prev_depth = prev_pit->getDepth();
685 int const prev_labeltype = prev_pit->layout()->labeltype;
686 if (prev_depth <= cur_depth) {
687 if (prev_labeltype != LABEL_ENUMERATE) {
688 switch (pit->itemdepth) {
690 counters.reset("enumi");
692 counters.reset("enumii");
694 counters.reset("enumiii");
696 counters.reset("enumiv");
702 if (prev_pit == firstpit)
712 // set the counter of a paragraph. This includes the labels
713 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
715 BufferParams const & bufparams = buf.params();
716 LyXTextClass const & textclass = bufparams.getLyXTextClass();
717 LyXLayout_ptr const & layout = pit->layout();
718 ParagraphList::iterator first_pit = paragraphs().begin();
719 Counters & counters = textclass.counters();
724 if (pit == first_pit) {
725 pit->params().appendix(pit->params().startOfAppendix());
727 pit->params().appendix(boost::prior(pit)->params().appendix());
728 if (!pit->params().appendix() &&
729 pit->params().startOfAppendix()) {
730 pit->params().appendix(true);
731 textclass.counters().reset();
734 // Maybe we have to increment the item depth.
735 incrementItemDepth(pit, first_pit);
738 // erase what was there before
739 pit->params().labelString(string());
741 if (layout->margintype == MARGIN_MANUAL) {
742 if (pit->params().labelWidthString().empty())
743 pit->setLabelWidthString(layout->labelstring());
745 pit->setLabelWidthString(string());
748 // is it a layout that has an automatic label?
749 if (layout->labeltype == LABEL_COUNTER) {
750 BufferParams const & bufparams = buf.params();
751 LyXTextClass const & textclass = bufparams.getLyXTextClass();
752 counters.step(layout->counter);
753 string label = expandLabel(textclass, layout, pit->params().appendix());
754 pit->params().labelString(label);
755 } else if (layout->labeltype == LABEL_ITEMIZE) {
756 // At some point of time we should do something more
757 // clever here, like:
758 // pit->params().labelString(
759 // bufparams.user_defined_bullet(pit->itemdepth).getText());
760 // for now, use a simple hardcoded label
762 switch (pit->itemdepth) {
777 pit->params().labelString(itemlabel);
778 } else if (layout->labeltype == LABEL_ENUMERATE) {
779 // Maybe we have to reset the enumeration counter.
780 resetEnumCounterIfNeeded(pit, first_pit, counters);
783 // Yes I know this is a really, really! bad solution
785 string enumcounter = "enum";
787 switch (pit->itemdepth) {
799 // not a valid enumdepth...
803 counters.step(enumcounter);
805 pit->params().labelString(counters.enumLabel(enumcounter));
806 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
807 counters.step("bibitem");
808 int number = counters.value("bibitem");
809 if (pit->bibitem()) {
810 pit->bibitem()->setCounter(number);
811 pit->params().labelString(layout->labelstring());
813 // In biblio should't be following counters but...
815 string s = buf.B_(layout->labelstring());
818 if (layout->labeltype == LABEL_SENSITIVE) {
819 ParagraphList::iterator end = paragraphs().end();
820 ParagraphList::iterator tmppit = pit;
823 while (tmppit != end && tmppit->inInset()
824 // the single '=' is intended below
825 && (in = tmppit->inInset()->owner()))
827 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
828 in->lyxCode() == InsetBase::WRAP_CODE) {
832 Paragraph const * owner = &ownerPar(buf, in);
834 for ( ; tmppit != end; ++tmppit)
835 if (&*tmppit == owner)
843 if (in->lyxCode() == InsetBase::FLOAT_CODE)
844 type = static_cast<InsetFloat*>(in)->params().type;
845 else if (in->lyxCode() == InsetBase::WRAP_CODE)
846 type = static_cast<InsetWrap*>(in)->params().type;
850 Floating const & fl = textclass.floats().getType(type);
852 counters.step(fl.type());
854 // Doesn't work... yet.
855 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
857 // par->SetLayout(0);
858 // s = layout->labelstring;
859 s = _("Senseless: ");
862 pit->params().labelString(s);
868 // Updates all counters.
869 void LyXText::updateCounters()
872 bv()->buffer()->params().getLyXTextClass().counters().reset();
874 bool update_pos = false;
876 ParagraphList::iterator beg = paragraphs().begin();
877 ParagraphList::iterator end = paragraphs().end();
878 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
879 string const oldLabel = pit->params().labelString();
882 maxdepth = boost::prior(pit)->getMaxDepthAfter();
884 if (pit->params().depth() > maxdepth)
885 pit->params().depth(maxdepth);
887 // setCounter can potentially change the labelString.
888 setCounter(*bv()->buffer(), pit);
889 string const & newLabel = pit->params().labelString();
890 if (oldLabel != newLabel) {
891 redoParagraphInternal(pit);
897 updateParPositions();
901 void LyXText::insertInset(InsetBase * inset)
903 if (!cursorPar()->insetAllowed(inset->lyxCode()))
906 recUndo(cursor().par());
908 cursorPar()->insertInset(cursor().pos(), inset);
909 // Just to rebreak and refresh correctly.
910 // The character will not be inserted a second time
911 insertChar(Paragraph::META_INSET);
912 // If we enter a highly editable inset the cursor should be before
913 // the inset. After an undo LyX tries to call inset->edit(...)
914 // and fails if the cursor is behind the inset and getInset
915 // does not return the inset!
916 if (isHighlyEditableInset(inset))
923 void LyXText::cutSelection(bool doclear, bool realcut)
925 LCursor & cur = bv()->cursor();
926 // Stuff what we got on the clipboard. Even if there is no selection.
928 // There is a problem with having the stuffing here in that the
929 // larger the selection the slower LyX will get. This can be
930 // solved by running the line below only when the selection has
931 // finished. The solution used currently just works, to make it
932 // faster we need to be more clever and probably also have more
933 // calls to stuffClipboard. (Lgb)
934 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
936 // This doesn't make sense, if there is no selection
937 if (!cur.selection())
940 // OK, we have a selection. This is always between cur.selBegin()
943 // make sure that the depth behind the selection are restored, too
944 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
945 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
946 ParagraphList::iterator undopit = undoSpan(endpit);
947 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
949 int endpos = cur.selEnd().pos();
951 BufferParams const & bufparams = bv()->buffer()->params();
952 boost::tie(endpit, endpos) = realcut ?
953 CutAndPaste::cutSelection(bufparams,
956 cur.selBegin().pos(), endpos,
959 : CutAndPaste::eraseSelection(bufparams,
962 cur.selBegin().pos(), endpos,
964 // sometimes necessary
966 begpit->stripLeadingSpaces();
968 redoParagraphs(begpit, undopit);
969 // cutSelection can invalidate the cursor so we need to set
971 // we prefer the end for when tracking changes
972 cursor().pos(endpos);
973 cursor().par(parOffset(endpit));
975 // need a valid cursor. (Lgb)
976 cur.clearSelection();
981 void LyXText::copySelection()
983 LCursor & cur = bv()->cursor();
984 // stuff the selection onto the X clipboard, from an explicit copy request
985 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
987 // this doesnt make sense, if there is no selection
988 if (!cur.selection())
991 // ok we have a selection. This is always between cur.selBegin()
992 // and sel_end cursor
994 // copy behind a space if there is one
995 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
996 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
997 && (cur.selBegin().par() != cur.selEnd().par()
998 || cur.selBegin().pos() < cur.selEnd().pos()))
999 cur.selBegin().pos(cur.selBegin().pos() + 1);
1001 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
1002 getPar(cur.selEnd().par()),
1003 cur.selBegin().pos(),
1005 bv()->buffer()->params().textclass);
1009 void LyXText::pasteSelection(size_t sel_index)
1011 LCursor & cur = bv()->cursor();
1012 // this does not make sense, if there is nothing to paste
1013 if (!CutAndPaste::checkPastePossible())
1016 recUndo(cursor().par());
1018 ParagraphList::iterator endpit;
1023 boost::tie(ppp, endpit) =
1024 CutAndPaste::pasteSelection(*bv()->buffer(),
1026 cursorPar(), cursor().pos(),
1027 bv()->buffer()->params().textclass,
1029 bufferErrors(*bv()->buffer(), el);
1030 bv()->showErrorList(_("Paste"));
1032 redoParagraphs(cursorPar(), endpit);
1034 cur.clearSelection();
1036 setCursor(ppp.first, ppp.second);
1042 void LyXText::setSelectionRange(lyx::pos_type length)
1047 LCursor & cur = bv()->cursor();
1055 // simple replacing. The font of the first selected character is used
1056 void LyXText::replaceSelectionWithString(string const & str)
1058 LCursor & cur = bv()->cursor();
1062 // Get font setting before we cut
1063 pos_type pos = cur.selEnd().pos();
1064 LyXFont const font = getPar(cur.selBegin())
1065 ->getFontSettings(bv()->buffer()->params(),
1066 cur.selBegin().pos());
1068 // Insert the new string
1069 string::const_iterator cit = str.begin();
1070 string::const_iterator end = str.end();
1071 for (; cit != end; ++cit) {
1072 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1076 // Cut the selection
1077 cutSelection(true, false);
1083 // needed to insert the selection
1084 void LyXText::insertStringAsLines(string const & str)
1086 LCursor & cur = bv()->cursor();
1087 ParagraphList::iterator pit = cursorPar();
1088 pos_type pos = cursor().pos();
1089 ParagraphList::iterator endpit = boost::next(cursorPar());
1091 recUndo(cursor().par());
1093 // only to be sure, should not be neccessary
1094 cur.clearSelection();
1095 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1097 redoParagraphs(cursorPar(), endpit);
1099 setCursor(pit, pos);
1104 // turn double CR to single CR, others are converted into one
1105 // blank. Then insertStringAsLines is called
1106 void LyXText::insertStringAsParagraphs(string const & str)
1108 string linestr(str);
1109 bool newline_inserted = false;
1110 string::size_type const siz = linestr.length();
1112 for (string::size_type i = 0; i < siz; ++i) {
1113 if (linestr[i] == '\n') {
1114 if (newline_inserted) {
1115 // we know that \r will be ignored by
1116 // insertStringAsLines. Of course, it is a dirty
1117 // trick, but it works...
1118 linestr[i - 1] = '\r';
1122 newline_inserted = true;
1124 } else if (IsPrintable(linestr[i])) {
1125 newline_inserted = false;
1128 insertStringAsLines(linestr);
1132 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1134 setCursor(parOffset(pit), pos);
1138 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1141 CursorSlice old_cursor = cursor();
1142 setCursorIntern(par, pos, setfont, boundary);
1143 return deleteEmptyParagraphMechanism(old_cursor);
1147 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1148 pos_type pos, bool boundary)
1150 BOOST_ASSERT(par != int(paragraphs().size()));
1154 cur.boundary(boundary);
1156 // no rows, no fun...
1157 if (paragraphs().begin()->rows.empty())
1160 // now some strict checking
1161 Paragraph & para = *getPar(par);
1162 Row const & row = *para.getRow(pos);
1163 pos_type const end = row.endpos();
1165 // None of these should happen, but we're scaredy-cats
1167 lyxerr << "dont like -1" << endl;
1170 BOOST_ASSERT(false);
1171 } else if (pos > para.size()) {
1172 lyxerr << "dont like 1, pos: " << pos
1173 << " size: " << para.size()
1174 << " row.pos():" << row.pos()
1175 << " paroffset: " << par << endl;
1178 BOOST_ASSERT(false);
1179 } else if (pos > end) {
1180 lyxerr << "dont like 2 please report" << endl;
1181 // This shouldn't happen.
1184 BOOST_ASSERT(false);
1185 } else if (pos < row.pos()) {
1186 lyxerr << "dont like 3 please report pos:" << pos
1187 << " size: " << para.size()
1188 << " row.pos():" << row.pos()
1189 << " paroffset: " << par << endl;
1192 BOOST_ASSERT(false);
1197 void LyXText::setCursorIntern(paroffset_type par,
1198 pos_type pos, bool setfont, bool boundary)
1200 setCursor(cursor(), par, pos, boundary);
1201 bv()->cursor().x_target() = cursorX(cursor());
1207 void LyXText::setCurrentFont()
1209 LCursor & cur = bv()->cursor();
1210 pos_type pos = cur.pos();
1211 ParagraphList::iterator pit = cursorPar();
1213 if (cursor().boundary() && pos > 0)
1217 if (pos == pit->size())
1219 else // potentional bug... BUG (Lgb)
1220 if (pit->isSeparator(pos)) {
1221 if (pos > pit->getRow(pos)->pos() &&
1222 bidi.level(pos) % 2 ==
1223 bidi.level(pos - 1) % 2)
1225 else if (pos + 1 < pit->size())
1230 BufferParams const & bufparams = bv()->buffer()->params();
1231 current_font = pit->getFontSettings(bufparams, pos);
1232 real_current_font = getFont(pit, pos);
1234 if (cursor().pos() == pit->size() &&
1235 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1236 !cursor().boundary()) {
1237 Language const * lang =
1238 pit->getParLanguage(bufparams);
1239 current_font.setLanguage(lang);
1240 current_font.setNumber(LyXFont::OFF);
1241 real_current_font.setLanguage(lang);
1242 real_current_font.setNumber(LyXFont::OFF);
1247 // returns the column near the specified x-coordinate of the row
1248 // x is set to the real beginning of this column
1249 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1250 Row const & row, int & x, bool & boundary) const
1253 double tmpx = row.x();
1254 double fill_separator = row.fill_separator();
1255 double fill_hfill = row.fill_hfill();
1256 double fill_label_hfill = row.fill_label_hfill();
1258 pos_type vc = row.pos();
1259 pos_type end = row.endpos();
1261 LyXLayout_ptr const & layout = pit->layout();
1263 bool left_side = false;
1265 pos_type body_pos = pit->beginOfBody();
1266 double last_tmpx = tmpx;
1269 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1272 // check for empty row
1274 x = int(tmpx) + xo_;
1278 while (vc < end && tmpx <= x) {
1279 c = bidi.vis2log(vc);
1281 if (body_pos > 0 && c == body_pos - 1) {
1282 tmpx += fill_label_hfill +
1283 font_metrics::width(layout->labelsep, getLabelFont(pit));
1284 if (pit->isLineSeparator(body_pos - 1))
1285 tmpx -= singleWidth(pit, body_pos - 1);
1288 if (hfillExpansion(*pit, row, c)) {
1289 tmpx += singleWidth(pit, c);
1293 tmpx += fill_label_hfill;
1294 } else if (pit->isSeparator(c)) {
1295 tmpx += singleWidth(pit, c);
1297 tmpx += fill_separator;
1299 tmpx += singleWidth(pit, c);
1304 if ((tmpx + last_tmpx) / 2 > x) {
1309 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1312 // This (rtl_support test) is not needed, but gives
1313 // some speedup if rtl_support == false
1314 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1316 // If lastrow is false, we don't need to compute
1317 // the value of rtl.
1318 bool const rtl = lastrow
1319 ? pit->isRightToLeftPar(bv()->buffer()->params())
1322 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1323 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1325 else if (vc == row.pos()) {
1326 c = bidi.vis2log(vc);
1327 if (bidi.level(c) % 2 == 1)
1330 c = bidi.vis2log(vc - 1);
1331 bool const rtl = (bidi.level(c) % 2 == 1);
1332 if (left_side == rtl) {
1334 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1338 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1339 if (bidi.level(end -1) % 2 == 0)
1340 tmpx -= singleWidth(pit, end - 1);
1342 tmpx += singleWidth(pit, end - 1);
1346 x = int(tmpx) + xo_;
1347 return c - row.pos();
1351 void LyXText::setCursorFromCoordinates(int x, int y)
1353 CursorSlice old_cursor = cursor();
1354 setCursorFromCoordinates(cursor(), x, y);
1356 deleteEmptyParagraphMechanism(old_cursor);
1360 // x,y are coordinates relative to this LyXText
1361 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1363 ParagraphList::iterator pit;
1364 Row const & row = *getRowNearY(y, pit);
1366 pos_type const pos = row.pos() + getColumnNearX(pit, row, x, bound);
1367 cur.par() = parOffset(pit);
1369 cur.boundary() = bound;
1373 // x,y are absolute screen coordinates
1374 void LyXText::edit(LCursor & cur, int x, int y)
1376 int xx = x; // is modified by getColumnNearX
1377 ParagraphList::iterator pit;
1378 Row const & row = *getRowNearY(y, pit);
1380 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1381 cur.par() = parOffset(pit);
1383 cur.boundary() = bound;
1385 // try to descend into nested insets
1386 InsetBase * inset = checkInsetHit(x, y);
1388 // This should be just before or just behind the cursor position
1390 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1391 || inset == pit->getInset(pos));
1392 // Make sure the cursor points to the position before this inset.
1393 if (inset == pit->getInset(pos - 1))
1395 inset->edit(cur, x, y);
1400 bool LyXText::checkAndActivateInset(bool front)
1402 if (cursor().pos() == cursorPar()->size())
1404 InsetBase * inset = cursorPar()->getInset(cursor().pos());
1405 if (!isHighlyEditableInset(inset))
1407 inset->edit(bv()->cursor(), front);
1412 DispatchResult LyXText::moveRight()
1414 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1415 return moveLeftIntern(false, true, false);
1417 return moveRightIntern(true, true, false);
1421 DispatchResult LyXText::moveLeft()
1423 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1424 return moveRightIntern(true, true, false);
1426 return moveLeftIntern(false, true, false);
1430 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1432 ParagraphList::iterator c_par = cursorPar();
1433 if (boost::next(c_par) == paragraphs().end()
1434 && cursor().pos() >= c_par->size())
1435 return DispatchResult(false, FINISHED_RIGHT);
1436 if (activate_inset && checkAndActivateInset(front))
1437 return DispatchResult(true, true);
1440 bv()->cursor().clearSelection();
1441 return DispatchResult(true);
1445 DispatchResult LyXText::moveLeftIntern(bool front,
1446 bool activate_inset, bool selecting)
1448 if (cursor().par() == 0 && cursor().pos() <= 0)
1449 return DispatchResult(false, FINISHED);
1452 bv()->cursor().clearSelection();
1453 if (activate_inset && checkAndActivateInset(front))
1454 return DispatchResult(true, true);
1455 return DispatchResult(true);
1459 DispatchResult LyXText::moveUp()
1461 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1462 return DispatchResult(false, FINISHED_UP);
1464 bv()->cursor().clearSelection();
1465 return DispatchResult(true);
1469 DispatchResult LyXText::moveDown()
1471 LCursor & cur = bv()->cursor();
1472 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1473 return DispatchResult(false, FINISHED_DOWN);
1475 cur.clearSelection();
1476 return DispatchResult(true);
1480 bool LyXText::cursorLeft(bool internal)
1482 LCursor & cur = bv()->cursor();
1483 if (cur.pos() > 0) {
1484 bool boundary = cur.boundary();
1485 setCursor(cur.par(), cur.pos() - 1, true, false);
1486 if (!internal && !boundary &&
1487 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1488 setCursor(cur.par(), cur.pos() + 1, true, true);
1492 if (cur.par() != 0) {
1493 // steps into the paragraph above
1494 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1502 bool LyXText::cursorRight(bool internal)
1504 LCursor & cur = bv()->cursor();
1505 if (!internal && cur.boundary()) {
1506 setCursor(cur.par(), cur.pos(), true, false);
1510 if (cur.pos() != cur.lastpos()) {
1511 setCursor(cur.par(), cur.pos() + 1, true, false);
1512 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1514 setCursor(cur.par(), cur.pos(), true, true);
1518 if (cur.par() + 1 != int(paragraphs().size())) {
1519 setCursor(cur.par() + 1, 0);
1527 void LyXText::cursorUp(bool selecting)
1529 LCursor & cur = bv()->cursor();
1530 Row const & row = *cursorRow();
1531 int x = cur.x_target();
1532 int y = cursorY(cur.current()) - row.baseline() - 1;
1533 setCursorFromCoordinates(x, y);
1536 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1537 if (inset_hit && isHighlyEditableInset(inset_hit))
1538 inset_hit->edit(cur, cur.x_target(), y);
1543 void LyXText::cursorDown(bool selecting)
1545 LCursor & cur = bv()->cursor();
1546 Row const & row = *cursorRow();
1547 int x = cur.x_target();
1548 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1549 setCursorFromCoordinates(x, y);
1552 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1553 if (inset_hit && isHighlyEditableInset(inset_hit))
1554 inset_hit->edit(cur, cur.x_target(), y);
1559 void LyXText::cursorUpParagraph()
1561 ParagraphList::iterator cpit = cursorPar();
1562 if (cursor().pos() > 0)
1564 else if (cpit != paragraphs().begin())
1565 setCursor(boost::prior(cpit), 0);
1569 void LyXText::cursorDownParagraph()
1571 ParagraphList::iterator pit = cursorPar();
1572 ParagraphList::iterator next_pit = boost::next(pit);
1574 if (next_pit != paragraphs().end())
1575 setCursor(next_pit, 0);
1577 setCursor(pit, pit->size());
1581 // fix the cursor `cur' after a characters has been deleted at `where'
1582 // position. Called by deleteEmptyParagraphMechanism
1583 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1585 // if cursor is not in the paragraph where the delete occured,
1587 if (cur.par() != where.par())
1590 // if cursor position is after the place where the delete occured,
1592 if (cur.pos() > where.pos())
1593 cur.pos(cur.pos()-1);
1595 // check also if we don't want to set the cursor on a spot behind the
1596 // pagragraph because we erased the last character.
1597 if (cur.pos() > cur.lastpos())
1598 cur.pos() = cur.lastpos();
1602 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1604 #warning Disabled as it crashes after the cursor data shift... (Andre)
1607 // Would be wrong to delete anything if we have a selection.
1608 if (bv()->cursor().selection())
1611 // Don't do anything if the cursor is invalid
1612 if (old_cursor.par() == -1)
1616 // We allow all kinds of "mumbo-jumbo" when freespacing.
1617 ParagraphList::iterator const old_pit = getPar(old_cursor);
1618 if (old_pit->isFreeSpacing())
1621 /* Ok I'll put some comments here about what is missing.
1622 I have fixed BackSpace (and thus Delete) to not delete
1623 double-spaces automagically. I have also changed Cut,
1624 Copy and Paste to hopefully do some sensible things.
1625 There are still some small problems that can lead to
1626 double spaces stored in the document file or space at
1627 the beginning of paragraphs(). This happens if you have
1628 the cursor between to spaces and then save. Or if you
1629 cut and paste and the selection have a space at the
1630 beginning and then save right after the paste. I am
1631 sure none of these are very hard to fix, but I will
1632 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1633 that I can get some feedback. (Lgb)
1636 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1637 // delete the LineSeparator.
1640 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1641 // delete the LineSeparator.
1644 // If the pos around the old_cursor were spaces, delete one of them.
1645 if (old_cursor.par() != cursor().par()
1646 || old_cursor.pos() != cursor().pos()) {
1648 // Only if the cursor has really moved
1649 if (old_cursor.pos() > 0
1650 && old_cursor.pos() < old_pit->size()
1651 && old_pit->isLineSeparator(old_cursor.pos())
1652 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1653 bool erased = old_pit->erase(old_cursor.pos() - 1);
1654 redoParagraph(old_pit);
1658 #ifdef WITH_WARNINGS
1659 #warning This will not work anymore when we have multiple views of the same buffer
1660 // In this case, we will have to correct also the cursors held by
1661 // other bufferviews. It will probably be easier to do that in a more
1662 // automated way in CursorSlice code. (JMarc 26/09/2001)
1664 // correct all cursors held by the LyXText
1665 fixCursorAfterDelete(cursor(), old_cursor);
1666 fixCursorAfterDelete(anchor(), old_cursor);
1671 // don't delete anything if this is the ONLY paragraph!
1672 if (paragraphs().size() == 1)
1675 // Do not delete empty paragraphs with keepempty set.
1676 if (old_pit->allowEmpty())
1679 // only do our magic if we changed paragraph
1680 if (old_cursor.par() == cursor().par())
1683 // record if we have deleted a paragraph
1684 // we can't possibly have deleted a paragraph before this point
1685 bool deleted = false;
1687 if (old_pit->empty()
1688 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1689 // ok, we will delete something
1690 CursorSlice tmpcursor;
1694 bool selection_position_was_oldcursor_position =
1695 anchor().par() == old_cursor.par()
1696 && anchor().pos() == old_cursor.pos();
1698 tmpcursor = cursor();
1699 cursor() = old_cursor; // that undo can restore the right cursor position
1701 ParagraphList::iterator endpit = boost::next(old_pit);
1702 while (endpit != paragraphs().end() && endpit->getDepth())
1705 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1706 cursor() = tmpcursor;
1709 ParagraphList::iterator tmppit = cursorPar();
1711 paragraphs().erase(old_pit);
1712 // update cursor par offset
1713 cursor().par(parOffset(tmppit));
1716 if (selection_position_was_oldcursor_position) {
1717 // correct selection
1718 bv()->resetAnchor();
1725 if (old_pit->stripLeadingSpaces()) {
1726 redoParagraph(old_pit);
1727 bv()->resetAnchor();
1734 ParagraphList & LyXText::paragraphs() const
1736 return const_cast<ParagraphList &>(paragraphs_);
1740 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1742 recordUndo(Undo::ATOMIC, this, first, last);
1746 void LyXText::recUndo(lyx::paroffset_type par) const
1748 recordUndo(Undo::ATOMIC, this, par, par);
1752 bool LyXText::isInInset() const
1758 int defaultRowHeight()
1760 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);