1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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
;
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
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
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
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
;
138 /// a descriptor for a filter class (which in the final dialog is represented by one filter entry)
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
);
161 aClassDesc
.getNodeValue( "DisplayName" ) >>= _rClass
.sDisplayName
;
162 aClassDesc
.getNodeValue( "Filters" ) >>= _rClass
.aSubFilters
;
167 struct CreateEmptyClassRememberPos
170 FilterClassList
& m_rClassList
;
171 FilterClassReferrer
& m_rClassesReferrer
;
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();
188 // remember this position
189 m_rClassesReferrer
.emplace( _rLogicalFilterName
, aInsertPos
);
194 struct ReadGlobalFilter
197 OConfigurationNode m_aClassesNode
;
198 FilterClassReferrer
& m_aClassReferrer
;
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...
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( "GlobalFilters/Order" ) >>= 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
;
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( "GlobalFilters/Classes" );
258 const Sequence
< OUString
> aFilterClasses
= aFilterClassesNode
.getNodeNames();
260 aFilterClasses
.begin(),
261 aFilterClasses
.end(),
262 ReadGlobalFilter( aFilterClassesNode
, aClassReferrer
)
268 struct ReadLocalFilter
271 OConfigurationNode m_aClassesNode
;
272 FilterClassList
& m_rClasses
;
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
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( "LocalFilters/Classes" );
302 const Sequence
< OUString
> aFilterClasses
= aFilterClassesNode
.getNodeNames();
305 aFilterClasses
.begin(),
306 aFilterClasses
.end(),
307 ReadLocalFilter( 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 "org.openoffice.Office.UI/FilterClassification",
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
338 // a struct which adds helps remembering a reference to a class entry
339 struct ReferToFilterEntry
342 FilterGroupEntryReferrer
& m_rEntryReferrer
;
343 FilterGroup::iterator m_aClassPos
;
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
);
358 !aInsertRes
.second
, "sfx.dialog",
359 "already have an element for " << _rName
);
364 struct FillClassGroup
367 FilterGroup
& m_rClassGroup
;
368 FilterGroupEntryReferrer
& m_rClassReferrer
;
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();
391 // and for all the sub filters of the class, remember the class
392 // (respectively the position of the class it the group)
394 _rClass
.aSubFilters
.begin(),
395 _rClass
.aSubFilters
.end(),
396 ReferToFilterEntry( m_rClassReferrer
, aClassEntryPos
)
403 const sal_Unicode
s_cWildcardSeparator( ';' );
405 static OUString
getSeparatorString()
412 struct CheckAppendSingleWildcard
414 OUString
& _rToBeExtended
;
416 explicit CheckAppendSingleWildcard( OUString
& _rBase
) : _rToBeExtended( _rBase
) { }
418 void operator() ( std::u16string_view _rWC
)
420 // check for double wildcards
421 sal_Int32 nExistentPos
= _rToBeExtended
.indexOf( _rWC
);
422 if ( -1 < nExistentPos
)
423 { // found this wildcard (already part of _rToBeExtended)
424 if ( ( 0 == nExistentPos
)
425 || ( s_cWildcardSeparator
== _rToBeExtended
[ nExistentPos
- 1 ] )
427 { // the wildcard really starts at this position (it starts at pos 0 or the previous character is a separator
428 sal_Int32 nExistentWCEnd
= nExistentPos
+ _rWC
.size();
429 if ( ( _rToBeExtended
.getLength() == nExistentWCEnd
)
430 || ( s_cWildcardSeparator
== _rToBeExtended
[ nExistentWCEnd
] )
432 { // it's really the complete wildcard we found
433 // (not something like _rWC being "*.t" and _rToBeExtended containing "*.txt")
440 if ( !_rToBeExtended
.isEmpty() )
441 _rToBeExtended
+= getSeparatorString();
442 _rToBeExtended
+= _rWC
;
447 // a helper struct which adds a fixed (Sfx-)filter to a filter group entry given by iterator
448 struct AppendWildcardToDescriptor
451 ::std::vector
< OUString
> aWildCards
;
454 explicit AppendWildcardToDescriptor( const OUString
& _rWildCard
);
456 // operate on a single class entry
457 void operator() ( const FilterGroupEntryReferrer::value_type
& _rClassReference
)
459 // simply add our wildcards
463 CheckAppendSingleWildcard( _rClassReference
.second
->Second
)
470 AppendWildcardToDescriptor::AppendWildcardToDescriptor( const OUString
& _rWildCard
)
472 DBG_ASSERT( !_rWildCard
.isEmpty(),
473 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: invalid wildcard!" );
474 DBG_ASSERT( _rWildCard
.isEmpty() || _rWildCard
[0] != s_cWildcardSeparator
,
475 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: wildcard already separated!" );
477 aWildCards
.reserve( comphelper::string::getTokenCount(_rWildCard
, s_cWildcardSeparator
) );
479 const sal_Unicode
* pTokenLoop
= _rWildCard
.getStr();
480 const sal_Unicode
* pTokenLoopEnd
= pTokenLoop
+ _rWildCard
.getLength();
481 const sal_Unicode
* pTokenStart
= pTokenLoop
;
482 for ( ; pTokenLoop
!= pTokenLoopEnd
; ++pTokenLoop
)
484 if ( ( s_cWildcardSeparator
== *pTokenLoop
) && ( pTokenLoop
> pTokenStart
) )
485 { // found a new token separator (and a non-empty token)
486 aWildCards
.emplace_back( pTokenStart
, pTokenLoop
- pTokenStart
);
488 // search the start of the next token
489 while ( ( pTokenStart
!= pTokenLoopEnd
) && ( *pTokenStart
!= s_cWildcardSeparator
) )
492 if ( pTokenStart
== pTokenLoopEnd
)
497 pTokenLoop
= pTokenStart
;
500 if ( pTokenLoop
> pTokenStart
)
502 aWildCards
.emplace_back( pTokenStart
, pTokenLoop
- pTokenStart
);
506 static void lcl_InitGlobalClasses( GroupedFilterList
& _rAllFilters
, const FilterClassList
& _rGlobalClasses
, FilterGroupEntryReferrer
& _rGlobalClassesRef
)
508 // we need an extra group in our "all filters" container
509 _rAllFilters
.push_front( FilterGroup() );
510 FilterGroup
& rGlobalFilters
= _rAllFilters
.front();
511 // it's important to work on the reference: we want to access the members of this filter group
512 // by an iterator (FilterGroup::const_iterator)
513 // the referrer for the global classes
515 // initialize the group
517 _rGlobalClasses
.begin(),
518 _rGlobalClasses
.end(),
519 FillClassGroup( rGlobalFilters
, _rGlobalClassesRef
)
522 // in rGlobalFilters: a list of FilterDescriptor's, where each's descriptor's display name is set to the name of a class
523 // in aGlobalClassesRef: a mapping from logical filter names to positions within rGlobalFilters
524 // this way, if we encounter an arbitrary filter, we can easily (and efficient) check if it belongs to a global class
525 // and modify the descriptor for this class accordingly
529 typedef ::std::vector
< ::std::pair
< FilterGroupEntryReferrer::mapped_type
, FilterGroup::iterator
> >
530 MapGroupEntry2GroupEntry
;
531 // this is not really a map - it's just called this way because it is used as a map
535 struct FindGroupEntry
537 FilterGroupEntryReferrer::mapped_type aLookingFor
;
538 explicit FindGroupEntry( FilterGroupEntryReferrer::mapped_type _aLookingFor
) : aLookingFor(std::move( _aLookingFor
)) { }
540 bool operator() ( const MapGroupEntry2GroupEntry::value_type
& _rMapEntry
)
542 return _rMapEntry
.first
== aLookingFor
;
546 struct CopyGroupEntryContent
548 void operator() ( const MapGroupEntry2GroupEntry::value_type
& _rMapEntry
)
550 *_rMapEntry
.second
= *_rMapEntry
.first
;
555 struct CopyNonEmptyFilter
557 FilterGroup
& rTarget
;
558 explicit CopyNonEmptyFilter( FilterGroup
& _rTarget
) :rTarget( _rTarget
) { }
560 void operator() ( const FilterDescriptor
& _rFilter
)
562 if ( !_rFilter
.Second
.isEmpty() )
563 rTarget
.push_back( _rFilter
);
569 static void lcl_GroupAndClassify( TSortedFilterList
& _rFilterMatcher
, GroupedFilterList
& _rAllFilters
)
571 _rAllFilters
.clear();
574 // read the classification of filters
575 FilterClassList aGlobalClasses
, aLocalClasses
;
576 std::vector
<OUString
> aGlobalClassNames
;
577 lcl_ReadClassification( aGlobalClasses
, aGlobalClassNames
, aLocalClasses
);
580 // for the global filter classes
581 FilterGroupEntryReferrer aGlobalClassesRef
;
582 lcl_InitGlobalClasses( _rAllFilters
, aGlobalClasses
, aGlobalClassesRef
);
584 // insert as much placeholders (FilterGroup's) into _rAllFilter for groups as we have global classes
585 // (this assumes that both numbers are the same, which, speaking strictly, must not hold - but it does, as we know ...)
586 sal_Int32 nGlobalClasses
= aGlobalClasses
.size();
587 while ( nGlobalClasses
-- )
588 _rAllFilters
.emplace_back( );
591 // for the local classes:
592 // if n filters belong to a local class, they do not appear in their respective group explicitly, instead
593 // and entry for the class is added to the group and the extensions of the filters are collected under
595 FilterGroupEntryReferrer aLocalClassesRef
;
596 FilterGroup aCollectedLocals
;
598 aLocalClasses
.begin(),
600 FillClassGroup( aCollectedLocals
, aLocalClassesRef
)
602 // to map from the position within aCollectedLocals to positions within the real groups
603 // (where they finally belong to)
604 MapGroupEntry2GroupEntry aLocalFinalPositions
;
607 // now add the filters
608 // the group which we currently work with
609 GroupedFilterList::iterator aCurrentGroup
= _rAllFilters
.end(); // no current group
610 // the filter container of the current group - if this changes between two filters, a new group is reached
611 OUString aCurrentServiceName
;
613 OUString sFilterWildcard
;
614 OUString sFilterName
;
615 // loop through all the filters
616 for ( std::shared_ptr
<const SfxFilter
> pFilter
= _rFilterMatcher
.First(); pFilter
; pFilter
= _rFilterMatcher
.Next() )
618 sFilterName
= pFilter
->GetFilterName();
619 sFilterWildcard
= pFilter
->GetWildcard().getGlob();
620 AppendWildcardToDescriptor
aExtendWildcard( sFilterWildcard
);
622 DBG_ASSERT( !sFilterWildcard
.isEmpty(), "sfx2::lcl_GroupAndClassify: invalid wildcard of this filter!" );
625 // check for a change in the group
626 OUString aServiceName
= pFilter
->GetServiceName();
627 if ( aServiceName
!= aCurrentServiceName
)
628 { // we reached a new group
630 // look for the place in _rAllFilters where this ne group belongs - this is determined
631 // by the order of classes in aGlobalClassNames
632 GroupedFilterList::iterator aGroupPos
= _rAllFilters
.begin();
633 DBG_ASSERT( aGroupPos
!= _rAllFilters
.end(),
634 "sfx2::lcl_GroupAndClassify: invalid all-filters array here!" );
635 // the loop below will work on invalid objects else ...
637 auto aGlobalIter
= std::find(aGlobalClassNames
.begin(), aGlobalClassNames
.end(), aServiceName
);
638 auto nGroupPosShift
= std::min(
639 std::distance(aGlobalClassNames
.begin(), aGlobalIter
),
640 std::distance(aGroupPos
, _rAllFilters
.end()));
641 std::advance(aGroupPos
, nGroupPosShift
);
642 if ( aGroupPos
!= _rAllFilters
.end() )
643 // we found a global class name which matches the doc service name -> fill the filters of this
644 // group in the respective prepared group
645 aCurrentGroup
= aGroupPos
;
647 // insert a new entry in our overall-list
648 aCurrentGroup
= _rAllFilters
.insert( _rAllFilters
.end(), FilterGroup() );
650 // remember the container to properly detect the next group
651 aCurrentServiceName
= aServiceName
;
654 assert(aCurrentGroup
!= _rAllFilters
.end()); //invalid current group!
655 if (aCurrentGroup
== _rAllFilters
.end())
656 aCurrentGroup
= _rAllFilters
.begin();
659 // check if the filter is part of a global group
660 ::std::pair
< FilterGroupEntryReferrer::iterator
, FilterGroupEntryReferrer::iterator
>
661 aBelongsTo
= aGlobalClassesRef
.equal_range( sFilterName
);
662 // add the filter to the entries for these classes
663 // (if they exist - if not, the range is empty and the for_each is a no-op)
671 // add the filter to its group
673 // for this, check if the filter is part of a local filter
674 FilterGroupEntryReferrer::iterator aBelongsToLocal
= aLocalClassesRef
.find( sFilterName
);
675 if ( aLocalClassesRef
.end() != aBelongsToLocal
)
677 // okay, there is a local class which the filter belongs to
678 // -> append the wildcard
679 aExtendWildcard( *aBelongsToLocal
);
681 if ( std::none_of( aLocalFinalPositions
.begin(), aLocalFinalPositions
.end(), FindGroupEntry( aBelongsToLocal
->second
) ) )
682 { // the position within aCollectedLocals has not been mapped to a final position
683 // within the "real" group (aCollectedLocals is only temporary)
684 // -> do this now (as we just encountered the first filter belonging to this local class
685 // add a new entry which is the "real" group entry
686 aCurrentGroup
->push_back( FilterDescriptor( aBelongsToLocal
->second
->First
, OUString() ) );
687 // the position where we inserted the entry
688 FilterGroup::iterator aInsertPos
= aCurrentGroup
->end();
691 aLocalFinalPositions
.emplace_back( aBelongsToLocal
->second
, aInsertPos
);
695 aCurrentGroup
->push_back( FilterDescriptor( pFilter
->GetUIName(), sFilterWildcard
) );
698 // now just complete the infos for the local groups:
699 // During the above loop, they have been collected in aCollectedLocals, but this is only temporary
700 // They have to be copied into their final positions (which are stored in aLocalFinalPositions)
702 aLocalFinalPositions
.begin(),
703 aLocalFinalPositions
.end(),
704 CopyGroupEntryContent()
707 // and remove local groups which do not apply - e.g. have no entries due to the limited content of the
708 // current SfxFilterMatcherIter
710 FilterGroup
& rGlobalFilters
= _rAllFilters
.front();
711 FilterGroup aNonEmptyGlobalFilters
;
713 rGlobalFilters
.begin(),
714 rGlobalFilters
.end(),
715 CopyNonEmptyFilter( aNonEmptyGlobalFilters
)
717 rGlobalFilters
.swap( aNonEmptyGlobalFilters
);
725 Reference
< XFilterManager
> m_xFilterManager
;
726 FileDialogHelper_Impl
* m_pFileDlgImpl
;
727 bool m_bAddExtension
;
730 AppendFilter( const Reference
< XFilterManager
>& _rxFilterManager
,
731 FileDialogHelper_Impl
* _pImpl
, bool _bAddExtension
) :
733 m_xFilterManager( _rxFilterManager
),
734 m_pFileDlgImpl ( _pImpl
),
735 m_bAddExtension ( _bAddExtension
)
738 DBG_ASSERT( m_xFilterManager
.is(), "AppendFilter::AppendFilter: invalid filter manager!" );
739 DBG_ASSERT( m_pFileDlgImpl
, "AppendFilter::AppendFilter: invalid filedlg impl!" );
742 // operate on a single filter
743 void operator() ( const FilterDescriptor
& _rFilterEntry
)
745 OUString sDisplayText
= m_bAddExtension
746 ? addExtension( _rFilterEntry
.First
, _rFilterEntry
.Second
, true, *m_pFileDlgImpl
)
747 : _rFilterEntry
.First
;
748 m_xFilterManager
->appendFilter( sDisplayText
, _rFilterEntry
.Second
);
754 // = handling for the "all files" entry
757 static bool lcl_hasAllFilesFilter( TSortedFilterList
& _rFilterMatcher
, OUString
& /* [out] */ _rAllFilterName
)
759 bool bHasAll
= false;
760 _rAllFilterName
= SfxResId( STR_SFX_FILTERNAME_ALL
);
763 // check if there's already a filter <ALL>
764 for ( std::shared_ptr
<const SfxFilter
> pFilter
= _rFilterMatcher
.First(); pFilter
&& !bHasAll
; pFilter
= _rFilterMatcher
.Next() )
766 if ( pFilter
->GetUIName() == _rAllFilterName
)
773 static void lcl_EnsureAllFilesEntry( TSortedFilterList
& _rFilterMatcher
, GroupedFilterList
& _rFilters
)
776 OUString sAllFilterName
;
777 if ( !lcl_hasAllFilesFilter( _rFilterMatcher
, sAllFilterName
) )
779 // get the first group of filters (by definition, this group contains the global classes)
780 DBG_ASSERT( !_rFilters
.empty(), "lcl_EnsureAllFilesEntry: invalid filter list!" );
781 if ( !_rFilters
.empty() )
783 FilterGroup
& rGlobalClasses
= *_rFilters
.begin();
784 rGlobalClasses
.push_front( FilterDescriptor( sAllFilterName
, FILEDIALOG_FILTER_ALL
) );
790 // = filling an XFilterManager
794 struct AppendFilterGroup
797 Reference
< XFilterManager
> m_xFilterManager
;
798 Reference
< XFilterGroupManager
> m_xFilterGroupManager
;
799 FileDialogHelper_Impl
* m_pFileDlgImpl
;
802 AppendFilterGroup( const Reference
< XFilterManager
>& _rxFilterManager
, FileDialogHelper_Impl
* _pImpl
)
803 :m_xFilterManager ( _rxFilterManager
)
804 ,m_xFilterGroupManager ( _rxFilterManager
, UNO_QUERY
)
805 ,m_pFileDlgImpl ( _pImpl
)
807 DBG_ASSERT( m_xFilterManager
.is(), "AppendFilterGroup::AppendFilterGroup: invalid filter manager!" );
808 DBG_ASSERT( m_pFileDlgImpl
, "AppendFilterGroup::AppendFilterGroup: invalid filedlg impl!" );
811 void appendGroup( const FilterGroup
& _rGroup
, bool _bAddExtension
)
815 if ( m_xFilterGroupManager
.is() )
816 { // the file dialog implementation supports visual grouping of filters
817 // create a representation of the group which is understandable by the XFilterGroupManager
818 if ( !_rGroup
.empty() )
820 Sequence
< StringPair
> aFilters( comphelper::containerToSequence(_rGroup
) );
821 if ( _bAddExtension
)
823 for ( StringPair
& filter
: asNonConstRange(aFilters
) )
824 filter
.First
= addExtension( filter
.First
, filter
.Second
, true, *m_pFileDlgImpl
);
826 m_xFilterGroupManager
->appendFilterGroup( OUString(), aFilters
);
834 AppendFilter( m_xFilterManager
, m_pFileDlgImpl
, _bAddExtension
) );
837 catch( const Exception
& )
839 DBG_UNHANDLED_EXCEPTION("sfx.dialog");
843 // operate on a single filter group
844 void operator() ( const FilterGroup
& _rGroup
)
846 appendGroup( _rGroup
, true );
852 TSortedFilterList::TSortedFilterList(const css::uno::Reference
< css::container::XEnumeration
>& xFilterList
)
855 if (!xFilterList
.is())
859 while(xFilterList
->hasMoreElements())
861 ::comphelper::SequenceAsHashMap
lFilterProps (xFilterList
->nextElement());
862 OUString sFilterName
= lFilterProps
.getUnpackedValueOrDefault(
865 if (!sFilterName
.isEmpty())
866 m_lFilters
.push_back(sFilterName
);
871 std::shared_ptr
<const SfxFilter
> TSortedFilterList::First()
874 return impl_getFilter(m_nIterator
);
878 std::shared_ptr
<const SfxFilter
> TSortedFilterList::Next()
881 return impl_getFilter(m_nIterator
);
885 std::shared_ptr
<const SfxFilter
> TSortedFilterList::impl_getFilter(sal_Int32 nIndex
)
887 if (nIndex
<0 || o3tl::make_unsigned(nIndex
)>=m_lFilters
.size())
889 const OUString
& sFilterName
= m_lFilters
[nIndex
];
890 if (sFilterName
.isEmpty())
892 return SfxFilter::GetFilterByName(sFilterName
);
896 void appendFiltersForSave( TSortedFilterList
& _rFilterMatcher
,
897 const Reference
< XFilterManager
>& _rxFilterManager
,
898 OUString
& _rFirstNonEmpty
, FileDialogHelper_Impl
& _rFileDlgImpl
,
899 std::u16string_view _rFactory
)
901 DBG_ASSERT( _rxFilterManager
.is(), "sfx2::appendFiltersForSave: invalid manager!" );
902 if ( !_rxFilterManager
.is() )
908 // retrieve the default filter for this application module.
909 // It must be set as first of the generated filter list.
910 std::shared_ptr
<const SfxFilter
> pDefaultFilter
= SfxFilterContainer::GetDefaultFilter_Impl(_rFactory
);
911 // Only use one extension (#i32434#)
912 // (and always the first if there are more than one)
913 sExtension
= pDefaultFilter
->GetWildcard().getGlob().getToken(0, ';');
914 sUIName
= addExtension( pDefaultFilter
->GetUIName(), sExtension
, false, _rFileDlgImpl
);
917 _rxFilterManager
->appendFilter( sUIName
, sExtension
);
918 if ( _rFirstNonEmpty
.isEmpty() )
919 _rFirstNonEmpty
= sUIName
;
921 catch( const IllegalArgumentException
& )
923 SAL_WARN( "sfx.dialog", "Could not append DefaultFilter" << sUIName
);
926 for ( std::shared_ptr
<const SfxFilter
> pFilter
= _rFilterMatcher
.First(); pFilter
; pFilter
= _rFilterMatcher
.Next() )
928 if (pFilter
->GetName() == pDefaultFilter
->GetName())
931 // Only use one extension (#i32434#)
932 // (and always the first if there are more than one)
933 sExtension
= pFilter
->GetWildcard().getGlob().getToken(0, ';');
934 sUIName
= addExtension( pFilter
->GetUIName(), sExtension
, false, _rFileDlgImpl
);
937 _rxFilterManager
->appendFilter( sUIName
, sExtension
);
938 if ( _rFirstNonEmpty
.isEmpty() )
939 _rFirstNonEmpty
= sUIName
;
941 catch( const IllegalArgumentException
& )
943 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName
);
952 ExportFilter( OUString _aUIName
, OUString _aWildcard
) :
953 aUIName(std::move( _aUIName
)), aWildcard(std::move( _aWildcard
)) {}
961 void appendExportFilters( TSortedFilterList
& _rFilterMatcher
,
962 const Reference
< XFilterManager
>& _rxFilterManager
,
963 OUString
& _rFirstNonEmpty
, FileDialogHelper_Impl
& _rFileDlgImpl
)
965 DBG_ASSERT( _rxFilterManager
.is(), "sfx2::appendExportFilters: invalid manager!" );
966 if ( !_rxFilterManager
.is() )
969 sal_Int32 nHTMLIndex
= -1;
970 sal_Int32 nXHTMLIndex
= -1;
971 sal_Int32 nPDFIndex
= -1;
973 OUString sExtensions
;
974 std::vector
< ExportFilter
> aImportantFilterGroup
;
975 std::vector
< ExportFilter
> aFilterGroup
;
976 Reference
< XFilterGroupManager
> xFilterGroupManager( _rxFilterManager
, UNO_QUERY
);
979 for ( std::shared_ptr
<const SfxFilter
> pFilter
= _rFilterMatcher
.First(); pFilter
; pFilter
= _rFilterMatcher
.Next() )
981 sTypeName
= pFilter
->GetTypeName();
982 sUIName
= pFilter
->GetUIName();
983 sExtensions
= pFilter
->GetWildcard().getGlob();
984 ExportFilter
aExportFilter( sUIName
, sExtensions
);
986 if ( nHTMLIndex
== -1 &&
987 ( sTypeName
== "generic_HTML" || sTypeName
== "graphic_HTML" ) )
989 aImportantFilterGroup
.insert( aImportantFilterGroup
.begin(), aExportFilter
);
992 else if ( nXHTMLIndex
== -1 && sTypeName
== "XHTML_File" )
994 std::vector
< ExportFilter
>::iterator aIter
= aImportantFilterGroup
.begin();
995 if ( nHTMLIndex
== -1 )
996 aImportantFilterGroup
.insert( aIter
, aExportFilter
);
998 aImportantFilterGroup
.insert( ++aIter
, aExportFilter
);
1001 else if ( nPDFIndex
== -1 && sTypeName
== "pdf_Portable_Document_Format" )
1003 std::vector
< ExportFilter
>::iterator aIter
= aImportantFilterGroup
.begin();
1004 if ( nHTMLIndex
!= -1 )
1006 if ( nXHTMLIndex
!= -1 )
1008 aImportantFilterGroup
.insert( aIter
, aExportFilter
);
1012 aFilterGroup
.push_back( aExportFilter
);
1015 if ( xFilterGroupManager
.is() )
1017 // Add both html/pdf filter as a filter group to get a separator between both groups
1018 if ( !aImportantFilterGroup
.empty() )
1020 Sequence
< StringPair
> aFilters( aImportantFilterGroup
.size() );
1021 auto pFilters
= aFilters
.getArray();
1022 for ( sal_Int32 i
= 0; i
< static_cast<sal_Int32
>(aImportantFilterGroup
.size()); i
++ )
1024 pFilters
[i
].First
= addExtension( aImportantFilterGroup
[i
].aUIName
,
1025 aImportantFilterGroup
[i
].aWildcard
,
1026 false, _rFileDlgImpl
);
1027 pFilters
[i
].Second
= aImportantFilterGroup
[i
].aWildcard
;
1032 xFilterGroupManager
->appendFilterGroup( OUString(), aFilters
);
1034 catch( const IllegalArgumentException
& )
1039 if ( !aFilterGroup
.empty() )
1041 Sequence
< StringPair
> aFilters( aFilterGroup
.size() );
1042 auto pFilters
= aFilters
.getArray();
1043 for ( sal_Int32 i
= 0; i
< static_cast<sal_Int32
>(aFilterGroup
.size()); i
++ )
1045 pFilters
[i
].First
= addExtension( aFilterGroup
[i
].aUIName
,
1046 aFilterGroup
[i
].aWildcard
,
1047 false, _rFileDlgImpl
);
1048 pFilters
[i
].Second
= aFilterGroup
[i
].aWildcard
;
1053 xFilterGroupManager
->appendFilterGroup( OUString(), aFilters
);
1055 catch( const IllegalArgumentException
& )
1062 // Fallback solution just add both filter groups as single filters
1065 for ( n
= 0; n
< static_cast<sal_Int32
>(aImportantFilterGroup
.size()); n
++ )
1069 OUString aUIName
= addExtension( aImportantFilterGroup
[n
].aUIName
,
1070 aImportantFilterGroup
[n
].aWildcard
,
1071 false, _rFileDlgImpl
);
1072 _rxFilterManager
->appendFilter( aUIName
, aImportantFilterGroup
[n
].aWildcard
);
1073 if ( _rFirstNonEmpty
.isEmpty() )
1074 _rFirstNonEmpty
= sUIName
;
1077 catch( const IllegalArgumentException
& )
1079 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName
);
1083 for ( n
= 0; n
< static_cast<sal_Int32
>(aFilterGroup
.size()); n
++ )
1087 OUString aUIName
= addExtension( aFilterGroup
[n
].aUIName
,
1088 aFilterGroup
[n
].aWildcard
,
1089 false, _rFileDlgImpl
);
1090 _rxFilterManager
->appendFilter( aUIName
, aFilterGroup
[n
].aWildcard
);
1091 if ( _rFirstNonEmpty
.isEmpty() )
1092 _rFirstNonEmpty
= sUIName
;
1095 catch( const IllegalArgumentException
& )
1097 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName
);
1104 void appendFiltersForOpen( TSortedFilterList
& _rFilterMatcher
,
1105 const Reference
< XFilterManager
>& _rxFilterManager
,
1106 OUString
& _rFirstNonEmpty
, FileDialogHelper_Impl
& _rFileDlgImpl
)
1108 DBG_ASSERT( _rxFilterManager
.is(), "sfx2::appendFiltersForOpen: invalid manager!" );
1109 if ( !_rxFilterManager
.is() )
1113 // group and classify the filters
1114 GroupedFilterList aAllFilters
;
1115 lcl_GroupAndClassify( _rFilterMatcher
, aAllFilters
);
1118 // ensure that we have the one "all files" entry
1119 lcl_EnsureAllFilesEntry( _rFilterMatcher
, aAllFilters
);
1122 // the first non-empty string - which we assume is the first overall entry
1123 if ( !aAllFilters
.empty() )
1125 const FilterGroup
& rFirstGroup
= *aAllFilters
.begin(); // should be the global classes
1126 if ( !rFirstGroup
.empty() )
1127 _rFirstNonEmpty
= rFirstGroup
.begin()->First
;
1128 // append first group, without extension
1129 AppendFilterGroup
aGroup( _rxFilterManager
, &_rFileDlgImpl
);
1130 aGroup
.appendGroup( rFirstGroup
, false );
1134 // append the filters to the manager
1135 if ( !aAllFilters
.empty() )
1137 ::std::list
< FilterGroup
>::iterator pIter
= aAllFilters
.begin();
1140 pIter
, // first filter group was handled separately, see above
1142 AppendFilterGroup( _rxFilterManager
, &_rFileDlgImpl
) );
1146 OUString
addExtension( const OUString
& _rDisplayText
,
1147 const OUString
& _rExtension
,
1148 bool _bForOpen
, FileDialogHelper_Impl
& _rFileDlgImpl
)
1150 OUString sRet
= _rDisplayText
;
1152 if ( sRet
.indexOf( "(*.*)" ) == -1 )
1154 OUString sExt
= _rExtension
;
1157 // show '*' in extensions only when opening a document
1158 sExt
= sExt
.replaceAll("*", "");
1160 sRet
+= " (" + sExt
+ ")";
1162 _rFileDlgImpl
.addFilterPair( _rDisplayText
, sRet
);
1170 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */