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_svtools.hxx"
30 
31 #include <svtools/contextmenuhelper.hxx>
32 #include <svtools/menuoptions.hxx>
33 #include <svtools/miscopt.hxx>
34 
35 #include <com/sun/star/frame/XDispatch.hpp>
36 #include <com/sun/star/frame/XDispatchProvider.hpp>
37 #include <com/sun/star/frame/XModuleManager.hpp>
38 #include <com/sun/star/frame/XStatusListener.hpp>
39 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
40 #include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp>
41 #include <com/sun/star/ui/XUIConfigurationManager.hpp>
42 #include <com/sun/star/ui/XModuleUIConfigurationManagerSupplier.hpp>
43 #include <com/sun/star/ui/ImageType.hpp>
44 #include <com/sun/star/beans/PropertyValue.hpp>
45 
46 #include <osl/conditn.hxx>
47 #include <cppuhelper/weak.hxx>
48 #include <comphelper/processfactory.hxx>
49 #include <vos/mutex.hxx>
50 #include <vcl/svapp.hxx>
51 #include <vcl/image.hxx>
52 #include <toolkit/unohlp.hxx>
53 #include <toolkit/awt/vclxwindow.hxx>
54 #include <toolkit/awt/vclxmenu.hxx>
55 
56 using namespace ::com::sun::star;
57 
58 namespace svt
59 {
60 
61 // internal helper class to retrieve status updates
62 class StateEventHelper : public ::com::sun::star::frame::XStatusListener,
63                          public ::cppu::OWeakObject
64 {
65     public:
66         StateEventHelper( const uno::Reference< frame::XDispatchProvider >& xDispatchProvider,
67                           const uno::Reference< util::XURLTransformer >& xURLTransformer,
68                           const rtl::OUString& aCommandURL );
69         virtual ~StateEventHelper();
70 
71         bool isCommandEnabled();
72 
73 		// XInterface
74 		virtual uno::Any SAL_CALL queryInterface( const uno::Type& aType ) throw ( uno::RuntimeException);
75 		virtual void SAL_CALL acquire() throw ();
76 		virtual void SAL_CALL release() throw ();
77 
78         // XEventListener
79 	    virtual void SAL_CALL disposing(const lang::EventObject& Source) throw( uno::RuntimeException );
80 
81         // XStatusListener
82 	    virtual void SAL_CALL statusChanged(const frame::FeatureStateEvent& Event) throw( uno::RuntimeException );
83 
84     private:
85         StateEventHelper();
86         StateEventHelper( const StateEventHelper& );
87         StateEventHelper& operator=( const StateEventHelper& );
88 
89         bool                                       m_bCurrentCommandEnabled;
90         ::rtl::OUString                            m_aCommandURL;
91         uno::Reference< frame::XDispatchProvider > m_xDispatchProvider;
92         uno::Reference< util::XURLTransformer >    m_xURLTransformer;
93         osl::Condition                             m_aCondition;
94 };
95 
96 StateEventHelper::StateEventHelper(
97     const uno::Reference< frame::XDispatchProvider >& xDispatchProvider,
98     const uno::Reference< util::XURLTransformer >& xURLTransformer,
99     const rtl::OUString& rCommandURL ) :
100     m_bCurrentCommandEnabled( true ),
101     m_aCommandURL( rCommandURL ),
102     m_xDispatchProvider( xDispatchProvider ),
103     m_xURLTransformer( xURLTransformer )
104 {
105     m_aCondition.reset();
106 }
107 
108 StateEventHelper::~StateEventHelper()
109 {}
110 
111 uno::Any SAL_CALL StateEventHelper::queryInterface(
112     const uno::Type& aType )
113 throw ( uno::RuntimeException )
114 {
115     uno::Any a = ::cppu::queryInterface(
116 				aType,
117 				SAL_STATIC_CAST( XStatusListener*, this ));
118 
119 	if( a.hasValue() )
120 		return a;
121 
122     return ::cppu::OWeakObject::queryInterface( aType );
123 }
124 
125 void SAL_CALL StateEventHelper::acquire()
126 throw ()
127 {
128     ::cppu::OWeakObject::acquire();
129 }
130 
131 void SAL_CALL StateEventHelper::release()
132 throw ()
133 {
134     ::cppu::OWeakObject::release();
135 }
136 
137 void SAL_CALL StateEventHelper::disposing(
138     const lang::EventObject& )
139 throw ( uno::RuntimeException )
140 {
141     vos::OGuard	aSolarGuard( Application::GetSolarMutex() );
142     m_xDispatchProvider.clear();
143     m_xURLTransformer.clear();
144     m_aCondition.set();
145 }
146 
147 void SAL_CALL StateEventHelper::statusChanged(
148     const frame::FeatureStateEvent& Event )
149 throw ( uno::RuntimeException )
150 {
151     vos::OGuard	aSolarGuard( Application::GetSolarMutex() );
152     m_bCurrentCommandEnabled = Event.IsEnabled;
153     m_aCondition.set();
154 }
155 
156 bool StateEventHelper::isCommandEnabled()
157 {
158     // Be sure that we cannot die during condition wait
159     uno::Reference< frame::XStatusListener > xSelf(
160         SAL_STATIC_CAST( frame::XStatusListener*, this ));
161 
162     uno::Reference< frame::XDispatch > xDispatch;
163     util::URL                          aTargetURL;
164     {
165         vos::OGuard	aSolarGuard( Application::GetSolarMutex() );
166         if ( m_xDispatchProvider.is() && m_xURLTransformer.is() )
167         {
168             ::rtl::OUString aSelf( RTL_CONSTASCII_USTRINGPARAM( "_self" ));
169 
170             aTargetURL.Complete = m_aCommandURL;
171             m_xURLTransformer->parseStrict( aTargetURL );
172 
173             try
174             {
175                 xDispatch = m_xDispatchProvider->queryDispatch( aTargetURL, aSelf, 0 );
176             }
177             catch ( uno::RuntimeException& )
178             {
179                 throw;
180             }
181             catch ( uno::Exception& )
182             {
183             }
184         }
185     }
186 
187     bool bResult( false );
188     if ( xDispatch.is() )
189     {
190         try
191         {
192             // add/remove ourself to retrieve status by callback
193             xDispatch->addStatusListener( xSelf, aTargetURL );
194             xDispatch->removeStatusListener( xSelf, aTargetURL );
195 
196             // wait for anwser
197             m_aCondition.wait();
198         }
199         catch ( uno::RuntimeException& )
200         {
201             throw;
202         }
203         catch ( uno::Exception& )
204         {
205         }
206 
207         vos::OGuard	aSolarGuard( Application::GetSolarMutex() );
208         bResult = m_bCurrentCommandEnabled;
209     }
210 
211     return bResult;
212 }
213 
214 /*************************************************************************/
215 
216 struct ExecuteInfo
217 {
218     uno::Reference< frame::XDispatch >    xDispatch;
219     util::URL                             aTargetURL;
220     uno::Sequence< beans::PropertyValue > aArgs;
221 };
222 
223 static const PopupMenu* lcl_FindPopupFromItemId( const PopupMenu* pPopupMenu, sal_uInt16 nItemId )
224 {
225     if ( pPopupMenu )
226     {
227         sal_uInt16 nCount = pPopupMenu->GetItemCount();
228         for ( sal_uInt16 i = 0; i < nCount; i++ )
229         {
230             sal_uInt16 nId = pPopupMenu->GetItemId( i );
231             if ( nId == nItemId )
232                 return pPopupMenu;
233             else
234             {
235                 const PopupMenu* pResult( 0 );
236 
237                 const PopupMenu* pSubPopup = pPopupMenu->GetPopupMenu( i );
238                 if ( pPopupMenu )
239                     pResult = lcl_FindPopupFromItemId( pSubPopup, nItemId );
240                 if ( pResult != 0 )
241                     return pResult;
242             }
243         }
244     }
245 
246     return NULL;
247 }
248 
249 static ::rtl::OUString lcl_GetItemCommandRecursive( const PopupMenu* pPopupMenu, sal_uInt16 nItemId )
250 {
251     const PopupMenu* pPopup = lcl_FindPopupFromItemId( pPopupMenu, nItemId );
252     if ( pPopup )
253         return pPopup->GetItemCommand( nItemId );
254     else
255         return ::rtl::OUString();
256 }
257 
258 /*************************************************************************/
259 
260 ContextMenuHelper::ContextMenuHelper(
261     const uno::Reference< frame::XFrame >& xFrame,
262     bool bAutoRefresh ) :
263     m_xWeakFrame( xFrame ),
264     m_aSelf( RTL_CONSTASCII_USTRINGPARAM( "_self" )),
265     m_bAutoRefresh( bAutoRefresh ),
266     m_bUICfgMgrAssociated( false )
267 {
268 }
269 
270 ContextMenuHelper::~ContextMenuHelper()
271 {
272 }
273 
274 void
275 ContextMenuHelper::completeAndExecute(
276     const Point& aPos,
277     PopupMenu& rPopupMenu )
278 {
279     vos::OGuard	aSolarGuard( Application::GetSolarMutex() );
280 
281     associateUIConfigurationManagers();
282     completeMenuProperties( &rPopupMenu );
283     executePopupMenu( aPos, &rPopupMenu );
284     resetAssociations();
285 }
286 
287 void
288 ContextMenuHelper::completeAndExecute(
289     const Point& aPos,
290     const uno::Reference< awt::XPopupMenu >& xPopupMenu )
291 {
292     vos::OGuard	aSolarGuard( Application::GetSolarMutex() );
293 
294     VCLXMenu* pXMenu = VCLXMenu::GetImplementation( xPopupMenu );
295     if ( pXMenu )
296     {
297         PopupMenu* pPopupMenu = dynamic_cast< PopupMenu* >( pXMenu->GetMenu() );
298         // as dynamic_cast can return zero check pointer
299         if ( pPopupMenu )
300         {
301             associateUIConfigurationManagers();
302             completeMenuProperties( pPopupMenu );
303             executePopupMenu( aPos, pPopupMenu );
304             resetAssociations();
305         }
306     }
307 }
308 
309 uno::Reference< awt::XPopupMenu >
310 ContextMenuHelper::create(
311     const ::rtl::OUString& /*aPopupMenuResourceId*/ )
312 {
313     // NOT IMPLEMENTED YET!
314     return uno::Reference< awt::XPopupMenu >();
315 }
316 
317 bool
318 ContextMenuHelper::createAndExecute(
319     const Point& /*aPos*/,
320     const ::rtl::OUString& /*aPopupMenuResourceId*/ )
321 {
322     // NOT IMPLEMENTED YET!
323     return false;
324 }
325 
326 // private member
327 
328 void
329 ContextMenuHelper::executePopupMenu(
330     const Point& rPos,
331     PopupMenu* pMenu )
332 {
333     if ( pMenu )
334     {
335         uno::Reference< frame::XFrame > xFrame( m_xWeakFrame );
336         if ( xFrame.is() )
337         {
338             uno::Reference< awt::XWindow > xWindow( xFrame->getContainerWindow() );
339             if ( xWindow.is() )
340             {
341                 Window* pParent = VCLUnoHelper::GetWindow( xWindow );
342                 sal_uInt16 nResult = pMenu->Execute( pParent, rPos );
343 
344                 if ( nResult > 0 )
345                 {
346                     ::rtl::OUString aCommand = lcl_GetItemCommandRecursive( pMenu, nResult );
347                     if ( aCommand.getLength() > 0 )
348                         dispatchCommand( xFrame, aCommand );
349                 }
350             }
351         }
352     }
353 }
354 
355 bool
356 ContextMenuHelper::dispatchCommand(
357     const uno::Reference< ::frame::XFrame >& rFrame,
358     const ::rtl::OUString& aCommandURL )
359 {
360     if ( !m_xURLTransformer.is() )
361     {
362         m_xURLTransformer = uno::Reference< util::XURLTransformer >(
363             ::comphelper::getProcessServiceFactory()->createInstance(
364                 rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
365                     "com.sun.star.util.URLTransformer" ))),
366             uno::UNO_QUERY );
367     }
368 
369     util::URL aTargetURL;
370     uno::Reference< frame::XDispatch > xDispatch;
371     if ( m_xURLTransformer.is() )
372     {
373         aTargetURL.Complete = aCommandURL;
374         m_xURLTransformer->parseStrict( aTargetURL );
375 
376         uno::Reference< frame::XDispatchProvider > xDispatchProvider(
377             rFrame, uno::UNO_QUERY );
378         if ( xDispatchProvider.is() )
379         {
380             try
381             {
382                 xDispatch = xDispatchProvider->queryDispatch( aTargetURL, m_aSelf, 0 );
383             }
384             catch ( uno::RuntimeException& )
385             {
386                 throw;
387             }
388             catch ( uno::Exception& )
389             {
390             }
391         }
392     }
393 
394     if ( xDispatch.is() )
395     {
396         ExecuteInfo* pExecuteInfo = new ExecuteInfo;
397         pExecuteInfo->xDispatch    = xDispatch;
398         pExecuteInfo->aTargetURL   = aTargetURL;
399         pExecuteInfo->aArgs        = m_aDefaultArgs;
400 
401         Application::PostUserEvent( STATIC_LINK(0, ContextMenuHelper , ExecuteHdl_Impl), pExecuteInfo );
402         return true;
403     }
404 
405     return false;
406 }
407 
408 // retrieves and stores references to our user-interface
409 // configuration managers, like image manager, ui command
410 // description manager.
411 bool
412 ContextMenuHelper::associateUIConfigurationManagers()
413 {
414     uno::Reference< frame::XFrame > xFrame( m_xWeakFrame );
415     if ( !m_bUICfgMgrAssociated && xFrame.is() )
416     {
417         // clear current state
418         m_xDocImageMgr.clear();
419         m_xModuleImageMgr.clear();
420         m_xUICommandLabels.clear();
421 
422         try
423         {
424             uno::Reference < frame::XController > xController;
425             uno::Reference < frame::XModel > xModel;
426             xController = xFrame->getController();
427             if ( xController.is() )
428                 xModel = xController->getModel();
429 
430             if ( xModel.is() )
431             {
432                 // retrieve document image manager form model
433                 uno::Reference< ui::XUIConfigurationManagerSupplier > xSupplier( xModel, uno::UNO_QUERY );
434                 if ( xSupplier.is() )
435                 {
436                     uno::Reference< ui::XUIConfigurationManager > xDocUICfgMgr(
437                         xSupplier->getUIConfigurationManager(), uno::UNO_QUERY );
438                     m_xDocImageMgr = uno::Reference< ui::XImageManager >(
439                         xDocUICfgMgr->getImageManager(), uno::UNO_QUERY );
440                 }
441             }
442 
443             uno::Reference< frame::XModuleManager > xModuleManager(
444                 ::comphelper::getProcessServiceFactory()->createInstance(
445                     rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
446                         "com.sun.star.frame.ModuleManager" ))),
447                 uno::UNO_QUERY );
448 
449             uno::Reference< ui::XImageManager > xModuleImageManager;
450             rtl::OUString                       aModuleId;
451             if ( xModuleManager.is() )
452             {
453                 // retrieve module image manager
454                 aModuleId = xModuleManager->identify( xFrame );
455 
456                 uno::Reference< ui::XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier(
457                     ::comphelper::getProcessServiceFactory()->createInstance(
458                         rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
459                             "com.sun.star.ui.ModuleUIConfigurationManagerSupplier" ))),
460                         uno::UNO_QUERY );
461                 if ( xModuleCfgMgrSupplier.is() )
462                 {
463                     uno::Reference< ui::XUIConfigurationManager > xUICfgMgr(
464                         xModuleCfgMgrSupplier->getUIConfigurationManager( aModuleId ));
465                     if ( xUICfgMgr.is() )
466                     {
467                         m_xModuleImageMgr = uno::Reference< ui::XImageManager >(
468                             xUICfgMgr->getImageManager(), uno::UNO_QUERY );
469                     }
470                 }
471             }
472 
473             uno::Reference< container::XNameAccess > xNameAccess(
474                 ::comphelper::getProcessServiceFactory()->createInstance(
475                     rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
476                         "com.sun.star.frame.UICommandDescription" ))),
477                     uno::UNO_QUERY );
478             if ( xNameAccess.is() )
479             {
480                 try
481                 {
482                     uno::Any a = xNameAccess->getByName( aModuleId );
483                     a >>= m_xUICommandLabels;
484                 }
485                 catch ( container::NoSuchElementException& )
486                 {
487                 }
488             }
489         }
490         catch ( uno::RuntimeException& )
491         {
492             throw;
493         }
494         catch ( uno::Exception& )
495         {
496             m_bUICfgMgrAssociated = true;
497             return false;
498         }
499         m_bUICfgMgrAssociated = true;
500     }
501 
502     return true;
503 }
504 
505 Image
506 ContextMenuHelper::getImageFromCommandURL(
507     const ::rtl::OUString& aCmdURL,
508     bool                   bHiContrast ) const
509 {
510     Image     aImage;
511     sal_Int16 nImageType( ui::ImageType::COLOR_NORMAL|
512                           ui::ImageType::SIZE_DEFAULT );
513     if ( bHiContrast )
514         nImageType |= ui::ImageType::COLOR_HIGHCONTRAST;
515 
516     uno::Sequence< uno::Reference< graphic::XGraphic > > aGraphicSeq;
517     uno::Sequence< ::rtl::OUString > aImageCmdSeq( 1 );
518     aImageCmdSeq[0] = aCmdURL;
519 
520     if ( m_xDocImageMgr.is() )
521     {
522         try
523         {
524             aGraphicSeq = m_xDocImageMgr->getImages( nImageType, aImageCmdSeq );
525             uno::Reference< graphic::XGraphic > xGraphic = aGraphicSeq[0];
526             aImage = Image( xGraphic );
527 
528             if ( !!aImage )
529                 return aImage;
530         }
531         catch ( uno::RuntimeException& )
532         {
533             throw;
534         }
535         catch ( uno::Exception& )
536         {
537         }
538     }
539 
540     if ( m_xModuleImageMgr.is() )
541     {
542         try
543         {
544             aGraphicSeq = m_xModuleImageMgr->getImages( nImageType, aImageCmdSeq );
545             uno::Reference< ::com::sun::star::graphic::XGraphic > xGraphic = aGraphicSeq[0];
546 	    aImage = Image( xGraphic );
547 
548             if ( !!aImage )
549                 return aImage;
550         }
551         catch ( uno::RuntimeException& )
552         {
553             throw;
554         }
555         catch ( uno::Exception& )
556         {
557         }
558     }
559 
560     return aImage;
561 }
562 
563 rtl::OUString
564 ContextMenuHelper::getLabelFromCommandURL(
565     const ::rtl::OUString& aCmdURL ) const
566 {
567     ::rtl::OUString aLabel;
568 
569     if ( m_xUICommandLabels.is() )
570     {
571         try
572         {
573             if ( aCmdURL.getLength() > 0 )
574             {
575                 rtl::OUString aStr;
576                 uno::Sequence< beans::PropertyValue > aPropSeq;
577                 uno::Any a( m_xUICommandLabels->getByName( aCmdURL ));
578                 if ( a >>= aPropSeq )
579                 {
580                     for ( sal_Int32 i = 0; i < aPropSeq.getLength(); i++ )
581                     {
582                         if ( aPropSeq[i].Name.equalsAscii( "Label" ))
583                         {
584                             aPropSeq[i].Value >>= aStr;
585                             break;
586                         }
587                     }
588                 }
589                 aLabel = aStr;
590             }
591         }
592         catch ( uno::RuntimeException& )
593         {
594         }
595         catch ( uno::Exception& )
596         {
597         }
598     }
599 
600     return aLabel;
601 }
602 
603 void
604 ContextMenuHelper::completeMenuProperties(
605     Menu* pMenu )
606 {
607     // Retrieve some settings necessary to display complete context
608     // menu correctly.
609     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
610 	bool  bShowMenuImages( rSettings.GetUseImagesInMenus() );
611 	bool  bIsHiContrast( rSettings.GetHighContrastMode() );
612 
613     if ( pMenu )
614     {
615         uno::Reference< frame::XFrame > xFrame( m_xWeakFrame );
616         uno::Reference< frame::XDispatchProvider > xDispatchProvider( xFrame, uno::UNO_QUERY );
617 
618         if ( !m_xURLTransformer.is() )
619         {
620             m_xURLTransformer = uno::Reference< util::XURLTransformer >(
621                 ::comphelper::getProcessServiceFactory()->createInstance(
622                     rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
623                         "com.sun.star.util.URLTransformer" ))),
624                 uno::UNO_QUERY );
625         }
626 
627         for ( sal_uInt16 nPos = 0; nPos < pMenu->GetItemCount(); nPos++ )
628 	    {
629 		    sal_uInt16 nId        = pMenu->GetItemId( nPos );
630 		    PopupMenu* pPopupMenu = pMenu->GetPopupMenu( nId );
631             if ( pPopupMenu )
632                 completeMenuProperties( pPopupMenu );
633             if ( pMenu->GetItemType( nPos ) != MENUITEM_SEPARATOR )
634 		    {
635                 ::rtl::OUString aCmdURL( pMenu->GetItemCommand( nId ));
636 
637                 if ( bShowMenuImages )
638 			    {
639 				    Image aImage;
640                     if ( aCmdURL.getLength() > 0 )
641 					    aImage = getImageFromCommandURL( aCmdURL, bIsHiContrast );
642                     pMenu->SetItemImage( nId, aImage );
643 			    }
644 			    else
645 				    pMenu->SetItemImage( nId, Image() );
646 
647                 if ( pMenu->GetItemText( nId ).Len() == 0 )
648                 {
649                     ::rtl::OUString aLabel( getLabelFromCommandURL( aCmdURL ));
650                     pMenu->SetItemText( nId, aLabel );
651                 }
652 
653                 // Use helper to retrieve state of the command URL
654 				StateEventHelper* pHelper = new StateEventHelper(
655 													xDispatchProvider,
656 													m_xURLTransformer,
657 													aCmdURL );
658 
659 				uno::Reference< frame::XStatusListener > xHelper( pHelper );
660                 pMenu->EnableItem( nId, pHelper->isCommandEnabled() );
661 		    }
662 	    }
663     }
664 }
665 
666 
667 IMPL_STATIC_LINK_NOINSTANCE( ContextMenuHelper, ExecuteHdl_Impl, ExecuteInfo*, pExecuteInfo )
668 {
669     // Release solar mutex to prevent deadlocks with clipboard thread
670     const sal_uInt32 nRef = Application::ReleaseSolarMutex();
671     try
672     {
673         // Asynchronous execution as this can lead to our own destruction while we are
674         // on the stack. Stack unwinding would access the destroyed context menu.
675         pExecuteInfo->xDispatch->dispatch( pExecuteInfo->aTargetURL, pExecuteInfo->aArgs );
676     }
677     catch ( uno::Exception& )
678     {
679     }
680 
681     // Acquire solar mutex again
682     Application::AcquireSolarMutex( nRef );
683     delete pExecuteInfo;
684     return 0;
685 }
686 
687 } // namespace svt
688