blob: 8d86912f400a2472deeb353026469ef102d88023 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sfx2.hxx"
#include "filtergrouping.hxx"
#include <sfx2/fcontnr.hxx>
#include <sfx2/filedlghelper.hxx>
#include <sfx2/sfx.hrc>
#include <sfx2/docfac.hxx>
#include "sfx2/sfxresid.hxx"
#include <osl/thread.h>
#include <com/sun/star/ui/dialogs/XFilterGroupManager.hpp>
#include <com/sun/star/beans/StringPair.hpp>
#include <com/sun/star/uno/Sequence.hxx>
#include <unotools/confignode.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <tools/wldcrd.hxx>
#include <tools/diagnose_ex.h>
#include <list>
#include <vector>
#include <map>
#include <algorithm>
//........................................................................
namespace sfx2
{
//........................................................................
//#define DISABLE_GROUPING_AND_CLASSIFYING
// not using the functionallity herein, yet
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ui::dialogs;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::beans;
using namespace ::utl;
//====================================================================
/**
Some general words about what's going on here ....
<p>In our file open dialog, usually we display every filter we know. That's how it was before: every filter
lead to an own line in the filter list box, e.g. "StarWriter 5.0 Dokument" or "Microsoft Word 97".</p>
<p>But then the PM came. And everything changed ....</p>
<p>A basic idea are groups: Why simply listing all the single filters? Couldn't we draw nice separators
between the filters which logically belong together? I.e. all the filters which open a document in StarWriter:
couldn't we separate them from all the filters which open the document in StarCalc?<br/>
So spoke the PM, and engineering obeyed.</p>
<p>So we have groups. They're just a visual aspect: All the filters of a group are presented together, separated
by a line from other groups.</p>
<p>Let's be honest: How the concrete implementation of the file picker service separates the different groups
is a matter of this implementation. We only do this grouping and suggest it to the FilePicker service ...</p>
<p>Now for the second concept:<br/>
Thinking about it (and that's what the PM did), both "StarWriter 5.0 Dokument" and "Microsoft Word 97"
describe a text document. It's a text. It's of no interest for the user that one of the texts was saved in
MS' format, and one in our own format.<br/>
So in a first step, we want to have a filter entry "Text documents". This would cover both above-mentioned
filters, as well as any other filters for documents which are texts.</p>
<p>Such an entry as "Text documents" is - within the scope of this file - called "class" or "filter class".</p>
<p>In the file-open-dialog, such a class looks like an ordinary filter: it's simply a name in the filter
listbox. Selecting means that all the files matching one of the "sub-filters" are displayed (in the example above,
this would be "*.sdw", "*.doc" and so on).</p>
<p>Now there are two types of filter classes: global ones and local ones. "Text documents" is a global class. As
well as "Spreadsheets". Or "Web pages".<br/>
Let's have a look at a local class: The filters "MS Word 95" and "MS WinWord 6.0" together form the class
"Microsoft Word 6.0 / 95" (don't ask for the reasons. At least not me. Ask the PM). There are a lot of such
local classes ...</p>
<p>The difference between global and local classes is as follows: Global classes are presented in an own group.
There is one dedicated group at the top of the list, containing all the global groups - no local groups and no
single filters.</p>
<p>Ehm - it was a lie. Not really at the top. Before this group, there is this single "All files" entry. It forms
it's own group. But this is uninteresting here.</p>
<p>Local classes must consist of filters which - without the classification - would all belong to the same group.
Then, they're combined to one entry (in the example above: "Microsoft Word 6.0 / 95"), and this entry is inserted
into the file picker filter list, instead of the single filters which form the class.</p>
<p>This is an interesting difference between local and global classes: Filters which are part of a global class
are listed in there own group, too. Filters in local classes aren't listed a second time - neither directly (as
the filter itself) nor indirectly (as part of another local group).</p>
<p>The only exception are filters which are part of a global class <em>and</em> a local class. This is allowed.
Beeing cotained in two local classes isn't.</p>
<p>So that's all what you need to know: Understand the concept of "filter classes" (a filter class combines
different filters and acts as if it's a filter itself) and the concept of groups (a group just describes a
logical correlation of filters and usually is represented to the user by drawing group separators in the filter
list).</p>
<p>If you got it, go try understanding this file :).</p>
*/
//====================================================================
typedef StringPair FilterDescriptor; // a single filter or a filter class (display name and filter mask)
typedef ::std::list< FilterDescriptor > FilterGroup; // a list of single filter entries
typedef ::std::list< FilterGroup > GroupedFilterList; // a list of all filters, already grouped
/// the logical name of a filter
typedef ::rtl::OUString FilterName;
// a struct which holds references from a logical filter name to a filter group entry
// used for quick lookup of classes (means class entries - entries representing a class)
// which a given filter may belong to
typedef ::std::map< ::rtl::OUString, FilterGroup::iterator > FilterGroupEntryReferrer;
/// a descriptor for a filter class (which in the final dialog is represented by one filter entry)
typedef struct _tagFilterClass
{
::rtl::OUString sDisplayName; // the display name
Sequence< FilterName > aSubFilters; // the (logical) names of the filter which belong to the class
} FilterClass;
typedef ::std::list< FilterClass > FilterClassList;
typedef ::std::map< ::rtl::OUString, FilterClassList::iterator > FilterClassReferrer;
typedef ::std::vector< ::rtl::OUString > StringArray;
// =======================================================================
// = reading of configuration data
// =======================================================================
//--------------------------------------------------------------------
void lcl_ReadFilterClass( const OConfigurationNode& _rClassesNode, const ::rtl::OUString& _rLogicalClassName,
FilterClass& /* [out] */ _rClass )
{
static const ::rtl::OUString sDisplaNameNodeName( RTL_CONSTASCII_USTRINGPARAM( "DisplayName" ) );
static const ::rtl::OUString sSubFiltersNodeName( RTL_CONSTASCII_USTRINGPARAM( "Filters" ) );
// the description node for the current class
OConfigurationNode aClassDesc = _rClassesNode.openNode( _rLogicalClassName );
// the values
aClassDesc.getNodeValue( sDisplaNameNodeName ) >>= _rClass.sDisplayName;
aClassDesc.getNodeValue( sSubFiltersNodeName ) >>= _rClass.aSubFilters;
}
//--------------------------------------------------------------------
struct CreateEmptyClassRememberPos : public ::std::unary_function< FilterName, void >
{
protected:
FilterClassList& m_rClassList;
FilterClassReferrer& m_rClassesReferrer;
public:
CreateEmptyClassRememberPos( FilterClassList& _rClassList, FilterClassReferrer& _rClassesReferrer )
:m_rClassList ( _rClassList )
,m_rClassesReferrer ( _rClassesReferrer )
{
}
// operate on a single class name
void operator() ( const FilterName& _rLogicalFilterName )
{
// insert a new (empty) class
m_rClassList.push_back( FilterClass() );
// get the position of this new entry
FilterClassList::iterator aInsertPos = m_rClassList.end();
--aInsertPos;
// remember this position
m_rClassesReferrer.insert( FilterClassReferrer::value_type( _rLogicalFilterName, aInsertPos ) );
}
};
//--------------------------------------------------------------------
struct ReadGlobalFilter : public ::std::unary_function< FilterName, void >
{
protected:
OConfigurationNode m_aClassesNode;
FilterClassReferrer& m_aClassReferrer;
public:
ReadGlobalFilter( const OConfigurationNode& _rClassesNode, FilterClassReferrer& _rClassesReferrer )
:m_aClassesNode ( _rClassesNode )
,m_aClassReferrer ( _rClassesReferrer )
{
}
// operate on a single logical name
void operator() ( const FilterName& _rName )
{
FilterClassReferrer::iterator aClassRef = m_aClassReferrer.find( _rName );
if ( m_aClassReferrer.end() == aClassRef )
{
// we do not know this global class
DBG_ERROR( "ReadGlobalFilter::operator(): unknown filter name!" );
// TODO: perhaps we should be more tolerant - at the moment, the filter is dropped
// We could silently push_back it to the container ....
}
else
{
// read the data of this class into the node referred to by aClassRef
lcl_ReadFilterClass( m_aClassesNode, _rName, *aClassRef->second );
}
}
};
//--------------------------------------------------------------------
void lcl_ReadGlobalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rGlobalClasses, StringArray& _rGlobalClassNames )
{
_rGlobalClasses.clear();
_rGlobalClassNames.clear();
//================================================================
// get the list describing the order of all global classes
Sequence< ::rtl::OUString > aGlobalClasses;
_rFilterClassification.getNodeValue( DEFINE_CONST_OUSTRING( "GlobalFilters/Order" ) ) >>= aGlobalClasses;
const ::rtl::OUString* pNames = aGlobalClasses.getConstArray();
const ::rtl::OUString* pNamesEnd = pNames + aGlobalClasses.getLength();
// copy the logical names
_rGlobalClassNames.resize( aGlobalClasses.getLength() );
::std::copy( pNames, pNamesEnd, _rGlobalClassNames.begin() );
// Global classes are presented in an own group, so their order matters (while the order of the
// "local classes" doesn't).
// That's why we can't simply add the global classes to _rGlobalClasses using the order in which they
// are returned from the configuration - it is completely undefined, and we need a _defined_ order.
FilterClassReferrer aClassReferrer;
::std::for_each(
pNames,
pNamesEnd,
CreateEmptyClassRememberPos( _rGlobalClasses, aClassReferrer )
);
// now _rGlobalClasses contains a dummy entry for each global class,
// while aClassReferrer maps from the logical name of the class to the position within _rGlobalClasses where
// it's dummy entry resides
//================================================================
// go for all the single class entries
OConfigurationNode aFilterClassesNode =
_rFilterClassification.openNode( DEFINE_CONST_OUSTRING( "GlobalFilters/Classes" ) );
Sequence< ::rtl::OUString > aFilterClasses = aFilterClassesNode.getNodeNames();
::std::for_each(
aFilterClasses.getConstArray(),
aFilterClasses.getConstArray() + aFilterClasses.getLength(),
ReadGlobalFilter( aFilterClassesNode, aClassReferrer )
);
}
//--------------------------------------------------------------------
struct ReadLocalFilter : public ::std::unary_function< FilterName, void >
{
protected:
OConfigurationNode m_aClassesNode;
FilterClassList& m_rClasses;
public:
ReadLocalFilter( const OConfigurationNode& _rClassesNode, FilterClassList& _rClasses )
:m_aClassesNode ( _rClassesNode )
,m_rClasses ( _rClasses )
{
}
// operate on a single logical name
void operator() ( const FilterName& _rName )
{
// read the data for this class
FilterClass aClass;
lcl_ReadFilterClass( m_aClassesNode, _rName, aClass );
// insert the class descriptor
m_rClasses.push_back( aClass );
}
};
//--------------------------------------------------------------------
void lcl_ReadLocalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rLocalClasses )
{
_rLocalClasses.clear();
// the node for the local classes
OConfigurationNode aFilterClassesNode =
_rFilterClassification.openNode( DEFINE_CONST_OUSTRING( "LocalFilters/Classes" ) );
Sequence< ::rtl::OUString > aFilterClasses = aFilterClassesNode.getNodeNames();
::std::for_each(
aFilterClasses.getConstArray(),
aFilterClasses.getConstArray() + aFilterClasses.getLength(),
ReadLocalFilter( aFilterClassesNode, _rLocalClasses )
);
}
//--------------------------------------------------------------------
void lcl_ReadClassification( FilterClassList& _rGlobalClasses, StringArray& _rGlobalClassNames, FilterClassList& _rLocalClasses )
{
//================================================================
// open our config node
OConfigurationTreeRoot aFilterClassification = OConfigurationTreeRoot::createWithServiceFactory(
::comphelper::getProcessServiceFactory(),
DEFINE_CONST_OUSTRING( "org.openoffice.Office.UI/FilterClassification" ),
-1,
OConfigurationTreeRoot::CM_READONLY
);
//================================================================
// go for the global classes
lcl_ReadGlobalFilters( aFilterClassification, _rGlobalClasses, _rGlobalClassNames );
//================================================================
// fo for the local classes
lcl_ReadLocalFilters( aFilterClassification, _rLocalClasses );
}
// =======================================================================
// = grouping and classifying
// =======================================================================
//--------------------------------------------------------------------
// a struct which adds helps remembering a reference to a class entry
struct ReferToFilterEntry : public ::std::unary_function< FilterName, void >
{
protected:
FilterGroupEntryReferrer& m_rEntryReferrer;
FilterGroup::iterator m_aClassPos;
public:
ReferToFilterEntry( FilterGroupEntryReferrer& _rEntryReferrer, const FilterGroup::iterator& _rClassPos )
:m_rEntryReferrer( _rEntryReferrer )
,m_aClassPos( _rClassPos )
{
}
// operate on a single filter name
void operator() ( const FilterName& _rName )
{
#ifdef DBG_UTIL
::std::pair< FilterGroupEntryReferrer::iterator, bool > aInsertRes =
#endif
m_rEntryReferrer.insert( FilterGroupEntryReferrer::value_type( _rName, m_aClassPos ) );
DBG_ASSERT( aInsertRes.second, "ReferToFilterEntry::operator(): already have an element for this name!" );
}
};
//--------------------------------------------------------------------
struct FillClassGroup : public ::std::unary_function< FilterClass, void >
{
protected:
FilterGroup& m_rClassGroup;
FilterGroupEntryReferrer& m_rClassReferrer;
public:
FillClassGroup( FilterGroup& _rClassGroup, FilterGroupEntryReferrer& _rClassReferrer )
:m_rClassGroup ( _rClassGroup )
,m_rClassReferrer ( _rClassReferrer )
{
}
// operate on a single class
void operator() ( const FilterClass& _rClass )
{
// create an empty filter descriptor for the class
FilterDescriptor aClassEntry;
// set it's name (which is all we know by now)
aClassEntry.First = _rClass.sDisplayName;
// add it to the group
m_rClassGroup.push_back( aClassEntry );
// the position of the newly added class
FilterGroup::iterator aClassEntryPos = m_rClassGroup.end();
--aClassEntryPos;
// and for all the sub filters of the class, remember the class
// (respectively the position of the class it the group)
::std::for_each(
_rClass.aSubFilters.getConstArray(),
_rClass.aSubFilters.getConstArray() + _rClass.aSubFilters.getLength(),
ReferToFilterEntry( m_rClassReferrer, aClassEntryPos )
);
}
};
//--------------------------------------------------------------------
static const sal_Unicode s_cWildcardSeparator( ';' );
//====================================================================
const ::rtl::OUString& getSeparatorString()
{
static ::rtl::OUString s_sSeparatorString( &s_cWildcardSeparator, 1 );
return s_sSeparatorString;
}
//====================================================================
struct CheckAppendSingleWildcard : public ::std::unary_function< ::rtl::OUString, void >
{
::rtl::OUString& _rToBeExtended;
CheckAppendSingleWildcard( ::rtl::OUString& _rBase ) : _rToBeExtended( _rBase ) { }
void operator() ( const ::rtl::OUString& _rWC )
{
// check for double wildcards
sal_Int32 nExistentPos = _rToBeExtended.indexOf( _rWC );
if ( -1 < nExistentPos )
{ // found this wildcard (already part of _rToBeExtended)
const sal_Unicode* pBuffer = _rToBeExtended.getStr();
if ( ( 0 == nExistentPos )
|| ( s_cWildcardSeparator == pBuffer[ nExistentPos - 1 ] )
)
{ // the wildcard really starts at this position (it starts at pos 0 or the previous character is a separator
sal_Int32 nExistentWCEnd = nExistentPos + _rWC.getLength();
if ( ( _rToBeExtended.getLength() == nExistentWCEnd )
|| ( s_cWildcardSeparator == pBuffer[ nExistentWCEnd ] )
)
{ // it's really the complete wildcard we found
// (not something like _rWC beeing "*.t" and _rToBeExtended containing "*.txt")
// -> outta here
return;
}
}
}
if ( _rToBeExtended.getLength() )
_rToBeExtended += getSeparatorString();
_rToBeExtended += _rWC;
}
};
//====================================================================
// a helper struct which adds a fixed (Sfx-)filter to a filter group entry given by iterator
struct AppendWildcardToDescriptor : public ::std::unary_function< FilterGroupEntryReferrer::value_type, void >
{
protected:
::std::vector< ::rtl::OUString > aWildCards;
public:
AppendWildcardToDescriptor( const String& _rWildCard );
// operate on a single class entry
void operator() ( const FilterGroupEntryReferrer::value_type& _rClassReference )
{
// simply add our wildcards
::std::for_each(
aWildCards.begin(),
aWildCards.end(),
CheckAppendSingleWildcard( _rClassReference.second->Second )
);
}
};
//====================================================================
AppendWildcardToDescriptor::AppendWildcardToDescriptor( const String& _rWildCard )
{
DBG_ASSERT( _rWildCard.Len(),
"AppendWildcardToDescriptor::AppendWildcardToDescriptor: invalid wildcard!" );
DBG_ASSERT( _rWildCard.GetBuffer()[0] != s_cWildcardSeparator,
"AppendWildcardToDescriptor::AppendWildcardToDescriptor: wildcard already separated!" );
aWildCards.reserve( _rWildCard.GetTokenCount( s_cWildcardSeparator ) );
const sal_Unicode* pTokenLoop = _rWildCard.GetBuffer();
const sal_Unicode* pTokenLoopEnd = pTokenLoop + _rWildCard.Len();
const sal_Unicode* pTokenStart = pTokenLoop;
for ( ; pTokenLoop != pTokenLoopEnd; ++pTokenLoop )
{
if ( ( s_cWildcardSeparator == *pTokenLoop ) && ( pTokenLoop > pTokenStart ) )
{ // found a new token separator (and a non-empty token)
aWildCards.push_back( ::rtl::OUString( pTokenStart, pTokenLoop - pTokenStart ) );
// search the start of the next token
while ( ( pTokenStart != pTokenLoopEnd ) && ( *pTokenStart != s_cWildcardSeparator ) )
++pTokenStart;
if ( pTokenStart == pTokenLoopEnd )
// reached the end
break;
++pTokenStart;
pTokenLoop = pTokenStart;
}
}
if ( pTokenLoop > pTokenStart )
// the last one ....
aWildCards.push_back( ::rtl::OUString( pTokenStart, pTokenLoop - pTokenStart ) );
}
//--------------------------------------------------------------------
void lcl_InitGlobalClasses( GroupedFilterList& _rAllFilters, const FilterClassList& _rGlobalClasses, FilterGroupEntryReferrer& _rGlobalClassesRef )
{
// we need an extra group in our "all filters" container
_rAllFilters.push_front( FilterGroup() );
FilterGroup& rGlobalFilters = _rAllFilters.front();
// it's important to work on the reference: we want to access the members of this filter group
// by an iterator (FilterGroup::const_iterator)
// the referrer for the global classes
// initialize the group
::std::for_each(
_rGlobalClasses.begin(),
_rGlobalClasses.end(),
FillClassGroup( rGlobalFilters, _rGlobalClassesRef )
);
// now we have:
// in rGlobalFilters: a list of FilterDescriptor's, where each's discriptor's display name is set to the name of a class
// in aGlobalClassesRef: a mapping from logical filter names to positions within rGlobalFilters
// this way, if we encounter an arbitrary filter, we can easily (and efficient) check if it belongs to a global class
// and modify the descriptor for this class accordingly
}
//--------------------------------------------------------------------
typedef ::std::vector< ::std::pair< FilterGroupEntryReferrer::mapped_type, FilterGroup::iterator > >
MapGroupEntry2GroupEntry;
// this is not really a map - it's just called this way because it is used as a map
struct FindGroupEntry : public ::std::unary_function< MapGroupEntry2GroupEntry::value_type, sal_Bool >
{
FilterGroupEntryReferrer::mapped_type aLookingFor;
FindGroupEntry( FilterGroupEntryReferrer::mapped_type _rLookingFor ) : aLookingFor( _rLookingFor ) { }
sal_Bool operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
{
return _rMapEntry.first == aLookingFor ? sal_True : sal_False;
}
};
struct CopyGroupEntryContent : public ::std::unary_function< MapGroupEntry2GroupEntry::value_type, void >
{
void operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry )
{
#ifdef DBG_UTIL
FilterDescriptor aHaveALook = *_rMapEntry.first;
#endif
*_rMapEntry.second = *_rMapEntry.first;
}
};
//--------------------------------------------------------------------
struct CopyNonEmptyFilter : public ::std::unary_function< FilterDescriptor, void >
{
FilterGroup& rTarget;
CopyNonEmptyFilter( FilterGroup& _rTarget ) :rTarget( _rTarget ) { }
void operator() ( const FilterDescriptor& _rFilter )
{
if ( _rFilter.Second.getLength() )
rTarget.push_back( _rFilter );
}
};
//--------------------------------------------------------------------
void lcl_GroupAndClassify( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rAllFilters )
{
_rAllFilters.clear();
// ===============================================================
// read the classification of filters
FilterClassList aGlobalClasses, aLocalClasses;
StringArray aGlobalClassNames;
lcl_ReadClassification( aGlobalClasses, aGlobalClassNames, aLocalClasses );
// ===============================================================
// for the global filter classes
FilterGroupEntryReferrer aGlobalClassesRef;
lcl_InitGlobalClasses( _rAllFilters, aGlobalClasses, aGlobalClassesRef );
// insert as much placeholders (FilterGroup's) into _rAllFilter for groups as we have global classes
// (this assumes that both numbers are the same, which, speaking strictly, must not hold - but it does, as we know ...)
sal_Int32 nGlobalClasses = aGlobalClasses.size();
while ( nGlobalClasses-- )
_rAllFilters.push_back( FilterGroup() );
// ===============================================================
// for the local classes:
// if n filters belong to a local class, they do not appear in their respective group explicitly, instead
// and entry for the class is added to the group and the extensions of the filters are collected under
// this entry
FilterGroupEntryReferrer aLocalClassesRef;
FilterGroup aCollectedLocals;
::std::for_each(
aLocalClasses.begin(),
aLocalClasses.end(),
FillClassGroup( aCollectedLocals, aLocalClassesRef )
);
// to map from the position within aCollectedLocals to positions within the real groups
// (where they finally belong to)
MapGroupEntry2GroupEntry aLocalFinalPositions;
// ===============================================================
// now add the filters
// the group which we currently work with
GroupedFilterList::iterator aCurrentGroup = _rAllFilters.end(); // no current group
// the filter container of the current group - if this changes between two filters, a new group is reached
String aCurrentServiceName;
String sFilterWildcard;
::rtl::OUString sFilterName;
// loop through all the filters
for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
{
sFilterName = pFilter->GetFilterName();
sFilterWildcard = pFilter->GetWildcard().GetWildCard();
AppendWildcardToDescriptor aExtendWildcard( sFilterWildcard );
DBG_ASSERT( sFilterWildcard.Len(), "sfx2::lcl_GroupAndClassify: invalid wildcard of this filter!" );
// ===========================================================
// check for a change in the group
String aServiceName = pFilter->GetServiceName();
if ( aServiceName != aCurrentServiceName )
{ // we reached a new group
::rtl::OUString sDocServName = aServiceName;
// look for the place in _rAllFilters where this ne group belongs - this is determined
// by the order of classes in aGlobalClassNames
GroupedFilterList::iterator aGroupPos = _rAllFilters.begin();
DBG_ASSERT( aGroupPos != _rAllFilters.end(),
"sfx2::lcl_GroupAndClassify: invalid all-filters array here!" );
// the loop below will work on invalid objects else ...
++aGroupPos;
StringArray::iterator aGlobalIter = aGlobalClassNames.begin();
while ( ( aGroupPos != _rAllFilters.end() )
&& ( aGlobalIter != aGlobalClassNames.end() )
&& ( *aGlobalIter != sDocServName )
)
{
++aGlobalIter;
++aGroupPos;
}
if ( aGroupPos != _rAllFilters.end() )
// we found a global class name which matchies the doc service name -> fill the filters of this
// group in the respective prepared group
aCurrentGroup = aGroupPos;
else
// insert a new entry in our overall-list
aCurrentGroup = _rAllFilters.insert( _rAllFilters.end(), FilterGroup() );
// remember the container to properly detect the next group
aCurrentServiceName = aServiceName;
}
DBG_ASSERT( aCurrentGroup != _rAllFilters.end(), "sfx2::lcl_GroupAndClassify: invalid current group!" );
// ===========================================================
// check if the filter is part of a global group
::std::pair< FilterGroupEntryReferrer::iterator, FilterGroupEntryReferrer::iterator >
aBelongsTo = aGlobalClassesRef.equal_range( sFilterName );
// add the filter to the entries for these classes
// (if they exist - if not, the range is empty and the for_each is a no-op)
::std::for_each(
aBelongsTo.first,
aBelongsTo.second,
aExtendWildcard
);
// ===========================================================
// add the filter to it's group
// for this, check if the filter is part of a local filter
FilterGroupEntryReferrer::iterator aBelongsToLocal = aLocalClassesRef.find( sFilterName );
if ( aLocalClassesRef.end() != aBelongsToLocal )
{
/*
#ifdef DBG_UTIL
const ::rtl::OUString& rLocalClassDisplayName = aBelongsToLocal->second->First;
const ::rtl::OUString& rLocalClassExtension = aBelongsToLocal->second->Second;
#endif
*/
// okay, there is a local class which the filter belongs to
// -> append the wildcard
aExtendWildcard( *aBelongsToLocal );
MapGroupEntry2GroupEntry::iterator aThisGroupFinalPos =
::std::find_if( aLocalFinalPositions.begin(), aLocalFinalPositions.end(), FindGroupEntry( aBelongsToLocal->second ) );
if ( aLocalFinalPositions.end() == aThisGroupFinalPos )
{ // the position within aCollectedLocals has not been mapped to a final position
// within the "real" group (aCollectedLocals is only temporary)
// -> do this now (as we just encountered the first filter belonging to this local class
// add a new entry which is the "real" group entry
aCurrentGroup->push_back( FilterDescriptor( aBelongsToLocal->second->First, String() ) );
// the position where we inserted the entry
FilterGroup::iterator aInsertPos = aCurrentGroup->end();
--aInsertPos;
// remember this pos
aLocalFinalPositions.push_back( MapGroupEntry2GroupEntry::value_type( aBelongsToLocal->second, aInsertPos ) );
}
}
else
aCurrentGroup->push_back( FilterDescriptor( pFilter->GetUIName(), sFilterWildcard ) );
}
// now just complete the infos for the local groups:
// During the above loop, they have been collected in aCollectedLocals, but this is only temporary
// They have to be copied into their final positions (which are stored in aLocalFinalPositions)
::std::for_each(
aLocalFinalPositions.begin(),
aLocalFinalPositions.end(),
CopyGroupEntryContent()
);
// and remove local groups which do not apply - e.g. have no entries due to the limited content of the
// current SfxFilterMatcherIter
FilterGroup& rGlobalFilters = _rAllFilters.front();
FilterGroup aNonEmptyGlobalFilters;
::std::for_each(
rGlobalFilters.begin(),
rGlobalFilters.end(),
CopyNonEmptyFilter( aNonEmptyGlobalFilters )
);
rGlobalFilters.swap( aNonEmptyGlobalFilters );
}
//--------------------------------------------------------------------
struct AppendFilter : public ::std::unary_function< FilterDescriptor, void >
{
protected:
Reference< XFilterManager > m_xFilterManager;
FileDialogHelper_Impl* m_pFileDlgImpl;
bool m_bAddExtension;
public:
AppendFilter( const Reference< XFilterManager >& _rxFilterManager,
FileDialogHelper_Impl* _pImpl, bool _bAddExtension ) :
m_xFilterManager( _rxFilterManager ),
m_pFileDlgImpl ( _pImpl ),
m_bAddExtension ( _bAddExtension )
{
DBG_ASSERT( m_xFilterManager.is(), "AppendFilter::AppendFilter: invalid filter manager!" );
DBG_ASSERT( m_pFileDlgImpl, "AppendFilter::AppendFilter: invalid filedlg impl!" );
}
// operate on a single filter
void operator() ( const FilterDescriptor& _rFilterEntry )
{
String sDisplayText = m_bAddExtension
? addExtension( _rFilterEntry.First, _rFilterEntry.Second, sal_True, *m_pFileDlgImpl )
: _rFilterEntry.First;
m_xFilterManager->appendFilter( sDisplayText, _rFilterEntry.Second );
}
};
// =======================================================================
// = handling for the "all files" entry
// =======================================================================
//--------------------------------------------------------------------
sal_Bool lcl_hasAllFilesFilter( TSortedFilterList& _rFilterMatcher, String& /* [out] */ _rAllFilterName )
{
::rtl::OUString sUIName;
sal_Bool bHasAll = sal_False;
_rAllFilterName = String( SfxResId( STR_SFX_FILTERNAME_ALL ) );
// ===============================================================
// check if there's already a filter <ALL>
for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter && !bHasAll; pFilter = _rFilterMatcher.Next() )
{
if ( pFilter->GetUIName() == _rAllFilterName )
bHasAll = sal_True;
}
return bHasAll;
}
//--------------------------------------------------------------------
void lcl_EnsureAllFilesEntry( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rFilters )
{
// ===============================================================
String sAllFilterName;
if ( !lcl_hasAllFilesFilter( _rFilterMatcher, sAllFilterName ) )
{
// get the first group of filters (by definition, this group contains the global classes)
DBG_ASSERT( !_rFilters.empty(), "lcl_EnsureAllFilesEntry: invalid filter list!" );
if ( !_rFilters.empty() )
{
FilterGroup& rGlobalClasses = *_rFilters.begin();
rGlobalClasses.push_front( FilterDescriptor( sAllFilterName, DEFINE_CONST_UNICODE( FILEDIALOG_FILTER_ALL ) ) );
}
}
}
#ifdef DISABLE_GROUPING_AND_CLASSIFYING
//--------------------------------------------------------------------
void lcl_EnsureAllFilesEntry( TSortedFilterList& _rFilterMatcher, const Reference< XFilterManager >& _rxFilterManager, ::rtl::OUString& _rFirstNonEmpty )
{
// ===============================================================
String sAllFilterName;
if ( !lcl_hasAllFilesFilter( _rFilterMatcher, sAllFilterName ) )
{
try
{
_rxFilterManager->appendFilter( sAllFilterName, DEFINE_CONST_UNICODE( FILEDIALOG_FILTER_ALL ) );
_rFirstNonEmpty = sAllFilterName;
}
catch( const IllegalArgumentException& )
{
#ifdef DBG_UTIL
ByteString aMsg( "sfx2::lcl_EnsureAllFilesEntry: could not append Filter" );
aMsg += ByteString( String( sAllFilterName ), RTL_TEXTENCODING_UTF8 );
DBG_ERROR( aMsg.GetBuffer() );
#endif
}
}
}
#endif
// =======================================================================
// = filling an XFilterManager
// =======================================================================
//--------------------------------------------------------------------
struct AppendFilterGroup : public ::std::unary_function< FilterGroup, void >
{
protected:
Reference< XFilterManager > m_xFilterManager;
Reference< XFilterGroupManager > m_xFilterGroupManager;
FileDialogHelper_Impl* m_pFileDlgImpl;
public:
AppendFilterGroup( const Reference< XFilterManager >& _rxFilterManager, FileDialogHelper_Impl* _pImpl )
:m_xFilterManager ( _rxFilterManager )
,m_xFilterGroupManager ( _rxFilterManager, UNO_QUERY )
,m_pFileDlgImpl ( _pImpl )
{
DBG_ASSERT( m_xFilterManager.is(), "AppendFilterGroup::AppendFilterGroup: invalid filter manager!" );
DBG_ASSERT( m_pFileDlgImpl, "AppendFilterGroup::AppendFilterGroup: invalid filedlg impl!" );
}
void appendGroup( const FilterGroup& _rGroup, bool _bAddExtension )
{
try
{
if ( m_xFilterGroupManager.is() )
{ // the file dialog implementation supports visual grouping of filters
// create a representation of the group which is understandable by the XFilterGroupManager
if ( _rGroup.size() )
{
Sequence< StringPair > aFilters( _rGroup.size() );
::std::copy(
_rGroup.begin(),
_rGroup.end(),
aFilters.getArray()
);
if ( _bAddExtension )
{
StringPair* pFilters = aFilters.getArray();
StringPair* pEnd = pFilters + aFilters.getLength();
for ( ; pFilters != pEnd; ++pFilters )
pFilters->First = addExtension( pFilters->First, pFilters->Second, sal_True, *m_pFileDlgImpl );
}
m_xFilterGroupManager->appendFilterGroup( ::rtl::OUString(), aFilters );
}
}
else
{
::std::for_each(
_rGroup.begin(),
_rGroup.end(),
AppendFilter( m_xFilterManager, m_pFileDlgImpl, _bAddExtension ) );
}
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION();
}
}
// operate on a single filter group
void operator() ( const FilterGroup& _rGroup )
{
appendGroup( _rGroup, true );
}
};
//--------------------------------------------------------------------
TSortedFilterList::TSortedFilterList(const ::com::sun::star::uno::Reference< ::com::sun::star::container::XEnumeration >& xFilterList)
: m_nIterator(0)
{
if (!xFilterList.is())
return;
m_lFilters.clear();
while(xFilterList->hasMoreElements())
{
::comphelper::SequenceAsHashMap lFilterProps (xFilterList->nextElement());
::rtl::OUString sFilterName = lFilterProps.getUnpackedValueOrDefault(
::rtl::OUString::createFromAscii("Name"),
::rtl::OUString());
if (sFilterName.getLength())
m_lFilters.push_back(sFilterName);
}
}
//--------------------------------------------------------------------
const SfxFilter* TSortedFilterList::First()
{
m_nIterator = 0;
return impl_getFilter(m_nIterator);
}
//--------------------------------------------------------------------
const SfxFilter* TSortedFilterList::Next()
{
++m_nIterator;
return impl_getFilter(m_nIterator);
}
//--------------------------------------------------------------------
const SfxFilter* TSortedFilterList::impl_getFilter(sal_Int32 nIndex)
{
if (nIndex<0 || nIndex>=(sal_Int32)m_lFilters.size())
return 0;
const ::rtl::OUString& sFilterName = m_lFilters[nIndex];
if (!sFilterName.getLength())
return 0;
return SfxFilter::GetFilterByName(String(sFilterName));
}
//--------------------------------------------------------------------
void appendFiltersForSave( TSortedFilterList& _rFilterMatcher,
const Reference< XFilterManager >& _rxFilterManager,
::rtl::OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl,
const ::rtl::OUString& _rFactory )
{
DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForSave: invalid manager!" );
if ( !_rxFilterManager.is() )
return;
::rtl::OUString sUIName;
::rtl::OUString sExtension;
// retrieve the default filter for this application module.
// It must be set as first of the generated filter list.
const SfxFilter* pDefaultFilter = SfxFilterContainer::GetDefaultFilter_Impl(_rFactory);
// --> PB 2004-11-01 #i32434# only use one extension
// (and always the first if there are more than one)
sExtension = pDefaultFilter->GetWildcard().GetWildCard().GetToken( 0, ';' );
// <--
sUIName = addExtension( pDefaultFilter->GetUIName(), sExtension, sal_False, _rFileDlgImpl );
try
{
_rxFilterManager->appendFilter( sUIName, sExtension );
if ( !_rFirstNonEmpty.getLength() )
_rFirstNonEmpty = sUIName;
}
catch( IllegalArgumentException )
{
#ifdef DBG_UTIL
ByteString aMsg( "Could not append DefaultFilter" );
aMsg += ByteString( String( sUIName ), osl_getThreadTextEncoding() );
DBG_ERRORFILE( aMsg.GetBuffer() );
#endif
}
for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
{
if (pFilter->GetName() == pDefaultFilter->GetName())
continue;
// --> PB 2004-09-21 #i32434# only use one extension
// (and always the first if there are more than one)
sExtension = pFilter->GetWildcard().GetWildCard().GetToken( 0, ';' );
// <--
sUIName = addExtension( pFilter->GetUIName(), sExtension, sal_False, _rFileDlgImpl );
try
{
_rxFilterManager->appendFilter( sUIName, sExtension );
if ( !_rFirstNonEmpty.getLength() )
_rFirstNonEmpty = sUIName;
}
catch( IllegalArgumentException )
{
#ifdef DBG_UTIL
ByteString aMsg( "Could not append Filter" );
aMsg += ByteString( String( sUIName ), osl_getThreadTextEncoding() );
DBG_ERRORFILE( aMsg.GetBuffer() );
#endif
}
}
}
struct ExportFilter
{
ExportFilter( const rtl::OUString& _aUIName, const rtl::OUString& _aWildcard ) :
aUIName( _aUIName ), aWildcard( _aWildcard ) {}
rtl::OUString aUIName;
rtl::OUString aWildcard;
};
//--------------------------------------------------------------------
void appendExportFilters( TSortedFilterList& _rFilterMatcher,
const Reference< XFilterManager >& _rxFilterManager,
::rtl::OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
{
DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendExportFilters: invalid manager!" );
if ( !_rxFilterManager.is() )
return;
sal_Int32 nHTMLIndex = -1;
sal_Int32 nXHTMLIndex = -1;
sal_Int32 nPDFIndex = -1;
sal_Int32 nFlashIndex = -1;
::rtl::OUString sUIName;
::rtl::OUString sExtensions;
std::vector< ExportFilter > aImportantFilterGroup;
std::vector< ExportFilter > aFilterGroup;
Reference< XFilterGroupManager > xFilterGroupManager( _rxFilterManager, UNO_QUERY );
::rtl::OUString sTypeName;
const ::rtl::OUString sWriterHTMLType( DEFINE_CONST_OUSTRING("writer_web_HTML") );
const ::rtl::OUString sGraphicHTMLType( DEFINE_CONST_OUSTRING("graphic_HTML") );
const ::rtl::OUString sXHTMLType( DEFINE_CONST_OUSTRING("XHTML_File") );
const ::rtl::OUString sPDFType( DEFINE_CONST_OUSTRING("pdf_Portable_Document_Format") );
const ::rtl::OUString sFlashType( DEFINE_CONST_OUSTRING("graphic_SWF") );
for ( const SfxFilter* pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() )
{
sTypeName = pFilter->GetTypeName();
sUIName = pFilter->GetUIName();
sExtensions = pFilter->GetWildcard().GetWildCard();
ExportFilter aExportFilter( sUIName, sExtensions );
String aExt = sExtensions;
if ( nHTMLIndex == -1 &&
( sTypeName.equals( sWriterHTMLType ) || sTypeName.equals( sGraphicHTMLType ) ) )
{
aImportantFilterGroup.insert( aImportantFilterGroup.begin(), aExportFilter );
nHTMLIndex = 0;
}
else if ( nXHTMLIndex == -1 && sTypeName.equals( sXHTMLType ) )
{
std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
if ( nHTMLIndex == -1 )
aImportantFilterGroup.insert( aIter, aExportFilter );
else
aImportantFilterGroup.insert( ++aIter, aExportFilter );
nXHTMLIndex = 0;
}
else if ( nPDFIndex == -1 && sTypeName.equals( sPDFType ) )
{
std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
if ( nHTMLIndex != -1 )
aIter++;
if ( nXHTMLIndex != -1 )
aIter++;
aImportantFilterGroup.insert( aIter, aExportFilter );
nPDFIndex = 0;
}
else if ( nFlashIndex == -1 && sTypeName.equals( sFlashType ) )
{
std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin();
if ( nHTMLIndex != -1 )
aIter++;
if ( nXHTMLIndex != -1 )
aIter++;
if ( nPDFIndex != -1 )
aIter++;
aImportantFilterGroup.insert( aIter, aExportFilter );
nFlashIndex = 0;
}
else
aFilterGroup.push_back( aExportFilter );
}
if ( xFilterGroupManager.is() )
{
// Add both html/pdf filter as a filter group to get a separator between both groups
if ( aImportantFilterGroup.size() > 0 )
{
Sequence< StringPair > aFilters( aImportantFilterGroup.size() );
for ( sal_Int32 i = 0; i < (sal_Int32)aImportantFilterGroup.size(); i++ )
{
aFilters[i].First = addExtension( aImportantFilterGroup[i].aUIName,
aImportantFilterGroup[i].aWildcard,
sal_False, _rFileDlgImpl );
aFilters[i].Second = aImportantFilterGroup[i].aWildcard;
}
try
{
xFilterGroupManager->appendFilterGroup( ::rtl::OUString(), aFilters );
}
catch( IllegalArgumentException )
{
}
}
if ( aFilterGroup.size() > 0 )
{
Sequence< StringPair > aFilters( aFilterGroup.size() );
for ( sal_Int32 i = 0; i < (sal_Int32)aFilterGroup.size(); i++ )
{
aFilters[i].First = addExtension( aFilterGroup[i].aUIName,
aFilterGroup[i].aWildcard,
sal_False, _rFileDlgImpl );
aFilters[i].Second = aFilterGroup[i].aWildcard;
}
try
{
xFilterGroupManager->appendFilterGroup( ::rtl::OUString(), aFilters );
}
catch( IllegalArgumentException )
{
}
}
}
else
{
// Fallback solution just add both filter groups as single filters
sal_Int32 n;
for ( n = 0; n < (sal_Int32)aImportantFilterGroup.size(); n++ )
{
try
{
rtl::OUString aUIName = addExtension( aImportantFilterGroup[n].aUIName,
aImportantFilterGroup[n].aWildcard,
sal_False, _rFileDlgImpl );
_rxFilterManager->appendFilter( aUIName, aImportantFilterGroup[n].aWildcard );
if ( !_rFirstNonEmpty.getLength() )
_rFirstNonEmpty = sUIName;
}
catch( IllegalArgumentException )
{
#ifdef DBG_UTIL
ByteString aMsg( "Could not append Filter" );
aMsg += ByteString( String( sUIName ), osl_getThreadTextEncoding() );
DBG_ERRORFILE( aMsg.GetBuffer() );
#endif
}
}
for ( n = 0; n < (sal_Int32)aFilterGroup.size(); n++ )
{
try
{
rtl::OUString aUIName = addExtension( aFilterGroup[n].aUIName,
aFilterGroup[n].aWildcard,
sal_False, _rFileDlgImpl );
_rxFilterManager->appendFilter( aUIName, aFilterGroup[n].aWildcard );
if ( !_rFirstNonEmpty.getLength() )
_rFirstNonEmpty = sUIName;
}
catch( IllegalArgumentException )
{
#ifdef DBG_UTIL
ByteString aMsg( "Could not append Filter" );
aMsg += ByteString( String( sUIName ), osl_getThreadTextEncoding() );
DBG_ERRORFILE( aMsg.GetBuffer() );
#endif
}
}
}
}
//--------------------------------------------------------------------
void appendFiltersForOpen( TSortedFilterList& _rFilterMatcher,
const Reference< XFilterManager >& _rxFilterManager,
::rtl::OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl )
{
DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForOpen: invalid manager!" );
if ( !_rxFilterManager.is() )
return;
#ifdef DISABLE_GROUPING_AND_CLASSIFYING
// ===============================================================
// ensure that there's an entry "all" (with wildcard *.*)
lcl_EnsureAllFilesEntry( _rFilterMatcher, _rxFilterManager, _rFirstNonEmpty );
// ===============================================================
appendFilters( _rFilterMatcher, _rxFilterManager, _rFirstNonEmpty );
#else
// ===============================================================
// group and classify the filters
GroupedFilterList aAllFilters;
lcl_GroupAndClassify( _rFilterMatcher, aAllFilters );
// ===============================================================
// ensure that we have the one "all files" entry
lcl_EnsureAllFilesEntry( _rFilterMatcher, aAllFilters );
// ===============================================================
// the first non-empty string - which we assume is the first overall entry
if ( !aAllFilters.empty() )
{
const FilterGroup& rFirstGroup = *aAllFilters.begin(); // should be the global classes
if ( !rFirstGroup.empty() )
_rFirstNonEmpty = rFirstGroup.begin()->First;
// append first group, without extension
AppendFilterGroup aGroup( _rxFilterManager, &_rFileDlgImpl );
aGroup.appendGroup( rFirstGroup, false );
}
// ===============================================================
// append the filters to the manager
if ( !aAllFilters.empty() )
{
::std::list< FilterGroup >::iterator pIter = aAllFilters.begin();
++pIter;
::std::for_each(
pIter, // first filter group was handled seperately, see above
aAllFilters.end(),
AppendFilterGroup( _rxFilterManager, &_rFileDlgImpl ) );
}
#endif
}
::rtl::OUString addExtension( const ::rtl::OUString& _rDisplayText,
const ::rtl::OUString& _rExtension,
sal_Bool _bForOpen, FileDialogHelper_Impl& _rFileDlgImpl )
{
static ::rtl::OUString sAllFilter( RTL_CONSTASCII_USTRINGPARAM( "(*.*)" ) );
static ::rtl::OUString sOpenBracket( RTL_CONSTASCII_USTRINGPARAM( " (" ) );
static ::rtl::OUString sCloseBracket( RTL_CONSTASCII_USTRINGPARAM( ")" ) );
::rtl::OUString sRet = _rDisplayText;
if ( sRet.indexOf( sAllFilter ) == -1 )
{
String sExt = _rExtension;
if ( !_bForOpen )
// show '*' in extensions only when opening a document
sExt.EraseAllChars( '*' );
sRet += sOpenBracket;
sRet += sExt;
sRet += sCloseBracket;
}
_rFileDlgImpl.addFilterPair( _rDisplayText, sRet );
return sRet;
}
//........................................................................
} // namespace sfx2
//........................................................................