diff options
author | Kohei Yoshida <kohei.yoshida@gmail.com> | 2013-01-29 17:39:45 -0500 |
---|---|---|
committer | Kohei Yoshida <kohei.yoshida@gmail.com> | 2013-01-29 17:42:19 -0500 |
commit | 8a5b9413bd098b4643a0ca73eaa4834e962e5647 (patch) | |
tree | 9b5a932435143dfbf9340922e4d13039171bd6e5 /sc | |
parent | 0941a1b0ad99be73f1b051aae79a7a433fefeadf (diff) |
bnc#484599: Prevent pivot table from getting sheared when cells are shifted.
Change-Id: Ic6766105bb221aa4ebc700cbf99b4f6f5b3abf8b
Diffstat (limited to 'sc')
-rw-r--r-- | sc/inc/document.hxx | 2 | ||||
-rw-r--r-- | sc/inc/dpobject.hxx | 4 | ||||
-rw-r--r-- | sc/inc/globstr.hrc | 4 | ||||
-rw-r--r-- | sc/source/core/data/documen3.cxx | 10 | ||||
-rw-r--r-- | sc/source/core/data/dpobject.cxx | 98 | ||||
-rw-r--r-- | sc/source/ui/docshell/docfunc.cxx | 192 | ||||
-rw-r--r-- | sc/source/ui/src/globstr.src | 5 |
7 files changed, 313 insertions, 2 deletions
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx index b857cb7336f3..8e12ba39a5d2 100644 --- a/sc/inc/document.hxx +++ b/sc/inc/document.hxx @@ -483,7 +483,9 @@ public: SC_DLLPUBLIC const ScRangeData* GetRangeAtBlock( const ScRange& rBlock, rtl::OUString* pName=NULL ) const; + bool HasPivotTable() const; SC_DLLPUBLIC ScDPCollection* GetDPCollection(); + SC_DLLPUBLIC const ScDPCollection* GetDPCollection() const; ScDPObject* GetDPAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab) const; ScDPObject* GetDPAtBlock( const ScRange& rBlock ) const; diff --git a/sc/inc/dpobject.hxx b/sc/inc/dpobject.hxx index fc8d066d2be0..b0971ac8bce2 100644 --- a/sc/inc/dpobject.hxx +++ b/sc/inc/dpobject.hxx @@ -395,6 +395,10 @@ public: NameCaches& GetNameCaches(); DBCaches& GetDBCaches(); + bool IntersectsTableByColumns( SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab ) const; + bool IntersectsTableByRows( SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab ) const; + bool HasTable( const ScRange& rRange ) const; + private: /** Only to be called from ScDPCache::RemoveReference(). */ void RemoveCache(const ScDPCache* pCache); diff --git a/sc/inc/globstr.hrc b/sc/inc/globstr.hrc index bf0451ccc1f0..7709579ebb01 100644 --- a/sc/inc/globstr.hrc +++ b/sc/inc/globstr.hrc @@ -634,6 +634,8 @@ #define STR_QUERY_FORMULA_RECALC_ONLOAD_XLS 507 #define STR_ALWAYS_PERFORM_SELECTED 508 -#define STR_COUNT 509 +#define STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE 509 + +#define STR_COUNT 510 #endif diff --git a/sc/source/core/data/documen3.cxx b/sc/source/core/data/documen3.cxx index 1fb4c5bb5a4c..60e5a4e2bd35 100644 --- a/sc/source/core/data/documen3.cxx +++ b/sc/source/core/data/documen3.cxx @@ -302,6 +302,11 @@ ScDBData* ScDocument::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nC return NULL; } +bool ScDocument::HasPivotTable() const +{ + return pDPCollection && pDPCollection->GetCount(); +} + ScDPCollection* ScDocument::GetDPCollection() { if (!pDPCollection) @@ -309,6 +314,11 @@ ScDPCollection* ScDocument::GetDPCollection() return pDPCollection; } +const ScDPCollection* ScDocument::GetDPCollection() const +{ + return pDPCollection; +} + ScDPObject* ScDocument::GetDPAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab) const { if (!pDPCollection) diff --git a/sc/source/core/data/dpobject.cxx b/sc/source/core/data/dpobject.cxx index c82926827f22..6823ed5c2b45 100644 --- a/sc/source/core/data/dpobject.cxx +++ b/sc/source/core/data/dpobject.cxx @@ -561,6 +561,86 @@ public: } }; +class FindIntersectingTable : std::unary_function<ScDPObject, bool> +{ + ScRange maRange; +public: + FindIntersectingTable(const ScRange& rRange) : maRange(rRange) {} + + bool operator() (const ScDPObject& rObj) const + { + return maRange.Intersects(rObj.GetOutRange()); + } +}; + +class FindIntersetingTableByColumns : std::unary_function<ScDPObject, bool> +{ + SCCOL mnCol1; + SCCOL mnCol2; + SCROW mnRow; + SCTAB mnTab; +public: + FindIntersetingTableByColumns(SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab) : + mnCol1(nCol1), mnCol2(nCol2), mnRow(nRow), mnTab(nTab) {} + + bool operator() (const ScDPObject& rObj) const + { + const ScRange& rRange = rObj.GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return false; + + if (rRange.aEnd.Row() < mnRow) + // This table is above the row. It's safe. + return false; + + if (mnCol1 <= rRange.aStart.Col() && rRange.aEnd.Col() <= mnCol2) + // This table is fully enclosed in this column range. + return false; + + if (rRange.aEnd.Col() < mnCol1 || mnCol2 < rRange.aStart.Col()) + // This table is entirely outside this column range. + return false; + + // This table must be intersected by this column range. + return true; + } +}; + +class FindIntersectingTableByRows : std::unary_function<ScDPObject, bool> +{ + SCCOL mnCol; + SCROW mnRow1; + SCROW mnRow2; + SCTAB mnTab; +public: + FindIntersectingTableByRows(SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab) : + mnCol(nCol), mnRow1(nRow1), mnRow2(nRow2), mnTab(nTab) {} + + bool operator() (const ScDPObject& rObj) const + { + const ScRange& rRange = rObj.GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return false; + + if (rRange.aEnd.Col() < mnCol) + // This table is to the left of the column. It's safe. + return false; + + if (mnRow1 <= rRange.aStart.Row() && rRange.aEnd.Row() <= mnRow2) + // This table is fully enclosed in this row range. + return false; + + if (rRange.aEnd.Row() < mnRow1 || mnRow2 < rRange.aStart.Row()) + // This table is entirely outside this row range. + return false; + + // This table must be intersected by this row range. + return true; + } +}; + } ScDPTableData* ScDPObject::GetTableData() @@ -3398,6 +3478,24 @@ ScDPCollection::DBCaches& ScDPCollection::GetDBCaches() return maDBCaches; } +bool ScDPCollection::IntersectsTableByColumns( SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab ) const +{ + return std::find_if( + maTables.begin(), maTables.end(), FindIntersetingTableByColumns(nCol1, nCol2, nRow, nTab)) != maTables.end(); +} + +bool ScDPCollection::IntersectsTableByRows( SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab ) const +{ + return std::find_if( + maTables.begin(), maTables.end(), FindIntersectingTableByRows(nCol, nRow1, nRow2, nTab)) != maTables.end(); +} + +bool ScDPCollection::HasTable( const ScRange& rRange ) const +{ + return std::find_if( + maTables.begin(), maTables.end(), FindIntersectingTable(rRange)) != maTables.end(); +} + void ScDPCollection::RemoveCache(const ScDPCache* pCache) { if (maSheetCaches.remove(pCache)) diff --git a/sc/source/ui/docshell/docfunc.cxx b/sc/source/ui/docshell/docfunc.cxx index b8f17c543c04..770180d4e7fa 100644 --- a/sc/source/ui/docshell/docfunc.cxx +++ b/sc/source/ui/docshell/docfunc.cxx @@ -79,6 +79,7 @@ #include "externalrefmgr.hxx" #include "undorangename.hxx" #include "progress.hxx" +#include "dpobject.hxx" #include <memory> #include <basic/basmgr.hxx> @@ -1346,7 +1347,182 @@ sal_Bool ScDocFunc::ApplyStyle( const ScMarkData& rMark, const String& rStyleNam return sal_True; } -//------------------------------------------------------------------------ +namespace { + +/** + * Check if this insertion attempt would end up cutting one or more pivot + * tables in half, which is not desirable. + * + * @return true if this insertion can be done safely without shearing any + * existing pivot tables, false otherwise. + */ +bool canInsertCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, InsCellCmd eCmd, const ScDocument* pDoc) +{ + if (!pDoc->HasPivotTable()) + // This document has no pivot tables. + return true; + + const ScDPCollection* pDPs = pDoc->GetDPCollection(); + ScMarkData::const_iterator itBeg = rMarkData.begin(), itEnd = rMarkData.end(); + + ScRange aRange(rRange); // local copy + switch (eCmd) + { + case INS_INSROWS: + { + aRange.aStart.SetCol(0); + aRange.aEnd.SetCol(MAXCOL); + // Continue below. + } + case INS_CELLSDOWN: + { + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + if (pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), *it)) + // This column range cuts through at least one pivot table. Not good. + return false; + } + + // Start row must be either at the top or above any pivot tables. + if (aRange.aStart.Row() < 0) + // I don't know how to handle this case. + return false; + + if (aRange.aStart.Row() == 0) + // First row is always allowed. + return true; + + ScRange aTest(aRange); + aTest.aStart.IncRow(-1); // Test one row up. + aTest.aEnd.SetRow(aTest.aStart.Row()); + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + aTest.aStart.SetTab(*it); + aTest.aEnd.SetTab(*it); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + case INS_INSCOLS: + { + aRange.aStart.SetRow(0); + aRange.aEnd.SetRow(MAXROW); + // Continue below. + } + case INS_CELLSRIGHT: + { + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + if (pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), *it)) + // This column range cuts through at least one pivot table. Not good. + return false; + } + + // Start row must be either at the top or above any pivot tables. + if (aRange.aStart.Col() < 0) + // I don't know how to handle this case. + return false; + + if (aRange.aStart.Col() == 0) + // First row is always allowed. + return true; + + ScRange aTest(aRange); + aTest.aStart.IncCol(-1); // Test one column to the left. + aTest.aEnd.SetCol(aTest.aStart.Col()); + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + aTest.aStart.SetTab(*it); + aTest.aEnd.SetTab(*it); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + default: + ; + } + return true; +} + +/** + * Check if this deletion attempt would end up cutting one or more pivot + * tables in half, which is not desirable. + * + * @return true if this deletion can be done safely without shearing any + * existing pivot tables, false otherwise. + */ +bool canDeleteCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, DelCellCmd eCmd, const ScDocument* pDoc) +{ + if (!pDoc->HasPivotTable()) + // This document has no pivot tables. + return true; + + const ScDPCollection* pDPs = pDoc->GetDPCollection(); + ScMarkData::const_iterator itBeg = rMarkData.begin(), itEnd = rMarkData.end(); + + ScRange aRange(rRange); // local copy + + switch (eCmd) + { + case DEL_DELROWS: + { + aRange.aStart.SetCol(0); + aRange.aEnd.SetCol(MAXCOL); + // Continue below. + } + case DEL_CELLSUP: + { + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + if (pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), *it)) + // This column range cuts through at least one pivot table. Not good. + return false; + } + + ScRange aTest(aRange); + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + aTest.aStart.SetTab(*it); + aTest.aEnd.SetTab(*it); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + case DEL_DELCOLS: + { + aRange.aStart.SetRow(0); + aRange.aEnd.SetRow(MAXROW); + // Continue below. + } + case DEL_CELLSLEFT: + { + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + if (pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), *it)) + // This column range cuts through at least one pivot table. Not good. + return false; + } + + ScRange aTest(aRange); + for (ScMarkData::const_iterator it = itBeg; it != itEnd; ++it) + { + aTest.aStart.SetTab(*it); + aTest.aEnd.SetTab(*it); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + default: + ; + } + return true; +} + +} bool ScDocFunc::InsertCells( const ScRange& rRange, const ScMarkData* pTabMark, InsCellCmd eCmd, bool bRecord, bool bApi, bool bPartOfPaste ) @@ -1463,6 +1639,14 @@ bool ScDocFunc::InsertCells( const ScRange& rRange, const ScMarkData* pTabMark, return false; } + // Check if this insertion is allowed with respect to pivot table. + if (!canInsertCellsByPivot(rRange, aMark, eCmd, pDoc)) + { + if (!bApi) + rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE); + return false; + } + WaitObject aWait( rDocShell.GetActiveDialogParent() ); // wichtig wegen TrackFormulas bei UpdateReference ScDocument* pRefUndoDoc = NULL; @@ -1880,6 +2064,12 @@ bool ScDocFunc::DeleteCells( const ScRange& rRange, const ScMarkData* pTabMark, return false; } + if (!canDeleteCellsByPivot(rRange, aMark, eCmd, pDoc)) + { + if (!bApi) + rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE); + return false; + } // Test zusammengefasste SCCOL nMergeTestEndCol = (eCmd==DEL_CELLSLEFT) ? MAXCOL : nUndoEndCol; diff --git a/sc/source/ui/src/globstr.src b/sc/source/ui/src/globstr.src index 6d53c05c40d9..73059504c0a8 100644 --- a/sc/source/ui/src/globstr.src +++ b/sc/source/ui/src/globstr.src @@ -2017,5 +2017,10 @@ Resource RID_GLOBSTR { Text [ en-US ] = "Always perform this without prompt in the future."; }; + + String STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE + { + Text [ en-US ] = "You cannot insert or delete cells when the affected range intersects with pivot table."; + }; }; |