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