/* LIBGIMP - The GIMP Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * gimppickbutton-quartz.c * Copyright (C) 2015 Kristian Rietveld * * 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 * Library 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 "config.h" #include #include #include #include "libgimpcolor/gimpcolor.h" #include "gimpwidgetstypes.h" #include "gimppickbutton.h" #include "gimppickbutton-quartz.h" #include "cursors/gimp-color-picker-cursors.c" #ifdef GDK_WINDOWING_QUARTZ #import #include /* For virtual key codes ... */ #include #endif #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 @protocol NSWindowDelegate @end #endif @interface NSWindow (GIMPExt) - (NSRect) convertRectToScreen: (NSRect)aRect; @end @implementation NSWindow (GIMPExt) - (NSRect) convertRectToScreen: (NSRect)aRect { NSRect result = aRect; NSPoint origin = result.origin; result.origin = [self convertBaseToScreen:origin]; return result; } @end #endif @interface GimpPickWindowController : NSObject { GimpPickButton *button; NSMutableArray *windows; } @property (nonatomic, assign) BOOL firstBecameKey; @property (readonly, retain) NSCursor *cursor; - (id)initWithButton:(GimpPickButton *)_button; - (void)updateKeyWindow; - (void)shutdown; @end @interface GimpPickView : NSView { GimpPickButton *button; GimpPickWindowController *controller; } @property (readonly,assign) NSTrackingArea *area; - (id)initWithButton:(GimpPickButton *)_button controller:(GimpPickWindowController *)controller; @end @implementation GimpPickView @synthesize area; - (id)initWithButton:(GimpPickButton *)_button controller:(GimpPickWindowController *)_controller { self = [super init]; if (self) { button = _button; controller = _controller; } return self; } - (void)dealloc { [self removeTrackingArea:self.area]; [super dealloc]; } - (void)viewDidMoveToWindow { NSTrackingAreaOptions options; [super viewDidMoveToWindow]; if ([self window] == nil) return; options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways; /* Add assume inside if mouse pointer is above this window */ if (NSPointInRect ([NSEvent mouseLocation], self.window.frame)) options |= NSTrackingAssumeInside; area = [[NSTrackingArea alloc] initWithRect:self.bounds options:options owner:self userInfo:nil]; [self addTrackingArea:self.area]; } - (void)mouseEntered:(NSEvent *)event { /* We handle the mouse cursor manually, see also the comment in * [GimpPickWindow windowDidBecomeMain below]. */ if (controller.cursor) [controller.cursor push]; } - (void)mouseExited:(NSEvent *)event { if (controller.cursor) [controller.cursor pop]; [controller updateKeyWindow]; } - (void)mouseMoved:(NSEvent *)event { [self pickColor:event]; } - (void)mouseUp:(NSEvent *)event { [self pickColor:event]; [controller shutdown]; } - (void)rightMouseUp:(NSEvent *)event { [self mouseUp:event]; } - (void)otherMouseUp:(NSEvent *)event { [self mouseUp:event]; } - (void)keyDown:(NSEvent *)event { if (event.keyCode == kVK_Escape) [controller shutdown]; } - (void)pickColor:(NSEvent *)event { CGImageRef root_image_ref; CFDataRef pixel_data; const guchar *data; GimpRGB rgb; NSPoint point; GimpColorProfile *profile = NULL; CGColorSpaceRef color_space = NULL; /* The event gives us a point in Cocoa window coordinates. The function * CGWindowListCreateImage expects a rectangle in screen coordinates * with the origin in the upper left (contrary to Cocoa). The origin is * on the screen showing the menu bar (this is the screen at index 0 in the * screens array). So, after converting the rectangle to Cocoa screen * coordinates, we use the height of this particular screen to translate * to the coordinate space expected by CGWindowListCreateImage. */ point = event.locationInWindow; NSRect rect = NSMakeRect (point.x, point.y, 1, 1); rect = [self.window convertRectToScreen:rect]; rect.origin.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.origin.y; root_image_ref = CGWindowListCreateImage (rect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault); pixel_data = CGDataProviderCopyData (CGImageGetDataProvider (root_image_ref)); data = CFDataGetBytePtr (pixel_data); color_space = CGImageGetColorSpace (root_image_ref); if (color_space) { CFDataRef icc_data = NULL; icc_data = CGColorSpaceCopyICCProfile (color_space); if (icc_data) { UInt8 *buffer = g_malloc (CFDataGetLength (icc_data)); CFDataGetBytes (icc_data, CFRangeMake (0, CFDataGetLength (icc_data)), buffer); profile = gimp_color_profile_new_from_icc_profile (buffer, CFDataGetLength (icc_data), NULL); g_free (buffer); CFRelease (icc_data); } } gimp_rgba_set_uchar (&rgb, data[2], data[1], data[0], 255); if (profile) { GimpColorProfile *srgb_profile; GimpColorTransform *transform; const Babl *format; GimpColorTransformFlags flags = 0; format = babl_format ("R'G'B'A double"); flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE; flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION; srgb_profile = gimp_color_profile_new_rgb_srgb (); transform = gimp_color_transform_new (profile, format, srgb_profile, format, GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL, flags); if (transform) { gimp_color_transform_process_pixels (transform, format, &rgb, format, &rgb, 1); gimp_rgb_clamp (&rgb); g_object_unref (transform); } g_object_unref (srgb_profile); g_object_unref (profile); } CGImageRelease (root_image_ref); CFRelease (pixel_data); g_signal_emit_by_name (button, "color-picked", &rgb); } @end @interface GimpPickWindow : NSWindow { GimpPickWindowController *controller; } - (id)initWithButton:(GimpPickButton *)button forScreen:(NSScreen *)screen withController:(GimpPickWindowController *)_controller; @end @implementation GimpPickWindow - (id)initWithButton:(GimpPickButton *)button forScreen:(NSScreen *)screen withController:(GimpPickWindowController *)_controller { self = [super initWithContentRect:screen.frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; if (self) { GimpPickView *view; controller = _controller; [self setDelegate:self]; [self setAlphaValue:0.0]; #if 0 /* Useful for debugging purposes */ [self setBackgroundColor:[NSColor redColor]]; [self setAlphaValue:0.2]; #endif [self setIgnoresMouseEvents:NO]; [self setAcceptsMouseMovedEvents:YES]; [self setHasShadow:NO]; [self setOpaque:NO]; /* Set the highest level, so on top of everything */ [self setLevel:NSScreenSaverWindowLevel]; view = [[GimpPickView alloc] initWithButton:button controller:controller]; [self setContentView:view]; [self makeFirstResponder:view]; [view release]; [self disableCursorRects]; } return self; } /* Borderless windows cannot become key/main by default, so we force it * to make it so. We need this to receive events. */ - (BOOL)canBecomeKeyWindow { return YES; } - (BOOL)canBecomeMainWindow { return YES; } - (void)windowDidBecomeKey:(NSNotification *)aNotification { /* We cannot use the usual Cocoa method for handling cursor updates, * since the GDK Quartz backend is interfering. Additionally, because * one of the screen-spanning windows pops up under the mouse pointer this * view will not receive a MouseEntered event. So, we synthesize such * an event here and the view can set the mouse pointer in response to * this. So, this event only has to be synthesized once and only for * the window that pops up under the mouse cursor. Synthesizing multiple * times messes up the mouse cursor stack. * * We cannot set the mouse pointer at this moment, because the GDK window * will still receive an MouseExited event in which turn it will modify * the cursor. So, with this synthesized event we also ensure we set * the mouse cursor *after* the GDK window has manipulated the cursor. */ NSEvent *event; if (!controller.firstBecameKey || !NSPointInRect ([NSEvent mouseLocation], self.frame)) return; controller.firstBecameKey = NO; event = [NSEvent enterExitEventWithType:NSMouseEntered location:[self mouseLocationOutsideOfEventStream] modifierFlags:0 timestamp:[[NSApp currentEvent] timestamp] windowNumber:self.windowNumber context:nil eventNumber:0 trackingNumber:(NSInteger)[[self contentView] area] userData:nil]; [NSApp postEvent:event atStart:NO]; } @end /* To properly handle multi-monitor setups we need to create a * GimpPickWindow for each monitor (NSScreen). This is necessary because * a window on Mac OS X (tested on 10.9) cannot span more than one * monitor, so any approach that attempts to create one large window * spanning all monitors cannot work. So, we have to create multiple * windows in case of multi-monitor setups and these different windows * are managed by GimpPickWindowController. */ @implementation GimpPickWindowController @synthesize firstBecameKey; @synthesize cursor; - (id)initWithButton:(GimpPickButton *)_button; { self = [super init]; if (self) { firstBecameKey = YES; button = _button; cursor = [GimpPickWindowController makePickCursor]; windows = [[NSMutableArray alloc] init]; for (NSScreen *screen in [NSScreen screens]) { GimpPickWindow *window; window = [[GimpPickWindow alloc] initWithButton:button forScreen:screen withController:self]; [window orderFrontRegardless]; [window makeMainWindow]; [windows addObject:window]; } [self updateKeyWindow]; } return self; } - (void)updateKeyWindow { for (GimpPickWindow *window in windows) { if (NSPointInRect ([NSEvent mouseLocation], window.frame)) [window makeKeyWindow]; } } - (void)shutdown { GtkWidget *window; for (GimpPickWindow *window in windows) [window close]; [windows release]; if (cursor) [cursor release]; /* Give focus back to the window containing the pick button */ window = gtk_widget_get_toplevel (GTK_WIDGET (button)); gtk_window_present_with_time (GTK_WINDOW (window), GDK_CURRENT_TIME); [self release]; } + (NSCursor *)makePickCursor { GBytes *bytes = NULL; GError *error = NULL; bytes = g_resources_lookup_data ("/org/gimp/color-picker-cursors-raw/cursor-color-picker.png", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); if (bytes) { NSData *data = [NSData dataWithBytes:g_bytes_get_data (bytes, NULL) length:g_bytes_get_size (bytes)]; NSImage *image = [[NSImage alloc] initWithData:data]; NSCursor *cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(1, 30)]; [image release]; g_bytes_unref (bytes); return [cursor retain]; } else { g_critical ("Failed to create cursor image: %s", error->message); g_clear_error (&error); } return NULL; } @end /* entry point to this file, called from gimppickbutton.c */ void _gimp_pick_button_quartz_pick (GimpPickButton *button) { GimpPickWindowController *controller; NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; controller = [[GimpPickWindowController alloc] initWithButton:button]; [pool release]; }