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