summaryrefslogtreecommitdiffstats
path: root/svl/source/items/stylepool.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'svl/source/items/stylepool.cxx')
-rw-r--r--svl/source/items/stylepool.cxx468
1 files changed, 468 insertions, 0 deletions
diff --git a/svl/source/items/stylepool.cxx b/svl/source/items/stylepool.cxx
new file mode 100644
index 000000000..dc992a6ed
--- /dev/null
+++ b/svl/source/items/stylepool.cxx
@@ -0,0 +1,468 @@
+/* -*- 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 <svl/stylepool.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/itempool.hxx>
+#include <tools/debug.hxx>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <optional>
+#include <vector>
+
+namespace {
+ /** A "Node" represents a subset of inserted SfxItemSets
+ * The root node represents the empty set
+ * The other nodes contain a SfxPoolItem and represents an item set which contains their
+ * pool item and the pool items of their parents.
+ */
+ class Node
+ {
+ std::vector<std::unique_ptr<Node>> mChildren; // child nodes, create by findChildNode(..)
+ // container of shared pointers of inserted item sets; for non-poolable
+ // items more than one item set is needed
+ std::vector< std::shared_ptr<SfxItemSet> > maItemSet;
+ std::unique_ptr<const SfxPoolItem> mpItem; // my pool item
+ Node *mpUpper; // if I'm a child node that's my parent node
+ // #i86923#
+ const bool mbIsItemIgnorable;
+ public:
+ // #i86923#
+ Node() // root node Ctor
+ : mpUpper( nullptr ),
+ mbIsItemIgnorable( false )
+ {}
+ Node( const SfxPoolItem& rItem, Node* pParent, const bool bIgnorable ) // child node Ctor
+ : mpItem( rItem.Clone() ),
+ mpUpper( pParent ),
+ mbIsItemIgnorable( bIgnorable )
+ {}
+ // #i86923#
+ bool hasItemSet( const bool bCheckUsage ) const;
+ // #i87808#
+ std::shared_ptr<SfxItemSet> const & getItemSet() const
+ {
+ return maItemSet.back();
+ }
+ std::shared_ptr<SfxItemSet> const & getUsedOrLastAddedItemSet() const;
+ void setItemSet( const SfxItemSet& rSet ){ maItemSet.push_back( std::shared_ptr<SfxItemSet>( rSet.Clone() ) ); }
+ // #i86923#
+ Node* findChildNode( const SfxPoolItem& rItem,
+ const bool bIsItemIgnorable );
+ Node* nextItemSet( Node const * pLast,
+ const bool bSkipUnusedItemSet,
+ const bool bSkipIgnorable );
+ // #i86923#
+ bool hasIgnorableChildren( const bool bCheckUsage ) const;
+ std::shared_ptr<SfxItemSet> getItemSetOfIgnorableChild(
+ const bool bSkipUnusedItemSets ) const;
+ };
+
+ // #i87808#
+ std::shared_ptr<SfxItemSet> const & Node::getUsedOrLastAddedItemSet() const
+ {
+ auto aIter = std::find_if(maItemSet.rbegin(), maItemSet.rend(),
+ [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
+
+ if (aIter != maItemSet.rend())
+ return *aIter;
+
+ return maItemSet.back();
+ }
+
+ // #i86923#
+ bool Node::hasItemSet( const bool bCheckUsage ) const
+ {
+ bool bHasItemSet = false;
+
+ if ( !maItemSet.empty())
+ {
+ if ( bCheckUsage )
+ {
+ bHasItemSet = std::any_of(maItemSet.rbegin(), maItemSet.rend(),
+ [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
+ }
+ else
+ {
+ bHasItemSet = true;
+ }
+ }
+ return bHasItemSet;
+ }
+
+ // #i86923#
+ Node* Node::findChildNode( const SfxPoolItem& rItem,
+ const bool bIsItemIgnorable )
+ {
+ for( auto const & rChild : mChildren )
+ {
+ if( rItem.Which() == rChild->mpItem->Which() &&
+ rItem == *rChild->mpItem )
+ return rChild.get();
+ }
+ // #i86923#
+ auto pNextNode = new Node( rItem, this, bIsItemIgnorable );
+ mChildren.emplace_back( pNextNode );
+ return pNextNode;
+ }
+
+ /**
+ * Find the next node which has a SfxItemSet.
+ * The input parameter pLast has a sophisticated meaning:
+ * downstairs only:
+ * pLast == 0 => scan your children and their children
+ * but neither your parents neither your siblings
+ * downstairs and upstairs:
+ * pLast == this => scan your children, their children,
+ * the children of your parent behind you, and so on
+ * partial downstairs and upstairs
+ * pLast != 0 && pLast != this => scan your children behind the given children,
+ * the children of your parent behind you and so on.
+ *
+ * OD 2008-03-11 #i86923#
+ * introduce parameters <bSkipUnusedItemSets> and <bSkipIgnorable>
+ * and its handling.
+ */
+ Node* Node::nextItemSet( Node const * pLast,
+ const bool bSkipUnusedItemSets,
+ const bool bSkipIgnorable )
+ {
+ // Searching downstairs
+ auto aIter = mChildren.begin();
+ // For pLast == 0 and pLast == this all children are of interest
+ // for another pLast the search starts behind pLast...
+ if( pLast && pLast != this )
+ {
+ aIter = std::find_if( mChildren.begin(), mChildren.end(),
+ [&] (std::unique_ptr<Node> const &p) { return p.get() == pLast; });
+ if( aIter != mChildren.end() )
+ ++aIter;
+ }
+ Node *pNext = nullptr;
+ while( aIter != mChildren.end() )
+ {
+ // #i86923#
+ if ( bSkipIgnorable && (*aIter)->mbIsItemIgnorable )
+ {
+ ++aIter;
+ continue;
+ }
+ pNext = aIter->get();
+ // #i86923#
+ if ( pNext->hasItemSet( bSkipUnusedItemSets ) )
+ {
+ return pNext;
+ }
+ if ( bSkipIgnorable &&
+ pNext->hasIgnorableChildren( bSkipUnusedItemSets ) )
+ {
+ return pNext;
+ }
+ pNext = pNext->nextItemSet( nullptr, bSkipUnusedItemSets, bSkipIgnorable ); // 0 => downstairs only
+ if( pNext )
+ return pNext;
+ ++aIter;
+ }
+ // Searching upstairs
+ if( pLast && mpUpper )
+ {
+ // #i86923#
+ pNext = mpUpper->nextItemSet( this, bSkipUnusedItemSets, bSkipIgnorable );
+ }
+ return pNext;
+ }
+
+ // #i86923#
+ bool Node::hasIgnorableChildren( const bool bCheckUsage ) const
+ {
+ return std::any_of(mChildren.begin(), mChildren.end(),
+ [&bCheckUsage](const std::unique_ptr<Node>& rxChild) {
+ Node* pChild = rxChild.get();
+ return pChild->mbIsItemIgnorable &&
+ (!bCheckUsage ||
+ ( pChild->hasItemSet( bCheckUsage /* == true */ ) ||
+ pChild->hasIgnorableChildren( bCheckUsage /* == true */ ) ));
+ });
+ }
+
+ std::shared_ptr<SfxItemSet> Node::getItemSetOfIgnorableChild(
+ const bool bSkipUnusedItemSets ) const
+ {
+ DBG_ASSERT( hasIgnorableChildren( bSkipUnusedItemSets ),
+ "<Node::getItemSetOfIgnorableChild> - node has no ignorable children" );
+
+ for( const auto& rxChild : mChildren )
+ {
+ Node* pChild = rxChild.get();
+ if ( pChild->mbIsItemIgnorable )
+ {
+ if ( pChild->hasItemSet( bSkipUnusedItemSets ) )
+ {
+ return pChild->getUsedOrLastAddedItemSet();
+ }
+ else
+ {
+ pChild = pChild->nextItemSet( nullptr, bSkipUnusedItemSets, false );
+ if ( pChild )
+ {
+ return pChild->getUsedOrLastAddedItemSet();
+ }
+ }
+ }
+ }
+
+ std::shared_ptr<SfxItemSet> pReturn;
+ return pReturn;
+ }
+
+ class Iterator : public IStylePoolIteratorAccess
+ {
+ std::map< const SfxItemSet*, Node >& mrRoot;
+ std::map< const SfxItemSet*, Node >::iterator mpCurrNode;
+ Node* mpNode;
+ const bool mbSkipUnusedItemSets;
+ const bool mbSkipIgnorable;
+ /// List of item set parents, ordered by their name.
+ std::vector<const SfxItemSet*> maParents;
+ /// The iterator's current position.
+ std::vector<const SfxItemSet*>::iterator mpCurrParent;
+ public:
+ // #i86923#
+ Iterator( std::map< const SfxItemSet*, Node >& rR,
+ const bool bSkipUnusedItemSets,
+ const bool bSkipIgnorable,
+ const std::map< const SfxItemSet*, OUString>& rParentNames )
+ : mrRoot( rR ),
+ mpNode(nullptr),
+ mbSkipUnusedItemSets( bSkipUnusedItemSets ),
+ mbSkipIgnorable( bSkipIgnorable )
+ {
+ // Collect the parent pointers into a vector we can sort.
+ for (const auto& rParent : mrRoot)
+ maParents.push_back(rParent.first);
+
+ // Sort the parents using their name, if they have one.
+ if (!rParentNames.empty())
+ {
+ std::stable_sort(maParents.begin(), maParents.end(),
+ [&rParentNames](const SfxItemSet* pA, const SfxItemSet* pB) {
+ OUString aA;
+ OUString aB;
+ auto it = rParentNames.find(pA);
+ if (it != rParentNames.end())
+ aA = it->second;
+ it = rParentNames.find(pB);
+ if (it != rParentNames.end())
+ aB = it->second;
+ return aA < aB;
+ });
+ }
+
+ // Start the iteration.
+ mpCurrParent = maParents.begin();
+ if (mpCurrParent != maParents.end())
+ mpCurrNode = mrRoot.find(*mpCurrParent);
+ }
+ virtual std::shared_ptr<SfxItemSet> getNext() override;
+ };
+
+ std::shared_ptr<SfxItemSet> Iterator::getNext()
+ {
+ std::shared_ptr<SfxItemSet> pReturn;
+ while( mpNode || mpCurrParent != maParents.end() )
+ {
+ if( !mpNode )
+ {
+ mpNode = &mpCurrNode->second;
+ // Perform the actual increment.
+ ++mpCurrParent;
+ if (mpCurrParent != maParents.end())
+ mpCurrNode = mrRoot.find(*mpCurrParent);
+ // #i86923#
+ if ( mpNode->hasItemSet( mbSkipUnusedItemSets ) )
+ {
+ // #i87808#
+ return mpNode->getUsedOrLastAddedItemSet();
+ }
+ }
+ // #i86923#
+ mpNode = mpNode->nextItemSet( mpNode, mbSkipUnusedItemSets, mbSkipIgnorable );
+ if ( mpNode && mpNode->hasItemSet( mbSkipUnusedItemSets ) )
+ {
+ // #i87808#
+ return mpNode->getUsedOrLastAddedItemSet();
+ }
+ if ( mbSkipIgnorable &&
+ mpNode && mpNode->hasIgnorableChildren( mbSkipUnusedItemSets ) )
+ {
+ return mpNode->getItemSetOfIgnorableChild( mbSkipUnusedItemSets );
+ }
+ }
+ return pReturn;
+ }
+
+}
+
+/**
+ * This static method creates a unique name from a shared pointer to a SfxItemSet
+ * The name is the memory address of the SfxItemSet itself.
+ */
+OUString StylePool::nameOf( const std::shared_ptr<SfxItemSet>& pSet )
+{
+ return OUString::number( reinterpret_cast<sal_IntPtr>( pSet.get() ), 16 );
+}
+
+/**
+ * class StylePoolImpl organized a tree-structure where every node represents a SfxItemSet.
+ * The insertItemSet method adds a SfxItemSet into the tree if necessary and returns a shared_ptr
+ * to a copy of the SfxItemSet.
+ * The aRoot-Node represents an empty SfxItemSet.
+ */
+class StylePoolImpl
+{
+private:
+ std::map< const SfxItemSet*, Node > maRoot;
+ /// Names of maRoot keys.
+ std::map< const SfxItemSet*, OUString> maParentNames;
+ // #i86923#
+ std::unique_ptr<SfxItemSet> mpIgnorableItems;
+#ifdef DEBUG
+ sal_Int32 mnCount;
+#endif
+public:
+ // #i86923#
+ explicit StylePoolImpl( SfxItemSet const * pIgnorableItems )
+ :
+#ifdef DEBUG
+ mnCount(0),
+#endif
+ mpIgnorableItems( pIgnorableItems != nullptr
+ ? pIgnorableItems->Clone( false )
+ : nullptr )
+ {
+ DBG_ASSERT( !pIgnorableItems || !pIgnorableItems->Count(),
+ "<StylePoolImpl::StylePoolImpl(..)> - misusage: item set for ignorable item should be empty. Please correct usage." );
+ DBG_ASSERT( !mpIgnorableItems || !mpIgnorableItems->Count(),
+ "<StylePoolImpl::StylePoolImpl(..)> - <SfxItemSet::Clone( sal_False )> does not work as expected - <mpIgnorableItems> is not empty." );
+ }
+
+ std::shared_ptr<SfxItemSet> insertItemSet( const SfxItemSet& rSet, const OUString* pParentName = nullptr );
+
+ // #i86923#
+ std::unique_ptr<IStylePoolIteratorAccess> createIterator( bool bSkipUnusedItemSets,
+ bool bSkipIgnorableItems );
+};
+
+
+std::shared_ptr<SfxItemSet> StylePoolImpl::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
+{
+ bool bNonPoolable = false;
+ Node* pCurNode = &maRoot[ rSet.GetParent() ];
+ if (pParentName)
+ maParentNames[ rSet.GetParent() ] = *pParentName;
+ SfxItemIter aIter( rSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ // Every SfxPoolItem in the SfxItemSet causes a step deeper into the tree,
+ // a complete empty SfxItemSet would stay at the root node.
+ // #i86923# insert ignorable items to the tree leaves.
+ std::optional<SfxItemSet> xFoundIgnorableItems;
+ if ( mpIgnorableItems )
+ {
+ xFoundIgnorableItems.emplace( *mpIgnorableItems );
+ }
+ while( pItem )
+ {
+ if( !rSet.GetPool()->IsItemPoolable(pItem->Which() ) )
+ bNonPoolable = true;
+ if (!xFoundIgnorableItems || (xFoundIgnorableItems->Put(*pItem) == nullptr))
+ {
+ pCurNode = pCurNode->findChildNode( *pItem, false );
+ }
+ pItem = aIter.NextItem();
+ }
+ if ( xFoundIgnorableItems && xFoundIgnorableItems->Count() > 0 )
+ {
+ SfxItemIter aIgnorableItemsIter( *xFoundIgnorableItems );
+ pItem = aIgnorableItemsIter.GetCurItem();
+ while( pItem )
+ {
+ if( !rSet.GetPool()->IsItemPoolable(pItem->Which() ) )
+ bNonPoolable = true;
+ pCurNode = pCurNode->findChildNode( *pItem, true );
+ pItem = aIgnorableItemsIter.NextItem();
+ }
+ }
+ // Every leaf node represents an inserted item set, but "non-leaf" nodes represents subsets
+ // of inserted itemsets.
+ // These nodes could have but does not need to have a shared_ptr to an item set.
+ if( !pCurNode->hasItemSet( false ) )
+ {
+ pCurNode->setItemSet( rSet );
+ bNonPoolable = false; // to avoid a double insertion
+#ifdef DEBUG
+ ++mnCount;
+#endif
+ }
+ // If rSet contains at least one non poolable item, a new itemset has to be inserted
+ if( bNonPoolable )
+ pCurNode->setItemSet( rSet );
+#ifdef DEBUG
+ {
+ sal_Int32 nCheck = -1;
+ std::unique_ptr<IStylePoolIteratorAccess> pIter = createIterator(false,false);
+ std::shared_ptr<SfxItemSet> pTemp;
+ do
+ {
+ ++nCheck;
+ pTemp = pIter->getNext();
+ } while( pTemp.get() );
+ DBG_ASSERT( mnCount == nCheck, "Wrong counting");
+ }
+#endif
+ return pCurNode->getItemSet();
+}
+
+// #i86923#
+std::unique_ptr<IStylePoolIteratorAccess> StylePoolImpl::createIterator( bool bSkipUnusedItemSets,
+ bool bSkipIgnorableItems )
+{
+ return std::make_unique<Iterator>( maRoot, bSkipUnusedItemSets, bSkipIgnorableItems, maParentNames );
+}
+// Ctor, Dtor and redirected methods of class StylePool, nearly inline ;-)
+
+// #i86923#
+StylePool::StylePool( SfxItemSet const * pIgnorableItems )
+ : pImpl( new StylePoolImpl( pIgnorableItems ) )
+{}
+
+std::shared_ptr<SfxItemSet> StylePool::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
+{ return pImpl->insertItemSet( rSet, pParentName ); }
+
+// #i86923#
+std::unique_ptr<IStylePoolIteratorAccess> StylePool::createIterator( const bool bSkipUnusedItemSets,
+ const bool bSkipIgnorableItems )
+{
+ return pImpl->createIterator( bSkipUnusedItemSets, bSkipIgnorableItems );
+}
+
+StylePool::~StylePool()
+{}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */