sd: keep a non-owning pointer to the OverridingShell
[LibreOffice.git] / sfx2 / source / dialog / filtergrouping.cxx
blob793d33d8fe4e7df2a1c6a7a15c0449c5a04e40f9
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include "filtergrouping.hxx"
21 #include <o3tl/safeint.hxx>
22 #include <sfx2/fcontnr.hxx>
23 #include <sfx2/filedlghelper.hxx>
24 #include <sfx2/strings.hrc>
25 #include <sfx2/docfilt.hxx>
26 #include <sfx2/sfxresid.hxx>
27 #include <sal/log.hxx>
28 #include <com/sun/star/ui/dialogs/XFilterGroupManager.hpp>
29 #include <com/sun/star/beans/StringPair.hpp>
30 #include <com/sun/star/uno/Sequence.hxx>
31 #include <unotools/confignode.hxx>
32 #include <comphelper/processfactory.hxx>
33 #include <comphelper/sequenceashashmap.hxx>
34 #include <comphelper/sequence.hxx>
35 #include <comphelper/string.hxx>
36 #include <comphelper/diagnose_ex.hxx>
37 #include <tools/debug.hxx>
39 #include <list>
40 #include <utility>
41 #include <vector>
42 #include <map>
43 #include <algorithm>
46 namespace sfx2
50 using namespace ::com::sun::star::uno;
51 using namespace ::com::sun::star::ui::dialogs;
52 using namespace ::com::sun::star::lang;
53 using namespace ::com::sun::star::beans;
54 using namespace ::utl;
57 /**
59 Some general words about what's going on here...
61 <p>In our file open dialog, usually we display every filter we know. That's how it was before: every filter
62 lead to an own line in the filter list box, e.g. "StarWriter 5.0 Document" or "Microsoft Word 97".</p>
64 <p>But then the PM came. And everything changed...</p>
66 <p>A basic idea are groups: Why simply listing all the single filters? Couldn't we draw nice separators
67 between the filters which logically belong together? I.e. all the filters which open a document in StarWriter:
68 couldn't we separate them from all the filters which open the document in StarCalc?<br/>
69 So spoke the PM, and engineering obeyed.</p>
71 <p>So we have groups. They're just a visual aspect: All the filters of a group are presented together, separated
72 by a line from other groups.</p>
74 <p>Let's be honest: How the concrete implementation of the file picker service separates the different groups
75 is a matter of this implementation. We only do this grouping and suggest it to the FilePicker service ...</p>
77 <p>Now for the second concept:<br/>
78 Thinking about it (and that's what the PM did), both "StarWriter 5.0 Document" and "Microsoft Word 97"
79 describe a text document. It's a text. It's of no interest for the user that one of the texts was saved in
80 MS' format, and one in our own format.<br/>
81 So in a first step, we want to have a filter entry "Text documents". This would cover both above-mentioned
82 filters, as well as any other filters for documents which are texts.</p>
84 <p>Such an entry as "Text documents" is - within the scope of this file - called "class" or "filter class".</p>
86 <p>In the file-open-dialog, such a class looks like an ordinary filter: it's simply a name in the filter
87 listbox. Selecting means that all the files matching one of the "sub-filters" are displayed (in the example above,
88 this would be "*.sdw", "*.doc" and so on).</p>
90 <p>Now there are two types of filter classes: global ones and local ones. "Text documents" is a global class. As
91 well as "Spreadsheets". Or "Web pages".<br/>
92 Let's have a look at a local class: The filters "MS Word 95" and "MS WinWord 6.0" together form the class
93 "Microsoft Word 6.0 / 95" (don't ask for the reasons. At least not me. Ask the PM). There are a lot of such
94 local classes ...</p>
96 <p>The difference between global and local classes is as follows: Global classes are presented in an own group.
97 There is one dedicated group at the top of the list, containing all the global groups - no local groups and no
98 single filters.</p>
100 <p>Ehm - it was a lie. Not really at the top. Before this group, there is this single "All files" entry. It forms
101 its own group. But this is uninteresting here.</p>
103 <p>Local classes must consist of filters which - without the classification - would all belong to the same group.
104 Then, they're combined to one entry (in the example above: "Microsoft Word 6.0 / 95"), and this entry is inserted
105 into the file picker filter list, instead of the single filters which form the class.</p>
107 <p>This is an interesting difference between local and global classes: Filters which are part of a global class
108 are listed in their own group, too. Filters in local classes aren't listed a second time - neither directly (as
109 the filter itself) nor indirectly (as part of another local group).</p>
111 <p>The only exception are filters which are part of a global class <em>and</em> a local class. This is allowed.
112 Being contained in two local classes isn't.</p>
114 <p>So that's all what you need to know: Understand the concept of "filter classes" (a filter class combines
115 different filters and acts as if it's a filter itself) and the concept of groups (a group just describes a
116 logical correlation of filters and usually is represented to the user by drawing group separators in the filter
117 list).</p>
119 <p>If you got it, go try understanding this file :).</p>
124 typedef StringPair FilterDescriptor; // a single filter or a filter class (display name and filter mask)
125 typedef ::std::list< FilterDescriptor > FilterGroup; // a list of single filter entries
126 typedef ::std::list< FilterGroup > GroupedFilterList; // a list of all filters, already grouped
128 /// the logical name of a filter
129 typedef OUString FilterName;
131 // a struct which holds references from a logical filter name to a filter group entry
132 // used for quick lookup of classes (means class entries - entries representing a class)
133 // which a given filter may belong to
134 typedef ::std::map< OUString, FilterGroup::iterator > FilterGroupEntryReferrer;
136 namespace {
138 /// a descriptor for a filter class (which in the final dialog is represented by one filter entry)
139 struct FilterClass
141 OUString sDisplayName; // the display name
142 Sequence< FilterName > aSubFilters; // the (logical) names of the filter which belong to the class
147 typedef ::std::list< FilterClass > FilterClassList;
148 typedef ::std::map< OUString, FilterClassList::iterator > FilterClassReferrer;
151 // = reading of configuration data
154 static void lcl_ReadFilterClass( const OConfigurationNode& _rClassesNode, const OUString& _rLogicalClassName,
155 FilterClass& /* [out] */ _rClass )
157 // the description node for the current class
158 OConfigurationNode aClassDesc = _rClassesNode.openNode( _rLogicalClassName );
160 // the values
161 aClassDesc.getNodeValue( u"DisplayName"_ustr ) >>= _rClass.sDisplayName;
162 aClassDesc.getNodeValue( u"Filters"_ustr ) >>= _rClass.aSubFilters;
165 namespace {
167 struct CreateEmptyClassRememberPos
169 protected:
170 FilterClassList& m_rClassList;
171 FilterClassReferrer& m_rClassesReferrer;
173 public:
174 CreateEmptyClassRememberPos( FilterClassList& _rClassList, FilterClassReferrer& _rClassesReferrer )
175 :m_rClassList ( _rClassList )
176 ,m_rClassesReferrer ( _rClassesReferrer )
180 // operate on a single class name
181 void operator() ( const FilterName& _rLogicalFilterName )
183 // insert a new (empty) class
184 m_rClassList.emplace_back( );
185 // get the position of this new entry
186 FilterClassList::iterator aInsertPos = m_rClassList.end();
187 --aInsertPos;
188 // remember this position
189 m_rClassesReferrer.emplace( _rLogicalFilterName, aInsertPos );
194 struct ReadGlobalFilter
196 protected:
197 OConfigurationNode m_aClassesNode;
198 FilterClassReferrer& m_aClassReferrer;
200 public:
201 ReadGlobalFilter( OConfigurationNode _aClassesNode, FilterClassReferrer& _rClassesReferrer )
202 :m_aClassesNode (std::move( _aClassesNode ))
203 ,m_aClassReferrer ( _rClassesReferrer )
207 // operate on a single logical name
208 void operator() ( const FilterName& _rName )
210 FilterClassReferrer::iterator aClassRef = m_aClassReferrer.find( _rName );
211 if ( m_aClassReferrer.end() == aClassRef )
213 // we do not know this global class
214 OSL_FAIL( "ReadGlobalFilter::operator(): unknown filter name!" );
215 // TODO: perhaps we should be more tolerant - at the moment, the filter is dropped
216 // We could silently push_back it to the container...
218 else
220 // read the data of this class into the node referred to by aClassRef
221 lcl_ReadFilterClass( m_aClassesNode, _rName, *aClassRef->second );
228 static void lcl_ReadGlobalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rGlobalClasses, std::vector<OUString>& _rGlobalClassNames )
230 _rGlobalClasses.clear();
231 _rGlobalClassNames.clear();
233 // get the list describing the order of all global classes
234 Sequence< OUString > aGlobalClasses;
235 _rFilterClassification.getNodeValue( u"GlobalFilters/Order"_ustr ) >>= aGlobalClasses;
237 // copy the logical names
238 comphelper::sequenceToContainer(_rGlobalClassNames, aGlobalClasses);
240 // Global classes are presented in an own group, so their order matters (while the order of the
241 // "local classes" doesn't).
242 // That's why we can't simply add the global classes to _rGlobalClasses using the order in which they
243 // are returned from the configuration - it is completely undefined, and we need a _defined_ order.
244 FilterClassReferrer aClassReferrer;
245 ::std::for_each(
246 std::cbegin(aGlobalClasses),
247 std::cend(aGlobalClasses),
248 CreateEmptyClassRememberPos( _rGlobalClasses, aClassReferrer )
250 // now _rGlobalClasses contains a dummy entry for each global class,
251 // while aClassReferrer maps from the logical name of the class to the position within _rGlobalClasses where
252 // it's dummy entry resides
255 // go for all the single class entries
256 OConfigurationNode aFilterClassesNode =
257 _rFilterClassification.openNode( u"GlobalFilters/Classes"_ustr );
258 const Sequence< OUString > aFilterClasses = aFilterClassesNode.getNodeNames();
259 ::std::for_each(
260 aFilterClasses.begin(),
261 aFilterClasses.end(),
262 ReadGlobalFilter( std::move(aFilterClassesNode), aClassReferrer )
266 namespace {
268 struct ReadLocalFilter
270 protected:
271 OConfigurationNode m_aClassesNode;
272 FilterClassList& m_rClasses;
274 public:
275 ReadLocalFilter( OConfigurationNode _aClassesNode, FilterClassList& _rClasses )
276 :m_aClassesNode (std::move( _aClassesNode ))
277 ,m_rClasses ( _rClasses )
281 // operate on a single logical name
282 void operator() ( const FilterName& _rName )
284 // read the data for this class
285 FilterClass aClass;
286 lcl_ReadFilterClass( m_aClassesNode, _rName, aClass );
288 // insert the class descriptor
289 m_rClasses.push_back( aClass );
295 static void lcl_ReadLocalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rLocalClasses )
297 _rLocalClasses.clear();
299 // the node for the local classes
300 OConfigurationNode aFilterClassesNode =
301 _rFilterClassification.openNode( u"LocalFilters/Classes"_ustr );
302 const Sequence< OUString > aFilterClasses = aFilterClassesNode.getNodeNames();
304 ::std::for_each(
305 aFilterClasses.begin(),
306 aFilterClasses.end(),
307 ReadLocalFilter( std::move(aFilterClassesNode), _rLocalClasses )
312 static void lcl_ReadClassification( FilterClassList& _rGlobalClasses, std::vector<OUString>& _rGlobalClassNames, FilterClassList& _rLocalClasses )
315 // open our config node
316 OConfigurationTreeRoot aFilterClassification = OConfigurationTreeRoot::createWithComponentContext(
317 ::comphelper::getProcessComponentContext(),
318 u"org.openoffice.Office.UI/FilterClassification"_ustr,
320 OConfigurationTreeRoot::CM_READONLY
324 // go for the global classes
325 lcl_ReadGlobalFilters( aFilterClassification, _rGlobalClasses, _rGlobalClassNames );
328 // go for the local classes
329 lcl_ReadLocalFilters( aFilterClassification, _rLocalClasses );
334 // = grouping and classifying
336 namespace {
338 // a struct which adds helps remembering a reference to a class entry
339 struct ReferToFilterEntry
341 protected:
342 FilterGroupEntryReferrer& m_rEntryReferrer;
343 FilterGroup::iterator m_aClassPos;
345 public:
346 ReferToFilterEntry( FilterGroupEntryReferrer& _rEntryReferrer, FilterGroup::iterator _aClassPos )
347 :m_rEntryReferrer( _rEntryReferrer )
348 ,m_aClassPos(std::move( _aClassPos ))
352 // operate on a single filter name
353 void operator() ( const FilterName& _rName )
355 ::std::pair< FilterGroupEntryReferrer::iterator, bool > aInsertRes =
356 m_rEntryReferrer.emplace( _rName, m_aClassPos );
357 SAL_WARN_IF(
358 !aInsertRes.second, "sfx.dialog",
359 "already have an element for " << _rName);
364 struct FillClassGroup
366 protected:
367 FilterGroup& m_rClassGroup;
368 FilterGroupEntryReferrer& m_rClassReferrer;
370 public:
371 FillClassGroup( FilterGroup& _rClassGroup, FilterGroupEntryReferrer& _rClassReferrer )
372 :m_rClassGroup ( _rClassGroup )
373 ,m_rClassReferrer ( _rClassReferrer )
377 // operate on a single class
378 void operator() ( const FilterClass& _rClass )
380 // create an empty filter descriptor for the class
381 FilterDescriptor aClassEntry;
382 // set its name (which is all we know by now)
383 aClassEntry.First = _rClass.sDisplayName;
385 // add it to the group
386 m_rClassGroup.push_back( aClassEntry );
387 // the position of the newly added class
388 FilterGroup::iterator aClassEntryPos = m_rClassGroup.end();
389 --aClassEntryPos;
391 // and for all the sub filters of the class, remember the class
392 // (respectively the position of the class it the group)
393 ::std::for_each(
394 _rClass.aSubFilters.begin(),
395 _rClass.aSubFilters.end(),
396 ReferToFilterEntry( m_rClassReferrer, aClassEntryPos )
403 const sal_Unicode s_cWildcardSeparator( ';' );
405 constexpr OUString SEPARATOR = u";"_ustr;
407 namespace {
409 struct CheckAppendSingleWildcard
411 OUString& _rToBeExtended;
413 explicit CheckAppendSingleWildcard( OUString& _rBase ) : _rToBeExtended( _rBase ) { }
415 void operator() ( std::u16string_view _rWC )
417 // check for double wildcards
418 sal_Int32 nExistentPos = _rToBeExtended.indexOf( _rWC );
419 if ( -1 < nExistentPos )
420 { // found this wildcard (already part of _rToBeExtended)
421 if ( ( 0 == nExistentPos )
422 || ( s_cWildcardSeparator == _rToBeExtended[ nExistentPos - 1 ] )
424 { // the wildcard really starts at this position (it starts at pos 0 or the previous character is a separator
425 sal_Int32 nExistentWCEnd = nExistentPos + _rWC.size();
426 if ( ( _rToBeExtended.getLength() == nExistentWCEnd )
427 || ( s_cWildcardSeparator == _rToBeExtended[ nExistentWCEnd ] )
429 { // it's really the complete wildcard we found
430 // (not something like _rWC being "*.t" and _rToBeExtended containing "*.txt")
431 // -> outta here
432 return;
437 if ( !_rToBeExtended.isEmpty() )
438 _rToBeExtended += SEPARATOR;
439 _rToBeExtended += _rWC;
444 // a helper struct which adds a fixed (Sfx-)filter to a filter group entry given by iterator
445 struct AppendWildcardToDescriptor
447 protected:
448 ::std::vector< OUString > aWildCards;
450 public:
451 explicit AppendWildcardToDescriptor( const OUString& _rWildCard );
453 // operate on a single class entry
454 void operator() ( const FilterGroupEntryReferrer::value_type& _rClassReference )
456 // simply add our wildcards
457 ::std::for_each(
458 aWildCards.begin(),
459 aWildCards.end(),
460 CheckAppendSingleWildcard( _rClassReference.second->Second )
467 AppendWildcardToDescriptor::AppendWildcardToDescriptor( const OUString& _rWildCard )
469 DBG_ASSERT( !_rWildCard.isEmpty(),
470 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: invalid wildcard!" );
471 DBG_ASSERT( _rWildCard.isEmpty() || _rWildCard[0] != s_cWildcardSeparator,
472 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: wildcard already separated!" );
474 aWildCards.reserve( comphelper::string::getTokenCount(_rWildCard, s_cWildcardSeparator) );
476 const sal_Unicode* pTokenLoop = _rWildCard.getStr();
477 const sal_Unicode* pTokenLoopEnd = pTokenLoop + _rWildCard.getLength();
478 const sal_Unicode* pTokenStart = pTokenLoop;
479 for ( ; pTokenLoop != pTokenLoopEnd; ++pTokenLoop )
481 if ( ( s_cWildcardSeparator == *pTokenLoop ) && ( pTokenLoop > pTokenStart ) )
482 { // found a new token separator (and a non-empty token)
483 aWildCards.emplace_back( pTokenStart, pTokenLoop - pTokenStart );
485 // search the start of the next token
486 while ( ( pTokenStart != pTokenLoopEnd ) && ( *pTokenStart != s_cWildcardSeparator ) )
487 ++pTokenStart;
489 if ( pTokenStart == pTokenLoopEnd )
490 // reached the end
491 break;
493 ++pTokenStart;
494 pTokenLoop = pTokenStart;
497 if ( pTokenLoop > pTokenStart )
498 // the last one...
499 aWildCards.emplace_back( pTokenStart, pTokenLoop - pTokenStart );
503 static void lcl_InitGlobalClasses( GroupedFilterList& _rAllFilters, const FilterClassList& _rGlobalClasses, FilterGroupEntryReferrer& _rGlobalClassesRef )
505 // we need an extra group in our "all filters" container
506 _rAllFilters.push_front( FilterGroup() );
507 FilterGroup& rGlobalFilters = _rAllFilters.front();
508 // it's important to work on the reference: we want to access the members of this filter group
509 // by an iterator (FilterGroup::const_iterator)
510 // the referrer for the global classes
512 // initialize the group
513 ::std::for_each(
514 _rGlobalClasses.begin(),
515 _rGlobalClasses.end(),
516 FillClassGroup( rGlobalFilters, _rGlobalClassesRef )
518 // now we have:
519 // in rGlobalFilters: a list of FilterDescriptor's, where each's descriptor's display name is set to the name of a class
520 // in aGlobalClassesRef: a mapping from logical filter names to positions within rGlobalFilters
521 // this way, if we encounter an arbitrary filter, we can easily (and efficient) check if it belongs to a global class
522 // and modify the descriptor for this class accordingly
526 typedef ::std::vector< ::std::pair< FilterGroupEntryReferrer::mapped_type, FilterGroup::iterator > >
527 MapGroupEntry2GroupEntry;
528 // this is not really a map - it's just called this way because it is used as a map
530 namespace {
532 struct FindGroupEntry
534 FilterGroupEntryReferrer::mapped_type aLookingFor;
535 explicit FindGroupEntry( FilterGroupEntryReferrer::mapped_type _aLookingFor ) : aLookingFor(std::move( _aLookingFor )) { }
537 bool operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
539 return _rMapEntry.first == aLookingFor;
543 struct CopyGroupEntryContent
545 void operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
547 *_rMapEntry.second = *_rMapEntry.first;
552 struct CopyNonEmptyFilter
554 FilterGroup& rTarget;
555 explicit CopyNonEmptyFilter( FilterGroup& _rTarget ) :rTarget( _rTarget ) { }
557 void operator() ( const FilterDescriptor& _rFilter )
559 if ( !_rFilter.Second.isEmpty() )
560 rTarget.push_back( _rFilter );
566 static void lcl_GroupAndClassify( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rAllFilters )
568 _rAllFilters.clear();
571 // read the classification of filters
572 FilterClassList aGlobalClasses, aLocalClasses;
573 std::vector<OUString> aGlobalClassNames;
574 lcl_ReadClassification( aGlobalClasses, aGlobalClassNames, aLocalClasses );
577 // for the global filter classes
578 FilterGroupEntryReferrer aGlobalClassesRef;
579 lcl_InitGlobalClasses( _rAllFilters, aGlobalClasses, aGlobalClassesRef );
581 // insert as much placeholders (FilterGroup's) into _rAllFilter for groups as we have global classes
582 // (this assumes that both numbers are the same, which, speaking strictly, must not hold - but it does, as we know ...)
583 sal_Int32 nGlobalClasses = aGlobalClasses.size();
584 while ( nGlobalClasses-- )
585 _rAllFilters.emplace_back( );
588 // for the local classes:
589 // if n filters belong to a local class, they do not appear in their respective group explicitly, instead
590 // and entry for the class is added to the group and the extensions of the filters are collected under
591 // this entry
592 FilterGroupEntryReferrer aLocalClassesRef;
593 FilterGroup aCollectedLocals;
594 ::std::for_each(
595 aLocalClasses.begin(),
596 aLocalClasses.end(),
597 FillClassGroup( aCollectedLocals, aLocalClassesRef )
599 // to map from the position within aCollectedLocals to positions within the real groups
600 // (where they finally belong to)
601 MapGroupEntry2GroupEntry aLocalFinalPositions;
604 // now add the filters
605 // the group which we currently work with
606 GroupedFilterList::iterator aCurrentGroup = _rAllFilters.end(); // no current group
607 // the filter container of the current group - if this changes between two filters, a new group is reached
608 OUString aCurrentServiceName;
610 OUString sFilterWildcard;
611 OUString sFilterName;
612 // loop through all the filters
613 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
615 sFilterName = pFilter->GetFilterName();
616 sFilterWildcard = pFilter->GetWildcard().getGlob();
617 AppendWildcardToDescriptor aExtendWildcard( sFilterWildcard );
619 DBG_ASSERT( !sFilterWildcard.isEmpty(), "sfx2::lcl_GroupAndClassify: invalid wildcard of this filter!" );
622 // check for a change in the group
623 OUString aServiceName = pFilter->GetServiceName();
624 if ( aServiceName != aCurrentServiceName )
625 { // we reached a new group
627 // look for the place in _rAllFilters where this ne group belongs - this is determined
628 // by the order of classes in aGlobalClassNames
629 GroupedFilterList::iterator aGroupPos = _rAllFilters.begin();
630 DBG_ASSERT( aGroupPos != _rAllFilters.end(),
631 "sfx2::lcl_GroupAndClassify: invalid all-filters array here!" );
632 // the loop below will work on invalid objects else ...
633 ++aGroupPos;
634 auto aGlobalIter = std::find(aGlobalClassNames.begin(), aGlobalClassNames.end(), aServiceName);
635 auto nGroupPosShift = std::min(
636 std::distance(aGlobalClassNames.begin(), aGlobalIter),
637 std::distance(aGroupPos, _rAllFilters.end()));
638 std::advance(aGroupPos, nGroupPosShift);
639 if ( aGroupPos != _rAllFilters.end() )
640 // we found a global class name which matches the doc service name -> fill the filters of this
641 // group in the respective prepared group
642 aCurrentGroup = aGroupPos;
643 else
644 // insert a new entry in our overall-list
645 aCurrentGroup = _rAllFilters.insert( _rAllFilters.end(), FilterGroup() );
647 // remember the container to properly detect the next group
648 aCurrentServiceName = aServiceName;
651 assert(aCurrentGroup != _rAllFilters.end()); //invalid current group!
652 if (aCurrentGroup == _rAllFilters.end())
653 aCurrentGroup = _rAllFilters.begin();
656 // check if the filter is part of a global group
657 ::std::pair< FilterGroupEntryReferrer::iterator, FilterGroupEntryReferrer::iterator >
658 aBelongsTo = aGlobalClassesRef.equal_range( sFilterName );
659 // add the filter to the entries for these classes
660 // (if they exist - if not, the range is empty and the for_each is a no-op)
661 ::std::for_each(
662 aBelongsTo.first,
663 aBelongsTo.second,
664 aExtendWildcard
668 // add the filter to its group
670 // for this, check if the filter is part of a local filter
671 FilterGroupEntryReferrer::iterator aBelongsToLocal = aLocalClassesRef.find( sFilterName );
672 if ( aLocalClassesRef.end() != aBelongsToLocal )
674 // okay, there is a local class which the filter belongs to
675 // -> append the wildcard
676 aExtendWildcard( *aBelongsToLocal );
678 if ( std::none_of( aLocalFinalPositions.begin(), aLocalFinalPositions.end(), FindGroupEntry( aBelongsToLocal->second ) ) )
679 { // the position within aCollectedLocals has not been mapped to a final position
680 // within the "real" group (aCollectedLocals is only temporary)
681 // -> do this now (as we just encountered the first filter belonging to this local class
682 // add a new entry which is the "real" group entry
683 aCurrentGroup->push_back( FilterDescriptor( aBelongsToLocal->second->First, OUString() ) );
684 // the position where we inserted the entry
685 FilterGroup::iterator aInsertPos = aCurrentGroup->end();
686 --aInsertPos;
687 // remember this pos
688 aLocalFinalPositions.emplace_back( aBelongsToLocal->second, aInsertPos );
691 else
692 aCurrentGroup->push_back( FilterDescriptor( pFilter->GetUIName(), sFilterWildcard ) );
695 // now just complete the infos for the local groups:
696 // During the above loop, they have been collected in aCollectedLocals, but this is only temporary
697 // They have to be copied into their final positions (which are stored in aLocalFinalPositions)
698 ::std::for_each(
699 aLocalFinalPositions.begin(),
700 aLocalFinalPositions.end(),
701 CopyGroupEntryContent()
704 // and remove local groups which do not apply - e.g. have no entries due to the limited content of the
705 // current SfxFilterMatcherIter
707 FilterGroup& rGlobalFilters = _rAllFilters.front();
708 FilterGroup aNonEmptyGlobalFilters;
709 ::std::for_each(
710 rGlobalFilters.begin(),
711 rGlobalFilters.end(),
712 CopyNonEmptyFilter( aNonEmptyGlobalFilters )
714 rGlobalFilters.swap( aNonEmptyGlobalFilters );
717 namespace {
719 struct AppendFilter
721 protected:
722 Reference< XFilterManager > m_xFilterManager;
723 FileDialogHelper_Impl* m_pFileDlgImpl;
724 bool m_bAddExtension;
726 public:
727 AppendFilter( const Reference< XFilterManager >& _rxFilterManager,
728 FileDialogHelper_Impl* _pImpl, bool _bAddExtension ) :
730 m_xFilterManager( _rxFilterManager ),
731 m_pFileDlgImpl ( _pImpl ),
732 m_bAddExtension ( _bAddExtension )
735 DBG_ASSERT( m_xFilterManager.is(), "AppendFilter::AppendFilter: invalid filter manager!" );
736 DBG_ASSERT( m_pFileDlgImpl, "AppendFilter::AppendFilter: invalid filedlg impl!" );
739 // operate on a single filter
740 void operator() ( const FilterDescriptor& _rFilterEntry )
742 OUString sDisplayText = m_bAddExtension
743 ? addExtension( _rFilterEntry.First, _rFilterEntry.Second, true, *m_pFileDlgImpl )
744 : _rFilterEntry.First;
745 m_xFilterManager->appendFilter( sDisplayText, _rFilterEntry.Second );
751 // = handling for the "all files" entry
754 static bool lcl_hasAllFilesFilter( TSortedFilterList& _rFilterMatcher, OUString& /* [out] */ _rAllFilterName )
756 bool bHasAll = false;
757 _rAllFilterName = SfxResId( STR_SFX_FILTERNAME_ALL );
760 // check if there's already a filter <ALL>
761 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter && !bHasAll; pFilter = _rFilterMatcher.Next() )
763 if ( pFilter->GetUIName() == _rAllFilterName )
764 bHasAll = true;
766 return bHasAll;
770 static void lcl_EnsureAllFilesEntry( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rFilters )
773 OUString sAllFilterName;
774 if ( !lcl_hasAllFilesFilter( _rFilterMatcher, sAllFilterName ) )
776 // get the first group of filters (by definition, this group contains the global classes)
777 DBG_ASSERT( !_rFilters.empty(), "lcl_EnsureAllFilesEntry: invalid filter list!" );
778 if ( !_rFilters.empty() )
780 FilterGroup& rGlobalClasses = *_rFilters.begin();
781 rGlobalClasses.push_front( FilterDescriptor( sAllFilterName, FILEDIALOG_FILTER_ALL ) );
787 // = filling an XFilterManager
789 namespace {
791 struct AppendFilterGroup
793 protected:
794 Reference< XFilterManager > m_xFilterManager;
795 Reference< XFilterGroupManager > m_xFilterGroupManager;
796 FileDialogHelper_Impl* m_pFileDlgImpl;
798 public:
799 AppendFilterGroup( const Reference< XFilterManager >& _rxFilterManager, FileDialogHelper_Impl* _pImpl )
800 :m_xFilterManager ( _rxFilterManager )
801 ,m_xFilterGroupManager ( _rxFilterManager, UNO_QUERY )
802 ,m_pFileDlgImpl ( _pImpl )
804 DBG_ASSERT( m_xFilterManager.is(), "AppendFilterGroup::AppendFilterGroup: invalid filter manager!" );
805 DBG_ASSERT( m_pFileDlgImpl, "AppendFilterGroup::AppendFilterGroup: invalid filedlg impl!" );
808 void appendGroup( const FilterGroup& _rGroup, bool _bAddExtension )
812 if ( m_xFilterGroupManager.is() )
813 { // the file dialog implementation supports visual grouping of filters
814 // create a representation of the group which is understandable by the XFilterGroupManager
815 if ( !_rGroup.empty() )
817 Sequence< StringPair > aFilters( comphelper::containerToSequence(_rGroup) );
818 if ( _bAddExtension )
820 for ( StringPair & filter : asNonConstRange(aFilters) )
821 filter.First = addExtension( filter.First, filter.Second, true, *m_pFileDlgImpl );
823 m_xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
826 else
828 ::std::for_each(
829 _rGroup.begin(),
830 _rGroup.end(),
831 AppendFilter( m_xFilterManager, m_pFileDlgImpl, _bAddExtension ) );
834 catch( const Exception& )
836 DBG_UNHANDLED_EXCEPTION("sfx.dialog");
840 // operate on a single filter group
841 void operator() ( const FilterGroup& _rGroup )
843 appendGroup( _rGroup, true );
849 TSortedFilterList::TSortedFilterList(const css::uno::Reference< css::container::XEnumeration >& xFilterList)
850 : m_nIterator(0)
852 if (!xFilterList.is())
853 return;
855 m_lFilters.clear();
856 while(xFilterList->hasMoreElements())
858 ::comphelper::SequenceAsHashMap lFilterProps (xFilterList->nextElement());
859 OUString sFilterName = lFilterProps.getUnpackedValueOrDefault(
860 u"Name"_ustr,
861 OUString());
862 if (!sFilterName.isEmpty())
863 m_lFilters.push_back(sFilterName);
868 std::shared_ptr<const SfxFilter> TSortedFilterList::First()
870 m_nIterator = 0;
871 return impl_getFilter(m_nIterator);
875 std::shared_ptr<const SfxFilter> TSortedFilterList::Next()
877 ++m_nIterator;
878 return impl_getFilter(m_nIterator);
882 std::shared_ptr<const SfxFilter> TSortedFilterList::impl_getFilter(sal_Int32 nIndex)
884 if (nIndex<0 || o3tl::make_unsigned(nIndex)>=m_lFilters.size())
885 return nullptr;
886 const OUString& sFilterName = m_lFilters[nIndex];
887 if (sFilterName.isEmpty())
888 return nullptr;
889 return SfxFilter::GetFilterByName(sFilterName);
893 void appendFiltersForSave( TSortedFilterList& _rFilterMatcher,
894 const Reference< XFilterManager >& _rxFilterManager,
895 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl,
896 std::u16string_view _rFactory )
898 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForSave: invalid manager!" );
899 if ( !_rxFilterManager.is() )
900 return;
902 OUString sUIName;
903 OUString sExtension;
905 // retrieve the default filter for this application module.
906 // It must be set as first of the generated filter list.
907 std::shared_ptr<const SfxFilter> pDefaultFilter = SfxFilterContainer::GetDefaultFilter_Impl(_rFactory);
908 // Only use one extension (#i32434#)
909 // (and always the first if there are more than one)
910 sExtension = pDefaultFilter->GetWildcard().getGlob().getToken(0, ';');
911 sUIName = addExtension( pDefaultFilter->GetUIName(), sExtension, false, _rFileDlgImpl );
914 _rxFilterManager->appendFilter( sUIName, sExtension );
915 if ( _rFirstNonEmpty.isEmpty() )
916 _rFirstNonEmpty = sUIName;
918 catch( const IllegalArgumentException& )
920 SAL_WARN( "sfx.dialog", "Could not append DefaultFilter" << sUIName );
923 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
925 if (pFilter->GetName() == pDefaultFilter->GetName())
926 continue;
928 // Only use one extension (#i32434#)
929 // (and always the first if there are more than one)
930 sExtension = pFilter->GetWildcard().getGlob().getToken(0, ';');
931 sUIName = addExtension( pFilter->GetUIName(), sExtension, false, _rFileDlgImpl );
934 _rxFilterManager->appendFilter( sUIName, sExtension );
935 if ( _rFirstNonEmpty.isEmpty() )
936 _rFirstNonEmpty = sUIName;
938 catch( const IllegalArgumentException& )
940 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
945 namespace {
947 struct ExportFilter
949 ExportFilter( OUString _aUIName, OUString _aWildcard ) :
950 aUIName(std::move( _aUIName )), aWildcard(std::move( _aWildcard )) {}
952 OUString aUIName;
953 OUString aWildcard;
958 void appendExportFilters( TSortedFilterList& _rFilterMatcher,
959 const Reference< XFilterManager >& _rxFilterManager,
960 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
962 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendExportFilters: invalid manager!" );
963 if ( !_rxFilterManager.is() )
964 return;
966 sal_Int32 nHTMLIndex = -1;
967 sal_Int32 nXHTMLIndex = -1;
968 sal_Int32 nPDFIndex = -1;
969 OUString sUIName;
970 OUString sExtensions;
971 std::vector< ExportFilter > aImportantFilterGroup;
972 std::vector< ExportFilter > aFilterGroup;
973 Reference< XFilterGroupManager > xFilterGroupManager( _rxFilterManager, UNO_QUERY );
974 OUString sTypeName;
976 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
978 sTypeName = pFilter->GetTypeName();
979 sUIName = pFilter->GetUIName();
980 sExtensions = pFilter->GetWildcard().getGlob();
981 ExportFilter aExportFilter( sUIName, sExtensions );
983 if ( nHTMLIndex == -1 &&
984 ( sTypeName == "generic_HTML" || sTypeName == "graphic_HTML" ) )
986 aImportantFilterGroup.insert( aImportantFilterGroup.begin(), aExportFilter );
987 nHTMLIndex = 0;
989 else if ( nXHTMLIndex == -1 && sTypeName == "XHTML_File" )
991 std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
992 if ( nHTMLIndex == -1 )
993 aImportantFilterGroup.insert( aIter, aExportFilter );
994 else
995 aImportantFilterGroup.insert( ++aIter, aExportFilter );
996 nXHTMLIndex = 0;
998 else if ( nPDFIndex == -1 && sTypeName == "pdf_Portable_Document_Format" )
1000 std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
1001 if ( nHTMLIndex != -1 )
1002 ++aIter;
1003 if ( nXHTMLIndex != -1 )
1004 ++aIter;
1005 aImportantFilterGroup.insert( aIter, aExportFilter );
1006 nPDFIndex = 0;
1008 else
1009 aFilterGroup.push_back( aExportFilter );
1012 if ( xFilterGroupManager.is() )
1014 // Add both html/pdf filter as a filter group to get a separator between both groups
1015 if ( !aImportantFilterGroup.empty() )
1017 Sequence< StringPair > aFilters( aImportantFilterGroup.size() );
1018 auto pFilters = aFilters.getArray();
1019 for ( sal_Int32 i = 0; i < static_cast<sal_Int32>(aImportantFilterGroup.size()); i++ )
1021 pFilters[i].First = addExtension( aImportantFilterGroup[i].aUIName,
1022 aImportantFilterGroup[i].aWildcard,
1023 false, _rFileDlgImpl );
1024 pFilters[i].Second = aImportantFilterGroup[i].aWildcard;
1029 xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1031 catch( const IllegalArgumentException& )
1036 if ( !aFilterGroup.empty() )
1038 Sequence< StringPair > aFilters( aFilterGroup.size() );
1039 auto pFilters = aFilters.getArray();
1040 for ( sal_Int32 i = 0; i < static_cast<sal_Int32>(aFilterGroup.size()); i++ )
1042 pFilters[i].First = addExtension( aFilterGroup[i].aUIName,
1043 aFilterGroup[i].aWildcard,
1044 false, _rFileDlgImpl );
1045 pFilters[i].Second = aFilterGroup[i].aWildcard;
1050 xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1052 catch( const IllegalArgumentException& )
1057 else
1059 // Fallback solution just add both filter groups as single filters
1060 sal_Int32 n;
1062 for ( n = 0; n < static_cast<sal_Int32>(aImportantFilterGroup.size()); n++ )
1066 OUString aUIName = addExtension( aImportantFilterGroup[n].aUIName,
1067 aImportantFilterGroup[n].aWildcard,
1068 false, _rFileDlgImpl );
1069 _rxFilterManager->appendFilter( aUIName, aImportantFilterGroup[n].aWildcard );
1070 if ( _rFirstNonEmpty.isEmpty() )
1071 _rFirstNonEmpty = sUIName;
1074 catch( const IllegalArgumentException& )
1076 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
1080 for ( n = 0; n < static_cast<sal_Int32>(aFilterGroup.size()); n++ )
1084 OUString aUIName = addExtension( aFilterGroup[n].aUIName,
1085 aFilterGroup[n].aWildcard,
1086 false, _rFileDlgImpl );
1087 _rxFilterManager->appendFilter( aUIName, aFilterGroup[n].aWildcard );
1088 if ( _rFirstNonEmpty.isEmpty() )
1089 _rFirstNonEmpty = sUIName;
1092 catch( const IllegalArgumentException& )
1094 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
1101 void appendFiltersForOpen( TSortedFilterList& _rFilterMatcher,
1102 const Reference< XFilterManager >& _rxFilterManager,
1103 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
1105 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForOpen: invalid manager!" );
1106 if ( !_rxFilterManager.is() )
1107 return;
1110 // group and classify the filters
1111 GroupedFilterList aAllFilters;
1112 lcl_GroupAndClassify( _rFilterMatcher, aAllFilters );
1115 // ensure that we have the one "all files" entry
1116 lcl_EnsureAllFilesEntry( _rFilterMatcher, aAllFilters );
1119 // the first non-empty string - which we assume is the first overall entry
1120 if ( !aAllFilters.empty() )
1122 const FilterGroup& rFirstGroup = *aAllFilters.begin(); // should be the global classes
1123 if ( !rFirstGroup.empty() )
1124 _rFirstNonEmpty = rFirstGroup.begin()->First;
1125 // append first group, without extension
1126 AppendFilterGroup aGroup( _rxFilterManager, &_rFileDlgImpl );
1127 aGroup.appendGroup( rFirstGroup, false );
1131 // append the filters to the manager
1132 if ( !aAllFilters.empty() )
1134 ::std::list< FilterGroup >::iterator pIter = aAllFilters.begin();
1135 ++pIter;
1136 ::std::for_each(
1137 pIter, // first filter group was handled separately, see above
1138 aAllFilters.end(),
1139 AppendFilterGroup( _rxFilterManager, &_rFileDlgImpl ) );
1143 OUString addExtension( const OUString& _rDisplayText,
1144 const OUString& _rExtension,
1145 bool _bForOpen, FileDialogHelper_Impl& _rFileDlgImpl )
1147 OUString sRet = _rDisplayText;
1149 if ( sRet.indexOf( "(*.*)" ) == -1 )
1151 OUString sExt = _rExtension;
1152 if ( !_bForOpen )
1154 // show '*' in extensions only when opening a document
1155 sExt = sExt.replaceAll("*", "");
1157 sRet += " (" + sExt + ")";
1159 _rFileDlgImpl.addFilterPair( _rDisplayText, sRet );
1160 return sRet;
1164 } // namespace sfx2
1167 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */