xref: /core/sfx2/source/dialog/filtergrouping.cxx (revision d4f07d4c)
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 "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>
38 
39 #include <list>
40 #include <utility>
41 #include <vector>
42 #include <map>
43 #include <algorithm>
44 
45 
46 namespace sfx2
47 {
48 
49 
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;
55 
56 
57     /**
58 
59     Some general words about what's going on here...
60 
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>
63 
64     <p>But then the PM came. And everything changed...</p>
65 
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>
70 
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>
73 
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>
76 
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>
83 
84     <p>Such an entry as "Text documents" is - within the scope of this file - called "class" or "filter class".</p>
85 
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>
89 
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>
95 
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>
99 
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>
102 
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>
106 
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>
110 
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>
113 
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>
118 
119     <p>If you got it, go try understanding this file :).</p>
120 
121     */
122 
123 
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
127 
128     /// the logical name of a filter
129     typedef OUString                     FilterName;
130 
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;
135 
136     namespace {
137 
138     /// a descriptor for a filter class (which in the final dialog is represented by one filter entry)
139     struct FilterClass
140     {
141         OUString             sDisplayName;       // the display name
142         Sequence< FilterName >      aSubFilters;        // the (logical) names of the filter which belong to the class
143     };
144 
145     }
146 
147     typedef ::std::list< FilterClass >                                  FilterClassList;
148     typedef ::std::map< OUString, FilterClassList::iterator >    FilterClassReferrer;
149 
150 
151 // = reading of configuration data
152 
153 
lcl_ReadFilterClass(const OConfigurationNode & _rClassesNode,const OUString & _rLogicalClassName,FilterClass & _rClass)154     static void lcl_ReadFilterClass( const OConfigurationNode& _rClassesNode, const OUString& _rLogicalClassName,
155         FilterClass& /* [out] */ _rClass )
156     {
157             // the description node for the current class
158         OConfigurationNode aClassDesc = _rClassesNode.openNode( _rLogicalClassName );
159 
160         // the values
161         aClassDesc.getNodeValue( u"DisplayName"_ustr ) >>= _rClass.sDisplayName;
162         aClassDesc.getNodeValue( u"Filters"_ustr ) >>= _rClass.aSubFilters;
163     }
164 
165     namespace {
166 
167     struct CreateEmptyClassRememberPos
168     {
169     protected:
170         FilterClassList&        m_rClassList;
171         FilterClassReferrer&    m_rClassesReferrer;
172 
173     public:
CreateEmptyClassRememberPossfx2::__anon701969180211::CreateEmptyClassRememberPos174         CreateEmptyClassRememberPos( FilterClassList& _rClassList, FilterClassReferrer& _rClassesReferrer )
175             :m_rClassList       ( _rClassList )
176             ,m_rClassesReferrer ( _rClassesReferrer )
177         {
178         }
179 
180         // operate on a single class name
operator ()sfx2::__anon701969180211::CreateEmptyClassRememberPos181         void operator() ( const FilterName& _rLogicalFilterName )
182         {
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 );
190         }
191     };
192 
193 
194     struct ReadGlobalFilter
195     {
196     protected:
197         OConfigurationNode      m_aClassesNode;
198         FilterClassReferrer&    m_aClassReferrer;
199 
200     public:
ReadGlobalFiltersfx2::__anon701969180211::ReadGlobalFilter201         ReadGlobalFilter( OConfigurationNode _aClassesNode, FilterClassReferrer& _rClassesReferrer )
202             :m_aClassesNode     (std::move( _aClassesNode ))
203             ,m_aClassReferrer   ( _rClassesReferrer )
204         {
205         }
206 
207         // operate on a single logical name
operator ()sfx2::__anon701969180211::ReadGlobalFilter208         void operator() ( const FilterName& _rName )
209         {
210             FilterClassReferrer::iterator aClassRef = m_aClassReferrer.find( _rName );
211             if ( m_aClassReferrer.end() == aClassRef )
212             {
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...
217             }
218             else
219             {
220                 // read the data of this class into the node referred to by aClassRef
221                 lcl_ReadFilterClass( m_aClassesNode, _rName, *aClassRef->second );
222             }
223         }
224     };
225 
226     }
227 
lcl_ReadGlobalFilters(const OConfigurationNode & _rFilterClassification,FilterClassList & _rGlobalClasses,std::vector<OUString> & _rGlobalClassNames)228     static void lcl_ReadGlobalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rGlobalClasses, std::vector<OUString>& _rGlobalClassNames )
229     {
230         _rGlobalClasses.clear();
231         _rGlobalClassNames.clear();
232 
233         // get the list describing the order of all global classes
234         Sequence< OUString > aGlobalClasses;
235         _rFilterClassification.getNodeValue( u"GlobalFilters/Order"_ustr ) >>= aGlobalClasses;
236 
237         // copy the logical names
238         comphelper::sequenceToContainer(_rGlobalClassNames, aGlobalClasses);
239 
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 )
249         );
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
253 
254 
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( aFilterClassesNode, aClassReferrer )
263         );
264     }
265 
266     namespace {
267 
268     struct ReadLocalFilter
269     {
270     protected:
271         OConfigurationNode      m_aClassesNode;
272         FilterClassList&        m_rClasses;
273 
274     public:
ReadLocalFiltersfx2::__anon701969180311::ReadLocalFilter275         ReadLocalFilter( OConfigurationNode _aClassesNode, FilterClassList& _rClasses )
276             :m_aClassesNode (std::move( _aClassesNode ))
277             ,m_rClasses     ( _rClasses )
278         {
279         }
280 
281         // operate on a single logical name
operator ()sfx2::__anon701969180311::ReadLocalFilter282         void operator() ( const FilterName& _rName )
283         {
284             // read the data for this class
285             FilterClass aClass;
286             lcl_ReadFilterClass( m_aClassesNode, _rName, aClass );
287 
288             // insert the class descriptor
289             m_rClasses.push_back( aClass );
290         }
291     };
292 
293     }
294 
lcl_ReadLocalFilters(const OConfigurationNode & _rFilterClassification,FilterClassList & _rLocalClasses)295     static void lcl_ReadLocalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rLocalClasses )
296     {
297         _rLocalClasses.clear();
298 
299         // the node for the local classes
300         OConfigurationNode aFilterClassesNode =
301             _rFilterClassification.openNode( u"LocalFilters/Classes"_ustr );
302         const Sequence< OUString > aFilterClasses = aFilterClassesNode.getNodeNames();
303 
304         ::std::for_each(
305             aFilterClasses.begin(),
306             aFilterClasses.end(),
307             ReadLocalFilter( aFilterClassesNode, _rLocalClasses )
308         );
309     }
310 
311 
lcl_ReadClassification(FilterClassList & _rGlobalClasses,std::vector<OUString> & _rGlobalClassNames,FilterClassList & _rLocalClasses)312     static void lcl_ReadClassification( FilterClassList& _rGlobalClasses, std::vector<OUString>& _rGlobalClassNames, FilterClassList& _rLocalClasses )
313     {
314 
315         // open our config node
316         OConfigurationTreeRoot aFilterClassification = OConfigurationTreeRoot::createWithComponentContext(
317             ::comphelper::getProcessComponentContext(),
318             u"org.openoffice.Office.UI/FilterClassification"_ustr,
319             -1,
320             OConfigurationTreeRoot::CM_READONLY
321         );
322 
323 
324         // go for the global classes
325         lcl_ReadGlobalFilters( aFilterClassification, _rGlobalClasses, _rGlobalClassNames );
326 
327 
328         // go for the local classes
329         lcl_ReadLocalFilters( aFilterClassification, _rLocalClasses );
330 
331     }
332 
333 
334 // = grouping and classifying
335 
336     namespace {
337 
338     // a struct which adds helps remembering a reference to a class entry
339     struct ReferToFilterEntry
340     {
341     protected:
342         FilterGroupEntryReferrer&   m_rEntryReferrer;
343         FilterGroup::iterator       m_aClassPos;
344 
345     public:
ReferToFilterEntrysfx2::__anon701969180411::ReferToFilterEntry346         ReferToFilterEntry( FilterGroupEntryReferrer& _rEntryReferrer, FilterGroup::iterator  _aClassPos )
347             :m_rEntryReferrer( _rEntryReferrer )
348             ,m_aClassPos(std::move( _aClassPos ))
349         {
350         }
351 
352         // operate on a single filter name
operator ()sfx2::__anon701969180411::ReferToFilterEntry353         void operator() ( const FilterName& _rName )
354         {
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);
360         }
361     };
362 
363 
364     struct FillClassGroup
365     {
366     protected:
367         FilterGroup&                m_rClassGroup;
368         FilterGroupEntryReferrer&   m_rClassReferrer;
369 
370     public:
FillClassGroupsfx2::__anon701969180411::FillClassGroup371         FillClassGroup( FilterGroup& _rClassGroup, FilterGroupEntryReferrer& _rClassReferrer )
372             :m_rClassGroup      ( _rClassGroup )
373             ,m_rClassReferrer   ( _rClassReferrer )
374         {
375         }
376 
377         // operate on a single class
operator ()sfx2::__anon701969180411::FillClassGroup378         void operator() ( const FilterClass& _rClass )
379         {
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;
384 
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;
390 
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 )
397             );
398         }
399     };
400 
401     }
402 
403     const sal_Unicode s_cWildcardSeparator( ';' );
404 
405     constexpr OUString SEPARATOR = u";"_ustr;
406 
407     namespace {
408 
409     struct CheckAppendSingleWildcard
410     {
411         OUString& _rToBeExtended;
412 
CheckAppendSingleWildcardsfx2::__anon701969180511::CheckAppendSingleWildcard413         explicit CheckAppendSingleWildcard( OUString& _rBase ) : _rToBeExtended( _rBase ) { }
414 
operator ()sfx2::__anon701969180511::CheckAppendSingleWildcard415         void operator() ( std::u16string_view _rWC )
416         {
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 ] )
423                     )
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 ] )
428                         )
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;
433                     }
434                 }
435             }
436 
437             if ( !_rToBeExtended.isEmpty() )
438                 _rToBeExtended += SEPARATOR;
439             _rToBeExtended += _rWC;
440         }
441     };
442 
443 
444     // a helper struct which adds a fixed (Sfx-)filter to a filter group entry given by iterator
445     struct AppendWildcardToDescriptor
446     {
447     protected:
448         ::std::vector< OUString > aWildCards;
449 
450     public:
451         explicit AppendWildcardToDescriptor( const OUString& _rWildCard );
452 
453         // operate on a single class entry
operator ()sfx2::__anon701969180511::AppendWildcardToDescriptor454         void operator() ( const FilterGroupEntryReferrer::value_type& _rClassReference )
455         {
456             // simply add our wildcards
457             ::std::for_each(
458                 aWildCards.begin(),
459                 aWildCards.end(),
460                 CheckAppendSingleWildcard( _rClassReference.second->Second )
461             );
462         }
463     };
464 
465     }
466 
AppendWildcardToDescriptor(const OUString & _rWildCard)467     AppendWildcardToDescriptor::AppendWildcardToDescriptor( const OUString& _rWildCard )
468     {
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!" );
473 
474         aWildCards.reserve( comphelper::string::getTokenCount(_rWildCard, s_cWildcardSeparator) );
475 
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 )
480         {
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 );
484 
485                 // search the start of the next token
486                 while ( ( pTokenStart != pTokenLoopEnd ) && ( *pTokenStart != s_cWildcardSeparator ) )
487                     ++pTokenStart;
488 
489                 if ( pTokenStart == pTokenLoopEnd )
490                     // reached the end
491                     break;
492 
493                 ++pTokenStart;
494                 pTokenLoop = pTokenStart;
495             }
496         }
497         if ( pTokenLoop > pTokenStart )
498             // the last one...
499             aWildCards.emplace_back( pTokenStart, pTokenLoop - pTokenStart );
500     }
501 
502 
lcl_InitGlobalClasses(GroupedFilterList & _rAllFilters,const FilterClassList & _rGlobalClasses,FilterGroupEntryReferrer & _rGlobalClassesRef)503     static void lcl_InitGlobalClasses( GroupedFilterList& _rAllFilters, const FilterClassList& _rGlobalClasses, FilterGroupEntryReferrer& _rGlobalClassesRef )
504     {
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
511 
512         // initialize the group
513         ::std::for_each(
514             _rGlobalClasses.begin(),
515             _rGlobalClasses.end(),
516             FillClassGroup( rGlobalFilters, _rGlobalClassesRef )
517         );
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
523     }
524 
525 
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
529 
530     namespace {
531 
532     struct FindGroupEntry
533     {
534         FilterGroupEntryReferrer::mapped_type aLookingFor;
FindGroupEntrysfx2::__anon701969180611::FindGroupEntry535         explicit FindGroupEntry( FilterGroupEntryReferrer::mapped_type _aLookingFor ) : aLookingFor(std::move( _aLookingFor )) { }
536 
operator ()sfx2::__anon701969180611::FindGroupEntry537         bool operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
538         {
539             return _rMapEntry.first == aLookingFor;
540         }
541     };
542 
543     struct CopyGroupEntryContent
544     {
operator ()sfx2::__anon701969180611::CopyGroupEntryContent545         void operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
546         {
547             *_rMapEntry.second = *_rMapEntry.first;
548         }
549     };
550 
551 
552     struct CopyNonEmptyFilter
553     {
554         FilterGroup& rTarget;
CopyNonEmptyFiltersfx2::__anon701969180611::CopyNonEmptyFilter555         explicit CopyNonEmptyFilter( FilterGroup& _rTarget ) :rTarget( _rTarget ) { }
556 
operator ()sfx2::__anon701969180611::CopyNonEmptyFilter557         void operator() ( const FilterDescriptor& _rFilter )
558         {
559             if ( !_rFilter.Second.isEmpty() )
560                 rTarget.push_back( _rFilter );
561         }
562     };
563 
564     }
565 
lcl_GroupAndClassify(TSortedFilterList & _rFilterMatcher,GroupedFilterList & _rAllFilters)566     static void lcl_GroupAndClassify( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rAllFilters )
567     {
568         _rAllFilters.clear();
569 
570 
571         // read the classification of filters
572         FilterClassList aGlobalClasses, aLocalClasses;
573         std::vector<OUString> aGlobalClassNames;
574         lcl_ReadClassification( aGlobalClasses, aGlobalClassNames, aLocalClasses );
575 
576 
577         // for the global filter classes
578         FilterGroupEntryReferrer aGlobalClassesRef;
579         lcl_InitGlobalClasses( _rAllFilters, aGlobalClasses, aGlobalClassesRef );
580 
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( );
586 
587 
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 )
598         );
599         // to map from the position within aCollectedLocals to positions within the real groups
600         // (where they finally belong to)
601         MapGroupEntry2GroupEntry    aLocalFinalPositions;
602 
603 
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;
609 
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() )
614         {
615             sFilterName = pFilter->GetFilterName();
616             sFilterWildcard = pFilter->GetWildcard().getGlob();
617             AppendWildcardToDescriptor aExtendWildcard( sFilterWildcard );
618 
619             DBG_ASSERT( !sFilterWildcard.isEmpty(), "sfx2::lcl_GroupAndClassify: invalid wildcard of this filter!" );
620 
621 
622             // check for a change in the group
623             OUString aServiceName = pFilter->GetServiceName();
624             if ( aServiceName != aCurrentServiceName )
625             {   // we reached a new group
626 
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() );
646 
647                 // remember the container to properly detect the next group
648                 aCurrentServiceName = aServiceName;
649             }
650 
651             assert(aCurrentGroup != _rAllFilters.end()); //invalid current group!
652             if (aCurrentGroup == _rAllFilters.end())
653                 aCurrentGroup = _rAllFilters.begin();
654 
655 
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
665             );
666 
667 
668             // add the filter to its group
669 
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 )
673             {
674                 // okay, there is a local class which the filter belongs to
675                 // -> append the wildcard
676                 aExtendWildcard( *aBelongsToLocal );
677 
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 );
689                 }
690             }
691             else
692                 aCurrentGroup->push_back( FilterDescriptor( pFilter->GetUIName(), sFilterWildcard ) );
693         }
694 
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()
702         );
703 
704         // and remove local groups which do not apply - e.g. have no entries due to the limited content of the
705         // current SfxFilterMatcherIter
706 
707         FilterGroup& rGlobalFilters = _rAllFilters.front();
708         FilterGroup aNonEmptyGlobalFilters;
709         ::std::for_each(
710             rGlobalFilters.begin(),
711             rGlobalFilters.end(),
712             CopyNonEmptyFilter( aNonEmptyGlobalFilters )
713         );
714         rGlobalFilters.swap( aNonEmptyGlobalFilters );
715     }
716 
717     namespace {
718 
719     struct AppendFilter
720     {
721         protected:
722             Reference< XFilterManager >         m_xFilterManager;
723             FileDialogHelper_Impl*              m_pFileDlgImpl;
724             bool                                m_bAddExtension;
725 
726         public:
AppendFiltersfx2::__anon701969180711::AppendFilter727             AppendFilter( const Reference< XFilterManager >& _rxFilterManager,
728                           FileDialogHelper_Impl* _pImpl, bool _bAddExtension ) :
729 
730                 m_xFilterManager( _rxFilterManager ),
731                 m_pFileDlgImpl  ( _pImpl ),
732                 m_bAddExtension ( _bAddExtension )
733 
734             {
735                 DBG_ASSERT( m_xFilterManager.is(), "AppendFilter::AppendFilter: invalid filter manager!" );
736                 DBG_ASSERT( m_pFileDlgImpl, "AppendFilter::AppendFilter: invalid filedlg impl!" );
737             }
738 
739             // operate on a single filter
operator ()sfx2::__anon701969180711::AppendFilter740             void operator() ( const FilterDescriptor& _rFilterEntry )
741             {
742                 OUString sDisplayText = m_bAddExtension
743                     ? addExtension( _rFilterEntry.First, _rFilterEntry.Second, true, *m_pFileDlgImpl )
744                     : _rFilterEntry.First;
745                 m_xFilterManager->appendFilter( sDisplayText, _rFilterEntry.Second );
746             }
747     };
748 
749     }
750 
751 // = handling for the "all files" entry
752 
753 
lcl_hasAllFilesFilter(TSortedFilterList & _rFilterMatcher,OUString & _rAllFilterName)754     static bool lcl_hasAllFilesFilter( TSortedFilterList& _rFilterMatcher, OUString& /* [out] */ _rAllFilterName )
755     {
756         bool        bHasAll = false;
757         _rAllFilterName = SfxResId( STR_SFX_FILTERNAME_ALL );
758 
759 
760         // check if there's already a filter <ALL>
761         for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter && !bHasAll; pFilter = _rFilterMatcher.Next() )
762         {
763             if ( pFilter->GetUIName() == _rAllFilterName )
764                 bHasAll = true;
765         }
766         return bHasAll;
767     }
768 
769 
lcl_EnsureAllFilesEntry(TSortedFilterList & _rFilterMatcher,GroupedFilterList & _rFilters)770     static void lcl_EnsureAllFilesEntry( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rFilters )
771     {
772 
773         OUString sAllFilterName;
774         if ( !lcl_hasAllFilesFilter( _rFilterMatcher, sAllFilterName ) )
775         {
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() )
779             {
780                 FilterGroup& rGlobalClasses = *_rFilters.begin();
781                 rGlobalClasses.push_front( FilterDescriptor( sAllFilterName, FILEDIALOG_FILTER_ALL ) );
782             }
783         }
784     }
785 
786 
787 // = filling an XFilterManager
788 
789     namespace {
790 
791     struct AppendFilterGroup
792     {
793     protected:
794         Reference< XFilterManager >         m_xFilterManager;
795         Reference< XFilterGroupManager >    m_xFilterGroupManager;
796         FileDialogHelper_Impl*              m_pFileDlgImpl;
797 
798     public:
AppendFilterGroupsfx2::__anon701969180811::AppendFilterGroup799         AppendFilterGroup( const Reference< XFilterManager >& _rxFilterManager, FileDialogHelper_Impl* _pImpl )
800             :m_xFilterManager       ( _rxFilterManager )
801             ,m_xFilterGroupManager  ( _rxFilterManager, UNO_QUERY )
802             ,m_pFileDlgImpl         ( _pImpl )
803         {
804             DBG_ASSERT( m_xFilterManager.is(), "AppendFilterGroup::AppendFilterGroup: invalid filter manager!" );
805             DBG_ASSERT( m_pFileDlgImpl, "AppendFilterGroup::AppendFilterGroup: invalid filedlg impl!" );
806         }
807 
appendGroupsfx2::__anon701969180811::AppendFilterGroup808         void appendGroup( const FilterGroup& _rGroup, bool _bAddExtension )
809         {
810             try
811             {
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() )
816                     {
817                         Sequence< StringPair > aFilters( comphelper::containerToSequence(_rGroup) );
818                         if ( _bAddExtension )
819                         {
820                             for ( StringPair & filter : asNonConstRange(aFilters) )
821                                 filter.First = addExtension( filter.First, filter.Second, true, *m_pFileDlgImpl );
822                         }
823                         m_xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
824                     }
825                 }
826                 else
827                 {
828                     ::std::for_each(
829                         _rGroup.begin(),
830                         _rGroup.end(),
831                         AppendFilter( m_xFilterManager, m_pFileDlgImpl, _bAddExtension ) );
832                 }
833             }
834             catch( const Exception& )
835             {
836                 DBG_UNHANDLED_EXCEPTION("sfx.dialog");
837             }
838         }
839 
840         // operate on a single filter group
operator ()sfx2::__anon701969180811::AppendFilterGroup841         void operator() ( const FilterGroup& _rGroup )
842         {
843             appendGroup( _rGroup, true );
844         }
845     };
846 
847     }
848 
TSortedFilterList(const css::uno::Reference<css::container::XEnumeration> & xFilterList)849     TSortedFilterList::TSortedFilterList(const css::uno::Reference< css::container::XEnumeration >& xFilterList)
850         : m_nIterator(0)
851     {
852         if (!xFilterList.is())
853             return;
854 
855         m_lFilters.clear();
856         while(xFilterList->hasMoreElements())
857         {
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);
864         }
865     }
866 
867 
First()868     std::shared_ptr<const SfxFilter> TSortedFilterList::First()
869     {
870         m_nIterator = 0;
871         return impl_getFilter(m_nIterator);
872     }
873 
874 
Next()875     std::shared_ptr<const SfxFilter> TSortedFilterList::Next()
876     {
877         ++m_nIterator;
878         return impl_getFilter(m_nIterator);
879     }
880 
881 
impl_getFilter(sal_Int32 nIndex)882     std::shared_ptr<const SfxFilter> TSortedFilterList::impl_getFilter(sal_Int32 nIndex)
883     {
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);
890     }
891 
892 
appendFiltersForSave(TSortedFilterList & _rFilterMatcher,const Reference<XFilterManager> & _rxFilterManager,OUString & _rFirstNonEmpty,FileDialogHelper_Impl & _rFileDlgImpl,std::u16string_view _rFactory)893     void appendFiltersForSave( TSortedFilterList& _rFilterMatcher,
894                                const Reference< XFilterManager >& _rxFilterManager,
895                                OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl,
896                                std::u16string_view _rFactory )
897     {
898         DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForSave: invalid manager!" );
899         if ( !_rxFilterManager.is() )
900             return;
901 
902         OUString sUIName;
903         OUString sExtension;
904 
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 );
912         try
913         {
914             _rxFilterManager->appendFilter( sUIName, sExtension );
915             if ( _rFirstNonEmpty.isEmpty() )
916                 _rFirstNonEmpty = sUIName;
917         }
918         catch( const IllegalArgumentException& )
919         {
920             SAL_WARN( "sfx.dialog", "Could not append DefaultFilter" << sUIName );
921         }
922 
923         for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
924         {
925             if (pFilter->GetName() == pDefaultFilter->GetName())
926                 continue;
927 
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 );
932             try
933             {
934                 _rxFilterManager->appendFilter( sUIName, sExtension );
935                 if ( _rFirstNonEmpty.isEmpty() )
936                     _rFirstNonEmpty = sUIName;
937             }
938             catch( const IllegalArgumentException& )
939             {
940                 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
941             }
942         }
943     }
944 
945     namespace {
946 
947     struct ExportFilter
948     {
ExportFiltersfx2::__anon701969180911::ExportFilter949         ExportFilter( OUString _aUIName, OUString _aWildcard ) :
950             aUIName(std::move( _aUIName )), aWildcard(std::move( _aWildcard )) {}
951 
952         OUString aUIName;
953         OUString aWildcard;
954     };
955 
956     }
957 
appendExportFilters(TSortedFilterList & _rFilterMatcher,const Reference<XFilterManager> & _rxFilterManager,OUString & _rFirstNonEmpty,FileDialogHelper_Impl & _rFileDlgImpl)958     void appendExportFilters( TSortedFilterList& _rFilterMatcher,
959                               const Reference< XFilterManager >& _rxFilterManager,
960                               OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
961     {
962         DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendExportFilters: invalid manager!" );
963         if ( !_rxFilterManager.is() )
964             return;
965 
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;
975 
976         for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
977         {
978             sTypeName   = pFilter->GetTypeName();
979             sUIName     = pFilter->GetUIName();
980             sExtensions = pFilter->GetWildcard().getGlob();
981             ExportFilter aExportFilter( sUIName, sExtensions );
982 
983             if ( nHTMLIndex == -1 &&
984                 ( sTypeName == "generic_HTML" || sTypeName == "graphic_HTML" ) )
985             {
986                 aImportantFilterGroup.insert( aImportantFilterGroup.begin(), aExportFilter );
987                 nHTMLIndex = 0;
988             }
989             else if ( nXHTMLIndex == -1 && sTypeName == "XHTML_File" )
990             {
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;
997             }
998             else if ( nPDFIndex == -1 && sTypeName == "pdf_Portable_Document_Format" )
999             {
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;
1007             }
1008             else
1009                 aFilterGroup.push_back( aExportFilter );
1010         }
1011 
1012         if ( xFilterGroupManager.is() )
1013         {
1014             // Add both html/pdf filter as a filter group to get a separator between both groups
1015             if ( !aImportantFilterGroup.empty() )
1016             {
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++ )
1020                 {
1021                     pFilters[i].First   = addExtension( aImportantFilterGroup[i].aUIName,
1022                                                         aImportantFilterGroup[i].aWildcard,
1023                                                         false, _rFileDlgImpl );
1024                     pFilters[i].Second  = aImportantFilterGroup[i].aWildcard;
1025                 }
1026 
1027                 try
1028                 {
1029                     xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1030                 }
1031                 catch( const IllegalArgumentException& )
1032                 {
1033                 }
1034             }
1035 
1036             if ( !aFilterGroup.empty() )
1037             {
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++ )
1041                 {
1042                     pFilters[i].First   = addExtension( aFilterGroup[i].aUIName,
1043                                                         aFilterGroup[i].aWildcard,
1044                                                         false, _rFileDlgImpl );
1045                     pFilters[i].Second  = aFilterGroup[i].aWildcard;
1046                 }
1047 
1048                 try
1049                 {
1050                     xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1051                 }
1052                 catch( const IllegalArgumentException& )
1053                 {
1054                 }
1055             }
1056         }
1057         else
1058         {
1059             // Fallback solution just add both filter groups as single filters
1060             sal_Int32 n;
1061 
1062             for ( n = 0; n < static_cast<sal_Int32>(aImportantFilterGroup.size()); n++ )
1063             {
1064                 try
1065                 {
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;
1072 
1073                 }
1074                 catch( const IllegalArgumentException& )
1075                 {
1076                     SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
1077                 }
1078             }
1079 
1080             for ( n = 0; n < static_cast<sal_Int32>(aFilterGroup.size()); n++ )
1081             {
1082                 try
1083                 {
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;
1090 
1091                 }
1092                 catch( const IllegalArgumentException& )
1093                 {
1094                     SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
1095                 }
1096             }
1097         }
1098     }
1099 
1100 
appendFiltersForOpen(TSortedFilterList & _rFilterMatcher,const Reference<XFilterManager> & _rxFilterManager,OUString & _rFirstNonEmpty,FileDialogHelper_Impl & _rFileDlgImpl)1101     void appendFiltersForOpen( TSortedFilterList& _rFilterMatcher,
1102                                const Reference< XFilterManager >& _rxFilterManager,
1103                                OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
1104     {
1105         DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForOpen: invalid manager!" );
1106         if ( !_rxFilterManager.is() )
1107             return;
1108 
1109 
1110         // group and classify the filters
1111         GroupedFilterList aAllFilters;
1112         lcl_GroupAndClassify( _rFilterMatcher, aAllFilters );
1113 
1114 
1115         // ensure that we have the one "all files" entry
1116         lcl_EnsureAllFilesEntry( _rFilterMatcher, aAllFilters );
1117 
1118 
1119         // the first non-empty string - which we assume is the first overall entry
1120         if ( !aAllFilters.empty() )
1121         {
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 );
1128         }
1129 
1130 
1131         // append the filters to the manager
1132         if ( !aAllFilters.empty() )
1133         {
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 ) );
1140         }
1141     }
1142 
addExtension(const OUString & _rDisplayText,const OUString & _rExtension,bool _bForOpen,FileDialogHelper_Impl & _rFileDlgImpl)1143     OUString addExtension( const OUString& _rDisplayText,
1144                                   const OUString& _rExtension,
1145                                   bool _bForOpen, FileDialogHelper_Impl& _rFileDlgImpl )
1146     {
1147         OUString sRet = _rDisplayText;
1148 
1149         if ( sRet.indexOf( "(*.*)" ) == -1 )
1150         {
1151             OUString sExt = _rExtension;
1152             if ( !_bForOpen )
1153             {
1154                 // show '*' in extensions only when opening a document
1155                 sExt = sExt.replaceAll("*", "");
1156             }
1157             sRet += " (" + sExt + ")";
1158         }
1159         _rFileDlgImpl.addFilterPair( _rDisplayText, sRet );
1160         return sRet;
1161     }
1162 
1163 
1164 }   // namespace sfx2
1165 
1166 
1167 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1168