1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <svl/stylepool.hxx>
21 #include <svl/itemiter.hxx>
22 #include <svl/itempool.hxx>
23 #include <tools/debug.hxx>
24 #include <algorithm>
25 #include <map>
26 #include <memory>
27 #include <vector>
28
29 namespace {
30 /** A "Node" represents a subset of inserted SfxItemSets
31 * The root node represents the empty set
32 * The other nodes contain a SfxPoolItem and represents an item set which contains their
33 * pool item and the pool items of their parents.
34 */
35 class Node
36 {
37 std::vector<std::unique_ptr<Node>> mChildren; // child nodes, create by findChildNode(..)
38 // container of shared pointers of inserted item sets; for non-poolable
39 // items more than one item set is needed
40 std::vector< std::shared_ptr<SfxItemSet> > maItemSet;
41 std::unique_ptr<const SfxPoolItem> mpItem; // my pool item
42 Node *mpUpper; // if I'm a child node that's my parent node
43 // #i86923#
44 const bool mbIsItemIgnorable;
45 public:
46 // #i86923#
Node()47 Node() // root node Ctor
48 : mChildren(),
49 maItemSet(),
50 mpUpper( nullptr ),
51 mbIsItemIgnorable( false )
52 {}
Node(const SfxPoolItem & rItem,Node * pParent,const bool bIgnorable)53 Node( const SfxPoolItem& rItem, Node* pParent, const bool bIgnorable ) // child node Ctor
54 : mChildren(),
55 maItemSet(),
56 mpItem( rItem.Clone() ),
57 mpUpper( pParent ),
58 mbIsItemIgnorable( bIgnorable )
59 {}
60 // #i86923#
61 bool hasItemSet( const bool bCheckUsage ) const;
62 // #i87808#
getItemSet() const63 std::shared_ptr<SfxItemSet> const & getItemSet() const
64 {
65 return maItemSet.back();
66 }
67 std::shared_ptr<SfxItemSet> const & getUsedOrLastAddedItemSet() const;
setItemSet(const SfxItemSet & rSet)68 void setItemSet( const SfxItemSet& rSet ){ maItemSet.push_back( std::shared_ptr<SfxItemSet>( rSet.Clone() ) ); }
69 // #i86923#
70 Node* findChildNode( const SfxPoolItem& rItem,
71 const bool bIsItemIgnorable );
72 Node* nextItemSet( Node const * pLast,
73 const bool bSkipUnusedItemSet,
74 const bool bSkipIgnorable );
75 // #i86923#
76 bool hasIgnorableChildren( const bool bCheckUsage ) const;
77 std::shared_ptr<SfxItemSet> getItemSetOfIgnorableChild(
78 const bool bSkipUnusedItemSets ) const;
79 };
80
81 // #i87808#
getUsedOrLastAddedItemSet() const82 std::shared_ptr<SfxItemSet> const & Node::getUsedOrLastAddedItemSet() const
83 {
84 auto aIter = std::find_if(maItemSet.rbegin(), maItemSet.rend(),
85 [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
86
87 if (aIter != maItemSet.rend())
88 return *aIter;
89
90 return maItemSet.back();
91 }
92
93 // #i86923#
hasItemSet(const bool bCheckUsage) const94 bool Node::hasItemSet( const bool bCheckUsage ) const
95 {
96 bool bHasItemSet = false;
97
98 if ( !maItemSet.empty())
99 {
100 if ( bCheckUsage )
101 {
102 bHasItemSet = std::any_of(maItemSet.rbegin(), maItemSet.rend(),
103 [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
104 }
105 else
106 {
107 bHasItemSet = true;
108 }
109 }
110 return bHasItemSet;
111 }
112
113 // #i86923#
findChildNode(const SfxPoolItem & rItem,const bool bIsItemIgnorable)114 Node* Node::findChildNode( const SfxPoolItem& rItem,
115 const bool bIsItemIgnorable )
116 {
117 for( auto const & rChild : mChildren )
118 {
119 if( rItem.Which() == rChild->mpItem->Which() &&
120 rItem == *rChild->mpItem )
121 return rChild.get();
122 }
123 // #i86923#
124 auto pNextNode = new Node( rItem, this, bIsItemIgnorable );
125 mChildren.emplace_back( pNextNode );
126 return pNextNode;
127 }
128
129 /**
130 * Find the next node which has a SfxItemSet.
131 * The input parameter pLast has a sophisticated meaning:
132 * downstairs only:
133 * pLast == 0 => scan your children and their children
134 * but neither your parents neither your siblings
135 * downstairs and upstairs:
136 * pLast == this => scan your children, their children,
137 * the children of your parent behind you, and so on
138 * partial downstairs and upstairs
139 * pLast != 0 && pLast != this => scan your children behind the given children,
140 * the children of your parent behind you and so on.
141 *
142 * OD 2008-03-11 #i86923#
143 * introduce parameters <bSkipUnusedItemSets> and <bSkipIgnorable>
144 * and its handling.
145 */
nextItemSet(Node const * pLast,const bool bSkipUnusedItemSets,const bool bSkipIgnorable)146 Node* Node::nextItemSet( Node const * pLast,
147 const bool bSkipUnusedItemSets,
148 const bool bSkipIgnorable )
149 {
150 // Searching downstairs
151 auto aIter = mChildren.begin();
152 // For pLast == 0 and pLast == this all children are of interest
153 // for another pLast the search starts behind pLast...
154 if( pLast && pLast != this )
155 {
156 aIter = std::find_if( mChildren.begin(), mChildren.end(),
157 [&] (std::unique_ptr<Node> const &p) { return p.get() == pLast; });
158 if( aIter != mChildren.end() )
159 ++aIter;
160 }
161 Node *pNext = nullptr;
162 while( aIter != mChildren.end() )
163 {
164 // #i86923#
165 if ( bSkipIgnorable && (*aIter)->mbIsItemIgnorable )
166 {
167 ++aIter;
168 continue;
169 }
170 pNext = aIter->get();
171 // #i86923#
172 if ( pNext->hasItemSet( bSkipUnusedItemSets ) )
173 {
174 return pNext;
175 }
176 if ( bSkipIgnorable &&
177 pNext->hasIgnorableChildren( bSkipUnusedItemSets ) )
178 {
179 return pNext;
180 }
181 pNext = pNext->nextItemSet( nullptr, bSkipUnusedItemSets, bSkipIgnorable ); // 0 => downstairs only
182 if( pNext )
183 return pNext;
184 ++aIter;
185 }
186 // Searching upstairs
187 if( pLast && mpUpper )
188 {
189 // #i86923#
190 pNext = mpUpper->nextItemSet( this, bSkipUnusedItemSets, bSkipIgnorable );
191 }
192 return pNext;
193 }
194
195 // #i86923#
hasIgnorableChildren(const bool bCheckUsage) const196 bool Node::hasIgnorableChildren( const bool bCheckUsage ) const
197 {
198 return std::any_of(mChildren.begin(), mChildren.end(),
199 [&bCheckUsage](const std::unique_ptr<Node>& rxChild) {
200 Node* pChild = rxChild.get();
201 return pChild->mbIsItemIgnorable &&
202 (!bCheckUsage ||
203 ( pChild->hasItemSet( bCheckUsage /* == true */ ) ||
204 pChild->hasIgnorableChildren( bCheckUsage /* == true */ ) ));
205 });
206 }
207
getItemSetOfIgnorableChild(const bool bSkipUnusedItemSets) const208 std::shared_ptr<SfxItemSet> Node::getItemSetOfIgnorableChild(
209 const bool bSkipUnusedItemSets ) const
210 {
211 DBG_ASSERT( hasIgnorableChildren( bSkipUnusedItemSets ),
212 "<Node::getItemSetOfIgnorableChild> - node has no ignorable children" );
213
214 for( const auto& rxChild : mChildren )
215 {
216 Node* pChild = rxChild.get();
217 if ( pChild->mbIsItemIgnorable )
218 {
219 if ( pChild->hasItemSet( bSkipUnusedItemSets ) )
220 {
221 return pChild->getUsedOrLastAddedItemSet();
222 }
223 else
224 {
225 pChild = pChild->nextItemSet( nullptr, bSkipUnusedItemSets, false );
226 if ( pChild )
227 {
228 return pChild->getUsedOrLastAddedItemSet();
229 }
230 }
231 }
232 }
233
234 std::shared_ptr<SfxItemSet> pReturn;
235 return pReturn;
236 }
237
238 class Iterator : public IStylePoolIteratorAccess
239 {
240 std::map< const SfxItemSet*, Node >& mrRoot;
241 std::map< const SfxItemSet*, Node >::iterator mpCurrNode;
242 Node* mpNode;
243 const bool mbSkipUnusedItemSets;
244 const bool mbSkipIgnorable;
245 /// List of item set parents, ordered by their name.
246 std::vector<const SfxItemSet*> maParents;
247 /// The iterator's current position.
248 std::vector<const SfxItemSet*>::iterator mpCurrParent;
249 public:
250 // #i86923#
Iterator(std::map<const SfxItemSet *,Node> & rR,const bool bSkipUnusedItemSets,const bool bSkipIgnorable,const std::map<const SfxItemSet *,OUString> & rParentNames)251 Iterator( std::map< const SfxItemSet*, Node >& rR,
252 const bool bSkipUnusedItemSets,
253 const bool bSkipIgnorable,
254 const std::map< const SfxItemSet*, OUString>& rParentNames )
255 : mrRoot( rR ),
256 mpNode(nullptr),
257 mbSkipUnusedItemSets( bSkipUnusedItemSets ),
258 mbSkipIgnorable( bSkipIgnorable )
259 {
260 // Collect the parent pointers into a vector we can sort.
261 for (const auto& rParent : mrRoot)
262 maParents.push_back(rParent.first);
263
264 // Sort the parents using their name, if they have one.
265 if (!rParentNames.empty())
266 {
267 std::sort(maParents.begin(), maParents.end(),
268 [&rParentNames](const SfxItemSet* pA, const SfxItemSet* pB) {
269 OUString aA;
270 OUString aB;
271 auto it = rParentNames.find(pA);
272 if (it != rParentNames.end())
273 aA = it->second;
274 it = rParentNames.find(pB);
275 if (it != rParentNames.end())
276 aB = it->second;
277 return aA < aB;
278 });
279 }
280
281 // Start the iteration.
282 mpCurrParent = maParents.begin();
283 if (mpCurrParent != maParents.end())
284 mpCurrNode = mrRoot.find(*mpCurrParent);
285 }
286 virtual std::shared_ptr<SfxItemSet> getNext() override;
287 };
288
getNext()289 std::shared_ptr<SfxItemSet> Iterator::getNext()
290 {
291 std::shared_ptr<SfxItemSet> pReturn;
292 while( mpNode || mpCurrParent != maParents.end() )
293 {
294 if( !mpNode )
295 {
296 mpNode = &mpCurrNode->second;
297 // Perform the actual increment.
298 ++mpCurrParent;
299 if (mpCurrParent != maParents.end())
300 mpCurrNode = mrRoot.find(*mpCurrParent);
301 // #i86923#
302 if ( mpNode->hasItemSet( mbSkipUnusedItemSets ) )
303 {
304 // #i87808#
305 return mpNode->getUsedOrLastAddedItemSet();
306 }
307 }
308 // #i86923#
309 mpNode = mpNode->nextItemSet( mpNode, mbSkipUnusedItemSets, mbSkipIgnorable );
310 if ( mpNode && mpNode->hasItemSet( mbSkipUnusedItemSets ) )
311 {
312 // #i87808#
313 return mpNode->getUsedOrLastAddedItemSet();
314 }
315 if ( mbSkipIgnorable &&
316 mpNode && mpNode->hasIgnorableChildren( mbSkipUnusedItemSets ) )
317 {
318 return mpNode->getItemSetOfIgnorableChild( mbSkipUnusedItemSets );
319 }
320 }
321 return pReturn;
322 }
323
324 }
325
326 /**
327 * This static method creates a unique name from a shared pointer to a SfxItemSet
328 * The name is the memory address of the SfxItemSet itself.
329 */
nameOf(const std::shared_ptr<SfxItemSet> & pSet)330 OUString StylePool::nameOf( const std::shared_ptr<SfxItemSet>& pSet )
331 {
332 return OUString::number( reinterpret_cast<sal_IntPtr>( pSet.get() ), 16 );
333 }
334
335 /**
336 * class StylePoolImpl organized a tree-structure where every node represents a SfxItemSet.
337 * The insertItemSet method adds a SfxItemSet into the tree if necessary and returns a shared_ptr
338 * to a copy of the SfxItemSet.
339 * The aRoot-Node represents an empty SfxItemSet.
340 */
341 class StylePoolImpl
342 {
343 private:
344 std::map< const SfxItemSet*, Node > maRoot;
345 /// Names of maRoot keys.
346 std::map< const SfxItemSet*, OUString> maParentNames;
347 // #i86923#
348 std::unique_ptr<SfxItemSet> mpIgnorableItems;
349 #ifdef DEBUG
350 sal_Int32 mnCount;
351 #endif
352 public:
353 // #i86923#
StylePoolImpl(SfxItemSet const * pIgnorableItems)354 explicit StylePoolImpl( SfxItemSet const * pIgnorableItems )
355 : maRoot(),
356 #ifdef DEBUG
357 mnCount(0),
358 #endif
359 mpIgnorableItems( pIgnorableItems != nullptr
360 ? pIgnorableItems->Clone( false )
361 : nullptr )
362 {
363 DBG_ASSERT( !pIgnorableItems || !pIgnorableItems->Count(),
364 "<StylePoolImpl::StylePoolImpl(..)> - misusage: item set for ignorable item should be empty. Please correct usage." );
365 DBG_ASSERT( !mpIgnorableItems || !mpIgnorableItems->Count(),
366 "<StylePoolImpl::StylePoolImpl(..)> - <SfxItemSet::Clone( sal_False )> does not work as excepted - <mpIgnorableItems> is not empty." );
367 }
368
369 std::shared_ptr<SfxItemSet> insertItemSet( const SfxItemSet& rSet, const OUString* pParentName = nullptr );
370
371 // #i86923#
372 std::unique_ptr<IStylePoolIteratorAccess> createIterator( bool bSkipUnusedItemSets,
373 bool bSkipIgnorableItems );
374 };
375
376
insertItemSet(const SfxItemSet & rSet,const OUString * pParentName)377 std::shared_ptr<SfxItemSet> StylePoolImpl::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
378 {
379 bool bNonPoolable = false;
380 Node* pCurNode = &maRoot[ rSet.GetParent() ];
381 if (pParentName)
382 maParentNames[ rSet.GetParent() ] = *pParentName;
383 SfxItemIter aIter( rSet );
384 const SfxPoolItem* pItem = aIter.GetCurItem();
385 // Every SfxPoolItem in the SfxItemSet causes a step deeper into the tree,
386 // a complete empty SfxItemSet would stay at the root node.
387 // #i86923# insert ignorable items to the tree leaves.
388 std::unique_ptr<SfxItemSet> xFoundIgnorableItems;
389 if ( mpIgnorableItems )
390 {
391 xFoundIgnorableItems.reset( new SfxItemSet( *mpIgnorableItems ) );
392 }
393 while( pItem )
394 {
395 if( !rSet.GetPool()->IsItemPoolable(pItem->Which() ) )
396 bNonPoolable = true;
397 if (!xFoundIgnorableItems || (xFoundIgnorableItems->Put(*pItem) == nullptr))
398 {
399 pCurNode = pCurNode->findChildNode( *pItem, false );
400 }
401 pItem = aIter.NextItem();
402 }
403 if ( xFoundIgnorableItems.get() &&
404 xFoundIgnorableItems->Count() > 0 )
405 {
406 SfxItemIter aIgnorableItemsIter( *xFoundIgnorableItems );
407 pItem = aIgnorableItemsIter.GetCurItem();
408 while( pItem )
409 {
410 if( !rSet.GetPool()->IsItemPoolable(pItem->Which() ) )
411 bNonPoolable = true;
412 pCurNode = pCurNode->findChildNode( *pItem, true );
413 pItem = aIgnorableItemsIter.NextItem();
414 }
415 }
416 // Every leaf node represents an inserted item set, but "non-leaf" nodes represents subsets
417 // of inserted itemsets.
418 // These nodes could have but does not need to have a shared_ptr to an item set.
419 if( !pCurNode->hasItemSet( false ) )
420 {
421 pCurNode->setItemSet( rSet );
422 bNonPoolable = false; // to avoid a double insertion
423 #ifdef DEBUG
424 ++mnCount;
425 #endif
426 }
427 // If rSet contains at least one non poolable item, a new itemset has to be inserted
428 if( bNonPoolable )
429 pCurNode->setItemSet( rSet );
430 #ifdef DEBUG
431 {
432 sal_Int32 nCheck = -1;
433 std::unique_ptr<IStylePoolIteratorAccess> pIter = createIterator(false,false);
434 std::shared_ptr<SfxItemSet> pTemp;
435 do
436 {
437 ++nCheck;
438 pTemp = pIter->getNext();
439 } while( pTemp.get() );
440 DBG_ASSERT( mnCount == nCheck, "Wrong counting");
441 }
442 #endif
443 return pCurNode->getItemSet();
444 }
445
446 // #i86923#
createIterator(bool bSkipUnusedItemSets,bool bSkipIgnorableItems)447 std::unique_ptr<IStylePoolIteratorAccess> StylePoolImpl::createIterator( bool bSkipUnusedItemSets,
448 bool bSkipIgnorableItems )
449 {
450 return std::make_unique<Iterator>( maRoot, bSkipUnusedItemSets, bSkipIgnorableItems, maParentNames );
451 }
452 // Ctor, Dtor and redirected methods of class StylePool, nearly inline ;-)
453
454 // #i86923#
StylePool(SfxItemSet const * pIgnorableItems)455 StylePool::StylePool( SfxItemSet const * pIgnorableItems )
456 : pImpl( new StylePoolImpl( pIgnorableItems ) )
457 {}
458
insertItemSet(const SfxItemSet & rSet,const OUString * pParentName)459 std::shared_ptr<SfxItemSet> StylePool::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
460 { return pImpl->insertItemSet( rSet, pParentName ); }
461
462 // #i86923#
createIterator(const bool bSkipUnusedItemSets,const bool bSkipIgnorableItems)463 std::unique_ptr<IStylePoolIteratorAccess> StylePool::createIterator( const bool bSkipUnusedItemSets,
464 const bool bSkipIgnorableItems )
465 {
466 return pImpl->createIterator( bSkipUnusedItemSets, bSkipIgnorableItems );
467 }
468
~StylePool()469 StylePool::~StylePool()
470 {}
471
472 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
473