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