xref: /trunk/main/oox/source/ole/vbacontrol.cxx (revision ca5ec200)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 #include "oox/ole/vbacontrol.hxx"
25 
26 #include <algorithm>
27 #include <set>
28 #include <com/sun/star/awt/XControlModel.hpp>
29 #include <com/sun/star/container/XNameContainer.hpp>
30 #include <com/sun/star/io/XInputStreamProvider.hpp>
31 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
32 #include <com/sun/star/uno/XComponentContext.hpp>
33 #include <rtl/ustrbuf.hxx>
34 #include <xmlscript/xmldlg_imexp.hxx>
35 #include "oox/helper/attributelist.hxx"
36 #include "oox/helper/binaryinputstream.hxx"
37 #include "oox/helper/containerhelper.hxx"
38 #include "oox/helper/propertymap.hxx"
39 #include "oox/helper/propertyset.hxx"
40 #include "oox/helper/storagebase.hxx"
41 #include "oox/helper/textinputstream.hxx"
42 #include "oox/ole/vbahelper.hxx"
43 
44 namespace oox {
45 namespace ole {
46 
47 // ============================================================================
48 
49 using namespace ::com::sun::star::awt;
50 using namespace ::com::sun::star::container;
51 using namespace ::com::sun::star::frame;
52 using namespace ::com::sun::star::io;
53 using namespace ::com::sun::star::lang;
54 using namespace ::com::sun::star::uno;
55 
56 using ::rtl::OUString;
57 using ::rtl::OUStringBuffer;
58 
59 // ============================================================================
60 
61 namespace {
62 
63 const sal_uInt16 VBA_SITE_CLASSIDINDEX          = 0x8000;
64 const sal_uInt16 VBA_SITE_INDEXMASK             = 0x7FFF;
65 const sal_uInt16 VBA_SITE_FORM                  = 7;
66 const sal_uInt16 VBA_SITE_IMAGE                 = 12;
67 const sal_uInt16 VBA_SITE_FRAME                 = 14;
68 const sal_uInt16 VBA_SITE_SPINBUTTON            = 16;
69 const sal_uInt16 VBA_SITE_COMMANDBUTTON         = 17;
70 const sal_uInt16 VBA_SITE_TABSTRIP              = 18;
71 const sal_uInt16 VBA_SITE_LABEL                 = 21;
72 const sal_uInt16 VBA_SITE_TEXTBOX               = 23;
73 const sal_uInt16 VBA_SITE_LISTBOX               = 24;
74 const sal_uInt16 VBA_SITE_COMBOBOX              = 25;
75 const sal_uInt16 VBA_SITE_CHECKBOX              = 26;
76 const sal_uInt16 VBA_SITE_OPTIONBUTTON          = 27;
77 const sal_uInt16 VBA_SITE_TOGGLEBUTTON          = 28;
78 const sal_uInt16 VBA_SITE_SCROLLBAR             = 47;
79 const sal_uInt16 VBA_SITE_MULTIPAGE             = 57;
80 const sal_uInt16 VBA_SITE_UNKNOWN               = 0x7FFF;
81 
82 const sal_uInt32 VBA_SITE_TABSTOP               = 0x00000001;
83 const sal_uInt32 VBA_SITE_VISIBLE               = 0x00000002;
84 const sal_uInt32 VBA_SITE_DEFAULTBUTTON         = 0x00000004;
85 const sal_uInt32 VBA_SITE_CANCELBUTTON          = 0x00000008;
86 const sal_uInt32 VBA_SITE_OSTREAM               = 0x00000010;
87 const sal_uInt32 VBA_SITE_DEFFLAGS              = 0x00000033;
88 
89 const sal_uInt8 VBA_SITEINFO_COUNT              = 0x80;
90 const sal_uInt8 VBA_SITEINFO_MASK               = 0x7F;
91 
92 // ----------------------------------------------------------------------------
93 
94 /** Collects names of all controls in a user form or container control. Allows
95     to generate unused names for dummy controls separating option groups.
96  */
97 class VbaControlNamesSet
98 {
99 public:
100     explicit            VbaControlNamesSet();
101 
102     /** Inserts the name of the passed control. */
103     void                insertName( const VbaFormControl& rControl );
104     /** Returns a name that is not contained in this set. */
105     OUString            generateDummyName();
106 
107 private:
108     typedef ::std::set< OUString > OUStringSet;
109     OUStringSet         maCtrlNames;
110     const OUString      maDummyBaseName;
111     sal_Int32           mnIndex;
112 };
113 
VbaControlNamesSet()114 VbaControlNamesSet::VbaControlNamesSet() :
115     maDummyBaseName( CREATE_OUSTRING( "DummyGroupSep" ) ),
116     mnIndex( 0 )
117 {
118 }
119 
insertName(const VbaFormControl & rControl)120 void VbaControlNamesSet::insertName( const VbaFormControl& rControl )
121 {
122     OUString aName = rControl.getControlName();
123     if( aName.getLength() > 0 )
124         maCtrlNames.insert( aName );
125 }
126 
generateDummyName()127 OUString VbaControlNamesSet::generateDummyName()
128 {
129     OUString aCtrlName;
130     do
131     {
132         aCtrlName = OUStringBuffer( maDummyBaseName ).append( ++mnIndex ).makeStringAndClear();
133     }
134     while( maCtrlNames.count( aCtrlName ) > 0 );
135     maCtrlNames.insert( aCtrlName );
136     return aCtrlName;
137 }
138 
139 // ----------------------------------------------------------------------------
140 
141 /** Functor that inserts the name of a control into a VbaControlNamesSet. */
142 struct VbaControlNameInserter
143 {
144 public:
145     VbaControlNamesSet& mrCtrlNames;
VbaControlNameInserteroox::ole::__anoneb805c5d0111::VbaControlNameInserter146     inline explicit     VbaControlNameInserter( VbaControlNamesSet& rCtrlNames ) : mrCtrlNames( rCtrlNames ) {}
operator ()oox::ole::__anoneb805c5d0111::VbaControlNameInserter147     inline void         operator()( const VbaFormControl& rControl ) { mrCtrlNames.insertName( rControl ); }
148 };
149 
150 // ----------------------------------------------------------------------------
151 
152 /** A dummy invisible form control (fixed label without text) that is used to
153     separate two groups of option buttons.
154  */
155 class VbaDummyFormControl : public VbaFormControl
156 {
157 public:
158     explicit            VbaDummyFormControl( const OUString& rName );
159 };
160 
VbaDummyFormControl(const OUString & rName)161 VbaDummyFormControl::VbaDummyFormControl( const OUString& rName )
162 {
163     mxSiteModel.reset( new VbaSiteModel );
164     mxSiteModel->importProperty( XML_Name, rName );
165     mxSiteModel->importProperty( XML_VariousPropertyBits, OUString( sal_Unicode( '0' ) ) );
166 
167     mxCtrlModel.reset( new AxLabelModel );
168     mxCtrlModel->setAwtModelMode();
169     mxCtrlModel->importProperty( XML_Size, CREATE_OUSTRING( "10;10" ) );
170 }
171 
172 } // namespace
173 
174 // ============================================================================
175 
VbaSiteModel()176 VbaSiteModel::VbaSiteModel() :
177     maPos( 0, 0 ),
178     mnId( 0 ),
179     mnHelpContextId( 0 ),
180     mnFlags( VBA_SITE_DEFFLAGS ),
181     mnStreamLen( 0 ),
182     mnTabIndex( -1 ),
183     mnClassIdOrCache( VBA_SITE_UNKNOWN ),
184     mnGroupId( 0 )
185 {
186 }
187 
~VbaSiteModel()188 VbaSiteModel::~VbaSiteModel()
189 {
190 }
191 
importProperty(sal_Int32 nPropId,const OUString & rValue)192 void VbaSiteModel::importProperty( sal_Int32 nPropId, const OUString& rValue )
193 {
194     switch( nPropId )
195     {
196         case XML_Name:                  maName = rValue;                                            break;
197         case XML_Tag:                   maTag = rValue;                                             break;
198         case XML_VariousPropertyBits:   mnFlags = AttributeConversion::decodeUnsigned( rValue );    break;
199     }
200 }
201 
importBinaryModel(BinaryInputStream & rInStrm)202 bool VbaSiteModel::importBinaryModel( BinaryInputStream& rInStrm )
203 {
204     AxBinaryPropertyReader aReader( rInStrm );
205     aReader.readStringProperty( maName );
206     aReader.readStringProperty( maTag );
207     aReader.readIntProperty< sal_Int32 >( mnId );
208     aReader.readIntProperty< sal_Int32 >( mnHelpContextId );
209     aReader.readIntProperty< sal_uInt32 >( mnFlags );
210     aReader.readIntProperty< sal_uInt32 >( mnStreamLen );
211     aReader.readIntProperty< sal_Int16 >( mnTabIndex );
212     aReader.readIntProperty< sal_uInt16 >( mnClassIdOrCache );
213     aReader.readPairProperty( maPos );
214     aReader.readIntProperty< sal_uInt16 >( mnGroupId );
215     aReader.skipUndefinedProperty();
216     aReader.readStringProperty( maToolTip );
217     aReader.skipStringProperty();   // license key
218     aReader.readStringProperty( maControlSource );
219     aReader.readStringProperty( maRowSource );
220     return aReader.finalizeImport();
221 }
222 
moveRelative(const AxPairData & rDistance)223 void VbaSiteModel::moveRelative( const AxPairData& rDistance )
224 {
225     maPos.first += rDistance.first;
226     maPos.second += rDistance.second;
227 }
228 
isVisible() const229 bool VbaSiteModel::isVisible() const
230 {
231     return getFlag( mnFlags, VBA_SITE_VISIBLE );
232 }
233 
isContainer() const234 bool VbaSiteModel::isContainer() const
235 {
236     return !getFlag( mnFlags, VBA_SITE_OSTREAM );
237 }
238 
getStreamLength() const239 sal_uInt32 VbaSiteModel::getStreamLength() const
240 {
241     return isContainer() ? 0 : mnStreamLen;
242 }
243 
getSubStorageName() const244 OUString VbaSiteModel::getSubStorageName() const
245 {
246     if( mnId >= 0 )
247     {
248         OUStringBuffer aBuffer;
249         aBuffer.append( sal_Unicode( 'i' ) );
250         if( mnId < 10 )
251             aBuffer.append( sal_Unicode( '0' ) );
252         aBuffer.append( mnId );
253         return aBuffer.makeStringAndClear();
254     }
255     return OUString();
256 }
257 
createControlModel(const AxClassTable & rClassTable) const258 ControlModelRef VbaSiteModel::createControlModel( const AxClassTable& rClassTable ) const
259 {
260     ControlModelRef xCtrlModel;
261 
262     sal_Int32 nTypeIndex = static_cast< sal_Int32 >( mnClassIdOrCache & VBA_SITE_INDEXMASK );
263     if( !getFlag( mnClassIdOrCache, VBA_SITE_CLASSIDINDEX ) )
264     {
265         switch( nTypeIndex )
266         {
267             case VBA_SITE_COMMANDBUTTON:    xCtrlModel.reset( new AxCommandButtonModel );   break;
268             case VBA_SITE_LABEL:            xCtrlModel.reset( new AxLabelModel );           break;
269             case VBA_SITE_IMAGE:            xCtrlModel.reset( new AxImageModel );           break;
270             case VBA_SITE_TOGGLEBUTTON:     xCtrlModel.reset( new AxToggleButtonModel );    break;
271             case VBA_SITE_CHECKBOX:         xCtrlModel.reset( new AxCheckBoxModel );        break;
272             case VBA_SITE_OPTIONBUTTON:     xCtrlModel.reset( new AxOptionButtonModel );    break;
273             case VBA_SITE_TEXTBOX:          xCtrlModel.reset( new AxTextBoxModel );         break;
274             case VBA_SITE_LISTBOX:          xCtrlModel.reset( new AxListBoxModel );         break;
275             case VBA_SITE_COMBOBOX:         xCtrlModel.reset( new AxComboBoxModel );        break;
276             case VBA_SITE_SPINBUTTON:       /*xCtrlModel.reset( new AxSpinButtonModel );*/  break;  // not supported (?)
277             case VBA_SITE_SCROLLBAR:        xCtrlModel.reset( new AxScrollBarModel );       break;
278             case VBA_SITE_TABSTRIP:                                                         break;  // not supported
279             case VBA_SITE_FRAME:            xCtrlModel.reset( new AxFrameModel );           break;
280             case VBA_SITE_MULTIPAGE:                                                        break;  // not supported
281             case VBA_SITE_FORM:                                                             break;  // not supported
282             default:    OSL_ENSURE( false, "VbaSiteModel::createControlModel - unknown type index" );
283         }
284     }
285     else
286     {
287         const OUString* pGuid = ContainerHelper::getVectorElement( rClassTable, nTypeIndex );
288         OSL_ENSURE( pGuid, "VbaSiteModel::createControlModel - invalid class table index" );
289         if( pGuid )
290         {
291             if( pGuid->equalsAscii( COMCTL_GUID_SCROLLBAR_60 ) )
292                 xCtrlModel.reset( new ComCtlScrollBarModel( 6 ) );
293             else if( pGuid->equalsAscii( COMCTL_GUID_PROGRESSBAR_50 ) )
294                 xCtrlModel.reset( new ComCtlProgressBarModel( 5 ) );
295             else if( pGuid->equalsAscii( COMCTL_GUID_PROGRESSBAR_60 ) )
296                 xCtrlModel.reset( new ComCtlProgressBarModel( 6 ) );
297         }
298     }
299 
300     if( xCtrlModel.get() )
301     {
302         // user form controls are AWT models
303         xCtrlModel->setAwtModelMode();
304 
305         // check that container model matches container flag in site data
306         bool bModelIsContainer = dynamic_cast< const AxContainerModelBase* >( xCtrlModel.get() ) != 0;
307         bool bTypeMatch = bModelIsContainer == isContainer();
308         OSL_ENSURE( bTypeMatch, "VbaSiteModel::createControlModel - container type does not match container flag" );
309         if( !bTypeMatch )
310             xCtrlModel.reset();
311     }
312     return xCtrlModel;
313 }
314 
convertProperties(PropertyMap & rPropMap,const ControlConverter & rConv,ApiControlType eCtrlType,sal_Int32 nCtrlIndex) const315 void VbaSiteModel::convertProperties( PropertyMap& rPropMap,
316         const ControlConverter& rConv, ApiControlType eCtrlType, sal_Int32 nCtrlIndex ) const
317 {
318     rPropMap.setProperty( PROP_Name, maName );
319     rPropMap.setProperty( PROP_Tag, maTag );
320 
321     if( eCtrlType != API_CONTROL_DIALOG )
322     {
323         rPropMap.setProperty( PROP_HelpText, maToolTip );
324         rPropMap.setProperty( PROP_EnableVisible, getFlag( mnFlags, VBA_SITE_VISIBLE ) );
325         // we need to set the passed control index to make option button groups work
326         if( (0 <= nCtrlIndex) && (nCtrlIndex <= SAL_MAX_INT16) )
327             rPropMap.setProperty( PROP_TabIndex, static_cast< sal_Int16 >( nCtrlIndex ) );
328         // progress bar and group box support TabIndex, but not Tabstop...
329         if( (eCtrlType != API_CONTROL_PROGRESSBAR) && (eCtrlType != API_CONTROL_GROUPBOX) && (eCtrlType != API_CONTROL_FRAME) && (eCtrlType != API_CONTROL_PAGE) )
330             rPropMap.setProperty( PROP_Tabstop, getFlag( mnFlags, VBA_SITE_TABSTOP ) );
331         rConv.convertPosition( rPropMap, maPos );
332     }
333 }
334 
bindToSources(const Reference<XControlModel> & rxCtrlModel,const ControlConverter & rConv) const335 void VbaSiteModel::bindToSources( const Reference< XControlModel >& rxCtrlModel, const ControlConverter& rConv ) const
336 {
337     rConv.bindToSources( rxCtrlModel, maControlSource, maRowSource );
338 }
339 
340 // ============================================================================
341 
VbaFormControl()342 VbaFormControl::VbaFormControl()
343 {
344 }
345 
~VbaFormControl()346 VbaFormControl::~VbaFormControl()
347 {
348 }
349 
importModelOrStorage(BinaryInputStream & rInStrm,StorageBase & rStrg,const AxClassTable & rClassTable)350 void VbaFormControl::importModelOrStorage( BinaryInputStream& rInStrm, StorageBase& rStrg, const AxClassTable& rClassTable )
351 {
352     if( mxSiteModel.get() )
353     {
354         if( mxSiteModel->isContainer() )
355         {
356             StorageRef xSubStrg = rStrg.openSubStorage( mxSiteModel->getSubStorageName(), false );
357             OSL_ENSURE( xSubStrg.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
358             if( xSubStrg.get() )
359                 importStorage( *xSubStrg, rClassTable );
360         }
361         else if( !rInStrm.isEof() )
362         {
363             sal_Int64 nNextStrmPos = rInStrm.tell() + mxSiteModel->getStreamLength();
364             importControlModel( rInStrm, rClassTable );
365             rInStrm.seek( nNextStrmPos );
366         }
367     }
368 }
369 
getControlName() const370 OUString VbaFormControl::getControlName() const
371 {
372     return mxSiteModel.get() ? mxSiteModel->getName() : OUString();
373 }
374 
getControlId() const375 sal_Int32 VbaFormControl::getControlId() const
376 {
377     return mxSiteModel.get() ? mxSiteModel->getId() : -1;
378 }
379 
createAndConvert(sal_Int32 nCtrlIndex,const Reference<XNameContainer> & rxParentNC,const ControlConverter & rConv) const380 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex,
381         const Reference< XNameContainer >& rxParentNC, const ControlConverter& rConv ) const
382 {
383     if( rxParentNC.is() && mxSiteModel.get() && mxCtrlModel.get() ) try
384     {
385         // create the control model
386         OUString aServiceName = mxCtrlModel->getServiceName();
387         Reference< XMultiServiceFactory > xModelFactory( rxParentNC, UNO_QUERY_THROW );
388         Reference< XControlModel > xCtrlModel( xModelFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
389 
390         // convert all properties and embedded controls
391         if( convertProperties( xCtrlModel, rConv, nCtrlIndex ) )
392         {
393             // insert into parent container
394             const OUString& rCtrlName = mxSiteModel->getName();
395             OSL_ENSURE( !rxParentNC->hasByName( rCtrlName ), "VbaFormControl::createAndConvert - multiple controls with equal name" );
396             ContainerHelper::insertByName( rxParentNC, rCtrlName, Any( xCtrlModel ) );
397         }
398     }
399     catch( Exception& )
400     {
401     }
402 }
403 
404 // protected ------------------------------------------------------------------
405 
importControlModel(BinaryInputStream & rInStrm,const AxClassTable & rClassTable)406 void VbaFormControl::importControlModel( BinaryInputStream& rInStrm, const AxClassTable& rClassTable )
407 {
408     createControlModel( rClassTable );
409     if( mxCtrlModel.get() )
410         mxCtrlModel->importBinaryModel( rInStrm );
411 }
412 
importStorage(StorageBase & rStrg,const AxClassTable & rClassTable)413 void VbaFormControl::importStorage( StorageBase& rStrg, const AxClassTable& rClassTable )
414 {
415     createControlModel( rClassTable );
416     AxContainerModelBase* pContainerModel = dynamic_cast< AxContainerModelBase* >( mxCtrlModel.get() );
417     OSL_ENSURE( pContainerModel, "VbaFormControl::importStorage - missing container control model" );
418     if( pContainerModel )
419     {
420         /*  Open the 'f' stream containing the model of this control and a list
421             of site models for all child controls. */
422         BinaryXInputStream aFStrm( rStrg.openInputStream( CREATE_OUSTRING( "f" ) ), true );
423         OSL_ENSURE( !aFStrm.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
424 
425         /*  Read the properties of this container control and the class table
426             (into the maClassTable vector) containing a list of GUIDs for
427             exotic embedded controls. */
428         if( !aFStrm.isEof() && pContainerModel->importBinaryModel( aFStrm ) && pContainerModel->importClassTable( aFStrm, maClassTable ) )
429         {
430             /*  Read the site models of all embedded controls (this fills the
431                 maControls vector). Ignore failure of importSiteModels() but
432                 try to import as much controls as possible. */
433             importEmbeddedSiteModels( aFStrm );
434 
435             /*  Open the 'o' stream containing models of embedded simple
436                 controls. Stream may be empty or missing, if this control
437                 contains no controls or only container controls. */
438             BinaryXInputStream aOStrm( rStrg.openInputStream( CREATE_OUSTRING( "o" ) ), true );
439 
440             /*  Iterate over all embedded controls, import model from 'o'
441                 stream (for embedded simple controls) or from the substorage
442                 (for embedded container controls). */
443             maControls.forEachMem( &VbaFormControl::importModelOrStorage,
444                 ::boost::ref( aOStrm ), ::boost::ref( rStrg ), ::boost::cref( maClassTable ) );
445 
446             /*  Reorder the controls (sorts all option buttons of an option
447                 group together), and move all children of all embedded frames
448                 (group boxes) to this control (UNO group boxes cannot contain
449                 other controls). */
450             finalizeEmbeddedControls();
451         }
452     }
453 }
454 
convertProperties(const Reference<XControlModel> & rxCtrlModel,const ControlConverter & rConv,sal_Int32 nCtrlIndex) const455 bool VbaFormControl::convertProperties( const Reference< XControlModel >& rxCtrlModel,
456         const ControlConverter& rConv, sal_Int32 nCtrlIndex ) const
457 {
458     if( rxCtrlModel.is() && mxSiteModel.get() && mxCtrlModel.get() )
459     {
460         const OUString& rCtrlName = mxSiteModel->getName();
461         OSL_ENSURE( rCtrlName.getLength() > 0, "VbaFormControl::convertProperties - control without name" );
462         if( rCtrlName.getLength() > 0 )
463         {
464             // convert all properties
465             PropertyMap aPropMap;
466             mxSiteModel->convertProperties( aPropMap, rConv, mxCtrlModel->getControlType(), nCtrlIndex );
467             mxCtrlModel->convertProperties( aPropMap, rConv );
468             mxCtrlModel->convertSize( aPropMap, rConv );
469             PropertySet aPropSet( rxCtrlModel );
470             aPropSet.setProperties( aPropMap );
471 
472             // create and convert all embedded controls
473             if( !maControls.empty() ) try
474             {
475                 Reference< XNameContainer > xCtrlModelNC( rxCtrlModel, UNO_QUERY_THROW );
476                 /*  Call conversion for all controls. Pass vector index as new
477                     tab order to make option button groups work correctly. */
478                 maControls.forEachMemWithIndex( &VbaFormControl::createAndConvert,
479                     ::boost::cref( xCtrlModelNC ), ::boost::cref( rConv ) );
480             }
481             catch( Exception& )
482             {
483                 OSL_ENSURE( false, "VbaFormControl::convertProperties - cannot get control container interface" );
484             }
485 
486             return true;
487         }
488     }
489     return false;
490 }
491 
492 // private --------------------------------------------------------------------
493 
createControlModel(const AxClassTable & rClassTable)494 void VbaFormControl::createControlModel( const AxClassTable& rClassTable )
495 {
496     // derived classes may have created their own control model
497     if( !mxCtrlModel && mxSiteModel.get() )
498         mxCtrlModel = mxSiteModel->createControlModel( rClassTable );
499 }
500 
importSiteModel(BinaryInputStream & rInStrm)501 bool VbaFormControl::importSiteModel( BinaryInputStream& rInStrm )
502 {
503     mxSiteModel.reset( new VbaSiteModel );
504     return mxSiteModel->importBinaryModel( rInStrm );
505 }
506 
importEmbeddedSiteModels(BinaryInputStream & rInStrm)507 bool VbaFormControl::importEmbeddedSiteModels( BinaryInputStream& rInStrm )
508 {
509     sal_uInt64 nAnchorPos = rInStrm.tell();
510     sal_uInt32 nSiteCount, nSiteDataSize;
511     rInStrm >> nSiteCount >> nSiteDataSize;
512     sal_Int64 nSiteEndPos = rInStrm.tell() + nSiteDataSize;
513 
514     // skip the site info structure
515     sal_uInt32 nSiteIndex = 0;
516     while( !rInStrm.isEof() && (nSiteIndex < nSiteCount) )
517     {
518         rInStrm.skip( 1 ); // site depth
519         sal_uInt8 nTypeCount = rInStrm.readuInt8(); // 'type-or-count' byte
520         if( getFlag( nTypeCount, VBA_SITEINFO_COUNT ) )
521         {
522             /*  Count flag is set: the 'type-or-count' byte contains the number
523                 of controls in the lower bits, the type specifier follows in
524                 the next byte. The type specifier should always be 1 according
525                 to the specification. */
526             rInStrm.skip( 1 );
527             nSiteIndex += (nTypeCount & VBA_SITEINFO_MASK);
528         }
529         else
530         {
531             /*  Count flag is not set: the 'type-or-count' byte contains the
532                 type specifier of *one* control in the lower bits (this type
533                 should be 1, see above). */
534             ++nSiteIndex;
535         }
536     }
537     // align the stream to 32bit, relative to start of entire site info
538     rInStrm.alignToBlock( 4, nAnchorPos );
539 
540     // import the site models for all embedded controls
541     maControls.clear();
542     bool bValid = !rInStrm.isEof();
543     for( nSiteIndex = 0; bValid && (nSiteIndex < nSiteCount); ++nSiteIndex )
544     {
545         VbaFormControlRef xControl( new VbaFormControl );
546         maControls.push_back( xControl );
547         bValid = xControl->importSiteModel( rInStrm );
548     }
549 
550     rInStrm.seek( nSiteEndPos );
551     return bValid;
552 }
553 
finalizeEmbeddedControls()554 void VbaFormControl::finalizeEmbeddedControls()
555 {
556     /*  This function performs two tasks:
557 
558         1)  Reorder the controls appropriately (sort all option buttons of an
559             option group together to make grouping work).
560         2)  Move all children of all embedded frames (group boxes) to this
561             control (UNO group boxes cannot contain other controls).
562      */
563 
564     // first, sort all controls by original tab index
565     ::std::sort( maControls.begin(), maControls.end(), &compareByTabIndex );
566 
567     /*  Collect the programmatical names of all embedded controls (needed to be
568         able to set unused names to new dummy controls created below). Also
569         collect the names of all children of embedded frames (group boxes).
570         Luckily, names of controls must be unique in the entire form, not just
571         in the current container. */
572     VbaControlNamesSet aControlNames;
573     VbaControlNameInserter aInserter( aControlNames );
574     maControls.forEach( aInserter );
575     for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
576         if( (*aIt)->mxCtrlModel.get() && ((*aIt)->mxCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
577             (*aIt)->maControls.forEach( aInserter );
578 
579     /*  Reprocess the sorted list and collect all option button controls that
580         are part of the same option group (determined by group name). All
581         controls will be stored in a vector of vectors, that collects every
582         option button group in one vector element, and other controls between
583         these option groups (or leading or trailing controls) in other vector
584         elements. If an option button group follows another group, a dummy
585         separator control has to be inserted. */
586     typedef RefVector< VbaFormControlVector > VbaFormControlVectorVector;
587     VbaFormControlVectorVector aControlGroups;
588 
589     typedef RefMap< OUString, VbaFormControlVector > VbaFormControlVectorMap;
590     VbaFormControlVectorMap aOptionGroups;
591 
592     typedef VbaFormControlVectorMap::mapped_type VbaFormControlVectorRef;
593     bool bLastWasOptionButton = false;
594     for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
595     {
596         VbaFormControlRef xControl = *aIt;
597         const ControlModelBase* pCtrlModel = xControl->mxCtrlModel.get();
598 
599         if( const AxOptionButtonModel* pOptButtonModel = dynamic_cast< const AxOptionButtonModel* >( pCtrlModel ) )
600         {
601             // check if a new option group needs to be created
602             const OUString& rGroupName = pOptButtonModel->getGroupName();
603             VbaFormControlVectorRef& rxOptionGroup = aOptionGroups[ rGroupName ];
604             if( !rxOptionGroup )
605             {
606                 /*  If last control was an option button too, we have two
607                     option groups following each other, so a dummy separator
608                     control is needed. */
609                 if( bLastWasOptionButton )
610                 {
611                     VbaFormControlVectorRef xDummyGroup( new VbaFormControlVector );
612                     aControlGroups.push_back( xDummyGroup );
613                     OUString aName = aControlNames.generateDummyName();
614                     VbaFormControlRef xDummyControl( new VbaDummyFormControl( aName ) );
615                     xDummyGroup->push_back( xDummyControl );
616                 }
617                 rxOptionGroup.reset( new VbaFormControlVector );
618                 aControlGroups.push_back( rxOptionGroup );
619             }
620             /*  Append the option button to the control group (which is now
621                 referred by the vector aControlGroups and by the map
622                 aOptionGroups). */
623             rxOptionGroup->push_back( xControl );
624             bLastWasOptionButton = true;
625         }
626         else
627         {
628             // open a new control group, if the last group is an option group
629             if( bLastWasOptionButton || aControlGroups.empty() )
630             {
631                 VbaFormControlVectorRef xControlGroup( new VbaFormControlVector );
632                 aControlGroups.push_back( xControlGroup );
633             }
634             // append the control to the last control group
635             VbaFormControlVector& rLastGroup = *aControlGroups.back();
636             rLastGroup.push_back( xControl );
637             bLastWasOptionButton = false;
638 
639             // if control is a group box, move all its children to this control
640             if( pCtrlModel && (pCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
641             {
642                 /*  Move all embedded controls of the group box relative to the
643                     position of the group box. */
644                 xControl->moveEmbeddedToAbsoluteParent();
645                 /*  Insert all children of the group box into the last control
646                     group (following the group box). */
647                 rLastGroup.insert( rLastGroup.end(), xControl->maControls.begin(), xControl->maControls.end() );
648                 xControl->maControls.clear();
649                 // check if last control of the group box is an option button
650                 bLastWasOptionButton = dynamic_cast< const AxOptionButtonModel* >( rLastGroup.back()->mxCtrlModel.get() ) != 0;
651             }
652         }
653     }
654 
655     // flatten the vector of vectors of form controls to a single vector
656     maControls.clear();
657     for( VbaFormControlVectorVector::iterator aIt = aControlGroups.begin(), aEnd = aControlGroups.end(); aIt != aEnd; ++aIt )
658         maControls.insert( maControls.end(), (*aIt)->begin(), (*aIt)->end() );
659 }
660 
moveRelative(const AxPairData & rDistance)661 void VbaFormControl::moveRelative( const AxPairData& rDistance )
662 {
663     if( mxSiteModel.get() )
664         mxSiteModel->moveRelative( rDistance );
665 }
666 
moveEmbeddedToAbsoluteParent()667 void VbaFormControl::moveEmbeddedToAbsoluteParent()
668 {
669     if( mxSiteModel.get() && !maControls.empty() )
670     {
671         // distance to move is equal to position of this control in its parent
672         AxPairData aDistance = mxSiteModel->getPosition();
673 
674         /*  For group boxes: add half of the font height to Y position (VBA
675             positions relative to frame border line, not to 'top' of frame). */
676         const AxFontDataModel* pFontModel = dynamic_cast< const AxFontDataModel* >( mxCtrlModel.get() );
677         if( pFontModel && (pFontModel->getControlType() == API_CONTROL_GROUPBOX) )
678         {
679             // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm)
680             sal_Int32 nFontHeight = static_cast< sal_Int32 >( pFontModel->getFontHeight() * 2540 / 72 );
681             aDistance.second += nFontHeight / 2;
682         }
683 
684         // move the embedded controls
685         maControls.forEachMem( &VbaFormControl::moveRelative, ::boost::cref( aDistance ) );
686     }
687 }
688 
compareByTabIndex(const VbaFormControlRef & rxLeft,const VbaFormControlRef & rxRight)689 /*static*/ bool VbaFormControl::compareByTabIndex( const VbaFormControlRef& rxLeft, const VbaFormControlRef& rxRight )
690 {
691     // sort controls without model to the end
692     sal_Int32 nLeftTabIndex = rxLeft->mxSiteModel.get() ? rxLeft->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
693     sal_Int32 nRightTabIndex = rxRight->mxSiteModel.get() ? rxRight->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
694     return nLeftTabIndex < nRightTabIndex;
695 }
696 
697 // ============================================================================
698 
699 namespace {
700 
lclGetQuotedString(const OUString & rCodeLine)701 OUString lclGetQuotedString( const OUString& rCodeLine )
702 {
703     OUStringBuffer aBuffer;
704     sal_Int32 nLen = rCodeLine.getLength();
705     if( (nLen > 0) && (rCodeLine[ 0 ] == '"') )
706     {
707         bool bExitLoop = false;
708         for( sal_Int32 nIndex = 1; !bExitLoop && (nIndex < nLen); ++nIndex )
709         {
710             sal_Unicode cChar = rCodeLine[ nIndex ];
711             // exit on closing quote char (but check on double quote chars)
712             bExitLoop = (cChar == '"') && ((nIndex + 1 == nLen) || (rCodeLine[ nIndex + 1 ] != '"'));
713             if( !bExitLoop )
714             {
715                 aBuffer.append( cChar );
716                 // skip second quote char
717                 if( cChar == '"' )
718                     ++nIndex;
719             }
720         }
721     }
722     return aBuffer.makeStringAndClear();
723 }
724 
lclEatWhitespace(OUString & rCodeLine)725 bool lclEatWhitespace( OUString& rCodeLine )
726 {
727     sal_Int32 nIndex = 0;
728     while( (nIndex < rCodeLine.getLength()) && ((rCodeLine[ nIndex ] == ' ') || (rCodeLine[ nIndex ] == '\t')) )
729         ++nIndex;
730     if( nIndex > 0 )
731     {
732         rCodeLine = rCodeLine.copy( nIndex );
733         return true;
734     }
735     return false;
736 }
737 
lclEatKeyword(OUString & rCodeLine,const OUString & rKeyword)738 bool lclEatKeyword( OUString& rCodeLine, const OUString& rKeyword )
739 {
740     if( rCodeLine.matchIgnoreAsciiCase( rKeyword ) )
741     {
742         rCodeLine = rCodeLine.copy( rKeyword.getLength() );
743         // success, if code line ends after keyword, or if whitespace follows
744         return (rCodeLine.getLength() == 0) || lclEatWhitespace( rCodeLine );
745     }
746     return false;
747 }
748 
749 } // namespace
750 
751 // ----------------------------------------------------------------------------
752 
VbaUserForm(const Reference<XComponentContext> & rxContext,const Reference<XModel> & rxDocModel,const GraphicHelper & rGraphicHelper,bool bDefaultColorBgr)753 VbaUserForm::VbaUserForm( const Reference< XComponentContext >& rxContext,
754         const Reference< XModel >& rxDocModel, const GraphicHelper& rGraphicHelper, bool bDefaultColorBgr ) :
755     mxContext( rxContext ),
756     mxDocModel( rxDocModel ),
757     maConverter( rxDocModel, rGraphicHelper, bDefaultColorBgr )
758 {
759     OSL_ENSURE( mxContext.is(), "VbaUserForm::VbaUserForm - missing component context" );
760     OSL_ENSURE( mxDocModel.is(), "VbaUserForm::VbaUserForm - missing document model" );
761 }
762 
importForm(const Reference<XNameContainer> & rxDialogLib,StorageBase & rVbaFormStrg,const OUString & rModuleName,rtl_TextEncoding eTextEnc)763 void VbaUserForm::importForm( const Reference< XNameContainer >& rxDialogLib,
764         StorageBase& rVbaFormStrg, const OUString& rModuleName, rtl_TextEncoding eTextEnc )
765 {
766     OSL_ENSURE( rxDialogLib.is(), "VbaUserForm::importForm - missing dialog library" );
767     if( !mxContext.is() || !mxDocModel.is() || !rxDialogLib.is() )
768         return;
769 
770     // check that the '03VBFrame' stream exists, this is required for forms
771     BinaryXInputStream aInStrm( rVbaFormStrg.openInputStream( CREATE_OUSTRING( "\003VBFrame" ) ), true );
772     OSL_ENSURE( !aInStrm.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
773     if( aInStrm.isEof() )
774         return;
775 
776     // scan for the line 'Begin {GUID} <FormName>'
777     TextInputStream aFrameTextStrm( mxContext, aInStrm, eTextEnc );
778     const OUString aBegin = CREATE_OUSTRING( "Begin" );
779     OUString aLine;
780     bool bBeginFound = false;
781     while( !bBeginFound && !aFrameTextStrm.isEof() )
782     {
783         aLine = aFrameTextStrm.readLine().trim();
784         bBeginFound = lclEatKeyword( aLine, aBegin );
785     }
786     // check for the specific GUID that represents VBA forms
787     if( !bBeginFound || !lclEatKeyword( aLine, CREATE_OUSTRING( "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) ) )
788         return;
789 
790     // remaining line is the form name
791     OUString aFormName = aLine.trim();
792     OSL_ENSURE( aFormName.getLength() > 0, "VbaUserForm::importForm - missing form name" );
793     OSL_ENSURE( rModuleName.equalsIgnoreAsciiCase( aFormName ), "VbaUserForm::importFrameStream - form and module name mismatch" );
794     if( aFormName.getLength() == 0 )
795         aFormName = rModuleName;
796     if( aFormName.getLength() == 0 )
797         return;
798     mxSiteModel.reset( new VbaSiteModel );
799     mxSiteModel->importProperty( XML_Name, aFormName );
800 
801     // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
802     mxCtrlModel.reset( new AxUserFormModel );
803     OUString aKey, aValue;
804     bool bExitLoop = false;
805     while( !bExitLoop && !aFrameTextStrm.isEof() )
806     {
807         aLine = aFrameTextStrm.readLine().trim();
808         bExitLoop = aLine.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "End" ) );
809         if( !bExitLoop && VbaHelper::extractKeyValue( aKey, aValue, aLine ) )
810         {
811             if( aKey.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "Caption" ) ) )
812                 mxCtrlModel->importProperty( XML_Caption, lclGetQuotedString( aValue ) );
813             else if( aKey.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "Tag" ) ) )
814                 mxSiteModel->importProperty( XML_Tag, lclGetQuotedString( aValue ) );
815         }
816     }
817 
818     // use generic container control functionality to import the embedded controls
819     importStorage( rVbaFormStrg, AxClassTable() );
820 
821     try
822     {
823         // create the dialog model
824         OUString aServiceName = mxCtrlModel->getServiceName();
825         Reference< XMultiServiceFactory > xFactory( mxContext->getServiceManager(), UNO_QUERY_THROW );
826         Reference< XControlModel > xDialogModel( xFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
827         Reference< XNameContainer > xDialogNC( xDialogModel, UNO_QUERY_THROW );
828 
829         // convert properties and embedded controls
830         if( convertProperties( xDialogModel, maConverter, 0 ) )
831         {
832             // export the dialog to XML and insert it into the dialog library
833             Reference< XInputStreamProvider > xDialogSource( ::xmlscript::exportDialogModel( xDialogNC, mxContext ), UNO_SET_THROW );
834             OSL_ENSURE( !rxDialogLib->hasByName( aFormName ), "VbaUserForm::importForm - multiple dialogs with equal name" );
835             ContainerHelper::insertByName( rxDialogLib, aFormName, Any( xDialogSource ) );
836         }
837     }
838     catch( Exception& )
839     {
840     }
841 }
842 
843 // ============================================================================
844 
845 } // namespace ole
846 } // namespace oox
847