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