]> git.lyx.org Git - lyx.git/blob - src/CutAndPaste.cpp
Track change of label name
[lyx.git] / src / CutAndPaste.cpp
1 /**
2  * \file CutAndPaste.cpp
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  * \author Michael Gerz
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "CutAndPaste.h"
17
18 #include "BranchList.h"
19 #include "Buffer.h"
20 #include "buffer_funcs.h"
21 #include "BufferList.h"
22 #include "BufferParams.h"
23 #include "BufferView.h"
24 #include "Changes.h"
25 #include "Cursor.h"
26 #include "Encoding.h"
27 #include "ErrorList.h"
28 #include "FuncCode.h"
29 #include "FuncRequest.h"
30 #include "InsetIterator.h"
31 #include "InsetList.h"
32 #include "Language.h"
33 #include "LyX.h"
34 #include "LyXRC.h"
35 #include "Text.h"
36 #include "Paragraph.h"
37 #include "ParagraphParameters.h"
38 #include "ParIterator.h"
39 #include "TextClass.h"
40
41 #include "insets/InsetBibitem.h"
42 #include "insets/InsetBranch.h"
43 #include "insets/InsetCitation.h"
44 #include "insets/InsetCommand.h"
45 #include "insets/InsetFlex.h"
46 #include "insets/InsetGraphics.h"
47 #include "insets/InsetGraphicsParams.h"
48 #include "insets/InsetInclude.h"
49 #include "insets/InsetLabel.h"
50 #include "insets/InsetTabular.h"
51
52 #include "mathed/MathData.h"
53 #include "mathed/InsetMath.h"
54 #include "mathed/InsetMathHull.h"
55 #include "mathed/InsetMathRef.h"
56 #include "mathed/MathSupport.h"
57
58 #include "support/debug.h"
59 #include "support/docstream.h"
60 #include "support/gettext.h"
61 #include "support/lassert.h"
62 #include "support/limited_stack.h"
63 #include "support/lstrings.h"
64 #include "support/lyxalgo.h"
65 #include "support/TempFile.h"
66 #include "support/unique_ptr.h"
67
68 #include "frontends/alert.h"
69 #include "frontends/Clipboard.h"
70 #include "frontends/Selection.h"
71
72 #include <string>
73 #include <tuple>
74
75 using namespace std;
76 using namespace lyx::support;
77 using lyx::frontend::Clipboard;
78
79 namespace lyx {
80
81 namespace {
82
83 typedef pair<pit_type, int> PitPosPair;
84
85 typedef limited_stack<pair<ParagraphList, DocumentClassConstPtr> > CutStack;
86
87 CutStack theCuts(10);
88 // persistent selection, cleared until the next selection
89 CutStack selectionBuffer(1);
90 // temporary scratch area
91 CutStack tempCut(1);
92
93 // store whether the tabular stack is newer than the normal copy stack
94 // FIXME: this is a workaround for bug 1919. Should be removed for 1.5,
95 // when we (hopefully) have a one-for-all paste mechanism.
96 bool dirty_tabular_stack_ = false;
97
98
99 bool checkPastePossible(int index)
100 {
101         return size_t(index) < theCuts.size() && !theCuts[index].first.empty();
102 }
103
104
105 struct PasteReturnValue {
106         PasteReturnValue(pit_type r_pit, pos_type r_pos, bool r_nu) :
107           pit(r_pit), pos(r_pos), needupdate(r_nu)
108         {}
109
110         pit_type pit;
111         pos_type pos;
112         bool needupdate;
113 };
114
115 PasteReturnValue
116 pasteSelectionHelper(DocIterator const & cur, ParagraphList const & parlist,
117                      DocumentClassConstPtr oldDocClass, cap::BranchAction branchAction,
118                      ErrorList & errorlist)
119 {
120         Buffer const & buffer = *cur.buffer();
121         pit_type pit = cur.pit();
122         pos_type pos = cur.pos();
123         bool need_update = false;
124
125         if (parlist.empty())
126                 return PasteReturnValue(pit, pos, need_update);
127
128         // Check whether we paste into an inset that does not
129         // produce output (needed for label duplicate check)
130         bool in_active_inset = cur.paragraph().inInset().producesOutput();
131         if (in_active_inset) {
132                 for (size_type sl = 0 ; sl < cur.depth() ; ++sl) {
133                         Paragraph const & outer_par = cur[sl].paragraph();
134                         if (!outer_par.inInset().producesOutput()) {
135                                 in_active_inset = false;
136                                 break;
137                         }
138                 }
139         }
140
141         InsetText * target_inset = cur.inset().asInsetText();
142         if (!target_inset) {
143                 InsetTabular * it = cur.inset().asInsetTabular();
144                 target_inset = it ? it->cell(cur.idx())->asInsetText() : nullptr;
145         }
146         LASSERT(target_inset, return PasteReturnValue(pit, pos, need_update));
147
148         ParagraphList & pars = target_inset->paragraphs();
149         LASSERT(pos <= pars[pit].size(),
150                         return PasteReturnValue(pit, pos, need_update));
151
152         // Make a copy of the CaP paragraphs.
153         ParagraphList insertion = parlist;
154
155         // Now remove all out of the pars which is NOT allowed in the
156         // new environment and set also another font if that is required.
157
158         // Merge paragraphs that are to be pasted into a text inset
159         // that does not allow multiple pars.
160         InsetText * inset_text = target_inset->asInsetText();
161         if (inset_text && !inset_text->allowMultiPar()) {
162                 while (insertion.size() > 1)
163                         mergeParagraph(buffer.params(), insertion, 0);
164         }
165
166         // Convert newline to paragraph break in ParbreakIsNewline
167         if (target_inset->getLayout().parbreakIsNewline()
168             || pars[pit].layout().parbreak_is_newline) {
169                 for (size_t i = 0; i != insertion.size(); ++i) {
170                         for (pos_type j = 0; j != insertion[i].size(); ++j) {
171                                 if (insertion[i].isNewline(j)) {
172                                         // do not track deletion of newline
173                                         insertion[i].eraseChar(j, false);
174                                         insertion[i].setInsetOwner(target_inset);
175                                         breakParagraphConservative(
176                                                         buffer.params(),
177                                                         insertion, i, j);
178                                         break;
179                                 }
180                         }
181                 }
182         }
183
184         // Prevent to paste uncodable characters in verbatim and ERT.
185         // The encoding is inherited from the context here.
186         docstring uncodable_content;
187         if (target_inset->getLayout().isPassThru() && cur.getEncoding()) {
188                 odocstringstream res;
189                 Encoding const * e = cur.getEncoding();
190                 for (size_t i = 0; i != insertion.size(); ++i) {
191                         pos_type end = insertion[i].size();
192                         for (pos_type j = 0; j != end; ++j) {
193                                 // skip insets
194                                 if (insertion[i].isInset(j))
195                                         continue;
196                                 char_type const c = insertion[i].getChar(j);
197                                 if (!e->encodable(c)) {
198                                         // do not track deletion
199                                         res.put(c);
200                                         insertion[i].eraseChar(j, false);
201                                         --end;
202                                         --j;
203                                 }
204                         }
205                 }
206                 docstring const uncodable = res.str();
207                 if (!uncodable.empty()) {
208                         if (uncodable.size() == 1)
209                                 uncodable_content = bformat(_("The character \"%1$s\" is uncodable in this verbatim context "
210                                                       "and thus has not been pasted."),
211                                                     uncodable);
212                         else
213                                 uncodable_content = bformat(_("The characters \"%1$s\" are uncodable in this verbatim context "
214                                                       "and thus have not been pasted."),
215                                                     uncodable);
216                 }
217         }
218
219         // set the paragraphs to plain layout if necessary
220         DocumentClassConstPtr newDocClass = buffer.params().documentClassPtr();
221         if (cur.inset().usePlainLayout()) {
222                 bool forcePlainLayout = target_inset->forcePlainLayout();
223                 Layout const & plainLayout = newDocClass->plainLayout();
224                 Layout const & defaultLayout = newDocClass->defaultLayout();
225                 ParagraphList::iterator const end = insertion.end();
226                 ParagraphList::iterator par = insertion.begin();
227                 for (; par != end; ++par) {
228                         Layout const & parLayout = par->layout();
229                         if (forcePlainLayout || parLayout == defaultLayout)
230                                 par->setLayout(plainLayout);
231                 }
232         } else {
233                 // check if we need to reset from plain layout
234                 Layout const & defaultLayout = newDocClass->defaultLayout();
235                 Layout const & plainLayout = newDocClass->plainLayout();
236                 ParagraphList::iterator const end = insertion.end();
237                 ParagraphList::iterator par = insertion.begin();
238                 for (; par != end; ++par) {
239                         Layout const & parLayout = par->layout();
240                         if (parLayout == plainLayout)
241                                 par->setLayout(defaultLayout);
242                 }
243         }
244
245         InsetText in(cur.buffer());
246         // Make sure there is no class difference.
247         in.paragraphs().clear();
248         // This works without copying any paragraph data because we have
249         // a specialized swap method for ParagraphList. This is important
250         // since we store pointers to insets at some places and we don't
251         // want to invalidate them.
252         insertion.swap(in.paragraphs());
253         cap::switchBetweenClasses(oldDocClass, newDocClass, in, errorlist);
254         // Do this here since switchBetweenClasses clears the errorlist
255         if (!uncodable_content.empty())
256                 errorlist.push_back(ErrorItem(_("Uncodable content"), uncodable_content));
257         insertion.swap(in.paragraphs());
258
259         ParagraphList::iterator tmpbuf = insertion.begin();
260         int depth_delta = pars[pit].params().depth() - tmpbuf->params().depth();
261
262         depth_type max_depth = pars[pit].getMaxDepthAfter();
263
264         for (; tmpbuf != insertion.end(); ++tmpbuf) {
265                 // If we have a negative jump so that the depth would
266                 // go below 0 depth then we have to redo the delta to
267                 // this new max depth level so that subsequent
268                 // paragraphs are aligned correctly to this paragraph
269                 // at level 0.
270                 if (int(tmpbuf->params().depth()) + depth_delta < 0)
271                         depth_delta = 0;
272
273                 // Set the right depth so that we are not too deep or shallow.
274                 tmpbuf->params().depth(tmpbuf->params().depth() + depth_delta);
275                 if (tmpbuf->params().depth() > max_depth)
276                         tmpbuf->params().depth(max_depth);
277
278                 // Set max_depth for the next paragraph
279                 max_depth = tmpbuf->getMaxDepthAfter();
280
281                 // Set the inset owner of this paragraph.
282                 tmpbuf->setInsetOwner(target_inset);
283                 for (pos_type i = 0; i < tmpbuf->size(); ++i) {
284                         // do not track deletion of invalid insets
285                         if (Inset * inset = tmpbuf->getInset(i))
286                                 if (!target_inset->insetAllowed(inset->lyxCode()))
287                                         tmpbuf->eraseChar(i--, false);
288                 }
289
290                 tmpbuf->setChange(Change(buffer.params().track_changes ?
291                                          Change::INSERTED : Change::UNCHANGED));
292         }
293
294         bool const empty = pars[pit].empty();
295         if (!empty) {
296                 // Make the buf exactly the same layout as the cursor
297                 // paragraph.
298                 insertion.begin()->makeSameLayout(pars[pit]);
299         }
300
301         // Prepare the paragraphs and insets for insertion.
302         insertion.swap(in.paragraphs());
303
304         InsetIterator const i_end = inset_iterator_end(in);
305         for (InsetIterator it = inset_iterator_begin(in); it != i_end; ++it) {
306                 // Even though this will also be done later, it has to be done here
307                 // since some inset might try to access the buffer() member.
308                 it->setBuffer(const_cast<Buffer &>(buffer));
309                 switch (it->lyxCode()) {
310
311                 case MATH_HULL_CODE: {
312                         // check for equation labels and resolve duplicates
313                         InsetMathHull * ins = it->asInsetMath()->asHullInset();
314                         std::vector<InsetLabel *> labels = ins->getLabels();
315                         for (size_t i = 0; i != labels.size(); ++i) {
316                                 if (!labels[i])
317                                         continue;
318                                 InsetLabel * lab = labels[i];
319                                 docstring const oldname = lab->getParam("name");
320                                 lab->updateLabel(oldname, in_active_inset);
321                                 // We need to update the buffer reference cache.
322                                 need_update = true;
323                                 docstring const newname = lab->getParam("name");
324                                 if (oldname == newname)
325                                         continue;
326                                 // adapt the references
327                                 for (InsetIterator itt = inset_iterator_begin(in);
328                                       itt != i_end; ++itt) {
329                                         if (itt->lyxCode() == REF_CODE) {
330                                                 InsetCommand * ref = itt->asInsetCommand();
331                                                 if (ref->getParam("reference") == oldname)
332                                                         ref->setParam("reference", newname);
333                                         } else if (itt->lyxCode() == MATH_REF_CODE) {
334                                                 InsetMathRef * mi = itt->asInsetMath()->asRefInset();
335                                                 // this is necessary to prevent an uninitialized
336                                                 // buffer when the RefInset is in a MathBox.
337                                                 // FIXME audit setBuffer calls
338                                                 mi->setBuffer(const_cast<Buffer &>(buffer));
339                                                 if (mi->getTarget() == oldname)
340                                                         mi->changeTarget(newname);
341                                         }
342                                 }
343                         }
344                         break;
345                 }
346
347                 case LABEL_CODE: {
348                         // check for duplicates
349                         InsetLabel & lab = static_cast<InsetLabel &>(*it);
350                         docstring const oldname = lab.getParam("name");
351                         lab.updateLabel(oldname, in_active_inset);
352                         // We need to update the buffer reference cache.
353                         need_update = true;
354                         docstring const newname = lab.getParam("name");
355                         if (oldname == newname)
356                                 break;
357                         // adapt the references
358                         for (InsetIterator itt = inset_iterator_begin(in); itt != i_end; ++itt) {
359                                 if (itt->lyxCode() == REF_CODE) {
360                                         InsetCommand & ref = static_cast<InsetCommand &>(*itt);
361                                         if (ref.getParam("reference") == oldname)
362                                                 ref.setParam("reference", newname);
363                                 } else if (itt->lyxCode() == MATH_REF_CODE) {
364                                         InsetMathRef * mi = itt->asInsetMath()->asRefInset();
365                                         // this is necessary to prevent an uninitialized
366                                         // buffer when the RefInset is in a MathBox.
367                                         // FIXME audit setBuffer calls
368                                         mi->setBuffer(const_cast<Buffer &>(buffer));
369                                         if (mi->getTarget() == oldname)
370                                                 mi->changeTarget(newname);
371                                 }
372                         }
373                         break;
374                 }
375
376                 case INCLUDE_CODE: {
377                         InsetInclude & inc = static_cast<InsetInclude &>(*it);
378                         inc.updateCommand();
379                         // We need to update the list of included files.
380                         need_update = true;
381                         break;
382                 }
383
384                 case CITE_CODE: {
385                         InsetCitation & cit = static_cast<InsetCitation &>(*it);
386                         // This actually only needs to be done if the cite engine
387                         // differs, but we do it in general.
388                         cit.redoLabel();
389                         // We need to update the list of citations.
390                         need_update = true;
391                         break;
392                 }
393
394                 case BIBITEM_CODE: {
395                         // check for duplicates
396                         InsetBibitem & bib = static_cast<InsetBibitem &>(*it);
397                         docstring const oldkey = bib.getParam("key");
398                         bib.updateCommand(oldkey, false);
399                         // We need to update the buffer reference cache.
400                         need_update = true;
401                         docstring const newkey = bib.getParam("key");
402                         if (oldkey == newkey)
403                                 break;
404                         // adapt the references
405                         for (InsetIterator itt = inset_iterator_begin(in);
406                              itt != i_end; ++itt) {
407                                 if (itt->lyxCode() == CITE_CODE) {
408                                         InsetCommand * ref = itt->asInsetCommand();
409                                         if (ref->getParam("key") == oldkey)
410                                                 ref->setParam("key", newkey);
411                                 }
412                         }
413                         break;
414                 }
415
416                 case BRANCH_CODE: {
417                         // check if branch is known to target buffer
418                         // or its master
419                         InsetBranch & br = static_cast<InsetBranch &>(*it);
420                         docstring const name = br.branch();
421                         if (name.empty())
422                                 break;
423                         bool const is_child = (&buffer != buffer.masterBuffer());
424                         BranchList branchlist = buffer.params().branchlist();
425                         if ((!is_child && branchlist.find(name))
426                             || (is_child && (branchlist.find(name)
427                                 || buffer.masterBuffer()->params().branchlist().find(name))))
428                                 break;
429                         switch(branchAction) {
430                         case cap::BRANCH_ADD: {
431                                 // This is for a temporary buffer, so simply create the branch.
432                                 // Must not use lyx::dispatch(), since tmpbuffer has no view.
433                                 DispatchResult dr;
434                                 const_cast<Buffer&>(buffer).dispatch(FuncRequest(LFUN_BRANCH_ADD, name), dr);
435                                 break;
436                         }
437                         case cap::BRANCH_ASK: {
438                                 docstring text = bformat(
439                                         _("The pasted branch \"%1$s\" is undefined.\n"
440                                           "Do you want to add it to the document's branch list?"),
441                                         name);
442                                 if (frontend::Alert::prompt(_("Unknown branch"),
443                                           text, 0, 1, _("&Add"), _("&Don't Add")) != 0)
444                                         break;
445                                 lyx::dispatch(FuncRequest(LFUN_BRANCH_ADD, name));
446                                 break;
447                         }
448                         case cap::BRANCH_IGNORE:
449                                 break;
450                         }
451                         // We need to update the list of branches.
452                         need_update = true;
453                         break;
454                 }
455
456                 default:
457                         break; // nothing
458                 }
459         }
460         insertion.swap(in.paragraphs());
461
462         // Split the paragraph for inserting the buf if necessary.
463         if (!empty)
464                 breakParagraphConservative(buffer.params(), pars, pit, pos);
465
466         // Paste it!
467         if (empty) {
468                 pars.insert(lyx::next(pars.begin(), pit),
469                             insertion.begin(),
470                             insertion.end());
471
472                 // merge the empty par with the last par of the insertion
473                 mergeParagraph(buffer.params(), pars,
474                                pit + insertion.size() - 1);
475         } else {
476                 pars.insert(lyx::next(pars.begin(), pit + 1),
477                             insertion.begin(),
478                             insertion.end());
479
480                 // merge the first par of the insertion with the current par
481                 mergeParagraph(buffer.params(), pars, pit);
482         }
483
484         // Store the new cursor position.
485         pit_type last_paste = pit + insertion.size() - 1;
486         pit_type startpit = pit;
487         pit = last_paste;
488         pos = pars[last_paste].size();
489
490         // FIXME Should we do it here, or should we let updateBuffer() do it?
491         // Set paragraph buffers. It's important to do this right away
492         // before something calls Inset::buffer() and causes a crash.
493         for (pit_type p = startpit; p <= pit; ++p)
494                 pars[p].setInsetBuffers(const_cast<Buffer &>(buffer));
495
496         // Join (conditionally) last pasted paragraph with next one, i.e.,
497         // the tail of the spliced document paragraph
498         if (!empty && last_paste + 1 != pit_type(pars.size())) {
499                 if (pars[last_paste + 1].hasSameLayout(pars[last_paste])) {
500                         mergeParagraph(buffer.params(), pars, last_paste);
501                 } else if (pars[last_paste + 1].empty()) {
502                         pars[last_paste + 1].makeSameLayout(pars[last_paste]);
503                         mergeParagraph(buffer.params(), pars, last_paste);
504                 } else if (pars[last_paste].empty()) {
505                         pars[last_paste].makeSameLayout(pars[last_paste + 1]);
506                         mergeParagraph(buffer.params(), pars, last_paste);
507                 } else {
508                         pars[last_paste + 1].stripLeadingSpaces(buffer.params().track_changes);
509                         ++last_paste;
510                 }
511         }
512
513         return PasteReturnValue(pit, pos, need_update);
514 }
515
516
517 PitPosPair eraseSelectionHelper(BufferParams const & params,
518         ParagraphList & pars,
519         pit_type startpit, pit_type endpit,
520         int startpos, int endpos)
521 {
522         // Start of selection is really invalid.
523         if (startpit == pit_type(pars.size()) ||
524             (startpos > pars[startpit].size()))
525                 return PitPosPair(endpit, endpos);
526
527         // Start and end is inside same paragraph
528         if (endpit == pit_type(pars.size()) || startpit == endpit) {
529                 endpos -= pars[startpit].eraseChars(startpos, endpos, params.track_changes);
530                 return PitPosPair(endpit, endpos);
531         }
532
533         for (pit_type pit = startpit; pit != endpit + 1;) {
534                 pos_type const left  = (pit == startpit ? startpos : 0);
535                 pos_type right = (pit == endpit ? endpos : pars[pit].size() + 1);
536                 bool const merge = pars[pit].isMergedOnEndOfParDeletion(params.track_changes);
537
538                 // Logically erase only, including the end-of-paragraph character
539                 pars[pit].eraseChars(left, right, params.track_changes);
540
541                 // Separate handling of paragraph break:
542                 if (merge && pit != endpit &&
543                     (pit + 1 != endpit
544                      || pars[pit].hasSameLayout(pars[endpit])
545                      || pars[endpit].size() == endpos)) {
546                         if (pit + 1 == endpit)
547                                 endpos += pars[pit].size();
548                         mergeParagraph(params, pars, pit);
549                         --endpit;
550                 } else
551                         ++pit;
552         }
553
554         // Ensure legal cursor pos:
555         endpit = startpit;
556         endpos = startpos;
557         return PitPosPair(endpit, endpos);
558 }
559
560
561 Buffer * copyToTempBuffer(ParagraphList const & paragraphs, DocumentClassConstPtr docclass)
562 {
563         // This used to need to be static to avoid a memory leak. It no longer needs
564         // to be so, but the alternative is to construct a new one of these (with a
565         // new temporary directory, etc) every time, and then to destroy it. So maybe
566         // it's worth just keeping this one around.
567         static TempFile tempfile("clipboard.internal");
568         tempfile.setAutoRemove(false);
569         // The initialization of staticbuffer is thread-safe. Using a lambda
570         // guarantees that the properties are set only once.
571         static Buffer * staticbuffer = [&](){
572                 Buffer * b =
573                         theBufferList().newInternalBuffer(tempfile.name().absFileName());
574                 b->setUnnamed(true);
575                 b->inset().setBuffer(*b);
576                 //initialize staticbuffer with b
577                 return b;
578         }();
579         // Use a clone for the complicated stuff so that we do not need to clean
580         // up in order to avoid a crash.
581         Buffer * buffer = staticbuffer->cloneBufferOnly();
582         LASSERT(buffer, return nullptr);
583
584         // This needs doing every time.
585         // Since setDocumentClass() causes deletion of the old document class
586         // we need to reset all layout pointers in paragraphs (otherwise they
587         // would be dangling).
588         ParIterator const end = buffer->par_iterator_end();
589         for (ParIterator it = buffer->par_iterator_begin(); it != end; ++it) {
590                 docstring const name = it->layout().name();
591                 if (docclass->hasLayout(name))
592                         it->setLayout((*docclass)[name]);
593                 else
594                         it->setPlainOrDefaultLayout(*docclass);
595         }
596         buffer->params().setDocumentClass(docclass);
597
598         // we will use pasteSelectionHelper to copy the paragraphs into the
599         // temporary Buffer, since it does a lot of things to fix them up.
600         DocIterator dit = doc_iterator_begin(buffer, &buffer->inset());
601         ErrorList el;
602         pasteSelectionHelper(dit, paragraphs, docclass, cap::BRANCH_ADD, el);
603
604         return buffer;
605 }
606
607
608 void putClipboard(ParagraphList const & paragraphs,
609         DocumentClassConstPtr docclass, docstring const & plaintext)
610 {
611         Buffer * buffer = copyToTempBuffer(paragraphs, docclass);
612         if (!buffer) // already asserted in copyToTempBuffer()
613                 return;
614
615         // We don't want to produce images that are not used. Therefore,
616         // output formulas as MathML. Even if this is not understood by all
617         // applications, the number that can parse it should go up in the future.
618         buffer->params().html_math_output = BufferParams::MathML;
619
620         // Make sure MarkAsExporting is deleted before buffer is
621         {
622                 // The Buffer is being used to export. This is necessary so that the
623                 // updateMacros call will record the needed information.
624                 MarkAsExporting mex(buffer);
625
626                 buffer->updateBuffer(Buffer::UpdateMaster, OutputUpdate);
627                 buffer->updateMacros();
628                 buffer->updateMacroInstances(OutputUpdate);
629
630                 // LyX's own format
631                 string lyx;
632                 ostringstream oslyx;
633                 if (buffer->write(oslyx))
634                         lyx = oslyx.str();
635
636                 // XHTML format
637                 odocstringstream oshtml;
638                 OutputParams runparams(encodings.fromLyXName("utf8"));
639                 // We do not need to produce images, etc.
640                 runparams.dryrun = true;
641                 // We are not interested in errors (bug 8866)
642                 runparams.silent = true;
643                 buffer->writeLyXHTMLSource(oshtml, runparams, Buffer::FullSource);
644
645                 theClipboard().put(lyx, oshtml.str(), plaintext);
646         }
647
648         // Save that memory
649         delete buffer;
650 }
651
652
653 /// return true if the whole ParagraphList is deleted
654 static bool isFullyDeleted(ParagraphList const & pars)
655 {
656         pit_type const pars_size = static_cast<pit_type>(pars.size());
657
658         // check all paragraphs
659         for (pit_type pit = 0; pit < pars_size; ++pit) {
660                 if (!pars[pit].empty())   // prevent assertion failure
661                         if (!pars[pit].isDeleted(0, pars[pit].size()))
662                                 return false;
663         }
664         return true;
665 }
666
667
668 void copySelectionHelper(Buffer const & buf, Text const & text,
669         pit_type startpit, pit_type endpit,
670         int start, int end, DocumentClassConstPtr dc, CutStack & cutstack)
671 {
672         ParagraphList const & pars = text.paragraphs();
673
674         // In most of these cases, we can try to recover.
675         LASSERT(0 <= start, start = 0);
676         LASSERT(start <= pars[startpit].size(), start = pars[startpit].size());
677         LASSERT(0 <= end, end = 0);
678         LASSERT(end <= pars[endpit].size(), end = pars[endpit].size());
679         LASSERT(startpit != endpit || start <= end, return);
680
681         // Clone the paragraphs within the selection.
682         ParagraphList copy_pars(lyx::next(pars.begin(), startpit),
683                                 lyx::next(pars.begin(), endpit + 1));
684
685         // Remove the end of the last paragraph; afterwards, remove the
686         // beginning of the first paragraph. Keep this order - there may only
687         // be one paragraph!  Do not track deletions here; this is an internal
688         // action not visible to the user
689
690         Paragraph & back = copy_pars.back();
691         back.eraseChars(end, back.size(), false);
692         Paragraph & front = copy_pars.front();
693         front.eraseChars(0, start, false);
694
695         ParagraphList::iterator it = copy_pars.begin();
696         ParagraphList::iterator it_end = copy_pars.end();
697
698         for (; it != it_end; ++it) {
699                 // Since we have a copy of the paragraphs, the insets
700                 // do not have a proper buffer reference. It makes
701                 // sense to add them temporarily, because the
702                 // operations below depend on that (acceptChanges included).
703                 it->setInsetBuffers(const_cast<Buffer &>(buf));
704                 // PassThru paragraphs have the Language
705                 // latex_language. This is invalid for others, so we
706                 // need to change it to the buffer language.
707                 if (it->isPassThru())
708                         it->changeLanguage(buf.params(),
709                                            latex_language, buf.language());
710         }
711
712         // do not copy text (also nested in insets) which is marked as
713         // deleted, unless the whole selection was deleted
714         if (!isFullyDeleted(copy_pars))
715                 acceptChanges(copy_pars, buf.params());
716         else
717                 rejectChanges(copy_pars, buf.params());
718
719
720         // do some final cleanup now, to make sure that the paragraphs
721         // are not linked to something else.
722         it = copy_pars.begin();
723         for (; it != it_end; ++it) {
724                 it->resetBuffer();
725                 it->setInsetOwner(nullptr);
726         }
727
728         cutstack.push(make_pair(copy_pars, dc));
729 }
730
731 } // namespace
732
733
734 namespace cap {
735
736 void region(CursorSlice const & i1, CursorSlice const & i2,
737             Inset::row_type & r1, Inset::row_type & r2,
738             Inset::col_type & c1, Inset::col_type & c2)
739 {
740         Inset & p = i1.inset();
741         c1 = p.col(i1.idx());
742         c2 = p.col(i2.idx());
743         if (c1 > c2)
744                 swap(c1, c2);
745         r1 = p.row(i1.idx());
746         r2 = p.row(i2.idx());
747         if (r1 > r2)
748                 swap(r1, r2);
749 }
750
751
752 docstring grabAndEraseSelection(Cursor & cur)
753 {
754         if (!cur.selection())
755                 return docstring();
756         docstring res = grabSelection(cur);
757         eraseSelection(cur);
758         return res;
759 }
760
761
762 bool reduceSelectionToOneCell(CursorData & cur)
763 {
764         if (!cur.selection() || !cur.inMathed())
765                 return false;
766
767         CursorSlice i1 = cur.selBegin();
768         CursorSlice i2 = cur.selEnd();
769         if (!i1.inset().asInsetMath())
770                 return false;
771
772         // the easy case: do nothing if only one cell is selected
773         if (i1.idx() == i2.idx())
774                 return true;
775
776         cur.top().pos() = 0;
777         cur.resetAnchor();
778         cur.top().pos() = cur.top().lastpos();
779
780         return true;
781 }
782
783
784 bool multipleCellsSelected(CursorData const & cur)
785 {
786         if (!cur.selection() || !cur.inMathed())
787                 return false;
788
789         CursorSlice i1 = cur.selBegin();
790         CursorSlice i2 = cur.selEnd();
791         if (!i1.inset().asInsetMath())
792                 return false;
793
794         if (i1.idx() == i2.idx())
795                 return false;
796
797         return true;
798 }
799
800
801 void switchBetweenClasses(DocumentClassConstPtr oldone,
802                 DocumentClassConstPtr newone, InsetText & in, ErrorList & errorlist)
803 {
804         errorlist.clear();
805
806         LBUFERR(!in.paragraphs().empty());
807         if (oldone == newone)
808                 return;
809
810         DocumentClass const & oldtc = *oldone;
811         DocumentClass const & newtc = *newone;
812
813         // layouts
814         ParIterator it = par_iterator_begin(in);
815         ParIterator end = par_iterator_end(in);
816         // for remembering which layouts we've had to add
817         set<docstring> newlayouts;
818         for (; it != end; ++it) {
819                 docstring const name = it->layout().name();
820
821                 // the pasted text will keep their own layout name. If this layout does
822                 // not exist in the new document, it will behave like a standard layout.
823                 bool const added_one = newtc.addLayoutIfNeeded(name);
824                 if (added_one)
825                         newlayouts.insert(name);
826
827                 if (added_one || newlayouts.find(name) != newlayouts.end()) {
828                         // Warn the user.
829                         docstring const s = bformat(_("Layout `%1$s' was not found."), name);
830                         errorlist.push_back(ErrorItem(_("Layout Not Found"), s,
831                                                       {it->id(), 0}, {it->id(), -1}));
832                 }
833
834                 if (in.usePlainLayout())
835                         it->setLayout(newtc.plainLayout());
836                 else
837                         it->setLayout(newtc[name]);
838         }
839
840         // character styles and hidden table cells
841         InsetIterator const i_end = inset_iterator_end(in);
842         for (InsetIterator iit = inset_iterator_begin(in); iit != i_end; ++iit) {
843                 InsetCode const code = iit->lyxCode();
844                 if (code == FLEX_CODE) {
845                         // FIXME: Should we verify all InsetCollapsible?
846                         docstring const layoutName = iit->layoutName();
847                         docstring const & n = newone->insetLayout(layoutName).name();
848                         bool const is_undefined = n.empty() ||
849                                 n == DocumentClass::plainInsetLayout().name();
850                         if (!is_undefined)
851                                 continue;
852
853                         // The flex inset is undefined in newtc
854                         docstring const oldname = from_utf8(oldtc.name());
855                         docstring const newname = from_utf8(newtc.name());
856                         docstring s;
857                         if (oldname == newname)
858                                 s = bformat(_("Flex inset %1$s is undefined after "
859                                         "reloading `%2$s' layout."), layoutName, oldname);
860                         else
861                                 s = bformat(_("Flex inset %1$s is undefined because of "
862                                         "conversion from `%2$s' layout to `%3$s'."),
863                                         layoutName, oldname, newname);
864                         // To warn the user that something had to be done.
865                         errorlist.push_back(ErrorItem(
866                                                       _("Undefined flex inset"), s,
867                                                       {iit.paragraph().id(), iit.pos()},
868                                                       {iit.paragraph().id(), iit.pos() + 1}));
869                 } else if (code == TABULAR_CODE) {
870                         // The recursion above does not catch paragraphs in "hidden" cells,
871                         // i.e., ones that are part of a multirow or multicolum. So we need
872                         // to handle those separately.
873                         // This is the cause of bug #9049.
874                         InsetTabular * table = iit->asInsetTabular();
875                         table->setLayoutForHiddenCells(newtc);
876                 }
877         }
878 }
879
880
881 vector<docstring> availableSelections(Buffer const * buf)
882 {
883         vector<docstring> selList;
884         if (!buf)
885                 return selList;
886
887         CutStack::const_iterator cit = theCuts.begin();
888         CutStack::const_iterator end = theCuts.end();
889         for (; cit != end; ++cit) {
890                 // we do not use cit-> here because gcc 2.9x does not
891                 // like it (JMarc)
892                 ParagraphList const & pars = (*cit).first;
893                 docstring textSel;
894                 ParagraphList::const_iterator pit = pars.begin();
895                 ParagraphList::const_iterator pend = pars.end();
896                 for (; pit != pend; ++pit) {
897                         Paragraph par(*pit, 0, 46);
898                         // adapt paragraph to current buffer.
899                         par.setInsetBuffers(const_cast<Buffer &>(*buf));
900                         textSel += par.asString(AS_STR_INSETS);
901                         if (textSel.size() > 45) {
902                                 support::truncateWithEllipsis(textSel,45);
903                                 break;
904                         }
905                 }
906                 selList.push_back(textSel);
907         }
908
909         return selList;
910 }
911
912
913 size_type numberOfSelections()
914 {
915         return theCuts.size();
916 }
917
918 namespace {
919
920 void cutSelectionHelper(Cursor & cur, CutStack & cuts, bool realcut, bool putclip)
921 {
922         // This doesn't make sense, if there is no selection
923         if (!cur.selection())
924                 return;
925
926         // OK, we have a selection. This is always between cur.selBegin()
927         // and cur.selEnd()
928
929         if (cur.inTexted()) {
930                 Text * text = cur.text();
931                 LBUFERR(text);
932
933                 saveSelection(cur);
934
935                 // make sure that the depth behind the selection are restored, too
936                 cur.recordUndoSelection();
937                 pit_type begpit = cur.selBegin().pit();
938                 pit_type endpit = cur.selEnd().pit();
939
940                 int endpos = cur.selEnd().pos();
941
942                 BufferParams const & bp = cur.buffer()->params();
943                 if (realcut) {
944                         copySelectionHelper(*cur.buffer(),
945                                 *text,
946                                 begpit, endpit,
947                                 cur.selBegin().pos(), endpos,
948                                 bp.documentClassPtr(), cuts);
949                         // Stuff what we got on the clipboard.
950                         // Even if there is no selection.
951                         if (putclip)
952                                 putClipboard(cuts[0].first, cuts[0].second,
953                                              cur.selectionAsString(true));
954                 }
955
956                 if (begpit != endpit)
957                         cur.screenUpdateFlags(Update::Force | Update::FitCursor);
958
959                 tie(endpit, endpos) =
960                         eraseSelectionHelper(bp, text->paragraphs(), begpit, endpit,
961                                              cur.selBegin().pos(), endpos);
962
963                 // cutSelection can invalidate the cursor so we need to set
964                 // it anew. (Lgb)
965                 // we prefer the end for when tracking changes
966                 cur.pos() = endpos;
967                 cur.pit() = endpit;
968
969                 // need a valid cursor. (Lgb)
970                 cur.clearSelection();
971
972                 // After a cut operation, we must make sure that the Buffer is updated
973                 // because some further operation might need updated label information for
974                 // example. So we cannot just use "cur.forceBufferUpdate()" here.
975                 // This fixes #7071.
976                 cur.buffer()->updateBuffer();
977
978                 // tell tabular that a recent copy happened
979                 dirtyTabularStack(false);
980         }
981
982         if (cur.inMathed()) {
983                 if (cur.selBegin().idx() != cur.selEnd().idx()) {
984                         // The current selection spans more than one cell.
985                         // Record all cells
986                         cur.recordUndoInset();
987                 } else {
988                         // Record only the current cell to avoid a jumping
989                         // cursor after undo
990                         cur.recordUndo();
991                 }
992                 if (realcut)
993                         copySelection(cur);
994                 eraseSelection(cur);
995         }
996 }
997
998 } // namespace
999
1000 void cutSelection(Cursor & cur, bool realcut)
1001 {
1002         cutSelectionHelper(cur, theCuts, realcut, true);
1003 }
1004
1005
1006 void cutSelectionToTemp(Cursor & cur, bool realcut)
1007 {
1008         cutSelectionHelper(cur, tempCut, realcut, false);
1009 }
1010
1011
1012 void copySelection(Cursor const & cur)
1013 {
1014         copySelection(cur, cur.selectionAsString(true));
1015 }
1016
1017
1018 void copyInset(Cursor const & cur, Inset * inset, docstring const & plaintext)
1019 {
1020         ParagraphList pars;
1021         Paragraph par;
1022         BufferParams const & bp = cur.buffer()->params();
1023         par.setLayout(bp.documentClass().plainLayout());
1024         Font font(inherit_font, bp.language);
1025         par.insertInset(0, inset, font, Change(Change::UNCHANGED));
1026         pars.push_back(par);
1027         theCuts.push(make_pair(pars, bp.documentClassPtr()));
1028
1029         // stuff the selection onto the X clipboard, from an explicit copy request
1030         putClipboard(theCuts[0].first, theCuts[0].second, plaintext);
1031 }
1032
1033
1034 namespace {
1035
1036 void copySelectionToStack(CursorData const & cur, CutStack & cutstack)
1037 {
1038         // this doesn't make sense, if there is no selection
1039         if (!cur.selection())
1040                 return;
1041
1042         // copySelection can not yet handle the case of cross idx selection
1043         if (cur.selBegin().idx() != cur.selEnd().idx())
1044                 return;
1045
1046         if (cur.inTexted()) {
1047                 Text * text = cur.text();
1048                 LBUFERR(text);
1049                 // ok we have a selection. This is always between cur.selBegin()
1050                 // and sel_end cursor
1051                 copySelectionHelper(*cur.buffer(), *text,
1052                                     cur.selBegin().pit(), cur.selEnd().pit(),
1053                                     cur.selBegin().pos(), cur.selEnd().pos(),
1054                                     cur.buffer()->params().documentClassPtr(),
1055                                     cutstack);
1056                 // Reset the dirty_tabular_stack_ flag only when something
1057                 // is copied to the clipboard (not to the selectionBuffer).
1058                 if (&cutstack == &theCuts)
1059                         dirtyTabularStack(false);
1060         }
1061
1062         if (cur.inMathed()) {
1063                 //lyxerr << "copySelection in mathed" << endl;
1064                 ParagraphList pars;
1065                 Paragraph par;
1066                 BufferParams const & bp = cur.buffer()->params();
1067                 // FIXME This should be the plain layout...right?
1068                 par.setLayout(bp.documentClass().plainLayout());
1069                 // For pasting into text, we set the language to the paragraph language
1070                 // (rather than the default_language which is always English; see #2596)
1071                 par.insert(0, grabSelection(cur), Font(sane_font, par.getParLanguage(bp)),
1072                            Change(Change::UNCHANGED));
1073                 pars.push_back(par);
1074                 cutstack.push(make_pair(pars, bp.documentClassPtr()));
1075         }
1076 }
1077
1078 } // namespace
1079
1080
1081 void copySelectionToStack()
1082 {
1083         if (!selectionBuffer.empty())
1084                 theCuts.push(selectionBuffer[0]);
1085 }
1086
1087
1088 void copySelectionToTemp(Cursor & cur)
1089 {
1090         copySelectionToStack(cur, tempCut);
1091 }
1092
1093
1094 void copySelection(Cursor const & cur, docstring const & plaintext)
1095 {
1096         // In tablemode, because copy and paste actually use special table stack
1097         // we do not attempt to get selected paragraphs under cursor. Instead, a
1098         // paragraph with the plain text version is generated so that table cells
1099         // can be pasted as pure text somewhere else.
1100         if (cur.selBegin().idx() != cur.selEnd().idx()) {
1101                 ParagraphList pars;
1102                 Paragraph par;
1103                 BufferParams const & bp = cur.buffer()->params();
1104                 par.setLayout(bp.documentClass().plainLayout());
1105                 par.insert(0, plaintext, Font(), Change(Change::UNCHANGED));
1106                 pars.push_back(par);
1107                 theCuts.push(make_pair(pars, bp.documentClassPtr()));
1108         } else {
1109                 copySelectionToStack(cur, theCuts);
1110         }
1111
1112         // stuff the selection onto the X clipboard, from an explicit copy request
1113         putClipboard(theCuts[0].first, theCuts[0].second, plaintext);
1114 }
1115
1116
1117 void saveSelection(Cursor const & cur)
1118 {
1119         // This function is called, not when a selection is formed, but when
1120         // a selection is cleared. Therefore, multiple keyboard selection
1121         // will not repeatively trigger this function (bug 3877).
1122         if (cur.selection()
1123             && cur.selBegin() == cur.bv().cursor().selBegin()
1124             && cur.selEnd() == cur.bv().cursor().selEnd()) {
1125                 LYXERR(Debug::SELECTION, "saveSelection: '" << cur.selectionAsString(true) << "'");
1126                 copySelectionToStack(cur, selectionBuffer);
1127         }
1128 }
1129
1130
1131 bool selection()
1132 {
1133         return !selectionBuffer.empty();
1134 }
1135
1136
1137 void clearSelection()
1138 {
1139         selectionBuffer.clear();
1140 }
1141
1142
1143 void clearCutStack()
1144 {
1145         theCuts.clear();
1146         tempCut.clear();
1147 }
1148
1149
1150 docstring selection(size_t sel_index, DocumentClassConstPtr docclass)
1151 {
1152         if (sel_index >= theCuts.size())
1153                 return docstring();
1154
1155         unique_ptr<Buffer> buffer(copyToTempBuffer(theCuts[sel_index].first,
1156                                                    docclass));
1157         if (!buffer)
1158                 return docstring();
1159
1160         return buffer->paragraphs().back().asString(AS_STR_INSETS | AS_STR_NEWLINES);
1161 }
1162
1163
1164 void pasteParagraphList(Cursor & cur, ParagraphList const & parlist,
1165                                                 DocumentClassConstPtr docclass, ErrorList & errorList,
1166                                                 cap::BranchAction branchAction)
1167 {
1168         if (cur.inTexted()) {
1169                 Text * text = cur.text();
1170                 LBUFERR(text);
1171
1172                 PasteReturnValue prv =
1173                         pasteSelectionHelper(cur, parlist, docclass, branchAction, errorList);
1174                 cur.forceBufferUpdate();
1175                 cur.clearSelection();
1176                 text->setCursor(cur, prv.pit, prv.pos);
1177         }
1178
1179         // mathed is handled in InsetMathNest/InsetMathGrid
1180         LATTEST(!cur.inMathed());
1181 }
1182
1183
1184 bool pasteFromStack(Cursor & cur, ErrorList & errorList, size_t sel_index)
1185 {
1186         // this does not make sense, if there is nothing to paste
1187         if (!checkPastePossible(sel_index))
1188                 return false;
1189
1190         cur.recordUndo();
1191         pasteParagraphList(cur, theCuts[sel_index].first,
1192                            theCuts[sel_index].second, errorList, BRANCH_ASK);
1193         return true;
1194 }
1195
1196
1197 bool pasteFromTemp(Cursor & cur, ErrorList & errorList)
1198 {
1199         // this does not make sense, if there is nothing to paste
1200         if (tempCut.empty() || tempCut[0].first.empty())
1201                 return false;
1202
1203         cur.recordUndo();
1204         pasteParagraphList(cur, tempCut[0].first,
1205                            tempCut[0].second, errorList, BRANCH_IGNORE);
1206         return true;
1207 }
1208
1209
1210 bool pasteClipboardText(Cursor & cur, ErrorList & errorList, bool asParagraphs,
1211                         Clipboard::TextType type)
1212 {
1213         // Use internal clipboard if it is the most recent one
1214         // This overrides asParagraphs and type on purpose!
1215         if (theClipboard().isInternal())
1216                 return pasteFromStack(cur, errorList, 0);
1217
1218         // First try LyX format
1219         if ((type == Clipboard::LyXTextType ||
1220              type == Clipboard::LyXOrPlainTextType ||
1221              type == Clipboard::AnyTextType) &&
1222             theClipboard().hasTextContents(Clipboard::LyXTextType)) {
1223                 string lyx = theClipboard().getAsLyX();
1224                 if (!lyx.empty()) {
1225                         // For some strange reason gcc 3.2 and 3.3 do not accept
1226                         // Buffer buffer(string(), false);
1227                         Buffer buffer("", false);
1228                         buffer.setUnnamed(true);
1229                         if (buffer.readString(lyx)) {
1230                                 cur.recordUndo();
1231                                 pasteParagraphList(cur, buffer.paragraphs(),
1232                                         buffer.params().documentClassPtr(), errorList);
1233                                 return true;
1234                         }
1235                 }
1236         }
1237
1238         // Then try TeX and HTML
1239         Clipboard::TextType types[2] = {Clipboard::HtmlTextType, Clipboard::LaTeXTextType};
1240         string names[2] = {"html", "latexclipboard"};
1241         for (int i = 0; i < 2; ++i) {
1242                 if (type != types[i] && type != Clipboard::AnyTextType)
1243                         continue;
1244                 bool available = theClipboard().hasTextContents(types[i]);
1245
1246                 // If a specific type was explicitly requested, try to
1247                 // interpret plain text: The user told us that the clipboard
1248                 // contents is in the desired format
1249                 if (!available && type == types[i]) {
1250                         types[i] = Clipboard::PlainTextType;
1251                         available = theClipboard().hasTextContents(types[i]);
1252                 }
1253
1254                 if (available) {
1255                         docstring text = theClipboard().getAsText(types[i]);
1256                         available = !text.empty();
1257                         if (available) {
1258                                 // For some strange reason gcc 3.2 and 3.3 do not accept
1259                                 // Buffer buffer(string(), false);
1260                                 Buffer buffer("", false);
1261                                 buffer.setUnnamed(true);
1262                                 available = buffer.importString(names[i], text, errorList);
1263                                 if (available)
1264                                         available = !buffer.paragraphs().empty();
1265                                 if (available && !buffer.paragraphs()[0].empty()) {
1266                                         // TeX2lyx (also used in the HTML chain) assumes English as document language
1267                                         // if no language is explicitly set (as is the case here).
1268                                         // We thus reset the temp buffer's language to the context language
1269                                         buffer.changeLanguage(buffer.language(), cur.getFont().language());
1270                                         cur.recordUndo();
1271                                         pasteParagraphList(cur, buffer.paragraphs(),
1272                                                 buffer.params().documentClassPtr(), errorList);
1273                                         return true;
1274                                 }
1275                         }
1276                 }
1277         }
1278
1279         // Then try plain text
1280         docstring const text = theClipboard().getAsText(Clipboard::PlainTextType);
1281         if (text.empty())
1282                 return false;
1283         cur.recordUndo();
1284         if (asParagraphs)
1285                 cur.text()->insertStringAsParagraphs(cur, text, cur.current_font);
1286         else
1287                 cur.text()->insertStringAsLines(cur, text, cur.current_font);
1288         cur.forceBufferUpdate();
1289         return true;
1290 }
1291
1292
1293 void pasteSimpleText(Cursor & cur, bool asParagraphs)
1294 {
1295         docstring text;
1296         // Use internal clipboard if it is the most recent one
1297         if (theClipboard().isInternal()) {
1298                 if (!checkPastePossible(0))
1299                         return;
1300
1301                 ParagraphList const & pars = theCuts[0].first;
1302                 ParagraphList::const_iterator it = pars.begin();
1303                 for (; it != pars.end(); ++it) {
1304                         if (it != pars.begin())
1305                                 text += "\n";
1306                         text += (*it).asString();
1307                 }
1308                 asParagraphs = false;
1309         } else {
1310                 // Then try plain text
1311                 text = theClipboard().getAsText(Clipboard::PlainTextType);
1312         }
1313
1314         if (text.empty())
1315                 return;
1316
1317         cur.recordUndo();
1318         cutSelection(cur, false);
1319         if (asParagraphs)
1320                 cur.text()->insertStringAsParagraphs(cur, text, cur.current_font);
1321         else
1322                 cur.text()->insertStringAsLines(cur, text, cur.current_font);
1323 }
1324
1325
1326 void pasteClipboardGraphics(Cursor & cur, ErrorList & /* errorList */,
1327                             Clipboard::GraphicsType preferedType)
1328 {
1329         LASSERT(theClipboard().hasGraphicsContents(preferedType), return);
1330
1331         // get picture from clipboard
1332         FileName filename = theClipboard().getAsGraphics(cur, preferedType);
1333         if (filename.empty())
1334                 return;
1335
1336         // create inset for graphic
1337         InsetGraphics * inset = new InsetGraphics(cur.buffer());
1338         InsetGraphicsParams params;
1339         params.filename = support::DocFileName(filename.absFileName(), false);
1340         inset->setParams(params);
1341         cur.recordUndo();
1342         cur.insert(inset);
1343 }
1344
1345
1346 void pasteSelection(Cursor & cur, ErrorList & errorList)
1347 {
1348         if (selectionBuffer.empty())
1349                 return;
1350         cur.recordUndo();
1351         pasteParagraphList(cur, selectionBuffer[0].first,
1352                            selectionBuffer[0].second, errorList);
1353 }
1354
1355
1356 void replaceSelectionWithString(Cursor & cur, docstring const & str)
1357 {
1358         cur.recordUndo();
1359         DocIterator selbeg = cur.selectionBegin();
1360
1361         // Get font setting before we cut, we need a copy here, not a bare reference.
1362         Font const font =
1363                 selbeg.paragraph().getFontSettings(cur.buffer()->params(), selbeg.pos());
1364
1365         // Insert the new string
1366         pos_type pos = cur.selEnd().pos();
1367         Paragraph & par = cur.selEnd().paragraph();
1368         docstring::const_iterator cit = str.begin();
1369         docstring::const_iterator end = str.end();
1370         for (; cit != end; ++cit, ++pos)
1371                 par.insertChar(pos, *cit, font, cur.buffer()->params().track_changes);
1372
1373         // Cut the selection
1374         cutSelection(cur, false);
1375 }
1376
1377
1378 void replaceSelection(Cursor & cur)
1379 {
1380         if (cur.selection())
1381                 cutSelection(cur, false);
1382 }
1383
1384
1385 void eraseSelection(Cursor & cur)
1386 {
1387         //lyxerr << "cap::eraseSelection begin: " << cur << endl;
1388         CursorSlice const & i1 = cur.selBegin();
1389         CursorSlice const & i2 = cur.selEnd();
1390         if (!i1.asInsetMath()) {
1391                 LYXERR0("Can't erase this selection");
1392                 return;
1393         }
1394
1395         saveSelection(cur);
1396         cur.top() = i1;
1397         InsetMath * p = i1.asInsetMath();
1398         if (i1.idx() == i2.idx()) {
1399                 i1.cell().erase(i1.pos(), i2.pos());
1400                 // We may have deleted i1.cell(cur.pos()).
1401                 // Make sure that pos is valid.
1402                 if (cur.pos() > cur.lastpos())
1403                         cur.pos() = cur.lastpos();
1404         } else if (p->nrows() > 0 && p->ncols() > 0) {
1405                 // This is a grid, delete a nice square region
1406                 Inset::row_type r1, r2;
1407                 Inset::col_type c1, c2;
1408                 region(i1, i2, r1, r2, c1, c2);
1409                 for (Inset::row_type row = r1; row <= r2; ++row)
1410                         for (Inset::col_type col = c1; col <= c2; ++col)
1411                                 p->cell(p->index(row, col)).clear();
1412                 // We've deleted the whole cell. Only pos 0 is valid.
1413                 cur.pos() = 0;
1414         } else {
1415                 Inset::idx_type idx1 = i1.idx();
1416                 Inset::idx_type idx2 = i2.idx();
1417                 if (idx1 > idx2)
1418                         swap(idx1, idx2);
1419                 for (Inset::idx_type idx = idx1 ; idx <= idx2; ++idx)
1420                         p->cell(idx).clear();
1421                 // We've deleted the whole cell. Only pos 0 is valid.
1422                 cur.pos() = 0;
1423         }
1424
1425         // need a valid cursor. (Lgb)
1426         cur.clearSelection();
1427         //lyxerr << "cap::eraseSelection end: " << cur << endl;
1428 }
1429
1430
1431 void selDel(Cursor & cur)
1432 {
1433         //lyxerr << "cap::selDel" << endl;
1434         if (cur.selection())
1435                 eraseSelection(cur);
1436 }
1437
1438
1439 void selClearOrDel(Cursor & cur)
1440 {
1441         //lyxerr << "cap::selClearOrDel" << endl;
1442         if (lyxrc.auto_region_delete)
1443                 selDel(cur);
1444         else
1445                 cur.selection(false);
1446 }
1447
1448
1449 docstring grabSelection(CursorData const & cur)
1450 {
1451         if (!cur.selection())
1452                 return docstring();
1453
1454 #if 0
1455         // grab selection by glueing multiple cells together. This is not what
1456         // we want because selections spanning multiple cells will get "&" and "\\"
1457         // seperators.
1458         ostringstream os;
1459         for (DocIterator dit = cur.selectionBegin();
1460              dit != cur.selectionEnd(); dit.forwardPos())
1461                 os << asString(dit.cell());
1462         return os.str();
1463 #endif
1464
1465         CursorSlice i1 = cur.selBegin();
1466         CursorSlice i2 = cur.selEnd();
1467
1468         if (i1.idx() == i2.idx()) {
1469                 if (i1.inset().asInsetMath()) {
1470                         MathData::const_iterator it = i1.cell().begin();
1471                         Buffer * buf = cur.buffer();
1472                         return asString(MathData(buf, it + i1.pos(), it + i2.pos()));
1473                 } else {
1474                         return from_ascii("unknown selection 1");
1475                 }
1476         }
1477
1478         Inset::row_type r1, r2;
1479         Inset::col_type c1, c2;
1480         region(i1, i2, r1, r2, c1, c2);
1481
1482         docstring data;
1483         if (i1.inset().asInsetMath()) {
1484                 for (Inset::row_type row = r1; row <= r2; ++row) {
1485                         if (row > r1)
1486                                 data += "\\\\";
1487                         for (Inset::col_type col = c1; col <= c2; ++col) {
1488                                 if (col > c1)
1489                                         data += '&';
1490                                 data += asString(i1.asInsetMath()->
1491                                         cell(i1.asInsetMath()->index(row, col)));
1492                         }
1493                 }
1494         } else {
1495                 data = from_ascii("unknown selection 2");
1496         }
1497         return data;
1498 }
1499
1500
1501 void dirtyTabularStack(bool b)
1502 {
1503         dirty_tabular_stack_ = b;
1504 }
1505
1506
1507 bool tabularStackDirty()
1508 {
1509         return dirty_tabular_stack_;
1510 }
1511
1512
1513 } // namespace cap
1514 } // namespace lyx