2011-01-13 Dimitri Glazkov <dglazkov@chromium.org>
[WebKit.git] / Source / WebCore / rendering / RenderSlider.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "RenderSlider.h"
23
24 #include "CSSPropertyNames.h"
25 #include "CSSStyleSelector.h"
26 #include "Document.h"
27 #include "Event.h"
28 #include "EventHandler.h"
29 #include "EventNames.h"
30 #include "Frame.h"
31 #include "HTMLInputElement.h"
32 #include "HTMLNames.h"
33 #include "HTMLParserIdioms.h"
34 #include "MediaControlElements.h"
35 #include "MouseEvent.h"
36 #include "RenderLayer.h"
37 #include "RenderTheme.h"
38 #include "RenderView.h"
39 #include "ShadowElement.h"
40 #include "SliderThumbElement.h"
41 #include "StepRange.h"
42 #include <wtf/MathExtras.h>
43
44 using std::min;
45
46 namespace WebCore {
47
48 static const int defaultTrackLength = 129;
49
50 // Returns a value between 0 and 1.
51 static double sliderPosition(HTMLInputElement* element)
52 {
53     StepRange range(element);
54     return range.proportionFromValue(range.valueFromElement(element));
55 }
56
57 RenderSlider::RenderSlider(HTMLInputElement* element)
58     : RenderBlock(element)
59 {
60 }
61
62 RenderSlider::~RenderSlider()
63 {
64 }
65
66 int RenderSlider::baselinePosition(FontBaseline, bool /*firstLine*/, LineDirectionMode, LinePositionMode) const
67 {
68     // FIXME: Patch this function for writing-mode.
69     return height() + marginTop();
70 }
71
72 void RenderSlider::computePreferredLogicalWidths()
73 {
74     m_minPreferredLogicalWidth = 0;
75     m_maxPreferredLogicalWidth = 0;
76
77     if (style()->width().isFixed() && style()->width().value() > 0)
78         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
79     else
80         m_maxPreferredLogicalWidth = defaultTrackLength * style()->effectiveZoom();
81
82     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
83         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
84         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
85     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
86         m_minPreferredLogicalWidth = 0;
87     else
88         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
89     
90     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
91         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
92         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
93     }
94
95     int toAdd = borderAndPaddingWidth();
96     m_minPreferredLogicalWidth += toAdd;
97     m_maxPreferredLogicalWidth += toAdd;
98
99     setPreferredLogicalWidthsDirty(false); 
100 }
101
102 IntRect RenderSlider::thumbRect()
103 {
104     SliderThumbElement* thumbElement = sliderThumbElement();
105     if (!thumbElement)
106         return IntRect();
107
108     IntRect thumbRect;
109     RenderBox* thumb = toRenderBox(thumbElement->renderer());
110
111     thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth()));
112     thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight()));
113
114     double fraction = sliderPosition(static_cast<HTMLInputElement*>(node()));
115     IntRect contentRect = contentBoxRect();
116     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) {
117         thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2);
118         thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction)));
119     } else {
120         thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction));
121         thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2);
122     }
123
124     return thumbRect;
125 }
126
127 void RenderSlider::layout()
128 {
129     ASSERT(needsLayout());
130
131     SliderThumbElement* thumbElement = sliderThumbElement();
132     RenderBox* thumb = thumbElement ? toRenderBox(thumbElement->renderer()) : 0;
133
134     IntSize baseSize(borderAndPaddingWidth(), borderAndPaddingHeight());
135
136     if (thumb) {
137         // Allow the theme to set the size of the thumb.
138         if (thumb->style()->hasAppearance()) {
139             // FIXME: This should pass the style, not the renderer, to the theme.
140             theme()->adjustSliderThumbSize(thumb);
141         }
142
143         baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0));
144     }
145
146     LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
147
148     IntSize oldSize = size();
149
150     setSize(baseSize);
151     computeLogicalWidth();
152     computeLogicalHeight();
153     updateLayerTransform();
154
155     if (thumb) {
156         if (oldSize != size())
157             thumb->setChildNeedsLayout(true, false);
158
159         LayoutStateMaintainer statePusher(view(), this, size(), style()->isFlippedBlocksWritingMode());
160
161         IntRect oldThumbRect = thumb->frameRect();
162
163         thumb->layoutIfNeeded();
164
165         IntRect rect = thumbRect();
166         thumb->setFrameRect(rect);
167         if (thumb->checkForRepaintDuringLayout())
168             thumb->repaintDuringLayoutIfMoved(oldThumbRect);
169
170         statePusher.pop();
171         addOverflowFromChild(thumb);
172     }
173
174     repainter.repaintAfterLayout();    
175
176     setNeedsLayout(false);
177 }
178
179 SliderThumbElement* RenderSlider::sliderThumbElement() const
180 {
181     return toSliderThumbElement(static_cast<Element*>(node())->shadowRoot());
182 }
183
184 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
185 {
186     SliderThumbElement* thumbElement = sliderThumbElement();
187     if (!thumbElement || !thumbElement->renderer())
188         return false;
189
190 #if ENABLE(VIDEO)
191     if (style()->appearance() == MediaSliderPart || style()->appearance() == MediaVolumeSliderPart) {
192         MediaControlInputElement* sliderThumb = static_cast<MediaControlInputElement*>(thumbElement->renderer()->node());
193         return sliderThumb->hitTest(evt->absoluteLocation());
194     }
195 #endif
196
197     FloatPoint localPoint = thumbElement->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
198     IntRect thumbBounds = thumbElement->renderBox()->borderBoxRect();
199     return thumbBounds.contains(roundedIntPoint(localPoint));
200 }
201
202 FloatPoint RenderSlider::mouseEventOffsetToThumb(MouseEvent* evt)
203 {
204     SliderThumbElement* thumbElement = sliderThumbElement();
205     ASSERT(thumbElement && thumbElement->renderer());
206     FloatPoint localPoint = thumbElement->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
207     IntRect thumbBounds = thumbElement->renderBox()->borderBoxRect();
208     FloatPoint offset;
209     offset.setX(thumbBounds.x() + thumbBounds.width() / 2 - localPoint.x());
210     offset.setY(thumbBounds.y() + thumbBounds.height() / 2 - localPoint.y());
211     return offset;
212 }
213
214 void RenderSlider::setValueForPosition(int position)
215 {
216     SliderThumbElement* thumbElement = sliderThumbElement();
217     if (!thumbElement || !thumbElement->renderer())
218         return;
219
220     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
221
222     // Calculate the new value based on the position, and send it to the element.
223     StepRange range(element);
224     double fraction = static_cast<double>(position) / trackSize();
225     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
226         fraction = 1 - fraction;
227     double value = range.clampValue(range.valueFromProportion(fraction));
228     element->setValueFromRenderer(serializeForNumberType(value));
229
230     // Also update the position if appropriate.
231     if (position != currentPosition()) {
232         setNeedsLayout(true);
233
234         // FIXME: It seems like this could send extra change events if the same value is set
235         // multiple times with no layout in between.
236         element->dispatchFormControlChangeEvent();
237     }
238 }
239
240 int RenderSlider::positionForOffset(const IntPoint& p)
241 {
242     SliderThumbElement* thumbElement = sliderThumbElement();
243     if (!thumbElement || !thumbElement->renderer())
244         return 0;
245
246     int position;
247     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
248         position = p.y() - thumbElement->renderBox()->height() / 2;
249     else
250         position = p.x() - thumbElement->renderBox()->width() / 2;
251     
252     return max(0, min(position, trackSize()));
253 }
254
255 int RenderSlider::currentPosition()
256 {
257     SliderThumbElement* thumbElement = sliderThumbElement();
258     ASSERT(thumbElement && thumbElement->renderer());
259
260     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
261         return toRenderBox(thumbElement->renderer())->y() - contentBoxRect().y();
262     return toRenderBox(thumbElement->renderer())->x() - contentBoxRect().x();
263 }
264
265 int RenderSlider::trackSize()
266 {
267     SliderThumbElement* thumbElement = sliderThumbElement();
268     ASSERT(thumbElement && thumbElement->renderer());
269
270     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
271         return contentHeight() - thumbElement->renderBox()->height();
272     return contentWidth() - thumbElement->renderBox()->width();
273 }
274
275 void RenderSlider::forwardEvent(Event* event)
276 {
277     SliderThumbElement* thumbElement = sliderThumbElement();
278     if (!thumbElement)
279         return;
280
281     if (event->isMouseEvent()) {
282         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
283         if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) {
284             if (!mouseEventIsInThumb(mouseEvent)) {
285                 IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
286                 setValueForPosition(positionForOffset(eventOffset));
287             }
288         }
289     }
290
291     thumbElement->defaultEventHandler(event);
292 }
293
294 bool RenderSlider::inDragMode() const
295 {
296     SliderThumbElement* thumbElement = sliderThumbElement();
297     return thumbElement && thumbElement->inDragMode();
298 }
299
300 } // namespace WebCore