X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCutAndPaste.C;h=88fbbb6141900187a35658d9131614bb2bbb425f;hb=0049b4d3e46a36bd4d5ce07a54555cfba2295cfd;hp=d89b0ca6c78e1b8305cc5aaf2b4e35644be01479;hpb=ef68cb1128275870a3b3ca4c793e74f61922a723;p=lyx.git diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index d89b0ca6c7..88fbbb6141 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -17,13 +17,13 @@ #include "buffer.h" #include "buffer_funcs.h" #include "bufferparams.h" -#include "BufferView.h" #include "cursor.h" #include "debug.h" #include "errorlist.h" #include "funcrequest.h" #include "gettext.h" #include "insetiterator.h" +#include "language.h" #include "lfuns.h" #include "lyxrc.h" #include "lyxtext.h" @@ -38,19 +38,21 @@ #include "insets/insetcharstyle.h" #include "insets/insettabular.h" -#include "mathed/math_data.h" -#include "mathed/math_inset.h" -#include "mathed/math_support.h" +#include "mathed/MathData.h" +#include "mathed/InsetMath.h" +#include "mathed/MathSupport.h" #include "support/lstrings.h" +#include "frontends/Clipboard.h" + #include -using lyx::pos_type; -using lyx::pit_type; -using lyx::textclass_type; -using lyx::support::bformat; +namespace lyx { + +using support::bformat; +using frontend::Clipboard; using std::endl; using std::for_each; @@ -62,19 +64,16 @@ using std::string; namespace { -typedef std::pair PitPosPair; +typedef std::pair PitPosPair; typedef limited_stack > CutStack; CutStack theCuts(10); -class resetOwnerAndChanges : public std::unary_function { -public: - void operator()(Paragraph & p) const { - p.cleanChanges(); - p.setInsetOwner(0); - } -}; +// 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_; void region(CursorSlice const & i1, CursorSlice const & i2, @@ -99,167 +98,181 @@ bool checkPastePossible(int index) } -void pasteSelectionHelper(LCursor & cur, size_t cut_index) +pair +pasteSelectionHelper(LCursor & cur, ParagraphList const & parlist, + textclass_type textclass, ErrorList & errorlist) { - recordUndo(cur); Buffer const & buffer = cur.buffer(); - InsetText & inset = static_cast(cur.inset()); pit_type pit = cur.pit(); pos_type pos = cur.pos(); - pit_type endpit = pit; - textclass_type const & tc = buffer.params().textclass; - ErrorList errorlist; - - if (checkPastePossible(cut_index)) { - ParagraphList & pars = inset.paragraphs(); - BOOST_ASSERT (pos <= pars[pit].size()); - - // Make a copy of the CaP paragraphs. - ParagraphList insertion = theCuts[cut_index].first; - textclass_type const textclass = theCuts[cut_index].second; - - // 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)) { - insertion[i].erase(j); - breakParagraphConservative( - buffer.params(), - insertion, i, j); - } + ParagraphList & pars = cur.text()->paragraphs(); + + if (parlist.empty()) + return make_pair(PitPosPair(pit, pos), pit); + + BOOST_ASSERT (pos <= pars[pit].size()); + + // Make a copy of the CaP paragraphs. + 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); } } } + } - // Make sure there is no class difference. - lyx::cap::SwitchBetweenClasses(textclass, tc, insertion, errorlist); - - ParagraphList::iterator tmpbuf = insertion.begin(); - int depth_delta = pars[pit].params().depth() - tmpbuf->params().depth(); - - Paragraph::depth_type max_depth = pars[pit].getMaxDepthAfter(); - - 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) - depth_delta = 0; - - // Set the right depth so that we are not too deep or shallow. - tmpbuf->params().depth(tmpbuf->params().depth() + depth_delta); - if (tmpbuf->params().depth() > max_depth) - tmpbuf->params().depth(max_depth); - - // Only set this from the 2nd on as the 2nd depends - // for maxDepth still on pit. - if (tmpbuf != insertion.begin()) - max_depth = tmpbuf->getMaxDepthAfter(); - - // Set the inset owner of this paragraph. - tmpbuf->setInsetOwner(pars[pit].inInset()); - for (pos_type i = 0; i < tmpbuf->size(); ++i) { - if (tmpbuf->getChar(i) == Paragraph::META_INSET && - !inset.insetAllowed(tmpbuf->getInset(i)->lyxCode())) - tmpbuf->erase(i--); - } - } + // 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); + } - 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 sure there is no class difference. + 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 = insertion.begin(); + int depth_delta = pars[pit].params().depth() - tmpbuf->params().depth(); + + depth_type max_depth = pars[pit].getMaxDepthAfter(); + + 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) + depth_delta = 0; + + // Set the right depth so that we are not too deep or shallow. + tmpbuf->params().depth(tmpbuf->params().depth() + depth_delta); + if (tmpbuf->params().depth() > max_depth) + tmpbuf->params().depth(max_depth); + + // Only set this from the 2nd on as the 2nd depends + // for maxDepth still on pit. + if (tmpbuf != insertion.begin()) + max_depth = tmpbuf->getMaxDepthAfter(); + + // Set the inset owner of this paragraph. + tmpbuf->setInsetOwner(pars[pit].inInset()); + for (pos_type i = 0; i < tmpbuf->size(); ++i) { + 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); } - // Prepare the paragraphs and insets for insertion. - // A couple of insets store buffer references so need updating. - InsetText in; - std::swap(in.paragraphs(), insertion); + tmpbuf->setChange(Change(buffer.params().trackChanges ? + Change::INSERTED : Change::UNCHANGED)); + } - ParIterator fpit = par_iterator_begin(in); - ParIterator fend = par_iterator_end(in); + bool const empty = pars[pit].empty(); + if (!empty) { + // Make the buf exactly the same layout as the cursor + // paragraph. + insertion.begin()->makeSameLayout(pars[pit]); + } - for (; fpit != fend; ++fpit) { - InsetList::iterator lit = fpit->insetlist.begin(); - InsetList::iterator eit = fpit->insetlist.end(); + // Prepare the paragraphs and insets for insertion. + // A couple of insets store buffer references so need updating. + insertion.swap(in.paragraphs()); - for (; lit != eit; ++lit) { - switch (lit->inset->lyxCode()) { - case InsetBase::TABULAR_CODE: { - InsetTabular * it = static_cast(lit->inset); - it->buffer(&buffer); - break; - } + ParIterator fpit = par_iterator_begin(in); + ParIterator fend = par_iterator_end(in); - default: - break; // nothing - } + for (; fpit != fend; ++fpit) { + InsetList::iterator lit = fpit->insetlist.begin(); + InsetList::iterator eit = fpit->insetlist.end(); + + for (; lit != eit; ++lit) { + switch (lit->inset->lyxCode()) { + case InsetBase::TABULAR_CODE: { + InsetTabular * it = static_cast(lit->inset); + it->buffer(&buffer); + break; + } + + default: + break; // nothing } } - std::swap(in.paragraphs(), insertion); + } + insertion.swap(in.paragraphs()); - // Split the paragraph for inserting the buf if necessary. - if (!empty) - breakParagraphConservative(buffer.params(), pars, pit, pos); + // Split the paragraph for inserting the buf if necessary. + if (!empty) + breakParagraphConservative(buffer.params(), pars, pit, pos); - // Paste it! - if (empty) { - pars.insert(pars.begin() + pit, insertion.begin(), - insertion.end()); + // 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(pars.begin() + pit + 1, 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); - } + // merge the first par of the insertion with the current par + mergeParagraph(buffer.params(), pars, pit); + } - pit_type last_paste = pit + insertion.size() - 1; - - // Store the new cursor position. - pit = last_paste; - pos = pars[last_paste].size(); - - // Maybe some pasting. - 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; - } + pit_type last_paste = pit + insertion.size() - 1; + + // 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; } - - endpit = last_paste + 1; } - bufferErrors(cur.buffer(), errorlist); - cur.bv().showErrorList(_("Paste")); - cur.clearSelection(); - cur.resetAnchor(); - BOOST_ASSERT(cur.text()); - cur.text()->setCursor(cur, pit, pos); - cur.setSelection(); - updateCounters(cur.buffer()); + return make_pair(PitPosPair(pit, pos), last_paste + 1); } @@ -268,73 +281,51 @@ PitPosPair eraseSelectionHelper(BufferParams const & params, 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); - if (endpit == pit_type(pars.size()) || - startpit == endpit) { - endpos -= pars[startpit].erase(startpos, endpos); + // 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); } - // clear end/begin fragments of the first/last par in selection - bool all_erased = true; - - pars[startpit].erase(startpos, pars[startpit].size()); - if (pars[startpit].size() != startpos) - all_erased = false; - - endpos -= pars[endpit].erase(0, endpos); - if (endpos != 0) - all_erased = false; - - // Loop through the deleted pars if any, erasing as needed - for (pit_type pit = startpit + 1; pit != endpit;) { - // "erase" the contents of the par - pars[pit].erase(0, pars[pit].size()); - if (!pars[pit].size()) { - // remove the par if it's now empty - pars.erase(pars.begin() + pit); + // A paragraph break has to be physically removed by merging, but + // only if either (1) change tracking is off, or (2) the para break + // is "blue" + for (pit_type pit = startpit; pit != endpit + 1;) { + // FIXME: Change tracking (MG) + bool const merge = !params.trackChanges || + pars[pit].isInserted(pars[pit].size()); + pos_type const left = ( pit == startpit ? startpos : 0 ); + pos_type const right = ( pit == endpit ? endpos : + pars[pit].size() + 1 ); + // Logical erase only: + pars[pit].eraseChars(left, right, false); + // Separate handling of para 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; - } else { + if (pit == endpit) + endpos += thissize; + } else ++pit; - all_erased = false; - } - } - -#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 (startpit + 1 == pit_type(pars.size())) - return PitPosPair(endpit, endpos); - - if (doclear) { - pars[startpit + 1].stripLeadingSpaces(); - } - - // paste the paragraphs again, if possible - if (all_erased && - (pars[startpit].hasSameLayout(pars[startpit + 1]) || - pars[startpit + 1].empty())) { - mergeParagraph(params, pars, startpit); - // this because endpar gets deleted here! - endpit = startpit; - endpos = startpos; } + // Ensure legal cursor pos: + endpit = startpit; + endpos = startpos; return PitPosPair(endpit, endpos); - } -void copySelectionHelper(ParagraphList & pars, +void copySelectionHelper(Buffer const & buf, ParagraphList & pars, pit_type startpit, pit_type endpit, int start, int end, textclass_type tc) { @@ -343,16 +334,32 @@ void copySelectionHelper(ParagraphList & pars, BOOST_ASSERT(startpit != endpit || start <= end); // Clone the paragraphs within the selection. - ParagraphList paragraphs(pars.begin() + startpit, pars.begin() + endpit + 1); - for_each(paragraphs.begin(), paragraphs.end(), resetOwnerAndChanges()); + 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(); - back.erase(end, back.size()); + // 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(); - front.erase(0, start); + // again, do not track deletion + front.eraseChars(0, start, false); theCuts.push(make_pair(paragraphs, tc)); } @@ -360,33 +367,30 @@ void copySelectionHelper(ParagraphList & pars, } // namespace anon -namespace lyx { + + namespace cap { -string grabAndEraseSelection(LCursor & cur) +docstring grabAndEraseSelection(LCursor & cur) { if (!cur.selection()) - return string(); - string res = grabSelection(cur); + return docstring(); + docstring res = grabSelection(cur); eraseSelection(cur); - cur.selection() = false; return res; } -void SwitchBetweenClasses(textclass_type c1, textclass_type c2, - ParagraphList & pars, ErrorList & errorlist) +void switchBetweenClasses(textclass_type c1, textclass_type c2, + InsetText & in, ErrorList & errorlist) { - BOOST_ASSERT(!pars.empty()); + BOOST_ASSERT(!in.paragraphs().empty()); if (c1 == c2) return; LyXTextClass const & tclass1 = textclasslist[c1]; LyXTextClass const & tclass2 = textclasslist[c2]; - InsetText in; - std::swap(in.paragraphs(), pars); - // layouts ParIterator end = par_iterator_end(in); for (ParIterator it = par_iterator_begin(in); it != end; ++it) { @@ -399,10 +403,11 @@ void SwitchBetweenClasses(textclass_type c1, textclass_type c2, it->layout(tclass2.defaultLayout()); if (!hasLayout && name != tclass1.defaultLayoutName()) { - 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, it->id(), 0, @@ -422,16 +427,16 @@ void SwitchBetweenClasses(textclass_type c1, textclass_type c2, if (found_cs == tclass2.charstyles().end()) { // The character style is undefined in tclass2 inset.setUndefined(); - string const s = bformat(_( + docstring const s = bformat(_( "Character style %1$s is " "undefined because of class " "conversion from\n%2$s to %3$s"), - name, tclass1.name(), tclass2.name()); + 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)); + _("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 @@ -439,14 +444,12 @@ void SwitchBetweenClasses(textclass_type c1, textclass_type c2, } } } - - std::swap(in.paragraphs(), pars); } -std::vector const availableSelections(Buffer const & buffer) +std::vector const availableSelections(Buffer const & buffer) { - vector selList; + vector selList; CutStack::const_iterator cit = theCuts.begin(); CutStack::const_iterator end = theCuts.end(); @@ -454,13 +457,14 @@ std::vector const availableSelections(Buffer const & buffer) // we do not use cit-> here because gcc 2.9x does not // like it (JMarc) ParagraphList const & pars = (*cit).first; - string asciiSel; + 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, string::npos, "..."); + asciiSel.replace(22, docstring::npos, + from_ascii("...")); break; } } @@ -472,14 +476,21 @@ std::vector const availableSelections(Buffer const & buffer) } -int nrOfParagraphs() +size_type numberOfSelections() { - return theCuts.empty() ? 0 : theCuts[0].first.size(); + 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); @@ -490,15 +501,9 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut) // 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 stuffClipboard. (Lgb) - cur.bv().stuffClipboard(cur.selectionAsString(true)); + // calls to theSelection().put. (Lgb) +// theSelection().put(cur.selectionAsString(true)); - // 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() // make sure that the depth behind the selection are restored, too recordUndoSelection(cur); @@ -509,7 +514,8 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut) BufferParams const & bp = cur.buffer().params(); if (realcut) { - copySelectionHelper(text->paragraphs(), + copySelectionHelper(cur.buffer(), + text->paragraphs(), begpit, endpit, cur.selBegin().pos(), endpos, bp.textclass); @@ -534,15 +540,25 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut) // need a valid cursor. (Lgb) cur.clearSelection(); - updateCounters(cur.buffer()); + updateLabels(cur.buffer()); + + // tell tabular that a recent copy happened + dirtyTabularStack(false); } if (cur.inMathed()) { - lyxerr << "cutSelection in mathed" << endl; - LCursor tmp = cur; - copySelection(cur); - cur.selection() = false; - eraseSelection(tmp); + 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); } } @@ -550,7 +566,7 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut) void copySelection(LCursor & cur) { // stuff the selection onto the X clipboard, from an explicit copy request - cur.bv().stuffClipboard(cur.selectionAsString(true)); + theClipboard().put(cur.selectionAsString(true)); // this doesn't make sense, if there is no selection if (!cur.selection()) @@ -571,77 +587,96 @@ void copySelection(LCursor & cur) && (par != cur.selEnd().pit() || pos < cur.selEnd().pos())) ++pos; - copySelectionHelper(pars, par, cur.selEnd().pit(), + 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; - pars.push_back(Paragraph()); + Paragraph par; BufferParams const & bp = cur.buffer().params(); - pars.back().layout(bp.getLyXTextClass().defaultLayout()); - for_each(pars.begin(), pars.end(), resetOwnerAndChanges()); - pars.back().insert(0, grabSelection(cur), LyXFont()); + 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); } -std::string getSelection(Buffer const & buf, size_t sel_index) +docstring getSelection(Buffer const & buf, size_t sel_index) { return sel_index < theCuts.size() ? theCuts[sel_index].first.back().asString(buf, false) - : string(); + : docstring(); } -void pasteSelection(LCursor & cur, size_t sel_index) +void pasteParagraphList(LCursor & cur, ParagraphList const & parlist, + textclass_type textclass, ErrorList & errorList) { - // this does not make sense, if there is nothing to paste - if (!checkPastePossible(sel_index)) - return; - if (cur.inTexted()) - pasteSelectionHelper(cur, sel_index); - if (cur.inMathed()) - lyxerr << "### should be handled in MathNest/GridInset" << endl; + 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 setSelectionRange(LCursor & cur, pos_type length) +void pasteSelection(LCursor & cur, ErrorList & errorList, size_t sel_index) { - LyXText * text = cur.text(); - BOOST_ASSERT(text); - if (!length) + // this does not make sense, if there is nothing to paste + if (!checkPastePossible(sel_index)) return; - cur.resetAnchor(); - while (length--) - text->cursorRight(cur); + + 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) +void replaceSelectionWithString(LCursor & cur, string const & str, bool backwards) { - LyXText * text = cur.text(); - BOOST_ASSERT(text); recordUndo(cur); + DocIterator selbeg = cur.selectionBegin(); // Get font setting before we cut - pos_type pos = cur.selEnd().pos(); - Paragraph & par = text->getPar(cur.selEnd().pit()); LyXFont const font = - par.getFontSettings(cur.buffer().params(), cur.selBegin().pos()); + 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); + 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()); } @@ -652,32 +687,21 @@ void replaceSelection(LCursor & cur) } -// only used by the spellchecker -void replaceWord(LCursor & cur, string const & replacestring) -{ - LyXText * text = cur.text(); - BOOST_ASSERT(text); - - replaceSelectionWithString(cur, replacestring); - setSelectionRange(cur, replacestring.length()); - - // Go back so that replacement string is also spellchecked - for (string::size_type i = 0; i < replacestring.length() + 1; ++i) - text->cursorLeft(cur); -} - - void eraseSelection(LCursor & cur) { //lyxerr << "LCursor::eraseSelection begin: " << cur << endl; CursorSlice const & i1 = cur.selBegin(); CursorSlice const & i2 = cur.selEnd(); - if (i1.inset().asMathInset()) { + 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 { - MathInset * p = i1.asMathInset(); + InsetMath * p = i1.asInsetMath(); InsetBase::row_type r1, r2; InsetBase::col_type c1, c2; region(i1, i2, r1, r2, c1, c2); @@ -687,7 +711,8 @@ void eraseSelection(LCursor & cur) // We've deleted the whole cell. Only pos 0 is valid. cur.pos() = 0; } - cur.resetAnchor(); + // need a valid cursor. (Lgb) + cur.clearSelection(); } else { lyxerr << "can't erase this selection 1" << endl; } @@ -698,10 +723,8 @@ void eraseSelection(LCursor & cur) void selDel(LCursor & cur) { //lyxerr << "LCursor::selDel" << endl; - if (cur.selection()) { + if (cur.selection()) eraseSelection(cur); - cur.selection() = false; - } } @@ -715,20 +738,29 @@ void selClearOrDel(LCursor & cur) } -string grabSelection(LCursor & cur) +docstring grabSelection(LCursor const & cur) { if (!cur.selection()) - return string(); + 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().asMathInset()) { + if (i1.inset().asInsetMath()) { MathArray::const_iterator it = i1.cell().begin(); return asString(MathArray(it + i1.pos(), it + i2.pos())); } else { - return "unknown selection 1"; + return from_ascii("unknown selection 1"); } } @@ -736,24 +768,37 @@ string grabSelection(LCursor & cur) InsetBase::col_type c1, c2; region(i1, i2, r1, r2, c1, c2); - string data; - if (i1.inset().asMathInset()) { + 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.asMathInset()-> - cell(i1.asMathInset()->index(row, col))); + data += asString(i1.asInsetMath()-> + cell(i1.asInsetMath()->index(row, col))); } } } else { - data = "unknown selection 2"; + 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