Kaydet (Commit) 8a5b9413 authored tarafından Kohei Yoshida's avatar Kohei Yoshida

bnc#484599: Prevent pivot table from getting sheared when cells are shifted.

Change-Id: Ic6766105bb221aa4ebc700cbf99b4f6f5b3abf8b
üst 0941a1b0
......@@ -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;
......
......@@ -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);
......
......@@ -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
......@@ -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)
......
......@@ -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))
......
......@@ -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;
......
......@@ -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.";
};
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment