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