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