Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/framework/source/fwe/classes/addonmenu.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
22
#include <climits>
23
24
#include <addonmenu.hxx>
25
#include <framework/addonsoptions.hxx>
26
#include <menuconfiguration.hxx>
27
28
#include <com/sun/star/uno/Reference.hxx>
29
#include <com/sun/star/uno/Sequence.hxx>
30
#include <com/sun/star/beans/PropertyValue.hpp>
31
32
#include <vcl/commandinfoprovider.hxx>
33
#include <vcl/menu.hxx>
34
35
using namespace ::com::sun::star::uno;
36
using namespace ::com::sun::star::frame;
37
using namespace ::com::sun::star::beans;
38
39
namespace framework
40
{
41
42
bool AddonMenuManager::HasAddonMenuElements()
43
0
{
44
0
    return AddonsOptions().HasAddonsMenu();
45
0
}
46
47
// Create the Add-Ons menu
48
VclPtr<PopupMenu> AddonMenuManager::CreateAddonMenu( const Reference< XFrame >& rFrame )
49
0
{
50
0
    AddonsOptions     aOptions;
51
0
    VclPtr<PopupMenu> pAddonMenu;
52
53
0
    const Sequence< Sequence< PropertyValue > >& rAddonMenuEntries = aOptions.GetAddonsMenu();
54
0
    if ( rAddonMenuEntries.hasElements() )
55
0
    {
56
0
        sal_uInt16  nUniqueMenuId   = ADDONMENU_ITEMID_START;
57
0
        pAddonMenu = VclPtr<PopupMenu>::Create();
58
0
        OUString aModuleIdentifier = vcl::CommandInfoProvider::GetModuleIdentifier( rFrame );
59
0
        AddonMenuManager::BuildMenu( pAddonMenu, MENU_APPEND, nUniqueMenuId, rAddonMenuEntries, rFrame, aModuleIdentifier );
60
61
        // Don't return an empty Add-On menu
62
0
        if ( pAddonMenu->GetItemCount() == 0 )
63
0
        {
64
0
            pAddonMenu.disposeAndClear();
65
0
        }
66
0
    }
67
68
0
    return pAddonMenu;
69
0
}
70
71
// Returns the next insert position from nPos.
72
sal_uInt16 AddonMenuManager::GetNextPos( sal_uInt16 nPos )
73
0
{
74
0
    return ( nPos == MENU_APPEND ) ? MENU_APPEND : ( nPos+1 );
75
0
}
76
77
static sal_uInt16 FindMenuId( Menu const * pMenu, std::u16string_view aCommand )
78
0
{
79
0
    sal_uInt16 nPos = 0;
80
0
    OUString aCmd;
81
0
    for ( nPos = 0; nPos < pMenu->GetItemCount(); nPos++ )
82
0
    {
83
0
        sal_uInt16 nId = pMenu->GetItemId( nPos );
84
0
        aCmd = pMenu->GetItemCommand( nId );
85
0
        if ( aCmd == aCommand )
86
0
            return nId;
87
0
    }
88
89
0
    return USHRT_MAX;
90
0
}
91
92
// Merge the Add-Ons help menu items into the given menu bar at a defined pos
93
void AddonMenuManager::MergeAddonHelpMenu( const Reference< XFrame >& rFrame,
94
                                           MenuBar const * pMergeMenuBar )
95
0
{
96
0
    if ( !pMergeMenuBar )
97
0
        return;
98
99
0
    PopupMenu* pHelpMenu(nullptr);
100
0
    sal_uInt16 nId = FindMenuId(pMergeMenuBar, u".uno:HelpMenu");
101
0
    if ( nId != USHRT_MAX )
102
0
        pHelpMenu = pMergeMenuBar->GetPopupMenu( nId );
103
104
0
    if ( !pHelpMenu )
105
0
        return;
106
107
    // Add-Ons help menu items should be inserted after the "registration" menu item
108
0
    sal_uInt16 nItemCount       = pHelpMenu->GetItemCount();
109
0
    sal_uInt16 nInsSepAfterPos  = MENU_APPEND;
110
0
    sal_uInt16 nUniqueMenuId    = ADDONMENU_ITEMID_START;
111
0
    AddonsOptions aOptions;
112
113
    // try to detect the about menu item with the command URL
114
0
    nId = FindMenuId(pHelpMenu, u".uno:About");
115
0
    sal_uInt16 nInsPos = pHelpMenu->GetItemPos( nId );
116
117
0
    const Sequence< Sequence< PropertyValue > >& rAddonHelpMenuEntries = aOptions.GetAddonsHelpMenu();
118
119
0
    if ( nInsPos < nItemCount && pHelpMenu->GetItemType( nInsPos ) != MenuItemType::SEPARATOR )
120
0
        nInsSepAfterPos = nInsPos;
121
122
0
    OUString aModuleIdentifier = vcl::CommandInfoProvider::GetModuleIdentifier(rFrame);
123
0
    AddonMenuManager::BuildMenu( pHelpMenu, nInsPos, nUniqueMenuId, rAddonHelpMenuEntries, rFrame, aModuleIdentifier );
124
125
0
    if ( pHelpMenu->GetItemCount() > nItemCount )
126
0
    {
127
0
        if ( nInsSepAfterPos < MENU_APPEND )
128
0
        {
129
0
            nInsSepAfterPos += ( pHelpMenu->GetItemCount() - nItemCount );
130
0
            if ( pHelpMenu->GetItemType( nInsSepAfterPos ) != MenuItemType::SEPARATOR )
131
0
                pHelpMenu->InsertSeparator({}, nInsSepAfterPos);
132
0
        }
133
0
        pHelpMenu->InsertSeparator({}, nItemCount);
134
0
    }
135
0
}
136
137
// Merge the addon popup menus into the given menu bar at the provided pos.
138
void AddonMenuManager::MergeAddonPopupMenus( const Reference< XFrame >& rFrame,
139
                                             sal_uInt16               nMergeAtPos,
140
                                             MenuBar*             pMergeMenuBar )
141
0
{
142
0
    if ( !pMergeMenuBar )
143
0
        return;
144
145
0
    AddonsOptions   aAddonsOptions;
146
0
    sal_uInt16          nInsertPos = nMergeAtPos;
147
148
0
    OUString                              aTitle;
149
0
    OUString                              aURL;
150
0
    OUString                              aTarget;
151
0
    OUString                              aContext;
152
0
    Sequence< Sequence< PropertyValue > > aAddonSubMenu;
153
0
    sal_uInt16                            nUniqueMenuId = ADDONMENU_ITEMID_START;
154
155
0
    OUString aModuleIdentifier = vcl::CommandInfoProvider::GetModuleIdentifier(rFrame);
156
157
0
    const Sequence< Sequence< PropertyValue > >&    rAddonMenuEntries = aAddonsOptions.GetAddonsMenuBarPart();
158
0
    for ( const Sequence< PropertyValue >& rEntry : rAddonMenuEntries )
159
0
    {
160
0
        AddonMenuManager::GetMenuEntry( rEntry,
161
0
                                        aTitle,
162
0
                                        aURL,
163
0
                                        aTarget,
164
0
                                        aContext,
165
0
                                        aAddonSubMenu );
166
0
        if ( !aTitle.isEmpty() &&
167
0
             !aURL.isEmpty()   &&
168
0
             aAddonSubMenu.hasElements() &&
169
0
             AddonMenuManager::IsCorrectContext( aModuleIdentifier, aContext ))
170
0
        {
171
0
            sal_uInt16          nId             = nUniqueMenuId++;
172
0
            VclPtrInstance<PopupMenu> pAddonPopupMenu;
173
174
0
            AddonMenuManager::BuildMenu( pAddonPopupMenu, MENU_APPEND, nUniqueMenuId, aAddonSubMenu, rFrame, aModuleIdentifier );
175
176
0
            if ( pAddonPopupMenu->GetItemCount() > 0 )
177
0
            {
178
0
                pMergeMenuBar->InsertItem( nId, aTitle, MenuItemBits::NONE, {}, nInsertPos++);
179
0
                pMergeMenuBar->SetPopupMenu( nId, pAddonPopupMenu );
180
181
                // Store the command URL into the VCL menu bar for later identification
182
0
                pMergeMenuBar->SetItemCommand( nId, aURL );
183
0
            }
184
0
            else
185
0
                pAddonPopupMenu.disposeAndClear();
186
0
        }
187
0
    }
188
0
}
189
190
// Insert the menu and sub menu entries into pCurrentMenu with the aAddonMenuDefinition provided
191
void AddonMenuManager::BuildMenu( PopupMenu*                            pCurrentMenu,
192
                                  sal_uInt16                            nInsPos,
193
                                  sal_uInt16&                           nUniqueMenuId,
194
                                  const Sequence< Sequence< PropertyValue > >& aAddonMenuDefinition,
195
                                  const Reference< XFrame >&            rFrame,
196
                                  const OUString&               rModuleIdentifier )
197
0
{
198
0
    Sequence< Sequence< PropertyValue > >   aAddonSubMenu;
199
0
    bool                                    bInsertSeparator    = false;
200
0
    sal_uInt32                              i                   = 0;
201
0
    sal_uInt32                              nElements           = 0;
202
0
    sal_uInt32                              nCount              = aAddonMenuDefinition.getLength();
203
204
0
    OUString aTitle;
205
0
    OUString aURL;
206
0
    OUString aTarget;
207
0
    OUString aContext;
208
209
0
    for ( i = 0; i < nCount; ++i )
210
0
    {
211
0
        GetMenuEntry( aAddonMenuDefinition[i], aTitle, aURL, aTarget, aContext, aAddonSubMenu );
212
213
0
        if ( !IsCorrectContext( rModuleIdentifier, aContext ) || ( aTitle.isEmpty() && aURL.isEmpty() ))
214
0
            continue;
215
216
0
        if ( aURL == "private:separator" )
217
0
            bInsertSeparator = true;
218
0
        else
219
0
        {
220
0
            VclPtr<PopupMenu> pSubMenu;
221
0
            if ( aAddonSubMenu.hasElements() )
222
0
            {
223
0
                pSubMenu = VclPtr<PopupMenu>::Create();
224
0
                AddonMenuManager::BuildMenu( pSubMenu, MENU_APPEND, nUniqueMenuId, aAddonSubMenu, rFrame, rModuleIdentifier );
225
226
                // Don't create a menu item for an empty sub menu
227
0
                if ( pSubMenu->GetItemCount() == 0 )
228
0
                {
229
0
                    pSubMenu.disposeAndClear();
230
0
                    continue;
231
0
                }
232
0
            }
233
234
0
            if ( bInsertSeparator && nElements > 0 )
235
0
            {
236
                // Insert a separator only when we insert a new element afterwards and we
237
                // have already one before us
238
0
                nElements = 0;
239
0
                bInsertSeparator = false;
240
0
                pCurrentMenu->InsertSeparator({}, nInsPos);
241
0
                nInsPos = AddonMenuManager::GetNextPos( nInsPos );
242
0
            }
243
244
0
            sal_uInt16 nId = nUniqueMenuId++;
245
0
            pCurrentMenu->InsertItem(nId, aTitle, MenuItemBits::NONE, {}, nInsPos);
246
0
            nInsPos = AddonMenuManager::GetNextPos( nInsPos );
247
248
0
            ++nElements;
249
250
0
            void* nAttributePtr = MenuAttributes::CreateAttribute(aTarget, OUString());
251
0
            pCurrentMenu->SetUserValue(nId, nAttributePtr, MenuAttributes::ReleaseAttribute);
252
0
            pCurrentMenu->SetItemCommand( nId, aURL );
253
254
0
            if ( pSubMenu )
255
0
                pCurrentMenu->SetPopupMenu( nId, pSubMenu );
256
0
        }
257
0
    }
258
0
}
259
260
// Retrieve the menu entry property values from a sequence
261
void AddonMenuManager::GetMenuEntry( const Sequence< PropertyValue >& rAddonMenuEntry,
262
                                     OUString& rTitle,
263
                                     OUString& rURL,
264
                                     OUString& rTarget,
265
                                     OUString& rContext,
266
                                     Sequence< Sequence< PropertyValue > >& rAddonSubMenu )
267
0
{
268
    // Reset submenu parameter
269
0
    rAddonSubMenu   = Sequence< Sequence< PropertyValue > >();
270
271
0
    for ( const PropertyValue& rEntry : rAddonMenuEntry )
272
0
    {
273
0
        OUString aMenuEntryPropName = rEntry.Name;
274
0
        if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_URL )
275
0
            rEntry.Value >>= rURL;
276
0
        else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_TITLE )
277
0
            rEntry.Value >>= rTitle;
278
0
        else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_TARGET )
279
0
            rEntry.Value >>= rTarget;
280
0
        else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_SUBMENU )
281
0
            rEntry.Value >>= rAddonSubMenu;
282
0
        else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_CONTEXT )
283
0
            rEntry.Value >>= rContext;
284
0
    }
285
0
}
286
287
// Check if the context string matches the provided xModel context
288
bool AddonMenuManager::IsCorrectContext( std::u16string_view rModuleIdentifier, std::u16string_view rContext )
289
0
{
290
0
    if ( rContext.empty() )
291
0
        return true;
292
293
0
    if ( !rModuleIdentifier.empty() )
294
0
    {
295
0
        return rContext.find( rModuleIdentifier ) != std::u16string_view::npos;
296
0
    }
297
298
0
    return false;
299
0
}
300
301
}
302
303
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */