/* -*- 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 "vbasheetobjects.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vbasheetobject.hxx" #include using namespace ::com::sun::star; using namespace ::ooo::vba; namespace { template< typename Type > bool lclGetProperty( Type& orValue, const uno::Reference< beans::XPropertySet >& rxPropSet, const OUString& rPropName ) { try { return rxPropSet->getPropertyValue( rPropName ) >>= orValue; } catch( uno::Exception& ) { } return false; } /** Rounds the passed value to a multiple of 0.75 and converts it to 1/100 mm. @throws uno::RuntimeException */ sal_Int32 lclPointsToHmm( const uno::Any& rPoints ) { return std::round(o3tl::convert(::rtl::math::approxFloor(rPoints.get() / 0.75) * 0.75, o3tl::Length::pt, o3tl::Length::mm100)); } } // namespace // Base implementations /** Container for a specific type of drawing object in a spreadsheet. Derived classes provide all required functionality specific to the type of shapes covered by the container. */ class ScVbaObjectContainer : public ::cppu::WeakImplHelper< container::XIndexAccess > { public: /// @throws uno::RuntimeException explicit ScVbaObjectContainer( uno::Reference< XHelperInterface > xParent, uno::Reference< uno::XComponentContext > xContext, const uno::Reference< frame::XModel >& rxModel, const uno::Reference< sheet::XSpreadsheet >& rxSheet, const uno::Type& rVbaType ); /** Returns the VBA helper interface of the VBA collection object. */ const uno::Reference< XHelperInterface >& getParent() const { return mxParent; } /** Returns the component context of the VBA collection object. */ const uno::Reference< uno::XComponentContext >& getContext() const { return mxContext; } /** Returns the VBA type information of the objects in this container. */ const uno::Type& getVbaType() const { return maVbaType; } /** Collects all shapes supported by this instance and inserts them into the internal shape vector. @throws uno::RuntimeException */ void collectShapes(); /** Creates and returns a new UNO shape. @throws uno::RuntimeException */ uno::Reference< drawing::XShape > createShape( const awt::Point& rPos, const awt::Size& rSize ); /** Inserts the passed shape into the draw page and into this container, and returns its index in the draw page. @throws uno::RuntimeException */ sal_Int32 insertShape( const uno::Reference< drawing::XShape >& rxShape ); /** Creates and returns a new VBA implementation object for the passed shape. @throws uno::RuntimeException */ ::rtl::Reference< ScVbaSheetObjectBase > createVbaObject( const uno::Reference< drawing::XShape >& rxShape ); /** Creates and returns a new VBA implementation object for the passed shape in an Any. @throws uno::RuntimeException */ uno::Any createCollectionObject( const uno::Any& rSource ); /** Returns the VBA implementation object with the specified name. @throws uno::RuntimeException */ uno::Any getItemByStringIndex( const OUString& rIndex ); // XIndexAccess virtual sal_Int32 SAL_CALL getCount() override; virtual uno::Any SAL_CALL getByIndex( sal_Int32 nIndex ) override; // XElementAccess virtual uno::Type SAL_CALL getElementType() override; virtual sal_Bool SAL_CALL hasElements() override; protected: /** Derived classes return true, if the passed shape is supported by the instance. */ virtual bool implPickShape( const uno::Reference< drawing::XShape >& rxShape ) const = 0; /** Derived classes create and return a new VBA implementation object for the passed shape. @throws uno::RuntimeException */ virtual rtl::Reference implCreateVbaObject( const uno::Reference< drawing::XShape >& rxShape ) = 0; /** Derived classes return the service name of the UNO shape. */ virtual OUString implGetShapeServiceName() const = 0; /** Returns the shape name via 'Name' property of the UNO shape. May be overwritten. @throws uno::RuntimeException */ virtual OUString implGetShapeName( const uno::Reference< drawing::XShape >& rxShape ) const; /** Is called when a new UNO shape has been created but not yet inserted into the drawing page. @throws uno::RuntimeException */ virtual void implOnShapeCreated( const uno::Reference< drawing::XShape >& rxShape ); protected: uno::Reference< XHelperInterface > mxParent; uno::Reference< uno::XComponentContext > mxContext; uno::Reference< frame::XModel > mxModel; uno::Reference< lang::XMultiServiceFactory > mxFactory; uno::Reference< drawing::XShapes > mxShapes; private: typedef ::std::vector< uno::Reference< drawing::XShape > > ShapeVector; const uno::Type maVbaType; ShapeVector maShapes; }; ScVbaObjectContainer::ScVbaObjectContainer( uno::Reference< XHelperInterface > xParent, uno::Reference< uno::XComponentContext > xContext, const uno::Reference< frame::XModel >& rxModel, const uno::Reference< sheet::XSpreadsheet >& rxSheet, const uno::Type& rVbaType ) : mxParent(std::move( xParent )), mxContext(std::move( xContext )), mxModel( rxModel, uno::UNO_SET_THROW ), mxFactory( rxModel, uno::UNO_QUERY_THROW ), maVbaType( rVbaType ) { uno::Reference< drawing::XDrawPageSupplier > xDrawPageSupp( rxSheet, uno::UNO_QUERY_THROW ); mxShapes.set( xDrawPageSupp->getDrawPage(), uno::UNO_QUERY_THROW ); } void ScVbaObjectContainer::collectShapes() { maShapes.clear(); for( sal_Int32 nIndex = 0, nCount = mxShapes->getCount(); nIndex < nCount; ++nIndex ) { uno::Reference< drawing::XShape > xShape( mxShapes->getByIndex( nIndex ), uno::UNO_QUERY_THROW ); if( implPickShape( xShape ) ) maShapes.push_back( xShape ); } } uno::Reference< drawing::XShape > ScVbaObjectContainer::createShape( const awt::Point& rPos, const awt::Size& rSize ) { uno::Reference< drawing::XShape > xShape( mxFactory->createInstance( implGetShapeServiceName() ), uno::UNO_QUERY_THROW ); xShape->setPosition( rPos ); xShape->setSize( rSize ); implOnShapeCreated( xShape ); return xShape; } sal_Int32 ScVbaObjectContainer::insertShape( const uno::Reference< drawing::XShape >& rxShape ) { mxShapes->add( rxShape ); maShapes.push_back( rxShape ); return mxShapes->getCount() - 1; } ::rtl::Reference< ScVbaSheetObjectBase > ScVbaObjectContainer::createVbaObject( const uno::Reference< drawing::XShape >& rxShape ) { return implCreateVbaObject( rxShape ); } uno::Any ScVbaObjectContainer::createCollectionObject( const uno::Any& rSource ) { uno::Reference< drawing::XShape > xShape( rSource, uno::UNO_QUERY_THROW ); uno::Reference< excel::XSheetObject > xSheetObject( implCreateVbaObject( xShape ) ); return uno::Any( xSheetObject ); } uno::Any ScVbaObjectContainer::getItemByStringIndex( const OUString& rIndex ) { auto aIt = std::find_if(maShapes.begin(), maShapes.end(), [&rIndex, this](const ShapeVector::value_type& rxShape) { return rIndex == implGetShapeName( rxShape ); }); if (aIt != maShapes.end()) return createCollectionObject( uno::Any( *aIt ) ); throw uno::RuntimeException(); } // XIndexAccess sal_Int32 SAL_CALL ScVbaObjectContainer::getCount() { return static_cast< sal_Int32 >( maShapes.size() ); } uno::Any SAL_CALL ScVbaObjectContainer::getByIndex( sal_Int32 nIndex ) { if( (0 <= nIndex) && (nIndex < getCount()) ) return uno::Any( maShapes[ static_cast< size_t >( nIndex ) ] ); throw lang::IndexOutOfBoundsException(); } // XElementAccess uno::Type SAL_CALL ScVbaObjectContainer::getElementType() { return cppu::UnoType::get(); } sal_Bool SAL_CALL ScVbaObjectContainer::hasElements() { return !maShapes.empty(); } // private OUString ScVbaObjectContainer::implGetShapeName( const uno::Reference< drawing::XShape >& rxShape ) const { uno::Reference< beans::XPropertySet > xPropSet( rxShape, uno::UNO_QUERY_THROW ); return xPropSet->getPropertyValue( u"Name"_ustr ).get< OUString >(); } void ScVbaObjectContainer::implOnShapeCreated( const uno::Reference< drawing::XShape >& /*rxShape*/ ) { } namespace { class ScVbaObjectEnumeration : public SimpleEnumerationBase { public: explicit ScVbaObjectEnumeration( const ScVbaObjectContainerRef& rxContainer ); virtual uno::Any createCollectionObject( const uno::Any& rSource ) override; private: ScVbaObjectContainerRef mxContainer; }; } ScVbaObjectEnumeration::ScVbaObjectEnumeration( const ScVbaObjectContainerRef& rxContainer ) : SimpleEnumerationBase( rxContainer ), mxContainer( rxContainer ) { } uno::Any ScVbaObjectEnumeration::createCollectionObject( const uno::Any& rSource ) { return mxContainer->createCollectionObject( rSource ); } ScVbaSheetObjectsBase::ScVbaSheetObjectsBase( const ScVbaObjectContainerRef& rxContainer ) : ScVbaSheetObjects_BASE( rxContainer->getParent(), rxContainer->getContext(), rxContainer ), mxContainer( rxContainer ) { mxContainer->collectShapes(); } ScVbaSheetObjectsBase::~ScVbaSheetObjectsBase() { } void ScVbaSheetObjectsBase::collectShapes() { mxContainer->collectShapes(); } // XEnumerationAccess uno::Reference< container::XEnumeration > SAL_CALL ScVbaSheetObjectsBase::createEnumeration() { return new ScVbaObjectEnumeration( mxContainer ); } // XElementAccess uno::Type SAL_CALL ScVbaSheetObjectsBase::getElementType() { return mxContainer->getVbaType(); } // ScVbaCollectionBase uno::Any ScVbaSheetObjectsBase::createCollectionObject( const uno::Any& rSource ) { return mxContainer->createCollectionObject( rSource ); } uno::Any ScVbaSheetObjectsBase::getItemByStringIndex( const OUString& rIndex ) { return mxContainer->getItemByStringIndex( rIndex ); } // Graphic object containers supporting ooo.vba.excel.XGraphicObject ScVbaGraphicObjectsBase::ScVbaGraphicObjectsBase( const ScVbaObjectContainerRef& rxContainer ) : ScVbaGraphicObjects_BASE( rxContainer ) { } // XGraphicObjects uno::Any SAL_CALL ScVbaGraphicObjectsBase::Add( const uno::Any& rLeft, const uno::Any& rTop, const uno::Any& rWidth, const uno::Any& rHeight ) { /* Extract double values from passed Anys (the lclPointsToHmm() helper function will throw a RuntimeException on any error), and convert from points to 1/100 mm. */ awt::Point aPos( lclPointsToHmm( rLeft ), lclPointsToHmm( rTop ) ); awt::Size aSize( lclPointsToHmm( rWidth ), lclPointsToHmm( rHeight ) ); // TODO: translate coordinates for RTL sheets if( (aPos.X < 0) || (aPos.Y < 0) || (aSize.Width <= 0) || (aSize.Height <= 0) ) throw uno::RuntimeException(); // create the UNO shape uno::Reference< drawing::XShape > xShape( mxContainer->createShape( aPos, aSize ), uno::UNO_SET_THROW ); sal_Int32 nIndex = mxContainer->insertShape( xShape ); // create and return the VBA object ::rtl::Reference< ScVbaSheetObjectBase > xVbaObject = mxContainer->createVbaObject( xShape ); xVbaObject->setDefaultProperties( nIndex ); return uno::Any( uno::Reference< excel::XSheetObject >( xVbaObject ) ); } // Drawing controls namespace { class ScVbaControlContainer : public ScVbaObjectContainer { public: /// @throws uno::RuntimeException explicit ScVbaControlContainer( const uno::Reference< XHelperInterface >& rxParent, const uno::Reference< uno::XComponentContext >& rxContext, const uno::Reference< frame::XModel >& rxModel, const uno::Reference< sheet::XSpreadsheet >& rxSheet, const uno::Type& rVbaType, OUString aModelServiceName, sal_Int16 /* css::form::FormComponentType */ eType ); protected: /// @throws uno::RuntimeException uno::Reference< container::XIndexContainer > const & createForm(); virtual bool implPickShape( const uno::Reference< drawing::XShape >& rxShape ) const override; virtual OUString implGetShapeServiceName() const override; virtual bool implCheckProperties( const uno::Reference< beans::XPropertySet >& rxModelProps ) const; virtual OUString implGetShapeName( const uno::Reference< drawing::XShape >& rxShape ) const override; virtual void implOnShapeCreated( const uno::Reference< drawing::XShape >& rxShape ) override; protected: uno::Reference< container::XIndexContainer > mxFormIC; OUString maModelServiceName; sal_Int16 /* css::form::FormComponentType */ meType; }; } ScVbaControlContainer::ScVbaControlContainer( const uno::Reference< XHelperInterface >& rxParent, const uno::Reference< uno::XComponentContext >& rxContext, const uno::Reference< frame::XModel >& rxModel, const uno::Reference< sheet::XSpreadsheet >& rxSheet, const uno::Type& rVbaType, OUString aModelServiceName, sal_Int16 /* css::form::FormComponentType */ eType ) : ScVbaObjectContainer( rxParent, rxContext, rxModel, rxSheet, rVbaType ), maModelServiceName(std::move( aModelServiceName )), meType( eType ) { } uno::Reference< container::XIndexContainer > const & ScVbaControlContainer::createForm() { if( !mxFormIC.is() ) { uno::Reference< form::XFormsSupplier > xFormsSupp( mxShapes, uno::UNO_QUERY_THROW ); uno::Reference< container::XNameContainer > xFormsNC( xFormsSupp->getForms(), uno::UNO_SET_THROW ); OUString aFormName = u"Standard"_ustr; if( xFormsNC->hasByName( aFormName ) ) { mxFormIC.set( xFormsNC->getByName( aFormName ), uno::UNO_QUERY_THROW ); } else { uno::Reference< form::XForm > xForm( mxFactory->createInstance( u"com.sun.star.form.component.Form"_ustr ), uno::UNO_QUERY_THROW ); xFormsNC->insertByName( aFormName, uno::Any( xForm ) ); mxFormIC.set( xForm, uno::UNO_QUERY_THROW ); } } return mxFormIC; } bool ScVbaControlContainer::implPickShape( const uno::Reference< drawing::XShape >& rxShape ) const { try { uno::Reference< drawing::XControlShape > xControlShape( rxShape, uno::UNO_QUERY_THROW ); uno::Reference< beans::XPropertySet > xModelProps( xControlShape->getControl(), uno::UNO_QUERY_THROW ); sal_Int16 nClassId = -1; return lclGetProperty( nClassId, xModelProps, u"ClassId"_ustr ) && (nClassId == meType) && implCheckProperties( xModelProps ); } catch( uno::Exception& ) { } return false; } OUString ScVbaControlContainer::implGetShapeServiceName() const { return u"com.sun.star.drawing.ControlShape"_ustr; } bool ScVbaControlContainer::implCheckProperties( const uno::Reference< beans::XPropertySet >& /*rxModelProps*/ ) const { return true; } OUString ScVbaControlContainer::implGetShapeName( const uno::Reference< drawing::XShape >& rxShape ) const { uno::Reference< drawing::XControlShape > xControlShape( rxShape, uno::UNO_QUERY_THROW ); return uno::Reference< container::XNamed >( xControlShape->getControl(), uno::UNO_QUERY_THROW )->getName(); } void ScVbaControlContainer::implOnShapeCreated( const uno::Reference< drawing::XShape >& rxShape ) { // passed shape must be a control shape uno::Reference< drawing::XControlShape > xControlShape( rxShape, uno::UNO_QUERY_THROW ); // create the UNO control model uno::Reference< form::XFormComponent > xFormComponent( mxFactory->createInstance( maModelServiceName ), uno::UNO_QUERY_THROW ); uno::Reference< awt::XControlModel > xControlModel( xFormComponent, uno::UNO_QUERY_THROW ); // insert the control model into the form and the shape createForm(); mxFormIC->insertByIndex( mxFormIC->getCount(), uno::Any( xFormComponent ) ); xControlShape->setControl( xControlModel ); } // Push button namespace { class ScVbaButtonContainer : public ScVbaControlContainer { bool mbOptionButtons; public: /// @throws uno::RuntimeException explicit ScVbaButtonContainer( const uno::Reference< XHelperInterface >& rxParent, const uno::Reference< uno::XComponentContext >& rxContext, const uno::Reference< frame::XModel >& rxModel, const uno::Reference< sheet::XSpreadsheet >& rxSheet, bool bOptionButtons); protected: virtual rtl::Reference implCreateVbaObject( const uno::Reference< drawing::XShape >& rxShape ) override; virtual bool implCheckProperties( const uno::Reference< beans::XPropertySet >& rxModelProps ) const override; }; } ScVbaButtonContainer::ScVbaButtonContainer( const uno::Reference< XHelperInterface >& rxParent, const uno::Reference< uno::XComponentContext >& rxContext, const uno::Reference< frame::XModel >& rxModel, const uno::Reference< sheet::XSpreadsheet >& rxSheet, bool bOptionButtons ) : ScVbaControlContainer( rxParent, rxContext, rxModel, rxSheet, cppu::UnoType::get(), ( bOptionButtons ? u"com.sun.star.form.component.RadioButton"_ustr : u"com.sun.star.form.component.CommandButton"_ustr ), ( bOptionButtons ? form::FormComponentType::RADIOBUTTON : form::FormComponentType::COMMANDBUTTON) ), mbOptionButtons(bOptionButtons) { } rtl::Reference ScVbaButtonContainer::implCreateVbaObject( const uno::Reference< drawing::XShape >& rxShape ) { uno::Reference< drawing::XControlShape > xControlShape( rxShape, uno::UNO_QUERY_THROW ); return new ScVbaButton( mxParent, mxContext, mxModel, createForm(), xControlShape ); } bool ScVbaButtonContainer::implCheckProperties( const uno::Reference< beans::XPropertySet >& rxModelProps ) const { if (mbOptionButtons) return true; // do not insert toggle buttons into the 'Buttons' collection bool bToggle = false; return lclGetProperty( bToggle, rxModelProps, u"Toggle"_ustr ) && !bToggle; } ScVbaButtons::ScVbaButtons( const uno::Reference< XHelperInterface >& rxParent, const uno::Reference< uno::XComponentContext >& rxContext, const uno::Reference< frame::XModel >& rxModel, const uno::Reference< sheet::XSpreadsheet >& rxSheet, bool bOptionButtons) : ScVbaGraphicObjectsBase( new ScVbaButtonContainer( rxParent, rxContext, rxModel, rxSheet, bOptionButtons ) ) { } VBAHELPER_IMPL_XHELPERINTERFACE( ScVbaButtons, u"ooo.vba.excel.Buttons"_ustr ) /* vim:set shiftwidth=4 softtabstop=4 expandtab: */