1/*
2 * Copyright (C) 2004, 2005, 2006, 2008, 2010, 2011 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 COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "Widget.h"
28
29#ifdef BUILDING_ON_TIGER
30#import "AutodrainedPool.h"
31#endif
32
33#import "BlockExceptions.h"
34#import "Chrome.h"
35#import "Cursor.h"
36#import "Document.h"
37#import "FloatConversion.h"
38#import "Font.h"
39#import "Frame.h"
40#import "GraphicsContext.h"
41#import "NotImplemented.h"
42#import "Page.h"
43#import "PlatformMouseEvent.h"
44#import "ScrollView.h"
45#import "WebCoreFrameView.h"
46#import "WebCoreView.h"
47#import <wtf/RetainPtr.h>
48
49@interface NSWindow (WebWindowDetails)
50- (BOOL)_needsToResetDragMargins;
51- (void)_setNeedsToResetDragMargins:(BOOL)needs;
52@end
53
54@interface NSView (WebSetSelectedMethods)
55- (void)setIsSelected:(BOOL)isSelected;
56- (void)webPlugInSetIsSelected:(BOOL)isSelected;
57@end
58
59@interface NSView (Widget)
60- (void)visibleRectDidChange;
61@end
62
63namespace WebCore {
64
65class WidgetPrivate {
66public:
67    WidgetPrivate()
68        : previousVisibleRect(NSZeroRect)
69    {
70    }
71
72    bool mustStayInWindow;
73    bool removeFromSuperviewSoon;
74    NSRect previousVisibleRect;
75};
76
77static void safeRemoveFromSuperview(NSView *view)
78{
79    // If the the view is the first responder, then set the window's first responder to nil so
80    // we don't leave the window pointing to a view that's no longer in it.
81    NSWindow *window = [view window];
82    NSResponder *firstResponder = [window firstResponder];
83    if ([firstResponder isKindOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:view])
84        [window makeFirstResponder:nil];
85
86    // Suppress the resetting of drag margins since we know we can't affect them.
87    BOOL resetDragMargins = [window _needsToResetDragMargins];
88    [window _setNeedsToResetDragMargins:NO];
89    [view removeFromSuperview];
90    [window _setNeedsToResetDragMargins:resetDragMargins];
91}
92
93Widget::Widget(NSView *view)
94    : m_data(new WidgetPrivate)
95{
96    init(view);
97    m_data->mustStayInWindow = false;
98    m_data->removeFromSuperviewSoon = false;
99}
100
101Widget::~Widget()
102{
103    delete m_data;
104}
105
106// FIXME: Should move this to Chrome; bad layering that this knows about Frame.
107void Widget::setFocus(bool focused)
108{
109    if (!focused)
110        return;
111
112    Frame* frame = Frame::frameForWidget(this);
113    if (!frame)
114        return;
115
116    BEGIN_BLOCK_OBJC_EXCEPTIONS;
117
118    // If there's no platformWidget, WK2 is running. The focus() method needs to be used
119    // to bring focus to the right view on the UIProcess side.
120    NSView *view = [platformWidget() _webcore_effectiveFirstResponder];
121    if (Page* page = frame->page()) {
122        if (!platformWidget())
123            page->chrome()->focus();
124        else
125            page->chrome()->focusNSView(view);
126    }
127    END_BLOCK_OBJC_EXCEPTIONS;
128}
129
130void Widget::setCursor(const Cursor& cursor)
131{
132    ScrollView* view = root();
133    if (!view)
134        return;
135    view->hostWindow()->setCursor(cursor);
136}
137
138void Widget::show()
139{
140    if (isSelfVisible())
141        return;
142
143    setSelfVisible(true);
144
145    BEGIN_BLOCK_OBJC_EXCEPTIONS;
146    [getOuterView() setHidden:NO];
147    END_BLOCK_OBJC_EXCEPTIONS;
148}
149
150void Widget::hide()
151{
152    if (!isSelfVisible())
153        return;
154
155    setSelfVisible(false);
156
157    BEGIN_BLOCK_OBJC_EXCEPTIONS;
158    [getOuterView() setHidden:YES];
159    END_BLOCK_OBJC_EXCEPTIONS;
160}
161
162IntRect Widget::frameRect() const
163{
164    if (!platformWidget())
165        return m_frame;
166
167    BEGIN_BLOCK_OBJC_EXCEPTIONS;
168    return enclosingIntRect([getOuterView() frame]);
169    END_BLOCK_OBJC_EXCEPTIONS;
170
171    return m_frame;
172}
173
174void Widget::setFrameRect(const IntRect& rect)
175{
176    m_frame = rect;
177
178    BEGIN_BLOCK_OBJC_EXCEPTIONS;
179    NSView *outerView = getOuterView();
180    if (!outerView)
181        return;
182
183    // Take a reference to this Widget, because sending messages to outerView can invoke arbitrary
184    // code, which can deref it.
185    RefPtr<Widget> protectedThis(this);
186
187    NSRect visibleRect = [outerView visibleRect];
188    NSRect f = rect;
189    if (!NSEqualRects(f, [outerView frame])) {
190        [outerView setFrame:f];
191        [outerView setNeedsDisplay:NO];
192    } else if (!NSEqualRects(visibleRect, m_data->previousVisibleRect) && [outerView respondsToSelector:@selector(visibleRectDidChange)])
193        [outerView visibleRectDidChange];
194
195    m_data->previousVisibleRect = visibleRect;
196    END_BLOCK_OBJC_EXCEPTIONS;
197}
198
199void Widget::setBoundsSize(const IntSize& size)
200{
201    NSSize nsSize = size;
202
203    BEGIN_BLOCK_OBJC_EXCEPTIONS;
204    NSView *outerView = getOuterView();
205    if (!outerView)
206        return;
207
208    // Take a reference to this Widget, because sending messages to outerView can invoke arbitrary
209    // code, which can deref it.
210    RefPtr<Widget> protectedThis(this);
211    if (!NSEqualSizes(nsSize, [outerView bounds].size)) {
212        [outerView setBoundsSize:nsSize];
213        [outerView setNeedsDisplay:NO];
214    }
215    END_BLOCK_OBJC_EXCEPTIONS;
216}
217
218NSView *Widget::getOuterView() const
219{
220    NSView *view = platformWidget();
221
222    // If this widget's view is a WebCoreFrameScrollView then we
223    // resize its containing view, a WebFrameView.
224    if ([view conformsToProtocol:@protocol(WebCoreFrameScrollView)]) {
225        view = [view superview];
226        ASSERT(view);
227    }
228
229    return view;
230}
231
232void Widget::paint(GraphicsContext* p, const IntRect& r)
233{
234    if (p->paintingDisabled())
235        return;
236    NSView *view = getOuterView();
237
238    // Take a reference to this Widget, because sending messages to the views can invoke arbitrary
239    // code, which can deref it.
240    RefPtr<Widget> protectedThis(this);
241
242    IntPoint transformOrigin = frameRect().location();
243    AffineTransform widgetToViewTranform = makeMapBetweenRects(IntRect(IntPoint(), frameRect().size()), [view bounds]);
244
245    NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
246    if (currentContext == [[view window] graphicsContext] || ![currentContext isDrawingToScreen]) {
247        // This is the common case of drawing into a window or printing.
248        BEGIN_BLOCK_OBJC_EXCEPTIONS;
249
250        CGContextRef context = (CGContextRef)[currentContext graphicsPort];
251
252        CGContextSaveGState(context);
253        CGContextTranslateCTM(context, transformOrigin.x(), transformOrigin.y());
254        CGContextScaleCTM(context, narrowPrecisionToFloat(widgetToViewTranform.xScale()), narrowPrecisionToFloat(widgetToViewTranform.yScale()));
255        CGContextTranslateCTM(context, -transformOrigin.x(), -transformOrigin.y());
256
257        IntRect dirtyRect = r;
258        dirtyRect.move(-transformOrigin.x(), -transformOrigin.y());
259        if (![view isFlipped])
260            dirtyRect.setY([view bounds].size.height - dirtyRect.maxY());
261
262        [view displayRectIgnoringOpacity:dirtyRect];
263
264        CGContextRestoreGState(context);
265
266        END_BLOCK_OBJC_EXCEPTIONS;
267    } else {
268        // This is the case of drawing into a bitmap context other than a window backing store. It gets hit beneath
269        // -cacheDisplayInRect:toBitmapImageRep:, and when painting into compositing layers.
270
271        // Transparent subframes are in fact implemented with scroll views that return YES from -drawsBackground (whenever the WebView
272        // itself is in drawsBackground mode). In the normal drawing code path, the scroll views are never asked to draw the background,
273        // so this is not an issue, but in this code path they are, so the following code temporarily turns background drwaing off.
274        NSView *innerView = platformWidget();
275        NSScrollView *scrollView = 0;
276        if ([innerView conformsToProtocol:@protocol(WebCoreFrameScrollView)]) {
277            ASSERT([innerView isKindOfClass:[NSScrollView class]]);
278            NSScrollView *scrollView = static_cast<NSScrollView *>(innerView);
279            // -copiesOnScroll will return NO whenever the content view is not fully opaque.
280            if ([scrollView drawsBackground] && ![[scrollView contentView] copiesOnScroll])
281                [scrollView setDrawsBackground:NO];
282            else
283                scrollView = 0;
284        }
285
286        CGContextRef cgContext = p->platformContext();
287        ASSERT(cgContext == [currentContext graphicsPort]);
288        CGContextSaveGState(cgContext);
289
290        CGContextTranslateCTM(cgContext, transformOrigin.x(), transformOrigin.y());
291        CGContextScaleCTM(cgContext, narrowPrecisionToFloat(widgetToViewTranform.xScale()), narrowPrecisionToFloat(widgetToViewTranform.yScale()));
292        CGContextTranslateCTM(cgContext, -transformOrigin.x(), -transformOrigin.y());
293
294        NSRect viewFrame = [view frame];
295        NSRect viewBounds = [view bounds];
296        // Set up the translation and (flipped) orientation of the graphics context. In normal drawing, AppKit does it as it descends down
297        // the view hierarchy.
298        CGContextTranslateCTM(cgContext, viewFrame.origin.x - viewBounds.origin.x, viewFrame.origin.y + viewFrame.size.height + viewBounds.origin.y);
299        CGContextScaleCTM(cgContext, 1, -1);
300
301        IntRect dirtyRect = r;
302        dirtyRect.move(-transformOrigin.x(), -transformOrigin.y());
303        if (![view isFlipped])
304            dirtyRect.setY([view bounds].size.height - dirtyRect.maxY());
305
306        BEGIN_BLOCK_OBJC_EXCEPTIONS;
307        {
308#ifdef BUILDING_ON_TIGER
309            AutodrainedPool pool;
310#endif
311            NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES];
312            [view displayRectIgnoringOpacity:dirtyRect inContext:nsContext];
313        }
314        END_BLOCK_OBJC_EXCEPTIONS;
315
316        CGContextRestoreGState(cgContext);
317
318        if (scrollView)
319            [scrollView setDrawsBackground:YES];
320    }
321}
322
323void Widget::setIsSelected(bool isSelected)
324{
325    NSView *view = platformWidget();
326
327    BEGIN_BLOCK_OBJC_EXCEPTIONS;
328    if ([view respondsToSelector:@selector(webPlugInSetIsSelected:)])
329        [view webPlugInSetIsSelected:isSelected];
330    else if ([view respondsToSelector:@selector(setIsSelected:)])
331        [view setIsSelected:isSelected];
332    END_BLOCK_OBJC_EXCEPTIONS;
333}
334
335void Widget::removeFromSuperview()
336{
337    if (m_data->mustStayInWindow)
338        m_data->removeFromSuperviewSoon = true;
339    else {
340        m_data->removeFromSuperviewSoon = false;
341        BEGIN_BLOCK_OBJC_EXCEPTIONS;
342        safeRemoveFromSuperview(getOuterView());
343        END_BLOCK_OBJC_EXCEPTIONS;
344    }
345}
346
347void Widget::beforeMouseDown(NSView *unusedView, Widget* widget)
348{
349    if (widget) {
350        ASSERT_UNUSED(unusedView, unusedView == widget->getOuterView());
351        ASSERT(!widget->m_data->mustStayInWindow);
352        widget->m_data->mustStayInWindow = true;
353    }
354}
355
356void Widget::afterMouseDown(NSView *view, Widget* widget)
357{
358    if (!widget) {
359        BEGIN_BLOCK_OBJC_EXCEPTIONS;
360        safeRemoveFromSuperview(view);
361        END_BLOCK_OBJC_EXCEPTIONS;
362    } else {
363        ASSERT(widget->m_data->mustStayInWindow);
364        widget->m_data->mustStayInWindow = false;
365        if (widget->m_data->removeFromSuperviewSoon)
366            widget->removeFromSuperview();
367    }
368}
369
370// These are here to deal with flipped coords on Mac.
371IntRect Widget::convertFromRootToContainingWindow(const Widget* rootWidget, const IntRect& rect)
372{
373    if (!rootWidget->platformWidget())
374        return rect;
375
376    BEGIN_BLOCK_OBJC_EXCEPTIONS;
377    return enclosingIntRect([rootWidget->platformWidget() convertRect:rect toView:nil]);
378    END_BLOCK_OBJC_EXCEPTIONS;
379
380    return rect;
381}
382
383IntRect Widget::convertFromContainingWindowToRoot(const Widget* rootWidget, const IntRect& rect)
384{
385    if (!rootWidget->platformWidget())
386        return rect;
387
388    BEGIN_BLOCK_OBJC_EXCEPTIONS;
389    return enclosingIntRect([rootWidget->platformWidget() convertRect:rect fromView:nil]);
390    END_BLOCK_OBJC_EXCEPTIONS;
391
392    return rect;
393}
394
395IntPoint Widget::convertFromRootToContainingWindow(const Widget* rootWidget, const IntPoint& point)
396{
397    if (!rootWidget->platformWidget())
398        return point;
399
400    BEGIN_BLOCK_OBJC_EXCEPTIONS;
401    return IntPoint([rootWidget->platformWidget() convertPoint:point toView:nil]);
402    END_BLOCK_OBJC_EXCEPTIONS;
403    return point;
404}
405
406IntPoint Widget::convertFromContainingWindowToRoot(const Widget* rootWidget, const IntPoint& point)
407{
408    if (!rootWidget->platformWidget())
409        return point;
410
411    BEGIN_BLOCK_OBJC_EXCEPTIONS;
412    return IntPoint([rootWidget->platformWidget() convertPoint:point fromView:nil]);
413    END_BLOCK_OBJC_EXCEPTIONS;
414
415    return point;
416}
417
418NSView *Widget::platformWidget() const
419{
420    return m_widget.get();
421}
422
423void Widget::setPlatformWidget(NSView *widget)
424{
425    if (widget == m_widget)
426        return;
427
428    m_widget = widget;
429    m_data->previousVisibleRect = NSZeroRect;
430}
431
432} // namespace WebCore
433