X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCutAndPaste.C;h=373ee6c01b80f31833daacc0e5112e708e9c2466;hb=e7fc677261bd14fdf159e594fcf422e985c72664;hp=18d131816c5b040d6c97b2af41b3083ec044a88f;hpb=17c8764e0a3c5adcb970fd7803a6255cfe4bf01f;p=lyx.git diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index 18d131816c..373ee6c01b 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -1,425 +1,802 @@ -/* This file is part of - * ====================================================== +/* + * \file CutAndPaste.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. * - * LyX, The Document Processor + * \author Jürgen Vigna + * \author Lars Gullik Bjønnes + * \author Alfredo Braunstein * - * Copyright 1995-2001 The LyX Team. - * - * ====================================================== */ + * Full author contact details are available in file CREDITS. + */ #include #include "CutAndPaste.h" -#include "BufferView.h" + #include "buffer.h" -#include "paragraph.h" -#include "ParagraphParameters.h" -#include "lyxtext.h" -#include "lyxcursor.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 "iterators.h" +#include "insetiterator.h" +#include "lfuns.h" +#include "lyxrc.h" +#include "lyxtext.h" #include "lyxtextclasslist.h" -#include "undo_funcs.h" +#include "paragraph.h" #include "paragraph_funcs.h" -#include "debug.h" +#include "ParagraphParameters.h" +#include "ParagraphList_fwd.h" +#include "pariterator.h" +#include "undo.h" -#include "insets/inseterror.h" +#include "insets/insetcharstyle.h" +#include "insets/insettabular.h" -#include "support/BoostFormat.h" +#include "mathed/math_data.h" +#include "mathed/math_inset.h" +#include "mathed/math_support.h" + +#include "support/lstrings.h" + +#include -using std::endl; -using std::pair; using lyx::pos_type; +using lyx::pit_type; using lyx::textclass_type; -extern BufferView * current_view; - -// Jürgen, note that this means that you cannot currently have a list -// of selections cut/copied. So IMHO later we should have a -// list/vector/deque that we could store -// struct selection_item { -// Paragraph * buf; -// LyXTextClassList::size_type textclass; -// }; -// in and some method of choosing beween them (based on the first few chars -// in the selection probably.) This would be a nice feature and quite -// easy to implement. (Lgb) -// -// Sure but I just cleaned up this code for now with the same functionality -// as before. I also want to add a XClipboard function so that we can copy -// text from LyX to some other X-application in the form of ASCII or in the -// form of LaTeX (or Docbook depending on the document-class!). Think how nice -// it could be to select a math-inset do a "Copy to X-Clipboard as LaTeX" and -// then do a middle mouse button click in the application you want and have -// the whole formula there in LaTeX-Code. (Jug) +using lyx::support::bformat; + +using std::endl; +using std::for_each; +using std::make_pair; +using std::pair; +using std::vector; +using std::string; + namespace { -// FIXME: stupid name -ParagraphList paragraphs; -textclass_type textclass = 0; +typedef std::pair PitPosPair; -} // namespace anon +typedef limited_stack > CutStack; +CutStack theCuts(10); -bool CutAndPaste::cutSelection(Paragraph * startpar, Paragraph ** endpar, - int start, int & end, textclass_type tc, - bool doclear, bool realcut) -{ - if (!startpar || (start > startpar->size())) - return false; +// 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_; - if (realcut) { - copySelection(startpar, *endpar, start, end, tc); +class resetOwnerAndChanges : public std::unary_function { +public: + void operator()(Paragraph & p) const { + p.cleanChanges(); + p.setInsetOwner(0); } +}; + - if (!endpar || startpar == *endpar) { - if (startpar->erase(start, end)) { - // Some chars were erased, go to start to be safe - end = start; +void region(CursorSlice const & i1, CursorSlice const & i2, + InsetBase::row_type & r1, InsetBase::row_type & r2, + InsetBase::col_type & c1, InsetBase::col_type & c2) +{ + 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); +} + + +bool checkPastePossible(int index) +{ + return size_t(index) < theCuts.size() && !theCuts[index].first.empty(); +} + + +pair +pasteSelectionHelper(Buffer const & buffer, + ParagraphList & pars, pit_type pit, int pos, + ParagraphList const & parlist, textclass_type textclass, + ErrorList & errorlist) +{ + 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)) { + insertion[i].erase(j); + breakParagraphConservative( + buffer.params(), + insertion, i, j); + } + } } - return true; } - bool actually_erased = false; + // Make sure there is no class difference. + lyx::cap::SwitchBetweenClasses(textclass, tc, insertion, errorlist); - // clear end/begin fragments of the first/last par in selection - actually_erased |= (startpar)->erase(start, startpar->size()); - if ((*endpar)->erase(0, end)) { - actually_erased = true; - end = 0; - } + ParagraphList::iterator tmpbuf = insertion.begin(); + int depth_delta = pars[pit].params().depth() - tmpbuf->params().depth(); - // Loop through the deleted pars if any, erasing as needed + Paragraph::depth_type max_depth = pars[pit].getMaxDepthAfter(); - Paragraph * pit = startpar->next(); + 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; - while (true) { - // *endpar can be 0 - if (!pit) - break; + // 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); - Paragraph * next = pit->next(); + // Only set this from the 2nd on as the 2nd depends + // for maxDepth still on pit. + if (tmpbuf != insertion.begin()) + max_depth = tmpbuf->getMaxDepthAfter(); - // "erase" the contents of the par - if (pit != *endpar) { - actually_erased |= pit->erase(0, pit->size()); + // 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())) + tmpbuf->erase(i--); + } + } - // remove the par if it's now empty - if (actually_erased) { - pit->previous()->next(pit->next()); - if (next) { - next->previous(pit->previous()); - } + bool const empty = pars[pit].empty(); + if (!empty) { + // Make the buf exactly the same layout as the cursor + // paragraph. + insertion.begin()->makeSameLayout(pars[pit]); + } + + // Prepare the paragraphs and insets for insertion. + // A couple of insets store buffer references so need updating. + InsetText in; + std::swap(in.paragraphs(), insertion); - delete pit; + 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(); + + 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); - if (pit == *endpar) - break; + // Split the paragraph for inserting the buf if necessary. + if (!empty) + breakParagraphConservative(buffer.params(), pars, pit, pos); - pit = next; + // 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); } -#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()); + 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; + } } -#endif - if (!startpar->next()) - return true; + return make_pair(PitPosPair(pit, pos), last_paste + 1); +} - Buffer * buffer = current_view->buffer(); - if (doclear) { - startpar->next()->stripLeadingSpaces(); +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); + + // Start and end is inside same paragraph + if (endpit == pit_type(pars.size()) || + startpit == endpit) { + endpos -= pars[startpit].erase(startpos, endpos); + return PitPosPair(endpit, endpos); } - if (!actually_erased) - return true; - - // paste the paragraphs again, if possible - if (startpar->hasSameLayout(startpar->next()) || - startpar->next()->empty()) { -#warning This is suspect. (Lgb) - // When doing this merge we must know if the par really - // belongs to an inset, and if it does then we have to use - // the insets paragraphs, and not the buffers. (Lgb) - mergeParagraph(buffer->params, buffer->paragraphs, startpar); - // this because endpar gets deleted here! - (*endpar) = startpar; + // 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;) { + bool const merge = !params.tracking_changes || + pars[pit].lookupChange(pars[pit].size()) == + Change::INSERTED; + pos_type const left = ( pit == startpit ? startpos : 0 ); + pos_type const right = ( pit == endpit ? endpos : + pars[pit].size() + 1 ); + // Logical erase only: + pars[pit].erase(left, right); + // Separate handling of para break: + if (merge && pit != 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 + ++pit; } - return true; + // Ensure legal cursor pos: + endpit = startpit; + endpos = startpos; + return PitPosPair(endpit, endpos); } -bool CutAndPaste::copySelection(Paragraph * startpar, Paragraph * endpar, - int start, int end, textclass_type tc) +void copySelectionHelper(ParagraphList & pars, + pit_type startpit, pit_type endpit, + int start, int end, textclass_type tc) { - if (!startpar || (start > startpar->size())) - return false; + BOOST_ASSERT(0 <= start && start <= pars[startpit].size()); + BOOST_ASSERT(0 <= end && end <= pars[endpit].size()); + BOOST_ASSERT(startpit != endpit || start <= end); - paragraphs.clear(); + // Clone the paragraphs within the selection. + ParagraphList paragraphs(boost::next(pars.begin(), startpit), + boost::next(pars.begin(), endpit + 1)); - textclass = tc; + for_each(paragraphs.begin(), paragraphs.end(), resetOwnerAndChanges()); - if (!endpar || startpar == endpar) { - // only within one paragraph - ParagraphList::iterator buf = - paragraphs.insert(paragraphs.begin(), new Paragraph); + // Cut out the end of the last paragraph. + Paragraph & back = paragraphs.back(); + back.erase(end, back.size()); - buf->layout(startpar->layout()); - pos_type i = start; - if (end > startpar->size()) - end = startpar->size(); - for (; i < end; ++i) { - startpar->copyIntoMinibuffer(*current_view->buffer(), i); - buf->insertFromMinibuffer(buf->size()); - } - } else { - // copy more than one paragraph - // clone the paragraphs within the selection - Paragraph * tmppar = startpar; - - while (tmppar != endpar) { - Paragraph * newpar = new Paragraph(*tmppar, false); - // reset change info - newpar->cleanChanges(); - newpar->setInsetOwner(0); - - paragraphs.push_back(newpar); - tmppar = tmppar->next(); - } + // Cut out the begin of the first paragraph + Paragraph & front = paragraphs.front(); + front.erase(0, start); - // The first paragraph is too big. - Paragraph & front = paragraphs.front(); - pos_type tmpi2 = start; - for (; tmpi2; --tmpi2) - front.erase(0); - - // Now last paragraph is too big, delete all after end. - Paragraph & back = paragraphs.back(); - tmpi2 = end; - while (back.size() > tmpi2) { - back.erase(back.size() - 1); - } - } - return true; + theCuts.push(make_pair(paragraphs, tc)); } +} // namespace anon + + + + +namespace lyx { +namespace cap { -bool CutAndPaste::pasteSelection(Paragraph ** par, Paragraph ** endpar, - int & pos, textclass_type tc) +string grabAndEraseSelection(LCursor & cur) { - if (!checkPastePossible()) - return false; + if (!cur.selection()) + return string(); + string res = grabSelection(cur); + eraseSelection(cur); + return res; +} + + +void SwitchBetweenClasses(textclass_type c1, textclass_type c2, + ParagraphList & pars, ErrorList & errorlist) +{ + BOOST_ASSERT(!pars.empty()); + if (c1 == c2) + return; + + LyXTextClass const & tclass1 = textclasslist[c1]; + LyXTextClass const & tclass2 = textclasslist[c2]; - if (pos > (*par)->size()) - pos = (*par)->size(); + InsetText in; + std::swap(in.paragraphs(), pars); - // many paragraphs + // 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); - // make a copy of the simple cut_buffer -#if 1 - ParagraphList::iterator it = paragraphs.begin(); + if (hasLayout) + it->layout(tclass2[name]); + else + it->layout(tclass2.defaultLayout()); - ParagraphList simple_cut_clone; - simple_cut_clone.insert(simple_cut_clone.begin(), - new Paragraph(*it, false)); + 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()); + // To warn the user that something had to be done. + errorlist.push_back(ErrorItem(_("Changed Layout"), s, + it->id(), 0, + it->size())); + } + } - ParagraphList::iterator end = paragraphs.end(); - while (boost::next(it) != end) { - ++it; - simple_cut_clone.insert(simple_cut_clone.end(), - new Paragraph(*it, false)); + // 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(); + string 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()); + // 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); + } + } } -#else - // Later we want it done like this: - ParagraphList simple_cut_clone(paragraphs.begin(), - paragraphs.end()); -#endif - // now remove all out of the buffer which is NOT allowed in the - // new environment and set also another font if that is required - ParagraphList::iterator tmpbuf = paragraphs.begin(); - int depth_delta = (*par)->params().depth() - tmpbuf->params().depth(); - // Temporary set *par as previous of tmpbuf as we might have - // to realize the font. - tmpbuf->previous(*par); - // make sure there is no class difference - SwitchLayoutsBetweenClasses(textclass, tc, &*tmpbuf, - current_view->buffer()->params); + std::swap(in.paragraphs(), pars); +} - Paragraph::depth_type max_depth = (*par)->getMaxDepthAfter(); - while (tmpbuf != paragraphs.end()) { - // 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 *par - if (tmpbuf->previous() != (*par)) - max_depth = tmpbuf->getMaxDepthAfter(); - // set the inset owner of this paragraph - tmpbuf->setInsetOwner((*par)->inInset()); - for (pos_type i = 0; i < tmpbuf->size(); ++i) { - if (tmpbuf->getChar(i) == Paragraph::META_INSET) { - if (!(*par)->insetAllowed(tmpbuf->getInset(i)->lyxCode())) { - tmpbuf->erase(i--); - } - } else { - LyXFont f1 = tmpbuf->getFont(current_view->buffer()->params,i); - LyXFont f2 = f1; - if (!(*par)->checkInsertChar(f1)) { - tmpbuf->erase(i--); - } else if (f1 != f2) { - tmpbuf->setFont(i, f1); - } +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; + 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; } } - tmpbuf = tmpbuf->next(); + + selList.push_back(asciiSel); } - // now reset it to 0 - paragraphs.begin()->previous(0); + return selList; +} - // make the buf exactly the same layout than - // the cursor paragraph - paragraphs.begin()->makeSameLayout(*par); - // find the end of the buffer - ParagraphList::iterator lastbuffer = paragraphs.begin(); - while (boost::next(lastbuffer) != paragraphs.end()) - ++lastbuffer; +lyx::size_type numberOfSelections() +{ + return theCuts.size(); +} - bool paste_the_end = false; - // open the paragraph for inserting the buf - // if necessary - if (((*par)->size() > pos) || !(*par)->next()) { - breakParagraphConservative( - current_view->buffer()->params, current_view->buffer()->paragraphs, *par, pos); - paste_the_end = true; +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 stuffClipboard. (Lgb) +// cur.bv().stuffClipboard(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(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(); + updateCounters(cur.buffer()); + + // tell tabular that a recent copy happened + dirtyTabularStack(false); } - // set the end for redoing later - *endpar = (*par)->next()->next(); - - // paste it! - lastbuffer->next((*par)->next()); - (*par)->next()->previous(&*lastbuffer); - - (*par)->next(&*paragraphs.begin()); - paragraphs.begin()->previous(*par); - - if ((*par)->next() == lastbuffer) - lastbuffer = *par; - - mergeParagraph(current_view->buffer()->params, - current_view->buffer()->paragraphs, *par); - // store the new cursor position - *par = &*lastbuffer; - pos = lastbuffer->size(); - // maybe some pasting - if (lastbuffer->next() && paste_the_end) { - if (lastbuffer->next()->hasSameLayout(&*lastbuffer)) { - mergeParagraph(current_view->buffer()->params, - current_view->buffer()->paragraphs, lastbuffer); - } else if (!lastbuffer->next()->size()) { - lastbuffer->next()->makeSameLayout(&*lastbuffer); - mergeParagraph(current_view->buffer()->params, current_view->buffer()->paragraphs, lastbuffer); - } else if (!lastbuffer->size()) { - lastbuffer->makeSameLayout(lastbuffer->next()); - mergeParagraph(current_view->buffer()->params, - current_view->buffer()->paragraphs, lastbuffer); - } else - lastbuffer->next()->stripLeadingSpaces(); + + 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 + cur.bv().stuffClipboard(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(pars, par, cur.selEnd().pit(), + pos, cur.selEnd().pos(), cur.buffer().params().textclass); } - // restore the simple cut buffer - paragraphs = simple_cut_clone; - return true; + if (cur.inMathed()) { + lyxerr << "copySelection in mathed" << endl; + ParagraphList pars; + pars.push_back(Paragraph()); + 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()); + theCuts.push(make_pair(pars, bp.textclass)); + } + // tell tabular that a recent copy happened + dirtyTabularStack(false); } -int CutAndPaste::nrOfParagraphs() +std::string getSelection(Buffer const & buf, size_t sel_index) { - return paragraphs.size(); + return sel_index < theCuts.size() + ? theCuts[sel_index].first.back().asString(buf, false) + : string(); } -int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1, - textclass_type c2, - Paragraph * par, - BufferParams const & /*bparams*/) +void pasteParagraphList(LCursor & cur, ParagraphList const & parlist, + textclass_type textclass) { - int ret = 0; - if (!par || c1 == c2) - return ret; + if (cur.inTexted()) { + LyXText * text = cur.text(); + BOOST_ASSERT(text); + + recordUndo(cur); + + pit_type endpit; + PitPosPair ppp; + ErrorList el; + + boost::tie(ppp, endpit) = + pasteSelectionHelper(cur.buffer(), + text->paragraphs(), + cur.pit(), cur.pos(), + parlist, textclass, + el); + bufferErrors(cur.buffer(), el); + updateCounters(cur.buffer()); + cur.clearSelection(); + text->setCursor(cur, ppp.first, ppp.second); + } - LyXTextClass const & tclass1 = textclasslist[c1]; - LyXTextClass const & tclass2 = textclasslist[c2]; - ParIterator end = ParIterator(); - for (ParIterator it = ParIterator(par); it != end; ++it) { - par = *it; - string const name = par->layout()->name(); - bool hasLayout = tclass2.hasLayout(name); + // mathed is handled in MathNestInset/MathGridInset + BOOST_ASSERT(!cur.inMathed()); +} - if (hasLayout) - par->layout(tclass2[name]); - else - par->layout(tclass2.defaultLayout()); - if (!hasLayout && name != tclass1.defaultLayoutName()) { - ++ret; -#if USE_BOOST_FORMAT - boost::format fmt(_("Layout had to be changed from\n" - "%1$s to %2$s\n" - "because of class conversion from\n" - "%3$s to %4$s")); - fmt % name - % par->layout()->name() - % tclass1.name() - % tclass2.name(); - - string const s = fmt.str(); -#else - string const s = _("Layout had to be changed from\n") - + name + _(" to ") - + par->layout()->name() - + _("\nbecause of class conversion from\n") - + tclass1.name() + _(" to ") - + tclass2.name(); +void pasteSelection(LCursor & cur, size_t sel_index) +{ + // this does not make sense, if there is nothing to paste + if (!checkPastePossible(sel_index)) + return; + + pasteParagraphList(cur, theCuts[sel_index].first, + theCuts[sel_index].second); + cur.bv().showErrorList(_("Paste")); + cur.setSelection(); +} + + +void setSelectionRange(LCursor & cur, pos_type length) +{ + LyXText * text = cur.text(); + BOOST_ASSERT(text); + if (!length) + return; + cur.resetAnchor(); + while (length--) + text->cursorRight(cur); + cur.setSelection(); +} + + +// simple replacing. The font of the first selected character is used +void replaceSelectionWithString(LCursor & cur, string const & str) +{ + LyXText * text = cur.text(); + BOOST_ASSERT(text); + recordUndo(cur); + + // 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()); + + // Insert the new string + string::const_iterator cit = str.begin(); + string::const_iterator end = str.end(); + for (; cit != end; ++cit, ++pos) + par.insertChar(pos, (*cit), font); + + // Cut the selection + cutSelection(cur, true, false); +} + + +void replaceSelection(LCursor & cur) +{ + if (cur.selection()) + cutSelection(cur, true, false); +} + + +// 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()) { + 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(); + 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; +} + + +void selDel(LCursor & cur) +{ + //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; +} + + +string grabSelection(LCursor const & cur) +{ + if (!cur.selection()) + return string(); + + // 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 - freezeUndo(); - InsetError * new_inset = new InsetError(s); - LyXText * txt = current_view->getLyXText(); - LyXCursor cur = txt->cursor; - txt->setCursorIntern(par, 0); - txt->insertInset(new_inset); - txt->fullRebreak(); - txt->setCursorIntern(cur.par(), cur.pos()); - unFreezeUndo(); + + CursorSlice i1 = cur.selBegin(); + CursorSlice i2 = cur.selEnd(); + + if (i1.idx() == i2.idx()) { + if (i1.inset().asMathInset()) { + MathArray::const_iterator it = i1.cell().begin(); + return asString(MathArray(it + i1.pos(), it + i2.pos())); + } else { + return "unknown selection 1"; } } - return ret; + + InsetBase::row_type r1, r2; + InsetBase::col_type c1, c2; + region(i1, i2, r1, r2, c1, c2); + + string data; + if (i1.inset().asMathInset()) { + 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))); + } + } + } else { + data = "unknown selection 2"; + } + return data; } -bool CutAndPaste::checkPastePossible() +void dirtyTabularStack(bool b) { - return !paragraphs.empty(); + dirty_tabular_stack_ = b; } + + +bool tabularStackDirty() +{ + return dirty_tabular_stack_; +} + + +} // namespace cap +} // namespace lyx