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