/* Copyright (c) 2006-2007 Christopher J. W. Lloyd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import @implementation NSColorWell + (void) initialize { // FIXME: need [NSObject setKeys:triggerChangeNotificationsForDependentKey:] // [self setKeys:[NSArray arrayWithObjects:@"color", @"something", nil] // triggerChangeNotificationsForDependentKey:@"value"]; } - (id) _replacementKeyPathForBinding: (id) binding { if ([binding isEqual: @"value"]) return @"color"; return [super _replacementKeyPathForBinding: binding]; } // private NSNotificationName _NSColorWellDidBecomeExclusiveNotification = @"_NSColorWellDidBecomeExclusiveNotification"; - (void) encodeWithCoder: (NSCoder *) coder { NSUnimplementedMethod(); } - (instancetype) initWithCoder: (NSCoder *) coder { [super initWithCoder: coder]; if ([coder allowsKeyedCoding]) { NSKeyedUnarchiver *keyed = (NSKeyedUnarchiver *) coder; _isEnabled = [keyed decodeBoolForKey: @"NSEnabled"]; _isContinuous = ![keyed decodeBoolForKey: @"NSIsNotContinuous"]; _isBordered = [keyed decodeBoolForKey: @"NSIsBordered"]; _color = [[keyed decodeObjectForKey: @"NSColor"] retain]; } else { [NSException raise: NSInvalidArgumentException format: @"-[%@ %s] is not implemented for coder %@", [self class], sel_getName(_cmd), coder]; } return self; } - (instancetype) initWithFrame: (NSRect) frame { [super initWithFrame: frame]; _isEnabled = YES; _isContinuous = YES; _isBordered = YES; _color = [[NSColor whiteColor] copy]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(colorPanelWillClose:) name: NSWindowWillCloseNotification object: [NSColorPanel sharedColorPanel]]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(colorWellDidBecomeExclusive:) name: _NSColorWellDidBecomeExclusiveNotification object: nil]; [self registerForDraggedTypes: @[ NSColorPboardType ]]; return self; } - (void) awakeFromNib { // this should be moved the nib initWithCoder: [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(colorPanelWillClose:) name: NSWindowWillCloseNotification object: [NSColorPanel sharedColorPanel]]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(colorWellDidBecomeExclusive:) name: _NSColorWellDidBecomeExclusiveNotification object: nil]; [self registerForDraggedTypes: @[ NSColorPboardType ]]; } - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver: self]; [_color release]; [super dealloc]; } - (void) colorWellDidBecomeExclusive: (NSNotification *) note { if ([note object] != self) [self deactivate]; } - (void) colorPanelWillClose: (NSNotification *) note { if ([self isActive]) [self deactivate]; } - (id) target { return _target; } - (void) setTarget: (id) target { _target = target; } - (SEL) action { return _action; } - (void) setAction: (SEL) action { _action = action; } - (BOOL) isEnabled { return _isEnabled; } - (void) setEnabled: (BOOL) flag { _isEnabled = flag; [self setNeedsDisplay: YES]; } - (NSColor *) color { return _color; } - (BOOL) isBordered { return _isBordered; } - (BOOL) isActive { return _isActive && [self isEnabled]; } - (void) setColor: (NSColor *) color { if (NSIsControllerMarker(color)) return [self setColor: [NSColor blackColor]]; if ([_color isEqual: color]) { return; } color = [color retain]; [_color release]; _color = color; if ([self isActive] && color != nil) { // Pass it on. _notifyingColorPanel = YES; [[NSColorPanel sharedColorPanel] setColor: color]; _notifyingColorPanel = NO; } [self setNeedsDisplay: YES]; } - (void) setBordered: (BOOL) flag { _isBordered = flag; } - (void) activate: (BOOL) exclusive { if (_isActive == YES) { return; } if (exclusive) { NSNotification *notification = [NSNotification notificationWithName: _NSColorWellDidBecomeExclusiveNotification object: self]; [[NSNotificationQueue defaultQueue] enqueueNotification: notification postingStyle: NSPostNow coalesceMask: NSNotificationCoalescingOnName forModes: nil]; } [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(changeColorWhenActive:) name: NSColorPanelColorDidChangeNotification object: [NSColorPanel sharedColorPanel]]; // Update the color panel with our color. [[NSColorPanel sharedColorPanel] setColor: [self color]]; [NSApp orderFrontColorPanel: self]; _isActive = YES; [self setNeedsDisplay: YES]; } - (void) deactivate { if (_isActive == NO) return; _isActive = NO; [[NSNotificationCenter defaultCenter] removeObserver: self name: NSColorPanelColorDidChangeNotification object: [NSColorPanel sharedColorPanel]]; [self setNeedsDisplay: YES]; } - (void) changeColorWhenActive: (NSNotification *) note { if (_notifyingColorPanel == NO) { [self setColor: [[note object] color]]; [self sendAction: _action to: _target]; } } - (BOOL) isOpaque { return YES; } - (void) drawWellInside: (NSRect) rect { [_color drawSwatchInRect: rect]; } - (void) drawRect: (NSRect) rect { rect = _bounds; rect = [[self graphicsStyle] drawColorWellBorderInRect: rect enabled: [self isEnabled] bordered: [self isBordered] active: [self isActive]]; [self drawWellInside: rect]; } - (void) takeColorFrom: (id) sender { [self setColor: [sender color]]; } - (void) mouseDown: (NSEvent *) event { if (![self isEnabled]) { return; } if ([self isBordered]) { /* * Bordered color wells have interesting logic: o If the user clicks and drags in the swatch then they can drag a color out - and the well becomes disabled o If the user clicks on the border and drags then the control activates or not depending on whether the mouse is within the control or not o If the user simply clicks in the swatch or the border the well is toggled between active and inactive states */ BOOL wasActive = [self isActive]; NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil]; BOOL mouseInBorder = !NSMouseInRect(point, NSInsetRect(_bounds, 8, 8), [self isFlipped]); BOOL canStartDrag = !mouseInBorder; if (mouseInBorder) { // Toggle the initial state. if (wasActive) [self deactivate]; else [self activate: YES]; } BOOL shouldStartDrag = NO; do { event = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask]; point = [self convertPoint: [event locationInWindow] fromView: nil]; BOOL mouseInBounds = NSMouseInRect(point, _bounds, [self isFlipped]); if ([event type] == NSLeftMouseDragged) { if (canStartDrag) { // Get dragging the color. shouldStartDrag = YES; } else { // Toggle the state based on where the cursor is. if (mouseInBounds) { if (wasActive) { [self deactivate]; } else { [self activate: YES]; } } else { if (wasActive) { [self activate: YES]; } else { [self deactivate]; } } } } else if (mouseInBounds) { // Just toggle the state. if (wasActive) { [self deactivate]; } else { [self activate: YES]; } } } while ([event type] != NSLeftMouseUp && shouldStartDrag == NO); if (shouldStartDrag == NO) { if ([self isActive] == YES) { if (!([event modifierFlags] & NSShiftKeyMask)) { [self activate: YES]; } [[NSColorPanel sharedColorPanel] setColor: [self color]]; [NSApp orderFrontColorPanel: self]; } return; } else { // We're going to drag a swatch so deactivate (like Cocoa) [self deactivate]; } } [NSColorPanel dragColor: _color withEvent: event fromView: self]; } - (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL) isLocal { return NSDragOperationCopy; } - (NSDragOperation) draggingEntered: (id) sender { return NSDragOperationCopy; } - (NSDragOperation) draggingUpdated: (id) sender { return NSDragOperationCopy; } - (BOOL) prepareForDragOperation: (id) sender { return YES; } - (BOOL) performDragOperation: (id) sender { NSPasteboard *pboard = [sender draggingPasteboard]; NSColor *color = [NSColor colorFromPasteboard: pboard]; [self setColor: color]; [self sendAction: _action to: _target]; return YES; } @end