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