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 * bv)
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 bv->cursor().resetAnchor();
103 // Gets the fully instantiated font at a given position in a paragraph
104 // Basically the same routine as Paragraph::getFont() in paragraph.C.
105 // The difference is that this one is used for displaying, and thus we
106 // are allowed to make cosmetic improvements. For instance make footnotes
108 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
110 BOOST_ASSERT(pos >= 0);
112 LyXLayout_ptr const & layout = pit->layout();
114 BufferParams const & params = bv()->buffer()->params();
115 pos_type const body_pos = pit->beginOfBody();
117 // We specialize the 95% common case:
118 if (!pit->getDepth()) {
119 LyXFont f = pit->getFontSettings(params, pos);
122 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
123 return f.realize(layout->reslabelfont);
125 return f.realize(layout->resfont);
128 // The uncommon case need not be optimized as much
131 layoutfont = layout->labelfont;
133 layoutfont = layout->font;
135 LyXFont font = pit->getFontSettings(params, pos);
136 font.realize(layoutfont);
141 // Realize with the fonts of lesser depth.
142 //font.realize(outerFont(pit, paragraphs()));
143 font.realize(defaultfont_);
149 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
151 LyXLayout_ptr const & layout = pit->layout();
153 if (!pit->getDepth())
154 return layout->resfont;
156 LyXFont font = layout->font;
157 // Realize with the fonts of lesser depth.
158 //font.realize(outerFont(pit, paragraphs()));
159 font.realize(defaultfont_);
165 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
167 LyXLayout_ptr const & layout = pit->layout();
169 if (!pit->getDepth())
170 return layout->reslabelfont;
172 LyXFont font = layout->labelfont;
173 // Realize with the fonts of lesser depth.
174 font.realize(outerFont(pit, paragraphs()));
175 font.realize(defaultfont_);
181 void LyXText::setCharFont(
182 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
185 LyXLayout_ptr const & layout = pit->layout();
187 // Get concrete layout font to reduce against
190 if (pos < pit->beginOfBody())
191 layoutfont = layout->labelfont;
193 layoutfont = layout->font;
195 // Realize against environment font information
196 if (pit->getDepth()) {
197 ParagraphList::iterator tp = pit;
198 while (!layoutfont.resolved() &&
199 tp != paragraphs().end() &&
201 tp = outerHook(tp, paragraphs());
202 if (tp != paragraphs().end())
203 layoutfont.realize(tp->layout()->font);
207 layoutfont.realize(defaultfont_);
209 // Now, reduce font against full layout font
210 font.reduce(layoutfont);
212 pit->setFont(pos, font);
217 // Asger is not sure we want to do this...
218 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
221 LyXLayout_ptr const & layout = par.layout();
222 pos_type const psize = par.size();
225 for (pos_type pos = 0; pos < psize; ++pos) {
226 if (pos < par.beginOfBody())
227 layoutfont = layout->labelfont;
229 layoutfont = layout->font;
231 LyXFont tmpfont = par.getFontSettings(params, pos);
232 tmpfont.reduce(layoutfont);
233 par.setFont(pos, tmpfont);
238 // return past-the-last paragraph influenced by a layout change on pit
239 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
241 ParagraphList::iterator end = paragraphs().end();
242 ParagraphList::iterator nextpit = boost::next(pit);
245 //because of parindents
246 if (!pit->getDepth())
247 return boost::next(nextpit);
248 //because of depth constrains
249 for (; nextpit != end; ++pit, ++nextpit) {
250 if (!pit->getDepth())
257 ParagraphList::iterator
258 LyXText::setLayout(ParagraphList::iterator start,
259 ParagraphList::iterator end,
260 string const & layout)
262 BOOST_ASSERT(start != end);
263 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
264 recUndo(parOffset(start), parOffset(undopit) - 1);
266 BufferParams const & bufparams = bv()->buffer()->params();
267 LyXLayout_ptr const & lyxlayout =
268 bufparams.getLyXTextClass()[layout];
270 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
271 pit->applyLayout(lyxlayout);
272 makeFontEntriesLayoutSpecific(bufparams, *pit);
273 if (lyxlayout->margintype == MARGIN_MANUAL)
274 pit->setLabelWidthString(lyxlayout->labelstring());
281 // set layout over selection and make a total rebreak of those paragraphs
282 void LyXText::setLayout(LCursor & cur, string const & layout)
284 // special handling of new environment insets
285 BufferParams const & params = bv()->buffer()->params();
286 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
287 if (lyxlayout->is_environment) {
288 // move everything in a new environment inset
289 lyxerr << "setting layout " << layout << endl;
290 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
291 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
292 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
293 InsetBase * inset = new InsetEnvironment(params, layout);
294 insertInset(cur, inset);
296 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
300 ParagraphList::iterator start = getPar(cur.selBegin().par());
301 ParagraphList::iterator end = boost::next(getPar(cur.selEnd().par()));
302 ParagraphList::iterator endpit = setLayout(start, end, layout);
303 redoParagraphs(start, endpit);
311 void getSelectionSpan(LCursor & cur, LyXText & text,
312 ParagraphList::iterator & beg,
313 ParagraphList::iterator & end)
315 if (!cur.selection()) {
316 beg = text.getPar(cur.par());
317 end = boost::next(beg);
319 beg = text.getPar(cur.selBegin());
320 end = boost::next(text.getPar(cur.selEnd()));
325 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
326 Paragraph const & par,
329 if (par.layout()->labeltype == LABEL_BIBLIO)
331 int const depth = par.params().depth();
332 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
334 if (type == bv_funcs::DEC_DEPTH && depth > 0)
343 bool LyXText::changeDepthAllowed(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
345 ParagraphList::iterator beg, end;
346 getSelectionSpan(cur, *this, beg, end);
348 if (beg != paragraphs().begin())
349 max_depth = boost::prior(beg)->getMaxDepthAfter();
351 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
352 if (::changeDepthAllowed(type, *pit, max_depth))
354 max_depth = pit->getMaxDepthAfter();
360 void LyXText::changeDepth(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
362 ParagraphList::iterator beg, end;
363 getSelectionSpan(cur, *this, beg, end);
364 recordUndoSelection(cur);
367 if (beg != paragraphs().begin())
368 max_depth = boost::prior(beg)->getMaxDepthAfter();
370 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
371 if (::changeDepthAllowed(type, *pit, max_depth)) {
372 int const depth = pit->params().depth();
373 if (type == bv_funcs::INC_DEPTH)
374 pit->params().depth(depth + 1);
376 pit->params().depth(depth - 1);
378 max_depth = pit->getMaxDepthAfter();
380 // this handles the counter labels, and also fixes up
381 // depth values for follow-on (child) paragraphs
386 // set font over selection and make a total rebreak of those paragraphs
387 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
389 // if there is no selection just set the current_font
390 if (!cur.selection()) {
391 // Determine basis font
393 ParagraphList::iterator pit = getPar(cur.par());
394 if (cur.pos() < pit->beginOfBody())
395 layoutfont = getLabelFont(pit);
397 layoutfont = getLayoutFont(pit);
399 // Update current font
400 real_current_font.update(font,
401 bv()->buffer()->params().language,
404 // Reduce to implicit settings
405 current_font = real_current_font;
406 current_font.reduce(layoutfont);
407 // And resolve it completely
408 real_current_font.realize(layoutfont);
413 // ok we have a selection.
414 recordUndoSelection(cur);
417 ParagraphList::iterator beg = getPar(cur.selBegin().par());
418 ParagraphList::iterator end = getPar(cur.selEnd().par());
420 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
421 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
423 BufferParams const & params = bv()->buffer()->params();
425 for (; pos != posend; ++pos) {
426 LyXFont f = getFont(pos.pit(), pos.pos());
427 f.update(font, params.language, toggleall);
428 setCharFont(pos.pit(), pos.pos(), f);
433 redoParagraphs(beg, ++end);
437 // the cursor set functions have a special mechanism. When they
438 // realize you left an empty paragraph, they will delete it.
440 void LyXText::cursorHome(LCursor & cur)
442 setCursor(cur.par(), cur.textRow().pos());
446 void LyXText::cursorEnd(LCursor & cur)
448 // if not on the last row of the par, put the cursor before
450 pos_type const end = cur.textRow().endpos();
451 setCursor(cur.par(), end == cur.lastpos() ? end : end - 1);
455 void LyXText::cursorTop(LCursor &)
461 void LyXText::cursorBottom(LCursor & cur)
463 setCursor(cur.lastpar(), boost::prior(paragraphs().end())->size());
467 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
469 // If the mask is completely neutral, tell user
470 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
471 // Could only happen with user style
472 cur.message(_("No font change defined. "
473 "Use Character under the Layout menu to define font change."));
477 // Try implicit word selection
478 // If there is a change in the language the implicit word selection
480 CursorSlice resetCursor = cur.current();
481 bool implicitSelection =
482 font.language() == ignore_language
483 && font.number() == LyXFont::IGNORE
484 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
487 setFont(cur, font, toggleall);
489 // Implicit selections are cleared afterwards
490 // and cursor is set to the original position.
491 if (implicitSelection) {
492 cur.clearSelection();
493 cur.current() = resetCursor;
499 string LyXText::getStringToIndex(LCursor & cur)
501 // Try implicit word selection
502 // If there is a change in the language the implicit word selection
504 CursorSlice const reset_cursor = cur.current();
505 bool const implicitSelection =
506 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
509 if (!cur.selection())
510 cur.message(_("Nothing to index!"));
511 else if (cur.selBegin().par() != cur.selEnd().par())
512 cur.message(_("Cannot index more than one paragraph!"));
514 idxstring = cur.selectionAsString(false);
516 // Reset cursors to their original position.
517 cur.current() = reset_cursor;
520 // Clear the implicit selection.
521 if (implicitSelection)
522 cur.clearSelection();
528 // the DTP switches for paragraphs(). LyX will store them in the first
529 // physical paragraph. When a paragraph is broken, the top settings rest,
530 // the bottom settings are given to the new one. So I can make sure,
531 // they do not duplicate themself and you cannot play dirty tricks with
534 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
535 string const & labelwidthstring, bool noindent)
537 LCursor & cur = bv()->cursor();
538 // make sure that the depth behind the selection are restored, too
539 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
540 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
542 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
543 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
545 for (--pit; pit != beg; ++pit) {
546 ParagraphParameters & params = pit->params();
547 params.spacing(spacing);
549 // does the layout allow the new alignment?
550 LyXLayout_ptr const & layout = pit->layout();
552 if (align == LYX_ALIGN_LAYOUT)
553 align = layout->align;
554 if (align & layout->alignpossible) {
555 if (align == layout->align)
556 params.align(LYX_ALIGN_LAYOUT);
560 pit->setLabelWidthString(labelwidthstring);
561 params.noindent(noindent);
564 redoParagraphs(getPar(cur.selBegin()), undopit);
568 string expandLabel(LyXTextClass const & textclass,
569 LyXLayout_ptr const & layout, bool appendix)
571 string fmt = appendix ?
572 layout->labelstring_appendix() : layout->labelstring();
574 // handle 'inherited level parts' in 'fmt',
575 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
576 size_t const i = fmt.find('@', 0);
577 if (i != string::npos) {
578 size_t const j = fmt.find('@', i + 1);
579 if (j != string::npos) {
580 string parent(fmt, i + 1, j - i - 1);
581 string label = expandLabel(textclass, textclass[parent], appendix);
582 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
586 return textclass.counters().counterLabel(fmt);
592 void incrementItemDepth(ParagraphList::iterator pit,
593 ParagraphList::iterator first_pit)
595 int const cur_labeltype = pit->layout()->labeltype;
597 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
600 int const cur_depth = pit->getDepth();
602 ParagraphList::iterator prev_pit = boost::prior(pit);
604 int const prev_depth = prev_pit->getDepth();
605 int const prev_labeltype = prev_pit->layout()->labeltype;
606 if (prev_depth == 0 && cur_depth > 0) {
607 if (prev_labeltype == cur_labeltype) {
608 pit->itemdepth = prev_pit->itemdepth + 1;
611 } else if (prev_depth < cur_depth) {
612 if (prev_labeltype == cur_labeltype) {
613 pit->itemdepth = prev_pit->itemdepth + 1;
616 } else if (prev_depth == cur_depth) {
617 if (prev_labeltype == cur_labeltype) {
618 pit->itemdepth = prev_pit->itemdepth;
622 if (prev_pit == first_pit)
630 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
631 ParagraphList::iterator firstpit,
637 int const cur_depth = pit->getDepth();
638 ParagraphList::iterator prev_pit = boost::prior(pit);
640 int const prev_depth = prev_pit->getDepth();
641 int const prev_labeltype = prev_pit->layout()->labeltype;
642 if (prev_depth <= cur_depth) {
643 if (prev_labeltype != LABEL_ENUMERATE) {
644 switch (pit->itemdepth) {
646 counters.reset("enumi");
648 counters.reset("enumii");
650 counters.reset("enumiii");
652 counters.reset("enumiv");
658 if (prev_pit == firstpit)
668 // set the counter of a paragraph. This includes the labels
669 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
671 BufferParams const & bufparams = buf.params();
672 LyXTextClass const & textclass = bufparams.getLyXTextClass();
673 LyXLayout_ptr const & layout = pit->layout();
674 ParagraphList::iterator first_pit = paragraphs().begin();
675 Counters & counters = textclass.counters();
680 if (pit == first_pit) {
681 pit->params().appendix(pit->params().startOfAppendix());
683 pit->params().appendix(boost::prior(pit)->params().appendix());
684 if (!pit->params().appendix() &&
685 pit->params().startOfAppendix()) {
686 pit->params().appendix(true);
687 textclass.counters().reset();
690 // Maybe we have to increment the item depth.
691 incrementItemDepth(pit, first_pit);
694 // erase what was there before
695 pit->params().labelString(string());
697 if (layout->margintype == MARGIN_MANUAL) {
698 if (pit->params().labelWidthString().empty())
699 pit->setLabelWidthString(layout->labelstring());
701 pit->setLabelWidthString(string());
704 // is it a layout that has an automatic label?
705 if (layout->labeltype == LABEL_COUNTER) {
706 BufferParams const & bufparams = buf.params();
707 LyXTextClass const & textclass = bufparams.getLyXTextClass();
708 counters.step(layout->counter);
709 string label = expandLabel(textclass, layout, pit->params().appendix());
710 pit->params().labelString(label);
711 } else if (layout->labeltype == LABEL_ITEMIZE) {
712 // At some point of time we should do something more
713 // clever here, like:
714 // pit->params().labelString(
715 // bufparams.user_defined_bullet(pit->itemdepth).getText());
716 // for now, use a simple hardcoded label
718 switch (pit->itemdepth) {
733 pit->params().labelString(itemlabel);
734 } else if (layout->labeltype == LABEL_ENUMERATE) {
735 // Maybe we have to reset the enumeration counter.
736 resetEnumCounterIfNeeded(pit, first_pit, counters);
739 // Yes I know this is a really, really! bad solution
741 string enumcounter = "enum";
743 switch (pit->itemdepth) {
755 // not a valid enumdepth...
759 counters.step(enumcounter);
761 pit->params().labelString(counters.enumLabel(enumcounter));
762 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
763 counters.step("bibitem");
764 int number = counters.value("bibitem");
765 if (pit->bibitem()) {
766 pit->bibitem()->setCounter(number);
767 pit->params().labelString(layout->labelstring());
769 // In biblio should't be following counters but...
771 string s = buf.B_(layout->labelstring());
774 if (layout->labeltype == LABEL_SENSITIVE) {
775 ParagraphList::iterator end = paragraphs().end();
776 ParagraphList::iterator tmppit = pit;
779 while (tmppit != end && tmppit->inInset()
780 // the single '=' is intended below
781 && (in = tmppit->inInset()->owner()))
783 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
784 in->lyxCode() == InsetBase::WRAP_CODE) {
788 Paragraph const * owner = &ownerPar(buf, in);
790 for ( ; tmppit != end; ++tmppit)
791 if (&*tmppit == owner)
799 if (in->lyxCode() == InsetBase::FLOAT_CODE)
800 type = static_cast<InsetFloat*>(in)->params().type;
801 else if (in->lyxCode() == InsetBase::WRAP_CODE)
802 type = static_cast<InsetWrap*>(in)->params().type;
806 Floating const & fl = textclass.floats().getType(type);
808 counters.step(fl.type());
810 // Doesn't work... yet.
811 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
813 // par->SetLayout(0);
814 // s = layout->labelstring;
815 s = _("Senseless: ");
818 pit->params().labelString(s);
824 // Updates all counters.
825 void LyXText::updateCounters()
828 bv()->buffer()->params().getLyXTextClass().counters().reset();
830 bool update_pos = false;
832 ParagraphList::iterator beg = paragraphs().begin();
833 ParagraphList::iterator end = paragraphs().end();
834 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
835 string const oldLabel = pit->params().labelString();
838 maxdepth = boost::prior(pit)->getMaxDepthAfter();
840 if (pit->params().depth() > maxdepth)
841 pit->params().depth(maxdepth);
843 // setCounter can potentially change the labelString.
844 setCounter(*bv()->buffer(), pit);
845 string const & newLabel = pit->params().labelString();
846 if (oldLabel != newLabel) {
847 redoParagraphInternal(pit);
853 updateParPositions();
857 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
861 cur.paragraph().insertInset(cur.pos(), inset);
862 // Just to rebreak and refresh correctly.
863 // The character will not be inserted a second time
864 insertChar(cur, Paragraph::META_INSET);
865 // If we enter a highly editable inset the cursor should be before
866 // the inset. After an undo LyX tries to call inset->edit(...)
867 // and fails if the cursor is behind the inset and getInset
868 // does not return the inset!
869 if (isHighlyEditableInset(inset))
870 cursorLeft(cur, true);
875 void LyXText::cutSelection(bool doclear, bool realcut)
877 LCursor & cur = bv()->cursor();
878 // Stuff what we got on the clipboard. Even if there is no selection.
880 // There is a problem with having the stuffing here in that the
881 // larger the selection the slower LyX will get. This can be
882 // solved by running the line below only when the selection has
883 // finished. The solution used currently just works, to make it
884 // faster we need to be more clever and probably also have more
885 // calls to stuffClipboard. (Lgb)
886 bv()->stuffClipboard(cur.selectionAsString(true));
888 // This doesn't make sense, if there is no selection
889 if (!cur.selection())
892 // OK, we have a selection. This is always between cur.selBegin()
895 // make sure that the depth behind the selection are restored, too
896 recordUndoSelection(cur);
897 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
898 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
899 ParagraphList::iterator undopit = undoSpan(endpit);
901 int endpos = cur.selEnd().pos();
903 BufferParams const & bufparams = bv()->buffer()->params();
904 boost::tie(endpit, endpos) = realcut ?
905 CutAndPaste::cutSelection(bufparams,
908 cur.selBegin().pos(), endpos,
911 : CutAndPaste::eraseSelection(bufparams,
914 cur.selBegin().pos(), endpos,
916 // sometimes necessary
918 begpit->stripLeadingSpaces();
920 redoParagraphs(begpit, undopit);
921 // cutSelection can invalidate the cursor so we need to set
923 // we prefer the end for when tracking changes
925 cur.par() = parOffset(endpit);
927 // need a valid cursor. (Lgb)
928 cur.clearSelection();
933 void LyXText::copySelection()
935 LCursor & cur = bv()->cursor();
936 // stuff the selection onto the X clipboard, from an explicit copy request
937 bv()->stuffClipboard(cur.selectionAsString(true));
939 // this doesnt make sense, if there is no selection
940 if (!cur.selection())
943 // ok we have a selection. This is always between cur.selBegin()
944 // and sel_end cursor
946 // copy behind a space if there is one
947 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
948 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
949 && (cur.selBegin().par() != cur.selEnd().par()
950 || cur.selBegin().pos() < cur.selEnd().pos()))
951 cur.selBegin().pos(cur.selBegin().pos() + 1);
953 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
954 getPar(cur.selEnd().par()),
955 cur.selBegin().pos(),
957 bv()->buffer()->params().textclass);
961 void LyXText::pasteSelection(LCursor & cur, size_t sel_index)
963 // this does not make sense, if there is nothing to paste
964 if (!CutAndPaste::checkPastePossible())
969 ParagraphList::iterator endpit;
974 boost::tie(ppp, endpit) =
975 CutAndPaste::pasteSelection(*bv()->buffer(),
977 cursorPar(), cursor().pos(),
978 bv()->buffer()->params().textclass,
980 bufferErrors(*bv()->buffer(), el);
981 bv()->showErrorList(_("Paste"));
983 redoParagraphs(cursorPar(), endpit);
985 cur.clearSelection();
987 setCursor(parOffset(ppp.first), ppp.second);
993 void LyXText::setSelectionRange(LCursor & cur, lyx::pos_type length)
999 cursorRight(cur, true);
1004 // simple replacing. The font of the first selected character is used
1005 void LyXText::replaceSelectionWithString(LCursor & cur, string const & str)
1010 // Get font setting before we cut
1011 pos_type pos = cur.selEnd().pos();
1012 LyXFont const font = getPar(cur.selBegin())
1013 ->getFontSettings(bv()->buffer()->params(),
1014 cur.selBegin().pos());
1016 // Insert the new string
1017 string::const_iterator cit = str.begin();
1018 string::const_iterator end = str.end();
1019 for (; cit != end; ++cit) {
1020 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1024 // Cut the selection
1025 cutSelection(true, false);
1031 // needed to insert the selection
1032 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
1034 ParagraphList::iterator pit = getPar(cur.par());
1035 ParagraphList::iterator endpit = boost::next(pit);
1036 pos_type pos = cursor().pos();
1039 // only to be sure, should not be neccessary
1040 cur.clearSelection();
1041 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1043 redoParagraphs(getPar(cur.par()), endpit);
1045 setCursor(cur.par(), pos);
1050 // turn double CR to single CR, others are converted into one
1051 // blank. Then insertStringAsLines is called
1052 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
1054 string linestr = str;
1055 bool newline_inserted = false;
1057 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
1058 if (linestr[i] == '\n') {
1059 if (newline_inserted) {
1060 // we know that \r will be ignored by
1061 // insertStringAsLines. Of course, it is a dirty
1062 // trick, but it works...
1063 linestr[i - 1] = '\r';
1067 newline_inserted = true;
1069 } else if (IsPrintable(linestr[i])) {
1070 newline_inserted = false;
1073 insertStringAsLines(cur, linestr);
1077 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1080 CursorSlice old_cursor = cursor();
1081 setCursorIntern(par, pos, setfont, boundary);
1082 return deleteEmptyParagraphMechanism(old_cursor);
1086 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1087 pos_type pos, bool boundary)
1089 BOOST_ASSERT(par != int(paragraphs().size()));
1093 cur.boundary(boundary);
1095 // no rows, no fun...
1096 if (paragraphs().begin()->rows.empty())
1099 // now some strict checking
1100 Paragraph & para = *getPar(par);
1101 Row const & row = *para.getRow(pos);
1102 pos_type const end = row.endpos();
1104 // None of these should happen, but we're scaredy-cats
1106 lyxerr << "dont like -1" << endl;
1109 BOOST_ASSERT(false);
1110 } else if (pos > para.size()) {
1111 lyxerr << "dont like 1, pos: " << pos
1112 << " size: " << para.size()
1113 << " row.pos():" << row.pos()
1114 << " paroffset: " << par << endl;
1117 BOOST_ASSERT(false);
1118 } else if (pos > end) {
1119 lyxerr << "dont like 2 please report" << endl;
1120 // This shouldn't happen.
1123 BOOST_ASSERT(false);
1124 } else if (pos < row.pos()) {
1125 lyxerr << "dont like 3 please report pos:" << pos
1126 << " size: " << para.size()
1127 << " row.pos():" << row.pos()
1128 << " paroffset: " << par << endl;
1131 BOOST_ASSERT(false);
1136 void LyXText::setCursorIntern(paroffset_type par,
1137 pos_type pos, bool setfont, bool boundary)
1139 setCursor(cursor(), par, pos, boundary);
1140 bv()->cursor().x_target() = cursorX(cursor());
1146 void LyXText::setCurrentFont()
1148 LCursor & cur = bv()->cursor();
1149 pos_type pos = cur.pos();
1150 ParagraphList::iterator pit = cursorPar();
1152 if (cursor().boundary() && pos > 0)
1156 if (pos == pit->size())
1158 else // potentional bug... BUG (Lgb)
1159 if (pit->isSeparator(pos)) {
1160 if (pos > pit->getRow(pos)->pos() &&
1161 bidi.level(pos) % 2 ==
1162 bidi.level(pos - 1) % 2)
1164 else if (pos + 1 < pit->size())
1169 BufferParams const & bufparams = bv()->buffer()->params();
1170 current_font = pit->getFontSettings(bufparams, pos);
1171 real_current_font = getFont(pit, pos);
1173 if (cursor().pos() == pit->size() &&
1174 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1175 !cursor().boundary()) {
1176 Language const * lang =
1177 pit->getParLanguage(bufparams);
1178 current_font.setLanguage(lang);
1179 current_font.setNumber(LyXFont::OFF);
1180 real_current_font.setLanguage(lang);
1181 real_current_font.setNumber(LyXFont::OFF);
1186 // x is an absolute screen coord
1187 // returns the column near the specified x-coordinate of the row
1188 // x is set to the real beginning of this column
1189 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1190 Row const & row, int & x, bool & boundary) const
1193 double tmpx = row.x();
1194 double fill_separator = row.fill_separator();
1195 double fill_hfill = row.fill_hfill();
1196 double fill_label_hfill = row.fill_label_hfill();
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();
1206 double last_tmpx = tmpx;
1209 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1212 // check for empty row
1214 x = int(tmpx) + xo_;
1218 while (vc < end && tmpx <= x) {
1219 c = bidi.vis2log(vc);
1221 if (body_pos > 0 && c == body_pos - 1) {
1222 tmpx += fill_label_hfill +
1223 font_metrics::width(layout->labelsep, getLabelFont(pit));
1224 if (pit->isLineSeparator(body_pos - 1))
1225 tmpx -= singleWidth(pit, body_pos - 1);
1228 if (hfillExpansion(*pit, row, c)) {
1229 tmpx += singleWidth(pit, c);
1233 tmpx += fill_label_hfill;
1234 } else if (pit->isSeparator(c)) {
1235 tmpx += singleWidth(pit, c);
1237 tmpx += fill_separator;
1239 tmpx += singleWidth(pit, c);
1244 if ((tmpx + last_tmpx) / 2 > x) {
1249 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1252 // This (rtl_support test) is not needed, but gives
1253 // some speedup if rtl_support == false
1254 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1256 // If lastrow is false, we don't need to compute
1257 // the value of rtl.
1258 bool const rtl = lastrow
1259 ? pit->isRightToLeftPar(bv()->buffer()->params())
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 void LyXText::setCursorFromCoordinates(int x, int y)
1293 CursorSlice old_cursor = cursor();
1294 setCursorFromCoordinates(cursor(), x, y);
1296 deleteEmptyParagraphMechanism(old_cursor);
1300 // x,y are coordinates relative to this LyXText
1301 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1303 ParagraphList::iterator pit;
1304 Row const & row = *getRowNearY(y, pit);
1306 int xx = x + xo_; // getRowNearX get absolute x coords
1307 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1308 cur.par() = parOffset(pit);
1310 cur.boundary() = bound;
1314 // x,y are absolute screen coordinates
1315 void LyXText::edit(LCursor & cur, int x, int y)
1317 ParagraphList::iterator pit;
1318 Row const & row = *getRowNearY(y - yo_, pit);
1321 int xx = x; // is modified by getColumnNearX
1322 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1323 cur.par() = parOffset(pit);
1325 cur.boundary() = bound;
1327 // try to descend into nested insets
1328 InsetBase * inset = checkInsetHit(x, y);
1330 // This should be just before or just behind the
1331 // cursor position set above.
1332 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1333 || inset == pit->getInset(pos));
1334 // Make sure the cursor points to the position before
1336 if (inset == pit->getInset(pos - 1))
1338 inset->edit(cur, x, y);
1343 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1345 if (cur.pos() == cur.lastpos())
1347 InsetBase * inset = cur.nextInset();
1348 if (!isHighlyEditableInset(inset))
1350 inset->edit(cur, front);
1355 DispatchResult LyXText::moveRight(LCursor & cur)
1357 if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1358 return moveLeftIntern(cur, false, true, false);
1360 return moveRightIntern(cur, true, true, false);
1364 DispatchResult LyXText::moveLeft(LCursor & cur)
1366 if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1367 return moveRightIntern(cur, true, true, false);
1369 return moveLeftIntern(cur, false, true, false);
1373 DispatchResult LyXText::moveRightIntern(LCursor & cur,
1374 bool front, bool activate_inset, bool selecting)
1376 if (cur.par() == cur.lastpar() && cur.pos() == cur.lastpos())
1377 return DispatchResult(false, FINISHED_RIGHT);
1378 if (activate_inset && checkAndActivateInset(cur, front))
1379 return DispatchResult(true, true);
1380 cursorRight(cur, true);
1382 cur.clearSelection();
1383 return DispatchResult(true);
1387 DispatchResult LyXText::moveLeftIntern(LCursor & cur,
1388 bool front, bool activate_inset, bool selecting)
1390 if (cur.par() == 0 && cur.pos() == 0)
1391 return DispatchResult(false, FINISHED);
1392 cursorLeft(cur, true);
1394 cur.clearSelection();
1395 if (activate_inset && checkAndActivateInset(cur, front))
1396 return DispatchResult(true, true);
1397 return DispatchResult(true);
1401 DispatchResult LyXText::moveUp(LCursor & cur)
1403 if (cur.par() == 0 && cur.row() == 0)
1404 return DispatchResult(false, FINISHED_UP);
1405 cursorUp(cur, false);
1406 cur.clearSelection();
1407 return DispatchResult(true);
1411 DispatchResult LyXText::moveDown(LCursor & cur)
1413 if (cur.par() == cur.lastpar() && cur.textRow().endpos() == cur.lastpos())
1414 return DispatchResult(false, FINISHED_DOWN);
1415 cursorDown(cur, false);
1416 cur.clearSelection();
1417 return DispatchResult(true);
1421 bool LyXText::cursorLeft(LCursor & cur, bool internal)
1423 if (cur.pos() != 0) {
1424 bool boundary = cur.boundary();
1425 setCursor(cur.par(), cur.pos() - 1, true, false);
1426 if (!internal && !boundary &&
1427 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1428 setCursor(cur.par(), cur.pos() + 1, true, true);
1432 if (cur.par() != 0) {
1433 // steps into the paragraph above
1434 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1442 bool LyXText::cursorRight(LCursor & cur, bool internal)
1444 if (!internal && cur.boundary()) {
1445 setCursor(cur.par(), cur.pos(), true, false);
1449 if (cur.pos() != cur.lastpos()) {
1450 setCursor(cur.par(), cur.pos() + 1, true, false);
1451 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1453 setCursor(cur.par(), cur.pos(), true, true);
1457 if (cur.par() != cur.lastpar()) {
1458 setCursor(cur.par() + 1, 0);
1466 void LyXText::cursorUp(LCursor & cur, bool selecting)
1468 Row const & row = cur.textRow();
1469 int x = cur.x_target();
1470 int y = cursorY(cur.current()) - row.baseline() - 1;
1471 setCursorFromCoordinates(x, y);
1474 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1475 if (inset_hit && isHighlyEditableInset(inset_hit))
1476 inset_hit->edit(cur, cur.x_target(), y);
1481 void LyXText::cursorDown(LCursor & cur, bool selecting)
1483 Row const & row = cur.textRow();
1484 int x = cur.x_target();
1485 int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1486 setCursorFromCoordinates(cur.current(), x, y);
1489 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1490 if (inset_hit && isHighlyEditableInset(inset_hit))
1491 inset_hit->edit(cur, cur.x_target(), y);
1496 void LyXText::cursorUpParagraph(LCursor & cur)
1499 setCursor(cur.par(), 0);
1500 else if (cur.par() != 0)
1501 setCursor(cur.par() - 1, 0);
1505 void LyXText::cursorDownParagraph(LCursor & cur)
1507 if (cur.par() != cur.lastpar())
1508 setCursor(cur.par() + 1, 0);
1510 setCursor(cur.par(), cur.lastpos());
1514 // fix the cursor `cur' after a characters has been deleted at `where'
1515 // position. Called by deleteEmptyParagraphMechanism
1516 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1518 // if cursor is not in the paragraph where the delete occured,
1520 if (cur.par() != where.par())
1523 // if cursor position is after the place where the delete occured,
1525 if (cur.pos() > where.pos())
1528 // check also if we don't want to set the cursor on a spot behind the
1529 // pagragraph because we erased the last character.
1530 if (cur.pos() > cur.lastpos())
1531 cur.pos() = cur.lastpos();
1535 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1537 #warning Disabled as it crashes after the cursor data shift... (Andre)
1540 // Would be wrong to delete anything if we have a selection.
1541 if (bv()->cursor().selection())
1544 // Don't do anything if the cursor is invalid
1545 if (old_cursor.par() == -1)
1549 // We allow all kinds of "mumbo-jumbo" when freespacing.
1550 ParagraphList::iterator const old_pit = getPar(old_cursor);
1551 if (old_pit->isFreeSpacing())
1554 /* Ok I'll put some comments here about what is missing.
1555 I have fixed BackSpace (and thus Delete) to not delete
1556 double-spaces automagically. I have also changed Cut,
1557 Copy and Paste to hopefully do some sensible things.
1558 There are still some small problems that can lead to
1559 double spaces stored in the document file or space at
1560 the beginning of paragraphs(). This happens if you have
1561 the cursor between to spaces and then save. Or if you
1562 cut and paste and the selection have a space at the
1563 beginning and then save right after the paste. I am
1564 sure none of these are very hard to fix, but I will
1565 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1566 that I can get some feedback. (Lgb)
1569 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1570 // delete the LineSeparator.
1573 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1574 // delete the LineSeparator.
1577 // If the pos around the old_cursor were spaces, delete one of them.
1578 if (old_cursor.par() != cursor().par()
1579 || old_cursor.pos() != cursor().pos()) {
1581 // Only if the cursor has really moved
1582 if (old_cursor.pos() > 0
1583 && old_cursor.pos() < old_pit->size()
1584 && old_pit->isLineSeparator(old_cursor.pos())
1585 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1586 bool erased = old_pit->erase(old_cursor.pos() - 1);
1587 redoParagraph(old_pit);
1591 #ifdef WITH_WARNINGS
1592 #warning This will not work anymore when we have multiple views of the same buffer
1593 // In this case, we will have to correct also the cursors held by
1594 // other bufferviews. It will probably be easier to do that in a more
1595 // automated way in CursorSlice code. (JMarc 26/09/2001)
1597 // correct all cursors held by the LyXText
1598 fixCursorAfterDelete(cursor(), old_cursor);
1599 fixCursorAfterDelete(anchor(), old_cursor);
1604 // don't delete anything if this is the ONLY paragraph!
1605 if (paragraphs().size() == 1)
1608 // Do not delete empty paragraphs with keepempty set.
1609 if (old_pit->allowEmpty())
1612 // only do our magic if we changed paragraph
1613 if (old_cursor.par() == cursor().par())
1616 // record if we have deleted a paragraph
1617 // we can't possibly have deleted a paragraph before this point
1618 bool deleted = false;
1620 if (old_pit->empty()
1621 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1622 // ok, we will delete something
1623 CursorSlice tmpcursor;
1627 bool selection_position_was_oldcursor_position =
1628 anchor().par() == old_cursor.par()
1629 && anchor().pos() == old_cursor.pos();
1631 tmpcursor = cursor();
1632 cursor() = old_cursor; // that undo can restore the right cursor position
1634 ParagraphList::iterator endpit = boost::next(old_pit);
1635 while (endpit != paragraphs().end() && endpit->getDepth())
1638 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1639 cursor() = tmpcursor;
1642 ParagraphList::iterator tmppit = cursorPar();
1644 paragraphs().erase(old_pit);
1645 // update cursor par offset
1646 cursor().par(parOffset(tmppit));
1649 if (selection_position_was_oldcursor_position) {
1650 // correct selection
1651 bv()->resetAnchor();
1658 if (old_pit->stripLeadingSpaces()) {
1659 redoParagraph(old_pit);
1660 bv()->resetAnchor();
1667 ParagraphList & LyXText::paragraphs() const
1669 return const_cast<ParagraphList &>(paragraphs_);
1673 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1675 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1679 void LyXText::recUndo(lyx::paroffset_type par) const
1681 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1685 bool LyXText::isInInset() const
1691 bool LyXText::toggleInset(LCursor & cur)
1693 InsetBase * inset = cur.nextInset();
1694 // is there an editable inset at cursor position?
1695 if (!isEditableInset(inset))
1697 cur.message(inset->editMessage());
1699 // do we want to keep this?? (JMarc)
1700 if (!isHighlyEditableInset(inset))
1703 if (inset->isOpen())
1711 int defaultRowHeight()
1713 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);