X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCutAndPaste.C;h=36c863428ff1f183dce6c9092f39c18c789aea12;hb=c544107e324090c6eafb4c56749da2624b9b1122;hp=18d131816c5b040d6c97b2af41b3083ec044a88f;hpb=17c8764e0a3c5adcb970fd7803a6255cfe4bf01f;p=lyx.git diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index 18d131816c..36c863428f 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -1,124 +1,137 @@ -/* 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 Juergen 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 "errorlist.h" #include "paragraph.h" #include "ParagraphParameters.h" #include "lyxtext.h" #include "lyxcursor.h" -#include "gettext.h" #include "iterators.h" #include "lyxtextclasslist.h" #include "undo_funcs.h" +#include "gettext.h" #include "paragraph_funcs.h" #include "debug.h" +#include "insets/insetinclude.h" +#include "insets/insettabular.h" -#include "insets/inseterror.h" - -#include "support/BoostFormat.h" +#include "support/LAssert.h" +#include "support/lstrings.h" +#include "support/limited_stack.h" using std::endl; using std::pair; +using std::make_pair; +using std::for_each; +using std::vector; + +using namespace lyx::support; using lyx::pos_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) + +typedef limited_stack > CutStack; namespace { -// FIXME: stupid name -ParagraphList paragraphs; -textclass_type textclass = 0; +CutStack cuts(10); } // namespace anon -bool CutAndPaste::cutSelection(Paragraph * startpar, Paragraph ** endpar, - int start, int & end, textclass_type tc, - bool doclear, bool realcut) +std::vector +CutAndPaste::availableSelections(Buffer const & buffer) { - if (!startpar || (start > startpar->size())) - return false; + 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; + } + } - if (realcut) { - copySelection(startpar, *endpar, start, end, tc); + selList.push_back(asciiSel); } - if (!endpar || startpar == *endpar) { - if (startpar->erase(start, end)) { - // Some chars were erased, go to start to be safe - end = start; - } - return true; - } + return selList; +} - bool actually_erased = false; - // 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; - } +PitPosPair CutAndPaste::cutSelection(BufferParams const & params, + ParagraphList & pars, + ParagraphList::iterator startpit, + ParagraphList::iterator endpit, + int startpos, int endpos, + textclass_type tc, bool doclear) +{ + copySelection(startpit, endpit, startpos, endpos, tc); + return eraseSelection(params, pars, startpit, endpit, startpos, + endpos, doclear); +} - // Loop through the deleted pars if any, erasing as needed - Paragraph * pit = startpar->next(); +PitPosPair CutAndPaste::eraseSelection(BufferParams const & params, + ParagraphList & pars, + ParagraphList::iterator startpit, + ParagraphList::iterator endpit, + int startpos, int endpos, bool doclear) +{ + if (startpit == pars.end() || (startpos > startpit->size())) + return PitPosPair(endpit, endpos); - while (true) { - // *endpar can be 0 - if (!pit) - break; + if (endpit == pars.end() || startpit == endpit) { + endpos -= startpit->erase(startpos, endpos); + return PitPosPair(endpit, endpos); + } - Paragraph * next = pit->next(); + // clear end/begin fragments of the first/last par in selection + bool all_erased = true; - // "erase" the contents of the par - if (pit != *endpar) { - actually_erased |= pit->erase(0, pit->size()); + startpit->erase(startpos, startpit->size()); + if (startpit->size() != startpos) + all_erased = false; - // remove the par if it's now empty - if (actually_erased) { - pit->previous()->next(pit->next()); - if (next) { - next->previous(pit->previous()); - } + endpos -= endpit->erase(0, endpos); + if (endpos != 0) + all_erased = false; - delete pit; - } - } + // Loop through the deleted pars if any, erasing as needed - if (pit == *endpar) - break; + 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; } @@ -131,134 +144,109 @@ bool CutAndPaste::cutSelection(Paragraph * startpar, Paragraph ** endpar, } #endif - if (!startpar->next()) - return true; - - Buffer * buffer = current_view->buffer(); + if (boost::next(startpit) == pars.end()) + return PitPosPair(endpit, endpos); if (doclear) { - startpar->next()->stripLeadingSpaces(); + boost::next(startpit)->stripLeadingSpaces(); } - 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); + if (all_erased && + (startpit->hasSameLayout(*boost::next(startpit)) || + boost::next(startpit)->empty())) { + mergeParagraph(params, pars, startpit); // this because endpar gets deleted here! - (*endpar) = startpar; + endpit = startpit; + endpos = startpos; } - return true; + return PitPosPair(endpit, endpos); + } -bool CutAndPaste::copySelection(Paragraph * startpar, Paragraph * endpar, +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) { - if (!startpar || (start > startpar->size())) - return false; + Assert(0 <= start && start <= startpit->size()); + Assert(0 <= end && end <= endpit->size()); + Assert(startpit != endpit || start <= end); - paragraphs.clear(); + ParagraphList paragraphs; - textclass = tc; + // Clone the paragraphs within the selection. + ParagraphList::iterator postend = boost::next(endpit); - if (!endpar || startpar == endpar) { - // only within one paragraph - ParagraphList::iterator buf = - paragraphs.insert(paragraphs.begin(), new Paragraph); + paragraphs.assign(startpit, postend); + for_each(paragraphs.begin(), paragraphs.end(), resetOwnerAndChanges()); - 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 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)); - // 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; } -bool CutAndPaste::pasteSelection(Paragraph ** par, Paragraph ** endpar, - int & pos, textclass_type tc) +pair +CutAndPaste::pasteSelection(Buffer const & buffer, + ParagraphList & pars, + ParagraphList::iterator pit, int pos, + textclass_type tc, + ErrorList & errorlist) { - if (!checkPastePossible()) - return false; + return pasteSelection(buffer, pars, pit, pos, tc, 0, errorlist); +} - if (pos > (*par)->size()) - pos = (*par)->size(); - // many paragraphs +pair +CutAndPaste::pasteSelection(Buffer const & buffer, + ParagraphList & pars, + ParagraphList::iterator pit, int pos, + textclass_type tc, size_t cut_index, + ErrorList & errorlist) +{ + if (!checkPastePossible()) + return make_pair(PitPosPair(pit, pos), pit); - // make a copy of the simple cut_buffer -#if 1 - ParagraphList::iterator it = paragraphs.begin(); + Assert (pos <= pit->size()); - ParagraphList simple_cut_clone; - simple_cut_clone.insert(simple_cut_clone.begin(), - new Paragraph(*it, false)); + // Make a copy of the CaP paragraphs. + ParagraphList simple_cut_clone = cuts[cut_index].first; + textclass_type const textclass = cuts[cut_index].second; - ParagraphList::iterator end = paragraphs.end(); - while (boost::next(it) != end) { - ++it; - simple_cut_clone.insert(simple_cut_clone.end(), - new Paragraph(*it, false)); - } -#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); + // Now remove all out of the pars which is NOT allowed in the + // new environment and set also another font if that is required. - // make sure there is no class difference - SwitchLayoutsBetweenClasses(textclass, tc, &*tmpbuf, - current_view->buffer()->params); + // Make sure there is no class difference. + SwitchLayoutsBetweenClasses(textclass, tc, simple_cut_clone, + errorlist); - Paragraph::depth_type max_depth = (*par)->getMaxDepthAfter(); + ParagraphList::iterator tmpbuf = simple_cut_clone.begin(); + int depth_delta = pit->params().depth() - tmpbuf->params().depth(); - while (tmpbuf != paragraphs.end()) { + Paragraph::depth_type max_depth = pit->getMaxDepthAfter(); + + for (; tmpbuf != simple_cut_clone.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 @@ -266,153 +254,163 @@ bool CutAndPaste::pasteSelection(Paragraph ** par, Paragraph ** endpar, // 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. + + // 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)) + + // Only set this from the 2nd on as the 2nd depends + // for maxDepth still on pit. + if (tmpbuf != simple_cut_clone.begin()) max_depth = tmpbuf->getMaxDepthAfter(); - // set the inset owner of this paragraph - tmpbuf->setInsetOwner((*par)->inInset()); + + // Set the inset owner of this paragraph. + tmpbuf->setInsetOwner(pit->inInset()); for (pos_type i = 0; i < tmpbuf->size(); ++i) { if (tmpbuf->getChar(i) == Paragraph::META_INSET) { - if (!(*par)->insetAllowed(tmpbuf->getInset(i)->lyxCode())) { + if (!pit->insetAllowed(tmpbuf->getInset(i)->lyxCode())) { tmpbuf->erase(i--); } } else { - LyXFont f1 = tmpbuf->getFont(current_view->buffer()->params,i); + LyXFont f1 = tmpbuf->getFont(buffer.params, i, outerFont(pit, pars)); LyXFont f2 = f1; - if (!(*par)->checkInsertChar(f1)) { + if (!pit->checkInsertChar(f1)) { tmpbuf->erase(i--); } else if (f1 != f2) { tmpbuf->setFont(i, f1); } } } - tmpbuf = tmpbuf->next(); } - // now reset it to 0 - paragraphs.begin()->previous(0); + // 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 + ParIterator fpit(simple_cut_clone.begin(), simple_cut_clone); + ParIterator fend(simple_cut_clone.end(), simple_cut_clone); + + 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 InsetOld::INCLUDE_CODE: { + InsetInclude * ii = static_cast(lit->inset); + InsetInclude::Params ip = ii->params(); + ip.masterFilename_ = buffer.fileName(); + ii->set(ip); + break; + } - // make the buf exactly the same layout than - // the cursor paragraph - paragraphs.begin()->makeSameLayout(*par); + case InsetOld::TABULAR_CODE: { + InsetTabular * it = static_cast(lit->inset); + it->buffer(const_cast(&buffer)); + break; + } - // find the end of the buffer - ParagraphList::iterator lastbuffer = paragraphs.begin(); - while (boost::next(lastbuffer) != paragraphs.end()) - ++lastbuffer; + default: + break; // nothing + } + } + } 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); + // 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; } - // 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); + + // Set the end for redoing later. + ParagraphList::iterator endpit = boost::next(boost::next(pit)); + + // Paste it! + + ParagraphList::iterator past_pit = boost::next(pit); + pars.splice(past_pit, simple_cut_clone); + ParagraphList::iterator last_paste = boost::prior(past_pit); + + // If we only inserted one paragraph. + if (boost::next(pit) == last_paste) + last_paste = pit; + + mergeParagraph(buffer.params, pars, pit); + + // Store the new cursor position. + pit = last_paste; + pos = last_paste->size(); + + // Maybe some pasting. +#warning CHECK! Are we comparing last_paste to the wrong list here? (Lgb) + 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); } else - lastbuffer->next()->stripLeadingSpaces(); + boost::next(last_paste)->stripLeadingSpaces(); } - // restore the simple cut buffer - paragraphs = simple_cut_clone; - return true; + return make_pair(PitPosPair(pit, pos), endpit); } int CutAndPaste::nrOfParagraphs() { - return paragraphs.size(); + return cuts.empty() ? 0 : cuts[0].first.size(); } int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1, textclass_type c2, - Paragraph * par, - BufferParams const & /*bparams*/) + ParagraphList & pars, + ErrorList & errorlist) { + Assert(!pars.empty()); + int ret = 0; - if (!par || c1 == c2) + if (c1 == c2) return ret; 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(); + ParIterator end = ParIterator(pars.end(), pars); + for (ParIterator it = ParIterator(pars.begin(), pars); it != end; ++it) { + string const name = it->layout()->name(); bool hasLayout = tclass2.hasLayout(name); if (hasLayout) - par->layout(tclass2[name]); + it->layout(tclass2[name]); else - par->layout(tclass2.defaultLayout()); + it->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(); -#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(); + 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())); } } return ret; @@ -421,5 +419,5 @@ int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1, bool CutAndPaste::checkPastePossible() { - return !paragraphs.empty(); + return !cuts.empty() && !cuts[0].first.empty(); }