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(bv()->selStart().par());
337 ParagraphList::iterator end = boost::next(getPar(bv()->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.bv()->selection().set()) {
353 beg = text.cursorPar();
354 end = boost::next(beg);
356 beg = text.getPar(text.bv()->selStart());
357 end = boost::next(text.getPar(text.bv()->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 (!bv()->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(bv()->selStart().par(), bv()->selEnd().par());
454 ParagraphList::iterator beg = getPar(bv()->selStart().par());
455 ParagraphList::iterator end = getPar(bv()->selEnd().par());
457 PosIterator pos(¶graphs(), beg, bv()->selStart().pos());
458 PosIterator posend(¶graphs(), end, bv()->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 // the cursor set functions have a special mechanism. When they
475 // realize you left an empty paragraph, they will delete it.
477 void LyXText::cursorHome()
479 ParagraphList::iterator cpit = cursorPar();
480 setCursor(cpit, cpit->getRow(cursor().pos())->pos());
484 void LyXText::cursorEnd()
486 ParagraphList::iterator cpit = cursorPar();
487 pos_type end = cpit->getRow(cursor().pos())->endpos();
488 // if not on the last row of the par, put the cursor before
490 setCursor(cpit, end == cpit->size() ? end : end - 1);
494 void LyXText::cursorTop()
496 setCursor(paragraphs().begin(), 0);
500 void LyXText::cursorBottom()
502 ParagraphList::iterator lastpit =
503 boost::prior(paragraphs().end());
504 setCursor(lastpit, lastpit->size());
508 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
510 // If the mask is completely neutral, tell user
511 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
512 // Could only happen with user style
513 bv()->owner()->message(_("No font change defined. "
514 "Use Character under the Layout menu to define font change."));
518 // Try implicit word selection
519 // If there is a change in the language the implicit word selection
521 CursorSlice resetCursor = cursor();
522 bool implicitSelection =
523 font.language() == ignore_language
524 && font.number() == LyXFont::IGNORE
525 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
528 setFont(font, toggleall);
530 // Implicit selections are cleared afterwards
531 //and cursor is set to the original position.
532 if (implicitSelection) {
533 bv()->clearSelection();
534 cursor() = resetCursor;
540 string LyXText::getStringToIndex()
542 // Try implicit word selection
543 // If there is a change in the language the implicit word selection
545 CursorSlice const reset_cursor = cursor();
546 bool const implicitSelection =
547 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
550 if (!bv()->selection().set())
551 bv()->owner()->message(_("Nothing to index!"));
552 else if (bv()->selStart().par() != bv()->selEnd().par())
553 bv()->owner()->message(_("Cannot index more than one paragraph!"));
555 idxstring = selectionAsString(*bv()->buffer(), false);
557 // Reset cursors to their original position.
558 cursor() = reset_cursor;
561 // Clear the implicit selection.
562 if (implicitSelection)
563 bv()->clearSelection();
569 // the DTP switches for paragraphs(). LyX will store them in the first
570 // physical paragraph. When a paragraph is broken, the top settings rest,
571 // the bottom settings are given to the new one. So I can make sure,
572 // they do not duplicate themself and you cannot play dirty tricks with
575 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
576 string const & labelwidthstring, bool noindent)
578 // make sure that the depth behind the selection are restored, too
579 ParagraphList::iterator undopit = undoSpan(getPar(bv()->selEnd()));
580 recUndo(bv()->selStart().par(), parOffset(undopit) - 1);
582 ParagraphList::reverse_iterator pit(getPar(bv()->selEnd().par()));
583 ParagraphList::reverse_iterator beg(getPar(bv()->selStart().par()));
585 for (--pit; pit != beg; ++pit) {
586 ParagraphParameters & params = pit->params();
587 params.spacing(spacing);
589 // does the layout allow the new alignment?
590 LyXLayout_ptr const & layout = pit->layout();
592 if (align == LYX_ALIGN_LAYOUT)
593 align = layout->align;
594 if (align & layout->alignpossible) {
595 if (align == layout->align)
596 params.align(LYX_ALIGN_LAYOUT);
600 pit->setLabelWidthString(labelwidthstring);
601 params.noindent(noindent);
604 redoParagraphs(getPar(bv()->selStart()), undopit);
608 string expandLabel(LyXTextClass const & textclass,
609 LyXLayout_ptr const & layout, bool appendix)
611 string fmt = appendix ?
612 layout->labelstring_appendix() : layout->labelstring();
614 // handle 'inherited level parts' in 'fmt',
615 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
616 size_t const i = fmt.find('@', 0);
617 if (i != string::npos) {
618 size_t const j = fmt.find('@', i + 1);
619 if (j != string::npos) {
620 string parent(fmt, i + 1, j - i - 1);
621 string label = expandLabel(textclass, textclass[parent], appendix);
622 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
626 return textclass.counters().counterLabel(fmt);
632 void incrementItemDepth(ParagraphList::iterator pit,
633 ParagraphList::iterator first_pit)
635 int const cur_labeltype = pit->layout()->labeltype;
637 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
640 int const cur_depth = pit->getDepth();
642 ParagraphList::iterator prev_pit = boost::prior(pit);
644 int const prev_depth = prev_pit->getDepth();
645 int const prev_labeltype = prev_pit->layout()->labeltype;
646 if (prev_depth == 0 && cur_depth > 0) {
647 if (prev_labeltype == cur_labeltype) {
648 pit->itemdepth = prev_pit->itemdepth + 1;
651 } else if (prev_depth < cur_depth) {
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;
662 if (prev_pit == first_pit)
670 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
671 ParagraphList::iterator firstpit,
677 int const cur_depth = pit->getDepth();
678 ParagraphList::iterator prev_pit = boost::prior(pit);
680 int const prev_depth = prev_pit->getDepth();
681 int const prev_labeltype = prev_pit->layout()->labeltype;
682 if (prev_depth <= cur_depth) {
683 if (prev_labeltype != LABEL_ENUMERATE) {
684 switch (pit->itemdepth) {
686 counters.reset("enumi");
688 counters.reset("enumii");
690 counters.reset("enumiii");
692 counters.reset("enumiv");
698 if (prev_pit == firstpit)
708 // set the counter of a paragraph. This includes the labels
709 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
711 BufferParams const & bufparams = buf.params();
712 LyXTextClass const & textclass = bufparams.getLyXTextClass();
713 LyXLayout_ptr const & layout = pit->layout();
714 ParagraphList::iterator first_pit = paragraphs().begin();
715 Counters & counters = textclass.counters();
720 if (pit == first_pit) {
721 pit->params().appendix(pit->params().startOfAppendix());
723 pit->params().appendix(boost::prior(pit)->params().appendix());
724 if (!pit->params().appendix() &&
725 pit->params().startOfAppendix()) {
726 pit->params().appendix(true);
727 textclass.counters().reset();
730 // Maybe we have to increment the item depth.
731 incrementItemDepth(pit, first_pit);
734 // erase what was there before
735 pit->params().labelString(string());
737 if (layout->margintype == MARGIN_MANUAL) {
738 if (pit->params().labelWidthString().empty())
739 pit->setLabelWidthString(layout->labelstring());
741 pit->setLabelWidthString(string());
744 // is it a layout that has an automatic label?
745 if (layout->labeltype == LABEL_COUNTER) {
746 BufferParams const & bufparams = buf.params();
747 LyXTextClass const & textclass = bufparams.getLyXTextClass();
748 counters.step(layout->counter);
749 string label = expandLabel(textclass, layout, pit->params().appendix());
750 pit->params().labelString(label);
751 } else if (layout->labeltype == LABEL_ITEMIZE) {
752 // At some point of time we should do something more
753 // clever here, like:
754 // pit->params().labelString(
755 // bufparams.user_defined_bullet(pit->itemdepth).getText());
756 // for now, use a simple hardcoded label
758 switch (pit->itemdepth) {
773 pit->params().labelString(itemlabel);
774 } else if (layout->labeltype == LABEL_ENUMERATE) {
775 // Maybe we have to reset the enumeration counter.
776 resetEnumCounterIfNeeded(pit, first_pit, counters);
779 // Yes I know this is a really, really! bad solution
781 string enumcounter = "enum";
783 switch (pit->itemdepth) {
795 // not a valid enumdepth...
799 counters.step(enumcounter);
801 pit->params().labelString(counters.enumLabel(enumcounter));
802 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
803 counters.step("bibitem");
804 int number = counters.value("bibitem");
805 if (pit->bibitem()) {
806 pit->bibitem()->setCounter(number);
807 pit->params().labelString(layout->labelstring());
809 // In biblio should't be following counters but...
811 string s = buf.B_(layout->labelstring());
814 if (layout->labeltype == LABEL_SENSITIVE) {
815 ParagraphList::iterator end = paragraphs().end();
816 ParagraphList::iterator tmppit = pit;
819 while (tmppit != end && tmppit->inInset()
820 // the single '=' is intended below
821 && (in = tmppit->inInset()->owner()))
823 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
824 in->lyxCode() == InsetOld::WRAP_CODE) {
828 Paragraph const * owner = &ownerPar(buf, in);
830 for ( ; tmppit != end; ++tmppit)
831 if (&*tmppit == owner)
839 if (in->lyxCode() == InsetOld::FLOAT_CODE)
840 type = static_cast<InsetFloat*>(in)->params().type;
841 else if (in->lyxCode() == InsetOld::WRAP_CODE)
842 type = static_cast<InsetWrap*>(in)->params().type;
846 Floating const & fl = textclass.floats().getType(type);
848 counters.step(fl.type());
850 // Doesn't work... yet.
851 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
853 // par->SetLayout(0);
854 // s = layout->labelstring;
855 s = _("Senseless: ");
858 pit->params().labelString(s);
864 // Updates all counters.
865 void LyXText::updateCounters()
868 bv()->buffer()->params().getLyXTextClass().counters().reset();
870 bool update_pos = false;
872 ParagraphList::iterator beg = paragraphs().begin();
873 ParagraphList::iterator end = paragraphs().end();
874 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
875 string const oldLabel = pit->params().labelString();
878 maxdepth = boost::prior(pit)->getMaxDepthAfter();
880 if (pit->params().depth() > maxdepth)
881 pit->params().depth(maxdepth);
883 // setCounter can potentially change the labelString.
884 setCounter(*bv()->buffer(), pit);
885 string const & newLabel = pit->params().labelString();
886 if (oldLabel != newLabel) {
887 redoParagraphInternal(pit);
893 updateParPositions();
897 void LyXText::insertInset(InsetOld * inset)
899 if (!cursorPar()->insetAllowed(inset->lyxCode()))
902 recUndo(cursor().par());
904 cursorPar()->insertInset(cursor().pos(), inset);
905 // Just to rebreak and refresh correctly.
906 // The character will not be inserted a second time
907 insertChar(Paragraph::META_INSET);
908 // If we enter a highly editable inset the cursor should be before
909 // the inset. After an undo LyX tries to call inset->edit(...)
910 // and fails if the cursor is behind the inset and getInset
911 // does not return the inset!
912 if (isHighlyEditableInset(inset))
919 void LyXText::cutSelection(bool doclear, bool realcut)
921 // Stuff what we got on the clipboard. Even if there is no selection.
923 // There is a problem with having the stuffing here in that the
924 // larger the selection the slower LyX will get. This can be
925 // solved by running the line below only when the selection has
926 // finished. The solution used currently just works, to make it
927 // faster we need to be more clever and probably also have more
928 // calls to stuffClipboard. (Lgb)
929 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
931 // This doesn't make sense, if there is no selection
932 if (!bv()->selection().set())
935 // OK, we have a selection. This is always between bv()->selStart()
936 // and bv()->selEnd()
938 // make sure that the depth behind the selection are restored, too
939 ParagraphList::iterator begpit = getPar(bv()->selStart().par());
940 ParagraphList::iterator endpit = getPar(bv()->selEnd().par());
941 ParagraphList::iterator undopit = undoSpan(endpit);
942 recUndo(bv()->selStart().par(), parOffset(undopit) - 1);
944 int endpos = bv()->selEnd().pos();
946 BufferParams const & bufparams = bv()->buffer()->params();
947 boost::tie(endpit, endpos) = realcut ?
948 CutAndPaste::cutSelection(bufparams,
951 bv()->selStart().pos(), endpos,
954 : CutAndPaste::eraseSelection(bufparams,
957 bv()->selStart().pos(), endpos,
959 // sometimes necessary
961 begpit->stripLeadingSpaces();
963 redoParagraphs(begpit, undopit);
964 // cutSelection can invalidate the cursor so we need to set
966 // we prefer the end for when tracking changes
967 cursor().pos(endpos);
968 cursor().par(parOffset(endpit));
970 // need a valid cursor. (Lgb)
971 bv()->clearSelection();
976 void LyXText::copySelection()
978 // stuff the selection onto the X clipboard, from an explicit copy request
979 bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
981 // this doesnt make sense, if there is no selection
982 if (!bv()->selection().set())
985 // ok we have a selection. This is always between bv()->selStart()
986 // and sel_end cursor
988 // copy behind a space if there is one
989 while (getPar(bv()->selStart())->size() > bv()->selStart().pos()
990 && getPar(bv()->selStart())->isLineSeparator(bv()->selStart().pos())
991 && (bv()->selStart().par() != bv()->selEnd().par()
992 || bv()->selStart().pos() < bv()->selEnd().pos()))
993 bv()->selStart().pos(bv()->selStart().pos() + 1);
995 CutAndPaste::copySelection(getPar(bv()->selStart().par()),
996 getPar(bv()->selEnd().par()),
997 bv()->selStart().pos(),
998 bv()->selEnd().pos(),
999 bv()->buffer()->params().textclass);
1003 void LyXText::pasteSelection(size_t sel_index)
1005 // this does not make sense, if there is nothing to paste
1006 if (!CutAndPaste::checkPastePossible())
1009 recUndo(cursor().par());
1011 ParagraphList::iterator endpit;
1016 boost::tie(ppp, endpit) =
1017 CutAndPaste::pasteSelection(*bv()->buffer(),
1019 cursorPar(), cursor().pos(),
1020 bv()->buffer()->params().textclass,
1022 bufferErrors(*bv()->buffer(), el);
1023 bv()->showErrorList(_("Paste"));
1025 redoParagraphs(cursorPar(), endpit);
1027 bv()->clearSelection();
1028 bv()->resetAnchor();
1029 setCursor(ppp.first, ppp.second);
1030 bv()->setSelection();
1035 void LyXText::setSelectionRange(lyx::pos_type length)
1040 bv()->resetAnchor();
1043 bv()->setSelection();
1047 // simple replacing. The font of the first selected character is used
1048 void LyXText::replaceSelectionWithString(string const & str)
1050 recUndo(cursor().par());
1053 // Get font setting before we cut
1054 pos_type pos = bv()->selEnd().pos();
1055 LyXFont const font = getPar(bv()->selStart())
1056 ->getFontSettings(bv()->buffer()->params(),
1057 bv()->selStart().pos());
1059 // Insert the new string
1060 string::const_iterator cit = str.begin();
1061 string::const_iterator end = str.end();
1062 for (; cit != end; ++cit) {
1063 getPar(bv()->selEnd())->insertChar(pos, (*cit), font);
1067 // Cut the selection
1068 cutSelection(true, false);
1074 // needed to insert the selection
1075 void LyXText::insertStringAsLines(string const & str)
1077 ParagraphList::iterator pit = cursorPar();
1078 pos_type pos = cursor().pos();
1079 ParagraphList::iterator endpit = boost::next(cursorPar());
1081 recUndo(cursor().par());
1083 // only to be sure, should not be neccessary
1084 bv()->clearSelection();
1085 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1087 redoParagraphs(cursorPar(), endpit);
1088 bv()->resetAnchor();
1089 setCursor(pit, pos);
1090 bv()->setSelection();
1094 // turn double CR to single CR, others are converted into one
1095 // blank. Then insertStringAsLines is called
1096 void LyXText::insertStringAsParagraphs(string const & str)
1098 string linestr(str);
1099 bool newline_inserted = false;
1100 string::size_type const siz = linestr.length();
1102 for (string::size_type i = 0; i < siz; ++i) {
1103 if (linestr[i] == '\n') {
1104 if (newline_inserted) {
1105 // we know that \r will be ignored by
1106 // insertStringAsLines. Of course, it is a dirty
1107 // trick, but it works...
1108 linestr[i - 1] = '\r';
1112 newline_inserted = true;
1114 } else if (IsPrintable(linestr[i])) {
1115 newline_inserted = false;
1118 insertStringAsLines(linestr);
1122 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1124 setCursor(parOffset(pit), pos);
1128 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1131 CursorSlice old_cursor = cursor();
1132 setCursorIntern(par, pos, setfont, boundary);
1133 return deleteEmptyParagraphMechanism(old_cursor);
1137 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1138 pos_type pos, bool boundary)
1140 BOOST_ASSERT(par != int(paragraphs().size()));
1144 cur.boundary(boundary);
1146 // no rows, no fun...
1147 if (paragraphs().begin()->rows.empty())
1150 // now some strict checking
1151 ParagraphList::iterator pit = getPar(par);
1152 Row const & row = *pit->getRow(pos);
1153 pos_type const end = row.endpos();
1155 // None of these should happen, but we're scaredy-cats
1157 lyxerr << "dont like -1" << endl;
1160 BOOST_ASSERT(false);
1161 } else if (pos > pit->size()) {
1162 lyxerr << "dont like 1, pos: " << pos
1163 << " size: " << pit->size()
1164 << " row.pos():" << row.pos()
1165 << " paroffset: " << par << endl;
1168 BOOST_ASSERT(false);
1169 } else if (pos > end) {
1170 lyxerr << "dont like 2 please report" << endl;
1171 // This shouldn't happen.
1174 BOOST_ASSERT(false);
1175 } else if (pos < row.pos()) {
1176 lyxerr << "dont like 3 please report pos:" << pos
1177 << " size: " << pit->size()
1178 << " row.pos():" << row.pos()
1179 << " paroffset: " << par << endl;
1182 BOOST_ASSERT(false);
1187 void LyXText::setCursorIntern(paroffset_type par,
1188 pos_type pos, bool setfont, bool boundary)
1190 setCursor(cursor(), par, pos, boundary);
1191 bv()->x_target(cursorX() + xo_);
1197 void LyXText::setCurrentFont()
1199 pos_type pos = cursor().pos();
1200 ParagraphList::iterator pit = cursorPar();
1202 if (cursor().boundary() && pos > 0)
1206 if (pos == pit->size())
1208 else // potentional bug... BUG (Lgb)
1209 if (pit->isSeparator(pos)) {
1210 if (pos > pit->getRow(pos)->pos() &&
1211 bidi.level(pos) % 2 ==
1212 bidi.level(pos - 1) % 2)
1214 else if (pos + 1 < pit->size())
1219 BufferParams const & bufparams = bv()->buffer()->params();
1220 current_font = pit->getFontSettings(bufparams, pos);
1221 real_current_font = getFont(pit, pos);
1223 if (cursor().pos() == pit->size() &&
1224 bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1225 !cursor().boundary()) {
1226 Language const * lang =
1227 pit->getParLanguage(bufparams);
1228 current_font.setLanguage(lang);
1229 current_font.setNumber(LyXFont::OFF);
1230 real_current_font.setLanguage(lang);
1231 real_current_font.setNumber(LyXFont::OFF);
1236 // returns the column near the specified x-coordinate of the row
1237 // x is set to the real beginning of this column
1238 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1239 Row const & row, int & x, bool & boundary) const
1241 double tmpx = row.x();
1242 double fill_separator = row.fill_separator();
1243 double fill_hfill = row.fill_hfill();
1244 double fill_label_hfill = row.fill_label_hfill();
1246 pos_type vc = row.pos();
1247 pos_type end = row.endpos();
1249 LyXLayout_ptr const & layout = pit->layout();
1251 bool left_side = false;
1253 pos_type body_pos = pit->beginOfBody();
1254 double last_tmpx = tmpx;
1257 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1260 // check for empty row
1266 while (vc < end && tmpx <= x) {
1267 c = bidi.vis2log(vc);
1269 if (body_pos > 0 && c == body_pos - 1) {
1270 tmpx += fill_label_hfill +
1271 font_metrics::width(layout->labelsep, getLabelFont(pit));
1272 if (pit->isLineSeparator(body_pos - 1))
1273 tmpx -= singleWidth(pit, body_pos - 1);
1276 if (hfillExpansion(*pit, row, c)) {
1277 tmpx += singleWidth(pit, c);
1281 tmpx += fill_label_hfill;
1282 } else if (pit->isSeparator(c)) {
1283 tmpx += singleWidth(pit, c);
1285 tmpx += fill_separator;
1287 tmpx += singleWidth(pit, c);
1292 if ((tmpx + last_tmpx) / 2 > x) {
1297 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1300 // This (rtl_support test) is not needed, but gives
1301 // some speedup if rtl_support == false
1302 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1304 // If lastrow is false, we don't need to compute
1305 // the value of rtl.
1306 bool const rtl = (lastrow)
1307 ? pit->isRightToLeftPar(bv()->buffer()->params())
1310 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1311 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1313 else if (vc == row.pos()) {
1314 c = bidi.vis2log(vc);
1315 if (bidi.level(c) % 2 == 1)
1318 c = bidi.vis2log(vc - 1);
1319 bool const rtl = (bidi.level(c) % 2 == 1);
1320 if (left_side == rtl) {
1322 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1326 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1327 if (bidi.level(end -1) % 2 == 0)
1328 tmpx -= singleWidth(pit, end - 1);
1330 tmpx += singleWidth(pit, end - 1);
1340 void LyXText::setCursorFromCoordinates(int x, int y)
1342 CursorSlice old_cursor = cursor();
1343 setCursorFromCoordinates(cursor(), x, y);
1345 deleteEmptyParagraphMechanism(old_cursor);
1349 // x,y are coordinates relative to this LyXText
1350 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1352 ParagraphList::iterator pit;
1353 Row const & row = *getRowNearY(y, pit);
1355 pos_type const column = getColumnNearX(pit, row, x, bound);
1356 cur.par(parOffset(pit));
1357 cur.pos(row.pos() + column);
1358 cur.boundary(bound);
1362 bool LyXText::checkAndActivateInset(bool front)
1364 if (cursor().pos() == cursorPar()->size())
1366 InsetOld * inset = cursorPar()->getInset(cursor().pos());
1367 if (!isHighlyEditableInset(inset))
1369 inset->edit(bv(), front);
1374 DispatchResult LyXText::moveRight()
1376 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1377 return moveLeftIntern(false, true, false);
1379 return moveRightIntern(true, true, false);
1383 DispatchResult LyXText::moveLeft()
1385 if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1386 return moveRightIntern(true, true, false);
1388 return moveLeftIntern(false, true, false);
1392 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1394 ParagraphList::iterator c_par = cursorPar();
1395 if (boost::next(c_par) == paragraphs().end()
1396 && cursor().pos() >= c_par->size())
1397 return DispatchResult(false, FINISHED_RIGHT);
1398 if (activate_inset && checkAndActivateInset(front))
1399 return DispatchResult(true, true);
1402 bv()->clearSelection();
1403 return DispatchResult(true);
1407 DispatchResult LyXText::moveLeftIntern(bool front,
1408 bool activate_inset, bool selecting)
1410 if (cursor().par() == 0 && cursor().pos() <= 0)
1411 return DispatchResult(false, FINISHED);
1414 bv()->clearSelection();
1415 if (activate_inset && checkAndActivateInset(front))
1416 return DispatchResult(true, true);
1417 return DispatchResult(true);
1421 DispatchResult LyXText::moveUp()
1423 if (cursorPar() == firstPar() && cursorRow() == firstRow())
1424 return DispatchResult(false, FINISHED_UP);
1426 bv()->clearSelection();
1427 return DispatchResult(true);
1431 DispatchResult LyXText::moveDown()
1433 if (cursorPar() == lastPar() && cursorRow() == lastRow())
1434 return DispatchResult(false, FINISHED_DOWN);
1436 bv()->clearSelection();
1437 return DispatchResult(true);
1441 bool LyXText::cursorLeft(bool internal)
1443 if (cursor().pos() > 0) {
1444 bool boundary = cursor().boundary();
1445 setCursor(cursor().par(), cursor().pos() - 1, true, false);
1446 if (!internal && !boundary &&
1447 bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor().pos() + 1))
1448 setCursor(cursor().par(), cursor().pos() + 1, true, true);
1452 if (cursor().par() != 0) {
1453 // steps into the paragraph above
1454 setCursor(cursor().par() - 1, boost::prior(cursorPar())->size());
1462 bool LyXText::cursorRight(bool internal)
1464 if (!internal && cursor().boundary()) {
1465 setCursor(cursor().par(), cursor().pos(), true, false);
1469 if (cursor().pos() != cursorPar()->size()) {
1470 setCursor(cursor().par(), cursor().pos() + 1, true, false);
1471 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1473 setCursor(cursor().par(), cursor().pos(), true, true);
1477 if (cursor().par() + 1 != int(paragraphs().size())) {
1478 setCursor(cursor().par() + 1, 0);
1486 void LyXText::cursorUp(bool selecting)
1488 Row const & row = *cursorRow();
1489 int x = bv()->x_target() - xo_;
1490 int y = cursorY() - row.baseline() - 1;
1491 setCursorFromCoordinates(x, y);
1494 int y_abs = y + yo_ - bv()->top_y();
1495 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1496 if (inset_hit && isHighlyEditableInset(inset_hit))
1497 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1502 void LyXText::cursorDown(bool selecting)
1504 Row const & row = *cursorRow();
1505 int x = bv()->x_target() - xo_;
1506 int y = cursorY() - row.baseline() + row.height() + 1;
1507 setCursorFromCoordinates(x, y);
1510 int y_abs = y + yo_ - bv()->top_y();
1511 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1512 if (inset_hit && isHighlyEditableInset(inset_hit))
1513 inset_hit->edit(bv(), bv()->x_target(), y_abs);
1518 void LyXText::cursorUpParagraph()
1520 ParagraphList::iterator cpit = cursorPar();
1521 if (cursor().pos() > 0)
1523 else if (cpit != paragraphs().begin())
1524 setCursor(boost::prior(cpit), 0);
1528 void LyXText::cursorDownParagraph()
1530 ParagraphList::iterator pit = cursorPar();
1531 ParagraphList::iterator next_pit = boost::next(pit);
1533 if (next_pit != paragraphs().end())
1534 setCursor(next_pit, 0);
1536 setCursor(pit, pit->size());
1540 // fix the cursor `cur' after a characters has been deleted at `where'
1541 // position. Called by deleteEmptyParagraphMechanism
1542 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1544 // if cursor is not in the paragraph where the delete occured,
1546 if (cur.par() != where.par())
1549 // if cursor position is after the place where the delete occured,
1551 if (cur.pos() > where.pos())
1552 cur.pos(cur.pos()-1);
1554 // check also if we don't want to set the cursor on a spot behind the
1555 // pagragraph because we erased the last character.
1556 if (cur.pos() > getPar(cur)->size())
1557 cur.pos(getPar(cur)->size());
1561 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1563 #warning Disabled as it crashes after the cursor data shift... (Andre)
1566 // Would be wrong to delete anything if we have a selection.
1567 if (bv()->selection().set())
1570 // Don't do anything if the cursor is invalid
1571 if (old_cursor.par() == -1)
1574 // We allow all kinds of "mumbo-jumbo" when freespacing.
1575 ParagraphList::iterator const old_pit = getPar(old_cursor);
1576 if (old_pit->isFreeSpacing())
1579 /* Ok I'll put some comments here about what is missing.
1580 I have fixed BackSpace (and thus Delete) to not delete
1581 double-spaces automagically. I have also changed Cut,
1582 Copy and Paste to hopefully do some sensible things.
1583 There are still some small problems that can lead to
1584 double spaces stored in the document file or space at
1585 the beginning of paragraphs(). This happens if you have
1586 the cursor between to spaces and then save. Or if you
1587 cut and paste and the selection have a space at the
1588 beginning and then save right after the paste. I am
1589 sure none of these are very hard to fix, but I will
1590 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1591 that I can get some feedback. (Lgb)
1594 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1595 // delete the LineSeparator.
1598 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1599 // delete the LineSeparator.
1602 // If the pos around the old_cursor were spaces, delete one of them.
1603 if (old_cursor.par() != cursor().par()
1604 || old_cursor.pos() != cursor().pos()) {
1606 // Only if the cursor has really moved
1607 if (old_cursor.pos() > 0
1608 && old_cursor.pos() < old_pit->size()
1609 && old_pit->isLineSeparator(old_cursor.pos())
1610 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1611 bool erased = old_pit->erase(old_cursor.pos() - 1);
1612 redoParagraph(old_pit);
1616 #ifdef WITH_WARNINGS
1617 #warning This will not work anymore when we have multiple views of the same buffer
1618 // In this case, we will have to correct also the cursors held by
1619 // other bufferviews. It will probably be easier to do that in a more
1620 // automated way in CursorSlice code. (JMarc 26/09/2001)
1622 // correct all cursors held by the LyXText
1623 fixCursorAfterDelete(cursor(), old_cursor);
1624 fixCursorAfterDelete(anchor(), old_cursor);
1629 // don't delete anything if this is the ONLY paragraph!
1630 if (paragraphs().size() == 1)
1633 // Do not delete empty paragraphs with keepempty set.
1634 if (old_pit->allowEmpty())
1637 // only do our magic if we changed paragraph
1638 if (old_cursor.par() == cursor().par())
1641 // record if we have deleted a paragraph
1642 // we can't possibly have deleted a paragraph before this point
1643 bool deleted = false;
1645 if (old_pit->empty()
1646 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1647 // ok, we will delete something
1648 CursorSlice tmpcursor;
1652 bool selection_position_was_oldcursor_position =
1653 anchor().par() == old_cursor.par()
1654 && anchor().pos() == old_cursor.pos();
1656 tmpcursor = cursor();
1657 cursor() = old_cursor; // that undo can restore the right cursor position
1659 ParagraphList::iterator endpit = boost::next(old_pit);
1660 while (endpit != paragraphs().end() && endpit->getDepth())
1663 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1664 cursor() = tmpcursor;
1667 ParagraphList::iterator tmppit = cursorPar();
1669 paragraphs().erase(old_pit);
1670 // update cursor par offset
1671 cursor().par(parOffset(tmppit));
1674 if (selection_position_was_oldcursor_position) {
1675 // correct selection
1676 bv()->resetAnchor();
1683 if (old_pit->stripLeadingSpaces()) {
1684 redoParagraph(old_pit);
1685 bv()->resetAnchor();
1691 ParagraphList & LyXText::paragraphs() const
1693 return const_cast<ParagraphList &>(paragraphs_);
1697 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1699 recordUndo(Undo::ATOMIC, this, first, last);
1703 void LyXText::recUndo(lyx::paroffset_type par) const
1705 recordUndo(Undo::ATOMIC, this, par, par);
1709 bool LyXText::isInInset() const
1715 int defaultRowHeight()
1717 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);