API  0.9.6
 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 
25 CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
26 
27 var PREVIEW_HEIGHT = 20.0,
29  SWATCH_HEIGHT = 14.0,
30  ICON_WIDTH = 32.0,
31  ICON_PADDING = 12.0;
32 
33 var SharedColorPanel = nil,
35 
36 /*
37  A color wheel
38  @global
39  @group CPColorPanelMode
40 */
42 /*
43  Slider based picker
44  @global
45  @group CPColorPanelMode
46 */
48 
51 
60 @implementation CPColorPanel : CPPanel
61 {
62  _CPColorPanelToolbar _toolbar;
63  _CPColorPanelSwatches _swatchView;
64  _CPColorPanelPreview _previewView;
65 
66  CPSlider _opacitySlider;
67 
68  CPArray _colorPickers;
69  CPView _currentView;
70  id _activePicker;
71 
72  CPColor _color;
73 
74  id _target;
75  SEL _action;
76 
77  int _mode;
78 }
79 
84 + (void)provideColorPickerClass:(Class)aColorPickerSubclass
85 {
86  ColorPickerClasses.push(aColorPickerSubclass);
87 }
88 
92 + (CPColorPanel)sharedColorPanel
93 {
94  if (!SharedColorPanel)
96 
97  return SharedColorPanel;
98 }
99 
104 + (void)setPickerMode:(CPColorPanelMode)mode
105 {
106  var panel = [CPColorPanel sharedColorPanel];
107  [panel setMode:mode];
108 }
109 
110 /*
111  To obtain the color panel, use \c +sharedColorPanel.
112  @ignore
113 */
114 - (id)init
115 {
116  self = [super initWithContentRect:CGRectMake(500.0, 50.0, 219.0, 370.0)
117  styleMask:(CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask)];
118 
119  if (self)
120  {
122 
123  [self setTitle:@"Color Panel"];
124  [self setLevel:CPFloatingWindowLevel];
125 
126  [self setFloatingPanel:YES];
127  [self setBecomesKeyOnlyIfNeeded:YES];
128 
129  [self setMinSize:CGSizeMake(219.0, 363.0)];
130  [self setMaxSize:CGSizeMake(323.0, 537.0)];
131  }
132 
133  return self;
134 }
135 
139 - (void)setColor:(CPColor)aColor
140 {
141  _color = aColor;
142  [_previewView setBackgroundColor:_color];
143 
144  [CPApp sendAction:@selector(changeColor:) to:nil from:self];
145 
146  if (_target && _action)
147  [CPApp sendAction:_action to:_target from:self];
148 
150  postNotificationName:CPColorPanelColorDidChangeNotification
151  object:self];
152 
153  [_activePicker setColor:_color];
154  [_opacitySlider setFloatValue:[_color alphaComponent]];
155 }
156 
162 - (void)setColor:(CPColor)aColor updatePicker:(BOOL)bool
163 {
164  [self setColor:aColor];
165 
166  if (bool)
167  [_activePicker setColor:_color];
168 }
169 
173 - (CPColor)color
174 {
175  return _color;
176 }
177 
178 - (float)opacity
179 {
180  return [_opacitySlider floatValue];
181 }
182 
187 - (void)setTarget:(id)aTarget
188 {
189  _target = aTarget;
190 }
191 
196 - (id)target
197 {
198  return _target;
199 }
200 
206 - (void)setAction:(selector)anAction
207 {
208  _action = anAction;
209 }
210 
214 - (selector)action
215 {
216  return _action;
217 }
218 
223 - (void)setMode:(CPColorPanelMode)mode
224 {
225  _mode = mode;
226 }
227 
228 - (void)_setPicker:(id)sender
229 {
230  var picker = _colorPickers[[sender tag]],
231  view = [picker provideNewView:NO];
232 
233  if (!view)
234  view = [picker provideNewView:YES];
235 
236  if (view == _currentView)
237  return;
238 
239  if (_currentView)
240  [view setFrame:[_currentView frame]];
241  else
242  {
243  var height = (TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5 + SWATCH_HEIGHT + 32),
244  bounds = [[self contentView] bounds];
245 
246  [view setFrameSize:CPSizeMake(bounds.size.width - 10, bounds.size.height - height)];
247  [view setFrameOrigin:CPPointMake(5, height)];
248  }
249 
250  [_currentView removeFromSuperview];
251  [[self contentView] addSubview:view];
252 
253  _currentView = view;
254  _activePicker = picker;
255 
256  [picker setColor:[self color]];
257 }
258 
262 - (CPColorPanelMode)mode
263 {
264  return _mode;
265 }
266 
267 - (void)orderFront:(id)aSender
268 {
269  [self _loadContentsIfNecessary];
270  [super orderFront:aSender];
271 }
272 
273 /* @ignore */
274 - (void)_loadContentsIfNecessary
275 {
276  if (_toolbar)
277  return;
278 
279  if (!_color)
280  _color = [CPColor whiteColor];
281 
282  _colorPickers = [];
283 
284  var count = [ColorPickerClasses count];
285  for (var i = 0; i < count; i++)
286  {
287  var currentPickerClass = ColorPickerClasses[i],
288  currentPicker = [[currentPickerClass alloc] initWithPickerMask:0 colorPanel:self];
289 
290  _colorPickers.push(currentPicker);
291  }
292 
293  var contentView = [self contentView],
294  bounds = [contentView bounds];
295 
296  _toolbar = [[CPView alloc] initWithFrame:CGRectMake(0, 6, CGRectGetWidth(bounds), TOOLBAR_HEIGHT)];
297  [_toolbar setAutoresizingMask:CPViewWidthSizable];
298 
299  var totalToolbarWidth = count * ICON_WIDTH + (count - 1) * ICON_PADDING,
300  leftOffset = (CGRectGetWidth(bounds) - totalToolbarWidth) / 2.0,
301  buttonForLater = nil;
302 
303  for (var i = 0; i < count; i++)
304  {
305  var image = [_colorPickers[i] provideNewButtonImage],
306  highlightImage = [_colorPickers[i] provideNewAlternateButtonImage],
307  button = [[CPButton alloc] initWithFrame:CGRectMake(leftOffset + i * (ICON_WIDTH + ICON_PADDING), 0, ICON_WIDTH, ICON_WIDTH)];
308 
309  [button setTag:i];
310  [button setTarget:self];
311  [button setAction:@selector(_setPicker:)];
312  [button setBordered:NO];
313  [button setAutoresizingMask:CPViewMinXMargin | CPViewMaxXMargin];
314 
315  [button setImage:image];
316  [button setAlternateImage:highlightImage];
317 
318  [_toolbar addSubview:button];
319 
320  if (!buttonForLater)
321  buttonForLater = button;
322  }
323 
324  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
325  var previewBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10, CGRectGetWidth(bounds) - 86, PREVIEW_HEIGHT)];
326 
327  _previewView = [[_CPColorPanelPreview alloc] initWithFrame:CGRectInset([previewBox bounds], 2.0, 2.0)];
328 
329  [_previewView setColorPanel:self];
330  [_previewView setAutoresizingMask:CPViewWidthSizable];
331 
332  [previewBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
333  [previewBox setAutoresizingMask:CPViewWidthSizable];
334 
335  [previewBox addSubview:_previewView];
336 
337  var _previewLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + 10, 60, 15)];
338  [_previewLabel setStringValue:"Preview:"];
339  [_previewLabel setTextColor:[CPColor blackColor]];
340  [_previewLabel setAlignment:CPRightTextAlignment];
341 
342  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
343  var swatchBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5, CGRectGetWidth(bounds) - 86, SWATCH_HEIGHT + 2.0)];
344 
345  [swatchBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
346  [swatchBox setAutoresizingMask:CPViewWidthSizable];
347 
348  _swatchView = [[_CPColorPanelSwatches alloc] initWithFrame:CGRectInset([swatchBox bounds], 1.0, 1.0)];
349 
350  [_swatchView setColorPanel:self];
351  [_swatchView setAutoresizingMask:CPViewWidthSizable];
352 
353  [swatchBox addSubview:_swatchView];
354 
355  var _swatchLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + 8 + PREVIEW_HEIGHT + 6, 60, 15)];
356  [_swatchLabel setStringValue:"Swatches:"];
357  [_swatchLabel setTextColor:[CPColor blackColor]];
358  [_swatchLabel setAlignment:CPRightTextAlignment];
359 
360 
361  var opacityLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + PREVIEW_HEIGHT + 35, 60, 20)];
362  [opacityLabel setStringValue:"Opacity:"];
363  [opacityLabel setTextColor:[CPColor blackColor]];
364  [opacityLabel setAlignment:CPRightTextAlignment];
365 
366  _opacitySlider = [[CPSlider alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + PREVIEW_HEIGHT + 34, CGRectGetWidth(bounds) - 86, 20.0)];
367 
368  [_opacitySlider setMinValue:0.0];
369  [_opacitySlider setMaxValue:1.0];
370  [_opacitySlider setAutoresizingMask:CPViewWidthSizable];
371 
372  [_opacitySlider setTarget:self];
373  [_opacitySlider setAction:@selector(setOpacity:)];
374 
375  [contentView addSubview:_toolbar];
376  [contentView addSubview:previewBox];
377  [contentView addSubview:_previewLabel];
378  [contentView addSubview:swatchBox];
379  [contentView addSubview:_swatchLabel];
380  [contentView addSubview:opacityLabel];
381  [contentView addSubview:_opacitySlider];
382 
383  _target = nil;
384  _action = nil;
385  _activePicker = nil;
386 
387  [_previewView setBackgroundColor:_color];
388 
389  if (buttonForLater)
390  [self _setPicker:buttonForLater];
391 }
392 
393 - (void)setOpacity:(id)sender
394 {
395  var components = [[self color] components],
396  alpha = [sender floatValue];
397 
398  [self setColor:[_color colorWithAlphaComponent:alpha] updatePicker:YES];
399 }
400 
401 @end
402 
403 
404 CPColorDragType = "CPColorDragType";
405 
406 var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
407 
408 /* @ignore */
409 @implementation _CPColorPanelSwatches : CPView
410 {
411  CPArray _swatches;
412  CPColor _dragColor;
413  CPColorPanel _colorPanel;
414  CPCookie _swatchCookie;
415 }
416 
417 - (id)initWithFrame:(CPRect)aFrame
418 {
419  self = [super initWithFrame:aFrame];
420 
421  [self setBackgroundColor:[CPColor grayColor]];
422 
423  [self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
424 
425  var whiteColor = [CPColor whiteColor];
426 
427  _swatchCookie = [[CPCookie alloc] initWithName:CPColorPanelSwatchesCookie];
428  var colorList = [self startingColorList];
429 
430  _swatches = [];
431 
432  for (var i = 0; i < 50; i++)
433  {
434  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
435  var view = [[CPView alloc] initWithFrame:CPRectMake(13 * i + 1, 1, 12, 12)],
436  fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
437 
438  [view setBackgroundColor:whiteColor];
439  [fillView setBackgroundColor:(i < colorList.length) ? colorList[i] : whiteColor];
440 
441  [view addSubview:fillView];
442 
443  [self addSubview:view];
444 
445  _swatches.push(view);
446  }
447 
448  return self;
449 }
450 
451 - (BOOL)isOpaque
452 {
453  return YES;
454 }
455 
456 - (CPArray)startingColorList
457 {
458  var cookieValue = [_swatchCookie value];
459 
460  if (!cookieValue)
461  {
462  return [
465  [CPColor grayColor],
468  [CPColor redColor],
470  [CPColor blueColor],
472  ];
473  }
474 
475  var cookieValue = eval(cookieValue),
476  result = [];
477 
478  for (var i = 0; i < cookieValue.length; i++)
479  result.push([CPColor colorWithHexString:cookieValue[i]]);
480 
481  return result;
482 }
483 
484 - (CPArray)saveColorList
485 {
486  var result = [];
487  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
488  for (var i = 0; i < _swatches.length; i++)
489  result.push([[[_swatches[i] subviews][0] backgroundColor] hexString]);
490 
491  var future = new Date();
492  future.setYear(2019);
493 
494  [_swatchCookie setValue:JSON.stringify(result) expires:future domain:nil];
495 }
496 
497 - (void)setColorPanel:(CPColorPanel)panel
498 {
499  _colorPanel = panel;
500 }
501 
502 - (CPColorPanel)colorPanel
503 {
504  return _colorPanel;
505 }
506 
507 - (CPColor)colorAtIndex:(int)index
508 {
509  return [[_swatches[index] subviews][0] backgroundColor];
510 }
511 
512 - (void)setColor:(CPColor)aColor atIndex:(int)index
513 {
514  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
515  [[_swatches[index] subviews][0] setBackgroundColor:aColor];
516  [self saveColorList];
517 }
518 
519 - (void)mouseUp:(CPEvent)anEvent
520 {
521  var point = [self convertPoint:[anEvent locationInWindow] fromView:nil],
522  bounds = [self bounds];
523 
524  if (!CGRectContainsPoint(bounds, point) || point.x > [self bounds].size.width - 1 || point.x < 1)
525  return NO;
526 
527  [_colorPanel setColor:[self colorAtIndex:FLOOR(point.x / 13)] updatePicker:YES];
528 }
529 
530 - (void)mouseDragged:(CPEvent)anEvent
531 {
532  var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
533 
534  if (point.x > [self bounds].size.width - 1 || point.x < 1)
535  return NO;
536 
537  [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
538 
539  var swatch = _swatches[FLOOR(point.x / 13)];
540 
541  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
542  _dragColor = [[swatch subviews][0] backgroundColor];
543 
544  var bounds = CGRectMakeCopy([swatch bounds]);
545 
546  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
547  var dragView = [[CPView alloc] initWithFrame:bounds],
548  dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
549 
550  [dragView setBackgroundColor:[CPColor blackColor]];
551  [dragFillView setBackgroundColor:_dragColor];
552 
553  [dragView addSubview:dragFillView];
554 
555  [self dragView:dragView
556  at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
557  offset:CGPointMake(0.0, 0.0)
558  event:anEvent
559  pasteboard:nil
560  source:self
561  slideBack:YES];
562 }
563 
564 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
565 {
566  if (aType == CPColorDragType)
567  [aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:aType];
568 }
569 
570 - (void)performDragOperation:(id <CPDraggingInfo>)aSender
571 {
572  var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
573  pasteboard = [aSender draggingPasteboard],
574  swatch = nil;
575 
576  if (![pasteboard availableTypeFromArray:[CPColorDragType]] || location.x > [self bounds].size.width - 1 || location.x < 1)
577  return NO;
578 
579  [self setColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] atIndex:FLOOR(location.x / 13)];
580 }
581 
582 @end
583 
584 /* @ignore */
585 @implementation _CPColorPanelPreview : CPView
586 {
587  CPColorPanel _colorPanel;
588 }
589 
590 - (id)initWithFrame:(CPRect)aFrame
591 {
592  self = [super initWithFrame:aFrame];
593 
594  [self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
595 
596  return self;
597 }
598 
599 - (void)setColorPanel:(CPColorPanel)aPanel
600 {
601  _colorPanel = aPanel;
602 }
603 
604 - (CPColorPanel)colorPanel
605 {
606  return _colorPanel;
607 }
608 
609 - (void)performDragOperation:(id <CPDraggingInfo>)aSender
610 {
611  var pasteboard = [aSender draggingPasteboard];
612 
613  if (![pasteboard availableTypeFromArray:[CPColorDragType]])
614  return NO;
615 
616  var color = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]];
617  [_colorPanel setColor:color updatePicker:YES];
618 }
619 
620 - (BOOL)isOpaque
621 {
622  return YES;
623 }
624 
625 - (void)mouseDragged:(CPEvent)anEvent
626 {
627  var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
628 
629  [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPColorDragType] owner:self];
630 
631  var bounds = CPRectMake(0, 0, 15, 15);
632 
633  // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
634  var dragView = [[CPView alloc] initWithFrame:bounds],
635  dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
636 
637  [dragView setBackgroundColor:[CPColor blackColor]];
638  [dragFillView setBackgroundColor:[self backgroundColor]];
639 
640  [dragView addSubview:dragFillView];
641 
642  [self dragView:dragView
643  at:CPPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
644  offset:CPPointMake(0.0, 0.0)
645  event:anEvent
646  pasteboard:nil
647  source:self
648  slideBack:YES];
649 }
650 
651 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
652 {
653  if (aType == CPColorDragType)
654  [aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:aType];
655 }
656 
657 @end
658 
659 
660 [CPColorPanel provideColorPickerClass:CPColorWheelColorPicker];
661 [CPColorPanel provideColorPickerClass:CPSliderColorPicker];