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