summaryrefslogtreecommitdiffstats
path: root/libgimpwidgets/gimppickbutton-quartz.c
diff options
context:
space:
mode:
Diffstat (limited to 'libgimpwidgets/gimppickbutton-quartz.c')
-rw-r--r--libgimpwidgets/gimppickbutton-quartz.c485
1 files changed, 485 insertions, 0 deletions
diff --git a/libgimpwidgets/gimppickbutton-quartz.c b/libgimpwidgets/gimppickbutton-quartz.c
new file mode 100644
index 0000000..5d581d3
--- /dev/null
+++ b/libgimpwidgets/gimppickbutton-quartz.c
@@ -0,0 +1,485 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimppickbutton-quartz.c
+ * Copyright (C) 2015 Kristian Rietveld <kris@loopnest.org>
+ *
+ * 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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <AppKit/AppKit.h>
+#include <Carbon/Carbon.h> /* For virtual key codes ... */
+#include <ApplicationServices/ApplicationServices.h>
+#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 <NSWindowDelegate>
+{
+ 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];
+}