API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPColorPanel.j
Go to the documentation of this file.
1 /*
2  * CPColorPanel.j
3  * AppKit
4  *
5  * Created by Ross Boucher.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 @class CPSlider
25 
26 @global CPApp
27 
28 /*
29  A color wheel
30  @global
31  @group CPColorPanelMode
32 */
33 CPWheelColorPickerMode = 1;
34 
35 /*
36  Slider based picker
37  @global
38  @group CPColorPanelMode
39 */
40 CPSliderColorPickerMode = 2;
41 
42 CPColorPickerViewWidth = 265;
43 CPColorPickerViewHeight = 370;
44 
45 CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
46 
47 var PREVIEW_HEIGHT = 20.0,
48  TOOLBAR_HEIGHT = 32.0,
49  SWATCH_HEIGHT = 14.0,
50  ICON_WIDTH = 32.0,
51  ICON_PADDING = 12.0;
52 
53 var SharedColorPanel = nil,
54  ColorPickerClasses = [];
55 
64 @implementation CPColorPanel : CPPanel
65 {
66  _CPColorPanelToolbar _toolbar;
67  _CPColorPanelSwatches _swatchView;
68  _CPColorPanelPreview _previewView;
69 
70  CPSlider _opacitySlider;
71 
72  CPArray _colorPickers;
73  CPView _currentView;
74  id _activePicker;
75 
76  CPColor _color;
77 
78  id _target;
79  SEL _action;
80 
81  int _mode;
82 }
83 
88 + (void)provideColorPickerClass:(Class)aColorPickerSubclass
89 {
90  ColorPickerClasses.push(aColorPickerSubclass);
91 }
92 
96 + (CPColorPanel)sharedColorPanel
97 {
98  if (!SharedColorPanel)
99  SharedColorPanel = [[CPColorPanel alloc] init];
100 
101  return SharedColorPanel;
102 }
103 
108 + (void)setPickerMode:(CPColorPanelMode)mode
109 {
110  var panel = [CPColorPanel sharedColorPanel];
111  [panel setMode:mode];
112 }
113 
114 /*
115  To obtain the color panel, use \c +sharedColorPanel.
116  @ignore
117 */
118 - (id)init
119 {
120  self = [super initWithContentRect:CGRectMake(500.0, 50.0, 219.0, 370.0)
121  styleMask:(CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask)];
122 
123  if (self)
124  {
125  //[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
126 
127  [self setTitle:@"Color Panel"];
128  [self setLevel:CPFloatingWindowLevel];
129 
130  [self setFloatingPanel:YES];
131  [self setBecomesKeyOnlyIfNeeded:YES];
132 
133  [self setMinSize:CGSizeMake(219.0, 363.0)];
134  [self setMaxSize:CGSizeMake(323.0, 537.0)];
135  }
136 
137  return self;
138 }
139 
143 - (void)setColor:(CPColor)aColor
144 {
145  _color = aColor;
146  [_previewView setBackgroundColor:_color];
147 
148  [CPApp sendAction:@selector(changeColor:) to:nil from:self];
149 
150  if (_target && _action)
151  [CPApp sendAction:_action to:_target from:self];
152 
154  postNotificationName:CPColorPanelColorDidChangeNotification
155  object:self];
156 
157  [_activePicker setColor:_color];
158  [_opacitySlider setFloatValue:[_color alphaComponent]];
159 }
160 
166 - (void)setColor:(CPColor)aColor updatePicker:(BOOL)bool
167 {
168  [self setColor:aColor];
169 
170  if (bool)
171  [_activePicker setColor:_color];
172 }
173 
177 - (CPColor)color
178 {
179  return _color;
180 }
181 
182 - (float)opacity
183 {
184  return [_opacitySlider floatValue];
185 }
186 
191 - (void)setTarget:(id)aTarget
192 {
193  _target = aTarget;
194 }
195 
200 - (id)target
201 {
202  return _target;
203 }
204 
210 - (void)setAction:(selector)anAction
211 {
212  _action = anAction;
213 }
214 
218 - (selector)action
219 {
220  return _action;
221 }
222 
227 - (void)setMode:(CPColorPanelMode)mode
228 {
229  _mode = mode;
230 }
231 
232 - (void)_setPicker:(id)sender
233 {
234  var picker = _colorPickers[[sender tag]],
235  view = [picker provideNewView:NO];
236 
237  if (!view)
238  view = [picker provideNewView:YES];
239 
240  if (view == _currentView)
241  return;
242 
243  if (_currentView)
244  [view setFrame:[_currentView frame]];
245  else
246  {
247  var height = (TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5 + SWATCH_HEIGHT + 32),
248  bounds = [[self contentView] bounds];
249 
250  [view setFrameSize:CGSizeMake(bounds.size.width - 10, bounds.size.height - height)];
251  [view setFrameOrigin:CGPointMake(5, height)];
252  }
253 
254  [_currentView removeFromSuperview];
255  [[self contentView] addSubview:view];
256 
257  _currentView = view;
258  _activePicker = picker;
259 
260  [picker setColor:[self color]];
261 }
262 
266 - (CPColorPanelMode)mode
267 {
268  return _mode;
269 }
270 
271 - (void)orderFront:(id)aSender
272 {
273  [self _loadContentsIfNecessary];
274  [super orderFront:aSender];
275 }
276 
277 /* @ignore */
278 - (void)_loadContentsIfNecessary
279 {
280  if (_toolbar)
281  return;
282 
283  if (!_color)
284  _color = [CPColor whiteColor];
285 
286  _colorPickers = [];
287 
288  var count = [ColorPickerClasses count];
289  for (var i = 0; i < count; i++)
290  {
291  var currentPickerClass = ColorPickerClasses[i],
292  currentPicker = [[currentPickerClass alloc] initWithPickerMask:0 colorPanel:self];
293 
294  _colorPickers.push(currentPicker);
295  }
296 
297  var contentView = [self contentView],
298  bounds = [contentView bounds];
299 
300  _toolbar = [[CPView alloc] initWithFrame:CGRectMake(0, 6, CGRectGetWidth(bounds), TOOLBAR_HEIGHT)];
301  [_toolbar setAutoresizingMask:CPViewWidthSizable];
302 
303  var totalToolbarWidth = count * ICON_WIDTH + (count - 1) * ICON_PADDING,
304  leftOffset = (CGRectGetWidth(bounds) - totalToolbarWidth) / 2.0,
305  buttonForLater = nil;
306 
307  for (var i = 0; i < count; i++)
308  {
309  var image = [_colorPickers[i] provideNewButtonImage],
310  highlightImage = [_colorPickers[i] provideNewAlternateButtonImage],
311  button = [[CPButton alloc] initWithFrame:CGRectMake(leftOffset + i * (ICON_WIDTH + ICON_PADDING), 0, ICON_WIDTH, ICON_WIDTH)];
312 
313  [button setTag:i];
314  [button setTarget:self];
315  [button setAction:@selector(_setPicker:)];
316  [button setBordered:NO];
317  [button setAutoresizingMask:CPViewMinXMargin | CPViewMaxXMargin];
318 
319  [button setImage:image];
320  [button setAlternateImage:highlightImage];
321 
322  [_toolbar addSubview:button];
323 
324  if (!buttonForLater)
325  buttonForLater = button;
326  }
327 
328  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
329  var previewBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10, CGRectGetWidth(bounds) - 86, PREVIEW_HEIGHT)];
330 
331  _previewView = [[_CPColorPanelPreview alloc] initWithFrame:CGRectInset([previewBox bounds], 2.0, 2.0)];
332 
333  [_previewView setColorPanel:self];
334  [_previewView setAutoresizingMask:CPViewWidthSizable];
335 
336  [previewBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
337  [previewBox setAutoresizingMask:CPViewWidthSizable];
338 
339  [previewBox addSubview:_previewView];
340 
341  var _previewLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + 10, 60, 15)];
342  [_previewLabel setStringValue:"Preview:"];
343  [_previewLabel setTextColor:[CPColor blackColor]];
344  [_previewLabel setAlignment:CPRightTextAlignment];
345 
346  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
347  var swatchBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5, CGRectGetWidth(bounds) - 86, SWATCH_HEIGHT + 2.0)];
348 
349  [swatchBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
350  [swatchBox setAutoresizingMask:CPViewWidthSizable];
351 
352  _swatchView = [[_CPColorPanelSwatches alloc] initWithFrame:CGRectInset([swatchBox bounds], 1.0, 1.0)];
353 
354  [_swatchView setColorPanel:self];
355  [_swatchView setAutoresizingMask:CPViewWidthSizable];
356 
357  [swatchBox addSubview:_swatchView];
358 
359  var _swatchLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + 8 + PREVIEW_HEIGHT + 6, 60, 15)];
360  [_swatchLabel setStringValue:"Swatches:"];
361  [_swatchLabel setTextColor:[CPColor blackColor]];
362  [_swatchLabel setAlignment:CPRightTextAlignment];
363 
364 
365  var opacityLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + PREVIEW_HEIGHT + 35, 60, 20)];
366  [opacityLabel setStringValue:"Opacity:"];
367  [opacityLabel setTextColor:[CPColor blackColor]];
368  [opacityLabel setAlignment:CPRightTextAlignment];
369 
370  _opacitySlider = [[CPSlider alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + PREVIEW_HEIGHT + 34, CGRectGetWidth(bounds) - 86, 20.0)];
371 
372  [_opacitySlider setMinValue:0.0];
373  [_opacitySlider setMaxValue:1.0];
374  [_opacitySlider setAutoresizingMask:CPViewWidthSizable];
375 
376  [_opacitySlider setTarget:self];
377  [_opacitySlider setAction:@selector(setOpacity:)];
378 
379  [contentView addSubview:_toolbar];
380  [contentView addSubview:previewBox];
381  [contentView addSubview:_previewLabel];
382  [contentView addSubview:swatchBox];
383  [contentView addSubview:_swatchLabel];
384  [contentView addSubview:opacityLabel];
385  [contentView addSubview:_opacitySlider];
386 
387  _target = nil;
388  _action = nil;
389  _activePicker = nil;
390 
391  [_previewView setBackgroundColor:_color];
392 
393  if (buttonForLater)
394  [self _setPicker:buttonForLater];
395 }
396 
397 - (void)setOpacity:(id)sender
398 {
399  var components = [[self color] components],
400  alpha = [sender floatValue];
401 
402  [self setColor:[_color colorWithAlphaComponent:alpha] updatePicker:YES];
403 }
404 
405 @end
406 
407 
408 CPColorDragType = "CPColorDragType";
409 
410 var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
411 
412 /* @ignore */
413 @implementation _CPColorPanelSwatches : CPView
414 {
415  CPArray _swatches;
416  CPColor _dragColor;
417  CPColorPanel _colorPanel;
418  CPCookie _swatchCookie;
419 }
420 
421 - (id)initWithFrame:(CGRect)aFrame
422 {
423  self = [super initWithFrame:aFrame];
424 
425  [self setBackgroundColor:[CPColor grayColor]];
426 
427  [self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
428 
429  var whiteColor = [CPColor whiteColor];
430 
431  _swatchCookie = [[CPCookie alloc] initWithName:CPColorPanelSwatchesCookie];
432  var colorList = [self startingColorList];
433 
434  _swatches = [];
435 
436  for (var i = 0; i < 50; i++)
437  {
438  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
439  var view = [[CPView alloc] initWithFrame:CGRectMake(13 * i + 1, 1, 12, 12)],
440  fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
441 
442  [view setBackgroundColor:whiteColor];
443  [fillView setBackgroundColor:(i < colorList.length) ? colorList[i] : whiteColor];
444 
445  [view addSubview:fillView];
446 
447  [self addSubview:view];
448 
449  _swatches.push(view);
450  }
451 
452  return self;
453 }
454 
455 - (BOOL)isOpaque
456 {
457  return YES;
458 }
459 
460 - (CPArray)startingColorList
461 {
462  var cookieValue = [_swatchCookie value];
463 
464  if (!cookieValue)
465  {
466  return [
469  [CPColor grayColor],
472  [CPColor redColor],
474  [CPColor blueColor],
476  ];
477  }
478 
479  var cookieValue = eval(cookieValue),
480  result = [];
481 
482  for (var i = 0; i < cookieValue.length; i++)
483  result.push([CPColor colorWithHexString:cookieValue[i]]);
484 
485  return result;
486 }
487 
488 - (CPArray)saveColorList
489 {
490  var result = [];
491  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
492  for (var i = 0; i < _swatches.length; i++)
493  result.push([[[_swatches[i] subviews][0] backgroundColor] hexString]);
494 
495  var future = new Date();
496  future.setYear(2019);
497 
498  [_swatchCookie setValue:JSON.stringify(result) expires:future domain:nil];
499 }
500 
501 - (void)setColorPanel:(CPColorPanel)panel
502 {
503  _colorPanel = panel;
504 }
505 
506 - (CPColorPanel)colorPanel
507 {
508  return _colorPanel;
509 }
510 
511 - (CPColor)colorAtIndex:(int)index
512 {
513  return [[_swatches[index] subviews][0] backgroundColor];
514 }
515 
516 - (void)setColor:(CPColor)aColor atIndex:(int)index
517 {
518  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
519  [[_swatches[index] subviews][0] setBackgroundColor:aColor];
520  [self saveColorList];
521 }
522 
523 - (void)mouseUp:(CPEvent)anEvent
524 {
525  var point = [self convertPoint:[anEvent locationInWindow] fromView:nil],
526  bounds = [self bounds];
527 
528  if (!CGRectContainsPoint(bounds, point) || point.x > [self bounds].size.width - 1 || point.x < 1)
529  return NO;
530 
531  [_colorPanel setColor:[self colorAtIndex:FLOOR(point.x / 13)] updatePicker:YES];
532 }
533 
534 - (void)mouseDragged:(CPEvent)anEvent
535 {
536  var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
537 
538  if (point.x > [self bounds].size.width - 1 || point.x < 1)
539  return NO;
540 
541  [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
542 
543  var swatch = _swatches[FLOOR(point.x / 13)];
544 
545  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
546  _dragColor = [[swatch subviews][0] backgroundColor];
547 
548  var bounds = CGRectMakeCopy([swatch bounds]);
549 
550  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
551  var dragView = [[CPView alloc] initWithFrame:bounds],
552  dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
553 
554  [dragView setBackgroundColor:[CPColor blackColor]];
555  [dragFillView setBackgroundColor:_dragColor];
556 
557  [dragView addSubview:dragFillView];
558 
559  [self dragView:dragView
560  at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
561  offset:CGPointMake(0.0, 0.0)
562  event:anEvent
563  pasteboard:nil
564  source:self
565  slideBack:YES];
566 }
567 
568 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
569 {
570  if (aType == CPColorDragType)
571  [aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:aType];
572 }
573 
574 - (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
575 {
576  var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
577  pasteboard = [aSender draggingPasteboard],
578  swatch = nil;
579 
580  if (![pasteboard availableTypeFromArray:[CPColorDragType]] || location.x > [self bounds].size.width - 1 || location.x < 1)
581  return NO;
582 
583  [self setColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] atIndex:FLOOR(location.x / 13)];
584 }
585 
586 @end
587 
588 /* @ignore */
589 @implementation _CPColorPanelPreview : CPView
590 {
591  CPColorPanel _colorPanel;
592 }
593 
594 - (id)initWithFrame:(CGRect)aFrame
595 {
596  self = [super initWithFrame:aFrame];
597 
598  [self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
599 
600  return self;
601 }
602 
603 - (void)setColorPanel:(CPColorPanel)aPanel
604 {
605  _colorPanel = aPanel;
606 }
607 
608 - (CPColorPanel)colorPanel
609 {
610  return _colorPanel;
611 }
612 
613 - (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
614 {
615  var pasteboard = [aSender draggingPasteboard];
616 
617  if (![pasteboard availableTypeFromArray:[CPColorDragType]])
618  return NO;
619 
620  var color = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]];
621  [_colorPanel setColor:color updatePicker:YES];
622 }
623 
624 - (BOOL)isOpaque
625 {
626  return YES;
627 }
628 
629 - (void)mouseDragged:(CPEvent)anEvent
630 {
631  var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
632 
633  [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPColorDragType] owner:self];
634 
635  var bounds = CGRectMake(0, 0, 15, 15);
636 
637  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
638  var dragView = [[CPView alloc] initWithFrame:bounds],
639  dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
640 
641  [dragView setBackgroundColor:[CPColor blackColor]];
642  [dragFillView setBackgroundColor:[self backgroundColor]];
643 
644  [dragView addSubview:dragFillView];
645 
646  [self dragView:dragView
647  at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
648  offset:CGPointMake(0.0, 0.0)
649  event:anEvent
650  pasteboard:nil
651  source:self
652  slideBack:YES];
653 }
654 
655 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
656 {
657  if (aType == CPColorDragType)
658  [aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:aType];
659 }
660 
661 @end
662 
663 
664 [CPColorPanel provideColorPickerClass:CPColorWheelColorPicker];
665 [CPColorPanel provideColorPickerClass:CPSliderColorPicker];