96bcde2ae6f5822ce6eedb796181862d3d43ee63
[WebKit-https.git] / Source / WebCore / platform / mac / WidgetMac.mm
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
63 namespace WebCore {
64
65 class WidgetPrivate {
66 public:
67     WidgetPrivate()
68         : previousVisibleRect(NSZeroRect)
69     {
70     }
71
72     bool mustStayInWindow;
73     bool removeFromSuperviewSoon;
74     NSRect previousVisibleRect;
75 };
76
77 static 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
93 Widget::Widget(NSView *view)
94     : m_data(new WidgetPrivate)
95 {
96     init(view);
97     m_data->mustStayInWindow = false;
98     m_data->removeFromSuperviewSoon = false;
99 }
100
101 Widget::~Widget()
102 {
103     delete m_data;
104 }
105
106 // FIXME: Should move this to Chrome; bad layering that this knows about Frame.
107 void 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     NSView *view = [platformWidget() _webcore_effectiveFirstResponder];
119     if (Page* page = frame->page())
120         page->chrome()->focusNSView(view);
121     
122     END_BLOCK_OBJC_EXCEPTIONS;
123 }
124
125 void Widget::setCursor(const Cursor& cursor)
126 {
127     ScrollView* view = root();
128     if (!view)
129         return;
130     view->hostWindow()->setCursor(cursor);
131 }
132
133 void Widget::show()
134 {
135     if (isSelfVisible())
136         return;
137
138     setSelfVisible(true);
139
140     BEGIN_BLOCK_OBJC_EXCEPTIONS;
141     [getOuterView() setHidden:NO];
142     END_BLOCK_OBJC_EXCEPTIONS;
143 }
144
145 void Widget::hide()
146 {
147     if (!isSelfVisible())
148         return;
149
150     setSelfVisible(false);
151
152     BEGIN_BLOCK_OBJC_EXCEPTIONS;
153     [getOuterView() setHidden:YES];
154     END_BLOCK_OBJC_EXCEPTIONS;
155 }
156
157 IntRect Widget::frameRect() const
158 {
159     if (!platformWidget())
160         return m_frame;
161
162     BEGIN_BLOCK_OBJC_EXCEPTIONS;
163     return enclosingIntRect([getOuterView() frame]);
164     END_BLOCK_OBJC_EXCEPTIONS;
165     
166     return m_frame;
167 }
168
169 void Widget::setFrameRect(const IntRect& rect)
170 {
171     m_frame = rect;
172
173     BEGIN_BLOCK_OBJC_EXCEPTIONS;
174     NSView *outerView = getOuterView();
175     if (!outerView)
176         return;
177
178     // Take a reference to this Widget, because sending messages to outerView can invoke arbitrary
179     // code, which can deref it.
180     RefPtr<Widget> protectedThis(this);
181
182     NSRect visibleRect = [outerView visibleRect];
183     NSRect f = rect;
184     if (!NSEqualRects(f, [outerView frame])) {
185         [outerView setFrame:f];
186         [outerView setNeedsDisplay:NO];
187     } else if (!NSEqualRects(visibleRect, m_data->previousVisibleRect) && [outerView respondsToSelector:@selector(visibleRectDidChange)])
188         [outerView visibleRectDidChange];
189
190     m_data->previousVisibleRect = visibleRect;
191     END_BLOCK_OBJC_EXCEPTIONS;
192 }
193
194 void Widget::setBoundsSize(const IntSize& size)
195 {
196     NSSize nsSize = size;
197
198     BEGIN_BLOCK_OBJC_EXCEPTIONS;
199     NSView *outerView = getOuterView();
200     if (!outerView)
201         return;
202
203     // Take a reference to this Widget, because sending messages to outerView can invoke arbitrary
204     // code, which can deref it.
205     RefPtr<Widget> protectedThis(this);
206     if (!NSEqualSizes(nsSize, [outerView bounds].size)) {
207         [outerView setBoundsSize:nsSize];
208         [outerView setNeedsDisplay:NO];
209     }
210     END_BLOCK_OBJC_EXCEPTIONS;
211 }
212
213 NSView *Widget::getOuterView() const
214 {
215     NSView *view = platformWidget();
216
217     // If this widget's view is a WebCoreFrameScrollView then we
218     // resize its containing view, a WebFrameView.
219     if ([view conformsToProtocol:@protocol(WebCoreFrameScrollView)]) {
220         view = [view superview];
221         ASSERT(view);
222     }
223
224     return view;
225 }
226
227 void Widget::paint(GraphicsContext* p, const IntRect& r)
228 {
229     if (p->paintingDisabled())
230         return;
231     NSView *view = getOuterView();
232
233     // Take a reference to this Widget, because sending messages to the views can invoke arbitrary
234     // code, which can deref it.
235     RefPtr<Widget> protectedThis(this);
236
237     IntPoint transformOrigin = frameRect().location();
238     AffineTransform widgetToViewTranform = makeMapBetweenRects(IntRect(IntPoint(), frameRect().size()), [view bounds]);
239
240     NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
241     if (currentContext == [[view window] graphicsContext] || ![currentContext isDrawingToScreen]) {
242         // This is the common case of drawing into a window or printing.
243         BEGIN_BLOCK_OBJC_EXCEPTIONS;
244         
245         CGContextRef context = (CGContextRef)[currentContext graphicsPort];
246
247         CGContextSaveGState(context);
248         CGContextTranslateCTM(context, transformOrigin.x(), transformOrigin.y());
249         CGContextScaleCTM(context, narrowPrecisionToFloat(widgetToViewTranform.xScale()), narrowPrecisionToFloat(widgetToViewTranform.yScale()));
250         CGContextTranslateCTM(context, -transformOrigin.x(), -transformOrigin.y());
251
252         IntRect dirtyRect = r;
253         dirtyRect.move(-transformOrigin.x(), -transformOrigin.y());
254         if (![view isFlipped])
255             dirtyRect.setY([view bounds].size.height - dirtyRect.maxY());
256
257         [view displayRectIgnoringOpacity:dirtyRect];
258
259         CGContextRestoreGState(context);
260
261         END_BLOCK_OBJC_EXCEPTIONS;
262     } else {
263         // This is the case of drawing into a bitmap context other than a window backing store. It gets hit beneath
264         // -cacheDisplayInRect:toBitmapImageRep:, and when painting into compositing layers.
265
266         // Transparent subframes are in fact implemented with scroll views that return YES from -drawsBackground (whenever the WebView
267         // itself is in drawsBackground mode). In the normal drawing code path, the scroll views are never asked to draw the background,
268         // so this is not an issue, but in this code path they are, so the following code temporarily turns background drwaing off.
269         NSView *innerView = platformWidget();
270         NSScrollView *scrollView = 0;
271         if ([innerView conformsToProtocol:@protocol(WebCoreFrameScrollView)]) {
272             ASSERT([innerView isKindOfClass:[NSScrollView class]]);
273             NSScrollView *scrollView = static_cast<NSScrollView *>(innerView);
274             // -copiesOnScroll will return NO whenever the content view is not fully opaque.
275             if ([scrollView drawsBackground] && ![[scrollView contentView] copiesOnScroll])
276                 [scrollView setDrawsBackground:NO];
277             else
278                 scrollView = 0;
279         }
280
281         CGContextRef cgContext = p->platformContext();
282         ASSERT(cgContext == [currentContext graphicsPort]);
283         CGContextSaveGState(cgContext);
284
285         CGContextTranslateCTM(cgContext, transformOrigin.x(), transformOrigin.y());
286         CGContextScaleCTM(cgContext, narrowPrecisionToFloat(widgetToViewTranform.xScale()), narrowPrecisionToFloat(widgetToViewTranform.yScale()));
287         CGContextTranslateCTM(cgContext, -transformOrigin.x(), -transformOrigin.y());
288
289         NSRect viewFrame = [view frame];
290         NSRect viewBounds = [view bounds];
291         // Set up the translation and (flipped) orientation of the graphics context. In normal drawing, AppKit does it as it descends down
292         // the view hierarchy.
293         CGContextTranslateCTM(cgContext, viewFrame.origin.x - viewBounds.origin.x, viewFrame.origin.y + viewFrame.size.height + viewBounds.origin.y);
294         CGContextScaleCTM(cgContext, 1, -1);
295
296         IntRect dirtyRect = r;
297         dirtyRect.move(-transformOrigin.x(), -transformOrigin.y());
298         if (![view isFlipped])
299             dirtyRect.setY([view bounds].size.height - dirtyRect.maxY());
300
301         BEGIN_BLOCK_OBJC_EXCEPTIONS;
302         {
303 #ifdef BUILDING_ON_TIGER
304             AutodrainedPool pool;
305 #endif
306             NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES];
307             [view displayRectIgnoringOpacity:dirtyRect inContext:nsContext];
308         }
309         END_BLOCK_OBJC_EXCEPTIONS;
310
311         CGContextRestoreGState(cgContext);
312
313         if (scrollView)
314             [scrollView setDrawsBackground:YES];
315     }
316 }
317
318 void Widget::setIsSelected(bool isSelected)
319 {
320     NSView *view = platformWidget();
321
322     BEGIN_BLOCK_OBJC_EXCEPTIONS;
323     if ([view respondsToSelector:@selector(webPlugInSetIsSelected:)])
324         [view webPlugInSetIsSelected:isSelected];
325     else if ([view respondsToSelector:@selector(setIsSelected:)])
326         [view setIsSelected:isSelected];
327     END_BLOCK_OBJC_EXCEPTIONS;
328 }
329
330 void Widget::removeFromSuperview()
331 {
332     if (m_data->mustStayInWindow)
333         m_data->removeFromSuperviewSoon = true;
334     else {
335         m_data->removeFromSuperviewSoon = false;
336         BEGIN_BLOCK_OBJC_EXCEPTIONS;
337         safeRemoveFromSuperview(getOuterView());
338         END_BLOCK_OBJC_EXCEPTIONS;
339     }
340 }
341
342 void Widget::beforeMouseDown(NSView *unusedView, Widget* widget)
343 {
344     if (widget) {
345         ASSERT_UNUSED(unusedView, unusedView == widget->getOuterView());
346         ASSERT(!widget->m_data->mustStayInWindow);
347         widget->m_data->mustStayInWindow = true;
348     }
349 }
350
351 void Widget::afterMouseDown(NSView *view, Widget* widget)
352 {
353     if (!widget) {
354         BEGIN_BLOCK_OBJC_EXCEPTIONS;
355         safeRemoveFromSuperview(view);
356         END_BLOCK_OBJC_EXCEPTIONS;
357     } else {
358         ASSERT(widget->m_data->mustStayInWindow);
359         widget->m_data->mustStayInWindow = false;
360         if (widget->m_data->removeFromSuperviewSoon)
361             widget->removeFromSuperview();
362     }
363 }
364
365 // These are here to deal with flipped coords on Mac.
366 IntRect Widget::convertFromRootToContainingWindow(const Widget* rootWidget, const IntRect& rect)
367 {
368     if (!rootWidget->platformWidget())
369         return rect;
370
371     BEGIN_BLOCK_OBJC_EXCEPTIONS;
372     return enclosingIntRect([rootWidget->platformWidget() convertRect:rect toView:nil]);
373     END_BLOCK_OBJC_EXCEPTIONS;
374
375     return rect;
376 }
377
378 IntRect Widget::convertFromContainingWindowToRoot(const Widget* rootWidget, const IntRect& rect)
379 {
380     if (!rootWidget->platformWidget())
381         return rect;
382
383     BEGIN_BLOCK_OBJC_EXCEPTIONS;
384     return enclosingIntRect([rootWidget->platformWidget() convertRect:rect fromView:nil]);
385     END_BLOCK_OBJC_EXCEPTIONS;
386
387     return rect;
388 }
389
390 IntPoint Widget::convertFromRootToContainingWindow(const Widget* rootWidget, const IntPoint& point)
391 {
392     if (!rootWidget->platformWidget())
393         return point;
394
395     BEGIN_BLOCK_OBJC_EXCEPTIONS;
396     return IntPoint([rootWidget->platformWidget() convertPoint:point toView:nil]);
397     END_BLOCK_OBJC_EXCEPTIONS;
398     return point;
399 }
400
401 IntPoint Widget::convertFromContainingWindowToRoot(const Widget* rootWidget, const IntPoint& point)
402 {
403     if (!rootWidget->platformWidget())
404         return point;
405
406     BEGIN_BLOCK_OBJC_EXCEPTIONS;
407     return IntPoint([rootWidget->platformWidget() convertPoint:point fromView:nil]);
408     END_BLOCK_OBJC_EXCEPTIONS;
409
410     return point;
411 }
412
413 NSView *Widget::platformWidget() const
414 {
415     return m_widget.get();
416 }
417
418 void Widget::setPlatformWidget(NSView *widget)
419 {
420     if (widget == m_widget)
421         return;
422
423     m_widget = widget;
424     m_data->previousVisibleRect = NSZeroRect;
425 }
426
427 } // namespace WebCore