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);
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 // return past-the-last paragraph influenced by a layout change on pit
272 ParagraphList::iterator
273 LyXText::undoSpan(ParagraphList::iterator pit)
275 ParagraphList::iterator end = paragraphs().end();
276 ParagraphList::iterator nextpit = boost::next(pit);
279 //because of parindents
280 if (!pit->getDepth())
281 return boost::next(nextpit);
282 //because of depth constrains
283 for (; nextpit != end; ++pit, ++nextpit) {
284 if (!pit->getDepth())
291 ParagraphList::iterator
292 LyXText::setLayout(ParagraphList::iterator start,
293 ParagraphList::iterator end,
294 string const & layout)
296 BOOST_ASSERT(start != end);
297 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
298 recUndo(parOffset(start), parOffset(undopit) - 1);
300 BufferParams const & bufparams = bv()->buffer()->params();
301 LyXLayout_ptr const & lyxlayout =
302 bufparams.getLyXTextClass()[layout];
304 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
305 pit->applyLayout(lyxlayout);
306 makeFontEntriesLayoutSpecific(bufparams, *pit);
307 if (lyxlayout->margintype == MARGIN_MANUAL)
308 pit->setLabelWidthString(lyxlayout->labelstring());
315 // set layout over selection and make a total rebreak of those paragraphs
316 void LyXText::setLayout(string const & layout)
318 // special handling of new environment insets
319 BufferParams const & params = bv()->buffer()->params();
320 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
321 if (lyxlayout->is_environment) {
322 // move everything in a new environment inset
323 lyxerr << "setting layout " << layout << endl;
324 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
325 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
326 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
327 InsetOld * inset = new InsetEnvironment(params, layout);
328 if (bv()->insertInset(inset)) {
330 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
336 ParagraphList::iterator start = getPar(selStart().par());
337 ParagraphList::iterator end = boost::next(getPar(selEnd().par()));
338 ParagraphList::iterator endpit = setLayout(start, end, layout);
340 redoParagraphs(start, endpit);
348 void getSelectionSpan(LyXText & text,
349 ParagraphList::iterator & beg,
350 ParagraphList::iterator & end)
352 if (!text.selection.set()) {
353 beg = text.cursorPar();
354 end = boost::next(beg);
356 beg = text.getPar(text.selStart());
357 end = boost::next(text.getPar(text.selEnd()));
362 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
363 Paragraph const & par,
366 if (par.layout()->labeltype == LABEL_BIBLIO)
368 int const depth = par.params().depth();
369 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
371 if (type == bv_funcs::DEC_DEPTH && depth > 0)
380 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
382 ParagraphList::iterator beg, end;
383 getSelectionSpan(*this, beg, end);
385 if (beg != paragraphs().begin())
386 max_depth = boost::prior(beg)->getMaxDepthAfter();
388 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
389 if (::changeDepthAllowed(type, *pit, max_depth))
391 max_depth = pit->getMaxDepthAfter();
397 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
399 ParagraphList::iterator beg, end;
400 getSelectionSpan(*this, beg, end);
402 recUndo(parOffset(beg), parOffset(end) - 1);
405 if (beg != paragraphs().begin())
406 max_depth = boost::prior(beg)->getMaxDepthAfter();
408 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
409 if (::changeDepthAllowed(type, *pit, max_depth)) {
410 int const depth = pit->params().depth();
411 if (type == bv_funcs::INC_DEPTH)
412 pit->params().depth(depth + 1);
414 pit->params().depth(depth - 1);
416 max_depth = pit->getMaxDepthAfter();
418 // this handles the counter labels, and also fixes up
419 // depth values for follow-on (child) paragraphs
424 // set font over selection and make a total rebreak of those paragraphs
425 void LyXText::setFont(LyXFont const & font, bool toggleall)
427 // if there is no selection just set the current_font
428 if (!selection.set()) {
429 // Determine basis font
431 if (cursor().pos() < cursorPar()->beginOfBody())
432 layoutfont = getLabelFont(cursorPar());
434 layoutfont = getLayoutFont(cursorPar());
436 // Update current font
437 real_current_font.update(font,
438 bv()->buffer()->params().language,
441 // Reduce to implicit settings
442 current_font = real_current_font;
443 current_font.reduce(layoutfont);
444 // And resolve it completely
445 real_current_font.realize(layoutfont);
450 // ok we have a selection.
451 recUndo(selStart().par(), selEnd().par());
454 ParagraphList::iterator beg = getPar(selStart().par());
455 ParagraphList::iterator end = getPar(selEnd().par());
457 PosIterator pos(¶graphs(), beg, selStart().pos());
458 PosIterator posend(¶graphs(), end, selEnd().pos());
460 BufferParams const & params = bv()->buffer()->params();
462 for (; pos != posend; ++pos) {
463 LyXFont f = getFont(pos.pit(), pos.pos());
464 f.update(font, params.language, toggleall);
465 setCharFont(pos.pit(), pos.pos(), f);
470 redoParagraphs(beg, ++end);
474 // important for the screen
477 // the cursor set functions have a special mechanism. When they
478 // realize, that you left an empty paragraph, they will delete it.
480 // need the selection cursor:
481 void LyXText::setSelection()
483 TextCursor::setSelection();
487 void LyXText::clearSelection()
489 TextCursor::clearSelection();
491 // reset this in the bv()!
492 if (bv() && bv()->text())
497 void LyXText::cursorHome()
499 ParagraphList::iterator cpit = cursorPar();
500 setCursor(cpit, cpit->getRow(cursor().pos())->pos());
504 void LyXText::cursorEnd()
506 ParagraphList::iterator cpit = cursorPar();
507 pos_type end = cpit->getRow(cursor().pos())->endpos();
508 // if not on the last row of the par, put the cursor before
510 setCursor(cpit, end == cpit->size() ? end : end - 1);
514 void LyXText::cursorTop()
516 setCursor(paragraphs().begin(), 0);
520 void LyXText::cursorBottom()
522 ParagraphList::iterator lastpit =
523 boost::prior(paragraphs().end());
524 setCursor(lastpit, lastpit->size());
528 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
530 // If the mask is completely neutral, tell user
531 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
532 // Could only happen with user style
533 bv()->owner()->message(_("No font change defined. "
534 "Use Character under the Layout menu to define font change."));
538 // Try implicit word selection
539 // If there is a change in the language the implicit word selection
541 LyXCursor resetCursor = cursor();
542 bool implicitSelection =
543 font.language() == ignore_language
544 && font.number() == LyXFont::IGNORE
545 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
548 setFont(font, toggleall);
550 // Implicit selections are cleared afterwards
551 //and cursor is set to the original position.
552 if (implicitSelection) {
554 cursor() = resetCursor;
560 string LyXText::getStringToIndex()
562 // Try implicit word selection
563 // If there is a change in the language the implicit word selection
565 LyXCursor const reset_cursor = cursor();
566 bool const implicitSelection =
567 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
570 if (!selection.set())
571 bv()->owner()->message(_("Nothing to index!"));
572 else if (selStart().par() != selEnd().par())
573 bv()->owner()->message(_("Cannot index more than one paragraph!"));
575 idxstring = selectionAsString(*bv()->buffer(), false);
577 // Reset cursors to their original position.
578 cursor() = reset_cursor;
581 // Clear the implicit selection.
582 if (implicitSelection)
589 // the DTP switches for paragraphs(). LyX will store them in the first
590 // physical paragraph. When a paragraph is broken, the top settings rest,
591 // the bottom settings are given to the new one. So I can make sure,
592 // they do not duplicate themself and you cannot play dirty tricks with
595 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
596 string const & labelwidthstring, bool noindent)
598 // make sure that the depth behind the selection are restored, too
599 ParagraphList::iterator undopit = undoSpan(getPar(selEnd()));
600 recUndo(selStart().par(), parOffset(undopit) - 1);
602 ParagraphList::reverse_iterator pit(getPar(selEnd().par()));
603 ParagraphList::reverse_iterator beg(getPar(selStart().par()));
605 for (--pit; pit != beg; ++pit) {
606 ParagraphParameters & params = pit->params();
607 params.spacing(spacing);
609 // does the layout allow the new alignment?
610 LyXLayout_ptr const & layout = pit->layout();
612 if (align == LYX_ALIGN_LAYOUT)
613 align = layout->align;
614 if (align & layout->alignpossible) {
615 if (align == layout->align)
616 params.align(LYX_ALIGN_LAYOUT);
620 pit->setLabelWidthString(labelwidthstring);
621 params.noindent(noindent);
624 redoParagraphs(getPar(selStart()), undopit);
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 = undoSpan(endpit);
962 recUndo(selStart().par(), parOffset(undopit) - 1);
964 int endpos = selEnd().pos();
966 BufferParams const & bufparams = bv()->buffer()->params();
967 boost::tie(endpit, endpos) = realcut ?
968 CutAndPaste::cutSelection(bufparams,
971 selStart().pos(), endpos,
974 : CutAndPaste::eraseSelection(bufparams,
977 selStart().pos(), endpos,
979 // sometimes necessary
981 begpit->stripLeadingSpaces();
983 redoParagraphs(begpit, undopit);
984 // cutSelection can invalidate the cursor so we need to set
986 // we prefer the end for when tracking changes
987 cursor().pos(endpos);
988 cursor().par(parOffset(endpit));
990 // need a valid cursor. (Lgb)
996 void LyXText::copySelection()
998 // stuff the selection onto the X clipboard, from an explicit copy request
999 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1001 // this doesnt make sense, if there is no selection
1002 if (!selection.set())
1005 // ok we have a selection. This is always between selStart()
1006 // and sel_end cursor
1008 // copy behind a space if there is one
1009 while (getPar(selStart())->size() > selStart().pos()
1010 && getPar(selStart())->isLineSeparator(selStart().pos())
1011 && (selStart().par() != selEnd().par()
1012 || selStart().pos() < selEnd().pos()))
1013 selStart().pos(selStart().pos() + 1);
1015 CutAndPaste::copySelection(getPar(selStart().par()),
1016 getPar(selEnd().par()),
1019 bv()->buffer()->params().textclass);
1023 void LyXText::pasteSelection(size_t sel_index)
1025 // this does not make sense, if there is nothing to paste
1026 if (!CutAndPaste::checkPastePossible())
1029 recUndo(cursor().par());
1031 ParagraphList::iterator endpit;
1036 boost::tie(ppp, endpit) =
1037 CutAndPaste::pasteSelection(*bv()->buffer(),
1039 cursorPar(), cursor().pos(),
1040 bv()->buffer()->params().textclass,
1042 bufferErrors(*bv()->buffer(), el);
1043 bv()->showErrorList(_("Paste"));
1045 redoParagraphs(cursorPar(), endpit);
1049 anchor() = cursor();
1050 setCursor(ppp.first, ppp.second);
1056 void LyXText::setSelectionRange(lyx::pos_type length)
1061 anchor() = cursor();
1068 // simple replacing. The font of the first selected character is used
1069 void LyXText::replaceSelectionWithString(string const & str)
1071 recUndo(cursor().par());
1074 // Get font setting before we cut
1075 pos_type pos = selEnd().pos();
1076 LyXFont const font = getPar(selStart())
1077 ->getFontSettings(bv()->buffer()->params(),
1080 // Insert the new string
1081 string::const_iterator cit = str.begin();
1082 string::const_iterator end = str.end();
1083 for (; cit != end; ++cit) {
1084 getPar(selEnd())->insertChar(pos, (*cit), font);
1088 // Cut the selection
1089 cutSelection(true, false);
1095 // needed to insert the selection
1096 void LyXText::insertStringAsLines(string const & str)
1098 ParagraphList::iterator pit = cursorPar();
1099 pos_type pos = cursor().pos();
1100 ParagraphList::iterator endpit = boost::next(cursorPar());
1102 recUndo(cursor().par());
1104 // only to be sure, should not be neccessary
1107 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1109 redoParagraphs(cursorPar(), endpit);
1110 anchor() = cursor();
1111 setCursor(pit, pos);
1116 // turn double CR to single CR, others are converted into one
1117 // blank. Then insertStringAsLines is called
1118 void LyXText::insertStringAsParagraphs(string const & str)
1120 string linestr(str);
1121 bool newline_inserted = false;
1122 string::size_type const siz = linestr.length();
1124 for (string::size_type i = 0; i < siz; ++i) {
1125 if (linestr[i] == '\n') {
1126 if (newline_inserted) {
1127 // we know that \r will be ignored by
1128 // insertStringAsLines. Of course, it is a dirty
1129 // trick, but it works...
1130 linestr[i - 1] = '\r';
1134 newline_inserted = true;
1136 } else if (IsPrintable(linestr[i])) {
1137 newline_inserted = false;
1140 insertStringAsLines(linestr);
1144 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1146 setCursor(parOffset(pit), pos);
1150 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1153 LyXCursor old_cursor = cursor();
1154 setCursorIntern(par, pos, setfont, boundary);
1155 return deleteEmptyParagraphMechanism(old_cursor);
1159 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1160 pos_type pos, bool boundary)
1162 BOOST_ASSERT(par != int(paragraphs().size()));
1166 cur.boundary(boundary);
1168 // no rows, no fun...
1169 if (paragraphs().begin()->rows.empty())
1172 // get the cursor y position in text
1174 ParagraphList::iterator pit = getPar(par);
1175 Row const & row = *pit->getRow(pos);
1176 pos_type const end = row.endpos();
1178 // None of these should happen, but we're scaredy-cats
1180 lyxerr << "dont like -1" << endl;
1183 BOOST_ASSERT(false);
1184 } else if (pos > pit->size()) {
1185 lyxerr << "dont like 1, pos: " << pos
1186 << " size: " << pit->size()
1187 << " row.pos():" << row.pos()
1188 << " paroffset: " << par << endl;
1191 BOOST_ASSERT(false);
1192 } else if (pos > end) {
1193 lyxerr << "dont like 2 please report" << endl;
1194 // This shouldn't happen.
1197 BOOST_ASSERT(false);
1198 } else if (pos < row.pos()) {
1199 lyxerr << "dont like 3 please report pos:" << pos
1200 << " size: " << pit->size()
1201 << " row.pos():" << row.pos()
1202 << " paroffset: " << par << endl;
1205 BOOST_ASSERT(false);
1210 void LyXText::setCursorIntern(paroffset_type par,
1211 pos_type pos, bool setfont, bool boundary)
1213 setCursor(cursor(), par, pos, boundary);
1214 bv()->x_target(cursorX() + xo_);
1220 void LyXText::setCurrentFont()
1222 pos_type pos = cursor().pos();
1223 ParagraphList::iterator pit = cursorPar();
1225 if (cursor().boundary() && pos > 0)
1229 if (pos == pit->size())
1231 else // potentional bug... BUG (Lgb)
1232 if (pit->isSeparator(pos)) {
1233 if (pos > pit->getRow(pos)->pos() &&
1234 bidi.level(pos) % 2 ==
1235 bidi.level(pos - 1) % 2)
1237 else if (pos + 1 < pit->size())
1242 BufferParams const & bufparams = bv()->buffer()->params();
1243 current_font = pit->getFontSettings(bufparams, pos);
1244 real_current_font = getFont(pit, pos);
1246 if (cursor().pos() == pit->size() &&
1247 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1248 !cursor().boundary()) {
1249 Language const * lang =
1250 pit->getParLanguage(bufparams);
1251 current_font.setLanguage(lang);
1252 current_font.setNumber(LyXFont::OFF);
1253 real_current_font.setLanguage(lang);
1254 real_current_font.setNumber(LyXFont::OFF);
1259 // returns the column near the specified x-coordinate of the row
1260 // x is set to the real beginning of this column
1261 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1262 Row const & row, int & x, bool & boundary) const
1264 double tmpx = row.x();
1265 double fill_separator = row.fill_separator();
1266 double fill_hfill = row.fill_hfill();
1267 double fill_label_hfill = row.fill_label_hfill();
1269 pos_type vc = row.pos();
1270 pos_type end = row.endpos();
1272 LyXLayout_ptr const & layout = pit->layout();
1274 bool left_side = false;
1276 pos_type body_pos = pit->beginOfBody();
1277 double last_tmpx = tmpx;
1280 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1283 // check for empty row
1289 while (vc < end && tmpx <= x) {
1290 c = bidi.vis2log(vc);
1292 if (body_pos > 0 && c == body_pos - 1) {
1293 tmpx += fill_label_hfill +
1294 font_metrics::width(layout->labelsep, getLabelFont(pit));
1295 if (pit->isLineSeparator(body_pos - 1))
1296 tmpx -= singleWidth(pit, body_pos - 1);
1299 if (hfillExpansion(*pit, row, c)) {
1300 tmpx += singleWidth(pit, c);
1304 tmpx += fill_label_hfill;
1305 } else if (pit->isSeparator(c)) {
1306 tmpx += singleWidth(pit, c);
1308 tmpx += fill_separator;
1310 tmpx += singleWidth(pit, c);
1315 if ((tmpx + last_tmpx) / 2 > x) {
1320 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1323 // This (rtl_support test) is not needed, but gives
1324 // some speedup if rtl_support == false
1325 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1327 // If lastrow is false, we don't need to compute
1328 // the value of rtl.
1329 bool const rtl = (lastrow)
1330 ? pit->isRightToLeftPar(bv()->buffer()->params())
1333 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1334 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1336 else if (vc == row.pos()) {
1337 c = bidi.vis2log(vc);
1338 if (bidi.level(c) % 2 == 1)
1341 c = bidi.vis2log(vc - 1);
1342 bool const rtl = (bidi.level(c) % 2 == 1);
1343 if (left_side == rtl) {
1345 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1349 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1350 if (bidi.level(end -1) % 2 == 0)
1351 tmpx -= singleWidth(pit, end - 1);
1353 tmpx += singleWidth(pit, end - 1);
1363 void LyXText::setCursorFromCoordinates(int x, int y)
1365 LyXCursor old_cursor = cursor();
1366 setCursorFromCoordinates(cursor(), x, y);
1368 deleteEmptyParagraphMechanism(old_cursor);
1372 // x,y are coordinates relative to this LyXText
1373 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1375 ParagraphList::iterator pit;
1376 Row const & row = *getRowNearY(y, pit);
1378 pos_type const column = getColumnNearX(pit, row, x, bound);
1379 cur.par(parOffset(pit));
1380 cur.pos(row.pos() + column);
1381 cur.boundary(bound);
1385 bool LyXText::checkAndActivateInset(bool front)
1387 if (cursor().pos() == cursorPar()->size())
1389 InsetOld * inset = cursorPar()->getInset(cursor().pos());
1390 if (!isHighlyEditableInset(inset))
1392 inset->edit(bv(), front);
1397 DispatchResult LyXText::moveRight()
1399 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1400 return moveLeftIntern(false, true, false);
1402 return moveRightIntern(true, true, false);
1406 DispatchResult LyXText::moveLeft()
1408 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1409 return moveRightIntern(true, true, false);
1411 return moveLeftIntern(false, true, false);
1415 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1417 ParagraphList::iterator c_par = cursorPar();
1418 if (boost::next(c_par) == paragraphs().end()
1419 && cursor().pos() >= c_par->size())
1420 return DispatchResult(false, FINISHED_RIGHT);
1421 if (activate_inset && checkAndActivateInset(front))
1422 return DispatchResult(true, true);
1426 return DispatchResult(true);
1430 DispatchResult LyXText::moveLeftIntern(bool front,
1431 bool activate_inset, bool selecting)
1433 if (cursor().par() == 0 && cursor().pos() <= 0)
1434 return DispatchResult(false, FINISHED);
1438 if (activate_inset && checkAndActivateInset(front))
1439 return DispatchResult(true, true);
1440 return DispatchResult(true);
1444 DispatchResult LyXText::moveUp()
1446 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1447 return DispatchResult(false, FINISHED_UP);
1450 return DispatchResult(true);
1454 DispatchResult LyXText::moveDown()
1456 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1457 return DispatchResult(false, FINISHED_DOWN);
1460 return DispatchResult(true);
1464 bool LyXText::cursorLeft(bool internal)
1466 if (cursor().pos() > 0) {
1467 bool boundary = cursor().boundary();
1468 setCursor(cursor().par(), cursor().pos() - 1, true, false);
1469 if (!internal && !boundary &&
1470 bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor().pos() + 1))
1471 setCursor(cursor().par(), cursor().pos() + 1, true, true);
1475 if (cursor().par() != 0) {
1476 // steps into the paragraph above
1477 setCursor(cursor().par() - 1, boost::prior(cursorPar())->size());
1485 bool LyXText::cursorRight(bool internal)
1487 if (!internal && cursor().boundary()) {
1488 setCursor(cursor().par(), cursor().pos(), true, false);
1492 if (cursor().pos() != cursorPar()->size()) {
1493 setCursor(cursor().par(), cursor().pos() + 1, true, false);
1494 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1496 setCursor(cursor().par(), cursor().pos(), true, true);
1500 if (cursor().par() + 1 != int(paragraphs().size())) {
1501 setCursor(cursor().par() + 1, 0);
1509 void LyXText::cursorUp(bool selecting)
1511 Row const & row = *cursorRow();
1512 int x = bv()->x_target() - xo_;
1513 int y = cursorY() - row.baseline() - 1;
1514 setCursorFromCoordinates(x, y);
1517 int y_abs = y + yo_ - bv()->top_y();
1518 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1519 if (inset_hit && isHighlyEditableInset(inset_hit))
1520 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1525 void LyXText::cursorDown(bool selecting)
1527 Row const & row = *cursorRow();
1528 int x = bv()->x_target() - xo_;
1529 int y = cursorY() - row.baseline() + row.height() + 1;
1530 setCursorFromCoordinates(x, y);
1533 int y_abs = y + yo_ - bv()->top_y();
1534 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1535 if (inset_hit && isHighlyEditableInset(inset_hit))
1536 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1541 void LyXText::cursorUpParagraph()
1543 ParagraphList::iterator cpit = cursorPar();
1544 if (cursor().pos() > 0)
1546 else if (cpit != paragraphs().begin())
1547 setCursor(boost::prior(cpit), 0);
1551 void LyXText::cursorDownParagraph()
1553 ParagraphList::iterator pit = cursorPar();
1554 ParagraphList::iterator next_pit = boost::next(pit);
1556 if (next_pit != paragraphs().end())
1557 setCursor(next_pit, 0);
1559 setCursor(pit, pit->size());
1563 // fix the cursor `cur' after a characters has been deleted at `where'
1564 // position. Called by deleteEmptyParagraphMechanism
1565 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1567 // if cursor is not in the paragraph where the delete occured,
1569 if (cur.par() != where.par())
1572 // if cursor position is after the place where the delete occured,
1574 if (cur.pos() > where.pos())
1575 cur.pos(cur.pos()-1);
1577 // check also if we don't want to set the cursor on a spot behind the
1578 // pagragraph because we erased the last character.
1579 if (cur.pos() > getPar(cur)->size())
1580 cur.pos(getPar(cur)->size());
1584 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1586 // Would be wrong to delete anything if we have a selection.
1587 if (selection.set())
1590 // Don't do anything if the cursor is invalid
1591 if (old_cursor.par() == -1)
1594 // We allow all kinds of "mumbo-jumbo" when freespacing.
1595 ParagraphList::iterator const old_pit = getPar(old_cursor);
1596 if (old_pit->isFreeSpacing())
1599 /* Ok I'll put some comments here about what is missing.
1600 I have fixed BackSpace (and thus Delete) to not delete
1601 double-spaces automagically. I have also changed Cut,
1602 Copy and Paste to hopefully do some sensible things.
1603 There are still some small problems that can lead to
1604 double spaces stored in the document file or space at
1605 the beginning of paragraphs(). This happens if you have
1606 the cursor between to spaces and then save. Or if you
1607 cut and paste and the selection have a space at the
1608 beginning and then save right after the paste. I am
1609 sure none of these are very hard to fix, but I will
1610 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1611 that I can get some feedback. (Lgb)
1614 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1615 // delete the LineSeparator.
1618 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1619 // delete the LineSeparator.
1622 // If the pos around the old_cursor were spaces, delete one of them.
1623 if (old_cursor.par() != cursor().par()
1624 || old_cursor.pos() != cursor().pos()) {
1626 // Only if the cursor has really moved
1627 if (old_cursor.pos() > 0
1628 && old_cursor.pos() < old_pit->size()
1629 && old_pit->isLineSeparator(old_cursor.pos())
1630 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1631 bool erased = old_pit->erase(old_cursor.pos() - 1);
1632 redoParagraph(old_pit);
1636 #ifdef WITH_WARNINGS
1637 #warning This will not work anymore when we have multiple views of the same buffer
1638 // In this case, we will have to correct also the cursors held by
1639 // other bufferviews. It will probably be easier to do that in a more
1640 // automated way in LyXCursor code. (JMarc 26/09/2001)
1642 // correct all cursors held by the LyXText
1643 fixCursorAfterDelete(cursor(), old_cursor);
1644 fixCursorAfterDelete(anchor(), old_cursor);
1649 // don't delete anything if this is the ONLY paragraph!
1650 if (paragraphs().size() == 1)
1653 // Do not delete empty paragraphs with keepempty set.
1654 if (old_pit->allowEmpty())
1657 // only do our magic if we changed paragraph
1658 if (old_cursor.par() == cursor().par())
1661 // record if we have deleted a paragraph
1662 // we can't possibly have deleted a paragraph before this point
1663 bool deleted = false;
1665 if (old_pit->empty()
1666 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1667 // ok, we will delete something
1668 LyXCursor tmpcursor;
1672 bool selection_position_was_oldcursor_position =
1673 anchor().par() == old_cursor.par()
1674 && anchor().pos() == old_cursor.pos();
1676 tmpcursor = cursor();
1677 cursor() = old_cursor; // that undo can restore the right cursor position
1679 ParagraphList::iterator endpit = boost::next(old_pit);
1680 while (endpit != paragraphs().end() && endpit->getDepth())
1683 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1684 cursor() = tmpcursor;
1687 ParagraphList::iterator tmppit = cursorPar();
1689 paragraphs().erase(old_pit);
1690 // update cursor par offset
1691 cursor().par(parOffset(tmppit));
1694 if (selection_position_was_oldcursor_position) {
1695 // correct selection
1696 anchor() = cursor();
1703 if (old_pit->stripLeadingSpaces()) {
1704 redoParagraph(old_pit);
1705 anchor() = cursor();
1711 ParagraphList & LyXText::paragraphs() const
1713 return const_cast<ParagraphList &>(paragraphs_);
1717 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1719 recordUndo(Undo::ATOMIC, this, first, last);
1723 void LyXText::recUndo(lyx::paroffset_type par) const
1725 recordUndo(Undo::ATOMIC, this, par, par);
1729 bool LyXText::isInInset() const
1735 int defaultRowHeight()
1737 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);