vcl/inc/osx/salframeview.h |    2 
 vcl/osx/salframeview.mm    |  120 ++++++++++++++++++++++++++++++++++++---------
 2 files changed, 98 insertions(+), 24 deletions(-)

New commits:
commit 4041d52c9c459760eca5aa42bc25c0abd99efc83
Author:     Patrick Luby <[email protected]>
AuthorDate: Thu Dec 25 16:25:59 2025 -0500
Commit:     Patrick Luby <[email protected]>
CommitDate: Thu Jan 8 15:23:41 2026 +0100

    tdf#170149 Fix clicking on a character in the input method's popup window
    
    [self setMarkedText:selectedRange:replacementRange:] does not
    get called on macOS Tahoe. So when pressing and holding a key,
    the original character would never get selected and clicking
    on a replacement character in the input method's popup window
    would fail. Surprisingly, pressing a replacement character's
    number still worked.
    
    Also, make the following scenarios behave as close as possible
    to the TextEdit application whether or not there is any arrowing
    into the input method's popup window. I tested with Canadian and
    Japanese - Romaji keyboards:
    - Pressing the Escape or Return keys commits uncommitted text
    - Pressing the Delete key discards uncommitted text
    - Pressing the Backspace or Fn-Delete keys commits uncommitted text
    
    Change-Id: I7c3fdfecb4d1979d38e9f652bea8c7d66e6c9612
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196239
    Tested-by: Jenkins
    Reviewed-by: Patrick Luby <[email protected]>

diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h
index eae401fc8841..bd8671ef7d8e 100644
--- a/vcl/inc/osx/salframeview.h
+++ b/vcl/inc/osx/salframeview.h
@@ -111,6 +111,8 @@ enum class SalEvent;
     BOOL            mbInCommitMarkedText;
     NSAttributedString* mpLastMarkedText;
     BOOL            mbTextInputWantsNonRepeatKeyDown;
+    BOOL            mbTextInputWantsInsertText;
+    BOOL            mbInTextInputDelete;
     NSTrackingArea* mpLastTrackingArea;
 
     BOOL            mbInViewDidChangeEffectiveAppearance;
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
index c761e646536c..41f85cb16a12 100644
--- a/vcl/osx/salframeview.mm
+++ b/vcl/osx/salframeview.mm
@@ -1145,6 +1145,8 @@ static NSString* getCurrentSelection()
         mbInCommitMarkedText = NO;
         mpLastMarkedText = nil;
         mbTextInputWantsNonRepeatKeyDown = NO;
+        mbTextInputWantsInsertText = NO;
+        mbInTextInputDelete = NO;
         mpLastTrackingArea = nil;
 
         mbInViewDidChangeEffectiveAppearance = NO;
