Bump version to 4.1-6
[LibreOffice.git] / sfx2 / source / dialog / filtergrouping.cxx
blob5cae6dfa6740bee855c641f9b3f3ba2fafa426b3
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "filtergrouping.hxx"
21 #include <sfx2/fcontnr.hxx>
22 #include <sfx2/filedlghelper.hxx>
23 #include <sfx2/sfx.hrc>
24 #include <sfx2/docfac.hxx>
25 #include "sfx2/sfxresid.hxx"
26 #include <osl/thread.h>
27 #include <rtl/strbuf.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/string.hxx>
35 #include <tools/diagnose_ex.h>
37 #include <list>
38 #include <vector>
39 #include <map>
40 #include <algorithm>
42 //........................................................................
43 namespace sfx2
45 //........................................................................
47 using namespace ::com::sun::star::uno;
48 using namespace ::com::sun::star::ui::dialogs;
49 using namespace ::com::sun::star::lang;
50 using namespace ::com::sun::star::beans;
51 using namespace ::utl;
53 //====================================================================
54 /**
56 Some general words about what's going on here ....
58 <p>In our file open dialog, usually we display every filter we know. That's how it was before: every filter
59 lead to an own line in the filter list box, e.g. "StarWriter 5.0 Dokument" or "Microsoft Word 97".</p>
61 <p>But then the PM came. And everything changed ....</p>
63 <p>A basic idea are groups: Why simply listing all the single filters? Couldn't we draw nice separators
64 between the filters which logically belong together? I.e. all the filters which open a document in StarWriter:
65 couldn't we separate them from all the filters which open the document in StarCalc?<br/>
66 So spoke the PM, and engineering obeyed.</p>
68 <p>So we have groups. They're just a visual aspect: All the filters of a group are presented together, separated
69 by a line from other groups.</p>
71 <p>Let's be honest: How the concrete implementation of the file picker service separates the different groups
72 is a matter of this implementation. We only do this grouping and suggest it to the FilePicker service ...</p>
74 <p>Now for the second concept:<br/>
75 Thinking about it (and that's what the PM did), both "StarWriter 5.0 Dokument" and "Microsoft Word 97"
76 describe a text document. It's a text. It's of no interest for the user that one of the texts was saved in
77 MS' format, and one in our own format.<br/>
78 So in a first step, we want to have a filter entry "Text documents". This would cover both above-mentioned
79 filters, as well as any other filters for documents which are texts.</p>
81 <p>Such an entry as "Text documents" is - within the scope of this file - called "class" or "filter class".</p>
83 <p>In the file-open-dialog, such a class looks like an ordinary filter: it's simply a name in the filter
84 listbox. Selecting means that all the files matching one of the "sub-filters" are displayed (in the example above,
85 this would be "*.sdw", "*.doc" and so on).</p>
87 <p>Now there are two types of filter classes: global ones and local ones. "Text documents" is a global class. As
88 well as "Spreadsheets". Or "Web pages".<br/>
89 Let's have a look at a local class: The filters "MS Word 95" and "MS WinWord 6.0" together form the class
90 "Microsoft Word 6.0 / 95" (don't ask for the reasons. At least not me. Ask the PM). There are a lot of such
91 local classes ...</p>
93 <p>The difference between global and local classes is as follows: Global classes are presented in an own group.
94 There is one dedicated group at the top of the list, containing all the global groups - no local groups and no
95 single filters.</p>
97 <p>Ehm - it was a lie. Not really at the top. Before this group, there is this single "All files" entry. It forms
98 it's own group. But this is uninteresting here.</p>
100 <p>Local classes must consist of filters which - without the classification - would all belong to the same group.
101 Then, they're combined to one entry (in the example above: "Microsoft Word 6.0 / 95"), and this entry is inserted
102 into the file picker filter list, instead of the single filters which form the class.</p>
104 <p>This is an interesting difference between local and global classes: Filters which are part of a global class
105 are listed in there own group, too. Filters in local classes aren't listed a second time - neither directly (as
106 the filter itself) nor indirectly (as part of another local group).</p>
108 <p>The only exception are filters which are part of a global class <em>and</em> a local class. This is allowed.
109 Beeing cotained in two local classes isn't.</p>
111 <p>So that's all what you need to know: Understand the concept of "filter classes" (a filter class combines
112 different filters and acts as if it's a filter itself) and the concept of groups (a group just describes a
113 logical correlation of filters and usually is represented to the user by drawing group separators in the filter
114 list).</p>
116 <p>If you got it, go try understanding this file :).</p>
121 //====================================================================
123 typedef StringPair FilterDescriptor; // a single filter or a filter class (display name and filter mask)
124 typedef ::std::list< FilterDescriptor > FilterGroup; // a list of single filter entries
125 typedef ::std::list< FilterGroup > GroupedFilterList; // a list of all filters, already grouped
127 /// the logical name of a filter
128 typedef OUString FilterName;
130 // a struct which holds references from a logical filter name to a filter group entry
131 // used for quick lookup of classes (means class entries - entries representing a class)
132 // which a given filter may belong to
133 typedef ::std::map< OUString, FilterGroup::iterator > FilterGroupEntryReferrer;
135 /// a descriptor for a filter class (which in the final dialog is represented by one filter entry)
136 typedef struct _tagFilterClass
138 OUString sDisplayName; // the display name
139 Sequence< FilterName > aSubFilters; // the (logical) names of the filter which belong to the class
140 } FilterClass;
142 typedef ::std::list< FilterClass > FilterClassList;
143 typedef ::std::map< OUString, FilterClassList::iterator > FilterClassReferrer;
145 typedef ::std::vector< OUString > StringArray;
147 // =======================================================================
148 // = reading of configuration data
149 // =======================================================================
151 //--------------------------------------------------------------------
152 void lcl_ReadFilterClass( const OConfigurationNode& _rClassesNode, const OUString& _rLogicalClassName,
153 FilterClass& /* [out] */ _rClass )
155 static const OUString sDisplaNameNodeName( "DisplayName" );
156 static const OUString sSubFiltersNodeName( "Filters" );
158 // the description node for the current class
159 OConfigurationNode aClassDesc = _rClassesNode.openNode( _rLogicalClassName );
161 // the values
162 aClassDesc.getNodeValue( sDisplaNameNodeName ) >>= _rClass.sDisplayName;
163 aClassDesc.getNodeValue( sSubFiltersNodeName ) >>= _rClass.aSubFilters;
166 //--------------------------------------------------------------------
167 struct CreateEmptyClassRememberPos : public ::std::unary_function< FilterName, void >
169 protected:
170 FilterClassList& m_rClassList;
171 FilterClassReferrer& m_rClassesReferrer;
173 public:
174 CreateEmptyClassRememberPos( FilterClassList& _rClassList, FilterClassReferrer& _rClassesReferrer )
175 :m_rClassList ( _rClassList )
176 ,m_rClassesReferrer ( _rClassesReferrer )
180 // operate on a single class name
181 void operator() ( const FilterName& _rLogicalFilterName )
183 // insert a new (empty) class
184 m_rClassList.push_back( FilterClass() );
185 // get the position of this new entry
186 FilterClassList::iterator aInsertPos = m_rClassList.end();
187 --aInsertPos;
188 // remember this position
189 m_rClassesReferrer.insert( FilterClassReferrer::value_type( _rLogicalFilterName, aInsertPos ) );
193 //--------------------------------------------------------------------
194 struct ReadGlobalFilter : public ::std::unary_function< FilterName, void >
196 protected:
197 OConfigurationNode m_aClassesNode;
198 FilterClassReferrer& m_aClassReferrer;
200 public:
201 ReadGlobalFilter( const OConfigurationNode& _rClassesNode, FilterClassReferrer& _rClassesReferrer )
202 :m_aClassesNode ( _rClassesNode )
203 ,m_aClassReferrer ( _rClassesReferrer )
207 // operate on a single logical name
208 void operator() ( const FilterName& _rName )
210 FilterClassReferrer::iterator aClassRef = m_aClassReferrer.find( _rName );
211 if ( m_aClassReferrer.end() == aClassRef )
213 // we do not know this global class
214 OSL_FAIL( "ReadGlobalFilter::operator(): unknown filter name!" );
215 // TODO: perhaps we should be more tolerant - at the moment, the filter is dropped
216 // We could silently push_back it to the container ....
218 else
220 // read the data of this class into the node referred to by aClassRef
221 lcl_ReadFilterClass( m_aClassesNode, _rName, *aClassRef->second );
226 //--------------------------------------------------------------------
227 void lcl_ReadGlobalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rGlobalClasses, StringArray& _rGlobalClassNames )
229 _rGlobalClasses.clear();
230 _rGlobalClassNames.clear();
232 //================================================================
233 // get the list describing the order of all global classes
234 Sequence< OUString > aGlobalClasses;
235 _rFilterClassification.getNodeValue( "GlobalFilters/Order" ) >>= aGlobalClasses;
237 const OUString* pNames = aGlobalClasses.getConstArray();
238 const OUString* pNamesEnd = pNames + aGlobalClasses.getLength();
240 // copy the logical names
241 _rGlobalClassNames.resize( aGlobalClasses.getLength() );
242 ::std::copy( pNames, pNamesEnd, _rGlobalClassNames.begin() );
244 // Global classes are presented in an own group, so their order matters (while the order of the
245 // "local classes" doesn't).
246 // That's why we can't simply add the global classes to _rGlobalClasses using the order in which they
247 // are returned from the configuration - it is completely undefined, and we need a _defined_ order.
248 FilterClassReferrer aClassReferrer;
249 ::std::for_each(
250 pNames,
251 pNamesEnd,
252 CreateEmptyClassRememberPos( _rGlobalClasses, aClassReferrer )
254 // now _rGlobalClasses contains a dummy entry for each global class,
255 // while aClassReferrer maps from the logical name of the class to the position within _rGlobalClasses where
256 // it's dummy entry resides
258 //================================================================
259 // go for all the single class entries
260 OConfigurationNode aFilterClassesNode =
261 _rFilterClassification.openNode( "GlobalFilters/Classes" );
262 Sequence< OUString > aFilterClasses = aFilterClassesNode.getNodeNames();
263 ::std::for_each(
264 aFilterClasses.getConstArray(),
265 aFilterClasses.getConstArray() + aFilterClasses.getLength(),
266 ReadGlobalFilter( aFilterClassesNode, aClassReferrer )
270 //--------------------------------------------------------------------
271 struct ReadLocalFilter : public ::std::unary_function< FilterName, void >
273 protected:
274 OConfigurationNode m_aClassesNode;
275 FilterClassList& m_rClasses;
277 public:
278 ReadLocalFilter( const OConfigurationNode& _rClassesNode, FilterClassList& _rClasses )
279 :m_aClassesNode ( _rClassesNode )
280 ,m_rClasses ( _rClasses )
284 // operate on a single logical name
285 void operator() ( const FilterName& _rName )
287 // read the data for this class
288 FilterClass aClass;
289 lcl_ReadFilterClass( m_aClassesNode, _rName, aClass );
291 // insert the class descriptor
292 m_rClasses.push_back( aClass );
296 //--------------------------------------------------------------------
297 void lcl_ReadLocalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rLocalClasses )
299 _rLocalClasses.clear();
301 // the node for the local classes
302 OConfigurationNode aFilterClassesNode =
303 _rFilterClassification.openNode( "LocalFilters/Classes" );
304 Sequence< OUString > aFilterClasses = aFilterClassesNode.getNodeNames();
306 ::std::for_each(
307 aFilterClasses.getConstArray(),
308 aFilterClasses.getConstArray() + aFilterClasses.getLength(),
309 ReadLocalFilter( aFilterClassesNode, _rLocalClasses )
313 //--------------------------------------------------------------------
314 void lcl_ReadClassification( FilterClassList& _rGlobalClasses, StringArray& _rGlobalClassNames, FilterClassList& _rLocalClasses )
316 //================================================================
317 // open our config node
318 OConfigurationTreeRoot aFilterClassification = OConfigurationTreeRoot::createWithComponentContext(
319 ::comphelper::getProcessComponentContext(),
320 "org.openoffice.Office.UI/FilterClassification",
322 OConfigurationTreeRoot::CM_READONLY
325 //================================================================
326 // go for the global classes
327 lcl_ReadGlobalFilters( aFilterClassification, _rGlobalClasses, _rGlobalClassNames );
329 //================================================================
330 // fo for the local classes
331 lcl_ReadLocalFilters( aFilterClassification, _rLocalClasses );
335 // =======================================================================
336 // = grouping and classifying
337 // =======================================================================
339 //--------------------------------------------------------------------
340 // a struct which adds helps remembering a reference to a class entry
341 struct ReferToFilterEntry : public ::std::unary_function< FilterName, void >
343 protected:
344 FilterGroupEntryReferrer& m_rEntryReferrer;
345 FilterGroup::iterator m_aClassPos;
347 public:
348 ReferToFilterEntry( FilterGroupEntryReferrer& _rEntryReferrer, const FilterGroup::iterator& _rClassPos )
349 :m_rEntryReferrer( _rEntryReferrer )
350 ,m_aClassPos( _rClassPos )
354 // operate on a single filter name
355 void operator() ( const FilterName& _rName )
357 ::std::pair< FilterGroupEntryReferrer::iterator, bool > aInsertRes =
358 m_rEntryReferrer.insert( FilterGroupEntryReferrer::value_type( _rName, m_aClassPos ) );
359 SAL_WARN_IF(
360 !aInsertRes.second, "sfx2.dialog",
361 "already have an element for " << _rName);
365 //--------------------------------------------------------------------
366 struct FillClassGroup : public ::std::unary_function< FilterClass, void >
368 protected:
369 FilterGroup& m_rClassGroup;
370 FilterGroupEntryReferrer& m_rClassReferrer;
372 public:
373 FillClassGroup( FilterGroup& _rClassGroup, FilterGroupEntryReferrer& _rClassReferrer )
374 :m_rClassGroup ( _rClassGroup )
375 ,m_rClassReferrer ( _rClassReferrer )
379 // operate on a single class
380 void operator() ( const FilterClass& _rClass )
382 // create an empty filter descriptor for the class
383 FilterDescriptor aClassEntry;
384 // set it's name (which is all we know by now)
385 aClassEntry.First = _rClass.sDisplayName;
387 // add it to the group
388 m_rClassGroup.push_back( aClassEntry );
389 // the position of the newly added class
390 FilterGroup::iterator aClassEntryPos = m_rClassGroup.end();
391 --aClassEntryPos;
393 // and for all the sub filters of the class, remember the class
394 // (respectively the position of the class it the group)
395 ::std::for_each(
396 _rClass.aSubFilters.getConstArray(),
397 _rClass.aSubFilters.getConstArray() + _rClass.aSubFilters.getLength(),
398 ReferToFilterEntry( m_rClassReferrer, aClassEntryPos )
403 //--------------------------------------------------------------------
404 static const sal_Unicode s_cWildcardSeparator( ';' );
406 //====================================================================
407 const OUString& getSeparatorString()
409 static OUString s_sSeparatorString( &s_cWildcardSeparator, 1 );
410 return s_sSeparatorString;
413 //====================================================================
414 struct CheckAppendSingleWildcard : public ::std::unary_function< OUString, void >
416 OUString& _rToBeExtended;
418 CheckAppendSingleWildcard( OUString& _rBase ) : _rToBeExtended( _rBase ) { }
420 void operator() ( const OUString& _rWC )
422 // check for double wildcards
423 sal_Int32 nExistentPos = _rToBeExtended.indexOf( _rWC );
424 if ( -1 < nExistentPos )
425 { // found this wildcard (already part of _rToBeExtended)
426 const sal_Unicode* pBuffer = _rToBeExtended.getStr();
427 if ( ( 0 == nExistentPos )
428 || ( s_cWildcardSeparator == pBuffer[ nExistentPos - 1 ] )
430 { // the wildcard really starts at this position (it starts at pos 0 or the previous character is a separator
431 sal_Int32 nExistentWCEnd = nExistentPos + _rWC.getLength();
432 if ( ( _rToBeExtended.getLength() == nExistentWCEnd )
433 || ( s_cWildcardSeparator == pBuffer[ nExistentWCEnd ] )
435 { // it's really the complete wildcard we found
436 // (not something like _rWC beeing "*.t" and _rToBeExtended containing "*.txt")
437 // -> outta here
438 return;
443 if ( !_rToBeExtended.isEmpty() )
444 _rToBeExtended += getSeparatorString();
445 _rToBeExtended += _rWC;
449 //====================================================================
450 // a helper struct which adds a fixed (Sfx-)filter to a filter group entry given by iterator
451 struct AppendWildcardToDescriptor : public ::std::unary_function< FilterGroupEntryReferrer::value_type, void >
453 protected:
454 ::std::vector< OUString > aWildCards;
456 public:
457 AppendWildcardToDescriptor( const String& _rWildCard );
459 // operate on a single class entry
460 void operator() ( const FilterGroupEntryReferrer::value_type& _rClassReference )
462 // simply add our wildcards
463 ::std::for_each(
464 aWildCards.begin(),
465 aWildCards.end(),
466 CheckAppendSingleWildcard( _rClassReference.second->Second )
471 //====================================================================
472 AppendWildcardToDescriptor::AppendWildcardToDescriptor( const String& _rWildCard )
474 DBG_ASSERT( _rWildCard.Len(),
475 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: invalid wildcard!" );
476 DBG_ASSERT( _rWildCard.GetBuffer()[0] != s_cWildcardSeparator,
477 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: wildcard already separated!" );
479 aWildCards.reserve( comphelper::string::getTokenCount(_rWildCard, s_cWildcardSeparator) );
481 const sal_Unicode* pTokenLoop = _rWildCard.GetBuffer();
482 const sal_Unicode* pTokenLoopEnd = pTokenLoop + _rWildCard.Len();
483 const sal_Unicode* pTokenStart = pTokenLoop;
484 for ( ; pTokenLoop != pTokenLoopEnd; ++pTokenLoop )
486 if ( ( s_cWildcardSeparator == *pTokenLoop ) && ( pTokenLoop > pTokenStart ) )
487 { // found a new token separator (and a non-empty token)
488 aWildCards.push_back( OUString( pTokenStart, pTokenLoop - pTokenStart ) );
490 // search the start of the next token
491 while ( ( pTokenStart != pTokenLoopEnd ) && ( *pTokenStart != s_cWildcardSeparator ) )
492 ++pTokenStart;
494 if ( pTokenStart == pTokenLoopEnd )
495 // reached the end
496 break;
498 ++pTokenStart;
499 pTokenLoop = pTokenStart;
502 if ( pTokenLoop > pTokenStart )
503 // the last one ....
504 aWildCards.push_back( OUString( pTokenStart, pTokenLoop - pTokenStart ) );
507 //--------------------------------------------------------------------
508 void lcl_InitGlobalClasses( GroupedFilterList& _rAllFilters, const FilterClassList& _rGlobalClasses, FilterGroupEntryReferrer& _rGlobalClassesRef )
510 // we need an extra group in our "all filters" container
511 _rAllFilters.push_front( FilterGroup() );
512 FilterGroup& rGlobalFilters = _rAllFilters.front();
513 // it's important to work on the reference: we want to access the members of this filter group
514 // by an iterator (FilterGroup::const_iterator)
515 // the referrer for the global classes
517 // initialize the group
518 ::std::for_each(
519 _rGlobalClasses.begin(),
520 _rGlobalClasses.end(),
521 FillClassGroup( rGlobalFilters, _rGlobalClassesRef )
523 // now we have:
524 // in rGlobalFilters: a list of FilterDescriptor's, where each's discriptor's display name is set to the name of a class
525 // in aGlobalClassesRef: a mapping from logical filter names to positions within rGlobalFilters
526 // this way, if we encounter an arbitrary filter, we can easily (and efficient) check if it belongs to a global class
527 // and modify the descriptor for this class accordingly
530 //--------------------------------------------------------------------
531 typedef ::std::vector< ::std::pair< FilterGroupEntryReferrer::mapped_type, FilterGroup::iterator > >
532 MapGroupEntry2GroupEntry;
533 // this is not really a map - it's just called this way because it is used as a map
535 struct FindGroupEntry : public ::std::unary_function< MapGroupEntry2GroupEntry::value_type, sal_Bool >
537 FilterGroupEntryReferrer::mapped_type aLookingFor;
538 FindGroupEntry( FilterGroupEntryReferrer::mapped_type _rLookingFor ) : aLookingFor( _rLookingFor ) { }
540 sal_Bool operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
542 return _rMapEntry.first == aLookingFor ? sal_True : sal_False;
546 struct CopyGroupEntryContent : public ::std::unary_function< MapGroupEntry2GroupEntry::value_type, void >
548 void operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
550 #ifdef DBG_UTIL
551 FilterDescriptor aHaveALook = *_rMapEntry.first;
552 #endif
553 *_rMapEntry.second = *_rMapEntry.first;
557 //--------------------------------------------------------------------
558 struct CopyNonEmptyFilter : public ::std::unary_function< FilterDescriptor, void >
560 FilterGroup& rTarget;
561 CopyNonEmptyFilter( FilterGroup& _rTarget ) :rTarget( _rTarget ) { }
563 void operator() ( const FilterDescriptor& _rFilter )
565 if ( !_rFilter.Second.isEmpty() )
566 rTarget.push_back( _rFilter );
570 //--------------------------------------------------------------------
571 void lcl_GroupAndClassify( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rAllFilters )
573 _rAllFilters.clear();
575 // ===============================================================
576 // read the classification of filters
577 FilterClassList aGlobalClasses, aLocalClasses;
578 StringArray aGlobalClassNames;
579 lcl_ReadClassification( aGlobalClasses, aGlobalClassNames, aLocalClasses );
581 // ===============================================================
582 // for the global filter classes
583 FilterGroupEntryReferrer aGlobalClassesRef;
584 lcl_InitGlobalClasses( _rAllFilters, aGlobalClasses, aGlobalClassesRef );
586 // insert as much placeholders (FilterGroup's) into _rAllFilter for groups as we have global classes
587 // (this assumes that both numbers are the same, which, speaking strictly, must not hold - but it does, as we know ...)
588 sal_Int32 nGlobalClasses = aGlobalClasses.size();
589 while ( nGlobalClasses-- )
590 _rAllFilters.push_back( FilterGroup() );
592 // ===============================================================
593 // for the local classes:
594 // if n filters belong to a local class, they do not appear in their respective group explicitly, instead
595 // and entry for the class is added to the group and the extensions of the filters are collected under
596 // this entry
597 FilterGroupEntryReferrer aLocalClassesRef;
598 FilterGroup aCollectedLocals;
599 ::std::for_each(
600 aLocalClasses.begin(),
601 aLocalClasses.end(),
602 FillClassGroup( aCollectedLocals, aLocalClassesRef )
604 // to map from the position within aCollectedLocals to positions within the real groups
605 // (where they finally belong to)
606 MapGroupEntry2GroupEntry aLocalFinalPositions;
608 // ===============================================================
609 // now add the filters
610 // the group which we currently work with
611 GroupedFilterList::iterator aCurrentGroup = _rAllFilters.end(); // no current group
612 // the filter container of the current group - if this changes between two filters, a new group is reached
613 String aCurrentServiceName;
615 String sFilterWildcard;
616 OUString sFilterName;
617 // loop through all the filters
618 for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
620 sFilterName = pFilter->GetFilterName();
621 sFilterWildcard = pFilter->GetWildcard().getGlob();
622 AppendWildcardToDescriptor aExtendWildcard( sFilterWildcard );
624 DBG_ASSERT( sFilterWildcard.Len(), "sfx2::lcl_GroupAndClassify: invalid wildcard of this filter!" );
626 // ===========================================================
627 // check for a change in the group
628 String aServiceName = pFilter->GetServiceName();
629 if ( aServiceName != aCurrentServiceName )
630 { // we reached a new group
632 OUString sDocServName = aServiceName;
634 // look for the place in _rAllFilters where this ne group belongs - this is determined
635 // by the order of classes in aGlobalClassNames
636 GroupedFilterList::iterator aGroupPos = _rAllFilters.begin();
637 DBG_ASSERT( aGroupPos != _rAllFilters.end(),
638 "sfx2::lcl_GroupAndClassify: invalid all-filters array here!" );
639 // the loop below will work on invalid objects else ...
640 ++aGroupPos;
641 StringArray::iterator aGlobalIter = aGlobalClassNames.begin();
642 while ( ( aGroupPos != _rAllFilters.end() )
643 && ( aGlobalIter != aGlobalClassNames.end() )
644 && ( *aGlobalIter != sDocServName )
647 ++aGlobalIter;
648 ++aGroupPos;
650 if ( aGroupPos != _rAllFilters.end() )
651 // we found a global class name which matchies the doc service name -> fill the filters of this
652 // group in the respective prepared group
653 aCurrentGroup = aGroupPos;
654 else
655 // insert a new entry in our overall-list
656 aCurrentGroup = _rAllFilters.insert( _rAllFilters.end(), FilterGroup() );
658 // remember the container to properly detect the next group
659 aCurrentServiceName = aServiceName;
662 DBG_ASSERT( aCurrentGroup != _rAllFilters.end(), "sfx2::lcl_GroupAndClassify: invalid current group!" );
664 // ===========================================================
665 // check if the filter is part of a global group
666 ::std::pair< FilterGroupEntryReferrer::iterator, FilterGroupEntryReferrer::iterator >
667 aBelongsTo = aGlobalClassesRef.equal_range( sFilterName );
668 // add the filter to the entries for these classes
669 // (if they exist - if not, the range is empty and the for_each is a no-op)
670 ::std::for_each(
671 aBelongsTo.first,
672 aBelongsTo.second,
673 aExtendWildcard
676 // ===========================================================
677 // add the filter to it's group
679 // for this, check if the filter is part of a local filter
680 FilterGroupEntryReferrer::iterator aBelongsToLocal = aLocalClassesRef.find( sFilterName );
681 if ( aLocalClassesRef.end() != aBelongsToLocal )
683 // okay, there is a local class which the filter belongs to
684 // -> append the wildcard
685 aExtendWildcard( *aBelongsToLocal );
687 MapGroupEntry2GroupEntry::iterator aThisGroupFinalPos =
688 ::std::find_if( aLocalFinalPositions.begin(), aLocalFinalPositions.end(), FindGroupEntry( aBelongsToLocal->second ) );
690 if ( aLocalFinalPositions.end() == aThisGroupFinalPos )
691 { // the position within aCollectedLocals has not been mapped to a final position
692 // within the "real" group (aCollectedLocals is only temporary)
693 // -> do this now (as we just encountered the first filter belonging to this local class
694 // add a new entry which is the "real" group entry
695 aCurrentGroup->push_back( FilterDescriptor( aBelongsToLocal->second->First, String() ) );
696 // the position where we inserted the entry
697 FilterGroup::iterator aInsertPos = aCurrentGroup->end();
698 --aInsertPos;
699 // remember this pos
700 aLocalFinalPositions.push_back( MapGroupEntry2GroupEntry::value_type( aBelongsToLocal->second, aInsertPos ) );
703 else
704 aCurrentGroup->push_back( FilterDescriptor( pFilter->GetUIName(), sFilterWildcard ) );
707 // now just complete the infos for the local groups:
708 // During the above loop, they have been collected in aCollectedLocals, but this is only temporary
709 // They have to be copied into their final positions (which are stored in aLocalFinalPositions)
710 ::std::for_each(
711 aLocalFinalPositions.begin(),
712 aLocalFinalPositions.end(),
713 CopyGroupEntryContent()
716 // and remove local groups which do not apply - e.g. have no entries due to the limited content of the
717 // current SfxFilterMatcherIter
719 FilterGroup& rGlobalFilters = _rAllFilters.front();
720 FilterGroup aNonEmptyGlobalFilters;
721 ::std::for_each(
722 rGlobalFilters.begin(),
723 rGlobalFilters.end(),
724 CopyNonEmptyFilter( aNonEmptyGlobalFilters )
726 rGlobalFilters.swap( aNonEmptyGlobalFilters );
729 //--------------------------------------------------------------------
730 struct AppendFilter : public ::std::unary_function< FilterDescriptor, void >
732 protected:
733 Reference< XFilterManager > m_xFilterManager;
734 FileDialogHelper_Impl* m_pFileDlgImpl;
735 bool m_bAddExtension;
737 public:
738 AppendFilter( const Reference< XFilterManager >& _rxFilterManager,
739 FileDialogHelper_Impl* _pImpl, bool _bAddExtension ) :
741 m_xFilterManager( _rxFilterManager ),
742 m_pFileDlgImpl ( _pImpl ),
743 m_bAddExtension ( _bAddExtension )
746 DBG_ASSERT( m_xFilterManager.is(), "AppendFilter::AppendFilter: invalid filter manager!" );
747 DBG_ASSERT( m_pFileDlgImpl, "AppendFilter::AppendFilter: invalid filedlg impl!" );
750 // operate on a single filter
751 void operator() ( const FilterDescriptor& _rFilterEntry )
753 String sDisplayText = m_bAddExtension
754 ? addExtension( _rFilterEntry.First, _rFilterEntry.Second, sal_True, *m_pFileDlgImpl )
755 : _rFilterEntry.First;
756 m_xFilterManager->appendFilter( sDisplayText, _rFilterEntry.Second );
760 // =======================================================================
761 // = handling for the "all files" entry
762 // =======================================================================
764 //--------------------------------------------------------------------
765 sal_Bool lcl_hasAllFilesFilter( TSortedFilterList& _rFilterMatcher, String& /* [out] */ _rAllFilterName )
767 sal_Bool bHasAll = sal_False;
768 _rAllFilterName = SfxResId( STR_SFX_FILTERNAME_ALL ).toString();
770 // ===============================================================
771 // check if there's already a filter <ALL>
772 for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter && !bHasAll; pFilter = _rFilterMatcher.Next() )
774 if ( pFilter->GetUIName() == _rAllFilterName )
775 bHasAll = sal_True;
777 return bHasAll;
780 //--------------------------------------------------------------------
781 void lcl_EnsureAllFilesEntry( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rFilters )
783 // ===============================================================
784 String sAllFilterName;
785 if ( !lcl_hasAllFilesFilter( _rFilterMatcher, sAllFilterName ) )
787 // get the first group of filters (by definition, this group contains the global classes)
788 DBG_ASSERT( !_rFilters.empty(), "lcl_EnsureAllFilesEntry: invalid filter list!" );
789 if ( !_rFilters.empty() )
791 FilterGroup& rGlobalClasses = *_rFilters.begin();
792 rGlobalClasses.push_front( FilterDescriptor( sAllFilterName, FILEDIALOG_FILTER_ALL ) );
797 // =======================================================================
798 // = filling an XFilterManager
799 // =======================================================================
801 //--------------------------------------------------------------------
802 struct AppendFilterGroup : public ::std::unary_function< FilterGroup, void >
804 protected:
805 Reference< XFilterManager > m_xFilterManager;
806 Reference< XFilterGroupManager > m_xFilterGroupManager;
807 FileDialogHelper_Impl* m_pFileDlgImpl;
809 public:
810 AppendFilterGroup( const Reference< XFilterManager >& _rxFilterManager, FileDialogHelper_Impl* _pImpl )
811 :m_xFilterManager ( _rxFilterManager )
812 ,m_xFilterGroupManager ( _rxFilterManager, UNO_QUERY )
813 ,m_pFileDlgImpl ( _pImpl )
815 DBG_ASSERT( m_xFilterManager.is(), "AppendFilterGroup::AppendFilterGroup: invalid filter manager!" );
816 DBG_ASSERT( m_pFileDlgImpl, "AppendFilterGroup::AppendFilterGroup: invalid filedlg impl!" );
819 void appendGroup( const FilterGroup& _rGroup, bool _bAddExtension )
823 if ( m_xFilterGroupManager.is() )
824 { // the file dialog implementation supports visual grouping of filters
825 // create a representation of the group which is understandable by the XFilterGroupManager
826 if ( _rGroup.size() )
828 Sequence< StringPair > aFilters( _rGroup.size() );
829 ::std::copy(
830 _rGroup.begin(),
831 _rGroup.end(),
832 aFilters.getArray()
834 if ( _bAddExtension )
836 StringPair* pFilters = aFilters.getArray();
837 StringPair* pEnd = pFilters + aFilters.getLength();
838 for ( ; pFilters != pEnd; ++pFilters )
839 pFilters->First = addExtension( pFilters->First, pFilters->Second, sal_True, *m_pFileDlgImpl );
841 m_xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
844 else
846 ::std::for_each(
847 _rGroup.begin(),
848 _rGroup.end(),
849 AppendFilter( m_xFilterManager, m_pFileDlgImpl, _bAddExtension ) );
852 catch( const Exception& )
854 DBG_UNHANDLED_EXCEPTION();
858 // operate on a single filter group
859 void operator() ( const FilterGroup& _rGroup )
861 appendGroup( _rGroup, true );
865 //--------------------------------------------------------------------
866 TSortedFilterList::TSortedFilterList(const ::com::sun::star::uno::Reference< ::com::sun::star::container::XEnumeration >& xFilterList)
867 : m_nIterator(0)
869 if (!xFilterList.is())
870 return;
872 m_lFilters.clear();
873 while(xFilterList->hasMoreElements())
875 ::comphelper::SequenceAsHashMap lFilterProps (xFilterList->nextElement());
876 OUString sFilterName = lFilterProps.getUnpackedValueOrDefault(
877 OUString("Name"),
878 OUString());
879 if (!sFilterName.isEmpty())
880 m_lFilters.push_back(sFilterName);
884 //--------------------------------------------------------------------
885 const SfxFilter* TSortedFilterList::First()
887 m_nIterator = 0;
888 return impl_getFilter(m_nIterator);
891 //--------------------------------------------------------------------
892 const SfxFilter* TSortedFilterList::Next()
894 ++m_nIterator;
895 return impl_getFilter(m_nIterator);
898 //--------------------------------------------------------------------
899 const SfxFilter* TSortedFilterList::impl_getFilter(sal_Int32 nIndex)
901 if (nIndex<0 || nIndex>=(sal_Int32)m_lFilters.size())
902 return 0;
903 const OUString& sFilterName = m_lFilters[nIndex];
904 if (sFilterName.isEmpty())
905 return 0;
906 return SfxFilter::GetFilterByName(String(sFilterName));
909 //--------------------------------------------------------------------
910 void appendFiltersForSave( TSortedFilterList& _rFilterMatcher,
911 const Reference< XFilterManager >& _rxFilterManager,
912 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl,
913 const OUString& _rFactory )
915 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForSave: invalid manager!" );
916 if ( !_rxFilterManager.is() )
917 return;
919 OUString sUIName;
920 OUString sExtension;
922 // retrieve the default filter for this application module.
923 // It must be set as first of the generated filter list.
924 const SfxFilter* pDefaultFilter = SfxFilterContainer::GetDefaultFilter_Impl(_rFactory);
925 // Only use one extension (#i32434#)
926 // (and always the first if there are more than one)
927 using comphelper::string::getToken;
928 sExtension = getToken(pDefaultFilter->GetWildcard().getGlob(), 0, ';');
929 sUIName = addExtension( pDefaultFilter->GetUIName(), sExtension, sal_False, _rFileDlgImpl );
932 _rxFilterManager->appendFilter( sUIName, sExtension );
933 if ( _rFirstNonEmpty.isEmpty() )
934 _rFirstNonEmpty = sUIName;
936 catch( const IllegalArgumentException& )
938 SAL_WARN( "sfx2.dialog", "Could not append DefaultFilter" << sUIName );
941 for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
943 if (pFilter->GetName() == pDefaultFilter->GetName())
944 continue;
946 // Only use one extension (#i32434#)
947 // (and always the first if there are more than one)
948 sExtension = getToken(pFilter->GetWildcard().getGlob(), 0, ';');
949 sUIName = addExtension( pFilter->GetUIName(), sExtension, sal_False, _rFileDlgImpl );
952 _rxFilterManager->appendFilter( sUIName, sExtension );
953 if ( _rFirstNonEmpty.isEmpty() )
954 _rFirstNonEmpty = sUIName;
956 catch( const IllegalArgumentException& )
958 SAL_WARN( "sfx2.dialog", "Could not append Filter" << sUIName );
963 struct ExportFilter
965 ExportFilter( const OUString& _aUIName, const OUString& _aWildcard ) :
966 aUIName( _aUIName ), aWildcard( _aWildcard ) {}
968 OUString aUIName;
969 OUString aWildcard;
972 //--------------------------------------------------------------------
973 void appendExportFilters( TSortedFilterList& _rFilterMatcher,
974 const Reference< XFilterManager >& _rxFilterManager,
975 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
977 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendExportFilters: invalid manager!" );
978 if ( !_rxFilterManager.is() )
979 return;
981 sal_Int32 nHTMLIndex = -1;
982 sal_Int32 nXHTMLIndex = -1;
983 sal_Int32 nPDFIndex = -1;
984 sal_Int32 nFlashIndex = -1;
985 OUString sUIName;
986 OUString sExtensions;
987 std::vector< ExportFilter > aImportantFilterGroup;
988 std::vector< ExportFilter > aFilterGroup;
989 Reference< XFilterGroupManager > xFilterGroupManager( _rxFilterManager, UNO_QUERY );
990 OUString sTypeName;
991 const OUString sWriterHTMLType( "generic_HTML" );
992 const OUString sGraphicHTMLType( "graphic_HTML" );
993 const OUString sXHTMLType( "XHTML_File" );
994 const OUString sPDFType( "pdf_Portable_Document_Format" );
995 const OUString sFlashType( "graphic_SWF" );
997 for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
999 sTypeName = pFilter->GetTypeName();
1000 sUIName = pFilter->GetUIName();
1001 sExtensions = pFilter->GetWildcard().getGlob();
1002 ExportFilter aExportFilter( sUIName, sExtensions );
1004 if ( nHTMLIndex == -1 &&
1005 ( sTypeName.equals( sWriterHTMLType ) || sTypeName.equals( sGraphicHTMLType ) ) )
1007 aImportantFilterGroup.insert( aImportantFilterGroup.begin(), aExportFilter );
1008 nHTMLIndex = 0;
1010 else if ( nXHTMLIndex == -1 && sTypeName.equals( sXHTMLType ) )
1012 std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
1013 if ( nHTMLIndex == -1 )
1014 aImportantFilterGroup.insert( aIter, aExportFilter );
1015 else
1016 aImportantFilterGroup.insert( ++aIter, aExportFilter );
1017 nXHTMLIndex = 0;
1019 else if ( nPDFIndex == -1 && sTypeName.equals( sPDFType ) )
1021 std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
1022 if ( nHTMLIndex != -1 )
1023 ++aIter;
1024 if ( nXHTMLIndex != -1 )
1025 ++aIter;
1026 aImportantFilterGroup.insert( aIter, aExportFilter );
1027 nPDFIndex = 0;
1029 else if ( nFlashIndex == -1 && sTypeName.equals( sFlashType ) )
1031 std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
1032 if ( nHTMLIndex != -1 )
1033 ++aIter;
1034 if ( nXHTMLIndex != -1 )
1035 ++aIter;
1036 if ( nPDFIndex != -1 )
1037 ++aIter;
1038 aImportantFilterGroup.insert( aIter, aExportFilter );
1039 nFlashIndex = 0;
1041 else
1042 aFilterGroup.push_back( aExportFilter );
1045 if ( xFilterGroupManager.is() )
1047 // Add both html/pdf filter as a filter group to get a separator between both groups
1048 if ( !aImportantFilterGroup.empty() )
1050 Sequence< StringPair > aFilters( aImportantFilterGroup.size() );
1051 for ( sal_Int32 i = 0; i < (sal_Int32)aImportantFilterGroup.size(); i++ )
1053 aFilters[i].First = addExtension( aImportantFilterGroup[i].aUIName,
1054 aImportantFilterGroup[i].aWildcard,
1055 sal_False, _rFileDlgImpl );
1056 aFilters[i].Second = aImportantFilterGroup[i].aWildcard;
1061 xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1063 catch( const IllegalArgumentException& )
1068 if ( !aFilterGroup.empty() )
1070 Sequence< StringPair > aFilters( aFilterGroup.size() );
1071 for ( sal_Int32 i = 0; i < (sal_Int32)aFilterGroup.size(); i++ )
1073 aFilters[i].First = addExtension( aFilterGroup[i].aUIName,
1074 aFilterGroup[i].aWildcard,
1075 sal_False, _rFileDlgImpl );
1076 aFilters[i].Second = aFilterGroup[i].aWildcard;
1081 xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1083 catch( const IllegalArgumentException& )
1088 else
1090 // Fallback solution just add both filter groups as single filters
1091 sal_Int32 n;
1093 for ( n = 0; n < (sal_Int32)aImportantFilterGroup.size(); n++ )
1097 OUString aUIName = addExtension( aImportantFilterGroup[n].aUIName,
1098 aImportantFilterGroup[n].aWildcard,
1099 sal_False, _rFileDlgImpl );
1100 _rxFilterManager->appendFilter( aUIName, aImportantFilterGroup[n].aWildcard );
1101 if ( _rFirstNonEmpty.isEmpty() )
1102 _rFirstNonEmpty = sUIName;
1105 catch( const IllegalArgumentException& )
1107 SAL_WARN( "sfx2.dialog", "Could not append Filter" << sUIName );
1111 for ( n = 0; n < (sal_Int32)aFilterGroup.size(); n++ )
1115 OUString aUIName = addExtension( aFilterGroup[n].aUIName,
1116 aFilterGroup[n].aWildcard,
1117 sal_False, _rFileDlgImpl );
1118 _rxFilterManager->appendFilter( aUIName, aFilterGroup[n].aWildcard );
1119 if ( _rFirstNonEmpty.isEmpty() )
1120 _rFirstNonEmpty = sUIName;
1123 catch( const IllegalArgumentException& )
1125 SAL_WARN( "sfx2.dialog", "Could not append Filter" << sUIName );
1131 //--------------------------------------------------------------------
1132 void appendFiltersForOpen( TSortedFilterList& _rFilterMatcher,
1133 const Reference< XFilterManager >& _rxFilterManager,
1134 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
1136 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForOpen: invalid manager!" );
1137 if ( !_rxFilterManager.is() )
1138 return;
1140 // ===============================================================
1141 // group and classify the filters
1142 GroupedFilterList aAllFilters;
1143 lcl_GroupAndClassify( _rFilterMatcher, aAllFilters );
1145 // ===============================================================
1146 // ensure that we have the one "all files" entry
1147 lcl_EnsureAllFilesEntry( _rFilterMatcher, aAllFilters );
1149 // ===============================================================
1150 // the first non-empty string - which we assume is the first overall entry
1151 if ( !aAllFilters.empty() )
1153 const FilterGroup& rFirstGroup = *aAllFilters.begin(); // should be the global classes
1154 if ( !rFirstGroup.empty() )
1155 _rFirstNonEmpty = rFirstGroup.begin()->First;
1156 // append first group, without extension
1157 AppendFilterGroup aGroup( _rxFilterManager, &_rFileDlgImpl );
1158 aGroup.appendGroup( rFirstGroup, false );
1161 // ===============================================================
1162 // append the filters to the manager
1163 if ( !aAllFilters.empty() )
1165 ::std::list< FilterGroup >::iterator pIter = aAllFilters.begin();
1166 ++pIter;
1167 ::std::for_each(
1168 pIter, // first filter group was handled separately, see above
1169 aAllFilters.end(),
1170 AppendFilterGroup( _rxFilterManager, &_rFileDlgImpl ) );
1174 OUString addExtension( const OUString& _rDisplayText,
1175 const OUString& _rExtension,
1176 sal_Bool _bForOpen, FileDialogHelper_Impl& _rFileDlgImpl )
1178 static OUString sAllFilter( "(*.*)" );
1179 static OUString sOpenBracket( " (" );
1180 static OUString sCloseBracket( ")" );
1181 OUString sRet = _rDisplayText;
1183 if ( sRet.indexOf( sAllFilter ) == -1 )
1185 String sExt = _rExtension;
1186 if ( !_bForOpen )
1188 // show '*' in extensions only when opening a document
1189 sExt = comphelper::string::remove(sExt, '*');
1191 sRet += sOpenBracket;
1192 sRet += sExt;
1193 sRet += sCloseBracket;
1195 _rFileDlgImpl.addFilterPair( _rDisplayText, sRet );
1196 return sRet;
1199 //........................................................................
1200 } // namespace sfx2
1201 //........................................................................
1204 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */