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 // MARKER(update_precomp.py): autogen include statement, do not remove 29 #include "precompiled_framework.hxx" 30 #include <uielement/recentfilesmenucontroller.hxx> 31 32 //_________________________________________________________________________________________________________________ 33 // my own includes 34 //_________________________________________________________________________________________________________________ 35 #include <threadhelp/resetableguard.hxx> 36 #include "services.h" 37 38 #ifndef __FRAMEWORK_CLASSES_RESOURCE_HRC_ 39 #include <classes/resource.hrc> 40 #endif 41 #include <classes/fwkresid.hxx> 42 43 //_________________________________________________________________________________________________________________ 44 // interface includes 45 //_________________________________________________________________________________________________________________ 46 #include <com/sun/star/awt/XDevice.hpp> 47 #include <com/sun/star/beans/PropertyValue.hpp> 48 #include <com/sun/star/awt/MenuItemStyle.hpp> 49 #include <com/sun/star/util/XStringWidth.hpp> 50 //_________________________________________________________________________________________________________________ 51 // includes of other projects 52 //_________________________________________________________________________________________________________________ 53 54 #ifndef _VCL_MENU_HXX_ 55 #include <vcl/menu.hxx> 56 #endif 57 #include <vcl/svapp.hxx> 58 #include <vcl/i18nhelp.hxx> 59 #include <tools/urlobj.hxx> 60 #include <rtl/ustrbuf.hxx> 61 #include <unotools/historyoptions.hxx> 62 #include <cppuhelper/implbase1.hxx> 63 #include <osl/file.hxx> 64 //#include <tools/solar.hrc> 65 #include <dispatch/uieventloghelper.hxx> 66 #include <vos/mutex.hxx> 67 68 //_________________________________________________________________________________________________________________ 69 // Defines 70 //_________________________________________________________________________________________________________________ 71 // 72 73 using namespace com::sun::star::uno; 74 using namespace com::sun::star::lang; 75 using namespace com::sun::star::frame; 76 using namespace com::sun::star::beans; 77 using namespace com::sun::star::util; 78 using namespace com::sun::star::container; 79 80 static const char SFX_REFERER_USER[] = "private:user"; 81 82 namespace framework 83 { 84 85 class RecentFilesStringLength : public ::cppu::WeakImplHelper1< ::com::sun::star::util::XStringWidth > 86 { 87 public: 88 RecentFilesStringLength() {} 89 virtual ~RecentFilesStringLength() {} 90 91 // XStringWidth 92 sal_Int32 SAL_CALL queryStringWidth( const ::rtl::OUString& aString ) 93 throw (::com::sun::star::uno::RuntimeException) 94 { 95 return aString.getLength(); 96 } 97 }; 98 99 DEFINE_XSERVICEINFO_MULTISERVICE ( RecentFilesMenuController , 100 OWeakObject , 101 SERVICENAME_POPUPMENUCONTROLLER , 102 IMPLEMENTATIONNAME_RECENTFILESMENUCONTROLLER 103 ) 104 105 DEFINE_INIT_SERVICE ( RecentFilesMenuController, {} ) 106 107 RecentFilesMenuController::RecentFilesMenuController( const ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory >& xServiceManager ) : 108 svt::PopupMenuControllerBase( xServiceManager ), 109 m_bDisabled( sal_False ) 110 { 111 } 112 113 RecentFilesMenuController::~RecentFilesMenuController() 114 { 115 } 116 117 // private function 118 void RecentFilesMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu >& rPopupMenu ) 119 { 120 VCLXPopupMenu* pPopupMenu = (VCLXPopupMenu *)VCLXMenu::GetImplementation( rPopupMenu ); 121 PopupMenu* pVCLPopupMenu = 0; 122 123 vos::OGuard aSolarMutexGuard( Application::GetSolarMutex() ); 124 125 resetPopupMenu( rPopupMenu ); 126 if ( pPopupMenu ) 127 pVCLPopupMenu = (PopupMenu *)pPopupMenu->GetMenu(); 128 129 if ( pVCLPopupMenu ) 130 { 131 Sequence< Sequence< PropertyValue > > aHistoryList = SvtHistoryOptions().GetList( ePICKLIST ); 132 Reference< XStringWidth > xStringLength( new RecentFilesStringLength ); 133 134 int nPickListMenuItems = ( aHistoryList.getLength() > 99 ) ? 99 : aHistoryList.getLength(); 135 136 // New vnd.sun.star.popup: command URL to support direct dispatches 137 const rtl::OUString aCmdPrefix( RTL_CONSTASCII_USTRINGPARAM( "vnd.sun.star.popup:RecentFileList?entry=" )); 138 139 m_aRecentFilesItems.clear(); 140 if (( nPickListMenuItems > 0 ) && !m_bDisabled ) 141 { 142 for ( int i = 0; i < nPickListMenuItems; i++ ) 143 { 144 Sequence< PropertyValue >& rPickListEntry = aHistoryList[i]; 145 RecentFile aRecentFile; 146 147 for ( int j = 0; j < rPickListEntry.getLength(); j++ ) 148 { 149 Any a = rPickListEntry[j].Value; 150 151 if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_URL ) 152 a >>= aRecentFile.aURL; 153 else if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_FILTER ) 154 a >>= aRecentFile.aFilter; 155 else if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_TITLE ) 156 a >>= aRecentFile.aTitle; 157 else if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_PASSWORD ) 158 a >>= aRecentFile.aPassword; 159 } 160 161 m_aRecentFilesItems.push_back( aRecentFile ); 162 } 163 } 164 165 if ( !m_aRecentFilesItems.empty() ) 166 { 167 URL aTargetURL; 168 169 const sal_uInt32 nCount = m_aRecentFilesItems.size(); 170 for ( sal_uInt32 i = 0; i < nCount; i++ ) 171 { 172 char menuShortCut[5] = "~n: "; 173 174 ::rtl::OUString aMenuShortCut; 175 if ( i <= 9 ) 176 { 177 if ( i == 9 ) 178 aMenuShortCut = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "1~0: " )); 179 else 180 { 181 menuShortCut[1] = (char)( '1' + i ); 182 aMenuShortCut = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( menuShortCut )); 183 } 184 } 185 else 186 { 187 aMenuShortCut = rtl::OUString::valueOf((sal_Int32)( i + 1 )); 188 aMenuShortCut += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( ": " )); 189 } 190 191 // Abbreviate URL 192 rtl::OUString aURLString( aCmdPrefix + rtl::OUString::valueOf( sal_Int32( i ))); 193 rtl::OUString aTipHelpText; 194 rtl::OUString aMenuTitle; 195 INetURLObject aURL( m_aRecentFilesItems[i].aURL ); 196 197 if ( aURL.GetProtocol() == INET_PROT_FILE ) 198 { 199 // Do handle file URL differently => convert it to a system 200 // path and abbreviate it with a special function: 201 String aFileSystemPath( aURL.getFSysPath( INetURLObject::FSYS_DETECT ) ); 202 203 ::rtl::OUString aSystemPath( aFileSystemPath ); 204 ::rtl::OUString aCompactedSystemPath; 205 206 aTipHelpText = aSystemPath; 207 oslFileError nError = osl_abbreviateSystemPath( aSystemPath.pData, &aCompactedSystemPath.pData, 46, NULL ); 208 if ( !nError ) 209 aMenuTitle = String( aCompactedSystemPath ); 210 else 211 aMenuTitle = aSystemPath; 212 } 213 else 214 { 215 // Use INetURLObject to abbreviate all other URLs 216 String aShortURL; 217 aShortURL = aURL.getAbbreviated( xStringLength, 46, INetURLObject::DECODE_UNAMBIGUOUS ); 218 aMenuTitle += aShortURL; 219 aTipHelpText = aURLString; 220 } 221 222 ::rtl::OUString aTitle( aMenuShortCut + aMenuTitle ); 223 224 pVCLPopupMenu->InsertItem( sal_uInt16( i+1 ), aTitle ); 225 pVCLPopupMenu->SetTipHelpText( sal_uInt16( i+1 ), aTipHelpText ); 226 pVCLPopupMenu->SetItemCommand( sal_uInt16( i+1 ), aURLString ); 227 } 228 } 229 else 230 { 231 // No recent documents => insert "no document" string 232 String aNoDocumentStr = String( FwkResId( STR_NODOCUMENT )); 233 pVCLPopupMenu->InsertItem( 1, aNoDocumentStr ); 234 pVCLPopupMenu->EnableItem( 1, sal_False ); 235 } 236 } 237 } 238 239 void RecentFilesMenuController::executeEntry( sal_Int32 nIndex ) 240 { 241 static int NUM_OF_PICKLIST_ARGS = 3; 242 243 Reference< css::awt::XPopupMenu > xPopupMenu; 244 Reference< XDispatch > xDispatch; 245 Reference< XDispatchProvider > xDispatchProvider; 246 Reference< XMultiServiceFactory > xServiceManager; 247 248 osl::ClearableMutexGuard aLock( m_aMutex ); 249 xPopupMenu = m_xPopupMenu; 250 xDispatchProvider = Reference< XDispatchProvider >( m_xFrame, UNO_QUERY ); 251 xServiceManager = m_xServiceManager; 252 aLock.clear(); 253 254 css::util::URL aTargetURL; 255 Sequence< PropertyValue > aArgsList; 256 257 if (( nIndex >= 0 ) && 258 ( nIndex < sal::static_int_cast<sal_Int32>( m_aRecentFilesItems.size() ))) 259 { 260 const RecentFile& rRecentFile = m_aRecentFilesItems[ nIndex ]; 261 262 aTargetURL.Complete = rRecentFile.aURL; 263 m_xURLTransformer->parseStrict( aTargetURL ); 264 265 aArgsList.realloc( NUM_OF_PICKLIST_ARGS ); 266 aArgsList[0].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Referer" )); 267 aArgsList[0].Value = makeAny( ::rtl::OUString::createFromAscii( SFX_REFERER_USER )); 268 269 // documents in the picklist will never be opened as templates 270 aArgsList[1].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "AsTemplate" )); 271 aArgsList[1].Value = makeAny( (sal_Bool) sal_False ); 272 273 ::rtl::OUString aFilter( rRecentFile.aFilter ); 274 sal_Int32 nPos = aFilter.indexOf( '|' ); 275 if ( nPos >= 0 ) 276 { 277 ::rtl::OUString aFilterOptions; 278 279 if ( nPos < ( aFilter.getLength() - 1 ) ) 280 aFilterOptions = aFilter.copy( nPos+1 ); 281 282 aArgsList[2].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "FilterOptions" )); 283 aArgsList[2].Value <<= aFilterOptions; 284 285 aFilter = aFilter.copy( 0, nPos-1 ); 286 aArgsList.realloc( ++NUM_OF_PICKLIST_ARGS ); 287 } 288 289 aArgsList[NUM_OF_PICKLIST_ARGS-1].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "FilterName" )); 290 aArgsList[NUM_OF_PICKLIST_ARGS-1].Value <<= aFilter; 291 292 xDispatch = xDispatchProvider->queryDispatch( aTargetURL, ::rtl::OUString::createFromAscii("_default"), 0 ); 293 } 294 295 if ( xDispatch.is() ) 296 { 297 // Call dispatch asychronously as we can be destroyed while dispatch is 298 // executed. VCL is not able to survive this as it wants to call listeners 299 // after select!!! 300 LoadRecentFile* pLoadRecentFile = new LoadRecentFile; 301 pLoadRecentFile->xDispatch = xDispatch; 302 pLoadRecentFile->aTargetURL = aTargetURL; 303 pLoadRecentFile->aArgSeq = aArgsList; 304 if(::comphelper::UiEventsLogger::isEnabled()) //#i88653# 305 UiEventLogHelper(::rtl::OUString::createFromAscii("RecentFilesMenuController")).log(m_xServiceManager, m_xFrame, aTargetURL, aArgsList); 306 Application::PostUserEvent( STATIC_LINK(0, RecentFilesMenuController, ExecuteHdl_Impl), pLoadRecentFile ); 307 } 308 } 309 310 // XEventListener 311 void SAL_CALL RecentFilesMenuController::disposing( const EventObject& ) throw ( RuntimeException ) 312 { 313 Reference< css::awt::XMenuListener > xHolder(( OWeakObject *)this, UNO_QUERY ); 314 315 osl::MutexGuard aLock( m_aMutex ); 316 m_xFrame.clear(); 317 m_xDispatch.clear(); 318 m_xServiceManager.clear(); 319 320 if ( m_xPopupMenu.is() ) 321 m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(( OWeakObject *)this, UNO_QUERY )); 322 m_xPopupMenu.clear(); 323 } 324 325 // XStatusListener 326 void SAL_CALL RecentFilesMenuController::statusChanged( const FeatureStateEvent& Event ) throw ( RuntimeException ) 327 { 328 osl::MutexGuard aLock( m_aMutex ); 329 m_bDisabled = !Event.IsEnabled; 330 } 331 332 void SAL_CALL RecentFilesMenuController::select( const css::awt::MenuEvent& rEvent ) throw (RuntimeException) 333 { 334 Reference< css::awt::XPopupMenu > xPopupMenu; 335 Reference< XDispatch > xDispatch; 336 Reference< XDispatchProvider > xDispatchProvider; 337 Reference< XMultiServiceFactory > xServiceManager; 338 339 osl::ClearableMutexGuard aLock( m_aMutex ); 340 xPopupMenu = m_xPopupMenu; 341 xDispatchProvider = Reference< XDispatchProvider >( m_xFrame, UNO_QUERY ); 342 xServiceManager = m_xServiceManager; 343 aLock.clear(); 344 345 css::util::URL aTargetURL; 346 Sequence< PropertyValue > aArgsList; 347 348 if ( xPopupMenu.is() && xDispatchProvider.is() ) 349 { 350 VCLXPopupMenu* pPopupMenu = (VCLXPopupMenu *)VCLXPopupMenu::GetImplementation( xPopupMenu ); 351 if ( pPopupMenu ) 352 executeEntry( rEvent.MenuId-1 ); 353 } 354 } 355 356 void SAL_CALL RecentFilesMenuController::activate( const css::awt::MenuEvent& ) throw (RuntimeException) 357 { 358 osl::MutexGuard aLock( m_aMutex ); 359 impl_setPopupMenu(); 360 } 361 362 // XPopupMenuController 363 void RecentFilesMenuController::impl_setPopupMenu() 364 { 365 if ( m_xPopupMenu.is() ) 366 fillPopupMenu( m_xPopupMenu ); 367 } 368 369 void SAL_CALL RecentFilesMenuController::updatePopupMenu() throw (RuntimeException) 370 { 371 osl::ClearableMutexGuard aLock( m_aMutex ); 372 373 throwIfDisposed(); 374 375 Reference< XStatusListener > xStatusListener( static_cast< OWeakObject* >( this ), UNO_QUERY ); 376 Reference< XDispatch > xDispatch( m_xDispatch ); 377 com::sun::star::util::URL aTargetURL; 378 aTargetURL.Complete = m_aCommandURL; 379 m_xURLTransformer->parseStrict( aTargetURL ); 380 aLock.clear(); 381 382 // Add/remove status listener to get a status update once 383 if ( xDispatch.is() ) 384 { 385 xDispatch->addStatusListener( xStatusListener, aTargetURL ); 386 xDispatch->removeStatusListener( xStatusListener, aTargetURL ); 387 } 388 } 389 390 // XDispatchProvider 391 Reference< XDispatch > SAL_CALL RecentFilesMenuController::queryDispatch( 392 const URL& aURL, 393 const ::rtl::OUString& /*sTarget*/, 394 sal_Int32 /*nFlags*/ ) 395 throw( RuntimeException ) 396 { 397 osl::MutexGuard aLock( m_aMutex ); 398 399 throwIfDisposed(); 400 401 if ( aURL.Complete.indexOf( m_aBaseURL ) == 0 ) 402 return Reference< XDispatch >( static_cast< OWeakObject* >( this ), UNO_QUERY ); 403 else 404 return Reference< XDispatch >(); 405 } 406 407 // XDispatch 408 void SAL_CALL RecentFilesMenuController::dispatch( 409 const URL& aURL, 410 const Sequence< PropertyValue >& /*seqProperties*/ ) 411 throw( RuntimeException ) 412 { 413 osl::MutexGuard aLock( m_aMutex ); 414 415 throwIfDisposed(); 416 417 if ( aURL.Complete.indexOf( m_aBaseURL ) == 0 ) 418 { 419 // Parse URL to retrieve entry argument and its value 420 sal_Int32 nQueryPart = aURL.Complete.indexOf( '?', m_aBaseURL.getLength() ); 421 if ( nQueryPart > 0 ) 422 { 423 const rtl::OUString aEntryArgStr( RTL_CONSTASCII_USTRINGPARAM( "entry=" )); 424 sal_Int32 nEntryArg = aURL.Complete.indexOf( aEntryArgStr, nQueryPart ); 425 sal_Int32 nEntryPos = nEntryArg + aEntryArgStr.getLength(); 426 if (( nEntryArg > 0 ) && ( nEntryPos < aURL.Complete.getLength() )) 427 { 428 sal_Int32 nAddArgs = aURL.Complete.indexOf( '&', nEntryPos ); 429 rtl::OUString aEntryArg; 430 431 if ( nAddArgs < 0 ) 432 aEntryArg = aURL.Complete.copy( nEntryPos ); 433 else 434 aEntryArg = aURL.Complete.copy( nEntryPos, nAddArgs-nEntryPos ); 435 436 sal_Int32 nEntry = aEntryArg.toInt32(); 437 executeEntry( nEntry ); 438 } 439 } 440 } 441 } 442 443 void SAL_CALL RecentFilesMenuController::addStatusListener( 444 const Reference< XStatusListener >& xControl, 445 const URL& aURL ) 446 throw( RuntimeException ) 447 { 448 osl::MutexGuard aLock( m_aMutex ); 449 450 throwIfDisposed(); 451 452 svt::PopupMenuControllerBase::addStatusListener( xControl, aURL ); 453 } 454 455 void SAL_CALL RecentFilesMenuController::removeStatusListener( 456 const Reference< XStatusListener >& xControl, 457 const URL& aURL ) 458 throw( RuntimeException ) 459 { 460 svt::PopupMenuControllerBase::removeStatusListener( xControl, aURL ); 461 } 462 463 IMPL_STATIC_LINK_NOINSTANCE( RecentFilesMenuController, ExecuteHdl_Impl, LoadRecentFile*, pLoadRecentFile ) 464 { 465 try 466 { 467 // Asynchronous execution as this can lead to our own destruction! 468 // Framework can recycle our current frame and the layout manager disposes all user interface 469 // elements if a component gets detached from its frame! 470 pLoadRecentFile->xDispatch->dispatch( pLoadRecentFile->aTargetURL, pLoadRecentFile->aArgSeq ); 471 } 472 catch ( Exception& ) 473 { 474 } 475 476 delete pLoadRecentFile; 477 return 0; 478 } 479 480 } 481