LibreOffice Module sfx2 (master) 1
filtergrouping.cxx
Go to the documentation of this file.
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>
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>
35#include <comphelper/string.hxx>
37#include <tools/debug.hxx>
38
39#include <list>
40#include <utility>
41#include <vector>
42#include <map>
43#include <algorithm>
44
45
46namespace 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
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
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
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
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( "DisplayName" ) >>= _rClass.sDisplayName;
162 aClassDesc.getNodeValue( "Filters" ) >>= _rClass.aSubFilters;
163 }
164
165 namespace {
166
167 struct CreateEmptyClassRememberPos
168 {
169 protected:
172
173 public:
174 CreateEmptyClassRememberPos( FilterClassList& _rClassList, FilterClassReferrer& _rClassesReferrer )
175 :m_rClassList ( _rClassList )
176 ,m_rClassesReferrer ( _rClassesReferrer )
177 {
178 }
179
180 // operate on a single class name
181 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;
199
200 public:
201 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
208 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
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( "GlobalFilters/Order" ) >>= 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( "GlobalFilters/Classes" );
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;
273
274 public:
275 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
282 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
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( "LocalFilters/Classes" );
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
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 "org.openoffice.Office.UI/FilterClassification",
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:
343 FilterGroup::iterator m_aClassPos;
344
345 public:
346 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
353 void operator() ( const FilterName& _rName )
354 {
355 ::std::pair< FilterGroupEntryReferrer::iterator, bool > aInsertRes =
356 m_rEntryReferrer.emplace( _rName, m_aClassPos );
358 !aInsertRes.second, "sfx.dialog",
359 "already have an element for " << _rName);
360 }
361 };
362
363
364 struct FillClassGroup
365 {
366 protected:
369
370 public:
371 FillClassGroup( FilterGroup& _rClassGroup, FilterGroupEntryReferrer& _rClassReferrer )
372 :m_rClassGroup ( _rClassGroup )
373 ,m_rClassReferrer ( _rClassReferrer )
374 {
375 }
376
377 // operate on a single class
378 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
404
405 static OUString getSeparatorString()
406 {
407 return ";";
408 }
409
410 namespace {
411
412 struct CheckAppendSingleWildcard
413 {
414 OUString& _rToBeExtended;
415
416 explicit CheckAppendSingleWildcard( OUString& _rBase ) : _rToBeExtended( _rBase ) { }
417
418 void operator() ( std::u16string_view _rWC )
419 {
420 // check for double wildcards
421 sal_Int32 nExistentPos = _rToBeExtended.indexOf( _rWC );
422 if ( -1 < nExistentPos )
423 { // found this wildcard (already part of _rToBeExtended)
424 if ( ( 0 == nExistentPos )
425 || ( s_cWildcardSeparator == _rToBeExtended[ nExistentPos - 1 ] )
426 )
427 { // the wildcard really starts at this position (it starts at pos 0 or the previous character is a separator
428 sal_Int32 nExistentWCEnd = nExistentPos + _rWC.size();
429 if ( ( _rToBeExtended.getLength() == nExistentWCEnd )
430 || ( s_cWildcardSeparator == _rToBeExtended[ nExistentWCEnd ] )
431 )
432 { // it's really the complete wildcard we found
433 // (not something like _rWC being "*.t" and _rToBeExtended containing "*.txt")
434 // -> outta here
435 return;
436 }
437 }
438 }
439
440 if ( !_rToBeExtended.isEmpty() )
442 _rToBeExtended += _rWC;
443 }
444 };
445
446
447 // a helper struct which adds a fixed (Sfx-)filter to a filter group entry given by iterator
448 struct AppendWildcardToDescriptor
449 {
450 protected:
451 ::std::vector< OUString > aWildCards;
452
453 public:
454 explicit AppendWildcardToDescriptor( const OUString& _rWildCard );
455
456 // operate on a single class entry
457 void operator() ( const FilterGroupEntryReferrer::value_type& _rClassReference )
458 {
459 // simply add our wildcards
460 ::std::for_each(
461 aWildCards.begin(),
462 aWildCards.end(),
463 CheckAppendSingleWildcard( _rClassReference.second->Second )
464 );
465 }
466 };
467
468 }
469
470 AppendWildcardToDescriptor::AppendWildcardToDescriptor( const OUString& _rWildCard )
471 {
472 DBG_ASSERT( !_rWildCard.isEmpty(),
473 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: invalid wildcard!" );
474 DBG_ASSERT( _rWildCard.isEmpty() || _rWildCard[0] != s_cWildcardSeparator,
475 "AppendWildcardToDescriptor::AppendWildcardToDescriptor: wildcard already separated!" );
476
478
479 const sal_Unicode* pTokenLoop = _rWildCard.getStr();
480 const sal_Unicode* pTokenLoopEnd = pTokenLoop + _rWildCard.getLength();
481 const sal_Unicode* pTokenStart = pTokenLoop;
482 for ( ; pTokenLoop != pTokenLoopEnd; ++pTokenLoop )
483 {
484 if ( ( s_cWildcardSeparator == *pTokenLoop ) && ( pTokenLoop > pTokenStart ) )
485 { // found a new token separator (and a non-empty token)
486 aWildCards.emplace_back( pTokenStart, pTokenLoop - pTokenStart );
487
488 // search the start of the next token
489 while ( ( pTokenStart != pTokenLoopEnd ) && ( *pTokenStart != s_cWildcardSeparator ) )
490 ++pTokenStart;
491
492 if ( pTokenStart == pTokenLoopEnd )
493 // reached the end
494 break;
495
496 ++pTokenStart;
497 pTokenLoop = pTokenStart;
498 }
499 }
500 if ( pTokenLoop > pTokenStart )
501 // the last one...
502 aWildCards.emplace_back( pTokenStart, pTokenLoop - pTokenStart );
503 }
504
505
506 static void lcl_InitGlobalClasses( GroupedFilterList& _rAllFilters, const FilterClassList& _rGlobalClasses, FilterGroupEntryReferrer& _rGlobalClassesRef )
507 {
508 // we need an extra group in our "all filters" container
509 _rAllFilters.push_front( FilterGroup() );
510 FilterGroup& rGlobalFilters = _rAllFilters.front();
511 // it's important to work on the reference: we want to access the members of this filter group
512 // by an iterator (FilterGroup::const_iterator)
513 // the referrer for the global classes
514
515 // initialize the group
516 ::std::for_each(
517 _rGlobalClasses.begin(),
518 _rGlobalClasses.end(),
519 FillClassGroup( rGlobalFilters, _rGlobalClassesRef )
520 );
521 // now we have:
522 // in rGlobalFilters: a list of FilterDescriptor's, where each's descriptor's display name is set to the name of a class
523 // in aGlobalClassesRef: a mapping from logical filter names to positions within rGlobalFilters
524 // this way, if we encounter an arbitrary filter, we can easily (and efficient) check if it belongs to a global class
525 // and modify the descriptor for this class accordingly
526 }
527
528
529 typedef ::std::vector< ::std::pair< FilterGroupEntryReferrer::mapped_type, FilterGroup::iterator > >
531 // this is not really a map - it's just called this way because it is used as a map
532
533 namespace {
534
535 struct FindGroupEntry
536 {
537 FilterGroupEntryReferrer::mapped_type aLookingFor;
538 explicit FindGroupEntry( FilterGroupEntryReferrer::mapped_type _aLookingFor ) : aLookingFor(std::move( _aLookingFor )) { }
539
540 bool operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
541 {
542 return _rMapEntry.first == aLookingFor;
543 }
544 };
545
546 struct CopyGroupEntryContent
547 {
548 void operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
549 {
550 *_rMapEntry.second = *_rMapEntry.first;
551 }
552 };
553
554
555 struct CopyNonEmptyFilter
556 {
558 explicit CopyNonEmptyFilter( FilterGroup& _rTarget ) :rTarget( _rTarget ) { }
559
560 void operator() ( const FilterDescriptor& _rFilter )
561 {
562 if ( !_rFilter.Second.isEmpty() )
563 rTarget.push_back( _rFilter );
564 }
565 };
566
567 }
568
569 static void lcl_GroupAndClassify( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rAllFilters )
570 {
571 _rAllFilters.clear();
572
573
574 // read the classification of filters
575 FilterClassList aGlobalClasses, aLocalClasses;
576 std::vector<OUString> aGlobalClassNames;
577 lcl_ReadClassification( aGlobalClasses, aGlobalClassNames, aLocalClasses );
578
579
580 // for the global filter classes
581 FilterGroupEntryReferrer aGlobalClassesRef;
582 lcl_InitGlobalClasses( _rAllFilters, aGlobalClasses, aGlobalClassesRef );
583
584 // insert as much placeholders (FilterGroup's) into _rAllFilter for groups as we have global classes
585 // (this assumes that both numbers are the same, which, speaking strictly, must not hold - but it does, as we know ...)
586 sal_Int32 nGlobalClasses = aGlobalClasses.size();
587 while ( nGlobalClasses-- )
588 _rAllFilters.emplace_back( );
589
590
591 // for the local classes:
592 // if n filters belong to a local class, they do not appear in their respective group explicitly, instead
593 // and entry for the class is added to the group and the extensions of the filters are collected under
594 // this entry
595 FilterGroupEntryReferrer aLocalClassesRef;
596 FilterGroup aCollectedLocals;
597 ::std::for_each(
598 aLocalClasses.begin(),
599 aLocalClasses.end(),
600 FillClassGroup( aCollectedLocals, aLocalClassesRef )
601 );
602 // to map from the position within aCollectedLocals to positions within the real groups
603 // (where they finally belong to)
604 MapGroupEntry2GroupEntry aLocalFinalPositions;
605
606
607 // now add the filters
608 // the group which we currently work with
609 GroupedFilterList::iterator aCurrentGroup = _rAllFilters.end(); // no current group
610 // the filter container of the current group - if this changes between two filters, a new group is reached
611 OUString aCurrentServiceName;
612
613 OUString sFilterWildcard;
614 OUString sFilterName;
615 // loop through all the filters
616 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
617 {
618 sFilterName = pFilter->GetFilterName();
619 sFilterWildcard = pFilter->GetWildcard().getGlob();
620 AppendWildcardToDescriptor aExtendWildcard( sFilterWildcard );
621
622 DBG_ASSERT( !sFilterWildcard.isEmpty(), "sfx2::lcl_GroupAndClassify: invalid wildcard of this filter!" );
623
624
625 // check for a change in the group
626 OUString aServiceName = pFilter->GetServiceName();
627 if ( aServiceName != aCurrentServiceName )
628 { // we reached a new group
629
630 // look for the place in _rAllFilters where this ne group belongs - this is determined
631 // by the order of classes in aGlobalClassNames
632 GroupedFilterList::iterator aGroupPos = _rAllFilters.begin();
633 DBG_ASSERT( aGroupPos != _rAllFilters.end(),
634 "sfx2::lcl_GroupAndClassify: invalid all-filters array here!" );
635 // the loop below will work on invalid objects else ...
636 ++aGroupPos;
637 auto aGlobalIter = std::find(aGlobalClassNames.begin(), aGlobalClassNames.end(), aServiceName);
638 auto nGroupPosShift = std::min(
639 std::distance(aGlobalClassNames.begin(), aGlobalIter),
640 std::distance(aGroupPos, _rAllFilters.end()));
641 std::advance(aGroupPos, nGroupPosShift);
642 if ( aGroupPos != _rAllFilters.end() )
643 // we found a global class name which matches the doc service name -> fill the filters of this
644 // group in the respective prepared group
645 aCurrentGroup = aGroupPos;
646 else
647 // insert a new entry in our overall-list
648 aCurrentGroup = _rAllFilters.insert( _rAllFilters.end(), FilterGroup() );
649
650 // remember the container to properly detect the next group
651 aCurrentServiceName = aServiceName;
652 }
653
654 assert(aCurrentGroup != _rAllFilters.end()); //invalid current group!
655 if (aCurrentGroup == _rAllFilters.end())
656 aCurrentGroup = _rAllFilters.begin();
657
658
659 // check if the filter is part of a global group
660 ::std::pair< FilterGroupEntryReferrer::iterator, FilterGroupEntryReferrer::iterator >
661 aBelongsTo = aGlobalClassesRef.equal_range( sFilterName );
662 // add the filter to the entries for these classes
663 // (if they exist - if not, the range is empty and the for_each is a no-op)
664 ::std::for_each(
665 aBelongsTo.first,
666 aBelongsTo.second,
667 aExtendWildcard
668 );
669
670
671 // add the filter to its group
672
673 // for this, check if the filter is part of a local filter
674 FilterGroupEntryReferrer::iterator aBelongsToLocal = aLocalClassesRef.find( sFilterName );
675 if ( aLocalClassesRef.end() != aBelongsToLocal )
676 {
677 // okay, there is a local class which the filter belongs to
678 // -> append the wildcard
679 aExtendWildcard( *aBelongsToLocal );
680
681 if ( std::none_of( aLocalFinalPositions.begin(), aLocalFinalPositions.end(), FindGroupEntry( aBelongsToLocal->second ) ) )
682 { // the position within aCollectedLocals has not been mapped to a final position
683 // within the "real" group (aCollectedLocals is only temporary)
684 // -> do this now (as we just encountered the first filter belonging to this local class
685 // add a new entry which is the "real" group entry
686 aCurrentGroup->push_back( FilterDescriptor( aBelongsToLocal->second->First, OUString() ) );
687 // the position where we inserted the entry
688 FilterGroup::iterator aInsertPos = aCurrentGroup->end();
689 --aInsertPos;
690 // remember this pos
691 aLocalFinalPositions.emplace_back( aBelongsToLocal->second, aInsertPos );
692 }
693 }
694 else
695 aCurrentGroup->push_back( FilterDescriptor( pFilter->GetUIName(), sFilterWildcard ) );
696 }
697
698 // now just complete the infos for the local groups:
699 // During the above loop, they have been collected in aCollectedLocals, but this is only temporary
700 // They have to be copied into their final positions (which are stored in aLocalFinalPositions)
701 ::std::for_each(
702 aLocalFinalPositions.begin(),
703 aLocalFinalPositions.end(),
704 CopyGroupEntryContent()
705 );
706
707 // and remove local groups which do not apply - e.g. have no entries due to the limited content of the
708 // current SfxFilterMatcherIter
709
710 FilterGroup& rGlobalFilters = _rAllFilters.front();
711 FilterGroup aNonEmptyGlobalFilters;
712 ::std::for_each(
713 rGlobalFilters.begin(),
714 rGlobalFilters.end(),
715 CopyNonEmptyFilter( aNonEmptyGlobalFilters )
716 );
717 rGlobalFilters.swap( aNonEmptyGlobalFilters );
718 }
719
720 namespace {
721
722 struct AppendFilter
723 {
724 protected:
725 Reference< XFilterManager > m_xFilterManager;
726 FileDialogHelper_Impl* m_pFileDlgImpl;
728
729 public:
730 AppendFilter( const Reference< XFilterManager >& _rxFilterManager,
731 FileDialogHelper_Impl* _pImpl, bool _bAddExtension ) :
732
733 m_xFilterManager( _rxFilterManager ),
734 m_pFileDlgImpl ( _pImpl ),
735 m_bAddExtension ( _bAddExtension )
736
737 {
738 DBG_ASSERT( m_xFilterManager.is(), "AppendFilter::AppendFilter: invalid filter manager!" );
739 DBG_ASSERT( m_pFileDlgImpl, "AppendFilter::AppendFilter: invalid filedlg impl!" );
740 }
741
742 // operate on a single filter
743 void operator() ( const FilterDescriptor& _rFilterEntry )
744 {
745 OUString sDisplayText = m_bAddExtension
746 ? addExtension( _rFilterEntry.First, _rFilterEntry.Second, true, *m_pFileDlgImpl )
747 : _rFilterEntry.First;
748 m_xFilterManager->appendFilter( sDisplayText, _rFilterEntry.Second );
749 }
750 };
751
752 }
753
754// = handling for the "all files" entry
755
756
757 static bool lcl_hasAllFilesFilter( TSortedFilterList& _rFilterMatcher, OUString& /* [out] */ _rAllFilterName )
758 {
759 bool bHasAll = false;
760 _rAllFilterName = SfxResId( STR_SFX_FILTERNAME_ALL );
761
762
763 // check if there's already a filter <ALL>
764 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter && !bHasAll; pFilter = _rFilterMatcher.Next() )
765 {
766 if ( pFilter->GetUIName() == _rAllFilterName )
767 bHasAll = true;
768 }
769 return bHasAll;
770 }
771
772
773 static void lcl_EnsureAllFilesEntry( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rFilters )
774 {
775
776 OUString sAllFilterName;
777 if ( !lcl_hasAllFilesFilter( _rFilterMatcher, sAllFilterName ) )
778 {
779 // get the first group of filters (by definition, this group contains the global classes)
780 DBG_ASSERT( !_rFilters.empty(), "lcl_EnsureAllFilesEntry: invalid filter list!" );
781 if ( !_rFilters.empty() )
782 {
783 FilterGroup& rGlobalClasses = *_rFilters.begin();
784 rGlobalClasses.push_front( FilterDescriptor( sAllFilterName, FILEDIALOG_FILTER_ALL ) );
785 }
786 }
787 }
788
789
790// = filling an XFilterManager
791
792 namespace {
793
794 struct AppendFilterGroup
795 {
796 protected:
797 Reference< XFilterManager > m_xFilterManager;
798 Reference< XFilterGroupManager > m_xFilterGroupManager;
799 FileDialogHelper_Impl* m_pFileDlgImpl;
800
801 public:
802 AppendFilterGroup( const Reference< XFilterManager >& _rxFilterManager, FileDialogHelper_Impl* _pImpl )
803 :m_xFilterManager ( _rxFilterManager )
804 ,m_xFilterGroupManager ( _rxFilterManager, UNO_QUERY )
805 ,m_pFileDlgImpl ( _pImpl )
806 {
807 DBG_ASSERT( m_xFilterManager.is(), "AppendFilterGroup::AppendFilterGroup: invalid filter manager!" );
808 DBG_ASSERT( m_pFileDlgImpl, "AppendFilterGroup::AppendFilterGroup: invalid filedlg impl!" );
809 }
810
811 void appendGroup( const FilterGroup& _rGroup, bool _bAddExtension )
812 {
813 try
814 {
815 if ( m_xFilterGroupManager.is() )
816 { // the file dialog implementation supports visual grouping of filters
817 // create a representation of the group which is understandable by the XFilterGroupManager
818 if ( !_rGroup.empty() )
819 {
820 Sequence< StringPair > aFilters( comphelper::containerToSequence(_rGroup) );
821 if ( _bAddExtension )
822 {
823 for ( StringPair & filter : asNonConstRange(aFilters) )
824 filter.First = addExtension( filter.First, filter.Second, true, *m_pFileDlgImpl );
825 }
826 m_xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
827 }
828 }
829 else
830 {
831 ::std::for_each(
832 _rGroup.begin(),
833 _rGroup.end(),
834 AppendFilter( m_xFilterManager, m_pFileDlgImpl, _bAddExtension ) );
835 }
836 }
837 catch( const Exception& )
838 {
839 DBG_UNHANDLED_EXCEPTION("sfx.dialog");
840 }
841 }
842
843 // operate on a single filter group
844 void operator() ( const FilterGroup& _rGroup )
845 {
846 appendGroup( _rGroup, true );
847 }
848 };
849
850 }
851
852 TSortedFilterList::TSortedFilterList(const css::uno::Reference< css::container::XEnumeration >& xFilterList)
853 : m_nIterator(0)
854 {
855 if (!xFilterList.is())
856 return;
857
858 m_lFilters.clear();
859 while(xFilterList->hasMoreElements())
860 {
861 ::comphelper::SequenceAsHashMap lFilterProps (xFilterList->nextElement());
862 OUString sFilterName = lFilterProps.getUnpackedValueOrDefault(
863 "Name",
864 OUString());
865 if (!sFilterName.isEmpty())
866 m_lFilters.push_back(sFilterName);
867 }
868 }
869
870
871 std::shared_ptr<const SfxFilter> TSortedFilterList::First()
872 {
873 m_nIterator = 0;
875 }
876
877
878 std::shared_ptr<const SfxFilter> TSortedFilterList::Next()
879 {
880 ++m_nIterator;
882 }
883
884
885 std::shared_ptr<const SfxFilter> TSortedFilterList::impl_getFilter(sal_Int32 nIndex)
886 {
888 return nullptr;
889 const OUString& sFilterName = m_lFilters[nIndex];
890 if (sFilterName.isEmpty())
891 return nullptr;
892 return SfxFilter::GetFilterByName(sFilterName);
893 }
894
895
897 const Reference< XFilterManager >& _rxFilterManager,
898 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl,
899 std::u16string_view _rFactory )
900 {
901 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForSave: invalid manager!" );
902 if ( !_rxFilterManager.is() )
903 return;
904
905 OUString sUIName;
906 OUString sExtension;
907
908 // retrieve the default filter for this application module.
909 // It must be set as first of the generated filter list.
910 std::shared_ptr<const SfxFilter> pDefaultFilter = SfxFilterContainer::GetDefaultFilter_Impl(_rFactory);
911 // Only use one extension (#i32434#)
912 // (and always the first if there are more than one)
913 sExtension = pDefaultFilter->GetWildcard().getGlob().getToken(0, ';');
914 sUIName = addExtension( pDefaultFilter->GetUIName(), sExtension, false, _rFileDlgImpl );
915 try
916 {
917 _rxFilterManager->appendFilter( sUIName, sExtension );
918 if ( _rFirstNonEmpty.isEmpty() )
919 _rFirstNonEmpty = sUIName;
920 }
921 catch( const IllegalArgumentException& )
922 {
923 SAL_WARN( "sfx.dialog", "Could not append DefaultFilter" << sUIName );
924 }
925
926 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
927 {
928 if (pFilter->GetName() == pDefaultFilter->GetName())
929 continue;
930
931 // Only use one extension (#i32434#)
932 // (and always the first if there are more than one)
933 sExtension = pFilter->GetWildcard().getGlob().getToken(0, ';');
934 sUIName = addExtension( pFilter->GetUIName(), sExtension, false, _rFileDlgImpl );
935 try
936 {
937 _rxFilterManager->appendFilter( sUIName, sExtension );
938 if ( _rFirstNonEmpty.isEmpty() )
939 _rFirstNonEmpty = sUIName;
940 }
941 catch( const IllegalArgumentException& )
942 {
943 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
944 }
945 }
946 }
947
948 namespace {
949
950 struct ExportFilter
951 {
952 ExportFilter( OUString _aUIName, OUString _aWildcard ) :
953 aUIName(std::move( _aUIName )), aWildcard(std::move( _aWildcard )) {}
954
955 OUString aUIName;
956 OUString aWildcard;
957 };
958
959 }
960
962 const Reference< XFilterManager >& _rxFilterManager,
963 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
964 {
965 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendExportFilters: invalid manager!" );
966 if ( !_rxFilterManager.is() )
967 return;
968
969 sal_Int32 nHTMLIndex = -1;
970 sal_Int32 nXHTMLIndex = -1;
971 sal_Int32 nPDFIndex = -1;
972 OUString sUIName;
973 OUString sExtensions;
974 std::vector< ExportFilter > aImportantFilterGroup;
975 std::vector< ExportFilter > aFilterGroup;
976 Reference< XFilterGroupManager > xFilterGroupManager( _rxFilterManager, UNO_QUERY );
977 OUString sTypeName;
978
979 for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
980 {
981 sTypeName = pFilter->GetTypeName();
982 sUIName = pFilter->GetUIName();
983 sExtensions = pFilter->GetWildcard().getGlob();
984 ExportFilter aExportFilter( sUIName, sExtensions );
985
986 if ( nHTMLIndex == -1 &&
987 ( sTypeName == "generic_HTML" || sTypeName == "graphic_HTML" ) )
988 {
989 aImportantFilterGroup.insert( aImportantFilterGroup.begin(), aExportFilter );
990 nHTMLIndex = 0;
991 }
992 else if ( nXHTMLIndex == -1 && sTypeName == "XHTML_File" )
993 {
994 std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
995 if ( nHTMLIndex == -1 )
996 aImportantFilterGroup.insert( aIter, aExportFilter );
997 else
998 aImportantFilterGroup.insert( ++aIter, aExportFilter );
999 nXHTMLIndex = 0;
1000 }
1001 else if ( nPDFIndex == -1 && sTypeName == "pdf_Portable_Document_Format" )
1002 {
1003 std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
1004 if ( nHTMLIndex != -1 )
1005 ++aIter;
1006 if ( nXHTMLIndex != -1 )
1007 ++aIter;
1008 aImportantFilterGroup.insert( aIter, aExportFilter );
1009 nPDFIndex = 0;
1010 }
1011 else
1012 aFilterGroup.push_back( aExportFilter );
1013 }
1014
1015 if ( xFilterGroupManager.is() )
1016 {
1017 // Add both html/pdf filter as a filter group to get a separator between both groups
1018 if ( !aImportantFilterGroup.empty() )
1019 {
1020 Sequence< StringPair > aFilters( aImportantFilterGroup.size() );
1021 auto pFilters = aFilters.getArray();
1022 for ( sal_Int32 i = 0; i < static_cast<sal_Int32>(aImportantFilterGroup.size()); i++ )
1023 {
1024 pFilters[i].First = addExtension( aImportantFilterGroup[i].aUIName,
1025 aImportantFilterGroup[i].aWildcard,
1026 false, _rFileDlgImpl );
1027 pFilters[i].Second = aImportantFilterGroup[i].aWildcard;
1028 }
1029
1030 try
1031 {
1032 xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1033 }
1034 catch( const IllegalArgumentException& )
1035 {
1036 }
1037 }
1038
1039 if ( !aFilterGroup.empty() )
1040 {
1041 Sequence< StringPair > aFilters( aFilterGroup.size() );
1042 auto pFilters = aFilters.getArray();
1043 for ( sal_Int32 i = 0; i < static_cast<sal_Int32>(aFilterGroup.size()); i++ )
1044 {
1045 pFilters[i].First = addExtension( aFilterGroup[i].aUIName,
1046 aFilterGroup[i].aWildcard,
1047 false, _rFileDlgImpl );
1048 pFilters[i].Second = aFilterGroup[i].aWildcard;
1049 }
1050
1051 try
1052 {
1053 xFilterGroupManager->appendFilterGroup( OUString(), aFilters );
1054 }
1055 catch( const IllegalArgumentException& )
1056 {
1057 }
1058 }
1059 }
1060 else
1061 {
1062 // Fallback solution just add both filter groups as single filters
1063 sal_Int32 n;
1064
1065 for ( n = 0; n < static_cast<sal_Int32>(aImportantFilterGroup.size()); n++ )
1066 {
1067 try
1068 {
1069 OUString aUIName = addExtension( aImportantFilterGroup[n].aUIName,
1070 aImportantFilterGroup[n].aWildcard,
1071 false, _rFileDlgImpl );
1072 _rxFilterManager->appendFilter( aUIName, aImportantFilterGroup[n].aWildcard );
1073 if ( _rFirstNonEmpty.isEmpty() )
1074 _rFirstNonEmpty = sUIName;
1075
1076 }
1077 catch( const IllegalArgumentException& )
1078 {
1079 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
1080 }
1081 }
1082
1083 for ( n = 0; n < static_cast<sal_Int32>(aFilterGroup.size()); n++ )
1084 {
1085 try
1086 {
1087 OUString aUIName = addExtension( aFilterGroup[n].aUIName,
1088 aFilterGroup[n].aWildcard,
1089 false, _rFileDlgImpl );
1090 _rxFilterManager->appendFilter( aUIName, aFilterGroup[n].aWildcard );
1091 if ( _rFirstNonEmpty.isEmpty() )
1092 _rFirstNonEmpty = sUIName;
1093
1094 }
1095 catch( const IllegalArgumentException& )
1096 {
1097 SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName );
1098 }
1099 }
1100 }
1101 }
1102
1103
1105 const Reference< XFilterManager >& _rxFilterManager,
1106 OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
1107 {
1108 DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForOpen: invalid manager!" );
1109 if ( !_rxFilterManager.is() )
1110 return;
1111
1112
1113 // group and classify the filters
1114 GroupedFilterList aAllFilters;
1115 lcl_GroupAndClassify( _rFilterMatcher, aAllFilters );
1116
1117
1118 // ensure that we have the one "all files" entry
1119 lcl_EnsureAllFilesEntry( _rFilterMatcher, aAllFilters );
1120
1121
1122 // the first non-empty string - which we assume is the first overall entry
1123 if ( !aAllFilters.empty() )
1124 {
1125 const FilterGroup& rFirstGroup = *aAllFilters.begin(); // should be the global classes
1126 if ( !rFirstGroup.empty() )
1127 _rFirstNonEmpty = rFirstGroup.begin()->First;
1128 // append first group, without extension
1129 AppendFilterGroup aGroup( _rxFilterManager, &_rFileDlgImpl );
1130 aGroup.appendGroup( rFirstGroup, false );
1131 }
1132
1133
1134 // append the filters to the manager
1135 if ( !aAllFilters.empty() )
1136 {
1137 ::std::list< FilterGroup >::iterator pIter = aAllFilters.begin();
1138 ++pIter;
1139 ::std::for_each(
1140 pIter, // first filter group was handled separately, see above
1141 aAllFilters.end(),
1142 AppendFilterGroup( _rxFilterManager, &_rFileDlgImpl ) );
1143 }
1144 }
1145
1146 OUString addExtension( const OUString& _rDisplayText,
1147 const OUString& _rExtension,
1148 bool _bForOpen, FileDialogHelper_Impl& _rFileDlgImpl )
1149 {
1150 OUString sRet = _rDisplayText;
1151
1152 if ( sRet.indexOf( "(*.*)" ) == -1 )
1153 {
1154 OUString sExt = _rExtension;
1155 if ( !_bForOpen )
1156 {
1157 // show '*' in extensions only when opening a document
1158 sExt = sExt.replaceAll("*", "");
1159 }
1160 sRet += " (" + sExt + ")";
1161 }
1162 _rFileDlgImpl.addFilterPair( _rDisplayText, sRet );
1163 return sRet;
1164 }
1165
1166
1167} // namespace sfx2
1168
1169
1170/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static SAL_DLLPRIVATE std::shared_ptr< const SfxFilter > GetDefaultFilter_Impl(std::u16string_view)
Definition: fltfnc.cxx:141
static std::shared_ptr< const SfxFilter > GetFilterByName(const OUString &rName)
Definition: docfilt.cxx:115
TValueType getUnpackedValueOrDefault(const OUString &sKey, const TValueType &aDefault) const
void addFilterPair(const OUString &rFilter, const OUString &rFilterWithExtension)
std::shared_ptr< const SfxFilter > impl_getFilter(sal_Int32 nIndex)
::std::vector< OUString > m_lFilters
std::shared_ptr< const SfxFilter > First()
std::shared_ptr< const SfxFilter > Next()
TSortedFilterList(const css::uno::Reference< css::container::XEnumeration > &xFilterList)
#define DBG_ASSERT(sCon, aError)
#define DBG_UNHANDLED_EXCEPTION(...)
constexpr OUStringLiteral FILEDIALOG_FILTER_ALL
bool m_bAddExtension
OUString sDisplayName
Reference< XFilterGroupManager > m_xFilterGroupManager
::std::vector< OUString > aWildCards
FilterGroupEntryReferrer & m_rEntryReferrer
OUString aUIName
FilterGroup::iterator m_aClassPos
FilterClassList & m_rClasses
FilterClassList & m_rClassList
FilterGroupEntryReferrer::mapped_type aLookingFor
FilterGroup & m_rClassGroup
OConfigurationNode m_aClassesNode
OUString & _rToBeExtended
OUString aWildcard
FilterGroup & rTarget
FilterClassReferrer & m_rClassesReferrer
FileDialogHelper_Impl * m_pFileDlgImpl
FilterGroupEntryReferrer & m_rClassReferrer
FilterClassReferrer & m_aClassReferrer
Sequence< FilterName > aSubFilters
Reference< XFilterManager > m_xFilterManager
sal_Int32 nIndex
sal_Int64 n
#define SAL_WARN_IF(condition, area, stream)
#define SAL_WARN(area, stream)
sal_Int32 getTokenCount(std::string_view rIn, char cTok)
DstType sequenceToContainer(const css::uno::Sequence< SrcType > &i_Sequence)
css::uno::Sequence< DstElementType > containerToSequence(const SrcType &i_Container)
int i
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
static void lcl_ReadFilterClass(const OConfigurationNode &_rClassesNode, const OUString &_rLogicalClassName, FilterClass &_rClass)
OUString addExtension(const OUString &_rDisplayText, const OUString &_rExtension, bool _bForOpen, FileDialogHelper_Impl &_rFileDlgImpl)
adds the given extension to the display text.
::std::list< FilterGroup > GroupedFilterList
const sal_Unicode s_cWildcardSeparator(';')
::std::map< OUString, FilterGroup::iterator > FilterGroupEntryReferrer
void appendFiltersForOpen(TSortedFilterList &_rFilterMatcher, const Reference< XFilterManager > &_rxFilterManager, OUString &_rFirstNonEmpty, FileDialogHelper_Impl &_rFileDlgImpl)
::std::map< OUString, FilterClassList::iterator > FilterClassReferrer
static bool lcl_hasAllFilesFilter(TSortedFilterList &_rFilterMatcher, OUString &_rAllFilterName)
StringPair FilterDescriptor
Some general words about what's going on here...
void appendExportFilters(TSortedFilterList &_rFilterMatcher, const Reference< XFilterManager > &_rxFilterManager, OUString &_rFirstNonEmpty, FileDialogHelper_Impl &_rFileDlgImpl)
::std::list< FilterClass > FilterClassList
OUString FilterName
the logical name of a filter
static void lcl_InitGlobalClasses(GroupedFilterList &_rAllFilters, const FilterClassList &_rGlobalClasses, FilterGroupEntryReferrer &_rGlobalClassesRef)
::std::list< FilterDescriptor > FilterGroup
::std::vector< ::std::pair< FilterGroupEntryReferrer::mapped_type, FilterGroup::iterator > > MapGroupEntry2GroupEntry
static void lcl_ReadClassification(FilterClassList &_rGlobalClasses, std::vector< OUString > &_rGlobalClassNames, FilterClassList &_rLocalClasses)
static void lcl_ReadLocalFilters(const OConfigurationNode &_rFilterClassification, FilterClassList &_rLocalClasses)
static void lcl_ReadGlobalFilters(const OConfigurationNode &_rFilterClassification, FilterClassList &_rGlobalClasses, std::vector< OUString > &_rGlobalClassNames)
static void lcl_EnsureAllFilesEntry(TSortedFilterList &_rFilterMatcher, GroupedFilterList &_rFilters)
static void lcl_GroupAndClassify(TSortedFilterList &_rFilterMatcher, GroupedFilterList &_rAllFilters)
void appendFiltersForSave(TSortedFilterList &_rFilterMatcher, const Reference< XFilterManager > &_rxFilterManager, OUString &_rFirstNonEmpty, FileDialogHelper_Impl &_rFileDlgImpl, std::u16string_view _rFactory)
static OUString getSeparatorString()
OUString SfxResId(TranslateId aId)
Definition: sfxresid.cxx:22
sal_uInt16 sal_Unicode