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 selection.cursor = cursor;
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 InsetOld * 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 InsetOld * 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 ParagraphList::iterator
272 LyXText::setLayout(ParagraphList::iterator start,
273 ParagraphList::iterator end,
274 string const & layout)
276 ParagraphList::iterator undopit = end;
277 ParagraphList::iterator pars_end = paragraphs().end();
279 while (undopit != pars_end && undopit->getDepth())
281 //because of parindets etc
282 if (undopit != pars_end)
284 recUndo(parOffset(start), parOffset(undopit) - 1);
286 BufferParams const & bufparams = bv()->buffer()->params();
287 LyXLayout_ptr const & lyxlayout =
288 bufparams.getLyXTextClass()[layout];
290 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
291 pit->applyLayout(lyxlayout);
292 makeFontEntriesLayoutSpecific(bufparams, *pit);
293 if (lyxlayout->margintype == MARGIN_MANUAL)
294 pit->setLabelWidthString(lyxlayout->labelstring());
301 // set layout over selection and make a total rebreak of those paragraphs
302 void LyXText::setLayout(string const & layout)
306 // special handling of new environment insets
307 BufferParams const & params = bv()->buffer()->params();
308 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
309 if (lyxlayout->is_environment) {
310 // move everything in a new environment inset
311 lyxerr << "setting layout " << layout << endl;
312 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
313 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
314 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
315 InsetOld * inset = new InsetEnvironment(params, layout);
316 if (bv()->insertInset(inset)) {
318 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
324 ParagraphList::iterator start = getPar(selection.start.par());
325 ParagraphList::iterator end = boost::next(getPar(selection.end.par()));
326 ParagraphList::iterator endpit = setLayout(start, end, layout);
328 redoParagraphs(start, endpit);
337 void getSelectionSpan(LyXText & text,
338 ParagraphList::iterator & beg,
339 ParagraphList::iterator & end)
341 if (!text.selection.set()) {
342 beg = text.cursorPar();
343 end = boost::next(beg);
345 beg = text.getPar(text.selection.start);
346 end = boost::next(text.getPar(text.selection.end));
351 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
352 Paragraph const & par,
355 if (par.layout()->labeltype == LABEL_BIBLIO)
357 int const depth = par.params().depth();
358 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
360 if (type == bv_funcs::DEC_DEPTH && depth > 0)
369 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
371 ParagraphList::iterator beg, end;
372 getSelectionSpan(*this, beg, end);
374 if (beg != paragraphs().begin())
375 max_depth = boost::prior(beg)->getMaxDepthAfter();
377 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
378 if (::changeDepthAllowed(type, *pit, max_depth))
380 max_depth = pit->getMaxDepthAfter();
386 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
388 ParagraphList::iterator beg, end;
389 getSelectionSpan(*this, beg, end);
391 recUndo(parOffset(beg), parOffset(end) - 1);
394 if (beg != paragraphs().begin())
395 max_depth = boost::prior(beg)->getMaxDepthAfter();
397 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
398 if (::changeDepthAllowed(type, *pit, max_depth)) {
399 int const depth = pit->params().depth();
400 if (type == bv_funcs::INC_DEPTH)
401 pit->params().depth(depth + 1);
403 pit->params().depth(depth - 1);
405 max_depth = pit->getMaxDepthAfter();
407 // this handles the counter labels, and also fixes up
408 // depth values for follow-on (child) paragraphs
414 // set font over selection and make a total rebreak of those paragraphs
415 void LyXText::setFont(LyXFont const & font, bool toggleall)
417 // if there is no selection just set the current_font
418 if (!selection.set()) {
419 // Determine basis font
421 if (cursor.pos() < cursorPar()->beginOfBody())
422 layoutfont = getLabelFont(cursorPar());
424 layoutfont = getLayoutFont(cursorPar());
426 // Update current font
427 real_current_font.update(font,
428 bv()->buffer()->params().language,
431 // Reduce to implicit settings
432 current_font = real_current_font;
433 current_font.reduce(layoutfont);
434 // And resolve it completely
435 real_current_font.realize(layoutfont);
440 // ok we have a selection.
441 recUndo(selection.start.par(), selection.end.par());
444 ParagraphList::iterator beg = getPar(selection.start.par());
445 ParagraphList::iterator end = getPar(selection.end.par());
447 PosIterator pos(¶graphs(), beg, selection.start.pos());
448 PosIterator posend(¶graphs(), end, selection.end.pos());
450 BufferParams const & params = bv()->buffer()->params();
452 for (; pos != posend; ++pos) {
453 LyXFont f = getFont(pos.pit(), pos.pos());
454 f.update(font, params.language, toggleall);
455 setCharFont(pos.pit(), pos.pos(), f);
460 redoParagraphs(beg, ++end);
465 // important for the screen
468 // the cursor set functions have a special mechanism. When they
469 // realize, that you left an empty paragraph, they will delete it.
471 // need the selection cursor:
472 void LyXText::setSelection()
474 TextCursor::setSelection();
478 void LyXText::clearSelection()
480 TextCursor::clearSelection();
482 // reset this in the bv()!
483 if (bv() && bv()->text())
484 bv()->text()->xsel_cache.set(false);
488 void LyXText::cursorHome()
490 ParagraphList::iterator cpit = cursorPar();
491 setCursor(cpit, cpit->getRow(cursor.pos())->pos());
495 void LyXText::cursorEnd()
497 ParagraphList::iterator cpit = cursorPar();
498 pos_type end = cpit->getRow(cursor.pos())->endpos();
499 // if not on the last row of the par, put the cursor before
501 setCursor(cpit, end == cpit->size() ? end : end - 1);
505 void LyXText::cursorTop()
507 setCursor(paragraphs().begin(), 0);
511 void LyXText::cursorBottom()
513 ParagraphList::iterator lastpit =
514 boost::prior(paragraphs().end());
515 setCursor(lastpit, lastpit->size());
519 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
521 // If the mask is completely neutral, tell user
522 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
523 // Could only happen with user style
524 bv()->owner()->message(_("No font change defined. "
525 "Use Character under the Layout menu to define font change."));
529 // Try implicit word selection
530 // If there is a change in the language the implicit word selection
532 LyXCursor resetCursor = cursor;
533 bool implicitSelection =
534 font.language() == ignore_language
535 && font.number() == LyXFont::IGNORE
536 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
539 setFont(font, toggleall);
541 // Implicit selections are cleared afterwards
542 //and cursor is set to the original position.
543 if (implicitSelection) {
545 cursor = resetCursor;
546 setCursor(cursorPar(), cursor.pos());
547 selection.cursor = cursor;
552 string LyXText::getStringToIndex()
554 // Try implicit word selection
555 // If there is a change in the language the implicit word selection
557 LyXCursor const reset_cursor = cursor;
558 bool const implicitSelection =
559 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
562 if (!selection.set())
563 bv()->owner()->message(_("Nothing to index!"));
564 else if (selection.start.par() != selection.end.par())
565 bv()->owner()->message(_("Cannot index more than one paragraph!"));
567 idxstring = selectionAsString(*bv()->buffer(), false);
569 // Reset cursors to their original position.
570 cursor = reset_cursor;
571 setCursor(cursorPar(), cursor.pos());
572 selection.cursor = cursor;
574 // Clear the implicit selection.
575 if (implicitSelection)
582 // the DTP switches for paragraphs(). LyX will store them in the first
583 // physical paragraph. When a paragraph is broken, the top settings rest,
584 // the bottom settings are given to the new one. So I can make sure,
585 // they do not duplicate themself and you cannot play dirty tricks with
588 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
589 string const & labelwidthstring, bool noindent)
592 // make sure that the depth behind the selection are restored, too
593 ParagraphList::iterator endpit = boost::next(getPar(selection.end));
594 ParagraphList::iterator pars_end = paragraphs().end();
596 while (endpit != pars_end && endpit->getDepth())
598 // because of parindents etc.
599 if (endpit != pars_end)
602 recUndo(selection.start.par(), parOffset(endpit) - 1);
604 ParagraphList::reverse_iterator pit(getPar(selection.end.par()));
605 ParagraphList::reverse_iterator beg(getPar(selection.start.par()));
607 for (++beg; pit != beg; ++pit) {
608 ParagraphParameters & params = pit->params();
609 params.spacing(spacing);
611 // does the layout allow the new alignment?
612 LyXLayout_ptr const & layout = pit->layout();
614 if (align == LYX_ALIGN_LAYOUT)
615 align = layout->align;
616 if (align & layout->alignpossible) {
617 if (align == layout->align)
618 params.align(LYX_ALIGN_LAYOUT);
622 pit->setLabelWidthString(labelwidthstring);
623 params.noindent(noindent);
626 redoParagraphs(getPar(selection.start), endpit);
631 string expandLabel(LyXTextClass const & textclass,
632 LyXLayout_ptr const & layout, bool appendix)
634 string fmt = appendix ?
635 layout->labelstring_appendix() : layout->labelstring();
637 // handle 'inherited level parts' in 'fmt',
638 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
639 size_t const i = fmt.find('@', 0);
640 if (i != string::npos) {
641 size_t const j = fmt.find('@', i + 1);
642 if (j != string::npos) {
643 string parent(fmt, i + 1, j - i - 1);
644 string label = expandLabel(textclass, textclass[parent], appendix);
645 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
649 return textclass.counters().counterLabel(fmt);
655 void incrementItemDepth(ParagraphList::iterator pit,
656 ParagraphList::iterator first_pit)
658 int const cur_labeltype = pit->layout()->labeltype;
660 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
663 int const cur_depth = pit->getDepth();
665 ParagraphList::iterator prev_pit = boost::prior(pit);
667 int const prev_depth = prev_pit->getDepth();
668 int const prev_labeltype = prev_pit->layout()->labeltype;
669 if (prev_depth == 0 && cur_depth > 0) {
670 if (prev_labeltype == cur_labeltype) {
671 pit->itemdepth = prev_pit->itemdepth + 1;
674 } else if (prev_depth < cur_depth) {
675 if (prev_labeltype == cur_labeltype) {
676 pit->itemdepth = prev_pit->itemdepth + 1;
679 } else if (prev_depth == cur_depth) {
680 if (prev_labeltype == cur_labeltype) {
681 pit->itemdepth = prev_pit->itemdepth;
685 if (prev_pit == first_pit)
693 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
694 ParagraphList::iterator firstpit,
700 int const cur_depth = pit->getDepth();
701 ParagraphList::iterator prev_pit = boost::prior(pit);
703 int const prev_depth = prev_pit->getDepth();
704 int const prev_labeltype = prev_pit->layout()->labeltype;
705 if (prev_depth <= cur_depth) {
706 if (prev_labeltype != LABEL_ENUMERATE) {
707 switch (pit->itemdepth) {
709 counters.reset("enumi");
711 counters.reset("enumii");
713 counters.reset("enumiii");
715 counters.reset("enumiv");
721 if (prev_pit == firstpit)
731 // set the counter of a paragraph. This includes the labels
732 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
734 BufferParams const & bufparams = buf.params();
735 LyXTextClass const & textclass = bufparams.getLyXTextClass();
736 LyXLayout_ptr const & layout = pit->layout();
737 ParagraphList::iterator first_pit = paragraphs().begin();
738 Counters & counters = textclass.counters();
743 if (pit == first_pit) {
744 pit->params().appendix(pit->params().startOfAppendix());
746 pit->params().appendix(boost::prior(pit)->params().appendix());
747 if (!pit->params().appendix() &&
748 pit->params().startOfAppendix()) {
749 pit->params().appendix(true);
750 textclass.counters().reset();
753 // Maybe we have to increment the item depth.
754 incrementItemDepth(pit, first_pit);
757 // erase what was there before
758 pit->params().labelString(string());
760 if (layout->margintype == MARGIN_MANUAL) {
761 if (pit->params().labelWidthString().empty())
762 pit->setLabelWidthString(layout->labelstring());
764 pit->setLabelWidthString(string());
767 // is it a layout that has an automatic label?
768 if (layout->labeltype == LABEL_COUNTER) {
769 BufferParams const & bufparams = buf.params();
770 LyXTextClass const & textclass = bufparams.getLyXTextClass();
771 counters.step(layout->counter);
772 string label = expandLabel(textclass, layout, pit->params().appendix());
773 pit->params().labelString(label);
774 } else if (layout->labeltype == LABEL_ITEMIZE) {
775 // At some point of time we should do something more
776 // clever here, like:
777 // pit->params().labelString(
778 // bufparams.user_defined_bullet(pit->itemdepth).getText());
779 // for now, use a simple hardcoded label
781 switch (pit->itemdepth) {
796 pit->params().labelString(itemlabel);
797 } else if (layout->labeltype == LABEL_ENUMERATE) {
798 // Maybe we have to reset the enumeration counter.
799 resetEnumCounterIfNeeded(pit, first_pit, counters);
802 // Yes I know this is a really, really! bad solution
804 string enumcounter = "enum";
806 switch (pit->itemdepth) {
818 // not a valid enumdepth...
822 counters.step(enumcounter);
824 pit->params().labelString(counters.enumLabel(enumcounter));
825 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
826 counters.step("bibitem");
827 int number = counters.value("bibitem");
828 if (pit->bibitem()) {
829 pit->bibitem()->setCounter(number);
830 pit->params().labelString(layout->labelstring());
832 // In biblio should't be following counters but...
834 string s = buf.B_(layout->labelstring());
837 if (layout->labeltype == LABEL_SENSITIVE) {
838 ParagraphList::iterator end = paragraphs().end();
839 ParagraphList::iterator tmppit = pit;
842 while (tmppit != end && tmppit->inInset()
843 // the single '=' is intended below
844 && (in = tmppit->inInset()->owner()))
846 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
847 in->lyxCode() == InsetOld::WRAP_CODE) {
851 Paragraph const * owner = &ownerPar(buf, in);
853 for ( ; tmppit != end; ++tmppit)
854 if (&*tmppit == owner)
862 if (in->lyxCode() == InsetOld::FLOAT_CODE)
863 type = static_cast<InsetFloat*>(in)->params().type;
864 else if (in->lyxCode() == InsetOld::WRAP_CODE)
865 type = static_cast<InsetWrap*>(in)->params().type;
869 Floating const & fl = textclass.floats().getType(type);
871 counters.step(fl.type());
873 // Doesn't work... yet.
874 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
876 // par->SetLayout(0);
877 // s = layout->labelstring;
878 s = _("Senseless: ");
881 pit->params().labelString(s);
887 // Updates all counters.
888 void LyXText::updateCounters()
891 bv()->buffer()->params().getLyXTextClass().counters().reset();
893 bool update_pos = false;
895 ParagraphList::iterator beg = paragraphs().begin();
896 ParagraphList::iterator end = paragraphs().end();
897 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
898 string const oldLabel = pit->params().labelString();
901 maxdepth = boost::prior(pit)->getMaxDepthAfter();
903 if (pit->params().depth() > maxdepth)
904 pit->params().depth(maxdepth);
906 // setCounter can potentially change the labelString.
907 setCounter(*bv()->buffer(), pit);
908 string const & newLabel = pit->params().labelString();
909 if (oldLabel != newLabel) {
910 redoParagraphInternal(pit);
916 updateParPositions();
920 void LyXText::insertInset(InsetOld * inset)
922 if (!cursorPar()->insetAllowed(inset->lyxCode()))
925 recUndo(cursor.par());
927 cursorPar()->insertInset(cursor.pos(), inset);
928 // Just to rebreak and refresh correctly.
929 // The character will not be inserted a second time
930 insertChar(Paragraph::META_INSET);
931 // If we enter a highly editable inset the cursor should be before
932 // the inset. After an undo LyX tries to call inset->edit(...)
933 // and fails if the cursor is behind the inset and getInset
934 // does not return the inset!
935 if (isHighlyEditableInset(inset))
942 void LyXText::cutSelection(bool doclear, bool realcut)
944 // Stuff what we got on the clipboard. Even if there is no selection.
946 // There is a problem with having the stuffing here in that the
947 // larger the selection the slower LyX will get. This can be
948 // solved by running the line below only when the selection has
949 // finished. The solution used currently just works, to make it
950 // faster we need to be more clever and probably also have more
951 // calls to stuffClipboard. (Lgb)
952 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
954 // This doesn't make sense, if there is no selection
955 if (!selection.set())
958 // OK, we have a selection. This is always between selection.start
961 // make sure that the depth behind the selection are restored, too
962 ParagraphList::iterator begpit = getPar(selection.start.par());
963 ParagraphList::iterator endpit = getPar(selection.end.par());
964 ParagraphList::iterator undopit = boost::next(endpit);
965 ParagraphList::iterator pars_end = paragraphs().end();
967 while (undopit != pars_end && undopit->getDepth())
969 //because of parindents etc.
970 if (undopit != pars_end)
972 recUndo(selection.start.par(), parOffset(undopit) - 1);
974 int endpos = selection.end.pos();
976 BufferParams const & bufparams = bv()->buffer()->params();
977 boost::tie(endpit, endpos) = realcut ?
978 CutAndPaste::cutSelection(bufparams,
981 selection.start.pos(), endpos,
984 : CutAndPaste::eraseSelection(bufparams,
987 selection.start.pos(), endpos,
989 // sometimes necessary
991 begpit->stripLeadingSpaces();
993 redoParagraphs(begpit, undopit);
994 // cutSelection can invalidate the cursor so we need to set
996 // we prefer the end for when tracking changes
998 cursor.par(parOffset(endpit));
1000 // need a valid cursor. (Lgb)
1007 void LyXText::copySelection()
1009 // stuff the selection onto the X clipboard, from an explicit copy request
1010 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1012 // this doesnt make sense, if there is no selection
1013 if (!selection.set())
1016 // ok we have a selection. This is always between selection.start
1017 // and sel_end cursor
1019 // copy behind a space if there is one
1020 while (getPar(selection.start)->size() > selection.start.pos()
1021 && getPar(selection.start)->isLineSeparator(selection.start.pos())
1022 && (selection.start.par() != selection.end.par()
1023 || selection.start.pos() < selection.end.pos()))
1024 selection.start.pos(selection.start.pos() + 1);
1026 CutAndPaste::copySelection(getPar(selection.start.par()),
1027 getPar(selection.end.par()),
1028 selection.start.pos(), selection.end.pos(),
1029 bv()->buffer()->params().textclass);
1033 void LyXText::pasteSelection(size_t sel_index)
1035 // this does not make sense, if there is nothing to paste
1036 if (!CutAndPaste::checkPastePossible())
1039 recUndo(cursor.par());
1041 ParagraphList::iterator endpit;
1046 boost::tie(ppp, endpit) =
1047 CutAndPaste::pasteSelection(*bv()->buffer(),
1049 cursorPar(), cursor.pos(),
1050 bv()->buffer()->params().textclass,
1052 bufferErrors(*bv()->buffer(), el);
1053 bv()->showErrorList(_("Paste"));
1055 redoParagraphs(cursorPar(), endpit);
1057 setCursor(cursor.par(), cursor.pos());
1060 selection.cursor = cursor;
1061 setCursor(ppp.first, ppp.second);
1067 void LyXText::setSelectionRange(lyx::pos_type length)
1072 selection.cursor = cursor;
1079 // simple replacing. The font of the first selected character is used
1080 void LyXText::replaceSelectionWithString(string const & str)
1082 recUndo(cursor.par());
1085 if (!selection.set()) { // create a dummy selection
1086 selection.end = cursor;
1087 selection.start = cursor;
1090 // Get font setting before we cut
1091 pos_type pos = selection.end.pos();
1092 LyXFont const font = getPar(selection.start)
1093 ->getFontSettings(bv()->buffer()->params(),
1094 selection.start.pos());
1096 // Insert the new string
1097 string::const_iterator cit = str.begin();
1098 string::const_iterator end = str.end();
1099 for (; cit != end; ++cit) {
1100 getPar(selection.end)->insertChar(pos, (*cit), font);
1104 // Cut the selection
1105 cutSelection(true, false);
1111 // needed to insert the selection
1112 void LyXText::insertStringAsLines(string const & str)
1114 ParagraphList::iterator pit = cursorPar();
1115 pos_type pos = cursor.pos();
1116 ParagraphList::iterator endpit = boost::next(cursorPar());
1118 recUndo(cursor.par());
1120 // only to be sure, should not be neccessary
1123 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1125 redoParagraphs(cursorPar(), endpit);
1126 setCursor(cursorPar(), cursor.pos());
1127 selection.cursor = cursor;
1128 setCursor(pit, pos);
1133 // turn double CR to single CR, others are converted into one
1134 // blank. Then insertStringAsLines is called
1135 void LyXText::insertStringAsParagraphs(string const & str)
1137 string linestr(str);
1138 bool newline_inserted = false;
1139 string::size_type const siz = linestr.length();
1141 for (string::size_type i = 0; i < siz; ++i) {
1142 if (linestr[i] == '\n') {
1143 if (newline_inserted) {
1144 // we know that \r will be ignored by
1145 // insertStringAsLines. Of course, it is a dirty
1146 // trick, but it works...
1147 linestr[i - 1] = '\r';
1151 newline_inserted = true;
1153 } else if (IsPrintable(linestr[i])) {
1154 newline_inserted = false;
1157 insertStringAsLines(linestr);
1161 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1163 setCursor(parOffset(pit), pos);
1167 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1170 LyXCursor old_cursor = cursor;
1171 setCursorIntern(par, pos, setfont, boundary);
1172 return deleteEmptyParagraphMechanism(old_cursor);
1176 void LyXText::redoCursor()
1178 setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary());
1180 if (!selection.set())
1183 LyXCursor tmpcursor = cursor;
1184 setCursor(selection.cursor.par(), selection.cursor.pos());
1185 selection.cursor = cursor;
1186 setCursor(tmpcursor.par(), tmpcursor.pos());
1191 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1192 pos_type pos, bool boundary)
1194 BOOST_ASSERT(par != int(paragraphs().size()));
1198 cur.boundary(boundary);
1200 // no rows, no fun...
1201 if (paragraphs().begin()->rows.empty())
1204 // get the cursor y position in text
1206 ParagraphList::iterator pit = getPar(par);
1207 Row const & row = *pit->getRow(pos);
1209 int y = pit->y + row.y_offset();
1211 // y is now the beginning of the cursor row
1212 y += row.baseline();
1213 // y is now the cursor baseline
1216 pos_type const end = row.endpos();
1218 // None of these should happen, but we're scaredy-cats
1220 lyxerr << "dont like -1" << endl;
1223 BOOST_ASSERT(false);
1224 } else if (pos > pit->size()) {
1225 lyxerr << "dont like 1, pos: " << pos
1226 << " size: " << pit->size()
1227 << " row.pos():" << row.pos()
1228 << " paroffset: " << par << endl;
1231 BOOST_ASSERT(false);
1232 } else if (pos > end) {
1233 lyxerr << "dont like 2 please report" << endl;
1234 // This shouldn't happen.
1237 BOOST_ASSERT(false);
1238 } else if (pos < row.pos()) {
1239 lyxerr << "dont like 3 please report pos:" << pos
1240 << " size: " << pit->size()
1241 << " row.pos():" << row.pos()
1242 << " paroffset: " << par << endl;
1245 BOOST_ASSERT(false);
1247 // now get the cursors x position
1248 cur.x(int(getCursorX(pit, row, pos, boundary)));
1252 float LyXText::getCursorX(ParagraphList::iterator pit, Row const & row,
1253 pos_type pos, bool boundary) const
1255 pos_type cursor_vpos = 0;
1257 double fill_separator = row.fill_separator();
1258 double fill_hfill = row.fill_hfill();
1259 double fill_label_hfill = row.fill_label_hfill();
1260 pos_type const row_pos = row.pos();
1261 pos_type const end = row.endpos();
1264 cursor_vpos = row_pos;
1265 else if (pos >= end && !boundary)
1266 cursor_vpos = (pit->isRightToLeftPar(bv()->buffer()->params()))
1268 else if (pos > row_pos && (pos >= end || boundary))
1269 // Place cursor after char at (logical) position pos - 1
1270 cursor_vpos = (bidi.level(pos - 1) % 2 == 0)
1271 ? bidi.log2vis(pos - 1) + 1 : bidi.log2vis(pos - 1);
1273 // Place cursor before char at (logical) position pos
1274 cursor_vpos = (bidi.level(pos) % 2 == 0)
1275 ? bidi.log2vis(pos) : bidi.log2vis(pos) + 1;
1277 pos_type body_pos = pit->beginOfBody();
1279 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1282 for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) {
1283 pos_type pos = bidi.vis2log(vpos);
1284 if (body_pos > 0 && pos == body_pos - 1) {
1285 x += fill_label_hfill
1286 + font_metrics::width(pit->layout()->labelsep,
1288 if (pit->isLineSeparator(body_pos - 1))
1289 x -= singleWidth(pit, body_pos - 1);
1292 if (hfillExpansion(*pit, row, pos)) {
1293 x += singleWidth(pit, pos);
1294 if (pos >= body_pos)
1297 x += fill_label_hfill;
1298 } else if (pit->isSeparator(pos)) {
1299 x += singleWidth(pit, pos);
1300 if (pos >= body_pos)
1301 x += fill_separator;
1303 x += singleWidth(pit, pos);
1309 void LyXText::setCursorIntern(paroffset_type par,
1310 pos_type pos, bool setfont, bool boundary)
1312 setCursor(cursor, par, pos, boundary);
1313 bv()->x_target(cursor.x() + xo_);
1319 void LyXText::setCurrentFont()
1321 pos_type pos = cursor.pos();
1322 ParagraphList::iterator pit = cursorPar();
1324 if (cursor.boundary() && pos > 0)
1328 if (pos == pit->size())
1330 else // potentional bug... BUG (Lgb)
1331 if (pit->isSeparator(pos)) {
1332 if (pos > pit->getRow(pos)->pos() &&
1333 bidi.level(pos) % 2 ==
1334 bidi.level(pos - 1) % 2)
1336 else if (pos + 1 < pit->size())
1341 BufferParams const & bufparams = bv()->buffer()->params();
1342 current_font = pit->getFontSettings(bufparams, pos);
1343 real_current_font = getFont(pit, pos);
1345 if (cursor.pos() == pit->size() &&
1346 bidi.isBoundary(*bv()->buffer(), *pit, cursor.pos()) &&
1347 !cursor.boundary()) {
1348 Language const * lang =
1349 pit->getParLanguage(bufparams);
1350 current_font.setLanguage(lang);
1351 current_font.setNumber(LyXFont::OFF);
1352 real_current_font.setLanguage(lang);
1353 real_current_font.setNumber(LyXFont::OFF);
1358 // returns the column near the specified x-coordinate of the row
1359 // x is set to the real beginning of this column
1360 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1361 Row const & row, int & x, bool & boundary) const
1363 double tmpx = row.x();
1364 double fill_separator = row.fill_separator();
1365 double fill_hfill = row.fill_hfill();
1366 double fill_label_hfill = row.fill_label_hfill();
1368 pos_type vc = row.pos();
1369 pos_type end = row.endpos();
1371 LyXLayout_ptr const & layout = pit->layout();
1373 bool left_side = false;
1375 pos_type body_pos = pit->beginOfBody();
1376 double last_tmpx = tmpx;
1379 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1382 // check for empty row
1388 while (vc < end && tmpx <= x) {
1389 c = bidi.vis2log(vc);
1391 if (body_pos > 0 && c == body_pos - 1) {
1392 tmpx += fill_label_hfill +
1393 font_metrics::width(layout->labelsep, getLabelFont(pit));
1394 if (pit->isLineSeparator(body_pos - 1))
1395 tmpx -= singleWidth(pit, body_pos - 1);
1398 if (hfillExpansion(*pit, row, c)) {
1399 tmpx += singleWidth(pit, c);
1403 tmpx += fill_label_hfill;
1404 } else if (pit->isSeparator(c)) {
1405 tmpx += singleWidth(pit, c);
1407 tmpx += fill_separator;
1409 tmpx += singleWidth(pit, c);
1414 if ((tmpx + last_tmpx) / 2 > x) {
1419 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1422 // This (rtl_support test) is not needed, but gives
1423 // some speedup if rtl_support == false
1424 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1426 // If lastrow is false, we don't need to compute
1427 // the value of rtl.
1428 bool const rtl = (lastrow)
1429 ? pit->isRightToLeftPar(bv()->buffer()->params())
1432 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1433 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1435 else if (vc == row.pos()) {
1436 c = bidi.vis2log(vc);
1437 if (bidi.level(c) % 2 == 1)
1440 c = bidi.vis2log(vc - 1);
1441 bool const rtl = (bidi.level(c) % 2 == 1);
1442 if (left_side == rtl) {
1444 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1448 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1449 if (bidi.level(end -1) % 2 == 0)
1450 tmpx -= singleWidth(pit, end - 1);
1452 tmpx += singleWidth(pit, end - 1);
1462 void LyXText::setCursorFromCoordinates(int x, int y)
1464 LyXCursor old_cursor = cursor;
1465 setCursorFromCoordinates(cursor, x, y);
1467 deleteEmptyParagraphMechanism(old_cursor);
1470 // x,y are coordinates relative to this LyXText
1471 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1473 // Get the row first.
1474 ParagraphList::iterator pit;
1475 Row const & row = *getRowNearY(y, pit);
1476 y = pit->y + row.y_offset();
1479 pos_type const column = getColumnNearX(pit, row, x, bound);
1480 cur.par(parOffset(pit));
1481 cur.pos(row.pos() + column);
1483 cur.y(y + row.baseline());
1485 cur.boundary(bound);
1489 bool LyXText::checkAndActivateInset(bool front)
1491 if (cursor.pos() == cursorPar()->size())
1493 InsetOld * inset = cursorPar()->getInset(cursor.pos());
1494 if (!isHighlyEditableInset(inset))
1496 inset->edit(bv(), front);
1501 DispatchResult LyXText::moveRight()
1503 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1504 return moveLeftIntern(false, true, false);
1506 return moveRightIntern(true, true, false);
1510 DispatchResult LyXText::moveLeft()
1512 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1513 return moveRightIntern(true, true, false);
1515 return moveLeftIntern(false, true, false);
1519 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1521 ParagraphList::iterator c_par = cursorPar();
1522 if (boost::next(c_par) == paragraphs().end()
1523 && cursor.pos() >= c_par->size())
1524 return DispatchResult(false, FINISHED_RIGHT);
1525 if (activate_inset && checkAndActivateInset(front))
1526 return DispatchResult(true, true);
1530 return DispatchResult(true);
1534 DispatchResult LyXText::moveLeftIntern(bool front,
1535 bool activate_inset, bool selecting)
1537 if (cursor.par() == 0 && cursor.pos() <= 0)
1538 return DispatchResult(false, FINISHED);
1542 if (activate_inset && checkAndActivateInset(front))
1543 return DispatchResult(true, true);
1544 return DispatchResult(true);
1548 DispatchResult LyXText::moveUp()
1550 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1551 return DispatchResult(false, FINISHED_UP);
1554 return DispatchResult(true);
1558 DispatchResult LyXText::moveDown()
1560 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1561 return DispatchResult(false, FINISHED_DOWN);
1564 return DispatchResult(true);
1568 bool LyXText::cursorLeft(bool internal)
1570 if (cursor.pos() > 0) {
1571 bool boundary = cursor.boundary();
1572 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1573 if (!internal && !boundary &&
1574 bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1))
1575 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1579 if (cursor.par() != 0) {
1580 // steps into the paragraph above
1581 setCursor(cursor.par() - 1, boost::prior(cursorPar())->size());
1589 bool LyXText::cursorRight(bool internal)
1591 if (!internal && cursor.boundary()) {
1592 setCursor(cursor.par(), cursor.pos(), true, false);
1596 if (cursor.pos() != cursorPar()->size()) {
1597 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1598 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1600 setCursor(cursor.par(), cursor.pos(), true, true);
1604 if (cursor.par() + 1 != int(paragraphs().size())) {
1605 setCursor(cursor.par() + 1, 0);
1613 void LyXText::cursorUp(bool selecting)
1615 Row const & row = *cursorRow();
1616 int x = bv()->x_target() - xo_;
1617 int y = cursor.y() - row.baseline() - 1;
1618 setCursorFromCoordinates(x, y);
1621 int y_abs = y + yo_ - bv()->top_y();
1622 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1623 if (inset_hit && isHighlyEditableInset(inset_hit))
1624 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1629 void LyXText::cursorDown(bool selecting)
1631 Row const & row = *cursorRow();
1632 int x = bv()->x_target() - xo_;
1633 int y = cursor.y() - row.baseline() + row.height() + 1;
1634 setCursorFromCoordinates(x, y);
1637 int y_abs = y + yo_ - bv()->top_y();
1638 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1639 if (inset_hit && isHighlyEditableInset(inset_hit))
1640 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1645 void LyXText::cursorUpParagraph()
1647 ParagraphList::iterator cpit = cursorPar();
1648 if (cursor.pos() > 0)
1650 else if (cpit != paragraphs().begin())
1651 setCursor(boost::prior(cpit), 0);
1655 void LyXText::cursorDownParagraph()
1657 ParagraphList::iterator pit = cursorPar();
1658 ParagraphList::iterator next_pit = boost::next(pit);
1660 if (next_pit != paragraphs().end())
1661 setCursor(next_pit, 0);
1663 setCursor(pit, pit->size());
1667 // fix the cursor `cur' after a characters has been deleted at `where'
1668 // position. Called by deleteEmptyParagraphMechanism
1669 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1671 // if cursor is not in the paragraph where the delete occured,
1673 if (cur.par() != where.par())
1676 // if cursor position is after the place where the delete occured,
1678 if (cur.pos() > where.pos())
1679 cur.pos(cur.pos()-1);
1681 // check also if we don't want to set the cursor on a spot behind the
1682 // pagragraph because we erased the last character.
1683 if (cur.pos() > getPar(cur)->size())
1684 cur.pos(getPar(cur)->size());
1686 // recompute row et al. for this cursor
1687 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1691 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1693 // Would be wrong to delete anything if we have a selection.
1694 if (selection.set())
1697 // Don't do anything if the cursor is invalid
1698 if (old_cursor.par() == -1)
1701 // We allow all kinds of "mumbo-jumbo" when freespacing.
1702 ParagraphList::iterator const old_pit = getPar(old_cursor);
1703 if (old_pit->isFreeSpacing())
1706 /* Ok I'll put some comments here about what is missing.
1707 I have fixed BackSpace (and thus Delete) to not delete
1708 double-spaces automagically. I have also changed Cut,
1709 Copy and Paste to hopefully do some sensible things.
1710 There are still some small problems that can lead to
1711 double spaces stored in the document file or space at
1712 the beginning of paragraphs(). This happens if you have
1713 the cursor between to spaces and then save. Or if you
1714 cut and paste and the selection have a space at the
1715 beginning and then save right after the paste. I am
1716 sure none of these are very hard to fix, but I will
1717 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1718 that I can get some feedback. (Lgb)
1721 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1722 // delete the LineSeparator.
1725 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1726 // delete the LineSeparator.
1729 // If the pos around the old_cursor were spaces, delete one of them.
1730 if (old_cursor.par() != cursor.par()
1731 || old_cursor.pos() != cursor.pos()) {
1733 // Only if the cursor has really moved
1734 if (old_cursor.pos() > 0
1735 && old_cursor.pos() < old_pit->size()
1736 && old_pit->isLineSeparator(old_cursor.pos())
1737 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1738 bool erased = old_pit->erase(old_cursor.pos() - 1);
1739 redoParagraph(old_pit);
1743 #ifdef WITH_WARNINGS
1744 #warning This will not work anymore when we have multiple views of the same buffer
1745 // In this case, we will have to correct also the cursors held by
1746 // other bufferviews. It will probably be easier to do that in a more
1747 // automated way in LyXCursor code. (JMarc 26/09/2001)
1749 // correct all cursors held by the LyXText
1750 fixCursorAfterDelete(cursor, old_cursor);
1751 fixCursorAfterDelete(selection.cursor, old_cursor);
1752 fixCursorAfterDelete(selection.start, old_cursor);
1753 fixCursorAfterDelete(selection.end, old_cursor);
1758 // don't delete anything if this is the ONLY paragraph!
1759 if (paragraphs().size() == 1)
1762 // Do not delete empty paragraphs with keepempty set.
1763 if (old_pit->allowEmpty())
1766 // only do our magic if we changed paragraph
1767 if (old_cursor.par() == cursor.par())
1770 // record if we have deleted a paragraph
1771 // we can't possibly have deleted a paragraph before this point
1772 bool deleted = false;
1774 if (old_pit->empty()
1775 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1776 // ok, we will delete something
1777 LyXCursor tmpcursor;
1781 bool selection_position_was_oldcursor_position =
1782 selection.cursor.par() == old_cursor.par()
1783 && selection.cursor.pos() == old_cursor.pos();
1786 cursor = old_cursor; // that undo can restore the right cursor position
1788 ParagraphList::iterator endpit = boost::next(old_pit);
1789 while (endpit != paragraphs().end() && endpit->getDepth())
1792 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1796 ParagraphList::iterator tmppit = cursorPar();
1798 paragraphs().erase(old_pit);
1799 // update cursor par offset
1800 cursor.par(parOffset(tmppit));
1804 setCursorIntern(cursor.par(), cursor.pos());
1806 if (selection_position_was_oldcursor_position) {
1807 // correct selection
1808 selection.cursor = cursor;
1815 if (old_pit->stripLeadingSpaces()) {
1816 redoParagraph(old_pit);
1818 setCursorIntern(cursor.par(), cursor.pos());
1819 selection.cursor = cursor;
1825 ParagraphList & LyXText::paragraphs() const
1827 return const_cast<ParagraphList &>(paragraphs_);
1831 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1833 recordUndo(Undo::ATOMIC, this, first, last);
1837 void LyXText::recUndo(lyx::paroffset_type par) const
1839 recordUndo(Undo::ATOMIC, this, par, par);
1843 bool LyXText::isInInset() const
1849 int defaultRowHeight()
1851 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);