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