Unreviewed, rolling out r218373.
[WebKit-https.git] / Source / WebCore / platform / ios / wak / WAKView.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WAKViewInternal.h"
28
29 #if PLATFORM(IOS)
30
31 #import "GraphicsContext.h"
32 #import "WAKClipView.h"
33 #import "WAKScrollView.h"
34 #import "WAKWindow.h"
35 #import "WKGraphics.h"
36 #import "WKUtilities.h"
37 #import "WKViewPrivate.h"
38 #import "WebCoreThreadMessage.h"
39 #import "WebEvent.h"
40 #import <wtf/Assertions.h>
41
42 WEBCORE_EXPORT NSString *WAKViewFrameSizeDidChangeNotification =   @"WAKViewFrameSizeDidChangeNotification";
43 WEBCORE_EXPORT NSString *WAKViewDidScrollNotification =            @"WAKViewDidScrollNotification";
44
45 static WAKView *globalFocusView = nil;
46 static CGInterpolationQuality sInterpolationQuality;
47
48 static void setGlobalFocusView(WAKView *view)
49 {
50     if (view == globalFocusView)
51         return;
52
53     [view retain];
54     [globalFocusView release];
55     globalFocusView = view;
56 }
57
58 static WAKScrollView *enclosingScrollView(WAKView *view)
59 {
60     view = [view superview];
61     while (view && ![view isKindOfClass:[WAKScrollView class]])
62         view = [view superview];
63     return (WAKScrollView *)view;
64 }
65
66 @interface WAKScrollView()
67 - (void)_adjustScrollers;
68 @end
69
70 @interface WAKView (WAKInternal)
71 - (id)_initWithViewRef:(WKViewRef)viewRef;
72 @end
73
74 @implementation WAKView
75
76 static NSInvocation* invocationForPostNotification(NSString *name, id object, id userInfo)
77 {
78     NSNotificationCenter *target = [NSNotificationCenter defaultCenter];
79     NSInvocation *invocation = WebThreadMakeNSInvocation(target, @selector(postNotificationName:object:userInfo:));
80     [invocation setArgument:&name atIndex:2];
81     [invocation setArgument:&object atIndex:3];
82     [invocation setArgument:&userInfo atIndex:4];
83     return invocation;
84 }
85
86 static void notificationCallback (WKViewRef v, WKViewNotificationType type, void *userInfo)
87 {
88     UNUSED_PARAM(v);
89     WAKView *view = (WAKView *)userInfo;
90     switch (type){
91         case WKViewNotificationViewDidMoveToWindow: {
92             [view viewDidMoveToWindow];
93             break;
94         }
95         case WKViewNotificationViewFrameSizeChanged: {
96             [view frameSizeChanged];
97             if (WAKScrollView *scrollView = enclosingScrollView(view))
98                 [scrollView _adjustScrollers];
99
100             // Posting a notification to the main thread can cause the WebThreadLock to be
101             // relinquished, which gives the main thread a change to run (and possible try
102             // to paint). We don't want this to happen if we've updating view sizes in the middle
103             // of layout, so use an async notification. <rdar://problem/6745974>
104             NSInvocation *invocation = invocationForPostNotification(WAKViewFrameSizeDidChangeNotification, view, nil);
105             WebThreadCallDelegateAsync(invocation);
106             break;
107         }
108         case WKViewNotificationViewDidScroll: {
109             WebThreadRunOnMainThread(^ {
110                  [[NSNotificationCenter defaultCenter] postNotificationName:WAKViewDidScrollNotification object:view userInfo:nil];
111             });
112             break;
113         }            
114         default: {
115             break;
116         }
117     }
118 }
119
120 - (void)handleEvent:(WebEvent *)event
121 {
122     ASSERT(event);
123     WAKView *view = self;
124     while (view) {
125         if ([view _selfHandleEvent:event])
126             break;
127         view = [view superview];
128     }
129 }
130
131 - (BOOL)_selfHandleEvent:(WebEvent *)event
132 {
133     WebEventType type = event.type;
134     
135     switch (type) {
136         case WebEventMouseDown: {
137             [self mouseDown:event];
138             break;
139         }
140         case WebEventMouseUp: {
141             [self mouseUp:event];
142             break;
143         }
144         case WebEventMouseMoved: {
145             [self mouseMoved:event];
146             break;
147         }
148         case WebEventKeyDown: {
149             [self keyDown:event];
150             break;
151         }
152         case WebEventKeyUp: {
153             [self keyUp:event];
154             break;
155         }
156         case WebEventScrollWheel: {
157             [self scrollWheel:event];
158             break;
159         }
160 #if ENABLE(TOUCH_EVENTS)
161         case WebEventTouchBegin:
162         case WebEventTouchChange:
163         case WebEventTouchEnd:
164         case WebEventTouchCancel: {
165             [self touch:event];
166             break;
167         }
168 #endif
169         default:
170             ASSERT_NOT_REACHED();
171             break;
172     }
173     return YES;
174 }
175
176 - (NSResponder *)nextResponder
177 {
178     return [self superview];
179 }
180
181 static bool responderCallback(WKViewRef, WKViewResponderCallbackType type, void *userInfo)
182 {
183     return [(WAKView *)userInfo _handleResponderCall:type];
184 }
185
186 - (BOOL)_handleResponderCall:(WKViewResponderCallbackType)type
187 {
188     switch (type) {
189         case WKViewResponderAcceptsFirstResponder:
190             return [self acceptsFirstResponder];
191         case WKViewResponderBecomeFirstResponder:
192             return [self becomeFirstResponder];
193         case WKViewResponderResignFirstResponder:
194             return [self resignFirstResponder];
195     }
196     return NO;
197 }
198
199 static void willRemoveSubviewCallback(WKViewRef view, WKViewRef subview)
200 {
201     [WAKViewForWKViewRef(view) willRemoveSubview:WAKViewForWKViewRef(subview)];
202 }
203
204 static void invalidateGStateCallback(WKViewRef view)
205 {
206     [WAKViewForWKViewRef(view) invalidateGState];
207 }
208
209 + (WAKView *)_wrapperForViewRef:(WKViewRef)_viewRef
210 {
211     ASSERT(_viewRef);
212     if (_viewRef->isa.classInfo == &WKViewClassInfo)
213         return [[[WAKView alloc] _initWithViewRef:_viewRef] autorelease];
214     WKError ("unable to create wrapper for %s\n", _viewRef->isa.classInfo->name);
215     return nil;
216 }
217
218 - (id)_initWithViewRef:(WKViewRef)viewR
219 {
220     self = [super init];
221     if (!self)
222         return nil;
223
224     viewRef = (WKViewRef)WKRetain (viewR);
225     viewRef->wrapper = (void *)self;
226
227     return self;
228 }
229
230 - (id)init
231 {
232     return [self initWithFrame:CGRectZero];
233 }
234
235 - (id)initWithFrame:(CGRect)rect
236 {
237     WKViewRef view = WKViewCreateWithFrame(rect, &viewContext);
238     self = [self _initWithViewRef:view];
239     if (self) {
240         viewContext.notificationCallback = notificationCallback;
241         viewContext.notificationUserInfo = self;
242         viewContext.responderCallback = responderCallback;
243         viewContext.responderUserInfo = self;
244         viewContext.willRemoveSubviewCallback = willRemoveSubviewCallback;
245         viewContext.invalidateGStateCallback = invalidateGStateCallback;
246     }
247     WKRelease(view);
248     return self;
249 }
250
251 - (void)dealloc
252 {
253     [[[subviewReferences copy] autorelease] makeObjectsPerformSelector:@selector(removeFromSuperview)];
254
255     if (viewRef) {
256         _WKViewSetViewContext (viewRef, 0);
257         viewRef->wrapper = NULL;
258         WKRelease (viewRef);
259     }
260     
261     [subviewReferences release];
262     
263     [super dealloc];
264 }
265
266 - (WAKWindow *)window
267 {
268     return WKViewGetWindow(viewRef);
269 }
270
271 - (WKViewRef)_viewRef
272 {
273     return viewRef;
274 }
275
276 - (NSMutableSet *)_subviewReferences
277 {
278     if (!subviewReferences)
279         subviewReferences = [[NSMutableSet alloc] init];
280     return subviewReferences;
281 }
282
283 static void _WAKCopyWrapper(const void *value, void *context)
284 {
285     if (!value)
286         return;
287     if (!context)
288         return;
289     
290     NSMutableArray *array = (NSMutableArray *)context;
291     WAKView *view = WAKViewForWKViewRef((WKViewRef)value);
292     if (view)
293         [array addObject:view];
294 }
295
296 - (NSArray *)subviews
297 {
298     CFArrayRef subviews = WKViewGetSubviews([self _viewRef]);
299     if (!subviews)
300         return [NSArray array];
301     
302     CFIndex count = CFArrayGetCount(subviews);
303     if (count == 0)
304         return [NSArray array];
305     
306     NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
307     if (!result)
308         return [NSArray array];
309     
310     CFArrayApplyFunction(subviews, CFRangeMake(0, count), _WAKCopyWrapper, (void*)result);
311     
312     return result;
313 }
314
315 - (WAKView *)superview
316 {
317     return WAKViewForWKViewRef(viewRef->superview);
318 }
319
320 - (WAKView *)lastScrollableAncestor
321 {
322     WAKView *view = nil;
323     WAKScrollView *scrollView = enclosingScrollView(self);
324     
325     while (scrollView) {
326         
327         CGSize scrollViewSize = WKViewGetFrame((WKViewRef)scrollView).size;
328
329         WAKView *documentView = [scrollView documentView];
330         scrollView = enclosingScrollView(scrollView);
331                                          
332         // If this document view can be scrolled, and this is NOT the last scroll view.
333         // Our last scroll view is non-existent as far as we're concerned.
334
335         if (scrollView && !CGSizeEqualToSize(scrollViewSize, [documentView frame].size))
336             view = documentView;
337     }
338     
339     return view;
340 }
341
342 - (void)addSubview:(WAKView *)subview
343 {
344     [subview retain];
345     [subview removeFromSuperview];
346     WKViewAddSubview (viewRef, [subview _viewRef]);
347     
348     // Keep a reference to subview so it sticks around.
349     [[self _subviewReferences] addObject:subview];
350     [subview release];
351 }
352
353 - (void)willRemoveSubview:(WAKView *)subview
354 {
355     UNUSED_PARAM(subview);
356 }
357
358 - (void)removeFromSuperview
359 {
360     WAKView *oldSuperview = [[self superview] retain];
361     WKViewRemoveFromSuperview (viewRef);
362     [[oldSuperview _subviewReferences] removeObject:self];
363     [oldSuperview release];
364 }
365
366 - (void)viewDidMoveToWindow
367 {
368 }
369
370 - (void)frameSizeChanged
371 {
372 }
373
374 - (void)setNeedsDisplay:(BOOL)flag
375 {
376     if (!flag)
377         return;
378     [self setNeedsDisplayInRect:WKViewGetBounds(viewRef)];
379 }
380
381 - (void)setNeedsDisplayInRect:(CGRect)invalidRect
382 {
383     if (_isHidden)
384         return;
385
386     WAKWindow *window = [self window];
387     if (!window || CGRectIsEmpty(invalidRect))
388         return;
389
390     CGRect rect = CGRectIntersection(invalidRect, [self bounds]);
391     if (CGRectIsEmpty(rect))
392         return;
393
394     CGRect baseRect = WKViewConvertRectToBase(viewRef, rect);
395     [window setNeedsDisplayInRect:baseRect];
396 }
397
398 - (BOOL)needsDisplay 
399 {
400     return NO;
401 }
402
403 - (void)display
404 {
405     [self displayRect:WKViewGetBounds(viewRef)];
406 }
407
408 - (void)displayIfNeeded
409 {
410 }
411
412 - (void)drawRect:(CGRect)rect
413 {
414     // Do nothing.  Override in subclass.
415     UNUSED_PARAM(rect);
416 }
417
418 - (void)viewWillDraw
419 {
420     [[self subviews] makeObjectsPerformSelector:@selector(viewWillDraw)];
421 }
422
423 + (WAKView *)focusView
424 {
425     return globalFocusView;
426 }
427
428 - (NSRect)bounds
429
430     return WKViewGetBounds (viewRef);
431 }
432
433 - (NSRect)frame 
434
435     return WKViewGetFrame (viewRef);
436 }
437
438 - (void)setFrame:(NSRect)frameRect
439 {
440     WKViewSetFrameOrigin (viewRef, frameRect.origin);
441     WKViewSetFrameSize (viewRef, frameRect.size);
442 }
443
444 - (void)setFrameOrigin:(NSPoint)newOrigin
445 {
446     WKViewSetFrameOrigin (viewRef, newOrigin);
447 }
448
449 - (void)setFrameSize:(NSSize)newSize
450 {
451     WKViewSetFrameSize (viewRef, newSize);
452 }
453
454 - (void)setBoundsSize:(NSSize)size
455 {
456     WKViewSetBoundsSize (viewRef, size);
457 }
458
459 - (void)setBoundsOrigin:(NSPoint)newOrigin
460 {
461     WKViewSetBoundsOrigin(viewRef, newOrigin);
462 }
463
464 - (void)_lockFocusViewInContext:(CGContextRef)context
465 {
466     ASSERT(context);
467     setGlobalFocusView(self);
468     CGContextSaveGState(context);
469
470     WAKView *superview = [self superview];
471     if (superview)
472         CGContextClipToRect(context, [superview bounds]);
473
474     CGContextConcatCTM(context, _WKViewGetTransform(viewRef));
475 }
476
477 - (void)_unlockFocusViewInContext:(CGContextRef)context
478 {
479     ASSERT(context);
480     CGContextRestoreGState(context);
481     setGlobalFocusView(nil);
482 }
483
484 static CGInterpolationQuality toCGInterpolationQuality(WebCore::InterpolationQuality quality)
485 {
486     switch (quality) {
487     case WebCore::InterpolationDefault:
488         return kCGInterpolationDefault;
489     case WebCore::InterpolationNone:
490         return kCGInterpolationNone;
491     case WebCore::InterpolationLow:
492         return kCGInterpolationLow;
493     case WebCore::InterpolationMedium:
494         return kCGInterpolationMedium;
495     case WebCore::InterpolationHigh:
496         return kCGInterpolationHigh;
497     default:
498         ASSERT_NOT_REACHED();
499         return kCGInterpolationLow;
500     }
501 }
502
503 + (void)_setInterpolationQuality:(int)quality
504 {
505     sInterpolationQuality = toCGInterpolationQuality((WebCore::InterpolationQuality)quality);
506 }
507
508 - (void)_drawRect:(NSRect)dirtyRect context:(CGContextRef)context lockFocus:(BOOL)lockFocus
509 {
510     if (_isHidden)
511         return;
512
513     ASSERT(viewRef);
514     ASSERT(context);
515     CGRect localRect = WKViewGetBounds(viewRef);
516     dirtyRect = CGRectIntersection(localRect, dirtyRect);
517     if (CGRectIsEmpty(dirtyRect))
518         return;
519
520     if (lockFocus)
521         [self _lockFocusViewInContext:context];
522
523     if (viewRef->scale != 1)
524         CGContextSetInterpolationQuality(context, sInterpolationQuality);
525
526     CGContextClipToRect(context, dirtyRect);
527     [self drawRect:dirtyRect];
528
529     if (!_drawsOwnDescendants) {
530         NSArray *subViews = [self subviews];
531         for (WAKView *subView in subViews) {
532             NSRect childDirtyRect = [self convertRect:dirtyRect toView:subView];
533             [subView _drawRect:CGRectIntegral(childDirtyRect) context:context lockFocus:YES];
534         }
535     }
536
537     if (lockFocus)
538         [self _unlockFocusViewInContext:context];
539 }
540
541 - (void)displayRect:(NSRect)rect
542 {
543     CGContextRef context = WKGetCurrentGraphicsContext();
544     if (!context) {
545         WKError ("unable to get context for view");
546         return;
547     }
548
549     [self _drawRect:rect context:context lockFocus:YES];
550 }
551
552 - (void)displayRectIgnoringOpacity:(NSRect)rect
553 {
554     [self displayRect:rect];
555 }
556
557 - (void)displayRectIgnoringOpacity:(NSRect)rect inContext:(CGContextRef)context
558 {
559     if (!context) {
560         WKError ("invalid parameter: context must not be NULL");
561         return;
562     }
563
564     CGContextRef previousContext = WKGetCurrentGraphicsContext();
565     if (context != previousContext)
566         WKSetCurrentGraphicsContext(context);
567
568     [self _drawRect:rect context:context lockFocus:NO];
569
570     if (context != previousContext)
571         WKSetCurrentGraphicsContext (previousContext);
572 }
573
574 - (NSRect)visibleRect
575 {
576     return WKViewGetVisibleRect (viewRef);
577 }
578
579 - (NSPoint)convertPoint:(NSPoint)aPoint toView:(WAKView *)aView 
580 {
581     CGPoint p = WKViewConvertPointToBase (viewRef, aPoint);
582     if (aView)
583         return WKViewConvertPointFromBase ([aView _viewRef], p);
584     return p;
585 }
586
587 - (NSPoint)convertPoint:(NSPoint)aPoint fromView:(WAKView *)aView
588 {
589     if (aView)
590         aPoint = WKViewConvertPointToBase ([aView _viewRef], aPoint);
591     return WKViewConvertPointFromBase (viewRef, aPoint);
592 }
593
594 - (NSSize)convertSize:(NSSize)size toView:(WAKView *)aView
595 {
596     return [self convertRect:NSMakeRect(0.0f, 0.0f, size.width, size.height) toView:aView].size;
597 }
598
599 - (NSRect)convertRect:(NSRect)aRect fromView:(WAKView *)aView
600 {
601     if (aView)
602         aRect = WKViewConvertRectToBase ([aView _viewRef], aRect);
603     return WKViewConvertRectFromBase (viewRef, aRect);
604 }
605
606 - (NSRect)convertRect:(NSRect)aRect toView:(WAKView *)aView
607 {
608     CGRect r = WKViewConvertRectToBase (viewRef, aRect);
609     if (aView)
610         return WKViewConvertRectFromBase ([aView _viewRef], r);
611     return r;
612 }
613
614 - (void)lockFocus
615 {
616     [self _lockFocusViewInContext:WKGetCurrentGraphicsContext()];
617 }
618
619 - (void)unlockFocus
620 {
621     [self _unlockFocusViewInContext:WKGetCurrentGraphicsContext()];
622 }
623
624 - (WAKView *)hitTest:(NSPoint)point
625 {
626     if (!CGRectContainsPoint([self frame], point))
627         return nil;
628
629     CGPoint subviewPoint = WKViewConvertPointFromSuperview(viewRef, point);
630     for (WAKView *subview in [self subviews]) {
631         if (WAKView *hitView = [subview hitTest: subviewPoint])
632             return hitView;
633     }
634
635     return self;
636 }
637
638 - (void)setHidden:(BOOL)flag 
639 {
640     ASSERT(viewRef);
641     if (_isHidden != flag) {
642         // setNeedsDisplay does nothing if the view is hidden so call it at the right time.
643         if (flag) {
644             [self setNeedsDisplay:YES];
645             _isHidden = flag;
646         } else {
647             _isHidden = flag;
648             [self setNeedsDisplay:YES];
649         }
650     }
651 }
652
653 - (BOOL)isDescendantOf:(NSView *)aView
654 {
655     if (aView == self)
656         return YES;
657     else if (![self superview])
658         return NO;
659     else
660         return [[self superview] isDescendantOf:aView];
661 }
662
663 - (BOOL)isHiddenOrHasHiddenAncestor
664 {
665     if (_isHidden)
666         return YES;
667
668     if (![self superview])
669         return NO;
670
671     return [[self superview] isHiddenOrHasHiddenAncestor];
672 }
673
674 - (BOOL)mouse:(NSPoint)aPoint inRect:(NSRect)aRect 
675
676     return aPoint.x >= aRect.origin.x
677         && aPoint.x < (aRect.origin.x + aRect.size.width)
678         && aPoint.y >= aRect.origin.y && aPoint.y < (aRect.origin.y + aRect.size.height);
679 }
680
681 - (BOOL)needsPanelToBecomeKey
682 {
683     return true;
684 }
685
686 - (void)setNextKeyView:(NSView *)aView
687 {
688     UNUSED_PARAM(aView);
689 }
690
691 - (WAKView *)previousValidKeyView { return nil; }
692 - (WAKView *)nextKeyView { return nil; }
693 - (WAKView *)nextValidKeyView { return nil; }
694 - (WAKView *)previousKeyView { return nil; }
695
696 - (void)invalidateGState { }
697 - (void)releaseGState { }
698 - (BOOL)inLiveResize { return NO; }
699
700 - (void)setAutoresizingMask:(unsigned int)mask
701 {
702     WKViewSetAutoresizingMask(viewRef, mask);
703 }
704
705 - (unsigned int)autoresizingMask
706 {
707     return WKViewGetAutoresizingMask(viewRef);
708 }
709
710 - (void)scrollPoint:(NSPoint)point 
711 {
712     if (WAKScrollView *scrollView = enclosingScrollView(self)) {
713         CGPoint scrollViewPoint = [self convertPoint:point toView:scrollView];
714         [scrollView scrollPoint:scrollViewPoint];
715     }
716 }
717
718 - (BOOL)scrollRectToVisible:(NSRect)rect
719 {
720     // Scroll if the rect is not already visible.
721     CGRect visibleRect = [self visibleRect];
722     if (!CGRectContainsRect(visibleRect, rect) && !CGRectContainsRect(rect, visibleRect))
723         [self scrollPoint:rect.origin];
724
725     // Return value ignored by WebCore.
726
727     return NO;
728 }
729
730 // Overridden by subclasses (only WebHTMLView for now).
731 - (void)setNeedsLayout:(BOOL)flag
732 {
733     UNUSED_PARAM(flag);
734 }
735
736 - (void)layout { }
737 - (void)layoutIfNeeded { }
738
739 - (void)setScale:(float)scale
740 {
741     WKViewSetScale(viewRef, scale);
742 }
743
744 - (float)scale
745 {
746     return WKViewGetScale(viewRef);
747 }
748
749 - (void)_setDrawsOwnDescendants:(BOOL)draw
750 {
751     _drawsOwnDescendants = draw;
752 }
753
754 - (NSString *)description
755 {
756     NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: WAK: %p (WK: %p); ", [self class], self, viewRef];
757
758     float scale = [self scale];
759     if (scale != 1)
760         [description appendFormat:@"scale = %g ", scale];
761
762     CGPoint origin = WKViewGetOrigin(viewRef);
763     if (origin.x || origin.y)
764         [description appendFormat:@"origin = (%g %g) ", origin.x, origin.y];
765
766     CGRect bounds = [self bounds];
767     CGRect frame = [self frame];
768
769     if (frame.origin.x != bounds.origin.x || frame.origin.y != bounds.origin.y || frame.size.width != bounds.size.width || frame.size.height != bounds.size.height)
770         [description appendFormat:@"bounds = (%g %g; %g %g) ", bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height];
771
772     [description appendFormat:@"frame = (%g %g; %g %g)>", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height];
773
774     return description;
775 }
776
777 - (void)_appendDescriptionToString:(NSMutableString *)info atLevel:(int)level
778 {
779     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
780
781     if ([info length] != 0)
782         [info appendString:@"\n"];
783
784     for (int i = 1; i <= level; i++)
785         [info appendString:@"   | "];
786
787     [info appendString:[self description]];
788
789     for (WAKView *subview in [self subviews])
790         [subview _appendDescriptionToString:info atLevel:level + 1];
791
792     [pool release];
793 }
794
795 @end
796
797 #endif // PLATFORM(IOS)