X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCutAndPaste.C;h=c2ddae5c60dfcd7720ca3933fb553b34cf921502;hb=28902af06012253c7b5eb80e179a352aac17d6a5;hp=a1205d72a49aa6c72dd5b21a61a69cea3d8d804d;hpb=44cd0fc9a1687cc63911c7f98d978594458e7813;p=lyx.git diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index a1205d72a4..c2ddae5c60 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -6,6 +6,7 @@ * \author Jürgen Vigna * \author Lars Gullik Bjønnes * \author Alfredo Braunstein + * \author Michael Gerz * * Full author contact details are available in file CREDITS. */ @@ -15,24 +16,46 @@ #include "CutAndPaste.h" #include "buffer.h" +#include "buffer_funcs.h" #include "bufferparams.h" +#include "cursor.h" +#include "debug.h" #include "errorlist.h" +#include "funcrequest.h" #include "gettext.h" -#include "iterators.h" +#include "insetiterator.h" +#include "language.h" +#include "lfuns.h" +#include "lyxrc.h" +#include "lyxtext.h" #include "lyxtextclasslist.h" #include "paragraph.h" #include "paragraph_funcs.h" #include "ParagraphParameters.h" +#include "ParagraphList_fwd.h" +#include "pariterator.h" +#include "undo.h" +#include "insets/insetcharstyle.h" #include "insets/insettabular.h" +#include "mathed/MathData.h" +#include "mathed/InsetMath.h" +#include "mathed/MathSupport.h" + #include "support/lstrings.h" -using lyx::pos_type; -using lyx::textclass_type; +#include "frontends/Clipboard.h" + +#include + + +namespace lyx { -using lyx::support::bformat; +using support::bformat; +using frontend::Clipboard; +using std::endl; using std::for_each; using std::make_pair; using std::pair; @@ -40,216 +63,113 @@ using std::vector; using std::string; -typedef limited_stack > CutStack; - namespace { -CutStack cuts(10); - -} // namespace anon +typedef std::pair PitPosPair; +typedef limited_stack > CutStack; -std::vector -CutAndPaste::availableSelections(Buffer const & buffer) -{ - vector selList; - - CutStack::const_iterator cit = cuts.begin(); - CutStack::const_iterator end = cuts.end(); - for (; cit != end; ++cit) { - // we do not use cit-> here because gcc 2.9x does not - // like it (JMarc) - ParagraphList const & pars = (*cit).first; - string asciiSel; - ParagraphList::const_iterator pit = pars.begin(); - ParagraphList::const_iterator pend = pars.end(); - for (; pit != pend; ++pit) { - asciiSel += pit->asString(buffer, false); - if (asciiSel.size() > 25) { - asciiSel.replace(22, string::npos, "..."); - break; - } - } - - selList.push_back(asciiSel); - } +CutStack theCuts(10); - return selList; -} +// store whether the tabular stack is newer than the normal copy stack +// FIXME: this is a workaround for bug 1919. Should be removed for 1.5, +// when we (hopefully) have a one-for-all paste mechanism. +bool dirty_tabular_stack_; -PitPosPair CutAndPaste::cutSelection(BufferParams const & params, - ParagraphList & pars, - ParagraphList::iterator startpit, - ParagraphList::iterator endpit, - int startpos, int endpos, - textclass_type tc, bool doclear) +void region(CursorSlice const & i1, CursorSlice const & i2, + InsetBase::row_type & r1, InsetBase::row_type & r2, + InsetBase::col_type & c1, InsetBase::col_type & c2) { - copySelection(startpit, endpit, startpos, endpos, tc); - return eraseSelection(params, pars, startpit, endpit, startpos, - endpos, doclear); + InsetBase & p = i1.inset(); + c1 = p.col(i1.idx()); + c2 = p.col(i2.idx()); + if (c1 > c2) + std::swap(c1, c2); + r1 = p.row(i1.idx()); + r2 = p.row(i2.idx()); + if (r1 > r2) + std::swap(r1, r2); } -PitPosPair CutAndPaste::eraseSelection(BufferParams const & params, - ParagraphList & pars, - ParagraphList::iterator startpit, - ParagraphList::iterator endpit, - int startpos, int endpos, bool doclear) +bool checkPastePossible(int index) { - if (startpit == pars.end() || (startpos > startpit->size())) - return PitPosPair(endpit, endpos); - - if (endpit == pars.end() || startpit == endpit) { - endpos -= startpit->erase(startpos, endpos); - return PitPosPair(endpit, endpos); - } - - // clear end/begin fragments of the first/last par in selection - bool all_erased = true; - - startpit->erase(startpos, startpit->size()); - if (startpit->size() != startpos) - all_erased = false; - - endpos -= endpit->erase(0, endpos); - if (endpos != 0) - all_erased = false; - - // Loop through the deleted pars if any, erasing as needed - - ParagraphList::iterator pit = boost::next(startpit); - - while (pit != endpit && pit != pars.end()) { - ParagraphList::iterator const next = boost::next(pit); - // "erase" the contents of the par - pit->erase(0, pit->size()); - if (!pit->size()) { - // remove the par if it's now empty - pars.erase(pit); - } else - all_erased = false; - pit = next; - } - -#if 0 // FIXME: why for cut but not copy ? - // the cut selection should begin with standard layout - if (realcut) { - buf->params().clear(); - buf->bibkey = 0; - buf->layout(textclasslist[buffer->params.textclass].defaultLayoutName()); - } -#endif - - if (boost::next(startpit) == pars.end()) - return PitPosPair(endpit, endpos); - - if (doclear) { - boost::next(startpit)->stripLeadingSpaces(); - } - - // paste the paragraphs again, if possible - if (all_erased && - (startpit->hasSameLayout(*boost::next(startpit)) || - boost::next(startpit)->empty())) { - mergeParagraph(params, pars, startpit); - // this because endpar gets deleted here! - endpit = startpit; - endpos = startpos; - } - - return PitPosPair(endpit, endpos); - + return size_t(index) < theCuts.size() && !theCuts[index].first.empty(); } -namespace { - -struct resetOwnerAndChanges { - void operator()(Paragraph & p) { - p.cleanChanges(); - p.setInsetOwner(0); - } -}; - -} // anon namespace - -bool CutAndPaste::copySelection(ParagraphList::iterator startpit, - ParagraphList::iterator endpit, - int start, int end, textclass_type tc) +pair +pasteSelectionHelper(LCursor & cur, ParagraphList const & parlist, + textclass_type textclass, ErrorList & errorlist) { - BOOST_ASSERT(0 <= start && start <= startpit->size()); - BOOST_ASSERT(0 <= end && end <= endpit->size()); - BOOST_ASSERT(startpit != endpit || start <= end); + Buffer const & buffer = cur.buffer(); + pit_type pit = cur.pit(); + pos_type pos = cur.pos(); + ParagraphList & pars = cur.text()->paragraphs(); - ParagraphList paragraphs; - - // Clone the paragraphs within the selection. - ParagraphList::iterator postend = boost::next(endpit); - - paragraphs.assign(startpit, postend); - for_each(paragraphs.begin(), paragraphs.end(), resetOwnerAndChanges()); - - // Cut out the end of the last paragraph. - Paragraph & back = paragraphs.back(); - back.erase(end, back.size()); - - // Cut out the begin of the first paragraph - Paragraph & front = paragraphs.front(); - front.erase(0, start); - - cuts.push(make_pair(paragraphs, tc)); - - return true; -} - - -pair -CutAndPaste::pasteSelection(Buffer const & buffer, - ParagraphList & pars, - ParagraphList::iterator pit, int pos, - textclass_type tc, - ErrorList & errorlist) -{ - return pasteSelection(buffer, pars, pit, pos, tc, 0, errorlist); -} - - -pair -CutAndPaste::pasteSelection(Buffer const & buffer, - ParagraphList & pars, - ParagraphList::iterator pit, int pos, - textclass_type tc, size_t cut_index, - ErrorList & errorlist) -{ - if (!checkPastePossible()) + if (parlist.empty()) return make_pair(PitPosPair(pit, pos), pit); - BOOST_ASSERT (pos <= pit->size()); + BOOST_ASSERT (pos <= pars[pit].size()); // Make a copy of the CaP paragraphs. - ParagraphList simple_cut_clone = cuts[cut_index].first; - textclass_type const textclass = cuts[cut_index].second; + ParagraphList insertion = parlist; + textclass_type const tc = buffer.params().textclass; // Now remove all out of the pars which is NOT allowed in the // new environment and set also another font if that is required. + // Convert newline to paragraph break in ERT inset. + // This should not be here! + if (pars[pit].inInset() && + pars[pit].inInset()->lyxCode() == InsetBase::ERT_CODE) { + for (ParagraphList::size_type i = 0; i < insertion.size(); ++i) { + for (pos_type j = 0; j < insertion[i].size(); ++j) { + if (insertion[i].isNewline(j)) { + // do not track deletion of newline + insertion[i].eraseChar(j, false); + breakParagraphConservative( + buffer.params(), + insertion, i, j); + } + } + } + } + + // If we are in an inset which returns forceDefaultParagraphs, + // set the paragraphs to default + if (cur.inset().forceDefaultParagraphs(cur.idx())) { + LyXLayout_ptr const layout = + buffer.params().getLyXTextClass().defaultLayout(); + ParagraphList::iterator const end = insertion.end(); + for (ParagraphList::iterator par = insertion.begin(); + par != end; ++par) + par->layout(layout); + } + // Make sure there is no class difference. - SwitchLayoutsBetweenClasses(textclass, tc, simple_cut_clone, - errorlist); + InsetText in; + // This works without copying any paragraph data because we have + // a specialized swap method for ParagraphList. This is important + // since we store pointers to insets at some places and we don't + // want to invalidate them. + insertion.swap(in.paragraphs()); + cap::switchBetweenClasses(textclass, tc, in, errorlist); + insertion.swap(in.paragraphs()); - ParagraphList::iterator tmpbuf = simple_cut_clone.begin(); - int depth_delta = pit->params().depth() - tmpbuf->params().depth(); + ParagraphList::iterator tmpbuf = insertion.begin(); + int depth_delta = pars[pit].params().depth() - tmpbuf->params().depth(); - Paragraph::depth_type max_depth = pit->getMaxDepthAfter(); + depth_type max_depth = pars[pit].getMaxDepthAfter(); - for (; tmpbuf != simple_cut_clone.end(); ++tmpbuf) { + for (; tmpbuf != insertion.end(); ++tmpbuf) { // If we have a negative jump so that the depth would // go below 0 depth then we have to redo the delta to // this new max depth level so that subsequent // paragraphs are aligned correctly to this paragraph // at level 0. - if ((int(tmpbuf->params().depth()) + depth_delta) < 0) + if (int(tmpbuf->params().depth()) + depth_delta < 0) depth_delta = 0; // Set the right depth so that we are not too deep or shallow. @@ -259,47 +179,45 @@ CutAndPaste::pasteSelection(Buffer const & buffer, // Only set this from the 2nd on as the 2nd depends // for maxDepth still on pit. - if (tmpbuf != simple_cut_clone.begin()) + if (tmpbuf != insertion.begin()) max_depth = tmpbuf->getMaxDepthAfter(); // Set the inset owner of this paragraph. - tmpbuf->setInsetOwner(pit->inInset()); + tmpbuf->setInsetOwner(pars[pit].inInset()); for (pos_type i = 0; i < tmpbuf->size(); ++i) { - if (tmpbuf->getChar(i) == Paragraph::META_INSET) { - if (!pit->insetAllowed(tmpbuf->getInset(i)->lyxCode())) { - tmpbuf->erase(i--); - } - } else { - LyXFont f1 = tmpbuf->getFont(buffer.params(), i, outerFont(pit, pars)); - LyXFont f2 = f1; - if (!pit->checkInsertChar(f1)) { - tmpbuf->erase(i--); - } else if (f1 != f2) { - tmpbuf->setFont(i, f1); - } - } + if (tmpbuf->getChar(i) == Paragraph::META_INSET && + !pars[pit].insetAllowed(tmpbuf->getInset(i)->lyxCode())) + // do not track deletion of invalid insets + tmpbuf->eraseChar(i--, false); } + + tmpbuf->setChange(Change(buffer.params().trackChanges ? + Change::INSERTED : Change::UNCHANGED)); + } + + bool const empty = pars[pit].empty(); + if (!empty) { + // Make the buf exactly the same layout as the cursor + // paragraph. + insertion.begin()->makeSameLayout(pars[pit]); } - // Make the buf exactly the same layout than - // the cursor paragraph. - simple_cut_clone.begin()->makeSameLayout(*pit); + // Prepare the paragraphs and insets for insertion. + // A couple of insets store buffer references so need updating. + insertion.swap(in.paragraphs()); - // Prepare the paragraphs and insets for insertion - // A couple of insets store buffer references so need - // updating - ParIterator fpit(simple_cut_clone.begin(), simple_cut_clone); - ParIterator fend(simple_cut_clone.end(), simple_cut_clone); + ParIterator fpit = par_iterator_begin(in); + ParIterator fend = par_iterator_end(in); for (; fpit != fend; ++fpit) { - InsetList::iterator lit = fpit->insetlist.begin(); - InsetList::iterator eit = fpit->insetlist.end(); + InsetList::const_iterator lit = fpit->insetlist.begin(); + InsetList::const_iterator eit = fpit->insetlist.end(); for (; lit != eit; ++lit) { switch (lit->inset->lyxCode()) { - case InsetOld::TABULAR_CODE: { + case InsetBase::TABULAR_CODE: { InsetTabular * it = static_cast(lit->inset); - it->buffer(const_cast(&buffer)); + it->buffer(&buffer); break; } @@ -308,79 +226,172 @@ CutAndPaste::pasteSelection(Buffer const & buffer, } } } + insertion.swap(in.paragraphs()); - bool paste_the_end = false; + // Split the paragraph for inserting the buf if necessary. + if (!empty) + breakParagraphConservative(buffer.params(), pars, pit, pos); - // Open the paragraph for inserting the buf - // if necessary. - if (pit->size() > pos || boost::next(pit) == pars.end()) { - breakParagraphConservative(buffer.params(), - pars, pit, pos); - paste_the_end = true; + // Paste it! + if (empty) { + pars.insert(boost::next(pars.begin(), pit), + insertion.begin(), + insertion.end()); + + // merge the empty par with the last par of the insertion + mergeParagraph(buffer.params(), pars, + pit + insertion.size() - 1); + } else { + pars.insert(boost::next(pars.begin(), pit + 1), + insertion.begin(), + insertion.end()); + + // merge the first par of the insertion with the current par + mergeParagraph(buffer.params(), pars, pit); } - // Set the end for redoing later. - ParagraphList::iterator endpit = boost::next(boost::next(pit)); + pit_type last_paste = pit + insertion.size() - 1; - // Paste it! + // Store the new cursor position. + pit = last_paste; + pos = pars[last_paste].size(); + + // Join (conditionally) last pasted paragraph with next one, i.e., + // the tail of the spliced document paragraph + if (!empty && last_paste + 1 != pit_type(pars.size())) { + if (pars[last_paste + 1].hasSameLayout(pars[last_paste])) { + mergeParagraph(buffer.params(), pars, last_paste); + } else if (pars[last_paste + 1].empty()) { + pars[last_paste + 1].makeSameLayout(pars[last_paste]); + mergeParagraph(buffer.params(), pars, last_paste); + } else if (pars[last_paste].empty()) { + pars[last_paste].makeSameLayout(pars[last_paste + 1]); + mergeParagraph(buffer.params(), pars, last_paste); + } else { + pars[last_paste + 1].stripLeadingSpaces(); + ++last_paste; + } + } - ParagraphList::iterator past_pit = boost::next(pit); - pars.splice(past_pit, simple_cut_clone); - ParagraphList::iterator last_paste = boost::prior(past_pit); + return make_pair(PitPosPair(pit, pos), last_paste + 1); +} - // If we only inserted one paragraph. - if (boost::next(pit) == last_paste) - last_paste = pit; - mergeParagraph(buffer.params(), pars, pit); +PitPosPair eraseSelectionHelper(BufferParams const & params, + ParagraphList & pars, + pit_type startpit, pit_type endpit, + int startpos, int endpos, bool doclear) +{ + // Start of selection is really invalid. + if (startpit == pit_type(pars.size()) || + (startpos > pars[startpit].size())) + return PitPosPair(endpit, endpos); - // Store the new cursor position. - pit = last_paste; - pos = last_paste->size(); - - // Maybe some pasting. - if (boost::next(last_paste) != pars.end() && - paste_the_end) { - if (boost::next(last_paste)->hasSameLayout(*last_paste)) { - mergeParagraph(buffer.params(), pars, - last_paste); - } else if (boost::next(last_paste)->empty()) { - boost::next(last_paste)->makeSameLayout(*last_paste); - mergeParagraph(buffer.params(), pars, - last_paste); - } else if (last_paste->empty()) { - last_paste->makeSameLayout(*boost::next(last_paste)); - mergeParagraph(buffer.params(), pars, - last_paste); + // Start and end is inside same paragraph + if (endpit == pit_type(pars.size()) || startpit == endpit) { + endpos -= pars[startpit].eraseChars(startpos, endpos, params.trackChanges); + return PitPosPair(endpit, endpos); + } + + for (pit_type pit = startpit; pit != endpit + 1;) { + pos_type const left = (pit == startpit ? startpos : 0); + pos_type const right = (pit == endpit ? endpos : pars[pit].size() + 1); + + bool const merge = pars[pit].isMergedOnEndOfParDeletion(params.trackChanges); + + // Logically erase only, including the end-of-paragraph character + pars[pit].eraseChars(left, right, params.trackChanges); + + // Separate handling of paragraph break: + if (merge && pit != endpit && + (pit + 1 != endpit || pars[pit].hasSameLayout(pars[pit + 1]))) { + pos_type const thissize = pars[pit].size(); + if (doclear) + pars[pit + 1].stripLeadingSpaces(); + mergeParagraph(params, pars, pit); + --endpit; + if (pit == endpit) + endpos += thissize; } else - boost::next(last_paste)->stripLeadingSpaces(); + ++pit; } - return make_pair(PitPosPair(pit, pos), endpit); + // Ensure legal cursor pos: + endpit = startpit; + endpos = startpos; + return PitPosPair(endpit, endpos); } -int CutAndPaste::nrOfParagraphs() +void copySelectionHelper(Buffer const & buf, ParagraphList & pars, + pit_type startpit, pit_type endpit, + int start, int end, textclass_type tc) { - return cuts.empty() ? 0 : cuts[0].first.size(); + BOOST_ASSERT(0 <= start && start <= pars[startpit].size()); + BOOST_ASSERT(0 <= end && end <= pars[endpit].size()); + BOOST_ASSERT(startpit != endpit || start <= end); + + // Clone the paragraphs within the selection. + ParagraphList paragraphs(boost::next(pars.begin(), startpit), + boost::next(pars.begin(), endpit + 1)); + + ParagraphList::iterator it = paragraphs.begin(); + ParagraphList::iterator it_end = paragraphs.end(); + + for (; it != it_end; it++) { + // ERT paragraphs have the Language latex_language. + // This is invalid outside of ERT, so we need to change it + // to the buffer language. + if (it->ownerCode() == InsetBase::ERT_CODE) { + it->changeLanguage(buf.params(), latex_language, + buf.getLanguage()); + } + it->setInsetOwner(0); + } + + // Cut out the end of the last paragraph. + Paragraph & back = paragraphs.back(); + // do not track deletion here; it is an internal action not visible to the user + back.eraseChars(end, back.size(), false); + + // Cut out the begin of the first paragraph + Paragraph & front = paragraphs.front(); + // again, do not track deletion + front.eraseChars(0, start, false); + + theCuts.push(make_pair(paragraphs, tc)); } +} // namespace anon + -int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1, - textclass_type c2, - ParagraphList & pars, - ErrorList & errorlist) + + +namespace cap { + +docstring grabAndEraseSelection(LCursor & cur) { - BOOST_ASSERT(!pars.empty()); + if (!cur.selection()) + return docstring(); + docstring res = grabSelection(cur); + eraseSelection(cur); + return res; +} - int ret = 0; + +void switchBetweenClasses(textclass_type c1, textclass_type c2, + InsetText & in, ErrorList & errorlist) +{ + BOOST_ASSERT(!in.paragraphs().empty()); if (c1 == c2) - return ret; + return; LyXTextClass const & tclass1 = textclasslist[c1]; LyXTextClass const & tclass2 = textclasslist[c2]; - ParIterator end = ParIterator(pars.end(), pars); - for (ParIterator it = ParIterator(pars.begin(), pars); it != end; ++it) { + + // layouts + ParIterator end = par_iterator_end(in); + for (ParIterator it = par_iterator_begin(in); it != end; ++it) { string const name = it->layout()->name(); bool hasLayout = tclass2.hasLayout(name); @@ -390,22 +401,402 @@ int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1, it->layout(tclass2.defaultLayout()); if (!hasLayout && name != tclass1.defaultLayoutName()) { - ++ret; - string const s = bformat( - _("Layout had to be changed from\n%1$s to %2$s\n" - "because of class conversion from\n%3$s to %4$s"), - name, it->layout()->name(), tclass1.name(), tclass2.name()); + docstring const s = bformat( + _("Layout had to be changed from\n%1$s to %2$s\n" + "because of class conversion from\n%3$s to %4$s"), + from_utf8(name), from_utf8(it->layout()->name()), + from_utf8(tclass1.name()), from_utf8(tclass2.name())); // To warn the user that something had to be done. - errorlist.push_back(ErrorItem("Changed Layout", s, + errorlist.push_back(ErrorItem(_("Changed Layout"), s, it->id(), 0, it->size())); } } - return ret; + + // character styles + InsetIterator const i_end = inset_iterator_end(in); + for (InsetIterator it = inset_iterator_begin(in); it != i_end; ++it) { + if (it->lyxCode() == InsetBase::CHARSTYLE_CODE) { + InsetCharStyle & inset = + static_cast(*it); + string const name = inset.params().type; + CharStyles::iterator const found_cs = + tclass2.charstyle(name); + if (found_cs == tclass2.charstyles().end()) { + // The character style is undefined in tclass2 + inset.setUndefined(); + docstring const s = bformat(_( + "Character style %1$s is " + "undefined because of class " + "conversion from\n%2$s to %3$s"), + from_utf8(name), from_utf8(tclass1.name()), + from_utf8(tclass2.name())); + // To warn the user that something had to be done. + errorlist.push_back(ErrorItem( + _("Undefined character style"), + s, it.paragraph().id(), it.pos(), it.pos() + 1)); + } else if (inset.undefined()) { + // The character style is undefined in + // tclass1 and is defined in tclass2 + inset.setDefined(found_cs); + } + } + } +} + + +std::vector const availableSelections(Buffer const & buffer) +{ + vector selList; + + CutStack::const_iterator cit = theCuts.begin(); + CutStack::const_iterator end = theCuts.end(); + for (; cit != end; ++cit) { + // we do not use cit-> here because gcc 2.9x does not + // like it (JMarc) + ParagraphList const & pars = (*cit).first; + docstring asciiSel; + ParagraphList::const_iterator pit = pars.begin(); + ParagraphList::const_iterator pend = pars.end(); + for (; pit != pend; ++pit) { + asciiSel += pit->asString(buffer, false); + if (asciiSel.size() > 25) { + asciiSel.replace(22, docstring::npos, + from_ascii("...")); + break; + } + } + + selList.push_back(asciiSel); + } + + return selList; +} + + +size_type numberOfSelections() +{ + return theCuts.size(); +} + + +void cutSelection(LCursor & cur, bool doclear, bool realcut) +{ + // This doesn't make sense, if there is no selection + if (!cur.selection()) + return; + + // OK, we have a selection. This is always between cur.selBegin() + // and cur.selEnd() + + if (cur.inTexted()) { + LyXText * text = cur.text(); + BOOST_ASSERT(text); + // Stuff what we got on the clipboard. Even if there is no selection. + + // There is a problem with having the stuffing here in that the + // larger the selection the slower LyX will get. This can be + // solved by running the line below only when the selection has + // finished. The solution used currently just works, to make it + // faster we need to be more clever and probably also have more + // calls to theSelection().put. (Lgb) +// theSelection().put(cur.selectionAsString(true)); + + + // make sure that the depth behind the selection are restored, too + recordUndoSelection(cur); + pit_type begpit = cur.selBegin().pit(); + pit_type endpit = cur.selEnd().pit(); + + int endpos = cur.selEnd().pos(); + + BufferParams const & bp = cur.buffer().params(); + if (realcut) { + copySelectionHelper(cur.buffer(), + text->paragraphs(), + begpit, endpit, + cur.selBegin().pos(), endpos, + bp.textclass); + } + + boost::tie(endpit, endpos) = + eraseSelectionHelper(bp, + text->paragraphs(), + begpit, endpit, + cur.selBegin().pos(), endpos, + doclear); + + // sometimes necessary + if (doclear) + text->paragraphs()[begpit].stripLeadingSpaces(); + + // cutSelection can invalidate the cursor so we need to set + // it anew. (Lgb) + // we prefer the end for when tracking changes + cur.pos() = endpos; + cur.pit() = endpit; + + // need a valid cursor. (Lgb) + cur.clearSelection(); + updateLabels(cur.buffer()); + + // tell tabular that a recent copy happened + dirtyTabularStack(false); + } + + if (cur.inMathed()) { + if (cur.selBegin().idx() != cur.selEnd().idx()) { + // The current selection spans more than one cell. + // Record all cells + recordUndoInset(cur); + } else { + // Record only the current cell to avoid a jumping + // cursor after undo + recordUndo(cur); + } + if (realcut) + copySelection(cur); + eraseSelection(cur); + } +} + + +void copySelection(LCursor & cur) +{ + // stuff the selection onto the X clipboard, from an explicit copy request + theClipboard().put(cur.selectionAsString(true)); + + // this doesn't make sense, if there is no selection + if (!cur.selection()) + return; + + if (cur.inTexted()) { + LyXText * text = cur.text(); + BOOST_ASSERT(text); + // ok we have a selection. This is always between cur.selBegin() + // and sel_end cursor + + // copy behind a space if there is one + ParagraphList & pars = text->paragraphs(); + pos_type pos = cur.selBegin().pos(); + pit_type par = cur.selBegin().pit(); + while (pos < pars[par].size() + && pars[par].isLineSeparator(pos) + && (par != cur.selEnd().pit() || pos < cur.selEnd().pos())) + ++pos; + + copySelectionHelper(cur.buffer(), pars, par, cur.selEnd().pit(), + pos, cur.selEnd().pos(), cur.buffer().params().textclass); + } + + if (cur.inMathed()) { + //lyxerr << "copySelection in mathed" << endl; + ParagraphList pars; + Paragraph par; + BufferParams const & bp = cur.buffer().params(); + par.layout(bp.getLyXTextClass().defaultLayout()); + par.insert(0, grabSelection(cur), LyXFont(), Change(Change::UNCHANGED)); + pars.push_back(par); + theCuts.push(make_pair(pars, bp.textclass)); + } + // tell tabular that a recent copy happened + dirtyTabularStack(false); +} + + +docstring getSelection(Buffer const & buf, size_t sel_index) +{ + return sel_index < theCuts.size() + ? theCuts[sel_index].first.back().asString(buf, false) + : docstring(); +} + + +void pasteParagraphList(LCursor & cur, ParagraphList const & parlist, + textclass_type textclass, ErrorList & errorList) +{ + if (cur.inTexted()) { + LyXText * text = cur.text(); + BOOST_ASSERT(text); + + pit_type endpit; + PitPosPair ppp; + + boost::tie(ppp, endpit) = + pasteSelectionHelper(cur, parlist, + textclass, errorList); + updateLabels(cur.buffer()); + cur.clearSelection(); + text->setCursor(cur, ppp.first, ppp.second); + } + + // mathed is handled in InsetMathNest/InsetMathGrid + BOOST_ASSERT(!cur.inMathed()); +} + + +void pasteSelection(LCursor & cur, ErrorList & errorList, size_t sel_index) +{ + // this does not make sense, if there is nothing to paste + if (!checkPastePossible(sel_index)) + return; + + recordUndo(cur); + pasteParagraphList(cur, theCuts[sel_index].first, + theCuts[sel_index].second, errorList); + cur.setSelection(); +} + + +// simple replacing. The font of the first selected character is used +void replaceSelectionWithString(LCursor & cur, string const & str, bool backwards) +{ + recordUndo(cur); + DocIterator selbeg = cur.selectionBegin(); + + // Get font setting before we cut + LyXFont const font = + selbeg.paragraph().getFontSettings(cur.buffer().params(), selbeg.pos()); + + // Insert the new string + pos_type pos = cur.selEnd().pos(); + Paragraph & par = cur.selEnd().paragraph(); + string::const_iterator cit = str.begin(); + string::const_iterator end = str.end(); + for (; cit != end; ++cit, ++pos) + par.insertChar(pos, (*cit), font, cur.buffer().params().trackChanges); + + // Cut the selection + cutSelection(cur, true, false); + + // select the replacement + if (backwards) { + selbeg.pos() += str.length(); + cur.setSelection(selbeg, -int(str.length())); + } else + cur.setSelection(selbeg, str.length()); +} + + +void replaceSelection(LCursor & cur) +{ + if (cur.selection()) + cutSelection(cur, true, false); +} + + +void eraseSelection(LCursor & cur) +{ + //lyxerr << "LCursor::eraseSelection begin: " << cur << endl; + CursorSlice const & i1 = cur.selBegin(); + CursorSlice const & i2 = cur.selEnd(); + if (i1.inset().asInsetMath()) { + cur.top() = i1; + if (i1.idx() == i2.idx()) { + i1.cell().erase(i1.pos(), i2.pos()); + // We may have deleted i1.cell(cur.pos()). + // Make sure that pos is valid. + if (cur.pos() > cur.lastpos()) + cur.pos() = cur.lastpos(); + } else { + InsetMath * p = i1.asInsetMath(); + InsetBase::row_type r1, r2; + InsetBase::col_type c1, c2; + region(i1, i2, r1, r2, c1, c2); + for (InsetBase::row_type row = r1; row <= r2; ++row) + for (InsetBase::col_type col = c1; col <= c2; ++col) + p->cell(p->index(row, col)).clear(); + // We've deleted the whole cell. Only pos 0 is valid. + cur.pos() = 0; + } + // need a valid cursor. (Lgb) + cur.clearSelection(); + } else { + lyxerr << "can't erase this selection 1" << endl; + } + //lyxerr << "LCursor::eraseSelection end: " << cur << endl; } -bool CutAndPaste::checkPastePossible() +void selDel(LCursor & cur) { - return !cuts.empty() && !cuts[0].first.empty(); + //lyxerr << "LCursor::selDel" << endl; + if (cur.selection()) + eraseSelection(cur); } + + +void selClearOrDel(LCursor & cur) +{ + //lyxerr << "LCursor::selClearOrDel" << endl; + if (lyxrc.auto_region_delete) + selDel(cur); + else + cur.selection() = false; +} + + +docstring grabSelection(LCursor const & cur) +{ + if (!cur.selection()) + return docstring(); + + // FIXME: What is wrong with the following? +#if 0 + std::ostringstream os; + for (DocIterator dit = cur.selectionBegin(); + dit != cur.selectionEnd(); dit.forwardPos()) + os << asString(dit.cell()); + return os.str(); +#endif + + CursorSlice i1 = cur.selBegin(); + CursorSlice i2 = cur.selEnd(); + + if (i1.idx() == i2.idx()) { + if (i1.inset().asInsetMath()) { + MathArray::const_iterator it = i1.cell().begin(); + return asString(MathArray(it + i1.pos(), it + i2.pos())); + } else { + return from_ascii("unknown selection 1"); + } + } + + InsetBase::row_type r1, r2; + InsetBase::col_type c1, c2; + region(i1, i2, r1, r2, c1, c2); + + docstring data; + if (i1.inset().asInsetMath()) { + for (InsetBase::row_type row = r1; row <= r2; ++row) { + if (row > r1) + data += "\\\\"; + for (InsetBase::col_type col = c1; col <= c2; ++col) { + if (col > c1) + data += '&'; + data += asString(i1.asInsetMath()-> + cell(i1.asInsetMath()->index(row, col))); + } + } + } else { + data = from_ascii("unknown selection 2"); + } + return data; +} + + +void dirtyTabularStack(bool b) +{ + dirty_tabular_stack_ = b; +} + + +bool tabularStackDirty() +{ + return dirty_tabular_stack_; +} + + +} // namespace cap + +} // namespace lyx