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 
31 //_________________________________________________________________________________________________________________
32 //	my own includes
33 //_________________________________________________________________________________________________________________
34 #include "framework/addonmenu.hxx"
35 #include "framework/addonsoptions.hxx"
36 #include <general.h>
37 #include <macros/debug/assertion.hxx>
38 #include <framework/imageproducer.hxx>
39 #include <framework/menuconfiguration.hxx>
40 
41 //_________________________________________________________________________________________________________________
42 //	interface includes
43 //_________________________________________________________________________________________________________________
44 #include <com/sun/star/uno/Reference.hxx>
45 #include <com/sun/star/util/URL.hpp>
46 #include <com/sun/star/util/XURLTransformer.hpp>
47 #include <com/sun/star/lang/XServiceInfo.hpp>
48 
49 //_________________________________________________________________________________________________________________
50 //	includes of other projects
51 //_________________________________________________________________________________________________________________
52 #include <tools/config.hxx>
53 #include <vcl/svapp.hxx>
54 #include <svtools/menuoptions.hxx>
55 #include <svl/solar.hrc>
56 //_________________________________________________________________________________________________________________
57 //	namespace
58 //_________________________________________________________________________________________________________________
59 
60 using namespace ::com::sun::star::uno;
61 using namespace ::com::sun::star::lang;
62 using namespace ::com::sun::star::frame;
63 using namespace ::com::sun::star::beans;
64 
65 // Please look at sfx2/inc/sfxsids.hrc the values are defined there. Due to build dependencies
66 // we cannot include the header file.
67 const sal_uInt16 SID_HELPMENU            = (SID_SFX_START + 410);
68 const sal_uInt16 SID_ONLINE_REGISTRATION = (SID_SFX_START + 1537);
69 
70 namespace framework
71 {
72 
73 AddonMenu::AddonMenu( const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XFrame >& rFrame ) :
74     m_xFrame( rFrame )
75 {
76 }
77 
78 AddonMenu::~AddonMenu()
79 {
80 	for ( sal_uInt16 i = 0; i < GetItemCount(); i++ )
81 	{
82 		if ( GetItemType( i ) != MENUITEM_SEPARATOR )
83 		{
84 			// delete user attributes created with new!
85 			sal_uInt16 nId = GetItemId( i );
86 			MenuConfiguration::Attributes* pUserAttributes = (MenuConfiguration::Attributes*)GetUserValue( nId );
87 			delete pUserAttributes;
88 			delete GetPopupMenu( nId );
89 		}
90 	}
91 }
92 
93 // ------------------------------------------------------------------------
94 
95 // ------------------------------------------------------------------------
96 // Check if command URL string has the unique prefix to identify addon popup menus
97 sal_Bool AddonPopupMenu::IsCommandURLPrefix( const ::rtl::OUString& aCmdURL )
98 {
99 	const char aPrefixCharBuf[] = ADDONSPOPUPMENU_URL_PREFIX_STR;
100 
101 	return aCmdURL.matchAsciiL( aPrefixCharBuf, sizeof( aPrefixCharBuf )-1, 0 );
102 }
103 
104 AddonPopupMenu::AddonPopupMenu( const com::sun::star::uno::Reference< com::sun::star::frame::XFrame >& rFrame ) :
105     AddonMenu( rFrame )
106 {
107 }
108 
109 AddonPopupMenu::~AddonPopupMenu()
110 {
111 }
112 
113 // ------------------------------------------------------------------------
114 
115 static Reference< XModel > GetModelFromFrame( const Reference< XFrame >& rFrame )
116 {
117     // Query for the model to get check the context information
118     Reference< XModel > xModel;
119 	if ( rFrame.is() )
120 	{
121 	    Reference< XController > xController( rFrame->getController(), UNO_QUERY );
122 	    if ( xController.is() )
123 	        xModel = xController->getModel();
124 	}
125 
126     return xModel;
127 }
128 
129 // ------------------------------------------------------------------------
130 
131 sal_Bool AddonMenuManager::HasAddonMenuElements()
132 {
133 	return AddonsOptions().HasAddonsMenu();
134 }
135 
136 sal_Bool AddonMenuManager::HasAddonHelpMenuElements()
137 {
138 	return AddonsOptions().HasAddonsHelpMenu();
139 }
140 
141 // Factory method to create different Add-On menu types
142 PopupMenu* AddonMenuManager::CreatePopupMenuType( MenuType eMenuType, const Reference< XFrame >& rFrame )
143 {
144     if ( eMenuType == ADDON_MENU )
145         return new AddonMenu( rFrame );
146     else if ( eMenuType == ADDON_POPUPMENU )
147         return new AddonPopupMenu( rFrame );
148     else
149         return NULL;
150 }
151 
152 // Create the Add-Ons menu
153 AddonMenu* AddonMenuManager::CreateAddonMenu( const Reference< XFrame >& rFrame )
154 {
155     AddonsOptions aOptions;
156     AddonMenu*  pAddonMenu      = NULL;
157     sal_uInt16      nUniqueMenuId   = ADDONMENU_ITEMID_START;
158 
159 	const Sequence< Sequence< PropertyValue > >& rAddonMenuEntries = aOptions.GetAddonsMenu();
160 	if ( rAddonMenuEntries.getLength() > 0 )
161 	{
162         pAddonMenu = (AddonMenu *)AddonMenuManager::CreatePopupMenuType( ADDON_MENU, rFrame );
163 		Reference< XModel > xModel = GetModelFromFrame( rFrame );
164         AddonMenuManager::BuildMenu( pAddonMenu, ADDON_MENU, MENU_APPEND, nUniqueMenuId, rAddonMenuEntries, rFrame, xModel );
165 
166         // Don't return an empty Add-On menu
167         if ( pAddonMenu->GetItemCount() == 0 )
168         {
169             delete pAddonMenu;
170             pAddonMenu = NULL;
171         }
172     }
173 
174     return pAddonMenu;
175 }
176 
177 // Returns the next insert position from nPos.
178 sal_uInt16 AddonMenuManager::GetNextPos( sal_uInt16 nPos )
179 {
180     return ( nPos == MENU_APPEND ) ? MENU_APPEND : ( nPos+1 );
181 }
182 
183 
184 static sal_uInt16 FindMenuId( Menu* pMenu, const String aCommand )
185 {
186     sal_uInt16 nPos = 0;
187     String aCmd;
188     for ( nPos = 0; nPos < pMenu->GetItemCount(); nPos++ )
189     {
190         sal_uInt16 nId = pMenu->GetItemId( nPos );
191         aCmd = pMenu->GetItemCommand( nId );
192         if ( aCmd == aCommand )
193             return nId;
194     }
195 
196     return USHRT_MAX;
197 }
198 
199 
200 // Merge the Add-Ons help menu items into the given menu bar at a defined pos
201 void AddonMenuManager::MergeAddonHelpMenu( const Reference< XFrame >& rFrame, MenuBar* pMergeMenuBar )
202 {
203     if ( pMergeMenuBar )
204     {
205         PopupMenu* pHelpMenu = pMergeMenuBar->GetPopupMenu( SID_HELPMENU );
206         if ( !pHelpMenu )
207         {
208             sal_uInt16 nId = FindMenuId( pMergeMenuBar, String::CreateFromAscii( ".uno:HelpMenu" ));
209             if ( nId != USHRT_MAX )
210                 pHelpMenu = pMergeMenuBar->GetPopupMenu( nId );
211         }
212 
213         if ( pHelpMenu )
214         {
215             static const char REFERENCECOMMAND_AFTER[]  = ".uno:OnlineRegistrationDlg";
216             static const char REFERENCECOMMAND_BEFORE[] = ".uno:About";
217 
218             // Add-Ons help menu items should be inserted after the "registration" menu item
219             bool   bAddAfter        = true;
220             sal_uInt16 nItemCount       = pHelpMenu->GetItemCount();
221             sal_uInt16 nRegPos          = pHelpMenu->GetItemPos( SID_ONLINE_REGISTRATION );
222             sal_uInt16 nInsPos          = nRegPos;
223             sal_uInt16 nInsSepAfterPos  = MENU_APPEND;
224             sal_uInt16 nUniqueMenuId    = ADDONMENU_ITEMID_START;
225             AddonsOptions aOptions;
226 
227             if ( nRegPos == USHRT_MAX )
228             {
229                 // try to detect the online registration dialog menu item with the command URL
230                 sal_uInt16 nId = FindMenuId( pHelpMenu, String::CreateFromAscii( REFERENCECOMMAND_AFTER ));
231                 nRegPos    = pHelpMenu->GetItemPos( nId );
232                 nInsPos    = nRegPos;
233             }
234 
235             if ( nRegPos == USHRT_MAX )
236             {
237                 // second try:
238                 // try to detect the about menu item with the command URL
239                 sal_uInt16 nId = FindMenuId( pHelpMenu, String::CreateFromAscii( REFERENCECOMMAND_BEFORE ));
240                 nRegPos    = pHelpMenu->GetItemPos( nId );
241                 nInsPos    = nRegPos;
242                 bAddAfter  = false;
243             }
244 
245 	        Sequence< Sequence< PropertyValue > > aAddonSubMenu;
246 	        const Sequence< Sequence< PropertyValue > >& rAddonHelpMenuEntries = aOptions.GetAddonsHelpMenu();
247 
248             nInsPos = bAddAfter ? AddonMenuManager::GetNextPos( nInsPos ) : nInsPos;
249 	        if ( nInsPos < nItemCount && pHelpMenu->GetItemType( nInsPos ) != MENUITEM_SEPARATOR )
250 	            nInsSepAfterPos = nInsPos;
251 
252 			Reference< XModel > xModel = GetModelFromFrame( rFrame );
253 	        AddonMenuManager::BuildMenu( pHelpMenu, ADDON_MENU, nInsPos, nUniqueMenuId, rAddonHelpMenuEntries, rFrame, xModel );
254 
255 	        if ( pHelpMenu->GetItemCount() > nItemCount )
256 	        {
257 	            if ( nInsSepAfterPos < MENU_APPEND )
258 	            {
259 	                nInsSepAfterPos += ( pHelpMenu->GetItemCount() - nItemCount );
260 	                if ( pHelpMenu->GetItemType( nInsSepAfterPos ) != MENUITEM_SEPARATOR )
261 	                    pHelpMenu->InsertSeparator( nInsSepAfterPos );
262 	            }
263 	            if ( nRegPos < MENU_APPEND )
264 	                pHelpMenu->InsertSeparator( nRegPos+1 );
265 	            else
266 	                pHelpMenu->InsertSeparator( nItemCount );
267 	        }
268 	    }
269 	}
270 }
271 
272 // Merge the addon popup menus into the given menu bar at the provided pos.
273 void AddonMenuManager::MergeAddonPopupMenus( const Reference< XFrame >& rFrame,
274 											 const Reference< XModel >& rModel,
275 										     sal_uInt16	              nMergeAtPos,
276 											 MenuBar*             pMergeMenuBar )
277 {
278 	if ( pMergeMenuBar )
279 	{
280 		AddonsOptions	aAddonsOptions;
281 		sal_uInt16			nInsertPos = nMergeAtPos;
282 
283 	    ::rtl::OUString                              aTitle;
284 	    ::rtl::OUString                              aURL;
285 	    ::rtl::OUString                              aTarget;
286 	    ::rtl::OUString                              aImageId;
287 	    ::rtl::OUString                              aContext;
288 	    Sequence< Sequence< PropertyValue > > aAddonSubMenu;
289 	    sal_uInt16                                nUniqueMenuId = ADDONMENU_ITEMID_START;
290 
291 		const Sequence< Sequence< PropertyValue > >&	rAddonMenuEntries = aAddonsOptions.GetAddonsMenuBarPart();
292 		for ( sal_Int32 i = 0; i < rAddonMenuEntries.getLength(); i++ )
293 		{
294             AddonMenuManager::GetMenuEntry( rAddonMenuEntries[i],
295                                             aTitle,
296                                             aURL,
297                                             aTarget,
298                                             aImageId,
299                                             aContext,
300                                             aAddonSubMenu );
301             if ( aTitle.getLength() > 0 &&
302                  aURL.getLength() > 0 &&
303                  aAddonSubMenu.getLength() > 0 &&
304                  AddonMenuManager::IsCorrectContext( rModel, aContext ))
305             {
306                 sal_uInt16          nId             = nUniqueMenuId++;
307                 AddonPopupMenu* pAddonPopupMenu = (AddonPopupMenu *)AddonMenuManager::CreatePopupMenuType( ADDON_POPUPMENU, rFrame );
308 
309                 AddonMenuManager::BuildMenu( pAddonPopupMenu, ADDON_MENU, MENU_APPEND, nUniqueMenuId, aAddonSubMenu, rFrame, rModel );
310 
311                 if ( pAddonPopupMenu->GetItemCount() > 0 )
312                 {
313                     pAddonPopupMenu->SetCommandURL( aURL );
314 				    pMergeMenuBar->InsertItem( nId, aTitle, 0, nInsertPos++ );
315 				    pMergeMenuBar->SetPopupMenu( nId, pAddonPopupMenu );
316 
317 				    // Store the command URL into the VCL menu bar for later identification
318 				    pMergeMenuBar->SetItemCommand( nId, aURL );
319                 }
320                 else
321                     delete pAddonPopupMenu;
322             }
323         }
324     }
325 }
326 
327 // Insert the menu and sub menu entries into pCurrentMenu with the aAddonMenuDefinition provided
328 void AddonMenuManager::BuildMenu( PopupMenu*                            pCurrentMenu,
329                                   MenuType                              nSubMenuType,
330                                   sal_uInt16                                nInsPos,
331                                   sal_uInt16&                               nUniqueMenuId,
332                                   Sequence< Sequence< PropertyValue > > aAddonMenuDefinition,
333                                   const Reference< XFrame >&            rFrame,
334                                   const Reference< XModel >&            rModel )
335 {
336 	Sequence< Sequence< PropertyValue > >	aAddonSubMenu;
337 	sal_Bool                                    bInsertSeparator    = sal_False;
338 	sal_uInt32									i                   = 0;
339 	sal_uInt32                                  nElements           = 0;
340 	sal_uInt32                                  nCount			    = aAddonMenuDefinition.getLength();
341 	AddonsOptions							aAddonsOptions;
342 
343 	::rtl::OUString aTitle;
344 	::rtl::OUString aURL;
345 	::rtl::OUString aTarget;
346 	::rtl::OUString aImageId;
347 	::rtl::OUString aContext;
348 
349 	for ( i = 0; i < nCount; ++i )
350 	{
351 		GetMenuEntry( aAddonMenuDefinition[i], aTitle, aURL, aTarget, aImageId, aContext, aAddonSubMenu );
352 
353 		if ( !IsCorrectContext( rModel, aContext ) || ( !aTitle.getLength() && !aURL.getLength() ))
354 		    continue;
355 
356         if ( aURL == ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "private:separator" )))
357 			bInsertSeparator = sal_True;
358 		else
359 		{
360             PopupMenu* pSubMenu = NULL;
361 			if ( aAddonSubMenu.getLength() > 0 )
362 			{
363 	            pSubMenu = AddonMenuManager::CreatePopupMenuType( nSubMenuType, rFrame );
364 				AddonMenuManager::BuildMenu( pSubMenu, nSubMenuType, MENU_APPEND, nUniqueMenuId, aAddonSubMenu, rFrame, rModel );
365 
366                 // Don't create a menu item for an empty sub menu
367 				if ( pSubMenu->GetItemCount() == 0 )
368 				{
369 				    delete pSubMenu;
370 				    pSubMenu =  NULL;
371 				    continue;
372 				}
373 		    }
374 
375             if ( bInsertSeparator && nElements > 0 )
376             {
377                 // Insert a separator only when we insert a new element afterwards and we
378                 // have already one before us
379                 nElements = 0;
380                 bInsertSeparator = sal_False;
381                 pCurrentMenu->InsertSeparator( nInsPos );
382                 nInsPos = AddonMenuManager::GetNextPos( nInsPos );
383             }
384 
385 			sal_uInt16 nId = nUniqueMenuId++;
386             pCurrentMenu->InsertItem( nId, aTitle, 0, nInsPos );
387             nInsPos = AddonMenuManager::GetNextPos( nInsPos );
388 
389 			++nElements;
390 
391 			// Store values from configuration to the New and Wizard menu entries to enable
392 			// sfx2 based code to support high contrast mode correctly!
393 			pCurrentMenu->SetUserValue( nId, sal_uIntPtr( new MenuConfiguration::Attributes( aTarget, aImageId )) );
394 			pCurrentMenu->SetItemCommand( nId, aURL );
395 
396 			if ( pSubMenu )
397 				pCurrentMenu->SetPopupMenu( nId, pSubMenu );
398 		}
399 	}
400 }
401 
402 // Retrieve the menu entry property values from a sequence
403 void AddonMenuManager::GetMenuEntry( const Sequence< PropertyValue >& rAddonMenuEntry,
404 	                                 ::rtl::OUString& rTitle,
405 	                                 ::rtl::OUString& rURL,
406 	                                 ::rtl::OUString& rTarget,
407 	                                 ::rtl::OUString& rImageId,
408 	                                 ::rtl::OUString& rContext,
409 	                                 Sequence< Sequence< PropertyValue > >&	rAddonSubMenu )
410 {
411 	// Reset submenu parameter
412 	rAddonSubMenu	= Sequence< Sequence< PropertyValue > >();
413 
414 	for ( int i = 0; i < rAddonMenuEntry.getLength(); i++ )
415 	{
416 		::rtl::OUString aMenuEntryPropName = rAddonMenuEntry[i].Name;
417 		if ( aMenuEntryPropName == ADDONSMENUITEM_PROPERTYNAME_URL )
418 			rAddonMenuEntry[i].Value >>= rURL;
419 		else if ( aMenuEntryPropName == ADDONSMENUITEM_PROPERTYNAME_TITLE )
420 			rAddonMenuEntry[i].Value >>= rTitle;
421 		else if ( aMenuEntryPropName == ADDONSMENUITEM_PROPERTYNAME_TARGET )
422 			rAddonMenuEntry[i].Value >>= rTarget;
423 		else if ( aMenuEntryPropName == ADDONSMENUITEM_PROPERTYNAME_IMAGEIDENTIFIER )
424 			rAddonMenuEntry[i].Value >>= rImageId;
425 		else if ( aMenuEntryPropName == ADDONSMENUITEM_PROPERTYNAME_SUBMENU )
426 			rAddonMenuEntry[i].Value >>= rAddonSubMenu;
427 		else if ( aMenuEntryPropName == ADDONSMENUITEM_PROPERTYNAME_CONTEXT )
428 			rAddonMenuEntry[i].Value >>= rContext;
429 	}
430 }
431 
432 // Check if the context string matches the provided xModel context
433 sal_Bool AddonMenuManager::IsCorrectContext( const Reference< XModel >& rModel, const ::rtl::OUString& aContext )
434 {
435 	if ( rModel.is() )
436 	{
437 		Reference< com::sun::star::lang::XServiceInfo > xServiceInfo( rModel, UNO_QUERY );
438 		if ( xServiceInfo.is() )
439 		{
440 			sal_Int32 nIndex = 0;
441 			do
442 			{
443 				::rtl::OUString aToken = aContext.getToken( 0, ',', nIndex );
444 
445 				if ( xServiceInfo->supportsService( aToken ))
446 					return sal_True;
447 			}
448 			while ( nIndex >= 0 );
449 		}
450 	}
451 
452 	return ( aContext.getLength() == 0 );
453 }
454 
455 }
456 
457