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