@@ -1711,31 +1713,50 @@ static NSString* getCurrentSelection()
         if( ! [self handleKeyDownException: pEvent] )
         {
             sal_uInt16 nKeyCode = ImplMapKeyCode( [pEvent keyCode] );
-            if ( nKeyCode == KEY_DELETE && mbTextInputWantsNonRepeatKeyDown )
+            if ( nKeyCode == KEY_DELETE && mpLastMarkedText )
             {
                 // tdf#42437 Enable press-and-hold special character input 
method
                 // Emulate the press-and-hold behavior of the TextEdit
                 // application by deleting the marked text when only the
                 // Delete key is pressed and keep the marked text when the
                 // Backspace key or Fn-Delete keys are pressed.
-                if ( [pEvent keyCode] == 51 )
+                if ( mbTextInputWantsInsertText )
                 {
-                    [self deleteTextInputWantsNonRepeatKeyDown];
-                }
-                else
-                {
-                    [self unmarkText];
-                    mbKeyHandled = true;
-                    mbInKeyInput = false;
+                    if ( [pEvent keyCode] == 51 )
+                        [self insertText:[NSString string] 
replacementRange:NSMakeRange( NSNotFound, 0 )];
+                    else
+                        [self insertText:[mpLastMarkedText string] 
replacementRange:NSMakeRange( 0, [mpLastMarkedText length] )];
                 }
 
-                [self endExtTextInput];
-                return;
+                // Related: tdf#170149 Calling [self interpretKeyEvents:] with
+                // a delete key event calls [self insertText:replacementRange:]
+                // and maybe [self deleteBackward:] or [self deleteForward:] so
+                // ignore those calls since the marked tecxt has already been
+                // discarded or committed.
+                mbInTextInputDelete = YES;
             }
 
             NSArray* pArray = [NSArray arrayWithObject: pEvent];
             [self interpretKeyEvents: pArray];
 
+            if ( mbInTextInputDelete )
+            {
+                // For some unknown reason, press-and-hold will fail after
+                // doing the following steps twice:
+                // - Arrowing into the input method's popup window
+                // - Arrowing out of the popup window
+                // - Pressing Delete or Backspace or Fn-Delete
+                // The only way I have found to reset the input method is to
+                // deactivate and then reactivate the input method.
+                NSTextInputContext *pInputContext = [NSTextInputContext 
currentInputContext];
+                if ( pInputContext )
+                {
+                    [pInputContext deactivate];
+                    [pInputContext activate];
+                }
+
+                mbInTextInputDelete = NO;
+            }
             // Handle repeat key events by explicitly inserting the text if
             // -[NSResponder interpretKeyEvents:] does not insert or mark any
             // text. Note: do not do this step if there is uncommitted text.
@@ -1743,7 +1764,7 @@ static NSString* getCurrentSelection()
             // Pressing and holding action keys such as arrow keys must not be
             // handled like pressing and holding a character key as it will
             // insert unexpected text.
-            if ( !mbKeyHandled && !mpLastMarkedText && mpLastEvent && 
[mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] )
+            else if ( !mbKeyHandled && !mpLastMarkedText && mpLastEvent && 
[mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] )
             {
                 NSString *pChars = [mpLastEvent characters];
                 if ( pChars )
@@ -1751,15 +1772,35 @@ static NSString* getCurrentSelection()
             }
             // tdf#42437 Enable press-and-hold special character input method
             // Emulate the press-and-hold behavior of the TextEdit application
-            // by committing an empty string for key down events dispatched
-            // while the special character input method popup is displayed.
-            else if ( mpLastMarkedText && mbTextInputWantsNonRepeatKeyDown && 
mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && ![mpLastEvent 
isARepeat] )
+            // by reapplying the existing marked text for key down events
+            // dispatched while the special character input method popup is
+            // displayed.
+            else if ( mpLastMarkedText && mbTextInputWantsInsertText && 
mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && ![mpLastEvent 
isARepeat] )
             {
-                // If the escape or return key is pressed, unmark the text to
-                // skip deletion of marked text
-                if ( nKeyCode == KEY_ESCAPE || nKeyCode == KEY_RETURN )
-                    [self unmarkText];
-                [self insertText:[NSString string] 
replacementRange:NSMakeRange( NSNotFound, 0 )];
+                // In most cases, if the escape or return key is pressed,
+                // commit the text to skip deletion of marked text
+                bool bCommitText = false;
+                if ( nKeyCode == KEY_ESCAPE )
+                {
+                    // Pressing escape should not commmit when using input
+                    // methods such as Japanese or Chinese so only commit
+                    // if the marked text is a single character below the
+                    // start of the Devanagari Unicode block.
+                    if ( [mpLastMarkedText length] == 1 && [[mpLastMarkedText 
string] characterAtIndex:0] < 0x0900 )
+                        bCommitText = true;
+                }
+                else if ( nKeyCode == KEY_RETURN )
+                {
+                    bCommitText = true;
+                }
+
+                if ( bCommitText )
+                    [self insertText:[mpLastMarkedText string] 
replacementRange:NSMakeRange( 0, [mpLastMarkedText length] )];
+                // Don't change or commit the marked text as it will break
+                // right/left-arrowing in input methods such as Japanese or
+                // Chinese that support editing subblocks of uncommitted text.
+                else
+                    mbTextInputWantsInsertText = NO;
             }
         }
 
@@ -1805,6 +1846,8 @@ static NSString* getCurrentSelection()
 
     SolarMutexGuard aGuard;
 
