API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPImageView.j
Go to the documentation of this file.
1 /*
2  * CPImageView.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
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 CPImagesPboardType
26 @global appkit_tag_dom_elements
27 
37 
39 
46 @implementation CPImageView : CPControl
47 {
48  DOMElement _DOMImageElement;
49 
50  BOOL _hasShadow;
51  CPView _shadowView;
52 
53  BOOL _isEditable;
54 
55  CGRect _imageRect;
56  CPImageAlignment _imageAlignment;
57 }
58 
59 + (void)initialize
60 {
61  if (self !== [CPImageView class])
62  return;
63 
64  var bundle = [CPBundle bundleForClass:[CPView class]];
65 
66  CPImageViewEmptyPlaceholderImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"empty.png"]];
67 }
68 
69 + (Class)_binderClassForBinding:(CPString)aBinding
70 {
71  if (aBinding === CPValueBinding || aBinding === CPValueURLBinding || aBinding === CPValuePathBinding || aBinding === CPDataBinding)
72  return [CPImageViewValueBinder class];
73  else if ([aBinding hasPrefix:CPEditableBinding])
74  return [CPMultipleValueAndBinding class];
75 
76  return [super _binderClassForBinding:aBinding];
77 }
78 
79 - (id)initWithFrame:(CGRect)aFrame
80 {
81  self = [super initWithFrame:aFrame];
82 
83  if (self)
84  {
85 #if PLATFORM(DOM)
86  [self _createDOMImageElement];
87 #endif
88  }
89 
90  return self;
91 }
92 
93 - (void)_createDOMImageElement
94 {
95 #if PLATFORM(DOM)
96  if (_DOMImageElement)
97  return;
98 
99  _DOMImageElement = document.createElement("img");
100  _DOMImageElement.style.position = "absolute";
101  _DOMImageElement.style.left = "0px";
102  _DOMImageElement.style.top = "0px";
103 
104  if ([CPPlatform supportsDragAndDrop])
105  {
106  _DOMImageElement.setAttribute("draggable", "true");
107  _DOMImageElement.style["-khtml-user-drag"] = "element";
108  }
109 
110  _DOMImageElement.style.visibility = "hidden";
111  AppKitTagDOMElement(self, _DOMImageElement);
112 
113  CPDOMDisplayServerAppendChild(_DOMElement, _DOMImageElement);
114 #endif
115 }
116 
120 - (CPImage)image
121 {
122  return [self objectValue];
123 }
124 
125 - (void)setImage:(CPImage)anImage
126 {
127  [self setObjectValue:anImage];
128 }
129 
131 - (void)setObjectValue:(CPImage)anImage
132 {
133  var oldImage = [self objectValue];
134 
135  if (oldImage === anImage)
136  return;
137 
138  [super setObjectValue:anImage];
139 
140  var defaultCenter = [CPNotificationCenter defaultCenter];
141 
142  if (oldImage)
143  [defaultCenter removeObserver:self name:CPImageDidLoadNotification object:oldImage];
144 
145  var newImage = [self objectValue];
146 
147 #if PLATFORM(DOM)
148  if (!_DOMImageElement)
149  [self _createDOMImageElement];
150 
151  _DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
152 #endif
153 
154  var size = [newImage size];
155 
156  if (size && size.width === -1 && size.height === -1)
157  {
158  [defaultCenter addObserver:self selector:@selector(imageDidLoad:) name:CPImageDidLoadNotification object:newImage];
159 
160 #if PLATFORM(DOM)
161  _DOMImageElement.width = 0;
162  _DOMImageElement.height = 0;
163 #endif
164 
165  [_shadowView setHidden:YES];
166  }
167  else
168  {
169  [self hideOrDisplayContents];
170  [self setNeedsLayout];
171  [self setNeedsDisplay:YES];
172  }
173 }
174 
175 - (void)imageDidLoad:(CPNotification)aNotification
176 {
177  [self hideOrDisplayContents];
178 
179  [self setNeedsLayout];
180  [self setNeedsDisplay:YES];
181 }
182 
187 - (BOOL)hasShadow
188 {
189  return _hasShadow;
190 }
191 
196 - (void)setHasShadow:(BOOL)shouldHaveShadow
197 {
198  if (_hasShadow == shouldHaveShadow)
199  return;
200 
201  _hasShadow = shouldHaveShadow;
202 
203  if (_hasShadow)
204  {
205  _shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
206 
207  [self addSubview:_shadowView];
208 
209  [self setNeedsLayout];
210  [self setNeedsDisplay:YES];
211  }
212  else
213  {
214  [_shadowView removeFromSuperview];
215 
216  _shadowView = nil;
217  }
218 
219  [self hideOrDisplayContents];
220 }
221 
227 - (void)setImageAlignment:(CPImageAlignment)anImageAlignment
228 {
229  if (_imageAlignment == anImageAlignment)
230  return;
231 
232  _imageAlignment = anImageAlignment;
233 
234  if (![self image])
235  return;
236 
237  [self setNeedsLayout];
238  [self setNeedsDisplay:YES];
239 }
240 
241 - (unsigned)imageAlignment
242 {
243  return _imageAlignment;
244 }
245 
251 - (void)setImageScaling:(CPImageScaling)anImageScaling
252 {
253  [super setImageScaling:anImageScaling];
254 
255 #if PLATFORM(DOM)
256  if ([self currentValueForThemeAttribute:@"image-scaling"] === CPImageScaleAxesIndependently)
257  {
258  CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, 0.0, 0.0);
259  }
260 #endif
261 
262  [self setNeedsLayout];
263  [self setNeedsDisplay:YES];
264 }
265 
266 - (CPUInteger)imageScaling
267 {
268  return [self currentValueForThemeAttribute:@"image-scaling"];
269 }
270 
274 - (void)hideOrDisplayContents
275 {
276  if (![self image])
277  {
278 #if PLATFORM(DOM)
279  _DOMImageElement.style.visibility = "hidden";
280 #endif
281  [_shadowView setHidden:YES];
282  }
283  else
284  {
285 #if PLATFORM(DOM)
286  _DOMImageElement.style.visibility = "visible";
287 #endif
288  [_shadowView setHidden:NO];
289  }
290 }
291 
295 - (CGRect)imageRect
296 {
297  return _imageRect;
298 }
299 
303 - (void)layoutSubviews
304 {
305  if (![self image])
306  return;
307 
308  var bounds = [self bounds],
309  image = [self image],
310  imageScaling = [self currentValueForThemeAttribute:@"image-scaling"],
311  x = 0.0,
312  y = 0.0,
313  insetWidth = (_hasShadow ? [_shadowView horizontalInset] : 0.0),
314  insetHeight = (_hasShadow ? [_shadowView verticalInset] : 0.0),
315  boundsWidth = CGRectGetWidth(bounds),
316  boundsHeight = CGRectGetHeight(bounds),
317  width = boundsWidth - insetWidth,
318  height = boundsHeight - insetHeight;
319 
320  if (imageScaling === CPImageScaleAxesIndependently)
321  {
322  #if PLATFORM(DOM)
323  _DOMImageElement.width = ROUND(width);
324  _DOMImageElement.height = ROUND(height);
325  #endif
326  }
327  else
328  {
329  var size = [image size];
330 
331  if (size.width == -1 && size.height == -1)
332  return;
333 
334  switch (imageScaling)
335  {
337  if (width >= size.width && height >= size.height)
338  {
339  width = size.width;
340  height = size.height;
341  break;
342  }
343 
344  // intentionally fall through to the next case
345 
347  var imageRatio = size.width / size.height,
348  viewRatio = width / height;
349 
350  if (viewRatio > imageRatio)
351  width = height * imageRatio;
352  else
353  height = width / imageRatio;
354  break;
355 
357  case CPImageScaleNone:
358  width = size.width;
359  height = size.height;
360  break;
361  }
362 
363  #if PLATFORM(DOM)
364  _DOMImageElement.width = ROUND(width);
365  _DOMImageElement.height = ROUND(height);
366  #endif
367 
368  var x,
369  y;
370 
371  switch (_imageAlignment)
372  {
373  case CPImageAlignLeft:
374  case CPImageAlignTopLeft:
376  x = 0.0;
377  break;
378 
379  case CPImageAlignRight:
382  x = boundsWidth - width;
383  break;
384 
385  default:
386  x = (boundsWidth - width) / 2.0;
387  break;
388  }
389 
390  switch (_imageAlignment)
391  {
392  case CPImageAlignTop:
393  case CPImageAlignTopLeft:
395  y = 0.0;
396  break;
397 
398  case CPImageAlignBottom:
401  y = boundsHeight - height;
402  break;
403 
404  default:
405  y = (boundsHeight - height) / 2.0;
406  break;
407  }
408 
409 #if PLATFORM(DOM)
410  CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, x, y);
411 #endif
412  }
413 
414  _imageRect = CGRectMake(x, y, width, height);
415 
416  if (_hasShadow)
417  [_shadowView setFrame:CGRectMake(x - [_shadowView leftInset], y - [_shadowView topInset], width + insetWidth, height + insetHeight)];
418 }
419 
420 - (void)mouseDown:(CPEvent)anEvent
421 {
422  // Should we do something with this event?
423  [[self nextResponder] mouseDown:anEvent];
424 }
425 
426 - (void)setEditable:(BOOL)shouldBeEditable
427 {
428  if (_isEditable === shouldBeEditable)
429  return;
430 
431  _isEditable = shouldBeEditable;
432 
433  if (_isEditable)
434  [self registerForDraggedTypes:[CPImagesPboardType]];
435 
436  else
437  {
438  var draggedTypes = [self registeredDraggedTypes];
439 
440  [self unregisterDraggedTypes];
441 
442  [draggedTypes removeObjectIdenticalTo:CPImagesPboardType];
443 
444  [self registerForDraggedTypes:draggedTypes];
445  }
446 }
447 
448 - (BOOL)isEditable
449 {
450  return _isEditable;
451 }
452 
453 - (BOOL)performDragOperation:(CPDraggingInfo)aSender
454 {
455  var images = [CPKeyedUnarchiver unarchiveObjectWithData:[[aSender draggingPasteboard] dataForType:CPImagesPboardType]];
456 
457  if ([images count])
458  {
459  [self setImage:images[0]];
460  [self sendAction:[self action] to:[self target]];
461  }
462 
463  return YES;
464 }
465 
466 @end
468 {
469  id __doxygen__;
470 }
471 
472 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
473 {
474  [self _setPlaceholder:nil forMarker:CPMultipleValuesMarker isDefault:YES];
475  [self _setPlaceholder:nil forMarker:CPNoSelectionMarker isDefault:YES];
476  [self _setPlaceholder:nil forMarker:CPNotApplicableMarker isDefault:YES];
477  [self _setPlaceholder:nil forMarker:CPNullMarker isDefault:YES];
478 }
479 
480 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
481 {
482  [_source setImage:nil];
483 }
484 
485 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
486 {
487  var image;
488 
489  if (aValue == nil)
490  image = nil;
491  else if (aBinding === CPDataBinding)
492  image = [[CPImage alloc] initWithData:aValue];
493  else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
494  image = [CPImage cachedImageWithContentsOfFile:aValue];
495  else if (aBinding === CPValueBinding)
496  image = aValue;
497 
498  [_source setImage:image];
499 }
500 
501 - (id)valueForBinding:(CPString)aBinding
502 {
503  var image = [_source image];
504 
505  if (aBinding === CPDataBinding)
506  return [image data];
507  else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
508  return [image filename];
509  else if (aBinding === CPValueBinding)
510  return image;
511 }
512 
513 @end
514 
515 var CPImageViewImageKey = @"CPImageViewImageKey",
516  CPImageViewImageScalingKey = @"CPImageViewImageScalingKey",
517  CPImageViewImageAlignmentKey = @"CPImageViewImageAlignmentKey",
518  CPImageViewHasShadowKey = @"CPImageViewHasShadowKey",
519  CPImageViewIsEditableKey = @"CPImageViewIsEditableKey";
520 
521 @implementation CPImageView (CPCoding)
522 
528 - (id)initWithCoder:(CPCoder)aCoder
529 {
530  self = [super initWithCoder:aCoder];
531 
532  if (self)
533  {
534 #if PLATFORM(DOM)
535  [self _createDOMImageElement];
536 #endif
537 
538  [self setHasShadow:[aCoder decodeBoolForKey:CPImageViewHasShadowKey]];
539  [self setImageAlignment:[aCoder decodeIntForKey:CPImageViewImageAlignmentKey]];
540 
541  if ([aCoder decodeBoolForKey:CPImageViewIsEditableKey])
542  [self setEditable:YES];
543 
544  [self setNeedsLayout];
545  [self setNeedsDisplay:YES];
546  }
547 
548  return self;
549 }
550 
556 - (void)encodeWithCoder:(CPCoder)aCoder
557 {
558  // We do this in order to avoid encoding the _shadowView, which
559  // should just automatically be created programmatically as needed.
560  if (_shadowView)
561  [_shadowView removeFromSuperview];
562 
563  [super encodeWithCoder:aCoder];
564 
565  if (_shadowView)
566  [self addSubview:_shadowView];
567 
568  [aCoder encodeBool:_hasShadow forKey:CPImageViewHasShadowKey];
569  [aCoder encodeInt:_imageAlignment forKey:CPImageViewImageAlignmentKey];
570 
571  if (_isEditable)
572  [aCoder encodeBool:_isEditable forKey:CPImageViewIsEditableKey];
573 }
574 
575 @end
576 
577 @implementation CPImage (CachedImage)
578 
579 + (CPImage)cachedImageWithContentsOfFile:(CPString)aFile
580 {
581  var cached_name = [CPString stringWithFormat:@"%@_%d", [self class], [aFile hash]],
582  image = [CPImage imageNamed:cached_name];
583 
584  if (!image)
585  {
586  image = [[CPImage alloc] initWithContentsOfFile:aFile];
587  [image setName:cached_name];
588  }
589 
590  return image;
591 }
592 
593 @end