Home | History | Annotate | Download | only in rendering
      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 (at) fas.harvard.edu>
     10  *   Christian Biesinger <cbiesinger (at) web.de>
     11  *   Randall Jesup <rjesup (at) wgate.com>
     12  *   Roland Mainz <roland.mainz (at) informatik.med.uni-giessen.de>
     13  *   Josh Soref <timeless (at) mac.com>
     14  *   Boris Zbarsky <bzbarsky (at) 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 "core/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 #include "platform/LengthFunctions.h"
     56 
     57 using namespace std;
     58 
     59 namespace WebCore {
     60 
     61 using namespace HTMLNames;
     62 
     63 RenderMarquee::RenderMarquee(HTMLMarqueeElement* element)
     64     : RenderBlockFlow(element)
     65     , m_currentLoop(0)
     66     , m_totalLoops(0)
     67     , m_timer(element, &HTMLMarqueeElement::timerFired)
     68     , m_start(0)
     69     , m_end(0)
     70     , m_speed(0)
     71     , m_reset(false)
     72     , m_suspended(false)
     73     , m_stopped(false)
     74     , m_direction(MAUTO)
     75 {
     76     UseCounter::count(document(), UseCounter::HTMLMarqueeElement);
     77 }
     78 
     79 RenderMarquee::~RenderMarquee()
     80 {
     81 }
     82 
     83 int RenderMarquee::marqueeSpeed() const
     84 {
     85     int result = style()->marqueeSpeed();
     86     if (Node* node = this->node())
     87         result = std::max(result, toHTMLMarqueeElement(node)->minimumDelay());
     88     return result;
     89 }
     90 
     91 EMarqueeDirection RenderMarquee::direction() const
     92 {
     93     // FIXME: Support the CSS3 "auto" value for determining the direction of the marquee.
     94     // For now just map MAUTO to MBACKWARD
     95     EMarqueeDirection result = style()->marqueeDirection();
     96     TextDirection dir = style()->direction();
     97     if (result == MAUTO)
     98         result = MBACKWARD;
     99     if (result == MFORWARD)
    100         result = (dir == LTR) ? MRIGHT : MLEFT;
    101     if (result == MBACKWARD)
    102         result = (dir == LTR) ? MLEFT : MRIGHT;
    103 
    104     // Now we have the real direction.  Next we check to see if the increment is negative.
    105     // If so, then we reverse the direction.
    106     Length increment = style()->marqueeIncrement();
    107     if (increment.isNegative())
    108         result = static_cast<EMarqueeDirection>(-result);
    109 
    110     return result;
    111 }
    112 
    113 bool RenderMarquee::isHorizontal() const
    114 {
    115     return direction() == MLEFT || direction() == MRIGHT;
    116 }
    117 
    118 int RenderMarquee::computePosition(EMarqueeDirection dir, bool stopAtContentEdge)
    119 {
    120     if (isHorizontal()) {
    121         bool ltr = style()->isLeftToRightDirection();
    122         LayoutUnit clientWidth = this->clientWidth();
    123         LayoutUnit contentWidth = ltr ? maxPreferredLogicalWidth() : minPreferredLogicalWidth();
    124         if (ltr)
    125             contentWidth += (paddingRight() - borderLeft());
    126         else {
    127             contentWidth = width() - contentWidth;
    128             contentWidth += (paddingLeft() - borderRight());
    129         }
    130         if (dir == MRIGHT) {
    131             if (stopAtContentEdge)
    132                 return max<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth));
    133             else
    134                 return ltr ? contentWidth : clientWidth;
    135         }
    136         else {
    137             if (stopAtContentEdge)
    138                 return min<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth));
    139             else
    140                 return ltr ? -clientWidth : -contentWidth;
    141         }
    142     }
    143     else {
    144         int contentHeight = layoutOverflowRect().maxY() - borderTop() + paddingBottom();
    145         int clientHeight = this->clientHeight();
    146         if (dir == MUP) {
    147             if (stopAtContentEdge)
    148                  return min(contentHeight - clientHeight, 0);
    149             else
    150                 return -clientHeight;
    151         }
    152         else {
    153             if (stopAtContentEdge)
    154                 return max(contentHeight - clientHeight, 0);
    155             else
    156                 return contentHeight;
    157         }
    158     }
    159 }
    160 
    161 void RenderMarquee::start()
    162 {
    163     if (m_timer.isActive() || style()->marqueeIncrement().isZero())
    164         return;
    165 
    166     if (!m_suspended && !m_stopped) {
    167         if (isHorizontal())
    168             layer()->scrollableArea()->scrollToOffset(IntSize(m_start, 0));
    169         else
    170             layer()->scrollableArea()->scrollToOffset(IntSize(0, m_start));
    171     } else {
    172         m_suspended = false;
    173         m_stopped = false;
    174     }
    175 
    176     m_timer.startRepeating(speed() * 0.001, FROM_HERE);
    177 }
    178 
    179 void RenderMarquee::suspend()
    180 {
    181     m_timer.stop();
    182     m_suspended = true;
    183 }
    184 
    185 void RenderMarquee::stop()
    186 {
    187     m_timer.stop();
    188     m_stopped = true;
    189 }
    190 
    191 void RenderMarquee::updateMarqueePosition()
    192 {
    193     bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops);
    194     if (activate) {
    195         EMarqueeBehavior behavior = style()->marqueeBehavior();
    196         m_start = computePosition(direction(), behavior == MALTERNATE);
    197         m_end = computePosition(reverseDirection(), behavior == MALTERNATE || behavior == MSLIDE);
    198         if (!m_stopped) {
    199             // Hits in compositing/overflow/do-not-repaint-if-scrolling-composited-layers.html during layout.
    200             DisableCompositingQueryAsserts disabler;
    201             start();
    202         }
    203     }
    204 }
    205 
    206 const char* RenderMarquee::renderName() const
    207 {
    208     if (isFloating())
    209         return "RenderMarquee (floating)";
    210     if (isOutOfFlowPositioned())
    211         return "RenderMarquee (positioned)";
    212     if (isAnonymous())
    213         return "RenderMarquee (generated)";
    214     if (isRelPositioned())
    215         return "RenderMarquee (relative positioned)";
    216     return "RenderMarquee";
    217 
    218 }
    219 
    220 void RenderMarquee::styleDidChange(StyleDifference difference, const RenderStyle* oldStyle)
    221 {
    222     RenderBlockFlow::styleDidChange(difference, oldStyle);
    223 
    224     RenderStyle* s = style();
    225 
    226     if (m_direction != s->marqueeDirection() || (m_totalLoops != s->marqueeLoopCount() && m_currentLoop >= m_totalLoops))
    227         m_currentLoop = 0; // When direction changes or our loopCount is a smaller number than our current loop, reset our loop.
    228 
    229     m_totalLoops = s->marqueeLoopCount();
    230     m_direction = s->marqueeDirection();
    231 
    232     // Hack for WinIE. In WinIE, a value of 0 or lower for the loop count for SLIDE means to only do
    233     // one loop.
    234     if (m_totalLoops <= 0 && s->marqueeBehavior() == MSLIDE)
    235         m_totalLoops = 1;
    236 
    237     // Hack alert: Set the white-space value to nowrap for horizontal marquees with inline children, thus ensuring
    238     // all the text ends up on one line by default. Limit this hack to the <marquee> element to emulate
    239     // WinIE's behavior. Someone using CSS3 can use white-space: nowrap on their own to get this effect.
    240     // Second hack alert: Set the text-align back to auto. WinIE completely ignores text-align on the
    241     // marquee element.
    242     // FIXME: Bring these up with the CSS WG.
    243     if (isHorizontal() && childrenInline()) {
    244         s->setWhiteSpace(NOWRAP);
    245         s->setTextAlign(TASTART);
    246     }
    247 
    248     // Legacy hack - multiple browsers default vertical marquees to 200px tall.
    249     if (!isHorizontal() && s->height().isAuto())
    250         s->setHeight(Length(200, Fixed));
    251 
    252     if (speed() != marqueeSpeed()) {
    253         m_speed = marqueeSpeed();
    254         if (m_timer.isActive())
    255             m_timer.startRepeating(speed() * 0.001, FROM_HERE);
    256     }
    257 
    258     // Check the loop count to see if we should now stop.
    259     bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops);
    260     if (activate && !m_timer.isActive())
    261         setNeedsLayoutAndFullPaintInvalidation();
    262     else if (!activate && m_timer.isActive())
    263         m_timer.stop();
    264 }
    265 
    266 void RenderMarquee::layoutBlock(bool relayoutChildren)
    267 {
    268     RenderBlockFlow::layoutBlock(relayoutChildren);
    269 
    270     updateMarqueePosition();
    271 }
    272 
    273 void RenderMarquee::timerFired()
    274 {
    275     // FIXME: Why do we need to check the view and not just the RenderMarquee itself?
    276     if (view()->needsLayout())
    277         return;
    278 
    279     if (m_reset) {
    280         m_reset = false;
    281         if (isHorizontal())
    282             layer()->scrollableArea()->scrollToXOffset(m_start);
    283         else
    284             layer()->scrollableArea()->scrollToYOffset(m_start);
    285         return;
    286     }
    287 
    288     RenderStyle* s = style();
    289 
    290     int endPoint = m_end;
    291     int range = m_end - m_start;
    292     int newPos;
    293     if (range == 0)
    294         newPos = m_end;
    295     else {
    296         bool addIncrement = direction() == MUP || direction() == MLEFT;
    297         bool isReversed = s->marqueeBehavior() == MALTERNATE && m_currentLoop % 2;
    298         if (isReversed) {
    299             // We're going in the reverse direction.
    300             endPoint = m_start;
    301             range = -range;
    302             addIncrement = !addIncrement;
    303         }
    304         bool positive = range > 0;
    305         int clientSize = (isHorizontal() ? clientWidth() : clientHeight());
    306         int increment = abs(intValueForLength(style()->marqueeIncrement(), clientSize));
    307         int currentPos = (isHorizontal() ? layer()->scrollableArea()->scrollXOffset() : layer()->scrollableArea()->scrollYOffset());
    308         newPos =  currentPos + (addIncrement ? increment : -increment);
    309         if (positive)
    310             newPos = min(newPos, endPoint);
    311         else
    312             newPos = max(newPos, endPoint);
    313     }
    314 
    315     if (newPos == endPoint) {
    316         m_currentLoop++;
    317         if (m_totalLoops > 0 && m_currentLoop >= m_totalLoops)
    318             m_timer.stop();
    319         else if (s->marqueeBehavior() != MALTERNATE)
    320             m_reset = true;
    321     }
    322 
    323     if (isHorizontal())
    324         layer()->scrollableArea()->scrollToXOffset(newPos);
    325     else
    326         layer()->scrollableArea()->scrollToYOffset(newPos);
    327 }
    328 
    329 } // namespace WebCore
    330