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 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_sc.hxx" 26 #ifdef _MSC_VER 27 #pragma optimize("",off) 28 #endif 29 30 //------------------------------------------------------------------ 31 32 33 34 // INCLUDE --------------------------------------------------------- 35 36 #include <sfx2/sfxsids.hrc> 37 #include <sfx2/app.hxx> 38 #include <svl/itemset.hxx> 39 #include <svl/stritem.hxx> 40 #include <sfx2/docfile.hxx> 41 #include <sfx2/docfilt.hxx> 42 #include <sfx2/fcontnr.hxx> 43 #include <sfx2/linkmgr.hxx> 44 #include <tools/urlobj.hxx> 45 #include <unotools/transliterationwrapper.hxx> 46 47 #include "tablink.hxx" 48 49 #include "scextopt.hxx" 50 #include "table.hxx" 51 #include "document.hxx" 52 #include "docsh.hxx" 53 #include "globstr.hrc" 54 #include "undoblk.hxx" 55 #include "undotab.hxx" 56 #include "global.hxx" 57 #include "hints.hxx" 58 #include "cell.hxx" 59 #include "dociter.hxx" 60 #include "formula/opcode.hxx" 61 62 struct TableLink_Impl 63 { 64 ScDocShell* m_pDocSh; 65 Window* m_pOldParent; 66 Link m_aEndEditLink; 67 68 TableLink_Impl() : m_pDocSh( NULL ), m_pOldParent( NULL ) {} 69 }; 70 71 TYPEINIT1(ScTableLink, ::sfx2::SvBaseLink); 72 73 //------------------------------------------------------------------------ 74 75 ScTableLink::ScTableLink(ScDocShell* pDocSh, const String& rFile, 76 const String& rFilter, const String& rOpt, 77 sal_uLong nRefresh ): 78 ::sfx2::SvBaseLink(sfx2::LINKUPDATE_ONCALL,FORMAT_FILE), 79 ScRefreshTimer( nRefresh ), 80 pImpl( new TableLink_Impl ), 81 aFileName(rFile), 82 aFilterName(rFilter), 83 aOptions(rOpt), 84 bInCreate( sal_False ), 85 bInEdit( sal_False ), 86 bAddUndo( sal_True ), 87 bDoPaint( sal_True ) 88 { 89 pImpl->m_pDocSh = pDocSh; 90 } 91 92 ScTableLink::ScTableLink(SfxObjectShell* pShell, const String& rFile, 93 const String& rFilter, const String& rOpt, 94 sal_uLong nRefresh ): 95 ::sfx2::SvBaseLink(sfx2::LINKUPDATE_ONCALL,FORMAT_FILE), 96 ScRefreshTimer( nRefresh ), 97 pImpl( new TableLink_Impl ), 98 aFileName(rFile), 99 aFilterName(rFilter), 100 aOptions(rOpt), 101 bInCreate( sal_False ), 102 bInEdit( sal_False ), 103 bAddUndo( sal_True ), 104 bDoPaint( sal_True ) 105 { 106 pImpl->m_pDocSh = static_cast< ScDocShell* >( pShell ); 107 SetRefreshHandler( LINK( this, ScTableLink, RefreshHdl ) ); 108 SetRefreshControl( pImpl->m_pDocSh->GetDocument()->GetRefreshTimerControlAddress() ); 109 } 110 111 __EXPORT ScTableLink::~ScTableLink() 112 { 113 // Verbindung aufheben 114 115 StopRefreshTimer(); 116 String aEmpty; 117 ScDocument* pDoc = pImpl->m_pDocSh->GetDocument(); 118 SCTAB nCount = pDoc->GetTableCount(); 119 for (SCTAB nTab=0; nTab<nCount; nTab++) 120 if (pDoc->IsLinked(nTab) && pDoc->GetLinkDoc(nTab)==aFileName) 121 pDoc->SetLink( nTab, SC_LINK_NONE, aEmpty, aEmpty, aEmpty, aEmpty, 0 ); 122 delete pImpl; 123 } 124 125 void __EXPORT ScTableLink::Edit( Window* pParent, const Link& rEndEditHdl ) 126 { 127 // DefModalDialogParent setzen, weil evtl. aus der DocShell beim ConvertFrom 128 // ein Optionen-Dialog kommt... 129 130 pImpl->m_aEndEditLink = rEndEditHdl; 131 pImpl->m_pOldParent = Application::GetDefDialogParent(); 132 if (pParent) 133 Application::SetDefDialogParent(pParent); 134 135 bInEdit = sal_True; 136 SvBaseLink::Edit( pParent, LINK( this, ScTableLink, TableEndEditHdl ) ); 137 } 138 139 void __EXPORT ScTableLink::DataChanged( const String&, 140 const ::com::sun::star::uno::Any& ) 141 { 142 sfx2::LinkManager* pLinkManager=pImpl->m_pDocSh->GetDocument()->GetLinkManager(); 143 if (pLinkManager!=NULL) 144 { 145 String aFile; 146 String aFilter; 147 pLinkManager->GetDisplayNames( this,0,&aFile,NULL,&aFilter); 148 149 // the file dialog returns the filter name with the application prefix 150 // -> remove prefix 151 ScDocumentLoader::RemoveAppPrefix( aFilter ); 152 153 if (!bInCreate) 154 Refresh( aFile, aFilter, NULL, GetRefreshDelay() ); // don't load twice 155 } 156 } 157 158 void __EXPORT ScTableLink::Closed() 159 { 160 // Verknuepfung loeschen: Undo 161 ScDocument* pDoc = pImpl->m_pDocSh->GetDocument(); 162 sal_Bool bUndo (pDoc->IsUndoEnabled()); 163 164 if (bAddUndo && bUndo) 165 { 166 pImpl->m_pDocSh->GetUndoManager()->AddUndoAction( 167 new ScUndoRemoveLink( pImpl->m_pDocSh, aFileName ) ); 168 169 bAddUndo = sal_False; // nur einmal 170 } 171 172 // Verbindung wird im dtor aufgehoben 173 174 SvBaseLink::Closed(); 175 } 176 177 sal_Bool ScTableLink::IsUsed() const 178 { 179 return pImpl->m_pDocSh->GetDocument()->HasLink( aFileName, aFilterName, aOptions ); 180 } 181 182 sal_Bool ScTableLink::Refresh(const String& rNewFile, const String& rNewFilter, 183 const String* pNewOptions, sal_uLong nNewRefresh ) 184 { 185 // Dokument laden 186 187 if (!rNewFile.Len() || !rNewFilter.Len()) 188 return sal_False; 189 190 String aNewUrl( ScGlobal::GetAbsDocName( rNewFile, pImpl->m_pDocSh ) ); 191 sal_Bool bNewUrlName = (aNewUrl != aFileName); 192 193 const SfxFilter* pFilter = pImpl->m_pDocSh->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter); 194 if (!pFilter) 195 return sal_False; 196 197 ScDocument* pDoc = pImpl->m_pDocSh->GetDocument(); 198 pDoc->SetInLinkUpdate( sal_True ); 199 200 sal_Bool bUndo(pDoc->IsUndoEnabled()); 201 202 // wenn neuer Filter ausgewaehlt wurde, Optionen vergessen 203 if ( rNewFilter != aFilterName ) 204 aOptions.Erase(); 205 if ( pNewOptions ) // Optionen hart angegeben? 206 aOptions = *pNewOptions; 207 208 // ItemSet immer anlegen, damit die DocShell die Optionen setzen kann 209 SfxItemSet* pSet = new SfxAllItemSet( SFX_APP()->GetPool() ); 210 if ( aOptions.Len() ) 211 pSet->Put( SfxStringItem( SID_FILE_FILTEROPTIONS, aOptions ) ); 212 213 SfxMedium* pMed = new SfxMedium(aNewUrl, STREAM_STD_READ, sal_False, pFilter, pSet); 214 215 if ( bInEdit ) // only if using the edit dialog, 216 pMed->UseInteractionHandler( sal_True ); // enable the filter options dialog 217 218 // aRef->DoClose() will be called explicitly, but it is still more safe to use SfxObjectShellLock here 219 ScDocShell* pSrcShell = new ScDocShell(SFX_CREATE_MODE_INTERNAL); 220 SfxObjectShellLock aRef = pSrcShell; 221 pSrcShell->DoLoad(pMed); 222 223 // Optionen koennten gesetzt worden sein 224 String aNewOpt = ScDocumentLoader::GetOptions(*pMed); 225 if (!aNewOpt.Len()) 226 aNewOpt = aOptions; 227 228 // Undo... 229 230 ScDocument* pUndoDoc = NULL; 231 sal_Bool bFirst = sal_True; 232 if (bAddUndo && bUndo) 233 pUndoDoc = new ScDocument( SCDOCMODE_UNDO ); 234 235 // Tabellen kopieren 236 237 ScDocShellModificator aModificator( *pImpl->m_pDocSh ); 238 239 sal_Bool bNotFound = sal_False; 240 ScDocument* pSrcDoc = pSrcShell->GetDocument(); 241 242 // #74835# from text filters that don't set the table name, 243 // use the one table regardless of link table name 244 sal_Bool bAutoTab = (pSrcDoc->GetTableCount() == 1) && 245 ScDocShell::HasAutomaticTableName( rNewFilter ); 246 247 SCTAB nCount = pDoc->GetTableCount(); 248 for (SCTAB nTab=0; nTab<nCount; nTab++) 249 { 250 sal_uInt8 nMode = pDoc->GetLinkMode(nTab); 251 if (nMode && pDoc->GetLinkDoc(nTab)==aFileName) 252 { 253 String aTabName = pDoc->GetLinkTab(nTab); 254 255 // Undo 256 257 if (bAddUndo && bUndo) 258 { 259 if (bFirst) 260 pUndoDoc->InitUndo( pDoc, nTab, nTab, sal_True, sal_True ); 261 else 262 pUndoDoc->AddUndoTab( nTab, nTab, sal_True, sal_True ); 263 bFirst = sal_False; 264 ScRange aRange(0,0,nTab,MAXCOL,MAXROW,nTab); 265 pDoc->CopyToDocument(aRange, IDF_ALL, sal_False, pUndoDoc); 266 pUndoDoc->TransferDrawPage( pDoc, nTab, nTab ); 267 pUndoDoc->SetLink( nTab, nMode, aFileName, aFilterName, 268 aOptions, aTabName, GetRefreshDelay() ); 269 } 270 271 // Tabellenname einer ExtDocRef anpassen 272 273 if ( bNewUrlName && nMode == SC_LINK_VALUE ) 274 { 275 String aName; 276 pDoc->GetName( nTab, aName ); 277 if ( ScGlobal::GetpTransliteration()->isEqual( 278 ScGlobal::GetDocTabName( aFileName, aTabName ), aName ) ) 279 { 280 pDoc->RenameTab( nTab, 281 ScGlobal::GetDocTabName( aNewUrl, aTabName ), 282 sal_False, sal_True ); // kein RefUpdate, kein ValidTabName 283 } 284 } 285 286 // kopieren 287 288 SCTAB nSrcTab = 0; 289 bool bFound = false; 290 /* #i71497# check if external document is loaded successfully, 291 otherwise we may find the empty default sheet "Sheet1" in 292 pSrcDoc, even if the document does not exist. */ 293 if( pMed->GetError() == 0 ) 294 { 295 // no sheet name -> use first sheet 296 if ( aTabName.Len() && !bAutoTab ) 297 bFound = pSrcDoc->GetTable( aTabName, nSrcTab ); 298 else 299 bFound = true; 300 } 301 302 if (bFound) 303 pDoc->TransferTab( pSrcDoc, nSrcTab, nTab, sal_False, // nicht neu einfuegen 304 (nMode == SC_LINK_VALUE) ); // nur Werte? 305 else 306 { 307 pDoc->DeleteAreaTab( 0,0,MAXCOL,MAXROW, nTab, IDF_ALL ); 308 309 bool bShowError = true; 310 if ( nMode == SC_LINK_VALUE ) 311 { 312 // #139464# Value link (used with external references in formulas): 313 // Look for formulas that reference the sheet, and put errors in the referenced cells. 314 315 ScRangeList aErrorCells; // cells on the linked sheets that need error values 316 317 ScCellIterator aCellIter( pDoc, 0,0,0, MAXCOL,MAXROW,MAXTAB ); // all sheets 318 ScBaseCell* pCell = aCellIter.GetFirst(); 319 while (pCell) 320 { 321 if (pCell->GetCellType() == CELLTYPE_FORMULA) 322 { 323 ScFormulaCell* pFCell = static_cast<ScFormulaCell*>(pCell); 324 325 ScDetectiveRefIter aRefIter( pFCell ); 326 ScRange aRefRange; 327 while ( aRefIter.GetNextRef( aRefRange ) ) 328 { 329 if ( aRefRange.aStart.Tab() <= nTab && aRefRange.aEnd.Tab() >= nTab ) 330 { 331 // use first cell of range references (don't fill potentially large ranges) 332 333 aErrorCells.Join( ScRange( aRefRange.aStart ) ); 334 } 335 } 336 } 337 pCell = aCellIter.GetNext(); 338 } 339 340 sal_uLong nRanges = aErrorCells.Count(); 341 if ( nRanges ) // found any? 342 { 343 ScTokenArray aTokenArr; 344 aTokenArr.AddOpCode( ocNotAvail ); 345 aTokenArr.AddOpCode( ocOpen ); 346 aTokenArr.AddOpCode( ocClose ); 347 aTokenArr.AddOpCode( ocStop ); 348 349 for (sal_uLong nPos=0; nPos<nRanges; nPos++) 350 { 351 const ScRange* pRange = aErrorCells.GetObject(nPos); 352 SCCOL nStartCol = pRange->aStart.Col(); 353 SCROW nStartRow = pRange->aStart.Row(); 354 SCCOL nEndCol = pRange->aEnd.Col(); 355 SCROW nEndRow = pRange->aEnd.Row(); 356 for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++) 357 for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++) 358 { 359 ScAddress aDestPos( nCol, nRow, nTab ); 360 ScFormulaCell* pNewCell = new ScFormulaCell( pDoc, aDestPos, &aTokenArr ); 361 pDoc->PutCell( aDestPos, pNewCell ); 362 } 363 } 364 365 bShowError = false; 366 } 367 // if no references were found, insert error message (don't leave the sheet empty) 368 } 369 370 if ( bShowError ) 371 { 372 // Normal link or no references: put error message on sheet. 373 374 pDoc->SetString( 0,0,nTab, ScGlobal::GetRscString(STR_LINKERROR) ); 375 pDoc->SetString( 0,1,nTab, ScGlobal::GetRscString(STR_LINKERRORFILE) ); 376 pDoc->SetString( 1,1,nTab, aNewUrl ); 377 pDoc->SetString( 0,2,nTab, ScGlobal::GetRscString(STR_LINKERRORTAB) ); 378 pDoc->SetString( 1,2,nTab, aTabName ); 379 } 380 381 bNotFound = sal_True; 382 } 383 384 if ( bNewUrlName || rNewFilter != aFilterName || 385 aNewOpt != aOptions || pNewOptions || 386 nNewRefresh != GetRefreshDelay() ) 387 pDoc->SetLink( nTab, nMode, aNewUrl, rNewFilter, aNewOpt, 388 aTabName, nNewRefresh ); 389 } 390 } 391 392 // neue Einstellungen merken 393 394 if ( bNewUrlName ) 395 aFileName = aNewUrl; 396 if ( rNewFilter != aFilterName ) 397 aFilterName = rNewFilter; 398 if ( aNewOpt != aOptions ) 399 aOptions = aNewOpt; 400 401 // aufraeumen 402 403 // pSrcShell->DoClose(); 404 aRef->DoClose(); 405 406 // Undo 407 408 if (bAddUndo && bUndo) 409 pImpl->m_pDocSh->GetUndoManager()->AddUndoAction( 410 new ScUndoRefreshLink( pImpl->m_pDocSh, pUndoDoc ) ); 411 412 // Paint (koennen mehrere Tabellen sein) 413 414 if (bDoPaint) 415 { 416 pImpl->m_pDocSh->PostPaint( ScRange(0,0,0,MAXCOL,MAXROW,MAXTAB), 417 PAINT_GRID | PAINT_TOP | PAINT_LEFT ); 418 aModificator.SetDocumentModified(); 419 } 420 421 if (bNotFound) 422 { 423 //! Fehler ausgeben ? 424 } 425 426 pDoc->SetInLinkUpdate( sal_False ); 427 428 // notify Uno objects (for XRefreshListener) 429 //! also notify Uno objects if file name was changed! 430 ScLinkRefreshedHint aHint; 431 aHint.SetSheetLink( aFileName ); 432 pDoc->BroadcastUno( aHint ); 433 434 return sal_True; 435 } 436 437 IMPL_LINK( ScTableLink, RefreshHdl, ScTableLink*, EMPTYARG ) 438 { 439 long nRes = Refresh( aFileName, aFilterName, NULL, GetRefreshDelay() ) != 0; 440 return nRes; 441 } 442 443 IMPL_LINK( ScTableLink, TableEndEditHdl, ::sfx2::SvBaseLink*, pLink ) 444 { 445 if ( pImpl->m_aEndEditLink.IsSet() ) 446 pImpl->m_aEndEditLink.Call( pLink ); 447 bInEdit = sal_False; 448 Application::SetDefDialogParent( pImpl->m_pOldParent ); 449 return 0; 450 } 451 452 // === ScDocumentLoader ================================================== 453 454 String ScDocumentLoader::GetOptions( SfxMedium& rMedium ) // static 455 { 456 SfxItemSet* pSet = rMedium.GetItemSet(); 457 const SfxPoolItem* pItem; 458 if ( pSet && SFX_ITEM_SET == pSet->GetItemState( SID_FILE_FILTEROPTIONS, sal_True, &pItem ) ) 459 return ((const SfxStringItem*)pItem)->GetValue(); 460 461 return EMPTY_STRING; 462 } 463 464 sal_Bool ScDocumentLoader::GetFilterName( const String& rFileName, 465 String& rFilter, String& rOptions, 466 sal_Bool bWithContent, sal_Bool bWithInteraction ) // static 467 { 468 TypeId aScType = TYPE(ScDocShell); 469 SfxObjectShell* pDocSh = SfxObjectShell::GetFirst( &aScType ); 470 while ( pDocSh ) 471 { 472 if ( pDocSh->HasName() ) 473 { 474 SfxMedium* pMed = pDocSh->GetMedium(); 475 if ( rFileName == pMed->GetName() ) 476 { 477 rFilter = pMed->GetFilter()->GetFilterName(); 478 rOptions = GetOptions(*pMed); 479 return sal_True; 480 } 481 } 482 pDocSh = SfxObjectShell::GetNext( *pDocSh, &aScType ); 483 } 484 485 INetURLObject aUrl( rFileName ); 486 INetProtocol eProt = aUrl.GetProtocol(); 487 if ( eProt == INET_PROT_NOT_VALID ) // invalid URL? 488 return sal_False; // abort without creating a medium 489 490 // Filter-Detection 491 492 const SfxFilter* pSfxFilter = NULL; 493 SfxMedium* pMedium = new SfxMedium( rFileName, STREAM_STD_READ, sal_False ); 494 if ( pMedium->GetError() == ERRCODE_NONE ) 495 { 496 if ( bWithInteraction ) 497 pMedium->UseInteractionHandler(sal_True); // #i73992# no longer called from GuessFilter 498 499 SfxFilterMatcher aMatcher( String::CreateFromAscii("scalc") ); 500 if( bWithContent ) 501 aMatcher.GuessFilter( *pMedium, &pSfxFilter ); 502 else 503 aMatcher.GuessFilterIgnoringContent( *pMedium, &pSfxFilter ); 504 } 505 506 sal_Bool bOK = sal_False; 507 if ( pMedium->GetError() == ERRCODE_NONE ) 508 { 509 if ( pSfxFilter ) 510 rFilter = pSfxFilter->GetFilterName(); 511 else 512 rFilter = ScDocShell::GetOwnFilterName(); // sonst Calc-Datei 513 bOK = (rFilter.Len()>0); 514 } 515 516 delete pMedium; 517 return bOK; 518 } 519 520 void ScDocumentLoader::RemoveAppPrefix( String& rFilterName ) // static 521 { 522 String aAppPrefix = String::CreateFromAscii(RTL_CONSTASCII_STRINGPARAM( STRING_SCAPP )); 523 aAppPrefix.AppendAscii(RTL_CONSTASCII_STRINGPARAM( ": " )); 524 xub_StrLen nPreLen = aAppPrefix.Len(); 525 if ( rFilterName.Copy(0,nPreLen) == aAppPrefix ) 526 rFilterName.Erase(0,nPreLen); 527 } 528 529 ScDocumentLoader::ScDocumentLoader( const String& rFileName, 530 String& rFilterName, String& rOptions, 531 sal_uInt32 nRekCnt, sal_Bool bWithInteraction ) : 532 pDocShell(0), 533 pMedium(0) 534 { 535 if ( !rFilterName.Len() ) 536 GetFilterName( rFileName, rFilterName, rOptions, sal_True, bWithInteraction ); 537 538 const SfxFilter* pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName( rFilterName ); 539 540 // ItemSet immer anlegen, damit die DocShell die Optionen setzen kann 541 SfxItemSet* pSet = new SfxAllItemSet( SFX_APP()->GetPool() ); 542 if ( rOptions.Len() ) 543 pSet->Put( SfxStringItem( SID_FILE_FILTEROPTIONS, rOptions ) ); 544 545 pMedium = new SfxMedium( rFileName, STREAM_STD_READ, sal_False, pFilter, pSet ); 546 if ( pMedium->GetError() != ERRCODE_NONE ) 547 return ; 548 549 if ( bWithInteraction ) 550 pMedium->UseInteractionHandler( sal_True ); // to enable the filter options dialog 551 552 pDocShell = new ScDocShell( SFX_CREATE_MODE_INTERNAL ); 553 aRef = pDocShell; 554 555 ScDocument* pDoc = pDocShell->GetDocument(); 556 if( pDoc ) 557 { 558 ScExtDocOptions* pExtDocOpt = pDoc->GetExtDocOptions(); 559 if( !pExtDocOpt ) 560 { 561 pExtDocOpt = new ScExtDocOptions; 562 pDoc->SetExtDocOptions( pExtDocOpt ); 563 } 564 pExtDocOpt->GetDocSettings().mnLinkCnt = nRekCnt; 565 } 566 567 pDocShell->DoLoad( pMedium ); 568 569 String aNew = GetOptions(*pMedium); // Optionen werden beim Laden per Dialog gesetzt 570 if (aNew.Len() && aNew != rOptions) 571 rOptions = aNew; 572 } 573 574 ScDocumentLoader::~ScDocumentLoader() 575 { 576 /* if ( pDocShell ) 577 pDocShell->DoClose(); 578 */ 579 if ( aRef.Is() ) 580 aRef->DoClose(); 581 else if ( pMedium ) 582 delete pMedium; 583 } 584 585 void ScDocumentLoader::ReleaseDocRef() 586 { 587 if ( aRef.Is() ) 588 { 589 // release reference without calling DoClose - caller must 590 // have another reference to the doc and call DoClose later 591 592 pDocShell = NULL; 593 pMedium = NULL; 594 aRef.Clear(); 595 } 596 } 597 598 ScDocument* ScDocumentLoader::GetDocument() 599 { 600 return pDocShell ? pDocShell->GetDocument() : 0; 601 } 602 603 sal_Bool ScDocumentLoader::IsError() const 604 { 605 if ( pDocShell && pMedium ) 606 return pMedium->GetError() != ERRCODE_NONE; 607 else 608 return sal_True; 609 } 610 611 String ScDocumentLoader::GetTitle() const 612 { 613 if ( pDocShell ) 614 return pDocShell->GetTitle(); 615 else 616 return EMPTY_STRING; 617 } 618 619