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