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