/* GdkQuartzView.m * * Copyright (C) 2005-2007 Imendio AB * Copyright (C) 2011 Hiroyuki Yamamoto * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include #include "config.h" #import "GdkQuartzView.h" #include "gdkquartzwindow.h" #include "gdkprivate-quartz.h" #include "gdkquartz.h" #include "gdkinternal-quartz.h" #include #import #import @implementation GdkQuartzView -(id)initWithFrame: (NSRect)frameRect { if ((self = [super initWithFrame: frameRect])) { pb_props = @{ (id)kCVPixelBufferIOSurfaceCoreAnimationCompatibilityKey: @1, (id)kCVPixelBufferBytesPerRowAlignmentKey: @64, }; [pb_props retain]; cfpb_props = (__bridge CFDictionaryRef)pb_props; markedRange = NSMakeRange (NSNotFound, 0); selectedRange = NSMakeRange (0, 0); } [self setValue: @(YES) forKey: @"postsFrameChangedNotifications"]; return self; } -(BOOL)acceptsFirstResponder { GDK_NOTE (EVENTS, g_message ("acceptsFirstResponder")); return YES; } -(BOOL)becomeFirstResponder { GDK_NOTE (EVENTS, g_message ("becomeFirstResponder")); return YES; } -(BOOL)resignFirstResponder { GDK_NOTE (EVENTS, g_message ("resignFirstResponder")); return YES; } -(void) keyDown: (NSEvent *) theEvent { /* NOTE: When user press Cmd+A, interpretKeyEvents: will call noop: method. When user press and hold A to show the accented char window, it consumed repeating key down events for key 'A' do NOT call any other method. We use this behavior to determine if this key down event is filtered by interpretKeyEvents. */ g_object_set_data (G_OBJECT (gdk_window), GIC_FILTER_KEY, GUINT_TO_POINTER (GIC_FILTER_FILTERED)); GDK_NOTE (EVENTS, g_message ("keyDown")); [self interpretKeyEvents: [NSArray arrayWithObject: theEvent]]; } -(void)flagsChanged: (NSEvent *) theEvent { } -(NSUInteger)characterIndexForPoint: (NSPoint)aPoint { GDK_NOTE (EVENTS, g_message ("characterIndexForPoint")); return 0; } -(NSRect)firstRectForCharacterRange: (NSRange)aRange actualRange: (NSRangePointer)actualRange { GDK_NOTE (EVENTS, g_message ("firstRectForCharacterRange")); gint ns_x, ns_y; GdkRectangle *rect; rect = g_object_get_data (G_OBJECT (gdk_window), GIC_CURSOR_RECT); if (rect) { _gdk_quartz_window_gdk_xy_to_xy (rect->x, rect->y + rect->height, &ns_x, &ns_y); return NSMakeRect (ns_x, ns_y, rect->width, rect->height); } else { return NSMakeRect (0, 0, 0, 0); } } -(NSArray *)validAttributesForMarkedText { GDK_NOTE (EVENTS, g_message ("validAttributesForMarkedText")); return [NSArray arrayWithObjects: NSUnderlineStyleAttributeName, nil]; } -(NSAttributedString *)attributedSubstringForProposedRange: (NSRange)aRange actualRange: (NSRangePointer)actualRange { GDK_NOTE (EVENTS, g_message ("attributedSubstringForProposedRange")); return nil; } -(BOOL)hasMarkedText { GDK_NOTE (EVENTS, g_message ("hasMarkedText")); return markedRange.location != NSNotFound && markedRange.length != 0; } -(NSRange)markedRange { GDK_NOTE (EVENTS, g_message ("markedRange")); return markedRange; } -(NSRange)selectedRange { GDK_NOTE (EVENTS, g_message ("selectedRange")); return selectedRange; } -(void)unmarkText { GDK_NOTE (EVENTS, g_message ("unmarkText")); selectedRange = NSMakeRange (0, 0); markedRange = NSMakeRange (NSNotFound, 0); g_object_set_data_full (G_OBJECT (gdk_window), TIC_MARKED_TEXT, NULL, g_free); } -(void)setMarkedText: (id)aString selectedRange: (NSRange)newSelection replacementRange: (NSRange)replacementRange { GDK_NOTE (EVENTS, g_message ("setMarkedText")); const char *str; if (replacementRange.location == NSNotFound) { markedRange = NSMakeRange (newSelection.location, [aString length]); selectedRange = NSMakeRange (newSelection.location, newSelection.length); } else { markedRange = NSMakeRange (replacementRange.location, [aString length]); selectedRange = NSMakeRange (replacementRange.location + newSelection.location, newSelection.length); } if ([aString isKindOfClass: [NSAttributedString class]]) { str = [[aString string] UTF8String]; } else { str = [aString UTF8String]; } g_object_set_data_full (G_OBJECT (gdk_window), TIC_MARKED_TEXT, g_strdup (str), g_free); g_object_set_data (G_OBJECT (gdk_window), TIC_SELECTED_POS, GUINT_TO_POINTER (selectedRange.location)); g_object_set_data (G_OBJECT (gdk_window), TIC_SELECTED_LEN, GUINT_TO_POINTER (selectedRange.length)); GDK_NOTE (EVENTS, g_message ("setMarkedText: set %s (%p, nsview %p): %s", TIC_MARKED_TEXT, gdk_window, self, str ? str : "(empty)")); /* handle text input changes by mouse events */ if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (gdk_window), TIC_IN_KEY_DOWN))) { _gdk_quartz_synthesize_null_key_event(gdk_window); } } -(void)doCommandBySelector: (SEL)aSelector { GDK_NOTE (EVENTS, g_message ("doCommandBySelector %s", [NSStringFromSelector (aSelector) UTF8String])); g_object_set_data (G_OBJECT (gdk_window), GIC_FILTER_KEY, GUINT_TO_POINTER (GIC_FILTER_PASSTHRU)); } -(void)insertText: (id)aString replacementRange: (NSRange)replacementRange { GDK_NOTE (EVENTS, g_message ("insertText")); const char *str; NSString *string; if ([self hasMarkedText]) [self unmarkText]; if ([aString isKindOfClass: [NSAttributedString class]]) string = [aString string]; else string = aString; NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; if ([string rangeOfCharacterFromSet:ctrlChars].length && [string rangeOfCharacterFromSet:wsnlChars].length == 0) { /* discard invalid text input with Chinese input methods */ str = ""; [self unmarkText]; #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; [currentInputManager markedTextAbandoned:self]; #else [[NSTextInputContext currentInputContext] discardMarkedText]; #endif } else { str = [string UTF8String]; selectedRange = NSMakeRange ([string length], 0); } if (replacementRange.length > 0) { g_object_set_data (G_OBJECT (gdk_window), TIC_INSERT_TEXT_REPLACE_LEN, GINT_TO_POINTER (replacementRange.length)); } g_object_set_data_full (G_OBJECT (gdk_window), TIC_INSERT_TEXT, g_strdup (str), g_free); GDK_NOTE (EVENTS, g_message ("insertText: set %s (%p, nsview %p): %s", TIC_INSERT_TEXT, gdk_window, self, str ? str : "(empty)")); g_object_set_data (G_OBJECT (gdk_window), GIC_FILTER_KEY, GUINT_TO_POINTER (GIC_FILTER_FILTERED)); /* handle text input changes by mouse events */ if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (gdk_window), TIC_IN_KEY_DOWN))) { _gdk_quartz_synthesize_null_key_event(gdk_window); } } /* --------------------------------------------------------------- */ -(void)dealloc { if (trackingRect) { [self removeTrackingRect: trackingRect]; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 10500 [(NSTrackingArea*)trackingRect release]; #endif trackingRect = 0; } if (pixels) { CVPixelBufferRelease (pixels); } [pb_props release]; [super dealloc]; } -(void)setGdkWindow: (GdkWindow *)window { gdk_window = window; } -(GdkWindow *)gdkWindow { return gdk_window; } -(NSTrackingRectTag)trackingRect { return trackingRect; } -(BOOL)isFlipped { return NO; } -(BOOL)isOpaque { if (GDK_WINDOW_DESTROYED (gdk_window)) return YES; /* A view is opaque if its GdkWindow doesn't have the RGBA visual */ return gdk_window_get_visual (gdk_window) != gdk_screen_get_rgba_visual (_gdk_screen); } - (void) viewWillDraw { /* MacOS 11 (Big Sur) has added a new, dynamic "accent" as default. * This uses a 10-bit colorspace so every GIMP drawing operation * has the additional cost of an 8-bit (ARGB) to 10-bit conversion. * Let's disable this mode to regain the lost performance. */ if(gdk_quartz_osx_version() >= GDK_OSX_BIGSUR) { #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101100 CALayer* layer = self.layer; layer.contentsFormat = kCAContentsFormatRGBA8Uint; #endif } [super viewWillDraw]; } #if MAC_OS_X_VERSION_MIN_REQUIRED >= 10900 -(BOOL)wantsUpdateLayer { return YES; } #endif -(BOOL)wantsLayer { return YES; } static void cairo_rect_from_nsrect (cairo_rectangle_int_t *rect, NSRect *nsrect) { rect->x = (int)nsrect->origin.x; rect->y = (int)nsrect->origin.y; rect->width = (int)nsrect->size.width; rect->height = (int)nsrect->size.height; } static cairo_status_t copy_rectangle_argb32 (cairo_surface_t *dest, cairo_surface_t *source, cairo_region_t *region) { cairo_surface_t *source_img, *dest_img; cairo_status_t status; cairo_format_t format; int height, width, stride; cairo_rectangle_int_t extents; cairo_region_get_extents (region, &extents); source_img = cairo_surface_map_to_image (source, &extents); status = cairo_surface_status (source_img); if (status) { g_warning ("Failed to map source image surface, %d %d %d %d on %d %d: %s\n", extents.x, extents.y, extents.width, extents.height, cairo_image_surface_get_width (source), cairo_image_surface_get_height (source), cairo_status_to_string (status)); return status; } format = cairo_image_surface_get_format (source_img); dest_img = cairo_surface_map_to_image (dest, &extents); status = cairo_surface_status (dest_img); if (status) { g_warning ("Failed to map destination image surface, %d %d %d %d on %d %d: %s\n", extents.x, extents.y, extents.width, extents.height, cairo_image_surface_get_width (dest), cairo_image_surface_get_height (dest), cairo_status_to_string (status)); goto CLEANUP; } width = cairo_image_surface_get_width (source_img); stride = cairo_format_stride_for_width (format, width); height = cairo_image_surface_get_height (source_img); memcpy (cairo_image_surface_get_data (dest_img), cairo_image_surface_get_data (source_img), stride * height); cairo_surface_unmap_image (dest, dest_img); CLEANUP: cairo_surface_unmap_image (source, source_img); return status; } -(void)updateLayer { GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (gdk_window->impl); cairo_rectangle_int_t impl_rect = {0, 0, 0, 0}; CGRect layer_bounds = [self.layer bounds]; CGRect backing_bounds = [self convertRectToBacking: layer_bounds]; cairo_rectangle_int_t bounds_rect; cairo_region_t *bounds_region; cairo_surface_t *cvpb_surface; if (GDK_WINDOW_DESTROYED (gdk_window)) return; ++impl->in_paint_rect_count; if (impl->needs_display_region) { cairo_region_t *region = impl->needs_display_region; _gdk_window_process_updates_recurse (gdk_window, region); cairo_region_destroy (region); impl->needs_display_region = NULL; } else { cairo_rectangle_int_t bounds; cairo_region_t *region; cairo_rect_from_nsrect (&bounds, &layer_bounds); region = cairo_region_create_rectangle(&bounds); _gdk_window_process_updates_recurse (gdk_window, region); cairo_region_destroy (region); } if (!impl || !impl->cairo_surface) return; CVPixelBufferLockBaseAddress (pixels, 0); cvpb_surface = cairo_image_surface_create_for_data (CVPixelBufferGetBaseAddress (pixels), CAIRO_FORMAT_ARGB32, (int)CVPixelBufferGetWidth (pixels), (int)CVPixelBufferGetHeight (pixels), (int)CVPixelBufferGetBytesPerRow (pixels)); cairo_rect_from_nsrect (&bounds_rect, &backing_bounds); bounds_region = cairo_region_create_rectangle (&bounds_rect); impl_rect.width = cairo_image_surface_get_width (impl->cairo_surface); impl_rect.height = cairo_image_surface_get_height (impl->cairo_surface); cairo_region_intersect_rectangle (bounds_region, &impl_rect); copy_rectangle_argb32 (cvpb_surface, impl->cairo_surface, bounds_region); cairo_surface_destroy (cvpb_surface); cairo_region_destroy (bounds_region); _gdk_quartz_unref_cairo_surface (gdk_window); // reffed in gdk_window_impl_quartz_begin_paint CVPixelBufferUnlockBaseAddress (pixels, 0); --impl->in_paint_rect_count; self.layer.contents = NULL; self.layer.contents = (id)CVPixelBufferGetIOSurface (pixels); } -(void)setNeedsInvalidateShadow: (BOOL)invalidate { needsInvalidateShadow = invalidate; } /* For information on setting up tracking rects properly, see here: * http://developer.apple.com/documentation/Cocoa/Conceptual/EventOverview/EventOverview.pdf */ -(void)updateTrackingRect { GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (gdk_window->impl); NSRect rect; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 10500 NSTrackingArea *trackingArea; #endif if (!impl || !impl->toplevel) return; if (trackingRect) { [self removeTrackingRect: trackingRect]; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 10500 [(NSTrackingArea*)trackingRect release]; #endif trackingRect = 0; } if (!impl->toplevel) return; /* Note, if we want to set assumeInside we can use: * NSPointInRect ([[self window] convertScreenToBase:[NSEvent mouseLocation]], rect) */ rect = [self bounds]; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 10500 trackingArea = [[NSTrackingArea alloc] initWithRect: rect options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingCursorUpdate | NSTrackingActiveInActiveApp | NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag owner: self userInfo: nil]; [self addTrackingArea: trackingArea]; trackingRect = (NSInteger)trackingArea; #else trackingRect = [self addTrackingRect: rect owner: self userData: nil assumeInside: NO]; #endif } -(void)viewDidMoveToWindow { if (![self window]) /* We are destroyed already */ return; [self updateTrackingRect]; } -(void)viewWillMoveToWindow: (NSWindow *)newWindow { if (newWindow == nil && trackingRect) { [self removeTrackingRect: trackingRect]; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 10500 [(NSTrackingArea*)trackingRect release]; #endif trackingRect = 0; } } -(void)createBackingStoreWithWidth: (CGFloat) width andHeight: (CGFloat) height { IOSurfaceRef surface; g_return_if_fail (width && height); CVPixelBufferRelease (pixels); CVPixelBufferCreate (NULL, width, height, kCVPixelFormatType_32BGRA, cfpb_props, &pixels); surface = CVPixelBufferGetIOSurface (pixels); IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB); } #if MAC_OS_X_VERSION_MIN_REQUIRED >= 10700 -(BOOL)layer:(CALayer*) layer shouldInheritContentsScale: (CGFloat)scale fromWindow: (NSWindow *) window { if (layer == self.layer && window == self.window) { _gdk_quartz_unref_cairo_surface (gdk_window); [self setNeedsDisplay: YES]; } return YES; } #endif -(void)setFrame: (NSRect)frame { if (GDK_WINDOW_DESTROYED (gdk_window)) return; _gdk_quartz_unref_cairo_surface (gdk_window); [super setFrame: frame]; if ([self window]) [self updateTrackingRect]; } @end