API  1.0.0
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 
28 @typedef CPImageAlignment
38 
40 
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)
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  var image = [self objectValue],
97  isCSSBasedImage = [image isCSSBased],
98  isIMGImageElement = _DOMImageElement && (_DOMImageElement.nodeName == "IMG");
99 
100  // First, check if we need to destroy a current DOM image element. This is the case if :
101  // - we have one but not the right one (that is a DIV but needing an IMG, and vice versa)
102 
103  if (_DOMImageElement)
104  {
105  if ((isIMGImageElement && isCSSBasedImage) || (!isIMGImageElement && !isCSSBasedImage))
106  {
107  // OK, destroy it
108 
109  _DOMElement.removeChild(_DOMImageElement);
110 
111  _DOMImageElement = nil;
112 
113  // CSS styling cleaning
114  _cssStylePreviousState = @[];
115  _cssStyleNode = nil;
116  }
117  else
118  return;
119  }
120 
121  _DOMImageElement = document.createElement(isCSSBasedImage ? "div" : "img");
122  _DOMImageElement.style.position = "absolute";
123  _DOMImageElement.style.left = "0px";
124  _DOMImageElement.style.top = "0px";
125 
126  if ([CPPlatform supportsDragAndDrop])
127  {
128  _DOMImageElement.setAttribute("draggable", "true");
129  _DOMImageElement.style["-khtml-user-drag"] = "element";
130  }
131 
132  _DOMImageElement.style.visibility = "hidden";
133  AppKitTagDOMElement(self, _DOMImageElement);
134 
135  CPDOMDisplayServerAppendChild(_DOMElement, _DOMImageElement);
136 #endif
137 }
138 
142 - (CPImage)image
143 {
144  return [self objectValue];
145 }
146 
147 - (void)setImage:(CPImage)anImage
148 {
149  [self setObjectValue:anImage];
150 }
151 
153 - (void)setObjectValue:(CPImage)anImage
154 {
155  var oldImage = [self objectValue];
156 
157  if (oldImage === anImage)
158  return;
159 
160  [super setObjectValue:anImage];
161 
162  var defaultCenter = [CPNotificationCenter defaultCenter];
163 
164  if (oldImage)
165  [defaultCenter removeObserver:self name:CPImageDidLoadNotification object:oldImage];
166 
167  var newImage = [self objectValue];
168 
169 #if PLATFORM(DOM)
170  [self _createDOMImageElement];
171 
172  if ([newImage isCSSBased])
173  _cssStyleNode = [newImage applyCSSImageForView:self
174  onDOMElement:_DOMImageElement
175  styleNode:_cssStyleNode
176  previousState:@ref(_cssStylePreviousState)];
177  else
178  _DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
179 #endif
180 
181  var size = [newImage size];
182 
183  if (size && size.width === -1 && size.height === -1)
184  {
185  [defaultCenter addObserver:self selector:@selector(imageDidLoad:) name:CPImageDidLoadNotification object:newImage];
186 
187 #if PLATFORM(DOM)
188  _DOMImageElement.width = 0;
189  _DOMImageElement.height = 0;
190 #endif
191 
192  [_shadowView setHidden:YES];
193  }
194  else
195  {
196  [self hideOrDisplayContents];
197  [self setNeedsLayout];
198  [self setNeedsDisplay:YES];
199  }
200 }
201 
202 - (void)imageDidLoad:(CPNotification)aNotification
203 {
204  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPImageDidLoadNotification object:[self objectValue]];
205  [self hideOrDisplayContents];
206 
207  [self setNeedsLayout];
208  [self setNeedsDisplay:YES];
209 }
210 
215 - (BOOL)hasShadow
216 {
217  return _hasShadow;
218 }
219 
224 - (void)setHasShadow:(BOOL)shouldHaveShadow
225 {
226  if (_hasShadow == shouldHaveShadow)
227  return;
228 
229  _hasShadow = shouldHaveShadow;
230 
231  if (_hasShadow)
232  {
233  _shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
234 
235  [self addSubview:_shadowView];
236 
237  [self setNeedsLayout];
238  [self setNeedsDisplay:YES];
239  }
240  else
241  {
242  [_shadowView removeFromSuperview];
243 
244  _shadowView = nil;
245  }
246 
247  [self hideOrDisplayContents];
248 }
249 
255 - (void)setImageAlignment:(CPImageAlignment)anImageAlignment
256 {
257  if (_imageAlignment == anImageAlignment)
258  return;
259 
260  _imageAlignment = anImageAlignment;
261 
262  if (![self image])
263  return;
264 
265  [self setNeedsLayout];
266  [self setNeedsDisplay:YES];
267 }
268 
269 - (unsigned)imageAlignment
270 {
271  return _imageAlignment;
272 }
273 
279 - (void)setImageScaling:(CPImageScaling)anImageScaling
280 {
281  [super setImageScaling:anImageScaling];
282 
283 #if PLATFORM(DOM)
284  if ([self currentValueForThemeAttribute:@"image-scaling"] === CPImageScaleAxesIndependently)
285  {
286  CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, 0.0, 0.0);
287  }
288 #endif
289 
290  [self setNeedsLayout];
291  [self setNeedsDisplay:YES];
292 }
293 
294 - (CPUInteger)imageScaling
295 {
296  return [self currentValueForThemeAttribute:@"image-scaling"];
297 }
298 
302 - (void)hideOrDisplayContents
303 {
304  if (![self image])
305  {
306 #if PLATFORM(DOM)
307  _DOMImageElement.style.visibility = "hidden";
308 #endif
309  [_shadowView setHidden:YES];
310  }
311  else
312  {
313 #if PLATFORM(DOM)
314  _DOMImageElement.style.visibility = "visible";
315 #endif
316  [_shadowView setHidden:NO];
317  }
318 }
319 
323 - (CGRect)imageRect
324 {
325  return _imageRect;
326 }
327 
331 - (void)layoutSubviews
332 {
333  if (![self image])
334  return;
335 
336  var bounds = [self bounds],
337  image = [self image],
338  imageScaling = [self currentValueForThemeAttribute:@"image-scaling"],
339  x = 0.0,
340  y = 0.0,
341  insetWidth = (_hasShadow ? [_shadowView horizontalInset] : 0.0),
342  insetHeight = (_hasShadow ? [_shadowView verticalInset] : 0.0),
343  boundsWidth = CGRectGetWidth(bounds),
344  boundsHeight = CGRectGetHeight(bounds),
345  width = boundsWidth - insetWidth,
346  height = boundsHeight - insetHeight;
347 
348  if (imageScaling === CPImageScaleAxesIndependently)
349  {
350  #if PLATFORM(DOM)
351  _DOMImageElement.width = ROUND(width);
352  _DOMImageElement.height = ROUND(height);
353  #endif
354  }
355  else
356  {
357  var size = [image size];
358 
359  if (size.width == -1 && size.height == -1)
360  return;
361 
362  switch (imageScaling)
363  {
365  if (width >= size.width && height >= size.height)
366  {
367  width = size.width;
368  height = size.height;
369  break;
370  }
371 
372  // intentionally fall through to the next case
373 
375  var imageRatio = size.width / size.height,
376  viewRatio = width / height;
377 
378  if (viewRatio > imageRatio)
379  width = height * imageRatio;
380  else
381  height = width / imageRatio;
382  break;
383 
385  case CPImageScaleNone:
386  width = size.width;
387  height = size.height;
388  break;
389  }
390 
391  #if PLATFORM(DOM)
392  _DOMImageElement.width = ROUND(width);
393  _DOMImageElement.height = ROUND(height);
394  #endif
395 
396  var x,
397  y;
398 
399  switch (_imageAlignment)
400  {
401  case CPImageAlignLeft:
402  case CPImageAlignTopLeft:
404  x = 0.0;
405  break;
406 
407  case CPImageAlignRight:
410  x = boundsWidth - width;
411  break;
412 
413  default:
414  x = (boundsWidth - width) / 2.0;
415  break;
416  }
417 
418  switch (_imageAlignment)
419  {
420  case CPImageAlignTop:
421  case CPImageAlignTopLeft:
423  y = 0.0;
424  break;
425 
426  case CPImageAlignBottom:
429  y = boundsHeight - height;
430  break;
431 
432  default:
433  y = (boundsHeight - height) / 2.0;
434  break;
435  }
436 
437 #if PLATFORM(DOM)
438  CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, x, y);
439 #endif
440  }
441 
442  _imageRect = CGRectMake(x, y, width, height);
443 
444  if (_hasShadow)
445  [_shadowView setFrame:CGRectMake(x - [_shadowView leftInset], y - [_shadowView topInset], width + insetWidth, height + insetHeight)];
446 }
447 
448 - (void)mouseDown:(CPEvent)anEvent
449 {
450  // Should we do something with this event?
451  [[self nextResponder] mouseDown:anEvent];
452 }
453 
454 - (void)setEditable:(BOOL)shouldBeEditable
455 {
456  if (_isEditable === shouldBeEditable)
457  return;
458 
459  _isEditable = shouldBeEditable;
460 
461  if (_isEditable)
462  [self registerForDraggedTypes:[CPImagesPboardType]];
463 
464  else
465  {
466  var draggedTypes = [self registeredDraggedTypes];
467 
468  [self unregisterDraggedTypes];
469 
470  [draggedTypes removeObjectIdenticalTo:CPImagesPboardType];
471 
472  [self registerForDraggedTypes:draggedTypes];
473  }
474 }
475 
476 - (BOOL)isEditable
477 {
478  return _isEditable;
479 }
480 
481 - (BOOL)performDragOperation:(CPDraggingInfo)aSender
482 {
483  var images = [CPKeyedUnarchiver unarchiveObjectWithData:[[aSender draggingPasteboard] dataForType:CPImagesPboardType]];
484 
485  if ([images count])
486  {
487  [self setImage:images[0]];
488  [self sendAction:[self action] to:[self target]];
489  }
490 
491  return YES;
492 }
493 
494 @end
496 {
497  id __doxygen__;
498 }
499 
500 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
501 {
502  [self _setPlaceholder:nil forMarker:CPMultipleValuesMarker isDefault:YES];
503  [self _setPlaceholder:nil forMarker:CPNoSelectionMarker isDefault:YES];
504  [self _setPlaceholder:nil forMarker:CPNotApplicableMarker isDefault:YES];
505  [self _setPlaceholder:nil forMarker:CPNullMarker isDefault:YES];
506 }
507 
508 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
509 {
510  [_source setImage:nil];
511 }
512 
513 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
514 {
515  var image;
516 
517  if (aValue == nil)
518  image = nil;
519  else if (aBinding === CPDataBinding)
520  image = [[CPImage alloc] initWithData:aValue];
521  else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
522  image = [CPImage cachedImageWithContentsOfFile:aValue];
523  else if (aBinding === CPValueBinding)
524  image = aValue;
525 
526  [_source setImage:image];
527 }
528 
529 - (id)valueForBinding:(CPString)aBinding
530 {
531  var image = [_source image];
532 
533  if (aBinding === CPDataBinding)
534  return [image data];
535  else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
536  return [image filename];
537  else if (aBinding === CPValueBinding)
538  return image;
539 }
540 
541 @end
542 
543 var CPImageViewImageKey = @"CPImageViewImageKey",
544  CPImageViewImageScalingKey = @"CPImageViewImageScalingKey",
545  CPImageViewImageAlignmentKey = @"CPImageViewImageAlignmentKey",
546  CPImageViewHasShadowKey = @"CPImageViewHasShadowKey",
547  CPImageViewIsEditableKey = @"CPImageViewIsEditableKey";
548 
549 @implementation CPImageView (CPCoding)
550 
556 - (id)initWithCoder:(CPCoder)aCoder
557 {
558  self = [super initWithCoder:aCoder];
559 
560  if (self)
561  {
562 #if PLATFORM(DOM)
563  [self _createDOMImageElement];
564 #endif
565 
566  [self setHasShadow:[aCoder decodeBoolForKey:CPImageViewHasShadowKey]];
567  [self setImageAlignment:[aCoder decodeIntForKey:CPImageViewImageAlignmentKey]];
568 
569  if ([aCoder decodeBoolForKey:CPImageViewIsEditableKey])
570  [self setEditable:YES];
571 
572  [self setNeedsLayout];
573  [self setNeedsDisplay:YES];
574  }
575 
576  return self;
577 }
578 
584 - (void)encodeWithCoder:(CPCoder)aCoder
585 {
586  // We do this in order to avoid encoding the _shadowView, which
587  // should just automatically be created programmatically as needed.
588  if (_shadowView)
589  [_shadowView removeFromSuperview];
590 
591  [super encodeWithCoder:aCoder];
592 
593  if (_shadowView)
594  [self addSubview:_shadowView];
595 
596  [aCoder encodeBool:_hasShadow forKey:CPImageViewHasShadowKey];
597  [aCoder encodeInt:_imageAlignment forKey:CPImageViewImageAlignmentKey];
598 
599  if (_isEditable)
600  [aCoder encodeBool:_isEditable forKey:CPImageViewIsEditableKey];
601 }
602 
603 @end
604 
605 @implementation CPImage (CachedImage)
606 
607 + (CPImage)cachedImageWithContentsOfFile:(CPString)aFile
608 {
609  var cached_name = [CPString stringWithFormat:@"%@_%d", [self class], [aFile hash]],
610  image = [CPImage imageNamed:cached_name];
611 
612  if (!image)
613  {
614  image = [[CPImage alloc] initWithContentsOfFile:aFile];
615  [image setName:cached_name];
616  }
617 
618  return image;
619 }
620 
621 @end
var CPImageViewImageAlignmentKey
Definition: CPImageView.j:545
CPPasteboard draggingPasteboard()
Definition: CPDragServer.j:46
void addSubview:(CPView aSubview)
Definition: CPView.j:536
CPImageAlignRight
Definition: CPImageView.j:37
CPImageAlignTopRight
Definition: CPImageView.j:32
CPImageAlignBottomRight
Definition: CPImageView.j:36
var CPImageViewIsEditableKey
Definition: CPImageView.j:547
CGSize size()
Definition: CPImage.j:267
int width
id initWithFrame:(CGRect aFrame)
Definition: CPControl.j:183
CGRect bounds()
Definition: CPView.j:1326
void setHasShadow:(BOOL shouldHaveShadow)
Definition: CPImageView.j:224
CPArray registeredDraggedTypes()
Definition: CPView.j:2482
CPImageAlignLeft
Definition: CPImageView.j:33
Unarchives objects created using CPKeyedArchiver.
CPImageScaleProportionallyDown
Definition: CPControl.j:63
void setImageScaling:(CPImageScaling scaling)
Definition: CPControl.j:940
var CPImageViewEmptyPlaceholderImage
Definition: CPImageView.j:39
CPNotificationCenter defaultCenter()
A mutable key-value pair collection.
Definition: CPDictionary.h:2
var CPImageViewHasShadowKey
Definition: CPImageView.j:546
CPImageScaleAxesIndependently
Definition: CPControl.j:64
CPImageAlignTop
Definition: CPImageView.j:30
CPImageScaleNone
Definition: CPControl.j:65
CGRect bounds()
Definition: CALayer.j:203
id imageNamed:(CPString aName)
Definition: CPImage.j:272
CPImagesPboardType
Definition: CPPasteboard.j:39
CPImage cachedImageWithContentsOfFile:(CPString aFile)
Definition: CPImageView.j:607
void setImage:(CPImage anImage)
Definition: CPImageView.j:147
An immutable string (collection of characters).
Definition: CPString.h:2
Definition: CPImage.h:2
BOOL sendAction:to:(SEL anAction, [to] id anObject)
Definition: CPControl.j:319
SEL action()
Definition: CPControl.j:290
id initWithCoder:(CPCoder aCoder)
Definition: CPControl.j:1092
void setNeedsDisplay:(BOOL aFlag)
Definition: CPView.j:2597
void setObjectValue:(id anObject)
Definition: CPControl.j:534
CPImage image()
Definition: CPImageView.j:142
id initWithContentsOfFile:(CPString aFilename)
Definition: CPImage.j:192
void setEditable:(BOOL shouldBeEditable)
Definition: CPImageView.j:454
A notification that can be posted to a CPNotificationCenter.
Definition: CPNotification.h:2
void setNeedsLayout()
Definition: CPView.j:2748
void hideOrDisplayContents()
Definition: CPImageView.j:302
id target()
Definition: CPControl.j:308
CPImageAlignTopLeft
Definition: CPImageView.j:31
CPImageAlignBottom
Definition: CPImageView.j:34
Image image()
Definition: CPImage.j:319
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
id unarchiveObjectWithData:(CPData aData)
void setObjectValue:(CPImage anImage)
Definition: CPImageView.j:153
Sends messages (CPNotification) between objects.
var CPImageViewImageScalingKey
Definition: CPImageView.j:544
CPImageAlignBottomLeft
Definition: CPImageView.j:35
CPBundle bundleForClass:(Class aClass)
Definition: CPBundle.j:77
void removeObserver:name:object:(id anObserver, [name] CPString aNotificationName, [object] id anObject)
Definition: CPEvent.h:2
Class class()
Definition: CPObject.j:179
CPData dataForType:(CPString aType)
Definition: CPPasteboard.j:251
unsigned hash()
Definition: CPObject.j:547
CPImageScaleProportionallyUpOrDown
Definition: CPControl.j:66
void encodeWithCoder:(CPCoder aCoder)
Definition: CPControl.j:1121
id objectValue()
Definition: CPControl.j:526
void unregisterDraggedTypes()
Definition: CPView.j:2493
void setImageAlignment:(CPImageAlignment anImageAlignment)
Definition: CPImageView.j:255
void registerForDraggedTypes:(CPArray pasteboardTypes)
Definition: CPView.j:2464
global CPImagesPboardType global appkit_tag_dom_elements typedef CPImageAlignment CPImageAlignCenter
Definition: CPImageView.j:29
id alloc()
Definition: CPObject.j:130
Definition: CPView.j:137
var CPImageViewImageKey
Definition: CPImageView.j:543
id stringWithFormat:(CPString format, [,] ...)
Definition: CPString.j:166