]> git.lyx.org Git - lyx.git/blob - src/CutAndPaste.C
738ea6229152a2e8c5f808570441fbafeae02c51
[lyx.git] / src / CutAndPaste.C
1 /*
2  * \file CutAndPaste.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Juergen Vigna
7  * \author Lars Gullik Bjønnes
8  * \author Alfredo Braunstein
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "CutAndPaste.h"
16 #include "buffer.h"
17 #include "ParagraphParameters.h"
18 #include "iterators.h"
19 #include "lyxtextclasslist.h"
20 #include "gettext.h"
21 #include "paragraph.h"
22 #include "paragraph_funcs.h"
23 #include "insets/insetinclude.h"
24 #include "insets/insettabular.h"
25
26 #include "support/LAssert.h"
27 #include "support/lstrings.h"
28
29 using std::endl;
30 using std::pair;
31 using std::make_pair;
32 using std::for_each;
33 using std::vector;
34
35 using namespace lyx::support;
36 using lyx::pos_type;
37 using lyx::textclass_type;
38
39
40 typedef limited_stack<pair<ParagraphList, textclass_type> > CutStack;
41
42 namespace {
43
44 CutStack cuts(10);
45
46 } // namespace anon
47
48
49 std::vector<string>
50 CutAndPaste::availableSelections(Buffer const & buffer)
51 {
52         vector<string> selList;
53
54         CutStack::const_iterator cit = cuts.begin();
55         CutStack::const_iterator end = cuts.end();
56         for (; cit != end; ++cit) {
57                 // we do not use cit-> here because gcc 2.9x does not
58                 // like it (JMarc)
59                 ParagraphList const & pars = (*cit).first;
60                 string asciiSel;
61                 ParagraphList::const_iterator pit = pars.begin();
62                 ParagraphList::const_iterator pend = pars.end();
63                 for (; pit != pend; ++pit) {
64                         asciiSel += pit->asString(buffer, false);
65                         if (asciiSel.size() > 25) {
66                                 asciiSel.replace(22, string::npos, "...");
67                                 break;
68                         }
69                 }
70
71                 selList.push_back(asciiSel);
72         }
73
74         return selList;
75 }
76
77
78 PitPosPair CutAndPaste::cutSelection(BufferParams const & params,
79                                      ParagraphList & pars,
80                                      ParagraphList::iterator startpit,
81                                      ParagraphList::iterator endpit,
82                                      int startpos, int endpos,
83                                      textclass_type tc, bool doclear)
84 {
85         copySelection(startpit, endpit, startpos, endpos, tc);
86         return eraseSelection(params, pars, startpit, endpit, startpos,
87                               endpos, doclear);
88 }
89
90
91 PitPosPair CutAndPaste::eraseSelection(BufferParams const & params,
92                                        ParagraphList & pars,
93                                        ParagraphList::iterator startpit,
94                                        ParagraphList::iterator endpit,
95                                        int startpos, int endpos, bool doclear)
96 {
97         if (startpit == pars.end() || (startpos > startpit->size()))
98                 return PitPosPair(endpit, endpos);
99
100         if (endpit == pars.end() || startpit == endpit) {
101                 endpos -= startpit->erase(startpos, endpos);
102                 return PitPosPair(endpit, endpos);
103         }
104
105         // clear end/begin fragments of the first/last par in selection
106         bool all_erased = true;
107
108         startpit->erase(startpos, startpit->size());
109         if (startpit->size() != startpos)
110                 all_erased = false;
111
112         endpos -= endpit->erase(0, endpos);
113         if (endpos != 0)
114                 all_erased = false;
115
116         // Loop through the deleted pars if any, erasing as needed
117
118         ParagraphList::iterator pit = boost::next(startpit);
119
120         while (pit != endpit && pit != pars.end()) {
121                 ParagraphList::iterator const next = boost::next(pit);
122                 // "erase" the contents of the par
123                 pit->erase(0, pit->size());
124                 if (!pit->size()) {
125                         // remove the par if it's now empty
126                         pars.erase(pit);
127                 } else
128                         all_erased = false;
129                 pit = next;
130         }
131
132 #if 0 // FIXME: why for cut but not copy ?
133         // the cut selection should begin with standard layout
134         if (realcut) {
135                 buf->params().clear();
136                 buf->bibkey = 0;
137                 buf->layout(textclasslist[buffer->params.textclass].defaultLayoutName());
138         }
139 #endif
140
141         if (boost::next(startpit) == pars.end())
142                 return PitPosPair(endpit, endpos);
143
144         if (doclear) {
145                 boost::next(startpit)->stripLeadingSpaces();
146         }
147
148         // paste the paragraphs again, if possible
149         if (all_erased &&
150             (startpit->hasSameLayout(*boost::next(startpit)) ||
151              boost::next(startpit)->empty())) {
152                 mergeParagraph(params, pars, startpit);
153                 // this because endpar gets deleted here!
154                 endpit = startpit;
155                 endpos = startpos;
156         }
157
158         return PitPosPair(endpit, endpos);
159
160 }
161
162
163 namespace {
164
165 struct resetOwnerAndChanges {
166         void operator()(Paragraph & p) {
167                 p.cleanChanges();
168                 p.setInsetOwner(0);
169         }
170 };
171
172 } // anon namespace
173
174 bool CutAndPaste::copySelection(ParagraphList::iterator startpit,
175                                 ParagraphList::iterator endpit,
176                                 int start, int end, textclass_type tc)
177 {
178         Assert(0 <= start && start <= startpit->size());
179         Assert(0 <= end && end <= endpit->size());
180         Assert(startpit != endpit || start <= end);
181
182         ParagraphList paragraphs;
183
184         // Clone the paragraphs within the selection.
185         ParagraphList::iterator postend = boost::next(endpit);
186
187         paragraphs.assign(startpit, postend);
188         for_each(paragraphs.begin(), paragraphs.end(), resetOwnerAndChanges());
189
190         // Cut out the end of the last paragraph.
191         Paragraph & back = paragraphs.back();
192         back.erase(end, back.size());
193
194         // Cut out the begin of the first paragraph
195         Paragraph & front = paragraphs.front();
196         front.erase(0, start);
197
198         cuts.push(make_pair(paragraphs, tc));
199
200         return true;
201 }
202
203
204 pair<PitPosPair, ParagraphList::iterator>
205 CutAndPaste::pasteSelection(Buffer const & buffer,
206                             ParagraphList & pars,
207                             ParagraphList::iterator pit, int pos,
208                             textclass_type tc,
209                             ErrorList & errorlist)
210 {
211         return pasteSelection(buffer, pars, pit, pos, tc, 0, errorlist);
212 }
213
214
215 pair<PitPosPair, ParagraphList::iterator>
216 CutAndPaste::pasteSelection(Buffer const & buffer,
217                             ParagraphList & pars,
218                             ParagraphList::iterator pit, int pos,
219                             textclass_type tc, size_t cut_index,
220                             ErrorList & errorlist)
221 {
222         if (!checkPastePossible())
223                 return make_pair(PitPosPair(pit, pos), pit);
224
225         Assert (pos <= pit->size());
226
227         // Make a copy of the CaP paragraphs.
228         ParagraphList simple_cut_clone = cuts[cut_index].first;
229         textclass_type const textclass = cuts[cut_index].second;
230
231         // Now remove all out of the pars which is NOT allowed in the
232         // new environment and set also another font if that is required.
233
234         // Make sure there is no class difference.
235         SwitchLayoutsBetweenClasses(textclass, tc, simple_cut_clone,
236                                     errorlist);
237
238         ParagraphList::iterator tmpbuf = simple_cut_clone.begin();
239         int depth_delta = pit->params().depth() - tmpbuf->params().depth();
240
241         Paragraph::depth_type max_depth = pit->getMaxDepthAfter();
242
243         for (; tmpbuf != simple_cut_clone.end(); ++tmpbuf) {
244                 // If we have a negative jump so that the depth would
245                 // go below 0 depth then we have to redo the delta to
246                 // this new max depth level so that subsequent
247                 // paragraphs are aligned correctly to this paragraph
248                 // at level 0.
249                 if ((int(tmpbuf->params().depth()) + depth_delta) < 0)
250                         depth_delta = 0;
251
252                 // Set the right depth so that we are not too deep or shallow.
253                 tmpbuf->params().depth(tmpbuf->params().depth() + depth_delta);
254                 if (tmpbuf->params().depth() > max_depth)
255                         tmpbuf->params().depth(max_depth);
256
257                 // Only set this from the 2nd on as the 2nd depends
258                 // for maxDepth still on pit.
259                 if (tmpbuf != simple_cut_clone.begin())
260                         max_depth = tmpbuf->getMaxDepthAfter();
261
262                 // Set the inset owner of this paragraph.
263                 tmpbuf->setInsetOwner(pit->inInset());
264                 for (pos_type i = 0; i < tmpbuf->size(); ++i) {
265                         if (tmpbuf->getChar(i) == Paragraph::META_INSET) {
266                                 if (!pit->insetAllowed(tmpbuf->getInset(i)->lyxCode())) {
267                                         tmpbuf->erase(i--);
268                                 }
269                         } else {
270                                 LyXFont f1 = tmpbuf->getFont(buffer.params, i, outerFont(pit, pars));
271                                 LyXFont f2 = f1;
272                                 if (!pit->checkInsertChar(f1)) {
273                                         tmpbuf->erase(i--);
274                                 } else if (f1 != f2) {
275                                         tmpbuf->setFont(i, f1);
276                                 }
277                         }
278                 }
279         }
280
281         // Make the buf exactly the same layout than
282         // the cursor paragraph.
283         simple_cut_clone.begin()->makeSameLayout(*pit);
284
285         // Prepare the paragraphs and insets for insertion
286         // A couple of insets store buffer references so need
287         // updating
288         ParIterator fpit(simple_cut_clone.begin(), simple_cut_clone);
289         ParIterator fend(simple_cut_clone.end(), simple_cut_clone);
290
291         for (; fpit != fend; ++fpit) {
292                 InsetList::iterator lit = fpit->insetlist.begin();
293                 InsetList::iterator eit = fpit->insetlist.end();
294
295                 for (; lit != eit; ++lit) {
296                         switch (lit->inset->lyxCode()) {
297                         case InsetOld::INCLUDE_CODE: {
298                                 InsetInclude * ii = static_cast<InsetInclude*>(lit->inset);
299                                 InsetInclude::Params ip = ii->params();
300                                 ip.masterFilename_ = buffer.fileName();
301                                 ii->set(ip);
302                                 break;
303                         }
304
305                         case InsetOld::TABULAR_CODE: {
306                                 InsetTabular * it = static_cast<InsetTabular*>(lit->inset);
307                                 it->buffer(const_cast<Buffer*>(&buffer));
308                                 break;
309                         }
310
311                         default:
312                                 break; // nothing
313                         }
314                 }
315         }
316
317         bool paste_the_end = false;
318
319         // Open the paragraph for inserting the buf
320         // if necessary.
321         if (pit->size() > pos || boost::next(pit) == pars.end()) {
322                 breakParagraphConservative(buffer.params,
323                                            pars, pit, pos);
324                 paste_the_end = true;
325         }
326
327         // Set the end for redoing later.
328         ParagraphList::iterator endpit = boost::next(boost::next(pit));
329
330         // Paste it!
331
332         ParagraphList::iterator past_pit = boost::next(pit);
333         pars.splice(past_pit, simple_cut_clone);
334         ParagraphList::iterator last_paste = boost::prior(past_pit);
335
336         // If we only inserted one paragraph.
337         if (boost::next(pit) == last_paste)
338                 last_paste = pit;
339
340         mergeParagraph(buffer.params, pars, pit);
341
342         // Store the new cursor position.
343         pit = last_paste;
344         pos = last_paste->size();
345
346         // Maybe some pasting.
347 #warning CHECK! Are we comparing last_paste to the wrong list here? (Lgb)
348         if (boost::next(last_paste) != pars.end() &&
349             paste_the_end) {
350                 if (boost::next(last_paste)->hasSameLayout(*last_paste)) {
351                         mergeParagraph(buffer.params, pars,
352                                        last_paste);
353                 } else if (boost::next(last_paste)->empty()) {
354                         boost::next(last_paste)->makeSameLayout(*last_paste);
355                         mergeParagraph(buffer.params, pars,
356                                        last_paste);
357                 } else if (last_paste->empty()) {
358                         last_paste->makeSameLayout(*boost::next(last_paste));
359                         mergeParagraph(buffer.params, pars,
360                                        last_paste);
361                 } else
362                         boost::next(last_paste)->stripLeadingSpaces();
363         }
364
365         return make_pair(PitPosPair(pit, pos), endpit);
366 }
367
368
369 int CutAndPaste::nrOfParagraphs()
370 {
371         return cuts.empty() ? 0 : cuts[0].first.size();
372 }
373
374
375 int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1,
376                                              textclass_type c2,
377                                              ParagraphList & pars,
378                                              ErrorList & errorlist)
379 {
380         Assert(!pars.empty());
381
382         int ret = 0;
383         if (c1 == c2)
384                 return ret;
385
386         LyXTextClass const & tclass1 = textclasslist[c1];
387         LyXTextClass const & tclass2 = textclasslist[c2];
388         ParIterator end = ParIterator(pars.end(), pars);
389         for (ParIterator it = ParIterator(pars.begin(), pars); it != end; ++it) {
390                 string const name = it->layout()->name();
391                 bool hasLayout = tclass2.hasLayout(name);
392
393                 if (hasLayout)
394                         it->layout(tclass2[name]);
395                 else
396                         it->layout(tclass2.defaultLayout());
397
398                 if (!hasLayout && name != tclass1.defaultLayoutName()) {
399                         ++ret;
400                         string const s = bformat(
401                                 _("Layout had to be changed from\n%1$s to %2$s\n"
402                                 "because of class conversion from\n%3$s to %4$s"),
403                          name, it->layout()->name(), tclass1.name(), tclass2.name());
404                         // To warn the user that something had to be done.
405                         errorlist.push_back(ErrorItem("Changed Layout", s,
406                                                       it->id(), 0,
407                                                       it->size()));
408                 }
409         }
410         return ret;
411 }
412
413
414 bool CutAndPaste::checkPastePossible()
415 {
416         return !cuts.empty() && !cuts[0].first.empty();
417 }