/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::chart2; using ::com::sun::star::uno::Reference; using ::com::sun::star::uno::Sequence; namespace { void lcl_getChildOIDs( ::chart::ObjectHierarchy::tChildContainer& rOutChildren, const Reference< container::XIndexAccess >& xShapes ) { if( !xShapes.is()) return; sal_Int32 nCount = xShapes->getCount(); for( sal_Int32 i=0; i xShapeProp( xShapes->getByIndex( i ), uno::UNO_QUERY ); if( xShapeProp.is()) { Reference< beans::XPropertySetInfo > xInfo( xShapeProp->getPropertySetInfo()); OUString aName; if( xInfo.is() && xInfo->hasPropertyByName( "Name") && (xShapeProp->getPropertyValue( "Name") >>= aName ) && !aName.isEmpty() && ::chart::ObjectIdentifier::isCID( aName )) { rOutChildren.emplace_back( aName ); } Reference< container::XIndexAccess > xNewShapes( xShapeProp, uno::UNO_QUERY ); if( xNewShapes.is()) lcl_getChildOIDs( rOutChildren, xNewShapes ); } } } void lcl_addAxisTitle( const Reference< XAxis >& xAxis, ::chart::ObjectHierarchy::tChildContainer& rContainer, const rtl::Reference<::chart::ChartModel>& xChartModel ) { Reference< XTitled > xAxisTitled( xAxis, uno::UNO_QUERY ); if( xAxisTitled.is()) { Reference< XTitle > xAxisTitle( xAxisTitled->getTitleObject()); if( xAxisTitle.is()) rContainer.emplace_back( ::chart::ObjectIdentifier::createClassifiedIdentifierForObject( xAxisTitle, xChartModel ) ); } } } // anonymous namespace namespace chart { void ObjectHierarchy::createTree( const rtl::Reference<::chart::ChartModel>& xChartDocument ) { m_aChildMap = tChildMap();//clear tree if( !xChartDocument.is() ) return; //@todo: change ObjectIdentifier to take an XChartDocument rather than XModel rtl::Reference< Diagram > xDiagram = ChartModelHelper::findDiagram( xChartDocument ); ObjectIdentifier aDiaOID; if( xDiagram.is() ) aDiaOID = ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForObject( static_cast(xDiagram.get()), xChartDocument ) ); ObjectHierarchy::tChildContainer aTopLevelContainer; // First Level // Chart Area if( m_bOrderingForElementSelector ) { aTopLevelContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, u"" ) ); if( xDiagram.is() ) { aTopLevelContainer.push_back( aDiaOID ); createWallAndFloor( aTopLevelContainer, xDiagram ); createLegendTree( aTopLevelContainer, xChartDocument, xDiagram ); } } // Main Title Reference< XTitle > xMainTitle( xChartDocument->getTitleObject()); if( xMainTitle.is()) aTopLevelContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierForObject( xMainTitle, xChartDocument ) ); if( xDiagram.is()) { // Sub Title. Note: This is interpreted of being top level Reference< XTitle > xSubTitle( xDiagram->getTitleObject()); if( xSubTitle.is()) aTopLevelContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierForObject( xSubTitle, xChartDocument ) ); if( !m_bOrderingForElementSelector ) { // Axis Titles. Note: These are interpreted of being top level const Sequence< Reference< XAxis > > aAxes( AxisHelper::getAllAxesOfDiagram( xDiagram ) ); for( Reference< XAxis > const & axis : aAxes ) lcl_addAxisTitle( axis, aTopLevelContainer, xChartDocument ); // Diagram aTopLevelContainer.push_back( aDiaOID ); } if( m_bFlattenDiagram ) createDiagramTree( aTopLevelContainer, xChartDocument, xDiagram ); else { ObjectHierarchy::tChildContainer aSubContainer; createDiagramTree( aSubContainer, xChartDocument, xDiagram ); if( !aSubContainer.empty() ) m_aChildMap[ aDiaOID ] = aSubContainer; } if( !m_bOrderingForElementSelector ) createLegendTree( aTopLevelContainer, xChartDocument, xDiagram ); } // #i12587# support for shapes in chart if ( !m_bOrderingForElementSelector ) { createAdditionalShapesTree( aTopLevelContainer ); } // Chart Area if( !m_bOrderingForElementSelector ) aTopLevelContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, u"" ) ); if( ! aTopLevelContainer.empty()) m_aChildMap[ ObjectHierarchy::getRootNodeOID() ] = aTopLevelContainer; } void ObjectHierarchy::createLegendTree( ObjectHierarchy::tChildContainer & rContainer, const rtl::Reference<::chart::ChartModel> & xChartDoc, const Reference< XDiagram > & xDiagram ) { if( !(xDiagram.is() && LegendHelper::hasLegend( xDiagram )) ) return; ObjectIdentifier aLegendOID( ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForObject( xDiagram->getLegend(), xChartDoc ) ) ); rContainer.push_back( aLegendOID ); // iterate over child shapes of legend and search for matching CIDs if( m_pExplicitValueProvider ) { Reference< container::XIndexAccess > xLegendShapeContainer( m_pExplicitValueProvider->getShapeForCID( aLegendOID.getObjectCID() ), uno::UNO_QUERY ); ObjectHierarchy::tChildContainer aLegendEntryOIDs; lcl_getChildOIDs( aLegendEntryOIDs, xLegendShapeContainer ); m_aChildMap[ aLegendOID ] = aLegendEntryOIDs; } } void ObjectHierarchy::createAxesTree( ObjectHierarchy::tChildContainer & rContainer, const rtl::Reference<::chart::ChartModel> & xChartDoc, const Reference< XDiagram > & xDiagram ) { Reference< XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY_THROW ); sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram ); uno::Reference< chart2::XChartType > xChartType( DiagramHelper::getChartTypeByIndex( xDiagram, 0 ) ); bool bSupportsAxesGrids = ChartTypeHelper::isSupportingMainAxis( xChartType, nDimensionCount, 0 ); if( !bSupportsAxesGrids ) return; Sequence< Reference< XAxis > > aAxes( AxisHelper::getAllAxesOfDiagram( xDiagram, /* bOnlyVisible = */ true ) ); if( !m_bOrderingForElementSelector ) { for (const auto & rAxis : std::as_const(aAxes)) rContainer.push_back( ObjectIdentifier::createClassifiedIdentifierForObject( rAxis, xChartDoc ) ); } // get all axes, also invisible ones aAxes = AxisHelper::getAllAxesOfDiagram( xDiagram ); // Grids for( Reference< XAxis > const & xAxis : std::as_const(aAxes) ) { if(!xAxis.is()) continue; sal_Int32 nCooSysIndex = 0; sal_Int32 nDimensionIndex = 0; sal_Int32 nAxisIndex = 0; AxisHelper::getIndicesForAxis( xAxis, xDiagram, nCooSysIndex, nDimensionIndex, nAxisIndex ); if( nAxisIndex>0 && !ChartTypeHelper::isSupportingSecondaryAxis( xChartType, nDimensionCount ) ) continue; if( m_bOrderingForElementSelector ) { // axis if( AxisHelper::isAxisVisible( xAxis ) ) rContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierForObject( xAxis, xChartDoc ) ); // axis title lcl_addAxisTitle( xAxis, rContainer, xChartDoc ); } Reference< beans::XPropertySet > xGridProperties( xAxis->getGridProperties() ); if( AxisHelper::isGridVisible( xGridProperties ) ) { //main grid rContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierForGrid( xAxis, xChartDoc ) ); } Sequence< Reference< beans::XPropertySet > > aSubGrids( xAxis->getSubGridProperties() ); sal_Int32 nSubGrid = 0; for( nSubGrid = 0; nSubGrid < aSubGrids.getLength(); ++nSubGrid ) { Reference< beans::XPropertySet > xSubGridProperties( aSubGrids[nSubGrid] ); if( AxisHelper::isGridVisible( xSubGridProperties ) ) { //sub grid rContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierForGrid( xAxis, xChartDoc, nSubGrid ) ); } } } } void ObjectHierarchy::createWallAndFloor( ObjectHierarchy::tChildContainer & rContainer, const Reference< XDiagram > & xDiagram ) { sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram ); bool bIsThreeD = ( nDimensionCount == 3 ); bool bHasWall = DiagramHelper::isSupportingFloorAndWall( xDiagram ); if( bHasWall && bIsThreeD ) { rContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_WALL, u"" ) ); Reference< beans::XPropertySet > xFloor( xDiagram->getFloor()); if( xFloor.is()) rContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_FLOOR, u"" ) ); } } void ObjectHierarchy::createDiagramTree( ObjectHierarchy::tChildContainer & rContainer, const rtl::Reference<::chart::ChartModel> & xChartDoc, const Reference< XDiagram > & xDiagram ) { if( !m_bOrderingForElementSelector ) { createDataSeriesTree( rContainer, xDiagram ); createAxesTree( rContainer, xChartDoc, xDiagram ); createWallAndFloor( rContainer, xDiagram ); } else { createAxesTree( rContainer, xChartDoc, xDiagram ); createDataSeriesTree( rContainer, xDiagram ); } } void ObjectHierarchy::createDataSeriesTree( ObjectHierarchy::tChildContainer & rOutDiagramSubContainer, const Reference< XDiagram > & xDiagram ) { Reference< XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY_THROW ); try { sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram ); Sequence< Reference< XCoordinateSystem > > aCooSysSeq( xCooSysCnt->getCoordinateSystems()); for( sal_Int32 nCooSysIdx=0; nCooSysIdx xCTCnt( aCooSysSeq[nCooSysIdx], uno::UNO_QUERY_THROW ); Sequence< Reference< XChartType > > aChartTypeSeq( xCTCnt->getChartTypes()); for( sal_Int32 nCTIdx=0; nCTIdx xChartType( aChartTypeSeq[nCTIdx] ); Reference< XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY_THROW ); Sequence< Reference< XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries() ); const sal_Int32 nNumberOfSeries = ChartTypeHelper::getNumberOfDisplayedSeries( xChartType, aSeriesSeq.getLength()); for( sal_Int32 nSeriesIdx=0; nSeriesIdx const & xSeries = aSeriesSeq[nSeriesIdx]; // data labels if( DataSeriesHelper::hasDataLabelsAtSeries( xSeries ) ) { OUString aChildParticle( ObjectIdentifier::getStringForType( OBJECTTYPE_DATA_LABELS ) + "=" ); aSeriesSubContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierForParticles( aSeriesParticle, aChildParticle ) ); } // Statistics if( ChartTypeHelper::isSupportingStatisticProperties( xChartType, nDimensionCount ) ) { Reference< chart2::XRegressionCurveContainer > xCurveCnt( xSeries, uno::UNO_QUERY ); if( xCurveCnt.is()) { Sequence< Reference< chart2::XRegressionCurve > > aCurves( xCurveCnt->getRegressionCurves()); for( sal_Int32 nCurveIdx=0; nCurveIdx xSeriesProp( xSeries, uno::UNO_QUERY ); Reference< beans::XPropertySet > xErrorBarProp; if( xSeriesProp.is() && (xSeriesProp->getPropertyValue( CHART_UNONAME_ERRORBAR_Y) >>= xErrorBarProp) && xErrorBarProp.is()) { sal_Int32 nStyle = css::chart::ErrorBarStyle::NONE; if( ( xErrorBarProp->getPropertyValue( "ErrorBarStyle") >>= nStyle ) && ( nStyle != css::chart::ErrorBarStyle::NONE ) ) { aSeriesSubContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierWithParent( OBJECTTYPE_DATA_ERRORS_Y, u"", aSeriesParticle ) ); } } if( xSeriesProp.is() && (xSeriesProp->getPropertyValue(CHART_UNONAME_ERRORBAR_X) >>= xErrorBarProp) && xErrorBarProp.is()) { sal_Int32 nStyle = css::chart::ErrorBarStyle::NONE; if( ( xErrorBarProp->getPropertyValue( "ErrorBarStyle") >>= nStyle ) && ( nStyle != css::chart::ErrorBarStyle::NONE ) ) { aSeriesSubContainer.emplace_back( ObjectIdentifier::createClassifiedIdentifierWithParent( OBJECTTYPE_DATA_ERRORS_X, u"", aSeriesParticle ) ); } } } } // Data Points // iterate over child shapes of legend and search for matching CIDs if( m_pExplicitValueProvider ) { Reference< container::XIndexAccess > xSeriesShapeContainer( m_pExplicitValueProvider->getShapeForCID( aSeriesOID.getObjectCID() ), uno::UNO_QUERY ); lcl_getChildOIDs( aSeriesSubContainer, xSeriesShapeContainer ); } if( ! aSeriesSubContainer.empty()) m_aChildMap[ aSeriesOID ] = aSeriesSubContainer; } } } } catch( const uno::Exception & ) { DBG_UNHANDLED_EXCEPTION("chart2"); } } void ObjectHierarchy::createAdditionalShapesTree( ObjectHierarchy::tChildContainer& rContainer ) { try { if ( m_pExplicitValueProvider ) { rtl::Reference xDrawPage( m_pExplicitValueProvider->getDrawModelWrapper()->getMainDrawPage() ); Reference< drawing::XShapes > xChartRoot( DrawModelWrapper::getChartRootShape( xDrawPage ) ); sal_Int32 nCount = xDrawPage->getCount(); for ( sal_Int32 i = 0; i < nCount; ++i ) { Reference< drawing::XShape > xShape; if ( xDrawPage->getByIndex( i ) >>= xShape ) { if ( xShape.is() && xShape != xChartRoot ) { rContainer.emplace_back( xShape ); } } } } } catch ( const uno::Exception& ) { DBG_UNHANDLED_EXCEPTION("chart2"); } } bool ObjectHierarchy::hasChildren( const ObjectIdentifier& rParent ) const { if ( rParent.isValid() ) { tChildMap::const_iterator aIt( m_aChildMap.find( rParent )); if( aIt != m_aChildMap.end()) return ! (aIt->second.empty()); } return false; } const ObjectHierarchy::tChildContainer & ObjectHierarchy::getChildren( const ObjectIdentifier& rParent ) const { if ( rParent.isValid() ) { tChildMap::const_iterator aIt( m_aChildMap.find( rParent )); if( aIt != m_aChildMap.end()) return aIt->second; } static const ObjectHierarchy::tChildContainer EMPTY; return EMPTY; } const ObjectHierarchy::tChildContainer & ObjectHierarchy::getSiblings( const ObjectIdentifier& rNode ) const { if ( rNode.isValid() && !ObjectHierarchy::isRootNode( rNode ) ) { for (auto const& child : m_aChildMap) { ObjectHierarchy::tChildContainer::const_iterator aElemIt( std::find( child.second.begin(), child.second.end(), rNode )); if( aElemIt != child.second.end()) return child.second; } } static const ObjectHierarchy::tChildContainer EMPTY; return EMPTY; } ObjectIdentifier ObjectHierarchy::getParentImpl( const ObjectIdentifier & rParentOID, const ObjectIdentifier & rOID ) const { // search children ObjectHierarchy::tChildContainer aChildren( getChildren( rParentOID )); ObjectHierarchy::tChildContainer::const_iterator aIt( std::find( aChildren.begin(), aChildren.end(), rOID )); // recursion end if( aIt != aChildren.end()) return rParentOID; for (auto const& child : aChildren) { // recursion ObjectIdentifier aTempParent( getParentImpl( child, rOID )); if ( aTempParent.isValid() ) { // exit on success return aTempParent; } } // exit on fail return ObjectIdentifier(); } ObjectIdentifier ObjectHierarchy::getParent( const ObjectIdentifier & rOID ) const { return getParentImpl( ObjectHierarchy::getRootNodeOID(), rOID ); } ObjectHierarchy::ObjectHierarchy( const rtl::Reference<::chart::ChartModel> & xChartDocument, ExplicitValueProvider * pExplicitValueProvider /* = 0 */, bool bFlattenDiagram /* = false */, bool bOrderingForElementSelector /* = false */) : m_pExplicitValueProvider( pExplicitValueProvider ), m_bFlattenDiagram( bFlattenDiagram ), m_bOrderingForElementSelector( bOrderingForElementSelector ) { createTree( xChartDocument ); // don't remember this helper to avoid access after lifetime m_pExplicitValueProvider = nullptr; } ObjectHierarchy::~ObjectHierarchy() {} ObjectIdentifier ObjectHierarchy::getRootNodeOID() { return ObjectIdentifier( "ROOT" ); } bool ObjectHierarchy::isRootNode( const ObjectIdentifier& rOID ) { return ( rOID == ObjectHierarchy::getRootNodeOID() ); } const ObjectHierarchy::tChildContainer & ObjectHierarchy::getTopLevelChildren() const { return getChildren( ObjectHierarchy::getRootNodeOID()); } sal_Int32 ObjectHierarchy::getIndexInParent( const ObjectIdentifier& rNode ) const { ObjectIdentifier aParentOID( getParent( rNode )); const tChildContainer & aChildren( getChildren( aParentOID ) ); sal_Int32 nIndex = 0; for (auto const& child : aChildren) { if ( child == rNode ) return nIndex; ++nIndex; } return -1; } ObjectKeyNavigation::ObjectKeyNavigation( const ObjectIdentifier & rCurrentOID, const rtl::Reference<::chart::ChartModel> & xChartDocument, ExplicitValueProvider * pExplicitValueProvider /* = 0 */ ) : m_aCurrentOID( rCurrentOID ), m_xChartDocument( xChartDocument ), m_pExplicitValueProvider( pExplicitValueProvider ) { if ( !m_aCurrentOID.isValid() ) { setCurrentSelection( ObjectHierarchy::getRootNodeOID() ); } } bool ObjectKeyNavigation::handleKeyEvent( const awt::KeyEvent & rEvent ) { bool bResult = false; switch( rEvent.KeyCode ) { case awt::Key::TAB: if( rEvent.Modifiers & awt::KeyModifier::SHIFT ) bResult = previous(); else bResult = next(); break; case awt::Key::HOME: bResult = first(); break; case awt::Key::END: bResult = last(); break; case awt::Key::F3: if( rEvent.Modifiers & awt::KeyModifier::SHIFT ) bResult = up(); else bResult = down(); break; case awt::Key::ESCAPE: setCurrentSelection( ObjectIdentifier() ); bResult = true; break; default: bResult = false; break; } return bResult; } void ObjectKeyNavigation::setCurrentSelection( const ObjectIdentifier& rOID ) { m_aCurrentOID = rOID; } bool ObjectKeyNavigation::first() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection() ) ); bool bResult = !aSiblings.empty(); if( bResult ) setCurrentSelection( aSiblings.front()); else bResult = veryFirst(); return bResult; } bool ObjectKeyNavigation::last() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection() ) ); bool bResult = !aSiblings.empty(); if( bResult ) setCurrentSelection( aSiblings.back()); else bResult = veryLast(); return bResult; } bool ObjectKeyNavigation::next() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection() ) ); bool bResult = !aSiblings.empty(); if( bResult ) { ObjectHierarchy::tChildContainer::const_iterator aIt( std::find( aSiblings.begin(), aSiblings.end(), getCurrentSelection())); assert(aIt != aSiblings.end()); if( ++aIt == aSiblings.end()) aIt = aSiblings.begin(); setCurrentSelection( *aIt ); } else bResult = veryFirst(); return bResult; } bool ObjectKeyNavigation::previous() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection())); bool bResult = !aSiblings.empty(); if( bResult ) { ObjectHierarchy::tChildContainer::const_iterator aIt( std::find( aSiblings.begin(), aSiblings.end(), getCurrentSelection())); OSL_ASSERT( aIt != aSiblings.end()); if( aIt == aSiblings.begin()) aIt = aSiblings.end(); --aIt; setCurrentSelection( *aIt ); } else bResult = veryLast(); return bResult; } bool ObjectKeyNavigation::up() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); bool bResult = !ObjectHierarchy::isRootNode( getCurrentSelection()); if( bResult ) setCurrentSelection( aHierarchy.getParent( getCurrentSelection())); return bResult; } bool ObjectKeyNavigation::down() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); bool bResult = aHierarchy.hasChildren( getCurrentSelection()); if( bResult ) { ObjectHierarchy::tChildContainer aChildren = aHierarchy.getChildren( getCurrentSelection()); OSL_ASSERT( !aChildren.empty()); setCurrentSelection( aChildren.front()); } return bResult; } bool ObjectKeyNavigation::veryFirst() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); ObjectHierarchy::tChildContainer aChildren( aHierarchy.getTopLevelChildren()); bool bResult = !aChildren.empty(); if( bResult ) setCurrentSelection( aChildren.front()); return bResult; } bool ObjectKeyNavigation::veryLast() { ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider ); ObjectHierarchy::tChildContainer aChildren( aHierarchy.getTopLevelChildren()); bool bResult = !aChildren.empty(); if( bResult ) setCurrentSelection( aChildren.back()); return bResult; } } // namespace chart /* vim:set shiftwidth=4 softtabstop=4 expandtab: */