3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
36 #include "FloatList.h"
37 #include "funcrequest.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "PosIterator.h"
51 #include "frontends/font_metrics.h"
52 #include "frontends/LyXView.h"
54 #include "insets/insetbibitem.h"
55 #include "insets/insetenv.h"
56 #include "insets/insetfloat.h"
57 #include "insets/insetwrap.h"
59 #include "support/lstrings.h"
60 #include "support/textutils.h"
61 #include "support/tostr.h"
62 #include "support/std_sstream.h"
64 #include <boost/tuple/tuple.hpp>
67 using lyx::paroffset_type;
68 using lyx::support::bformat;
71 using std::ostringstream;
75 LyXText::LyXText(BufferView * bv, bool in_inset)
76 : height(0), width(0), textwidth_(bv ? bv->workWidth() : 100),
77 background_color_(LColor::background),
78 bv_owner(bv), in_inset_(in_inset), xo_(0), yo_(0)
82 void LyXText::init(BufferView * bview)
86 ParagraphList::iterator const beg = paragraphs().begin();
87 ParagraphList::iterator const end = paragraphs().end();
88 for (ParagraphList::iterator pit = beg; pit != end; ++pit)
94 current_font = getFont(beg, 0);
96 redoParagraphs(beg, end);
97 setCursorIntern(0, 0);
98 bv()->cursor().resetAnchor();
104 // Gets the fully instantiated font at a given position in a paragraph
105 // Basically the same routine as Paragraph::getFont() in paragraph.C.
106 // The difference is that this one is used for displaying, and thus we
107 // are allowed to make cosmetic improvements. For instance make footnotes
109 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
111 BOOST_ASSERT(pos >= 0);
113 LyXLayout_ptr const & layout = pit->layout();
115 BufferParams const & params = bv()->buffer()->params();
116 pos_type const body_pos = pit->beginOfBody();
118 // We specialize the 95% common case:
119 if (!pit->getDepth()) {
120 LyXFont f = pit->getFontSettings(params, pos);
123 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
124 return f.realize(layout->reslabelfont);
126 return f.realize(layout->resfont);
129 // The uncommon case need not be optimized as much
132 layoutfont = layout->labelfont;
134 layoutfont = layout->font;
136 LyXFont font = pit->getFontSettings(params, pos);
137 font.realize(layoutfont);
142 // Realize with the fonts of lesser depth.
143 //font.realize(outerFont(pit, paragraphs()));
144 font.realize(defaultfont_);
150 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
152 LyXLayout_ptr const & layout = pit->layout();
154 if (!pit->getDepth())
155 return layout->resfont;
157 LyXFont font = layout->font;
158 // Realize with the fonts of lesser depth.
159 //font.realize(outerFont(pit, paragraphs()));
160 font.realize(defaultfont_);
166 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
168 LyXLayout_ptr const & layout = pit->layout();
170 if (!pit->getDepth())
171 return layout->reslabelfont;
173 LyXFont font = layout->labelfont;
174 // Realize with the fonts of lesser depth.
175 font.realize(outerFont(pit, paragraphs()));
176 font.realize(defaultfont_);
182 void LyXText::setCharFont(
183 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
186 LyXLayout_ptr const & layout = pit->layout();
188 // Get concrete layout font to reduce against
191 if (pos < pit->beginOfBody())
192 layoutfont = layout->labelfont;
194 layoutfont = layout->font;
196 // Realize against environment font information
197 if (pit->getDepth()) {
198 ParagraphList::iterator tp = pit;
199 while (!layoutfont.resolved() &&
200 tp != paragraphs().end() &&
202 tp = outerHook(tp, paragraphs());
203 if (tp != paragraphs().end())
204 layoutfont.realize(tp->layout()->font);
208 layoutfont.realize(defaultfont_);
210 // Now, reduce font against full layout font
211 font.reduce(layoutfont);
213 pit->setFont(pos, font);
217 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 =
337 getPar(bv()->cursor().selStart().par());
338 ParagraphList::iterator end =
339 boost::next(getPar(bv()->cursor().selEnd().par()));
340 ParagraphList::iterator endpit = setLayout(start, end, layout);
342 redoParagraphs(start, endpit);
350 void getSelectionSpan(LyXText & text,
351 ParagraphList::iterator & beg,
352 ParagraphList::iterator & end)
354 if (!text.bv()->cursor().selection()) {
355 beg = text.cursorPar();
356 end = boost::next(beg);
358 beg = text.getPar(text.bv()->cursor().selStart());
359 end = boost::next(text.getPar(text.bv()->cursor().selEnd()));
364 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
365 Paragraph const & par,
368 if (par.layout()->labeltype == LABEL_BIBLIO)
370 int const depth = par.params().depth();
371 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
373 if (type == bv_funcs::DEC_DEPTH && depth > 0)
382 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
384 ParagraphList::iterator beg, end;
385 getSelectionSpan(*this, beg, end);
387 if (beg != paragraphs().begin())
388 max_depth = boost::prior(beg)->getMaxDepthAfter();
390 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
391 if (::changeDepthAllowed(type, *pit, max_depth))
393 max_depth = pit->getMaxDepthAfter();
399 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
401 ParagraphList::iterator beg, end;
402 getSelectionSpan(*this, beg, end);
404 recUndo(parOffset(beg), parOffset(end) - 1);
407 if (beg != paragraphs().begin())
408 max_depth = boost::prior(beg)->getMaxDepthAfter();
410 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
411 if (::changeDepthAllowed(type, *pit, max_depth)) {
412 int const depth = pit->params().depth();
413 if (type == bv_funcs::INC_DEPTH)
414 pit->params().depth(depth + 1);
416 pit->params().depth(depth - 1);
418 max_depth = pit->getMaxDepthAfter();
420 // this handles the counter labels, and also fixes up
421 // depth values for follow-on (child) paragraphs
426 // set font over selection and make a total rebreak of those paragraphs
427 void LyXText::setFont(LyXFont const & font, bool toggleall)
429 LCursor & cur = bv()->cursor();
430 // if there is no selection just set the current_font
431 if (!cur.selection()) {
432 // Determine basis font
434 if (cursor().pos() < cursorPar()->beginOfBody())
435 layoutfont = getLabelFont(cursorPar());
437 layoutfont = getLayoutFont(cursorPar());
439 // Update current font
440 real_current_font.update(font,
441 bv()->buffer()->params().language,
444 // Reduce to implicit settings
445 current_font = real_current_font;
446 current_font.reduce(layoutfont);
447 // And resolve it completely
448 real_current_font.realize(layoutfont);
453 // ok we have a selection.
454 recUndo(cur.selStart().par(), cur.selEnd().par());
457 ParagraphList::iterator beg = getPar(cur.selStart().par());
458 ParagraphList::iterator end = getPar(cur.selEnd().par());
460 PosIterator pos(¶graphs(), beg, cur.selStart().pos());
461 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
463 BufferParams const & params = bv()->buffer()->params();
465 for (; pos != posend; ++pos) {
466 LyXFont f = getFont(pos.pit(), pos.pos());
467 f.update(font, params.language, toggleall);
468 setCharFont(pos.pit(), pos.pos(), f);
473 redoParagraphs(beg, ++end);
477 // the cursor set functions have a special mechanism. When they
478 // realize you left an empty paragraph, they will delete it.
480 void LyXText::cursorHome()
482 ParagraphList::iterator cpit = cursorPar();
483 setCursor(cpit, cpit->getRow(cursor().pos())->pos());
487 void LyXText::cursorEnd()
489 ParagraphList::iterator cpit = cursorPar();
490 pos_type end = cpit->getRow(cursor().pos())->endpos();
491 // if not on the last row of the par, put the cursor before
493 setCursor(cpit, end == cpit->size() ? end : end - 1);
497 void LyXText::cursorTop()
499 setCursor(paragraphs().begin(), 0);
503 void LyXText::cursorBottom()
505 ParagraphList::iterator lastpit =
506 boost::prior(paragraphs().end());
507 setCursor(lastpit, lastpit->size());
511 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
513 // If the mask is completely neutral, tell user
514 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
515 // Could only happen with user style
516 bv()->owner()->message(_("No font change defined. "
517 "Use Character under the Layout menu to define font change."));
521 // Try implicit word selection
522 // If there is a change in the language the implicit word selection
524 CursorSlice resetCursor = cursor();
525 bool implicitSelection =
526 font.language() == ignore_language
527 && font.number() == LyXFont::IGNORE
528 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
531 setFont(font, toggleall);
533 // Implicit selections are cleared afterwards
534 //and cursor is set to the original position.
535 if (implicitSelection) {
536 bv()->cursor().clearSelection();
537 cursor() = resetCursor;
538 bv()->cursor().resetAnchor();
543 string LyXText::getStringToIndex()
545 LCursor & cur = bv()->cursor();
546 // Try implicit word selection
547 // If there is a change in the language the implicit word selection
549 CursorSlice const reset_cursor = cursor();
550 bool const implicitSelection =
551 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
554 if (!cur.selection())
555 bv()->owner()->message(_("Nothing to index!"));
556 else if (cur.selStart().par() != cur.selEnd().par())
557 bv()->owner()->message(_("Cannot index more than one paragraph!"));
559 idxstring = selectionAsString(*bv()->buffer(), false);
561 // Reset cursors to their original position.
562 cursor() = reset_cursor;
565 // Clear the implicit selection.
566 if (implicitSelection)
567 cur.clearSelection();
573 // the DTP switches for paragraphs(). LyX will store them in the first
574 // physical paragraph. When a paragraph is broken, the top settings rest,
575 // the bottom settings are given to the new one. So I can make sure,
576 // they do not duplicate themself and you cannot play dirty tricks with
579 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
580 string const & labelwidthstring, bool noindent)
582 LCursor & cur = bv()->cursor();
583 // make sure that the depth behind the selection are restored, too
584 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
585 recUndo(cur.selStart().par(), parOffset(undopit) - 1);
587 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
588 ParagraphList::reverse_iterator beg(getPar(cur.selStart().par()));
590 for (--pit; pit != beg; ++pit) {
591 ParagraphParameters & params = pit->params();
592 params.spacing(spacing);
594 // does the layout allow the new alignment?
595 LyXLayout_ptr const & layout = pit->layout();
597 if (align == LYX_ALIGN_LAYOUT)
598 align = layout->align;
599 if (align & layout->alignpossible) {
600 if (align == layout->align)
601 params.align(LYX_ALIGN_LAYOUT);
605 pit->setLabelWidthString(labelwidthstring);
606 params.noindent(noindent);
609 redoParagraphs(getPar(cur.selStart()), undopit);
613 string expandLabel(LyXTextClass const & textclass,
614 LyXLayout_ptr const & layout, bool appendix)
616 string fmt = appendix ?
617 layout->labelstring_appendix() : layout->labelstring();
619 // handle 'inherited level parts' in 'fmt',
620 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
621 size_t const i = fmt.find('@', 0);
622 if (i != string::npos) {
623 size_t const j = fmt.find('@', i + 1);
624 if (j != string::npos) {
625 string parent(fmt, i + 1, j - i - 1);
626 string label = expandLabel(textclass, textclass[parent], appendix);
627 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
631 return textclass.counters().counterLabel(fmt);
637 void incrementItemDepth(ParagraphList::iterator pit,
638 ParagraphList::iterator first_pit)
640 int const cur_labeltype = pit->layout()->labeltype;
642 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
645 int const cur_depth = pit->getDepth();
647 ParagraphList::iterator prev_pit = boost::prior(pit);
649 int const prev_depth = prev_pit->getDepth();
650 int const prev_labeltype = prev_pit->layout()->labeltype;
651 if (prev_depth == 0 && cur_depth > 0) {
652 if (prev_labeltype == cur_labeltype) {
653 pit->itemdepth = prev_pit->itemdepth + 1;
656 } else if (prev_depth < cur_depth) {
657 if (prev_labeltype == cur_labeltype) {
658 pit->itemdepth = prev_pit->itemdepth + 1;
661 } else if (prev_depth == cur_depth) {
662 if (prev_labeltype == cur_labeltype) {
663 pit->itemdepth = prev_pit->itemdepth;
667 if (prev_pit == first_pit)
675 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
676 ParagraphList::iterator firstpit,
682 int const cur_depth = pit->getDepth();
683 ParagraphList::iterator prev_pit = boost::prior(pit);
685 int const prev_depth = prev_pit->getDepth();
686 int const prev_labeltype = prev_pit->layout()->labeltype;
687 if (prev_depth <= cur_depth) {
688 if (prev_labeltype != LABEL_ENUMERATE) {
689 switch (pit->itemdepth) {
691 counters.reset("enumi");
693 counters.reset("enumii");
695 counters.reset("enumiii");
697 counters.reset("enumiv");
703 if (prev_pit == firstpit)
713 // set the counter of a paragraph. This includes the labels
714 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
716 BufferParams const & bufparams = buf.params();
717 LyXTextClass const & textclass = bufparams.getLyXTextClass();
718 LyXLayout_ptr const & layout = pit->layout();
719 ParagraphList::iterator first_pit = paragraphs().begin();
720 Counters & counters = textclass.counters();
725 if (pit == first_pit) {
726 pit->params().appendix(pit->params().startOfAppendix());
728 pit->params().appendix(boost::prior(pit)->params().appendix());
729 if (!pit->params().appendix() &&
730 pit->params().startOfAppendix()) {
731 pit->params().appendix(true);
732 textclass.counters().reset();
735 // Maybe we have to increment the item depth.
736 incrementItemDepth(pit, first_pit);
739 // erase what was there before
740 pit->params().labelString(string());
742 if (layout->margintype == MARGIN_MANUAL) {
743 if (pit->params().labelWidthString().empty())
744 pit->setLabelWidthString(layout->labelstring());
746 pit->setLabelWidthString(string());
749 // is it a layout that has an automatic label?
750 if (layout->labeltype == LABEL_COUNTER) {
751 BufferParams const & bufparams = buf.params();
752 LyXTextClass const & textclass = bufparams.getLyXTextClass();
753 counters.step(layout->counter);
754 string label = expandLabel(textclass, layout, pit->params().appendix());
755 pit->params().labelString(label);
756 } else if (layout->labeltype == LABEL_ITEMIZE) {
757 // At some point of time we should do something more
758 // clever here, like:
759 // pit->params().labelString(
760 // bufparams.user_defined_bullet(pit->itemdepth).getText());
761 // for now, use a simple hardcoded label
763 switch (pit->itemdepth) {
778 pit->params().labelString(itemlabel);
779 } else if (layout->labeltype == LABEL_ENUMERATE) {
780 // Maybe we have to reset the enumeration counter.
781 resetEnumCounterIfNeeded(pit, first_pit, counters);
784 // Yes I know this is a really, really! bad solution
786 string enumcounter = "enum";
788 switch (pit->itemdepth) {
800 // not a valid enumdepth...
804 counters.step(enumcounter);
806 pit->params().labelString(counters.enumLabel(enumcounter));
807 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
808 counters.step("bibitem");
809 int number = counters.value("bibitem");
810 if (pit->bibitem()) {
811 pit->bibitem()->setCounter(number);
812 pit->params().labelString(layout->labelstring());
814 // In biblio should't be following counters but...
816 string s = buf.B_(layout->labelstring());
819 if (layout->labeltype == LABEL_SENSITIVE) {
820 ParagraphList::iterator end = paragraphs().end();
821 ParagraphList::iterator tmppit = pit;
824 while (tmppit != end && tmppit->inInset()
825 // the single '=' is intended below
826 && (in = tmppit->inInset()->owner()))
828 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
829 in->lyxCode() == InsetOld::WRAP_CODE) {
833 Paragraph const * owner = &ownerPar(buf, in);
835 for ( ; tmppit != end; ++tmppit)
836 if (&*tmppit == owner)
844 if (in->lyxCode() == InsetOld::FLOAT_CODE)
845 type = static_cast<InsetFloat*>(in)->params().type;
846 else if (in->lyxCode() == InsetOld::WRAP_CODE)
847 type = static_cast<InsetWrap*>(in)->params().type;
851 Floating const & fl = textclass.floats().getType(type);
853 counters.step(fl.type());
855 // Doesn't work... yet.
856 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
858 // par->SetLayout(0);
859 // s = layout->labelstring;
860 s = _("Senseless: ");
863 pit->params().labelString(s);
869 // Updates all counters.
870 void LyXText::updateCounters()
873 bv()->buffer()->params().getLyXTextClass().counters().reset();
875 bool update_pos = false;
877 ParagraphList::iterator beg = paragraphs().begin();
878 ParagraphList::iterator end = paragraphs().end();
879 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
880 string const oldLabel = pit->params().labelString();
883 maxdepth = boost::prior(pit)->getMaxDepthAfter();
885 if (pit->params().depth() > maxdepth)
886 pit->params().depth(maxdepth);
888 // setCounter can potentially change the labelString.
889 setCounter(*bv()->buffer(), pit);
890 string const & newLabel = pit->params().labelString();
891 if (oldLabel != newLabel) {
892 redoParagraphInternal(pit);
898 updateParPositions();
902 void LyXText::insertInset(InsetOld * inset)
904 if (!cursorPar()->insetAllowed(inset->lyxCode()))
907 recUndo(cursor().par());
909 cursorPar()->insertInset(cursor().pos(), inset);
910 // Just to rebreak and refresh correctly.
911 // The character will not be inserted a second time
912 insertChar(Paragraph::META_INSET);
913 // If we enter a highly editable inset the cursor should be before
914 // the inset. After an undo LyX tries to call inset->edit(...)
915 // and fails if the cursor is behind the inset and getInset
916 // does not return the inset!
917 if (isHighlyEditableInset(inset))
924 void LyXText::cutSelection(bool doclear, bool realcut)
926 LCursor & cur = bv()->cursor();
927 // Stuff what we got on the clipboard. Even if there is no selection.
929 // There is a problem with having the stuffing here in that the
930 // larger the selection the slower LyX will get. This can be
931 // solved by running the line below only when the selection has
932 // finished. The solution used currently just works, to make it
933 // faster we need to be more clever and probably also have more
934 // calls to stuffClipboard. (Lgb)
935 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
937 // This doesn't make sense, if there is no selection
938 if (!cur.selection())
941 // OK, we have a selection. This is always between cur.selStart()
944 // make sure that the depth behind the selection are restored, too
945 ParagraphList::iterator begpit = getPar(cur.selStart().par());
946 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
947 ParagraphList::iterator undopit = undoSpan(endpit);
948 recUndo(cur.selStart().par(), parOffset(undopit) - 1);
950 int endpos = cur.selEnd().pos();
952 BufferParams const & bufparams = bv()->buffer()->params();
953 boost::tie(endpit, endpos) = realcut ?
954 CutAndPaste::cutSelection(bufparams,
957 cur.selStart().pos(), endpos,
960 : CutAndPaste::eraseSelection(bufparams,
963 cur.selStart().pos(), endpos,
965 // sometimes necessary
967 begpit->stripLeadingSpaces();
969 redoParagraphs(begpit, undopit);
970 // cutSelection can invalidate the cursor so we need to set
972 // we prefer the end for when tracking changes
973 cursor().pos(endpos);
974 cursor().par(parOffset(endpit));
976 // need a valid cursor. (Lgb)
977 cur.clearSelection();
982 void LyXText::copySelection()
984 LCursor & cur = bv()->cursor();
985 // stuff the selection onto the X clipboard, from an explicit copy request
986 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
988 // this doesnt make sense, if there is no selection
989 if (!cur.selection())
992 // ok we have a selection. This is always between cur.selStart()
993 // and sel_end cursor
995 // copy behind a space if there is one
996 while (getPar(cur.selStart())->size() > cur.selStart().pos()
997 && getPar(cur.selStart())->isLineSeparator(cur.selStart().pos())
998 && (cur.selStart().par() != cur.selEnd().par()
999 || cur.selStart().pos() < cur.selEnd().pos()))
1000 cur.selStart().pos(cur.selStart().pos() + 1);
1002 CutAndPaste::copySelection(getPar(cur.selStart().par()),
1003 getPar(cur.selEnd().par()),
1004 cur.selStart().pos(),
1006 bv()->buffer()->params().textclass);
1010 void LyXText::pasteSelection(size_t sel_index)
1012 LCursor & cur = bv()->cursor();
1013 // this does not make sense, if there is nothing to paste
1014 if (!CutAndPaste::checkPastePossible())
1017 recUndo(cursor().par());
1019 ParagraphList::iterator endpit;
1024 boost::tie(ppp, endpit) =
1025 CutAndPaste::pasteSelection(*bv()->buffer(),
1027 cursorPar(), cursor().pos(),
1028 bv()->buffer()->params().textclass,
1030 bufferErrors(*bv()->buffer(), el);
1031 bv()->showErrorList(_("Paste"));
1033 redoParagraphs(cursorPar(), endpit);
1035 cur.clearSelection();
1037 setCursor(ppp.first, ppp.second);
1043 void LyXText::setSelectionRange(lyx::pos_type length)
1048 LCursor & cur = bv()->cursor();
1056 // simple replacing. The font of the first selected character is used
1057 void LyXText::replaceSelectionWithString(string const & str)
1059 LCursor & cur = bv()->cursor();
1063 // Get font setting before we cut
1064 pos_type pos = cur.selEnd().pos();
1065 LyXFont const font = getPar(cur.selStart())
1066 ->getFontSettings(bv()->buffer()->params(),
1067 cur.selStart().pos());
1069 // Insert the new string
1070 string::const_iterator cit = str.begin();
1071 string::const_iterator end = str.end();
1072 for (; cit != end; ++cit) {
1073 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1077 // Cut the selection
1078 cutSelection(true, false);
1084 // needed to insert the selection
1085 void LyXText::insertStringAsLines(string const & str)
1087 LCursor & cur = bv()->cursor();
1088 ParagraphList::iterator pit = cursorPar();
1089 pos_type pos = cursor().pos();
1090 ParagraphList::iterator endpit = boost::next(cursorPar());
1092 recUndo(cursor().par());
1094 // only to be sure, should not be neccessary
1095 cur.clearSelection();
1096 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1098 redoParagraphs(cursorPar(), endpit);
1100 setCursor(pit, pos);
1105 // turn double CR to single CR, others are converted into one
1106 // blank. Then insertStringAsLines is called
1107 void LyXText::insertStringAsParagraphs(string const & str)
1109 string linestr(str);
1110 bool newline_inserted = false;
1111 string::size_type const siz = linestr.length();
1113 for (string::size_type i = 0; i < siz; ++i) {
1114 if (linestr[i] == '\n') {
1115 if (newline_inserted) {
1116 // we know that \r will be ignored by
1117 // insertStringAsLines. Of course, it is a dirty
1118 // trick, but it works...
1119 linestr[i - 1] = '\r';
1123 newline_inserted = true;
1125 } else if (IsPrintable(linestr[i])) {
1126 newline_inserted = false;
1129 insertStringAsLines(linestr);
1133 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1135 setCursor(parOffset(pit), pos);
1139 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1142 CursorSlice old_cursor = cursor();
1143 setCursorIntern(par, pos, setfont, boundary);
1144 return deleteEmptyParagraphMechanism(old_cursor);
1148 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1149 pos_type pos, bool boundary)
1151 BOOST_ASSERT(par != int(paragraphs().size()));
1155 cur.boundary(boundary);
1157 // no rows, no fun...
1158 if (paragraphs().begin()->rows.empty())
1161 // now some strict checking
1162 Paragraph & para = *getPar(par);
1163 Row const & row = *para.getRow(pos);
1164 pos_type const end = row.endpos();
1166 // None of these should happen, but we're scaredy-cats
1168 lyxerr << "dont like -1" << endl;
1171 BOOST_ASSERT(false);
1172 } else if (pos > para.size()) {
1173 lyxerr << "dont like 1, pos: " << pos
1174 << " size: " << para.size()
1175 << " row.pos():" << row.pos()
1176 << " paroffset: " << par << endl;
1179 BOOST_ASSERT(false);
1180 } else if (pos > end) {
1181 lyxerr << "dont like 2 please report" << endl;
1182 // This shouldn't happen.
1185 BOOST_ASSERT(false);
1186 } else if (pos < row.pos()) {
1187 lyxerr << "dont like 3 please report pos:" << pos
1188 << " size: " << para.size()
1189 << " row.pos():" << row.pos()
1190 << " paroffset: " << par << endl;
1193 BOOST_ASSERT(false);
1198 void LyXText::setCursorIntern(paroffset_type par,
1199 pos_type pos, bool setfont, bool boundary)
1201 setCursor(cursor(), par, pos, boundary);
1202 bv()->cursor().x_target(cursorX() + xo_);
1208 void LyXText::setCurrentFont()
1210 LCursor & cur = bv()->cursor();
1211 pos_type pos = cur.pos();
1212 ParagraphList::iterator pit = cursorPar();
1214 if (cursor().boundary() && pos > 0)
1218 if (pos == pit->size())
1220 else // potentional bug... BUG (Lgb)
1221 if (pit->isSeparator(pos)) {
1222 if (pos > pit->getRow(pos)->pos() &&
1223 bidi.level(pos) % 2 ==
1224 bidi.level(pos - 1) % 2)
1226 else if (pos + 1 < pit->size())
1231 BufferParams const & bufparams = bv()->buffer()->params();
1232 current_font = pit->getFontSettings(bufparams, pos);
1233 real_current_font = getFont(pit, pos);
1235 if (cursor().pos() == pit->size() &&
1236 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1237 !cursor().boundary()) {
1238 Language const * lang =
1239 pit->getParLanguage(bufparams);
1240 current_font.setLanguage(lang);
1241 current_font.setNumber(LyXFont::OFF);
1242 real_current_font.setLanguage(lang);
1243 real_current_font.setNumber(LyXFont::OFF);
1248 // returns the column near the specified x-coordinate of the row
1249 // x is set to the real beginning of this column
1250 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1251 Row const & row, int & x, bool & boundary) const
1253 double tmpx = row.x();
1254 double fill_separator = row.fill_separator();
1255 double fill_hfill = row.fill_hfill();
1256 double fill_label_hfill = row.fill_label_hfill();
1258 pos_type vc = row.pos();
1259 pos_type end = row.endpos();
1261 LyXLayout_ptr const & layout = pit->layout();
1263 bool left_side = false;
1265 pos_type body_pos = pit->beginOfBody();
1266 double last_tmpx = tmpx;
1269 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1272 // check for empty row
1278 while (vc < end && tmpx <= x) {
1279 c = bidi.vis2log(vc);
1281 if (body_pos > 0 && c == body_pos - 1) {
1282 tmpx += fill_label_hfill +
1283 font_metrics::width(layout->labelsep, getLabelFont(pit));
1284 if (pit->isLineSeparator(body_pos - 1))
1285 tmpx -= singleWidth(pit, body_pos - 1);
1288 if (hfillExpansion(*pit, row, c)) {
1289 tmpx += singleWidth(pit, c);
1293 tmpx += fill_label_hfill;
1294 } else if (pit->isSeparator(c)) {
1295 tmpx += singleWidth(pit, c);
1297 tmpx += fill_separator;
1299 tmpx += singleWidth(pit, c);
1304 if ((tmpx + last_tmpx) / 2 > x) {
1309 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1312 // This (rtl_support test) is not needed, but gives
1313 // some speedup if rtl_support == false
1314 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1316 // If lastrow is false, we don't need to compute
1317 // the value of rtl.
1318 bool const rtl = lastrow
1319 ? pit->isRightToLeftPar(bv()->buffer()->params())
1322 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1323 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1325 else if (vc == row.pos()) {
1326 c = bidi.vis2log(vc);
1327 if (bidi.level(c) % 2 == 1)
1330 c = bidi.vis2log(vc - 1);
1331 bool const rtl = (bidi.level(c) % 2 == 1);
1332 if (left_side == rtl) {
1334 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1338 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1339 if (bidi.level(end -1) % 2 == 0)
1340 tmpx -= singleWidth(pit, end - 1);
1342 tmpx += singleWidth(pit, end - 1);
1352 void LyXText::setCursorFromCoordinates(int x, int y)
1354 CursorSlice old_cursor = cursor();
1355 setCursorFromCoordinates(cursor(), x, y);
1357 deleteEmptyParagraphMechanism(old_cursor);
1361 // x,y are coordinates relative to this LyXText
1362 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1364 ParagraphList::iterator pit;
1365 Row const & row = *getRowNearY(y, pit);
1367 pos_type const column = getColumnNearX(pit, row, x, bound);
1368 cur.par(parOffset(pit));
1369 cur.pos(row.pos() + column);
1370 cur.boundary(bound);
1374 bool LyXText::checkAndActivateInset(bool front)
1376 if (cursor().pos() == cursorPar()->size())
1378 InsetOld * inset = cursorPar()->getInset(cursor().pos());
1379 if (!isHighlyEditableInset(inset))
1381 inset->edit(bv()->cursor(), front);
1386 DispatchResult LyXText::moveRight()
1388 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1389 return moveLeftIntern(false, true, false);
1391 return moveRightIntern(true, true, false);
1395 DispatchResult LyXText::moveLeft()
1397 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1398 return moveRightIntern(true, true, false);
1400 return moveLeftIntern(false, true, false);
1404 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1406 ParagraphList::iterator c_par = cursorPar();
1407 if (boost::next(c_par) == paragraphs().end()
1408 && cursor().pos() >= c_par->size())
1409 return DispatchResult(false, FINISHED_RIGHT);
1410 if (activate_inset && checkAndActivateInset(front))
1411 return DispatchResult(true, true);
1414 bv()->cursor().clearSelection();
1415 return DispatchResult(true);
1419 DispatchResult LyXText::moveLeftIntern(bool front,
1420 bool activate_inset, bool selecting)
1422 if (cursor().par() == 0 && cursor().pos() <= 0)
1423 return DispatchResult(false, FINISHED);
1426 bv()->cursor().clearSelection();
1427 if (activate_inset && checkAndActivateInset(front))
1428 return DispatchResult(true, true);
1429 return DispatchResult(true);
1433 DispatchResult LyXText::moveUp()
1435 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1436 return DispatchResult(false, FINISHED_UP);
1438 bv()->cursor().clearSelection();
1439 return DispatchResult(true);
1443 DispatchResult LyXText::moveDown()
1445 LCursor & cur = bv()->cursor();
1446 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1447 return DispatchResult(false, FINISHED_DOWN);
1449 cur.clearSelection();
1450 return DispatchResult(true);
1454 bool LyXText::cursorLeft(bool internal)
1456 LCursor & cur = bv()->cursor();
1457 if (cur.pos() > 0) {
1458 bool boundary = cur.boundary();
1459 setCursor(cur.par(), cur.pos() - 1, true, false);
1460 if (!internal && !boundary &&
1461 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1462 setCursor(cur.par(), cur.pos() + 1, true, true);
1466 if (cur.par() != 0) {
1467 // steps into the paragraph above
1468 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1476 bool LyXText::cursorRight(bool internal)
1478 LCursor & cur = bv()->cursor();
1479 if (!internal && cur.boundary()) {
1480 setCursor(cur.par(), cur.pos(), true, false);
1484 if (cur.pos() != cur.lastpos()) {
1485 setCursor(cur.par(), cur.pos() + 1, true, false);
1486 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1488 setCursor(cur.par(), cur.pos(), true, true);
1492 if (cur.par() + 1 != int(paragraphs().size())) {
1493 setCursor(cur.par() + 1, 0);
1501 void LyXText::cursorUp(bool selecting)
1503 LCursor & cur = bv()->cursor();
1504 Row const & row = *cursorRow();
1505 int x = cur.x_target() - xo_;
1506 int y = cursorY() - row.baseline() - 1;
1507 setCursorFromCoordinates(x, y);
1510 int y_abs = y + yo_ - bv()->top_y();
1511 InsetOld * inset_hit = checkInsetHit(cur.x_target(), y_abs);
1512 if (inset_hit && isHighlyEditableInset(inset_hit))
1513 inset_hit->edit(cur, cur.x_target(), y_abs);
1518 void LyXText::cursorDown(bool selecting)
1520 LCursor & cur = bv()->cursor();
1521 Row const & row = *cursorRow();
1522 int x = cur.x_target() - xo_;
1523 int y = cursorY() - row.baseline() + row.height() + 1;
1524 setCursorFromCoordinates(x, y);
1527 int y_abs = y + yo_ - bv()->top_y();
1528 InsetOld * inset_hit = checkInsetHit(cur.x_target(), y_abs);
1529 if (inset_hit && isHighlyEditableInset(inset_hit))
1530 inset_hit->edit(cur, cur.x_target(), y_abs);
1535 void LyXText::cursorUpParagraph()
1537 ParagraphList::iterator cpit = cursorPar();
1538 if (cursor().pos() > 0)
1540 else if (cpit != paragraphs().begin())
1541 setCursor(boost::prior(cpit), 0);
1545 void LyXText::cursorDownParagraph()
1547 ParagraphList::iterator pit = cursorPar();
1548 ParagraphList::iterator next_pit = boost::next(pit);
1550 if (next_pit != paragraphs().end())
1551 setCursor(next_pit, 0);
1553 setCursor(pit, pit->size());
1557 // fix the cursor `cur' after a characters has been deleted at `where'
1558 // position. Called by deleteEmptyParagraphMechanism
1559 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1561 // if cursor is not in the paragraph where the delete occured,
1563 if (cur.par() != where.par())
1566 // if cursor position is after the place where the delete occured,
1568 if (cur.pos() > where.pos())
1569 cur.pos(cur.pos()-1);
1571 // check also if we don't want to set the cursor on a spot behind the
1572 // pagragraph because we erased the last character.
1573 if (cur.pos() > cur.lastpos())
1574 cur.pos() = cur.lastpos();
1578 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1580 #warning Disabled as it crashes after the cursor data shift... (Andre)
1583 // Would be wrong to delete anything if we have a selection.
1584 if (bv()->cursor().selection())
1587 // Don't do anything if the cursor is invalid
1588 if (old_cursor.par() == -1)
1592 // We allow all kinds of "mumbo-jumbo" when freespacing.
1593 ParagraphList::iterator const old_pit = getPar(old_cursor);
1594 if (old_pit->isFreeSpacing())
1597 /* Ok I'll put some comments here about what is missing.
1598 I have fixed BackSpace (and thus Delete) to not delete
1599 double-spaces automagically. I have also changed Cut,
1600 Copy and Paste to hopefully do some sensible things.
1601 There are still some small problems that can lead to
1602 double spaces stored in the document file or space at
1603 the beginning of paragraphs(). This happens if you have
1604 the cursor between to spaces and then save. Or if you
1605 cut and paste and the selection have a space at the
1606 beginning and then save right after the paste. I am
1607 sure none of these are very hard to fix, but I will
1608 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1609 that I can get some feedback. (Lgb)
1612 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1613 // delete the LineSeparator.
1616 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1617 // delete the LineSeparator.
1620 // If the pos around the old_cursor were spaces, delete one of them.
1621 if (old_cursor.par() != cursor().par()
1622 || old_cursor.pos() != cursor().pos()) {
1624 // Only if the cursor has really moved
1625 if (old_cursor.pos() > 0
1626 && old_cursor.pos() < old_pit->size()
1627 && old_pit->isLineSeparator(old_cursor.pos())
1628 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1629 bool erased = old_pit->erase(old_cursor.pos() - 1);
1630 redoParagraph(old_pit);
1634 #ifdef WITH_WARNINGS
1635 #warning This will not work anymore when we have multiple views of the same buffer
1636 // In this case, we will have to correct also the cursors held by
1637 // other bufferviews. It will probably be easier to do that in a more
1638 // automated way in CursorSlice code. (JMarc 26/09/2001)
1640 // correct all cursors held by the LyXText
1641 fixCursorAfterDelete(cursor(), old_cursor);
1642 fixCursorAfterDelete(anchor(), old_cursor);
1647 // don't delete anything if this is the ONLY paragraph!
1648 if (paragraphs().size() == 1)
1651 // Do not delete empty paragraphs with keepempty set.
1652 if (old_pit->allowEmpty())
1655 // only do our magic if we changed paragraph
1656 if (old_cursor.par() == cursor().par())
1659 // record if we have deleted a paragraph
1660 // we can't possibly have deleted a paragraph before this point
1661 bool deleted = false;
1663 if (old_pit->empty()
1664 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1665 // ok, we will delete something
1666 CursorSlice tmpcursor;
1670 bool selection_position_was_oldcursor_position =
1671 anchor().par() == old_cursor.par()
1672 && anchor().pos() == old_cursor.pos();
1674 tmpcursor = cursor();
1675 cursor() = old_cursor; // that undo can restore the right cursor position
1677 ParagraphList::iterator endpit = boost::next(old_pit);
1678 while (endpit != paragraphs().end() && endpit->getDepth())
1681 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1682 cursor() = tmpcursor;
1685 ParagraphList::iterator tmppit = cursorPar();
1687 paragraphs().erase(old_pit);
1688 // update cursor par offset
1689 cursor().par(parOffset(tmppit));
1692 if (selection_position_was_oldcursor_position) {
1693 // correct selection
1694 bv()->resetAnchor();
1701 if (old_pit->stripLeadingSpaces()) {
1702 redoParagraph(old_pit);
1703 bv()->resetAnchor();
1710 ParagraphList & LyXText::paragraphs() const
1712 return const_cast<ParagraphList &>(paragraphs_);
1716 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1718 recordUndo(Undo::ATOMIC, this, first, last);
1722 void LyXText::recUndo(lyx::paroffset_type par) const
1724 recordUndo(Undo::ATOMIC, this, par, par);
1728 bool LyXText::isInInset() const
1734 int defaultRowHeight()
1736 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);