vcl/inc/osx/salnsmenu.h |    6 +++
 vcl/osx/salframeview.mm |   32 +++++++++++++++++-
 vcl/osx/salmenu.cxx     |   12 +++++-
 vcl/osx/salnsmenu.mm    |   84 ++++++++++++++++++++++++++++++++++++++++--------
 4 files changed, 116 insertions(+), 18 deletions(-)

New commits:
commit 9240debe362a382389e7601073523162ea0cd80d
Author:     Patrick Luby <guibmac...@gmail.com>
AuthorDate: Thu Oct 3 19:47:25 2024 -0400
Commit:     Patrick Luby <guibomac...@gmail.com>
CommitDate: Tue Oct 8 20:31:29 2024 +0200

    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.
    
    Also, fix the following related bugs when using the
    Dvorak - QWERTY keyboard:
    
    - When pressing Command-V with a Dvorak - QWERTY
      keyboard, that single event passes through the
      -[SalNSMainMenu performKeyEquivalent:] selector twice
      which causes content to be pasted twice in any text
      fields in the Find and Replace dialog.
    
    - When using the Dvorak - QWERTY keyboard and the
      Command key is pressed, any key events that match a
      disabled menu item are handled in the
      -[SalFrameView noop:].  However, the Dvorak - QWERTY
      event's charactersIgnoringModifiers string can cause
      cutting and copying to fail in the Find toolbar and
      the Find and Replace dialog so replace the event's
      charactersIgnoringModifiers string with the event's
      character string.
    
    Lastly, fix temporarily disabled menu items failing to
    be reenabled when a modal dialog closes caused by the
    fix for tdf#126638 by returning the last enabled state
    set by the LibreOffice code. Apparently whatever is
    returned by -[SalNSMenuItem validateMenuItem:] will be
    passed to -[NSMenuItem setEnabled:] which can cause
    the enabled state to be different than the enabled
    state that the LibreOffice code expoects. 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.
    
    Change-Id: Iae9cc6f1b94484c1529b22ea3a7acdac2009a58b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/174462
    Tested-by: Jenkins
    Reviewed-by: Patrick Luby <guibomac...@gmail.com>

diff --git a/vcl/inc/osx/salnsmenu.h b/vcl/inc/osx/salnsmenu.h
index 9e0f9acf68ed..d6b065a7706e 100644
--- a/vcl/inc/osx/salnsmenu.h
+++ b/vcl/inc/osx/salnsmenu.h
@@ -44,15 +44,21 @@ class AquaSalMenuItem;
 @interface SalNSMenuItem : NSMenuItem <NSMenuItemValidation>
 {
     AquaSalMenuItem* mpMenuItem;
+    BOOL mbReallyEnabled;
 }
 - (id)initWithMenuItem:(AquaSalMenuItem*)pMenuItem;
+- (BOOL)isReallyEnabled;
 - (void)menuItemTriggered:(id)aSender;
 - (BOOL)validateMenuItem:(NSMenuItem*)pMenuItem;
+- (void)setReallyEnabled:(BOOL)bEnabled;
 @end
 
 @interface SalNSMainMenu : NSMenu
 {
+    NSEvent* mpLastPerformKeyEquivalentEvent;
 }
