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#include "config.h"
27#import "WKViewPrivate.h"
28
29#if PLATFORM(IOS)
30
31#import "WAKViewPrivate.h"
32#import "WAKWindow.h"
33#import "WKUtilities.h"
34#import <wtf/Assertions.h>
35
36void _WKViewSetSuperview (WKViewRef view, WKViewRef superview)
37{
38    // Not retained.
39    view->superview = superview;
40}
41
42void _WKViewWillRemoveSubview(WKViewRef view, WKViewRef subview)
43{
44    if (view->context && view->context->willRemoveSubviewCallback)
45        view->context->willRemoveSubviewCallback(view, subview);
46}
47
48void _WKViewSetWindow (WKViewRef view, WAKWindow *window)
49{
50    if (view->window == window)
51        return;
52
53    [window retain];
54    [view->window release];
55
56    view->window = window;
57
58    // Set the window on all subviews.
59    WKViewRef firstChild = WKViewFirstChild(view);
60    if (firstChild) {
61        _WKViewSetWindow (firstChild, window);
62        WKViewRef nextSibling = WKViewNextSibling(firstChild);
63        while (nextSibling) {
64            _WKViewSetWindow (nextSibling, window);
65            nextSibling = WKViewNextSibling(nextSibling);
66        }
67    }
68
69    if (view->context && view->context->notificationCallback)
70        view->context->notificationCallback (view, WKViewNotificationViewDidMoveToWindow, view->context->notificationUserInfo);
71}
72
73static void _WKViewClearSuperview(const void *value, void *context)
74{
75    UNUSED_PARAM(context);
76    _WKViewSetSuperview((WKViewRef)value, 0);
77}
78
79static void _WKViewDealloc (WKObjectRef v)
80{
81    WKViewRef view = (WKViewRef)v;
82
83    if (view->subviews) {
84        CFArrayApplyFunction(view->subviews, CFRangeMake(0, CFArrayGetCount(view->subviews)), _WKViewClearSuperview, NULL);
85        CFRelease (view->subviews);
86        view->subviews = 0;
87    }
88
89    [view->window release];
90    view->window = nil;
91}
92
93void WKViewInitialize (WKViewRef view, CGRect frame, WKViewContext *context)
94{
95    view->origin = frame.origin;
96    view->bounds.origin = CGPointZero;
97    view->bounds.size = frame.size;
98
99    view->context = context;
100    view->autoresizingMask = NSViewNotSizable;
101    view->scale = 1.0f;
102}
103
104WKClassInfo WKViewClassInfo = { &WKObjectClass, "WKView", _WKViewDealloc };
105
106WKViewRef WKViewCreateWithFrame (CGRect frame, WKViewContext *context)
107{
108    WKViewRef view = (WKViewRef)WKCreateObjectWithSize (sizeof(struct _WKView), &WKViewClassInfo);
109    if (!view)
110        return 0;
111
112    WKViewInitialize (view, frame, context);
113
114    return view;
115}
116
117void _WKViewSetViewContext (WKViewRef view, WKViewContext *context)
118{
119    if (!view) {
120        WKError ("invalid parameter");
121        return;
122    }
123    view->context = context;
124}
125
126CGRect WKViewGetBounds (WKViewRef view)
127{
128    if (!view) {
129        WKError ("invalid parameter");
130        return CGRectZero;
131    }
132
133    return view->bounds;
134}
135
136CGRect WKViewGetFrame (WKViewRef view)
137{
138    if (!view) {
139        WKError ("invalid parameter");
140        return CGRectZero;
141    }
142
143    return WKViewConvertRectToSuperview(view, view->bounds);
144}
145
146static void _WKViewRecursivelyInvalidateGState(WKViewRef view)
147{
148    if (!view) {
149        WKError ("invalid parameter");
150        return;
151    }
152
153    if (view->context && view->context->invalidateGStateCallback)
154        view->context->invalidateGStateCallback(view);
155
156    WKViewRef subview = WKViewFirstChild(view);
157    while (subview) {
158        _WKViewRecursivelyInvalidateGState(subview);
159        subview = WKViewNextSibling(subview);
160    }
161}
162
163void WKViewSetFrameOrigin (WKViewRef view, CGPoint newOrigin)
164{
165    if (!view) {
166        WKError ("invalid parameter");
167        return;
168    }
169
170    ASSERT(!isinf(newOrigin.x));
171    ASSERT(!isnan(newOrigin.x));
172    ASSERT(!isinf(newOrigin.y));
173    ASSERT(!isnan(newOrigin.y));
174
175    if (!CGPointEqualToPoint(view->origin, newOrigin)) {
176        view->origin = newOrigin;
177        _WKViewRecursivelyInvalidateGState(view);
178    }
179}
180
181#define XSIZING_BITS(mask)      ((mask) & 7)
182#define YSIZING_BITS(mask)      (((mask) >> 3) & 7)
183
184static void _WKViewAutoresizeCoord(bool bByHeight, unsigned int sizingMethod, const CGRect *origSuperFrame, const CGRect *newSuperFrame, CGRect *newFrame)
185{
186    CGFloat origSuperFrameWidthOrHeight;
187    CGFloat newSuperFrameWidthOrHeight;
188    CGFloat *origFrameXorY;
189    CGFloat *origFrameWidthOrHeight;
190    CGFloat prop;
191    CGFloat tmp;
192    CGFloat xOrY;
193    CGFloat widthOrHeight;
194    CGFloat origMarginsTotal;
195    CGFloat origWidthMinusMinMargin;
196
197    if (bByHeight) {
198        sizingMethod = YSIZING_BITS(sizingMethod);
199        origSuperFrameWidthOrHeight = origSuperFrame->size.height;
200        newSuperFrameWidthOrHeight = newSuperFrame->size.height;
201        origFrameXorY = &newFrame->origin.y;
202        origFrameWidthOrHeight = &newFrame->size.height;
203    } else {
204        sizingMethod = XSIZING_BITS(sizingMethod);
205        origSuperFrameWidthOrHeight = origSuperFrame->size.width;
206        newSuperFrameWidthOrHeight = newSuperFrame->size.width;
207        origFrameXorY = &newFrame->origin.x;
208        origFrameWidthOrHeight = &newFrame->size.width;
209    }
210
211    xOrY = *origFrameXorY;
212    widthOrHeight = *origFrameWidthOrHeight;
213
214    switch (sizingMethod) {
215        case NSViewNotSizable:
216        case NSViewMaxXMargin:
217            break;
218        case NSViewWidthSizable:
219            widthOrHeight = newSuperFrameWidthOrHeight - (origSuperFrameWidthOrHeight - widthOrHeight);
220            if (widthOrHeight < 0.0f)
221                widthOrHeight = 0.0f;
222                break;
223        case NSViewWidthSizable | NSViewMaxXMargin:
224            origWidthMinusMinMargin = origSuperFrameWidthOrHeight - xOrY;
225            if (widthOrHeight) {
226                if (origWidthMinusMinMargin != 0.0f) {
227                    prop = widthOrHeight / origWidthMinusMinMargin;
228                } else {
229                    prop = 0.0f;
230                }
231            } else {
232                prop = 1.0f;
233            }
234                widthOrHeight = ((newSuperFrameWidthOrHeight - xOrY)) * prop;
235            if (widthOrHeight < 0.0f)
236                widthOrHeight = 0.0f;
237                break;
238        case NSViewMinXMargin:
239            xOrY = newSuperFrameWidthOrHeight - (origSuperFrameWidthOrHeight - xOrY);
240            if (xOrY < 0.0f)
241                xOrY = 0.0f;
242            break;
243        case NSViewMinXMargin | NSViewMaxXMargin:
244            origMarginsTotal = origSuperFrameWidthOrHeight - widthOrHeight;
245            if (xOrY && origMarginsTotal != 0.0f) {
246                prop = xOrY / origMarginsTotal;
247            }
248                // Do the 50/50 split even if XorY = 0 and the WidthOrHeight is only
249                // one pixel shorter...
250                else if (origMarginsTotal == 0.0f
251                         || (abs(origMarginsTotal) == 1)) {
252                    prop = 0.5f;  // Then split it 50:50.
253                }
254                else {
255                    prop = 1.0f;
256                }
257                xOrY = ((newSuperFrameWidthOrHeight - widthOrHeight)) * prop;
258            if (xOrY < 0.0f)
259                xOrY = 0.0f;
260                break;
261        case NSViewMinXMargin | NSViewWidthSizable:
262            tmp = xOrY + widthOrHeight;
263            if (tmp)
264                prop = xOrY / tmp;
265            else
266                prop = 0.5f;
267            xOrY = ((newSuperFrameWidthOrHeight - (origSuperFrameWidthOrHeight - tmp))) * prop;
268            widthOrHeight  = newSuperFrameWidthOrHeight - (xOrY + (origSuperFrameWidthOrHeight - tmp));
269            if (xOrY < 0.0f)
270                xOrY = 0.0f;
271                if (widthOrHeight < 0.0f)
272                    widthOrHeight = 0.0f;
273                    break;
274        case NSViewMinXMargin | NSViewWidthSizable | NSViewMaxXMargin:
275            if (origSuperFrameWidthOrHeight)
276                prop = xOrY / origSuperFrameWidthOrHeight;
277            else
278                prop = 1.0f / 3.0f;
279            xOrY = (newSuperFrameWidthOrHeight * prop);
280            if (origSuperFrameWidthOrHeight)
281                prop = widthOrHeight / origSuperFrameWidthOrHeight;
282            else
283                prop = 1.0f / 3.0f;
284            widthOrHeight = (newSuperFrameWidthOrHeight * prop);
285            break;
286    }
287
288    *origFrameXorY = floorf(xOrY);
289    *origFrameWidthOrHeight = floorf(widthOrHeight);
290}
291
292void _WKViewAutoresize(WKViewRef view, const CGRect *oldSuperFrame, const CGRect *newSuperFrame)
293{
294    if (view->autoresizingMask != NSViewNotSizable) {
295        CGRect newFrame = WKViewGetFrame(view);
296        _WKViewAutoresizeCoord(false, view->autoresizingMask, oldSuperFrame, newSuperFrame, &newFrame);
297        _WKViewAutoresizeCoord(true, view->autoresizingMask, oldSuperFrame, newSuperFrame, &newFrame);
298        WKViewSetFrameOrigin(view, newFrame.origin);
299        WKViewSetFrameSize(view, newFrame.size);
300    }
301}
302
303static void _WKViewAutoresizeChildren(WKViewRef view, const CGRect *oldSuperFrame, const CGRect *newSuperFrame)
304{
305    WKViewRef child;
306    for (child = WKViewFirstChild(view); child != NULL; child = WKViewNextSibling(child)) {
307        _WKViewAutoresize(child, oldSuperFrame, newSuperFrame);
308    }
309}
310
311void WKViewSetFrameSize (WKViewRef view, CGSize newSize)
312{
313    if (!view) {
314        WKError ("invalid parameter");
315        return;
316    }
317
318    ASSERT(!isinf(newSize.width));
319    ASSERT(!isnan(newSize.width));
320    ASSERT(!isinf(newSize.height));
321    ASSERT(!isnan(newSize.height));
322
323    CGRect frame;
324    frame.origin = CGPointZero;
325    frame.size = newSize;
326    CGSize boundsSize = WKViewConvertRectFromSuperview(view, frame).size;
327    WKViewSetBoundsSize(view, boundsSize);
328}
329
330void WKViewSetBoundsSize (WKViewRef view, CGSize newSize)
331{
332    if (CGSizeEqualToSize(view->bounds.size, newSize))
333        return;
334
335    CGRect oldFrame = WKViewGetFrame(view);
336    view->bounds.size = newSize;
337    CGRect newFrame = WKViewGetFrame(view);
338
339    if (view->context && view->context->notificationCallback)
340        view->context->notificationCallback (view, WKViewNotificationViewFrameSizeChanged, view->context->notificationUserInfo);
341
342    _WKViewAutoresizeChildren(view, &oldFrame, &newFrame);
343    _WKViewRecursivelyInvalidateGState(view);
344}
345
346void WKViewSetBoundsOrigin(WKViewRef view, CGPoint newOrigin)
347{
348    if (CGPointEqualToPoint(view->bounds.origin, newOrigin))
349        return;
350
351    view->bounds.origin = newOrigin;
352
353    _WKViewRecursivelyInvalidateGState(view);
354}
355
356void WKViewSetScale(WKViewRef view, float scale)
357{
358    ASSERT(!isinf(scale));
359    ASSERT(!isnan(scale));
360
361    if (view->scale == scale)
362        return;
363
364    view->scale = scale;
365
366    if (view->context && view->context->notificationCallback)
367        view->context->notificationCallback (view, WKViewNotificationViewFrameSizeChanged, view->context->notificationUserInfo);
368}
369
370float WKViewGetScale(WKViewRef view)
371{
372    return view->scale;
373}
374
375WAKWindow *WKViewGetWindow (WKViewRef view)
376{
377    if (!view) {
378        WKError ("invalid parameter");
379        return 0;
380    }
381
382    return view->window;
383}
384
385CFArrayRef WKViewGetSubviews (WKViewRef view)
386{
387    if (!view) {
388        WKError ("invalid parameter");
389        return 0;
390    }
391
392    return view->subviews;
393}
394
395void WKViewAddSubview (WKViewRef view, WKViewRef subview)
396{
397    if (!view || !subview) {
398        WKError ("invalid parameter");
399        return;
400    }
401
402    if (!view->subviews) {
403        view->subviews = CFArrayCreateMutable(NULL, 0, &WKCollectionArrayCallBacks);
404    }
405    CFArrayAppendValue (view->subviews, subview);
406    _WKViewSetSuperview (subview, view);
407
408    // Set the window on subview and all it's children.
409    _WKViewSetWindow (subview, view->window);
410}
411
412void WKViewRemoveFromSuperview (WKViewRef view)
413{
414    if (!view) {
415        WKError ("invalid parameter");
416        return;
417    }
418
419    _WKViewSetWindow (view, 0);
420
421    if (!view->superview) {
422        return;
423    }
424
425    CFMutableArrayRef svs = view->superview->subviews;
426    if (!svs) {
427        WKError ("superview has no subviews");
428        return;
429    }
430
431    CFIndex index = WKArrayIndexOfValue (svs, view);
432    if (index < 0) {
433        WKError ("view not in superview subviews");
434        return;
435    }
436
437    _WKViewWillRemoveSubview(view->superview, view);
438
439    CFArrayRemoveValueAtIndex (svs, index);
440
441    _WKViewSetSuperview (view, 0);
442}
443
444WKViewRef WKViewFirstChild (WKViewRef view)
445{
446    if (!view) {
447        WKError ("invalid parameter");
448        return 0;
449    }
450
451    CFArrayRef sv = view->subviews;
452
453    if (!sv)
454        return 0;
455
456    CFIndex count = CFArrayGetCount (sv);
457    if (!count)
458        return 0;
459
460    return (const WKViewRef)CFArrayGetValueAtIndex (sv, 0);
461}
462
463WKViewRef WKViewNextSibling (WKViewRef view)
464{
465    if (!view) {
466        WKError ("invalid parameter");
467        return 0;
468    }
469
470    if (!view->superview)
471        return 0;
472
473    CFArrayRef svs = view->superview->subviews;
474    if (!svs)
475        return 0;
476
477    CFIndex thisIndex = WKArrayIndexOfValue (svs, view);
478    if (thisIndex < 0) {
479        WKError ("internal error, view is not present in superview subviews");
480        return 0;
481    }
482
483    CFIndex count = CFArrayGetCount (svs);
484    if (thisIndex+1 >= count)
485        return 0;
486
487    return (const WKViewRef)CFArrayGetValueAtIndex (svs, thisIndex+1);
488}
489
490// To remove, see: <rdar://problem/10360425> Remove WKViewTraverseNext from Skankphone
491WKViewRef WKViewTraverseNext(WKViewRef view)
492{
493    if (!view) {
494        WKError ("invalid parameter");
495        return 0;
496    }
497
498    WKViewRef firstChild = WKViewFirstChild(view);
499    if (firstChild)
500        return firstChild;
501
502    WKViewRef nextSibling = WKViewNextSibling(view);
503    if (nextSibling)
504        return nextSibling;
505
506    while (view && !WKViewNextSibling(view)) {
507        WAKView *wakView = WAKViewForWKViewRef(view);
508        WAKView *superView = [wakView superview];
509        view = [superView _viewRef];
510    }
511
512    if (view)
513        return WKViewNextSibling(view);
514
515    return 0;
516}
517
518CGAffineTransform _WKViewGetTransform(WKViewRef view)
519{
520    CGAffineTransform transform;
521    transform = CGAffineTransformMakeTranslation(view->origin.x, view->origin.y);
522    transform = CGAffineTransformScale(transform, view->scale, view->scale);
523    transform = CGAffineTransformTranslate(transform, -view->bounds.origin.x, -view->bounds.origin.y);
524    return transform;
525}
526
527CGRect WKViewGetVisibleRect(WKViewRef viewRef)
528{
529    if (!viewRef) {
530        WKError ("invalid parameter");
531        return CGRectZero;
532    }
533
534    WKViewRef view = viewRef;
535    CGRect rect = WKViewGetBounds(view);
536    WKViewRef superview = view->superview;
537
538    while (superview != NULL) {
539        rect = WKViewConvertRectToSuperview(view, rect);
540        rect = CGRectIntersection(WKViewGetBounds(superview), rect);
541        view = superview;
542        superview = superview->superview;
543    }
544
545    if (view != viewRef) {
546        rect = WKViewConvertRectToBase(view, rect);
547        rect = WKViewConvertRectFromBase(viewRef, rect);
548    }
549
550    return rect;
551}
552
553CGRect WKViewConvertRectToSuperview(WKViewRef view, CGRect r)
554{
555    if (!view) {
556        WKError ("invalid parameter");
557        return CGRectZero;
558    }
559
560    return CGRectApplyAffineTransform(r, _WKViewGetTransform(view));
561}
562
563CGRect WKViewConvertRectToBase(WKViewRef view, CGRect r)
564{
565    if (!view) {
566        WKError ("invalid parameter");
567        return CGRectZero;
568    }
569
570    CGRect aRect = r;
571
572    while (view) {
573        aRect = WKViewConvertRectToSuperview (view, aRect);
574        view = view->superview;
575    }
576
577    return aRect;
578}
579
580CGPoint WKViewConvertPointToSuperview(WKViewRef view, CGPoint p)
581{
582    if (!view) {
583        WKError ("invalid parameter");
584        return CGPointZero;
585    }
586
587    return CGPointApplyAffineTransform(p, _WKViewGetTransform(view));
588}
589
590CGPoint WKViewConvertPointFromSuperview(WKViewRef view, CGPoint p)
591{
592    if (!view) {
593        WKError ("invalid parameter");
594        return CGPointZero;
595    }
596
597    CGAffineTransform transform = CGAffineTransformInvert(_WKViewGetTransform(view));
598    return CGPointApplyAffineTransform(p, transform);
599}
600
601CGPoint WKViewConvertPointToBase(WKViewRef view, CGPoint p)
602{
603    if (!view) {
604        WKError ("invalid parameter");
605        return CGPointZero;
606    }
607
608    CGPoint aPoint = p;
609
610    while (view) {
611        aPoint = WKViewConvertPointToSuperview (view, aPoint);
612        view = view->superview;
613    }
614
615    return aPoint;
616}
617
618#define VIEW_ARRAY_SIZE 128
619
620static void _WKViewGetAncestorViewsIncludingView (WKViewRef view, WKViewRef *views, unsigned maxViews, unsigned *viewCount)
621{
622    unsigned count = 0;
623
624    views[count++] = view;
625    WKViewRef superview = view->superview;
626    while (superview) {
627        views[count++] = superview;
628        if (count >= maxViews) {
629            WKError ("Exceeded maxViews, use malloc/realloc");
630            *viewCount = 0;
631            return;
632        }
633        superview = superview->superview;
634    }
635    *viewCount = count;
636}
637
638CGPoint WKViewConvertPointFromBase(WKViewRef view, CGPoint p)
639{
640    if (!view) {
641        WKError ("invalid parameter");
642        return CGPointZero;
643    }
644
645    WKViewRef views[VIEW_ARRAY_SIZE];
646    unsigned viewCount = 0;
647
648    _WKViewGetAncestorViewsIncludingView (view, views, VIEW_ARRAY_SIZE, &viewCount);
649    if (viewCount == 0)
650        return CGPointZero;
651
652    CGPoint aPoint = p;
653    int i;
654    for (i = viewCount-1; i >= 0; i--) {
655        aPoint = WKViewConvertPointFromSuperview (views[i], aPoint);
656    }
657
658    return aPoint;
659}
660
661CGRect WKViewConvertRectFromSuperview(WKViewRef view, CGRect r)
662{
663    if (!view) {
664        WKError ("invalid parameter");
665        return CGRectZero;
666    }
667
668    CGAffineTransform transform = CGAffineTransformInvert(_WKViewGetTransform(view));
669    return CGRectApplyAffineTransform(r, transform);
670}
671
672CGRect WKViewConvertRectFromBase(WKViewRef view, CGRect r)
673{
674    if (!view) {
675        WKError ("invalid parameter");
676        return CGRectZero;
677    }
678
679    WKViewRef views[VIEW_ARRAY_SIZE];
680    unsigned viewCount = 0;
681
682    _WKViewGetAncestorViewsIncludingView (view, views, VIEW_ARRAY_SIZE, &viewCount);
683    if (viewCount == 0)
684        return CGRectZero;
685
686    CGRect aRect = r;
687    int i;
688    for (i = viewCount-1; i >= 0; i--) {
689        aRect = WKViewConvertRectFromSuperview (views[i], aRect);
690    }
691
692    return aRect;
693}
694
695bool WKViewAcceptsFirstResponder (WKViewRef view)
696{
697    bool result = TRUE;
698    if (view && view->context && view->context->responderCallback)
699        result = view->context->responderCallback(view, WKViewResponderAcceptsFirstResponder, view->context->responderUserInfo);
700    return result;
701}
702
703bool WKViewBecomeFirstResponder (WKViewRef view)
704{
705    bool result = TRUE;
706    if (view && view->context && view->context->responderCallback)
707        result = view->context->responderCallback(view, WKViewResponderBecomeFirstResponder, view->context->responderUserInfo);
708    return result;
709}
710
711bool WKViewResignFirstResponder (WKViewRef view)
712{
713    bool result = TRUE;
714    if (view && view->context && view->context->responderCallback)
715        result = view->context->responderCallback(view, WKViewResponderResignFirstResponder, view->context->responderUserInfo);
716    return result;
717}
718
719unsigned int WKViewGetAutoresizingMask(WKViewRef view)
720{
721    if (!view) {
722        WKError ("invalid parameter");
723        return 0;
724    }
725    return view->autoresizingMask;
726}
727
728void WKViewSetAutoresizingMask (WKViewRef view, unsigned int mask)
729{
730    if (!view) {
731        WKError ("invalid parameter");
732        return;
733    }
734    view->autoresizingMask = mask;
735}
736
737#endif // PLATFORM(IOS)
738