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#include <osl/diagnose.h>
22
23#include <vcl/window.hxx>
24
25#include <osx/salinst.h>
26#include <osx/saldata.hxx>
27#include <osx/salframe.h>
28#include <osx/salmenu.h>
29#include <osx/salnsmenu.h>
30
31@implementation SalNSMenu
32-(id)initWithMenu: (AquaSalMenu*)pMenu
33{
34    mpMenu = pMenu;
35    return [super initWithTitle: [NSString string]];
36}
37
38-(void)menuNeedsUpdate: (NSMenu*)pMenu
39{
40    SolarMutexGuard aGuard;
41
42    if( mpMenu )
43    {
44        const AquaSalFrame* pFrame = mpMenu->getFrame();
45        if( pFrame && AquaSalFrame::isAlive( pFrame ) )
46        {
47            SalMenuEvent aMenuEvt;
48            aMenuEvt.mnId   = 0;
49            aMenuEvt.mpMenu = mpMenu->mpVCLMenu;
50            if( aMenuEvt.mpMenu )
51            {
52                pFrame->CallCallback(SalEvent::MenuActivate, &aMenuEvt);
53                pFrame->CallCallback(SalEvent::MenuDeactivate, &aMenuEvt);
54            }
55            else
56                OSL_FAIL( "unconnected menu" );
57        }
58        else if( mpMenu->mpVCLMenu )
59        {
60            mpMenu->mpVCLMenu->Activate();
61            mpMenu->mpVCLMenu->Deactivate();
62
63            // Hide disabled items
64            NSArray* elements = [pMenu itemArray];
65            NSEnumerator* it = [elements objectEnumerator];
66            id element;
67            while ( ( element = [it nextObject] ) != nil )
68            {
69                NSMenuItem* item = static_cast< NSMenuItem* >( element );
70                if( ![item isSeparatorItem] )
71                    [item setHidden: ![item isEnabled]];
72            }
73        }
74    }
75}
76
77-(void)setSalMenu: (AquaSalMenu*)pMenu
78{
79    mpMenu = pMenu;
80}
81@end
82
83@implementation SalNSMenuItem
84-(id)initWithMenuItem: (AquaSalMenuItem*)pMenuItem
85{
86    mpMenuItem = pMenuItem;
87    id ret = [super initWithTitle: [NSString string]
88                    action: @selector(menuItemTriggered:)
89                    keyEquivalent: [NSString string]];
90    [ret setTarget: self];
91    return ret;
92}
93-(void)menuItemTriggered: (id)aSender
94{
95    (void)aSender;
96    SolarMutexGuard aGuard;
97
98    // tdf#49853 Keyboard shortcuts are also handled by the menu bar, but at least some of them
99    // must still end up in the view. This is necessary to handle common edit actions in docked
100    // windows (e.g. in toolbar fields).
101    NSEvent* pEvent = [NSApp currentEvent];
102SAL_WNODEPRECATED_DECLARATIONS_PUSH
103        // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
104        // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
105        // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
106        // 'NSKeyDown' is deprecated: first deprecated in macOS 10.12
107        // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
108    if( pEvent && [pEvent type] == NSKeyDown )
109    {
110        unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
111        NSString* charactersIgnoringModifiers = [pEvent charactersIgnoringModifiers];
112        if( nModMask == NSCommandKeyMask &&
113          ( [charactersIgnoringModifiers isEqualToString: @"v"] ||
114            [charactersIgnoringModifiers isEqualToString: @"c"] ||
115            [charactersIgnoringModifiers isEqualToString: @"x"] ||
116            [charactersIgnoringModifiers isEqualToString: @"a"] ||
117            [charactersIgnoringModifiers isEqualToString: @"z"] ) )
118        {
119            [[[NSApp keyWindow] contentView] keyDown: pEvent];
120            return;
121        }
122    }
123SAL_WNODEPRECATED_DECLARATIONS_POP
124
125    const AquaSalFrame* pFrame = mpMenuItem->mpParentMenu ? mpMenuItem->mpParentMenu->getFrame() : nullptr;
126    if( pFrame && AquaSalFrame::isAlive( pFrame ) && ! pFrame->GetWindow()->IsInModalMode() )
127    {
128        SalMenuEvent aMenuEvt( mpMenuItem->mnId, mpMenuItem->mpVCLMenu );
129        pFrame->CallCallback(SalEvent::MenuCommand, &aMenuEvt);
130    }
131    else if( mpMenuItem->mpVCLMenu )
132    {
133        // if an item from submenu was selected. the corresponding Window does not exist because
134        // we use native popup menus, so we have to set the selected menuitem directly
135        // incidentally this of course works for top level popup menus, too
136        PopupMenu * pPopupMenu = dynamic_cast<PopupMenu *>(mpMenuItem->mpVCLMenu.get());
137        if( pPopupMenu )
138        {
139            // FIXME: revise this ugly code
140
141            // select handlers in vcl are dispatch on the original menu
142            // if not consumed by the select handler of the current menu
143            // however since only the starting menu ever came into Execute
144            // the hierarchy is not build up. Workaround this by getting
145            // the menu it should have been
146
147            // get started from hierarchy in vcl menus
148            AquaSalMenu* pParentMenu = mpMenuItem->mpParentMenu;
149            Menu* pCurMenu = mpMenuItem->mpVCLMenu;
150            while( pParentMenu && pParentMenu->mpVCLMenu )
151            {
152                pCurMenu = pParentMenu->mpVCLMenu;
153                pParentMenu = pParentMenu->mpParentSalMenu;
154            }
155
156            pPopupMenu->SetSelectedEntry( mpMenuItem->mnId );
157            pPopupMenu->ImplSelectWithStart( pCurMenu );
158        }
159        else
160            OSL_FAIL( "menubar item without frame !" );
161    }
162}
163@end
164
165@implementation OOStatusItemView
166-(void)drawRect: (NSRect)aRect
167{
168    NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
169    [pContext saveGraphicsState];
170SAL_WNODEPRECATED_DECLARATIONS_PUSH
171        // "'drawStatusBarBackgroundInRect:withHighlight:' is deprecated: first deprecated in macOS
172        // 10.14 - Use the standard button instead which handles highlight drawing, making this
173        // method obsolete"
174    [SalData::getStatusItem() drawStatusBarBackgroundInRect: aRect withHighlight: NO];
175SAL_WNODEPRECATED_DECLARATIONS_POP
176    if( AquaSalMenu::pCurrentMenuBar )
177    {
178        const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
179        NSRect aFrame = [self frame];
180        NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
181        for( size_t i = 0; i < rButtons.size(); ++i )
182        {
183            const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
184            const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
185            aImgRect.origin.y = floor((aFrame.size.height - aFromRect.size.height)/2);
186            aImgRect.size = aFromRect.size;
187            if( rButtons[i].mpNSImage )
188SAL_WNODEPRECATED_DECLARATIONS_PUSH
189    // 'NSCompositeSourceOver' is deprecated: first deprecated in macOS 10.12
190                [rButtons[i].mpNSImage drawInRect: aImgRect fromRect: aFromRect operation: NSCompositeSourceOver fraction: 1.0];
191SAL_WNODEPRECATED_DECLARATIONS_POP
192            aImgRect.origin.x += aFromRect.size.width + 2;
193        }
194    }
195    [pContext restoreGraphicsState];
196}
197
198-(void)mouseUp: (NSEvent *)pEvent
199{
200    /* check if button goes up inside one of our status buttons */
201    if( AquaSalMenu::pCurrentMenuBar )
202    {
203        const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
204        NSRect aFrame = [self frame];
205        NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
206        NSPoint aMousePt = [pEvent locationInWindow];
207        for( size_t i = 0; i < rButtons.size(); ++i )
208        {
209            const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
210            const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
211            aImgRect.origin.y = (aFrame.size.height - aFromRect.size.height)/2;
212            aImgRect.size = aFromRect.size;
213            if( aMousePt.x >= aImgRect.origin.x && aMousePt.x <= (aImgRect.origin.x+aImgRect.size.width) &&
214                aMousePt.y >= aImgRect.origin.y && aMousePt.y <= (aImgRect.origin.y+aImgRect.size.height) )
215            {
216                if( AquaSalMenu::pCurrentMenuBar->mpFrame && AquaSalFrame::isAlive( AquaSalMenu::pCurrentMenuBar->mpFrame ) )
217                {
218                    SalMenuEvent aMenuEvt( rButtons[i].maButton.mnId, AquaSalMenu::pCurrentMenuBar->mpVCLMenu );
219                    AquaSalMenu::pCurrentMenuBar->mpFrame->CallCallback(SalEvent::MenuButtonCommand, &aMenuEvt);
220                }
221                return;
222            }
223
224            aImgRect.origin.x += aFromRect.size.width + 2;
225        }
226    }
227}
228
229-(void)layout
230{
231    NSStatusBar* pStatBar = [NSStatusBar systemStatusBar];
232    NSSize aSize = { 0, [pStatBar thickness] };
233    [self removeAllToolTips];
234    if( AquaSalMenu::pCurrentMenuBar )
235    {
236        const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
237        if( ! rButtons.empty() )
238        {
239            aSize.width = 2;
240            for( size_t i = 0; i < rButtons.size(); ++i )
241            {
242                NSRect aImgRect = { { aSize.width,
243                                      static_cast<CGFloat>(floor((aSize.height-rButtons[i].maButton.maImage.GetSizePixel().Height())/2)) },
244                                    { static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Width()),
245                                      static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Height()) } };
246                if( rButtons[i].mpToolTipString )
247                    [self addToolTipRect: aImgRect owner: rButtons[i].mpToolTipString userData: nullptr];
248                aSize.width += 2 + aImgRect.size.width;
249            }
250        }
251    }
252    [self setFrameSize: aSize];
253}
254@end
255
256
257/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
258