Spracherkennung für: .mm vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at
http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sal/config.h>
#include <osl/diagnose.h>
#include <vcl/window.hxx>
#include <osx/salinst.h>
#include <osx/saldata.hxx>
#include <osx/salframe.h>
#include <osx/salframeview.h>
#include <osx/salmenu.h>
#include <osx/salnsmenu.h>
@implementation SalNSMenu
+(BOOL)dispatchSpecialKeyEquivalents: (NSEvent*)pEvent
{
if( pEvent && [pEvent type] == NSEventTypeKeyDown )
{
// Related tdf#126638 and tdf#162010: match against -[NSEvent characters]
// When using some non-Western European keyboard layouts, the event's
// "characters ignoring modifiers" will be set to the original Unicode
// character instead of the resolved key equivalent character so match
// against the -[NSEvent characters] instead.
NSEventModifierFlags nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShif
t|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
NSString *pCharacters = [pEvent characters];
if( nModMask == NSEventModifierFlagCommand )
{
if( [pCharacters isEqualToString: @"v"] )
{
if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
return YES;
}
else if( [pCharacters isEqualToString: @"c"] )
{
if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
return YES;
}
else if( [pCharacters isEqualToString: @"x"] )
{
if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
return YES;
}
else if( [pCharacters isEqualToString: @"a"] )
{
if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
return YES;
}
else if( [pCharacters isEqualToString: @"z"] )
{
if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
return YES;
}
}
else if( nModMask == (NSEventModifierFlagCommand|NSEventModifierFlagShift) )
{
if( [pCharacters isEqualToString: @"z"] || [pCharacters isEqualToString: @"Z"] )
{
if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
return YES;
}
}
}
return NO;
}
-(id)initWithMenu: (AquaSalMenu*)pMenu
{
mpMenu = pMenu;
return [super initWithTitle: [NSString string]];
}
-(void)menuNeedsUpdate: (NSMenu*)pMenu
{
SolarMutexGuard aGuard;
if( mpMenu )
{
// Related: tdf#165448 hide menu items inserted by macOS if child window
// For some unknown reason, none of the menu items that macOS inserts
// into the windows menu work if the key window is a native child
// window of another window. Unfortunately, LibreOffice depends on
// a dialog window being a native child window of its parent window
// to mimic native modal dialogs so hide all the those menu items.
if( pMenu && pMenu == [NSApp windowsMenu] )
{
NSWindow *pKeyWin = [NSApp keyWindow];
bool bHidden = pKeyWin && [pKeyWin parentWindow];
int nItems = [pMenu numberOfItems];
bool bLastItemIsNative = false;
for( int n = mpMenu->mbMenuBar ? 1 : 0; n < nItems; n++ )
{
NSMenuItem* pItem = [pMenu itemAtIndex: n];
if( [pItem isKindOfClass: [SalNSMenuItem class]] )
{
bLastItemIsNative = false;
}
else if( [pItem isSeparatorItem] )
{
if ( bLastItemIsNative )
{
// Assume that macOS does not insert more than one
// separator item in a row
bLastItemIsNative = false;
[pItem setHidden: bHidden];
}
}
else
{
bLastItemIsNative = true;
[pItem setHidden: bHidden];
}
}
}
const AquaSalFrame* pFrame = mpMenu->getFrame();
if( pFrame && AquaSalFrame::isAlive( pFrame ) )
{
SalMenuEvent aMenuEvt;
aMenuEvt.mnId = 0;
aMenuEvt.mpMenu = mpMenu->mpVCLMenu;
if( aMenuEvt.mpMenu )
{
pFrame->CallCallback(SalEvent::MenuActivate, &aMenuEvt);
pFrame->CallCallback(SalEvent::MenuDeactivate, &aMenuEvt);
}
else
OSL_FAIL( "unconnected menu" );
}
else if( mpMenu->mpVCLMenu )
{
mpMenu->mpVCLMenu->Activate();
mpMenu->mpVCLMenu->Deactivate();
// Hide disabled items
NSArray* elements = [pMenu itemArray];
NSEnumerator* it = [elements objectEnumerator];
id element;
while ( ( element = [it nextObject] ) != nil )
{
NSMenuItem* item = static_cast< NSMenuItem* >( element );
if( ![item isSeparatorItem] )
[item setHidden: ![item isEnabled]];
}
}
}
}
-(void)setSalMenu: (AquaSalMenu*)pMenu
{
mpMenu = pMenu;
}
@end
@implementation SalNSMenuItem
-(id)initWithMenuItem: (AquaSalMenuItem*)pMenuItem
{
mpMenuItem = pMenuItem;
id ret = [super initWithTitle: [NSString string]
action: @selector(menuItemTriggered:)
keyEquivalent: [NSString string]];
[ret setTarget: self];
mbReallyEnabled = [ret isEnabled];
return ret;
}
-(BOOL)isReallyEnabled
{
return mbReallyEnabled;
}
-(void)menuItemTriggered: (id)aSender
{
(void)aSender;
SolarMutexGuard aGuard;
// Commit uncommitted text before dispatching the selecting menu item. In
// certain cases such as selecting the Insert > Comment menu item in a
// Writer document while there is uncommitted text will call
// AquaSalFrame::EndExtTextInput() which will dispatch a
// SalEvent::EndExtTextInput event. Writer's handler for that event will
// delete the uncommitted text and then insert the committed text but
// LibreOffice will crash when deleting the uncommitted text because
// deletion of the text also removes and deletes the newly inserted
// comment.
NSWindow* pKeyWin = [NSApp keyWindow];
if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
[static_cast<SalFrameWindow*>(pKeyWin) endExtTextInput];
// tdf#49853 Keyboard shortcuts are also handled by the menu bar, but at least some of them
// must still end up in the view. This is necessary to handle common edit actions in docked
// windows (e.g. in toolbar fields).
if( pKeyWin )
{
// tdf#162010 match based on key equivalent instead of key event
// The original fix for tdf#49853 only looked worked in the
// case when both a key shortcut was pressed and the resulting
// key event was not an input method event. If either of these
// conditions weren't true, tdf#49853 would still occur.
// Since we know which menu item is being triggered, check if
// this menu item's key equivalent has been set to one of the
// edit actions.
// This change basically expands the fix for tdf#49853 to
// include all cases that trigger a menu item, not just simple
// key events.
NSEventModifierFlags nModMask = [self keyEquivalentModifierMask];
NSString* pCharacters = [self keyEquivalent];
if( nModMask == NSEventModifierFlagCommand &&
( [pCharacters isEqualToString: @"v"] ||
[pCharacters isEqualToString: @"c"] ||
[pCharacters isEqualToString: @"x"] ||
[pCharacters isEqualToString: @"a"] ||
[pCharacters isEqualToString: @"z"] ) )
{
NSEvent* pKeyEvent = nil;
NSEvent* pEvent = [NSApp currentEvent];
if( pEvent )
{
switch( [pEvent type] )
{
case NSEventTypeKeyDown:
case NSEventTypeKeyUp:
case NSEventTypeFlagsChanged:
// tdf#162843 replace the event's string parameters
// When using the Dvorak - QWERTY keyboard, the
// event's charactersIgnoringModifiers string causes
// pasting to fail so replace both the event's
// characters and charactersIgnoringModifiers strings
// with this menu item's key equivalent.
pKeyEvent = [NSEvent keyEventWithType: [pEvent type] location: [pEvent locationInWindow] modifierFlags: nModMask timestamp: [pEvent timestamp] windowNumber: [pEvent windowNumber] context: nil characters: pCharacters charactersIgnoringModifiers: pCharacters isARepeat: [pEvent isARepeat] keyCode: [pEvent keyCode]];
break;
default:
break;
}
}
if( !pKeyEvent )
{
// Native key events appear to set the location to the
// top left corner of the key window
NSPoint aPoint = NSMakePoint(0, [pKeyWin frame].size.height);
pKeyEvent = [NSEvent keyEventWithType: NSEventTypeKeyDown location: aPoint modifierFlags: nModMask timestamp: [[NSProcessInfo processInfo] systemUptime] windowNumber: [pKeyWin windowNumber] context: nil characters: pCharacters charactersIgnoringModifiers: pCharacters isARepeat: NO keyCode: 0];
}
[[pKeyWin contentView] keyDown: pKeyEvent];
return;
}
}
const AquaSalFrame* pFrame = mpMenuItem->mpParentMenu ? mpMenuItem->mpParentMenu->getFrame() : nullptr;
if( pFrame && AquaSalFrame::isAlive( pFrame ) && ! pFrame->GetWindow()->IsInModalMode() )
{
SalMenuEvent aMenuEvt( mpMenuItem->mnId, mpMenuItem->mpVCLMenu );
pFrame->CallCallback(SalEvent::MenuCommand, &aMenuEvt);
}
else if( mpMenuItem->mpVCLMenu )
{
// if an item from submenu was selected. the corresponding Window does not exist because
// we use native popup menus, so we have to set the selected menuitem directly
// incidentally this of course works for top level popup menus, too
PopupMenu * pPopupMenu = dynamic_cast<PopupMenu *>(mpMenuItem->mpVCLMenu.get());
if( pPopupMenu )
{
// FIXME: revise this ugly code
// select handlers in vcl are dispatch on the original menu
// if not consumed by the select handler of the current menu
// however since only the starting menu ever came into Execute
// the hierarchy is not build up. Workaround this by getting
// the menu it should have been
// get started from hierarchy in vcl menus
AquaSalMenu* pParentMenu = mpMenuItem->mpParentMenu;
Menu* pCurMenu = mpMenuItem->mpVCLMenu;
while( pParentMenu && pParentMenu->mpVCLMenu )
{
pCurMenu = pParentMenu->mpVCLMenu;
pParentMenu = pParentMenu->mpParentSalMenu;
}
pPopupMenu->SetSelectedEntry( mpMenuItem->mnId );
pPopupMenu->ImplSelectWithStart( pCurMenu );
}
else
OSL_FAIL( "menubar item without frame !" );
}
}
-(void)setReallyEnabled: (BOOL)bEnabled
{
mbReallyEnabled = bEnabled;
[self setEnabled: mbReallyEnabled];
}
-(BOOL)validateMenuItem: (NSMenuItem *)pMenuItem
{
// Related: tdf#126638 disable all menu items when displaying modal windows
// For some unknown reason, key shortcuts are dispatched to the LibreOffice
// menu items instead of the modal window so disable all LibreOffice menu
// items while a native modal dialog such as the native Open, Save, or
// Print dialog is displayed.
if (!pMenuItem || [NSApp modalWindow])
return NO;
// Related: tdf#161623 the menubar is always visible when in native
// full screen mode so disable all menu items when also in LibreOffice
// full screen mode to mimic the effect of a hidden menubar.
SolarMutexGuard aGuard;
const AquaSalFrame* pFrame = mpMenuItem->mpParentMenu ? mpMenuItem->mpParentMenu->getFrame() : nullptr;
if (pFrame && AquaSalFrame::isAlive( pFrame ) && pFrame->mbInternalFullScreen)
{
NSMenu *pMainMenu = [NSApp mainMenu];
NSMenu *pParentMenu = [pMenuItem menu];
while (pParentMenu && pParentMenu != pMainMenu)
pParentMenu = [pParentMenu supermenu];
if (pParentMenu && pParentMenu == pMainMenu)
return NO;
}
// Related: tdf#126638 return the last enabled state set by the LibreOffice code
// Apparently whatever is returned will be passed to
// -[NSMenuItem setEnabled:] which can cause the enabled state
// to be different than the enabled state that the LibreOffice
// code expects. This results in menu items failing to be
// reenabled after being temporarily disabled such as when a
// native modal dialog is closed. So, return the last enabled
// state set by the LibreOffice code.
if ([pMenuItem isKindOfClass: [SalNSMenuItem class]])
return [static_cast<SalNSMenuItem*>(pMenuItem) isReallyEnabled];
else
return [pMenuItem isEnabled];
}
@end
@implementation OOStatusItemView
-(void)drawRect: (NSRect)aRect
{
NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
[pContext saveGraphicsState];
SAL_WNODEPRECATED_DECLARATIONS_PUSH
// "'drawStatusBarBackgroundInRect:withHighlight:' is deprecated: first deprecated in macOS
// 10.14 - Use the standard button instead which handles highlight drawing, making this
// method obsolete"
[SalData::getStatusItem() drawStatusBarBackgroundInRect: aRect withHighlight: NO];
SAL_WNODEPRECATED_DECLARATIONS_POP
if( AquaSalMenu::pCurrentMenuBar )
{
const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
NSRect aFrame = [self frame];
NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
for( size_t i = 0; i < rButtons.size(); ++i )
{
const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
aImgRect.origin.y = floor((aFrame.size.height - aFromRect.size.height)/2);
aImgRect.size = aFromRect.size;
if( rButtons[i].mpNSImage )
[rButtons[i].mpNSImage drawInRect: aImgRect fromRect: aFromRect operation: NSCompositingOperationSourceOver fraction: 1.0];
aImgRect.origin.x += aFromRect.size.width + 2;
}
}
[pContext restoreGraphicsState];
}
-(void)mouseUp: (NSEvent *)pEvent
{
/* check if button goes up inside one of our status buttons */
if( AquaSalMenu::pCurrentMenuBar )
{
const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
NSRect aFrame = [self frame];
NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
NSPoint aMousePt = [pEvent locationInWindow];
for( size_t i = 0; i < rButtons.size(); ++i )
{
const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
aImgRect.origin.y = (aFrame.size.height - aFromRect.size.height)/2;
aImgRect.size = aFromRect.size;
if( aMousePt.x >= aImgRect.origin.x && aMousePt.x <= (aImgRect.origin.x+aImgRect.size.width) &&
aMousePt.y >= aImgRect.origin.y && aMousePt.y <= (aImgRect.origin.y+aImgRect.size.height) )
{
if( AquaSalMenu::pCurrentMenuBar->mpFrame && AquaSalFrame::isAlive( AquaSalMenu::pCurrentMenuBar->mpFrame ) )
{
SalMenuEvent aMenuEvt( rButtons[i].maButton.mnId, AquaSalMenu::pCurrentMenuBar->mpVCLMenu );
AquaSalMenu::pCurrentMenuBar->mpFrame->CallCallback(SalEvent::MenuButtonCommand, &aMenuEvt);
}
return;
}
aImgRect.origin.x += aFromRect.size.width + 2;
}
}
}
-(void)layout
{
NSStatusBar* pStatBar = [NSStatusBar systemStatusBar];
NSSize aSize = { 0, [pStatBar thickness] };
[self removeAllToolTips];
if( AquaSalMenu::pCurrentMenuBar )
{
const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
if( ! rButtons.empty() )
{
aSize.width = 2;
for( size_t i = 0; i < rButtons.size(); ++i )
{
NSRect aImgRect = { { aSize.width,
static_cast<CGFloat>(floor((aSize.height-rButtons[i].maButton.maImage.GetSizePixel().Height())/2)) },
{ static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Width()),
static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Height()) } };
if( rButtons[i].mpToolTipString )
[self addToolTipRect: aImgRect owner: rButtons[i].mpToolTipString userData: nullptr];
aSize.width += 2 + aImgRect.size.width;
}
}
}
[self setFrameSize: aSize];
}
@end
@implementation SalNSMainMenu
- (id)initWithTitle:(NSString*)pTitle
{
mpLastPerformKeyEquivalentEvent = nil;
return [super initWithTitle:pTitle];
}
- (void)dealloc
{
if (mpLastPerformKeyEquivalentEvent)
[mpLastPerformKeyEquivalentEvent release];
[super dealloc];
}
- (BOOL)performKeyEquivalent:(NSEvent*)pEvent
{
// Related: tdf#162843 prevent dispatch of the same event more than once
// When pressing Command-V with a Dvorak - QWERTY keyboard,
// that single event passes through this selector twice which
// causes content to be pasted twice in any text fields in the
// Find and Replace dialog.
if (pEvent == mpLastPerformKeyEquivalentEvent)
return false;
if (mpLastPerformKeyEquivalentEvent)
[mpLastPerformKeyEquivalentEvent release];
mpLastPerformKeyEquivalentEvent = pEvent;
if (mpLastPerformKeyEquivalentEvent)
[mpLastPerformKeyEquivalentEvent retain];
bool bRet = [super performKeyEquivalent: pEvent];
// tdf#126638 dispatch key shortcut events to modal windows
// Some modal windows, such as the native Open and Save dialogs,
// return NO from -[NSWindow performKeyEquivalent:]. Fortunately,
// the main menu's -[NSMenu performKeyEquivalent:] is then called
// so we can catch and redirect any modal window's key shortcut
// events without triggering the modal window's "disallowed
// action" beep.
if( !bRet && [NSApp modalWindow] )
bRet = [SalNSMenu dispatchSpecialKeyEquivalents: pEvent];
return bRet;
}
@end
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */