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)
304 // special handling of new environment insets
305 BufferParams const & params = bv()->buffer()->params();
306 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
307 if (lyxlayout->is_environment) {
308 // move everything in a new environment inset
309 lyxerr << "setting layout " << layout << endl;
310 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
311 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
312 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
313 InsetOld * inset = new InsetEnvironment(params, layout);
314 if (bv()->insertInset(inset)) {
316 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
322 ParagraphList::iterator start = getPar(selStart().par());
323 ParagraphList::iterator end = boost::next(getPar(selEnd().par()));
324 ParagraphList::iterator endpit = setLayout(start, end, layout);
326 redoParagraphs(start, endpit);
335 void getSelectionSpan(LyXText & text,
336 ParagraphList::iterator & beg,
337 ParagraphList::iterator & end)
339 if (!text.selection.set()) {
340 beg = text.cursorPar();
341 end = boost::next(beg);
343 beg = text.getPar(text.selStart());
344 end = boost::next(text.getPar(text.selEnd()));
349 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
350 Paragraph const & par,
353 if (par.layout()->labeltype == LABEL_BIBLIO)
355 int const depth = par.params().depth();
356 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
358 if (type == bv_funcs::DEC_DEPTH && depth > 0)
367 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
369 ParagraphList::iterator beg, end;
370 getSelectionSpan(*this, beg, end);
372 if (beg != paragraphs().begin())
373 max_depth = boost::prior(beg)->getMaxDepthAfter();
375 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
376 if (::changeDepthAllowed(type, *pit, max_depth))
378 max_depth = pit->getMaxDepthAfter();
384 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
386 ParagraphList::iterator beg, end;
387 getSelectionSpan(*this, beg, end);
389 recUndo(parOffset(beg), parOffset(end) - 1);
392 if (beg != paragraphs().begin())
393 max_depth = boost::prior(beg)->getMaxDepthAfter();
395 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
396 if (::changeDepthAllowed(type, *pit, max_depth)) {
397 int const depth = pit->params().depth();
398 if (type == bv_funcs::INC_DEPTH)
399 pit->params().depth(depth + 1);
401 pit->params().depth(depth - 1);
403 max_depth = pit->getMaxDepthAfter();
405 // this handles the counter labels, and also fixes up
406 // depth values for follow-on (child) paragraphs
412 // set font over selection and make a total rebreak of those paragraphs
413 void LyXText::setFont(LyXFont const & font, bool toggleall)
415 // if there is no selection just set the current_font
416 if (!selection.set()) {
417 // Determine basis font
419 if (cursor.pos() < cursorPar()->beginOfBody())
420 layoutfont = getLabelFont(cursorPar());
422 layoutfont = getLayoutFont(cursorPar());
424 // Update current font
425 real_current_font.update(font,
426 bv()->buffer()->params().language,
429 // Reduce to implicit settings
430 current_font = real_current_font;
431 current_font.reduce(layoutfont);
432 // And resolve it completely
433 real_current_font.realize(layoutfont);
438 // ok we have a selection.
439 recUndo(selStart().par(), selEnd().par());
442 ParagraphList::iterator beg = getPar(selStart().par());
443 ParagraphList::iterator end = getPar(selEnd().par());
445 PosIterator pos(¶graphs(), beg, selStart().pos());
446 PosIterator posend(¶graphs(), end, selEnd().pos());
448 BufferParams const & params = bv()->buffer()->params();
450 for (; pos != posend; ++pos) {
451 LyXFont f = getFont(pos.pit(), pos.pos());
452 f.update(font, params.language, toggleall);
453 setCharFont(pos.pit(), pos.pos(), f);
458 redoParagraphs(beg, ++end);
463 // important for the screen
466 // the cursor set functions have a special mechanism. When they
467 // realize, that you left an empty paragraph, they will delete it.
469 // need the selection cursor:
470 void LyXText::setSelection()
472 TextCursor::setSelection();
476 void LyXText::clearSelection()
478 TextCursor::clearSelection();
480 // reset this in the bv()!
481 if (bv() && bv()->text())
486 void LyXText::cursorHome()
488 ParagraphList::iterator cpit = cursorPar();
489 setCursor(cpit, cpit->getRow(cursor.pos())->pos());
493 void LyXText::cursorEnd()
495 ParagraphList::iterator cpit = cursorPar();
496 pos_type end = cpit->getRow(cursor.pos())->endpos();
497 // if not on the last row of the par, put the cursor before
499 setCursor(cpit, end == cpit->size() ? end : end - 1);
503 void LyXText::cursorTop()
505 setCursor(paragraphs().begin(), 0);
509 void LyXText::cursorBottom()
511 ParagraphList::iterator lastpit =
512 boost::prior(paragraphs().end());
513 setCursor(lastpit, lastpit->size());
517 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
519 // If the mask is completely neutral, tell user
520 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
521 // Could only happen with user style
522 bv()->owner()->message(_("No font change defined. "
523 "Use Character under the Layout menu to define font change."));
527 // Try implicit word selection
528 // If there is a change in the language the implicit word selection
530 LyXCursor resetCursor = cursor;
531 bool implicitSelection =
532 font.language() == ignore_language
533 && font.number() == LyXFont::IGNORE
534 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
537 setFont(font, toggleall);
539 // Implicit selections are cleared afterwards
540 //and cursor is set to the original position.
541 if (implicitSelection) {
543 cursor = resetCursor;
544 setCursor(cursorPar(), cursor.pos());
545 selection.cursor = cursor;
550 string LyXText::getStringToIndex()
552 // Try implicit word selection
553 // If there is a change in the language the implicit word selection
555 LyXCursor const reset_cursor = cursor;
556 bool const implicitSelection =
557 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
560 if (!selection.set())
561 bv()->owner()->message(_("Nothing to index!"));
562 else if (selStart().par() != selEnd().par())
563 bv()->owner()->message(_("Cannot index more than one paragraph!"));
565 idxstring = selectionAsString(*bv()->buffer(), false);
567 // Reset cursors to their original position.
568 cursor = reset_cursor;
569 setCursor(cursorPar(), cursor.pos());
570 selection.cursor = cursor;
572 // Clear the implicit selection.
573 if (implicitSelection)
580 // the DTP switches for paragraphs(). LyX will store them in the first
581 // physical paragraph. When a paragraph is broken, the top settings rest,
582 // the bottom settings are given to the new one. So I can make sure,
583 // they do not duplicate themself and you cannot play dirty tricks with
586 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
587 string const & labelwidthstring, bool noindent)
589 // make sure that the depth behind the selection are restored, too
590 ParagraphList::iterator endpit = boost::next(getPar(selEnd()));
591 ParagraphList::iterator pars_end = paragraphs().end();
593 while (endpit != pars_end && endpit->getDepth())
595 // because of parindents etc.
596 if (endpit != pars_end)
599 recUndo(selStart().par(), parOffset(endpit) - 1);
601 ParagraphList::reverse_iterator pit(getPar(selEnd().par()));
602 ParagraphList::reverse_iterator beg(getPar(selStart().par()));
604 for (++beg; pit != beg; ++pit) {
605 ParagraphParameters & params = pit->params();
606 params.spacing(spacing);
608 // does the layout allow the new alignment?
609 LyXLayout_ptr const & layout = pit->layout();
611 if (align == LYX_ALIGN_LAYOUT)
612 align = layout->align;
613 if (align & layout->alignpossible) {
614 if (align == layout->align)
615 params.align(LYX_ALIGN_LAYOUT);
619 pit->setLabelWidthString(labelwidthstring);
620 params.noindent(noindent);
623 redoParagraphs(getPar(selStart()), endpit);
628 string expandLabel(LyXTextClass const & textclass,
629 LyXLayout_ptr const & layout, bool appendix)
631 string fmt = appendix ?
632 layout->labelstring_appendix() : layout->labelstring();
634 // handle 'inherited level parts' in 'fmt',
635 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
636 size_t const i = fmt.find('@', 0);
637 if (i != string::npos) {
638 size_t const j = fmt.find('@', i + 1);
639 if (j != string::npos) {
640 string parent(fmt, i + 1, j - i - 1);
641 string label = expandLabel(textclass, textclass[parent], appendix);
642 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
646 return textclass.counters().counterLabel(fmt);
652 void incrementItemDepth(ParagraphList::iterator pit,
653 ParagraphList::iterator first_pit)
655 int const cur_labeltype = pit->layout()->labeltype;
657 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
660 int const cur_depth = pit->getDepth();
662 ParagraphList::iterator prev_pit = boost::prior(pit);
664 int const prev_depth = prev_pit->getDepth();
665 int const prev_labeltype = prev_pit->layout()->labeltype;
666 if (prev_depth == 0 && cur_depth > 0) {
667 if (prev_labeltype == cur_labeltype) {
668 pit->itemdepth = prev_pit->itemdepth + 1;
671 } else if (prev_depth < cur_depth) {
672 if (prev_labeltype == cur_labeltype) {
673 pit->itemdepth = prev_pit->itemdepth + 1;
676 } else if (prev_depth == cur_depth) {
677 if (prev_labeltype == cur_labeltype) {
678 pit->itemdepth = prev_pit->itemdepth;
682 if (prev_pit == first_pit)
690 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
691 ParagraphList::iterator firstpit,
697 int const cur_depth = pit->getDepth();
698 ParagraphList::iterator prev_pit = boost::prior(pit);
700 int const prev_depth = prev_pit->getDepth();
701 int const prev_labeltype = prev_pit->layout()->labeltype;
702 if (prev_depth <= cur_depth) {
703 if (prev_labeltype != LABEL_ENUMERATE) {
704 switch (pit->itemdepth) {
706 counters.reset("enumi");
708 counters.reset("enumii");
710 counters.reset("enumiii");
712 counters.reset("enumiv");
718 if (prev_pit == firstpit)
728 // set the counter of a paragraph. This includes the labels
729 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
731 BufferParams const & bufparams = buf.params();
732 LyXTextClass const & textclass = bufparams.getLyXTextClass();
733 LyXLayout_ptr const & layout = pit->layout();
734 ParagraphList::iterator first_pit = paragraphs().begin();
735 Counters & counters = textclass.counters();
740 if (pit == first_pit) {
741 pit->params().appendix(pit->params().startOfAppendix());
743 pit->params().appendix(boost::prior(pit)->params().appendix());
744 if (!pit->params().appendix() &&
745 pit->params().startOfAppendix()) {
746 pit->params().appendix(true);
747 textclass.counters().reset();
750 // Maybe we have to increment the item depth.
751 incrementItemDepth(pit, first_pit);
754 // erase what was there before
755 pit->params().labelString(string());
757 if (layout->margintype == MARGIN_MANUAL) {
758 if (pit->params().labelWidthString().empty())
759 pit->setLabelWidthString(layout->labelstring());
761 pit->setLabelWidthString(string());
764 // is it a layout that has an automatic label?
765 if (layout->labeltype == LABEL_COUNTER) {
766 BufferParams const & bufparams = buf.params();
767 LyXTextClass const & textclass = bufparams.getLyXTextClass();
768 counters.step(layout->counter);
769 string label = expandLabel(textclass, layout, pit->params().appendix());
770 pit->params().labelString(label);
771 } else if (layout->labeltype == LABEL_ITEMIZE) {
772 // At some point of time we should do something more
773 // clever here, like:
774 // pit->params().labelString(
775 // bufparams.user_defined_bullet(pit->itemdepth).getText());
776 // for now, use a simple hardcoded label
778 switch (pit->itemdepth) {
793 pit->params().labelString(itemlabel);
794 } else if (layout->labeltype == LABEL_ENUMERATE) {
795 // Maybe we have to reset the enumeration counter.
796 resetEnumCounterIfNeeded(pit, first_pit, counters);
799 // Yes I know this is a really, really! bad solution
801 string enumcounter = "enum";
803 switch (pit->itemdepth) {
815 // not a valid enumdepth...
819 counters.step(enumcounter);
821 pit->params().labelString(counters.enumLabel(enumcounter));
822 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
823 counters.step("bibitem");
824 int number = counters.value("bibitem");
825 if (pit->bibitem()) {
826 pit->bibitem()->setCounter(number);
827 pit->params().labelString(layout->labelstring());
829 // In biblio should't be following counters but...
831 string s = buf.B_(layout->labelstring());
834 if (layout->labeltype == LABEL_SENSITIVE) {
835 ParagraphList::iterator end = paragraphs().end();
836 ParagraphList::iterator tmppit = pit;
839 while (tmppit != end && tmppit->inInset()
840 // the single '=' is intended below
841 && (in = tmppit->inInset()->owner()))
843 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
844 in->lyxCode() == InsetOld::WRAP_CODE) {
848 Paragraph const * owner = &ownerPar(buf, in);
850 for ( ; tmppit != end; ++tmppit)
851 if (&*tmppit == owner)
859 if (in->lyxCode() == InsetOld::FLOAT_CODE)
860 type = static_cast<InsetFloat*>(in)->params().type;
861 else if (in->lyxCode() == InsetOld::WRAP_CODE)
862 type = static_cast<InsetWrap*>(in)->params().type;
866 Floating const & fl = textclass.floats().getType(type);
868 counters.step(fl.type());
870 // Doesn't work... yet.
871 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
873 // par->SetLayout(0);
874 // s = layout->labelstring;
875 s = _("Senseless: ");
878 pit->params().labelString(s);
884 // Updates all counters.
885 void LyXText::updateCounters()
888 bv()->buffer()->params().getLyXTextClass().counters().reset();
890 bool update_pos = false;
892 ParagraphList::iterator beg = paragraphs().begin();
893 ParagraphList::iterator end = paragraphs().end();
894 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
895 string const oldLabel = pit->params().labelString();
898 maxdepth = boost::prior(pit)->getMaxDepthAfter();
900 if (pit->params().depth() > maxdepth)
901 pit->params().depth(maxdepth);
903 // setCounter can potentially change the labelString.
904 setCounter(*bv()->buffer(), pit);
905 string const & newLabel = pit->params().labelString();
906 if (oldLabel != newLabel) {
907 redoParagraphInternal(pit);
913 updateParPositions();
917 void LyXText::insertInset(InsetOld * inset)
919 if (!cursorPar()->insetAllowed(inset->lyxCode()))
922 recUndo(cursor.par());
924 cursorPar()->insertInset(cursor.pos(), inset);
925 // Just to rebreak and refresh correctly.
926 // The character will not be inserted a second time
927 insertChar(Paragraph::META_INSET);
928 // If we enter a highly editable inset the cursor should be before
929 // the inset. After an undo LyX tries to call inset->edit(...)
930 // and fails if the cursor is behind the inset and getInset
931 // does not return the inset!
932 if (isHighlyEditableInset(inset))
939 void LyXText::cutSelection(bool doclear, bool realcut)
941 // Stuff what we got on the clipboard. Even if there is no selection.
943 // There is a problem with having the stuffing here in that the
944 // larger the selection the slower LyX will get. This can be
945 // solved by running the line below only when the selection has
946 // finished. The solution used currently just works, to make it
947 // faster we need to be more clever and probably also have more
948 // calls to stuffClipboard. (Lgb)
949 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
951 // This doesn't make sense, if there is no selection
952 if (!selection.set())
955 // OK, we have a selection. This is always between selStart()
958 // make sure that the depth behind the selection are restored, too
959 ParagraphList::iterator begpit = getPar(selStart().par());
960 ParagraphList::iterator endpit = getPar(selEnd().par());
961 ParagraphList::iterator undopit = boost::next(endpit);
962 ParagraphList::iterator pars_end = paragraphs().end();
964 while (undopit != pars_end && undopit->getDepth())
966 //because of parindents etc.
967 if (undopit != pars_end)
969 recUndo(selStart().par(), parOffset(undopit) - 1);
971 int endpos = selEnd().pos();
973 BufferParams const & bufparams = bv()->buffer()->params();
974 boost::tie(endpit, endpos) = realcut ?
975 CutAndPaste::cutSelection(bufparams,
978 selStart().pos(), endpos,
981 : CutAndPaste::eraseSelection(bufparams,
984 selStart().pos(), endpos,
986 // sometimes necessary
988 begpit->stripLeadingSpaces();
990 redoParagraphs(begpit, undopit);
991 // cutSelection can invalidate the cursor so we need to set
993 // we prefer the end for when tracking changes
995 cursor.par(parOffset(endpit));
997 // need a valid cursor. (Lgb)
1004 void LyXText::copySelection()
1006 // stuff the selection onto the X clipboard, from an explicit copy request
1007 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1009 // this doesnt make sense, if there is no selection
1010 if (!selection.set())
1013 // ok we have a selection. This is always between selStart()
1014 // and sel_end cursor
1016 // copy behind a space if there is one
1017 while (getPar(selStart())->size() > selStart().pos()
1018 && getPar(selStart())->isLineSeparator(selStart().pos())
1019 && (selStart().par() != selEnd().par()
1020 || selStart().pos() < selEnd().pos()))
1021 selStart().pos(selStart().pos() + 1);
1023 CutAndPaste::copySelection(getPar(selStart().par()),
1024 getPar(selEnd().par()),
1027 bv()->buffer()->params().textclass);
1031 void LyXText::pasteSelection(size_t sel_index)
1033 // this does not make sense, if there is nothing to paste
1034 if (!CutAndPaste::checkPastePossible())
1037 recUndo(cursor.par());
1039 ParagraphList::iterator endpit;
1044 boost::tie(ppp, endpit) =
1045 CutAndPaste::pasteSelection(*bv()->buffer(),
1047 cursorPar(), cursor.pos(),
1048 bv()->buffer()->params().textclass,
1050 bufferErrors(*bv()->buffer(), el);
1051 bv()->showErrorList(_("Paste"));
1053 redoParagraphs(cursorPar(), endpit);
1055 setCursor(cursor.par(), cursor.pos());
1058 selection.cursor = cursor;
1059 setCursor(ppp.first, ppp.second);
1065 void LyXText::setSelectionRange(lyx::pos_type length)
1070 selection.cursor = cursor;
1077 // simple replacing. The font of the first selected character is used
1078 void LyXText::replaceSelectionWithString(string const & str)
1080 recUndo(cursor.par());
1083 // Get font setting before we cut
1084 pos_type pos = selEnd().pos();
1085 LyXFont const font = getPar(selStart())
1086 ->getFontSettings(bv()->buffer()->params(),
1089 // Insert the new string
1090 string::const_iterator cit = str.begin();
1091 string::const_iterator end = str.end();
1092 for (; cit != end; ++cit) {
1093 getPar(selEnd())->insertChar(pos, (*cit), font);
1097 // Cut the selection
1098 cutSelection(true, false);
1104 // needed to insert the selection
1105 void LyXText::insertStringAsLines(string const & str)
1107 ParagraphList::iterator pit = cursorPar();
1108 pos_type pos = cursor.pos();
1109 ParagraphList::iterator endpit = boost::next(cursorPar());
1111 recUndo(cursor.par());
1113 // only to be sure, should not be neccessary
1116 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1118 redoParagraphs(cursorPar(), endpit);
1119 setCursor(cursorPar(), cursor.pos());
1120 selection.cursor = cursor;
1121 setCursor(pit, pos);
1126 // turn double CR to single CR, others are converted into one
1127 // blank. Then insertStringAsLines is called
1128 void LyXText::insertStringAsParagraphs(string const & str)
1130 string linestr(str);
1131 bool newline_inserted = false;
1132 string::size_type const siz = linestr.length();
1134 for (string::size_type i = 0; i < siz; ++i) {
1135 if (linestr[i] == '\n') {
1136 if (newline_inserted) {
1137 // we know that \r will be ignored by
1138 // insertStringAsLines. Of course, it is a dirty
1139 // trick, but it works...
1140 linestr[i - 1] = '\r';
1144 newline_inserted = true;
1146 } else if (IsPrintable(linestr[i])) {
1147 newline_inserted = false;
1150 insertStringAsLines(linestr);
1154 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1156 setCursor(parOffset(pit), pos);
1160 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1163 LyXCursor old_cursor = cursor;
1164 setCursorIntern(par, pos, setfont, boundary);
1165 return deleteEmptyParagraphMechanism(old_cursor);
1169 void LyXText::redoCursor()
1171 setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary());
1173 if (!selection.set())
1176 LyXCursor tmpcursor = cursor;
1177 setCursor(selection.cursor.par(), selection.cursor.pos());
1178 selection.cursor = cursor;
1179 setCursor(tmpcursor.par(), tmpcursor.pos());
1184 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1185 pos_type pos, bool boundary)
1187 BOOST_ASSERT(par != int(paragraphs().size()));
1191 cur.boundary(boundary);
1193 // no rows, no fun...
1194 if (paragraphs().begin()->rows.empty())
1197 // get the cursor y position in text
1199 ParagraphList::iterator pit = getPar(par);
1200 Row const & row = *pit->getRow(pos);
1201 pos_type const end = row.endpos();
1203 // None of these should happen, but we're scaredy-cats
1205 lyxerr << "dont like -1" << endl;
1208 BOOST_ASSERT(false);
1209 } else if (pos > pit->size()) {
1210 lyxerr << "dont like 1, pos: " << pos
1211 << " size: " << pit->size()
1212 << " row.pos():" << row.pos()
1213 << " paroffset: " << par << endl;
1216 BOOST_ASSERT(false);
1217 } else if (pos > end) {
1218 lyxerr << "dont like 2 please report" << endl;
1219 // This shouldn't happen.
1222 BOOST_ASSERT(false);
1223 } else if (pos < row.pos()) {
1224 lyxerr << "dont like 3 please report pos:" << pos
1225 << " size: " << pit->size()
1226 << " row.pos():" << row.pos()
1227 << " paroffset: " << par << endl;
1230 BOOST_ASSERT(false);
1235 void LyXText::setCursorIntern(paroffset_type par,
1236 pos_type pos, bool setfont, bool boundary)
1238 setCursor(cursor, par, pos, boundary);
1239 bv()->x_target(cursorX() + xo_);
1245 void LyXText::setCurrentFont()
1247 pos_type pos = cursor.pos();
1248 ParagraphList::iterator pit = cursorPar();
1250 if (cursor.boundary() && pos > 0)
1254 if (pos == pit->size())
1256 else // potentional bug... BUG (Lgb)
1257 if (pit->isSeparator(pos)) {
1258 if (pos > pit->getRow(pos)->pos() &&
1259 bidi.level(pos) % 2 ==
1260 bidi.level(pos - 1) % 2)
1262 else if (pos + 1 < pit->size())
1267 BufferParams const & bufparams = bv()->buffer()->params();
1268 current_font = pit->getFontSettings(bufparams, pos);
1269 real_current_font = getFont(pit, pos);
1271 if (cursor.pos() == pit->size() &&
1272 bidi.isBoundary(*bv()->buffer(), *pit, cursor.pos()) &&
1273 !cursor.boundary()) {
1274 Language const * lang =
1275 pit->getParLanguage(bufparams);
1276 current_font.setLanguage(lang);
1277 current_font.setNumber(LyXFont::OFF);
1278 real_current_font.setLanguage(lang);
1279 real_current_font.setNumber(LyXFont::OFF);
1284 // returns the column near the specified x-coordinate of the row
1285 // x is set to the real beginning of this column
1286 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1287 Row const & row, int & x, bool & boundary) const
1289 double tmpx = row.x();
1290 double fill_separator = row.fill_separator();
1291 double fill_hfill = row.fill_hfill();
1292 double fill_label_hfill = row.fill_label_hfill();
1294 pos_type vc = row.pos();
1295 pos_type end = row.endpos();
1297 LyXLayout_ptr const & layout = pit->layout();
1299 bool left_side = false;
1301 pos_type body_pos = pit->beginOfBody();
1302 double last_tmpx = tmpx;
1305 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1308 // check for empty row
1314 while (vc < end && tmpx <= x) {
1315 c = bidi.vis2log(vc);
1317 if (body_pos > 0 && c == body_pos - 1) {
1318 tmpx += fill_label_hfill +
1319 font_metrics::width(layout->labelsep, getLabelFont(pit));
1320 if (pit->isLineSeparator(body_pos - 1))
1321 tmpx -= singleWidth(pit, body_pos - 1);
1324 if (hfillExpansion(*pit, row, c)) {
1325 tmpx += singleWidth(pit, c);
1329 tmpx += fill_label_hfill;
1330 } else if (pit->isSeparator(c)) {
1331 tmpx += singleWidth(pit, c);
1333 tmpx += fill_separator;
1335 tmpx += singleWidth(pit, c);
1340 if ((tmpx + last_tmpx) / 2 > x) {
1345 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1348 // This (rtl_support test) is not needed, but gives
1349 // some speedup if rtl_support == false
1350 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1352 // If lastrow is false, we don't need to compute
1353 // the value of rtl.
1354 bool const rtl = (lastrow)
1355 ? pit->isRightToLeftPar(bv()->buffer()->params())
1358 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1359 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1361 else if (vc == row.pos()) {
1362 c = bidi.vis2log(vc);
1363 if (bidi.level(c) % 2 == 1)
1366 c = bidi.vis2log(vc - 1);
1367 bool const rtl = (bidi.level(c) % 2 == 1);
1368 if (left_side == rtl) {
1370 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1374 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1375 if (bidi.level(end -1) % 2 == 0)
1376 tmpx -= singleWidth(pit, end - 1);
1378 tmpx += singleWidth(pit, end - 1);
1388 void LyXText::setCursorFromCoordinates(int x, int y)
1390 LyXCursor old_cursor = cursor;
1391 setCursorFromCoordinates(cursor, x, y);
1393 deleteEmptyParagraphMechanism(old_cursor);
1397 // x,y are coordinates relative to this LyXText
1398 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1400 ParagraphList::iterator pit;
1401 Row const & row = *getRowNearY(y, pit);
1403 pos_type const column = getColumnNearX(pit, row, x, bound);
1404 cur.par(parOffset(pit));
1405 cur.pos(row.pos() + column);
1406 cur.boundary(bound);
1410 bool LyXText::checkAndActivateInset(bool front)
1412 if (cursor.pos() == cursorPar()->size())
1414 InsetOld * inset = cursorPar()->getInset(cursor.pos());
1415 if (!isHighlyEditableInset(inset))
1417 inset->edit(bv(), front);
1422 DispatchResult LyXText::moveRight()
1424 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1425 return moveLeftIntern(false, true, false);
1427 return moveRightIntern(true, true, false);
1431 DispatchResult LyXText::moveLeft()
1433 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1434 return moveRightIntern(true, true, false);
1436 return moveLeftIntern(false, true, false);
1440 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1442 ParagraphList::iterator c_par = cursorPar();
1443 if (boost::next(c_par) == paragraphs().end()
1444 && cursor.pos() >= c_par->size())
1445 return DispatchResult(false, FINISHED_RIGHT);
1446 if (activate_inset && checkAndActivateInset(front))
1447 return DispatchResult(true, true);
1451 return DispatchResult(true);
1455 DispatchResult LyXText::moveLeftIntern(bool front,
1456 bool activate_inset, bool selecting)
1458 if (cursor.par() == 0 && cursor.pos() <= 0)
1459 return DispatchResult(false, FINISHED);
1463 if (activate_inset && checkAndActivateInset(front))
1464 return DispatchResult(true, true);
1465 return DispatchResult(true);
1469 DispatchResult LyXText::moveUp()
1471 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1472 return DispatchResult(false, FINISHED_UP);
1475 return DispatchResult(true);
1479 DispatchResult LyXText::moveDown()
1481 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1482 return DispatchResult(false, FINISHED_DOWN);
1485 return DispatchResult(true);
1489 bool LyXText::cursorLeft(bool internal)
1491 if (cursor.pos() > 0) {
1492 bool boundary = cursor.boundary();
1493 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1494 if (!internal && !boundary &&
1495 bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1))
1496 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1500 if (cursor.par() != 0) {
1501 // steps into the paragraph above
1502 setCursor(cursor.par() - 1, boost::prior(cursorPar())->size());
1510 bool LyXText::cursorRight(bool internal)
1512 if (!internal && cursor.boundary()) {
1513 setCursor(cursor.par(), cursor.pos(), true, false);
1517 if (cursor.pos() != cursorPar()->size()) {
1518 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1519 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1521 setCursor(cursor.par(), cursor.pos(), true, true);
1525 if (cursor.par() + 1 != int(paragraphs().size())) {
1526 setCursor(cursor.par() + 1, 0);
1534 void LyXText::cursorUp(bool selecting)
1536 Row const & row = *cursorRow();
1537 int x = bv()->x_target() - xo_;
1538 int y = cursorY() - row.baseline() - 1;
1539 setCursorFromCoordinates(x, y);
1542 int y_abs = y + yo_ - bv()->top_y();
1543 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1544 if (inset_hit && isHighlyEditableInset(inset_hit))
1545 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1550 void LyXText::cursorDown(bool selecting)
1552 Row const & row = *cursorRow();
1553 int x = bv()->x_target() - xo_;
1554 int y = cursorY() - row.baseline() + row.height() + 1;
1555 setCursorFromCoordinates(x, y);
1558 int y_abs = y + yo_ - bv()->top_y();
1559 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1560 if (inset_hit && isHighlyEditableInset(inset_hit))
1561 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1566 void LyXText::cursorUpParagraph()
1568 ParagraphList::iterator cpit = cursorPar();
1569 if (cursor.pos() > 0)
1571 else if (cpit != paragraphs().begin())
1572 setCursor(boost::prior(cpit), 0);
1576 void LyXText::cursorDownParagraph()
1578 ParagraphList::iterator pit = cursorPar();
1579 ParagraphList::iterator next_pit = boost::next(pit);
1581 if (next_pit != paragraphs().end())
1582 setCursor(next_pit, 0);
1584 setCursor(pit, pit->size());
1588 // fix the cursor `cur' after a characters has been deleted at `where'
1589 // position. Called by deleteEmptyParagraphMechanism
1590 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1592 // if cursor is not in the paragraph where the delete occured,
1594 if (cur.par() != where.par())
1597 // if cursor position is after the place where the delete occured,
1599 if (cur.pos() > where.pos())
1600 cur.pos(cur.pos()-1);
1602 // check also if we don't want to set the cursor on a spot behind the
1603 // pagragraph because we erased the last character.
1604 if (cur.pos() > getPar(cur)->size())
1605 cur.pos(getPar(cur)->size());
1607 // recompute row et al. for this cursor
1608 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1612 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1614 // Would be wrong to delete anything if we have a selection.
1615 if (selection.set())
1618 // Don't do anything if the cursor is invalid
1619 if (old_cursor.par() == -1)
1622 // We allow all kinds of "mumbo-jumbo" when freespacing.
1623 ParagraphList::iterator const old_pit = getPar(old_cursor);
1624 if (old_pit->isFreeSpacing())
1627 /* Ok I'll put some comments here about what is missing.
1628 I have fixed BackSpace (and thus Delete) to not delete
1629 double-spaces automagically. I have also changed Cut,
1630 Copy and Paste to hopefully do some sensible things.
1631 There are still some small problems that can lead to
1632 double spaces stored in the document file or space at
1633 the beginning of paragraphs(). This happens if you have
1634 the cursor between to spaces and then save. Or if you
1635 cut and paste and the selection have a space at the
1636 beginning and then save right after the paste. I am
1637 sure none of these are very hard to fix, but I will
1638 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1639 that I can get some feedback. (Lgb)
1642 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1643 // delete the LineSeparator.
1646 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1647 // delete the LineSeparator.
1650 // If the pos around the old_cursor were spaces, delete one of them.
1651 if (old_cursor.par() != cursor.par()
1652 || old_cursor.pos() != cursor.pos()) {
1654 // Only if the cursor has really moved
1655 if (old_cursor.pos() > 0
1656 && old_cursor.pos() < old_pit->size()
1657 && old_pit->isLineSeparator(old_cursor.pos())
1658 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1659 bool erased = old_pit->erase(old_cursor.pos() - 1);
1660 redoParagraph(old_pit);
1664 #ifdef WITH_WARNINGS
1665 #warning This will not work anymore when we have multiple views of the same buffer
1666 // In this case, we will have to correct also the cursors held by
1667 // other bufferviews. It will probably be easier to do that in a more
1668 // automated way in LyXCursor code. (JMarc 26/09/2001)
1670 // correct all cursors held by the LyXText
1671 fixCursorAfterDelete(cursor, old_cursor);
1672 fixCursorAfterDelete(selection.cursor, old_cursor);
1677 // don't delete anything if this is the ONLY paragraph!
1678 if (paragraphs().size() == 1)
1681 // Do not delete empty paragraphs with keepempty set.
1682 if (old_pit->allowEmpty())
1685 // only do our magic if we changed paragraph
1686 if (old_cursor.par() == cursor.par())
1689 // record if we have deleted a paragraph
1690 // we can't possibly have deleted a paragraph before this point
1691 bool deleted = false;
1693 if (old_pit->empty()
1694 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1695 // ok, we will delete something
1696 LyXCursor tmpcursor;
1700 bool selection_position_was_oldcursor_position =
1701 selection.cursor.par() == old_cursor.par()
1702 && selection.cursor.pos() == old_cursor.pos();
1705 cursor = old_cursor; // that undo can restore the right cursor position
1707 ParagraphList::iterator endpit = boost::next(old_pit);
1708 while (endpit != paragraphs().end() && endpit->getDepth())
1711 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1715 ParagraphList::iterator tmppit = cursorPar();
1717 paragraphs().erase(old_pit);
1718 // update cursor par offset
1719 cursor.par(parOffset(tmppit));
1723 setCursorIntern(cursor.par(), cursor.pos());
1725 if (selection_position_was_oldcursor_position) {
1726 // correct selection
1727 selection.cursor = cursor;
1734 if (old_pit->stripLeadingSpaces()) {
1735 redoParagraph(old_pit);
1737 setCursorIntern(cursor.par(), cursor.pos());
1738 selection.cursor = cursor;
1744 ParagraphList & LyXText::paragraphs() const
1746 return const_cast<ParagraphList &>(paragraphs_);
1750 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1752 recordUndo(Undo::ATOMIC, this, first, last);
1756 void LyXText::recUndo(lyx::paroffset_type par) const
1758 recordUndo(Undo::ATOMIC, this, par, par);
1762 bool LyXText::isInInset() const
1768 int defaultRowHeight()
1770 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);