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