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