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
