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