Edit

kc3-lang/angle/util/osx/OSXWindow.mm

Branch :

  • Show log

    Commit

  • Author : Corentin Wallez
    Date : 2016-11-02 14:11:01
    Hash : b526f541
    Message : OSXWindow: ignore deprecated warnings We can't fix them yet because our continuous testing doesn't compile with the updated definitions yet. BUG=angleproject:1598 Change-Id: I40bdeaa6bafbd03b5e3e6c16ac8485e3f488fe59 Reviewed-on: https://chromium-review.googlesource.com/406452 Commit-Queue: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Jamie Madill <jmadill@chromium.org>

  • util/osx/OSXWindow.mm
  • //
    // Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    // OSXWindow.mm: Implementation of OSWindow for OSX
    
    #include "osx/OSXWindow.h"
    
    #include <set>
    // Include Carbon to use the keycode names in Carbon's Event.h
    #include <Carbon/Carbon.h>
    
    #include "common/debug.h"
    
    // On OSX 10.12 a number of AppKit interfaces have been renamed for consistency, and the previous
    // symbols tagged as deprecated. However we can't simply use the new symbols as it would break
    // compilation on our automated testing that doesn't use OSX 10.12 yet. So we just ignore the
    // warnings.
    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    
    // Some events such as "ShouldTerminate" are sent to the whole application so we keep a list of
    // all the windows in order to forward the event to each of them. However this and calling pushEvent
    // in ApplicationDelegate is inherently unsafe in a multithreaded environment.
    static std::set<OSXWindow*> gAllWindows;
    
    @interface Application : NSApplication
    @end
    
    @implementation Application
        - (void) sendEvent: (NSEvent*) nsEvent
        {
            if ([nsEvent type] == NSApplicationDefined)
            {
                for (auto window : gAllWindows)
                {
                    if ([window->getNSWindow() windowNumber] == [nsEvent windowNumber])
                    {
                        Event event;
                        event.Type = Event::EVENT_TEST;
                        window->pushEvent(event);
                    }
                }
            }
            [super sendEvent: nsEvent];
        }
    @end
    
    // The Delegate receiving application-wide events.
    @interface ApplicationDelegate : NSObject
    @end
    
    @implementation ApplicationDelegate
        - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) sender
        {
            Event event;
            event.Type = Event::EVENT_CLOSED;
            for (auto window : gAllWindows)
            {
                window->pushEvent(event);
            }
            return NSTerminateCancel;
        }
    @end
    static ApplicationDelegate *gApplicationDelegate = nil;
    
    static bool InitializeAppKit()
    {
        if (NSApp != nil)
        {
            return true;
        }
    
        // Initialize the global variable "NSApp"
        [Application sharedApplication];
    
        // Make us appear in the dock
        [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    
        // Register our global event handler
        gApplicationDelegate = [[ApplicationDelegate alloc] init];
        if (gApplicationDelegate == nil)
        {
            return false;
        }
        [NSApp setDelegate: static_cast<id>(gApplicationDelegate)];
    
        // Set our status to "started" so we are not bouncing in the doc and can activate
        [NSApp finishLaunching];
        return true;
    }
    
    // NS's and CG's coordinate systems start at the bottom left, while OSWindow's coordinate
    // system starts at the top left. This function converts the Y coordinate accordingly.
    static float YCoordToFromCG(float y)
    {
        float screenHeight = CGDisplayBounds(CGMainDisplayID()).size.height;
        return screenHeight - y;
    }
    
    // Delegate for window-wide events, note that the protocol doesn't contain anything input related.
    @implementation WindowDelegate
        - (id) initWithWindow: (OSXWindow*) window
        {
            self = [super init];
            if (self != nil)
            {
                mWindow = window;
            }
            return self;
        }
    
        - (void) onOSXWindowDeleted
        {
            mWindow = nil;
        }
    
        - (BOOL) windowShouldClose: (id) sender
        {
            Event event;
            event.Type = Event::EVENT_CLOSED;
            mWindow->pushEvent(event);
            return NO;
        }
    
        - (void) windowDidResize: (NSNotification*) notification
        {
            NSSize windowSize = [[mWindow->getNSWindow() contentView] frame].size;
            Event event;
            event.Type = Event::EVENT_RESIZED;
            event.Size.Width = windowSize.width;
            event.Size.Height = windowSize.height;
            mWindow->pushEvent(event);
        }
    
        - (void) windowDidMove: (NSNotification*) notification
        {
            NSRect screenspace = [mWindow->getNSWindow() frame];
            Event event;
            event.Type = Event::EVENT_MOVED;
            event.Move.X = screenspace.origin.x;
            event.Move.Y = YCoordToFromCG(screenspace.origin.y + screenspace.size.height);
            mWindow->pushEvent(event);
        }
    
        - (void) windowDidBecomeKey: (NSNotification*) notification
        {
            Event event;
            event.Type = Event::EVENT_GAINED_FOCUS;
            mWindow->pushEvent(event);
            [self retain];
        }
    
        - (void) windowDidResignKey: (NSNotification*) notification
        {
            if (mWindow != nil)
            {
                Event event;
                event.Type = Event::EVENT_LOST_FOCUS;
                mWindow->pushEvent(event);
            }
            [self release];
        }
    @end
    
    static Key NSCodeToKey(int keyCode)
    {
        // Missing KEY_PAUSE
        switch (keyCode)
        {
          case kVK_Shift:               return KEY_LSHIFT;
          case kVK_RightShift:          return KEY_RSHIFT;
          case kVK_Option:              return KEY_LALT;
          case kVK_RightOption:         return KEY_RALT;
          case kVK_Control:             return KEY_LCONTROL;
          case kVK_RightControl:        return KEY_RCONTROL;
          case kVK_Command:             return KEY_LSYSTEM;
          // Right System doesn't have a name, but shows up as 0x36.
          case 0x36:                    return KEY_RSYSTEM;
          case kVK_Function:            return KEY_MENU;
    
          case kVK_ANSI_Semicolon:      return KEY_SEMICOLON;
          case kVK_ANSI_Slash:          return KEY_SLASH;
          case kVK_ANSI_Equal:          return KEY_EQUAL;
          case kVK_ANSI_Minus:          return KEY_DASH;
          case kVK_ANSI_LeftBracket:    return KEY_LBRACKET;
          case kVK_ANSI_RightBracket:   return KEY_RBRACKET;
          case kVK_ANSI_Comma:          return KEY_COMMA;
          case kVK_ANSI_Period:         return KEY_PERIOD;
          case kVK_ANSI_Backslash:      return KEY_BACKSLASH;
          case kVK_ANSI_Grave:          return KEY_TILDE;
          case kVK_Escape:              return KEY_ESCAPE;
          case kVK_Space:               return KEY_SPACE;
          case kVK_Return:              return KEY_RETURN;
          case kVK_Delete:              return KEY_BACK;
          case kVK_Tab:                 return KEY_TAB;
          case kVK_PageUp:              return KEY_PAGEUP;
          case kVK_PageDown:            return KEY_PAGEDOWN;
          case kVK_End:                 return KEY_END;
          case kVK_Home:                return KEY_HOME;
          case kVK_Help:                return KEY_INSERT;
          case kVK_ForwardDelete:       return KEY_DELETE;
          case kVK_ANSI_KeypadPlus:     return KEY_ADD;
          case kVK_ANSI_KeypadMinus:    return KEY_SUBTRACT;
          case kVK_ANSI_KeypadMultiply: return KEY_MULTIPLY;
          case kVK_ANSI_KeypadDivide:   return KEY_DIVIDE;
    
          case kVK_F1:                  return KEY_F1;
          case kVK_F2:                  return KEY_F2;
          case kVK_F3:                  return KEY_F3;
          case kVK_F4:                  return KEY_F4;
          case kVK_F5:                  return KEY_F5;
          case kVK_F6:                  return KEY_F6;
          case kVK_F7:                  return KEY_F7;
          case kVK_F8:                  return KEY_F8;
          case kVK_F9:                  return KEY_F9;
          case kVK_F10:                 return KEY_F10;
          case kVK_F11:                 return KEY_F11;
          case kVK_F12:                 return KEY_F12;
          case kVK_F13:                 return KEY_F13;
          case kVK_F14:                 return KEY_F14;
          case kVK_F15:                 return KEY_F15;
    
          case kVK_LeftArrow:           return KEY_LEFT;
          case kVK_RightArrow:          return KEY_RIGHT;
          case kVK_DownArrow:           return KEY_DOWN;
          case kVK_UpArrow:             return KEY_UP;
    
          case kVK_ANSI_Keypad0:        return KEY_NUMPAD0;
          case kVK_ANSI_Keypad1:        return KEY_NUMPAD1;
          case kVK_ANSI_Keypad2:        return KEY_NUMPAD2;
          case kVK_ANSI_Keypad3:        return KEY_NUMPAD3;
          case kVK_ANSI_Keypad4:        return KEY_NUMPAD4;
          case kVK_ANSI_Keypad5:        return KEY_NUMPAD5;
          case kVK_ANSI_Keypad6:        return KEY_NUMPAD6;
          case kVK_ANSI_Keypad7:        return KEY_NUMPAD7;
          case kVK_ANSI_Keypad8:        return KEY_NUMPAD8;
          case kVK_ANSI_Keypad9:        return KEY_NUMPAD9;
    
          case kVK_ANSI_A:              return KEY_A;
          case kVK_ANSI_B:              return KEY_B;
          case kVK_ANSI_C:              return KEY_C;
          case kVK_ANSI_D:              return KEY_D;
          case kVK_ANSI_E:              return KEY_E;
          case kVK_ANSI_F:              return KEY_F;
          case kVK_ANSI_G:              return KEY_G;
          case kVK_ANSI_H:              return KEY_H;
          case kVK_ANSI_I:              return KEY_I;
          case kVK_ANSI_J:              return KEY_J;
          case kVK_ANSI_K:              return KEY_K;
          case kVK_ANSI_L:              return KEY_L;
          case kVK_ANSI_M:              return KEY_M;
          case kVK_ANSI_N:              return KEY_N;
          case kVK_ANSI_O:              return KEY_O;
          case kVK_ANSI_P:              return KEY_P;
          case kVK_ANSI_Q:              return KEY_Q;
          case kVK_ANSI_R:              return KEY_R;
          case kVK_ANSI_S:              return KEY_S;
          case kVK_ANSI_T:              return KEY_T;
          case kVK_ANSI_U:              return KEY_U;
          case kVK_ANSI_V:              return KEY_V;
          case kVK_ANSI_W:              return KEY_W;
          case kVK_ANSI_X:              return KEY_X;
          case kVK_ANSI_Y:              return KEY_Y;
          case kVK_ANSI_Z:              return KEY_Z;
    
          case kVK_ANSI_1:              return KEY_NUM1;
          case kVK_ANSI_2:              return KEY_NUM2;
          case kVK_ANSI_3:              return KEY_NUM3;
          case kVK_ANSI_4:              return KEY_NUM4;
          case kVK_ANSI_5:              return KEY_NUM5;
          case kVK_ANSI_6:              return KEY_NUM6;
          case kVK_ANSI_7:              return KEY_NUM7;
          case kVK_ANSI_8:              return KEY_NUM8;
          case kVK_ANSI_9:              return KEY_NUM9;
          case kVK_ANSI_0:              return KEY_NUM0;
        }
    
        return Key(0);
    }
    
    static void AddNSKeyStateToEvent(Event *event, int state)
    {
        event->Key.Shift = state & NSShiftKeyMask;
        event->Key.Control = state & NSControlKeyMask;
        event->Key.Alt = state & NSAlternateKeyMask;
        event->Key.System = state & NSCommandKeyMask;
    }
    
    static MouseButton TranslateMouseButton(int button)
    {
        switch (button)
        {
          case 2:
            return MOUSEBUTTON_MIDDLE;
          case 3:
            return MOUSEBUTTON_BUTTON4;
          case 4:
            return MOUSEBUTTON_BUTTON5;
          default:
            return MOUSEBUTTON_UNKNOWN;
        }
    }
    
    // Delegate for NSView events, mostly the input events
    @implementation ContentView
        - (id) initWithWindow: (OSXWindow*) window
        {
            self = [super init];
            if (self != nil)
            {
                mWindow = window;
                mTrackingArea = nil;
                mCurrentModifier = 0;
                [self updateTrackingAreas];
            }
            return self;
        }
    
        - (void) dealloc
        {
            [mTrackingArea release];
            [super dealloc];
        }
    
        - (void) updateTrackingAreas
        {
            if (mTrackingArea != nil)
            {
                [self removeTrackingArea: mTrackingArea];
                [mTrackingArea release];
                mTrackingArea = nil;
            }
    
            NSRect bounds = [self bounds];
            NSTrackingAreaOptions flags = NSTrackingMouseEnteredAndExited |
                                          NSTrackingActiveInKeyWindow |
                                          NSTrackingCursorUpdate |
                                          NSTrackingInVisibleRect |
                                          NSTrackingAssumeInside;
            mTrackingArea = [[NSTrackingArea alloc] initWithRect: bounds
                                                        options: flags
                                                          owner: self
                                                       userInfo: nil];
    
            [self addTrackingArea: mTrackingArea];
            [super updateTrackingAreas];
        }
    
        // Helps with performance
        - (BOOL) isOpaque
        {
            return YES;
        }
    
        - (BOOL) canBecomeKeyView
        {
            return YES;
        }
    
        - (BOOL) acceptsFirstResponder
        {
            return YES;
        }
    
        // Handle mouse events from the NSResponder protocol
        - (float) translateMouseY: (float) y
        {
            return [self frame].size.height - y;
        }
    
        - (void) addButtonEvent: (NSEvent*) nsEvent type:(Event::EventType) eventType button:(MouseButton) button
        {
            Event event;
            event.Type = eventType;
            event.MouseButton.Button = button;
            event.MouseButton.X = [nsEvent locationInWindow].x;
            event.MouseButton.Y = [self translateMouseY: [nsEvent locationInWindow].y];
            mWindow->pushEvent(event);
        }
    
        - (void) mouseDown: (NSEvent*) event
        {
            [self addButtonEvent: event
                            type: Event::EVENT_MOUSE_BUTTON_PRESSED
                          button: MOUSEBUTTON_LEFT];
        }
    
        - (void) mouseDragged: (NSEvent*) event
        {
            [self mouseMoved: event];
        }
    
        - (void) mouseUp: (NSEvent*) event
        {
            [self addButtonEvent: event
                            type: Event::EVENT_MOUSE_BUTTON_RELEASED
                          button: MOUSEBUTTON_LEFT];
        }
    
        - (void) mouseMoved: (NSEvent*) nsEvent
        {
            Event event;
            event.Type = Event::EVENT_MOUSE_MOVED;
            event.MouseMove.X = [nsEvent locationInWindow].x;
            event.MouseMove.Y = [self translateMouseY: [nsEvent locationInWindow].y];
            mWindow->pushEvent(event);
        }
    
        - (void) mouseEntered: (NSEvent*) nsEvent
        {
            Event event;
            event.Type = Event::EVENT_MOUSE_ENTERED;
            mWindow->pushEvent(event);
        }
    
        - (void) mouseExited: (NSEvent*) nsEvent
        {
            Event event;
            event.Type = Event::EVENT_MOUSE_LEFT;
            mWindow->pushEvent(event);
        }
    
        - (void)rightMouseDown:(NSEvent *)event
        {
            [self addButtonEvent: event
                            type: Event::EVENT_MOUSE_BUTTON_PRESSED
                          button: MOUSEBUTTON_RIGHT];
        }
    
        - (void) rightMouseDragged: (NSEvent*) event
        {
            [self mouseMoved: event];
        }
    
        - (void) rightMouseUp: (NSEvent*)event
        {
            [self addButtonEvent: event
                            type: Event::EVENT_MOUSE_BUTTON_RELEASED
                          button: MOUSEBUTTON_RIGHT];
        }
    
        - (void) otherMouseDown: (NSEvent*) event
        {
            [self addButtonEvent: event
                            type: Event::EVENT_MOUSE_BUTTON_PRESSED
                          button: TranslateMouseButton([event buttonNumber])];
        }
    
        - (void) otherMouseDragged: (NSEvent*) event
        {
            [self mouseMoved: event];
        }
    
        - (void) otherMouseUp: (NSEvent*) event
        {
            [self addButtonEvent: event
                            type: Event::EVENT_MOUSE_BUTTON_RELEASED
                          button: TranslateMouseButton([event buttonNumber])];
        }
    
        - (void) scrollWheel: (NSEvent*) nsEvent
        {
            if (static_cast<int>([nsEvent deltaY]) == 0)
            {
                return;
            }
    
            Event event;
            event.Type = Event::EVENT_MOUSE_WHEEL_MOVED;
            event.MouseWheel.Delta = [nsEvent deltaY];
            mWindow->pushEvent(event);
        }
    
        // Handle key events from the NSResponder protocol
        - (void) keyDown: (NSEvent*) nsEvent
        {
            // TODO(cwallez) also send text events
            Event event;
            event.Type = Event::EVENT_KEY_PRESSED;
            event.Key.Code = NSCodeToKey([nsEvent keyCode]);
            AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
            mWindow->pushEvent(event);
        }
    
        - (void) keyUp: (NSEvent*) nsEvent
        {
            Event event;
            event.Type = Event::EVENT_KEY_RELEASED;
            event.Key.Code = NSCodeToKey([nsEvent keyCode]);
            AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
            mWindow->pushEvent(event);
        }
    
        // Modifier keys do not trigger keyUp/Down events but only flagsChanged events.
        - (void) flagsChanged: (NSEvent*) nsEvent
        {
            Event event;
    
            // Guess if the key has been pressed or released with the change of modifiers
            // It currently doesn't work when modifiers are unchanged, such as when pressing
            // both shift keys. GLFW has a solution for this but it requires tracking the
            // state of the keys. Implementing this is still TODO(cwallez)
            int modifier = [nsEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
            if (modifier < mCurrentModifier)
            {
                event.Type = Event::EVENT_KEY_RELEASED;
            }
            else
            {
                event.Type = Event::EVENT_KEY_PRESSED;
            }
            mCurrentModifier = modifier;
    
            event.Key.Code = NSCodeToKey([nsEvent keyCode]);
            AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
            mWindow->pushEvent(event);
        }
    @end
    
    OSXWindow::OSXWindow()
        : mWindow(nil),
          mDelegate(nil),
          mView(nil)
    {
    }
    
    OSXWindow::~OSXWindow()
    {
        destroy();
    }
    
    bool OSXWindow::initialize(const std::string &name, size_t width, size_t height)
    {
        if (!InitializeAppKit())
        {
            return false;
        }
    
        unsigned int styleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
                                 NSMiniaturizableWindowMask;
        mWindow = [[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, width, height)
                                              styleMask: styleMask
                                                backing: NSBackingStoreBuffered
                                                  defer: NO];
    
        if (mWindow == nil)
        {
            return false;
        }
    
        mDelegate = [[WindowDelegate alloc] initWithWindow: this];
        if (mDelegate == nil)
        {
            return false;
        }
        [mWindow setDelegate: static_cast<id>(mDelegate)];
    
        mView = [[ContentView alloc] initWithWindow: this];
        if (mView == nil)
        {
            return false;
        }
        [mView setWantsLayer:YES];
    
        [mWindow setContentView: mView];
        [mWindow setTitle: [NSString stringWithUTF8String: name.c_str()]];
        [mWindow setAcceptsMouseMovedEvents: YES];
        [mWindow center];
    
        [NSApp activateIgnoringOtherApps: YES];
    
        mX = 0;
        mY = 0;
        mWidth = width;
        mHeight = height;
    
        gAllWindows.insert(this);
        return true;
    }
    
    void OSXWindow::destroy()
    {
        gAllWindows.erase(this);
    
        [mView release];
        mView = nil;
        [mDelegate onOSXWindowDeleted];
        [mDelegate release];
        mDelegate = nil;
        [mWindow release];
        mWindow = nil;
    }
    
    EGLNativeWindowType OSXWindow::getNativeWindow() const
    {
        return [mView layer];
    }
    
    EGLNativeDisplayType OSXWindow::getNativeDisplay() const
    {
        // TODO(cwallez): implement it once we have defined what EGLNativeDisplayType is
        return static_cast<EGLNativeDisplayType>(0);
    }
    
    void OSXWindow::messageLoop()
    {
        @autoreleasepool
        {
            while (true)
            {
                NSEvent* event = [NSApp nextEventMatchingMask: NSAnyEventMask
                                                    untilDate: [NSDate distantPast]
                                                       inMode: NSDefaultRunLoopMode
                                                      dequeue: YES];
                if (event == nil)
                {
                    break;
                }
    
                if ([event type] == NSAppKitDefined)
                {
                    continue;
                }
                [NSApp sendEvent: event];
            }
        }
    }
    
    void OSXWindow::setMousePosition(int x, int y)
    {
        y = [mWindow frame].size.height - y -1;
        NSPoint screenspace;
    
        #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
            screenspace = [mWindow convertBaseToScreen: NSMakePoint(x, y)];
        #else
            screenspace = [mWindow convertRectToScreen: NSMakeRect(x, y, 0, 0)].origin;
        #endif
        CGWarpMouseCursorPosition(CGPointMake(screenspace.x, YCoordToFromCG(screenspace.y)));
    }
    
    bool OSXWindow::setPosition(int x, int y)
    {
        // Given CG and NS's coordinate system, the "Y" position of a window is the Y coordinate
        // of the bottom of the window.
        int newBottom = [mWindow frame].size.height + y;
        NSRect emptyRect = NSMakeRect(x, YCoordToFromCG(newBottom), 0, 0);
        [mWindow setFrameOrigin: [mWindow frameRectForContentRect: emptyRect].origin];
        return true;
    }
    
    bool OSXWindow::resize(int width, int height)
    {
        [mWindow setContentSize: NSMakeSize(width, height)];
        return true;
    }
    
    void OSXWindow::setVisible(bool isVisible)
    {
        if (isVisible)
        {
            [mWindow makeKeyAndOrderFront: nil];
        }
        else
        {
            [mWindow orderOut: nil];
        }
    }
    
    void OSXWindow::signalTestEvent()
    {
        @autoreleasepool
        {
            NSEvent *event = [NSEvent otherEventWithType: NSApplicationDefined
                                                location: NSMakePoint(0, 0)
                                           modifierFlags: 0
                                               timestamp: 0.0
                                            windowNumber: [mWindow windowNumber]
                                                 context: nil
                                                 subtype: 0
                                                   data1: 0
                                                   data2: 0];
            [NSApp postEvent: event atStart: YES];
        }
    }
    
    NSWindow* OSXWindow::getNSWindow() const
    {
        return mWindow;
    }
    
    OSWindow *CreateOSWindow()
    {
        return new OSXWindow;
    }