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::support::bformat;
70 using std::ostringstream;
74 LyXText::LyXText(BufferView * bv, bool in_inset)
75 : width_(0), maxwidth_(bv ? bv->workWidth() : 100), height_(0),
76 background_color_(LColor::background),
77 bv_owner(bv), in_inset_(in_inset), xo_(0), yo_(0)
81 void LyXText::init(BufferView * bv)
85 ParagraphList::iterator const beg = paragraphs().begin();
86 ParagraphList::iterator const end = paragraphs().end();
87 for (ParagraphList::iterator pit = beg; pit != end; ++pit)
90 maxwidth_ = bv->workWidth();
94 current_font = getFont(beg, 0);
96 redoParagraphs(beg, end);
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);
218 // Asger is not sure we want to do this...
219 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
222 LyXLayout_ptr const & layout = par.layout();
223 pos_type const psize = par.size();
226 for (pos_type pos = 0; pos < psize; ++pos) {
227 if (pos < par.beginOfBody())
228 layoutfont = layout->labelfont;
230 layoutfont = layout->font;
232 LyXFont tmpfont = par.getFontSettings(params, pos);
233 tmpfont.reduce(layoutfont);
234 par.setFont(pos, tmpfont);
239 // return past-the-last paragraph influenced by a layout change on pit
240 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
242 ParagraphList::iterator end = paragraphs().end();
243 ParagraphList::iterator nextpit = boost::next(pit);
246 //because of parindents
247 if (!pit->getDepth())
248 return boost::next(nextpit);
249 //because of depth constrains
250 for (; nextpit != end; ++pit, ++nextpit) {
251 if (!pit->getDepth())
258 ParagraphList::iterator
259 LyXText::setLayout(ParagraphList::iterator start,
260 ParagraphList::iterator end,
261 string const & layout)
263 BOOST_ASSERT(start != end);
264 ParagraphList::iterator undopit = undoSpan(boost::prior(end));
265 recUndo(parOffset(start), parOffset(undopit) - 1);
267 BufferParams const & bufparams = bv()->buffer()->params();
268 LyXLayout_ptr const & lyxlayout =
269 bufparams.getLyXTextClass()[layout];
271 for (ParagraphList::iterator pit = start; pit != end; ++pit) {
272 pit->applyLayout(lyxlayout);
273 makeFontEntriesLayoutSpecific(bufparams, *pit);
274 if (lyxlayout->margintype == MARGIN_MANUAL)
275 pit->setLabelWidthString(lyxlayout->labelstring());
282 // set layout over selection and make a total rebreak of those paragraphs
283 void LyXText::setLayout(LCursor & cur, string const & layout)
285 BOOST_ASSERT(this == cur.text());
286 // special handling of new environment insets
287 BufferParams const & params = bv()->buffer()->params();
288 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
289 if (lyxlayout->is_environment) {
290 // move everything in a new environment inset
291 lyxerr << "setting layout " << layout << endl;
292 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
293 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
294 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
295 InsetBase * inset = new InsetEnvironment(params, layout);
296 insertInset(cur, inset);
297 //inset->edit(cur, true);
298 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
302 ParagraphList::iterator start = getPar(cur.selBegin().par());
303 ParagraphList::iterator end = boost::next(getPar(cur.selEnd().par()));
304 ParagraphList::iterator endpit = setLayout(start, end, layout);
305 redoParagraphs(start, endpit);
313 void getSelectionSpan(LCursor & cur, LyXText & text,
314 ParagraphList::iterator & beg,
315 ParagraphList::iterator & end)
317 if (!cur.selection()) {
318 beg = text.getPar(cur.par());
319 end = boost::next(beg);
321 beg = text.getPar(cur.selBegin());
322 end = boost::next(text.getPar(cur.selEnd()));
327 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
328 Paragraph const & par,
331 if (par.layout()->labeltype == LABEL_BIBLIO)
333 int const depth = par.params().depth();
334 if (type == bv_funcs::INC_DEPTH && depth < max_depth)
336 if (type == bv_funcs::DEC_DEPTH && depth > 0)
345 bool LyXText::changeDepthAllowed(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
347 BOOST_ASSERT(this == cur.text());
348 ParagraphList::iterator beg, end;
349 getSelectionSpan(cur, *this, beg, end);
351 if (beg != paragraphs().begin())
352 max_depth = boost::prior(beg)->getMaxDepthAfter();
354 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
355 if (::changeDepthAllowed(type, *pit, max_depth))
357 max_depth = pit->getMaxDepthAfter();
363 void LyXText::changeDepth(LCursor & cur, bv_funcs::DEPTH_CHANGE type)
365 BOOST_ASSERT(this == cur.text());
366 ParagraphList::iterator beg, end;
367 getSelectionSpan(cur, *this, beg, end);
368 recordUndoSelection(cur);
371 if (beg != paragraphs().begin())
372 max_depth = boost::prior(beg)->getMaxDepthAfter();
374 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
375 if (::changeDepthAllowed(type, *pit, max_depth)) {
376 int const depth = pit->params().depth();
377 if (type == bv_funcs::INC_DEPTH)
378 pit->params().depth(depth + 1);
380 pit->params().depth(depth - 1);
382 max_depth = pit->getMaxDepthAfter();
384 // this handles the counter labels, and also fixes up
385 // depth values for follow-on (child) paragraphs
390 // set font over selection and make a total rebreak of those paragraphs
391 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
393 BOOST_ASSERT(this == cur.text());
394 // if there is no selection just set the current_font
395 if (!cur.selection()) {
396 // Determine basis font
398 ParagraphList::iterator pit = getPar(cur.par());
399 if (cur.pos() < pit->beginOfBody())
400 layoutfont = getLabelFont(pit);
402 layoutfont = getLayoutFont(pit);
404 // Update current font
405 real_current_font.update(font,
406 bv()->buffer()->params().language,
409 // Reduce to implicit settings
410 current_font = real_current_font;
411 current_font.reduce(layoutfont);
412 // And resolve it completely
413 real_current_font.realize(layoutfont);
418 // Ok, we have a selection.
419 recordUndoSelection(cur);
421 ParagraphList::iterator beg = getPar(cur.selBegin().par());
422 ParagraphList::iterator end = getPar(cur.selEnd().par());
424 PosIterator pos(¶graphs(), beg, cur.selBegin().pos());
425 PosIterator posend(¶graphs(), end, cur.selEnd().pos());
427 BufferParams const & params = bv()->buffer()->params();
429 for (; pos != posend; ++pos) {
430 LyXFont f = getFont(pos.pit(), pos.pos());
431 f.update(font, params.language, toggleall);
432 setCharFont(pos.pit(), pos.pos(), f);
435 redoParagraphs(beg, ++end);
439 // the cursor set functions have a special mechanism. When they
440 // realize you left an empty paragraph, they will delete it.
442 void LyXText::cursorHome(LCursor & cur)
444 BOOST_ASSERT(this == cur.text());
445 setCursor(cur, cur.par(), cur.textRow().pos());
449 void LyXText::cursorEnd(LCursor & cur)
451 BOOST_ASSERT(this == cur.text());
452 // if not on the last row of the par, put the cursor before
454 pos_type const end = cur.textRow().endpos();
455 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
459 void LyXText::cursorTop(LCursor & cur)
461 BOOST_ASSERT(this == cur.text());
462 setCursor(cur, 0, 0);
466 void LyXText::cursorBottom(LCursor & cur)
468 BOOST_ASSERT(this == cur.text());
469 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
473 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
475 BOOST_ASSERT(this == cur.text());
476 // If the mask is completely neutral, tell user
477 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
478 // Could only happen with user style
479 cur.message(_("No font change defined. "
480 "Use Character under the Layout menu to define font change."));
484 // Try implicit word selection
485 // If there is a change in the language the implicit word selection
487 CursorSlice resetCursor = cur.top();
488 bool implicitSelection =
489 font.language() == ignore_language
490 && font.number() == LyXFont::IGNORE
491 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
494 setFont(cur, font, toggleall);
496 // Implicit selections are cleared afterwards
497 // and cursor is set to the original position.
498 if (implicitSelection) {
499 cur.clearSelection();
500 cur.top() = resetCursor;
506 string LyXText::getStringToIndex(LCursor & cur)
508 BOOST_ASSERT(this == cur.text());
509 // Try implicit word selection
510 // If there is a change in the language the implicit word selection
512 CursorSlice const reset_cursor = cur.top();
513 bool const implicitSelection =
514 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
517 if (!cur.selection())
518 cur.message(_("Nothing to index!"));
519 else if (cur.selBegin().par() != cur.selEnd().par())
520 cur.message(_("Cannot index more than one paragraph!"));
522 idxstring = cur.selectionAsString(false);
524 // Reset cursors to their original position.
525 cur.top() = reset_cursor;
528 // Clear the implicit selection.
529 if (implicitSelection)
530 cur.clearSelection();
536 // the DTP switches for paragraphs(). LyX will store them in the first
537 // physical paragraph. When a paragraph is broken, the top settings rest,
538 // the bottom settings are given to the new one. So I can make sure,
539 // they do not duplicate themself and you cannot play dirty tricks with
542 void LyXText::setParagraph(LCursor & cur,
543 Spacing const & spacing, LyXAlignment align,
544 string const & labelwidthstring, bool noindent)
546 BOOST_ASSERT(cur.text());
547 // make sure that the depth behind the selection are restored, too
548 ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
549 recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
551 ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
552 ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
554 for (--pit; pit != beg; ++pit) {
555 ParagraphParameters & params = pit->params();
556 params.spacing(spacing);
558 // does the layout allow the new alignment?
559 LyXLayout_ptr const & layout = pit->layout();
561 if (align == LYX_ALIGN_LAYOUT)
562 align = layout->align;
563 if (align & layout->alignpossible) {
564 if (align == layout->align)
565 params.align(LYX_ALIGN_LAYOUT);
569 pit->setLabelWidthString(labelwidthstring);
570 params.noindent(noindent);
573 redoParagraphs(getPar(cur.selBegin()), undopit);
577 string expandLabel(LyXTextClass const & textclass,
578 LyXLayout_ptr const & layout, bool appendix)
580 string fmt = appendix ?
581 layout->labelstring_appendix() : layout->labelstring();
583 // handle 'inherited level parts' in 'fmt',
584 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
585 size_t const i = fmt.find('@', 0);
586 if (i != string::npos) {
587 size_t const j = fmt.find('@', i + 1);
588 if (j != string::npos) {
589 string parent(fmt, i + 1, j - i - 1);
590 string label = expandLabel(textclass, textclass[parent], appendix);
591 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
595 return textclass.counters().counterLabel(fmt);
601 void incrementItemDepth(ParagraphList::iterator pit,
602 ParagraphList::iterator first_pit)
604 int const cur_labeltype = pit->layout()->labeltype;
606 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
609 int const cur_depth = pit->getDepth();
611 ParagraphList::iterator prev_pit = boost::prior(pit);
613 int const prev_depth = prev_pit->getDepth();
614 int const prev_labeltype = prev_pit->layout()->labeltype;
615 if (prev_depth == 0 && cur_depth > 0) {
616 if (prev_labeltype == cur_labeltype) {
617 pit->itemdepth = prev_pit->itemdepth + 1;
620 } else if (prev_depth < cur_depth) {
621 if (prev_labeltype == cur_labeltype) {
622 pit->itemdepth = prev_pit->itemdepth + 1;
625 } else if (prev_depth == cur_depth) {
626 if (prev_labeltype == cur_labeltype) {
627 pit->itemdepth = prev_pit->itemdepth;
631 if (prev_pit == first_pit)
639 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
640 ParagraphList::iterator firstpit,
646 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 <= cur_depth) {
652 if (prev_labeltype != LABEL_ENUMERATE) {
653 switch (pit->itemdepth) {
655 counters.reset("enumi");
657 counters.reset("enumii");
659 counters.reset("enumiii");
661 counters.reset("enumiv");
667 if (prev_pit == firstpit)
677 // set the counter of a paragraph. This includes the labels
678 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
680 BufferParams const & bufparams = buf.params();
681 LyXTextClass const & textclass = bufparams.getLyXTextClass();
682 LyXLayout_ptr const & layout = pit->layout();
683 ParagraphList::iterator first_pit = paragraphs().begin();
684 Counters & counters = textclass.counters();
689 if (pit == first_pit) {
690 pit->params().appendix(pit->params().startOfAppendix());
692 pit->params().appendix(boost::prior(pit)->params().appendix());
693 if (!pit->params().appendix() &&
694 pit->params().startOfAppendix()) {
695 pit->params().appendix(true);
696 textclass.counters().reset();
699 // Maybe we have to increment the item depth.
700 incrementItemDepth(pit, first_pit);
703 // erase what was there before
704 pit->params().labelString(string());
706 if (layout->margintype == MARGIN_MANUAL) {
707 if (pit->params().labelWidthString().empty())
708 pit->setLabelWidthString(layout->labelstring());
710 pit->setLabelWidthString(string());
713 // is it a layout that has an automatic label?
714 if (layout->labeltype == LABEL_COUNTER) {
715 BufferParams const & bufparams = buf.params();
716 LyXTextClass const & textclass = bufparams.getLyXTextClass();
717 counters.step(layout->counter);
718 string label = expandLabel(textclass, layout, pit->params().appendix());
719 pit->params().labelString(label);
720 } else if (layout->labeltype == LABEL_ITEMIZE) {
721 // At some point of time we should do something more
722 // clever here, like:
723 // pit->params().labelString(
724 // bufparams.user_defined_bullet(pit->itemdepth).getText());
725 // for now, use a simple hardcoded label
727 switch (pit->itemdepth) {
742 pit->params().labelString(itemlabel);
743 } else if (layout->labeltype == LABEL_ENUMERATE) {
744 // Maybe we have to reset the enumeration counter.
745 resetEnumCounterIfNeeded(pit, first_pit, counters);
748 // Yes I know this is a really, really! bad solution
750 string enumcounter = "enum";
752 switch (pit->itemdepth) {
764 // not a valid enumdepth...
768 counters.step(enumcounter);
770 pit->params().labelString(counters.enumLabel(enumcounter));
771 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
772 counters.step("bibitem");
773 int number = counters.value("bibitem");
774 if (pit->bibitem()) {
775 pit->bibitem()->setCounter(number);
776 pit->params().labelString(layout->labelstring());
778 // In biblio should't be following counters but...
780 string s = buf.B_(layout->labelstring());
783 if (layout->labeltype == LABEL_SENSITIVE) {
784 ParagraphList::iterator end = paragraphs().end();
785 ParagraphList::iterator tmppit = pit;
788 while (tmppit != end && tmppit->inInset()
789 // the single '=' is intended below
790 && (in = tmppit->inInset()->owner()))
792 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
793 in->lyxCode() == InsetBase::WRAP_CODE) {
797 Paragraph const * owner = &ownerPar(buf, in);
799 for ( ; tmppit != end; ++tmppit)
800 if (&*tmppit == owner)
808 if (in->lyxCode() == InsetBase::FLOAT_CODE)
809 type = static_cast<InsetFloat*>(in)->params().type;
810 else if (in->lyxCode() == InsetBase::WRAP_CODE)
811 type = static_cast<InsetWrap*>(in)->params().type;
815 Floating const & fl = textclass.floats().getType(type);
817 counters.step(fl.type());
819 // Doesn't work... yet.
820 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
822 // par->SetLayout(0);
823 // s = layout->labelstring;
824 s = _("Senseless: ");
827 pit->params().labelString(s);
833 // Updates all counters.
834 void LyXText::updateCounters()
837 bv()->buffer()->params().getLyXTextClass().counters().reset();
839 bool update_pos = false;
841 ParagraphList::iterator beg = paragraphs().begin();
842 ParagraphList::iterator end = paragraphs().end();
843 for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
844 string const oldLabel = pit->params().labelString();
847 maxdepth = boost::prior(pit)->getMaxDepthAfter();
849 if (pit->params().depth() > maxdepth)
850 pit->params().depth(maxdepth);
852 // setCounter can potentially change the labelString.
853 setCounter(*bv()->buffer(), pit);
854 string const & newLabel = pit->params().labelString();
855 if (oldLabel != newLabel) {
856 redoParagraphInternal(pit);
862 updateParPositions();
866 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
868 BOOST_ASSERT(this == cur.text());
870 cur.paragraph().insertInset(cur.pos(), inset);
875 void LyXText::cutSelection(LCursor & cur, bool doclear, bool realcut)
877 BOOST_ASSERT(this == cur.text());
878 // Stuff what we got on the clipboard. Even if there is no selection.
880 // There is a problem with having the stuffing here in that the
881 // larger the selection the slower LyX will get. This can be
882 // solved by running the line below only when the selection has
883 // finished. The solution used currently just works, to make it
884 // faster we need to be more clever and probably also have more
885 // calls to stuffClipboard. (Lgb)
886 bv()->stuffClipboard(cur.selectionAsString(true));
888 // This doesn't make sense, if there is no selection
889 if (!cur.selection())
892 // OK, we have a selection. This is always between cur.selBegin()
895 // make sure that the depth behind the selection are restored, too
896 recordUndoSelection(cur);
897 ParagraphList::iterator begpit = getPar(cur.selBegin().par());
898 ParagraphList::iterator endpit = getPar(cur.selEnd().par());
899 ParagraphList::iterator undopit = undoSpan(endpit);
901 int endpos = cur.selEnd().pos();
903 BufferParams const & bufparams = bv()->buffer()->params();
904 boost::tie(endpit, endpos) = realcut ?
905 CutAndPaste::cutSelection(bufparams,
908 cur.selBegin().pos(), endpos,
911 : CutAndPaste::eraseSelection(bufparams,
914 cur.selBegin().pos(), endpos,
916 // sometimes necessary
918 begpit->stripLeadingSpaces();
920 redoParagraphs(begpit, undopit);
921 // cutSelection can invalidate the cursor so we need to set
923 // we prefer the end for when tracking changes
925 cur.par() = parOffset(endpit);
927 // need a valid cursor. (Lgb)
928 cur.clearSelection();
933 void LyXText::copySelection(LCursor & cur)
935 BOOST_ASSERT(this == cur.text());
936 // stuff the selection onto the X clipboard, from an explicit copy request
937 bv()->stuffClipboard(cur.selectionAsString(true));
939 // this doesnt make sense, if there is no selection
940 if (!cur.selection())
943 // ok we have a selection. This is always between cur.selBegin()
944 // and sel_end cursor
946 // copy behind a space if there is one
947 while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
948 && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
949 && (cur.selBegin().par() != cur.selEnd().par()
950 || cur.selBegin().pos() < cur.selEnd().pos()))
951 ++cur.selBegin().pos();
953 CutAndPaste::copySelection(getPar(cur.selBegin().par()),
954 getPar(cur.selEnd().par()),
955 cur.selBegin().pos(),
957 bv()->buffer()->params().textclass);
961 void LyXText::pasteSelection(LCursor & cur, size_t sel_index)
963 // this does not make sense, if there is nothing to paste
964 if (!CutAndPaste::checkPastePossible())
969 ParagraphList::iterator endpit;
974 boost::tie(ppp, endpit) =
975 CutAndPaste::pasteSelection(*bv()->buffer(),
977 getPar(cur.par()), cur.pos(),
978 bv()->buffer()->params().textclass,
980 bufferErrors(*bv()->buffer(), el);
981 bv()->showErrorList(_("Paste"));
983 redoParagraphs(getPar(cur.par()), endpit);
985 cur.clearSelection();
987 setCursor(cur, parOffset(ppp.first), ppp.second);
993 void LyXText::setSelectionRange(LCursor & cur, lyx::pos_type length)
1004 // simple replacing. The font of the first selected character is used
1005 void LyXText::replaceSelectionWithString(LCursor & cur, string const & str)
1009 // Get font setting before we cut
1010 pos_type pos = cur.selEnd().pos();
1011 LyXFont const font = getPar(cur.selBegin())
1012 ->getFontSettings(bv()->buffer()->params(),
1013 cur.selBegin().pos());
1015 // Insert the new string
1016 string::const_iterator cit = str.begin();
1017 string::const_iterator end = str.end();
1018 for (; cit != end; ++cit) {
1019 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1023 // Cut the selection
1024 cutSelection(cur, true, false);
1028 // needed to insert the selection
1029 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
1031 ParagraphList::iterator pit = getPar(cur.par());
1032 ParagraphList::iterator endpit = boost::next(pit);
1033 pos_type pos = cursor().pos();
1036 // only to be sure, should not be neccessary
1037 cur.clearSelection();
1038 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1040 redoParagraphs(getPar(cur.par()), endpit);
1042 setCursor(cur, cur.par(), pos);
1047 // turn double CR to single CR, others are converted into one
1048 // blank. Then insertStringAsLines is called
1049 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
1051 string linestr = str;
1052 bool newline_inserted = false;
1054 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
1055 if (linestr[i] == '\n') {
1056 if (newline_inserted) {
1057 // we know that \r will be ignored by
1058 // insertStringAsLines. Of course, it is a dirty
1059 // trick, but it works...
1060 linestr[i - 1] = '\r';
1064 newline_inserted = true;
1066 } else if (IsPrintable(linestr[i])) {
1067 newline_inserted = false;
1070 insertStringAsLines(cur, linestr);
1074 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
1075 bool setfont, bool boundary)
1077 CursorSlice old_cursor = cur.top();
1078 setCursorIntern(cur, par, pos, setfont, boundary);
1079 return deleteEmptyParagraphMechanism(cur.top(), old_cursor);
1083 void LyXText::setCursor(CursorSlice & cur, par_type par,
1084 pos_type pos, bool boundary)
1086 BOOST_ASSERT(par != int(paragraphs().size()));
1090 cur.boundary() = boundary;
1092 // no rows, no fun...
1093 if (paragraphs().begin()->rows.empty())
1096 // now some strict checking
1097 Paragraph & para = *getPar(par);
1098 Row const & row = *para.getRow(pos);
1099 pos_type const end = row.endpos();
1101 // None of these should happen, but we're scaredy-cats
1103 lyxerr << "dont like -1" << endl;
1104 BOOST_ASSERT(false);
1107 if (pos > para.size()) {
1108 lyxerr << "dont like 1, pos: " << pos
1109 << " size: " << para.size()
1110 << " row.pos():" << row.pos()
1111 << " par: " << par << endl;
1112 BOOST_ASSERT(false);
1116 lyxerr << "dont like 2, pos: " << pos
1117 << " size: " << para.size()
1118 << " row.pos():" << row.pos()
1119 << " par: " << par << endl;
1120 // This shouldn't happen.
1121 BOOST_ASSERT(false);
1124 if (pos < row.pos()) {
1125 lyxerr << "dont like 3 please report pos:" << pos
1126 << " size: " << para.size()
1127 << " row.pos():" << row.pos()
1128 << " par: " << par << endl;
1129 BOOST_ASSERT(false);
1134 void LyXText::setCursorIntern(LCursor & cur,
1135 par_type par, pos_type pos, bool setfont, bool boundary)
1137 setCursor(cur.top(), par, pos, boundary);
1138 cur.x_target() = cursorX(cur.top());
1140 setCurrentFont(cur);
1144 void LyXText::setCurrentFont(LCursor & cur)
1146 BOOST_ASSERT(this == cur.text());
1147 pos_type pos = cur.pos();
1148 ParagraphList::iterator pit = getPar(cur.par());
1150 if (cur.boundary() && pos > 0)
1154 if (pos == cur.lastpos())
1156 else // potentional bug... BUG (Lgb)
1157 if (pit->isSeparator(pos)) {
1158 if (pos > cur.textRow().pos() &&
1159 bidi.level(pos) % 2 ==
1160 bidi.level(pos - 1) % 2)
1162 else if (pos + 1 < cur.lastpos())
1167 BufferParams const & bufparams = bv()->buffer()->params();
1168 current_font = pit->getFontSettings(bufparams, pos);
1169 real_current_font = getFont(pit, pos);
1171 if (cur.pos() == cur.lastpos()
1172 && bidi.isBoundary(*bv()->buffer(), *pit, cur.pos())
1173 && !cur.boundary()) {
1174 Language const * lang = pit->getParLanguage(bufparams);
1175 current_font.setLanguage(lang);
1176 current_font.setNumber(LyXFont::OFF);
1177 real_current_font.setLanguage(lang);
1178 real_current_font.setNumber(LyXFont::OFF);
1183 // x is an absolute screen coord
1184 // returns the column near the specified x-coordinate of the row
1185 // x is set to the real beginning of this column
1186 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1187 Row const & row, int & x, bool & boundary) const
1190 RowMetrics const r = computeRowMetrics(pit, row);
1192 pos_type vc = row.pos();
1193 pos_type end = row.endpos();
1195 LyXLayout_ptr const & layout = pit->layout();
1197 bool left_side = false;
1199 pos_type body_pos = pit->beginOfBody();
1202 double last_tmpx = tmpx;
1205 (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1208 // check for empty row
1210 x = int(tmpx) + xo_;
1214 while (vc < end && tmpx <= x) {
1215 c = bidi.vis2log(vc);
1217 if (body_pos > 0 && c == body_pos - 1) {
1218 tmpx += r.label_hfill +
1219 font_metrics::width(layout->labelsep, getLabelFont(pit));
1220 if (pit->isLineSeparator(body_pos - 1))
1221 tmpx -= singleWidth(pit, body_pos - 1);
1224 if (hfillExpansion(*pit, row, c)) {
1225 tmpx += singleWidth(pit, c);
1229 tmpx += r.label_hfill;
1230 } else if (pit->isSeparator(c)) {
1231 tmpx += singleWidth(pit, c);
1233 tmpx += r.separator;
1235 tmpx += singleWidth(pit, c);
1240 if ((tmpx + last_tmpx) / 2 > x) {
1245 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1248 // This (rtl_support test) is not needed, but gives
1249 // some speedup if rtl_support == false
1250 bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1252 // If lastrow is false, we don't need to compute
1253 // the value of rtl.
1254 bool const rtl = lastrow ? isRTL(*pit) : false;
1256 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1257 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1259 else if (vc == row.pos()) {
1260 c = bidi.vis2log(vc);
1261 if (bidi.level(c) % 2 == 1)
1264 c = bidi.vis2log(vc - 1);
1265 bool const rtl = (bidi.level(c) % 2 == 1);
1266 if (left_side == rtl) {
1268 boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1272 if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1273 if (bidi.level(end -1) % 2 == 0)
1274 tmpx -= singleWidth(pit, end - 1);
1276 tmpx += singleWidth(pit, end - 1);
1280 x = int(tmpx) + xo_;
1281 return c - row.pos();
1285 // x,y are absolute coordinates
1286 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1290 CursorSlice old_cursor = cur.top();
1291 ParagraphList::iterator pit;
1292 Row const & row = *getRowNearY(y, pit);
1293 lyxerr << "hit row at: " << row.pos() << endl;
1295 int xx = x + xo_; // getRowNearX get absolute x coords
1296 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1297 cur.par() = parOffset(pit);
1299 cur.boundary() = bound;
1300 deleteEmptyParagraphMechanism(cur.top(), old_cursor);
1304 // x,y are absolute screen coordinates
1305 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1307 ParagraphList::iterator pit;
1308 Row const & row = *getRowNearY(y - yo_, pit);
1311 int xx = x; // is modified by getColumnNearX
1312 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1313 cur.par() = parOffset(pit);
1315 cur.boundary() = bound;
1317 // try to descend into nested insets
1318 InsetBase * inset = checkInsetHit(x, y);
1322 // This should be just before or just behind the
1323 // cursor position set above.
1324 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1325 || inset == pit->getInset(pos));
1326 // Make sure the cursor points to the position before
1328 if (inset == pit->getInset(pos - 1))
1330 return inset->editXY(cur, x, y);
1334 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1336 if (cur.selection())
1338 if (cur.pos() == cur.lastpos())
1340 InsetBase * inset = cur.nextInset();
1341 if (!isHighlyEditableInset(inset))
1343 inset->edit(cur, front);
1348 void LyXText::cursorLeft(LCursor & cur)
1350 if (cur.pos() != 0) {
1351 bool boundary = cur.boundary();
1352 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1353 if (!checkAndActivateInset(cur, false)) {
1354 if (false && !boundary &&
1355 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1356 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1361 if (cur.par() != 0) {
1362 // steps into the paragraph above
1363 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1)->size());
1368 void LyXText::cursorRight(LCursor & cur)
1370 if (false && cur.boundary()) {
1371 setCursor(cur, cur.par(), cur.pos(), true, false);
1375 if (cur.pos() != cur.lastpos()) {
1376 if (!checkAndActivateInset(cur, true)) {
1377 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1378 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1380 setCursor(cur, cur.par(), cur.pos(), true, true);
1385 if (cur.par() != cur.lastpar())
1386 setCursor(cur, cur.par() + 1, 0);
1390 void LyXText::cursorUp(LCursor & cur)
1392 Row const & row = cur.textRow();
1393 int x = cur.x_target();
1394 int y = cursorY(cur.top()) - row.baseline() - 1;
1395 setCursorFromCoordinates(cur, x, y);
1397 if (!cur.selection()) {
1398 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1399 if (inset_hit && isHighlyEditableInset(inset_hit))
1400 inset_hit->editXY(cur, cur.x_target(), y);
1405 void LyXText::cursorDown(LCursor & cur)
1407 Row const & row = cur.textRow();
1408 int x = cur.x_target();
1409 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1410 setCursorFromCoordinates(cur, x, y);
1412 if (!cur.selection()) {
1413 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1414 if (inset_hit && isHighlyEditableInset(inset_hit))
1415 inset_hit->editXY(cur, cur.x_target(), y);
1420 void LyXText::cursorUpParagraph(LCursor & cur)
1423 setCursor(cur, cur.par(), 0);
1424 else if (cur.par() != 0)
1425 setCursor(cur, cur.par() - 1, 0);
1429 void LyXText::cursorDownParagraph(LCursor & cur)
1431 if (cur.par() != cur.lastpar())
1432 setCursor(cur, cur.par() + 1, 0);
1434 setCursor(cur, cur.par(), cur.lastpos());
1438 // fix the cursor `cur' after a characters has been deleted at `where'
1439 // position. Called by deleteEmptyParagraphMechanism
1440 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1442 // do notheing if cursor is not in the paragraph where the
1443 // deletion occured,
1444 if (cur.par() != where.par())
1447 // if cursor position is after the deletion place update it
1448 if (cur.pos() > where.pos())
1451 // check also if we don't want to set the cursor on a spot behind the
1452 // pagragraph because we erased the last character.
1453 if (cur.pos() > cur.lastpos())
1454 cur.pos() = cur.lastpos();
1458 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice & cur,
1459 CursorSlice const & old_cursor)
1461 #warning Disabled as it crashes after the cursor data shift... (Andre)
1464 // Would be wrong to delete anything if we have a selection.
1465 //if (cur.selection())
1469 // We allow all kinds of "mumbo-jumbo" when freespacing.
1470 ParagraphList::iterator const old_pit = getPar(old_cursor.par());
1471 if (old_pit->isFreeSpacing())
1474 /* Ok I'll put some comments here about what is missing.
1475 I have fixed BackSpace (and thus Delete) to not delete
1476 double-spaces automagically. I have also changed Cut,
1477 Copy and Paste to hopefully do some sensible things.
1478 There are still some small problems that can lead to
1479 double spaces stored in the document file or space at
1480 the beginning of paragraphs(). This happens if you have
1481 the cursor between to spaces and then save. Or if you
1482 cut and paste and the selection have a space at the
1483 beginning and then save right after the paste. I am
1484 sure none of these are very hard to fix, but I will
1485 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1486 that I can get some feedback. (Lgb)
1489 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1490 // delete the LineSeparator.
1493 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1494 // delete the LineSeparator.
1497 // If the pos around the old_cursor were spaces, delete one of them.
1498 if (old_cursor.par() != cur.par() || old_cursor.pos() != cur.pos()) {
1500 // Only if the cursor has really moved
1501 if (old_cursor.pos() > 0
1502 && old_cursor.pos() < old_pit->size()
1503 && old_pit->isLineSeparator(old_cursor.pos())
1504 && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1505 bool erased = old_pit->erase(old_cursor.pos() - 1);
1506 redoParagraph(old_pit);
1510 #ifdef WITH_WARNINGS
1511 #warning This will not work anymore when we have multiple views of the same buffer
1512 // In this case, we will have to correct also the cursors held by
1513 // other bufferviews. It will probably be easier to do that in a more
1514 // automated way in CursorSlice code. (JMarc 26/09/2001)
1516 // correct all cursors held by the LyXText
1517 fixCursorAfterDelete(cursor(), old_cursor);
1518 fixCursorAfterDelete(anchor(), old_cursor);
1523 // don't delete anything if this is the ONLY paragraph!
1524 if (paragraphs().size() == 1)
1527 // Do not delete empty paragraphs with keepempty set.
1528 if (old_pit->allowEmpty())
1531 // only do our magic if we changed paragraph
1532 if (old_cursor.par() == cur.par())
1535 // record if we have deleted a paragraph
1536 // we can't possibly have deleted a paragraph before this point
1537 bool deleted = false;
1539 if (old_pit->empty()
1540 || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1541 // ok, we will delete something
1542 CursorSlice tmpcursor;
1546 bool selection_position_was_oldcursor_position =
1547 anchor().par() == old_cursor.par()
1548 && anchor().pos() == old_cursor.pos();
1550 tmpcursor = cursor();
1551 cursor() = old_cursor; // that undo can restore the right cursor position
1553 ParagraphList::iterator endpit = boost::next(old_pit);
1554 while (endpit != paragraphs().end() && endpit->getDepth())
1557 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1558 cursor() = tmpcursor;
1561 paragraphs().erase(old_pit);
1562 // update cursor par offset
1566 if (selection_position_was_oldcursor_position) {
1567 // correct selection
1568 bv()->resetAnchor();
1575 if (old_pit->stripLeadingSpaces()) {
1576 redoParagraph(old_pit);
1577 bv()->resetAnchor();
1584 ParagraphList & LyXText::paragraphs() const
1586 return const_cast<ParagraphList &>(paragraphs_);
1590 void LyXText::recUndo(par_type first, par_type last) const
1592 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1596 void LyXText::recUndo(par_type par) const
1598 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1602 bool LyXText::isInInset() const
1608 bool LyXText::toggleInset(LCursor & cur)
1610 InsetBase * inset = cur.nextInset();
1611 // is there an editable inset at cursor position?
1612 if (!isEditableInset(inset))
1614 cur.message(inset->editMessage());
1616 // do we want to keep this?? (JMarc)
1617 if (!isHighlyEditableInset(inset))
1620 if (inset->isOpen())
1628 int defaultRowHeight()
1630 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);