tdf#60382 sw offapi: add change tracking of table/row deletion
This is a minimal extension of the text range based change
tracking to record and apply table row and table deletions
with full Undo/Redo support.
Add property IsNotTracked to com::sun::star::text::TextTableRow.
During recording of track changes, deletion of table rows wasn't
recorded: the rows removed completely with their text content.
Now the deletion deletes the cell content with change tracking,
setting also IsNotTracked property of table rows to FALSE. If
all tracked deletions were accepted in a row, and the result is
an empty row, the row will be removed, if its IsNotTracked
property is FALSE.
Note: Deletion of empty lines isn't recorded (they are simply
deleted). Hiding deleted rows hasn't been supported yet in
the Hide Changes mode.
OpenDocument supports only track changes of text ranges, but
not changes of the table structure, e.g. deletion of table
rows. For the native export it needs to extend ODF, and
depending on this future extension, can be based also on
SwExtraRedlineTable (which lacks of recording and Undo/Redo,
but supports OOXML round-trip of tracked table changes).
See also commit d688069023959ab97d14eb1dbfd5bf6ad3c1b160
"Add support for 'Table Row Redlines' in SW core" and its
follow-up commits.
Change-Id: I2e3807cf8ae8212bd51c210ef1c20c85878d0da8
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115804
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/offapi/com/sun/star/text/TextTableRow.idl b/offapi/com/sun/star/text/TextTableRow.idl
index d640d1c..77191c9 100644
--- a/offapi/com/sun/star/text/TextTableRow.idl
+++ b/offapi/com/sun/star/text/TextTableRow.idl
@@ -105,6 +105,14 @@ published service TextTableRow
@since LibreOffice 6.1
*/
[optional, property] com::sun::star::graphic::XGraphic BackGraphic;
/** If TRUE, the table row wasn't deleted or inserted with its tracked cell content
@since LibreOffice 7.2
*/
[optional, property, maybevoid] boolean IsNotTracked;
};
diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx
index 08510f6..19a2b6a 100644
--- a/sw/inc/doc.hxx
+++ b/sw/inc/doc.hxx
@@ -1489,6 +1489,8 @@ public:
bool BalanceRowHeight( const SwCursor& rCursor, bool bTstOnly, const bool bOptimize );
void SetRowBackground( const SwCursor& rCursor, const SvxBrushItem &rNew );
static bool GetRowBackground( const SwCursor& rCursor, std::unique_ptr<SvxBrushItem>& rToFill );
/// rNotTracked = false means that the row was deleted or inserted with its tracked cell content
void SetRowNotTracked( const SwCursor& rCursor, const SvxPrintItem &rNotTracked );
void SetTabBorders( const SwCursor& rCursor, const SfxItemSet& rSet );
void SetTabLineStyle( const SwCursor& rCursor,
const Color* pColor, bool bSetLine,
diff --git a/sw/inc/swtable.hxx b/sw/inc/swtable.hxx
index 5432a24..6696c94 100644
--- a/sw/inc/swtable.hxx
+++ b/sw/inc/swtable.hxx
@@ -444,6 +444,8 @@ public:
void RemoveFromTable();
const SwStartNode *GetSttNd() const { return m_pStartNode; }
sal_uLong GetSttIdx() const;
// it doesn't contain box content
bool IsEmpty() const;
// Search next/previous box with content.
SwTableBox* FindNextBox( const SwTable&, const SwTableBox*,
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index 9cceac0..317ad79 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -736,6 +736,7 @@
#define UNO_NAME_ITEMS "Items"
#define UNO_NAME_SELITEM "SelectedItem"
#define UNO_NAME_IS_SPLIT_ALLOWED "IsSplitAllowed"
#define UNO_NAME_IS_NOT_TRACKED "IsNotTracked"
#define UNO_NAME_CHAR_HIDDEN "CharHidden"
#define UNO_NAME_IS_FOLLOWING_TEXT_FLOW "IsFollowingTextFlow"
#define UNO_NAME_WIDTH_TYPE "WidthType"
diff --git a/sw/qa/extras/uiwriter/uiwriter2.cxx b/sw/qa/extras/uiwriter/uiwriter2.cxx
index 02d8163..d1323c8 100644
--- a/sw/qa/extras/uiwriter/uiwriter2.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter2.cxx
@@ -3666,6 +3666,83 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf118311)
assertXPath(pXmlDoc, "//page[1]//body/tab", 0);
}
CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testRedlineTableRowDeletion)
{
// load a 1-row table, and delete the row with enabled change tracking:
// now the row is not deleted silently, but keeps the deleted cell contents,
// and only accepting all of them will result the deletion of the table row.
SwDoc* pDoc = createDoc("tdf118311.fodt");
SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
CPPUNIT_ASSERT(pTextDoc);
// turn on red-lining and show changes
pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowDelete
| RedlineFlags::ShowInsert);
CPPUNIT_ASSERT_MESSAGE("redlining should be on",
pDoc->getIDocumentRedlineAccess().IsRedlineOn());
CPPUNIT_ASSERT_MESSAGE(
"redlines should be visible",
IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags()));
// check table
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "//page[1]//body/tab");
// delete table row with enabled change tracking
dispatchCommand(mxComponent, ".uno:DeleteRows", {});
// This was deleted without change tracking
discardDumpedLayout();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "//page[1]//body/tab");
// accept the deletion of the content of the first cell
SwEditShell* const pEditShell(pDoc->GetEditShell());
CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), pEditShell->GetRedlineCount());
pEditShell->AcceptRedline(0);
// table row was still not deleted
discardDumpedLayout();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "//page[1]//body/tab");
// accept last redline
pEditShell->AcceptRedline(0);
// table row (and the 1-row table) was deleted finally
discardDumpedLayout();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "//page[1]//body/tab", 0);
// Undo, and delete the row without change tracking
dispatchCommand(mxComponent, ".uno:Undo", {});
dispatchCommand(mxComponent, ".uno:Undo", {});
dispatchCommand(mxComponent, ".uno:Undo", {});
// table exists again
discardDumpedLayout();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "//page[1]//body/tab");
// disable change tracking
pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowDelete
| RedlineFlags::ShowInsert);
CPPUNIT_ASSERT_MESSAGE("redlining should be off",
!pDoc->getIDocumentRedlineAccess().IsRedlineOn());
// delete table row without change tracking
dispatchCommand(mxComponent, ".uno:DeleteRows", {});
// the table (row) was deleted
discardDumpedLayout();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "//page[1]//body/tab", 0);
}
CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf128335)
{
// Load the bugdoc, which has 3 textboxes.
diff --git a/sw/source/core/bastyp/init.cxx b/sw/source/core/bastyp/init.cxx
index 19d2f12..f1b86f8 100644
--- a/sw/source/core/bastyp/init.cxx
+++ b/sw/source/core/bastyp/init.cxx
@@ -206,6 +206,8 @@ sal_uInt16 const aTableSetRange[] = {
sal_uInt16 const aTableLineSetRange[] = {
RES_FILL_ORDER, RES_FRM_SIZE,
// IsNotTracked
RES_PRINT, RES_PRINT,
RES_LR_SPACE, RES_UL_SPACE,
RES_BACKGROUND, RES_SHADOW,
RES_ROW_SPLIT, RES_ROW_SPLIT,
diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx b/sw/source/core/doc/DocumentRedlineManager.cxx
index 1c63619..0e92f16 100644
--- a/sw/source/core/doc/DocumentRedlineManager.cxx
+++ b/sw/source/core/doc/DocumentRedlineManager.cxx
@@ -38,6 +38,7 @@
#include <strings.hrc>
#include <swmodule.hxx>
#include <osl/diagnose.h>
#include <editeng/prntitem.hxx>
using namespace com::sun::star;
@@ -430,6 +431,36 @@ namespace
return nullptr;
}
// delete the empty tracked table row (i.e. if it's last tracked deletion was accepted)
void lcl_DeleteTrackedTableRow ( SwPosition* pPos )
{
if ( const SwTableBox* pBox = pPos->nNode.GetNode().GetTableBox() )
{
const SwTableLine* pLine = pBox->GetUpper();
const SvxPrintItem *pIsNoTrackedProp =
pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
// table row property "IsNotTracked" is set and its value is false
if ( pIsNoTrackedProp && !pIsNoTrackedProp->GetValue() )
{
bool bEmptyLine = true;
const SwTableBoxes & rBoxes = pLine->GetTabBoxes();
for (size_t nBox = 0; nBox < rBoxes.size(); ++nBox)
{
if ( !rBoxes[nBox]->IsEmpty() )
{
bEmptyLine = false;
break;
}
}
if ( bEmptyLine )
{
SwCursor aCursor( *pPos, nullptr );
pPos->GetDoc().DeleteRow( aCursor );
}
}
}
}
bool lcl_AcceptRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos,
bool bCallDelete,
const SwPosition* pSttRng = nullptr,
@@ -565,7 +596,10 @@ namespace
rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
if( pCSttNd && pCEndNd )
{
rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
lcl_DeleteTrackedTableRow( aPam.End() );
}
else if (pCSttNd && !pCEndNd)
{
aPam.GetBound().nContent.Assign( nullptr, 0 );
diff --git a/sw/source/core/docnode/ndtbl1.cxx b/sw/source/core/docnode/ndtbl1.cxx
index 6d2a677..9a4f80e 100644
--- a/sw/source/core/docnode/ndtbl1.cxx
+++ b/sw/source/core/docnode/ndtbl1.cxx
@@ -536,6 +536,32 @@ bool SwDoc::GetRowBackground( const SwCursor& rCursor, std::unique_ptr<SvxBrushI
return bRet;
}
void SwDoc::SetRowNotTracked( const SwCursor& rCursor, const SvxPrintItem &rNew )
{
SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode();
if( !pTableNd )
return;
std::vector<SwTableLine*> aRowArr; // For Lines collecting
::lcl_CollectLines( aRowArr, rCursor, true );
if( aRowArr.empty() )
return;
if (GetIDocumentUndoRedo().DoesUndo())
{
GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd));
}
std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp;
aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) );
for( auto pLn : aRowArr )
::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew );
getIDocumentState().SetModified();
}
static void InsertCell( std::vector<SwCellFrame*>& rCellArr, SwCellFrame* pCellFrame )
{
if( rCellArr.end() == std::find( rCellArr.begin(), rCellArr.end(), pCellFrame ) )
diff --git a/sw/source/core/frmedt/fetab.cxx b/sw/source/core/frmedt/fetab.cxx
index 1a44707..bf7f1d2 100644
--- a/sw/source/core/frmedt/fetab.cxx
+++ b/sw/source/core/frmedt/fetab.cxx
@@ -32,7 +32,9 @@
#include <fmtornt.hxx>
#include <frmatr.hxx>
#include <fesh.hxx>
#include <wrtsh.hxx>
#include <doc.hxx>
#include <docsh.hxx>
#include <IDocumentState.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <cntfrm.hxx>
@@ -325,6 +327,34 @@ bool SwFEShell::DeleteRow(bool bCompleteTable)
}
CurrShell aCurr( this );
// tracked deletion: remove only textbox content,
// and set IsNoTracked table line property to false
if ( GetDoc()->GetDocShell()->IsChangeRecording() )
{
StartUndo(bCompleteTable ? SwUndoId::UI_TABLE_DELETE : SwUndoId::ROW_DELETE);
StartAllAction();
SvxPrintItem aNotTracked(RES_PRINT, false);
GetDoc()->SetRowNotTracked( *getShellCursor( false ), aNotTracked );
if ( SwWrtShell* pWrtShell = dynamic_cast<SwWrtShell*>(this) )
pWrtShell->SelectTableRow();
SwEditShell* pEditShell = GetDoc()->GetEditShell();
SwRedlineTable::size_type nPrev = pEditShell->GetRedlineCount();
pEditShell->Delete();
EndAllActionAndCall();
EndUndo(bCompleteTable ? SwUndoId::UI_TABLE_DELETE : SwUndoId::ROW_DELETE);
// track row deletion only if there were tracked text changes
// FIXME redline count can be the same in special cases, e.g. adding a
// new tracked deletion with removing an own tracked insertion...
if ( nPrev != pEditShell->GetRedlineCount() )
return true;
}
StartAllAction();
// search for boxes via the layout
diff --git a/sw/source/core/table/swtable.cxx b/sw/source/core/table/swtable.cxx
index 65f3974..fda4da0 100644
--- a/sw/source/core/table/swtable.cxx
+++ b/sw/source/core/table/swtable.cxx
@@ -1868,6 +1868,21 @@ sal_uLong SwTableBox::GetSttIdx() const
return m_pStartNode ? m_pStartNode->GetIndex() : 0;
}
bool SwTableBox::IsEmpty() const
{
const SwStartNode *pSttNd = GetSttNd();
if( pSttNd &&
pSttNd->GetIndex() + 2 == pSttNd->EndOfSectionIndex() )
{
const SwContentNode *pCNd =
pSttNd->GetNodes()[pSttNd->GetIndex()+1]->GetContentNode();
if( pCNd && !pCNd->Len() )
return true;
}
return false;
}
// retrieve information from the client
bool SwTable::GetInfo( SfxPoolItem& rInfo ) const
{
diff --git a/sw/source/core/unocore/unomap.cxx b/sw/source/core/unocore/unomap.cxx
index 247f3d8..55b948a 100644
--- a/sw/source/core/unocore/unomap.cxx
+++ b/sw/source/core/unocore/unomap.cxx
@@ -542,6 +542,7 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetPropertyMapEntries(s
{ u"" UNO_NAME_SIZE_TYPE, RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_SIZE_TYPE },
{ u"" UNO_NAME_WIDTH_TYPE, RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_WIDTH_TYPE },
{ u"" UNO_NAME_IS_SPLIT_ALLOWED, RES_ROW_SPLIT, cppu::UnoType<bool>::get() , PropertyAttribute::MAYBEVOID, 0},
{ u"" UNO_NAME_IS_NOT_TRACKED, RES_PRINT, cppu::UnoType<bool>::get() , PropertyAttribute::MAYBEVOID, 0},
{ u"" UNO_NAME_ROW_INTEROP_GRAB_BAG, RES_FRMATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 },
{ u"", 0, css::uno::Type(), 0, 0 }
};
diff --git a/sw/source/filter/html/htmltab.cxx b/sw/source/filter/html/htmltab.cxx
index b56a016..5149640 100644
--- a/sw/source/filter/html/htmltab.cxx
+++ b/sw/source/filter/html/htmltab.cxx
@@ -1226,21 +1226,6 @@ const SwStartNode* HTMLTable::GetPrevBoxStartNode( sal_uInt16 nRow, sal_uInt16 n
return pTable->GetPrevBoxStartNode(USHRT_MAX, USHRT_MAX);
}
static bool IsBoxEmpty( const SwTableBox *pBox )
{
const SwStartNode *pSttNd = pBox->GetSttNd();
if( pSttNd &&
pSttNd->GetIndex() + 2 == pSttNd->EndOfSectionIndex() )
{
const SwContentNode *pCNd =
pSttNd->GetNodes()[pSttNd->GetIndex()+1]->GetContentNode();
if( pCNd && !pCNd->Len() )
return true;
}
return false;
}
sal_uInt16 HTMLTable::GetTopCellSpace( sal_uInt16 nRow ) const
{
sal_uInt16 nSpace = m_nCellPadding;
@@ -1413,7 +1398,7 @@ void HTMLTable::FixFrameFormat( SwTableBox *pBox,
pFrameFormat->ResetFormatAttr( RES_BACKGROUND );
// Only set format if there's a value or the box is empty
if( bHasNumFormat && (bHasValue || IsBoxEmpty(pBox)) )
if( bHasNumFormat && (bHasValue || pBox->IsEmpty()) )
{
bool bLock = pFrameFormat->GetDoc()->GetNumberFormatter()
->IsTextFormat( nNumFormat );