c151414d102050e07d11408dad1701a78e142e88
[WebKit-https.git] / Source / WebCore / rendering / RenderScrollbar.cpp
1 /*
2  * Copyright (C) 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. ``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 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 #include "config.h"
27 #include "RenderScrollbar.h"
28
29 #include "Frame.h"
30 #include "FrameView.h"
31 #include "RenderScrollbarPart.h"
32 #include "RenderScrollbarTheme.h"
33 #include "RenderWidget.h"
34 #include "StyleInheritedData.h"
35 #include "StyleResolver.h"
36
37 namespace WebCore {
38
39 PassRefPtr<Scrollbar> RenderScrollbar::createCustomScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Node* ownerNode, Frame* owningFrame)
40 {
41     return adoptRef(new RenderScrollbar(scrollableArea, orientation, ownerNode, owningFrame));
42 }
43
44 RenderScrollbar::RenderScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Node* ownerNode, Frame* owningFrame)
45     : Scrollbar(scrollableArea, orientation, RegularScrollbar, RenderScrollbarTheme::renderScrollbarTheme())
46     , m_owner(ownerNode)
47     , m_owningFrame(owningFrame)
48 {
49     ASSERT(ownerNode || owningFrame);
50
51     // FIXME: We need to do this because RenderScrollbar::styleChanged is called as soon as the scrollbar is created.
52     
53     // Update the scrollbar size.
54     int width = 0;
55     int height = 0;
56     updateScrollbarPart(ScrollbarBGPart);
57     if (RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart)) {
58         part->layout();
59         width = part->width();
60         height = part->height();
61     } else if (this->orientation() == HorizontalScrollbar)
62         width = this->width();
63     else
64         height = this->height();
65
66     setFrameRect(IntRect(0, 0, width, height));
67 }
68
69 RenderScrollbar::~RenderScrollbar()
70 {
71     if (!m_parts.isEmpty()) {
72         // When a scrollbar is detached from its parent (causing all parts removal) and 
73         // ready to be destroyed, its destruction can be delayed because of RefPtr
74         // maintained in other classes such as EventHandler (m_lastScrollbarUnderMouse).
75         // Meanwhile, we can have a call to updateScrollbarPart which recreates the 
76         // scrollbar part. So, we need to destroy these parts since we don't want them
77         // to call on a destroyed scrollbar. See webkit bug 68009.
78         updateScrollbarParts(true);
79     }
80 }
81
82 RenderBox* RenderScrollbar::owningRenderer() const
83 {
84     if (m_owningFrame) {
85         RenderWidget* currentRenderer = m_owningFrame->ownerRenderer();
86         return currentRenderer;
87     }
88     return m_owner && m_owner->renderer() ? m_owner->renderer()->enclosingBox() : 0;
89 }
90
91 void RenderScrollbar::setParent(ScrollView* parent)
92 {
93     Scrollbar::setParent(parent);
94     if (!parent) {
95         // Destroy all of the scrollbar's RenderBoxes.
96         updateScrollbarParts(true);
97     }
98 }
99
100 void RenderScrollbar::setEnabled(bool e)
101 {
102     bool wasEnabled = enabled();
103     Scrollbar::setEnabled(e);
104     if (wasEnabled != e)
105         updateScrollbarParts();
106 }
107
108 void RenderScrollbar::styleChanged()
109 {
110     updateScrollbarParts();
111 }
112
113 void RenderScrollbar::paint(GraphicsContext* context, const IntRect& damageRect)
114 {
115     if (context->updatingControlTints()) {
116         updateScrollbarParts();
117         return;
118     }
119     Scrollbar::paint(context, damageRect);
120 }
121
122 void RenderScrollbar::setHoveredPart(ScrollbarPart part)
123 {
124     if (part == m_hoveredPart)
125         return;
126
127     ScrollbarPart oldPart = m_hoveredPart;
128     m_hoveredPart = part;
129
130     updateScrollbarPart(oldPart);
131     updateScrollbarPart(m_hoveredPart);
132
133     updateScrollbarPart(ScrollbarBGPart);
134     updateScrollbarPart(TrackBGPart);
135 }
136
137 void RenderScrollbar::setPressedPart(ScrollbarPart part)
138 {
139     ScrollbarPart oldPart = m_pressedPart;
140     Scrollbar::setPressedPart(part);
141     
142     updateScrollbarPart(oldPart);
143     updateScrollbarPart(part);
144     
145     updateScrollbarPart(ScrollbarBGPart);
146     updateScrollbarPart(TrackBGPart);
147 }
148
149 PassRefPtr<RenderStyle> RenderScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId)
150 {
151     if (!owningRenderer())
152         return 0;
153
154     RefPtr<RenderStyle> result = owningRenderer()->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId, this, partType), owningRenderer()->style());
155     // Scrollbars for root frames should always have background color 
156     // unless explicitly specified as transparent. So we force it.
157     // This is because WebKit assumes scrollbar to be always painted and missing background
158     // causes visual artifact like non-repainted dirty region.
159     if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground())
160         result->setBackgroundColor(Color::white);
161
162     return result;
163 }
164
165 void RenderScrollbar::updateScrollbarParts(bool destroy)
166 {
167     updateScrollbarPart(ScrollbarBGPart, destroy);
168     updateScrollbarPart(BackButtonStartPart, destroy);
169     updateScrollbarPart(ForwardButtonStartPart, destroy);
170     updateScrollbarPart(BackTrackPart, destroy);
171     updateScrollbarPart(ThumbPart, destroy);
172     updateScrollbarPart(ForwardTrackPart, destroy);
173     updateScrollbarPart(BackButtonEndPart, destroy);
174     updateScrollbarPart(ForwardButtonEndPart, destroy);
175     updateScrollbarPart(TrackBGPart, destroy);
176     
177     if (destroy)
178         return;
179
180     // See if the scrollbar's thickness changed.  If so, we need to mark our owning object as needing a layout.
181     bool isHorizontal = orientation() == HorizontalScrollbar;    
182     int oldThickness = isHorizontal ? height() : width();
183     int newThickness = 0;
184     RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart);
185     if (part) {
186         part->layout();
187         newThickness = isHorizontal ? part->height() : part->width();
188     }
189     
190     if (newThickness != oldThickness) {
191         setFrameRect(IntRect(location(), IntSize(isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height())));
192         if (RenderBox* box = owningRenderer())
193             box->setChildNeedsLayout(true);
194     }
195 }
196
197 static PseudoId pseudoForScrollbarPart(ScrollbarPart part)
198 {
199     switch (part) {
200         case BackButtonStartPart:
201         case ForwardButtonStartPart:
202         case BackButtonEndPart:
203         case ForwardButtonEndPart:
204             return SCROLLBAR_BUTTON;
205         case BackTrackPart:
206         case ForwardTrackPart:
207             return SCROLLBAR_TRACK_PIECE;
208         case ThumbPart:
209             return SCROLLBAR_THUMB;
210         case TrackBGPart:
211             return SCROLLBAR_TRACK;
212         case ScrollbarBGPart:
213             return SCROLLBAR;
214         case NoPart:
215         case AllParts:
216             break;
217     }
218     ASSERT_NOT_REACHED();
219     return SCROLLBAR;
220 }
221
222 void RenderScrollbar::updateScrollbarPart(ScrollbarPart partType, bool destroy)
223 {
224     if (partType == NoPart)
225         return;
226
227     RefPtr<RenderStyle> partStyle = !destroy ? getScrollbarPseudoStyle(partType,  pseudoForScrollbarPart(partType)) : PassRefPtr<RenderStyle>(0);
228     
229     bool needRenderer = !destroy && partStyle && partStyle->display() != NONE;
230     
231     if (needRenderer && partStyle->display() != BLOCK) {
232         // See if we are a button that should not be visible according to OS settings.
233         ScrollbarButtonsPlacement buttonsPlacement = theme()->buttonsPlacement();
234         switch (partType) {
235             case BackButtonStartPart:
236                 needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleStart ||
237                                 buttonsPlacement == ScrollbarButtonsDoubleBoth);
238                 break;
239             case ForwardButtonStartPart:
240                 needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth);
241                 break;
242             case BackButtonEndPart:
243                 needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth);
244                 break;
245             case ForwardButtonEndPart:
246                 needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleEnd ||
247                                 buttonsPlacement == ScrollbarButtonsDoubleBoth);
248                 break;
249             default:
250                 break;
251         }
252     }
253     
254     RenderScrollbarPart* partRenderer = m_parts.get(partType);
255     if (!partRenderer && needRenderer) {
256         partRenderer = RenderScrollbarPart::createAnonymous(&owningRenderer()->document(), this, partType);
257         m_parts.set(partType, partRenderer);
258     } else if (partRenderer && !needRenderer) {
259         m_parts.remove(partType);
260         partRenderer->destroy();
261         partRenderer = 0;
262     }
263     
264     if (partRenderer)
265         partRenderer->setStyle(partStyle.release());
266 }
267
268 void RenderScrollbar::paintPart(GraphicsContext* graphicsContext, ScrollbarPart partType, const IntRect& rect)
269 {
270     RenderScrollbarPart* partRenderer = m_parts.get(partType);
271     if (!partRenderer)
272         return;
273     partRenderer->paintIntoRect(graphicsContext, location(), rect);
274 }
275
276 IntRect RenderScrollbar::buttonRect(ScrollbarPart partType)
277 {
278     RenderScrollbarPart* partRenderer = m_parts.get(partType);
279     if (!partRenderer)
280         return IntRect();
281         
282     partRenderer->layout();
283     
284     bool isHorizontal = orientation() == HorizontalScrollbar;
285     if (partType == BackButtonStartPart)
286         return IntRect(location(), IntSize(isHorizontal ? partRenderer->pixelSnappedWidth() : width(), isHorizontal ? height() : partRenderer->pixelSnappedHeight()));
287     if (partType == ForwardButtonEndPart)
288         return IntRect(isHorizontal ? x() + width() - partRenderer->pixelSnappedWidth() : x(),
289                        isHorizontal ? y() : y() + height() - partRenderer->pixelSnappedHeight(),
290                        isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
291                        isHorizontal ? height() : partRenderer->pixelSnappedHeight());
292     
293     if (partType == ForwardButtonStartPart) {
294         IntRect previousButton = buttonRect(BackButtonStartPart);
295         return IntRect(isHorizontal ? x() + previousButton.width() : x(),
296                        isHorizontal ? y() : y() + previousButton.height(),
297                        isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
298                        isHorizontal ? height() : partRenderer->pixelSnappedHeight());
299     }
300     
301     IntRect followingButton = buttonRect(ForwardButtonEndPart);
302     return IntRect(isHorizontal ? x() + width() - followingButton.width() - partRenderer->pixelSnappedWidth() : x(),
303                    isHorizontal ? y() : y() + height() - followingButton.height() - partRenderer->pixelSnappedHeight(),
304                    isHorizontal ? partRenderer->pixelSnappedWidth() : width(),
305                    isHorizontal ? height() : partRenderer->pixelSnappedHeight());
306 }
307
308 IntRect RenderScrollbar::trackRect(int startLength, int endLength)
309 {
310     RenderScrollbarPart* part = m_parts.get(TrackBGPart);
311     if (part)
312         part->layout();
313
314     if (orientation() == HorizontalScrollbar) {
315         int marginLeft = part ? static_cast<int>(part->marginLeft()) : 0;
316         int marginRight = part ? static_cast<int>(part->marginRight()) : 0;
317         startLength += marginLeft;
318         endLength += marginRight;
319         int totalLength = startLength + endLength;
320         return IntRect(x() + startLength, y(), width() - totalLength, height());
321     }
322     
323     int marginTop = part ? static_cast<int>(part->marginTop()) : 0;
324     int marginBottom = part ? static_cast<int>(part->marginBottom()) : 0;
325     startLength += marginTop;
326     endLength += marginBottom;
327     int totalLength = startLength + endLength;
328
329     return IntRect(x(), y() + startLength, width(), height() - totalLength);
330 }
331
332 IntRect RenderScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect)
333 {
334     RenderScrollbarPart* partRenderer = m_parts.get(partType);
335     if (!partRenderer)
336         return oldRect;
337     
338     partRenderer->layout();
339     
340     IntRect rect = oldRect;
341     if (orientation() == HorizontalScrollbar) {
342         rect.setX(rect.x() + partRenderer->marginLeft());
343         rect.setWidth(rect.width() - partRenderer->marginWidth());
344     } else {
345         rect.setY(rect.y() + partRenderer->marginTop());
346         rect.setHeight(rect.height() - partRenderer->marginHeight());
347     }
348     return rect;
349 }
350
351 int RenderScrollbar::minimumThumbLength()
352 {
353     RenderScrollbarPart* partRenderer = m_parts.get(ThumbPart);
354     if (!partRenderer)
355         return 0;    
356     partRenderer->layout();
357     return orientation() == HorizontalScrollbar ? partRenderer->width() : partRenderer->height();
358 }
359
360 float RenderScrollbar::opacity()
361 {
362     RenderScrollbarPart* partRenderer = m_parts.get(ScrollbarBGPart);
363     if (!partRenderer)
364         return 1;
365
366     return partRenderer->style()->opacity();
367 }
368
369 }