diff options
Diffstat (limited to 'sc/source/ui/vba/vbarange.cxx')
-rw-r--r-- | sc/source/ui/vba/vbarange.cxx | 5778 |
1 files changed, 5778 insertions, 0 deletions
diff --git a/sc/source/ui/vba/vbarange.cxx b/sc/source/ui/vba/vbarange.cxx new file mode 100644 index 0000000000..f1ce525daa --- /dev/null +++ b/sc/source/ui/vba/vbarange.cxx @@ -0,0 +1,5778 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "vbarange.hxx" + +#include <comphelper/servicehelper.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/unit_conversion.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <o3tl/string_view.hxx> + +#include <com/sun/star/script/ArrayWrapper.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <com/sun/star/script/vba/VBAEventId.hpp> +#include <com/sun/star/script/vba/XVBAEventProcessor.hpp> +#include <com/sun/star/sheet/XDatabaseRange.hpp> +#include <com/sun/star/sheet/XUnnamedDatabaseRanges.hpp> +#include <com/sun/star/sheet/XGoalSeek.hpp> +#include <com/sun/star/sheet/XSheetOperation.hpp> +#include <com/sun/star/sheet/CellFlags.hpp> +#include <com/sun/star/table/XColumnRowRange.hpp> +#include <com/sun/star/sheet/XCellAddressable.hpp> +#include <com/sun/star/table/CellContentType.hpp> +#include <com/sun/star/sheet/XCellSeries.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/sheet/XCellRangeAddressable.hpp> +#include <com/sun/star/table/CellAddress.hpp> +#include <com/sun/star/table/CellRangeAddress.hpp> +#include <com/sun/star/sheet/XSpreadsheetView.hpp> +#include <com/sun/star/sheet/XCellRangeReferrer.hpp> +#include <com/sun/star/sheet/XSheetCellRange.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XSheetCellCursor.hpp> +#include <com/sun/star/sheet/XArrayFormulaRange.hpp> +#include <com/sun/star/sheet/XNamedRange.hpp> +#include <com/sun/star/sheet/XNamedRanges.hpp> +#include <com/sun/star/sheet/XPrintAreas.hpp> +#include <com/sun/star/sheet/XCellRangesQuery.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <com/sun/star/table/XTableRows.hpp> +#include <com/sun/star/table/XTableColumns.hpp> +#include <com/sun/star/table/TableSortField.hpp> +#include <com/sun/star/util/XMergeable.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormats.hpp> +#include <com/sun/star/util/NumberFormat.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <com/sun/star/util/XReplaceable.hpp> +#include <com/sun/star/util/XSortable.hpp> +#include <com/sun/star/sheet/XCellRangeMovement.hpp> +#include <com/sun/star/sheet/FormulaResult.hpp> +#include <com/sun/star/sheet/FilterOperator2.hpp> +#include <com/sun/star/sheet/TableFilterField2.hpp> +#include <com/sun/star/sheet/XSheetFilterDescriptor2.hpp> +#include <com/sun/star/sheet/FilterConnection.hpp> +#include <com/sun/star/util/TriState.hpp> + +#include <com/sun/star/sheet/XSubTotalCalculatable.hpp> +#include <com/sun/star/sheet/XSubTotalDescriptor.hpp> +#include <com/sun/star/sheet/GeneralFunction.hpp> + +#include <com/sun/star/sheet/XSheetAnnotationsSupplier.hpp> +#include <com/sun/star/sheet/XSheetAnnotations.hpp> + +#include <ooo/vba/excel/XlPasteSpecialOperation.hpp> +#include <ooo/vba/excel/XlPasteType.hpp> +#include <ooo/vba/excel/XlFindLookIn.hpp> +#include <ooo/vba/excel/XlLookAt.hpp> +#include <ooo/vba/excel/XlSearchOrder.hpp> +#include <ooo/vba/excel/XlSortOrder.hpp> +#include <ooo/vba/excel/XlYesNoGuess.hpp> +#include <ooo/vba/excel/XlSortOrientation.hpp> +#include <ooo/vba/excel/XlSortMethod.hpp> +#include <ooo/vba/excel/XlDirection.hpp> +#include <ooo/vba/excel/XlSortDataOption.hpp> +#include <ooo/vba/excel/XlDeleteShiftDirection.hpp> +#include <ooo/vba/excel/XlInsertShiftDirection.hpp> +#include <ooo/vba/excel/XlReferenceStyle.hpp> +#include <ooo/vba/excel/XlBordersIndex.hpp> +#include <ooo/vba/excel/XlPageBreak.hpp> +#include <ooo/vba/excel/XlAutoFilterOperator.hpp> +#include <ooo/vba/excel/XlAutoFillType.hpp> +#include <ooo/vba/excel/XlCellType.hpp> +#include <ooo/vba/excel/XlSpecialCellsValue.hpp> +#include <ooo/vba/excel/XlConsolidationFunction.hpp> +#include <ooo/vba/excel/XlSearchDirection.hpp> + +#include <scitems.hxx> +#include <svl/srchitem.hxx> +#include <cellsuno.hxx> +#include <dbdata.hxx> +#include <docfunc.hxx> +#include <columnspanset.hxx> +#include <queryparam.hxx> +#include <sortparam.hxx> + +#include <sfx2/dispatch.hxx> +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewfrm.hxx> +#include <sc.hrc> +#include <unonames.hxx> + +#include "excelvbahelper.hxx" +#include "vbaapplication.hxx" +#include "vbafont.hxx" +#include "vbacomment.hxx" +#include "vbainterior.hxx" +#include "vbacharacters.hxx" +#include "vbaborders.hxx" +#include "vbaworksheet.hxx" +#include "vbavalidation.hxx" +#include "vbahyperlinks.hxx" + +#include <tabvwsh.hxx> +#include <rangelst.hxx> +#include <convuno.hxx> +#include <compiler.hxx> +#include <patattr.hxx> +#include <olinetab.hxx> +#include <transobj.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <basic/sberrors.hxx> +#include <cppuhelper/implbase.hxx> + +#include <global.hxx> + +#include "vbastyle.hxx" +#include "vbaname.hxx" +#include <utility> +#include <vector> +#include <vbahelper/vbacollectionimpl.hxx> + +#include <com/sun/star/bridge/oleautomation/Date.hpp> +#include <tokenarray.hxx> +#include <tokenuno.hxx> + +#include <memory> + +using namespace ::ooo::vba; +using namespace ::com::sun::star; +using ::std::vector; + +// difference between VBA and file format width, in character units +const double fExtraWidth = 182.0 / 256.0; + +const sal_Int16 supportedIndexTable[] = { excel::XlBordersIndex::xlEdgeLeft, excel::XlBordersIndex::xlEdgeTop, excel::XlBordersIndex::xlEdgeBottom, excel::XlBordersIndex::xlEdgeRight, excel::XlBordersIndex::xlDiagonalDown, excel::XlBordersIndex::xlDiagonalUp, excel::XlBordersIndex::xlInsideVertical, excel::XlBordersIndex::xlInsideHorizontal }; + +static sal_uInt16 lcl_pointsToTwips( double nVal ) +{ + nVal = nVal * static_cast<double>(20); + short nTwips = static_cast<short>(nVal); + return nTwips; +} +static double lcl_TwipsToPoints( sal_uInt16 nVal ) +{ + double nPoints = nVal; + return nPoints / 20; +} + +static double lcl_Round2DecPlaces( double nVal ) +{ + nVal = (nVal * double(100)); + tools::Long tmp = static_cast<tools::Long>(nVal); + if ( ( nVal - tmp ) >= 0.5 ) + ++tmp; + nVal = double(tmp)/100; + return nVal; +} + +static uno::Any lcl_makeRange( const uno::Reference< XHelperInterface >& rParent, const uno::Reference< uno::XComponentContext >& rContext, const uno::Any& rAny, bool bIsRows, bool bIsColumns ) +{ + uno::Reference< table::XCellRange > xCellRange(rAny, uno::UNO_QUERY_THROW); + return uno::Any( uno::Reference< excel::XRange >( new ScVbaRange( rParent, rContext, xCellRange, bIsRows, bIsColumns ) ) ); +} + +static uno::Reference< excel::XRange > lcl_makeXRangeFromSheetCellRanges( const uno::Reference< XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const uno::Reference< sheet::XSheetCellRanges >& xLocSheetCellRanges, ScDocShell* pDoc ) +{ + uno::Reference< excel::XRange > xRange; + const uno::Sequence< table::CellRangeAddress > sAddresses = xLocSheetCellRanges->getRangeAddresses(); + ScRangeList aCellRanges; + if ( sAddresses.hasElements() ) + { + for ( const auto& rAddress : sAddresses ) + { + ScRange refRange; + ScUnoConversion::FillScRange( refRange, rAddress ); + aCellRanges.push_back( refRange ); + } + // Single range + if ( aCellRanges.size() == 1 ) + { + uno::Reference< table::XCellRange > xTmpRange( new ScCellRangeObj( pDoc, aCellRanges.front() ) ); + xRange = new ScVbaRange( xParent, xContext, xTmpRange ); + } + else + { + uno::Reference< sheet::XSheetCellRangeContainer > xRanges( new ScCellRangesObj( pDoc, aCellRanges ) ); + xRange = new ScVbaRange( xParent, xContext, xRanges ); + } + } + return xRange; +} + +ScCellRangesBase* ScVbaRange::getCellRangesBase() +{ + if( mxRanges.is() ) + return dynamic_cast<ScCellRangesBase*>( mxRanges.get() ); + if( mxRange.is() ) + return dynamic_cast<ScCellRangesBase*>( mxRange.get() ); + throw uno::RuntimeException("General Error creating range - Unknown" ); +} + +ScCellRangeObj* ScVbaRange::getCellRangeObj() +{ + return dynamic_cast< ScCellRangeObj* >( getCellRangesBase() ); +} + +SfxItemSet* ScVbaRange::getCurrentDataSet( ) +{ + SfxItemSet* pDataSet = excel::ScVbaCellRangeAccess::GetDataSet( getCellRangesBase() ); + if ( !pDataSet ) + throw uno::RuntimeException("Can't access Itemset for range" ); + return pDataSet; +} + +void ScVbaRange::fireChangeEvent() +{ + if( !ScVbaApplication::getDocumentEventsEnabled() ) + return; + + ScDocument& rDoc = getScDocument(); + const uno::Reference< script::vba::XVBAEventProcessor >& xVBAEvents = rDoc.GetVbaEventProcessor(); + if( xVBAEvents.is() ) try + { + uno::Sequence< uno::Any > aArgs{ uno::Any(uno::Reference< excel::XRange >( this )) }; + xVBAEvents->processVbaEvent( script::vba::VBAEventId::WORKSHEET_CHANGE, aArgs ); + } + catch( uno::Exception& ) + { + } +} + +namespace { + +class SingleRangeEnumeration : public EnumerationHelper_BASE +{ + uno::Reference< table::XCellRange > m_xRange; + bool bHasMore; +public: + /// @throws uno::RuntimeException + explicit SingleRangeEnumeration( uno::Reference< table::XCellRange > xRange ) : m_xRange(std::move( xRange )), bHasMore( true ) { } + virtual sal_Bool SAL_CALL hasMoreElements( ) override { return bHasMore; } + virtual uno::Any SAL_CALL nextElement( ) override + { + if ( !bHasMore ) + throw container::NoSuchElementException(); + bHasMore = false; + return uno::Any( m_xRange ); + } +}; + +// very simple class to pass to ScVbaCollectionBaseImpl containing +// just one item + +class SingleRangeIndexAccess : public ::cppu::WeakImplHelper< container::XIndexAccess, + container::XEnumerationAccess > +{ +private: + uno::Reference< table::XCellRange > m_xRange; + +public: + explicit SingleRangeIndexAccess( uno::Reference< table::XCellRange > xRange ) : m_xRange(std::move( xRange )) {} + // XIndexAccess + virtual ::sal_Int32 SAL_CALL getCount() override { return 1; } + virtual uno::Any SAL_CALL getByIndex( ::sal_Int32 Index ) override + { + if ( Index != 0 ) + throw lang::IndexOutOfBoundsException(); + return uno::Any( m_xRange ); + } + // XElementAccess + virtual uno::Type SAL_CALL getElementType() override { return cppu::UnoType<table::XCellRange>::get(); } + virtual sal_Bool SAL_CALL hasElements() override { return true; } + // XEnumerationAccess + virtual uno::Reference< container::XEnumeration > SAL_CALL createEnumeration() override { return new SingleRangeEnumeration( m_xRange ); } + +}; + +class RangesEnumerationImpl : public EnumerationHelperImpl +{ + bool mbIsRows; + bool mbIsColumns; +public: + /// @throws uno::RuntimeException + RangesEnumerationImpl( const uno::Reference< XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const uno::Reference< container::XEnumeration >& xEnumeration, bool bIsRows, bool bIsColumns ) : EnumerationHelperImpl( xParent, xContext, xEnumeration ), mbIsRows( bIsRows ), mbIsColumns( bIsColumns ) {} + virtual uno::Any SAL_CALL nextElement( ) override + { + return lcl_makeRange( m_xParent, m_xContext, m_xEnumeration->nextElement(), mbIsRows, mbIsColumns ); + } +}; + +class ScVbaRangeAreas : public ScVbaCollectionBaseImpl +{ + bool mbIsRows; + bool mbIsColumns; +public: + ScVbaRangeAreas( const uno::Reference< XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const uno::Reference< container::XIndexAccess >& xIndexAccess, bool bIsRows, bool bIsColumns ) : ScVbaCollectionBaseImpl( xParent, xContext, xIndexAccess ), mbIsRows( bIsRows ), mbIsColumns( bIsColumns ) {} + + // XEnumerationAccess + virtual uno::Reference< container::XEnumeration > SAL_CALL createEnumeration() override; + + // XElementAccess + virtual uno::Type SAL_CALL getElementType() override { return cppu::UnoType<excel::XRange>::get(); } + + virtual uno::Any createCollectionObject( const uno::Any& aSource ) override; + + virtual OUString getServiceImplName() override { return OUString(); } + + virtual uno::Sequence< OUString > getServiceNames() override { return uno::Sequence< OUString >(); } + +}; + +} + +uno::Reference< container::XEnumeration > SAL_CALL +ScVbaRangeAreas::createEnumeration() +{ + uno::Reference< container::XEnumerationAccess > xEnumAccess( m_xIndexAccess, uno::UNO_QUERY_THROW ); + return new RangesEnumerationImpl( mxParent, mxContext, xEnumAccess->createEnumeration(), mbIsRows, mbIsColumns ); +} + +uno::Any +ScVbaRangeAreas::createCollectionObject( const uno::Any& aSource ) +{ + return lcl_makeRange( mxParent, mxContext, aSource, mbIsRows, mbIsColumns ); +} + +// assume that xIf is in fact a ScCellRangesBase +/// @throws uno::RuntimeException +static ScDocShell* +getDocShellFromIf( const uno::Reference< uno::XInterface >& xIf ) +{ + ScCellRangesBase* pUno = dynamic_cast<ScCellRangesBase*>( xIf.get() ); + if ( !pUno ) + throw uno::RuntimeException("Failed to access underlying uno range object" ); + return pUno->GetDocShell(); +} + +/// @throws uno::RuntimeException +static ScDocShell* +getDocShellFromRange( const uno::Reference< table::XCellRange >& xRange ) +{ + // need the ScCellRangesBase to get docshell + uno::Reference< uno::XInterface > xIf( xRange ); + return getDocShellFromIf(xIf ); +} + +/// @throws uno::RuntimeException +static ScDocShell* +getDocShellFromRanges( const uno::Reference< sheet::XSheetCellRangeContainer >& xRanges ) +{ + // need the ScCellRangesBase to get docshell + uno::Reference< uno::XInterface > xIf( xRanges ); + return getDocShellFromIf(xIf ); +} + +/// @throws uno::RuntimeException +static uno::Reference< frame::XModel > getModelFromXIf( const uno::Reference< uno::XInterface >& xIf ) +{ + ScDocShell* pDocShell = getDocShellFromIf(xIf ); + return pDocShell->GetModel(); +} + +/// @throws uno::RuntimeException +static uno::Reference< frame::XModel > getModelFromRange( const uno::Reference< table::XCellRange >& xRange ) +{ + // the XInterface for getImplementation can be any derived interface, no need for queryInterface + uno::Reference< uno::XInterface > xIf( xRange ); + return getModelFromXIf( xIf ); +} + +static ScDocument& +getDocumentFromRange( const uno::Reference< table::XCellRange >& xRange ) +{ + ScDocShell* pDocShell = getDocShellFromRange( xRange ); + if ( !pDocShell ) + throw uno::RuntimeException("Failed to access underlying docshell from uno range object" ); + ScDocument& rDoc = pDocShell->GetDocument(); + return rDoc; +} + +ScDocument& +ScVbaRange::getScDocument() +{ + if ( mxRanges.is() ) + { + uno::Reference< container::XIndexAccess > xIndex( mxRanges, uno::UNO_QUERY_THROW ); + uno::Reference< table::XCellRange > xRange( xIndex->getByIndex( 0 ), uno::UNO_QUERY_THROW ); + return getDocumentFromRange( xRange ); + } + return getDocumentFromRange( mxRange ); +} + +ScDocShell* +ScVbaRange::getScDocShell() +{ + if ( mxRanges.is() ) + { + uno::Reference< container::XIndexAccess > xIndex( mxRanges, uno::UNO_QUERY_THROW ); + uno::Reference< table::XCellRange > xRange( xIndex->getByIndex( 0 ), uno::UNO_QUERY_THROW ); + return getDocShellFromRange( xRange ); + } + return getDocShellFromRange( mxRange ); +} + +ScVbaRange* ScVbaRange::getImplementation( const uno::Reference< excel::XRange >& rxRange ) +{ + // FIXME: always save to use dynamic_cast? Or better to (implement and) use XTunnel? + return dynamic_cast< ScVbaRange* >( rxRange.get() ); +} + +uno::Reference< frame::XModel > ScVbaRange::getUnoModel() +{ + if( ScDocShell* pDocShell = getScDocShell() ) + return pDocShell->GetModel(); + throw uno::RuntimeException(); +} + +uno::Reference< frame::XModel > ScVbaRange::getUnoModel( const uno::Reference< excel::XRange >& rxRange ) +{ + if( ScVbaRange* pScVbaRange = getImplementation( rxRange ) ) + return pScVbaRange->getUnoModel(); + throw uno::RuntimeException(); +} + +const ScRangeList& ScVbaRange::getScRangeList() +{ + if( ScCellRangesBase* pScRangesBase = getCellRangesBase() ) + return pScRangesBase->GetRangeList(); + throw uno::RuntimeException("Cannot obtain UNO range implementation object" ); +} + +const ScRangeList& ScVbaRange::getScRangeList( const uno::Reference< excel::XRange >& rxRange ) +{ + if( ScVbaRange* pScVbaRange = getImplementation( rxRange ) ) + return pScVbaRange->getScRangeList(); + throw uno::RuntimeException("Cannot obtain VBA range implementation object" ); +} + +namespace { + +class NumFormatHelper +{ + uno::Reference< util::XNumberFormatsSupplier > mxSupplier; + uno::Reference< beans::XPropertySet > mxRangeProps; + uno::Reference< util::XNumberFormats > mxFormats; +public: + explicit NumFormatHelper( const uno::Reference< table::XCellRange >& xRange ) + { + mxSupplier.set( getModelFromRange( xRange ), uno::UNO_QUERY_THROW ); + mxRangeProps.set( xRange, uno::UNO_QUERY_THROW); + mxFormats = mxSupplier->getNumberFormats(); + } + uno::Reference< beans::XPropertySet > getNumberProps() + { + tools::Long nIndexKey = 0; + uno::Any aValue = mxRangeProps->getPropertyValue( "NumberFormat" ); + aValue >>= nIndexKey; + + if ( mxFormats.is() ) + return mxFormats->getByKey( nIndexKey ); + return uno::Reference< beans::XPropertySet > (); + } + + bool isBooleanType() + { + + return (getNumberFormat() & util::NumberFormat::LOGICAL) != 0; + } + + bool isDateType() + { + sal_Int16 nType = getNumberFormat(); + return ( nType & util::NumberFormat::DATETIME ) != 0; + } + + OUString getNumberFormatString() + { + uno::Reference< uno::XInterface > xIf( mxRangeProps, uno::UNO_QUERY_THROW ); + ScCellRangesBase* pUnoCellRange = dynamic_cast<ScCellRangesBase*>( xIf.get() ); + if ( pUnoCellRange ) + { + + SfxItemSet* pDataSet = excel::ScVbaCellRangeAccess::GetDataSet( pUnoCellRange ); + SfxItemState eState = pDataSet->GetItemState( ATTR_VALUE_FORMAT); + // one of the cells in the range is not like the other ;-) + // so return a zero length format to indicate that + if ( eState == SfxItemState::DONTCARE ) + return OUString(); + } + + uno::Reference< beans::XPropertySet > xNumberProps( getNumberProps(), uno::UNO_SET_THROW ); + OUString aFormatString; + uno::Any aString = xNumberProps->getPropertyValue( "FormatString" ); + aString >>= aFormatString; + return aFormatString; + } + + sal_Int16 getNumberFormat() + { + uno::Reference< beans::XPropertySet > xNumberProps = getNumberProps(); + sal_Int16 nType = ::comphelper::getINT16( + xNumberProps->getPropertyValue( "Type" ) ); + return nType; + } + + void setNumberFormat( const OUString& rFormat ) + { + // #163288# treat "General" as "Standard" format + sal_Int32 nNewIndex = 0; + if( !rFormat.equalsIgnoreAsciiCase( "General" ) ) + { + lang::Locale aLocale; + uno::Reference< beans::XPropertySet > xNumProps = getNumberProps(); + xNumProps->getPropertyValue( "Locale" ) >>= aLocale; + nNewIndex = mxFormats->queryKey( rFormat, aLocale, false ); + if ( nNewIndex == -1 ) // format not defined + nNewIndex = mxFormats->addNew( rFormat, aLocale ); + } + mxRangeProps->setPropertyValue( "NumberFormat", uno::Any( nNewIndex ) ); + } + + void setNumberFormat( sal_Int16 nType ) + { + uno::Reference< beans::XPropertySet > xNumberProps = getNumberProps(); + lang::Locale aLocale; + xNumberProps->getPropertyValue( "Locale" ) >>= aLocale; + uno::Reference<util::XNumberFormatTypes> xTypes( mxFormats, uno::UNO_QUERY ); + if ( xTypes.is() ) + { + sal_Int32 nNewIndex = xTypes->getStandardFormat( nType, aLocale ); + mxRangeProps->setPropertyValue( "NumberFormat", uno::Any( nNewIndex ) ); + } + } + +}; + +struct CellPos +{ + CellPos( sal_Int32 nRow, sal_Int32 nCol, sal_Int32 nArea ):m_nRow(nRow), m_nCol(nCol), m_nArea( nArea ) {}; +sal_Int32 m_nRow; +sal_Int32 m_nCol; +sal_Int32 m_nArea; +}; + +} + +typedef ::cppu::WeakImplHelper< container::XEnumeration > CellsEnumeration_BASE; +typedef ::std::vector< CellPos > vCellPos; + +namespace { + +// #FIXME - QUICK +// we could probably could and should modify CellsEnumeration below +// to handle rows and columns (but I do this separately for now +// and... this class only handles single areas (does it have to handle +// multi area ranges??) +class ColumnsRowEnumeration: public CellsEnumeration_BASE +{ + uno::Reference< excel::XRange > mxRange; + sal_Int32 mMaxElems; + sal_Int32 mCurElem; + +public: + ColumnsRowEnumeration( uno::Reference< excel::XRange > xRange, sal_Int32 nElems ) : mxRange(std::move( xRange )), mMaxElems( nElems ), mCurElem( 0 ) + { + } + + virtual sal_Bool SAL_CALL hasMoreElements() override { return mCurElem < mMaxElems; } + + virtual uno::Any SAL_CALL nextElement() override + { + if ( !hasMoreElements() ) + throw container::NoSuchElementException(); + sal_Int32 vbaIndex = 1 + mCurElem++; + return uno::Any( mxRange->Item( uno::Any( vbaIndex ), uno::Any() ) ); + } +}; + +class CellsEnumeration : public CellsEnumeration_BASE +{ + uno::WeakReference< XHelperInterface > mxParent; + uno::Reference< uno::XComponentContext > mxContext; + uno::Reference< XCollection > m_xAreas; + vCellPos m_CellPositions; + vCellPos::const_iterator m_it; + + /// @throws uno::RuntimeException + uno::Reference< table::XCellRange > getArea( sal_Int32 nVBAIndex ) + { + if ( nVBAIndex < 1 || nVBAIndex > m_xAreas->getCount() ) + throw uno::RuntimeException(); + uno::Reference< excel::XRange > xRange( m_xAreas->Item( uno::Any(nVBAIndex), uno::Any() ), uno::UNO_QUERY_THROW ); + uno::Reference< table::XCellRange > xCellRange( ScVbaRange::getCellRange( xRange ), uno::UNO_QUERY_THROW ); + return xCellRange; + } + + void populateArea( sal_Int32 nVBAIndex ) + { + uno::Reference< table::XCellRange > xRange = getArea( nVBAIndex ); + uno::Reference< table::XColumnRowRange > xColumnRowRange(xRange, uno::UNO_QUERY_THROW ); + sal_Int32 nRowCount = xColumnRowRange->getRows()->getCount(); + sal_Int32 nColCount = xColumnRowRange->getColumns()->getCount(); + for ( sal_Int32 i=0; i<nRowCount; ++i ) + { + for ( sal_Int32 j=0; j<nColCount; ++j ) + m_CellPositions.emplace_back( i,j,nVBAIndex ); + } + } +public: + CellsEnumeration( const uno::Reference< XHelperInterface >& xParent, uno::Reference< uno::XComponentContext > xContext, uno::Reference< XCollection > xAreas ): mxParent( xParent ), mxContext(std::move( xContext )), m_xAreas(std::move( xAreas )) + { + sal_Int32 nItems = m_xAreas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + populateArea( index ); + } + m_it = m_CellPositions.begin(); + } + virtual sal_Bool SAL_CALL hasMoreElements() override { return m_it != m_CellPositions.end(); } + + virtual uno::Any SAL_CALL nextElement() override + { + if ( !hasMoreElements() ) + throw container::NoSuchElementException(); + CellPos aPos = *m_it++; + + uno::Reference< table::XCellRange > xRangeArea = getArea( aPos.m_nArea ); + uno::Reference< table::XCellRange > xCellRange( xRangeArea->getCellByPosition( aPos.m_nCol, aPos.m_nRow ), uno::UNO_QUERY_THROW ); + return uno::Any( uno::Reference< excel::XRange >( new ScVbaRange( mxParent, mxContext, xCellRange ) ) ); + + } +}; + +} + +constexpr OUString ISVISIBLE = u"IsVisible"_ustr; +const char EQUALS[] = "="; +const char NOTEQUALS[] = "<>"; +const char GREATERTHAN[] = ">"; +const char GREATERTHANEQUALS[] = ">="; +const char LESSTHAN[] = "<"; +const char LESSTHANEQUALS[] = "<="; +constexpr OUString STR_ERRORMESSAGE_APPLIESTOSINGLERANGEONLY(u"The command you chose cannot be performed with multiple selections.\nSelect a single range and click the command again"_ustr); +constexpr OUString CELLSTYLE = u"CellStyle"_ustr; + +namespace { + +class CellValueSetter : public ValueSetter +{ +protected: + uno::Any maValue; +public: + explicit CellValueSetter( uno::Any aValue ); + virtual bool processValue( const uno::Any& aValue, const uno::Reference< table::XCell >& xCell ) override; + virtual void visitNode( sal_Int32 x, sal_Int32 y, const uno::Reference< table::XCell >& xCell ) override; + +}; + +} + +CellValueSetter::CellValueSetter( uno::Any aValue ): maValue(std::move( aValue )) {} + +void +CellValueSetter::visitNode( sal_Int32 /*i*/, sal_Int32 /*j*/, const uno::Reference< table::XCell >& xCell ) +{ + processValue( maValue, xCell ); +} + +bool +CellValueSetter::processValue( const uno::Any& aValue, const uno::Reference< table::XCell >& xCell ) +{ + + bool isExtracted = false; + switch ( aValue.getValueTypeClass() ) + { + case uno::TypeClass_BOOLEAN: + { + bool bState = false; + if ( aValue >>= bState ) + { + uno::Reference< table::XCellRange > xRange( xCell, uno::UNO_QUERY_THROW ); + if ( bState ) + xCell->setValue( double(1) ); + else + xCell->setValue( double(0) ); + NumFormatHelper cellNumFormat( xRange ); + cellNumFormat.setNumberFormat( util::NumberFormat::LOGICAL ); + } + break; + } + case uno::TypeClass_STRING: + { + OUString aString; + if ( aValue >>= aString ) + { + // The required behavior for a string value is: + // 1. If the first character is a single quote, use the rest as a string cell, regardless of the cell's number format. + // 2. Otherwise, if the cell's number format is "text", use the string value as a string cell. + // 3. Otherwise, parse the string value in English locale, and apply a corresponding number format with the cell's locale + // if the cell's number format was "General". + // Case 1 is handled here, the rest in ScCellObj::InputEnglishString + + if ( aString.toChar() == '\'' ) // case 1 - handle with XTextRange + { + OUString aRemainder( aString.copy(1) ); // strip the quote + uno::Reference< text::XTextRange > xTextRange( xCell, uno::UNO_QUERY_THROW ); + xTextRange->setString( aRemainder ); + } + else + { + // call implementation method InputEnglishString + ScCellObj* pCellObj = dynamic_cast< ScCellObj* >( xCell.get() ); + if ( pCellObj ) + pCellObj->InputEnglishString( aString ); + } + } + else + isExtracted = false; + break; + } + default: + { + double nDouble = 0.0; + if ( aValue >>= nDouble ) + { + uno::Reference< table::XCellRange > xRange( xCell, uno::UNO_QUERY_THROW ); + NumFormatHelper cellFormat( xRange ); + // If we are setting a number and the cell types was logical + // then we need to reset the logical format. ( see case uno::TypeClass_BOOLEAN: + // handling above ) + if ( cellFormat.isBooleanType() ) + cellFormat.setNumberFormat("General"); + xCell->setValue( nDouble ); + } + else + isExtracted = false; + break; + } + } + return isExtracted; + +} + +namespace { + +class CellValueGetter : public ValueGetter +{ +protected: + RangeValueType meValueType; + uno::Any maValue; +public: + CellValueGetter(RangeValueType eValueType) { meValueType = eValueType; } + virtual void visitNode( sal_Int32 x, sal_Int32 y, const uno::Reference< table::XCell >& xCell ) override; + virtual void processValue( const uno::Any& aValue ) override; + const uno::Any& getValue() const override { return maValue; } +}; + +} + +void +CellValueGetter::processValue( const uno::Any& aValue ) +{ + maValue = aValue; +} +void CellValueGetter::visitNode( sal_Int32 /*x*/, sal_Int32 /*y*/, const uno::Reference< table::XCell >& xCell ) +{ + uno::Any aValue; + table::CellContentType eCellContentType = xCell->getType(); + if( eCellContentType == table::CellContentType_VALUE || eCellContentType == table::CellContentType_FORMULA ) + { + if ( eCellContentType == table::CellContentType_FORMULA ) + { + + OUString sFormula = xCell->getFormula(); + if ( sFormula == "=TRUE()" ) + aValue <<= true; + else if ( sFormula == "=FALSE()" ) + aValue <<= false; + else + { + uno::Reference< beans::XPropertySet > xProp( xCell, uno::UNO_QUERY_THROW ); + + sal_Int32 nResultType = sheet::FormulaResult::VALUE; + // some formulas give textual results + xProp->getPropertyValue( "FormulaResultType2" ) >>= nResultType; + + if ( nResultType == sheet::FormulaResult::STRING ) + { + uno::Reference< text::XTextRange > xTextRange(xCell, ::uno::UNO_QUERY_THROW); + aValue <<= xTextRange->getString(); + } + else + aValue <<= xCell->getValue(); + } + } + else + { + uno::Reference< table::XCellRange > xRange( xCell, uno::UNO_QUERY_THROW ); + NumFormatHelper cellFormat( xRange ); + if ( cellFormat.isBooleanType() ) + aValue <<= ( xCell->getValue() != 0.0 ); + else if ( cellFormat.isDateType() && meValueType == RangeValueType::value) + aValue <<= bridge::oleautomation::Date( xCell->getValue() ); + else + aValue <<= xCell->getValue(); + } + } + if( eCellContentType == table::CellContentType_TEXT ) + { + uno::Reference< text::XTextRange > xTextRange(xCell, ::uno::UNO_QUERY_THROW); + aValue <<= xTextRange->getString(); + } + processValue( aValue ); +} + +namespace { + +class CellFormulaValueSetter : public CellValueSetter +{ +private: + ScDocument& m_rDoc; + formula::FormulaGrammar::Grammar m_eGrammar; +public: + CellFormulaValueSetter( const uno::Any& aValue, ScDocument& rDoc, formula::FormulaGrammar::Grammar eGram ):CellValueSetter( aValue ), m_rDoc( rDoc ), m_eGrammar( eGram ){} +protected: + bool processValue( const uno::Any& aValue, const uno::Reference< table::XCell >& xCell ) override + { + OUString sFormula; + double aDblValue = 0.0; + if ( aValue >>= sFormula ) + { + // convert to GRAM_API style grammar because XCell::setFormula + // always compile it in that grammar. Perhaps + // css.sheet.FormulaParser should be used in future to directly + // pass formula tokens when that API stabilizes. + if ( m_eGrammar != formula::FormulaGrammar::GRAM_API && ( o3tl::starts_with(o3tl::trim(sFormula), u"=") ) ) + { + uno::Reference< uno::XInterface > xIf( xCell, uno::UNO_QUERY_THROW ); + ScCellRangesBase* pUnoRangesBase + = dynamic_cast< ScCellRangesBase* >( xIf.get() ); + if ( pUnoRangesBase ) + { + const ScRangeList& rCellRanges = pUnoRangesBase->GetRangeList(); + if (!rCellRanges.empty()) + { + ScCompiler aCompiler( m_rDoc, rCellRanges.front().aStart, m_eGrammar ); + // compile the string in the format passed in + std::unique_ptr<ScTokenArray> pArray(aCompiler.CompileString(sFormula)); + // convert to API grammar + aCompiler.SetGrammar( formula::FormulaGrammar::GRAM_API ); + OUString sConverted; + aCompiler.CreateStringFromTokenArray(sConverted); + sFormula = EQUALS + sConverted; + } + } + } + + xCell->setFormula( sFormula ); + return true; + } + else if ( aValue >>= aDblValue ) + { + xCell->setValue( aDblValue ); + return true; + } + return false; + } + +}; + +class CellFormulaValueGetter : public CellValueGetter +{ +private: + ScDocument& m_rDoc; + formula::FormulaGrammar::Grammar m_eGrammar; +public: + CellFormulaValueGetter(ScDocument& rDoc, formula::FormulaGrammar::Grammar eGram ) : + CellValueGetter( RangeValueType::value ), m_rDoc( rDoc ), m_eGrammar( eGram ) {} + virtual void visitNode( sal_Int32 /*x*/, sal_Int32 /*y*/, const uno::Reference< table::XCell >& xCell ) override + { + uno::Any aValue; + aValue <<= xCell->getFormula(); + // XCell::getFormula() returns the formula in API grammar, convert. + if ((xCell->getType() == table::CellContentType_FORMULA) + && m_eGrammar != formula::FormulaGrammar::GRAM_API) + { + uno::Reference< uno::XInterface > xIf( xCell, uno::UNO_QUERY_THROW ); + ScCellRangesBase* pUnoRangesBase + = dynamic_cast< ScCellRangesBase* >( xIf.get() ); + if (pUnoRangesBase) + { + OUString sVal; + aValue >>= sVal; + const ScRangeList& rCellRanges = pUnoRangesBase->GetRangeList(); + if (!rCellRanges.empty()) + { + // Compile string from API grammar. + ScCompiler aCompiler( m_rDoc, rCellRanges.front().aStart, formula::FormulaGrammar::GRAM_API ); + std::unique_ptr<ScTokenArray> pArray(aCompiler.CompileString(sVal)); + // Convert to desired grammar. + aCompiler.SetGrammar( m_eGrammar ); + OUString sConverted; + aCompiler.CreateStringFromTokenArray(sConverted); + sVal = EQUALS + sConverted; + aValue <<= sVal; + } + } + } + + processValue( aValue ); + } + +}; + +class Dim2ArrayValueGetter : public ArrayVisitor +{ +protected: + uno::Any maValue; + ValueGetter& mValueGetter; + void processValue( sal_Int32 x, sal_Int32 y, const uno::Any& aValue ) + { + uno::Sequence< uno::Sequence< uno::Any > >& aMatrix = const_cast<css::uno::Sequence<css::uno::Sequence<css::uno::Any>> &>(*o3tl::doAccess<uno::Sequence<uno::Sequence<uno::Any>>>(maValue)); + aMatrix.getArray()[x].getArray()[y] = aValue; + } + +public: + Dim2ArrayValueGetter(sal_Int32 nRowCount, sal_Int32 nColCount, ValueGetter& rValueGetter ): mValueGetter(rValueGetter) + { + uno::Sequence< uno::Sequence< uno::Any > > aMatrix; + aMatrix.realloc( nRowCount ); + auto pMatrix = aMatrix.getArray(); + for ( sal_Int32 index = 0; index < nRowCount; ++index ) + pMatrix[index].realloc( nColCount ); + maValue <<= aMatrix; + } + void visitNode( sal_Int32 x, sal_Int32 y, const uno::Reference< table::XCell >& xCell ) override + + { + mValueGetter.visitNode( x, y, xCell ); + processValue( x, y, mValueGetter.getValue() ); + } + const uno::Any& getValue() const { return maValue; } + +}; + +} + +constexpr OUString sNA = u"#N/A"_ustr; + +namespace { + +class Dim1ArrayValueSetter : public ArrayVisitor +{ + uno::Sequence< uno::Any > aMatrix; + sal_Int32 nColCount; + ValueSetter& mCellValueSetter; +public: + Dim1ArrayValueSetter( const uno::Any& aValue, ValueSetter& rCellValueSetter ):mCellValueSetter( rCellValueSetter ) + { + aValue >>= aMatrix; + nColCount = aMatrix.getLength(); + } + virtual void visitNode( sal_Int32 /*x*/, sal_Int32 y, const uno::Reference< table::XCell >& xCell ) override + { + if ( y < nColCount ) + mCellValueSetter.processValue( aMatrix[ y ], xCell ); + else + mCellValueSetter.processValue( uno::Any( sNA ), xCell ); + } +}; + +class Dim2ArrayValueSetter : public ArrayVisitor +{ + uno::Sequence< uno::Sequence< uno::Any > > aMatrix; + ValueSetter& mCellValueSetter; + sal_Int32 nRowCount; + sal_Int32 nColCount; +public: + Dim2ArrayValueSetter( const uno::Any& aValue, ValueSetter& rCellValueSetter ) : mCellValueSetter( rCellValueSetter ) + { + aValue >>= aMatrix; + nRowCount = aMatrix.getLength(); + nColCount = aMatrix[0].getLength(); + } + + virtual void visitNode( sal_Int32 x, sal_Int32 y, const uno::Reference< table::XCell >& xCell ) override + { + if ( x < nRowCount && y < nColCount ) + mCellValueSetter.processValue( aMatrix[ x ][ y ], xCell ); + else + mCellValueSetter.processValue( uno::Any( sNA ), xCell ); + + } +}; + +class RangeProcessor +{ +public: + virtual void process( const uno::Reference< excel::XRange >& xRange ) = 0; + +protected: + ~RangeProcessor() {} +}; + +class RangeValueProcessor : public RangeProcessor +{ + const uno::Any& m_aVal; +public: + explicit RangeValueProcessor( const uno::Any& rVal ):m_aVal( rVal ) {} + virtual ~RangeValueProcessor() {} + virtual void process( const uno::Reference< excel::XRange >& xRange ) override + { + xRange->setValue( m_aVal ); + } +}; + +class RangeFormulaProcessor : public RangeProcessor +{ + const uno::Any& m_aVal; +public: + explicit RangeFormulaProcessor( const uno::Any& rVal ):m_aVal( rVal ) {} + virtual ~RangeFormulaProcessor() {} + virtual void process( const uno::Reference< excel::XRange >& xRange ) override + { + xRange->setFormula( m_aVal ); + } +}; + +class RangeCountProcessor : public RangeProcessor +{ + sal_Int32 nCount; +public: + RangeCountProcessor():nCount(0){} + virtual ~RangeCountProcessor() {} + virtual void process( const uno::Reference< excel::XRange >& xRange ) override + { + nCount = nCount + xRange->getCount(); + } + sal_Int32 value() { return nCount; } +}; +class AreasVisitor +{ +private: + uno::Reference< XCollection > m_Areas; +public: + explicit AreasVisitor( uno::Reference< XCollection > xAreas ):m_Areas(std::move( xAreas )){} + + void visit( RangeProcessor& processor ) + { + if ( m_Areas.is() ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + processor.process( xRange ); + } + } + } +}; + +class RangeHelper +{ + uno::Reference< table::XCellRange > m_xCellRange; + +public: + /// @throws uno::RuntimeException + explicit RangeHelper( uno::Reference< table::XCellRange > xCellRange ) : m_xCellRange(std::move( xCellRange )) + { + if ( !m_xCellRange.is() ) + throw uno::RuntimeException(); + } + /// @throws uno::RuntimeException + explicit RangeHelper( const uno::Any& rCellRange ) + { + m_xCellRange.set(rCellRange, uno::UNO_QUERY_THROW); + } + /// @throws uno::RuntimeException + uno::Reference< sheet::XSheetCellRange > getSheetCellRange() const + { + return uno::Reference< sheet::XSheetCellRange >(m_xCellRange, uno::UNO_QUERY_THROW); + } + /// @throws uno::RuntimeException + uno::Reference< sheet::XSpreadsheet > getSpreadSheet() const + { + return getSheetCellRange()->getSpreadsheet(); + } + + /// @throws uno::RuntimeException + uno::Reference< table::XCellRange > getCellRangeFromSheet() const + { + return uno::Reference< table::XCellRange >(getSpreadSheet(), uno::UNO_QUERY_THROW ); + } + + /// @throws uno::RuntimeException + uno::Reference< sheet::XCellRangeAddressable > getCellRangeAddressable() const + { + return uno::Reference< sheet::XCellRangeAddressable >(m_xCellRange, ::uno::UNO_QUERY_THROW); + + } + + /// @throws uno::RuntimeException + uno::Reference< sheet::XSheetCellCursor > getSheetCellCursor() const + { + return uno::Reference< sheet::XSheetCellCursor >( getSpreadSheet()->createCursorByRange( getSheetCellRange() ), uno::UNO_SET_THROW ); + } + + static uno::Reference< excel::XRange > createRangeFromRange( const uno::Reference< XHelperInterface >& xParent, const uno::Reference<uno::XComponentContext >& xContext, + const uno::Reference< table::XCellRange >& xRange, const uno::Reference< sheet::XCellRangeAddressable >& xCellRangeAddressable ) + { + const table::CellRangeAddress aRA( xCellRangeAddressable->getRangeAddress()); + return uno::Reference< excel::XRange >( new ScVbaRange( xParent, xContext, + xRange->getCellRangeByPosition( aRA.StartColumn, aRA.StartRow, aRA.EndColumn, aRA.EndRow))); + } + +}; + +} + +bool +ScVbaRange::getCellRangesForAddress( ScRefFlags& rResFlags, std::u16string_view sAddress, ScDocShell* pDocSh, ScRangeList& rCellRanges, formula::FormulaGrammar::AddressConvention eConv, char cDelimiter ) +{ + + if ( pDocSh ) + { + ScDocument& rDoc = pDocSh->GetDocument(); + rResFlags = rCellRanges.Parse( sAddress, rDoc, eConv, 0, cDelimiter ); + if ( rResFlags & ScRefFlags::VALID ) + { + return true; + } + } + return false; +} + +bool getScRangeListForAddress( const OUString& sName, ScDocShell* pDocSh, const ScRange& refRange, ScRangeList& aCellRanges, formula::FormulaGrammar::AddressConvention aConv ) +{ + // see if there is a match with a named range + uno::Reference< container::XNameAccess > xNameAccess( pDocSh->GetModel()->getPropertyValue( "NamedRanges" ), uno::UNO_QUERY_THROW ); + // Strange enough you can have Range( "namedRange1, namedRange2, etc," ) + // loop around each ',' separated name + std::vector< OUString > vNames; + sal_Int32 nIndex = 0; + do + { + OUString aToken = sName.getToken( 0, ',', nIndex ); + vNames.push_back( aToken ); + } while ( nIndex >= 0 ); + + if ( vNames.empty() ) + vNames.push_back( sName ); + + for ( const auto& rName : vNames ) + { + formula::FormulaGrammar::AddressConvention eConv = aConv; + // spaces are illegal ( but the user of course can enter them ) + OUString sAddress = rName.trim(); + // if a local name ( on the active sheet ) exists this will + // take precedence over a global with the same name + if ( !xNameAccess->hasByName( sAddress ) ) + { + // try a local name + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nCurTab = ScDocShell::GetCurTab(); + ScRangeName* pRangeName = rDoc.GetRangeName(nCurTab); + if (pRangeName) + { + // TODO: Handle local names correctly: + // bool bLocalName = pRangeName->findByUpperName(ScGlobal::getCharClass().uppercase(sAddress)) != nullptr; + } + } + char aChar = 0; + if ( xNameAccess->hasByName( sAddress ) ) + { + uno::Reference< sheet::XNamedRange > xNamed( xNameAccess->getByName( sAddress ), uno::UNO_QUERY_THROW ); + sAddress = xNamed->getContent(); + // As the address comes from OOO, the addressing + // style is may not be XL_A1 + eConv = pDocSh->GetDocument().GetAddressConvention(); + aChar = ';'; + } + + ScRefFlags nFlags = ScRefFlags::ZERO; + if ( !ScVbaRange::getCellRangesForAddress( nFlags, sAddress, pDocSh, aCellRanges, eConv, aChar ) ) + return false; + + bool bTabFromReferrer = !( nFlags & ScRefFlags::TAB_3D ); + + for ( size_t i = 0, nRanges = aCellRanges.size(); i < nRanges; ++i ) + { + ScRange & rRange = aCellRanges[ i ]; + rRange.aStart.SetCol( refRange.aStart.Col() + rRange.aStart.Col() ); + rRange.aStart.SetRow( refRange.aStart.Row() + rRange.aStart.Row() ); + rRange.aStart.SetTab( bTabFromReferrer ? refRange.aStart.Tab() : rRange.aStart.Tab() ); + rRange.aEnd.SetCol( refRange.aStart.Col() + rRange.aEnd.Col() ); + rRange.aEnd.SetRow( refRange.aStart.Row() + rRange.aEnd.Row() ); + rRange.aEnd.SetTab( bTabFromReferrer ? refRange.aEnd.Tab() : rRange.aEnd.Tab() ); + } + } + return true; +} + +/// @throws uno::RuntimeException +static rtl::Reference<ScVbaRange> +getRangeForName( const uno::Reference< uno::XComponentContext >& xContext, const OUString& sName, ScDocShell* pDocSh, const table::CellRangeAddress& pAddr, formula::FormulaGrammar::AddressConvention eConv = formula::FormulaGrammar::CONV_XL_A1 ) +{ + ScRangeList aCellRanges; + ScRange refRange; + ScUnoConversion::FillScRange( refRange, pAddr ); + if ( !getScRangeListForAddress ( sName, pDocSh, refRange, aCellRanges, eConv ) ) + throw uno::RuntimeException(); + // Single range + if ( aCellRanges.size() == 1 ) + { + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( pDocSh, aCellRanges.front() ) ); + uno::Reference< XHelperInterface > xFixThisParent = excel::getUnoSheetModuleObj( xRange ); + return new ScVbaRange( xFixThisParent, xContext, xRange ); + } + uno::Reference< sheet::XSheetCellRangeContainer > xRanges( new ScCellRangesObj( pDocSh, aCellRanges ) ); + + uno::Reference< XHelperInterface > xFixThisParent = excel::getUnoSheetModuleObj( xRanges ); + return new ScVbaRange( xFixThisParent, xContext, xRanges ); +} + +namespace { + +/// @throws uno::RuntimeException +template< typename RangeType > +table::CellRangeAddress lclGetRangeAddress( const uno::Reference< RangeType >& rxCellRange ) +{ + return uno::Reference< sheet::XCellRangeAddressable >( rxCellRange, uno::UNO_QUERY_THROW )->getRangeAddress(); +} + +/// @throws uno::RuntimeException +void lclClearRange( const uno::Reference< table::XCellRange >& rxCellRange ) +{ + using namespace ::com::sun::star::sheet::CellFlags; + sal_Int32 const nFlags = VALUE | DATETIME | STRING | ANNOTATION | FORMULA | HARDATTR | STYLES | EDITATTR | FORMATTED; + uno::Reference< sheet::XSheetOperation > xSheetOperation( rxCellRange, uno::UNO_QUERY_THROW ); + xSheetOperation->clearContents( nFlags ); +} + +/// @throws uno::RuntimeException +uno::Reference< sheet::XSheetCellRange > lclExpandToMerged( const uno::Reference< table::XCellRange >& rxCellRange, bool bRecursive ) +{ + uno::Reference< sheet::XSheetCellRange > xNewCellRange( rxCellRange, uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XSpreadsheet > xSheet( xNewCellRange->getSpreadsheet(), uno::UNO_SET_THROW ); + table::CellRangeAddress aNewAddress = lclGetRangeAddress( xNewCellRange ); + table::CellRangeAddress aOldAddress; + // expand as long as there are new merged ranges included + do + { + aOldAddress = aNewAddress; + uno::Reference< sheet::XSheetCellCursor > xCursor( xSheet->createCursorByRange( xNewCellRange ), uno::UNO_SET_THROW ); + if (xCursor.is()) + { + xCursor->collapseToMergedArea(); + xNewCellRange.set( xCursor, uno::UNO_QUERY_THROW ); + aNewAddress = lclGetRangeAddress( xNewCellRange ); + } + } + while( bRecursive && (aOldAddress != aNewAddress) ); + return xNewCellRange; +} + +/// @throws uno::RuntimeException +uno::Reference< sheet::XSheetCellRangeContainer > lclExpandToMerged( const uno::Reference< sheet::XSheetCellRangeContainer >& rxCellRanges ) +{ + if( !rxCellRanges.is() ) + throw uno::RuntimeException("Missing cell ranges object" ); + sal_Int32 nCount = rxCellRanges->getCount(); + if( nCount < 1 ) + throw uno::RuntimeException("Missing cell ranges object" ); + + ScRangeList aScRanges; + for( sal_Int32 nIndex = 0; nIndex < nCount; ++nIndex ) + { + uno::Reference< table::XCellRange > xRange( rxCellRanges->getByIndex( nIndex ), uno::UNO_QUERY_THROW ); + table::CellRangeAddress aRangeAddr = lclGetRangeAddress( lclExpandToMerged( xRange, /*bRecursive*/true ) ); + ScRange aScRange; + ScUnoConversion::FillScRange( aScRange, aRangeAddr ); + aScRanges.push_back( aScRange ); + } + return new ScCellRangesObj( getDocShellFromRanges( rxCellRanges ), aScRanges ); +} + +/// @throws uno::RuntimeException +void lclExpandAndMerge( const uno::Reference< table::XCellRange >& rxCellRange, bool bMerge ) +{ + uno::Reference< util::XMergeable > xMerge( lclExpandToMerged( rxCellRange, true ), uno::UNO_QUERY_THROW ); + // Calc cannot merge over merged ranges, always unmerge first + xMerge->merge( false ); + if( !bMerge ) + return; + + // clear all contents of the covered cells (not the top-left cell) + table::CellRangeAddress aRangeAddr = lclGetRangeAddress( rxCellRange ); + sal_Int32 nLastColIdx = aRangeAddr.EndColumn - aRangeAddr.StartColumn; + sal_Int32 nLastRowIdx = aRangeAddr.EndRow - aRangeAddr.StartRow; + // clear cells of top row, right of top-left cell + if( nLastColIdx > 0 ) + lclClearRange( rxCellRange->getCellRangeByPosition( 1, 0, nLastColIdx, 0 ) ); + // clear all rows below top row + if( nLastRowIdx > 0 ) + lclClearRange( rxCellRange->getCellRangeByPosition( 0, 1, nLastColIdx, nLastRowIdx ) ); + // merge the range + xMerge->merge( true ); +} + +/// @throws uno::RuntimeException +util::TriState lclGetMergedState( const uno::Reference< table::XCellRange >& rxCellRange ) +{ + /* 1) Check if range is completely inside one single merged range. To do + this, try to extend from top-left cell only (not from entire range). + This will exclude cases where this range consists of several merged + ranges (or parts of them). */ + table::CellRangeAddress aRangeAddr = lclGetRangeAddress( rxCellRange ); + uno::Reference< table::XCellRange > xTopLeft( rxCellRange->getCellRangeByPosition( 0, 0, 0, 0 ), uno::UNO_SET_THROW ); + uno::Reference< sheet::XSheetCellRange > xExpanded( lclExpandToMerged( xTopLeft, false ), uno::UNO_SET_THROW ); + table::CellRangeAddress aExpAddr = lclGetRangeAddress( xExpanded ); + // check that expanded range has more than one cell (really merged) + if( ((aExpAddr.StartColumn < aExpAddr.EndColumn) || (aExpAddr.StartRow < aExpAddr.EndRow)) && ScUnoConversion::Contains( aExpAddr, aRangeAddr ) ) + return util::TriState_YES; + + /* 2) Check if this range contains any merged cells (completely or + partly). This seems to be hardly possible via API, as + XMergeable::getIsMerged() returns only true, if the top-left cell of a + merged range is part of this range, so cases where just the lower part + of a merged range is part of this range are not covered. */ + ScRange aScRange; + ScUnoConversion::FillScRange( aScRange, aRangeAddr ); + bool bHasMerged = getDocumentFromRange( rxCellRange ).HasAttrib( aScRange, HasAttrFlags::Merged | HasAttrFlags::Overlapped ); + return bHasMerged ? util::TriState_INDETERMINATE : util::TriState_NO; +} + +} // namespace + +css::uno::Reference< excel::XRange > +ScVbaRange::getRangeObjectForName( + const uno::Reference< uno::XComponentContext >& xContext, const OUString& sRangeName, + ScDocShell* pDocSh, formula::FormulaGrammar::AddressConvention eConv ) +{ + table::CellRangeAddress refAddr; + return getRangeForName( xContext, sRangeName, pDocSh, refAddr, eConv ); +} + +/// @throws uno::RuntimeException +static table::CellRangeAddress getCellRangeAddressForVBARange( const uno::Any& aParam, ScDocShell* pDocSh ) +{ + uno::Reference< table::XCellRange > xRangeParam; + switch ( aParam.getValueTypeClass() ) + { + case uno::TypeClass_STRING: + { + OUString rString; + aParam >>= rString; + ScRangeList aCellRanges; + ScRange refRange; + if ( getScRangeListForAddress ( rString, pDocSh, refRange, aCellRanges ) ) + { + if ( aCellRanges.size() == 1 ) + { + table::CellRangeAddress aRangeAddress; + ScUnoConversion::FillApiRange( aRangeAddress, aCellRanges.front() ); + return aRangeAddress; + } + } + } + break; + + case uno::TypeClass_INTERFACE: + { + uno::Reference< excel::XRange > xRange; + aParam >>= xRange; + if ( xRange.is() ) + xRange->getCellRange() >>= xRangeParam; + } + break; + + default: + throw uno::RuntimeException("Can't extract CellRangeAddress from type" ); + } + return lclGetRangeAddress( xRangeParam ); +} + +/// @throws uno::RuntimeException +static uno::Reference< XCollection > +lcl_setupBorders( const uno::Reference< excel::XRange >& xParentRange, const uno::Reference<uno::XComponentContext>& xContext, const uno::Reference< table::XCellRange >& xRange ) +{ + uno::Reference< XHelperInterface > xParent( xParentRange, uno::UNO_QUERY_THROW ); + ScDocument& rDoc = getDocumentFromRange(xRange); + ScVbaPalette aPalette( rDoc.GetDocumentShell() ); + uno::Reference< XCollection > borders( new ScVbaBorders( xParent, xContext, xRange, aPalette ) ); + return borders; +} + +ScVbaRange::ScVbaRange( uno::Sequence< uno::Any> const & args, + uno::Reference< uno::XComponentContext> const & xContext ) : ScVbaRange_BASE( getXSomethingFromArgs< XHelperInterface >( args, 0 ), xContext, getXSomethingFromArgs< beans::XPropertySet >( args, 1, false ), getModelFromXIf( getXSomethingFromArgs< uno::XInterface >( args, 1 ) ), true ), mbIsRows( false ), mbIsColumns( false ) +{ + mxRange.set( mxPropertySet, uno::UNO_QUERY ); + mxRanges.set( mxPropertySet, uno::UNO_QUERY ); + uno::Reference< container::XIndexAccess > xIndex; + if ( mxRange.is() ) + { + xIndex = new SingleRangeIndexAccess( mxRange ); + } + else if ( mxRanges.is() ) + { + xIndex.set( mxRanges, uno::UNO_QUERY_THROW ); + } + m_Areas = new ScVbaRangeAreas( mxParent, mxContext, xIndex, mbIsRows, mbIsColumns ); +} + +ScVbaRange::ScVbaRange( const uno::Reference< XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const uno::Reference< table::XCellRange >& xRange, bool bIsRows, bool bIsColumns ) +: ScVbaRange_BASE( xParent, xContext, uno::Reference< beans::XPropertySet >( xRange, uno::UNO_QUERY_THROW ), getModelFromRange( xRange), true ), mxRange( xRange ), + mbIsRows( bIsRows ), + mbIsColumns( bIsColumns ) +{ + if ( !xContext.is() ) + throw lang::IllegalArgumentException("context is not set ", uno::Reference< uno::XInterface >() , 1 ); + if ( !xRange.is() ) + throw lang::IllegalArgumentException("range is not set ", uno::Reference< uno::XInterface >() , 1 ); + + uno::Reference< container::XIndexAccess > xIndex( new SingleRangeIndexAccess( xRange ) ); + m_Areas = new ScVbaRangeAreas( mxParent, mxContext, xIndex, mbIsRows, mbIsColumns ); + +} + +ScVbaRange::ScVbaRange(const uno::Reference< XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const uno::Reference< sheet::XSheetCellRangeContainer >& xRanges, bool bIsRows, bool bIsColumns) +: ScVbaRange_BASE( xParent, xContext, uno::Reference< beans::XPropertySet >( xRanges, uno::UNO_QUERY_THROW ), getModelFromXIf( uno::Reference< uno::XInterface >( xRanges, uno::UNO_QUERY_THROW ) ), true ), mxRanges( xRanges ),mbIsRows( bIsRows ), mbIsColumns( bIsColumns ) + +{ + uno::Reference< container::XIndexAccess > xIndex( mxRanges, uno::UNO_QUERY_THROW ); + m_Areas = new ScVbaRangeAreas( xParent, mxContext, xIndex, mbIsRows, mbIsColumns ); + +} + +ScVbaRange::~ScVbaRange() +{ +} + +uno::Reference< XCollection >& ScVbaRange::getBorders() +{ + if ( !m_Borders.is() ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32(1) ), uno::Any() ), uno::UNO_QUERY_THROW ); + m_Borders = lcl_setupBorders( this, mxContext, uno::Reference< table::XCellRange >( xRange->getCellRange(), uno::UNO_QUERY_THROW ) ); + } + return m_Borders; +} + +void +ScVbaRange::visitArray( ArrayVisitor& visitor ) +{ + ScDocShell* pDocSh = nullptr; + if(ScCellRangeObj* range = dynamic_cast<ScCellRangeObj*>(mxRange.get())) + pDocSh = range->GetDocShell(); + if ( pDocSh ) + pDocSh->LockPaint(); + table::CellRangeAddress aRangeAddr = lclGetRangeAddress( mxRange ); + sal_Int32 nRowCount = aRangeAddr.EndRow - aRangeAddr.StartRow + 1; + sal_Int32 nColCount = aRangeAddr.EndColumn - aRangeAddr.StartColumn + 1; + for ( sal_Int32 i=0; i<nRowCount; ++i ) + { + for ( sal_Int32 j=0; j<nColCount; ++j ) + { + uno::Reference< table::XCell > xCell( mxRange->getCellByPosition( j, i ), uno::UNO_SET_THROW ); + + visitor.visitNode( i, j, xCell ); + } + } + if ( pDocSh ) + pDocSh->UnlockPaint(); +} + +uno::Any +ScVbaRange::getValue( ValueGetter& valueGetter) +{ + uno::Reference< table::XColumnRowRange > xColumnRowRange(mxRange, uno::UNO_QUERY_THROW ); + // single cell range + if ( isSingleCellRange() ) + { + visitArray( valueGetter ); + return valueGetter.getValue(); + } + sal_Int32 nRowCount = xColumnRowRange->getRows()->getCount(); + sal_Int32 nColCount = xColumnRowRange->getColumns()->getCount(); + // multi cell range ( return array ) + Dim2ArrayValueGetter arrayGetter( nRowCount, nColCount, valueGetter ); + visitArray( arrayGetter ); + return uno::Any( script::ArrayWrapper( false, arrayGetter.getValue() ) ); +} + +css::uno::Any ScVbaRange::DoGetValue( RangeValueType eValueType ) +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->getValue(); + } + + CellValueGetter valueGetter( eValueType ); + return getValue( valueGetter ); +} + +uno::Any SAL_CALL +ScVbaRange::getValue() +{ + return DoGetValue( RangeValueType::value ); +} + +uno::Any SAL_CALL +ScVbaRange::getValue2() +{ + return DoGetValue( RangeValueType::value2 ); +} + + +void +ScVbaRange::setValue( const uno::Any& aValue, ValueSetter& valueSetter ) +{ + uno::TypeClass aClass = aValue.getValueTypeClass(); + if ( aClass == uno::TypeClass_SEQUENCE ) + { + const uno::Reference< script::XTypeConverter >& xConverter = getTypeConverter( mxContext ); + uno::Any aConverted; + try + { + // test for single dimension, could do + // with a better test than this + if ( aValue.getValueTypeName().indexOf('[') == aValue.getValueTypeName().lastIndexOf('[') ) + { + aConverted = xConverter->convertTo( aValue, cppu::UnoType<uno::Sequence< uno::Any >>::get() ); + Dim1ArrayValueSetter setter( aConverted, valueSetter ); + visitArray( setter ); + } + else + { + aConverted = xConverter->convertTo( aValue, cppu::UnoType<uno::Sequence< uno::Sequence< uno::Any > >>::get() ); + Dim2ArrayValueSetter setter( aConverted, valueSetter ); + visitArray( setter ); + } + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("sc", "Bahhh, caught" ); + } + } + else + { + visitArray( valueSetter ); + } + fireChangeEvent(); +} + +void SAL_CALL +ScVbaRange::setValue( const uno::Any &aValue ) +{ + // If this is a multiple selection apply setValue over all areas + if ( m_Areas->getCount() > 1 ) + { + AreasVisitor aVisitor( m_Areas ); + RangeValueProcessor valueProcessor( aValue ); + aVisitor.visit( valueProcessor ); + return; + } + CellValueSetter valueSetter( aValue ); + setValue( aValue, valueSetter ); +} + +void SAL_CALL +ScVbaRange::setValue2( const uno::Any &aValue ) +{ + return setValue( aValue ); +} + + +void SAL_CALL +ScVbaRange::Clear() +{ + using namespace ::com::sun::star::sheet::CellFlags; + sal_Int32 const nFlags = VALUE | DATETIME | STRING | FORMULA | HARDATTR | EDITATTR | FORMATTED; + ClearContents( nFlags, true ); +} + +//helper ClearContent +void +ScVbaRange::ClearContents( sal_Int32 nFlags, bool bFireEvent ) +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + ScVbaRange* pRange = getImplementation( xRange ); + if ( pRange ) + pRange->ClearContents( nFlags, false ); // do not fire for single ranges + } + // fire change event for the entire range list + if( bFireEvent ) fireChangeEvent(); + return; + } + + uno::Reference< sheet::XSheetOperation > xSheetOperation(mxRange, uno::UNO_QUERY_THROW); + xSheetOperation->clearContents( nFlags ); + if( bFireEvent ) fireChangeEvent(); +} + +void SAL_CALL +ScVbaRange::ClearComments() +{ + ClearContents( sheet::CellFlags::ANNOTATION, false ); +} + +void SAL_CALL +ScVbaRange::ClearContents() +{ + using namespace ::com::sun::star::sheet::CellFlags; + sal_Int32 const nFlags = VALUE | DATETIME | STRING | FORMULA; + ClearContents( nFlags, true ); +} + +void SAL_CALL +ScVbaRange::ClearFormats() +{ + // FIXME: need to check if we need to combine FORMATTED + using namespace ::com::sun::star::sheet::CellFlags; + sal_Int32 const nFlags = HARDATTR | FORMATTED | EDITATTR; + ClearContents( nFlags, false ); +} + +void +ScVbaRange::setFormulaValue( const uno::Any& rFormula, formula::FormulaGrammar::Grammar eGram ) +{ + // If this is a multiple selection apply setFormula over all areas + if ( m_Areas->getCount() > 1 ) + { + AreasVisitor aVisitor( m_Areas ); + RangeFormulaProcessor valueProcessor( rFormula ); + aVisitor.visit( valueProcessor ); + return; + } + CellFormulaValueSetter formulaValueSetter( rFormula, getScDocument(), eGram ); + setValue( rFormula, formulaValueSetter ); +} + +uno::Any +ScVbaRange::getFormulaValue( formula::FormulaGrammar::Grammar eGram ) +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->getFormula(); + } + CellFormulaValueGetter valueGetter( getScDocument(), eGram ); + return getValue( valueGetter ); + +} + +uno::Any +ScVbaRange::getFormula() +{ + return getFormulaValue( formula::FormulaGrammar::GRAM_ENGLISH_XL_A1 ); +} + +void +ScVbaRange::setFormula(const uno::Any &rFormula ) +{ + setFormulaValue( rFormula, formula::FormulaGrammar::GRAM_ENGLISH_XL_A1 ); +} + +uno::Any +ScVbaRange::getFormulaR1C1() +{ + return getFormulaValue( formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1 ); +} + +void +ScVbaRange::setFormulaR1C1(const uno::Any& rFormula ) +{ + setFormulaValue( rFormula, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1 ); +} + +uno::Any +ScVbaRange::getFormulaLocal() +{ + return getFormulaValue( formula::FormulaGrammar::GRAM_NATIVE_XL_A1 ); +} + +void +ScVbaRange::setFormulaLocal(const uno::Any &rFormula ) +{ + setFormulaValue( rFormula, formula::FormulaGrammar::GRAM_NATIVE_XL_A1 ); +} + +uno::Any +ScVbaRange::getFormulaR1C1Local() +{ + return getFormulaValue( formula::FormulaGrammar::GRAM_NATIVE_XL_R1C1 ); +} + +void +ScVbaRange::setFormulaR1C1Local(const uno::Any& rFormula ) +{ + setFormulaValue( rFormula, formula::FormulaGrammar::GRAM_NATIVE_XL_R1C1 ); +} + +sal_Int32 +ScVbaRange::getCount() +{ + // If this is a multiple selection apply setValue over all areas + if ( m_Areas->getCount() > 1 ) + { + AreasVisitor aVisitor( m_Areas ); + RangeCountProcessor valueProcessor; + aVisitor.visit( valueProcessor ); + return valueProcessor.value(); + } + sal_Int32 rowCount = 0; + sal_Int32 colCount = 0; + uno::Reference< table::XColumnRowRange > xColumnRowRange(mxRange, uno::UNO_QUERY_THROW ); + rowCount = xColumnRowRange->getRows()->getCount(); + colCount = xColumnRowRange->getColumns()->getCount(); + + if( mbIsRows ) + return rowCount; + if( mbIsColumns ) + return colCount; + return rowCount * colCount; +} + +sal_Int32 +ScVbaRange::getRow() +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->getRow(); + } + uno::Reference< sheet::XCellAddressable > xCellAddressable(mxRange->getCellByPosition(0, 0), uno::UNO_QUERY_THROW ); + return xCellAddressable->getCellAddress().Row + 1; // Zero value indexing +} + +sal_Int32 +ScVbaRange::getColumn() +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->getColumn(); + } + uno::Reference< sheet::XCellAddressable > xCellAddressable(mxRange->getCellByPosition(0, 0), uno::UNO_QUERY_THROW ); + return xCellAddressable->getCellAddress().Column + 1; // Zero value indexing +} + +uno::Any +ScVbaRange::HasFormula() +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + uno::Any aResult = aNULL(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + // if the HasFormula for any area is different to another + // return null + if ( index > 1 ) + if ( aResult != xRange->HasFormula() ) + return aNULL(); + aResult = xRange->HasFormula(); + if ( aNULL() == aResult ) + return aNULL(); + } + return aResult; + } + uno::Reference< uno::XInterface > xIf( mxRange, uno::UNO_QUERY_THROW ); + ScCellRangesBase* pThisRanges = dynamic_cast< ScCellRangesBase* > ( xIf.get() ); + if ( pThisRanges ) + { + uno::Reference<uno::XInterface> xRanges( pThisRanges->queryFormulaCells( sheet::FormulaResult::ERROR | sheet::FormulaResult::VALUE | sheet::FormulaResult::STRING ), uno::UNO_QUERY_THROW ); + ScCellRangesBase* pFormulaRanges + = dynamic_cast< ScCellRangesBase* > ( xRanges.get() ); + assert(pFormulaRanges); + // check if there are no formula cell, return false + if ( pFormulaRanges->GetRangeList().empty() ) + return uno::Any(false); + + // check if there are holes (where some cells are not formulas) + // or returned range is not equal to this range + if ( ( pFormulaRanges->GetRangeList().size() > 1 ) + || ( pFormulaRanges->GetRangeList().front().aStart != pThisRanges->GetRangeList().front().aStart ) + || ( pFormulaRanges->GetRangeList().front().aEnd != pThisRanges->GetRangeList().front().aEnd ) + ) + return aNULL(); // should return aNULL; + } + return uno::Any( true ); +} +void +ScVbaRange::fillSeries( sheet::FillDirection nFillDirection, sheet::FillMode nFillMode, sheet::FillDateMode nFillDateMode, double fStep, double fEndValue ) +{ + if ( m_Areas->getCount() > 1 ) + { + // Multi-Area Range + uno::Reference< XCollection > xCollection( m_Areas, uno::UNO_SET_THROW ); + for ( sal_Int32 index = 1; index <= xCollection->getCount(); ++index ) + { + uno::Reference< excel::XRange > xRange( xCollection->Item( uno::Any( index ), uno::Any() ), uno::UNO_QUERY_THROW ); + ScVbaRange* pThisRange = getImplementation( xRange ); + pThisRange->fillSeries( nFillDirection, nFillMode, nFillDateMode, fStep, fEndValue ); + + } + return; + } + + uno::Reference< sheet::XCellSeries > xCellSeries(mxRange, uno::UNO_QUERY_THROW ); + xCellSeries->fillSeries( nFillDirection, nFillMode, nFillDateMode, fStep, fEndValue ); + fireChangeEvent(); +} + +void +ScVbaRange::FillLeft() +{ + fillSeries(sheet::FillDirection_TO_LEFT, + sheet::FillMode_SIMPLE, sheet::FillDateMode_FILL_DATE_DAY, 0, 0x7FFFFFFF); +} + +void +ScVbaRange::FillRight() +{ + fillSeries(sheet::FillDirection_TO_RIGHT, + sheet::FillMode_SIMPLE, sheet::FillDateMode_FILL_DATE_DAY, 0, 0x7FFFFFFF); +} + +void +ScVbaRange::FillUp() +{ + fillSeries(sheet::FillDirection_TO_TOP, + sheet::FillMode_SIMPLE, sheet::FillDateMode_FILL_DATE_DAY, 0, 0x7FFFFFFF); +} + +void +ScVbaRange::FillDown() +{ + fillSeries(sheet::FillDirection_TO_BOTTOM, + sheet::FillMode_SIMPLE, sheet::FillDateMode_FILL_DATE_DAY, 0, 0x7FFFFFFF); +} + +OUString +ScVbaRange::getText() +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->getText(); + } + uno::Reference< text::XTextRange > xTextRange(mxRange->getCellByPosition(0,0), uno::UNO_QUERY_THROW ); + return xTextRange->getString(); +} + +uno::Reference< excel::XRange > +ScVbaRange::Offset( const ::uno::Any &nRowOff, const uno::Any &nColOff ) +{ + SCROW nRowOffset = 0; + SCCOL nColOffset = 0; + bool bIsRowOffset = ( nRowOff >>= nRowOffset ); + bool bIsColumnOffset = ( nColOff >>= nColOffset ); + ScCellRangesBase* pUnoRangesBase = getCellRangesBase(); + + ScRangeList aCellRanges = pUnoRangesBase->GetRangeList(); + + for ( size_t i = 0, nRanges = aCellRanges.size(); i < nRanges; ++i ) + { + ScRange & rRange = aCellRanges[ i ]; + if ( bIsColumnOffset ) + { + rRange.aStart.SetCol( rRange.aStart.Col() + nColOffset ); + rRange.aEnd.SetCol( rRange.aEnd.Col() + nColOffset ); + } + if ( bIsRowOffset ) + { + rRange.aStart.SetRow( rRange.aStart.Row() + nRowOffset ); + rRange.aEnd.SetRow( rRange.aEnd.Row() + nRowOffset ); + } + } + + if ( aCellRanges.size() > 1 ) // Multi-Area + { + uno::Reference< sheet::XSheetCellRangeContainer > xRanges( new ScCellRangesObj( pUnoRangesBase->GetDocShell(), aCellRanges ) ); + return new ScVbaRange( mxParent, mxContext, xRanges ); + } + // normal range + const ScRange aRange( obtainRangeEvenIfRangeListIsEmpty( aCellRanges)); + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( pUnoRangesBase->GetDocShell(), aRange)); + return new ScVbaRange( mxParent, mxContext, xRange ); +} + +uno::Reference< excel::XRange > +ScVbaRange::CurrentRegion() +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->CurrentRegion(); + } + + RangeHelper helper( mxRange ); + uno::Reference< sheet::XSheetCellCursor > xSheetCellCursor = + helper.getSheetCellCursor(); + xSheetCellCursor->collapseToCurrentRegion(); + uno::Reference< sheet::XCellRangeAddressable > xCellRangeAddressable(xSheetCellCursor, uno::UNO_QUERY_THROW); + return RangeHelper::createRangeFromRange( mxParent, mxContext, helper.getCellRangeFromSheet(), xCellRangeAddressable ); +} + +uno::Reference< excel::XRange > +ScVbaRange::CurrentArray() +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->CurrentArray(); + } + RangeHelper helper( mxRange ); + uno::Reference< sheet::XSheetCellCursor > xSheetCellCursor = + helper.getSheetCellCursor(); + xSheetCellCursor->collapseToCurrentArray(); + uno::Reference< sheet::XCellRangeAddressable > xCellRangeAddressable(xSheetCellCursor, uno::UNO_QUERY_THROW); + return RangeHelper::createRangeFromRange( mxParent, mxContext, helper.getCellRangeFromSheet(), xCellRangeAddressable ); +} + +uno::Any +ScVbaRange::getFormulaArray() +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->getFormulaArray(); + } + + // return a formula if there is one or else an array + // still not sure when the return as array code should run + // ( I think it is if there is more than one formula ) at least + // that is what the doc says ( but I am not even sure how to detect that ) + // for the moment any tests we have pass + uno::Reference< sheet::XArrayFormulaRange> xFormulaArray( mxRange, uno::UNO_QUERY_THROW ); + if ( !xFormulaArray->getArrayFormula().isEmpty() ) + return uno::Any( xFormulaArray->getArrayFormula() ); + + uno::Reference< sheet::XCellRangeFormula> xCellRangeFormula( mxRange, uno::UNO_QUERY_THROW ); + const uno::Reference< script::XTypeConverter >& xConverter = getTypeConverter( mxContext ); + uno::Any aSingleValueOrMatrix; + // When dealing with a single element ( embedded in the sequence of sequence ) unwrap and return + // that value + uno::Sequence< uno::Sequence<OUString> > aTmpSeq = xCellRangeFormula->getFormulaArray(); + if ( aTmpSeq.getLength() == 1 ) + { + if ( aTmpSeq[ 0 ].getLength() == 1 ) + aSingleValueOrMatrix <<= aTmpSeq[ 0 ][ 0 ]; + } + else + aSingleValueOrMatrix = xConverter->convertTo( uno::Any( aTmpSeq ) , cppu::UnoType<uno::Sequence< uno::Sequence< uno::Any > >>::get() ) ; + return aSingleValueOrMatrix; +} + +void +ScVbaRange::setFormulaArray(const uno::Any& rFormula) +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->setFormulaArray( rFormula ); + } + // #TODO need to distinguish between getFormula and getFormulaArray e.g. (R1C1) + // but for the moment it's just easier to treat them the same for setting + // seems + uno::Reference< lang::XMultiServiceFactory > xModelFactory( getUnoModel(), uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XFormulaParser > xParser( xModelFactory->createInstance( "com.sun.star.sheet.FormulaParser" ), uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XCellRangeAddressable > xSource( mxRange, uno::UNO_QUERY_THROW); + + table::CellRangeAddress aRangeAddress = xSource->getRangeAddress(); + // #TODO check if api orders the address + // e.g. do we need to order the RangeAddress to get the topleft ( or can we assume it + // is in the correct order ) + table::CellAddress aAddress; + aAddress.Sheet = aRangeAddress.Sheet; + aAddress.Column = aRangeAddress.StartColumn; + aAddress.Row = aRangeAddress.StartRow; + OUString sFormula; + rFormula >>= sFormula; + uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( sFormula, aAddress ); + ScTokenArray aTokenArray(getScDocument()); + (void)ScTokenConversion::ConvertToTokenArray( getScDocument(), aTokenArray, aTokens ); + + getScDocShell()->GetDocFunc().EnterMatrix( getScRangeList()[0], nullptr, &aTokenArray, OUString(), true, true, OUString(), formula::FormulaGrammar::GRAM_API ); +} + +OUString +ScVbaRange::Characters(const uno::Any& Start, const uno::Any& Length) +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->Characters( Start, Length ); + } + + tools::Long nIndex = 0, nCount = 0; + OUString rString; + uno::Reference< text::XTextRange > xTextRange(mxRange, ::uno::UNO_QUERY_THROW ); + rString = xTextRange->getString(); + if( !( Start >>= nIndex ) && !( Length >>= nCount ) ) + return rString; + if(!( Start >>= nIndex ) ) + nIndex = 1; + if(!( Length >>= nCount ) ) + nIndex = rString.getLength(); + return rString.copy( --nIndex, nCount ); // Zero value indexing +} + +OUString +ScVbaRange::Address( const uno::Any& RowAbsolute, const uno::Any& ColumnAbsolute, const uno::Any& ReferenceStyle, const uno::Any& External, const uno::Any& RelativeTo ) +{ + if ( m_Areas->getCount() > 1 ) + { + // Multi-Area Range + OUStringBuffer sAddress; + uno::Reference< XCollection > xCollection( m_Areas, uno::UNO_SET_THROW ); + uno::Any aExternalCopy = External; + for ( sal_Int32 index = 1; index <= xCollection->getCount(); ++index ) + { + uno::Reference< excel::XRange > xRange( xCollection->Item( uno::Any( index ), uno::Any() ), uno::UNO_QUERY_THROW ); + if ( index > 1 ) + { + sAddress.append(","); + // force external to be false + // only first address should have the + // document and sheet specifications + aExternalCopy <<= false; + } + sAddress.append(xRange->Address( RowAbsolute, ColumnAbsolute, ReferenceStyle, aExternalCopy, RelativeTo )); + } + return sAddress.makeStringAndClear(); + + } + ScAddress::Details dDetails( formula::FormulaGrammar::CONV_XL_A1, 0, 0 ); + if ( ReferenceStyle.hasValue() ) + { + sal_Int32 refStyle = excel::XlReferenceStyle::xlA1; + ReferenceStyle >>= refStyle; + if ( refStyle == excel::XlReferenceStyle::xlR1C1 ) + dDetails = ScAddress::Details( formula::FormulaGrammar::CONV_XL_R1C1, 0, 0 ); + } + // default + ScRefFlags nFlags = ScRefFlags::RANGE_ABS; + ScDocShell* pDocShell = getScDocShell(); + ScDocument& rDoc = pDocShell->GetDocument(); + + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + ScRange aRange( static_cast< SCCOL >( thisAddress.StartColumn ), static_cast< SCROW >( thisAddress.StartRow ), static_cast< SCTAB >( thisAddress.Sheet ), static_cast< SCCOL >( thisAddress.EndColumn ), static_cast< SCROW >( thisAddress.EndRow ), static_cast< SCTAB >( thisAddress.Sheet ) ); + constexpr ScRefFlags ROW_ABS = ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS; + constexpr ScRefFlags COL_ABS = ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS; + + if ( RowAbsolute.hasValue() ) + { + bool bVal = true; + RowAbsolute >>= bVal; + if ( !bVal ) + nFlags &= ~ROW_ABS; + } + if ( ColumnAbsolute.hasValue() ) + { + bool bVal = true; + ColumnAbsolute >>= bVal; + if ( !bVal ) + nFlags &= ~COL_ABS; + } + if ( External.hasValue() ) + { + bool bLocal = false; + External >>= bLocal; + if ( bLocal ) + nFlags |= ScRefFlags::TAB_3D | ScRefFlags::FORCE_DOC; + } + if ( RelativeTo.hasValue() ) + { + // #TODO should I throw an error if R1C1 is not set? + + table::CellRangeAddress refAddress = getCellRangeAddressForVBARange( RelativeTo, pDocShell ); + dDetails = ScAddress::Details( formula::FormulaGrammar::CONV_XL_R1C1, static_cast< SCROW >( refAddress.StartRow ), static_cast< SCCOL >( refAddress.StartColumn ) ); + } + return aRange.Format(rDoc, nFlags, dDetails); +} + +uno::Reference < excel::XFont > +ScVbaRange::Font() +{ + uno::Reference< beans::XPropertySet > xProps(mxRange, ::uno::UNO_QUERY ); + ScDocument& rDoc = getScDocument(); + if ( mxRange.is() ) + xProps.set(mxRange, ::uno::UNO_QUERY ); + else if ( mxRanges.is() ) + xProps.set(mxRanges, ::uno::UNO_QUERY ); + + ScVbaPalette aPalette( rDoc.GetDocumentShell() ); + ScCellRangeObj* pRangeObj = nullptr; + try + { + pRangeObj = getCellRangeObj(); + } + catch( uno::Exception& ) + { + } + return new ScVbaFont( this, mxContext, aPalette, xProps, pRangeObj ); +} + +uno::Reference< excel::XRange > +ScVbaRange::Cells( const uno::Any &nRowIndex, const uno::Any &nColumnIndex ) +{ + // #TODO code within the test below "if ( m_Areas... " can be removed + // Test is performed only because m_xRange is NOT set to be + // the first range in m_Areas ( to force failure while + // the implementations for each method are being updated ) + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->Cells( nRowIndex, nColumnIndex ); + } + + // Performance: Use a common helper method for ScVbaRange::Cells and ScVbaWorksheet::Cells, + // instead of creating a new ScVbaRange object in often-called ScVbaWorksheet::Cells + return CellsHelper( getScDocument(), mxParent, mxContext, mxRange, nRowIndex, nColumnIndex ); +} + +// static +uno::Reference< excel::XRange > +ScVbaRange::CellsHelper( const ScDocument& rDoc, + const uno::Reference< ov::XHelperInterface >& xParent, + const uno::Reference< uno::XComponentContext >& xContext, + const uno::Reference< css::table::XCellRange >& xRange, + const uno::Any &nRowIndex, const uno::Any &nColumnIndex ) +{ + sal_Int32 nRow = 0, nColumn = 0; + + bool bIsIndex = nRowIndex.hasValue(); + bool bIsColumnIndex = nColumnIndex.hasValue(); + + // Sometimes we might get a float or a double or whatever + // set in the Any, we should convert as appropriate + // #FIXME - perhaps worth turning this into some sort of + // conversion routine e.g. bSuccess = getValueFromAny( nRow, nRowIndex, cppu::UnoType<sal_Int32>::get() ) + if ( nRowIndex.hasValue() && !( nRowIndex >>= nRow ) ) + { + const uno::Reference< script::XTypeConverter >& xConverter = getTypeConverter( xContext ); + uno::Any aConverted; + try + { + aConverted = xConverter->convertTo( nRowIndex, cppu::UnoType<sal_Int32>::get() ); + bIsIndex = ( aConverted >>= nRow ); + } + catch( uno::Exception& ) {} // silence any errors + } + + if ( bIsColumnIndex ) + { + // Column index can be a col address e.g Cells( 1, "B" ) etc. + OUString sCol; + if ( nColumnIndex >>= sCol ) + { + ScAddress::Details dDetails( formula::FormulaGrammar::CONV_XL_A1, 0, 0 ); + ScRange tmpRange; + ScRefFlags flags = tmpRange.ParseCols( rDoc, sCol, dDetails ); + if ( (flags & ScRefFlags::COL_VALID) == ScRefFlags::ZERO ) + throw uno::RuntimeException(); + nColumn = tmpRange.aStart.Col() + 1; + } + else + { + if ( !( nColumnIndex >>= nColumn ) ) + { + const uno::Reference< script::XTypeConverter >& xConverter = getTypeConverter( xContext ); + uno::Any aConverted; + try + { + aConverted = xConverter->convertTo( nColumnIndex, cppu::UnoType<sal_Int32>::get() ); + bIsColumnIndex = ( aConverted >>= nColumn ); + } + catch( uno::Exception& ) {} // silence any errors + } + } + } + RangeHelper thisRange( xRange ); + table::CellRangeAddress thisRangeAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + uno::Reference< table::XCellRange > xSheetRange = thisRange.getCellRangeFromSheet(); + if( !bIsIndex && !bIsColumnIndex ) // .Cells + // #FIXME needs proper parent ( Worksheet ) + return uno::Reference< excel::XRange >( new ScVbaRange( xParent, xContext, xRange ) ); + + sal_Int32 nIndex = --nRow; + if( bIsIndex && !bIsColumnIndex ) // .Cells(n) + { + uno::Reference< table::XColumnRowRange > xColumnRowRange(xRange, ::uno::UNO_QUERY_THROW); + sal_Int32 nColCount = xColumnRowRange->getColumns()->getCount(); + + if ( !nIndex || nIndex < 0 ) + nRow = 0; + else + nRow = nIndex / nColCount; + nColumn = nIndex % nColCount; + } + else + --nColumn; + nRow = nRow + thisRangeAddress.StartRow; + nColumn = nColumn + thisRangeAddress.StartColumn; + return new ScVbaRange( xParent, xContext, xSheetRange->getCellRangeByPosition( nColumn, nRow, nColumn, nRow ) ); +} + +void +ScVbaRange::Select() +{ + ScCellRangesBase* pUnoRangesBase = getCellRangesBase(); + if ( !pUnoRangesBase ) + throw uno::RuntimeException("Failed to access underlying uno range object" ); + ScDocShell* pShell = pUnoRangesBase->GetDocShell(); + if ( !pShell ) + return; + + uno::Reference< frame::XModel > xModel( pShell->GetModel(), uno::UNO_SET_THROW ); + uno::Reference< view::XSelectionSupplier > xSelection( xModel->getCurrentController(), uno::UNO_QUERY_THROW ); + if ( mxRanges.is() ) + xSelection->select( uno::Any( lclExpandToMerged( mxRanges ) ) ); + else + xSelection->select( uno::Any( lclExpandToMerged( mxRange, true ) ) ); + // set focus on document e.g. + // ThisComponent.CurrentController.Frame.getContainerWindow.SetFocus + try + { + uno::Reference< frame::XController > xController( xModel->getCurrentController(), uno::UNO_SET_THROW ); + uno::Reference< frame::XFrame > xFrame( xController->getFrame(), uno::UNO_SET_THROW ); + uno::Reference< awt::XWindow > xWin( xFrame->getContainerWindow(), uno::UNO_SET_THROW ); + xWin->setFocus(); + } + catch( uno::Exception& ) + { + } +} + +static bool cellInRange( const table::CellRangeAddress& rAddr, sal_Int32 nCol, sal_Int32 nRow ) +{ + return nCol >= rAddr.StartColumn && nCol <= rAddr.EndColumn && + nRow >= rAddr.StartRow && nRow <= rAddr.EndRow; +} + +static void setCursor( SCCOL nCol, SCROW nRow, const uno::Reference< frame::XModel >& xModel, bool bInSel = true ) +{ + ScTabViewShell* pShell = excel::getBestViewShell( xModel ); + if ( pShell ) + { + if ( bInSel ) + pShell->SetCursor( nCol, nRow ); + else + pShell->MoveCursorAbs( nCol, nRow, SC_FOLLOW_NONE, false, false, true ); + } +} + +void +ScVbaRange::Activate() +{ + // get first cell of current range + uno::Reference< table::XCellRange > xCellRange; + if ( mxRanges.is() ) + { + uno::Reference< container::XIndexAccess > xIndex( mxRanges, uno::UNO_QUERY_THROW ); + xCellRange.set( xIndex->getByIndex( 0 ), uno::UNO_QUERY_THROW ); + } + else + xCellRange.set( mxRange, uno::UNO_SET_THROW ); + + RangeHelper thisRange( xCellRange ); + uno::Reference< sheet::XCellRangeAddressable > xThisRangeAddress = thisRange.getCellRangeAddressable(); + table::CellRangeAddress thisRangeAddress = xThisRangeAddress->getRangeAddress(); + uno::Reference< frame::XModel > xModel; + ScDocShell* pShell = getScDocShell(); + + if ( pShell ) + xModel = pShell->GetModel(); + + if ( !xModel.is() ) + throw uno::RuntimeException(); + + // get current selection + uno::Reference< sheet::XCellRangeAddressable > xRange( xModel->getCurrentSelection(), ::uno::UNO_QUERY); + + uno::Reference< sheet::XSheetCellRanges > xRanges( xModel->getCurrentSelection(), ::uno::UNO_QUERY); + + if ( xRanges.is() ) + { + const uno::Sequence< table::CellRangeAddress > nAddrs = xRanges->getRangeAddresses(); + for ( const auto& rAddr : nAddrs ) + { + if ( cellInRange( rAddr, thisRangeAddress.StartColumn, thisRangeAddress.StartRow ) ) + { + setCursor( static_cast< SCCOL >( thisRangeAddress.StartColumn ), static_cast< SCROW >( thisRangeAddress.StartRow ), xModel ); + return; + } + + } + } + + if ( xRange.is() && cellInRange( xRange->getRangeAddress(), thisRangeAddress.StartColumn, thisRangeAddress.StartRow ) ) + setCursor( static_cast< SCCOL >( thisRangeAddress.StartColumn ), static_cast< SCROW >( thisRangeAddress.StartRow ), xModel ); + else + { + // if this range is multi cell select the range other + // wise just position the cell at this single range position + if ( isSingleCellRange() ) + // This top-leftmost cell of this Range is not in the current + // selection so just select this range + setCursor( static_cast< SCCOL >( thisRangeAddress.StartColumn ), static_cast< SCROW >( thisRangeAddress.StartRow ), xModel, false ); + else + Select(); + } + +} + +ScRange ScVbaRange::obtainRangeEvenIfRangeListIsEmpty( const ScRangeList& rCellRanges ) const +{ + // XXX It may be that using the current range list was never correct, but + // always the initial sheet range would be instead, history is unclear. + + if (!rCellRanges.empty()) + return rCellRanges.front(); + + table::CellRangeAddress aRA( lclGetRangeAddress( mxRange )); + return ScRange( aRA.StartColumn, aRA.StartRow, aRA.Sheet, aRA.EndColumn, aRA.EndRow, aRA.Sheet); +} + +uno::Reference< excel::XRange > +ScVbaRange::Rows(const uno::Any& aIndex ) +{ + if ( aIndex.hasValue() ) + { + ScCellRangesBase* pUnoRangesBase = getCellRangesBase(); + ScRange aRange( obtainRangeEvenIfRangeListIsEmpty( pUnoRangesBase->GetRangeList())); + + sal_Int32 nValue = 0; + OUString sAddress; + if( aIndex >>= nValue ) + { + aRange.aStart.SetRow( aRange.aStart.Row() + --nValue ); + aRange.aEnd.SetRow( aRange.aStart.Row() ); + } + else if ( aIndex >>= sAddress ) + { + ScAddress::Details dDetails( formula::FormulaGrammar::CONV_XL_A1, 0, 0 ); + ScRange tmpRange; + tmpRange.ParseRows( getScDocument(), sAddress, dDetails ); + SCROW nStartRow = tmpRange.aStart.Row(); + SCROW nEndRow = tmpRange.aEnd.Row(); + + aRange.aStart.SetRow( aRange.aStart.Row() + nStartRow ); + aRange.aEnd.SetRow( aRange.aStart.Row() + ( nEndRow - nStartRow )); + } + else + throw uno::RuntimeException("Illegal param" ); + + if ( aRange.aStart.Row() < 0 || aRange.aEnd.Row() < 0 ) + throw uno::RuntimeException("Internal failure, illegal param" ); + // return a normal range ( even for multi-selection + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( pUnoRangesBase->GetDocShell(), aRange ) ); + return new ScVbaRange( mxParent, mxContext, xRange, true ); + } + // Rows() - no params + if ( m_Areas->getCount() > 1 ) + return new ScVbaRange( mxParent, mxContext, mxRanges, true ); + return new ScVbaRange( mxParent, mxContext, mxRange, true ); +} + +uno::Reference< excel::XRange > +ScVbaRange::Columns(const uno::Any& aIndex ) +{ + ScCellRangesBase* pUnoRangesBase = getCellRangesBase(); + ScRange aRange( obtainRangeEvenIfRangeListIsEmpty( pUnoRangesBase->GetRangeList())); + + if ( aIndex.hasValue() ) + { + OUString sAddress; + sal_Int32 nValue = 0; + if ( aIndex >>= nValue ) + { + aRange.aStart.SetCol( aRange.aStart.Col() + static_cast< SCCOL > ( --nValue ) ); + aRange.aEnd.SetCol( aRange.aStart.Col() ); + } + + else if ( aIndex >>= sAddress ) + { + ScAddress::Details dDetails( formula::FormulaGrammar::CONV_XL_A1, 0, 0 ); + ScRange tmpRange; + tmpRange.ParseCols( getScDocument(), sAddress, dDetails ); + SCCOL nStartCol = tmpRange.aStart.Col(); + SCCOL nEndCol = tmpRange.aEnd.Col(); + + aRange.aStart.SetCol( aRange.aStart.Col() + nStartCol ); + aRange.aEnd.SetCol( aRange.aStart.Col() + ( nEndCol - nStartCol )); + } + else + throw uno::RuntimeException("Illegal param" ); + + if ( aRange.aStart.Col() < 0 || aRange.aEnd.Col() < 0 ) + throw uno::RuntimeException("Internal failure, illegal param" ); + } + // Columns() - no params + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( pUnoRangesBase->GetDocShell(), aRange ) ); + return new ScVbaRange( mxParent, mxContext, xRange, false, true ); +} + +void +ScVbaRange::setMergeCells( const uno::Any& aIsMerged ) +{ + bool bMerge = extractBoolFromAny( aIsMerged ); + + if( mxRanges.is() ) + { + sal_Int32 nCount = mxRanges->getCount(); + + // VBA does nothing (no error) if the own ranges overlap somehow + ::std::vector< table::CellRangeAddress > aList; + for( sal_Int32 nIndex = 0; nIndex < nCount; ++nIndex ) + { + uno::Reference< sheet::XCellRangeAddressable > xRangeAddr( mxRanges->getByIndex( nIndex ), uno::UNO_QUERY_THROW ); + table::CellRangeAddress aAddress = xRangeAddr->getRangeAddress(); + if (std::any_of(aList.begin(), aList.end(), + [&aAddress](const table::CellRangeAddress& rAddress) + { return ScUnoConversion::Intersects( rAddress, aAddress ); })) + return; + aList.push_back( aAddress ); + } + + // (un)merge every range after it has been extended to intersecting merged ranges from sheet + for( sal_Int32 nIndex = 0; nIndex < nCount; ++nIndex ) + { + uno::Reference< table::XCellRange > xRange( mxRanges->getByIndex( nIndex ), uno::UNO_QUERY_THROW ); + lclExpandAndMerge( xRange, bMerge ); + } + return; + } + + // otherwise, merge single range + lclExpandAndMerge( mxRange, bMerge ); +} + +uno::Any +ScVbaRange::getMergeCells() +{ + if( mxRanges.is() ) + { + sal_Int32 nCount = mxRanges->getCount(); + for( sal_Int32 nIndex = 0; nIndex < nCount; ++nIndex ) + { + uno::Reference< table::XCellRange > xRange( mxRanges->getByIndex( nIndex ), uno::UNO_QUERY_THROW ); + util::TriState eMerged = lclGetMergedState( xRange ); + /* Excel always returns NULL, if one range of the range list is + partly or completely merged. Even if all ranges are completely + merged, the return value is still NULL. */ + if( eMerged != util::TriState_NO ) + return aNULL(); + } + // no range is merged anyhow, return false + return uno::Any( false ); + } + + // otherwise, check single range + switch( lclGetMergedState( mxRange ) ) + { + case util::TriState_YES: return uno::Any( true ); + case util::TriState_NO: return uno::Any( false ); + default: return aNULL(); + } +} + +void +ScVbaRange::Copy(const ::uno::Any& Destination) +{ + if ( Destination.hasValue() ) + { + // TODO copy with multiple selections should work here too + if ( m_Areas->getCount() > 1 ) + throw uno::RuntimeException("That command cannot be used on multiple selections" ); + uno::Reference< excel::XRange > xRange( Destination, uno::UNO_QUERY_THROW ); + uno::Any aRange = xRange->getCellRange(); + uno::Reference< table::XCellRange > xCellRange; + aRange >>= xCellRange; + uno::Reference< sheet::XSheetCellRange > xSheetCellRange(xCellRange, ::uno::UNO_QUERY_THROW); + uno::Reference< sheet::XSpreadsheet > xSheet = xSheetCellRange->getSpreadsheet(); + uno::Reference< table::XCellRange > xDest( xSheet, uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XCellRangeMovement > xMover( xSheet, uno::UNO_QUERY_THROW); + uno::Reference< sheet::XCellAddressable > xDestination( xDest->getCellByPosition( + xRange->getColumn()-1,xRange->getRow()-1), uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XCellRangeAddressable > xSource( mxRange, uno::UNO_QUERY); + xMover->copyRange( xDestination->getCellAddress(), xSource->getRangeAddress() ); + if ( ScVbaRange* pRange = getImplementation( xRange ) ) + pRange->fireChangeEvent(); + } + else + { + Select(); + excel::implnCopy(getUnoModel()); + } +} + +void +ScVbaRange::Cut(const ::uno::Any& Destination) +{ + if ( m_Areas->getCount() > 1 ) + throw uno::RuntimeException("That command cannot be used on multiple selections" ); + if (Destination.hasValue()) + { + uno::Reference< excel::XRange > xRange( Destination, uno::UNO_QUERY_THROW ); + uno::Reference< table::XCellRange > xCellRange( xRange->getCellRange(), uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XSheetCellRange > xSheetCellRange(xCellRange, ::uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XSpreadsheet > xSheet = xSheetCellRange->getSpreadsheet(); + uno::Reference< table::XCellRange > xDest( xSheet, uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XCellRangeMovement > xMover( xSheet, uno::UNO_QUERY_THROW); + uno::Reference< sheet::XCellAddressable > xDestination( xDest->getCellByPosition( + xRange->getColumn()-1,xRange->getRow()-1), uno::UNO_QUERY); + uno::Reference< sheet::XCellRangeAddressable > xSource( mxRange, uno::UNO_QUERY); + xMover->moveRange( xDestination->getCellAddress(), xSource->getRangeAddress() ); + } + else + { + uno::Reference< frame::XModel > xModel = getModelFromRange( mxRange ); + Select(); + excel::implnCut( xModel ); + } +} + +void +ScVbaRange::setNumberFormat( const uno::Any& aFormat ) +{ + OUString sFormat; + aFormat >>= sFormat; + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->setNumberFormat( aFormat ); + } + return; + } + NumFormatHelper numFormat( mxRange ); + numFormat.setNumberFormat( sFormat ); +} + +uno::Any +ScVbaRange::getNumberFormat() +{ + + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + uno::Any aResult = aNULL(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + // if the numberformat of one area is different to another + // return null + if ( index > 1 ) + if ( aResult != xRange->getNumberFormat() ) + return aNULL(); + aResult = xRange->getNumberFormat(); + if ( aNULL() == aResult ) + return aNULL(); + } + return aResult; + } + NumFormatHelper numFormat( mxRange ); + OUString sFormat = numFormat.getNumberFormatString(); + if ( !sFormat.isEmpty() ) + return uno::Any( sFormat ); + return aNULL(); +} + +uno::Reference< excel::XRange > +ScVbaRange::Resize( const uno::Any &RowSize, const uno::Any &ColumnSize ) +{ + tools::Long nRowSize = 0, nColumnSize = 0; + bool bIsRowChanged = ( RowSize >>= nRowSize ), bIsColumnChanged = ( ColumnSize >>= nColumnSize ); + uno::Reference< table::XColumnRowRange > xColumnRowRange(mxRange, ::uno::UNO_QUERY_THROW); + uno::Reference< sheet::XSheetCellRange > xSheetRange(mxRange, ::uno::UNO_QUERY_THROW); + uno::Reference< sheet::XSheetCellCursor > xCursor( xSheetRange->getSpreadsheet()->createCursorByRange(xSheetRange), ::uno::UNO_SET_THROW ); + + if( !bIsRowChanged ) + nRowSize = xColumnRowRange->getRows()->getCount(); + if( !bIsColumnChanged ) + nColumnSize = xColumnRowRange->getColumns()->getCount(); + + xCursor->collapseToSize( nColumnSize, nRowSize ); + uno::Reference< sheet::XCellRangeAddressable > xCellRangeAddressable(xCursor, ::uno::UNO_QUERY_THROW ); + uno::Reference< table::XCellRange > xRange( xSheetRange->getSpreadsheet(), ::uno::UNO_QUERY_THROW ); + const table::CellRangeAddress aRA( xCellRangeAddressable->getRangeAddress()); + return new ScVbaRange( mxParent, mxContext, xRange->getCellRangeByPosition( aRA.StartColumn, aRA.StartRow, aRA.EndColumn, aRA.EndRow)); +} + +void +ScVbaRange::setWrapText( const uno::Any& aIsWrapped ) +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->setWrapText( aIsWrapped ); + } + return; + } + + uno::Reference< beans::XPropertySet > xProps(mxRange, ::uno::UNO_QUERY_THROW ); + bool bIsWrapped = extractBoolFromAny( aIsWrapped ); + xProps->setPropertyValue( "IsTextWrapped", uno::Any( bIsWrapped ) ); +} + +uno::Any +ScVbaRange::getWrapText() +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + uno::Any aResult; + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + if ( index > 1 ) + if ( aResult != xRange->getWrapText() ) + return aNULL(); + aResult = xRange->getWrapText(); + } + return aResult; + } + + SfxItemSet* pDataSet = getCurrentDataSet(); + + SfxItemState eState = pDataSet->GetItemState( ATTR_LINEBREAK); + if ( eState == SfxItemState::DONTCARE ) + return aNULL(); + + uno::Reference< beans::XPropertySet > xProps(mxRange, ::uno::UNO_QUERY_THROW ); + uno::Any aValue = xProps->getPropertyValue( "IsTextWrapped" ); + return aValue; +} + +uno::Reference< excel::XInterior > ScVbaRange::Interior( ) +{ + uno::Reference< beans::XPropertySet > xProps( mxRange, uno::UNO_QUERY_THROW ); + return new ScVbaInterior ( this, mxContext, xProps, &getScDocument() ); +} +uno::Reference< excel::XRange > +ScVbaRange::Range( const uno::Any &Cell1, const uno::Any &Cell2 ) +{ + return Range( Cell1, Cell2, false ); +} +uno::Reference< excel::XRange > +ScVbaRange::Range( const uno::Any &Cell1, const uno::Any &Cell2, bool bForceUseInpuRangeTab ) + +{ + uno::Reference< table::XCellRange > xCellRange = mxRange; + + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< container::XIndexAccess > xIndex( mxRanges, uno::UNO_QUERY_THROW ); + xCellRange.set( xIndex->getByIndex( 0 ), uno::UNO_QUERY_THROW ); + } + else + xCellRange.set( mxRange ); + + RangeHelper thisRange( xCellRange ); + uno::Reference< table::XCellRange > xRanges = thisRange.getCellRangeFromSheet(); + uno::Reference< sheet::XCellRangeAddressable > xAddressable( xRanges, uno::UNO_QUERY_THROW ); + + uno::Reference< table::XCellRange > xReferrer = + xRanges->getCellRangeByPosition( getColumn()-1, getRow()-1, + xAddressable->getRangeAddress().EndColumn, + xAddressable->getRangeAddress().EndRow ); + // xAddressable now for this range + xAddressable.set( xReferrer, uno::UNO_QUERY_THROW ); + + if( !Cell1.hasValue() ) + throw uno::RuntimeException( "Invalid Argument" ); + + table::CellRangeAddress parentRangeAddress = xAddressable->getRangeAddress(); + + ScRange aRange; + // Cell1 defined only + if ( !Cell2.hasValue() ) + { + OUString sName; + Cell1 >>= sName; + RangeHelper referRange( xReferrer ); + table::CellRangeAddress referAddress = referRange.getCellRangeAddressable()->getRangeAddress(); + return getRangeForName( mxContext, sName, getScDocShell(), referAddress ); + + } + else + { + table::CellRangeAddress cell1, cell2; + cell1 = getCellRangeAddressForVBARange( Cell1, getScDocShell() ); + // Cell1 & Cell2 defined + // Excel seems to combine the range as the range defined by + // the combination of Cell1 & Cell2 + + cell2 = getCellRangeAddressForVBARange( Cell2, getScDocShell() ); + + table::CellRangeAddress resultAddress; + resultAddress.StartColumn = ( cell1.StartColumn < cell2.StartColumn ) ? cell1.StartColumn : cell2.StartColumn; + resultAddress.StartRow = ( cell1.StartRow < cell2.StartRow ) ? cell1.StartRow : cell2.StartRow; + resultAddress.EndColumn = std::max( cell1.EndColumn, cell2.EndColumn ); + resultAddress.EndRow = std::max( cell1.EndRow, cell2.EndRow ); + if ( bForceUseInpuRangeTab ) + { + // this is a call from Application.Range( x,y ) + // it's possible for x or y to specify a different sheet from + // the current or active on ( but they must be the same ) + if ( cell1.Sheet != cell2.Sheet ) + throw uno::RuntimeException(); + parentRangeAddress.Sheet = cell1.Sheet; + } + else + { + // this is not a call from Application.Range( x,y ) + // if a different sheet from this range is specified it's + // an error + if ( parentRangeAddress.Sheet != cell1.Sheet + || parentRangeAddress.Sheet != cell2.Sheet + ) + throw uno::RuntimeException(); + + } + ScUnoConversion::FillScRange( aRange, resultAddress ); + } + ScRange parentAddress; + ScUnoConversion::FillScRange( parentAddress, parentRangeAddress); + if ( aRange.aStart.Col() >= 0 && aRange.aStart.Row() >= 0 && aRange.aEnd.Col() >= 0 && aRange.aEnd.Row() >= 0 ) + { + sal_Int32 nStartX = parentAddress.aStart.Col() + aRange.aStart.Col(); + sal_Int32 nStartY = parentAddress.aStart.Row() + aRange.aStart.Row(); + sal_Int32 nEndX = parentAddress.aStart.Col() + aRange.aEnd.Col(); + sal_Int32 nEndY = parentAddress.aStart.Row() + aRange.aEnd.Row(); + + if ( nStartX <= nEndX && nEndX <= parentAddress.aEnd.Col() && + nStartY <= nEndY && nEndY <= parentAddress.aEnd.Row() ) + { + ScRange aNew( static_cast<SCCOL>(nStartX), static_cast<SCROW>(nStartY), parentAddress.aStart.Tab(), + static_cast<SCCOL>(nEndX), static_cast<SCROW>(nEndY), parentAddress.aEnd.Tab() ); + xCellRange = new ScCellRangeObj( getScDocShell(), aNew ); + } + } + + return new ScVbaRange( mxParent, mxContext, xCellRange ); + +} + +// Allow access to underlying openoffice uno api ( useful for debugging +// with openoffice basic ) +uno::Any SAL_CALL ScVbaRange::getCellRange( ) +{ + uno::Any aAny; + if ( mxRanges.is() ) + aAny <<= mxRanges; + else if ( mxRange.is() ) + aAny <<= mxRange; + return aAny; +} + +uno::Any ScVbaRange::getCellRange( const uno::Reference< excel::XRange >& rxRange ) +{ + if( ScVbaRange* pVbaRange = getImplementation( rxRange ) ) + return pVbaRange->getCellRange(); + throw uno::RuntimeException(); +} + +static InsertDeleteFlags getPasteFlags (sal_Int32 Paste) +{ + InsertDeleteFlags nFlags = InsertDeleteFlags::NONE; + switch (Paste) { + case excel::XlPasteType::xlPasteComments: + nFlags = InsertDeleteFlags::NOTE;break; + case excel::XlPasteType::xlPasteFormats: + nFlags = InsertDeleteFlags::ATTRIB;break; + case excel::XlPasteType::xlPasteFormulas: + nFlags = InsertDeleteFlags::FORMULA;break; + case excel::XlPasteType::xlPasteFormulasAndNumberFormats : + case excel::XlPasteType::xlPasteValues: + nFlags = ( InsertDeleteFlags::VALUE | InsertDeleteFlags::DATETIME | InsertDeleteFlags::STRING | InsertDeleteFlags::SPECIAL_BOOLEAN ); break; + case excel::XlPasteType::xlPasteValuesAndNumberFormats: + nFlags = InsertDeleteFlags::VALUE | InsertDeleteFlags::ATTRIB; break; + case excel::XlPasteType::xlPasteColumnWidths: + case excel::XlPasteType::xlPasteValidation: + nFlags = InsertDeleteFlags::NONE;break; + case excel::XlPasteType::xlPasteAll: + case excel::XlPasteType::xlPasteAllExceptBorders: + default: + nFlags = InsertDeleteFlags::ALL;break; + } + return nFlags; +} + +static ScPasteFunc +getPasteFormulaBits( sal_Int32 Operation) +{ + ScPasteFunc nFormulaBits = ScPasteFunc::NONE; + switch (Operation) + { + case excel::XlPasteSpecialOperation::xlPasteSpecialOperationAdd: + nFormulaBits = ScPasteFunc::ADD; break; + case excel::XlPasteSpecialOperation::xlPasteSpecialOperationSubtract: + nFormulaBits = ScPasteFunc::SUB;break; + case excel::XlPasteSpecialOperation::xlPasteSpecialOperationMultiply: + nFormulaBits = ScPasteFunc::MUL;break; + case excel::XlPasteSpecialOperation::xlPasteSpecialOperationDivide: + nFormulaBits = ScPasteFunc::DIV;break; + + case excel::XlPasteSpecialOperation::xlPasteSpecialOperationNone: + default: + nFormulaBits = ScPasteFunc::NONE; break; + } + + return nFormulaBits; +} +void SAL_CALL +ScVbaRange::PasteSpecial( const uno::Any& Paste, const uno::Any& Operation, const uno::Any& SkipBlanks, const uno::Any& Transpose ) +{ + if ( m_Areas->getCount() > 1 ) + throw uno::RuntimeException("That command cannot be used on multiple selections" ); + ScDocShell* pShell = getScDocShell(); + + if (!pShell) + throw uno::RuntimeException("That command cannot be used with no ScDocShell" ); + + uno::Reference< frame::XModel > xModel(pShell->GetModel(), uno::UNO_SET_THROW); + uno::Reference< view::XSelectionSupplier > xSelection( xModel->getCurrentController(), uno::UNO_QUERY_THROW ); + // select this range + xSelection->select( uno::Any( mxRange ) ); + // set up defaults + sal_Int32 nPaste = excel::XlPasteType::xlPasteAll; + sal_Int32 nOperation = excel::XlPasteSpecialOperation::xlPasteSpecialOperationNone; + bool bTranspose = false; + bool bSkipBlanks = false; + + if ( Paste.hasValue() ) + Paste >>= nPaste; + if ( Operation.hasValue() ) + Operation >>= nOperation; + if ( SkipBlanks.hasValue() ) + SkipBlanks >>= bSkipBlanks; + if ( Transpose.hasValue() ) + Transpose >>= bTranspose; + + InsertDeleteFlags nFlags = getPasteFlags(nPaste); + ScPasteFunc nFormulaBits = getPasteFormulaBits(nOperation); + + excel::implnPasteSpecial(xModel, nFlags, nFormulaBits, bSkipBlanks, bTranspose); +} + +uno::Reference< excel::XRange > +ScVbaRange::getEntireColumnOrRow( bool bColumn ) +{ + ScCellRangesBase* pUnoRangesBase = getCellRangesBase(); + // copy the range list + ScRangeList aCellRanges = pUnoRangesBase->GetRangeList(); + ScDocument& rDoc = getScDocument(); + + for ( size_t i = 0, nRanges = aCellRanges.size(); i < nRanges; ++i ) + { + ScRange & rRange = aCellRanges[ i ]; + if ( bColumn ) + { + rRange.aStart.SetRow( 0 ); + rRange.aEnd.SetRow( rDoc.MaxRow() ); + } + else + { + rRange.aStart.SetCol( 0 ); + rRange.aEnd.SetCol( rDoc.MaxCol() ); + } + } + if ( aCellRanges.size() > 1 ) // Multi-Area + { + uno::Reference< sheet::XSheetCellRangeContainer > xRanges( new ScCellRangesObj( pUnoRangesBase->GetDocShell(), aCellRanges ) ); + + return new ScVbaRange( mxParent, mxContext, xRanges, !bColumn, bColumn ); + } + const ScRange aRange( obtainRangeEvenIfRangeListIsEmpty( aCellRanges)); + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( pUnoRangesBase->GetDocShell(), aRange)); + return new ScVbaRange( mxParent, mxContext, xRange, !bColumn, bColumn ); +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::getEntireRow() +{ + return getEntireColumnOrRow(false); +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::getEntireColumn() +{ + return getEntireColumnOrRow(true); +} + +uno::Reference< excel::XComment > SAL_CALL +ScVbaRange::AddComment( const uno::Any& Text ) +{ + // if there is already a comment in the top-left cell then throw + if( getComment().is() ) + throw uno::RuntimeException(); + + // workaround: Excel allows to create empty comment, Calc does not + OUString aNoteText; + if( Text.hasValue() && !(Text >>= aNoteText) ) + throw uno::RuntimeException(); + if( aNoteText.isEmpty() ) + aNoteText = " "; + + // try to create a new annotation + table::CellRangeAddress aRangePos = lclGetRangeAddress( mxRange ); + table::CellAddress aNotePos( aRangePos.Sheet, aRangePos.StartColumn, aRangePos.StartRow ); + uno::Reference< sheet::XSheetCellRange > xCellRange( mxRange, uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XSheetAnnotationsSupplier > xAnnosSupp( xCellRange->getSpreadsheet(), uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XSheetAnnotations > xAnnos( xAnnosSupp->getAnnotations(), uno::UNO_SET_THROW ); + xAnnos->insertNew( aNotePos, aNoteText ); + return new ScVbaComment( this, mxContext, getUnoModel(), mxRange ); +} + +uno::Reference< excel::XComment > SAL_CALL +ScVbaRange::getComment() +{ + // intentional behavior to return a null object if no + // comment defined + uno::Reference< excel::XComment > xComment( new ScVbaComment( this, mxContext, getUnoModel(), mxRange ) ); + if ( xComment->Text( uno::Any(), uno::Any(), uno::Any() ).isEmpty() ) + return nullptr; + return xComment; + +} + +/// @throws uno::RuntimeException +static uno::Reference< beans::XPropertySet > +getRowOrColumnProps( const uno::Reference< table::XCellRange >& xCellRange, bool bRows ) +{ + uno::Reference< table::XColumnRowRange > xColRow( xCellRange, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProps; + if ( bRows ) + xProps.set( xColRow->getRows(), uno::UNO_QUERY_THROW ); + else + xProps.set( xColRow->getColumns(), uno::UNO_QUERY_THROW ); + return xProps; +} + +uno::Any SAL_CALL +ScVbaRange::getHidden() +{ + // if multi-area result is the result of the + // first area + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(sal_Int32(1)), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange->getHidden(); + } + bool bIsVisible = false; + try + { + uno::Reference< beans::XPropertySet > xProps = getRowOrColumnProps( mxRange, mbIsRows ); + if ( !( xProps->getPropertyValue( ISVISIBLE ) >>= bIsVisible ) ) + throw uno::RuntimeException("Failed to get IsVisible property" ); + } + catch( const uno::Exception& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + nullptr, anyEx ); + } + return uno::Any( !bIsVisible ); +} + +void SAL_CALL +ScVbaRange::setHidden( const uno::Any& _hidden ) +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->setHidden( _hidden ); + } + return; + } + + bool bHidden = extractBoolFromAny( _hidden ); + try + { + uno::Reference< beans::XPropertySet > xProps = getRowOrColumnProps( mxRange, mbIsRows ); + xProps->setPropertyValue( ISVISIBLE, uno::Any( !bHidden ) ); + } + catch( const uno::Exception& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + nullptr, anyEx ); + } +} + +sal_Bool SAL_CALL +ScVbaRange::Replace( const OUString& What, const OUString& Replacement, const uno::Any& LookAt, const uno::Any& SearchOrder, const uno::Any& MatchCase, const uno::Any& MatchByte, const uno::Any& SearchFormat, const uno::Any& ReplaceFormat ) +{ + if ( m_Areas->getCount() > 1 ) + { + for ( sal_Int32 index = 1; index <= m_Areas->getCount(); ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( index ), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->Replace( What, Replacement, LookAt, SearchOrder, MatchCase, MatchByte, SearchFormat, ReplaceFormat ); + } + return true; // seems to return true always ( or at least I haven't found the trick of + } + + // sanity check required params + if ( What.isEmpty() ) + throw uno::RuntimeException("Range::Replace, missing params" ); + // #TODO #FIXME SearchFormat & ReplacesFormat are not processed + // What do we do about MatchByte... we don't seem to support that + const SvxSearchItem& globalSearchOptions = ScGlobal::GetSearchItem(); + SvxSearchItem newOptions( globalSearchOptions ); + + uno::Reference< util::XReplaceable > xReplace( mxRange, uno::UNO_QUERY ); + if ( xReplace.is() ) + { + uno::Reference< util::XReplaceDescriptor > xDescriptor = + xReplace->createReplaceDescriptor(); + + xDescriptor->setSearchString(What); + xDescriptor->setPropertyValue(SC_UNO_SRCHWILDCARD, uno::Any(true)); + xDescriptor->setPropertyValue(SC_UNO_SRCHWCESCCHAR, uno::Any(sal_Int32('~'))); + xDescriptor->setReplaceString( Replacement); + if ( LookAt.hasValue() ) + { + // sets SearchWords ( true is Cell match ) + sal_Int16 nLook = ::comphelper::getINT16( LookAt ); + bool bSearchWords = false; + if ( nLook == excel::XlLookAt::xlPart ) + bSearchWords = false; + else if ( nLook == excel::XlLookAt::xlWhole ) + bSearchWords = true; + else + throw uno::RuntimeException("Range::Replace, illegal value for LookAt" ); + // set global search props ( affects the find dialog + // and of course the defaults for this method + newOptions.SetWordOnly( bSearchWords ); + xDescriptor->setPropertyValue( SC_UNO_SRCHWORDS, uno::Any( bSearchWords ) ); + } + // sets SearchByRow ( true for Rows ) + if ( SearchOrder.hasValue() ) + { + sal_Int16 nSearchOrder = ::comphelper::getINT16( SearchOrder ); + bool bSearchByRow = false; + if ( nSearchOrder == excel::XlSearchOrder::xlByColumns ) + bSearchByRow = false; + else if ( nSearchOrder == excel::XlSearchOrder::xlByRows ) + bSearchByRow = true; + else + throw uno::RuntimeException("Range::Replace, illegal value for SearchOrder" ); + + newOptions.SetRowDirection( bSearchByRow ); + xDescriptor->setPropertyValue( SC_UNO_SRCHBYROW, uno::Any( bSearchByRow ) ); + } + if ( MatchCase.hasValue() ) + { + bool bMatchCase = false; + + // SearchCaseSensitive + MatchCase >>= bMatchCase; + xDescriptor->setPropertyValue( SC_UNO_SRCHCASE, uno::Any( bMatchCase ) ); + } + + ScGlobal::SetSearchItem( newOptions ); + // ignore MatchByte for the moment, it's not supported in + // OOo.org afaik + + uno::Reference< container::XIndexAccess > xIndexAccess = xReplace->findAll( xDescriptor ); + xReplace->replaceAll( xDescriptor ); + if ( xIndexAccess.is() && xIndexAccess->getCount() > 0 ) + { + for ( sal_Int32 i = 0; i < xIndexAccess->getCount(); ++i ) + { + uno::Reference< table::XCellRange > xCellRange( xIndexAccess->getByIndex( i ), uno::UNO_QUERY ); + if ( xCellRange.is() ) + { + uno::Reference< excel::XRange > xRange( new ScVbaRange( mxParent, mxContext, xCellRange ) ); + uno::Reference< container::XEnumerationAccess > xEnumAccess( xRange, uno::UNO_QUERY_THROW ); + uno::Reference< container::XEnumeration > xEnum = xEnumAccess->createEnumeration(); + while ( xEnum->hasMoreElements() ) + { + uno::Reference< excel::XRange > xNextRange( xEnum->nextElement(), uno::UNO_QUERY_THROW ); + ScVbaRange* pRange = dynamic_cast< ScVbaRange * > ( xNextRange.get() ); + if ( pRange ) + pRange->fireChangeEvent(); + } + } + } + } + } + return true; // always +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::Find( const uno::Any& What, const uno::Any& After, const uno::Any& LookIn, const uno::Any& LookAt, const uno::Any& SearchOrder, const uno::Any& SearchDirection, const uno::Any& MatchCase, const uno::Any& /*MatchByte*/, const uno::Any& /*SearchFormat*/ ) +{ + // return a Range object that represents the first cell where that information is found. + OUString sWhat; + sal_Int32 nWhat = 0; + double fWhat = 0.0; + + // string. + if( What >>= sWhat ) + {} + else if( What >>= nWhat ) + { + sWhat = OUString::number( nWhat ); + } + else if( What >>= fWhat ) + { + sWhat = OUString::number( fWhat ); + } + else + throw uno::RuntimeException("Range::Find, missing search-for-what param" ); + + const SvxSearchItem& globalSearchOptions = ScGlobal::GetSearchItem(); + SvxSearchItem newOptions( globalSearchOptions ); + + uno::Reference< util::XSearchable > xSearch( mxRange, uno::UNO_QUERY ); + if( xSearch.is() ) + { + uno::Reference< util::XSearchDescriptor > xDescriptor = xSearch->createSearchDescriptor(); + xDescriptor->setSearchString(sWhat); + xDescriptor->setPropertyValue(SC_UNO_SRCHWILDCARD, uno::Any(true)); + xDescriptor->setPropertyValue(SC_UNO_SRCHWCESCCHAR, uno::Any(sal_Int32('~'))); + + uno::Reference< excel::XRange > xAfterRange; + uno::Reference< table::XCellRange > xStartCell; + if( After >>= xAfterRange ) + { + // After must be a single cell in the range + if( xAfterRange->getCount() > 1 ) + throw uno::RuntimeException("After must be a single cell." ); + uno::Reference< excel::XRange > xCell( Cells( uno::Any( xAfterRange->getRow() ), uno::Any( xAfterRange->getColumn() ) ), uno::UNO_SET_THROW ); + xStartCell.set( xAfterRange->getCellRange(), uno::UNO_QUERY_THROW ); + } + + // LookIn + if( LookIn.hasValue() ) + { + sal_Int32 nLookIn = 0; + if( LookIn >>= nLookIn ) + { + SvxSearchCellType nSearchType; + switch( nLookIn ) + { + case excel::XlFindLookIn::xlComments : + nSearchType = SvxSearchCellType::NOTE; // Notes + break; + case excel::XlFindLookIn::xlFormulas : + nSearchType = SvxSearchCellType::FORMULA; + break; + case excel::XlFindLookIn::xlValues : + nSearchType = SvxSearchCellType::VALUE; + break; + default: + throw uno::RuntimeException("Range::Find, illegal value for LookIn." ); + } + newOptions.SetCellType( nSearchType ); + xDescriptor->setPropertyValue( "SearchType", uno::Any( static_cast<sal_uInt16>(nSearchType) ) ); + } + } + + // LookAt + if ( LookAt.hasValue() ) + { + sal_Int16 nLookAt = ::comphelper::getINT16( LookAt ); + bool bSearchWords = false; + if ( nLookAt == excel::XlLookAt::xlPart ) + bSearchWords = false; + else if ( nLookAt == excel::XlLookAt::xlWhole ) + bSearchWords = true; + else + throw uno::RuntimeException("Range::Find, illegal value for LookAt" ); + newOptions.SetWordOnly( bSearchWords ); + xDescriptor->setPropertyValue( SC_UNO_SRCHWORDS, uno::Any( bSearchWords ) ); + } + + // SearchOrder + if ( SearchOrder.hasValue() ) + { + sal_Int16 nSearchOrder = ::comphelper::getINT16( SearchOrder ); + bool bSearchByRow = false; + if ( nSearchOrder == excel::XlSearchOrder::xlByColumns ) + bSearchByRow = false; + else if ( nSearchOrder == excel::XlSearchOrder::xlByRows ) + bSearchByRow = true; + else + throw uno::RuntimeException("Range::Find, illegal value for SearchOrder" ); + + newOptions.SetRowDirection( bSearchByRow ); + xDescriptor->setPropertyValue( SC_UNO_SRCHBYROW, uno::Any( bSearchByRow ) ); + } + + // SearchDirection + if ( SearchDirection.hasValue() ) + { + sal_Int32 nSearchDirection = 0; + if( SearchDirection >>= nSearchDirection ) + { + bool bSearchBackwards = false; + if ( nSearchDirection == excel::XlSearchDirection::xlNext ) + bSearchBackwards = false; + else if( nSearchDirection == excel::XlSearchDirection::xlPrevious ) + bSearchBackwards = true; + else + throw uno::RuntimeException("Range::Find, illegal value for SearchDirection" ); + newOptions.SetBackward( bSearchBackwards ); + xDescriptor->setPropertyValue( "SearchBackwards", uno::Any( bSearchBackwards ) ); + } + } + + // MatchCase + bool bMatchCase = false; + if ( MatchCase.hasValue() ) + { + // SearchCaseSensitive + if( !( MatchCase >>= bMatchCase ) ) + throw uno::RuntimeException("Range::Find illegal value for MatchCase" ); + } + xDescriptor->setPropertyValue( SC_UNO_SRCHCASE, uno::Any( bMatchCase ) ); + + // MatchByte + // SearchFormat + // ignore + + ScGlobal::SetSearchItem( newOptions ); + + uno::Reference< uno::XInterface > xInterface = xStartCell.is() ? xSearch->findNext( xStartCell, xDescriptor) : xSearch->findFirst( xDescriptor ); + uno::Reference< table::XCellRange > xCellRange( xInterface, uno::UNO_QUERY ); + // if we are searching from a starting cell and failed to find a match + // then try from the beginning + if ( !xCellRange.is() && xStartCell.is() ) + { + xInterface = xSearch->findFirst( xDescriptor ); + xCellRange.set( xInterface, uno::UNO_QUERY ); + } + if ( xCellRange.is() ) + { + uno::Reference< excel::XRange > xResultRange = new ScVbaRange( mxParent, mxContext, xCellRange ); + if( xResultRange.is() ) + { + return xResultRange; + } + } + + } + + return uno::Reference< excel::XRange >(); +} + +static uno::Reference< table::XCellRange > processKey( const uno::Any& Key, const uno::Reference< uno::XComponentContext >& xContext, ScDocShell* pDocSh ) +{ + uno::Reference< excel::XRange > xKeyRange; + if ( Key.getValueType() == cppu::UnoType<excel::XRange>::get() ) + { + xKeyRange.set( Key, uno::UNO_QUERY_THROW ); + } + else if ( Key.getValueType() == ::cppu::UnoType<OUString>::get() ) + + { + OUString sRangeName = ::comphelper::getString( Key ); + table::CellRangeAddress aRefAddr; + if ( !pDocSh ) + throw uno::RuntimeException("Range::Sort no docshell to calculate key param" ); + xKeyRange = getRangeForName( xContext, sRangeName, pDocSh, aRefAddr ); + } + else + throw uno::RuntimeException("Range::Sort illegal type value for key param" ); + uno::Reference< table::XCellRange > xKey; + xKey.set( xKeyRange->getCellRange(), uno::UNO_QUERY_THROW ); + return xKey; +} + +// helper method for Sort +/// @throws uno::RuntimeException +static sal_Int32 findSortPropertyIndex( const uno::Sequence< beans::PropertyValue >& props, +const OUString& sPropName ) +{ + const beans::PropertyValue* pProp = std::find_if(props.begin(), props.end(), + [&sPropName](const beans::PropertyValue& rProp) { return rProp.Name == sPropName; }); + + if ( pProp == props.end() ) + throw uno::RuntimeException("Range::Sort unknown sort property" ); + return static_cast<sal_Int32>(std::distance(props.begin(), pProp)); +} + +// helper method for Sort +/// @throws uno::RuntimeException +static void updateTableSortField( const uno::Reference< table::XCellRange >& xParentRange, + const uno::Reference< table::XCellRange >& xColRowKey, sal_Int16 nOrder, + table::TableSortField& aTableField, bool bIsSortColumn, bool bMatchCase ) +{ + RangeHelper parentRange( xParentRange ); + RangeHelper colRowRange( xColRowKey ); + + table::CellRangeAddress parentRangeAddress = parentRange.getCellRangeAddressable()->getRangeAddress(); + + table::CellRangeAddress colRowKeyAddress = colRowRange.getCellRangeAddressable()->getRangeAddress(); + + // make sure that upper left point of key range is within the + // parent range + if ( + ( bIsSortColumn || colRowKeyAddress.StartColumn < parentRangeAddress.StartColumn || + colRowKeyAddress.StartColumn > parentRangeAddress.EndColumn ) + && + ( !bIsSortColumn || colRowKeyAddress.StartRow < parentRangeAddress.StartRow || + colRowKeyAddress.StartRow > parentRangeAddress.EndRow ) + ) + throw uno::RuntimeException("Illegal Key param" ); + + //determine col/row index + if ( bIsSortColumn ) + aTableField.Field = colRowKeyAddress.StartRow - parentRangeAddress.StartRow; + else + aTableField.Field = colRowKeyAddress.StartColumn - parentRangeAddress.StartColumn; + aTableField.IsCaseSensitive = bMatchCase; + + if ( nOrder == excel::XlSortOrder::xlAscending ) + aTableField.IsAscending = true; + else + aTableField.IsAscending = false; + + +} + +void SAL_CALL +ScVbaRange::Sort( const uno::Any& Key1, const uno::Any& Order1, const uno::Any& Key2, const uno::Any& /*Type*/, const uno::Any& Order2, const uno::Any& Key3, const uno::Any& Order3, const uno::Any& Header, const uno::Any& OrderCustom, const uno::Any& MatchCase, const uno::Any& Orientation, const uno::Any& SortMethod, const uno::Any& DataOption1, const uno::Any& DataOption2, const uno::Any& DataOption3 ) +{ + // #TODO# #FIXME# can we do something with Type + if ( m_Areas->getCount() > 1 ) + throw uno::RuntimeException("That command cannot be used on multiple selections" ); + + sal_Int16 nDataOption1 = excel::XlSortDataOption::xlSortNormal; + sal_Int16 nDataOption2 = excel::XlSortDataOption::xlSortNormal; + sal_Int16 nDataOption3 = excel::XlSortDataOption::xlSortNormal; + + ScDocument& rDoc = getScDocument(); + + uno::Reference< table::XCellRange > xRangeCurrent; + if (isSingleCellRange()) + { + // Expand to CurrentRegion + uno::Reference< excel::XRange > xCurrent( CurrentRegion()); + if (xCurrent.is()) + { + const ScVbaRange* pRange = getImplementation( xCurrent ); + if (pRange) + xRangeCurrent = pRange->mxRange; + } + } + if (!xRangeCurrent.is()) + xRangeCurrent = mxRange; + RangeHelper thisRange( xRangeCurrent ); + table::CellRangeAddress thisRangeAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + + ScSortParam aSortParam; + SCTAB nTab = thisRangeAddress.Sheet; + rDoc.GetSortParam( aSortParam, nTab ); + + if ( DataOption1.hasValue() ) + DataOption1 >>= nDataOption1; + if ( DataOption2.hasValue() ) + DataOption2 >>= nDataOption2; + if ( DataOption3.hasValue() ) + DataOption3 >>= nDataOption3; + + // 1) #TODO #FIXME need to process DataOption[1..3] not used currently + // 2) #TODO #FIXME need to refactor this ( below ) into an IsSingleCell() method + uno::Reference< table::XColumnRowRange > xColumnRowRange(xRangeCurrent, uno::UNO_QUERY_THROW ); + + // set up defaults + + sal_Int16 nOrder1 = aSortParam.maKeyState[0].bAscending ? excel::XlSortOrder::xlAscending : excel::XlSortOrder::xlDescending; + sal_Int16 nOrder2 = aSortParam.maKeyState[1].bAscending ? excel::XlSortOrder::xlAscending : excel::XlSortOrder::xlDescending; + sal_Int16 nOrder3 = aSortParam.maKeyState[2].bAscending ? excel::XlSortOrder::xlAscending : excel::XlSortOrder::xlDescending; + + sal_Int16 nCustom = aSortParam.nUserIndex; + sal_Int16 nSortMethod = excel::XlSortMethod::xlPinYin; + bool bMatchCase = aSortParam.bCaseSens; + + // seems to work opposite to expected, see below + sal_Int16 nOrientation = aSortParam.bByRow ? excel::XlSortOrientation::xlSortColumns : excel::XlSortOrientation::xlSortRows; + + if ( Orientation.hasValue() ) + { + // Documentation says xlSortRows is default but that doesn't appear to be + // the case. Also it appears that xlSortColumns is the default which + // strangely enough sorts by Row + nOrientation = ::comphelper::getINT16( Orientation ); + // persist new option to be next calls default + if ( nOrientation == excel::XlSortOrientation::xlSortRows ) + aSortParam.bByRow = false; + else + aSortParam.bByRow = true; + + } + + bool bIsSortColumns=false; // sort by row + + if ( nOrientation == excel::XlSortOrientation::xlSortRows ) + bIsSortColumns = true; + sal_Int16 nHeader = aSortParam.nCompatHeader; + bool bContainsHeader = false; + + if ( Header.hasValue() ) + { + nHeader = ::comphelper::getINT16( Header ); + aSortParam.nCompatHeader = nHeader; + } + + if ( nHeader == excel::XlYesNoGuess::xlGuess ) + { + bool bHasColHeader = rDoc.HasColHeader( static_cast< SCCOL >( thisRangeAddress.StartColumn ), static_cast< SCROW >( thisRangeAddress.StartRow ), static_cast< SCCOL >( thisRangeAddress.EndColumn ), static_cast< SCROW >( thisRangeAddress.EndRow ), static_cast< SCTAB >( thisRangeAddress.Sheet )); + bool bHasRowHeader = rDoc.HasRowHeader( static_cast< SCCOL >( thisRangeAddress.StartColumn ), static_cast< SCROW >( thisRangeAddress.StartRow ), static_cast< SCCOL >( thisRangeAddress.EndColumn ), static_cast< SCROW >( thisRangeAddress.EndRow ), static_cast< SCTAB >( thisRangeAddress.Sheet ) ); + if ( bHasColHeader || bHasRowHeader ) + nHeader = excel::XlYesNoGuess::xlYes; + else + nHeader = excel::XlYesNoGuess::xlNo; + aSortParam.nCompatHeader = nHeader; + } + + if ( nHeader == excel::XlYesNoGuess::xlYes ) + bContainsHeader = true; + + if ( SortMethod.hasValue() ) + { + nSortMethod = ::comphelper::getINT16( SortMethod ); + } + + if ( OrderCustom.hasValue() ) + { + OrderCustom >>= nCustom; + --nCustom; // 0-based in OOo + aSortParam.nUserIndex = nCustom; + } + + if ( MatchCase.hasValue() ) + { + MatchCase >>= bMatchCase; + aSortParam.bCaseSens = bMatchCase; + } + + if ( Order1.hasValue() ) + { + nOrder1 = ::comphelper::getINT16(Order1); + if ( nOrder1 == excel::XlSortOrder::xlAscending ) + aSortParam.maKeyState[0].bAscending = true; + else + aSortParam.maKeyState[0].bAscending = false; + + } + if ( Order2.hasValue() ) + { + nOrder2 = ::comphelper::getINT16(Order2); + if ( nOrder2 == excel::XlSortOrder::xlAscending ) + aSortParam.maKeyState[1].bAscending = true; + else + aSortParam.maKeyState[1].bAscending = false; + } + if ( Order3.hasValue() ) + { + nOrder3 = ::comphelper::getINT16(Order3); + if ( nOrder3 == excel::XlSortOrder::xlAscending ) + aSortParam.maKeyState[2].bAscending = true; + else + aSortParam.maKeyState[2].bAscending = false; + } + + uno::Reference< table::XCellRange > xKey1; + uno::Reference< table::XCellRange > xKey2; + uno::Reference< table::XCellRange > xKey3; + ScDocShell* pDocShell = getScDocShell(); + xKey1 = processKey( Key1, mxContext, pDocShell ); + if ( !xKey1.is() ) + throw uno::RuntimeException("Range::Sort needs a key1 param" ); + + if ( Key2.hasValue() ) + xKey2 = processKey( Key2, mxContext, pDocShell ); + if ( Key3.hasValue() ) + xKey3 = processKey( Key3, mxContext, pDocShell ); + + uno::Reference< util::XSortable > xSort( xRangeCurrent, uno::UNO_QUERY_THROW ); + uno::Sequence< beans::PropertyValue > sortDescriptor = xSort->createSortDescriptor(); + auto psortDescriptor = sortDescriptor.getArray(); + sal_Int32 nTableSortFieldIndex = findSortPropertyIndex( sortDescriptor, "SortFields" ); + + uno::Sequence< table::TableSortField > sTableFields(1); + sal_Int32 nTableIndex = 0; + updateTableSortField( xRangeCurrent, xKey1, nOrder1, sTableFields.getArray()[ nTableIndex++ ], bIsSortColumns, bMatchCase ); + + if ( xKey2.is() ) + { + sTableFields.realloc( sTableFields.getLength() + 1 ); + updateTableSortField( xRangeCurrent, xKey2, nOrder2, sTableFields.getArray()[ nTableIndex++ ], bIsSortColumns, bMatchCase ); + } + if ( xKey3.is() ) + { + sTableFields.realloc( sTableFields.getLength() + 1 ); + updateTableSortField( xRangeCurrent, xKey3, nOrder3, sTableFields.getArray()[ nTableIndex++ ], bIsSortColumns, bMatchCase ); + } + psortDescriptor[ nTableSortFieldIndex ].Value <<= sTableFields; + + sal_Int32 nIndex = findSortPropertyIndex( sortDescriptor, "IsSortColumns" ); + psortDescriptor[ nIndex ].Value <<= bIsSortColumns; + + nIndex = findSortPropertyIndex( sortDescriptor, "ContainsHeader" ); + psortDescriptor[ nIndex ].Value <<= bContainsHeader; + + rDoc.SetSortParam( aSortParam, nTab ); + xSort->sort( sortDescriptor ); + + // #FIXME #TODO + // The SortMethod param is not processed ( not sure what its all about, need to + (void)nSortMethod; +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::End( ::sal_Int32 Direction ) +{ + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( getArea( 0 ), uno::UNO_SET_THROW ); + return xRange->End( Direction ); + } + + // #FIXME #TODO + // euch! found my orig implementation sucked, so + // trying this even sucker one (really need to use/expose code in + // around ScTabView::MoveCursorArea(), that's the bit that calculates + // where the cursor should go) + // Main problem with this method is the ultra hacky attempt to preserve + // the ActiveCell, there should be no need to go to these extremes + + // Save ActiveSheet/ActiveCell pos (to restore later) + uno::Any aDft; + uno::Reference< excel::XApplication > xApplication( Application(), uno::UNO_QUERY_THROW ); + uno::Reference< excel::XWorksheet > sActiveSheet = xApplication->getActiveSheet(); + OUString sActiveCell = xApplication->getActiveCell()->Address(aDft, aDft, aDft, aDft, aDft ); + + // position current cell upper left of this range + Cells( uno::Any( sal_Int32(1) ), uno::Any( sal_Int32(1) ) )->Select(); + + uno::Reference< frame::XModel > xModel = getModelFromRange( mxRange ); + + SfxViewFrame* pViewFrame = excel::getViewFrame( xModel ); + if ( pViewFrame ) + { + SfxAllItemSet aArgs( SfxGetpApp()->GetPool() ); + // Hoping this will make sure this slot is called + // synchronously + SfxBoolItem sfxAsync( SID_ASYNCHRON, false ); + aArgs.Put( sfxAsync, sfxAsync.Which() ); + SfxDispatcher* pDispatcher = pViewFrame->GetDispatcher(); + + sal_uInt16 nSID = 0; + + switch( Direction ) + { + case excel::XlDirection::xlDown: + nSID = SID_CURSORBLKDOWN; + break; + case excel::XlDirection::xlUp: + nSID = SID_CURSORBLKUP; + break; + case excel::XlDirection::xlToLeft: + nSID = SID_CURSORBLKLEFT; + break; + case excel::XlDirection::xlToRight: + nSID = SID_CURSORBLKRIGHT; + break; + default: + throw uno::RuntimeException(": Invalid ColumnIndex" ); + } + if ( pDispatcher ) + { + pDispatcher->Execute( nSID, SfxCallMode::SYNCHRON, aArgs ); + } + } + + // result is the ActiveCell + OUString sMoved = xApplication->getActiveCell()->Address(aDft, aDft, aDft, aDft, aDft ); + + uno::Any aVoid; + uno::Reference< excel::XRange > resultCell; + resultCell.set( xApplication->getActiveSheet()->Range( uno::Any( sMoved ), aVoid ), uno::UNO_SET_THROW ); + + // restore old ActiveCell + uno::Reference< excel::XRange > xOldActiveCell( sActiveSheet->Range( uno::Any( sActiveCell ), aVoid ), uno::UNO_SET_THROW ); + xOldActiveCell->Select(); + + + // return result + return resultCell; +} + +bool +ScVbaRange::isSingleCellRange() const +{ + uno::Reference< sheet::XCellRangeAddressable > xAddressable( mxRange, uno::UNO_QUERY ); + if ( xAddressable.is() ) + { + table::CellRangeAddress aRangeAddr = xAddressable->getRangeAddress(); + return ( aRangeAddr.EndColumn == aRangeAddr.StartColumn && aRangeAddr.EndRow == aRangeAddr.StartRow ); + } + return false; +} + +uno::Reference< excel::XCharacters > SAL_CALL +ScVbaRange::characters( const uno::Any& Start, const uno::Any& Length ) +{ + if ( !isSingleCellRange() ) + throw uno::RuntimeException("Can't create Characters property for multicell range " ); + uno::Reference< text::XSimpleText > xSimple(mxRange->getCellByPosition(0,0) , uno::UNO_QUERY_THROW ); + ScDocument& rDoc = getDocumentFromRange(mxRange); + + ScVbaPalette aPalette( rDoc.GetDocumentShell() ); + return new ScVbaCharacters( this, mxContext, aPalette, xSimple, Start, Length ); +} + + void SAL_CALL +ScVbaRange::Delete( const uno::Any& Shift ) +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->Delete( Shift ); + } + return; + } + sheet::CellDeleteMode mode = sheet::CellDeleteMode_NONE ; + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + if ( Shift.hasValue() ) + { + sal_Int32 nShift = 0; + Shift >>= nShift; + switch ( nShift ) + { + case excel::XlDeleteShiftDirection::xlShiftUp: + mode = sheet::CellDeleteMode_UP; + break; + case excel::XlDeleteShiftDirection::xlShiftToLeft: + mode = sheet::CellDeleteMode_LEFT; + break; + default: + throw uno::RuntimeException("Illegal parameter " ); + } + } + else + { + ScDocument& rDoc = getScDocument(); + bool bFullRow = ( thisAddress.StartColumn == 0 && thisAddress.EndColumn == rDoc.MaxCol() ); + sal_Int32 nCols = thisAddress.EndColumn - thisAddress.StartColumn; + sal_Int32 nRows = thisAddress.EndRow - thisAddress.StartRow; + if ( mbIsRows || bFullRow || ( nCols >= nRows ) ) + mode = sheet::CellDeleteMode_UP; + else + mode = sheet::CellDeleteMode_LEFT; + } + uno::Reference< sheet::XCellRangeMovement > xCellRangeMove( thisRange.getSpreadSheet(), uno::UNO_QUERY_THROW ); + xCellRangeMove->removeRange( thisAddress, mode ); + +} + +//XElementAccess +sal_Bool SAL_CALL +ScVbaRange::hasElements() +{ + uno::Reference< table::XColumnRowRange > xColumnRowRange(mxRange, uno::UNO_QUERY ); + if ( xColumnRowRange.is() ) + if ( xColumnRowRange->getRows()->getCount() || + xColumnRowRange->getColumns()->getCount() ) + return true; + return false; +} + +// XEnumerationAccess +uno::Reference< container::XEnumeration > SAL_CALL +ScVbaRange::createEnumeration() +{ + if ( mbIsColumns || mbIsRows ) + { + uno::Reference< table::XColumnRowRange > xColumnRowRange(mxRange, uno::UNO_QUERY ); + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32(1) ), uno::Any() ), uno::UNO_QUERY_THROW ); + sal_Int32 nElems = 0; + if ( mbIsColumns ) + nElems = xColumnRowRange->getColumns()->getCount(); + else + nElems = xColumnRowRange->getRows()->getCount(); + return new ColumnsRowEnumeration( xRange, nElems ); + + } + return new CellsEnumeration( mxParent, mxContext, m_Areas ); +} + +OUString SAL_CALL +ScVbaRange::getDefaultMethodName( ) +{ + return "Item"; +} + +// returns calc internal col. width ( in points ) +double +ScVbaRange::getCalcColWidth(const table::CellRangeAddress& rAddress) +{ + ScDocument& rDoc = getScDocument(); + sal_uInt16 nWidth = rDoc.GetOriginalWidth( static_cast< SCCOL >( rAddress.StartColumn ), static_cast< SCTAB >( rAddress.Sheet ) ); + double nPoints = lcl_TwipsToPoints( nWidth ); + nPoints = lcl_Round2DecPlaces( nPoints ); + return nPoints; +} + +double +ScVbaRange::getCalcRowHeight(const table::CellRangeAddress& rAddress) +{ + ScDocument& rDoc = getDocumentFromRange( mxRange ); + sal_uInt16 nWidth = rDoc.GetOriginalHeight( rAddress.StartRow, rAddress.Sheet ); + double nPoints = lcl_TwipsToPoints( nWidth ); + nPoints = lcl_Round2DecPlaces( nPoints ); + return nPoints; +} + +// return Char Width in points +static double getDefaultCharWidth( ScDocShell* pDocShell ) +{ + ScDocument& rDoc = pDocShell->GetDocument(); + OutputDevice* pRefDevice = rDoc.GetRefDevice(); + ScPatternAttr* pAttr = rDoc.GetDefPattern(); + vcl::Font aDefFont; + pAttr->fillFontOnly(aDefFont, pRefDevice); + pRefDevice->SetFont(aDefFont); + tools::Long nCharWidth = pRefDevice->GetTextWidth( OUString( '0' ) ); // 1/100th mm + return o3tl::convert<double>(nCharWidth, o3tl::Length::mm100, o3tl::Length::pt); +} + +uno::Any SAL_CALL +ScVbaRange::getColumnWidth() +{ + sal_Int32 nLen = m_Areas->getCount(); + if ( nLen > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32(1) ), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange->getColumnWidth(); + } + + double nColWidth = 0; + ScDocShell* pShell = getScDocShell(); + if ( pShell ) + { + double defaultCharWidth = getDefaultCharWidth( pShell ); + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + sal_Int32 nStartCol = thisAddress.StartColumn; + sal_Int32 nEndCol = thisAddress.EndColumn; + sal_uInt16 nColTwips = 0; + for( sal_Int32 nCol = nStartCol ; nCol <= nEndCol; ++nCol ) + { + thisAddress.StartColumn = nCol; + sal_uInt16 nCurTwips = pShell->GetDocument().GetOriginalWidth( static_cast< SCCOL >( thisAddress.StartColumn ), static_cast< SCTAB >( thisAddress.Sheet ) ); + if ( nCol == nStartCol ) + nColTwips = nCurTwips; + if ( nColTwips != nCurTwips ) + return aNULL(); + } + nColWidth = lcl_TwipsToPoints( nColTwips ); + if ( nColWidth != 0.0 ) + nColWidth = ( nColWidth / defaultCharWidth ) - fExtraWidth; + } + nColWidth = lcl_Round2DecPlaces( nColWidth ); + return uno::Any( nColWidth ); +} + +void SAL_CALL +ScVbaRange::setColumnWidth( const uno::Any& _columnwidth ) +{ + sal_Int32 nLen = m_Areas->getCount(); + if ( nLen > 1 ) + { + for ( sal_Int32 index = 1; index != nLen; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( index ), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->setColumnWidth( _columnwidth ); + } + return; + } + double nColWidth = 0; + _columnwidth >>= nColWidth; + nColWidth = lcl_Round2DecPlaces( nColWidth ); + ScDocShell* pDocShell = getScDocShell(); + if ( !pDocShell ) + return; + + if ( nColWidth != 0.0 ) + nColWidth = ( nColWidth + fExtraWidth ) * getDefaultCharWidth( pDocShell ); + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + sal_uInt16 nTwips = lcl_pointsToTwips( nColWidth ); + + std::vector<sc::ColRowSpan> aColArr(1, sc::ColRowSpan(thisAddress.StartColumn, thisAddress.EndColumn)); + // #163561# use mode SC_SIZE_DIRECT: hide for width 0, show for other values + pDocShell->GetDocFunc().SetWidthOrHeight( + true, aColArr, thisAddress.Sheet, SC_SIZE_DIRECT, nTwips, true, true); +} + +uno::Any SAL_CALL +ScVbaRange::getWidth() +{ + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32(1) ), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange->getWidth(); + } + uno::Reference< table::XColumnRowRange > xColRowRange( mxRange, uno::UNO_QUERY_THROW ); + uno::Reference< container::XIndexAccess > xIndexAccess( xColRowRange->getColumns(), uno::UNO_QUERY_THROW ); + sal_Int32 nElems = xIndexAccess->getCount(); + double nWidth = 0; + for ( sal_Int32 index=0; index<nElems; ++index ) + { + uno::Reference< sheet::XCellRangeAddressable > xAddressable( xIndexAccess->getByIndex( index ), uno::UNO_QUERY_THROW ); + double nTmpWidth = getCalcColWidth( xAddressable->getRangeAddress() ); + nWidth += nTmpWidth; + } + return uno::Any( nWidth ); +} + +uno::Any SAL_CALL +ScVbaRange::Areas( const uno::Any& item) +{ + if ( !item.hasValue() ) + return uno::Any( m_Areas ); + return m_Areas->Item( item, uno::Any() ); +} + +uno::Reference< excel::XRange > +ScVbaRange::getArea( sal_Int32 nIndex ) +{ + if ( !m_Areas.is() ) + throw uno::RuntimeException("No areas available" ); + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( ++nIndex ), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange; +} + +uno::Any +ScVbaRange::Borders( const uno::Any& item ) +{ + if ( !item.hasValue() ) + return uno::Any( getBorders() ); + return getBorders()->Item( item, uno::Any() ); +} + +uno::Any SAL_CALL +ScVbaRange::BorderAround( const css::uno::Any& LineStyle, const css::uno::Any& Weight, + const css::uno::Any& ColorIndex, const css::uno::Any& Color ) +{ + sal_Int32 nCount = getBorders()->getCount(); + + for( sal_Int32 i = 0; i < nCount; i++ ) + { + const sal_Int32 nLineType = supportedIndexTable[i]; + switch( nLineType ) + { + case excel::XlBordersIndex::xlEdgeLeft: + case excel::XlBordersIndex::xlEdgeTop: + case excel::XlBordersIndex::xlEdgeBottom: + case excel::XlBordersIndex::xlEdgeRight: + { + uno::Reference< excel::XBorder > xBorder( m_Borders->Item( uno::Any( nLineType ), uno::Any() ), uno::UNO_QUERY_THROW ); + if( LineStyle.hasValue() ) + { + xBorder->setLineStyle( LineStyle ); + } + if( Weight.hasValue() ) + { + xBorder->setWeight( Weight ); + } + if( ColorIndex.hasValue() ) + { + xBorder->setColorIndex( ColorIndex ); + } + if( Color.hasValue() ) + { + xBorder->setColor( Color ); + } + break; + } + case excel::XlBordersIndex::xlInsideVertical: + case excel::XlBordersIndex::xlInsideHorizontal: + case excel::XlBordersIndex::xlDiagonalDown: + case excel::XlBordersIndex::xlDiagonalUp: + break; + default: + return uno::Any( false ); + } + } + return uno::Any( true ); +} + +uno::Any SAL_CALL +ScVbaRange::getRowHeight() +{ + sal_Int32 nLen = m_Areas->getCount(); + if ( nLen > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32(1) ), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange->getRowHeight(); + } + + // if any row's RowHeight in the + // range is different from any other, then return NULL + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + + sal_Int32 nStartRow = thisAddress.StartRow; + sal_Int32 nEndRow = thisAddress.EndRow; + sal_uInt16 nRowTwips = 0; + // #TODO probably possible to use the SfxItemSet (and see if + // SfxItemState::DONTCARE is set) to improve performance +// #CHECKME looks like this is general behaviour not just row Range specific +// if ( mbIsRows ) + ScDocShell* pShell = getScDocShell(); + if ( pShell ) + { + for ( sal_Int32 nRow = nStartRow ; nRow <= nEndRow; ++nRow ) + { + thisAddress.StartRow = nRow; + sal_uInt16 nCurTwips = pShell->GetDocument().GetOriginalHeight( thisAddress.StartRow, thisAddress.Sheet ); + if ( nRow == nStartRow ) + nRowTwips = nCurTwips; + if ( nRowTwips != nCurTwips ) + return aNULL(); + } + } + double nHeight = lcl_Round2DecPlaces( lcl_TwipsToPoints( nRowTwips ) ); + return uno::Any( nHeight ); +} + +void SAL_CALL +ScVbaRange::setRowHeight( const uno::Any& _rowheight) +{ + sal_Int32 nLen = m_Areas->getCount(); + if ( nLen > 1 ) + { + for ( sal_Int32 index = 1; index != nLen; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( index ), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->setRowHeight( _rowheight ); + } + return; + } + double nHeight = 0; // Incoming height is in points + _rowheight >>= nHeight; + nHeight = lcl_Round2DecPlaces( nHeight ); + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + sal_uInt16 nTwips = lcl_pointsToTwips( nHeight ); + + ScDocShell* pDocShell = getDocShellFromRange( mxRange ); + std::vector<sc::ColRowSpan> aRowArr(1, sc::ColRowSpan(thisAddress.StartRow, thisAddress.EndRow)); + pDocShell->GetDocFunc().SetWidthOrHeight( + false, aRowArr, thisAddress.Sheet, SC_SIZE_ORIGINAL, nTwips, true, true); +} + +uno::Any SAL_CALL +ScVbaRange::getPageBreak() +{ + sal_Int32 nPageBreak = excel::XlPageBreak::xlPageBreakNone; + ScDocShell* pShell = getDocShellFromRange( mxRange ); + if ( pShell ) + { + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + bool bColumn = false; + + if (thisAddress.StartRow==0) + bColumn = true; + + uno::Reference< frame::XModel > xModel = pShell->GetModel(); + if ( xModel.is() ) + { + ScDocument& rDoc = getDocumentFromRange( mxRange ); + + ScBreakType nBreak = ScBreakType::NONE; + if ( !bColumn ) + nBreak = rDoc.HasRowBreak(thisAddress.StartRow, thisAddress.Sheet); + else + nBreak = rDoc.HasColBreak(thisAddress.StartColumn, thisAddress.Sheet); + + if (nBreak & ScBreakType::Page) + nPageBreak = excel::XlPageBreak::xlPageBreakAutomatic; + + if (nBreak & ScBreakType::Manual) + nPageBreak = excel::XlPageBreak::xlPageBreakManual; + } + } + + return uno::Any( nPageBreak ); +} + +void SAL_CALL +ScVbaRange::setPageBreak( const uno::Any& _pagebreak) +{ + sal_Int32 nPageBreak = 0; + _pagebreak >>= nPageBreak; + + ScDocShell* pShell = getDocShellFromRange( mxRange ); + if ( !pShell ) + return; + + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + if ((thisAddress.StartColumn==0) && (thisAddress.StartRow==0)) + return; + bool bColumn = false; + + if (thisAddress.StartRow==0) + bColumn = true; + + ScAddress aAddr( static_cast<SCCOL>(thisAddress.StartColumn), thisAddress.StartRow, thisAddress.Sheet ); + uno::Reference< frame::XModel > xModel = pShell->GetModel(); + if ( xModel.is() ) + { + ScTabViewShell* pViewShell = excel::getBestViewShell( xModel ); + if ( nPageBreak == excel::XlPageBreak::xlPageBreakManual ) + pViewShell->InsertPageBreak( bColumn, true, &aAddr); + else if ( nPageBreak == excel::XlPageBreak::xlPageBreakNone ) + pViewShell->DeletePageBreak( bColumn, true, &aAddr); + } +} + +uno::Any SAL_CALL +ScVbaRange::getHeight() +{ + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32(1) ), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange->getHeight(); + } + + uno::Reference< table::XColumnRowRange > xColRowRange( mxRange, uno::UNO_QUERY_THROW ); + uno::Reference< container::XIndexAccess > xIndexAccess( xColRowRange->getRows(), uno::UNO_QUERY_THROW ); + sal_Int32 nElems = xIndexAccess->getCount(); + double nHeight = 0; + for ( sal_Int32 index=0; index<nElems; ++index ) + { + uno::Reference< sheet::XCellRangeAddressable > xAddressable( xIndexAccess->getByIndex( index ), uno::UNO_QUERY_THROW ); + nHeight += getCalcRowHeight(xAddressable->getRangeAddress() ); + } + return uno::Any( nHeight ); +} + +awt::Point +ScVbaRange::getPosition() const +{ + awt::Point aPoint; + uno::Reference< beans::XPropertySet > xProps; + if ( mxRange.is() ) + xProps.set( mxRange, uno::UNO_QUERY_THROW ); + else + xProps.set( mxRanges, uno::UNO_QUERY_THROW ); + xProps->getPropertyValue( "Position" ) >>= aPoint; + return aPoint; +} +uno::Any SAL_CALL +ScVbaRange::getLeft() +{ + // helperapi returns the first ranges left ( and top below ) + if ( m_Areas->getCount() > 1 ) + return getArea( 0 )->getLeft(); + awt::Point aPoint = getPosition(); + return uno::Any(o3tl::convert<double>(aPoint.X, o3tl::Length::mm100, o3tl::Length::pt)); +} + +uno::Any SAL_CALL +ScVbaRange::getTop() +{ + // helperapi returns the first ranges top + if ( m_Areas->getCount() > 1 ) + return getArea( 0 )->getTop(); + awt::Point aPoint= getPosition(); + return uno::Any(o3tl::convert<double>(aPoint.Y, o3tl::Length::mm100, o3tl::Length::pt)); +} + +static uno::Reference< sheet::XCellRangeReferrer > getNamedRange( const uno::Reference< uno::XInterface >& xIf, const uno::Reference< table::XCellRange >& thisRange ) +{ + uno::Reference< beans::XPropertySet > xProps( xIf, uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xNameAccess( xProps->getPropertyValue( "NamedRanges" ), uno::UNO_QUERY_THROW ); + + const uno::Sequence< OUString > sNames = xNameAccess->getElementNames(); +// uno::Reference< table::XCellRange > thisRange( getCellRange(), uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XCellRangeReferrer > xNamedRange; + for ( const auto& rName : sNames ) + { + uno::Reference< sheet::XCellRangeReferrer > xName( xNameAccess->getByName( rName ), uno::UNO_QUERY ); + if ( xName.is() ) + { + if ( thisRange == xName->getReferredCells() ) + { + xNamedRange = xName; + break; + } + } + } + return xNamedRange; +} + +uno::Reference< excel::XName > +ScVbaRange::getName() +{ + uno::Reference< beans::XPropertySet > xProps( getUnoModel(), uno::UNO_QUERY ); + uno::Reference< table::XCellRange > thisRange( getCellRange(), uno::UNO_QUERY_THROW ); + // Application range + uno::Reference< sheet::XCellRangeReferrer > xNamedRange = getNamedRange( xProps, thisRange ); + + if ( !xNamedRange.is() ) + { + // not in application range then assume it might be in + // sheet namedranges + RangeHelper aRange( thisRange ); + uno::Reference< sheet::XSpreadsheet > xSheet = aRange.getSpreadSheet(); + xProps.set( xSheet, uno::UNO_QUERY ); + // impl here + xNamedRange = getNamedRange( xProps, thisRange ); + } + if ( xProps.is() && xNamedRange.is() ) + { + uno::Reference< sheet::XNamedRanges > xNamedRanges( xProps, uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XNamedRange > xName( xNamedRange, uno::UNO_QUERY_THROW ); + return new ScVbaName( mxParent, mxContext, xName, xNamedRanges, getUnoModel() ); + } + return uno::Reference< excel::XName >(); +} + +uno::Reference< excel::XWorksheet > +ScVbaRange::getWorksheet() +{ + // #TODO #FIXME parent should always be set up ( currently that's not + // the case ) + uno::Reference< excel::XWorksheet > xSheet( getParent(), uno::UNO_QUERY ); + if ( !xSheet.is() ) + { + uno::Reference< table::XCellRange > xRange = mxRange; + + if ( mxRanges.is() ) // assign xRange to first range + { + uno::Reference< container::XIndexAccess > xIndex( mxRanges, uno::UNO_QUERY_THROW ); + xRange.set( xIndex->getByIndex( 0 ), uno::UNO_QUERY_THROW ); + } + ScDocShell* pDocShell = getDocShellFromRange(xRange); + RangeHelper rHelper(xRange); + // parent should be Thisworkbook + xSheet.set( new ScVbaWorksheet( uno::Reference< XHelperInterface >(), mxContext,rHelper.getSpreadSheet(),pDocShell->GetModel()) ); + } + return xSheet; +} + +// #TODO remove this ugly application processing +// Process an application Range request e.g. 'Range("a1,b2,a4:b6") +uno::Reference< excel::XRange > +ScVbaRange::ApplicationRange( const uno::Reference< uno::XComponentContext >& xContext, const css::uno::Any &Cell1, const css::uno::Any &Cell2 ) +{ + // Although the documentation seems clear that Range without a + // qualifier then it's a shortcut for ActiveSheet.Range + // however, similarly Application.Range is apparently also a + // shortcut for ActiveSheet.Range + // The is however a subtle behavioural difference I've come across + // wrt to named ranges. + // If a named range "test" exists { Sheet1!$A1 } and the active sheet + // is Sheet2 then the following will fail + // msgbox ActiveSheet.Range("test").Address ' fails + // msgbox WorkSheets("Sheet2").Range("test").Address + // but!!! + // msgbox Range("test").Address ' works + // msgbox Application.Range("test").Address ' works + + // Single param Range + OUString sRangeName; + Cell1 >>= sRangeName; + if ( Cell1.hasValue() && !Cell2.hasValue() && !sRangeName.isEmpty() ) + { + uno::Reference< beans::XPropertySet > xPropSet( getCurrentExcelDoc(xContext), uno::UNO_QUERY_THROW ); + + uno::Reference< container::XNameAccess > xNamed( xPropSet->getPropertyValue( "NamedRanges" ), uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XCellRangeReferrer > xReferrer; + try + { + xReferrer.set ( xNamed->getByName( sRangeName ), uno::UNO_QUERY ); + } + catch( uno::Exception& /*e*/ ) + { + // do nothing + } + if ( xReferrer.is() ) + { + uno::Reference< table::XCellRange > xRange = xReferrer->getReferredCells(); + if ( xRange.is() ) + { + uno::Reference< excel::XRange > xVbRange = new ScVbaRange( excel::getUnoSheetModuleObj( xRange ), xContext, xRange ); + return xVbRange; + } + } + } + + uno::Reference<table::XCellRange> xSheetRange; + + try + { + uno::Reference<sheet::XSpreadsheetView> xView( + getCurrentExcelDoc(xContext)->getCurrentController(), uno::UNO_QUERY_THROW); + + xSheetRange.set(xView->getActiveSheet(), uno::UNO_QUERY_THROW); + } + catch (const uno::Exception&) + { + return uno::Reference<excel::XRange>(); + } + + rtl::Reference<ScVbaRange> pRange = new ScVbaRange( excel::getUnoSheetModuleObj( xSheetRange ), xContext, xSheetRange ); + return pRange->Range( Cell1, Cell2, true ); +} + +// Helper functions for AutoFilter +static ScDBData* lcl_GetDBData_Impl( ScDocShell* pDocShell, sal_Int16 nSheet ) +{ + ScDBData* pRet = nullptr; + if (pDocShell) + { + pRet = pDocShell->GetDocument().GetAnonymousDBData(nSheet); + } + return pRet; +} + +static void lcl_SelectAll( ScDocShell* pDocShell, const ScQueryParam& aParam ) +{ + if ( !pDocShell ) + return; + + ScViewData* pViewData = ScDocShell::GetViewData(); + if ( !pViewData ) + { + ScTabViewShell* pViewSh = pDocShell->GetBestViewShell( true ); + pViewData = pViewSh ? &pViewSh->GetViewData() : nullptr; + } + + if ( pViewData ) + { + pViewData->GetView()->Query( aParam, nullptr, true ); + } +} + +static ScQueryParam lcl_GetQueryParam( ScDocShell* pDocShell, sal_Int16 nSheet ) +{ + ScDBData* pDBData = lcl_GetDBData_Impl( pDocShell, nSheet ); + ScQueryParam aParam; + if (pDBData) + { + pDBData->GetQueryParam( aParam ); + } + return aParam; +} + +static void lcl_SetAllQueryForField( ScDocShell* pDocShell, SCCOLROW nField, sal_Int16 nSheet ) +{ + ScQueryParam aParam = lcl_GetQueryParam( pDocShell, nSheet ); + aParam.RemoveEntryByField(nField); + lcl_SelectAll( pDocShell, aParam ); +} + +// Modifies sCriteria, and nOp depending on the value of sCriteria +static void lcl_setTableFieldsFromCriteria( OUString& sCriteria1, const uno::Reference< beans::XPropertySet >& xDescProps, sheet::TableFilterField2& rFilterField ) +{ + // #TODO make this more efficient and cycle through + // sCriteria1 character by character to pick up <,<>,=, * etc. + // right now I am more concerned with just getting it to work right + + sCriteria1 = sCriteria1.trim(); + // table of translation of criteria text to FilterOperators + // <>searchtext - NOT_EQUAL + // =searchtext - EQUAL + // *searchtext - startwith + // <>*searchtext - doesn't startwith + // *searchtext* - contains + // <>*searchtext* - doesn't contain + // [>|>=|<=|...]searchtext for GREATER_value, GREATER_EQUAL_value etc. + if ( sCriteria1.startsWith( EQUALS ) ) + { + if ( o3tl::make_unsigned(sCriteria1.getLength()) == strlen(EQUALS) ) + rFilterField.Operator = sheet::FilterOperator2::EMPTY; + else + { + rFilterField.Operator = sheet::FilterOperator2::EQUAL; + sCriteria1 = sCriteria1.copy( strlen(EQUALS) ); + sCriteria1 = VBAToRegexp( sCriteria1 ); + // UseRegularExpressions + if ( xDescProps.is() ) + xDescProps->setPropertyValue( "UseRegularExpressions", uno::Any( true ) ); + } + + } + else if ( sCriteria1.startsWith( NOTEQUALS ) ) + { + if ( o3tl::make_unsigned(sCriteria1.getLength()) == strlen(NOTEQUALS) ) + rFilterField.Operator = sheet::FilterOperator2::NOT_EMPTY; + else + { + rFilterField.Operator = sheet::FilterOperator2::NOT_EQUAL; + sCriteria1 = sCriteria1.copy( strlen(NOTEQUALS) ); + sCriteria1 = VBAToRegexp( sCriteria1 ); + // UseRegularExpressions + if ( xDescProps.is() ) + xDescProps->setPropertyValue( "UseRegularExpressions", uno::Any( true ) ); + } + } + else if ( sCriteria1.startsWith( GREATERTHAN ) ) + { + if ( sCriteria1.startsWith( GREATERTHANEQUALS ) ) + { + sCriteria1 = sCriteria1.copy( strlen(GREATERTHANEQUALS) ); + rFilterField.Operator = sheet::FilterOperator2::GREATER_EQUAL; + } + else + { + sCriteria1 = sCriteria1.copy( strlen(GREATERTHAN) ); + rFilterField.Operator = sheet::FilterOperator2::GREATER; + } + + } + else if ( sCriteria1.startsWith( LESSTHAN ) ) + { + if ( sCriteria1.startsWith( LESSTHANEQUALS ) ) + { + sCriteria1 = sCriteria1.copy( strlen(LESSTHANEQUALS) ); + rFilterField.Operator = sheet::FilterOperator2::LESS_EQUAL; + } + else + { + sCriteria1 = sCriteria1.copy( strlen(LESSTHAN) ); + rFilterField.Operator = sheet::FilterOperator2::LESS; + } + + } + else + rFilterField.Operator = sheet::FilterOperator2::EQUAL; + + // tdf#107885 - check if criteria is numeric using locale dependent settings without group separator + // or, if the decimal separator is different from the English locale, without any locale. + sal_Int32 nParseEnd = 0; + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + double fValue = ScGlobal::getLocaleData().stringToDouble( sCriteria1, false, &eStatus, &nParseEnd ); + if ( nParseEnd == sCriteria1.getLength() && eStatus == rtl_math_ConversionStatus_Ok ) + { + rFilterField.IsNumeric = true; + rFilterField.NumericValue = fValue; + } + else if ( ScGlobal::getLocaleData().getNumDecimalSep().toChar() != '.' ) + { + eStatus = rtl_math_ConversionStatus_Ok; + fValue = ::rtl::math::stringToDouble( sCriteria1, '.', 0, &eStatus, &nParseEnd ); + if ( nParseEnd == sCriteria1.getLength() && eStatus == rtl_math_ConversionStatus_Ok ) + { + rFilterField.IsNumeric = true; + rFilterField.NumericValue = fValue; + } + } + + rFilterField.StringValue = sCriteria1; +} + +void SAL_CALL +ScVbaRange::AutoFilter( const uno::Any& aField, const uno::Any& Criteria1, const uno::Any& Operator, const uno::Any& Criteria2, const uno::Any& /*VisibleDropDown*/ ) +{ + // Is there an existing autofilter + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + sal_Int16 nSheet = thisAddress.Sheet; + ScDocShell* pShell = getScDocShell(); + bool bHasAuto = false; + uno::Reference< sheet::XDatabaseRange > xDataBaseRange = excel::GetAutoFiltRange( pShell, nSheet ); + if ( xDataBaseRange.is() ) + bHasAuto = true; + + if ( !bHasAuto ) + { + if ( m_Areas->getCount() > 1 ) + throw uno::RuntimeException( STR_ERRORMESSAGE_APPLIESTOSINGLERANGEONLY ); + + table::CellRangeAddress autoFiltAddress; + //CurrentRegion() + if ( isSingleCellRange() ) + { + uno::Reference< excel::XRange > xCurrent( CurrentRegion() ); + if ( xCurrent.is() ) + { + ScVbaRange* pRange = getImplementation( xCurrent ); + if ( pRange ) + { + if ( pRange->isSingleCellRange() ) + throw uno::RuntimeException("Can't create AutoFilter" ); + RangeHelper currentRegion( pRange->mxRange ); + autoFiltAddress = currentRegion.getCellRangeAddressable()->getRangeAddress(); + } + } + } + else // multi-cell range + { + RangeHelper multiCellRange( mxRange ); + autoFiltAddress = multiCellRange.getCellRangeAddressable()->getRangeAddress(); + // #163530# Filter box shows only entry of first row + ScDocument* pDocument = ( pShell ? &pShell->GetDocument() : nullptr ); + if ( pDocument ) + { + SCCOL nStartCol = autoFiltAddress.StartColumn; + SCROW nStartRow = autoFiltAddress.StartRow; + SCCOL nEndCol = autoFiltAddress.EndColumn; + SCROW nEndRow = autoFiltAddress.EndRow; + pDocument->GetDataArea( autoFiltAddress.Sheet, nStartCol, nStartRow, nEndCol, nEndRow, true, true ); + autoFiltAddress.StartColumn = nStartCol; + autoFiltAddress.StartRow = nStartRow; + autoFiltAddress.EndColumn = nEndCol; + autoFiltAddress.EndRow = nEndRow; + } + } + + uno::Reference< sheet::XUnnamedDatabaseRanges > xDBRanges = excel::GetUnnamedDataBaseRanges( pShell ); + if ( xDBRanges.is() ) + { + if ( !xDBRanges->hasByTable( nSheet ) ) + xDBRanges->setByTable( autoFiltAddress ); + xDataBaseRange.set( xDBRanges->getByTable(nSheet ), uno::UNO_QUERY_THROW ); + } + if ( !xDataBaseRange.is() ) + throw uno::RuntimeException("Failed to find the autofilter placeholder range" ); + + uno::Reference< beans::XPropertySet > xDBRangeProps( xDataBaseRange, uno::UNO_QUERY_THROW ); + // set autofilter + xDBRangeProps->setPropertyValue( "AutoFilter", uno::Any(true) ); + // set header (autofilter always need column headers) + uno::Reference< beans::XPropertySet > xFiltProps( xDataBaseRange->getFilterDescriptor(), uno::UNO_QUERY_THROW ); + xFiltProps->setPropertyValue( "ContainsHeader", uno::Any( true ) ); + } + + sal_Int32 nField = 0; // *IS* 1 based + sal_Int32 nOperator = excel::XlAutoFilterOperator::xlAnd; + + sheet::FilterConnection nConn = sheet::FilterConnection_AND; + double nCriteria1 = 0; + + bool bHasCritValue = Criteria1.hasValue(); + bool bCritHasNumericValue = false; // not sure if a numeric criteria is possible + if ( bHasCritValue ) + bCritHasNumericValue = ( Criteria1 >>= nCriteria1 ); + + if ( !aField.hasValue() && ( Criteria1.hasValue() || Operator.hasValue() || Criteria2.hasValue() ) ) + throw uno::RuntimeException(); + uno::Any Field( aField ); + if ( !( Field >>= nField ) ) + { + const uno::Reference< script::XTypeConverter >& xConverter = getTypeConverter( mxContext ); + try + { + Field = xConverter->convertTo( aField, cppu::UnoType<sal_Int32>::get() ); + } + catch( uno::Exception& ) + { + } + } + // Use the normal uno api, sometimes e.g. when you want to use ALL as the filter + // we can't use refresh as the uno interface doesn't have a concept of ALL + // in this case we just call the core calc functionality - + if ( Field >>= nField ) + { + uno::Reference< sheet::XSheetFilterDescriptor2 > xDesc( + xDataBaseRange->getFilterDescriptor(), uno::UNO_QUERY ); + if ( xDesc.is() ) + { + OUString sCriteria1; + bool bAcceptCriteria2 = true; + bool bAll = false; + uno::Sequence< sheet::TableFilterField2 > sTabFilts; + sheet::TableFilterField2* pTabFilts = nullptr; + uno::Reference< beans::XPropertySet > xDescProps( xDesc, uno::UNO_QUERY_THROW ); + if ( Criteria1.hasValue() ) + { + sTabFilts.realloc( 1 ); + pTabFilts = sTabFilts.getArray(); + pTabFilts[0].Operator = sheet::FilterOperator2::EQUAL;// sensible default + if ( !bCritHasNumericValue ) + { + Criteria1 >>= sCriteria1; + if ( sCriteria1.isEmpty() ) + { + uno::Sequence< OUString > aCriteria1; + Criteria1 >>= aCriteria1; + sal_uInt16 nLength = aCriteria1.getLength(); + if ( nLength ) + { + // When sequence is provided for Criteria1 don't care about Criteria2 + bAcceptCriteria2 = false; + + auto pCriteria1 = aCriteria1.getArray(); + sTabFilts.realloc( nLength ); + pTabFilts = sTabFilts.getArray(); + for ( sal_uInt16 i = 0; i < nLength; ++i ) + { + lcl_setTableFieldsFromCriteria( pCriteria1[i], xDescProps, pTabFilts[i] ); + pTabFilts[i].Connection = sheet::FilterConnection_OR; + pTabFilts[i].Field = (nField - 1); + } + } + else + bAll = true; + } + else + { + pTabFilts[0].IsNumeric = bCritHasNumericValue; + if ( bHasCritValue && !sCriteria1.isEmpty() ) + lcl_setTableFieldsFromCriteria( sCriteria1, xDescProps, pTabFilts[0] ); + else + bAll = true; + } + } + else // numeric + { + pTabFilts[0].IsNumeric = true; + pTabFilts[0].NumericValue = nCriteria1; + } + } + else // no value specified + bAll = true; + // not sure what the relationship between Criteria1 and Operator is, + // e.g. can you have an Operator without a Criteria? In LibreOffice it + if ( Operator.hasValue() && ( Operator >>= nOperator ) ) + { + // if it's a bottom/top Ten(Percent/Value) and there + // is no value specified for criteria1 set it to 10 + if ( !bCritHasNumericValue && sCriteria1.isEmpty() && ( nOperator != excel::XlAutoFilterOperator::xlOr ) && ( nOperator != excel::XlAutoFilterOperator::xlAnd ) ) + { + pTabFilts[0].IsNumeric = true; + pTabFilts[0].NumericValue = 10; + bAll = false; + } + switch ( nOperator ) + { + case excel::XlAutoFilterOperator::xlBottom10Items: + pTabFilts[0].Operator = sheet::FilterOperator2::BOTTOM_VALUES; + break; + case excel::XlAutoFilterOperator::xlBottom10Percent: + pTabFilts[0].Operator = sheet::FilterOperator2::BOTTOM_PERCENT; + break; + case excel::XlAutoFilterOperator::xlTop10Items: + pTabFilts[0].Operator = sheet::FilterOperator2::TOP_VALUES; + break; + case excel::XlAutoFilterOperator::xlTop10Percent: + pTabFilts[0].Operator = sheet::FilterOperator2::TOP_PERCENT; + break; + case excel::XlAutoFilterOperator::xlOr: + nConn = sheet::FilterConnection_OR; + break; + case excel::XlAutoFilterOperator::xlAnd: + nConn = sheet::FilterConnection_AND; + break; + default: + throw uno::RuntimeException("UnknownOption" ); + + } + + } + if ( !bAll && bAcceptCriteria2 ) + { + pTabFilts[0].Connection = sheet::FilterConnection_AND; + pTabFilts[0].Field = (nField - 1); + + uno::Sequence< OUString > aCriteria2; + if ( Criteria2.hasValue() ) // there is a Criteria2 + { + sTabFilts.realloc(2); + pTabFilts = sTabFilts.getArray(); + pTabFilts[1].Field = sTabFilts[0].Field; + pTabFilts[1].Connection = nConn; + + OUString sCriteria2; + if ( Criteria2 >>= sCriteria2 ) + { + if ( !sCriteria2.isEmpty() ) + { + uno::Reference< beans::XPropertySet > xProps; + lcl_setTableFieldsFromCriteria( sCriteria2, xProps, pTabFilts[1] ); + pTabFilts[1].IsNumeric = false; + } + } + else if ( Criteria2 >>= aCriteria2 ) + { + sal_uInt16 nLength = aCriteria2.getLength(); + if ( nLength ) + { + // For compatibility use only the last value from the sequence + lcl_setTableFieldsFromCriteria( aCriteria2.getArray()[nLength - 1], xDescProps, pTabFilts[1] ); + } + } + else // numeric + { + Criteria2 >>= pTabFilts[1].NumericValue; + pTabFilts[1].IsNumeric = true; + pTabFilts[1].Operator = sheet::FilterOperator2::EQUAL; + } + } + } + + xDesc->setFilterFields2( sTabFilts ); + if ( !bAll ) + { + xDataBaseRange->refresh(); + } + else + // was 0 based now seems to be 1 + lcl_SetAllQueryForField( pShell, nField, nSheet ); + } + } + else + { + // this is just to toggle autofilter on and off ( not to be confused with + // a VisibleDropDown option combined with a field, in that case just the + // button should be disabled ) - currently we don't support that + uno::Reference< beans::XPropertySet > xDBRangeProps( xDataBaseRange, uno::UNO_QUERY_THROW ); + if ( bHasAuto ) + { + // find the any field with the query and select all + ScQueryParam aParam = lcl_GetQueryParam( pShell, nSheet ); + for (SCSIZE i = 0; i< aParam.GetEntryCount(); ++i) + { + ScQueryEntry& rEntry = aParam.GetEntry(i); + if ( rEntry.bDoQuery ) + lcl_SetAllQueryForField( pShell, rEntry.nField, nSheet ); + } + // remove existing filters + uno::Reference< sheet::XSheetFilterDescriptor2 > xSheetFilterDescriptor( + xDataBaseRange->getFilterDescriptor(), uno::UNO_QUERY ); + if( xSheetFilterDescriptor.is() ) + xSheetFilterDescriptor->setFilterFields2( uno::Sequence< sheet::TableFilterField2 >() ); + } + xDBRangeProps->setPropertyValue( "AutoFilter", uno::Any(!bHasAuto) ); + + } +} + +void SAL_CALL +ScVbaRange::Insert( const uno::Any& Shift, const uno::Any& /*CopyOrigin*/ ) +{ + // It appears (from the web) that the undocumented CopyOrigin + // param should contain member of enum XlInsertFormatOrigin + // which can have values xlFormatFromLeftOrAbove or xlFormatFromRightOrBelow + // #TODO investigate resultant behaviour using these constants + // currently just processing Shift + + sheet::CellInsertMode mode = sheet::CellInsertMode_NONE; + if ( Shift.hasValue() ) + { + sal_Int32 nShift = 0; + Shift >>= nShift; + switch ( nShift ) + { + case excel::XlInsertShiftDirection::xlShiftToRight: + mode = sheet::CellInsertMode_RIGHT; + break; + case excel::XlInsertShiftDirection::xlShiftDown: + mode = sheet::CellInsertMode_DOWN; + break; + default: + throw uno::RuntimeException("Illegal parameter " ); + } + } + else + { + if ( getRow() >= getColumn() ) + mode = sheet::CellInsertMode_DOWN; + else + mode = sheet::CellInsertMode_RIGHT; + } + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + uno::Reference< sheet::XCellRangeMovement > xCellRangeMove( thisRange.getSpreadSheet(), uno::UNO_QUERY_THROW ); + xCellRangeMove->insertCells( thisAddress, mode ); + + // Paste from clipboard only if the clipboard content was copied via VBA, and not already pasted via VBA again. + // "Insert" behavior should not depend on random clipboard content previously copied by the user. + ScDocShell* pDocShell = getDocShellFromRange( mxRange ); + const ScTransferObj* pClipObj = pDocShell ? ScTransferObj::GetOwnClipboard(pDocShell->GetClipData()) : nullptr; + if ( pClipObj && pClipObj->GetUseInApi() ) + { + // After the insert ( this range ) actually has moved + ScRange aRange( static_cast< SCCOL >( thisAddress.StartColumn ), static_cast< SCROW >( thisAddress.StartRow ), static_cast< SCTAB >( thisAddress.Sheet ), static_cast< SCCOL >( thisAddress.EndColumn ), static_cast< SCROW >( thisAddress.EndRow ), static_cast< SCTAB >( thisAddress.Sheet ) ); + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( getDocShellFromRange( mxRange ) , aRange ) ); + uno::Reference< excel::XRange > xVbaRange( new ScVbaRange( mxParent, mxContext, xRange, mbIsRows, mbIsColumns ) ); + xVbaRange->PasteSpecial( uno::Any(), uno::Any(), uno::Any(), uno::Any() ); + } +} + +void SAL_CALL +ScVbaRange::Autofit() +{ + sal_Int32 nLen = m_Areas->getCount(); + if ( nLen > 1 ) + { + for ( sal_Int32 index = 1; index != nLen; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( index ), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->Autofit(); + } + return; + } + + // if the range is a not a row or column range autofit will + // throw an error + if ( !( mbIsColumns || mbIsRows ) ) + DebugHelper::basicexception(ERRCODE_BASIC_METHOD_FAILED, {}); + ScDocShell* pDocShell = getDocShellFromRange( mxRange ); + if ( !pDocShell ) + return; + + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + + std::vector<sc::ColRowSpan> aColArr(1, sc::ColRowSpan(thisAddress.StartColumn,thisAddress.EndColumn)); + bool bDirection = true; + if ( mbIsRows ) + { + bDirection = false; + aColArr[0].mnStart = thisAddress.StartRow; + aColArr[0].mnEnd = thisAddress.EndRow; + } + pDocShell->GetDocFunc().SetWidthOrHeight( + bDirection, aColArr, thisAddress.Sheet, SC_SIZE_OPTIMAL, 0, true, true); +} + +uno::Any SAL_CALL +ScVbaRange::Hyperlinks( const uno::Any& aIndex ) +{ + /* The range object always returns a new Hyperlinks object containing a + fixed list of existing hyperlinks in the range. + See vbahyperlinks.hxx for more details. */ + + // get the global hyperlink object of the sheet (sheet should always be the parent of a Range object) + uno::Reference< excel::XWorksheet > xWorksheet( getParent(), uno::UNO_QUERY_THROW ); + uno::Reference< excel::XHyperlinks > xSheetHlinks( xWorksheet->Hyperlinks( uno::Any() ), uno::UNO_QUERY_THROW ); + ScVbaHyperlinksRef xScSheetHlinks( dynamic_cast< ScVbaHyperlinks* >( xSheetHlinks.get() ) ); + if( !xScSheetHlinks.is() ) + throw uno::RuntimeException("Cannot obtain hyperlinks implementation object" ); + + // create a new local hyperlinks object based on the sheet hyperlinks + ScVbaHyperlinksRef xHlinks( new ScVbaHyperlinks( getParent(), mxContext, xScSheetHlinks, getScRangeList() ) ); + if( aIndex.hasValue() ) + return xHlinks->Item( aIndex, uno::Any() ); + return uno::Any( uno::Reference< excel::XHyperlinks >( xHlinks ) ); +} + +css::uno::Reference< excel::XValidation > SAL_CALL +ScVbaRange::getValidation() +{ + if ( !m_xValidation.is() ) + m_xValidation = new ScVbaValidation( this, mxContext, mxRange ); + return m_xValidation; +} + +namespace { + +/// @throws uno::RuntimeException +sal_Unicode lclGetPrefixChar( const uno::Reference< table::XCell >& rxCell ) +{ + /* TODO/FIXME: We need an apostroph-prefix property at the cell to + implement this correctly. For now, return an apostroph for every text + cell. + + TODO/FIXME: When Application.TransitionNavigKeys is supported and true, + this function needs to inspect the cell formatting and return different + prefixes according to the horizontal cell alignment. + */ + return (rxCell->getType() == table::CellContentType_TEXT) ? '\'' : 0; +} + +/// @throws uno::RuntimeException +sal_Unicode lclGetPrefixChar( const uno::Reference< table::XCellRange >& rxRange ) +{ + /* This implementation is able to handle different prefixes (needed if + Application.TransitionNavigKeys is true). The function lclGetPrefixChar + for single cells called from here may return any prefix. If that + function returns an empty prefix (NUL character) or different non-empty + prefixes for two cells, this function returns 0. + */ + sal_Unicode cCurrPrefix = 0; + table::CellRangeAddress aRangeAddr = lclGetRangeAddress( rxRange ); + sal_Int32 nEndCol = aRangeAddr.EndColumn - aRangeAddr.StartColumn; + sal_Int32 nEndRow = aRangeAddr.EndRow - aRangeAddr.StartRow; + for( sal_Int32 nRow = 0; nRow <= nEndRow; ++nRow ) + { + for( sal_Int32 nCol = 0; nCol <= nEndCol; ++nCol ) + { + uno::Reference< table::XCell > xCell( rxRange->getCellByPosition( nCol, nRow ), uno::UNO_SET_THROW ); + sal_Unicode cNewPrefix = lclGetPrefixChar( xCell ); + if( (cNewPrefix == 0) || ((cCurrPrefix != 0) && (cNewPrefix != cCurrPrefix)) ) + return 0; + cCurrPrefix = cNewPrefix; + } + } + // all cells contain the same prefix - return it + return cCurrPrefix; +} + +/// @throws uno::RuntimeException +sal_Unicode lclGetPrefixChar( const uno::Reference< sheet::XSheetCellRangeContainer >& rxRanges ) +{ + sal_Unicode cCurrPrefix = 0; + uno::Reference< container::XEnumerationAccess > xRangesEA( rxRanges, uno::UNO_QUERY_THROW ); + uno::Reference< container::XEnumeration > xRangesEnum( xRangesEA->createEnumeration(), uno::UNO_SET_THROW ); + while( xRangesEnum->hasMoreElements() ) + { + uno::Reference< table::XCellRange > xRange( xRangesEnum->nextElement(), uno::UNO_QUERY_THROW ); + sal_Unicode cNewPrefix = lclGetPrefixChar( xRange ); + if( (cNewPrefix == 0) || ((cCurrPrefix != 0) && (cNewPrefix != cCurrPrefix)) ) + return 0; + cCurrPrefix = cNewPrefix; + } + // all ranges contain the same prefix - return it + return cCurrPrefix; +} + +uno::Any lclGetPrefixVariant( sal_Unicode cPrefixChar ) +{ + return uno::Any( (cPrefixChar == 0) ? OUString() : OUString( cPrefixChar ) ); +} + +} // namespace + +uno::Any SAL_CALL ScVbaRange::getPrefixCharacter() +{ + /* (1) If Application.TransitionNavigKeys is false, this function returns + an apostroph character if the text cell begins with an apostroph + character (formula return values are not taken into account); otherwise + an empty string. + + (2) If Application.TransitionNavigKeys is true, this function returns + an apostroph character, if the cell is left-aligned; a double-quote + character, if the cell is right-aligned; a circumflex character, if the + cell is centered; a backslash character, if the cell is set to filled; + or an empty string, if nothing of the above. + + If a range or a list of ranges contains texts with leading apostroph + character as well as other cells, this function returns an empty + string. + */ + + if( mxRange.is() ) + return lclGetPrefixVariant( lclGetPrefixChar( mxRange ) ); + if( mxRanges.is() ) + return lclGetPrefixVariant( lclGetPrefixChar( mxRanges ) ); + throw uno::RuntimeException("Unexpected empty Range object" ); +} + +uno::Any ScVbaRange::getShowDetail() +{ + // #FIXME, If the specified range is in a PivotTable report + + // In MSO VBA, the specified range must be a single summary column or row in an outline. otherwise throw exception + if( m_Areas->getCount() > 1 ) + throw uno::RuntimeException("Can not get Range.ShowDetail attribute " ); + + RangeHelper helper( mxRange ); + uno::Reference< sheet::XSheetCellCursor > xSheetCellCursor = helper.getSheetCellCursor(); + xSheetCellCursor->collapseToCurrentRegion(); + uno::Reference< sheet::XCellRangeAddressable > xCellRangeAddressable(xSheetCellCursor, uno::UNO_QUERY_THROW); + table::CellRangeAddress aOutlineAddress = xCellRangeAddressable->getRangeAddress(); + + // check if the specified range is a single summary column or row. + table::CellRangeAddress thisAddress = helper.getCellRangeAddressable()->getRangeAddress(); + if( (thisAddress.StartRow != thisAddress.EndRow || thisAddress.EndRow != aOutlineAddress.EndRow ) && + (thisAddress.StartColumn != thisAddress.EndColumn || thisAddress.EndColumn != aOutlineAddress.EndColumn )) + { + throw uno::RuntimeException("Can not set Range.ShowDetail attribute" ); + } + + bool bColumn = thisAddress.StartRow != thisAddress.EndRow; + ScDocument& rDoc = getDocumentFromRange( mxRange ); + ScOutlineTable* pOutlineTable = rDoc.GetOutlineTable(static_cast<SCTAB>(thisAddress.Sheet), true); + const ScOutlineArray& rOutlineArray = bColumn ? pOutlineTable->GetColArray(): pOutlineTable->GetRowArray(); + SCCOLROW nPos = bColumn ? static_cast<SCCOLROW>(thisAddress.EndColumn-1):static_cast<SCCOLROW>(thisAddress.EndRow-1); + const ScOutlineEntry* pEntry = rOutlineArray.GetEntryByPos( 0, nPos ); + if( pEntry ) + { + const bool bShowDetail = !pEntry->IsHidden(); + return uno::Any( bShowDetail ); + } + + return aNULL(); +} + +void ScVbaRange::setShowDetail(const uno::Any& aShowDetail) +{ + // #FIXME, If the specified range is in a PivotTable report + + // In MSO VBA, the specified range must be a single summary column or row in an outline. otherwise throw exception + if( m_Areas->getCount() > 1 ) + throw uno::RuntimeException("Can not set Range.ShowDetail attribute" ); + + bool bShowDetail = extractBoolFromAny( aShowDetail ); + + RangeHelper helper( mxRange ); + uno::Reference< sheet::XSheetCellCursor > xSheetCellCursor = helper.getSheetCellCursor(); + xSheetCellCursor->collapseToCurrentRegion(); + uno::Reference< sheet::XCellRangeAddressable > xCellRangeAddressable(xSheetCellCursor, uno::UNO_QUERY_THROW); + table::CellRangeAddress aOutlineAddress = xCellRangeAddressable->getRangeAddress(); + + // check if the specified range is a single summary column or row. + table::CellRangeAddress thisAddress = helper.getCellRangeAddressable()->getRangeAddress(); + if( (thisAddress.StartRow != thisAddress.EndRow || thisAddress.EndRow != aOutlineAddress.EndRow ) && + (thisAddress.StartColumn != thisAddress.EndColumn || thisAddress.EndColumn != aOutlineAddress.EndColumn )) + { + throw uno::RuntimeException("Can not set Range.ShowDetail attribute" ); + } + + // #FIXME, seems there is a different behavior between MSO and OOo. + // In OOo, the showDetail will show all the level entries, while only show the first level entry in MSO + uno::Reference< sheet::XSheetOutline > xSheetOutline( helper.getSpreadSheet(), uno::UNO_QUERY_THROW ); + if( bShowDetail ) + xSheetOutline->showDetail( aOutlineAddress ); + else + xSheetOutline->hideDetail( aOutlineAddress ); + +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::MergeArea() +{ + uno::Reference< sheet::XSheetCellRange > xMergeShellCellRange(mxRange->getCellRangeByPosition(0,0,0,0), uno::UNO_QUERY_THROW); + uno::Reference< sheet::XSheetCellCursor > xMergeSheetCursor(xMergeShellCellRange->getSpreadsheet()->createCursorByRange( xMergeShellCellRange ), uno::UNO_SET_THROW); + if( xMergeSheetCursor.is() ) + { + xMergeSheetCursor->collapseToMergedArea(); + uno::Reference<sheet::XCellRangeAddressable> xMergeCellAddress(xMergeSheetCursor, uno::UNO_QUERY_THROW); + table::CellRangeAddress aCellAddress = xMergeCellAddress->getRangeAddress(); + if( aCellAddress.StartColumn ==0 && aCellAddress.EndColumn==0 && + aCellAddress.StartRow==0 && aCellAddress.EndRow==0) + { + return new ScVbaRange( mxParent,mxContext,mxRange ); + } + else + { + ScRange refRange( static_cast< SCCOL >( aCellAddress.StartColumn ), static_cast< SCROW >( aCellAddress.StartRow ), static_cast< SCTAB >( aCellAddress.Sheet ), + static_cast< SCCOL >( aCellAddress.EndColumn ), static_cast< SCROW >( aCellAddress.EndRow ), static_cast< SCTAB >( aCellAddress.Sheet ) ); + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( getScDocShell() , refRange ) ); + return new ScVbaRange( mxParent, mxContext,xRange ); + } + } + return new ScVbaRange( mxParent, mxContext, mxRange ); +} + +void SAL_CALL +ScVbaRange::PrintOut( const uno::Any& From, const uno::Any& To, const uno::Any& Copies, const uno::Any& Preview, const uno::Any& ActivePrinter, const uno::Any& PrintToFile, const uno::Any& Collate, const uno::Any& PrToFileName ) +{ + ScDocShell* pShell = nullptr; + + sal_Int32 nItems = m_Areas->getCount(); + uno::Sequence< table::CellRangeAddress > printAreas( nItems ); + auto printAreasRange = asNonConstRange(printAreas); + uno::Reference< sheet::XPrintAreas > xPrintAreas; + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + + RangeHelper thisRange( xRange->getCellRange() ); + table::CellRangeAddress rangeAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + if ( index == 1 ) + { + ScVbaRange* pRange = getImplementation( xRange ); + // initialise the doc shell and the printareas + pShell = getDocShellFromRange( pRange->mxRange ); + xPrintAreas.set( thisRange.getSpreadSheet(), uno::UNO_QUERY_THROW ); + } + printAreasRange[ index - 1 ] = rangeAddress; + } + if ( pShell && xPrintAreas.is() ) + { + xPrintAreas->setPrintAreas( printAreas ); + uno::Reference< frame::XModel > xModel = pShell->GetModel(); + PrintOutHelper( excel::getBestViewShell( xModel ), From, To, Copies, Preview, ActivePrinter, PrintToFile, Collate, PrToFileName, true ); + } +} + +void SAL_CALL +ScVbaRange::AutoFill( const uno::Reference< excel::XRange >& Destination, const uno::Any& Type ) +{ + uno::Reference< excel::XRange > xDest( Destination, uno::UNO_SET_THROW ); + ScVbaRange* pRange = getImplementation( xDest ); + RangeHelper destRangeHelper( pRange->mxRange ); + table::CellRangeAddress destAddress = destRangeHelper.getCellRangeAddressable()->getRangeAddress(); + + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + ScRange sourceRange; + ScRange destRange; + + ScUnoConversion::FillScRange( destRange, destAddress ); + ScUnoConversion::FillScRange( sourceRange, thisAddress ); + + FillDir eDir = FILL_TO_BOTTOM; + double fStep = 1.0; + + ScRange aRange( destRange ); + ScRange aSourceRange( destRange ); + + // default to include the number of Rows in the source range; + SCCOLROW nSourceCount = ( sourceRange.aEnd.Row() - sourceRange.aStart.Row() ) + 1; + SCCOLROW nCount = 0; + + if ( sourceRange != destRange ) + { + // Find direction of fill, vertical or horizontal + if ( sourceRange.aStart == destRange.aStart ) + { + if ( sourceRange.aEnd.Row() == destRange.aEnd.Row() ) + { + nSourceCount = ( sourceRange.aEnd.Col() - sourceRange.aStart.Col() + 1 ); + aSourceRange.aEnd.SetCol( static_cast<SCCOL>( aSourceRange.aStart.Col() + nSourceCount - 1 ) ); + eDir = FILL_TO_RIGHT; + nCount = aRange.aEnd.Col() - aSourceRange.aEnd.Col(); + } + else if ( sourceRange.aEnd.Col() == destRange.aEnd.Col() ) + { + aSourceRange.aEnd.SetRow( static_cast<SCROW>( aSourceRange.aStart.Row() + nSourceCount ) - 1 ); + nCount = aRange.aEnd.Row() - aSourceRange.aEnd.Row(); + eDir = FILL_TO_BOTTOM; + } + } + + else if ( aSourceRange.aEnd == destRange.aEnd ) + { + if ( sourceRange.aStart.Col() == destRange.aStart.Col() ) + { + aSourceRange.aStart.SetRow( static_cast<SCROW>( aSourceRange.aEnd.Row() - nSourceCount + 1 ) ); + nCount = aSourceRange.aStart.Row() - aRange.aStart.Row(); + eDir = FILL_TO_TOP; + fStep = -fStep; + } + else if ( sourceRange.aStart.Row() == destRange.aStart.Row() ) + { + nSourceCount = ( sourceRange.aEnd.Col() - sourceRange.aStart.Col() ) + 1; + aSourceRange.aStart.SetCol( static_cast<SCCOL>( aSourceRange.aEnd.Col() - nSourceCount + 1 ) ); + nCount = aSourceRange.aStart.Col() - aRange.aStart.Col(); + eDir = FILL_TO_LEFT; + fStep = -fStep; + } + } + } + + FillCmd eCmd = FILL_AUTO; + FillDateCmd eDateCmd = FILL_DAY; + + if ( Type.hasValue() ) + { + sal_Int16 nFillType = excel::XlAutoFillType::xlFillDefault; + Type >>= nFillType; + switch ( nFillType ) + { + case excel::XlAutoFillType::xlFillCopy: + eCmd = FILL_SIMPLE; + fStep = 0.0; + break; + case excel::XlAutoFillType::xlFillDays: + eCmd = FILL_DATE; + break; + case excel::XlAutoFillType::xlFillMonths: + eCmd = FILL_DATE; + eDateCmd = FILL_MONTH; + break; + case excel::XlAutoFillType::xlFillWeekdays: + eCmd = FILL_DATE; + eDateCmd = FILL_WEEKDAY; + break; + case excel::XlAutoFillType::xlFillYears: + eCmd = FILL_DATE; + eDateCmd = FILL_YEAR; + break; + case excel::XlAutoFillType::xlGrowthTrend: + eCmd = FILL_GROWTH; + break; + case excel::XlAutoFillType::xlFillFormats: + throw uno::RuntimeException("xlFillFormat not supported for AutoFill" ); + case excel::XlAutoFillType::xlFillValues: + case excel::XlAutoFillType::xlFillSeries: + case excel::XlAutoFillType::xlLinearTrend: + eCmd = FILL_LINEAR; + break; + case excel::XlAutoFillType::xlFillDefault: + default: + eCmd = FILL_AUTO; + break; + } + } + ScDocShell* pDocSh = getDocShellFromRange( mxRange ); + pDocSh->GetDocFunc().FillAuto( aSourceRange, nullptr, eDir, eCmd, eDateCmd, + nCount, fStep, MAXDOUBLE/*fEndValue*/, true, true ); +} +sal_Bool SAL_CALL +ScVbaRange::GoalSeek( const uno::Any& Goal, const uno::Reference< excel::XRange >& ChangingCell ) +{ + ScDocShell* pDocShell = getScDocShell(); + bool bRes = true; + ScVbaRange* pRange = static_cast< ScVbaRange* >( ChangingCell.get() ); + if ( pDocShell && pRange ) + { + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + RangeHelper changingCellRange( pRange->mxRange ); + table::CellRangeAddress changingCellAddr = changingCellRange.getCellRangeAddressable()->getRangeAddress(); + OUString sGoal = getAnyAsString( Goal ); + table::CellAddress thisCell( thisAddress.Sheet, thisAddress.StartColumn, thisAddress.StartRow ); + table::CellAddress changingCell( changingCellAddr.Sheet, changingCellAddr.StartColumn, changingCellAddr.StartRow ); + sheet::GoalResult res = pDocShell->GetModel()->seekGoal( thisCell, changingCell, sGoal ); + ChangingCell->setValue( uno::Any( res.Result ) ); + + // openoffice behaves differently, result is 0 if the divergence is too great + // but... if it detects 0 is the value it requires then it will use that + // e.g. divergence & result both = 0.0 does NOT mean there is an error + if ( ( res.Divergence != 0.0 ) && ( res.Result == 0.0 ) ) + bRes = false; + } + else + bRes = false; + return bRes; +} + +void +ScVbaRange::Calculate( ) +{ + getWorksheet()->Calculate(); +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::Item( const uno::Any& row, const uno::Any& column ) +{ + if ( mbIsRows || mbIsColumns ) + { + if ( column.hasValue() ) + DebugHelper::basicexception(ERRCODE_BASIC_BAD_PARAMETER, {} ); + uno::Reference< excel::XRange > xRange; + if ( mbIsColumns ) + xRange = Columns( row ); + else + xRange = Rows( row ); + return xRange; + } + return Cells( row, column ); +} + +void +ScVbaRange::AutoOutline( ) +{ + // #TODO #FIXME needs to check for summary row/col ( whatever they are ) + // not valid for multi Area Addresses + if ( m_Areas->getCount() > 1 ) + DebugHelper::basicexception(ERRCODE_BASIC_METHOD_FAILED, STR_ERRORMESSAGE_APPLIESTOSINGLERANGEONLY); + // So needs to either span an entire Row or a just be a single cell + // ( that contains a summary RowColumn ) + // also the Single cell cause doesn't seem to be handled specially in + // this code ( ported from the helperapi RangeImpl.java, + // RangeRowsImpl.java, RangesImpl.java, RangeSingleCellImpl.java + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + + if ( isSingleCellRange() || mbIsRows ) + { + uno::Reference< sheet::XSheetOutline > xSheetOutline( thisRange.getSpreadSheet(), uno::UNO_QUERY_THROW ); + xSheetOutline->autoOutline( thisAddress ); + } + else + DebugHelper::basicexception(ERRCODE_BASIC_METHOD_FAILED, {}); +} + +void SAL_CALL +ScVbaRange:: ClearOutline( ) +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->ClearOutline(); + } + return; + } + RangeHelper thisRange( mxRange ); + uno::Reference< sheet::XSheetOutline > xSheetOutline( thisRange.getSpreadSheet(), uno::UNO_QUERY_THROW ); + xSheetOutline->clearOutline(); +} + +void +ScVbaRange::groupUnGroup( bool bUnGroup ) +{ + if ( m_Areas->getCount() > 1 ) + DebugHelper::basicexception(ERRCODE_BASIC_METHOD_FAILED, STR_ERRORMESSAGE_APPLIESTOSINGLERANGEONLY); + table::TableOrientation nOrient = table::TableOrientation_ROWS; + if ( mbIsColumns ) + nOrient = table::TableOrientation_COLUMNS; + RangeHelper thisRange( mxRange ); + table::CellRangeAddress thisAddress = thisRange.getCellRangeAddressable()->getRangeAddress(); + uno::Reference< sheet::XSheetOutline > xSheetOutline( thisRange.getSpreadSheet(), uno::UNO_QUERY_THROW ); + if ( bUnGroup ) + xSheetOutline->ungroup( thisAddress, nOrient ); + else + xSheetOutline->group( thisAddress, nOrient ); +} + +void SAL_CALL +ScVbaRange::Group( ) +{ + groupUnGroup(false); +} +void SAL_CALL +ScVbaRange::Ungroup( ) +{ + groupUnGroup(true); +} + +/// @throws uno::RuntimeException +static void lcl_mergeCellsOfRange( const uno::Reference< table::XCellRange >& xCellRange, bool _bMerge ) +{ + uno::Reference< util::XMergeable > xMergeable( xCellRange, uno::UNO_QUERY_THROW ); + xMergeable->merge(_bMerge); +} +void SAL_CALL +ScVbaRange::Merge( const uno::Any& Across ) +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->Merge(Across); + } + return; + } + bool bAcross = false; + Across >>= bAcross; + if ( !bAcross ) + lcl_mergeCellsOfRange( mxRange, true ); + else + { + uno::Reference< excel::XRange > oRangeRowsImpl = Rows( uno::Any() ); + // #TODO #FIXME this seems incredibly lame, this can't be right + for (sal_Int32 i=1; i <= oRangeRowsImpl->getCount();i++) + { + oRangeRowsImpl->Cells( uno::Any( i ), uno::Any() )->Merge( uno::Any( false ) ); + } + } +} + +void SAL_CALL +ScVbaRange::UnMerge( ) +{ + if ( m_Areas->getCount() > 1 ) + { + sal_Int32 nItems = m_Areas->getCount(); + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->UnMerge(); + } + return; + } + lcl_mergeCellsOfRange( mxRange, false); +} + +uno::Any SAL_CALL +ScVbaRange::getStyle() +{ + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32( 1 ) ), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange->getStyle(); + } + uno::Reference< beans::XPropertySet > xProps( mxRange, uno::UNO_QUERY_THROW ); + OUString sStyleName; + xProps->getPropertyValue( CELLSTYLE ) >>= sStyleName; + ScDocShell* pShell = getScDocShell(); + uno::Reference< frame::XModel > xModel( pShell->GetModel() ); + uno::Reference< excel::XStyle > xStyle = new ScVbaStyle( this, mxContext, sStyleName, xModel ); + return uno::Any( xStyle ); +} +void SAL_CALL +ScVbaRange::setStyle( const uno::Any& _style ) +{ + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32( 1 ) ), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange->setStyle( _style ); + return; + } + uno::Reference< beans::XPropertySet > xProps( mxRange, uno::UNO_QUERY_THROW ); + uno::Reference< excel::XStyle > xStyle; + _style >>= xStyle; + if ( xStyle.is() ) + xProps->setPropertyValue( CELLSTYLE, uno::Any( xStyle->getName() ) ); +} + +uno::Reference< excel::XRange > +ScVbaRange::PreviousNext( bool bIsPrevious ) +{ + ScMarkData markedRange(getScDocument().GetSheetLimits()); + ScRange refRange; + RangeHelper thisRange( mxRange ); + + ScUnoConversion::FillScRange( refRange, thisRange.getCellRangeAddressable()->getRangeAddress()); + markedRange. SetMarkArea( refRange ); + short nMove = bIsPrevious ? -1 : 1; + + SCCOL nNewX = refRange.aStart.Col(); + SCROW nNewY = refRange.aStart.Row(); + SCTAB nTab = refRange.aStart.Tab(); + + ScDocument& rDoc = getScDocument(); + rDoc.GetNextPos( nNewX,nNewY, nTab, nMove,0, true,true, markedRange ); + refRange.aStart.SetCol( nNewX ); + refRange.aStart.SetRow( nNewY ); + refRange.aStart.SetTab( nTab ); + refRange.aEnd.SetCol( nNewX ); + refRange.aEnd.SetRow( nNewY ); + refRange.aEnd.SetTab( nTab ); + + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( getScDocShell() , refRange ) ); + + return new ScVbaRange( mxParent, mxContext, xRange ); +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::Next() +{ + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32( 1 ) ), uno::Any() ) , uno::UNO_QUERY_THROW ); + return xRange->Next(); + } + return PreviousNext( false ); +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::Previous() +{ + if ( m_Areas->getCount() > 1 ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any( sal_Int32( 1 ) ), uno::Any() ), uno::UNO_QUERY_THROW ); + return xRange->Previous(); + } + return PreviousNext( true ); +} + +uno::Reference< excel::XRange > SAL_CALL +ScVbaRange::SpecialCells( const uno::Any& _oType, const uno::Any& _oValue) +{ + bool bIsSingleCell = isSingleCellRange(); + bool bIsMultiArea = ( m_Areas->getCount() > 1 ); + ScVbaRange* pRangeToUse = this; + uno::Reference< excel::XRange > xUsedRange( getWorksheet()->getUsedRange() ); + sal_Int32 nType = 0; + if ( !( _oType >>= nType ) ) + DebugHelper::basicexception(ERRCODE_BASIC_BAD_PARAMETER, {} ); + switch(nType) + { + case excel::XlCellType::xlCellTypeSameFormatConditions: + case excel::XlCellType::xlCellTypeAllValidation: + case excel::XlCellType::xlCellTypeSameValidation: + DebugHelper::basicexception(ERRCODE_BASIC_NOT_IMPLEMENTED, {}); + break; + case excel::XlCellType::xlCellTypeBlanks: + case excel::XlCellType::xlCellTypeComments: + case excel::XlCellType::xlCellTypeConstants: + case excel::XlCellType::xlCellTypeFormulas: + case excel::XlCellType::xlCellTypeVisible: + case excel::XlCellType::xlCellTypeLastCell: + { + if ( bIsMultiArea ) + { + // need to process each area, gather the results and + // create a new range from those + std::vector< table::CellRangeAddress > rangeResults; + sal_Int32 nItems = m_Areas->getCount() + 1; + for ( sal_Int32 index=1; index <= nItems; ++index ) + { + uno::Reference< excel::XRange > xRange( m_Areas->Item( uno::Any(index), uno::Any() ), uno::UNO_QUERY_THROW ); + xRange = xRange->SpecialCells( _oType, _oValue); + ScVbaRange* pRange = getImplementation( xRange ); + if ( xRange.is() && pRange ) + { + sal_Int32 nElems = pRange->m_Areas->getCount() + 1; + for ( sal_Int32 nArea = 1; nArea < nElems; ++nArea ) + { + uno::Reference< excel::XRange > xTmpRange( m_Areas->Item( uno::Any( nArea ), uno::Any() ), uno::UNO_QUERY_THROW ); + RangeHelper rHelper( xTmpRange->getCellRange() ); + rangeResults.push_back( rHelper.getCellRangeAddressable()->getRangeAddress() ); + } + } + } + ScRangeList aCellRanges; + for ( const auto& rRangeResult : rangeResults ) + { + ScRange refRange; + ScUnoConversion::FillScRange( refRange, rRangeResult ); + aCellRanges.push_back( refRange ); + } + // Single range + if ( aCellRanges.size() == 1 ) + { + uno::Reference< table::XCellRange > xRange( new ScCellRangeObj( getScDocShell(), aCellRanges.front() ) ); + return new ScVbaRange( mxParent, mxContext, xRange ); + } + uno::Reference< sheet::XSheetCellRangeContainer > xRanges( new ScCellRangesObj( getScDocShell(), aCellRanges ) ); + + return new ScVbaRange( mxParent, mxContext, xRanges ); + } + else if ( bIsSingleCell ) + { + pRangeToUse = static_cast< ScVbaRange* >( xUsedRange.get() ); + } + + break; + } + default: + DebugHelper::basicexception(ERRCODE_BASIC_BAD_PARAMETER, {} ); + break; + } + if ( !pRangeToUse ) + DebugHelper::basicexception(ERRCODE_BASIC_METHOD_FAILED, {} ); + return pRangeToUse->SpecialCellsImpl( nType, _oValue ); +} + +static sal_Int32 getContentResultFlags(const uno::Any& aValue) +{ + if (sal_Int32 aType; aValue >>= aType) + { + switch (aType) + { + case excel::XlSpecialCellsValue::xlNumbers: + return sheet::CellFlags::VALUE | sheet::CellFlags::DATETIME; + case excel::XlSpecialCellsValue::xlTextValues: + return sheet::CellFlags::STRING; + case excel::XlSpecialCellsValue::xlLogical: + return sheet::CellFlags::VALUE | sheet::CellFlags::DATETIME; + case excel::XlSpecialCellsValue::xlErrors: + return 0; + default: + DebugHelper::basicexception(ERRCODE_BASIC_BAD_PARAMETER, {}); + } + } + return sheet::CellFlags::VALUE | sheet::CellFlags::STRING | sheet::CellFlags::DATETIME; +} + +/// @throws script::BasicErrorException +static sal_Int32 lcl_getFormulaResultFlags(const uno::Any& aType) +{ + sal_Int32 nType = excel::XlSpecialCellsValue::xlNumbers; + aType >>= nType; + sal_Int32 nRes = sheet::FormulaResult::VALUE; + + switch(nType) + { + case excel::XlSpecialCellsValue::xlErrors: + nRes= sheet::FormulaResult::ERROR; + break; + case excel::XlSpecialCellsValue::xlLogical: + //TODO bc93774: ask NN if this is really an appropriate substitute + nRes = sheet::FormulaResult::VALUE; + break; + case excel::XlSpecialCellsValue::xlNumbers: + nRes = sheet::FormulaResult::VALUE; + break; + case excel::XlSpecialCellsValue::xlTextValues: + nRes = sheet::FormulaResult::STRING; + break; + default: + DebugHelper::basicexception(ERRCODE_BASIC_BAD_PARAMETER, {} ); + } + return nRes; +} + +uno::Reference< excel::XRange > +ScVbaRange::SpecialCellsImpl( sal_Int32 nType, const uno::Any& _oValue) +{ + uno::Reference< excel::XRange > xRange; + try + { + uno::Reference< sheet::XCellRangesQuery > xQuery( mxRange, uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XSheetCellRanges > xLocSheetCellRanges; + switch(nType) + { + case excel::XlCellType::xlCellTypeAllFormatConditions: + case excel::XlCellType::xlCellTypeSameFormatConditions: + case excel::XlCellType::xlCellTypeAllValidation: + case excel::XlCellType::xlCellTypeSameValidation: + // Shouldn't get here ( should be filtered out by + // ScVbaRange::SpecialCells() + DebugHelper::basicexception(ERRCODE_BASIC_NOT_IMPLEMENTED, {}); + break; + case excel::XlCellType::xlCellTypeBlanks: + xLocSheetCellRanges = xQuery->queryEmptyCells(); + break; + case excel::XlCellType::xlCellTypeComments: + xLocSheetCellRanges = xQuery->queryContentCells(sheet::CellFlags::ANNOTATION); + break; + case excel::XlCellType::xlCellTypeConstants: + xLocSheetCellRanges = xQuery->queryContentCells(getContentResultFlags(_oValue)); + break; + case excel::XlCellType::xlCellTypeFormulas: + { + sal_Int32 nFormulaResult = lcl_getFormulaResultFlags(_oValue); + xLocSheetCellRanges = xQuery->queryFormulaCells(nFormulaResult); + break; + } + case excel::XlCellType::xlCellTypeLastCell: + xRange = Cells( uno::Any( getCount() ), uno::Any() ); + [[fallthrough]]; //TODO ??? + case excel::XlCellType::xlCellTypeVisible: + xLocSheetCellRanges = xQuery->queryVisibleCells(); + break; + default: + DebugHelper::basicexception(ERRCODE_BASIC_BAD_PARAMETER, {} ); + break; + } + if (xLocSheetCellRanges.is()) + { + xRange = lcl_makeXRangeFromSheetCellRanges( getParent(), mxContext, xLocSheetCellRanges, getScDocShell() ); + } + } + catch (uno::Exception& ) + { + DebugHelper::basicexception(ERRCODE_BASIC_METHOD_FAILED, u"No cells were found"); + } + return xRange; +} + +void SAL_CALL +ScVbaRange::RemoveSubtotal( ) +{ + uno::Reference< sheet::XSubTotalCalculatable > xSub( mxRange, uno::UNO_QUERY_THROW ); + xSub->removeSubTotals(); +} + +void SAL_CALL +ScVbaRange::Subtotal( ::sal_Int32 _nGroupBy, ::sal_Int32 _nFunction, const uno::Sequence< ::sal_Int32 >& _nTotalList, const uno::Any& aReplace, const uno::Any& PageBreaks, const uno::Any& /*SummaryBelowData*/ ) +{ + try + { + bool bDoReplace = false; + aReplace >>= bDoReplace; + bool bAddPageBreaks = false; + PageBreaks >>= bAddPageBreaks; + + uno::Reference< sheet::XSubTotalCalculatable> xSub(mxRange, uno::UNO_QUERY_THROW ); + uno::Reference< sheet::XSubTotalDescriptor > xSubDesc = xSub->createSubTotalDescriptor(true); + uno::Reference< beans::XPropertySet > xSubDescPropertySet( xSubDesc, uno::UNO_QUERY_THROW ); + xSubDescPropertySet->setPropertyValue("InsertPageBreaks", uno::Any( bAddPageBreaks)); + sal_Int32 nLen = _nTotalList.getLength(); + uno::Sequence< sheet::SubTotalColumn > aColumns( nLen ); + auto aColumnsRange = asNonConstRange(aColumns); + for (int i = 0; i < nLen; i++) + { + aColumnsRange[i].Column = _nTotalList[i] - 1; + switch (_nFunction) + { + case excel::XlConsolidationFunction::xlAverage: + aColumnsRange[i].Function = sheet::GeneralFunction_AVERAGE; + break; + case excel::XlConsolidationFunction::xlCount: + aColumnsRange[i].Function = sheet::GeneralFunction_COUNT; + break; + case excel::XlConsolidationFunction::xlCountNums: + aColumnsRange[i].Function = sheet::GeneralFunction_COUNTNUMS; + break; + case excel::XlConsolidationFunction::xlMax: + aColumnsRange[i].Function = sheet::GeneralFunction_MAX; + break; + case excel::XlConsolidationFunction::xlMin: + aColumnsRange[i].Function = sheet::GeneralFunction_MIN; + break; + case excel::XlConsolidationFunction::xlProduct: + aColumnsRange[i].Function = sheet::GeneralFunction_PRODUCT; + break; + case excel::XlConsolidationFunction::xlStDev: + aColumnsRange[i].Function = sheet::GeneralFunction_STDEV; + break; + case excel::XlConsolidationFunction::xlStDevP: + aColumnsRange[i].Function = sheet::GeneralFunction_STDEVP; + break; + case excel::XlConsolidationFunction::xlSum: + aColumnsRange[i].Function = sheet::GeneralFunction_SUM; + break; + case excel::XlConsolidationFunction::xlUnknown: + aColumnsRange[i].Function = sheet::GeneralFunction_NONE; + break; + case excel::XlConsolidationFunction::xlVar: + aColumnsRange[i].Function = sheet::GeneralFunction_VAR; + break; + case excel::XlConsolidationFunction::xlVarP: + aColumnsRange[i].Function = sheet::GeneralFunction_VARP; + break; + default: + DebugHelper::basicexception(ERRCODE_BASIC_BAD_PARAMETER, {}) ; + return; + } + } + xSubDesc->addNew(aColumns, _nGroupBy - 1); + xSub->applySubTotals(xSubDesc, bDoReplace); + } + catch (const uno::Exception&) + { + DebugHelper::basicexception(ERRCODE_BASIC_METHOD_FAILED, {}); + } +} + +void SAL_CALL +ScVbaRange::ExportAsFixedFormat(const css::uno::Any& Type, const css::uno::Any& FileName, const css::uno::Any& Quality, + const css::uno::Any& IncludeDocProperties, const css::uno::Any& /*IgnorePrintAreas*/, const css::uno::Any& From, + const css::uno::Any& To, const css::uno::Any& OpenAfterPublish, const css::uno::Any& /*FixedFormatExtClassPtr*/) +{ + ScCellRangesBase* pUnoRangesBase = getCellRangesBase(); + if (!pUnoRangesBase) + throw uno::RuntimeException("Failed to access underlying uno range object"); + ScDocShell* pShell = pUnoRangesBase->GetDocShell(); + if (!pShell) + return; + + uno::Reference< frame::XModel > xModel(pShell->GetModel(), uno::UNO_SET_THROW); + uno::Reference< excel::XApplication > xApplication(Application(), uno::UNO_QUERY_THROW); + + excel::ExportAsFixedFormatHelper(xModel, xApplication, Type, FileName, Quality, + IncludeDocProperties, From, To, OpenAfterPublish); +} + +OUString +ScVbaRange::getServiceImplName() +{ + return "ScVbaRange"; +} + +uno::Sequence< OUString > +ScVbaRange::getServiceNames() +{ + return { "ooo.vba.excel.Range" }; +} + +sal_Bool SAL_CALL +ScVbaRange::hasError() +{ + double dResult = 0.0; + uno::Reference< excel::XApplication > xApplication( Application(), uno::UNO_QUERY_THROW ); + uno::Reference< script::XInvocation > xInvoc( xApplication->WorksheetFunction(), uno::UNO_QUERY_THROW ); + + uno::Reference< excel::XRange > aRange( this ); + uno::Sequence< uno::Any > Params{ uno::Any(aRange) }; + uno::Sequence< sal_Int16 > OutParamIndex; + uno::Sequence< uno::Any > OutParam; + xInvoc->invoke( "IsError", Params, OutParamIndex, OutParam ) >>= dResult; + return dResult > 0.0; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Calc_ScVbaRange_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new ScVbaRange(args, context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |