]> git.lyx.org Git - lyx.git/blob - src/CutAndPaste.C
cure a couple of funny new bugs introduced in my latest patch
[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 Jürgen 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
17 #include "buffer.h"
18 #include "buffer_funcs.h"
19 #include "bufferparams.h"
20 #include "BufferView.h"
21 #include "cursor.h"
22 #include "errorlist.h"
23 #include "gettext.h"
24 #include "lyxtext.h"
25 #include "lyxtextclasslist.h"
26 #include "paragraph.h"
27 #include "paragraph_funcs.h"
28 #include "ParagraphParameters.h"
29 #include "ParagraphList_fwd.h"
30 #include "pariterator.h"
31 #include "undo.h"
32
33 #include "insets/insettabular.h"
34
35 #include "support/lstrings.h"
36
37 #include <boost/tuple/tuple.hpp>
38
39 using lyx::pos_type;
40 using lyx::par_type;
41 using lyx::textclass_type;
42
43 using lyx::support::bformat;
44
45 using std::for_each;
46 using std::make_pair;
47 using std::pair;
48 using std::vector;
49 using std::string;
50
51
52 namespace {
53
54 typedef std::pair<lyx::par_type, int> PitPosPair;
55
56 typedef limited_stack<pair<ParagraphList, textclass_type> > CutStack;
57
58 CutStack cuts(10);
59
60 struct resetOwnerAndChanges : public std::unary_function<Paragraph, void> {
61         void operator()(Paragraph & p) const {
62                 p.cleanChanges();
63                 p.setInsetOwner(0);
64         }
65 };
66
67 } // namespace anon
68
69
70 namespace lyx {
71 namespace cap {
72
73
74 int SwitchLayoutsBetweenClasses(textclass_type c1, textclass_type c2,
75         ParagraphList & pars, ErrorList & errorlist)
76 {
77         BOOST_ASSERT(!pars.empty());
78         int ret = 0;
79         if (c1 == c2)
80                 return ret;
81
82         LyXTextClass const & tclass1 = textclasslist[c1];
83         LyXTextClass const & tclass2 = textclasslist[c2];
84
85         InsetText in;
86         std::swap(in.paragraphs(), pars);
87         
88         ParIterator end = ParIterator(DocumentIterator());
89         for (ParIterator it = ParIterator(in, 0); it != end; ++it) {
90                 string const name = it->layout()->name();
91                 bool hasLayout = tclass2.hasLayout(name);
92
93                 if (hasLayout)
94                         it->layout(tclass2[name]);
95                 else
96                         it->layout(tclass2.defaultLayout());
97
98                 if (!hasLayout && name != tclass1.defaultLayoutName()) {
99                         ++ret;
100                         string const s = bformat(
101                                 _("Layout had to be changed from\n%1$s to %2$s\n"
102                                 "because of class conversion from\n%3$s to %4$s"),
103                          name, it->layout()->name(), tclass1.name(), tclass2.name());
104                         // To warn the user that something had to be done.
105                         errorlist.push_back(ErrorItem("Changed Layout", s,
106                                                       it->id(), 0,
107                                                       it->size()));
108                 }
109         }
110         std::swap(in.paragraphs(), pars);
111         return ret;
112 }
113
114
115 std::vector<string> const availableSelections(Buffer const & buffer)
116 {
117         vector<string> selList;
118
119         CutStack::const_iterator cit = cuts.begin();
120         CutStack::const_iterator end = cuts.end();
121         for (; cit != end; ++cit) {
122                 // we do not use cit-> here because gcc 2.9x does not
123                 // like it (JMarc)
124                 ParagraphList const & pars = (*cit).first;
125                 string asciiSel;
126                 ParagraphList::const_iterator pit = pars.begin();
127                 ParagraphList::const_iterator pend = pars.end();
128                 for (; pit != pend; ++pit) {
129                         asciiSel += pit->asString(buffer, false);
130                         if (asciiSel.size() > 25) {
131                                 asciiSel.replace(22, string::npos, "...");
132                                 break;
133                         }
134                 }
135
136                 selList.push_back(asciiSel);
137         }
138
139         return selList;
140 }
141
142
143 PitPosPair eraseSelection(BufferParams const & params, ParagraphList & pars,
144         par_type startpit, par_type endpit,
145         int startpos, int endpos, bool doclear)
146 {
147         if (startpit == par_type(pars.size()) ||
148             (startpos > pars[startpit].size()))
149                 return PitPosPair(endpit, endpos);
150
151         if (endpit == par_type(pars.size()) ||
152             startpit == endpit) {
153                 endpos -= pars[startpit].erase(startpos, endpos);
154                 return PitPosPair(endpit, endpos);
155         }
156
157         // clear end/begin fragments of the first/last par in selection
158         bool all_erased = true;
159
160         pars[startpit].erase(startpos, pars[startpit].size());
161         if (pars[startpit].size() != startpos)
162                 all_erased = false;
163
164         endpos -= pars[endpit].erase(0, endpos);
165         if (endpos != 0)
166                 all_erased = false;
167
168         // Loop through the deleted pars if any, erasing as needed
169         for (par_type pit = startpit + 1; pit != endpit;) {
170                 // "erase" the contents of the par
171                 pars[pit].erase(0, pars[pit].size());
172                 if (!pars[pit].size()) {
173                         // remove the par if it's now empty
174                         pars.erase(pars.begin() + pit);
175                         --endpit;
176                 } else {
177                         ++pit;
178                         all_erased = false;
179                 }
180         }
181
182 #if 0 // FIXME: why for cut but not copy ?
183         // the cut selection should begin with standard layout
184         if (realcut) {
185                 buf->params().clear();
186                 buf->bibkey = 0;
187                 buf->layout(textclasslist[buffer->params.textclass].defaultLayoutName());
188         }
189 #endif
190
191         if (startpit + 1 == par_type(pars.size()))
192                 return PitPosPair(endpit, endpos);
193
194         if (doclear) {
195                 pars[startpit + 1].stripLeadingSpaces();
196         }
197
198         // paste the paragraphs again, if possible
199         if (all_erased &&
200             (pars[startpit].hasSameLayout(pars[startpit + 1]) ||
201              pars[startpit + 1].empty())) {
202                 mergeParagraph(params, pars, startpit);
203                 // this because endpar gets deleted here!
204                 endpit = startpit;
205                 endpos = startpos;
206         }
207
208         return PitPosPair(endpit, endpos);
209
210 }
211
212
213 bool copySelection(ParagraphList & pars,
214         par_type startpit, par_type endpit,
215         int start, int end, textclass_type tc)
216 {
217         BOOST_ASSERT(0 <= start && start <= pars[startpit].size());
218         BOOST_ASSERT(0 <= end && end <= pars[endpit].size());
219         BOOST_ASSERT(startpit != endpit || start <= end);
220
221         // Clone the paragraphs within the selection.
222         ParagraphList paragraphs(pars.begin() + startpit, pars.begin() + endpit + 1);
223         for_each(paragraphs.begin(), paragraphs.end(), resetOwnerAndChanges());
224
225         // Cut out the end of the last paragraph.
226         Paragraph & back = paragraphs.back();
227         back.erase(end, back.size());
228
229         // Cut out the begin of the first paragraph
230         Paragraph & front = paragraphs.front();
231         front.erase(0, start);
232
233         cuts.push(make_pair(paragraphs, tc));
234
235         return true;
236 }
237
238
239 PitPosPair cutSelection(BufferParams const & params, ParagraphList & pars,
240         par_type startpit, par_type endpit,
241         int startpos, int endpos, textclass_type tc, bool doclear)
242 {
243         copySelection(pars, startpit, endpit, startpos, endpos, tc);
244         return eraseSelection(params, pars, startpit, endpit, startpos,
245                               endpos, doclear);
246 }
247
248
249 pair<PitPosPair, par_type>
250 pasteSelection(Buffer const & buffer, ParagraphList & pars,
251         par_type pit, int pos,
252         textclass_type tc, size_t cut_index, ErrorList & errorlist)
253 {
254         if (!checkPastePossible())
255                 return make_pair(PitPosPair(pit, pos), pit);
256
257         BOOST_ASSERT (pos <= pars[pit].size());
258
259         // Make a copy of the CaP paragraphs.
260         ParagraphList insertion = cuts[cut_index].first;
261         textclass_type const textclass = cuts[cut_index].second;
262
263         // Now remove all out of the pars which is NOT allowed in the
264         // new environment and set also another font if that is required.
265
266         // Make sure there is no class difference.
267         SwitchLayoutsBetweenClasses(textclass, tc, insertion,
268                                     errorlist);
269
270         ParagraphList::iterator tmpbuf = insertion.begin();
271         int depth_delta = pars[pit].params().depth() - tmpbuf->params().depth();
272
273         Paragraph::depth_type max_depth = pars[pit].getMaxDepthAfter();
274
275         for (; tmpbuf != insertion.end(); ++tmpbuf) {
276                 // If we have a negative jump so that the depth would
277                 // go below 0 depth then we have to redo the delta to
278                 // this new max depth level so that subsequent
279                 // paragraphs are aligned correctly to this paragraph
280                 // at level 0.
281                 if (int(tmpbuf->params().depth()) + depth_delta < 0)
282                         depth_delta = 0;
283
284                 // Set the right depth so that we are not too deep or shallow.
285                 tmpbuf->params().depth(tmpbuf->params().depth() + depth_delta);
286                 if (tmpbuf->params().depth() > max_depth)
287                         tmpbuf->params().depth(max_depth);
288
289                 // Only set this from the 2nd on as the 2nd depends
290                 // for maxDepth still on pit.
291                 if (tmpbuf != insertion.begin())
292                         max_depth = tmpbuf->getMaxDepthAfter();
293
294                 // Set the inset owner of this paragraph.
295                 tmpbuf->setInsetOwner(pars[pit].inInset());
296                 for (pos_type i = 0; i < tmpbuf->size(); ++i) {
297                         if (tmpbuf->getChar(i) == Paragraph::META_INSET) {
298                                 if (!pars[pit].insetAllowed(tmpbuf->getInset(i)->lyxCode()))
299                                         tmpbuf->erase(i--);
300                         }
301                 }
302         }
303
304         // Make the buf exactly the same layout as the cursor paragraph.
305         insertion.begin()->makeSameLayout(pars[pit]);
306
307         // Prepare the paragraphs and insets for insertion.
308         // A couple of insets store buffer references so need updating.
309         InsetText in;
310         std::swap(in.paragraphs(), insertion);
311
312         ParIterator fpit = ParIterator(in, 0);
313         ParIterator fend = ParIterator(DocumentIterator());
314
315         for (; fpit != fend; ++fpit) {
316                 InsetList::iterator lit = fpit->insetlist.begin();
317                 InsetList::iterator eit = fpit->insetlist.end();
318
319                 for (; lit != eit; ++lit) {
320                         switch (lit->inset->lyxCode()) {
321                         case InsetOld::TABULAR_CODE: {
322                                 InsetTabular * it = static_cast<InsetTabular*>(lit->inset);
323                                 it->buffer(const_cast<Buffer*>(&buffer));
324                                 break;
325                         }
326
327                         default:
328                                 break; // nothing
329                         }
330                 }
331         }
332         std::swap(in.paragraphs(), insertion);
333         
334         // Split the paragraph for inserting the buf if necessary.
335         bool did_split = false;
336         if (pars[pit].size() || pit + 1 == par_type(pars.size())) {
337                 breakParagraphConservative(buffer.params(), pars, pit, pos);
338                 did_split = true;
339         }
340
341         // Paste it!
342         pars.insert(pars.begin() + pit + 1, insertion.begin(), insertion.end());
343         mergeParagraph(buffer.params(), pars, pit);
344
345         par_type last_paste = pit + insertion.size() - 1;
346         
347         // Store the new cursor position.
348         pit = last_paste;
349         pos = pars[last_paste].size();
350
351         // Maybe some pasting.
352         if (did_split && last_paste + 1 != par_type(pars.size())) {
353                 if (pars[last_paste + 1].hasSameLayout(pars[last_paste])) {
354                         mergeParagraph(buffer.params(), pars, last_paste);
355                 } else if (pars[last_paste + 1].empty()) {
356                         pars[last_paste + 1].makeSameLayout(pars[last_paste]);
357                         mergeParagraph(buffer.params(), pars, last_paste);
358                 } else if (pars[last_paste].empty()) {
359                         pars[last_paste].makeSameLayout(pars[last_paste + 1]);
360                         mergeParagraph(buffer.params(), pars, last_paste);
361                 } else {
362                         pars[last_paste + 1].stripLeadingSpaces();
363                         ++last_paste;
364                 }
365         }
366
367         return make_pair(PitPosPair(pit, pos), last_paste + 1);
368 }
369
370
371 pair<PitPosPair, par_type>
372 pasteSelection(Buffer const & buffer, ParagraphList & pars,
373         par_type pit, int pos, textclass_type tc, ErrorList & errorlist)
374 {
375         return pasteSelection(buffer, pars, pit, pos, tc, 0, errorlist);
376 }
377
378
379 int nrOfParagraphs()
380 {
381         return cuts.empty() ? 0 : cuts[0].first.size();
382 }
383
384
385 bool checkPastePossible()
386 {
387         return !cuts.empty() && !cuts[0].first.empty();
388 }
389
390
391 void cutSelection(LCursor & cur, bool doclear, bool realcut)
392 {
393         LyXText * text = cur.text();
394         BOOST_ASSERT(text);
395         // Stuff what we got on the clipboard. Even if there is no selection.
396
397         // There is a problem with having the stuffing here in that the
398         // larger the selection the slower LyX will get. This can be
399         // solved by running the line below only when the selection has
400         // finished. The solution used currently just works, to make it
401         // faster we need to be more clever and probably also have more
402         // calls to stuffClipboard. (Lgb)
403         cur.bv().stuffClipboard(cur.selectionAsString(true));
404
405         // This doesn't make sense, if there is no selection
406         if (!cur.selection())
407                 return;
408
409         // OK, we have a selection. This is always between cur.selBegin()
410         // and cur.selEnd()
411
412         // make sure that the depth behind the selection are restored, too
413         recordUndoSelection(cur);
414         par_type begpit = cur.selBegin().par();
415         par_type endpit = cur.selEnd().par();
416
417         int endpos = cur.selEnd().pos();
418
419         BufferParams const & bufparams = cur.bv().buffer()->params();
420         boost::tie(endpit, endpos) = realcut ?
421                 cutSelection(bufparams,
422                                           text->paragraphs(),
423                                           begpit, endpit,
424                                           cur.selBegin().pos(), endpos,
425                                           bufparams.textclass,
426                                           doclear)
427                 : eraseSelection(bufparams,
428                                               text->paragraphs(),
429                                               begpit, endpit,
430                                               cur.selBegin().pos(), endpos,
431                                               doclear);
432         // sometimes necessary
433         if (doclear)
434                 text->paragraphs()[begpit].stripLeadingSpaces();
435
436         text->redoParagraphs(begpit, begpit + 1);
437         // cutSelection can invalidate the cursor so we need to set
438         // it anew. (Lgb)
439         // we prefer the end for when tracking changes
440         cur.pos() = endpos;
441         cur.par() = endpit;
442
443         // need a valid cursor. (Lgb)
444         cur.clearSelection();
445         text->updateCounters();
446 }
447
448
449 void copySelection(LCursor & cur)
450 {
451         LyXText * text = cur.text();
452         BOOST_ASSERT(text);
453         // stuff the selection onto the X clipboard, from an explicit copy request
454         cur.bv().stuffClipboard(cur.selectionAsString(true));
455
456         // this doesn't make sense, if there is no selection
457         if (!cur.selection())
458                 return;
459
460         // ok we have a selection. This is always between cur.selBegin()
461         // and sel_end cursor
462
463         // copy behind a space if there is one
464         ParagraphList & pars = text->paragraphs();
465         pos_type pos = cur.selBegin().pos();
466         par_type par = cur.selBegin().par();
467         while (pos < pars[par].size()
468                && pars[par].isLineSeparator(pos)
469                && (par != cur.selEnd().par() || pos < cur.selEnd().pos()))
470                 ++pos;
471
472         copySelection(pars, par, cur.selEnd().par(),
473                 pos, cur.selEnd().pos(), cur.bv().buffer()->params().textclass);
474 }
475
476
477 void pasteSelection(LCursor & cur, size_t sel_index)
478 {
479         LyXText * text = cur.text();
480         BOOST_ASSERT(text);
481         // this does not make sense, if there is nothing to paste
482         if (!checkPastePossible())
483                 return;
484
485         recordUndo(cur);
486
487         par_type endpit;
488         PitPosPair ppp;
489
490         ErrorList el;
491
492         boost::tie(ppp, endpit) =
493                 pasteSelection(*cur.bv().buffer(),
494                                             text->paragraphs(),
495                                             cur.par(), cur.pos(),
496                                             cur.bv().buffer()->params().textclass,
497                                             sel_index, el);
498         bufferErrors(*cur.bv().buffer(), el);
499         text->bv()->showErrorList(_("Paste"));
500
501         text->redoParagraphs(cur.par(), endpit);
502
503         cur.clearSelection();
504         cur.resetAnchor();
505         text->setCursor(cur, ppp.first, ppp.second);
506         cur.setSelection();
507         text->updateCounters();
508 }
509
510
511 void setSelectionRange(LCursor & cur, pos_type length)
512 {
513         LyXText * text = cur.text();
514         BOOST_ASSERT(text);
515         if (!length)
516                 return;
517         cur.resetAnchor();
518         while (length--)
519                 text->cursorRight(cur);
520         cur.setSelection();
521 }
522
523
524 // simple replacing. The font of the first selected character is used
525 void replaceSelectionWithString(LCursor & cur, string const & str)
526 {
527         LyXText * text = cur.text();
528         BOOST_ASSERT(text);
529         recordUndo(cur);
530
531         // Get font setting before we cut
532         pos_type pos = cur.selEnd().pos();
533         LyXFont const font = text->getPar(cur.selBegin().par()).
534                 getFontSettings(cur.bv().buffer()->params(), cur.selBegin().pos());
535
536         // Insert the new string
537         string::const_iterator cit = str.begin();
538         string::const_iterator end = str.end();
539         for (; cit != end; ++cit, ++pos)
540                 text->getPar(cur.selEnd().par()).insertChar(pos, (*cit), font);
541
542         // Cut the selection
543         cutSelection(cur, true, false);
544 }
545
546
547 void replaceSelection(LCursor & cur)
548 {
549         if (cur.selection())
550                 cutSelection(cur, true, false);
551 }
552
553
554 // only used by the spellchecker
555 void replaceWord(LCursor & cur, string const & replacestring)
556 {
557         LyXText * text = cur.text();
558         BOOST_ASSERT(text);
559
560         replaceSelectionWithString(cur, replacestring);
561         setSelectionRange(cur, replacestring.length());
562
563         // Go back so that replacement string is also spellchecked
564         for (string::size_type i = 0; i < replacestring.length() + 1; ++i)
565                 text->cursorLeft(cur);
566 }
567
568
569 } // namespace cap
570 } // namespace lyx