summaryrefslogtreecommitdiff
path: root/sc
diff options
context:
space:
mode:
authorKohei Yoshida <kohei.yoshida@gmail.com>2013-01-29 17:39:45 -0500
committerKohei Yoshida <kohei.yoshida@gmail.com>2013-01-29 17:42:19 -0500
commit8a5b9413bd098b4643a0ca73eaa4834e962e5647 (patch)
tree9b5a932435143dfbf9340922e4d13039171bd6e5 /sc
parent0941a1b0ad99be73f1b051aae79a7a433fefeadf (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.hxx2
-rw-r--r--sc/inc/dpobject.hxx4
-rw-r--r--sc/inc/globstr.hrc4
-rw-r--r--sc/source/core/data/documen3.cxx10
-rw-r--r--sc/source/core/data/dpobject.cxx98
-rw-r--r--sc/source/ui/docshell/docfunc.cxx192
-rw-r--r--sc/source/ui/src/globstr.src5
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.";
+ };
};