sc/qa/unit/tiledrendering/SheetViewTest.cxx | 100 +++++++++++++++++++++ sc/source/ui/inc/docfunc.hxx | 2 sc/source/ui/inc/operation/AutoFormatOperation.hxx | 3 sc/source/ui/inc/operation/Operation.hxx | 12 ++ sc/source/ui/operation/AutoFormatOperation.cxx | 52 ++++++++-- sc/source/ui/operation/Operation.cxx | 17 ++- 6 files changed, 169 insertions(+), 17 deletions(-)
New commits: commit 83c9c9909beb137b57d604b83d66b11802d3715d Author: Tomaž Vajngerl <[email protected]> AuthorDate: Sat Mar 7 10:29:58 2026 +0900 Commit: Miklos Vajna <[email protected]> CommitDate: Tue Mar 10 14:14:58 2026 +0100 sc: Add AutoFormatOperation sync, check to run mechanism This adds sheet view sync to AutoFormatOperation. AutoFormat is special in that it can't be run on a sheet view if the input range intersects with the auto filter. The reason is that we don't have control over how the cells will be resorted in the auto filter and will probably produce wrong results. It is ok to run it outside of the auto filter as in that case we only copy the data from the default view to sheet views with no need to take sorting into account. This adds canRunTheOperation virtual method to Operation, which will be used to reject running the operation if it returns false. In the case we check if the input is on sheet view and the input intersects with the auto filter, we disallow running the operation. Change-Id: I0f847cc097b90d463b0746b9946a5cc7d4a2e1fd Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201323 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sc/qa/unit/tiledrendering/SheetViewTest.cxx b/sc/qa/unit/tiledrendering/SheetViewTest.cxx index 18c6590b1979..c8751ff08a25 100644 --- a/sc/qa/unit/tiledrendering/SheetViewTest.cxx +++ b/sc/qa/unit/tiledrendering/SheetViewTest.cxx @@ -22,6 +22,7 @@ #include <scitems.hxx> #include <SheetView.hxx> #include <SheetViewManager.hxx> +#include <editeng/brushitem.hxx> using namespace css; @@ -133,6 +134,27 @@ protected: return aString; } + static OUString getBackgroundColor(ScDocument* pDocument, SCCOL nCol, SCROW nStartRow, + SCROW nEndRow, SCTAB nTab) + { + OUString aString; + bool bFirst = true; + for (SCROW nRow = nStartRow; nRow <= nEndRow; nRow++) + { + const ScPatternAttr* pPattern = pDocument->GetPattern(nCol, nRow, nTab); + const SvxBrushItem& rBrush = pPattern->GetItem(ATTR_BACKGROUND); + OUString aColor = rBrush.GetColor().AsRGBHexString(); + if (bFirst) + { + bFirst = false; + aString = u"\""_ustr + aColor + u"\""_ustr; + } + else + aString += u", \""_ustr + aColor + u"\""_ustr; + } + return aString; + } + void gotoCell(std::u16string_view aCellAddress) { dispatchCommand( @@ -1722,6 +1744,84 @@ CPPUNIT_TEST_FIXTURE(SyncTest, testSync_SheetView_ClearItemsOperation) getTextWeight(pDocument, 0, 1, 4, 0)); } +CPPUNIT_TEST_FIXTURE(SyncTest, testSync_AutoFormat_DefaultAndSheetView) +{ + ScModelObj* pModelObj = createDoc("SheetView_AutoFilter.ods"); + pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + ScDocument* pDocument = pModelObj->GetDocument(); + ScDocShell* pDocShell = dynamic_cast<ScDocShell*>(pModelObj->GetEmbeddedObject()); + + setupViews(); + + // Create new sheet view and sort autofilter ascending + { + switchToSheetView(); + createNewSheetViewInCurrentView(); + sortAscendingForCell(u"A1"); + } + + // Sort autofilter descending in default view + { + switchToDefaultView(); + sortDescendingForCell(u"A1"); + } + + // Switch to sheet view and apply AutoFormat + { + switchToSheetView(); + + // Current state default view + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"7", u"5", u"4", u"3" }), + getValues(mpTabViewDefaultView, 0, 1, 4)); + + // Current state sheet view + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"3", u"4", u"5", u"7" }), + getValues(mpTabViewSheetView, 0, 1, 4)); + + // Initial text weights + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"N", u"N", u"N", u"N" }), + getTextWeight(pDocument, 0, 1, 4, 0)); + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"N", u"N", u"N", u"N" }), + getTextWeight(pDocument, 0, 1, 4, 1)); + + // Apply AutoFormat (format 0) to A2:A5 on default view (index 0) + pDocShell->GetDocFunc().AutoFormat(ScRange(0, 1, 0, 0, 4, 0), nullptr, 0, true); + + // Values should remain unchanged after AutoFormat + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"7", u"5", u"4", u"3" }), + getValues(mpTabViewDefaultView, 0, 1, 4)); + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"3", u"4", u"5", u"7" }), + getValues(mpTabViewSheetView, 0, 1, 4)); + + // First row is defined as header row, so we can exploit that to test resorting. + // Default view: AutoFormat applies blue to first row, gray to others + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"000080", u"cccccc", u"cccccc", u"cccccc" }), + getBackgroundColor(pDocument, 0, 1, 4, 0)); + + // Sheet view: background colors synced and re-sorted + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"cccccc", u"cccccc", u"cccccc", u"000080" }), + getBackgroundColor(pDocument, 0, 1, 4, 1)); + } + + // AutoFormat on the sheet view should be blocked - input intersects autofilter + { + switchToSheetView(); + + SCTAB nSheetViewTab = mpTabViewSheetView->GetViewData().GetTabNumber(); + bool bResult = pDocShell->GetDocFunc().AutoFormat( + ScRange(0, 1, nSheetViewTab, 0, 4, nSheetViewTab), nullptr, 1, true); + + // Returns false because the running is blocked + CPPUNIT_ASSERT(!bResult); + + // Background colors should remain unchanged + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"000080", u"cccccc", u"cccccc", u"cccccc" }), + getBackgroundColor(pDocument, 0, 1, 4, 0)); + CPPUNIT_ASSERT_EQUAL(expectedValues({ u"cccccc", u"cccccc", u"cccccc", u"000080" }), + getBackgroundColor(pDocument, 0, 1, 4, nSheetViewTab)); + } +} + CPPUNIT_TEST_FIXTURE(SyncTest, testSync_EnterMatrix_DefaultAndSheetView) { ScModelObj* pModelObj = createDoc("SheetView_AutoFilter.ods"); diff --git a/sc/source/ui/inc/docfunc.hxx b/sc/source/ui/inc/docfunc.hxx index 11c3fac01157..9e22f01610c6 100644 --- a/sc/source/ui/inc/docfunc.hxx +++ b/sc/source/ui/inc/docfunc.hxx @@ -180,7 +180,7 @@ public: SC_DLLPUBLIC void ClearItems( const ScMarkData& rMark, const sal_uInt16* pWhich, bool bApi ); bool ChangeIndent( const ScMarkData& rMark, bool bIncrement, bool bApi ); - bool AutoFormat( const ScRange& rRange, const ScMarkData* pTabMark, + SC_DLLPUBLIC bool AutoFormat( const ScRange& rRange, const ScMarkData* pTabMark, sal_uInt16 nFormatNo, bool bApi ); SC_DLLPUBLIC bool diff --git a/sc/source/ui/inc/operation/AutoFormatOperation.hxx b/sc/source/ui/inc/operation/AutoFormatOperation.hxx index d215a67204f6..05e51298f897 100644 --- a/sc/source/ui/inc/operation/AutoFormatOperation.hxx +++ b/sc/source/ui/inc/operation/AutoFormatOperation.hxx @@ -29,6 +29,9 @@ private: ScMarkData const* mpTabMark; sal_uInt16 mnFormatNo; + bool isInputOnSheetViewAutoFilter() const; + + bool canRunTheOperation() const override; bool runImplementation() override; public: diff --git a/sc/source/ui/inc/operation/Operation.hxx b/sc/source/ui/inc/operation/Operation.hxx index 306071b36b9f..c6bd9f57f4ba 100644 --- a/sc/source/ui/inc/operation/Operation.hxx +++ b/sc/source/ui/inc/operation/Operation.hxx @@ -55,12 +55,20 @@ protected: /** Synchronizes the sheet views and the default view */ void syncSheetViews(); + /** Check if the input is on a sheet view tab */ + bool isInputOnSheetView() const; + + /** Check if we can run the operation or not */ + virtual bool canRunTheOperation() const { return true; } + + /** Operation implementation */ + virtual bool runImplementation() = 0; + public: Operation(OperationType eType, bool bRecord, bool bApi); + /** Run the operation */ bool run(); - - virtual bool runImplementation() = 0; }; } // end sc namespace diff --git a/sc/source/ui/operation/AutoFormatOperation.cxx b/sc/source/ui/operation/AutoFormatOperation.cxx index 0a5cdffb5a03..e99962a71448 100644 --- a/sc/source/ui/operation/AutoFormatOperation.cxx +++ b/sc/source/ui/operation/AutoFormatOperation.cxx @@ -11,6 +11,7 @@ #include <autoform.hxx> #include <columnspanset.hxx> +#include <dbdata.hxx> #include <docfunc.hxx> #include <docsh.hxx> #include <editable.hxx> @@ -40,17 +41,47 @@ AutoFormatOperation::AutoFormatOperation(ScDocFunc& rDocFunc, ScDocShell& rDocSh { } +bool AutoFormatOperation::canRunTheOperation() const { return !isInputOnSheetViewAutoFilter(); } + +bool AutoFormatOperation::isInputOnSheetViewAutoFilter() const +{ + ScDocument& rDoc = mrDocShell.GetDocument(); + + // Only block if the range is on a sheet view tab, not the default view + if (!rDoc.IsSheetViewHolder(maRange.aStart.Tab())) + return false; + + ScDBCollection* pDBCollection = rDoc.GetDBCollection(); + if (!pDBCollection) + return false; + + SCTAB nTab = maRange.aStart.Tab(); + for (ScDBData* pDBData : pDBCollection->GetAllDBsFromTab(nTab)) + { + if (!pDBData->HasAutoFilter()) + continue; + + ScRange aDBRange; + pDBData->GetArea(aDBRange); + if (maRange.Intersects(aDBRange)) + return true; + } + return false; +} + bool AutoFormatOperation::runImplementation() { ScDocShellModificator aModificator(mrDocShell); ScDocument& rDoc = mrDocShell.GetDocument(); - SCCOL nStartCol = maRange.aStart.Col(); - SCROW nStartRow = maRange.aStart.Row(); - SCTAB nStartTab = maRange.aStart.Tab(); - SCCOL nEndCol = maRange.aEnd.Col(); - SCROW nEndRow = maRange.aEnd.Row(); - SCTAB nEndTab = maRange.aEnd.Tab(); + + ScRange aRange = convertRange(maRange); + SCCOL nStartCol = aRange.aStart.Col(); + SCROW nStartRow = aRange.aStart.Row(); + SCTAB nStartTab = aRange.aStart.Tab(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nEndRow = aRange.aEnd.Row(); + SCTAB nEndTab = aRange.aEnd.Tab(); if (mbRecord && !rDoc.IsUndoEnabled()) mbRecord = false; @@ -66,9 +97,6 @@ bool AutoFormatOperation::runImplementation() ScAutoFormat* pAutoFormat = ScGlobal::GetOrCreateAutoFormat(); - if (!checkSheetViewProtection()) - return false; - ScEditableTester aTester = ScEditableTester::CreateAndTestSelectedBlock( rDoc, nStartCol, nStartRow, nEndCol, nEndRow, aMark); if (mnFormatNo < pAutoFormat->size() && aTester.IsEditable()) @@ -102,7 +130,7 @@ bool AutoFormatOperation::runImplementation() pUndoDoc->AddUndoTab(rTab, rTab, bSize, bSize); } - ScRange aCopyRange = maRange; + ScRange aCopyRange = aRange; aCopyRange.aStart.SetTab(0); aCopyRange.aStart.SetTab(nTabCount - 1); rDoc.CopyToDocument(aCopyRange, InsertDeleteFlags::ATTRIB, false, *pUndoDoc, &aMark); @@ -157,9 +185,11 @@ bool AutoFormatOperation::runImplementation() if (mbRecord) // only now is Draw-Undo available { mrDocShell.GetUndoManager()->AddUndoAction(std::make_unique<ScUndoAutoFormat>( - &mrDocShell, maRange, std::move(pUndoDoc), aMark, bSize, mnFormatNo)); + &mrDocShell, aRange, std::move(pUndoDoc), aMark, bSize, mnFormatNo)); } + syncSheetViews(); + aModificator.SetDocumentModified(); return true; } diff --git a/sc/source/ui/operation/Operation.cxx b/sc/source/ui/operation/Operation.cxx index 4e0427b5be73..35799cae4cda 100644 --- a/sc/source/ui/operation/Operation.cxx +++ b/sc/source/ui/operation/Operation.cxx @@ -241,6 +241,8 @@ void Operation::syncSheetViews() } } +bool Operation::isInputOnSheetView() const { return getCurrentSheetView(mpViewData) != nullptr; } + bool Operation::checkSheetViewProtection() { sc::SheetViewOperationsTester aSheetViewTester(mpViewData); @@ -250,9 +252,18 @@ bool Operation::checkSheetViewProtection() bool Operation::run() { SAL_INFO("sc.op", "Running operation '" << operationTypeString(meType) << "'."); - bool bResult = runImplementation(); - SAL_INFO("sc.op", "Operation '" << operationTypeString(meType) - << (bResult ? "' succeeded." : "' failed.")); + bool bResult = false; + if (canRunTheOperation()) + { + bResult = runImplementation(); + SAL_INFO("sc.op", "Operation '" << operationTypeString(meType) + << (bResult ? "' succeeded." : "' failed.")); + } + else + { + SAL_INFO("sc.op", "Operation '" << operationTypeString(meType) + << "' can not be run using this input data."); + } return bResult; } }
