X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCutAndPaste.C;h=36c863428ff1f183dce6c9092f39c18c789aea12;hb=c544107e324090c6eafb4c56749da2624b9b1122;hp=3e197f31d32bc41f474680ec5848b4b235a75430;hpb=d40723f0497f891ed62cfbeab2db6204ec699602;p=lyx.git diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index 3e197f31d3..36c863428f 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -1,487 +1,423 @@ -/* 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 -#ifdef __GNUG__ -#pragma implementation -#endif - #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 "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 -Paragraph * buf = 0; -textclass_type textclass = 0; +CutStack cuts(10); -// for now here this should be in another Cut&Paste Class! -// Jürgen, I moved this out of CutAndPaste since it does not operate on any -// member of the CutAndPaste class and in addition it was private. -// Perhaps it even should take a parameter? (Lgb) -void DeleteBuffer() -{ - if (!buf) - return; +} // namespace anon - Paragraph * tmppar; - while (buf) { - tmppar = buf; - buf = buf->next(); - delete tmppar; +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); } - buf = 0; + + return selList; } -} // namespace anon +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); +} -bool CutAndPaste::cutSelection(Paragraph * startpar, Paragraph ** endpar, - int start, int & end, char tc, bool doclear, - bool realcut) + +PitPosPair CutAndPaste::eraseSelection(BufferParams const & params, + ParagraphList & pars, + ParagraphList::iterator startpit, + ParagraphList::iterator endpit, + int startpos, int endpos, bool doclear) { - if (!startpar || (start > startpar->size())) - return false; + if (startpit == pars.end() || (startpos > startpit->size())) + return PitPosPair(endpit, endpos); - if (realcut) { - copySelection(startpar, *endpar, start, end, tc); + if (endpit == pars.end() || startpit == endpit) { + endpos -= startpit->erase(startpos, endpos); + return PitPosPair(endpit, endpos); } - if (!endpar || startpar == *endpar) { - if (startpar->erase(start, end)) { - // Some chars were erased, go to start to be safe - end = start; - } - return true; - } - - 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; - } - + 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 - - Paragraph * pit = startpar->next(); - - while (1) { - // *endpar can be 0 - if (!pit) - break; - - Paragraph * next = pit->next(); - - // "erase" the contents of the par - if (pit != *endpar) { - actually_erased |= pit->erase(0, pit->size()); + 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 - if (actually_erased) { - pit->previous()->next(pit->next()); - if (next) { - next->previous(pit->previous()); - } - - delete pit; - } - } - - if (pit == *endpar) - break; - + pars.erase(pit); + } else + all_erased = false; pit = next; } -#if 0 // FIXME: why for cut but not copy ? +#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 +#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()) { - mergeParagraph(buffer->params, 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, - int start, int end, char tc) -{ - if (!startpar || (start > startpar->size())) - return false; - - DeleteBuffer(); - - textclass = tc; - - if (!endpar || startpar == endpar) { - // only within one paragraph - buf = new Paragraph; - 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; - buf = new Paragraph(*tmppar, false); - Paragraph * tmppar2 = buf; - tmppar2->cleanChanges(); - - while (tmppar != endpar - && tmppar->next()) { - tmppar = tmppar->next(); - tmppar2->next(new Paragraph(*tmppar, false)); - tmppar2->next()->previous(tmppar2); - tmppar2 = tmppar2->next(); - // reset change info - tmppar2->cleanChanges(); - } - tmppar2->next(0); - // the buf paragraph is too big - pos_type tmpi2 = start; - for (; tmpi2; --tmpi2) - buf->erase(0); +namespace { - // now tmppar 2 is too big, delete all after end - tmpi2 = end; - while (tmppar2->size() > tmpi2) { - tmppar2->erase(tmppar2->size() - 1); - } - // this paragraph's are of noone's owner! - tmppar = buf; - while (tmppar) { - tmppar->setInsetOwner(0); - tmppar = tmppar->next(); - } +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) +{ + Assert(0 <= start && start <= startpit->size()); + Assert(0 <= end && end <= endpit->size()); + Assert(startpit != endpit || start <= end); + + 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; } -bool CutAndPaste::pasteSelection(Paragraph ** par, Paragraph ** endpar, - int & pos, char tc) +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(*par)) - return false; - - if (pos > (*par)->size()) - pos = (*par)->size(); - -#if 0 - // Paragraph * tmpbuf; - Paragraph * tmppar = *par; - int tmppos = pos; - - // There are two cases: cutbuffer only one paragraph or many - if (!buf->next()) { - // only within a paragraph - Paragraph * tmpbuf = new Paragraph(*buf, false); - - // Some provisions should be done here for checking - // if we are inserting at the beginning of a - // paragraph. If there are a space at the beginning - // of the text to insert and we are inserting at - // the beginning of the paragraph the space should - // be removed. - while (buf->size()) { - // This is an attempt to fix the - // "never insert a space at the - // beginning of a paragraph" problem. - if (!tmppos && buf->isLineSeparator(0)) { - buf->erase(0); + if (!checkPastePossible()) + return make_pair(PitPosPair(pit, pos), pit); + + Assert (pos <= 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; + + // 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, simple_cut_clone, + errorlist); + + ParagraphList::iterator tmpbuf = simple_cut_clone.begin(); + int depth_delta = pit->params().depth() - tmpbuf->params().depth(); + + 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 + // 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 != simple_cut_clone.begin()) + max_depth = tmpbuf->getMaxDepthAfter(); + + // 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 (!pit->insetAllowed(tmpbuf->getInset(i)->lyxCode())) { + tmpbuf->erase(i--); + } } else { - buf->cutIntoMinibuffer(current_view->buffer()->params, 0); - buf->erase(0); - if (tmppar->insertFromMinibuffer(tmppos)) - ++tmppos; + 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); + } } } - delete buf; - buf = tmpbuf; - *endpar = tmppar->next(); - pos = tmppos; - } else -#endif - { - // many paragraphs - - // make a copy of the simple cut_buffer - Paragraph * tmpbuf = buf; - Paragraph * simple_cut_clone = new Paragraph(*tmpbuf, false); - Paragraph * tmpbuf2 = simple_cut_clone; - - while (tmpbuf->next()) { - tmpbuf = tmpbuf->next(); - tmpbuf2->next(new Paragraph(*tmpbuf, false)); - tmpbuf2->next()->previous(tmpbuf2); - tmpbuf2 = tmpbuf2->next(); - } + } - // now remove all out of the buffer which is NOT allowed in the - // new environment and set also another font if that is required - tmpbuf = buf; - 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); - - Paragraph::depth_type max_depth = (*par)->getMaxDepthAfter(); - - while(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 ((static_cast(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); - } - } + // 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; + } + + case InsetOld::TABULAR_CODE: { + InsetTabular * it = static_cast(lit->inset); + it->buffer(const_cast(&buffer)); + break; + } + + default: + break; // nothing } - tmpbuf = tmpbuf->next(); - } - // now reset it to 0 - buf->previous(0); - - // make the buf exactly the same layout than - // the cursor paragraph - buf->makeSameLayout(*par); - - // find the end of the buffer - Paragraph * lastbuffer = buf; - while (lastbuffer->next()) - lastbuffer = lastbuffer->next(); - - 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, *par, 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(buf); - buf->previous(*par); - - if ((*par)->next() == lastbuffer) - lastbuffer = *par; - - mergeParagraph(current_view->buffer()->params, *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, lastbuffer); - } else if (!lastbuffer->next()->size()) { - lastbuffer->next()->makeSameLayout(lastbuffer); - mergeParagraph(current_view->buffer()->params, lastbuffer); - } else if (!lastbuffer->size()) { - lastbuffer->makeSameLayout(lastbuffer->next()); - mergeParagraph(current_view->buffer()->params, lastbuffer); - } else - lastbuffer->next()->stripLeadingSpaces(); } - // restore the simple cut buffer - buf = simple_cut_clone; } - return true; + bool paste_the_end = false; + + // 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. + 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 + boost::next(last_paste)->stripLeadingSpaces(); + } + + return make_pair(PitPosPair(pit, pos), endpit); } int CutAndPaste::nrOfParagraphs() { - if (!buf) - return 0; - - int n = 1; - Paragraph * tmppar = buf; - while (tmppar->next()) { - ++n; - tmppar = tmppar->next(); - } - return n; + 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(current_view, par, 0); - txt->insertInset(current_view, new_inset); - txt->fullRebreak(current_view); - txt->setCursorIntern(current_view, 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; } -bool CutAndPaste::checkPastePossible(Paragraph *) +bool CutAndPaste::checkPastePossible() { - if (!buf) return false; - - return true; + return !cuts.empty() && !cuts[0].first.empty(); }