1/** <title>NSColorWell</title> 2 3 <abstract>Control for selecting and display a single color value.</abstract> 4 5 Copyright (C) 1996 Free Software Foundation, Inc. 6 7 Author: Scott Christley <scottc@net-community.com> 8 Date: 1996 9 Author: Felipe A. Rodriguez <far@ix.netcom.com> 10 Date: May 1998 11 12 This file is part of the GNUstep GUI Library. 13 14 This library is free software; you can redistribute it and/or 15 modify it under the terms of the GNU Lesser General Public 16 License as published by the Free Software Foundation; either 17 version 2 of the License, or (at your option) any later version. 18 19 This library is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 Lesser General Public License for more details. 23 24 You should have received a copy of the GNU Lesser General Public 25 License along with this library; see the file COPYING.LIB. 26 If not, see <http://www.gnu.org/licenses/> or write to the 27 Free Software Foundation, 51 Franklin Street, Fifth Floor, 28 Boston, MA 02110-1301, USA. 29*/ 30 31#include "config.h" 32#import "AppKit/NSActionCell.h" 33#import "AppKit/NSApplication.h" 34#import "AppKit/NSBezierPath.h" 35#import "AppKit/NSColorPanel.h" 36#import "AppKit/NSColorWell.h" 37#import "AppKit/NSColor.h" 38#import "AppKit/NSDragging.h" 39#import "AppKit/NSEvent.h" 40#import "AppKit/NSGraphics.h" 41#import "AppKit/NSPasteboard.h" 42#import "AppKit/NSWindow.h" 43#import "GNUstepGUI/GSTheme.h" 44#import <Foundation/NSDebug.h> 45#import <Foundation/NSNotification.h> 46#include <math.h> 47 48static NSString *GSColorWellDidBecomeExclusiveNotification = 49 @"GSColorWellDidBecomeExclusiveNotification"; 50 51@implementation NSColorWell 52 53/* 54 * Class methods 55 */ 56+ (void) initialize 57{ 58 if (self == [NSColorWell class]) 59 { 60 [self setVersion: 1]; 61 } 62} 63 64/* 65 * Instance methods 66 */ 67 68- (BOOL) acceptsFirstMouse: (NSEvent *)event 69{ 70 return YES; 71} 72 73- (SEL) action 74{ 75 return _action; 76} 77 78/**<p>Activates the NSColorWell and displays the NSColorPanel with the current 79 NSColorWell's color. The NSColorWell can take color from the NSColorPanel. 80 If exclusive is YES other NSColorWells are desacivated 81 (through notifications).</p><p>See Also: -deactivate</p> 82 */ 83- (void) activate: (BOOL)exclusive 84{ 85 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 86 NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel]; 87 88 if (exclusive == YES) 89 { 90 [nc postNotificationName: GSColorWellDidBecomeExclusiveNotification 91 object: self]; 92 } 93 94 [nc addObserver: self 95 selector: @selector(deactivate) 96 name: GSColorWellDidBecomeExclusiveNotification 97 object: nil]; 98 99 [nc addObserver: self 100 selector: @selector(_takeColorFromPanel:) 101 name: NSColorPanelColorDidChangeNotification 102 object: nil]; 103 104 [nc addObserver: self 105 selector: @selector(deactivate) 106 name: NSWindowWillCloseNotification 107 object: colorPanel]; 108 109 _is_active = YES; 110 111 [colorPanel setColor: _the_color]; 112 [colorPanel orderFront: self]; 113 114 [self setNeedsDisplay: YES]; 115} 116 117/**<p> Returns the current NSColor of the NSColorWell.</p> 118 <p> See Also: -setColor:</p> 119 */ 120- (NSColor *) color 121{ 122 return _the_color; 123} 124 125/** <p>Deactivates the NSColorWell and marks self for display. 126 It is usally call from an observer, when another NSColorWell is 127 activate.</p><p>See Also: -activate:</p> 128 */ 129- (void) deactivate 130{ 131 _is_active = NO; 132 133 [[NSNotificationCenter defaultCenter] removeObserver: self]; 134 135 [self setNeedsDisplay: YES]; 136} 137 138- (void) dealloc 139{ 140 if (_is_active == YES) 141 { 142 [self deactivate]; 143 } 144 TEST_RELEASE(_the_color); 145 [self unregisterDraggedTypes]; 146 [super dealloc]; 147} 148 149- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>)sender 150{ 151 NSPasteboard *pb; 152 NSDragOperation sourceDragMask; 153 154 NSDebugLLog(@"NSColorWell", @"%@: draggingEntered", self); 155 156 if ([self isEnabled] == NO) 157 return NSDragOperationNone; 158 159 sourceDragMask = [sender draggingSourceOperationMask]; 160 pb = [sender draggingPasteboard]; 161 162 if ([[pb types] indexOfObject: NSColorPboardType] != NSNotFound) 163 { 164 if (sourceDragMask & NSDragOperationCopy) 165 { 166 return NSDragOperationCopy; 167 } 168 } 169 170 return NSDragOperationNone; 171} 172 173- (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL)flag 174{ 175 return NSDragOperationCopy; 176} 177 178- (void) drawRect: (NSRect)clipRect 179{ 180 if (NSIntersectsRect(_bounds, clipRect) == NO) 181 { 182 return; 183 } 184 185 _wellRect = [[GSTheme theme] drawColorWellBorder: self 186 withBounds: _bounds 187 withClip: clipRect]; 188 [self drawWellInside: _wellRect]; 189} 190 191/**<p>Draws the NSColorWell inside the rectangle <var>insideRect</var>.</p> 192 <p>See Also: [NSColor-drawSwatchInRect:]</p> 193 */ 194- (void) drawWellInside: (NSRect)insideRect 195{ 196 if (NSIsEmptyRect(insideRect)) 197 { 198 return; 199 } 200 [_the_color drawSwatchInRect: insideRect]; 201} 202 203- (void) encodeWithCoder: (NSCoder*)aCoder 204{ 205 [super encodeWithCoder: aCoder]; 206 if ([aCoder allowsKeyedCoding]) 207 { 208 [aCoder encodeObject: _the_color forKey: @"NSColor"]; 209 // [aCoder encodeBool: _is_active forKey: @"NSEnabled"]; 210 [aCoder encodeBool: _is_bordered forKey: @"NSIsBordered"]; 211 [aCoder encodeConditionalObject: _target forKey: @"NSTarget"]; 212 [aCoder encodeConditionalObject: NSStringFromSelector(_action) forKey: @"NSAction"]; 213 } 214 else 215 { 216 [aCoder encodeObject: _the_color]; 217 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_is_active]; 218 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_is_bordered]; 219 [aCoder encodeConditionalObject: _target]; 220 [aCoder encodeValueOfObjCType: @encode(SEL) at: &_action]; 221 } 222} 223 224- (id) initWithCoder: (NSCoder*)aDecoder 225{ 226 self = [super initWithCoder: aDecoder]; 227 if (self != nil) 228 { 229 if ([aDecoder allowsKeyedCoding]) 230 { 231 NSString *action; 232 233 ASSIGN(_the_color, [aDecoder decodeObjectForKey: @"NSColor"]); 234 // _is_active = [aDecoder decodeBoolForKey: @"NSEnabled"]; 235 _is_bordered = [aDecoder decodeBoolForKey: @"NSIsBordered"]; 236 _target = [aDecoder decodeObjectForKey: @"NSTarget"]; 237 action = [aDecoder decodeObjectForKey: @"NSAction"]; 238 _action = NSSelectorFromString(action); 239 [self registerForDraggedTypes: 240 [NSArray arrayWithObjects: NSColorPboardType, nil]]; 241 } 242 else 243 { 244 [aDecoder decodeValueOfObjCType: @encode(id) at: &_the_color]; 245 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_is_active]; 246 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_is_bordered]; 247 [aDecoder decodeValueOfObjCType: @encode(id) at: &_target]; 248 // Undo RETAIN by decoder 249 TEST_RELEASE(_target); 250 [aDecoder decodeValueOfObjCType: @encode(SEL) at: &_action]; 251 [self registerForDraggedTypes: 252 [NSArray arrayWithObjects: NSColorPboardType, nil]]; 253 } 254 } 255 return self; 256} 257 258- (id) initWithFrame: (NSRect)frameRect 259{ 260 self = [super initWithFrame: frameRect]; 261 if (self != nil) 262 { 263 _is_bordered = YES; 264 _is_active = NO; 265 _the_color = RETAIN([NSColor blackColor]); 266 267 [self registerForDraggedTypes: 268 [NSArray arrayWithObjects: NSColorPboardType, nil]]; 269 } 270 return self; 271} 272 273/** <p>Returns whether the NSColorWell is active. By default a NSColorWell 274 is not active.</p> 275 <p>See Also: -activate: -deactivate</p> 276 */ 277- (BOOL) isActive 278{ 279 return _is_active; 280} 281 282/** <p>Returns whether the NSColorWell has border. By default a NSColorWell 283 has border.</p><p>See Also: -setBordered:</p> 284 */ 285- (BOOL) isBordered 286{ 287 return _is_bordered; 288} 289 290- (BOOL) isOpaque 291{ 292 // May not be opaque, due to themes 293 return NO; 294} 295 296- (void) mouseDown: (NSEvent *)theEvent 297{ 298 // 299 // OPENSTEP 4.2 and OSX behavior indicates that the colorwell doesn't 300 // work when the widget is marked as disabled. 301 // 302 if ([self isEnabled] == NO) 303 return; 304 305 // Unbordered color wells start a drag immediately upon mouse down 306 if ([self isBordered] == NO) 307 { 308 [NSColorPanel dragColor: _the_color 309 withEvent: theEvent 310 fromView: self]; 311 return; 312 } 313 314 _mouseDownPoint = [self convertPoint: [theEvent locationInWindow] 315 fromView: nil]; 316 [[self cell] setHighlighted: YES]; 317 [self setNeedsDisplay: YES]; 318} 319 320- (void) mouseDragged: (NSEvent *)theEvent 321{ 322 NSPoint point = [self convertPoint: [theEvent locationInWindow] 323 fromView: nil]; 324 BOOL inside = [self mouse: point inRect: [self bounds]]; 325 BOOL startedInWell = [self mouse: _mouseDownPoint inRect: _wellRect]; 326 327 NSSize delta = NSMakeSize(_mouseDownPoint.x - point.x, 328 _mouseDownPoint.y - point.y); 329 double distance = sqrt(delta.width*delta.width + delta.height*delta.height); 330 331 // FIXME: Make the dragging threshold a user default 332 if (distance < 4) 333 return; 334 335 if ([self isEnabled] == NO) 336 return; 337 338 if (startedInWell) 339 { 340 [[self cell] setHighlighted: NO]; 341 [self setNeedsDisplay: YES]; 342 343 [NSColorPanel dragColor: _the_color 344 withEvent: theEvent 345 fromView: self]; 346 return; 347 } 348 else 349 { 350 [[self cell] setHighlighted: inside]; 351 [self setNeedsDisplay: YES]; 352 } 353} 354 355- (void) mouseUp: (NSEvent *)theEvent 356{ 357 NSPoint point = [self convertPoint: [theEvent locationInWindow] 358 fromView: nil]; 359 BOOL inside = [self mouse: point inRect: [self bounds]]; 360 361 if ([self isEnabled] == NO) 362 return; 363 364 [[self cell] setHighlighted: NO]; 365 [self setNeedsDisplay: YES]; 366 367 if (inside) 368 { 369 [self performClick: self]; 370 } 371} 372 373- (id) objectValue 374{ 375 return [self color]; 376} 377 378- (void) performClick: (id)sender 379{ 380 if ([self isActive]) 381 { 382 [self deactivate]; 383 } 384 else 385 { 386 [self activate: YES]; 387 } 388} 389 390- (BOOL) performDragOperation: (id <NSDraggingInfo>)sender 391{ 392 NSPasteboard *pb = [sender draggingPasteboard]; 393 394 NSDebugLLog(@"NSColorWell", @"%@: performDragOperation", self); 395 [self setColor: [NSColor colorFromPasteboard: pb]]; 396 /* When our color is changed by having a new color dropped on us, 397 * we send our action. 398 */ 399 [self sendAction: _action to: _target]; 400 return YES; 401} 402 403- (BOOL) prepareForDragOperation: (id <NSDraggingInfo>)sender 404{ 405 return YES; 406} 407 408- (void) setAction: (SEL)action 409{ 410 _action = action; 411} 412 413/**<p>Sets whether the NSColorWell has border and marks self for display. 414 By default a NSColorWell has border.</p><p>See Also: -isBordered</p> 415 */ 416- (void) setBordered: (BOOL)bordered 417{ 418 _is_bordered = bordered; 419 [self setNeedsDisplay: YES]; 420} 421 422/** <p>Sets the NSColorWell to color and marks self for display.<br /> 423 * Sets the NSColorPanel if active.<br /> 424 * Does NOT notify target of color change. 425 * </p> 426 * <p>See Also: -color</p> 427 */ 428- (void) setColor: (NSColor *)color 429{ 430 ASSIGN(_the_color, color); 431 [self setNeedsDisplay: YES]; 432 /* 433 * Experimentation with NeXTstep shows that when the color of an active 434 * colorwell is set, the color of the shared color panel is set too, 435 * though this does not raise the color panel, only the event of 436 * activation does that. 437 */ 438 if ([self isActive]) 439 { 440 NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel]; 441 442 [colorPanel setColor: _the_color]; 443 } 444} 445 446- (void) setObjectValue: (id)anObject 447{ 448 [self setColor: anObject]; 449} 450 451- (void) setTarget: (id)target 452{ 453 _target = target; 454} 455 456/** <p>Sets the NSColorWell's color to the sender color.</p> 457 * <p>See Also: -setColor: </p> 458 */ 459- (void) takeColorFrom: (id)sender 460{ 461 if ([sender respondsToSelector: @selector(color)]) 462 { 463 [self setColor: [sender color]]; 464 } 465} 466 467- (void) _takeColorFromPanel: (NSNotification *) notification 468{ 469 id sender = [notification object]; 470 471 if ([sender respondsToSelector: @selector(color)]) 472 { 473 NSColor *c = [(id)sender color]; 474 475 /* Don't use -setColor: as that would send a message back to the 476 * panel telling it to se its color again. 477 * Instead we assign the color and mark for redisplay directly. 478 * NB. For MacOS-X compatibility, we only send the action if the 479 * coor has actually changed. 480 */ 481 if (c != nil && [c isEqual: _the_color] == NO) 482 { 483 ASSIGN(_the_color, [(id)sender color]); 484 [self setNeedsDisplay: YES]; 485 /* When our color is changed from the color panel, we should 486 * send our action. 487 */ 488 [self sendAction: _action to: _target]; 489 } 490 } 491} 492 493- (void) takeObjectValueFrom: (id)sender 494{ 495 [self takeColorFrom: sender]; 496} 497 498- (id) target 499{ 500 return _target; 501} 502 503@end 504 505