]> git.lyx.org Git - lyx.git/blob - src/CutAndPaste.C
Move to the new menu layout
[lyx.git] / src / CutAndPaste.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 1995-2001 The LyX Team.
7  *
8  * ====================================================== */
9
10 #include <config.h>
11
12 #include "CutAndPaste.h"
13 #include "BufferView.h"
14 #include "buffer.h"
15 #include "paragraph.h"
16 #include "ParagraphParameters.h"
17 #include "lyxtext.h"
18 #include "lyxcursor.h"
19 #include "gettext.h"
20 #include "iterators.h"
21 #include "lyxtextclasslist.h"
22 #include "undo_funcs.h"
23 #include "paragraph_funcs.h"
24 #include "debug.h"
25
26 #include "insets/inseterror.h"
27
28 #include "support/BoostFormat.h"
29
30 using std::endl;
31 using std::pair;
32 using lyx::pos_type;
33 using lyx::textclass_type;
34
35 extern BufferView * current_view;
36
37 // Jürgen, note that this means that you cannot currently have a list
38 // of selections cut/copied. So IMHO later we should have a
39 // list/vector/deque that we could store
40 // struct selection_item {
41 //       Paragraph * buf;
42 //       LyXTextClassList::size_type textclass;
43 // };
44 // in and some method of choosing beween them (based on the first few chars
45 // in the selection probably.) This would be a nice feature and quite
46 // easy to implement. (Lgb)
47 //
48 // Sure but I just cleaned up this code for now with the same functionality
49 // as before. I also want to add a XClipboard function so that we can copy
50 // text from LyX to some other X-application in the form of ASCII or in the
51 // form of LaTeX (or Docbook depending on the document-class!). Think how nice
52 // it could be to select a math-inset do a "Copy to X-Clipboard as LaTeX" and
53 // then do a middle mouse button click in the application you want and have
54 // the whole formula there in LaTeX-Code. (Jug)
55
56 namespace {
57
58 // FIXME: stupid name
59 ParagraphList paragraphs;
60 textclass_type textclass = 0;
61
62 } // namespace anon
63
64 typedef std::pair<ParagraphList::iterator, int> pitPosPair;
65
66 pitPosPair CutAndPaste::cutSelection(ParagraphList & pars, 
67                                      ParagraphList::iterator startpit, 
68                                      ParagraphList::iterator endpit,
69                                      int startpos, int endpos, 
70                                      textclass_type tc, bool doclear)
71 {
72         copySelection(&*startpit, &*endpit, startpos, endpos, tc);
73         return eraseSelection(pars, startpit, endpit, startpos, 
74                               endpos, doclear);
75 }
76
77
78 pitPosPair CutAndPaste::eraseSelection(ParagraphList & pars, 
79                                        ParagraphList::iterator startpit, 
80                                        ParagraphList::iterator endpit,
81                                        int startpos, int endpos, bool doclear)
82 {
83         if (startpit == pars.end() || (startpos > startpit->size()))
84                 return pitPosPair(endpit, endpos);
85
86         if (endpit == pars.end() || startpit == endpit) {
87                 endpos -= startpit->erase(startpos, endpos);
88                 return pitPosPair(endpit, endpos);
89         }
90
91         // clear end/begin fragments of the first/last par in selection
92         bool all_erased = true;
93
94         startpit->erase(startpos, startpit->size());
95         if (startpit->size() != startpos)
96                 all_erased = false;
97
98         endpos -= endpit->erase(0, endpos);
99         if (endpos != 0)
100                 all_erased = false;
101
102         // Loop through the deleted pars if any, erasing as needed
103
104         ParagraphList::iterator pit = boost::next(startpit);
105
106         while (pit != endpit && pit != pars.end()) {
107                 ParagraphList::iterator const next = boost::next(pit);
108                 // "erase" the contents of the par
109                 pit->erase(0, pit->size());
110                 if (!pit->size()) {
111                         // remove the par if it's now empty
112                         pars.erase(pit);
113                 } else
114                         all_erased = false;
115                 pit = next;
116         }
117
118 #if 0 // FIXME: why for cut but not copy ?
119         // the cut selection should begin with standard layout
120         if (realcut) {
121                 buf->params().clear();
122                 buf->bibkey = 0;
123                 buf->layout(textclasslist[buffer->params.textclass].defaultLayoutName());
124         }
125 #endif
126
127         if (boost::next(startpit) == pars.end())
128                 return pitPosPair(endpit, endpos);
129
130         if (doclear) {
131                 boost::next(startpit)->stripLeadingSpaces();
132         }
133
134         // paste the paragraphs again, if possible
135         if (all_erased && 
136             (startpit->hasSameLayout(*boost::next(startpit)) ||
137              boost::next(startpit)->empty())) {
138 #warning current_view used here.
139 // should we pass buffer or buffer->params around?
140                 Buffer * buffer = current_view->buffer();
141                 mergeParagraph(buffer->params, pars, &*startpit);
142                 // this because endpar gets deleted here!
143                 endpit = startpit;
144                 endpos = startpos;
145         }
146
147         return pitPosPair(endpit, endpos);
148
149 }
150
151
152 bool CutAndPaste::copySelection(Paragraph * startpar, Paragraph * endpar,
153                                 int start, int end, textclass_type tc)
154 {
155         if (!startpar || (start > startpar->size()))
156                 return false;
157
158         paragraphs.clear();
159
160         textclass = tc;
161
162         if (!endpar || startpar == endpar) {
163                 // only within one paragraph
164                 ParagraphList::iterator buf =
165                         paragraphs.insert(paragraphs.begin(), new Paragraph);
166
167                 buf->layout(startpar->layout());
168                 pos_type i = start;
169                 if (end > startpar->size())
170                         end = startpar->size();
171                 for (; i < end; ++i) {
172                         startpar->copyIntoMinibuffer(*current_view->buffer(), i);
173                         buf->insertFromMinibuffer(buf->size());
174                 }
175         } else {
176                 // copy more than one paragraph
177                 // clone the paragraphs within the selection
178                 Paragraph * tmppar = startpar;
179
180                 while (tmppar != endpar) {
181                         Paragraph * newpar = new Paragraph(*tmppar, false);
182                         // reset change info
183                         newpar->cleanChanges();
184                         newpar->setInsetOwner(0);
185
186                         paragraphs.push_back(newpar);
187                         tmppar = tmppar->next();
188                 }
189
190                 // The first paragraph is too big.
191                 Paragraph & front = paragraphs.front();
192                 pos_type tmpi2 = start;
193                 for (; tmpi2; --tmpi2)
194                         front.erase(0);
195
196                 // Now last paragraph is too big, delete all after end.
197                 Paragraph & back = paragraphs.back();
198                 tmpi2 = end;
199                 while (back.size() > tmpi2) {
200                         back.erase(back.size() - 1);
201                 }
202         }
203         return true;
204 }
205
206
207 bool CutAndPaste::pasteSelection(Paragraph ** par, Paragraph ** endpar,
208                                  int & pos, textclass_type tc)
209 {
210         if (!checkPastePossible())
211                 return false;
212
213         if (pos > (*par)->size())
214                 pos = (*par)->size();
215
216         // many paragraphs
217
218         // make a copy of the simple cut_buffer
219 #if 1
220         ParagraphList::iterator it = paragraphs.begin();
221
222         ParagraphList simple_cut_clone;
223         simple_cut_clone.insert(simple_cut_clone.begin(),
224                                 new Paragraph(*it, false));
225
226         ParagraphList::iterator end = paragraphs.end();
227         while (boost::next(it) != end) {
228                 ++it;
229                 simple_cut_clone.insert(simple_cut_clone.end(),
230                                         new Paragraph(*it, false));
231         }
232 #else
233         // Later we want it done like this:
234         ParagraphList simple_cut_clone(paragraphs.begin(),
235                                        paragraphs.end());
236 #endif
237         // now remove all out of the buffer which is NOT allowed in the
238         // new environment and set also another font if that is required
239         ParagraphList::iterator tmpbuf = paragraphs.begin();
240         int depth_delta = (*par)->params().depth() - tmpbuf->params().depth();
241         // Temporary set *par as previous of tmpbuf as we might have
242         // to realize the font.
243         tmpbuf->previous(*par);
244
245         // make sure there is no class difference
246         SwitchLayoutsBetweenClasses(textclass, tc, &*tmpbuf,
247                                     current_view->buffer()->params);
248
249         Paragraph::depth_type max_depth = (*par)->getMaxDepthAfter();
250
251         while (tmpbuf != paragraphs.end()) {
252                 // If we have a negative jump so that the depth would
253                 // go below 0 depth then we have to redo the delta to
254                 // this new max depth level so that subsequent
255                 // paragraphs are aligned correctly to this paragraph
256                 // at level 0.
257                 if ((int(tmpbuf->params().depth()) + depth_delta) < 0)
258                         depth_delta = 0;
259                 // set the right depth so that we are not too deep or shallow.
260                 tmpbuf->params().depth(tmpbuf->params().depth() + depth_delta);
261                 if (tmpbuf->params().depth() > max_depth)
262                         tmpbuf->params().depth(max_depth);
263                 // only set this from the 2nd on as the 2nd depends for maxDepth
264                 // still on *par
265                 if (tmpbuf->previous() != (*par))
266                         max_depth = tmpbuf->getMaxDepthAfter();
267                 // set the inset owner of this paragraph
268                 tmpbuf->setInsetOwner((*par)->inInset());
269                 for (pos_type i = 0; i < tmpbuf->size(); ++i) {
270                         if (tmpbuf->getChar(i) == Paragraph::META_INSET) {
271                                 if (!(*par)->insetAllowed(tmpbuf->getInset(i)->lyxCode())) {
272                                         tmpbuf->erase(i--);
273                                 }
274                         } else {
275                                 LyXFont f1 = tmpbuf->getFont(current_view->buffer()->params, i, outerFont(tmpbuf, current_view->buffer()->paragraphs));
276                                 LyXFont f2 = f1;
277                                 if (!(*par)->checkInsertChar(f1)) {
278                                         tmpbuf->erase(i--);
279                                 } else if (f1 != f2) {
280                                         tmpbuf->setFont(i, f1);
281                                 }
282                         }
283                 }
284                 tmpbuf = tmpbuf->next();
285         }
286
287         // now reset it to 0
288         paragraphs.begin()->previous(0);
289
290         // make the buf exactly the same layout than
291         // the cursor paragraph
292         paragraphs.begin()->makeSameLayout(**par);
293
294         // find the end of the buffer
295         ParagraphList::iterator lastbuffer = paragraphs.begin();
296         while (boost::next(lastbuffer) != paragraphs.end())
297                 ++lastbuffer;
298
299         bool paste_the_end = false;
300
301         // open the paragraph for inserting the buf
302         // if necessary
303         if (((*par)->size() > pos) || !(*par)->next()) {
304                 breakParagraphConservative(
305                         current_view->buffer()->params, current_view->buffer()->paragraphs, *par, pos);
306                 paste_the_end = true;
307         }
308         // set the end for redoing later
309         *endpar = (*par)->next()->next();
310
311         // paste it!
312         lastbuffer->next((*par)->next());
313         (*par)->next()->previous(&*lastbuffer);
314
315         (*par)->next(&*paragraphs.begin());
316         paragraphs.begin()->previous(*par);
317
318         if ((*par)->next() == lastbuffer)
319                 lastbuffer = *par;
320
321         mergeParagraph(current_view->buffer()->params,
322                        current_view->buffer()->paragraphs, *par);
323         // store the new cursor position
324         *par = &*lastbuffer;
325         pos = lastbuffer->size();
326         // maybe some pasting
327         if (lastbuffer->next() && paste_the_end) {
328                 if (lastbuffer->next()->hasSameLayout(*lastbuffer)) {
329                         mergeParagraph(current_view->buffer()->params,
330                                        current_view->buffer()->paragraphs, lastbuffer);
331                 } else if (!lastbuffer->next()->size()) {
332                         lastbuffer->next()->makeSameLayout(*lastbuffer);
333                         mergeParagraph(current_view->buffer()->params, current_view->buffer()->paragraphs, lastbuffer);
334                 } else if (!lastbuffer->size()) {
335                         lastbuffer->makeSameLayout(*lastbuffer->next());
336                         mergeParagraph(current_view->buffer()->params,
337                                        current_view->buffer()->paragraphs, lastbuffer);
338                 } else
339                         lastbuffer->next()->stripLeadingSpaces();
340         }
341         // restore the simple cut buffer
342         paragraphs = simple_cut_clone;
343
344         return true;
345 }
346
347
348 int CutAndPaste::nrOfParagraphs()
349 {
350         return paragraphs.size();
351 }
352
353
354 int CutAndPaste::SwitchLayoutsBetweenClasses(textclass_type c1,
355                                              textclass_type c2,
356                                              Paragraph * par,
357                                              BufferParams const & /*bparams*/)
358 {
359         int ret = 0;
360         if (!par || c1 == c2)
361                 return ret;
362
363         LyXTextClass const & tclass1 = textclasslist[c1];
364         LyXTextClass const & tclass2 = textclasslist[c2];
365         ParIterator end = ParIterator();
366         for (ParIterator it = ParIterator(par); it != end; ++it) {
367                 par = *it;
368                 string const name = par->layout()->name();
369                 bool hasLayout = tclass2.hasLayout(name);
370
371                 if (hasLayout)
372                         par->layout(tclass2[name]);
373                 else
374                         par->layout(tclass2.defaultLayout());
375
376                 if (!hasLayout && name != tclass1.defaultLayoutName()) {
377                         ++ret;
378 #if USE_BOOST_FORMAT
379                         boost::format fmt(_("Layout had to be changed from\n"
380                                             "%1$s to %2$s\n"
381                                             "because of class conversion from\n"
382                                             "%3$s to %4$s"));
383                         fmt     % name
384                                 % par->layout()->name()
385                                 % tclass1.name()
386                                 % tclass2.name();
387
388                         string const s = fmt.str();
389 #else
390                         string const s = _("Layout had to be changed from\n")
391                                 + name + _(" to ")
392                                 + par->layout()->name()
393                                 + _("\nbecause of class conversion from\n")
394                                 + tclass1.name() + _(" to ")
395                                 + tclass2.name();
396 #endif
397                         freezeUndo();
398                         InsetError * new_inset = new InsetError(s);
399                         LyXText * txt = current_view->getLyXText();
400                         LyXCursor cur = txt->cursor;
401                         txt->setCursorIntern(par, 0);
402                         txt->insertInset(new_inset);
403                         txt->fullRebreak();
404                         txt->setCursorIntern(cur.par(), cur.pos());
405                         unFreezeUndo();
406                 }
407         }
408         return ret;
409 }
410
411
412 bool CutAndPaste::checkPastePossible()
413 {
414         return !paragraphs.empty();
415 }