+    mbTextInputWantsInsertText = NO;
+
     [self deleteTextInputWantsNonRepeatKeyDown];
 
     // Ignore duplicate events that are sometimes posted during cancellation
@@ -1820,7 +1863,18 @@ static NSString* getCurrentSelection()
     if( AquaSalFrame::isAlive( mpFrame ) )
     {
         NSString* pInsert = nil;
-        if( [aString isKindOfClass: [NSAttributedString class]] )
+        if (mbInTextInputDelete)
+        {
+            // In the TextEdit application, arrowing into the "press-and-hold"
+            // native popup window and then arrowing out (i.e. the popup
+            // window has no selection), pressing Backspace or Fn-Delete will
+            // commit the marked text.
+            if ( mpLastMarkedText && mpLastEvent && [mpLastEvent keyCode] != 
51 )
+                pInsert = [mpLastMarkedText string];
+            else
+                pInsert = [NSString string];
+        }
+        else if( [aString isKindOfClass: [NSAttributedString class]] )
             pInsert = [aString string];
         else
             pInsert = aString;
@@ -2154,13 +2208,17 @@ static NSString* getCurrentSelection()
 -(void)deleteBackward: (id)aSender
 {
     (void)aSender;
-    [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '' 
modifiers: 0];
+
+    if (!mbInTextInputDelete)
+        [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '' 
modifiers: 0];
 }
 
 -(void)deleteForward: (id)aSender
 {
     (void)aSender;
-    [self sendKeyInputAndReleaseToFrame: KEY_DELETE character: 0x7f modifiers: 
0];
+
+    if (!mbInTextInputDelete)
+        [self sendKeyInputAndReleaseToFrame: KEY_DELETE character: 0x7f 
modifiers: 0];
 }
 
 -(void)deleteBackwardByDecomposingPreviousCharacter: (id)aSender
@@ -2506,6 +2564,8 @@ static NSString* getCurrentSelection()
             bReschedule = true;
             mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void 
*>(&aInputEvent) );
         }
+
+        mbTextInputWantsInsertText = YES;
     } else {
         aInputEvent.maText.clear();
         aInputEvent.mnCursorPos = 0;
@@ -2625,6 +2685,7 @@ static NSString* getCurrentSelection()
     }
 
     mbTextInputWantsNonRepeatKeyDown = NO;
+    mbTextInputWantsInsertText = NO;
 }
 
 -(void)clearLastTrackingArea
@@ -2674,7 +2735,7 @@ static NSString* getCurrentSelection()
     // dispatch an empty SalEvent::ExtTextInput event, fetch the position,
     // and then dispatch a SalEvent::EndExtTextInput event.
     NSString *pNewMarkedText = nullptr;
-    NSString *pChars = [mpLastEvent characters];
+    NSString *pChars = mpLastEvent ? [mpLastEvent characters] : nil;
 
     // tdf#158124 KEY_DELETE events do not need an ExtTextInput event
     // When using various Japanese input methods, the last event will be a
@@ -2740,6 +2801,15 @@ static NSString* getCurrentSelection()
             mpLastMarkedText = [[NSAttributedString alloc] 
initWithString:pNewMarkedText];
             mSelectedRange = mMarkedRange = NSMakeRange( 0, [mpLastMarkedText 
length] );
             mbTextInputWantsNonRepeatKeyDown = YES;
+
+            // tdf#170149 Fix clicking on a character in the input method's 
popup window
+            // [self setMarkedText:selectedRange:replacementRange:] does not
+            // get called on macOS Tahoe. So when pressing and holding a key,
+            // the original character would never get selected and clicking
+            // on a replacement character in the input method's popup window
+            // would fail. Surprisingly, pressing a replacement character's
+            // number still worked.
+            [self setMarkedText:pNewMarkedText selectedRange:mSelectedRange 
replacementRange:mMarkedRange];
         }
     }
 
@@ -2911,6 +2981,8 @@ static NSString* getCurrentSelection()
     // [self insertText:replacementRange:].
     if (mbTextInputWantsNonRepeatKeyDown)
     {
+        mbTextInputWantsNonRepeatKeyDown = NO;
+
         if ( mpLastMarkedText )
         {
             NSString *pChars = [mpLastMarkedText string];

Reply via email to