| /************************************************************** |
| * |
| * 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. |
| * |
| *************************************************************/ |
| |
| |
| |
| #include "oox/ole/vbacontrol.hxx" |
| |
| #include <algorithm> |
| #include <set> |
| #include <com/sun/star/awt/XControlModel.hpp> |
| #include <com/sun/star/container/XNameContainer.hpp> |
| #include <com/sun/star/io/XInputStreamProvider.hpp> |
| #include <com/sun/star/lang/XMultiServiceFactory.hpp> |
| #include <com/sun/star/uno/XComponentContext.hpp> |
| #include <rtl/ustrbuf.hxx> |
| #include <xmlscript/xmldlg_imexp.hxx> |
| #include "oox/helper/attributelist.hxx" |
| #include "oox/helper/binaryinputstream.hxx" |
| #include "oox/helper/containerhelper.hxx" |
| #include "oox/helper/propertymap.hxx" |
| #include "oox/helper/propertyset.hxx" |
| #include "oox/helper/storagebase.hxx" |
| #include "oox/helper/textinputstream.hxx" |
| #include "oox/ole/vbahelper.hxx" |
| |
| namespace oox { |
| namespace ole { |
| |
| // ============================================================================ |
| |
| using namespace ::com::sun::star::awt; |
| using namespace ::com::sun::star::container; |
| using namespace ::com::sun::star::frame; |
| using namespace ::com::sun::star::io; |
| using namespace ::com::sun::star::lang; |
| using namespace ::com::sun::star::uno; |
| |
| using ::rtl::OUString; |
| using ::rtl::OUStringBuffer; |
| |
| // ============================================================================ |
| |
| namespace { |
| |
| const sal_uInt16 VBA_SITE_CLASSIDINDEX = 0x8000; |
| const sal_uInt16 VBA_SITE_INDEXMASK = 0x7FFF; |
| const sal_uInt16 VBA_SITE_FORM = 7; |
| const sal_uInt16 VBA_SITE_IMAGE = 12; |
| const sal_uInt16 VBA_SITE_FRAME = 14; |
| const sal_uInt16 VBA_SITE_SPINBUTTON = 16; |
| const sal_uInt16 VBA_SITE_COMMANDBUTTON = 17; |
| const sal_uInt16 VBA_SITE_TABSTRIP = 18; |
| const sal_uInt16 VBA_SITE_LABEL = 21; |
| const sal_uInt16 VBA_SITE_TEXTBOX = 23; |
| const sal_uInt16 VBA_SITE_LISTBOX = 24; |
| const sal_uInt16 VBA_SITE_COMBOBOX = 25; |
| const sal_uInt16 VBA_SITE_CHECKBOX = 26; |
| const sal_uInt16 VBA_SITE_OPTIONBUTTON = 27; |
| const sal_uInt16 VBA_SITE_TOGGLEBUTTON = 28; |
| const sal_uInt16 VBA_SITE_SCROLLBAR = 47; |
| const sal_uInt16 VBA_SITE_MULTIPAGE = 57; |
| const sal_uInt16 VBA_SITE_UNKNOWN = 0x7FFF; |
| |
| const sal_uInt32 VBA_SITE_TABSTOP = 0x00000001; |
| const sal_uInt32 VBA_SITE_VISIBLE = 0x00000002; |
| const sal_uInt32 VBA_SITE_DEFAULTBUTTON = 0x00000004; |
| const sal_uInt32 VBA_SITE_CANCELBUTTON = 0x00000008; |
| const sal_uInt32 VBA_SITE_OSTREAM = 0x00000010; |
| const sal_uInt32 VBA_SITE_DEFFLAGS = 0x00000033; |
| |
| const sal_uInt8 VBA_SITEINFO_COUNT = 0x80; |
| const sal_uInt8 VBA_SITEINFO_MASK = 0x7F; |
| |
| // ---------------------------------------------------------------------------- |
| |
| /** Collects names of all controls in a user form or container control. Allows |
| to generate unused names for dummy controls separating option groups. |
| */ |
| class VbaControlNamesSet |
| { |
| public: |
| explicit VbaControlNamesSet(); |
| |
| /** Inserts the name of the passed control. */ |
| void insertName( const VbaFormControl& rControl ); |
| /** Returns a name that is not contained in this set. */ |
| OUString generateDummyName(); |
| |
| private: |
| typedef ::std::set< OUString > OUStringSet; |
| OUStringSet maCtrlNames; |
| const OUString maDummyBaseName; |
| sal_Int32 mnIndex; |
| }; |
| |
| VbaControlNamesSet::VbaControlNamesSet() : |
| maDummyBaseName( CREATE_OUSTRING( "DummyGroupSep" ) ), |
| mnIndex( 0 ) |
| { |
| } |
| |
| void VbaControlNamesSet::insertName( const VbaFormControl& rControl ) |
| { |
| OUString aName = rControl.getControlName(); |
| if( aName.getLength() > 0 ) |
| maCtrlNames.insert( aName ); |
| } |
| |
| OUString VbaControlNamesSet::generateDummyName() |
| { |
| OUString aCtrlName; |
| do |
| { |
| aCtrlName = OUStringBuffer( maDummyBaseName ).append( ++mnIndex ).makeStringAndClear(); |
| } |
| while( maCtrlNames.count( aCtrlName ) > 0 ); |
| maCtrlNames.insert( aCtrlName ); |
| return aCtrlName; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| /** Functor that inserts the name of a control into a VbaControlNamesSet. */ |
| struct VbaControlNameInserter |
| { |
| public: |
| VbaControlNamesSet& mrCtrlNames; |
| inline explicit VbaControlNameInserter( VbaControlNamesSet& rCtrlNames ) : mrCtrlNames( rCtrlNames ) {} |
| inline void operator()( const VbaFormControl& rControl ) { mrCtrlNames.insertName( rControl ); } |
| }; |
| |
| // ---------------------------------------------------------------------------- |
| |
| /** A dummy invisible form control (fixed label without text) that is used to |
| separate two groups of option buttons. |
| */ |
| class VbaDummyFormControl : public VbaFormControl |
| { |
| public: |
| explicit VbaDummyFormControl( const OUString& rName ); |
| }; |
| |
| VbaDummyFormControl::VbaDummyFormControl( const OUString& rName ) |
| { |
| mxSiteModel.reset( new VbaSiteModel ); |
| mxSiteModel->importProperty( XML_Name, rName ); |
| mxSiteModel->importProperty( XML_VariousPropertyBits, OUString( sal_Unicode( '0' ) ) ); |
| |
| mxCtrlModel.reset( new AxLabelModel ); |
| mxCtrlModel->setAwtModelMode(); |
| mxCtrlModel->importProperty( XML_Size, CREATE_OUSTRING( "10;10" ) ); |
| } |
| |
| } // namespace |
| |
| // ============================================================================ |
| |
| VbaSiteModel::VbaSiteModel() : |
| maPos( 0, 0 ), |
| mnId( 0 ), |
| mnHelpContextId( 0 ), |
| mnFlags( VBA_SITE_DEFFLAGS ), |
| mnStreamLen( 0 ), |
| mnTabIndex( -1 ), |
| mnClassIdOrCache( VBA_SITE_UNKNOWN ), |
| mnGroupId( 0 ) |
| { |
| } |
| |
| VbaSiteModel::~VbaSiteModel() |
| { |
| } |
| |
| void VbaSiteModel::importProperty( sal_Int32 nPropId, const OUString& rValue ) |
| { |
| switch( nPropId ) |
| { |
| case XML_Name: maName = rValue; break; |
| case XML_Tag: maTag = rValue; break; |
| case XML_VariousPropertyBits: mnFlags = AttributeConversion::decodeUnsigned( rValue ); break; |
| } |
| } |
| |
| bool VbaSiteModel::importBinaryModel( BinaryInputStream& rInStrm ) |
| { |
| AxBinaryPropertyReader aReader( rInStrm ); |
| aReader.readStringProperty( maName ); |
| aReader.readStringProperty( maTag ); |
| aReader.readIntProperty< sal_Int32 >( mnId ); |
| aReader.readIntProperty< sal_Int32 >( mnHelpContextId ); |
| aReader.readIntProperty< sal_uInt32 >( mnFlags ); |
| aReader.readIntProperty< sal_uInt32 >( mnStreamLen ); |
| aReader.readIntProperty< sal_Int16 >( mnTabIndex ); |
| aReader.readIntProperty< sal_uInt16 >( mnClassIdOrCache ); |
| aReader.readPairProperty( maPos ); |
| aReader.readIntProperty< sal_uInt16 >( mnGroupId ); |
| aReader.skipUndefinedProperty(); |
| aReader.readStringProperty( maToolTip ); |
| aReader.skipStringProperty(); // license key |
| aReader.readStringProperty( maControlSource ); |
| aReader.readStringProperty( maRowSource ); |
| return aReader.finalizeImport(); |
| } |
| |
| void VbaSiteModel::moveRelative( const AxPairData& rDistance ) |
| { |
| maPos.first += rDistance.first; |
| maPos.second += rDistance.second; |
| } |
| |
| bool VbaSiteModel::isVisible() const |
| { |
| return getFlag( mnFlags, VBA_SITE_VISIBLE ); |
| } |
| |
| bool VbaSiteModel::isContainer() const |
| { |
| return !getFlag( mnFlags, VBA_SITE_OSTREAM ); |
| } |
| |
| sal_uInt32 VbaSiteModel::getStreamLength() const |
| { |
| return isContainer() ? 0 : mnStreamLen; |
| } |
| |
| OUString VbaSiteModel::getSubStorageName() const |
| { |
| if( mnId >= 0 ) |
| { |
| OUStringBuffer aBuffer; |
| aBuffer.append( sal_Unicode( 'i' ) ); |
| if( mnId < 10 ) |
| aBuffer.append( sal_Unicode( '0' ) ); |
| aBuffer.append( mnId ); |
| return aBuffer.makeStringAndClear(); |
| } |
| return OUString(); |
| } |
| |
| ControlModelRef VbaSiteModel::createControlModel( const AxClassTable& rClassTable ) const |
| { |
| ControlModelRef xCtrlModel; |
| |
| sal_Int32 nTypeIndex = static_cast< sal_Int32 >( mnClassIdOrCache & VBA_SITE_INDEXMASK ); |
| if( !getFlag( mnClassIdOrCache, VBA_SITE_CLASSIDINDEX ) ) |
| { |
| switch( nTypeIndex ) |
| { |
| case VBA_SITE_COMMANDBUTTON: xCtrlModel.reset( new AxCommandButtonModel ); break; |
| case VBA_SITE_LABEL: xCtrlModel.reset( new AxLabelModel ); break; |
| case VBA_SITE_IMAGE: xCtrlModel.reset( new AxImageModel ); break; |
| case VBA_SITE_TOGGLEBUTTON: xCtrlModel.reset( new AxToggleButtonModel ); break; |
| case VBA_SITE_CHECKBOX: xCtrlModel.reset( new AxCheckBoxModel ); break; |
| case VBA_SITE_OPTIONBUTTON: xCtrlModel.reset( new AxOptionButtonModel ); break; |
| case VBA_SITE_TEXTBOX: xCtrlModel.reset( new AxTextBoxModel ); break; |
| case VBA_SITE_LISTBOX: xCtrlModel.reset( new AxListBoxModel ); break; |
| case VBA_SITE_COMBOBOX: xCtrlModel.reset( new AxComboBoxModel ); break; |
| case VBA_SITE_SPINBUTTON: /*xCtrlModel.reset( new AxSpinButtonModel );*/ break; // not supported (?) |
| case VBA_SITE_SCROLLBAR: xCtrlModel.reset( new AxScrollBarModel ); break; |
| case VBA_SITE_TABSTRIP: break; // not supported |
| case VBA_SITE_FRAME: xCtrlModel.reset( new AxFrameModel ); break; |
| case VBA_SITE_MULTIPAGE: break; // not supported |
| case VBA_SITE_FORM: break; // not supported |
| default: OSL_ENSURE( false, "VbaSiteModel::createControlModel - unknown type index" ); |
| } |
| } |
| else |
| { |
| const OUString* pGuid = ContainerHelper::getVectorElement( rClassTable, nTypeIndex ); |
| OSL_ENSURE( pGuid, "VbaSiteModel::createControlModel - invalid class table index" ); |
| if( pGuid ) |
| { |
| if( pGuid->equalsAscii( COMCTL_GUID_SCROLLBAR_60 ) ) |
| xCtrlModel.reset( new ComCtlScrollBarModel( 6 ) ); |
| else if( pGuid->equalsAscii( COMCTL_GUID_PROGRESSBAR_50 ) ) |
| xCtrlModel.reset( new ComCtlProgressBarModel( 5 ) ); |
| else if( pGuid->equalsAscii( COMCTL_GUID_PROGRESSBAR_60 ) ) |
| xCtrlModel.reset( new ComCtlProgressBarModel( 6 ) ); |
| } |
| } |
| |
| if( xCtrlModel.get() ) |
| { |
| // user form controls are AWT models |
| xCtrlModel->setAwtModelMode(); |
| |
| // check that container model matches container flag in site data |
| bool bModelIsContainer = dynamic_cast< const AxContainerModelBase* >( xCtrlModel.get() ) != 0; |
| bool bTypeMatch = bModelIsContainer == isContainer(); |
| OSL_ENSURE( bTypeMatch, "VbaSiteModel::createControlModel - container type does not match container flag" ); |
| if( !bTypeMatch ) |
| xCtrlModel.reset(); |
| } |
| return xCtrlModel; |
| } |
| |
| void VbaSiteModel::convertProperties( PropertyMap& rPropMap, |
| const ControlConverter& rConv, ApiControlType eCtrlType, sal_Int32 nCtrlIndex ) const |
| { |
| rPropMap.setProperty( PROP_Name, maName ); |
| rPropMap.setProperty( PROP_Tag, maTag ); |
| |
| if( eCtrlType != API_CONTROL_DIALOG ) |
| { |
| rPropMap.setProperty( PROP_HelpText, maToolTip ); |
| rPropMap.setProperty( PROP_EnableVisible, getFlag( mnFlags, VBA_SITE_VISIBLE ) ); |
| // we need to set the passed control index to make option button groups work |
| if( (0 <= nCtrlIndex) && (nCtrlIndex <= SAL_MAX_INT16) ) |
| rPropMap.setProperty( PROP_TabIndex, static_cast< sal_Int16 >( nCtrlIndex ) ); |
| // progress bar and group box support TabIndex, but not Tabstop... |
| if( (eCtrlType != API_CONTROL_PROGRESSBAR) && (eCtrlType != API_CONTROL_GROUPBOX) && (eCtrlType != API_CONTROL_FRAME) && (eCtrlType != API_CONTROL_PAGE) ) |
| rPropMap.setProperty( PROP_Tabstop, getFlag( mnFlags, VBA_SITE_TABSTOP ) ); |
| rConv.convertPosition( rPropMap, maPos ); |
| } |
| } |
| |
| void VbaSiteModel::bindToSources( const Reference< XControlModel >& rxCtrlModel, const ControlConverter& rConv ) const |
| { |
| rConv.bindToSources( rxCtrlModel, maControlSource, maRowSource ); |
| } |
| |
| // ============================================================================ |
| |
| VbaFormControl::VbaFormControl() |
| { |
| } |
| |
| VbaFormControl::~VbaFormControl() |
| { |
| } |
| |
| void VbaFormControl::importModelOrStorage( BinaryInputStream& rInStrm, StorageBase& rStrg, const AxClassTable& rClassTable ) |
| { |
| if( mxSiteModel.get() ) |
| { |
| if( mxSiteModel->isContainer() ) |
| { |
| StorageRef xSubStrg = rStrg.openSubStorage( mxSiteModel->getSubStorageName(), false ); |
| OSL_ENSURE( xSubStrg.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" ); |
| if( xSubStrg.get() ) |
| importStorage( *xSubStrg, rClassTable ); |
| } |
| else if( !rInStrm.isEof() ) |
| { |
| sal_Int64 nNextStrmPos = rInStrm.tell() + mxSiteModel->getStreamLength(); |
| importControlModel( rInStrm, rClassTable ); |
| rInStrm.seek( nNextStrmPos ); |
| } |
| } |
| } |
| |
| OUString VbaFormControl::getControlName() const |
| { |
| return mxSiteModel.get() ? mxSiteModel->getName() : OUString(); |
| } |
| |
| sal_Int32 VbaFormControl::getControlId() const |
| { |
| return mxSiteModel.get() ? mxSiteModel->getId() : -1; |
| } |
| |
| void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex, |
| const Reference< XNameContainer >& rxParentNC, const ControlConverter& rConv ) const |
| { |
| if( rxParentNC.is() && mxSiteModel.get() && mxCtrlModel.get() ) try |
| { |
| // create the control model |
| OUString aServiceName = mxCtrlModel->getServiceName(); |
| Reference< XMultiServiceFactory > xModelFactory( rxParentNC, UNO_QUERY_THROW ); |
| Reference< XControlModel > xCtrlModel( xModelFactory->createInstance( aServiceName ), UNO_QUERY_THROW ); |
| |
| // convert all properties and embedded controls |
| if( convertProperties( xCtrlModel, rConv, nCtrlIndex ) ) |
| { |
| // insert into parent container |
| const OUString& rCtrlName = mxSiteModel->getName(); |
| OSL_ENSURE( !rxParentNC->hasByName( rCtrlName ), "VbaFormControl::createAndConvert - multiple controls with equal name" ); |
| ContainerHelper::insertByName( rxParentNC, rCtrlName, Any( xCtrlModel ) ); |
| } |
| } |
| catch( Exception& ) |
| { |
| } |
| } |
| |
| // protected ------------------------------------------------------------------ |
| |
| void VbaFormControl::importControlModel( BinaryInputStream& rInStrm, const AxClassTable& rClassTable ) |
| { |
| createControlModel( rClassTable ); |
| if( mxCtrlModel.get() ) |
| mxCtrlModel->importBinaryModel( rInStrm ); |
| } |
| |
| void VbaFormControl::importStorage( StorageBase& rStrg, const AxClassTable& rClassTable ) |
| { |
| createControlModel( rClassTable ); |
| AxContainerModelBase* pContainerModel = dynamic_cast< AxContainerModelBase* >( mxCtrlModel.get() ); |
| OSL_ENSURE( pContainerModel, "VbaFormControl::importStorage - missing container control model" ); |
| if( pContainerModel ) |
| { |
| /* Open the 'f' stream containing the model of this control and a list |
| of site models for all child controls. */ |
| BinaryXInputStream aFStrm( rStrg.openInputStream( CREATE_OUSTRING( "f" ) ), true ); |
| OSL_ENSURE( !aFStrm.isEof(), "VbaFormControl::importStorage - missing 'f' stream" ); |
| |
| /* Read the properties of this container control and the class table |
| (into the maClassTable vector) containing a list of GUIDs for |
| exotic embedded controls. */ |
| if( !aFStrm.isEof() && pContainerModel->importBinaryModel( aFStrm ) && pContainerModel->importClassTable( aFStrm, maClassTable ) ) |
| { |
| /* Read the site models of all embedded controls (this fills the |
| maControls vector). Ignore failure of importSiteModels() but |
| try to import as much controls as possible. */ |
| importEmbeddedSiteModels( aFStrm ); |
| |
| /* Open the 'o' stream containing models of embedded simple |
| controls. Stream may be empty or missing, if this control |
| contains no controls or only container controls. */ |
| BinaryXInputStream aOStrm( rStrg.openInputStream( CREATE_OUSTRING( "o" ) ), true ); |
| |
| /* Iterate over all embedded controls, import model from 'o' |
| stream (for embedded simple controls) or from the substorage |
| (for embedded container controls). */ |
| maControls.forEachMem( &VbaFormControl::importModelOrStorage, |
| ::boost::ref( aOStrm ), ::boost::ref( rStrg ), ::boost::cref( maClassTable ) ); |
| |
| /* Reorder the controls (sorts all option buttons of an option |
| group together), and move all children of all embedded frames |
| (group boxes) to this control (UNO group boxes cannot contain |
| other controls). */ |
| finalizeEmbeddedControls(); |
| } |
| } |
| } |
| |
| bool VbaFormControl::convertProperties( const Reference< XControlModel >& rxCtrlModel, |
| const ControlConverter& rConv, sal_Int32 nCtrlIndex ) const |
| { |
| if( rxCtrlModel.is() && mxSiteModel.get() && mxCtrlModel.get() ) |
| { |
| const OUString& rCtrlName = mxSiteModel->getName(); |
| OSL_ENSURE( rCtrlName.getLength() > 0, "VbaFormControl::convertProperties - control without name" ); |
| if( rCtrlName.getLength() > 0 ) |
| { |
| // convert all properties |
| PropertyMap aPropMap; |
| mxSiteModel->convertProperties( aPropMap, rConv, mxCtrlModel->getControlType(), nCtrlIndex ); |
| mxCtrlModel->convertProperties( aPropMap, rConv ); |
| mxCtrlModel->convertSize( aPropMap, rConv ); |
| PropertySet aPropSet( rxCtrlModel ); |
| aPropSet.setProperties( aPropMap ); |
| |
| // create and convert all embedded controls |
| if( !maControls.empty() ) try |
| { |
| Reference< XNameContainer > xCtrlModelNC( rxCtrlModel, UNO_QUERY_THROW ); |
| /* Call conversion for all controls. Pass vector index as new |
| tab order to make option button groups work correctly. */ |
| maControls.forEachMemWithIndex( &VbaFormControl::createAndConvert, |
| ::boost::cref( xCtrlModelNC ), ::boost::cref( rConv ) ); |
| } |
| catch( Exception& ) |
| { |
| OSL_ENSURE( false, "VbaFormControl::convertProperties - cannot get control container interface" ); |
| } |
| |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // private -------------------------------------------------------------------- |
| |
| void VbaFormControl::createControlModel( const AxClassTable& rClassTable ) |
| { |
| // derived classes may have created their own control model |
| if( !mxCtrlModel && mxSiteModel.get() ) |
| mxCtrlModel = mxSiteModel->createControlModel( rClassTable ); |
| } |
| |
| bool VbaFormControl::importSiteModel( BinaryInputStream& rInStrm ) |
| { |
| mxSiteModel.reset( new VbaSiteModel ); |
| return mxSiteModel->importBinaryModel( rInStrm ); |
| } |
| |
| bool VbaFormControl::importEmbeddedSiteModels( BinaryInputStream& rInStrm ) |
| { |
| sal_uInt64 nAnchorPos = rInStrm.tell(); |
| sal_uInt32 nSiteCount, nSiteDataSize; |
| rInStrm >> nSiteCount >> nSiteDataSize; |
| sal_Int64 nSiteEndPos = rInStrm.tell() + nSiteDataSize; |
| |
| // skip the site info structure |
| sal_uInt32 nSiteIndex = 0; |
| while( !rInStrm.isEof() && (nSiteIndex < nSiteCount) ) |
| { |
| rInStrm.skip( 1 ); // site depth |
| sal_uInt8 nTypeCount = rInStrm.readuInt8(); // 'type-or-count' byte |
| if( getFlag( nTypeCount, VBA_SITEINFO_COUNT ) ) |
| { |
| /* Count flag is set: the 'type-or-count' byte contains the number |
| of controls in the lower bits, the type specifier follows in |
| the next byte. The type specifier should always be 1 according |
| to the specification. */ |
| rInStrm.skip( 1 ); |
| nSiteIndex += (nTypeCount & VBA_SITEINFO_MASK); |
| } |
| else |
| { |
| /* Count flag is not set: the 'type-or-count' byte contains the |
| type specifier of *one* control in the lower bits (this type |
| should be 1, see above). */ |
| ++nSiteIndex; |
| } |
| } |
| // align the stream to 32bit, relative to start of entire site info |
| rInStrm.alignToBlock( 4, nAnchorPos ); |
| |
| // import the site models for all embedded controls |
| maControls.clear(); |
| bool bValid = !rInStrm.isEof(); |
| for( nSiteIndex = 0; bValid && (nSiteIndex < nSiteCount); ++nSiteIndex ) |
| { |
| VbaFormControlRef xControl( new VbaFormControl ); |
| maControls.push_back( xControl ); |
| bValid = xControl->importSiteModel( rInStrm ); |
| } |
| |
| rInStrm.seek( nSiteEndPos ); |
| return bValid; |
| } |
| |
| void VbaFormControl::finalizeEmbeddedControls() |
| { |
| /* This function performs two tasks: |
| |
| 1) Reorder the controls appropriately (sort all option buttons of an |
| option group together to make grouping work). |
| 2) Move all children of all embedded frames (group boxes) to this |
| control (UNO group boxes cannot contain other controls). |
| */ |
| |
| // first, sort all controls by original tab index |
| ::std::sort( maControls.begin(), maControls.end(), &compareByTabIndex ); |
| |
| /* Collect the programmatical names of all embedded controls (needed to be |
| able to set unused names to new dummy controls created below). Also |
| collect the names of all children of embedded frames (group boxes). |
| Luckily, names of controls must be unique in the entire form, not just |
| in the current container. */ |
| VbaControlNamesSet aControlNames; |
| VbaControlNameInserter aInserter( aControlNames ); |
| maControls.forEach( aInserter ); |
| for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt ) |
| if( (*aIt)->mxCtrlModel.get() && ((*aIt)->mxCtrlModel->getControlType() == API_CONTROL_GROUPBOX) ) |
| (*aIt)->maControls.forEach( aInserter ); |
| |
| /* Reprocess the sorted list and collect all option button controls that |
| are part of the same option group (determined by group name). All |
| controls will be stored in a vector of vectors, that collects every |
| option button group in one vector element, and other controls between |
| these option groups (or leading or trailing controls) in other vector |
| elements. If an option button group follows another group, a dummy |
| separator control has to be inserted. */ |
| typedef RefVector< VbaFormControlVector > VbaFormControlVectorVector; |
| VbaFormControlVectorVector aControlGroups; |
| |
| typedef RefMap< OUString, VbaFormControlVector > VbaFormControlVectorMap; |
| VbaFormControlVectorMap aOptionGroups; |
| |
| typedef VbaFormControlVectorMap::mapped_type VbaFormControlVectorRef; |
| bool bLastWasOptionButton = false; |
| for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt ) |
| { |
| VbaFormControlRef xControl = *aIt; |
| const ControlModelBase* pCtrlModel = xControl->mxCtrlModel.get(); |
| |
| if( const AxOptionButtonModel* pOptButtonModel = dynamic_cast< const AxOptionButtonModel* >( pCtrlModel ) ) |
| { |
| // check if a new option group needs to be created |
| const OUString& rGroupName = pOptButtonModel->getGroupName(); |
| VbaFormControlVectorRef& rxOptionGroup = aOptionGroups[ rGroupName ]; |
| if( !rxOptionGroup ) |
| { |
| /* If last control was an option button too, we have two |
| option groups following each other, so a dummy separator |
| control is needed. */ |
| if( bLastWasOptionButton ) |
| { |
| VbaFormControlVectorRef xDummyGroup( new VbaFormControlVector ); |
| aControlGroups.push_back( xDummyGroup ); |
| OUString aName = aControlNames.generateDummyName(); |
| VbaFormControlRef xDummyControl( new VbaDummyFormControl( aName ) ); |
| xDummyGroup->push_back( xDummyControl ); |
| } |
| rxOptionGroup.reset( new VbaFormControlVector ); |
| aControlGroups.push_back( rxOptionGroup ); |
| } |
| /* Append the option button to the control group (which is now |
| referred by the vector aControlGroups and by the map |
| aOptionGroups). */ |
| rxOptionGroup->push_back( xControl ); |
| bLastWasOptionButton = true; |
| } |
| else |
| { |
| // open a new control group, if the last group is an option group |
| if( bLastWasOptionButton || aControlGroups.empty() ) |
| { |
| VbaFormControlVectorRef xControlGroup( new VbaFormControlVector ); |
| aControlGroups.push_back( xControlGroup ); |
| } |
| // append the control to the last control group |
| VbaFormControlVector& rLastGroup = *aControlGroups.back(); |
| rLastGroup.push_back( xControl ); |
| bLastWasOptionButton = false; |
| |
| // if control is a group box, move all its children to this control |
| if( pCtrlModel && (pCtrlModel->getControlType() == API_CONTROL_GROUPBOX) ) |
| { |
| /* Move all embedded controls of the group box relative to the |
| position of the group box. */ |
| xControl->moveEmbeddedToAbsoluteParent(); |
| /* Insert all children of the group box into the last control |
| group (following the group box). */ |
| rLastGroup.insert( rLastGroup.end(), xControl->maControls.begin(), xControl->maControls.end() ); |
| xControl->maControls.clear(); |
| // check if last control of the group box is an option button |
| bLastWasOptionButton = dynamic_cast< const AxOptionButtonModel* >( rLastGroup.back()->mxCtrlModel.get() ) != 0; |
| } |
| } |
| } |
| |
| // flatten the vector of vectors of form controls to a single vector |
| maControls.clear(); |
| for( VbaFormControlVectorVector::iterator aIt = aControlGroups.begin(), aEnd = aControlGroups.end(); aIt != aEnd; ++aIt ) |
| maControls.insert( maControls.end(), (*aIt)->begin(), (*aIt)->end() ); |
| } |
| |
| void VbaFormControl::moveRelative( const AxPairData& rDistance ) |
| { |
| if( mxSiteModel.get() ) |
| mxSiteModel->moveRelative( rDistance ); |
| } |
| |
| void VbaFormControl::moveEmbeddedToAbsoluteParent() |
| { |
| if( mxSiteModel.get() && !maControls.empty() ) |
| { |
| // distance to move is equal to position of this control in its parent |
| AxPairData aDistance = mxSiteModel->getPosition(); |
| |
| /* For group boxes: add half of the font height to Y position (VBA |
| positions relative to frame border line, not to 'top' of frame). */ |
| const AxFontDataModel* pFontModel = dynamic_cast< const AxFontDataModel* >( mxCtrlModel.get() ); |
| if( pFontModel && (pFontModel->getControlType() == API_CONTROL_GROUPBOX) ) |
| { |
| // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm) |
| sal_Int32 nFontHeight = static_cast< sal_Int32 >( pFontModel->getFontHeight() * 2540 / 72 ); |
| aDistance.second += nFontHeight / 2; |
| } |
| |
| // move the embedded controls |
| maControls.forEachMem( &VbaFormControl::moveRelative, ::boost::cref( aDistance ) ); |
| } |
| } |
| |
| /*static*/ bool VbaFormControl::compareByTabIndex( const VbaFormControlRef& rxLeft, const VbaFormControlRef& rxRight ) |
| { |
| // sort controls without model to the end |
| sal_Int32 nLeftTabIndex = rxLeft->mxSiteModel.get() ? rxLeft->mxSiteModel->getTabIndex() : SAL_MAX_INT32; |
| sal_Int32 nRightTabIndex = rxRight->mxSiteModel.get() ? rxRight->mxSiteModel->getTabIndex() : SAL_MAX_INT32; |
| return nLeftTabIndex < nRightTabIndex; |
| } |
| |
| // ============================================================================ |
| |
| namespace { |
| |
| OUString lclGetQuotedString( const OUString& rCodeLine ) |
| { |
| OUStringBuffer aBuffer; |
| sal_Int32 nLen = rCodeLine.getLength(); |
| if( (nLen > 0) && (rCodeLine[ 0 ] == '"') ) |
| { |
| bool bExitLoop = false; |
| for( sal_Int32 nIndex = 1; !bExitLoop && (nIndex < nLen); ++nIndex ) |
| { |
| sal_Unicode cChar = rCodeLine[ nIndex ]; |
| // exit on closing quote char (but check on double quote chars) |
| bExitLoop = (cChar == '"') && ((nIndex + 1 == nLen) || (rCodeLine[ nIndex + 1 ] != '"')); |
| if( !bExitLoop ) |
| { |
| aBuffer.append( cChar ); |
| // skip second quote char |
| if( cChar == '"' ) |
| ++nIndex; |
| } |
| } |
| } |
| return aBuffer.makeStringAndClear(); |
| } |
| |
| bool lclEatWhitespace( OUString& rCodeLine ) |
| { |
| sal_Int32 nIndex = 0; |
| while( (nIndex < rCodeLine.getLength()) && ((rCodeLine[ nIndex ] == ' ') || (rCodeLine[ nIndex ] == '\t')) ) |
| ++nIndex; |
| if( nIndex > 0 ) |
| { |
| rCodeLine = rCodeLine.copy( nIndex ); |
| return true; |
| } |
| return false; |
| } |
| |
| bool lclEatKeyword( OUString& rCodeLine, const OUString& rKeyword ) |
| { |
| if( rCodeLine.matchIgnoreAsciiCase( rKeyword ) ) |
| { |
| rCodeLine = rCodeLine.copy( rKeyword.getLength() ); |
| // success, if code line ends after keyword, or if whitespace follows |
| return (rCodeLine.getLength() == 0) || lclEatWhitespace( rCodeLine ); |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // ---------------------------------------------------------------------------- |
| |
| VbaUserForm::VbaUserForm( const Reference< XComponentContext >& rxContext, |
| const Reference< XModel >& rxDocModel, const GraphicHelper& rGraphicHelper, bool bDefaultColorBgr ) : |
| mxContext( rxContext ), |
| mxDocModel( rxDocModel ), |
| maConverter( rxDocModel, rGraphicHelper, bDefaultColorBgr ) |
| { |
| OSL_ENSURE( mxContext.is(), "VbaUserForm::VbaUserForm - missing component context" ); |
| OSL_ENSURE( mxDocModel.is(), "VbaUserForm::VbaUserForm - missing document model" ); |
| } |
| |
| void VbaUserForm::importForm( const Reference< XNameContainer >& rxDialogLib, |
| StorageBase& rVbaFormStrg, const OUString& rModuleName, rtl_TextEncoding eTextEnc ) |
| { |
| OSL_ENSURE( rxDialogLib.is(), "VbaUserForm::importForm - missing dialog library" ); |
| if( !mxContext.is() || !mxDocModel.is() || !rxDialogLib.is() ) |
| return; |
| |
| // check that the '03VBFrame' stream exists, this is required for forms |
| BinaryXInputStream aInStrm( rVbaFormStrg.openInputStream( CREATE_OUSTRING( "\003VBFrame" ) ), true ); |
| OSL_ENSURE( !aInStrm.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" ); |
| if( aInStrm.isEof() ) |
| return; |
| |
| // scan for the line 'Begin {GUID} <FormName>' |
| TextInputStream aFrameTextStrm( mxContext, aInStrm, eTextEnc ); |
| const OUString aBegin = CREATE_OUSTRING( "Begin" ); |
| OUString aLine; |
| bool bBeginFound = false; |
| while( !bBeginFound && !aFrameTextStrm.isEof() ) |
| { |
| aLine = aFrameTextStrm.readLine().trim(); |
| bBeginFound = lclEatKeyword( aLine, aBegin ); |
| } |
| // check for the specific GUID that represents VBA forms |
| if( !bBeginFound || !lclEatKeyword( aLine, CREATE_OUSTRING( "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) ) ) |
| return; |
| |
| // remaining line is the form name |
| OUString aFormName = aLine.trim(); |
| OSL_ENSURE( aFormName.getLength() > 0, "VbaUserForm::importForm - missing form name" ); |
| OSL_ENSURE( rModuleName.equalsIgnoreAsciiCase( aFormName ), "VbaUserForm::importFrameStream - form and module name mismatch" ); |
| if( aFormName.getLength() == 0 ) |
| aFormName = rModuleName; |
| if( aFormName.getLength() == 0 ) |
| return; |
| mxSiteModel.reset( new VbaSiteModel ); |
| mxSiteModel->importProperty( XML_Name, aFormName ); |
| |
| // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream) |
| mxCtrlModel.reset( new AxUserFormModel ); |
| OUString aKey, aValue; |
| bool bExitLoop = false; |
| while( !bExitLoop && !aFrameTextStrm.isEof() ) |
| { |
| aLine = aFrameTextStrm.readLine().trim(); |
| bExitLoop = aLine.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "End" ) ); |
| if( !bExitLoop && VbaHelper::extractKeyValue( aKey, aValue, aLine ) ) |
| { |
| if( aKey.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "Caption" ) ) ) |
| mxCtrlModel->importProperty( XML_Caption, lclGetQuotedString( aValue ) ); |
| else if( aKey.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "Tag" ) ) ) |
| mxSiteModel->importProperty( XML_Tag, lclGetQuotedString( aValue ) ); |
| } |
| } |
| |
| // use generic container control functionality to import the embedded controls |
| importStorage( rVbaFormStrg, AxClassTable() ); |
| |
| try |
| { |
| // create the dialog model |
| OUString aServiceName = mxCtrlModel->getServiceName(); |
| Reference< XMultiServiceFactory > xFactory( mxContext->getServiceManager(), UNO_QUERY_THROW ); |
| Reference< XControlModel > xDialogModel( xFactory->createInstance( aServiceName ), UNO_QUERY_THROW ); |
| Reference< XNameContainer > xDialogNC( xDialogModel, UNO_QUERY_THROW ); |
| |
| // convert properties and embedded controls |
| if( convertProperties( xDialogModel, maConverter, 0 ) ) |
| { |
| // export the dialog to XML and insert it into the dialog library |
| Reference< XInputStreamProvider > xDialogSource( ::xmlscript::exportDialogModel( xDialogNC, mxContext ), UNO_SET_THROW ); |
| OSL_ENSURE( !rxDialogLib->hasByName( aFormName ), "VbaUserForm::importForm - multiple dialogs with equal name" ); |
| ContainerHelper::insertByName( rxDialogLib, aFormName, Any( xDialogSource ) ); |
| } |
| } |
| catch( Exception& ) |
| { |
| } |
| } |
| |
| // ============================================================================ |
| |
| } // namespace ole |
| } // namespace oox |