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