]> git.lyx.org Git - lyx.git/blob - src/CutAndPaste.C
Ensure that the build can find lyx_forms.h when srcdir != builddir.
[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 == pars.size() || (startpos > pars[startpit].size()))
143                 return PitPosPair(endpit, endpos);
144
145         if (endpit == pars.size() || startpit == endpit) {
146                 endpos -= pars[startpit].erase(startpos, endpos);
147                 return PitPosPair(endpit, endpos);
148         }
149
150         // clear end/begin fragments of the first/last par in selection
151         bool all_erased = true;
152
153         pars[startpit].erase(startpos, pars[startpit].size());
154         if (pars[startpit].size() != startpos)
155                 all_erased = false;
156
157         endpos -= pars[endpit].erase(0, endpos);
158         if (endpos != 0)
159                 all_erased = false;
160
161         // Loop through the deleted pars if any, erasing as needed
162
163         par_type pit = startpit + 1;
164
165         while (pit != endpit && pit != pars.size()) {
166                 par_type const next = pit + 1;
167                 // "erase" the contents of the par
168                 pars[pit].erase(0, pars[pit].size());
169                 if (!pars[pit].size()) {
170                         // remove the par if it's now empty
171                         pars.erase(pars.begin() + pit);
172                 } else
173                         all_erased = false;
174                 pit = next;
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 == 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 == 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         par_type last_paste = pit + insertion.size();
335
336         // If we only inserted one paragraph.
337         if (insertion.size() == 1)
338                 last_paste = pit;
339
340         mergeParagraph(buffer.params(), pars, pit);
341
342         // Store the new cursor position.
343         pit = last_paste;
344         pos = pars[last_paste].size();
345
346         // Maybe some pasting.
347         if (did_split && last_paste + 1 != pars.size()) {
348                 if (pars[last_paste + 1].hasSameLayout(pars[last_paste])) {
349                         mergeParagraph(buffer.params(), pars, last_paste);
350                 } else if (pars[last_paste + 1].empty()) {
351                         pars[last_paste + 1].makeSameLayout(pars[last_paste]);
352                         mergeParagraph(buffer.params(), pars, last_paste);
353                 } else if (pars[last_paste].empty()) {
354                         pars[last_paste].makeSameLayout(pars[last_paste]);
355                         mergeParagraph(buffer.params(), pars, last_paste);
356                 } else
357                         pars[last_paste + 1].stripLeadingSpaces();
358         }
359
360         return make_pair(PitPosPair(pit, pos), pit + insertion.size() + 1);
361 }
362
363
364 pair<PitPosPair, par_type>
365 pasteSelection(Buffer const & buffer, ParagraphList & pars,
366         par_type pit, int pos, textclass_type tc, ErrorList & errorlist)
367 {
368         return pasteSelection(buffer, pars, pit, pos, tc, 0, errorlist);
369 }
370
371
372 int nrOfParagraphs()
373 {
374         return cuts.empty() ? 0 : cuts[0].first.size();
375 }
376
377
378 bool checkPastePossible()
379 {
380         return !cuts.empty() && !cuts[0].first.empty();
381 }
382
383
384 void cutSelection(LCursor & cur, bool doclear, bool realcut)
385 {
386         LyXText * text = cur.text();
387         BOOST_ASSERT(text);
388         // Stuff what we got on the clipboard. Even if there is no selection.
389
390         // There is a problem with having the stuffing here in that the
391         // larger the selection the slower LyX will get. This can be
392         // solved by running the line below only when the selection has
393         // finished. The solution used currently just works, to make it
394         // faster we need to be more clever and probably also have more
395         // calls to stuffClipboard. (Lgb)
396         cur.bv().stuffClipboard(cur.selectionAsString(true));
397
398         // This doesn't make sense, if there is no selection
399         if (!cur.selection())
400                 return;
401
402         // OK, we have a selection. This is always between cur.selBegin()
403         // and cur.selEnd()
404
405         // make sure that the depth behind the selection are restored, too
406         recordUndoSelection(cur);
407         par_type begpit = cur.selBegin().par();
408         par_type endpit = cur.selEnd().par();
409
410         int endpos = cur.selEnd().pos();
411
412         BufferParams const & bufparams = cur.bv().buffer()->params();
413         boost::tie(endpit, endpos) = realcut ?
414                 cutSelection(bufparams,
415                                           text->paragraphs(),
416                                           begpit, endpit,
417                                           cur.selBegin().pos(), endpos,
418                                           bufparams.textclass,
419                                           doclear)
420                 : eraseSelection(bufparams,
421                                               text->paragraphs(),
422                                               begpit, endpit,
423                                               cur.selBegin().pos(), endpos,
424                                               doclear);
425         // sometimes necessary
426         if (doclear)
427                 text->paragraphs()[begpit].stripLeadingSpaces();
428
429         text->redoParagraphs(begpit, begpit + 1);
430         // cutSelection can invalidate the cursor so we need to set
431         // it anew. (Lgb)
432         // we prefer the end for when tracking changes
433         cur.pos() = endpos;
434         cur.par() = endpit;
435
436         // need a valid cursor. (Lgb)
437         cur.clearSelection();
438         text->updateCounters();
439 }
440
441
442 void copySelection(LCursor & cur)
443 {
444         LyXText * text = cur.text();
445         BOOST_ASSERT(text);
446         // stuff the selection onto the X clipboard, from an explicit copy request
447         cur.bv().stuffClipboard(cur.selectionAsString(true));
448
449         // this doesn't make sense, if there is no selection
450         if (!cur.selection())
451                 return;
452
453         // ok we have a selection. This is always between cur.selBegin()
454         // and sel_end cursor
455
456         // copy behind a space if there is one
457         ParagraphList & pars = text->paragraphs();
458         pos_type pos = cur.selBegin().pos();
459         par_type par = cur.selBegin().par();
460         while (pos < pars[par].size()
461                && pars[par].isLineSeparator(pos)
462                && (par != cur.selEnd().par() || pos < cur.selEnd().pos()))
463                 ++pos;
464
465         copySelection(pars, par, cur.selEnd().par(),
466                 pos, cur.selEnd().pos(), cur.bv().buffer()->params().textclass);
467 }
468
469
470 void pasteSelection(LCursor & cur, size_t sel_index)
471 {
472         LyXText * text = cur.text();
473         BOOST_ASSERT(text);
474         // this does not make sense, if there is nothing to paste
475         if (!checkPastePossible())
476                 return;
477
478         recordUndo(cur);
479
480         par_type endpit;
481         PitPosPair ppp;
482
483         ErrorList el;
484
485         boost::tie(ppp, endpit) =
486                 pasteSelection(*cur.bv().buffer(),
487                                             text->paragraphs(),
488                                             cur.par(), cur.pos(),
489                                             cur.bv().buffer()->params().textclass,
490                                             sel_index, el);
491         bufferErrors(*cur.bv().buffer(), el);
492         text->bv()->showErrorList(_("Paste"));
493
494         text->redoParagraphs(cur.par(), endpit);
495
496         cur.clearSelection();
497         cur.resetAnchor();
498         text->setCursor(cur, ppp.first, ppp.second);
499         cur.setSelection();
500         text->updateCounters();
501 }
502
503
504 void setSelectionRange(LCursor & cur, pos_type length)
505 {
506         LyXText * text = cur.text();
507         BOOST_ASSERT(text);
508         if (!length)
509                 return;
510         cur.resetAnchor();
511         while (length--)
512                 text->cursorRight(cur);
513         cur.setSelection();
514 }
515
516
517 // simple replacing. The font of the first selected character is used
518 void replaceSelectionWithString(LCursor & cur, string const & str)
519 {
520         LyXText * text = cur.text();
521         BOOST_ASSERT(text);
522         recordUndo(cur);
523
524         // Get font setting before we cut
525         pos_type pos = cur.selEnd().pos();
526         LyXFont const font = text->getPar(cur.selBegin().par()).
527                 getFontSettings(cur.bv().buffer()->params(), cur.selBegin().pos());
528
529         // Insert the new string
530         string::const_iterator cit = str.begin();
531         string::const_iterator end = str.end();
532         for (; cit != end; ++cit, ++pos)
533                 text->getPar(cur.selEnd().par()).insertChar(pos, (*cit), font);
534
535         // Cut the selection
536         cutSelection(cur, true, false);
537 }
538
539
540 void replaceSelection(LCursor & cur)
541 {
542         if (cur.selection())
543                 cutSelection(cur, true, false);
544 }
545
546
547 // only used by the spellchecker
548 void replaceWord(LCursor & cur, string const & replacestring)
549 {
550         LyXText * text = cur.text();
551         BOOST_ASSERT(text);
552
553         replaceSelectionWithString(cur, replacestring);
554         setSelectionRange(cur, replacestring.length());
555
556         // Go back so that replacement string is also spellchecked
557         for (string::size_type i = 0; i < replacestring.length() + 1; ++i)
558                 text->cursorLeft(cur);
559 }
560
561
562 } // namespace cap
563 } // namespace lyx