1/* 2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 4 * 5 * Portions are Copyright (C) 1998 Netscape Communications Corporation. 6 * 7 * Other contributors: 8 * Robert O'Callahan <roc+@cs.cmu.edu> 9 * David Baron <dbaron@fas.harvard.edu> 10 * Christian Biesinger <cbiesinger@web.de> 11 * Randall Jesup <rjesup@wgate.com> 12 * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> 13 * Josh Soref <timeless@mac.com> 14 * Boris Zbarsky <bzbarsky@mit.edu> 15 * 16 * This library is free software; you can redistribute it and/or 17 * modify it under the terms of the GNU Lesser General Public 18 * License as published by the Free Software Foundation; either 19 * version 2.1 of the License, or (at your option) any later version. 20 * 21 * This library is distributed in the hope that it will be useful, 22 * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 * Lesser General Public License for more details. 25 * 26 * You should have received a copy of the GNU Lesser General Public 27 * License along with this library; if not, write to the Free Software 28 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 29 * 30 * Alternatively, the contents of this file may be used under the terms 31 * of either the Mozilla Public License Version 1.1, found at 32 * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public 33 * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html 34 * (the "GPL"), in which case the provisions of the MPL or the GPL are 35 * applicable instead of those above. If you wish to allow use of your 36 * version of this file only under the terms of one of those two 37 * licenses (the MPL or the GPL) and not to allow others to use your 38 * version of this file under the LGPL, indicate your decision by 39 * deletingthe provisions above and replace them with the notice and 40 * other provisions required by the MPL or the GPL, as the case may be. 41 * If you do not delete the provisions above, a recipient may use your 42 * version of this file under any of the LGPL, the MPL or the GPL. 43 */ 44 45#include "config.h" 46 47#include "core/rendering/RenderMarquee.h" 48 49#include "HTMLNames.h" 50#include "core/html/HTMLMarqueeElement.h" 51#include "core/frame/FrameView.h" 52#include "core/frame/UseCounter.h" 53#include "core/rendering/RenderLayer.h" 54#include "core/rendering/RenderView.h" 55 56using namespace std; 57 58namespace WebCore { 59 60using namespace HTMLNames; 61 62RenderMarquee::RenderMarquee(HTMLMarqueeElement* element) 63 : RenderBlockFlow(element) 64 , m_currentLoop(0) 65 , m_totalLoops(0) 66 , m_timer(element, &HTMLMarqueeElement::timerFired) 67 , m_start(0) 68 , m_end(0) 69 , m_speed(0) 70 , m_reset(false) 71 , m_suspended(false) 72 , m_stopped(false) 73 , m_direction(MAUTO) 74{ 75 UseCounter::count(document(), UseCounter::HTMLMarqueeElement); 76} 77 78RenderMarquee::~RenderMarquee() 79{ 80} 81 82int RenderMarquee::marqueeSpeed() const 83{ 84 int result = style()->marqueeSpeed(); 85 if (Node* node = this->node()) 86 result = std::max(result, toHTMLMarqueeElement(node)->minimumDelay()); 87 return result; 88} 89 90EMarqueeDirection RenderMarquee::direction() const 91{ 92 // FIXME: Support the CSS3 "auto" value for determining the direction of the marquee. 93 // For now just map MAUTO to MBACKWARD 94 EMarqueeDirection result = style()->marqueeDirection(); 95 TextDirection dir = style()->direction(); 96 if (result == MAUTO) 97 result = MBACKWARD; 98 if (result == MFORWARD) 99 result = (dir == LTR) ? MRIGHT : MLEFT; 100 if (result == MBACKWARD) 101 result = (dir == LTR) ? MLEFT : MRIGHT; 102 103 // Now we have the real direction. Next we check to see if the increment is negative. 104 // If so, then we reverse the direction. 105 Length increment = style()->marqueeIncrement(); 106 if (increment.isNegative()) 107 result = static_cast<EMarqueeDirection>(-result); 108 109 return result; 110} 111 112bool RenderMarquee::isHorizontal() const 113{ 114 return direction() == MLEFT || direction() == MRIGHT; 115} 116 117int RenderMarquee::computePosition(EMarqueeDirection dir, bool stopAtContentEdge) 118{ 119 if (isHorizontal()) { 120 bool ltr = style()->isLeftToRightDirection(); 121 LayoutUnit clientWidth = this->clientWidth(); 122 LayoutUnit contentWidth = ltr ? maxPreferredLogicalWidth() : minPreferredLogicalWidth(); 123 if (ltr) 124 contentWidth += (paddingRight() - borderLeft()); 125 else { 126 contentWidth = width() - contentWidth; 127 contentWidth += (paddingLeft() - borderRight()); 128 } 129 if (dir == MRIGHT) { 130 if (stopAtContentEdge) 131 return max<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); 132 else 133 return ltr ? contentWidth : clientWidth; 134 } 135 else { 136 if (stopAtContentEdge) 137 return min<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); 138 else 139 return ltr ? -clientWidth : -contentWidth; 140 } 141 } 142 else { 143 int contentHeight = layoutOverflowRect().maxY() - borderTop() + paddingBottom(); 144 int clientHeight = this->clientHeight(); 145 if (dir == MUP) { 146 if (stopAtContentEdge) 147 return min(contentHeight - clientHeight, 0); 148 else 149 return -clientHeight; 150 } 151 else { 152 if (stopAtContentEdge) 153 return max(contentHeight - clientHeight, 0); 154 else 155 return contentHeight; 156 } 157 } 158} 159 160void RenderMarquee::start() 161{ 162 if (m_timer.isActive() || style()->marqueeIncrement().isZero()) 163 return; 164 165 if (!m_suspended && !m_stopped) { 166 if (isHorizontal()) 167 layer()->scrollableArea()->scrollToOffset(IntSize(m_start, 0)); 168 else 169 layer()->scrollableArea()->scrollToOffset(IntSize(0, m_start)); 170 } else { 171 m_suspended = false; 172 m_stopped = false; 173 } 174 175 m_timer.startRepeating(speed() * 0.001); 176} 177 178void RenderMarquee::suspend() 179{ 180 m_timer.stop(); 181 m_suspended = true; 182} 183 184void RenderMarquee::stop() 185{ 186 m_timer.stop(); 187 m_stopped = true; 188} 189 190void RenderMarquee::updateMarqueePosition() 191{ 192 bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); 193 if (activate) { 194 EMarqueeBehavior behavior = style()->marqueeBehavior(); 195 m_start = computePosition(direction(), behavior == MALTERNATE); 196 m_end = computePosition(reverseDirection(), behavior == MALTERNATE || behavior == MSLIDE); 197 if (!m_stopped) 198 start(); 199 } 200} 201 202const char* RenderMarquee::renderName() const 203{ 204 if (isFloating()) 205 return "RenderMarquee (floating)"; 206 if (isOutOfFlowPositioned()) 207 return "RenderMarquee (positioned)"; 208 if (isAnonymous()) 209 return "RenderMarquee (generated)"; 210 if (isRelPositioned()) 211 return "RenderMarquee (relative positioned)"; 212 return "RenderMarquee"; 213 214} 215 216void RenderMarquee::styleDidChange(StyleDifference difference, const RenderStyle* oldStyle) 217{ 218 RenderBlock::styleDidChange(difference, oldStyle); 219 220 RenderStyle* s = style(); 221 222 if (m_direction != s->marqueeDirection() || (m_totalLoops != s->marqueeLoopCount() && m_currentLoop >= m_totalLoops)) 223 m_currentLoop = 0; // When direction changes or our loopCount is a smaller number than our current loop, reset our loop. 224 225 m_totalLoops = s->marqueeLoopCount(); 226 m_direction = s->marqueeDirection(); 227 228 // Hack for WinIE. In WinIE, a value of 0 or lower for the loop count for SLIDE means to only do 229 // one loop. 230 if (m_totalLoops <= 0 && s->marqueeBehavior() == MSLIDE) 231 m_totalLoops = 1; 232 233 // Hack alert: Set the white-space value to nowrap for horizontal marquees with inline children, thus ensuring 234 // all the text ends up on one line by default. Limit this hack to the <marquee> element to emulate 235 // WinIE's behavior. Someone using CSS3 can use white-space: nowrap on their own to get this effect. 236 // Second hack alert: Set the text-align back to auto. WinIE completely ignores text-align on the 237 // marquee element. 238 // FIXME: Bring these up with the CSS WG. 239 if (isHorizontal() && childrenInline()) { 240 s->setWhiteSpace(NOWRAP); 241 s->setTextAlign(TASTART); 242 } 243 244 // Legacy hack - multiple browsers default vertical marquees to 200px tall. 245 if (!isHorizontal() && s->height().isAuto()) 246 s->setHeight(Length(200, Fixed)); 247 248 if (speed() != marqueeSpeed()) { 249 m_speed = marqueeSpeed(); 250 if (m_timer.isActive()) 251 m_timer.startRepeating(speed() * 0.001); 252 } 253 254 // Check the loop count to see if we should now stop. 255 bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); 256 if (activate && !m_timer.isActive()) 257 setNeedsLayout(); 258 else if (!activate && m_timer.isActive()) 259 m_timer.stop(); 260} 261 262void RenderMarquee::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight) 263{ 264 RenderBlockFlow::layoutBlock(relayoutChildren, pageLogicalHeight); 265 266 updateMarqueePosition(); 267} 268 269void RenderMarquee::timerFired() 270{ 271 // FIXME: Why do we need to check the view and not just the RenderMarquee itself? 272 if (view()->needsLayout()) 273 return; 274 275 if (m_reset) { 276 m_reset = false; 277 if (isHorizontal()) 278 layer()->scrollableArea()->scrollToXOffset(m_start); 279 else 280 layer()->scrollableArea()->scrollToYOffset(m_start); 281 return; 282 } 283 284 RenderStyle* s = style(); 285 286 int endPoint = m_end; 287 int range = m_end - m_start; 288 int newPos; 289 if (range == 0) 290 newPos = m_end; 291 else { 292 bool addIncrement = direction() == MUP || direction() == MLEFT; 293 bool isReversed = s->marqueeBehavior() == MALTERNATE && m_currentLoop % 2; 294 if (isReversed) { 295 // We're going in the reverse direction. 296 endPoint = m_start; 297 range = -range; 298 addIncrement = !addIncrement; 299 } 300 bool positive = range > 0; 301 int clientSize = (isHorizontal() ? clientWidth() : clientHeight()); 302 int increment = abs(intValueForLength(style()->marqueeIncrement(), clientSize)); 303 int currentPos = (isHorizontal() ? layer()->scrollableArea()->scrollXOffset() : layer()->scrollableArea()->scrollYOffset()); 304 newPos = currentPos + (addIncrement ? increment : -increment); 305 if (positive) 306 newPos = min(newPos, endPoint); 307 else 308 newPos = max(newPos, endPoint); 309 } 310 311 if (newPos == endPoint) { 312 m_currentLoop++; 313 if (m_totalLoops > 0 && m_currentLoop >= m_totalLoops) 314 m_timer.stop(); 315 else if (s->marqueeBehavior() != MALTERNATE) 316 m_reset = true; 317 } 318 319 if (isHorizontal()) 320 layer()->scrollableArea()->scrollToXOffset(newPos); 321 else 322 layer()->scrollableArea()->scrollToYOffset(newPos); 323} 324 325} // namespace WebCore 326