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