tdf#169795 sw: fix crashes around tracked changes in cells with formula
Copying a cell over another one with change recording on could crash, or destroy the tracked change it had just recorded when the target cell had a number format. The root cause was that formulas and formatted numbers overwrite existing cells and destroy some tracked changes.. The cursor could point to a character that is not exist anymore. So, first i made some cursor position fix in some key points to avoid some crashes. Then disabled tracked changes when a cell with formula is changed. Every time it was recalculated, it generated a new tracked change. Normally if we want it to be tracked, we should track the formula inside, not the visible value .. but we don't have that type of tracked change. ChgTextToNum() could also generate lost tracked changes: If we copied a formatted number cell content to an other similar cell, (for example a currency like $1,000.00 to $10.00) by replacing the text ... then at first it generated a cell content like $1,000.00($10.00) where ($10.00) is a delete change.. Then the ChgTextToNum() reformatted the cell.. and overwritten it with the $1,000.00 ... so we lost the delete change. now i changed it so it can keep tracked deletion changes as well (it was able to store some insert changes) and then it checked if the format is fine for the real text .. If it is, then it will leave it.. so tracked changes survive. If the format is not good, then i keep the old way of working to replace cell with the new value... and make it 1 insert tracked change I do this to avoid complex problems.. because at format changes a lot of things can change.. position of the characters .. and it could be a confusion where the old delete change should start from. It would need more planning to figure out a way to handle it. Side note: in some cases if we modify the value of the formatted number cell to something that does not fit the format, it can lose the formation and change to pure text. (so all track changes will work on it) like if you change this: $1,123,234.00 -> $1,123234.00 (This is not my change. i just mentioned this behavior.) Signed-off-by: Attila Szűcs <attila.szucs@collabora.com> Change-Id: I152ec4720ec8e133800186c7e5787fe0f6954fc8 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13456 Tested-by: Jenkins CPCI <releng@collaboraoffice.com> Reviewed-by: Stephan Bergmann <stephan.bergmann@collabora.com> (cherry picked from commit 699b9cb056cb283fc2699763e1a2340e12680c3f) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211684 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
d615c8f532
commit
bb952370f7
+2
-2
@@ -664,7 +664,7 @@ public:
|
||||
// select a table row, column or box (based on the current cursor)
|
||||
bool SelTableRow() { return SelTableRowOrCol( true ); }
|
||||
bool SelTableCol() { return SelTableRowOrCol( false ); }
|
||||
bool SelTableBox();
|
||||
SW_DLLPUBLIC bool SelTableBox();
|
||||
|
||||
SW_DLLPUBLIC bool SelTable();
|
||||
|
||||
@@ -870,7 +870,7 @@ public:
|
||||
bool CheckTableBoxContent( const SwPosition* pPos = nullptr );
|
||||
void SaveTableBoxContent( const SwPosition* pPos = nullptr );
|
||||
void ClearTableBoxContent();
|
||||
bool EndAllTableBoxEdit();
|
||||
SW_DLLPUBLIC bool EndAllTableBoxEdit();
|
||||
|
||||
void SetSelTableCells( bool bFlag ) { m_bSelTableCells = bFlag; }
|
||||
bool IsSelTableCells() const { return m_bSelTableCells; }
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -25,6 +25,17 @@
|
||||
#include <fmtpdsc.hxx>
|
||||
#include <pagefrm.hxx>
|
||||
#include <rootfrm.hxx>
|
||||
#include <docstat.hxx>
|
||||
#include <ndtxt.hxx>
|
||||
#include <pam.hxx>
|
||||
#include <IDocumentRedlineAccess.hxx>
|
||||
#include <redline.hxx>
|
||||
#include <docary.hxx>
|
||||
|
||||
#include <com/sun/star/table/XCell.hpp>
|
||||
#include <com/sun/star/text/XTextTable.hpp>
|
||||
#include <com/sun/star/text/XTextTablesSupplier.hpp>
|
||||
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
|
||||
|
||||
/// Covers sw/source/core/undo/ fixes.
|
||||
class SwCoreUndoTest : public SwModelTestBase
|
||||
@@ -105,6 +116,175 @@ CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testTableCopyRedline)
|
||||
pWrtShell->Undo();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Put the view cursor into the given cell of the given table.
|
||||
void lcl_GotoCell(const uno::Reference<lang::XComponent>& xComponent, SwXTextDocument* pTextDoc,
|
||||
const OUString& rTable, const OUString& rCell)
|
||||
{
|
||||
uno::Reference<text::XTextTablesSupplier> xSupplier(xComponent, uno::UNO_QUERY_THROW);
|
||||
uno::Reference<text::XTextTable> xTable(xSupplier->getTextTables()->getByName(rTable),
|
||||
uno::UNO_QUERY_THROW);
|
||||
uno::Reference<text::XText> xCell(xTable->getCellByName(rCell), uno::UNO_QUERY_THROW);
|
||||
uno::Reference<text::XTextViewCursorSupplier> xCursorSupplier(pTextDoc->getCurrentController(),
|
||||
uno::UNO_QUERY_THROW);
|
||||
xCursorSupplier->getViewCursor()->gotoRange(xCell->getStart(), /*bExpand=*/false);
|
||||
}
|
||||
|
||||
/// The text of the given cell of the given table.
|
||||
OUString lcl_GetCellText(const uno::Reference<lang::XComponent>& xComponent, const OUString& rTable,
|
||||
const OUString& rCell)
|
||||
{
|
||||
uno::Reference<text::XTextTablesSupplier> xSupplier(xComponent, uno::UNO_QUERY_THROW);
|
||||
uno::Reference<text::XTextTable> xTable(xSupplier->getTextTables()->getByName(rTable),
|
||||
uno::UNO_QUERY_THROW);
|
||||
uno::Reference<text::XText> xCell(xTable->getCellByName(rCell), uno::UNO_QUERY_THROW);
|
||||
return xCell->getString();
|
||||
}
|
||||
|
||||
/// The formula of the given cell of the given table, empty when it holds none.
|
||||
OUString lcl_GetCellFormula(const uno::Reference<lang::XComponent>& xComponent,
|
||||
const OUString& rTable, const OUString& rCell)
|
||||
{
|
||||
uno::Reference<text::XTextTablesSupplier> xSupplier(xComponent, uno::UNO_QUERY_THROW);
|
||||
uno::Reference<text::XTextTable> xTable(xSupplier->getTextTables()->getByName(rTable),
|
||||
uno::UNO_QUERY_THROW);
|
||||
uno::Reference<table::XCell> xCell(xTable->getCellByName(rCell));
|
||||
CPPUNIT_ASSERT(xCell.is());
|
||||
return xCell->getFormula();
|
||||
}
|
||||
|
||||
/// The tracked changes of the document, as a "<type>:<text>" list.
|
||||
OUString lcl_GetRedlines(SwDoc* pDoc)
|
||||
{
|
||||
OUString aRet;
|
||||
const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
|
||||
for (SwRedlineTable::size_type i = 0; i < rTable.size(); ++i)
|
||||
{
|
||||
if (!aRet.isEmpty())
|
||||
aRet += " ";
|
||||
aRet += SwRedlineTypeToOUString(rTable[i]->GetType()) + ":" + rTable[i]->GetText();
|
||||
}
|
||||
return aRet;
|
||||
}
|
||||
|
||||
/// Every cursor position has to stay inside the text of the node it points at.
|
||||
void lcl_AssertCursorInText(SwWrtShell* pWrtShell)
|
||||
{
|
||||
for (const SwPaM& rPaM : pWrtShell->GetCursor()->GetRingContainer())
|
||||
{
|
||||
for (const SwPosition* pPos : { rPaM.GetPoint(), rPaM.GetMark() })
|
||||
{
|
||||
const SwContentNode* pContentNode = pPos->GetNode().GetContentNode();
|
||||
if (!pContentNode)
|
||||
continue;
|
||||
CPPUNIT_ASSERT_LESSEQUAL(pContentNode->Len(), pPos->GetContentIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testTdf169795CopyCellWithFormula)
|
||||
{
|
||||
// Given a document with two tables and change recording enabled, where the cell to copy
|
||||
// holds a formula and the cell to copy it over does not:
|
||||
createSwDoc("tdf169795.odt");
|
||||
SwDocShell* pDocShell = getSwDocShell();
|
||||
SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
|
||||
SwXTextDocument* pTextDoc = getSwTextDoc();
|
||||
SwDoc* pDoc = pWrtShell->GetDoc();
|
||||
pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowInsert
|
||||
| RedlineFlags::ShowDelete);
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr));
|
||||
|
||||
// When copying that whole cell, "$100.00", over "$10.00":
|
||||
lcl_GotoCell(mxComponent, pTextDoc, u"Table2"_ustr, u"D3"_ustr);
|
||||
pWrtShell->SelTableBox();
|
||||
rtl::Reference<SwTransferable> pTransfer = new SwTransferable(*pWrtShell);
|
||||
pTransfer->Copy();
|
||||
TransferableDataHelper aHelper(pTransfer);
|
||||
lcl_GotoCell(mxComponent, pTextDoc, u"Table1"_ustr, u"D2"_ustr);
|
||||
pWrtShell->SelTableBox();
|
||||
SwTransferable::Paste(*pWrtShell, aHelper);
|
||||
|
||||
// Then no change is recorded over the cell: what it shows is worked out from the formula in
|
||||
// its new place, not text that somebody wrote, so there is nothing to accept or reject.
|
||||
// Without the accompanying fix in place, the copy was recorded as a change over text that
|
||||
// the recalculation then rewrote, and the records left over that text described text that
|
||||
// was no longer there.
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc));
|
||||
CPPUNIT_ASSERT(!lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr).isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL(u"$10.00"_ustr, lcl_GetCellText(mxComponent, u"Table1"_ustr, u"D2"_ustr));
|
||||
|
||||
// And undo takes the formula away again, leaving the old value behind.
|
||||
pWrtShell->Undo();
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc));
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr));
|
||||
CPPUNIT_ASSERT_EQUAL(u"$10.00"_ustr, lcl_GetCellText(mxComponent, u"Table1"_ustr, u"D2"_ustr));
|
||||
|
||||
// The cursor has to stay inside the text it points at. Without the fix, undo left it on the
|
||||
// range of the pasted text, which the undo removed again, so it pointed past the end of the
|
||||
// cell text: content index 11 of a 6 character long node.
|
||||
lcl_AssertCursorInText(pWrtShell);
|
||||
|
||||
// ... and counting words over that selection must not run out of the text either; this is
|
||||
// what the status bar does, and what crashed in SwScanner.
|
||||
SwDocStat aDocStat;
|
||||
pWrtShell->CountWords(aDocStat);
|
||||
|
||||
// Redo puts the formula back, again without recording a change, and again with a cursor that
|
||||
// describes something. Without the fix, redo went down the route the copy had not taken.
|
||||
pWrtShell->Redo();
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc));
|
||||
CPPUNIT_ASSERT(!lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr).isEmpty());
|
||||
lcl_AssertCursorInText(pWrtShell);
|
||||
pWrtShell->CountWords(aDocStat);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testTdf169795NumFormatCellPasteKeepsTrackedChanges)
|
||||
{
|
||||
// Given a document with number cells and change recording enabled:
|
||||
createSwDoc("tdf169795.odt");
|
||||
SwDocShell* pDocShell = getSwDocShell();
|
||||
SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
|
||||
SwXTextDocument* pTextDoc = getSwTextDoc();
|
||||
SwDoc* pDoc = pWrtShell->GetDoc();
|
||||
pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowInsert
|
||||
| RedlineFlags::ShowDelete);
|
||||
|
||||
// When copying one currency cell over another one:
|
||||
lcl_GotoCell(mxComponent, pTextDoc, u"Table1"_ustr, u"C3"_ustr);
|
||||
pWrtShell->SelTableBox();
|
||||
rtl::Reference<SwTransferable> pTransfer = new SwTransferable(*pWrtShell);
|
||||
pTransfer->Copy();
|
||||
TransferableDataHelper aHelper(pTransfer);
|
||||
lcl_GotoCell(mxComponent, pTextDoc, u"Table1"_ustr, u"C2"_ustr);
|
||||
pWrtShell->SelTableBox();
|
||||
SwTransferable::Paste(*pWrtShell, aHelper);
|
||||
|
||||
// Then the old value has to be kept as a deletion.
|
||||
// Without the accompanying fix in place, the number format of the box regenerated the text
|
||||
// of the cell right after the copy, which dropped the deletion: only the insertion was left,
|
||||
// and the old value was gone for good.
|
||||
OUString aExpected(u"Insert:$2.00 Delete:$1.00"_ustr);
|
||||
CPPUNIT_ASSERT_EQUAL(aExpected, lcl_GetRedlines(pDoc));
|
||||
|
||||
// Leaving the cell must not drop it either, that is where the cell is checked against its
|
||||
// number format.
|
||||
pWrtShell->EndAllTableBoxEdit();
|
||||
CPPUNIT_ASSERT_EQUAL(aExpected, lcl_GetRedlines(pDoc));
|
||||
|
||||
// And undo and redo have to be symmetric: undo leaves the old value alone,
|
||||
pWrtShell->Undo();
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc));
|
||||
CPPUNIT_ASSERT_EQUAL(u"$1.00"_ustr, lcl_GetCellText(mxComponent, u"Table1"_ustr, u"C2"_ustr));
|
||||
|
||||
// ... and redo puts the very same pair of changes back. Without the fix, redo produced two
|
||||
// empty redlines instead, which piled up over further undo and redo rounds.
|
||||
pWrtShell->Redo();
|
||||
CPPUNIT_ASSERT_EQUAL(aExpected, lcl_GetRedlines(pDoc));
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testImagePropsCreateUndoAndModifyDoc)
|
||||
{
|
||||
createSwDoc("image-as-character.odt");
|
||||
|
||||
@@ -4276,6 +4276,25 @@ void SwCursorShell::ClearUpCursors()
|
||||
pStartCursor->DeleteMark();
|
||||
bChanged = true;
|
||||
}
|
||||
|
||||
// sw_PosOk only asks whether the position is in a content node, so a
|
||||
// content index pointing past the end of the text survives it.
|
||||
// It should not happen, but still happened when an undo left it behind when
|
||||
// it rebuilt the text it had selected.
|
||||
// Keep the offsets inside the node, so that users of the selection cannot run
|
||||
// out of bounds.
|
||||
for (SwPaM& rPaM : pStartCursor->GetRingContainer())
|
||||
{
|
||||
for (SwPosition* pPos : { rPaM.GetPoint(), rPaM.GetMark() })
|
||||
{
|
||||
const SwContentNode* pContentNode = pPos->GetNode().GetContentNode();
|
||||
if (pContentNode && pPos->GetContentIndex() > pContentNode->Len())
|
||||
{
|
||||
pPos->SetContent(pContentNode->Len());
|
||||
bChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pStartCursor->GetPoint()->GetNode().IsTableNode())
|
||||
{
|
||||
// tdf#106959: When cursor points to start of a table, the proper content
|
||||
|
||||
@@ -505,10 +505,19 @@ static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox,
|
||||
pDstBox->GetSttNd()->EndOfSectionIndex() -
|
||||
pDstBox->GetSttIdx() );
|
||||
|
||||
if( pUndo )
|
||||
pUndo->AddBoxBefore( *pDstBox, bDelContent );
|
||||
// Do not record (track) changes if the cell holds a formula.
|
||||
// The text that arrives with the copy will be recalculated based on the formula,
|
||||
// and that would eliminate the tracked change from the cell anyway.
|
||||
// TODO: if we want tracked changes to work on it, we should create a new type of
|
||||
// tracked change that track the formula changes, not the cell values changes.
|
||||
const bool bTrackChanges = !pCpyBox
|
||||
|| SfxItemState::SET
|
||||
!= pCpyBox->GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA, false );
|
||||
|
||||
bool bUndoRedline = pUndo && rDoc.getIDocumentRedlineAccess().IsRedlineOn();
|
||||
if( pUndo )
|
||||
pUndo->AddBoxBefore( *pDstBox, bDelContent, bTrackChanges );
|
||||
|
||||
bool bUndoRedline = pUndo && bTrackChanges && rDoc.getIDocumentRedlineAccess().IsRedlineOn();
|
||||
::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
|
||||
|
||||
SwNodeIndex aSavePos( aInsIdx, -1 );
|
||||
@@ -574,7 +583,7 @@ static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox,
|
||||
|
||||
//b6341295: Table copy redlining will be managed by AddBoxAfter()
|
||||
if( pUndo )
|
||||
pUndo->AddBoxAfter( *pDstBox, aInsIdx, bDelContent );
|
||||
pUndo->AddBoxAfter( *pDstBox, aInsIdx, bDelContent, bTrackChanges );
|
||||
|
||||
// heading
|
||||
SwTextNode *const pTextNd = aSavePos.GetNode().GetTextNode();
|
||||
|
||||
@@ -292,9 +292,11 @@ public:
|
||||
virtual void UndoImpl( ::sw::UndoRedoContext & ) override;
|
||||
virtual void RedoImpl( ::sw::UndoRedoContext & ) override;
|
||||
|
||||
void AddBoxBefore( const SwTableBox& rBox, bool bDelContent );
|
||||
/// bTrackChanges is false for a box that is copied without recording it as a change, even
|
||||
/// though changes are being recorded: see lcl_CpyBox().
|
||||
void AddBoxBefore( const SwTableBox& rBox, bool bDelContent, bool bTrackChanges );
|
||||
void AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx,
|
||||
bool bDelContent );
|
||||
bool bDelContent, bool bTrackChanges );
|
||||
|
||||
bool IsEmpty() const;
|
||||
bool InsertRow( SwTable& rTable, const SwSelBoxes& rBoxes, sal_uInt16 nCnt );
|
||||
|
||||
@@ -2431,7 +2431,16 @@ void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol,
|
||||
}
|
||||
rBox.SetSaveNumFormatColor( pCol ? *pCol : std::optional<Color>() );
|
||||
|
||||
if( pTNd->GetText() != rText )
|
||||
// What the cell shows is its text without what a tracked deletion took out of it, which is
|
||||
// also the text HasNumContent() and IsNumberChanged() work the value out from. Ask whether
|
||||
// that already says what the format wants: if it does there is nothing to do, and the
|
||||
// changes recorded in the cell are left alone.
|
||||
//
|
||||
// If it does not, the text is rewritten as a whole below, which loses the changes recorded
|
||||
// over it. That is coarse, but a number format is free to rewrite the text into anything -
|
||||
// a number can come out as words, or grow a thousands separator that shifts every position
|
||||
// after it - so there is no general way to carry a set of changes across a reformat.
|
||||
if( pTNd->GetRedlineText() != rText )
|
||||
{
|
||||
// Exchange text. Bugfix to keep Tabs (front and back!) and annotations (inword comment anchors)
|
||||
const OUString& rOrig = pTNd->GetText();
|
||||
@@ -2475,12 +2484,18 @@ void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol,
|
||||
while( nCommentPos > aIdx.GetIndex() && nCommentPos == nEndPos );
|
||||
}
|
||||
|
||||
const sal_Int32 nInsStt = aIdx.GetIndex();
|
||||
pTNd->EraseText( aIdx, n, SwInsertFlags::EMPTYEXPAND );
|
||||
pTNd->InsertText( rText, aIdx, SwInsertFlags::EMPTYEXPAND );
|
||||
|
||||
if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
|
||||
// The text of a box with a formula is worked out from the formula and is rewritten
|
||||
// whenever the table is recalculated, by a change to some other box as much as by one
|
||||
// here. Nobody wrote it, so do not record it as an insertion.
|
||||
const bool bFormulaBox = SfxItemState::SET
|
||||
== rBox.GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA, false );
|
||||
if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() && !bFormulaBox )
|
||||
{
|
||||
SwPaM aTemp(*pTNd, 0, *pTNd, rText.getLength());
|
||||
SwPaM aTemp(*pTNd, nInsStt, *pTNd, nInsStt + rText.getLength());
|
||||
rDoc.getIDocumentRedlineAccess().AppendRedline(new SwRangeRedline(RedlineType::Insert, aTemp), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include <config_wasm_strip.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <hintids.hxx>
|
||||
#include <utility>
|
||||
@@ -751,7 +752,10 @@ SwScanner::SwScanner(std::function<LanguageType(sal_Int32, sal_Int32, bool)> aGe
|
||||
, m_bClip(bClp)
|
||||
{
|
||||
m_nStartPos = m_nBegin = nStart;
|
||||
m_nEndPos = nEnd;
|
||||
// The range comes from a selection, which can be stale - e.g. an undo that
|
||||
// rebuilt the text leaves the cursor describing the old, longer text. Keep
|
||||
// the scan inside the string;
|
||||
m_nEndPos = std::min(nEnd, m_aPreDashReplacementText.getLength());
|
||||
|
||||
//MSWord f.e has special emdash and endash behaviour in that they break
|
||||
//words for the purposes of word counting, while a hyphen etc. doesn't.
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <libxml/xmlwriter.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <UndoTable.hxx>
|
||||
#include <UndoRedline.hxx>
|
||||
#include <UndoDelete.hxx>
|
||||
@@ -91,6 +93,10 @@ struct UndoTableCpyTable_Entry
|
||||
// Was the last paragraph of the new and the first paragraph of the old content joined?
|
||||
bool bJoin; // For redlining only
|
||||
|
||||
// Was this box copied as a change? A box that holds a formula is not, see lcl_CpyBox(),
|
||||
// and then undo and redo have to take the same route the copy took.
|
||||
bool bTrackChanges;
|
||||
|
||||
explicit UndoTableCpyTable_Entry( const SwTableBox& rBox );
|
||||
|
||||
void dumpAsXml(xmlTextWriterPtr pWriter) const;
|
||||
@@ -2360,7 +2366,7 @@ void SwUndoTableNumFormat::SetBox( const SwTableBox& rBox )
|
||||
|
||||
UndoTableCpyTable_Entry::UndoTableCpyTable_Entry( const SwTableBox& rBox )
|
||||
: nBoxIdx( rBox.GetSttIdx() ), nOffset( 0 ),
|
||||
bJoin( false )
|
||||
bJoin( false ), bTrackChanges( true )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2397,6 +2403,11 @@ void UndoTableCpyTable_Entry::dumpAsXml(xmlTextWriterPtr pWriter) const
|
||||
BAD_CAST(OString::boolean(bJoin).getStr()));
|
||||
(void)xmlTextWriterEndElement(pWriter);
|
||||
|
||||
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("bTrackChanges"));
|
||||
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
|
||||
BAD_CAST(OString::boolean(bTrackChanges).getStr()));
|
||||
(void)xmlTextWriterEndElement(pWriter);
|
||||
|
||||
(void)xmlTextWriterEndElement(pWriter);
|
||||
}
|
||||
|
||||
@@ -2411,6 +2422,39 @@ SwUndoTableCpyTable::~SwUndoTableCpyTable()
|
||||
m_pInsRowUndo.reset();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// Move cursors off the temporary node at the start of the box, onto the content that stays.
|
||||
//
|
||||
// UndoImpl() and RedoImpl() park cursors on that node while they hand the content of the box to
|
||||
// an undo object, the same way AddBoxBefore() does, and the node is deleted right after, so the
|
||||
// cursors have to come off it first. This is what AddBoxAfter() does at the end of a copy.
|
||||
void lcl_MoveCursorsOffTempNode(const SwTableBox & rBox)
|
||||
{
|
||||
SwPaM aPam(SwNodeIndex(*rBox.GetSttNd(), 1));
|
||||
if (!aPam.GetPoint()->GetNode().IsTextNode())
|
||||
return;
|
||||
aPam.SetMark();
|
||||
aPam.Move(fnMoveForward, GoInContent);
|
||||
::PaMCorrAbs(aPam, *aPam.GetPoint());
|
||||
}
|
||||
|
||||
// Put the cursor at the start of the content of the box that starts at nBoxStart.
|
||||
void lcl_SetCursorToBoxContent(::sw::UndoRedoContext & rContext,
|
||||
const SwTableNode *const pTableNd, SwNodeOffset const nBoxStart)
|
||||
{
|
||||
if (!pTableNd)
|
||||
return;
|
||||
const SwTableBox *const pBox = pTableNd->GetTable().GetTableBox(nBoxStart);
|
||||
if (!pBox || !pBox->GetSttNd())
|
||||
return;
|
||||
SwPaM & rPaM(rContext.GetCursorSupplier().CreateNewShellCursor());
|
||||
rPaM.DeleteMark();
|
||||
rPaM.GetPoint()->Assign(*pBox->GetSttNd());
|
||||
rPaM.Move(fnMoveForward, GoInContent);
|
||||
}
|
||||
}
|
||||
|
||||
void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext)
|
||||
{
|
||||
SwDoc & rDoc = rContext.GetDoc();
|
||||
@@ -2436,14 +2480,22 @@ void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext)
|
||||
SwTableBox& rBox = *pBox;
|
||||
|
||||
SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 );
|
||||
rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() );
|
||||
SwTextNode *const pNewNode(
|
||||
rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() ));
|
||||
|
||||
// b62341295: Redline for copying tables
|
||||
const SwNode *pEndNode = rBox.GetSttNd()->EndOfSectionNode();
|
||||
SwPaM aPam( aInsIdx.GetNode(), *pEndNode );
|
||||
std::unique_ptr<SwUndoDelete> pUndo;
|
||||
|
||||
if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) )
|
||||
{ // move cursors to the new node which precedes aPam, the way AddBoxBefore() does:
|
||||
// the content below is about to be handed to an undo object, and a cursor left in it
|
||||
// still points there once that object lets the nodes go.
|
||||
SwPosition const pos(*pNewNode, 0);
|
||||
::PaMCorrAbs(aPam, pos);
|
||||
}
|
||||
|
||||
if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) && pEntry->bTrackChanges )
|
||||
{
|
||||
bool bDeleteCompleteParagraph = false;
|
||||
bool bShiftPam = false;
|
||||
@@ -2465,8 +2517,14 @@ void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext)
|
||||
SwTextNode *pText = aTmpIdx.GetNode().GetTextNode();
|
||||
if( pText )
|
||||
{
|
||||
// The content start was recorded when the copy was done. The text of a
|
||||
// box can be rebuilt after that, by the number format of the box
|
||||
// regenerating it from the value, and that leaves the recorded offset
|
||||
// describing text that is no longer there. Keep it inside the text, the
|
||||
// same way the redline table clamps its own stale positions.
|
||||
aPam.GetPoint()->Assign(*pText,
|
||||
pUndoRedlineDelete->ContentStart() );
|
||||
std::min(pUndoRedlineDelete->ContentStart(),
|
||||
pText->GetText().getLength()) );
|
||||
}
|
||||
else
|
||||
aPam.GetPoint()->Assign( aTmpIdx );
|
||||
@@ -2510,6 +2568,7 @@ void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext)
|
||||
pEntry->pUndo = std::move(pUndo);
|
||||
|
||||
aInsIdx = rBox.GetSttIdx() + 1;
|
||||
lcl_MoveCursorsOffTempNode( rBox );
|
||||
rDoc.GetNodes().Delete( aInsIdx );
|
||||
|
||||
SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSet<RES_VERT_ORIENT, RES_VERT_ORIENT,
|
||||
@@ -2543,6 +2602,10 @@ void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext)
|
||||
{
|
||||
m_pInsRowUndo->UndoImpl(rContext);
|
||||
}
|
||||
|
||||
if (!m_vArr.empty())
|
||||
lcl_SetCursorToBoxContent(rContext, pTableNd, m_vArr[0]->nBoxIdx + m_vArr[0]->nOffset);
|
||||
|
||||
DEBUG_REDLINE( rDoc )
|
||||
}
|
||||
|
||||
@@ -2570,15 +2633,24 @@ void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext)
|
||||
SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 );
|
||||
|
||||
// b62341295: Redline for copying tables - Start.
|
||||
rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() );
|
||||
SwTextNode *const pNewNode(
|
||||
rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() ));
|
||||
SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode());
|
||||
std::unique_ptr<SwUndo> pUndo(IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags())
|
||||
const bool bRedlineBox = IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags())
|
||||
&& pEntry->bTrackChanges;
|
||||
if( !bRedlineBox )
|
||||
{ // as in UndoImpl() above: take cursors out of the content that the undo object below
|
||||
// is about to take over
|
||||
SwPosition const pos(*pNewNode, 0);
|
||||
::PaMCorrAbs(aPam, pos);
|
||||
}
|
||||
std::unique_ptr<SwUndo> pUndo(bRedlineBox
|
||||
? nullptr
|
||||
: std::make_unique<SwUndoDelete>(aPam, SwDeleteFlags::Default, true));
|
||||
if( pEntry->pUndo )
|
||||
{
|
||||
pEntry->pUndo->UndoImpl(rContext);
|
||||
if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) )
|
||||
if( bRedlineBox )
|
||||
{
|
||||
// PrepareRedline has to be called with the beginning of the old content
|
||||
// When new and old content has been joined, the rIter.pAktPam has been set
|
||||
@@ -2603,6 +2675,7 @@ void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext)
|
||||
// b62341295: Redline for copying tables - End.
|
||||
|
||||
aInsIdx = rBox.GetSttIdx() + 1;
|
||||
lcl_MoveCursorsOffTempNode( rBox );
|
||||
rDoc.GetNodes().Delete( aInsIdx );
|
||||
|
||||
SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSet<RES_VERT_ORIENT, RES_VERT_ORIENT,
|
||||
@@ -2630,15 +2703,21 @@ void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext)
|
||||
|
||||
pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx;
|
||||
}
|
||||
|
||||
if (!m_vArr.empty())
|
||||
lcl_SetCursorToBoxContent(rContext, pTableNd, m_vArr[0]->nBoxIdx + m_vArr[0]->nOffset);
|
||||
|
||||
DEBUG_REDLINE( rDoc )
|
||||
}
|
||||
|
||||
void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent )
|
||||
void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent,
|
||||
bool bTrackChanges )
|
||||
{
|
||||
if (!m_vArr.empty() && !bDelContent)
|
||||
return;
|
||||
|
||||
UndoTableCpyTable_Entry* pEntry = new UndoTableCpyTable_Entry( rBox );
|
||||
pEntry->bTrackChanges = bTrackChanges;
|
||||
m_vArr.push_back(std::unique_ptr<UndoTableCpyTable_Entry>(pEntry));
|
||||
|
||||
SwDoc& rDoc = rBox.GetFrameFormat()->GetDoc();
|
||||
@@ -2649,7 +2728,7 @@ void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent
|
||||
SwTextNode *const pNewNode(rDoc.GetNodes().MakeTextNode(aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl()));
|
||||
SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode() );
|
||||
|
||||
if( !rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
|
||||
if( !bTrackChanges || !rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
|
||||
{
|
||||
{ // move cursors to new node which precedes aPam
|
||||
SwPosition const pos(*pNewNode, 0);
|
||||
@@ -2670,7 +2749,8 @@ void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent
|
||||
DEBUG_REDLINE( rDoc )
|
||||
}
|
||||
|
||||
void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, bool bDelContent )
|
||||
void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx,
|
||||
bool bDelContent, bool bTrackChanges )
|
||||
{
|
||||
UndoTableCpyTable_Entry *const pEntry = m_vArr.back().get();
|
||||
|
||||
@@ -2687,7 +2767,7 @@ void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex
|
||||
pam.Move(fnMoveForward, GoInContent);
|
||||
::PaMCorrAbs(pam, *pam.GetPoint());
|
||||
}
|
||||
if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
|
||||
if( bTrackChanges && rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
|
||||
{
|
||||
SwPosition aTmpPos( rIdx );
|
||||
pEntry->pUndo = PrepareRedline( rDoc, rBox, aTmpPos, pEntry->bJoin, false );
|
||||
|
||||
Reference in New Issue
Block a user