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