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 #include <uielement/menubarmerger.hxx>
32 #include <framework/addonsoptions.hxx>
33 
34 using namespace ::com::sun::star;
35 
36 static const char SEPARATOR_STRING[]               = "private:separator";
37 static const sal_uInt32 SEPARATOR_STRING_LEN       = 17;
38 
39 static const char MERGECOMMAND_ADDAFTER[]          = "AddAfter";
40 static const sal_uInt32 MERGECOMMAND_ADDAFTER_LEN  = 8;
41 static const char MERGECOMMAND_ADDBEFORE[]         = "AddBefore";
42 static const sal_uInt32 MERGECOMMAND_ADDBEFORE_LEN = 9;
43 static const char MERGECOMMAND_REPLACE[]           = "Replace";
44 static const sal_uInt32 MERGECOMMAND_REPLACE_LEN   = 7;
45 static const char MERGECOMMAND_REMOVE[]            = "Remove";
46 static const sal_uInt32 MERGECOMMAND_REMOVE_LEN    = 6;
47 
48 static const char MERGEFALLBACK_ADDPATH[]           = "AddPath";
49 static const char MERGEFALLBACK_ADDPATH_LEN         = 7;
50 static const char MERGEFALLBACK_IGNORE[]            = "Ignore";
51 static const char MERGEFALLBACK_IGNORE_LEN          = 6;
52 
53 
54 namespace framework
55 {
56 
57 /**
58  Check whether a module identifier is part of a context
59  defined by a colon separated list of module identifier.
60 
61  @param
62      rContext
63 
64      Describes a context string list where all contexts
65      are delimited by a colon. For more information about
66      the module identifier used as context strings see the
67      IDL description of com::sun::star::frame::XModuleManager
68 
69   @param
70      rModuleIdentifier
71 
72      A string describing a module identifier. See IDL
73      description of com::sun::star::frame::XModuleManager.
74 
75 */
76 bool MenuBarMerger::IsCorrectContext( const ::rtl::OUString& rContext, const ::rtl::OUString& rModuleIdentifier )
77 {
78     return (( rContext.getLength() == 0 ) || ( rContext.indexOf( rModuleIdentifier ) >= 0 ));
79 }
80 
81 void MenuBarMerger::RetrieveReferencePath(
82     const ::rtl::OUString& rReferencePathString,
83     ::std::vector< ::rtl::OUString >& rReferencePath )
84 {
85     const sal_Char aDelimiter = '\\';
86 
87     rReferencePath.clear();
88     sal_Int32 nIndex( 0 );
89 	do
90 	{
91         ::rtl::OUString aToken = rReferencePathString.getToken( 0, aDelimiter, nIndex );
92         if ( aToken.getLength() > 0 )
93             rReferencePath.push_back( aToken );
94     }
95 	while ( nIndex >= 0 );
96 }
97 
98 ReferencePathInfo MenuBarMerger::FindReferencePath(
99     const ::std::vector< ::rtl::OUString >& rReferencePath,
100     Menu* pMenu )
101 {
102     sal_uInt32       i( 0 );
103     const sal_uInt32 nCount( rReferencePath.size() );
104     Menu*            pCurrMenu( pMenu );
105     RPResultInfo     eResult( RP_OK );
106 
107     sal_Int32  nLevel( - 1 );
108     sal_uInt16 nPos( MENU_ITEM_NOTFOUND );
109     do
110     {
111         ++nLevel;
112         ::rtl::OUString aCmd( rReferencePath[i] );
113 
114         if ( i == nCount-1 )
115         {
116             // Check last reference path element. Must be a leave (menu item).
117             sal_uInt16 nTmpPos = FindMenuItem( aCmd, pCurrMenu );
118             if ( nTmpPos != MENU_ITEM_NOTFOUND )
119                 nPos = nTmpPos;
120             eResult = ( nTmpPos != MENU_ITEM_NOTFOUND ) ? RP_OK : RP_MENUITEM_NOT_FOUND;
121         }
122         else
123         {
124             // Check reference path element. Must be a node (popup menu)!
125             sal_uInt16 nTmpPos = FindMenuItem( aCmd, pCurrMenu );
126             if ( nTmpPos != MENU_ITEM_NOTFOUND )
127             {
128                 sal_uInt16 nItemId = pCurrMenu->GetItemId( nTmpPos );
129                 Menu* pTmpMenu     = pCurrMenu->GetPopupMenu( nItemId );
130                 if ( pTmpMenu != 0 )
131                     pCurrMenu = pTmpMenu;
132                 else
133                 {
134                     nPos    = nTmpPos;
135                     eResult = RP_MENUITEM_INSTEAD_OF_POPUPMENU_FOUND;
136                 }
137             }
138             else
139                 eResult = RP_POPUPMENU_NOT_FOUND;
140         }
141         i++;
142     }
143     while (( pCurrMenu != 0 ) && ( i < nCount ) && ( eResult == RP_OK ));
144 
145     ReferencePathInfo aResult;
146     aResult.pPopupMenu = pCurrMenu;
147     aResult.nPos       = nPos;
148     aResult.nLevel     = nLevel;
149     aResult.eResult    = eResult;
150 
151     return aResult;
152 }
153 
154 sal_uInt16 MenuBarMerger::FindMenuItem( const ::rtl::OUString& rCmd, Menu* pCurrMenu )
155 {
156     for ( sal_uInt16 i = 0; i < pCurrMenu->GetItemCount(); i++ )
157     {
158         const sal_uInt16 nItemId = pCurrMenu->GetItemId( i );
159         if ( nItemId > 0 )
160         {
161             if ( rCmd == ::rtl::OUString( pCurrMenu->GetItemCommand( nItemId )))
162                 return i;
163         }
164     }
165 
166     return MENU_ITEM_NOTFOUND;
167 }
168 
169 bool MenuBarMerger::CreateSubMenu(
170     Menu*                     pSubMenu,
171     sal_uInt16&               nItemId,
172     const ::rtl::OUString&    rModuleIdentifier,
173     const AddonMenuContainer& rAddonSubMenu )
174 {
175     const sal_uInt32 nSize = rAddonSubMenu.size();
176     for ( sal_uInt32 i = 0; i < nSize; i++ )
177     {
178         const AddonMenuItem& rMenuItem = rAddonSubMenu[i];
179 
180         if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier ))
181         {
182             if ( rMenuItem.aURL.equalsAsciiL( SEPARATOR_STRING, SEPARATOR_STRING_LEN ))
183             {
184                 pSubMenu->InsertSeparator( MENU_APPEND );
185             }
186             else
187             {
188                 pSubMenu->InsertItem( nItemId, rMenuItem.aTitle, 0, MENU_APPEND );
189                 pSubMenu->SetItemCommand( nItemId, rMenuItem.aURL );
190                 if ( !rMenuItem.aSubMenu.empty() )
191                 {
192                     PopupMenu* pPopupMenu = new PopupMenu();
193                     pSubMenu->SetPopupMenu( nItemId, pPopupMenu );
194                     ++nItemId;
195 
196                     CreateSubMenu( pPopupMenu, nItemId, rModuleIdentifier, rMenuItem.aSubMenu );
197                 }
198                 else
199                     ++nItemId;
200             }
201         }
202     }
203 
204     return true;
205 }
206 
207 bool MenuBarMerger::MergeMenuItems(
208     Menu*                     pMenu,
209     sal_uInt16                nPos,
210     sal_uInt16                nModIndex,
211     sal_uInt16&               nItemId,
212     const ::rtl::OUString&    rModuleIdentifier,
213     const AddonMenuContainer& rAddonMenuItems )
214 {
215     sal_uInt16       nIndex( 0 );
216     const sal_uInt32 nSize = rAddonMenuItems.size();
217     for ( sal_uInt32 i = 0; i < nSize; i++ )
218     {
219         const AddonMenuItem& rMenuItem = rAddonMenuItems[i];
220 
221         if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier ))
222         {
223             if ( rMenuItem.aURL.equalsAsciiL( SEPARATOR_STRING, SEPARATOR_STRING_LEN ))
224             {
225                 pMenu->InsertSeparator( nPos+nModIndex+nIndex );
226             }
227             else
228             {
229                 pMenu->InsertItem( nItemId, rMenuItem.aTitle, 0, nPos+nModIndex+nIndex );
230                 pMenu->SetItemCommand( nItemId, rMenuItem.aURL );
231                 if ( !rMenuItem.aSubMenu.empty() )
232                 {
233                     PopupMenu* pSubMenu = new PopupMenu();
234                     pMenu->SetPopupMenu( nItemId, pSubMenu );
235                     ++nItemId;
236 
237                     CreateSubMenu( pSubMenu, nItemId, rModuleIdentifier, rMenuItem.aSubMenu );
238                 }
239                 else
240                     ++nItemId;
241             }
242             ++nIndex;
243         }
244     }
245 
246     return true;
247 }
248 
249 bool MenuBarMerger::ReplaceMenuItem(
250     Menu*                     pMenu,
251     sal_uInt16                nPos,
252     sal_uInt16&               rItemId,
253     const ::rtl::OUString&    rModuleIdentifier,
254     const AddonMenuContainer& rAddonMenuItems )
255 {
256     // There is no replace available. Therfore we first have to
257     // remove the old menu entry,
258     pMenu->RemoveItem( nPos );
259 
260     return MergeMenuItems( pMenu, nPos, 0, rItemId, rModuleIdentifier, rAddonMenuItems );
261 }
262 
263 bool MenuBarMerger::RemoveMenuItems(
264     Menu*                     pMenu,
265     sal_uInt16                nPos,
266     const ::rtl::OUString&    rMergeCommandParameter )
267 {
268     const sal_uInt16 nParam( sal_uInt16( rMergeCommandParameter.toInt32() ));
269     sal_uInt16       nCount( 1 );
270 
271     nCount = std::max( nParam, nCount );
272 
273     sal_uInt16 i = 0;
274     while (( nPos < pMenu->GetItemCount() ) && ( i < nCount ))
275     {
276         pMenu->RemoveItem( nPos );
277         ++i;
278     }
279 
280     return true;
281 }
282 
283 bool MenuBarMerger::ProcessMergeOperation(
284     Menu*                     pMenu,
285     sal_uInt16                nPos,
286     sal_uInt16&               nItemId,
287     const ::rtl::OUString&    rMergeCommand,
288     const ::rtl::OUString&    rMergeCommandParameter,
289     const ::rtl::OUString&    rModuleIdentifier,
290     const AddonMenuContainer& rAddonMenuItems )
291 {
292     sal_uInt16 nModIndex( 0 );
293 
294     if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_ADDBEFORE, MERGECOMMAND_ADDBEFORE_LEN ))
295     {
296         nModIndex = 0;
297         return MergeMenuItems( pMenu, nPos, nModIndex, nItemId, rModuleIdentifier, rAddonMenuItems );
298     }
299     else if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_ADDAFTER, MERGECOMMAND_ADDAFTER_LEN ))
300     {
301         nModIndex = 1;
302         return MergeMenuItems( pMenu, nPos, nModIndex, nItemId, rModuleIdentifier, rAddonMenuItems );
303     }
304     else if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_REPLACE, MERGECOMMAND_REPLACE_LEN ))
305     {
306         return ReplaceMenuItem( pMenu, nPos, nItemId, rModuleIdentifier, rAddonMenuItems );
307     }
308     else if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_REMOVE, MERGECOMMAND_REMOVE_LEN ))
309     {
310         return RemoveMenuItems( pMenu, nPos, rMergeCommandParameter );
311     }
312 
313     return false;
314 }
315 
316 bool MenuBarMerger::ProcessFallbackOperation(
317     const ReferencePathInfo&                aRefPathInfo,
318     sal_uInt16&                             rItemId,
319     const ::rtl::OUString&                  rMergeCommand,
320     const ::rtl::OUString&                  rMergeFallback,
321     const ::std::vector< ::rtl::OUString >& rReferencePath,
322     const ::rtl::OUString&                  rModuleIdentifier,
323     const AddonMenuContainer&               rAddonMenuItems )
324 {
325     if (( rMergeFallback.equalsAsciiL( MERGEFALLBACK_IGNORE, MERGEFALLBACK_IGNORE_LEN )) ||
326         ( rMergeCommand.equalsAsciiL(  MERGECOMMAND_REPLACE, MERGECOMMAND_REPLACE_LEN )) ||
327         ( rMergeCommand.equalsAsciiL(  MERGECOMMAND_REMOVE, MERGECOMMAND_REMOVE_LEN   )) )
328     {
329         return true;
330     }
331     else if ( rMergeFallback.equalsAsciiL( MERGEFALLBACK_ADDPATH, MERGEFALLBACK_ADDPATH_LEN ))
332     {
333         Menu*            pCurrMenu( aRefPathInfo.pPopupMenu );
334         sal_Int32        nLevel( aRefPathInfo.nLevel );
335         const sal_Int32  nSize( rReferencePath.size() );
336         bool             bFirstLevel( true );
337 
338         while ( nLevel < nSize )
339         {
340             if ( nLevel == nSize-1 )
341             {
342                 const sal_uInt32 nCount = rAddonMenuItems.size();
343                 for ( sal_uInt32 i = 0; i < nCount; ++i )
344                 {
345                     const AddonMenuItem& rMenuItem = rAddonMenuItems[i];
346                     if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier ))
347                     {
348                         if ( rMenuItem.aURL.equalsAsciiL( SEPARATOR_STRING, SEPARATOR_STRING_LEN ))
349                             pCurrMenu->InsertSeparator( MENU_APPEND );
350                         else
351                         {
352                             pCurrMenu->InsertItem( rItemId, rMenuItem.aTitle, 0, MENU_APPEND );
353                             pCurrMenu->SetItemCommand( rItemId, rMenuItem.aURL );
354                             ++rItemId;
355                         }
356                     }
357                 }
358             }
359             else
360             {
361                 const ::rtl::OUString aCmd( rReferencePath[nLevel] );
362 
363                 sal_uInt16 nInsPos( MENU_APPEND );
364                 PopupMenu* pPopupMenu( new PopupMenu );
365 
366                 if ( bFirstLevel && ( aRefPathInfo.eResult == RP_MENUITEM_INSTEAD_OF_POPUPMENU_FOUND ))
367                 {
368                     // special case: menu item without popup
369                     nInsPos = aRefPathInfo.nPos;
370                     sal_uInt16 nSetItemId = pCurrMenu->GetItemId( nInsPos );
371                     pCurrMenu->SetItemCommand( nSetItemId, aCmd );
372                     pCurrMenu->SetPopupMenu( nSetItemId, pPopupMenu );
373                 }
374                 else
375                 {
376                     // normal case: insert a new item with popup
377                     pCurrMenu->InsertItem( rItemId, ::rtl::OUString(), 0, MENU_APPEND );
378                     pCurrMenu->SetItemCommand( rItemId, aCmd );
379                     pCurrMenu->SetPopupMenu( rItemId, pPopupMenu );
380                 }
381 
382                 pCurrMenu = pPopupMenu;
383                 ++rItemId;
384                 bFirstLevel = false;
385             }
386             ++nLevel;
387         }
388         return true;
389     }
390 
391     return false;
392 }
393 
394 void MenuBarMerger::GetMenuEntry(
395     const uno::Sequence< beans::PropertyValue >& rAddonMenuEntry,
396     AddonMenuItem&                               rAddonMenuItem )
397 {
398 	// Reset submenu member
399     rAddonMenuItem.aSubMenu.clear();
400 
401 	for ( sal_Int32 i = 0; i < rAddonMenuEntry.getLength(); i++ )
402 	{
403         ::rtl::OUString aMenuEntryPropName = rAddonMenuEntry[i].Name;
404 		if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_URL, ADDONSMENUITEM_URL_LEN ))
405 			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aURL;
406 		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_TITLE, ADDONSMENUITEM_TITLE_LEN ))
407 			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aTitle;
408 		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_TARGET, ADDONSMENUITEM_TARGET_LEN ))
409 			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aTarget;
410 		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_SUBMENU, ADDONSMENUITEM_SUBMENU_LEN ))
411         {
412             uno::Sequence< uno::Sequence< beans::PropertyValue > > aSubMenu;
413             rAddonMenuEntry[i].Value >>= aSubMenu;
414             GetSubMenu( aSubMenu, rAddonMenuItem.aSubMenu );
415         }
416 		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_CONTEXT, ADDONSMENUITEM_CONTEXT_LEN ))
417 			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aContext;
418 		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_IMAGEIDENTIFIER, ADDONSMENUITEM_IMAGEIDENTIFIER_LEN ))
419 			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aImageId;
420 	}
421 }
422 
423 void MenuBarMerger::GetSubMenu(
424     const uno::Sequence< uno::Sequence< beans::PropertyValue > >& rSubMenuEntries,
425     AddonMenuContainer&                                           rSubMenu )
426 {
427     rSubMenu.clear();
428 
429     const sal_Int32 nCount = rSubMenuEntries.getLength();
430     rSubMenu.reserve(rSubMenu.size() + nCount);
431     for ( sal_Int32 i = 0; i < nCount; i++ )
432     {
433         const uno::Sequence< beans::PropertyValue >& rMenuEntry = rSubMenuEntries[ i ];
434 
435         AddonMenuItem aMenuItem;
436         GetMenuEntry( rMenuEntry, aMenuItem );
437         rSubMenu.push_back( aMenuItem );
438     }
439 }
440 
441 } // namespace framework
442