581 lines
16 KiB
C
581 lines
16 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <AvailabilityMacros.h>
|
|
#include "config.h"
|
|
#import "GdkQuartzView.h"
|
|
#include "gdkquartzwindow.h"
|
|
#include "gdkprivate-quartz.h"
|
|
#include "gdkquartz.h"
|
|
#include "gdkinternal-quartz.h"
|
|
#include <cairo/cairo-quartz.h>
|
|
#import <AppKit/AppKit.h>
|
|
#import <IOSurface/IOSurface.h>
|
|
|
|
@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
|