3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Juergen Vigna
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
10 * Full author contact details are available in file CREDITS.
15 #include "CutAndPaste.h"
17 #include "ParagraphParameters.h"
18 #include "iterators.h"
19 #include "lyxtextclasslist.h"
21 #include "paragraph_funcs.h"
22 #include "insets/insetinclude.h"
23 #include "insets/insettabular.h"
25 #include "support/LAssert.h"
26 #include "support/lstrings.h"
34 using namespace lyx::support;
36 using lyx::textclass_type;
39 typedef limited_stack<pair<ParagraphList, textclass_type> > CutStack;
49 CutAndPaste::availableSelections(Buffer const & buffer)
51 vector<string> selList;
53 CutStack::const_iterator cit = cuts.begin();
54 CutStack::const_iterator end = cuts.end();
55 for (; cit != end; ++cit) {
56 // we do not use cit-> here because gcc 2.9x does not
58 ParagraphList const & pars = (*cit).first;
60 ParagraphList::const_iterator pit = pars.begin();
61 ParagraphList::const_iterator pend = pars.end();
62 for (; pit != pend; ++pit) {
63 asciiSel += pit->asString(buffer, false);
64 if (asciiSel.size() > 25) {
65 asciiSel.replace(22, string::npos, "...");
70 selList.push_back(asciiSel);
77 PitPosPair CutAndPaste::cutSelection(BufferParams const & params,
79 ParagraphList::iterator startpit,
80 ParagraphList::iterator endpit,
81 int startpos, int endpos,
82 textclass_type tc, bool doclear)
84 copySelection(startpit, endpit, startpos, endpos, tc);
85 return eraseSelection(params, pars, startpit, endpit, startpos,
90 PitPosPair CutAndPaste::eraseSelection(BufferParams const & params,
92 ParagraphList::iterator startpit,
93 ParagraphList::iterator endpit,
94 int startpos, int endpos, bool doclear)
96 if (startpit == pars.end() || (startpos > startpit->size()))
97 return PitPosPair(endpit, endpos);
99 if (endpit == pars.end() || startpit == endpit) {
100 endpos -= startpit->erase(startpos, endpos);
101 return PitPosPair(endpit, endpos);
104 // clear end/begin fragments of the first/last par in selection
105 bool all_erased = true;
107 startpit->erase(startpos, startpit->size());
108 if (startpit->size() != startpos)
111 endpos -= endpit->erase(0, endpos);
115 // Loop through the deleted pars if any, erasing as needed
117 ParagraphList::iterator pit = boost::next(startpit);
119 while (pit != endpit && pit != pars.end()) {
120 ParagraphList::iterator const next = boost::next(pit);
121 // "erase" the contents of the par
122 pit->erase(0, pit->size());
124 // remove the par if it's now empty
131 #if 0 // FIXME: why for cut but not copy ?
132 // the cut selection should begin with standard layout
134 buf->params().clear();
136 buf->layout(textclasslist[buffer->params.textclass].defaultLayoutName());
140 if (boost::next(startpit) == pars.end())
141 return PitPosPair(endpit, endpos);
144 boost::next(startpit)->stripLeadingSpaces();
147 // paste the paragraphs again, if possible
149 (startpit->hasSameLayout(*boost::next(startpit)) ||
150 boost::next(startpit)->empty())) {
151 mergeParagraph(params, pars, startpit);
152 // this because endpar gets deleted here!
157 return PitPosPair(endpit, endpos);
164 struct resetOwnerAndChanges {
165 void operator()(Paragraph & p) {
173 bool CutAndPaste::copySelection(ParagraphList::iterator startpit,
174 ParagraphList::iterator endpit,
175 int start, int end, textclass_type tc)
177 Assert(0 <= start && start <= startpit->size());
178 Assert(0 <= end && end <= endpit->size());
179 Assert(startpit != endpit || start <= end);
181 ParagraphList paragraphs;
183 // Clone the paragraphs within the selection.
184 ParagraphList::iterator postend = boost::next(endpit);
186 paragraphs.assign(startpit, postend);
187 for_each(paragraphs.begin(), paragraphs.end(), resetOwnerAndChanges());
189 // Cut out the end of the last paragraph.
190 Paragraph & back = paragraphs.back();
191 back.erase(end, back.size());
193 // Cut out the begin of the first paragraph
194 Paragraph & front = paragraphs.front();
195 front.erase(0, start);
197 cuts.push(make_pair(paragraphs, tc));
203 pair<PitPosPair, ParagraphList::iterator>
204 CutAndPaste::pasteSelection(Buffer const & buffer,
205 ParagraphList & pars,
206 ParagraphList::iterator pit, int pos,
208 ErrorList & errorlist)
210 return pasteSelection(buffer, pars, pit, pos, tc, 0, errorlist);
214 pair<PitPosPair, ParagraphList::iterator>
215 CutAndPaste::pasteSelection(Buffer const & buffer,
216 ParagraphList & pars,
217 ParagraphList::iterator pit, int pos,
218 textclass_type tc, size_t cut_index,
219 ErrorList & errorlist)
221 if (!checkPastePossible())
222 return make_pair(PitPosPair(pit, pos), pit);
224 Assert (pos <= pit->size());
226 // Make a copy of the CaP paragraphs.
227 ParagraphList simple_cut_clone = cuts[cut_index].first;
228 textclass_type const textclass = cuts[cut_index].second;
230 // Now remove all out of the pars which is NOT allowed in the
231 // new environment and set also another font if that is required.
233 // Make sure there is no class difference.
234 SwitchLayoutsBetweenClasses(textclass, tc, simple_cut_clone,
237 ParagraphList::iterator tmpbuf = simple_cut_clone.begin();
238 int depth_delta = pit->params().depth() - tmpbuf->params().depth();
240 Paragraph::depth_type max_depth = pit->getMaxDepthAfter();
242 for (; tmpbuf != simple_cut_clone.end(); ++tmpbuf) {
243 // If we have a negative jump so that the depth would
244 // go below 0 depth then we have to redo the delta to
245 // this new max depth level so that subsequent
246 // paragraphs are aligned correctly to this paragraph
248 if ((int(tmpbuf->params().depth()) + depth_delta) < 0)
251 // Set the right depth so that we are not too deep or shallow.
252 tmpbuf->params().depth(tmpbuf->params().depth() + depth_delta);
253 if (tmpbuf->params().depth() > max_depth)
254 tmpbuf->params().depth(max_depth);
256 // Only set this from the 2nd on as the 2nd depends
257 // for maxDepth still on pit.
258 if (tmpbuf != simple_cut_clone.begin())
259 max_depth = tmpbuf->getMaxDepthAfter();
261 // Set the inset owner of this paragraph.
262 tmpbuf->setInsetOwner(pit->inInset());
263 for (pos_type i = 0; i < tmpbuf->size(); ++i) {
264 if (tmpbuf->getChar(i) == Paragraph::META_INSET) {
265 if (!pit->insetAllowed(tmpbuf->getInset(i)->lyxCode())) {
269 LyXFont f1 = tmpbuf->getFont(buffer.params, i, outerFont(pit, pars));
271 if (!pit->checkInsertChar(f1)) {
273 } else if (f1 != f2) {
274 tmpbuf->setFont(i, f1);
280 // Make the buf exactly the same layout than
281 // the cursor paragraph.
282 simple_cut_clone.begin()->makeSameLayout(*pit);
284 // Prepare the paragraphs and insets for insertion
285 // A couple of insets store buffer references so need
287 ParIterator fpit(simple_cut_clone.begin(), simple_cut_clone);
288 ParIterator fend(simple_cut_clone.end(), simple_cut_clone);
290 for (; fpit != fend; ++fpit) {
291 InsetList::iterator lit = fpit->insetlist.begin();
292 InsetList::iterator eit = fpit->insetlist.end();
294 for (; lit != eit; ++lit) {
295 switch (lit->inset->lyxCode()) {
296 case InsetOld::INCLUDE_CODE: {
297 InsetInclude * ii = static_cast<InsetInclude*>(lit->inset);
298 InsetInclude::Params ip = ii->params();
299 ip.masterFilename_ = buffer.fileName();
304 case InsetOld::TABULAR_CODE: {
305 InsetTabular * it = static_cast<InsetTabular*>(lit->inset);
306 it->buffer(const_cast<Buffer*>(&buffer));
316 bool paste_the_end = false;
318 // Open the paragraph for inserting the buf
320 if (pit->size() > pos || boost::next(pit) == pars.end()) {
321 breakParagraphConservative(buffer.params,
323 paste_the_end = true;
326 // Set the end for redoing later.
327 ParagraphList::iterator endpit = boost::next(boost::next(pit));
331 ParagraphList::iterator past_pit = boost::next(pit);
332 pars.splice(past_pit, simple_cut_clone);
333 ParagraphList::iterator last_paste = boost::prior(past_pit);
335 // If we only inserted one paragraph.
336 if (boost::next(pit) == last_paste)
339 mergeParagraph(buffer.params, pars, pit);
341 // Store the new cursor position.
343 pos = last_paste->size();
345 // Maybe some pasting.
346 #warning CHECK! Are we comparing last_paste to the wrong list here? (Lgb)
347 if (boost::next(last_paste) != pars.end() &&
349 if (boost::next(last_paste)->hasSameLayout(*last_paste)) {
350 mergeParagraph(buffer.params, pars,
352 } else if (boost::next(last_paste)->empty()) {
353 boost::next(last_paste)->makeSameLayout(*last_paste);
354 mergeParagraph(buffer.params, pars,
356 } else if (last_paste->empty()) {
357 last_paste->makeSameLayout(*boost::next(last_paste));
358 mergeParagraph(buffer.params, pars,
361 boost::next(last_paste)->stripLeadingSpaces();
364 return make_pair(PitPosPair(pit, pos), endpit);
368 int CutAndPaste::nrOfParagraphs()
370 return cuts.empty() ? 0 : cuts[0].first.size();
374 int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1,
376 ParagraphList & pars,
377 ErrorList & errorlist)
379 Assert(!pars.empty());
385 LyXTextClass const & tclass1 = textclasslist[c1];
386 LyXTextClass const & tclass2 = textclasslist[c2];
387 ParIterator end = ParIterator(pars.end(), pars);
388 for (ParIterator it = ParIterator(pars.begin(), pars); it != end; ++it) {
389 string const name = it->layout()->name();
390 bool hasLayout = tclass2.hasLayout(name);
393 it->layout(tclass2[name]);
395 it->layout(tclass2.defaultLayout());
397 if (!hasLayout && name != tclass1.defaultLayoutName()) {
399 string const s = bformat(
400 _("Layout had to be changed from\n%1$s to %2$s\n"
401 "because of class conversion from\n%3$s to %4$s"),
402 name, it->layout()->name(), tclass1.name(), tclass2.name());
403 // To warn the user that something had to be done.
404 errorlist.push_back(ErrorItem("Changed Layout", s,
413 bool CutAndPaste::checkPastePossible()
415 return !cuts.empty() && !cuts[0].first.empty();