2 * \file CutAndPaste.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
11 * Full author contact details are available in file CREDITS.
16 #include "CutAndPaste.h"
19 #include "buffer_funcs.h"
20 #include "BufferParams.h"
21 #include "BufferView.h"
25 #include "ErrorList.h"
26 #include "FuncRequest.h"
28 #include "InsetIterator.h"
29 #include "InsetList.h"
35 #include "TextClassList.h"
36 #include "Paragraph.h"
37 #include "paragraph_funcs.h"
38 #include "ParagraphParameters.h"
39 #include "ParIterator.h"
42 #include "insets/InsetFlex.h"
43 #include "insets/InsetTabular.h"
45 #include "mathed/MathData.h"
46 #include "mathed/InsetMath.h"
47 #include "mathed/MathSupport.h"
49 #include "support/limited_stack.h"
50 #include "support/lstrings.h"
52 #include "frontends/Clipboard.h"
53 #include "frontends/Selection.h"
55 #include <boost/current_function.hpp>
56 #include <boost/tuple/tuple.hpp>
57 #include <boost/next_prior.hpp>
71 using support::bformat
;
72 using frontend::Clipboard
;
76 typedef std::pair
<pit_type
, int> PitPosPair
;
78 typedef limited_stack
<pair
<ParagraphList
, TextClassPtr
> > CutStack
;
81 // persistent selection, cleared until the next selection
82 CutStack
selectionBuffer(1);
84 // store whether the tabular stack is newer than the normal copy stack
85 // FIXME: this is a workaround for bug 1919. Should be removed for 1.5,
86 // when we (hopefully) have a one-for-all paste mechanism.
87 bool dirty_tabular_stack_
= false;
90 void region(CursorSlice
const & i1
, CursorSlice
const & i2
,
91 Inset::row_type
& r1
, Inset::row_type
& r2
,
92 Inset::col_type
& c1
, Inset::col_type
& c2
)
94 Inset
& p
= i1
.inset();
100 r2
= p
.row(i2
.idx());
106 bool checkPastePossible(int index
)
108 return size_t(index
) < theCuts
.size() && !theCuts
[index
].first
.empty();
112 pair
<PitPosPair
, pit_type
>
113 pasteSelectionHelper(Cursor
& cur
, ParagraphList
const & parlist
,
114 TextClassPtr textclass
, ErrorList
& errorlist
)
116 Buffer
const & buffer
= cur
.buffer();
117 pit_type pit
= cur
.pit();
118 pos_type pos
= cur
.pos();
119 ParagraphList
& pars
= cur
.text()->paragraphs();
122 return make_pair(PitPosPair(pit
, pos
), pit
);
124 BOOST_ASSERT (pos
<= pars
[pit
].size());
126 // Make a copy of the CaP paragraphs.
127 ParagraphList insertion
= parlist
;
128 TextClassPtr
const tc
= buffer
.params().getTextClassPtr();
130 // Now remove all out of the pars which is NOT allowed in the
131 // new environment and set also another font if that is required.
133 // Convert newline to paragraph break in ERT inset.
134 // This should not be here!
135 if (pars
[pit
].inInset() &&
136 (pars
[pit
].inInset()->lyxCode() == ERT_CODE
||
137 pars
[pit
].inInset()->lyxCode() == LISTINGS_CODE
)) {
138 for (ParagraphList::size_type i
= 0; i
< insertion
.size(); ++i
) {
139 for (pos_type j
= 0; j
< insertion
[i
].size(); ++j
) {
140 if (insertion
[i
].isNewline(j
)) {
141 // do not track deletion of newline
142 insertion
[i
].eraseChar(j
, false);
143 breakParagraphConservative(
151 // If we are in an inset which returns forceDefaultParagraphs,
152 // set the paragraphs to default
153 if (cur
.inset().forceDefaultParagraphs(cur
.idx())) {
154 LayoutPtr
const layout
=
155 buffer
.params().getTextClass().defaultLayout();
156 ParagraphList::iterator
const end
= insertion
.end();
157 for (ParagraphList::iterator par
= insertion
.begin();
162 // Make sure there is no class difference.
164 // This works without copying any paragraph data because we have
165 // a specialized swap method for ParagraphList. This is important
166 // since we store pointers to insets at some places and we don't
167 // want to invalidate them.
168 insertion
.swap(in
.paragraphs());
169 cap::switchBetweenClasses(textclass
, tc
, in
, errorlist
);
170 insertion
.swap(in
.paragraphs());
172 ParagraphList::iterator tmpbuf
= insertion
.begin();
173 int depth_delta
= pars
[pit
].params().depth() - tmpbuf
->params().depth();
175 depth_type max_depth
= pars
[pit
].getMaxDepthAfter();
177 for (; tmpbuf
!= insertion
.end(); ++tmpbuf
) {
178 // If we have a negative jump so that the depth would
179 // go below 0 depth then we have to redo the delta to
180 // this new max depth level so that subsequent
181 // paragraphs are aligned correctly to this paragraph
183 if (int(tmpbuf
->params().depth()) + depth_delta
< 0)
186 // Set the right depth so that we are not too deep or shallow.
187 tmpbuf
->params().depth(tmpbuf
->params().depth() + depth_delta
);
188 if (tmpbuf
->params().depth() > max_depth
)
189 tmpbuf
->params().depth(max_depth
);
191 // Only set this from the 2nd on as the 2nd depends
192 // for maxDepth still on pit.
193 if (tmpbuf
!= insertion
.begin())
194 max_depth
= tmpbuf
->getMaxDepthAfter();
196 // Set the inset owner of this paragraph.
197 tmpbuf
->setInsetOwner(pars
[pit
].inInset());
198 for (pos_type i
= 0; i
< tmpbuf
->size(); ++i
) {
199 if (tmpbuf
->getChar(i
) == Paragraph::META_INSET
&&
200 !pars
[pit
].insetAllowed(tmpbuf
->getInset(i
)->lyxCode()))
201 // do not track deletion of invalid insets
202 tmpbuf
->eraseChar(i
--, false);
205 tmpbuf
->setChange(Change(buffer
.params().trackChanges
?
206 Change::INSERTED
: Change::UNCHANGED
));
209 bool const empty
= pars
[pit
].empty();
211 // Make the buf exactly the same layout as the cursor
213 insertion
.begin()->makeSameLayout(pars
[pit
]);
216 // Prepare the paragraphs and insets for insertion.
217 // A couple of insets store buffer references so need updating.
218 insertion
.swap(in
.paragraphs());
220 ParIterator fpit
= par_iterator_begin(in
);
221 ParIterator fend
= par_iterator_end(in
);
223 for (; fpit
!= fend
; ++fpit
) {
224 InsetList::const_iterator lit
= fpit
->insetList().begin();
225 InsetList::const_iterator eit
= fpit
->insetList().end();
227 for (; lit
!= eit
; ++lit
) {
228 switch (lit
->inset
->lyxCode()) {
230 InsetTabular
* it
= static_cast<InsetTabular
*>(lit
->inset
);
240 insertion
.swap(in
.paragraphs());
242 // Split the paragraph for inserting the buf if necessary.
244 breakParagraphConservative(buffer
.params(), pars
, pit
, pos
);
248 pars
.insert(boost::next(pars
.begin(), pit
),
252 // merge the empty par with the last par of the insertion
253 mergeParagraph(buffer
.params(), pars
,
254 pit
+ insertion
.size() - 1);
256 pars
.insert(boost::next(pars
.begin(), pit
+ 1),
260 // merge the first par of the insertion with the current par
261 mergeParagraph(buffer
.params(), pars
, pit
);
264 pit_type last_paste
= pit
+ insertion
.size() - 1;
266 // Store the new cursor position.
268 pos
= pars
[last_paste
].size();
270 // Join (conditionally) last pasted paragraph with next one, i.e.,
271 // the tail of the spliced document paragraph
272 if (!empty
&& last_paste
+ 1 != pit_type(pars
.size())) {
273 if (pars
[last_paste
+ 1].hasSameLayout(pars
[last_paste
])) {
274 mergeParagraph(buffer
.params(), pars
, last_paste
);
275 } else if (pars
[last_paste
+ 1].empty()) {
276 pars
[last_paste
+ 1].makeSameLayout(pars
[last_paste
]);
277 mergeParagraph(buffer
.params(), pars
, last_paste
);
278 } else if (pars
[last_paste
].empty()) {
279 pars
[last_paste
].makeSameLayout(pars
[last_paste
+ 1]);
280 mergeParagraph(buffer
.params(), pars
, last_paste
);
282 pars
[last_paste
+ 1].stripLeadingSpaces(buffer
.params().trackChanges
);
287 return make_pair(PitPosPair(pit
, pos
), last_paste
+ 1);
291 PitPosPair
eraseSelectionHelper(BufferParams
const & params
,
292 ParagraphList
& pars
,
293 pit_type startpit
, pit_type endpit
,
294 int startpos
, int endpos
)
296 // Start of selection is really invalid.
297 if (startpit
== pit_type(pars
.size()) ||
298 (startpos
> pars
[startpit
].size()))
299 return PitPosPair(endpit
, endpos
);
301 // Start and end is inside same paragraph
302 if (endpit
== pit_type(pars
.size()) || startpit
== endpit
) {
303 endpos
-= pars
[startpit
].eraseChars(startpos
, endpos
, params
.trackChanges
);
304 return PitPosPair(endpit
, endpos
);
307 for (pit_type pit
= startpit
; pit
!= endpit
+ 1;) {
308 pos_type
const left
= (pit
== startpit
? startpos
: 0);
309 pos_type right
= (pit
== endpit
? endpos
: pars
[pit
].size() + 1);
310 bool const merge
= pars
[pit
].isMergedOnEndOfParDeletion(params
.trackChanges
);
312 // Logically erase only, including the end-of-paragraph character
313 pars
[pit
].eraseChars(left
, right
, params
.trackChanges
);
315 // Separate handling of paragraph break:
316 if (merge
&& pit
!= endpit
&&
317 (pit
+ 1 != endpit
|| pars
[pit
].hasSameLayout(pars
[endpit
]))) {
318 if (pit
+ 1 == endpit
)
319 endpos
+= pars
[pit
].size();
320 mergeParagraph(params
, pars
, pit
);
326 // Ensure legal cursor pos:
329 return PitPosPair(endpit
, endpos
);
333 void putClipboard(ParagraphList
const & paragraphs
, TextClassPtr textclass
,
334 docstring
const & plaintext
)
336 // For some strange reason gcc 3.2 and 3.3 do not accept
337 // Buffer buffer(string(), false);
338 Buffer
buffer("", false);
339 buffer
.setUnnamed(true);
340 buffer
.paragraphs() = paragraphs
;
341 buffer
.params().setTextClass(textclass
);
342 std::ostringstream lyx
;
343 if (buffer
.write(lyx
))
344 theClipboard().put(lyx
.str(), plaintext
);
346 theClipboard().put(string(), plaintext
);
350 void copySelectionHelper(Buffer
const & buf
, ParagraphList
& pars
,
351 pit_type startpit
, pit_type endpit
,
352 int start
, int end
, TextClassPtr tc
, CutStack
& cutstack
)
354 BOOST_ASSERT(0 <= start
&& start
<= pars
[startpit
].size());
355 BOOST_ASSERT(0 <= end
&& end
<= pars
[endpit
].size());
356 BOOST_ASSERT(startpit
!= endpit
|| start
<= end
);
358 // Clone the paragraphs within the selection.
359 ParagraphList
copy_pars(boost::next(pars
.begin(), startpit
),
360 boost::next(pars
.begin(), endpit
+ 1));
362 // Remove the end of the last paragraph; afterwards, remove the
363 // beginning of the first paragraph. Keep this order - there may only
364 // be one paragraph! Do not track deletions here; this is an internal
365 // action not visible to the user
367 Paragraph
& back
= copy_pars
.back();
368 back
.eraseChars(end
, back
.size(), false);
369 Paragraph
& front
= copy_pars
.front();
370 front
.eraseChars(0, start
, false);
372 ParagraphList::iterator it
= copy_pars
.begin();
373 ParagraphList::iterator it_end
= copy_pars
.end();
375 for (; it
!= it_end
; it
++) {
376 // ERT paragraphs have the Language latex_language.
377 // This is invalid outside of ERT, so we need to change it
378 // to the buffer language.
379 if (it
->ownerCode() == ERT_CODE
|| it
->ownerCode() == LISTINGS_CODE
) {
380 it
->changeLanguage(buf
.params(), latex_language
, buf
.language());
382 it
->setInsetOwner(0);
385 // do not copy text (also nested in insets) which is marked as deleted
386 acceptChanges(copy_pars
, buf
.params());
388 cutstack
.push(make_pair(copy_pars
, tc
));
398 docstring
grabAndEraseSelection(Cursor
& cur
)
400 if (!cur
.selection())
402 docstring res
= grabSelection(cur
);
408 void switchBetweenClasses(TextClassPtr
const & c1
,
409 TextClassPtr
const & c2
, InsetText
& in
, ErrorList
& errorlist
)
413 BOOST_ASSERT(!in
.paragraphs().empty());
417 TextClass
const & tclass1
= *c1
;
418 TextClass
const & tclass2
= *c2
;
421 ParIterator end
= par_iterator_end(in
);
422 for (ParIterator it
= par_iterator_begin(in
); it
!= end
; ++it
) {
423 docstring
const name
= it
->layout()->name();
424 bool hasLayout
= tclass2
.hasLayout(name
);
427 it
->layout(tclass2
[name
]);
429 it
->layout(tclass2
.defaultLayout());
431 if (!hasLayout
&& name
!= tclass1
.defaultLayoutName()) {
432 docstring
const s
= bformat(
433 _("Layout had to be changed from\n%1$s to %2$s\n"
434 "because of class conversion from\n%3$s to %4$s"),
435 name
, it
->layout()->name(),
436 from_utf8(tclass1
.name()), from_utf8(tclass2
.name()));
437 // To warn the user that something had to be done.
438 errorlist
.push_back(ErrorItem(_("Changed Layout"), s
,
445 InsetIterator
const i_end
= inset_iterator_end(in
);
446 for (InsetIterator it
= inset_iterator_begin(in
); it
!= i_end
; ++it
) {
447 if (it
->lyxCode() == FLEX_CODE
) {
449 static_cast<InsetFlex
&>(*it
);
450 string
const name
= inset
.params().name
;
451 InsetLayout
const il
=
452 tclass2
.insetlayout(from_utf8(name
));
454 if (il
.labelstring
== from_utf8("UNDEFINED")) {
455 // The flex inset is undefined in tclass2
456 docstring
const s
= bformat(_(
457 "Flex inset %1$s is "
458 "undefined because of class "
459 "conversion from\n%2$s to %3$s"),
460 from_utf8(name
), from_utf8(tclass1
.name()),
461 from_utf8(tclass2
.name()));
462 // To warn the user that something had to be done.
463 errorlist
.push_back(ErrorItem(
464 _("Undefined flex inset"),
465 s
, it
.paragraph().id(), it
.pos(), it
.pos() + 1));
472 std::vector
<docstring
> const availableSelections(Buffer
const & buffer
)
474 vector
<docstring
> selList
;
476 CutStack::const_iterator cit
= theCuts
.begin();
477 CutStack::const_iterator end
= theCuts
.end();
478 for (; cit
!= end
; ++cit
) {
479 // we do not use cit-> here because gcc 2.9x does not
481 ParagraphList
const & pars
= (*cit
).first
;
483 ParagraphList::const_iterator pit
= pars
.begin();
484 ParagraphList::const_iterator pend
= pars
.end();
485 for (; pit
!= pend
; ++pit
) {
486 asciiSel
+= pit
->asString(buffer
, false);
487 if (asciiSel
.size() > 25) {
488 asciiSel
.replace(22, docstring::npos
,
494 selList
.push_back(asciiSel
);
501 size_type
numberOfSelections()
503 return theCuts
.size();
507 void cutSelection(Cursor
& cur
, bool doclear
, bool realcut
)
509 // This doesn't make sense, if there is no selection
510 if (!cur
.selection())
513 // OK, we have a selection. This is always between cur.selBegin()
516 if (cur
.inTexted()) {
517 Text
* text
= cur
.text();
522 // make sure that the depth behind the selection are restored, too
523 cur
.recordUndoSelection();
524 pit_type begpit
= cur
.selBegin().pit();
525 pit_type endpit
= cur
.selEnd().pit();
527 int endpos
= cur
.selEnd().pos();
529 BufferParams
const & bp
= cur
.buffer().params();
531 copySelectionHelper(cur
.buffer(),
534 cur
.selBegin().pos(), endpos
,
535 bp
.getTextClassPtr(), theCuts
);
536 // Stuff what we got on the clipboard.
537 // Even if there is no selection.
538 putClipboard(theCuts
[0].first
, theCuts
[0].second
,
539 cur
.selectionAsString(true));
542 boost::tie(endpit
, endpos
) =
543 eraseSelectionHelper(bp
,
546 cur
.selBegin().pos(), endpos
);
548 // cutSelection can invalidate the cursor so we need to set
550 // we prefer the end for when tracking changes
554 // sometimes necessary
556 && text
->paragraphs()[begpit
].stripLeadingSpaces(bp
.trackChanges
))
559 // need a valid cursor. (Lgb)
560 cur
.clearSelection();
561 updateLabels(cur
.buffer());
563 // tell tabular that a recent copy happened
564 dirtyTabularStack(false);
567 if (cur
.inMathed()) {
568 if (cur
.selBegin().idx() != cur
.selEnd().idx()) {
569 // The current selection spans more than one cell.
571 cur
.recordUndoInset();
573 // Record only the current cell to avoid a jumping
584 void copySelection(Cursor
& cur
)
586 copySelection(cur
, cur
.selectionAsString(true));
592 void copySelectionToStack(Cursor
& cur
, CutStack
& cutstack
)
594 // this doesn't make sense, if there is no selection
595 if (!cur
.selection())
598 // copySelection can not yet handle the case of cross idx selection
599 if (cur
.selBegin().idx() != cur
.selEnd().idx())
602 if (cur
.inTexted()) {
603 Text
* text
= cur
.text();
605 // ok we have a selection. This is always between cur.selBegin()
606 // and sel_end cursor
608 // copy behind a space if there is one
609 ParagraphList
& pars
= text
->paragraphs();
610 pos_type pos
= cur
.selBegin().pos();
611 pit_type par
= cur
.selBegin().pit();
612 while (pos
< pars
[par
].size() &&
613 pars
[par
].isLineSeparator(pos
) &&
614 (par
!= cur
.selEnd().pit() || pos
< cur
.selEnd().pos()))
617 copySelectionHelper(cur
.buffer(), pars
, par
, cur
.selEnd().pit(),
618 pos
, cur
.selEnd().pos(),
619 cur
.buffer().params().getTextClassPtr(), cutstack
);
620 dirtyTabularStack(false);
623 if (cur
.inMathed()) {
624 //lyxerr << "copySelection in mathed" << endl;
627 BufferParams
const & bp
= cur
.buffer().params();
628 par
.layout(bp
.getTextClass().defaultLayout());
629 par
.insert(0, grabSelection(cur
), Font(), Change(Change::UNCHANGED
));
631 cutstack
.push(make_pair(pars
, bp
.getTextClassPtr()));
638 void copySelectionToStack()
640 if (!selectionBuffer
.empty())
641 theCuts
.push(selectionBuffer
[0]);
645 void copySelection(Cursor
& cur
, docstring
const & plaintext
)
647 // In tablemode, because copy and paste actually use special table stack
648 // we do not attemp to get selected paragraphs under cursor. Instead, a
649 // paragraph with the plain text version is generated so that table cells
650 // can be pasted as pure text somewhere else.
651 if (cur
.selBegin().idx() != cur
.selEnd().idx()) {
654 BufferParams
const & bp
= cur
.buffer().params();
655 par
.layout(bp
.getTextClass().defaultLayout());
656 par
.insert(0, plaintext
, Font(), Change(Change::UNCHANGED
));
658 theCuts
.push(make_pair(pars
, bp
.getTextClassPtr()));
660 copySelectionToStack(cur
, theCuts
);
662 // stuff the selection onto the X clipboard, from an explicit copy request
663 putClipboard(theCuts
[0].first
, theCuts
[0].second
, plaintext
);
667 void saveSelection(Cursor
& cur
)
669 // This function is called, not when a selection is formed, but when
670 // a selection is cleared. Therefore, multiple keyboard selection
671 // will not repeatively trigger this function (bug 3877).
673 && cur
.selBegin() == cur
.bv().cursor().selBegin()
674 && cur
.selEnd() == cur
.bv().cursor().selEnd()) {
675 LYXERR(Debug::ACTION
) << BOOST_CURRENT_FUNCTION
<< ": `"
676 << to_utf8(cur
.selectionAsString(true)) << "'."
678 copySelectionToStack(cur
, selectionBuffer
);
685 return !selectionBuffer
.empty();
689 void clearSelection()
691 selectionBuffer
.clear();
701 docstring
getSelection(Buffer
const & buf
, size_t sel_index
)
703 return sel_index
< theCuts
.size()
704 ? theCuts
[sel_index
].first
.back().asString(buf
, false)
709 void pasteParagraphList(Cursor
& cur
, ParagraphList
const & parlist
,
710 TextClassPtr textclass
, ErrorList
& errorList
)
712 if (cur
.inTexted()) {
713 Text
* text
= cur
.text();
719 boost::tie(ppp
, endpit
) =
720 pasteSelectionHelper(cur
, parlist
,
721 textclass
, errorList
);
722 updateLabels(cur
.buffer());
723 cur
.clearSelection();
724 text
->setCursor(cur
, ppp
.first
, ppp
.second
);
727 // mathed is handled in InsetMathNest/InsetMathGrid
728 BOOST_ASSERT(!cur
.inMathed());
732 void pasteFromStack(Cursor
& cur
, ErrorList
& errorList
, size_t sel_index
)
734 // this does not make sense, if there is nothing to paste
735 if (!checkPastePossible(sel_index
))
739 pasteParagraphList(cur
, theCuts
[sel_index
].first
,
740 theCuts
[sel_index
].second
, errorList
);
745 void pasteClipboard(Cursor
& cur
, ErrorList
& errorList
, bool asParagraphs
)
747 // Use internal clipboard if it is the most recent one
748 if (theClipboard().isInternal()) {
749 pasteFromStack(cur
, errorList
, 0);
753 // First try LyX format
754 if (theClipboard().hasLyXContents()) {
755 string lyx
= theClipboard().getAsLyX();
757 // For some strange reason gcc 3.2 and 3.3 do not accept
758 // Buffer buffer(string(), false);
759 Buffer
buffer("", false);
760 buffer
.setUnnamed(true);
761 if (buffer
.readString(lyx
)) {
763 pasteParagraphList(cur
, buffer
.paragraphs(),
764 buffer
.params().getTextClassPtr(), errorList
);
771 // Then try plain text
772 docstring
const text
= theClipboard().getAsText();
777 cur
.text()->insertStringAsParagraphs(cur
, text
);
779 cur
.text()->insertStringAsLines(cur
, text
);
783 void pasteSelection(Cursor
& cur
, ErrorList
& errorList
)
785 if (selectionBuffer
.empty())
788 pasteParagraphList(cur
, selectionBuffer
[0].first
,
789 selectionBuffer
[0].second
, errorList
);
793 void replaceSelectionWithString(Cursor
& cur
, docstring
const & str
, bool backwards
)
796 DocIterator selbeg
= cur
.selectionBegin();
798 // Get font setting before we cut
800 selbeg
.paragraph().getFontSettings(cur
.buffer().params(), selbeg
.pos());
802 // Insert the new string
803 pos_type pos
= cur
.selEnd().pos();
804 Paragraph
& par
= cur
.selEnd().paragraph();
805 docstring::const_iterator cit
= str
.begin();
806 docstring::const_iterator end
= str
.end();
807 for (; cit
!= end
; ++cit
, ++pos
)
808 par
.insertChar(pos
, *cit
, font
, cur
.buffer().params().trackChanges
);
811 cutSelection(cur
, true, false);
813 // select the replacement
815 selbeg
.pos() += str
.length();
816 cur
.setSelection(selbeg
, -int(str
.length()));
818 cur
.setSelection(selbeg
, str
.length());
822 void replaceSelection(Cursor
& cur
)
825 cutSelection(cur
, true, false);
829 void eraseSelection(Cursor
& cur
)
831 //lyxerr << "cap::eraseSelection begin: " << cur << endl;
832 CursorSlice
const & i1
= cur
.selBegin();
833 CursorSlice
const & i2
= cur
.selEnd();
834 if (i1
.inset().asInsetMath()) {
837 if (i1
.idx() == i2
.idx()) {
838 i1
.cell().erase(i1
.pos(), i2
.pos());
839 // We may have deleted i1.cell(cur.pos()).
840 // Make sure that pos is valid.
841 if (cur
.pos() > cur
.lastpos())
842 cur
.pos() = cur
.lastpos();
844 InsetMath
* p
= i1
.asInsetMath();
845 Inset::row_type r1
, r2
;
846 Inset::col_type c1
, c2
;
847 region(i1
, i2
, r1
, r2
, c1
, c2
);
848 for (Inset::row_type row
= r1
; row
<= r2
; ++row
)
849 for (Inset::col_type col
= c1
; col
<= c2
; ++col
)
850 p
->cell(p
->index(row
, col
)).clear();
851 // We've deleted the whole cell. Only pos 0 is valid.
854 // need a valid cursor. (Lgb)
855 cur
.clearSelection();
857 lyxerr
<< "can't erase this selection 1" << endl
;
859 //lyxerr << "cap::eraseSelection end: " << cur << endl;
863 void selDel(Cursor
& cur
)
865 //lyxerr << "cap::selDel" << endl;
871 void selClearOrDel(Cursor
& cur
)
873 //lyxerr << "cap::selClearOrDel" << endl;
874 if (lyxrc
.auto_region_delete
)
877 cur
.selection() = false;
881 docstring
grabSelection(Cursor
const & cur
)
883 if (!cur
.selection())
886 // FIXME: What is wrong with the following?
888 std::ostringstream os
;
889 for (DocIterator dit
= cur
.selectionBegin();
890 dit
!= cur
.selectionEnd(); dit
.forwardPos())
891 os
<< asString(dit
.cell());
895 CursorSlice i1
= cur
.selBegin();
896 CursorSlice i2
= cur
.selEnd();
898 if (i1
.idx() == i2
.idx()) {
899 if (i1
.inset().asInsetMath()) {
900 MathData::const_iterator it
= i1
.cell().begin();
901 return asString(MathData(it
+ i1
.pos(), it
+ i2
.pos()));
903 return from_ascii("unknown selection 1");
907 Inset::row_type r1
, r2
;
908 Inset::col_type c1
, c2
;
909 region(i1
, i2
, r1
, r2
, c1
, c2
);
912 if (i1
.inset().asInsetMath()) {
913 for (Inset::row_type row
= r1
; row
<= r2
; ++row
) {
916 for (Inset::col_type col
= c1
; col
<= c2
; ++col
) {
919 data
+= asString(i1
.asInsetMath()->
920 cell(i1
.asInsetMath()->index(row
, col
)));
924 data
= from_ascii("unknown selection 2");
930 void dirtyTabularStack(bool b
)
932 dirty_tabular_stack_
= b
;
936 bool tabularStackDirty()
938 return dirty_tabular_stack_
;