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 <memory> 23 24#include <basegfx/numeric/ftools.hxx> 25#include <officecfg/Office/Common.hxx> 26#include <sal/macros.h> 27#include <tools/helpers.hxx> 28#include <tools/long.hxx> 29#include <vcl/event.hxx> 30#include <vcl/inputctx.hxx> 31#include <vcl/settings.hxx> 32#include <vcl/svapp.hxx> 33#include <vcl/window.hxx> 34#include <vcl/commandevent.hxx> 35#include <vcl/toolkit/edit.hxx> 36 37#include <com/sun/star/frame/Desktop.hpp> 38#include <com/sun/star/text/XTextRange.hpp> 39 40#include <osx/a11yfactory.h> 41#include <osx/salframe.h> 42#include <osx/salframeview.h> 43#include <osx/salinst.h> 44#include <quartz/salgdi.h> 45#include <quartz/utils.h> 46 47#if HAVE_FEATURE_SKIA 48#include <vcl/skia/SkiaHelper.hxx> 49#include <premac.h> 50#include <QuartzCore/QuartzCore.h> 51#include <postmac.h> 52#endif 53 54#define WHEEL_EVENT_FACTOR 1.5 55 56static sal_uInt16 ImplGetModifierMask( unsigned int nMask ) 57{ 58 sal_uInt16 nRet = 0; 59 if( (nMask & NSEventModifierFlagShift) != 0 ) 60 nRet |= KEY_SHIFT; 61 if( (nMask & NSEventModifierFlagControl) != 0 ) 62 nRet |= KEY_MOD3; 63 if( (nMask & NSEventModifierFlagOption) != 0 ) 64 nRet |= KEY_MOD2; 65 if( (nMask & NSEventModifierFlagCommand) != 0 ) 66 nRet |= KEY_MOD1; 67 return nRet; 68} 69 70static sal_uInt16 ImplMapCharCode( sal_Unicode aCode ) 71{ 72 static sal_uInt16 aKeyCodeMap[ 128 ] = 73 { 74 0, 0, 0, 0, 0, 0, 0, 0, 75 KEY_BACKSPACE, KEY_TAB, KEY_RETURN, 0, 0, KEY_RETURN, 0, 0, 76 0, 0, 0, 0, 0, 0, 0, 0, 77 0, KEY_TAB, 0, KEY_ESCAPE, 0, 0, 0, 0, 78 KEY_SPACE, 0, 0, 0, 0, 0, 0, 0, 79 0, 0, KEY_MULTIPLY, KEY_ADD, KEY_COMMA, KEY_SUBTRACT, KEY_POINT, KEY_DIVIDE, 80 KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, 81 KEY_8, KEY_9, 0, 0, KEY_LESS, KEY_EQUAL, KEY_GREATER, 0, 82 0, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, 83 KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, 84 KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, 85 KEY_X, KEY_Y, KEY_Z, 0, 0, 0, 0, 0, 86 KEY_QUOTELEFT, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, 87 KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, 88 KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, 89 KEY_X, KEY_Y, KEY_Z, 0, 0, 0, KEY_TILDE, KEY_BACKSPACE 90 }; 91 92 // Note: the mapping 0x7f should by rights be KEY_DELETE 93 // however if you press "backspace" 0x7f is reported 94 // whereas for "delete" 0xf728 gets reported 95 96 // Note: the mapping of 0x19 to KEY_TAB is because for unknown reasons 97 // tab alone is reported as 0x09 (as expected) but shift-tab is 98 // reported as 0x19 (end of medium) 99 100 static sal_uInt16 aFunctionKeyCodeMap[ 128 ] = 101 { 102 KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_F1, KEY_F2, KEY_F3, KEY_F4, 103 KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, 104 KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, 105 KEY_F21, KEY_F22, KEY_F23, KEY_F24, KEY_F25, KEY_F26, 0, 0, 106 0, 0, 0, 0, 0, 0, 0, KEY_INSERT, 107 KEY_DELETE, KEY_HOME, 0, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, 0, 0, 108 0, 0, 0, 0, 0, KEY_MENU, 0, 0, 109 0, 0, 0, 0, 0, 0, 0, 0, 110 0, 0, 0, KEY_UNDO, KEY_REPEAT, KEY_FIND, KEY_HELP, 0, 111 0, 0, 0, 0, 0, 0, 0, 0, 112 0, 0, 0, 0, 0, 0, 0, 0, 113 0, 0, 0, 0, 0, 0, 0, 0, 114 0, 0, 0, 0, 0, 0, 0, 0, 115 0, 0, 0, 0, 0, 0, 0, 0, 116 0, 0, 0, 0, 0, 0, 0, 0, 117 0, 0, 0, 0, 0, 0, 0, 0 118 }; 119 120 sal_uInt16 nKeyCode = 0; 121 if( aCode < SAL_N_ELEMENTS( aKeyCodeMap) ) 122 nKeyCode = aKeyCodeMap[ aCode ]; 123 else if( aCode >= 0xf700 && aCode < 0xf780 ) 124 nKeyCode = aFunctionKeyCodeMap[ aCode - 0xf700 ]; 125 return nKeyCode; 126} 127 128static sal_uInt16 ImplMapKeyCode(sal_uInt16 nKeyCode) 129{ 130 /* 131 http://stackoverflow.com/questions/2080312/where-can-i-find-a-list-of-key-codes-for-use-with-cocoas-nsevent-class/2080324#2080324 132 /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h 133 */ 134 135 static sal_uInt16 aKeyCodeMap[ 0x80 ] = 136 { 137 KEY_A, KEY_S, KEY_D, KEY_F, KEY_H, KEY_G, KEY_Z, KEY_X, 138 KEY_C, KEY_V, 0, KEY_B, KEY_Q, KEY_W, KEY_E, KEY_R, 139 KEY_Y, KEY_T, KEY_1, KEY_2, KEY_3, KEY_4, KEY_6, KEY_5, 140 KEY_EQUAL, KEY_9, KEY_7, KEY_SUBTRACT, KEY_8, KEY_0, KEY_BRACKETRIGHT, KEY_RIGHTCURLYBRACKET, 141 KEY_U, KEY_BRACKETLEFT, KEY_I, KEY_P, KEY_RETURN, KEY_L, KEY_J, KEY_QUOTERIGHT, 142 KEY_K, KEY_SEMICOLON, 0, KEY_COMMA, KEY_DIVIDE, KEY_N, KEY_M, KEY_POINT, 143 KEY_TAB, KEY_SPACE, KEY_QUOTELEFT, KEY_DELETE, 0, KEY_ESCAPE, 0, 0, 144 0, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0, 145 KEY_F17, KEY_DECIMAL, 0, KEY_MULTIPLY, 0, KEY_ADD, 0, 0, 146 0, 0, 0, KEY_DIVIDE, KEY_RETURN, 0, KEY_SUBTRACT, KEY_F18, 147 KEY_F19, KEY_EQUAL, 0, 0, 0, 0, 0, 0, 148 0, 0, KEY_F20, 0, 0, 0, 0, 0, 149 KEY_F5, KEY_F6, KEY_F7, KEY_F3, KEY_F8, KEY_F9, 0, KEY_F11, 150 0, KEY_F13, KEY_F16, KEY_F14, 0, KEY_F10, 0, KEY_F12, 151 0, KEY_F15, KEY_HELP, KEY_HOME, KEY_PAGEUP, KEY_DELETE, KEY_F4, KEY_END, 152 KEY_F2, KEY_PAGEDOWN, KEY_F1, KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0 153 }; 154 155 if (nKeyCode < SAL_N_ELEMENTS(aKeyCodeMap)) 156 return aKeyCodeMap[nKeyCode]; 157 return 0; 158} 159 160// store the frame the mouse last entered 161static AquaSalFrame* s_pMouseFrame = nullptr; 162// store the last pressed button for enter/exit events 163// which lack that information 164static sal_uInt16 s_nLastButton = 0; 165 166static AquaSalFrame* getMouseContainerFrame() 167{ 168 AquaSalFrame* pDispatchFrame = nullptr; 169 NSArray* aWindows = [NSWindow windowNumbersWithOptions:0]; 170 for(NSUInteger i = 0; i < [aWindows count] && ! pDispatchFrame; i++ ) 171 { 172 NSWindow* pWin = [NSApp windowWithWindowNumber:[[aWindows objectAtIndex:i] integerValue]]; 173 if( pWin && [pWin isMemberOfClass: [SalFrameWindow class]] && [static_cast<SalFrameWindow*>(pWin) containsMouse] ) 174 pDispatchFrame = [static_cast<SalFrameWindow*>(pWin) getSalFrame]; 175 } 176 return pDispatchFrame; 177} 178 179static NSArray *getMergedAccessibilityChildren(NSArray *pDefaultChildren, NSArray *pUnignoredChildrenToAdd) 180{ 181 NSArray *pRet = pDefaultChildren; 182 183 if (pUnignoredChildrenToAdd && [pUnignoredChildrenToAdd count]) 184 { 185 NSMutableArray *pNewChildren = [NSMutableArray arrayWithCapacity:(pRet ? [pRet count] : 0) + 1]; 186 if (pNewChildren) 187 { 188 if (pRet) 189 [pNewChildren addObjectsFromArray:pRet]; 190 191 for (AquaA11yWrapper *pWrapper : pUnignoredChildrenToAdd) 192 { 193 if (pWrapper && ![pNewChildren containsObject:pWrapper]) 194 [pNewChildren addObject:pWrapper]; 195 } 196 197 pRet = pNewChildren; 198 } 199 else 200 { 201 pRet = pUnignoredChildrenToAdd; 202 } 203 } 204 205 return pRet; 206} 207 208// Update ImplGetSVData()->mpWinData->mbIsLiveResize 209static void updateWinDataInLiveResize(bool bInLiveResize) 210{ 211 ImplSVData* pSVData = ImplGetSVData(); 212 assert( pSVData ); 213 if ( pSVData ) 214 { 215 if ( pSVData->mpWinData->mbIsLiveResize != bInLiveResize ) 216 { 217 pSVData->mpWinData->mbIsLiveResize = bInLiveResize; 218 Scheduler::Wakeup(); 219 } 220 } 221} 222 223static void freezeWindowSizeAndReschedule( NSWindow *pWindow ) 224{ 225 if ( pWindow ) 226 { 227 // Application::Reschedule() can potentially display a modal 228 // dialog which will cause a hang so temporarily disable any 229 // resizing by clamping the window's minimum and maximum sizes 230 // to the current frame size which in Application::Reschedule(). 231 bool bIsLiveResize = ImplGetSVData()->mpWinData->mbIsLiveResize; 232 NSSize aMinSize = [pWindow minSize]; 233 NSSize aMaxSize = [pWindow maxSize]; 234 if ( bIsLiveResize ) 235 { 236 NSRect aFrame = [pWindow frame]; 237 [pWindow setMinSize:aFrame.size]; 238 [pWindow setMaxSize:aFrame.size]; 239 } 240 Application::Reschedule( true ); 241 if ( bIsLiveResize ) 242 { 243 [pWindow setMinSize:aMinSize]; 244 [pWindow setMaxSize:aMaxSize]; 245 } 246 } 247} 248 249static bool isMouseScrollWheelEvent( NSEvent *pEvent ) 250{ 251 // tdf#151423 allow trackpad or Magic Mouse to behave like a regular mouse 252 // Give both trackpad and Magic Mouse users the option to restore 253 // the legacy zoom via Command+swipe gesture. 254 // The IgnoreKeysWhenScrollingWithTrackpadOrMagicMouse preference is 255 // set to true by default and that disables zooming via swiping. 256 // The problem is that while trackpad users are able to zoom via a 257 // magnify gesture, the Magic Mouse doesn't have a magnify gesture. 258 // Since I have not found a reliable way to distinguish a Magic Mouse 259 // from a trackpad, Magic Mouse users have no obvious replacement 260 // for the zoom via Command+swipe gesture. 261 if ( !officecfg::Office::Common::VCL::macOS::IgnoreKeysWhenScrollingWithTrackpadOrMagicMouse::get() ) 262 return true; 263 264 return ( pEvent && [pEvent type] == NSEventTypeScrollWheel && [pEvent phase] == NSEventPhaseNone && [pEvent momentumPhase] == NSEventPhaseNone ); 265} 266 267static void updateMenuBarVisibility( const AquaSalFrame *pFrame ) 268{ 269 // Show the menubar if application is in native full screen mode 270 // since hiding the menubar in that mode will cause the window's 271 // titlebar to fail to display or fail to hide when expected. 272 if( [NSApp presentationOptions] & NSApplicationPresentationFullScreen ) 273 { 274 [NSMenu setMenuBarVisible: YES]; 275 } 276 // Hide the dock and the menubar if the key window or one of its 277 // parent windows are in LibreOffice full screen mode. Otherwise, 278 // show the dock and the menubar. 279 else if( AquaSalFrame::isAlive( pFrame ) ) 280 { 281 bool bInternalFullScreen = false; 282 bool bNativeFullScreen = false; 283 const AquaSalFrame *pParentFrame = pFrame; 284 while( pParentFrame ) 285 { 286 bInternalFullScreen |= pParentFrame->mbInternalFullScreen; 287 bNativeFullScreen |= pParentFrame->mbNativeFullScreen; 288 pParentFrame = AquaSalFrame::isAlive( pParentFrame->mpParent ) ? pParentFrame->mpParent : nullptr; 289 } 290 291 if( bInternalFullScreen && !bNativeFullScreen ) 292 { 293 const NSWindow *pParentWindow = [NSApp keyWindow]; 294 while( pParentWindow && pParentWindow != pFrame->getNSWindow() ) 295 pParentWindow = [pParentWindow parentWindow]; 296 297 // Related: tdf#161623 disable menubar visibility if no key window 298 // If a window is in LibreOffice's internal full screen mode 299 // and not in native full screen mode and then the user switches 300 // to a different application and back using the Command-Tab keys. 301 // the menubar and Dock would unexpectedly appear. 302 // It appears that the key window will still be nil in this 303 // case, so only enable menubar visibility if the key window 304 // is not nil. 305 if( pParentWindow && pParentWindow != pFrame->getNSWindow() ) 306 [NSMenu setMenuBarVisible: YES]; 307 else 308 [NSMenu setMenuBarVisible: NO]; 309 } 310 else 311 { 312 [NSMenu setMenuBarVisible: YES]; 313 } 314 } 315} 316 317static void updateWindowCollectionBehavior( const SalFrameStyleFlags nStyle, const AquaSalFrame *pParent, NSWindow *pNSWindow ) 318{ 319 if( !pNSWindow ) 320 return; 321 322 // Enable fullscreen options if available and useful 323 NSWindowCollectionBehavior eOldCollectionBehavior = [pNSWindow collectionBehavior]; 324 NSWindowCollectionBehavior eCollectionBehavior = NSWindowCollectionBehaviorFullScreenNone; 325 if ( officecfg::Office::Common::VCL::macOS::EnableNativeFullScreenWindows::get() ) 326 { 327 bool bAllowFullScreen = (SalFrameStyleFlags::NONE == (nStyle & (SalFrameStyleFlags::DIALOG | SalFrameStyleFlags::TOOLTIP | SalFrameStyleFlags::SYSTEMCHILD | SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::TOOLWINDOW | SalFrameStyleFlags::INTRO))); 328 bAllowFullScreen &= (SalFrameStyleFlags::NONE == (~nStyle & SalFrameStyleFlags::SIZEABLE)); 329 bAllowFullScreen &= (pParent == nullptr); 330 331 eCollectionBehavior = bAllowFullScreen ? NSWindowCollectionBehaviorFullScreenPrimary : NSWindowCollectionBehaviorFullScreenAuxiliary; 332 } 333 if ( eCollectionBehavior != eOldCollectionBehavior ) 334 [pNSWindow setCollectionBehavior: eCollectionBehavior]; 335} 336 337static NSString* getCurrentSelection() 338{ 339 SolarMutexGuard aGuard; 340 341 // The following is needed for text fields in dialogs, etc. 342 vcl::Window *pWin = ImplGetSVData()->mpWinData->mpFocusWin; 343 if (pWin) 344 { 345 Edit *pEditWin = dynamic_cast<Edit*>(pWin); 346 if (pEditWin) 347 return [CreateNSString(pEditWin->GetSelected()) autorelease]; 348 } 349 350 css::uno::Reference<css::frame::XDesktop> xDesktop = css::frame::Desktop::create(::comphelper::getProcessComponentContext()); 351 if (xDesktop.is()) 352 { 353 css::uno::Reference<css::frame::XModel> xModel(xDesktop->getCurrentComponent(), css::uno::UNO_QUERY); 354 if (xModel) 355 { 356 css::uno::Reference<css::uno::XInterface> xSelection(xModel->getCurrentSelection(), css::uno::UNO_QUERY); 357 if (xSelection) 358 { 359 css::uno::Reference<css::container::XIndexAccess> xIndexAccess(xSelection, css::uno::UNO_QUERY); 360 if (xIndexAccess.is()) 361 { 362 if (xIndexAccess->getCount() > 0) 363 { 364 css::uno::Reference<css::text::XTextRange> xTextRange(xIndexAccess->getByIndex(0), css::uno::UNO_QUERY); 365 if (xTextRange.is()) 366 return [CreateNSString(xTextRange->getString()) autorelease]; 367 } 368 } 369 370 // The Basic IDE returns a XEnumeration with a single item 371 // Note: the following code was adapted from 372 // svx/source/tbxctrls/tbunosearchcontrollers.cxx 373 css::uno::Reference<css::container::XEnumeration> xEnum(xSelection, css::uno::UNO_QUERY); 374 if (xEnum.is() && xEnum->hasMoreElements()) 375 { 376 OUString aString; 377 xEnum->nextElement() >>= aString; 378 return [CreateNSString(aString) autorelease]; 379 } 380 381 // The following is needed for cells and text fields in Calc 382 // and Impress 383 css::uno::Reference<css::text::XTextRange> xTextRange(xSelection, css::uno::UNO_QUERY); 384 if (xTextRange.is()) 385 return [CreateNSString(xTextRange->getString()) autorelease]; 386 } 387 } 388 } 389 390 return nil; 391} 392 393@interface NSResponder (SalFrameWindow) 394-(BOOL)accessibilityIsIgnored; 395@end 396 397@implementation SalFrameWindow 398-(id)initWithSalFrame: (AquaSalFrame*)pFrame 399{ 400 mDraggingDestinationHandler = nil; 401 mbInWindowDidResize = NO; 402 mpLiveResizeTimer = nil; 403 mpResetParentWindowTimer = nil; 404 mbInSetFrame = false; 405 mpFrame = pFrame; 406 const SalFrameGeometry rFrameGeometry = pFrame->GetUnmirroredGeometry(); 407 NSRect aRect = { { static_cast<CGFloat>(rFrameGeometry.x()), static_cast<CGFloat>(rFrameGeometry.y()) }, 408 { static_cast<CGFloat>(rFrameGeometry.width()), static_cast<CGFloat>(rFrameGeometry.height()) } }; 409 pFrame->VCLToCocoa( aRect ); 410 NSWindow* pNSWindow = [super initWithContentRect: aRect 411 styleMask: mpFrame->getStyleMask() 412 backing: NSBackingStoreBuffered 413 defer: Application::IsHeadlessModeEnabled()]; 414 415 updateWindowCollectionBehavior( mpFrame->mnStyle, mpFrame->mpParent, pNSWindow ); 416 417 [pNSWindow setReleasedWhenClosed: NO]; 418 419 // Disable window restoration until we support it directly 420 [pNSWindow setRestorable: NO]; 421 422 // tdf#137468: Restrict to 24-bit RGB as that is all that we can 423 // handle anyway. HDR is far off in the future for LibreOffice. 424 [pNSWindow setDynamicDepthLimit: NO]; 425 [pNSWindow setDepthLimit: NSWindowDepthTwentyfourBitRGB]; 426 427 return static_cast<SalFrameWindow *>(pNSWindow); 428} 429 430-(void)clearLiveResizeTimer 431{ 432 if ( mpLiveResizeTimer ) 433 { 434 [mpLiveResizeTimer invalidate]; 435 [mpLiveResizeTimer release]; 436 mpLiveResizeTimer = nil; 437 } 438} 439 440-(void)clearResetParentWindowTimer 441{ 442 if ( mpResetParentWindowTimer ) 443 { 444 [mpResetParentWindowTimer invalidate]; 445 [mpResetParentWindowTimer release]; 446 mpResetParentWindowTimer = nil; 447 } 448} 449 450-(void)dealloc 451{ 452 [self clearLiveResizeTimer]; 453 [self clearResetParentWindowTimer]; 454 [super dealloc]; 455} 456 457-(AquaSalFrame*)getSalFrame 458{ 459 return mpFrame; 460} 461 462-(void)displayIfNeeded 463{ 464 if( GetSalData() && GetSalData()->mpInstance ) 465 { 466 SolarMutexGuard aGuard; 467 [super displayIfNeeded]; 468 } 469} 470 471-(BOOL)containsMouse 472{ 473 // is this event actually inside that NSWindow ? 474 NSPoint aPt = [NSEvent mouseLocation]; 475 NSRect aFrameRect = [self frame]; 476 bool bInRect = NSPointInRect( aPt, aFrameRect ); 477 return bInRect; 478} 479 480-(BOOL)canBecomeKeyWindow 481{ 482 if( (mpFrame->mnStyle & 483 ( SalFrameStyleFlags::FLOAT | 484 SalFrameStyleFlags::TOOLTIP | 485 SalFrameStyleFlags::INTRO 486 )) == SalFrameStyleFlags::NONE ) 487 return YES; 488 if( mpFrame->mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION ) 489 return YES; 490 if( mpFrame->mbInternalFullScreen ) 491 return YES; 492 return [super canBecomeKeyWindow]; 493} 494 495-(void)windowDidBecomeKey: (NSNotification*)pNotification 496{ 497 (void)pNotification; 498 SolarMutexGuard aGuard; 499 500 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 501 { 502 updateWindowCollectionBehavior( mpFrame->mnStyle, mpFrame->mpParent, mpFrame->mpNSWindow); 503 504 static const SalFrameStyleFlags nGuessDocument = SalFrameStyleFlags::MOVEABLE| 505 SalFrameStyleFlags::SIZEABLE| 506 SalFrameStyleFlags::CLOSEABLE; 507 508 // Reset dark mode colors in HITheme controls after printing 509 // In dark mode, after an NSPrintOperation has completed, macOS draws 510 // HITheme controls with light mode colors so reset all dark mode 511 // colors when an NSWindow gains focus. 512 mpFrame->UpdateDarkMode(); 513 514 if( mpFrame->mpMenu ) 515 mpFrame->mpMenu->setMainMenu(); 516 else if( ! mpFrame->mpParent && 517 ( (mpFrame->mnStyle & nGuessDocument) == nGuessDocument || // set default menu for e.g. help 518 mpFrame->mbInternalFullScreen ) ) // set default menu for e.g. presentation 519 { 520 AquaSalMenu::setDefaultMenu(); 521 } 522 mpFrame->CallCallback( SalEvent::GetFocus, nullptr ); 523 mpFrame->SendPaintEvent(); // repaint controls as active 524 525 updateMenuBarVisibility( mpFrame ); 526 } 527 528 // Prevent the same native input method popup that was cancelled in a 529 // previous call to [self windowDidResignKey:] from reappearing 530 [self endExtTextInput]; 531} 532 533-(void)windowDidResignKey: (NSNotification*)pNotification 534{ 535 (void)pNotification; 536 SolarMutexGuard aGuard; 537 538 // Commit any uncommitted text and cancel the native input method session 539 // whenever a window loses focus like in Safari, Firefox, and Excel 540 [self endExtTextInput]; 541 542 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 543 { 544 mpFrame->CallCallback(SalEvent::LoseFocus, nullptr); 545 mpFrame->SendPaintEvent(); // repaint controls as inactive 546 } 547 548 // Show the menubar if application is in native full screen mode 549 // since hiding the menubar in that mode will cause the window's 550 // titlebar to fail to display or fail to hide when expected. 551 if( [NSApp presentationOptions] & NSApplicationPresentationFullScreen ) 552 { 553 [NSMenu setMenuBarVisible: YES]; 554 } 555 // Show the dock and the menubar if there is no native modal dialog 556 // and if the key window is nil or is not a SalFrameWindow instance. 557 // If a SalFrameWindow is the key window, it should have already set 558 // the menubar visibility to match its LibreOffice full screen mode 559 // state. 560 else if ( ![NSApp modalWindow] ) 561 { 562 NSWindow *pKeyWindow = [NSApp keyWindow]; 563 if( !pKeyWindow || ![pKeyWindow isKindOfClass: [SalFrameWindow class]] ) 564 [NSMenu setMenuBarVisible: YES]; 565 } 566} 567 568-(void)windowDidChangeScreen: (NSNotification*)pNotification 569{ 570 (void)pNotification; 571 SolarMutexGuard aGuard; 572 573 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 574 mpFrame->screenParametersChanged(); 575 576 // Start timer to handle hiding of native child windows that have been 577 // dragged to a different screen. 578 if( !mpResetParentWindowTimer ) 579 { 580 mpResetParentWindowTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1f target: self selector: @selector(resetParentWindow) userInfo: nil repeats: YES]; 581 if( mpResetParentWindowTimer ) 582 { 583 [mpResetParentWindowTimer retain]; 584 [[NSRunLoop currentRunLoop] addTimer: mpResetParentWindowTimer forMode: NSEventTrackingRunLoopMode]; 585 } 586 } 587} 588 589-(void)windowDidMove: (NSNotification*)pNotification 590{ 591 (void)pNotification; 592 SolarMutexGuard aGuard; 593 594 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 595 { 596 mpFrame->UpdateFrameGeometry(); 597 mpFrame->CallCallback( SalEvent::Move, nullptr ); 598 599#if HAVE_FEATURE_SKIA 600 // tdf#163734 Flush parent frame when Skia is enabled 601 // When a dockable window is dragged by its titlebar, a rectangle 602 // may be drawn in its parent window. However, the Skia flush 603 // timer doesn't run until after the mouse button has been 604 // released (probably due to lowering of the Skia flush timer's 605 // priority to fix tdf#163734). So run the parent frame's Skia 606 // flush timer immediately to display the rectangle. 607 if ( SkiaHelper::isVCLSkiaEnabled() && 608 mpFrame->mbShown && mpFrame->mpParent && 609 AquaSalFrame::isAlive( mpFrame->mpParent ) && 610 mpFrame->mpParent->mbShown ) 611 { 612 AquaSalGraphics* pGraphics = mpFrame->mpParent->mpGraphics; 613 if ( pGraphics ) 614 pGraphics->Flush(); 615 } 616#endif 617 } 618} 619 620-(void)windowDidResize: (NSNotification*)pNotification 621{ 622 SolarMutexGuard aGuard; 623 624 if ( mbInWindowDidResize ) 625 return; 626 627 mbInWindowDidResize = YES; 628 629 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 630 { 631 mpFrame->UpdateFrameGeometry(); 632 mpFrame->CallCallback( SalEvent::Resize, nullptr ); 633 634 updateWinDataInLiveResize( [self inLiveResize] ); 635 if ( ImplGetSVData()->mpWinData->mbIsLiveResize ) 636 { 637 // tdf#152703 Force relayout during live resizing of window 638 // During a live resize, macOS floods the application with 639 // windowDidResize: notifications so sending a paint event does 640 // not trigger redrawing with the new size. 641 // Instead, force relayout by dispatching all pending internal 642 // events and firing any pending timers. 643 freezeWindowSizeAndReschedule( self ); 644 645 // Related: tdf128186 Always run timer in full screen mode windows 646 // When opening new windows by pressing and holding Command-N 647 // in a full screen window, some of the new windows will have 648 // content that does not fill the new window. So still run the 649 // timer on full screen windows even if live resizing ended 650 // during the call to freezeWindowSizeAndReschedule(). 651 if ( ImplGetSVData()->mpWinData->mbIsLiveResize || [self styleMask] & NSWindowStyleMaskFullScreen ) 652 { 653 // tdf#152703 Force repaint after live resizing ends 654 // Repost this notification so that this selector will be called 655 // at least once after live resizing ends 656 if ( !mpLiveResizeTimer ) 657 { 658 mpLiveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(windowDidResizeWithTimer:) userInfo:pNotification repeats:YES]; 659 if ( mpLiveResizeTimer ) 660 { 661 [mpLiveResizeTimer retain]; 662 663 // The timer won't fire without a call to 664 // Application::Reschedule() unless we copy the fix for 665 // #i84055# from vcl/osx/saltimer.cxx and add the timer 666 // to the NSEventTrackingRunLoopMode run loop mode 667 [[NSRunLoop currentRunLoop] addTimer:mpLiveResizeTimer forMode:NSEventTrackingRunLoopMode]; 668 } 669 } 670 } 671 } 672 else 673 { 674 [self clearLiveResizeTimer]; 675 } 676 677 // tdf#158461 eliminate flicker during live resizing 678 // When using Skia/Metal, the window content will flicker while 679 // live resizing a window if we don't send a paint event. 680 mpFrame->SendPaintEvent(); 681 682#if HAVE_FEATURE_SKIA 683 // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing 684 // The window will clear its background so when Skia/Metal is 685 // enabled, explicitly flush the Skia graphics to the window 686 // during live resizing or else nothing will be drawn until after 687 // live resizing has ended. 688 // Also, flushing during [self windowDidResize:] eliminates flicker 689 // by forcing this window's SkSurface to recreate its underlying 690 // CAMetalLayer with the new size. Flushing in 691 // [self displayIfNeeded] does not eliminate flicker so apparently 692 // [self windowDidResize:] is called earlier. 693 // Lastly, flush after calling AquaSalFrame::SendPaintEvent(). 694 if ( SkiaHelper::isVCLSkiaEnabled() ) 695 { 696 AquaSalGraphics* pGraphics = mpFrame->mpGraphics; 697 if ( pGraphics ) 698 pGraphics->Flush(); 699 } 700#endif 701 } 702 703 mbInWindowDidResize = NO; 704} 705 706-(void)windowDidMiniaturize: (NSNotification*)pNotification 707{ 708 (void)pNotification; 709 SolarMutexGuard aGuard; 710 711 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 712 { 713 mpFrame->mbShown = false; 714 mpFrame->UpdateFrameGeometry(); 715 mpFrame->CallCallback( SalEvent::Resize, nullptr ); 716 } 717} 718 719-(void)windowDidDeminiaturize: (NSNotification*)pNotification 720{ 721 (void)pNotification; 722 SolarMutexGuard aGuard; 723 724 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 725 { 726 mpFrame->mbShown = true; 727 mpFrame->UpdateFrameGeometry(); 728 mpFrame->CallCallback( SalEvent::Resize, nullptr ); 729 } 730} 731 732-(BOOL)windowShouldClose: (NSNotification*)pNotification 733{ 734 (void)pNotification; 735 SolarMutexGuard aGuard; 736 737 bool bRet = true; 738 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 739 { 740 // #i84461# end possible input 741 [self endExtTextInput]; 742 if( AquaSalFrame::isAlive( mpFrame ) ) 743 { 744 mpFrame->CallCallback( SalEvent::Close, nullptr ); 745 bRet = false; // application will close the window or not, AppKit shouldn't 746 AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer ); 747 assert( pTimer ); 748 pTimer->handleWindowShouldClose(); 749 } 750 } 751 752 return bRet; 753} 754 755-(void)windowWillEnterFullScreen: (NSNotification*)pNotification 756{ 757 (void)pNotification; 758 SolarMutexGuard aGuard; 759 760 if( AquaSalFrame::isAlive( mpFrame) ) 761 { 762 mpFrame->mbNativeFullScreen = true; 763 764 if( mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) ) 765 { 766 mpFrame->maNativeFullScreenRestoreRect = mpFrame->maInternalFullScreenRestoreRect; 767 } 768 else 769 { 770 // Related: tdf#128186 restore rectangles are in VCL coordinates 771 NSRect aFrame = [mpFrame->getNSWindow() frame]; 772 NSRect aContentRect = [mpFrame->getNSWindow() contentRectForFrameRect: aFrame]; 773 mpFrame->CocoaToVCL( aContentRect ); 774 mpFrame->maNativeFullScreenRestoreRect = aContentRect; 775 } 776 777 updateMenuBarVisibility( mpFrame ); 778 779 // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property 780 // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1 781 // and create a new CAMetalLayer when the window resizes. When using 782 // Skia/Metal, flushing to an NSWindow during transitions into or out 783 // of native full screen mode causes the Skia/Metal surface to be 784 // drawn at the wrong window position which results in a noticeable 785 // flicker. 786 assert( SkiaHelper::isVCLSkiaEnabled() && "macos requires skia" ); 787 if( SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster ) 788 { 789 if( [mpFrame->getNSView() wantsLayer] ) 790 { 791 CALayer *pLayer = [mpFrame->getNSView() layer]; 792 if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] ) 793 [pLayer setHidden: YES]; 794 } 795 } 796 } 797} 798 799-(void)windowDidFailToEnterFullScreen: (NSWindow *)pWindow 800{ 801 (void)pWindow; 802 SolarMutexGuard aGuard; 803 804 if( AquaSalFrame::isAlive( mpFrame) ) 805 { 806 mpFrame->mbNativeFullScreen = false; 807 808 mpFrame->maNativeFullScreenRestoreRect = NSZeroRect; 809 810 updateMenuBarVisibility( mpFrame ); 811 } 812} 813 814-(void)windowWillExitFullScreen: (NSNotification*)pNotification 815{ 816 (void)pNotification; 817 SolarMutexGuard aGuard; 818 819 // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property 820 // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1 821 // and create a new CAMetalLayer when the window resizes. When using 822 // Skia/Metal, flushing to an NSWindow during transitions into or out 823 // of native full screen mode causes the Skia/Metal surface to be 824 // drawn at the wrong window position which results in a noticeable 825 // flicker. 826 assert( SkiaHelper::isVCLSkiaEnabled() && "macos requires skia" ); 827 if( AquaSalFrame::isAlive( mpFrame ) && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster ) 828 { 829 if( [mpFrame->getNSView() wantsLayer] ) 830 { 831 CALayer *pLayer = [mpFrame->getNSView() layer]; 832 if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] ) 833 [pLayer setHidden: YES]; 834 } 835 } 836} 837 838-(void)windowDidExitFullScreen: (NSNotification*)pNotification 839{ 840 (void)pNotification; 841 SolarMutexGuard aGuard; 842 843 if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbNativeFullScreen ) 844 { 845 mpFrame->mbNativeFullScreen = false; 846 847 if( !NSIsEmptyRect( mpFrame->maNativeFullScreenRestoreRect ) ) 848 { 849 // Related: tdf#128186 set window frame before exiting native full screen mode 850 // Setting the window frame just before exiting native full 851 // screen mode appears to set the desired non-full screen 852 // window frame without causing a noticeable flicker during 853 // the macOS default "exit full screen" animation. 854 NSRect aContentRect; 855 if( mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) ) 856 aContentRect = mpFrame->maInternalFullScreenRestoreRect; 857 else 858 aContentRect = mpFrame->maNativeFullScreenRestoreRect; 859 mpFrame->VCLToCocoa( aContentRect ); 860 NSRect aFrame = [NSWindow frameRectForContentRect: aContentRect styleMask: [mpFrame->getNSWindow() styleMask] & ~NSWindowStyleMaskFullScreen]; 861 [mpFrame->getNSWindow() setFrame: aFrame display: mpFrame->mbShown ? YES : NO]; 862 863 mpFrame->maNativeFullScreenRestoreRect = NSZeroRect; 864 } 865 866 updateMenuBarVisibility( mpFrame ); 867 } 868} 869 870-(void)windowDidChangeBackingProperties:(NSNotification *)pNotification 871{ 872 (void)pNotification; 873#if HAVE_FEATURE_SKIA 874 SolarMutexGuard aGuard; 875 876 sal::aqua::resetWindowScaling(); 877 878 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 879 { 880 // tdf#147342 Notify Skia that the window's backing properties changed 881 if ( SkiaHelper::isVCLSkiaEnabled() ) 882 { 883 AquaSalGraphics* pGraphics = mpFrame->mpGraphics; 884 if ( pGraphics ) 885 pGraphics->WindowBackingPropertiesChanged(); 886 } 887 } 888#endif 889} 890 891-(void)windowWillStartLiveResize:(NSNotification *)pNotification 892{ 893 SolarMutexGuard aGuard; 894 895 updateWinDataInLiveResize(true); 896} 897 898-(void)windowDidEndLiveResize:(NSNotification *)pNotification 899{ 900 SolarMutexGuard aGuard; 901 902 updateWinDataInLiveResize(false); 903} 904 905-(void)dockMenuItemTriggered: (id)sender 906{ 907 (void)sender; 908 SolarMutexGuard aGuard; 909 910 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 911 mpFrame->ToTop( SalFrameToTop::RestoreWhenMin | SalFrameToTop::GrabFocus ); 912} 913 914-(css::uno::Reference < css::accessibility::XAccessibleContext >)accessibleContext 915{ 916 return mpFrame -> GetWindow() -> GetAccessible() -> getAccessibleContext(); 917} 918 919-(BOOL)isIgnoredWindow 920{ 921 SolarMutexGuard aGuard; 922 923 // Treat tooltip windows as ignored 924 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 925 return (mpFrame->mnStyle & SalFrameStyleFlags::TOOLTIP) != SalFrameStyleFlags::NONE; 926 return YES; 927} 928 929-(id)accessibilityApplicationFocusedUIElement 930{ 931 return [self accessibilityFocusedUIElement]; 932} 933 934-(id)accessibilityFocusedUIElement 935{ 936 // Treat tooltip windows as ignored 937 if ([self isIgnoredWindow]) 938 return nil; 939 940 return [super accessibilityFocusedUIElement]; 941} 942 943-(BOOL)accessibilityIsIgnored 944{ 945 // Treat tooltip windows as ignored 946 if ([self isIgnoredWindow]) 947 return YES; 948 949 return [super accessibilityIsIgnored]; 950} 951 952-(BOOL)isAccessibilityElement 953{ 954 return ![self accessibilityIsIgnored]; 955} 956 957-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender 958{ 959 return [mDraggingDestinationHandler draggingEntered: sender]; 960} 961 962-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender 963{ 964 return [mDraggingDestinationHandler draggingUpdated: sender]; 965} 966 967-(void)draggingExited:(id <NSDraggingInfo>)sender 968{ 969 [mDraggingDestinationHandler draggingExited: sender]; 970} 971 972-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender 973{ 974 return [mDraggingDestinationHandler prepareForDragOperation: sender]; 975} 976 977-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender 978{ 979 return [mDraggingDestinationHandler performDragOperation: sender]; 980} 981 982-(void)concludeDragOperation:(id <NSDraggingInfo>)sender 983{ 984 [mDraggingDestinationHandler concludeDragOperation: sender]; 985} 986 987-(void)registerDraggingDestinationHandler:(id)theHandler 988{ 989 mDraggingDestinationHandler = theHandler; 990} 991 992-(void)unregisterDraggingDestinationHandler:(id)theHandler 993{ 994 (void)theHandler; 995 mDraggingDestinationHandler = nil; 996} 997 998-(void)endExtTextInput 999{ 1000 [self endExtTextInput:EndExtTextInputFlags::Complete]; 1001} 1002 1003-(void)endExtTextInput:(EndExtTextInputFlags)nFlags 1004{ 1005 SalFrameView *pView = static_cast<SalFrameView*>([self firstResponder]); 1006 if (pView && [pView isKindOfClass:[SalFrameView class]]) 1007 [pView endExtTextInput:nFlags]; 1008} 1009 1010-(void)windowDidResizeWithTimer:(NSTimer *)pTimer 1011{ 1012 if ( pTimer ) 1013 [self windowDidResize:[pTimer userInfo]]; 1014} 1015 1016-(void)resetParentWindow 1017{ 1018 // Wait until the left mouse button has been released. Otherwise 1019 // the code below will cause native child windows to flicker while 1020 // dragging the window in a different screen than its parent window. 1021 if( [NSEvent pressedMouseButtons] & 0x1 ) 1022 return; 1023 1024 // Stop hiding of child windows when dragged to a different screen 1025 // LibreOffice sets all dialog windows as a native child window of 1026 // its related document window in order to force the dialog windows 1027 // to always remain in front of their related document window. 1028 // However, for some unknown reason, if a native child window is 1029 // dragged to a different screen than its native parent window, 1030 // macOS will hide the native child window when the drag has ended. 1031 // So, once the current drag has finished, unattach and reattach 1032 // the native child window to its native parent window. This should 1033 // cause macOS to force the native child window to jump back to the 1034 // same screen as its native parent window. 1035 NSWindow *pParentWindow = [self parentWindow]; 1036 if( pParentWindow && [pParentWindow screen] != [self screen] ) 1037 { 1038 [pParentWindow removeChildWindow: self]; 1039 [pParentWindow addChildWindow: self ordered: NSWindowAbove]; 1040 } 1041 1042 [self clearResetParentWindowTimer]; 1043} 1044 1045-(NSRect)constrainFrameRect: (NSRect)aFrameRect toScreen: (NSScreen *)pScreen 1046{ 1047 SolarMutexGuard aGuard; 1048 1049 NSRect aRet = [super constrainFrameRect: aFrameRect toScreen: pScreen]; 1050 1051 // Related: tdf#161623 the menubar and Dock are both hidden when a 1052 // window enters LibreOffice full screen mode. However, the call to 1053 // -[super constrainFrameRect:toScreen:] shrinks the window frame to 1054 // allow room for the menubar if the window is on the main screen. So, 1055 // force the return value to match the frame that LibreOffice expects. 1056 // Related: tdf#165448 skip fix for menu items inserted by macOS 1057 // If the window is in LibreOffice's internal full screen mode and 1058 // any of the menu items that macOS inserts into the windows menu 1059 // is selected, the frame size will be changed without calling 1060 // -[SalFrameWindow setFrame:display:]. So only use the fix for 1061 // tdf#161623 when the LibreOffice code explicitly resizes the frame. 1062 // Otherwise, selecting any of the menu items inserted by macOS will 1063 // cause the window to snap back to full screen size. 1064 if( mbInSetFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenExpectedRect ) ) 1065 aRet = mpFrame->maInternalFullScreenExpectedRect; 1066 1067 return aRet; 1068} 1069 1070- (NSArray<NSWindow *> *)customWindowsToExitFullScreenForWindow: (NSWindow *)pWindow 1071{ 1072 SolarMutexGuard aGuard; 1073 1074 // Related: tdf#161623 suppress animation when in internal full screen mode 1075 // LibreOffice's internal full screen mode fills the screen with a 1076 // regular window so suppress animation when exiting native full 1077 // screen mode. 1078 if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenExpectedRect ) ) 1079 return [NSArray arrayWithObject: self]; 1080 1081 return nil; 1082} 1083 1084-(void)setFrame: (NSRect)aFrameRect display: (BOOL)bFlag 1085{ 1086 mbInSetFrame = true; 1087 [super setFrame: aFrameRect display: bFlag]; 1088 mbInSetFrame = false; 1089} 1090 1091@end 1092 1093@implementation SalFrameView 1094+(void)unsetMouseFrame: (AquaSalFrame*)pFrame 1095{ 1096 if( pFrame == s_pMouseFrame ) 1097 s_pMouseFrame = nullptr; 1098} 1099 1100-(id)initWithSalFrame: (AquaSalFrame*)pFrame 1101{ 1102 if ((self = [super initWithFrame: [NSWindow contentRectForFrameRect: [pFrame->getNSWindow() frame] styleMask: pFrame->mnStyleMask]]) != nil) 1103 { 1104 mDraggingDestinationHandler = nil; 1105 mpFrame = pFrame; 1106 mpChildWrapper = nil; 1107 mbNeedChildWrapper = NO; 1108 mpLastEvent = nil; 1109 mMarkedRange = NSMakeRange(NSNotFound, 0); 1110 mSelectedRange = NSMakeRange(NSNotFound, 0); 1111 mpMouseEventListener = nil; 1112 mpLastSuperEvent = nil; 1113 mfLastMagnifyTime = 0.0; 1114 1115 mbInEndExtTextInput = NO; 1116 mbInCommitMarkedText = NO; 1117 mpLastMarkedText = nil; 1118 mbTextInputWantsNonRepeatKeyDown = NO; 1119 mpLastTrackingArea = nil; 1120 1121 mbInViewDidChangeEffectiveAppearance = NO; 1122 1123 mpMouseDraggedTimer = nil; 1124 mpPendingMouseDraggedEvent = nil; 1125 } 1126 1127 return self; 1128} 1129 1130-(void)clearMouseDraggedTimer 1131{ 1132 if ( mpMouseDraggedTimer ) 1133 { 1134 [mpMouseDraggedTimer invalidate]; 1135 [mpMouseDraggedTimer release]; 1136 mpMouseDraggedTimer = nil; 1137 } 1138 1139 // Clear the pending mouse dragged event as well 1140 [self clearPendingMouseDraggedEvent]; 1141} 1142 1143-(void)clearPendingMouseDraggedEvent 1144{ 1145 if ( mpPendingMouseDraggedEvent ) 1146 { 1147 [mpPendingMouseDraggedEvent release]; 1148 mpPendingMouseDraggedEvent = nil; 1149 } 1150} 1151 1152-(void)dealloc 1153{ 1154 [self clearMouseDraggedTimer]; 1155 [self clearLastEvent]; 1156 [self clearLastMarkedText]; 1157 [self clearLastTrackingArea]; 1158 [self revokeWrapper]; 1159 1160 [super dealloc]; 1161} 1162 1163-(AquaSalFrame*)getSalFrame 1164{ 1165 return mpFrame; 1166} 1167 1168-(void)resetCursorRects 1169{ 1170 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 1171 { 1172 // FIXME: does this leak the returned NSCursor of getCurrentCursor ? 1173 const NSRect aRect = { NSZeroPoint, NSMakeSize(mpFrame->GetUnmirroredGeometry().width(), mpFrame->GetUnmirroredGeometry().height()) }; 1174 [self addCursorRect: aRect cursor: mpFrame->getCurrentCursor()]; 1175 } 1176} 1177 1178-(BOOL)acceptsFirstResponder 1179{ 1180 return YES; 1181} 1182 1183-(BOOL)acceptsFirstMouse: (NSEvent*)pEvent 1184{ 1185 (void)pEvent; 1186 return YES; 1187} 1188 1189-(BOOL)isOpaque 1190{ 1191 if( !mpFrame) 1192 return YES; 1193 if( !AquaSalFrame::isAlive( mpFrame)) 1194 return YES; 1195 if( !mpFrame->getClipPath()) 1196 return YES; 1197 return NO; 1198} 1199 1200-(void)drawRect: (NSRect)aRect 1201{ 1202 ImplSVData* pSVData = ImplGetSVData(); 1203 assert( pSVData ); 1204 if ( !pSVData ) 1205 return; 1206 1207 SolarMutexGuard aGuard; 1208 if (!mpFrame || !AquaSalFrame::isAlive(mpFrame)) 1209 return; 1210 1211 updateWinDataInLiveResize([self inLiveResize]); 1212 1213 AquaSalGraphics* pGraphics = mpFrame->mpGraphics; 1214 if (pGraphics) 1215 { 1216 pGraphics->UpdateWindow(aRect); 1217 if (mpFrame->getClipPath()) 1218 [mpFrame->getNSWindow() invalidateShadow]; 1219 } 1220} 1221 1222-(void)sendMouseEventToFrame: (NSEvent*)pEvent button:(sal_uInt16)nButton eventtype:(SalEvent)nEvent 1223{ 1224 SolarMutexGuard aGuard; 1225 1226 AquaSalFrame* pDispatchFrame = AquaSalFrame::GetCaptureFrame(); 1227 bool bIsCaptured = false; 1228 if( pDispatchFrame ) 1229 { 1230 bIsCaptured = true; 1231 if( nEvent == SalEvent::MouseLeave ) // no leave events if mouse is captured 1232 nEvent = SalEvent::MouseMove; 1233 } 1234 else if( s_pMouseFrame ) 1235 pDispatchFrame = s_pMouseFrame; 1236 else 1237 pDispatchFrame = mpFrame; 1238 1239 /* #i81645# Cocoa reports mouse events while a button is pressed 1240 to the window in which it was first pressed. This is reasonable and fine and 1241 gets one around most cases where on other platforms one uses CaptureMouse or XGrabPointer, 1242 however vcl expects mouse events to occur in the window the mouse is over, unless the 1243 mouse is explicitly captured. So we need to find the window the mouse is actually 1244 over for conformance with other platforms. 1245 */ 1246 if( ! bIsCaptured && nButton && pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) ) 1247 { 1248 // is this event actually inside that NSWindow ? 1249 NSPoint aPt = [NSEvent mouseLocation]; 1250 NSRect aFrameRect = [pDispatchFrame->getNSWindow() frame]; 1251 1252 if ( ! NSPointInRect( aPt, aFrameRect ) ) 1253 { 1254 // no, it is not 1255 // now we need to find the one it may be in 1256 /* #i93756# we ant to get enumerate the application windows in z-order 1257 to check if any contains the mouse. This could be elegantly done with this 1258 code: 1259 1260 // use NSApp to check windows in ZOrder whether they contain the mouse pointer 1261 NSWindow* pWindow = [NSApp makeWindowsPerform: @selector(containsMouse) inOrder: YES]; 1262 if( pWindow && [pWindow isMemberOfClass: [SalFrameWindow class]] ) 1263 pDispatchFrame = [(SalFrameWindow*)pWindow getSalFrame]; 1264 1265 However if a non SalFrameWindow is on screen (like e.g. the file dialog) 1266 it can be hit with the containsMouse selector, which it doesn't support. 1267 Sadly NSApplication:makeWindowsPerform does not check (for performance reasons 1268 I assume) whether a window supports a selector before sending it. 1269 */ 1270 AquaSalFrame* pMouseFrame = getMouseContainerFrame(); 1271 if( pMouseFrame ) 1272 pDispatchFrame = pMouseFrame; 1273 } 1274 } 1275 1276 if( pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) ) 1277 { 1278 pDispatchFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 ); 1279 pDispatchFrame->mnLastModifierFlags = [pEvent modifierFlags]; 1280 1281 NSPoint aPt = [NSEvent mouseLocation]; 1282 pDispatchFrame->CocoaToVCL( aPt ); 1283 1284 sal_uInt16 nModMask = ImplGetModifierMask( [pEvent modifierFlags] ); 1285 // #i82284# emulate ctrl left 1286 if( nModMask == KEY_MOD3 && nButton == MOUSE_LEFT ) 1287 { 1288 nModMask = 0; 1289 nButton = MOUSE_RIGHT; 1290 } 1291 1292 SalMouseEvent aEvent; 1293 aEvent.mnTime = pDispatchFrame->mnLastEventTime; 1294 aEvent.mnX = static_cast<tools::Long>(aPt.x) - pDispatchFrame->GetUnmirroredGeometry().x(); 1295 aEvent.mnY = static_cast<tools::Long>(aPt.y) - pDispatchFrame->GetUnmirroredGeometry().y(); 1296 aEvent.mnButton = nButton; 1297 aEvent.mnCode = aEvent.mnButton | nModMask; 1298 1299 if( AllSettings::GetLayoutRTL() ) 1300 aEvent.mnX = pDispatchFrame->GetWidth() - 1 - aEvent.mnX; 1301 1302 pDispatchFrame->CallCallback( nEvent, &aEvent ); 1303 1304 // tdf#155266 force flush after scrolling 1305 if (nButton == MOUSE_LEFT && nEvent == SalEvent::MouseMove) 1306 mpFrame->mbForceFlushScrolling = true; 1307 } 1308} 1309 1310-(void)mouseDown: (NSEvent*)pEvent 1311{ 1312 if ( mpMouseEventListener != nil && 1313 [mpMouseEventListener respondsToSelector: @selector(mouseDown:)]) 1314 { 1315 [mpMouseEventListener mouseDown: pEvent]; 1316 } 1317 1318 s_nLastButton = MOUSE_LEFT; 1319 [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonDown]; 1320} 1321 1322-(void)mouseDragged: (NSEvent*)pEvent 1323{ 1324 // tdf#163945 Coalesce mouse dragged events 1325 // When dragging a selection box on an empty background in Impress 1326 // while using Skia/Metal, the selection box would not keep up with 1327 // the pointer. The selection box would repaint sporadically or not 1328 // at all if the pointer was dragged rapidly and the status bar was 1329 // visible. 1330 // Apparently, flushing a graphics doesn't actually do much of 1331 // anything with Skia/Raster and Skia disabled so the selection box 1332 // repaints without any noticeable delay. 1333 // However, with Skia/Metal every flush of a graphics creates and 1334 // queues a new CAMetalLayer drawable. During rapid dragging, this 1335 // can lead to creating and queueing up to 200 drawables per second 1336 // leaving no spare time for the Impress selection box painting 1337 // timer to fire. So coalesce mouse dragged events so that only 1338 // a maximum of 50 mouse dragged events are dispatched per second. 1339 [self clearPendingMouseDraggedEvent]; 1340 mpPendingMouseDraggedEvent = [pEvent retain]; 1341 if ( !mpMouseDraggedTimer ) 1342 { 1343 mpMouseDraggedTimer = [NSTimer scheduledTimerWithTimeInterval:0.025f target:self selector:@selector(mouseDraggedWithTimer:) userInfo:nil repeats:YES]; 1344 if ( mpMouseDraggedTimer ) 1345 { 1346 [mpMouseDraggedTimer retain]; 1347 1348 // The timer won't fire without a call to 1349 // Application::Reschedule() unless we copy the fix for 1350 // #i84055# from vcl/osx/saltimer.cxx and add the timer 1351 // to the NSEventTrackingRunLoopMode run loop mode 1352 [[NSRunLoop currentRunLoop] addTimer:mpMouseDraggedTimer forMode:NSEventTrackingRunLoopMode]; 1353 } 1354 } 1355} 1356 1357-(void)mouseUp: (NSEvent*)pEvent 1358{ 1359 // Dispatch any pending mouse dragged event before dispatching the 1360 // mouse up event 1361 if ( mpPendingMouseDraggedEvent ) 1362 [self mouseDraggedWithTimer: nil]; 1363 [self clearMouseDraggedTimer]; 1364 1365 s_nLastButton = 0; 1366 [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonUp]; 1367} 1368 1369-(void)mouseMoved: (NSEvent*)pEvent 1370{ 1371 s_nLastButton = 0; 1372 [self sendMouseEventToFrame:pEvent button:0 eventtype:SalEvent::MouseMove]; 1373} 1374 1375-(void)mouseEntered: (NSEvent*)pEvent 1376{ 1377 s_pMouseFrame = mpFrame; 1378 1379 // #i107215# the only mouse events we get when inactive are enter/exit 1380 // actually we would like to have all of them, but better none than some 1381 if( [NSApp isActive] ) 1382 [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseMove]; 1383} 1384 1385-(void)mouseExited: (NSEvent*)pEvent 1386{ 1387 if( s_pMouseFrame == mpFrame ) 1388 s_pMouseFrame = nullptr; 1389 1390 // #i107215# the only mouse events we get when inactive are enter/exit 1391 // actually we would like to have all of them, but better none than some 1392 if( [NSApp isActive] ) 1393 [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseLeave]; 1394} 1395 1396-(void)rightMouseDown: (NSEvent*)pEvent 1397{ 1398 s_nLastButton = MOUSE_RIGHT; 1399 [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonDown]; 1400} 1401 1402-(void)rightMouseDragged: (NSEvent*)pEvent 1403{ 1404 s_nLastButton = MOUSE_RIGHT; 1405 [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseMove]; 1406} 1407 1408-(void)rightMouseUp: (NSEvent*)pEvent 1409{ 1410 s_nLastButton = 0; 1411 [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonUp]; 1412} 1413 1414-(void)otherMouseDown: (NSEvent*)pEvent 1415{ 1416 if( [pEvent buttonNumber] == 2 ) 1417 { 1418 s_nLastButton = MOUSE_MIDDLE; 1419 [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonDown]; 1420 } 1421 else 1422 s_nLastButton = 0; 1423} 1424 1425-(void)otherMouseDragged: (NSEvent*)pEvent 1426{ 1427 if( [pEvent buttonNumber] == 2 ) 1428 { 1429 s_nLastButton = MOUSE_MIDDLE; 1430 [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseMove]; 1431 } 1432 else 1433 s_nLastButton = 0; 1434} 1435 1436-(void)otherMouseUp: (NSEvent*)pEvent 1437{ 1438 s_nLastButton = 0; 1439 if( [pEvent buttonNumber] == 2 ) 1440 [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonUp]; 1441} 1442 1443- (void)magnifyWithEvent: (NSEvent*)pEvent 1444{ 1445 SolarMutexGuard aGuard; 1446 1447 // TODO: ?? -(float)magnification; 1448 if( AquaSalFrame::isAlive( mpFrame ) ) 1449 { 1450 const NSTimeInterval fMagnifyTime = [pEvent timestamp]; 1451 mpFrame->mnLastEventTime = static_cast<sal_uInt64>( fMagnifyTime * 1000.0 ); 1452 mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; 1453 1454 // check if this is a new series of magnify events 1455 static const NSTimeInterval fMaxDiffTime = 0.3; 1456 const bool bNewSeries = (fMagnifyTime - mfLastMagnifyTime > fMaxDiffTime); 1457 1458 if( bNewSeries ) 1459 mfMagnifyDeltaSum = 0.0; 1460 mfMagnifyDeltaSum += [pEvent magnification]; 1461 1462 mfLastMagnifyTime = [pEvent timestamp]; 1463// TODO: change to 0.1 when CommandWheelMode::ZOOM handlers allow finer zooming control 1464 static const float fMagnifyFactor = 0.25*500; // steps are 500 times smaller for -magnification 1465 static const float fMinMagnifyStep = 15.0 / fMagnifyFactor; 1466 if( fabs(mfMagnifyDeltaSum) <= fMinMagnifyStep ) 1467 return; 1468 1469 // adapt NSEvent-sensitivity to application expectations 1470 // TODO: rather make CommandWheelMode::ZOOM handlers smarter 1471 const float fDeltaZ = mfMagnifyDeltaSum * fMagnifyFactor; 1472 int nDeltaZ = basegfx::fround<int>( fDeltaZ ); 1473 if( !nDeltaZ ) 1474 { 1475 // handle new series immediately 1476 if( !bNewSeries ) 1477 return; 1478 nDeltaZ = (fDeltaZ >= 0.0) ? +1 : -1; 1479 } 1480 // eventually give credit for delta sum 1481 mfMagnifyDeltaSum -= nDeltaZ / fMagnifyFactor; 1482 1483 NSPoint aPt = [NSEvent mouseLocation]; 1484 mpFrame->CocoaToVCL( aPt ); 1485 1486 SalWheelMouseEvent aEvent; 1487 aEvent.mnTime = mpFrame->mnLastEventTime; 1488 aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x(); 1489 aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y(); 1490 aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags ); 1491 aEvent.mnCode |= KEY_MOD1; // we want zooming, no scrolling 1492 aEvent.mbDeltaIsPixel = true; 1493 1494 if( AllSettings::GetLayoutRTL() ) 1495 aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX; 1496 1497 aEvent.mnDelta = nDeltaZ; 1498 aEvent.mnNotchDelta = (nDeltaZ >= 0) ? +1 : -1; 1499 if( aEvent.mnDelta == 0 ) 1500 aEvent.mnDelta = aEvent.mnNotchDelta; 1501 aEvent.mbHorz = false; 1502 sal_uInt32 nScrollLines = nDeltaZ; 1503 if (nScrollLines == 0) 1504 nScrollLines = 1; 1505 aEvent.mnScrollLines = nScrollLines; 1506 mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent ); 1507 } 1508} 1509 1510- (void)rotateWithEvent: (NSEvent*)pEvent 1511{ 1512 //Rotation : -(float)rotation; 1513 // TODO: create new CommandType so rotation is available to the applications 1514 (void)pEvent; 1515} 1516 1517- (void)swipeWithEvent: (NSEvent*)pEvent 1518{ 1519 SolarMutexGuard aGuard; 1520 1521 if( AquaSalFrame::isAlive( mpFrame ) ) 1522 { 1523 mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 ); 1524 mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; 1525 1526 // merge pending scroll wheel events 1527 CGFloat dX = 0.0; 1528 CGFloat dY = 0.0; 1529 for(;;) 1530 { 1531 dX += [pEvent deltaX]; 1532 dY += [pEvent deltaY]; 1533 NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskSwipe 1534 untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ]; 1535 if( !pNextEvent ) 1536 break; 1537 pEvent = pNextEvent; 1538 } 1539 1540 NSPoint aPt = [NSEvent mouseLocation]; 1541 mpFrame->CocoaToVCL( aPt ); 1542 1543 SalWheelMouseEvent aEvent; 1544 aEvent.mnTime = mpFrame->mnLastEventTime; 1545 aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x(); 1546 aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y(); 1547 // tdf#151423 Ignore all modifiers for swipe events 1548 // It appears that devices that generate swipe events can generate 1549 // both vertical and horizontal swipe events. So, behave like most 1550 // macOS applications and ignore all modifiers if this a swipe event. 1551 aEvent.mnCode = 0; 1552 aEvent.mbDeltaIsPixel = true; 1553 1554 if( AllSettings::GetLayoutRTL() ) 1555 aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX; 1556 1557 if( dX != 0.0 ) 1558 { 1559 aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX)); 1560 aEvent.mnNotchDelta = (dX < 0) ? -1 : +1; 1561 if( aEvent.mnDelta == 0 ) 1562 aEvent.mnDelta = aEvent.mnNotchDelta; 1563 aEvent.mbHorz = true; 1564 aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL; 1565 mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent ); 1566 } 1567 if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame )) 1568 { 1569 aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY)); 1570 aEvent.mnNotchDelta = (dY < 0) ? -1 : +1; 1571 if( aEvent.mnDelta == 0 ) 1572 aEvent.mnDelta = aEvent.mnNotchDelta; 1573 aEvent.mbHorz = false; 1574 aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL; 1575 mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent ); 1576 } 1577 1578 // tdf#155266 force flush after scrolling 1579 mpFrame->mbForceFlushScrolling = true; 1580 } 1581} 1582 1583-(void)scrollWheel: (NSEvent*)pEvent 1584{ 1585 SolarMutexGuard aGuard; 1586 1587 if( AquaSalFrame::isAlive( mpFrame ) ) 1588 { 1589 mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 ); 1590 mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; 1591 1592 // merge pending scroll wheel events 1593 CGFloat dX = 0.0; 1594 CGFloat dY = 0.0; 1595 bool bAllowModifiers = isMouseScrollWheelEvent( pEvent ); 1596 for(;;) 1597 { 1598 dX += [pEvent deltaX]; 1599 dY += [pEvent deltaY]; 1600 NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskScrollWheel 1601 untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ]; 1602 if( !pNextEvent || ( isMouseScrollWheelEvent( pNextEvent ) != bAllowModifiers ) ) 1603 break; 1604 pEvent = pNextEvent; 1605 } 1606 1607 NSPoint aPt = [NSEvent mouseLocation]; 1608 mpFrame->CocoaToVCL( aPt ); 1609 1610 SalWheelMouseEvent aEvent; 1611 aEvent.mnTime = mpFrame->mnLastEventTime; 1612 aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x(); 1613 aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y(); 1614 // tdf#151423 Only allow modifiers for mouse scrollwheel events 1615 // The Command modifier converts scrollwheel events into 1616 // magnification events and the Shift modifier converts vertical 1617 // scrollwheel events into horizontal scrollwheel events. This 1618 // behavior is reasonable for mouse scrollwheel events since many 1619 // mice only have a single, vertical scrollwheel but trackpads 1620 // already have specific gestures for magnification and horizontal 1621 // scrolling. So, behave like most macOS applications and ignore 1622 // all modifiers if this a trackpad scrollwheel event. 1623 aEvent.mnCode = bAllowModifiers ? ImplGetModifierMask( mpFrame->mnLastModifierFlags ) : 0; 1624 aEvent.mbDeltaIsPixel = false; 1625 1626 if( AllSettings::GetLayoutRTL() ) 1627 aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX; 1628 1629 if( dX != 0.0 ) 1630 { 1631 aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX)); 1632 aEvent.mnNotchDelta = (dX < 0) ? -1 : +1; 1633 if( aEvent.mnDelta == 0 ) 1634 aEvent.mnDelta = aEvent.mnNotchDelta; 1635 aEvent.mbHorz = true; 1636 sal_uInt32 nScrollLines = fabs(dX) / WHEEL_EVENT_FACTOR; 1637 if (nScrollLines == 0) 1638 nScrollLines = 1; 1639 aEvent.mnScrollLines = nScrollLines; 1640 1641 mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent ); 1642 } 1643 if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ) ) 1644 { 1645 aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY)); 1646 aEvent.mnNotchDelta = (dY < 0) ? -1 : +1; 1647 if( aEvent.mnDelta == 0 ) 1648 aEvent.mnDelta = aEvent.mnNotchDelta; 1649 aEvent.mbHorz = false; 1650 sal_uInt32 nScrollLines = fabs(dY) / WHEEL_EVENT_FACTOR; 1651 if (nScrollLines == 0) 1652 nScrollLines = 1; 1653 aEvent.mnScrollLines = nScrollLines; 1654 1655 mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent ); 1656 } 1657 1658 // tdf#155266 force flush after scrolling 1659 mpFrame->mbForceFlushScrolling = true; 1660 } 1661} 1662 1663 1664-(void)keyDown: (NSEvent*)pEvent 1665{ 1666 SolarMutexGuard aGuard; 1667 1668 if( AquaSalFrame::isAlive( mpFrame ) ) 1669 { 1670 // Retain the event as it will be released sometime before a key up 1671 // event is dispatched 1672 [self clearLastEvent]; 1673 mpLastEvent = [pEvent retain]; 1674 1675 mbInKeyInput = true; 1676 mbNeedSpecialKeyHandle = false; 1677 mbKeyHandled = false; 1678 1679 mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 ); 1680 mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; 1681 1682 if( ! [self handleKeyDownException: pEvent] ) 1683 { 1684 sal_uInt16 nKeyCode = ImplMapKeyCode( [pEvent keyCode] ); 1685 if ( nKeyCode == KEY_DELETE && mbTextInputWantsNonRepeatKeyDown ) 1686 { 1687 // tdf#42437 Enable press-and-hold special character input method 1688 // Emulate the press-and-hold behavior of the TextEdit 1689 // application by deleting the marked text when only the 1690 // Delete key is pressed and keep the marked text when the 1691 // Backspace key or Fn-Delete keys are pressed. 1692 if ( [pEvent keyCode] == 51 ) 1693 { 1694 [self deleteTextInputWantsNonRepeatKeyDown]; 1695 } 1696 else 1697 { 1698 [self unmarkText]; 1699 mbKeyHandled = true; 1700 mbInKeyInput = false; 1701 } 1702 1703 [self endExtTextInput]; 1704 return; 1705 } 1706 1707 NSArray* pArray = [NSArray arrayWithObject: pEvent]; 1708 [self interpretKeyEvents: pArray]; 1709 1710 // Handle repeat key events by explicitly inserting the text if 1711 // -[NSResponder interpretKeyEvents:] does not insert or mark any 1712 // text. Note: do not do this step if there is uncommitted text. 1713 // Related: tdf#42437 Skip special press-and-hold handling for action keys 1714 // Pressing and holding action keys such as arrow keys must not be 1715 // handled like pressing and holding a character key as it will 1716 // insert unexpected text. 1717 if ( !mbKeyHandled && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] ) 1718 { 1719 NSString *pChars = [mpLastEvent characters]; 1720 if ( pChars ) 1721 [self insertText:pChars replacementRange:NSMakeRange( 0, [pChars length] )]; 1722 } 1723 // tdf#42437 Enable press-and-hold special character input method 1724 // Emulate the press-and-hold behavior of the TextEdit application 1725 // by committing an empty string for key down events dispatched 1726 // while the special character input method popup is displayed. 1727 else if ( mpLastMarkedText && mbTextInputWantsNonRepeatKeyDown && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && ![mpLastEvent isARepeat] ) 1728 { 1729 // If the escape or return key is pressed, unmark the text to 1730 // skip deletion of marked text 1731 if ( nKeyCode == KEY_ESCAPE || nKeyCode == KEY_RETURN ) 1732 [self unmarkText]; 1733 [self insertText:[NSString string] replacementRange:NSMakeRange( NSNotFound, 0 )]; 1734 } 1735 } 1736 1737 mbInKeyInput = false; 1738 } 1739} 1740 1741-(BOOL)handleKeyDownException:(NSEvent*)pEvent 1742{ 1743 // check for a very special set of modified characters 1744 NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers]; 1745 1746 if( pUnmodifiedString && [pUnmodifiedString length] == 1 ) 1747 { 1748 /* #i103102# key events with command and alternate don't make it through 1749 interpretKeyEvents (why?). Try to dispatch them here first, 1750 if not successful continue normally 1751 */ 1752 if( (mpFrame->mnLastModifierFlags & (NSEventModifierFlagOption | NSEventModifierFlagCommand)) 1753 == (NSEventModifierFlagOption | NSEventModifierFlagCommand) ) 1754 { 1755 if( [self sendSingleCharacter: mpLastEvent] ) 1756 return YES; 1757 } 1758 } 1759 return NO; 1760} 1761 1762-(void)flagsChanged: (NSEvent*)pEvent 1763{ 1764 SolarMutexGuard aGuard; 1765 1766 if( AquaSalFrame::isAlive( mpFrame ) ) 1767 { 1768 mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 ); 1769 mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; 1770 } 1771} 1772 1773-(void)insertText:(id)aString replacementRange:(NSRange)replacementRange 1774{ 1775 (void) replacementRange; // FIXME: surely it must be used 1776 1777 SolarMutexGuard aGuard; 1778 1779 [self deleteTextInputWantsNonRepeatKeyDown]; 1780 1781 // Ignore duplicate events that are sometimes posted during cancellation 1782 // of the native input method session. This usually happens when 1783 // [self endExtTextInput] is called from [self windowDidBecomeKey:] and, 1784 // if the native input method popup, that was cancelled in a 1785 // previous call to [self windowDidResignKey:], has reappeared. In such 1786 // cases, the native input context posts the reappearing popup's 1787 // uncommitted text. 1788 if (mbInEndExtTextInput && !mbInCommitMarkedText) 1789 return; 1790 1791 if( AquaSalFrame::isAlive( mpFrame ) ) 1792 { 1793 NSString* pInsert = nil; 1794 if( [aString isKindOfClass: [NSAttributedString class]] ) 1795 pInsert = [aString string]; 1796 else 1797 pInsert = aString; 1798 1799 int nLen = 0; 1800 if( pInsert && ( nLen = [pInsert length] ) > 0 ) 1801 { 1802 OUString aInsertString( GetOUString( pInsert ) ); 1803 // aCharCode initializer is safe since aInsertString will at least contain '\0' 1804 sal_Unicode aCharCode = *aInsertString.getStr(); 1805 1806 if( nLen == 1 && 1807 aCharCode < 0x80 && 1808 aCharCode > 0x1f && 1809 ! [self hasMarkedText ] 1810 ) 1811 { 1812 sal_uInt16 nKeyCode = ImplMapCharCode( aCharCode ); 1813 unsigned int nLastModifiers = mpFrame->mnLastModifierFlags; 1814 1815 // #i99567# 1816 // find out the unmodified key code 1817 1818 // sanity check 1819 if( mpLastEvent && ( [mpLastEvent type] == NSEventTypeKeyDown || [mpLastEvent type] == NSEventTypeKeyUp ) ) 1820 { 1821 // get unmodified string 1822 NSString* pUnmodifiedString = [mpLastEvent charactersIgnoringModifiers]; 1823 if( pUnmodifiedString && [pUnmodifiedString length] == 1 ) 1824 { 1825 // map the unmodified key code 1826 unichar keyChar = [pUnmodifiedString characterAtIndex: 0]; 1827 nKeyCode = ImplMapCharCode( keyChar ); 1828 } 1829 nLastModifiers = [mpLastEvent modifierFlags]; 1830 1831 } 1832 // #i99567# 1833 // applications and vcl's edit fields ignore key events with ALT 1834 // however we're at a place where we know text should be inserted 1835 // so it seems we need to strip the Alt modifier here 1836 if( (nLastModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption | NSEventModifierFlagCommand)) 1837 == NSEventModifierFlagOption ) 1838 { 1839 nLastModifiers = 0; 1840 } 1841 [self sendKeyInputAndReleaseToFrame: nKeyCode character: aCharCode modifiers: nLastModifiers]; 1842 } 1843 else 1844 { 1845 SalExtTextInputEvent aEvent; 1846 aEvent.maText = aInsertString; 1847 aEvent.mpTextAttr = nullptr; 1848 aEvent.mnCursorPos = aInsertString.getLength(); 1849 aEvent.mnCursorFlags = 0; 1850 mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent ); 1851 if( AquaSalFrame::isAlive( mpFrame ) ) 1852 mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); 1853 } 1854 } 1855 else 1856 { 1857 SalExtTextInputEvent aEvent; 1858 aEvent.maText.clear(); 1859 aEvent.mpTextAttr = nullptr; 1860 aEvent.mnCursorPos = 0; 1861 aEvent.mnCursorFlags = 0; 1862 mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent ); 1863 if( AquaSalFrame::isAlive( mpFrame ) ) 1864 mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); 1865 1866 } 1867 [self unmarkText]; 1868 } 1869 1870 // Mark event as handled even if the frame isn't valid like is done in 1871 // [self setMarkedText:selectedRange:replacementRange:] and 1872 // [self doCommandBySelector:] 1873 mbKeyHandled = true; 1874} 1875 1876-(void)insertTab: (id)aSender 1877{ 1878 (void)aSender; 1879 [self sendKeyInputAndReleaseToFrame: KEY_TAB character: '\t' modifiers: 0]; 1880} 1881 1882-(void)insertBacktab: (id)aSender 1883{ 1884 (void)aSender; 1885 [self sendKeyInputAndReleaseToFrame: (KEY_TAB | KEY_SHIFT) character: '\t' modifiers: 0]; 1886} 1887 1888-(void)moveLeft: (id)aSender 1889{ 1890 (void)aSender; 1891 [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: 0]; 1892} 1893 1894-(void)moveLeftAndModifySelection: (id)aSender 1895{ 1896 (void)aSender; 1897 [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: NSEventModifierFlagShift]; 1898} 1899 1900-(void)moveBackwardAndModifySelection: (id)aSender 1901{ 1902 (void)aSender; 1903 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_BACKWARD character: 0 modifiers: 0]; 1904} 1905 1906-(void)moveRight: (id)aSender 1907{ 1908 (void)aSender; 1909 [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: 0]; 1910} 1911 1912-(void)moveRightAndModifySelection: (id)aSender 1913{ 1914 (void)aSender; 1915 [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: NSEventModifierFlagShift]; 1916} 1917 1918-(void)moveForwardAndModifySelection: (id)aSender 1919{ 1920 (void)aSender; 1921 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_FORWARD character: 0 modifiers: 0]; 1922} 1923 1924-(void)moveWordLeft: (id)aSender 1925{ 1926 (void)aSender; 1927 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0]; 1928} 1929 1930-(void)moveWordBackward: (id)aSender 1931{ 1932 (void)aSender; 1933 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0]; 1934} 1935 1936-(void)moveWordBackwardAndModifySelection: (id)aSender 1937{ 1938 (void)aSender; 1939 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0]; 1940} 1941 1942-(void)moveWordLeftAndModifySelection: (id)aSender 1943{ 1944 (void)aSender; 1945 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0]; 1946} 1947 1948-(void)moveWordRight: (id)aSender 1949{ 1950 (void)aSender; 1951 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0]; 1952} 1953 1954-(void)moveWordForward: (id)aSender 1955{ 1956 (void)aSender; 1957 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0]; 1958} 1959 1960-(void)moveWordForwardAndModifySelection: (id)aSender 1961{ 1962 (void)aSender; 1963 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0]; 1964} 1965 1966-(void)moveWordRightAndModifySelection: (id)aSender 1967{ 1968 (void)aSender; 1969 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0]; 1970} 1971 1972-(void)moveToEndOfLine: (id)aSender 1973{ 1974 (void)aSender; 1975 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0]; 1976} 1977 1978-(void)moveToRightEndOfLine: (id)aSender 1979{ 1980 (void)aSender; 1981 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0]; 1982} 1983 1984-(void)moveToEndOfLineAndModifySelection: (id)aSender 1985{ 1986 (void)aSender; 1987 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0]; 1988} 1989 1990-(void)moveToRightEndOfLineAndModifySelection: (id)aSender 1991{ 1992 (void)aSender; 1993 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0]; 1994} 1995 1996-(void)moveToBeginningOfLine: (id)aSender 1997{ 1998 (void)aSender; 1999 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; 2000} 2001 2002-(void)moveToLeftEndOfLine: (id)aSender 2003{ 2004 (void)aSender; 2005 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; 2006} 2007 2008-(void)moveToBeginningOfLineAndModifySelection: (id)aSender 2009{ 2010 (void)aSender; 2011 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; 2012} 2013 2014-(void)moveToLeftEndOfLineAndModifySelection: (id)aSender 2015{ 2016 (void)aSender; 2017 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; 2018} 2019 2020-(void)moveToEndOfParagraph: (id)aSender 2021{ 2022 (void)aSender; 2023 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; 2024} 2025 2026-(void)moveToEndOfParagraphAndModifySelection: (id)aSender 2027{ 2028 (void)aSender; 2029 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; 2030} 2031 2032-(void)moveParagraphForward: (id)aSender 2033{ 2034 (void)aSender; 2035 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; 2036} 2037 2038-(void)moveParagraphForwardAndModifySelection: (id)aSender 2039{ 2040 (void)aSender; 2041 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; 2042} 2043 2044-(void)moveToBeginningOfParagraph: (id)aSender 2045{ 2046 (void)aSender; 2047 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; 2048} 2049 2050-(void)moveParagraphBackward: (id)aSender 2051{ 2052 (void)aSender; 2053 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; 2054} 2055 2056-(void)moveToBeginningOfParagraphAndModifySelection: (id)aSender 2057{ 2058 (void)aSender; 2059 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; 2060} 2061 2062-(void)moveParagraphBackwardAndModifySelection: (id)aSender 2063{ 2064 (void)aSender; 2065 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; 2066} 2067 2068-(void)moveToEndOfDocument: (id)aSender 2069{ 2070 (void)aSender; 2071 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0 modifiers: 0]; 2072} 2073 2074-(void)scrollToEndOfDocument: (id)aSender 2075{ 2076 (void)aSender; 2077 // this is not exactly what we should do, but it makes "End" and "Shift-End" behave consistent 2078 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0 modifiers: 0]; 2079} 2080 2081-(void)moveToEndOfDocumentAndModifySelection: (id)aSender 2082{ 2083 (void)aSender; 2084 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_DOCUMENT character: 0 modifiers: 0]; 2085} 2086 2087-(void)moveToBeginningOfDocument: (id)aSender 2088{ 2089 (void)aSender; 2090 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0]; 2091} 2092 2093-(void)scrollToBeginningOfDocument: (id)aSender 2094{ 2095 (void)aSender; 2096 // this is not exactly what we should do, but it makes "Home" and "Shift-Home" behave consistent 2097 [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0]; 2098} 2099 2100-(void)moveToBeginningOfDocumentAndModifySelection: (id)aSender 2101{ 2102 (void)aSender; 2103 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0]; 2104} 2105 2106-(void)moveUp: (id)aSender 2107{ 2108 (void)aSender; 2109 [self sendKeyInputAndReleaseToFrame: KEY_UP character: 0 modifiers: 0]; 2110} 2111 2112-(void)moveDown: (id)aSender 2113{ 2114 (void)aSender; 2115 [self sendKeyInputAndReleaseToFrame: KEY_DOWN character: 0 modifiers: 0]; 2116} 2117 2118-(void)insertNewline: (id)aSender 2119{ 2120 (void)aSender; 2121 // #i91267# make enter and shift-enter work by evaluating the modifiers 2122 [self sendKeyInputAndReleaseToFrame: KEY_RETURN character: '\n' modifiers: mpFrame->mnLastModifierFlags]; 2123} 2124 2125-(void)deleteBackward: (id)aSender 2126{ 2127 (void)aSender; 2128 [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0]; 2129} 2130 2131-(void)deleteForward: (id)aSender 2132{ 2133 (void)aSender; 2134 [self sendKeyInputAndReleaseToFrame: KEY_DELETE character: 0x7f modifiers: 0]; 2135} 2136 2137-(void)deleteBackwardByDecomposingPreviousCharacter: (id)aSender 2138{ 2139 (void)aSender; 2140 [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0]; 2141} 2142 2143-(void)deleteWordBackward: (id)aSender 2144{ 2145 (void)aSender; 2146 [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_BACKWARD character: 0 modifiers: 0]; 2147} 2148 2149-(void)deleteWordForward: (id)aSender 2150{ 2151 (void)aSender; 2152 [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_FORWARD character: 0 modifiers: 0]; 2153} 2154 2155-(void)deleteToBeginningOfLine: (id)aSender 2156{ 2157 (void)aSender; 2158 [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; 2159} 2160 2161-(void)deleteToEndOfLine: (id)aSender 2162{ 2163 (void)aSender; 2164 [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_LINE character: 0 modifiers: 0]; 2165} 2166 2167-(void)deleteToBeginningOfParagraph: (id)aSender 2168{ 2169 (void)aSender; 2170 [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; 2171} 2172 2173-(void)deleteToEndOfParagraph: (id)aSender 2174{ 2175 (void)aSender; 2176 [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; 2177} 2178 2179-(void)insertLineBreak: (id)aSender 2180{ 2181 (void)aSender; 2182 [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_LINEBREAK character: 0 modifiers: 0]; 2183} 2184 2185-(void)insertParagraphSeparator: (id)aSender 2186{ 2187 (void)aSender; 2188 [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_PARAGRAPH character: 0 modifiers: 0]; 2189} 2190 2191-(void)selectWord: (id)aSender 2192{ 2193 (void)aSender; 2194 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD character: 0 modifiers: 0]; 2195} 2196 2197-(void)selectLine: (id)aSender 2198{ 2199 (void)aSender; 2200 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_LINE character: 0 modifiers: 0]; 2201} 2202 2203-(void)selectParagraph: (id)aSender 2204{ 2205 (void)aSender; 2206 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_PARAGRAPH character: 0 modifiers: 0]; 2207} 2208 2209-(void)selectAll: (id)aSender 2210{ 2211 (void)aSender; 2212 [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_ALL character: 0 modifiers: 0]; 2213} 2214 2215-(void)cancelOperation: (id)aSender 2216{ 2217 (void)aSender; 2218 [self sendKeyInputAndReleaseToFrame: KEY_ESCAPE character: 0x1b modifiers: 0]; 2219} 2220 2221-(void)noop: (id)aSender 2222{ 2223 (void)aSender; 2224 if( ! mbKeyHandled && mpLastEvent ) 2225 { 2226 // Related tdf#162843: replace the event's string parameter 2227 // When using the Dvorak - QWERTY keyboard and the Command key 2228 // is pressed, any key events that match a disabled menu item 2229 // are handled here. However, the Dvorak - QWERTY event's 2230 // charactersIgnoringModifiers string can cause cutting and 2231 // copying to fail in the Find toolbar and the Find and Replace 2232 // dialog so replace the event's charactersIgnoringModifiers 2233 // string with the event's character string. 2234 NSEvent* pEvent = mpLastEvent; 2235 NSEventModifierFlags nModMask = [mpLastEvent modifierFlags]; 2236 if( nModMask & NSEventModifierFlagCommand ) 2237 { 2238 switch( [mpLastEvent type] ) 2239 { 2240 case NSEventTypeKeyDown: 2241 case NSEventTypeKeyUp: 2242 case NSEventTypeFlagsChanged: 2243 { 2244 NSString* pCharacters = [mpLastEvent characters]; 2245 NSString* pCharactersIgnoringModifiers = ( nModMask & NSEventModifierFlagShift ) ? [pCharacters uppercaseString] : pCharacters; 2246 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]]; 2247 break; 2248 } 2249 default: 2250 break; 2251 } 2252 } 2253 2254 if( ! [self sendSingleCharacter:pEvent] ) 2255 { 2256 /* prevent recursion */ 2257 if( mpLastEvent != mpLastSuperEvent && [NSApp respondsToSelector: @selector(sendSuperEvent:)] ) 2258 { 2259 id pLastSuperEvent = mpLastSuperEvent; 2260 mpLastSuperEvent = mpLastEvent; 2261 [NSApp performSelector:@selector(sendSuperEvent:) withObject: mpLastEvent]; 2262 mpLastSuperEvent = pLastSuperEvent; 2263 2264 std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent ); 2265 if( it != GetSalData()->maKeyEventAnswer.end() ) 2266 it->second = true; 2267 } 2268 } 2269 } 2270} 2271 2272-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar 2273{ 2274 return [self sendKeyInputAndReleaseToFrame: nKeyCode character: aChar modifiers: mpFrame->mnLastModifierFlags]; 2275} 2276 2277-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod 2278{ 2279 return [self sendKeyToFrameDirect: nKeyCode character: aChar modifiers: nMod] || 2280 [self sendSingleCharacter: mpLastEvent]; 2281} 2282 2283-(BOOL)sendKeyToFrameDirect: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod 2284{ 2285 SolarMutexGuard aGuard; 2286 2287 bool nRet = false; 2288 if( AquaSalFrame::isAlive( mpFrame ) ) 2289 { 2290 SalKeyEvent aEvent; 2291 aEvent.mnCode = nKeyCode | ImplGetModifierMask( nMod ); 2292 aEvent.mnCharCode = aChar; 2293 aEvent.mnRepeat = FALSE; 2294 nRet = mpFrame->CallCallback( SalEvent::KeyInput, &aEvent ); 2295 std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent ); 2296 if( it != GetSalData()->maKeyEventAnswer.end() ) 2297 it->second = nRet; 2298 if( AquaSalFrame::isAlive( mpFrame ) ) 2299 mpFrame->CallCallback( SalEvent::KeyUp, &aEvent ); 2300 } 2301 return nRet; 2302} 2303 2304 2305-(BOOL)sendSingleCharacter: (NSEvent *)pEvent 2306{ 2307 NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers]; 2308 2309 if( pUnmodifiedString && [pUnmodifiedString length] == 1 ) 2310 { 2311 unichar keyChar = [pUnmodifiedString characterAtIndex: 0]; 2312 sal_uInt16 nKeyCode = ImplMapCharCode( keyChar ); 2313 if (nKeyCode == 0) 2314 { 2315 sal_uInt16 nOtherKeyCode = [pEvent keyCode]; 2316 nKeyCode = ImplMapKeyCode(nOtherKeyCode); 2317 } 2318 if( nKeyCode != 0 ) 2319 { 2320 // don't send code points in the private use area 2321 if( keyChar >= 0xf700 && keyChar < 0xf780 ) 2322 keyChar = 0; 2323 bool bRet = [self sendKeyToFrameDirect: nKeyCode character: keyChar modifiers: mpFrame->mnLastModifierFlags]; 2324 mbInKeyInput = false; 2325 2326 return bRet; 2327 } 2328 } 2329 return NO; 2330} 2331 2332 2333// NSTextInput/NSTextInputClient protocol 2334- (NSArray *)validAttributesForMarkedText 2335{ 2336 return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, nil]; 2337} 2338 2339- (BOOL)hasMarkedText 2340{ 2341 bool bHasMarkedText; 2342 2343 bHasMarkedText = ( mMarkedRange.location != NSNotFound ) && 2344 ( mMarkedRange.length != 0 ); 2345 // hack to check keys like "Control-j" 2346 if( mbInKeyInput ) 2347 { 2348 mbNeedSpecialKeyHandle = true; 2349 } 2350 2351 return bHasMarkedText; 2352} 2353 2354- (NSRange)markedRange 2355{ 2356 return [self hasMarkedText] ? mMarkedRange : NSMakeRange( NSNotFound, 0 ); 2357} 2358 2359- (NSRange)selectedRange 2360{ 2361 // tdf#42437 Enable press-and-hold special character input method 2362 // Always return a valid range location. If the range location is 2363 // NSNotFound, -[NSResponder interpretKeyEvents:] will not call 2364 // [self firstRectForCharacterRange:actualRange:] and will not display the 2365 // special character input method popup. 2366 // tdf#128600 Implement handling of macOS "Reverse Conversion" menu item 2367 // When a Japanese keyboard is selected, the keyboard's "Reverse Conversion" 2368 // menu item would silently fail when an empty range was returned by 2369 // -[SalFrameView selectedRange]. 2370 // So return a valid range in that call using the following steps: 2371 // 1. If there is marked text, return the marked text range 2372 // 2. If LibreOffice is selected text, return the selected text length 2373 // Similar steps in the same order are in 2374 // -[SalFrameView attributedSubstringForProposedRange:actualRange:]. 2375 if ( [self hasMarkedText] ) 2376 return ( mMarkedRange.location == NSNotFound ? NSMakeRange( 0, 0 ) : mMarkedRange ); 2377 NSString *pSelectedText = getCurrentSelection(); 2378 if ( pSelectedText ) 2379 return NSMakeRange( 0, [pSelectedText length] ); 2380 return ( mSelectedRange.location == NSNotFound ? NSMakeRange( 0, 0 ) : mSelectedRange ); 2381} 2382 2383- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange 2384{ 2385 (void) replacementRange; // FIXME - use it! 2386 2387 SolarMutexGuard aGuard; 2388 2389 [self deleteTextInputWantsNonRepeatKeyDown]; 2390 2391 if( ![aString isKindOfClass:[NSAttributedString class]] ) 2392 aString = [[[NSAttributedString alloc] initWithString:aString] autorelease]; 2393 2394 // Reset cached state 2395 bool bOldHasMarkedText = [self hasMarkedText]; 2396 [self unmarkText]; 2397 2398 // tdf#163876 ignore marked text generated from Command-` events 2399 // For some unknown reason, when using the standard macOS French 2400 // layout, pressing Command-` causes -[NSView interpretKeyEvents:] 2401 // to temporarily set and unset the marked text. 2402 // Command-` should only cycle through the application's windows 2403 // so ignore marked text changes from such key down events. 2404 if( !bOldHasMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent keyCode] == 42 && ( [mpLastEvent modifierFlags] & ( NSEventModifierFlagCommand | NSEventModifierFlagOption | NSEventModifierFlagControl | NSEventModifierFlagShift ) ) == NSEventModifierFlagCommand ) 2405 { 2406 NSString* pUnmodifiedString = [mpLastEvent charactersIgnoringModifiers]; 2407 if( pUnmodifiedString && ![pUnmodifiedString length] ) 2408 return; 2409 } 2410 2411 int len = [aString length]; 2412 bool bReschedule = false; 2413 SalExtTextInputEvent aInputEvent; 2414 if( len > 0 ) { 2415 // Set the marked and selected ranges to the marked text and selected 2416 // range parameters 2417 mMarkedRange = NSMakeRange( 0, [aString length] ); 2418 if (selRange.location == NSNotFound || selRange.location >= mMarkedRange.length) 2419 mSelectedRange = NSMakeRange( NSNotFound, 0 ); 2420 else 2421 mSelectedRange = NSMakeRange( selRange.location, selRange.location + selRange.length > mMarkedRange.length ? mMarkedRange.length - selRange.location : selRange.length ); 2422 2423 // If we are going to post uncommitted text, cache the string parameter 2424 // as is needed in both [self endExtTextInput] and 2425 // [self attributedSubstringForProposedRange:actualRange:] 2426 mpLastMarkedText = [aString retain]; 2427 2428 NSString *pString = [aString string]; 2429 OUString aInsertString( GetOUString( pString ) ); 2430 std::vector<ExtTextInputAttr> aInputFlags( std::max( 1, len ), ExtTextInputAttr::NONE ); 2431 int nSelectionStart = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location); 2432 int nSelectionEnd = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location + selRange.length); 2433 for ( int i = 0; i < len; i++ ) 2434 { 2435 // Highlight all characters in the selected range. Normally 2436 // uncommitted text is underlined but when an item is selected in 2437 // the native input method popup or selecting a subblock of 2438 // uncommitted text using the left or right arrow keys, the 2439 // selection range is set and the selected range is either 2440 // highlighted like in Excel or is bold underlined like in 2441 // Safari. Highlighting the selected range was chosen because 2442 // using bold and double underlines can get clipped making the 2443 // selection range indistinguishable from the rest of the 2444 // uncommitted text. 2445 if (i >= nSelectionStart && i < nSelectionEnd) 2446 { 2447 aInputFlags[i] = ExtTextInputAttr::Highlight; 2448 continue; 2449 } 2450 2451 unsigned int nUnderlineValue; 2452 NSRange effectiveRange; 2453 2454 effectiveRange = NSMakeRange(i, 1); 2455 nUnderlineValue = [[aString attribute:NSUnderlineStyleAttributeName atIndex:i effectiveRange:&effectiveRange] unsignedIntValue]; 2456 2457 switch (nUnderlineValue & 0xff) { 2458 case NSUnderlineStyleSingle: 2459 aInputFlags[i] = ExtTextInputAttr::Underline; 2460 break; 2461 case NSUnderlineStyleThick: 2462 aInputFlags[i] = ExtTextInputAttr::BoldUnderline; 2463 break; 2464 case NSUnderlineStyleDouble: 2465 aInputFlags[i] = ExtTextInputAttr::DoubleUnderline; 2466 break; 2467 default: 2468 aInputFlags[i] = ExtTextInputAttr::Highlight; 2469 break; 2470 } 2471 } 2472 2473 aInputEvent.maText = aInsertString; 2474 aInputEvent.mnCursorPos = nSelectionStart; 2475 aInputEvent.mnCursorFlags = 0; 2476 aInputEvent.mpTextAttr = aInputFlags.data(); 2477 if( AquaSalFrame::isAlive( mpFrame ) ) 2478 { 2479 bReschedule = true; 2480 mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) ); 2481 } 2482 } else { 2483 aInputEvent.maText.clear(); 2484 aInputEvent.mnCursorPos = 0; 2485 aInputEvent.mnCursorFlags = 0; 2486 aInputEvent.mpTextAttr = nullptr; 2487 if( AquaSalFrame::isAlive( mpFrame ) ) 2488 { 2489 bReschedule = true; 2490 mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) ); 2491 if( AquaSalFrame::isAlive( mpFrame ) ) 2492 mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); 2493 } 2494 } 2495 2496 // tdf#163764 force pending timers to run after marked text changes 2497 // During native dictation, waiting for the next native event is 2498 // blocked while dictation runs in a loop within a native callback. 2499 // Because of this, LibreOffice's painting timers won't fire until 2500 // dictation is cancelled or the user pauses speaking. So, force 2501 // any pending timers to fire after the marked text changes. 2502 if( bReschedule && ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent ) 2503 freezeWindowSizeAndReschedule( [self window] ); 2504 2505 mbKeyHandled = true; 2506} 2507 2508- (void)unmarkText 2509{ 2510 [self clearLastMarkedText]; 2511 2512 mSelectedRange = mMarkedRange = NSMakeRange(NSNotFound, 0); 2513} 2514 2515- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange 2516{ 2517 (void)aRange; 2518 2519 // tdf#128600 Implement handling of macOS "Reverse Conversion" menu item 2520 // When a Japanese keyboard is selected, the keyboard's "Reverse Conversion" 2521 // menu item would silently fail when nil was returned by the unimplemented 2522 // -[SalFrameView attributedSubstringForProposedRange:actualRange:]. 2523 // So return a valid string in that call using the following steps: 2524 // 1. If there is marked text, return the last marked text 2525 // 2. If LibreOffice is selected text, return the selected text 2526 // Similar steps in the same order are in -[SalFrameView selectedRange]. 2527 if ( [self hasMarkedText] ) 2528 { 2529 if ( actualRange ) 2530 *actualRange = mMarkedRange; 2531 return mpLastMarkedText; 2532 } 2533 NSString *pSelectedText = getCurrentSelection(); 2534 if ( pSelectedText ) 2535 { 2536 // Related: tdf#128600 "Reverse Conversion" with Japanese keyboards 2537 // only edits the first non-whitespace chunk of selected text but 2538 // I don't know how to adjust LibreOffice's selected range to match 2539 // the substring that the input method will edit. This causes the 2540 // entire LibreOffice selection to be overwritten by the substring 2541 // so return nil if there is any whitespace in the selection. 2542 NSRange aWhitespaceRange = [pSelectedText rangeOfCharacterFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; 2543 if ( aWhitespaceRange.location == NSNotFound ) 2544 { 2545 if ( actualRange ) 2546 *actualRange = NSMakeRange( 0, [pSelectedText length] ); 2547 return [[[NSAttributedString alloc] initWithString: pSelectedText] autorelease]; 2548 } 2549 } 2550 return nil; 2551} 2552 2553- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint 2554{ 2555 (void)thePoint; 2556 // FIXME 2557 return 0; 2558} 2559 2560- (NSInteger)conversationIdentifier 2561{ 2562 return reinterpret_cast<long>(self); 2563} 2564 2565- (void)doCommandBySelector:(SEL)aSelector 2566{ 2567 if( AquaSalFrame::isAlive( mpFrame ) ) 2568 { 2569 if( (mpFrame->mnICOptions & InputContextFlags::Text) && 2570 aSelector != nullptr && [self respondsToSelector: aSelector] ) 2571 { 2572 [self performSelector: aSelector]; 2573 } 2574 else 2575 { 2576 [self sendSingleCharacter:mpLastEvent]; 2577 } 2578 } 2579 2580 mbKeyHandled = true; 2581} 2582 2583-(void)clearLastEvent 2584{ 2585 if (mpLastEvent) 2586 { 2587 [mpLastEvent release]; 2588 mpLastEvent = nil; 2589 } 2590} 2591 2592-(void)clearLastMarkedText 2593{ 2594 if (mpLastMarkedText) 2595 { 2596 [mpLastMarkedText release]; 2597 mpLastMarkedText = nil; 2598 } 2599 2600 mbTextInputWantsNonRepeatKeyDown = NO; 2601} 2602 2603-(void)clearLastTrackingArea 2604{ 2605 if (mpLastTrackingArea) 2606 { 2607 [self removeTrackingArea: mpLastTrackingArea]; 2608 [mpLastTrackingArea release]; 2609 mpLastTrackingArea = nil; 2610 } 2611} 2612 2613-(void)updateTrackingAreas 2614{ 2615 [super updateTrackingAreas]; 2616 2617 // tdf#155092 use tracking areas instead of tracking rectangles 2618 // Apparently, the older, tracking rectangles selectors cause 2619 // unexpected window resizing upon the first mouse down after the 2620 // window has been manually resized so switch to the newer, 2621 // tracking areas selectors. Also, the NSTrackingInVisibleRect 2622 // option allows us to create one single tracking area that 2623 // resizes itself automatically over the lifetime of the view. 2624 // Note: for some unknown reason, both NSTrackingMouseMoved and 2625 // NSTrackingAssumeInside are necessary options for this fix 2626 // to work. 2627 // Note: for some unknown reason, [mpLastTrackingArea rect] 2628 // returns an empty NSRect (at least on macOS Sequoia) so always 2629 // remove the old tracking area and add a new one. 2630 [self clearLastTrackingArea]; 2631 mpLastTrackingArea = [[NSTrackingArea alloc] initWithRect: [self bounds] options: ( NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingAssumeInside | NSTrackingInVisibleRect ) owner: self userInfo: nil]; 2632 [self addTrackingArea: mpLastTrackingArea]; 2633} 2634 2635- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange 2636{ 2637 // FIXME - These should probably be used? 2638 (void) aRange; 2639 (void) actualRange; 2640 2641 SolarMutexGuard aGuard; 2642 2643 // tdf#42437 Enable press-and-hold special character input method 2644 // Some text entry controls, such as Writer comments or the cell editor in 2645 // Calc's Formula Bar, need to have an input method session open or else 2646 // the returned position won't be anywhere near the text cursor. So, 2647 // dispatch an empty SalEvent::ExtTextInput event, fetch the position, 2648 // and then dispatch a SalEvent::EndExtTextInput event. 2649 NSString *pNewMarkedText = nullptr; 2650 NSString *pChars = [mpLastEvent characters]; 2651 2652 // tdf#158124 KEY_DELETE events do not need an ExtTextInput event 2653 // When using various Japanese input methods, the last event will be a 2654 // repeating key down event with a single delete character while the 2655 // Backspace key, Delete key, or Fn-Delete keys are pressed. These key 2656 // events are now ignored since setting mbTextInputWantsNonRepeatKeyDown 2657 // to YES for these events will trigger an assert or crash when saving a 2658 // .docx document. 2659 bool bNeedsExtTextInput = ( pChars && mbInKeyInput && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] && ImplMapKeyCode( [mpLastEvent keyCode] ) != KEY_DELETE ); 2660 if ( bNeedsExtTextInput ) 2661 { 2662 // tdf#154708 Preserve selection for repeating Shift-arrow on Japanese keyboard 2663 // Skip the posting of SalEvent::ExtTextInput and 2664 // SalEvent::EndExtTextInput events for private use area characters. 2665 NSUInteger nLen = [pChars length]; 2666 auto const pBuf = std::make_unique<unichar[]>( nLen + 1 ); 2667 NSUInteger nBufLen = 0; 2668 for ( NSUInteger i = 0; i < nLen; i++ ) 2669 { 2670 unichar aChar = [pChars characterAtIndex:i]; 2671 if ( aChar >= 0xf700 && aChar < 0xf780 ) 2672 continue; 2673 2674 pBuf[nBufLen++] = aChar; 2675 } 2676 pBuf[nBufLen] = 0; 2677 2678 pNewMarkedText = [NSString stringWithCharacters:pBuf.get() length:nBufLen]; 2679 if (!pNewMarkedText || ![pNewMarkedText length]) 2680 bNeedsExtTextInput = false; 2681 } 2682 2683 if ( bNeedsExtTextInput ) 2684 { 2685 SalExtTextInputEvent aInputEvent; 2686 aInputEvent.maText.clear(); 2687 aInputEvent.mnCursorPos = 0; 2688 aInputEvent.mnCursorFlags = 0; 2689 aInputEvent.mpTextAttr = nullptr; 2690 if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 2691 mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) ); 2692 } 2693 2694 SalExtTextInputPosEvent aPosEvent; 2695 if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 2696 mpFrame->CallCallback( SalEvent::ExtTextInputPos, static_cast<void *>(&aPosEvent) ); 2697 2698 if ( bNeedsExtTextInput ) 2699 { 2700 if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 2701 mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); 2702 2703 // tdf#42437 Enable press-and-hold special character input method 2704 // Emulate the press-and-hold behavior of the TextEdit application by 2705 // setting the marked text to the last key down event's characters. The 2706 // characters will already have been committed by the special character 2707 // input method so set the mbTextInputWantsNonRepeatKeyDown flag to 2708 // indicate that the characters need to be deleted if the input method 2709 // replaces the committed characters. 2710 if ( pNewMarkedText ) 2711 { 2712 [self unmarkText]; 2713 mpLastMarkedText = [[NSAttributedString alloc] initWithString:pNewMarkedText]; 2714 mSelectedRange = mMarkedRange = NSMakeRange( 0, [mpLastMarkedText length] ); 2715 mbTextInputWantsNonRepeatKeyDown = YES; 2716 } 2717 } 2718 2719 NSRect rect; 2720 2721 if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) 2722 { 2723 rect.origin.x = aPosEvent.mnX + mpFrame->GetUnmirroredGeometry().x(); 2724 rect.origin.y = aPosEvent.mnY + mpFrame->GetUnmirroredGeometry().y() + 4; // add some space for underlines 2725 rect.size.width = aPosEvent.mnWidth; 2726 rect.size.height = aPosEvent.mnHeight; 2727 2728 mpFrame->VCLToCocoa( rect ); 2729 } 2730 else 2731 { 2732 rect = NSMakeRect( aPosEvent.mnX, aPosEvent.mnY, aPosEvent.mnWidth, aPosEvent.mnHeight ); 2733 } 2734 2735 return rect; 2736} 2737 2738-(id)parentAttribute { 2739 return reinterpret_cast<NSView*>(mpFrame->getNSWindow()); 2740 //TODO: odd cast really needed for fdo#74121? 2741} 2742 2743-(css::accessibility::XAccessibleContext *)accessibleContext 2744{ 2745 SolarMutexGuard aGuard; 2746 2747 [self insertRegisteredWrapperIntoWrapperRepository]; 2748 if (mpChildWrapper) 2749 return [mpChildWrapper accessibleContext]; 2750 2751 return nil; 2752} 2753 2754-(NSWindow*)windowForParent 2755{ 2756 return mpFrame->getNSWindow(); 2757} 2758 2759-(void)registerMouseEventListener: (id)theListener 2760{ 2761 mpMouseEventListener = theListener; 2762} 2763 2764-(void)unregisterMouseEventListener: (id)theListener 2765{ 2766 (void)theListener; 2767 mpMouseEventListener = nil; 2768} 2769 2770-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender 2771{ 2772 return [mDraggingDestinationHandler draggingEntered: sender]; 2773} 2774 2775-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender 2776{ 2777 return [mDraggingDestinationHandler draggingUpdated: sender]; 2778} 2779 2780-(void)draggingExited:(id <NSDraggingInfo>)sender 2781{ 2782 [mDraggingDestinationHandler draggingExited: sender]; 2783} 2784 2785-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender 2786{ 2787 return [mDraggingDestinationHandler prepareForDragOperation: sender]; 2788} 2789 2790-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender 2791{ 2792 return [mDraggingDestinationHandler performDragOperation: sender]; 2793} 2794 2795-(void)concludeDragOperation:(id <NSDraggingInfo>)sender 2796{ 2797 [mDraggingDestinationHandler concludeDragOperation: sender]; 2798} 2799 2800-(void)registerDraggingDestinationHandler:(id)theHandler 2801{ 2802 mDraggingDestinationHandler = theHandler; 2803} 2804 2805-(void)unregisterDraggingDestinationHandler:(id)theHandler 2806{ 2807 (void)theHandler; 2808 mDraggingDestinationHandler = nil; 2809} 2810 2811-(void)endExtTextInput 2812{ 2813 [self endExtTextInput:EndExtTextInputFlags::Complete]; 2814} 2815 2816-(void)endExtTextInput:(EndExtTextInputFlags)nFlags 2817{ 2818 // Prevent recursion from any additional [self insertText:] calls that 2819 // may be called when cancelling the native input method session 2820 if (mbInEndExtTextInput) 2821 return; 2822 2823 mbInEndExtTextInput = YES; 2824 2825 SolarMutexGuard aGuard; 2826 2827 NSTextInputContext *pInputContext = [NSTextInputContext currentInputContext]; 2828 if (pInputContext) 2829 { 2830 // Cancel the native input method session 2831 [pInputContext discardMarkedText]; 2832 2833 // Commit any uncommitted text. Note: when the delete key is used to 2834 // remove all uncommitted characters, the marked range will be zero 2835 // length but a SalEvent::EndExtTextInput must still be dispatched. 2836 if (mpLastMarkedText && [mpLastMarkedText length] && mMarkedRange.location != NSNotFound && mpFrame && AquaSalFrame::isAlive(mpFrame)) 2837 { 2838 // If there is any marked text, SalEvent::EndExtTextInput may leave 2839 // the cursor hidden so commit the marked text to force the cursor 2840 // to be visible. 2841 mbInCommitMarkedText = YES; 2842 if (nFlags & EndExtTextInputFlags::Complete) 2843 { 2844 // Retain the last marked text as it will be released in 2845 // [self insertText:replacementText:] 2846 NSAttributedString *pText = [mpLastMarkedText retain]; 2847 [self insertText:pText replacementRange:NSMakeRange(0, [mpLastMarkedText length])]; 2848 [pText release]; 2849 } 2850 else 2851 { 2852 [self insertText:[NSString string] replacementRange:NSMakeRange(0, 0)]; 2853 } 2854 mbInCommitMarkedText = NO; 2855 } 2856 2857 [self unmarkText]; 2858 2859 // If a different view is the input context's client, commit that 2860 // view's uncommitted text as well 2861 id<NSTextInputClient> pClient = [pInputContext client]; 2862 if (pClient != self) 2863 { 2864 SalFrameView *pView = static_cast<SalFrameView*>(pClient); 2865 if ([pView isKindOfClass:[SalFrameView class]]) 2866 [pView endExtTextInput]; 2867 else 2868 [pClient unmarkText]; 2869 } 2870 } 2871 2872 mbInEndExtTextInput = NO; 2873} 2874 2875-(void)deleteTextInputWantsNonRepeatKeyDown 2876{ 2877 SolarMutexGuard aGuard; 2878 2879 // tdf#42437 Enable press-and-hold special character input method 2880 // Emulate the press-and-hold behavior of the TextEdit application by 2881 // dispatching backspace events to delete any marked characters. The 2882 // special character input method commits the marked characters so we must 2883 // delete the marked characters before the input method calls 2884 // [self insertText:replacementRange:]. 2885 if (mbTextInputWantsNonRepeatKeyDown) 2886 { 2887 if ( mpLastMarkedText ) 2888 { 2889 NSString *pChars = [mpLastMarkedText string]; 2890 if ( pChars ) 2891 { 2892 NSUInteger nLength = [pChars length]; 2893 for ( NSUInteger i = 0; i < nLength; i++ ) 2894 [self deleteBackward:self]; 2895 } 2896 } 2897 2898 [self unmarkText]; 2899 } 2900} 2901 2902-(void)insertRegisteredWrapperIntoWrapperRepository 2903{ 2904 SolarMutexGuard aGuard; 2905 2906 if (!mbNeedChildWrapper) 2907 return; 2908 2909 vcl::Window *pWindow = mpFrame->GetWindow(); 2910 if (!pWindow) 2911 return; 2912 2913 mbNeedChildWrapper = NO; 2914 2915 ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext > xAccessibleContext( pWindow->GetAccessible()->getAccessibleContext() ); 2916 assert(!mpChildWrapper); 2917 mpChildWrapper = [[SalFrameViewA11yWrapper alloc] initWithParent:self accessibleContext:xAccessibleContext]; 2918 [AquaA11yFactory insertIntoWrapperRepository:mpChildWrapper forAccessibleContext:xAccessibleContext]; 2919} 2920 2921-(void)registerWrapper 2922{ 2923 [self revokeWrapper]; 2924 2925 mbNeedChildWrapper = YES; 2926} 2927 2928-(void)revokeWrapper 2929{ 2930 mbNeedChildWrapper = NO; 2931 2932 if (mpChildWrapper) 2933 { 2934 [AquaA11yFactory revokeWrapper:mpChildWrapper]; 2935 [mpChildWrapper setAccessibilityParent:nil]; 2936 [mpChildWrapper release]; 2937 mpChildWrapper = nil; 2938 } 2939} 2940 2941-(id)accessibilityAttributeValue:(NSString *)pAttribute 2942{ 2943 SolarMutexGuard aGuard; 2944 2945 [self insertRegisteredWrapperIntoWrapperRepository]; 2946 if (mpChildWrapper) 2947 return [mpChildWrapper accessibilityAttributeValue:pAttribute]; 2948 else 2949 return nil; 2950} 2951 2952-(BOOL)accessibilityIsIgnored 2953{ 2954 SolarMutexGuard aGuard; 2955 2956 [self insertRegisteredWrapperIntoWrapperRepository]; 2957 if (mpChildWrapper) 2958 return [mpChildWrapper accessibilityIsIgnored]; 2959 else 2960 return YES; 2961} 2962 2963-(NSArray *)accessibilityAttributeNames 2964{ 2965 SolarMutexGuard aGuard; 2966 2967 [self insertRegisteredWrapperIntoWrapperRepository]; 2968 if (mpChildWrapper) 2969 return [mpChildWrapper accessibilityAttributeNames]; 2970 else 2971 return [NSArray array]; 2972} 2973 2974-(BOOL)accessibilityIsAttributeSettable:(NSString *)pAttribute 2975{ 2976 SolarMutexGuard aGuard; 2977 2978 [self insertRegisteredWrapperIntoWrapperRepository]; 2979 if (mpChildWrapper) 2980 return [mpChildWrapper accessibilityIsAttributeSettable:pAttribute]; 2981 else 2982 return NO; 2983} 2984 2985-(NSArray *)accessibilityParameterizedAttributeNames 2986{ 2987 SolarMutexGuard aGuard; 2988 2989 [self insertRegisteredWrapperIntoWrapperRepository]; 2990 if (mpChildWrapper) 2991 return [mpChildWrapper accessibilityParameterizedAttributeNames]; 2992 else 2993 return [NSArray array]; 2994} 2995 2996-(BOOL)accessibilitySetOverrideValue:(id)pValue forAttribute:(NSString *)pAttribute 2997{ 2998 SolarMutexGuard aGuard; 2999 3000 [self insertRegisteredWrapperIntoWrapperRepository]; 3001 if (mpChildWrapper) 3002 return [mpChildWrapper accessibilitySetOverrideValue:pValue forAttribute:pAttribute]; 3003 else 3004 return NO; 3005} 3006 3007-(void)accessibilitySetValue:(id)pValue forAttribute:(NSString *)pAttribute 3008{ 3009 SolarMutexGuard aGuard; 3010 3011 [self insertRegisteredWrapperIntoWrapperRepository]; 3012 if (mpChildWrapper) 3013 [mpChildWrapper accessibilitySetValue:pValue forAttribute:pAttribute]; 3014} 3015 3016-(id)accessibilityAttributeValue:(NSString *)pAttribute forParameter:(id)pParameter 3017{ 3018 SolarMutexGuard aGuard; 3019 3020 [self insertRegisteredWrapperIntoWrapperRepository]; 3021 if (mpChildWrapper) 3022 return [mpChildWrapper accessibilityAttributeValue:pAttribute forParameter:pParameter]; 3023 else 3024 return nil; 3025} 3026 3027-(id)accessibilityFocusedUIElement 3028{ 3029 SolarMutexGuard aGuard; 3030 3031 [self insertRegisteredWrapperIntoWrapperRepository]; 3032 if (mpChildWrapper) 3033 return [mpChildWrapper accessibilityFocusedUIElement]; 3034 else 3035 return nil; 3036} 3037 3038-(NSString *)accessibilityActionDescription:(NSString *)pAction 3039{ 3040 SolarMutexGuard aGuard; 3041 3042 [self insertRegisteredWrapperIntoWrapperRepository]; 3043 if (mpChildWrapper) 3044 return [mpChildWrapper accessibilityActionDescription:pAction]; 3045 else 3046 return nil; 3047} 3048 3049-(void)accessibilityPerformAction:(NSString *)pAction 3050{ 3051 SolarMutexGuard aGuard; 3052 3053 [self insertRegisteredWrapperIntoWrapperRepository]; 3054 if (mpChildWrapper) 3055 [mpChildWrapper accessibilityPerformAction:pAction]; 3056} 3057 3058-(NSArray *)accessibilityActionNames 3059{ 3060 SolarMutexGuard aGuard; 3061 3062 [self insertRegisteredWrapperIntoWrapperRepository]; 3063 if (mpChildWrapper) 3064 return [mpChildWrapper accessibilityActionNames]; 3065 else 3066 return [NSArray array]; 3067} 3068 3069-(id)accessibilityHitTest:(NSPoint)aPoint 3070{ 3071 SolarMutexGuard aGuard; 3072 3073 [self insertRegisteredWrapperIntoWrapperRepository]; 3074 if (mpChildWrapper) 3075 return [mpChildWrapper accessibilityHitTest:aPoint]; 3076 else 3077 return nil; 3078} 3079 3080-(id)accessibilityParent 3081{ 3082 return [self window]; 3083} 3084 3085-(NSArray *)accessibilityVisibleChildren 3086{ 3087 return [self accessibilityChildren]; 3088} 3089 3090-(NSArray *)accessibilitySelectedChildren 3091{ 3092 SolarMutexGuard aGuard; 3093 3094 NSArray *pRet = [super accessibilityChildren]; 3095 3096 [self insertRegisteredWrapperIntoWrapperRepository]; 3097 if (mpChildWrapper) 3098 pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilitySelectedChildren]); 3099 3100 return pRet; 3101} 3102 3103-(NSArray *)accessibilityChildren 3104{ 3105 SolarMutexGuard aGuard; 3106 3107 NSArray *pRet = [super accessibilityChildren]; 3108 3109 [self insertRegisteredWrapperIntoWrapperRepository]; 3110 if (mpChildWrapper) 3111 pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilityChildren]); 3112 3113 return pRet; 3114} 3115 3116-(NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder 3117{ 3118 return [self accessibilityChildren]; 3119} 3120 3121-(void)viewDidChangeEffectiveAppearance 3122{ 3123 if (mbInViewDidChangeEffectiveAppearance) 3124 return; 3125 3126 mbInViewDidChangeEffectiveAppearance = YES; 3127 3128 // Related: tdf#156855 force the current theme to reload its colors 3129 // This call is called when the macOS light/dark mode changes while 3130 // LibreOffice is running. Send a SalEvent::SettingsChanged event 3131 // but do it in an idle timer. Otherwise, an infinite recursion 3132 // can occur. 3133 NSWindow *pWindow = [self window]; 3134 if (pWindow && ([pWindow isVisible] || [pWindow isMiniaturized])) 3135 { 3136 SolarMutexGuard aGuard; 3137 3138 GetSalData()->mpInstance->delayedSettingsChanged(true); 3139 } 3140 3141 mbInViewDidChangeEffectiveAppearance = NO; 3142} 3143 3144-(void)mouseDraggedWithTimer: (NSTimer *)pTimer 3145{ 3146 (void)pTimer; 3147 3148 if ( mpPendingMouseDraggedEvent ) 3149 { 3150 if ( mpMouseEventListener != nil && 3151 [mpMouseEventListener respondsToSelector: @selector(mouseDragged:)]) 3152 { 3153 [mpMouseEventListener mouseDragged: mpPendingMouseDraggedEvent]; 3154 } 3155 3156 s_nLastButton = MOUSE_LEFT; 3157 [self sendMouseEventToFrame:mpPendingMouseDraggedEvent button:MOUSE_LEFT eventtype:SalEvent::MouseMove]; 3158 3159 [self clearPendingMouseDraggedEvent]; 3160 } 3161 else 3162 { 3163 [self clearMouseDraggedTimer]; 3164 } 3165} 3166 3167@end 3168 3169@implementation SalFrameViewA11yWrapper 3170 3171-(id)initWithParent:(SalFrameView *)pParentView accessibleContext:(::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >&)rxAccessibleContext 3172{ 3173 [super init]; 3174 3175 maReferenceWrapper.rAccessibleContext = rxAccessibleContext; 3176 3177 mpParentView = pParentView; 3178 if (mpParentView) 3179 { 3180 [mpParentView retain]; 3181 [self setAccessibilityParent:mpParentView]; 3182 } 3183 3184 return self; 3185} 3186 3187-(void)dealloc 3188{ 3189 if (mpParentView) 3190 [mpParentView release]; 3191 3192 [super dealloc]; 3193} 3194 3195-(id)parentAttribute 3196{ 3197 if (mpParentView) 3198 return NSAccessibilityUnignoredAncestor(mpParentView); 3199 else 3200 return nil; 3201} 3202 3203-(void)setAccessibilityParent:(id)pObject 3204{ 3205 if (mpParentView) 3206 { 3207 [mpParentView release]; 3208 mpParentView = nil; 3209 } 3210 3211 if (pObject && [pObject isKindOfClass:[SalFrameView class]]) 3212 { 3213 mpParentView = static_cast<SalFrameView *>(pObject); 3214 [mpParentView retain]; 3215 } 3216 3217 [super setAccessibilityParent:mpParentView]; 3218} 3219 3220-(id)windowAttribute 3221{ 3222 if (mpParentView) 3223 return [mpParentView window]; 3224 else 3225 return nil; 3226} 3227 3228-(NSWindow *)windowForParent 3229{ 3230 return [self windowAttribute]; 3231} 3232 3233@end 3234 3235/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 3236
