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