API  0.9.10
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  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 
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  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPImageDidLoadNotification object:[self objectValue]];
178  [self hideOrDisplayContents];
179 
180  [self setNeedsLayout];
181  [self setNeedsDisplay:YES];
182 }
183 
188 - (BOOL)hasShadow
189 {
190  return _hasShadow;
191 }
192 
197 - (void)setHasShadow:(BOOL)shouldHaveShadow
198 {
199  if (_hasShadow == shouldHaveShadow)
200  return;
201 
202  _hasShadow = shouldHaveShadow;
203 
204  if (_hasShadow)
205  {
206  _shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
207 
208  [self addSubview:_shadowView];
209 
210  [self setNeedsLayout];
211  [self setNeedsDisplay:YES];
212  }
213  else
214  {
215  [_shadowView removeFromSuperview];
216 
217  _shadowView = nil;
218  }
219 
220  [self hideOrDisplayContents];
221 }
222 
228 - (void)setImageAlignment:(CPImageAlignment)anImageAlignment
229 {
230  if (_imageAlignment == anImageAlignment)
231  return;
232 
233  _imageAlignment = anImageAlignment;
234 
235  if (![self image])
236  return;
237 
238  [self setNeedsLayout];
239  [self setNeedsDisplay:YES];
240 }
241 
242 - (unsigned)imageAlignment
243 {
244  return _imageAlignment;
245 }
246 
252 - (void)setImageScaling:(CPImageScaling)anImageScaling
253 {
254  [super setImageScaling:anImageScaling];
255 
256 #if PLATFORM(DOM)
257  if ([self currentValueForThemeAttribute:@"image-scaling"] === CPImageScaleAxesIndependently)
258  {
259  CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, 0.0, 0.0);
260  }
261 #endif
262 
263  [self setNeedsLayout];
264  [self setNeedsDisplay:YES];
265 }
266 
267 - (CPUInteger)imageScaling
268 {
269  return [self currentValueForThemeAttribute:@"image-scaling"];
270 }
271 
276 {
277  if (![self image])
278  {
279 #if PLATFORM(DOM)
280  _DOMImageElement.style.visibility = "hidden";
281 #endif
282  [_shadowView setHidden:YES];
283  }
284  else
285  {
286 #if PLATFORM(DOM)
287  _DOMImageElement.style.visibility = "visible";
288 #endif
289  [_shadowView setHidden:NO];
290  }
291 }
292 
296 - (CGRect)imageRect
297 {
298  return _imageRect;
299 }
300 
305 {
306  if (![self image])
307  return;
308 
309  var bounds = [self bounds],
310  image = [self image],
311  imageScaling = [self currentValueForThemeAttribute:@"image-scaling"],
312  x = 0.0,
313  y = 0.0,
314  insetWidth = (_hasShadow ? [_shadowView horizontalInset] : 0.0),
315  insetHeight = (_hasShadow ? [_shadowView verticalInset] : 0.0),
316  boundsWidth = CGRectGetWidth(bounds),
317  boundsHeight = CGRectGetHeight(bounds),
318  width = boundsWidth - insetWidth,
319  height = boundsHeight - insetHeight;
320 
322  {
323  #if PLATFORM(DOM)
324  _DOMImageElement.width = ROUND(width);
325  _DOMImageElement.height = ROUND(height);
326  #endif
327  }
328  else
329  {
330  var size = [image size];
331 
332  if (size.width == -1 && size.height == -1)
333  return;
334 
335  switch (imageScaling)
336  {
338  if (width >= size.width && height >= size.height)
339  {
340  width = size.width;
341  height = size.height;
342  break;
343  }
344 
345  // intentionally fall through to the next case
346 
348  var imageRatio = size.width / size.height,
349  viewRatio = width / height;
350 
351  if (viewRatio > imageRatio)
352  width = height * imageRatio;
353  else
354  height = width / imageRatio;
355  break;
356 
358  case CPImageScaleNone:
359  width = size.width;
360  height = size.height;
361  break;
362  }
363 
364  #if PLATFORM(DOM)
365  _DOMImageElement.width = ROUND(width);
366  _DOMImageElement.height = ROUND(height);
367  #endif
368 
369  var x,
370  y;
371 
372  switch (_imageAlignment)
373  {
374  case CPImageAlignLeft:
375  case CPImageAlignTopLeft:
377  x = 0.0;
378  break;
379 
380  case CPImageAlignRight:
383  x = boundsWidth - width;
384  break;
385 
386  default:
387  x = (boundsWidth - width) / 2.0;
388  break;
389  }
390 
391  switch (_imageAlignment)
392  {
393  case CPImageAlignTop:
394  case CPImageAlignTopLeft:
396  y = 0.0;
397  break;
398 
399  case CPImageAlignBottom:
402  y = boundsHeight - height;
403  break;
404 
405  default:
406  y = (boundsHeight - height) / 2.0;
407  break;
408  }
409 
410 #if PLATFORM(DOM)
411  CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, x, y);
412 #endif
413  }
414 
415  _imageRect = CGRectMake(x, y, width, height);
416 
417  if (_hasShadow)
418  [_shadowView setFrame:CGRectMake(x - [_shadowView leftInset], y - [_shadowView topInset], width + insetWidth, height + insetHeight)];
419 }
420 
421 - (void)mouseDown:(CPEvent)anEvent
422 {
423  // Should we do something with this event?
424  [[self nextResponder] mouseDown:anEvent];
425 }
426 
427 - (void)setEditable:(BOOL)shouldBeEditable
428 {
429  if (_isEditable === shouldBeEditable)
430  return;
431 
432  _isEditable = shouldBeEditable;
433 
434  if (_isEditable)
435  [self registerForDraggedTypes:[CPImagesPboardType]];
436 
437  else
438  {
439  var draggedTypes = [self registeredDraggedTypes];
440 
441  [self unregisterDraggedTypes];
442 
443  [draggedTypes removeObjectIdenticalTo:CPImagesPboardType];
444 
445  [self registerForDraggedTypes:draggedTypes];
446  }
447 }
448 
449 - (BOOL)isEditable
450 {
451  return _isEditable;
452 }
453 
454 - (BOOL)performDragOperation:(CPDraggingInfo)aSender
455 {
456  var images = [CPKeyedUnarchiver unarchiveObjectWithData:[[aSender draggingPasteboard] dataForType:CPImagesPboardType]];
457 
458  if ([images count])
459  {
460  [self setImage:images[0]];
461  [self sendAction:[self action] to:[self target]];
462  }
463 
464  return YES;
465 }
466 
467 @end
469 {
470  id __doxygen__;
471 }
472 
473 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
474 {
475  [self _setPlaceholder:nil forMarker:CPMultipleValuesMarker isDefault:YES];
476  [self _setPlaceholder:nil forMarker:CPNoSelectionMarker isDefault:YES];
477  [self _setPlaceholder:nil forMarker:CPNotApplicableMarker isDefault:YES];
478  [self _setPlaceholder:nil forMarker:CPNullMarker isDefault:YES];
479 }
480 
481 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
482 {
483  [_source setImage:nil];
484 }
485 
486 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
487 {
488  var image;
489 
490  if (aValue == nil)
491  image = nil;
492  else if (aBinding === CPDataBinding)
493  image = [[CPImage alloc] initWithData:aValue];
494  else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
496  else if (aBinding === CPValueBinding)
497  image = aValue;
498 
499  [_source setImage:image];
500 }
501 
502 - (id)valueForBinding:(CPString)aBinding
503 {
504  var image = [_source image];
505 
506  if (aBinding === CPDataBinding)
507  return [image data];
508  else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
509  return [image filename];
510  else if (aBinding === CPValueBinding)
511  return image;
512 }
513 
514 @end
515 
516 var CPImageViewImageKey = @"CPImageViewImageKey",
517  CPImageViewImageScalingKey = @"CPImageViewImageScalingKey",
518  CPImageViewImageAlignmentKey = @"CPImageViewImageAlignmentKey",
519  CPImageViewHasShadowKey = @"CPImageViewHasShadowKey",
520  CPImageViewIsEditableKey = @"CPImageViewIsEditableKey";
521 
522 @implementation CPImageView (CPCoding)
523 
529 - (id)initWithCoder:(CPCoder)aCoder
530 {
531  self = [super initWithCoder:aCoder];
532 
533  if (self)
534  {
535 #if PLATFORM(DOM)
536  [self _createDOMImageElement];
537 #endif
538 
539  [self setHasShadow:[aCoder decodeBoolForKey:CPImageViewHasShadowKey]];
540  [self setImageAlignment:[aCoder decodeIntForKey:CPImageViewImageAlignmentKey]];
541 
542  if ([aCoder decodeBoolForKey:CPImageViewIsEditableKey])
543  [self setEditable:YES];
544 
545  [self setNeedsLayout];
546  [self setNeedsDisplay:YES];
547  }
548 
549  return self;
550 }
551 
557 - (void)encodeWithCoder:(CPCoder)aCoder
558 {
559  // We do this in order to avoid encoding the _shadowView, which
560  // should just automatically be created programmatically as needed.
561  if (_shadowView)
562  [_shadowView removeFromSuperview];
563 
564  [super encodeWithCoder:aCoder];
565 
566  if (_shadowView)
567  [self addSubview:_shadowView];
568 
569  [aCoder encodeBool:_hasShadow forKey:CPImageViewHasShadowKey];
570  [aCoder encodeInt:_imageAlignment forKey:CPImageViewImageAlignmentKey];
571 
572  if (_isEditable)
573  [aCoder encodeBool:_isEditable forKey:CPImageViewIsEditableKey];
574 }
575 
576 @end
577 
578 @implementation CPImage (CachedImage)
579 
580 + (CPImage)cachedImageWithContentsOfFile:(CPString)aFile
581 {
582  var cached_name = [CPString stringWithFormat:@"%@_%d", [self class], [aFile hash]],
583  image = [CPImage imageNamed:cached_name];
584 
585  if (!image)
586  {
588  [image setName:cached_name];
589  }
590 
591  return image;
592 }
593 
594 @end
var CPImageViewImageAlignmentKey
Definition: CPImageView.j:518
CPPasteboard draggingPasteboard()
Definition: CPDragServer.j:46
void addSubview:(CPView aSubview)
Definition: CPView.j:512
BOOL hasShadow()
Definition: CPImageView.j:188
CPImageAlignRight
Definition: CPImageView.j:37
CPImageAlignTopRight
Definition: CPImageView.j:32
CPImageAlignBottomRight
Definition: CPImageView.j:36
CPValueURLBinding
var CPImageViewIsEditableKey
Definition: CPImageView.j:520
CGSize size()
Definition: CPImage.j:267
int width
id initWithFrame:(CGRect aFrame)
Definition: CPControl.j:183
CGRect bounds()
Definition: CPView.j:1302
void setHasShadow:(BOOL shouldHaveShadow)
Definition: CPImageView.j:197
CPArray registeredDraggedTypes()
Definition: CPView.j:2441
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
CGRect imageRect()
Definition: CPImageView.j:296
var CPImageViewHasShadowKey
Definition: CPImageView.j:519
CPImageScaleAxesIndependently
Definition: CPControl.j:64
CPImageAlignTop
Definition: CPImageView.j:30
CPImageScaleNone
Definition: CPControl.j:65
id imageNamed:(CPString aName)
Definition: CPImage.j:272
CPImagesPboardType
Definition: CPPasteboard.j:39
CPImage cachedImageWithContentsOfFile:(CPString aFile)
Definition: CPImageView.j:580
CPValuePathBinding
CPEditableBinding
void setImage:(CPImage anImage)
Definition: CPImageView.j:125
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:1085
void setNeedsDisplay:(BOOL aFlag)
Definition: CPView.j:2556
void setObjectValue:(id anObject)
Definition: CPControl.j:534
CPImage image()
Definition: CPImageView.j:120
void initialize()
Definition: CPImageView.j:59
id initWithContentsOfFile:(CPString aFilename)
Definition: CPImage.j:192
void setEditable:(BOOL shouldBeEditable)
Definition: CPImageView.j:427
A notification that can be posted to a CPNotificationCenter.
Definition: CPNotification.h:2
CPUInteger imageScaling()
Definition: CPImageView.j:267
void setNeedsLayout()
Definition: CPView.j:2707
void hideOrDisplayContents()
Definition: CPImageView.j:275
unsigned imageAlignment()
Definition: CPImageView.j:242
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:131
Sends messages (CPNotification) between objects.
var CPImageViewImageScalingKey
Definition: CPImageView.j:517
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:250
CPDataBinding
CPValueBinding
unsigned hash()
Definition: CPObject.j:547
BOOL isEditable()
Definition: CPImageView.j:449
CPImageScaleProportionallyUpOrDown
Definition: CPControl.j:66
void encodeWithCoder:(CPCoder aCoder)
Definition: CPControl.j:1114
id objectValue()
Definition: CPControl.j:526
void unregisterDraggedTypes()
Definition: CPView.j:2452
void setImageAlignment:(CPImageAlignment anImageAlignment)
Definition: CPImageView.j:228
void registerForDraggedTypes:(CPArray pasteboardTypes)
Definition: CPView.j:2423
global CPImagesPboardType global appkit_tag_dom_elements typedef CPImageAlignment CPImageAlignCenter
Definition: CPImageView.j:29
id alloc()
Definition: CPObject.j:130
Definition: CPView.j:136
void layoutSubviews()
Definition: CPImageView.j:304
var CPImageViewImageKey
Definition: CPImageView.j:516
id stringWithFormat:(CPString format, [,] ...)
Definition: CPString.j:166