+- (id)initWithTitle:(NSString*)pTitle;
+- (void)dealloc;
 - (BOOL)performKeyEquivalent:(NSEvent*)pEvent;
 @end
 
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
index 8c3e296d21ec..c96a71e1f2af 100644
--- a/vcl/osx/salframeview.mm
+++ b/vcl/osx/salframeview.mm
@@ -1749,9 +1749,37 @@ static void updateWinDataInLiveResize(bool bInLiveResize)
 -(void)noop: (id)aSender
 {
     (void)aSender;
-    if( ! mbKeyHandled )
+    if( ! mbKeyHandled && mpLastEvent )
     {
-        if( ! [self sendSingleCharacter:mpLastEvent] )
+        // Related tdf#162843: replace the event's string parameter
+        // When using the Dvorak - QWERTY keyboard and the Command key
+        // is pressed, any key events that match a disabled menu item
+        // are handled here. However, the Dvorak - QWERTY event's
+        // charactersIgnoringModifiers string can cause cutting and
+        // copying to fail in the Find toolbar and the Find and Replace
+        // dialog so replace the event's charactersIgnoringModifiers
+        // string with the event's character string.
+        NSEvent* pEvent = mpLastEvent;
+        NSEventModifierFlags nModMask = [mpLastEvent modifierFlags];
+        if( nModMask & NSEventModifierFlagCommand )
+        {
+            switch( [mpLastEvent type] )
+            {
+                case NSEventTypeKeyDown:
+                case NSEventTypeKeyUp:
+                case NSEventTypeFlagsChanged:
+                {
+                    NSString* pCharacters = [mpLastEvent characters];
+                    NSString* pCharactersIgnoringModifiers = ( nModMask & 
NSEventModifierFlagShift ) ? [pCharacters uppercaseString] : pCharacters;
+                    pEvent = [NSEvent keyEventWithType: [pEvent type] 
location: [pEvent locationInWindow] modifierFlags: nModMask timestamp: [pEvent 
timestamp] windowNumber: [pEvent windowNumber] context: nil characters: 
pCharacters charactersIgnoringModifiers: pCharactersIgnoringModifiers 
isARepeat: [pEvent isARepeat] keyCode: [pEvent keyCode]];
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+
+        if( ! [self sendSingleCharacter:pEvent] )
         {
             /* prevent recursion */
             if( mpLastEvent != mpLastSuperEvent && [NSApp respondsToSelector: 
@selector(sendSuperEvent:)] )
diff --git a/vcl/osx/salmenu.cxx b/vcl/osx/salmenu.cxx
index 4f4bc0e63169..08a0a3620137 100644
--- a/vcl/osx/salmenu.cxx
+++ b/vcl/osx/salmenu.cxx
@@ -412,7 +412,10 @@ void AquaSalMenu::enableMainMenu( bool bEnable )
         for( int n = 1; n < nItems; n++ )
         {
             NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
-            [pItem setEnabled: bEnable ? YES : NO];
+            if( [pItem isKindOfClass: [SalNSMenuItem class]])
+                [static_cast<SalNSMenuItem*>(pItem) setReallyEnabled: bEnable];
+            else
+                [pItem setEnabled: bEnable];
         }
     }
 }
@@ -581,7 +584,10 @@ void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
     if( nPos < maItems.size() )
     {
         NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
-        [pItem setEnabled: bEnable ? YES : NO];
+        if( [pItem isKindOfClass: [SalNSMenuItem class]])
+            [static_cast<SalNSMenuItem*>(pItem) setReallyEnabled: bEnable];
+        else
+            [pItem setEnabled: bEnable];
     }
 }
 
@@ -874,7 +880,7 @@ AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* 
pItemData ) :
     else
     {
         mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
-        [mpMenuItem setEnabled: YES];
+        [static_cast<SalNSMenuItem*>(mpMenuItem) setReallyEnabled: YES];
 
         // peel mnemonics because on mac there are no such things for menu 
items
         // Delete CJK-style mnemonics for the dropdown menu of the 'New 
button' and lower menu of 'File > New'
diff --git a/vcl/osx/salnsmenu.mm b/vcl/osx/salnsmenu.mm
index 31a2ace83f07..383e0b574a54 100644
--- a/vcl/osx/salnsmenu.mm
+++ b/vcl/osx/salnsmenu.mm
@@ -142,8 +142,15 @@
                     action: @selector(menuItemTriggered:)
                     keyEquivalent: [NSString string]];
     [ret setTarget: self];
+    mbReallyEnabled = [ret isEnabled];
     return ret;
 }
+
+-(BOOL)isReallyEnabled
+{
+    return mbReallyEnabled;
+}
+
 -(void)menuItemTriggered: (id)aSender
 {
     (void)aSender;
@@ -187,8 +194,8 @@
             [pCharacters isEqualToString: @"a"] ||
             [pCharacters isEqualToString: @"z"] ) )
         {
-            NSEvent* pEvent = [NSApp currentEvent];
             NSEvent* pKeyEvent = nil;
+            NSEvent* pEvent = [NSApp currentEvent];
             if( pEvent )
             {
                 switch( [pEvent type] )
@@ -196,23 +203,29 @@
                     case NSEventTypeKeyDown:
                     case NSEventTypeKeyUp:
                     case NSEventTypeFlagsChanged:
-                        pKeyEvent = pEvent;
+                        // 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;
+            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;
         }
     }
 
@@ -255,6 +268,12 @@
     }
 }
 
+-(void)setReallyEnabled: (BOOL)bEnabled
+{
+    mbReallyEnabled = bEnabled;
+    [self setEnabled: mbReallyEnabled];
+}
+
 -(BOOL)validateMenuItem: (NSMenuItem *)pMenuItem
 {
     // Related: tdf#126638 disable all menu items when displaying modal windows
@@ -265,7 +284,18 @@
     if (!pMenuItem || [NSApp modalWindow])
         return NO;
 
-    return [pMenuItem isEnabled];
+    // 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 expoects. 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
 
@@ -359,8 +389,36 @@ SAL_WNODEPRECATED_DECLARATIONS_POP
 
 @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

Reply via email to