5eff8262270cc7e63256f7beceec4ce67000db63
[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     if (m_thumb)
65         m_thumb->detach();
66 }
67
68 int RenderSlider::baselinePosition(FontBaseline, bool /*firstLine*/, LineDirectionMode, LinePositionMode) const
69 {
70     // FIXME: Patch this function for writing-mode.
71     return height() + marginTop();
72 }
73
74 void RenderSlider::computePreferredLogicalWidths()
75 {
76     m_minPreferredLogicalWidth = 0;
77     m_maxPreferredLogicalWidth = 0;
78
79     if (style()->width().isFixed() && style()->width().value() > 0)
80         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
81     else
82         m_maxPreferredLogicalWidth = defaultTrackLength * style()->effectiveZoom();
83
84     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
85         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
86         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
87     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
88         m_minPreferredLogicalWidth = 0;
89     else
90         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
91     
92     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
93         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
94         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
95     }
96
97     int toAdd = borderAndPaddingWidth();
98     m_minPreferredLogicalWidth += toAdd;
99     m_maxPreferredLogicalWidth += toAdd;
100
101     setPreferredLogicalWidthsDirty(false); 
102 }
103
104 void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
105 {
106     RenderBlock::styleDidChange(diff, oldStyle);
107
108     if (m_thumb)
109         m_thumb->renderer()->setStyle(createThumbStyle(style()));
110 }
111
112 PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle)
113 {
114     RefPtr<RenderStyle> thumbStyle = document()->styleSelector()->styleForElement(m_thumb.get(), style(), false);
115
116     if (parentStyle->appearance() == SliderVerticalPart)
117         thumbStyle->setAppearance(SliderThumbVerticalPart);
118     else if (parentStyle->appearance() == SliderHorizontalPart)
119         thumbStyle->setAppearance(SliderThumbHorizontalPart);
120     else if (parentStyle->appearance() == MediaSliderPart)
121         thumbStyle->setAppearance(MediaSliderThumbPart);
122     else if (parentStyle->appearance() == MediaVolumeSliderPart)
123         thumbStyle->setAppearance(MediaVolumeSliderThumbPart);
124
125     return thumbStyle.release();
126 }
127
128 IntRect RenderSlider::thumbRect()
129 {
130     if (!m_thumb)
131         return IntRect();
132
133     IntRect thumbRect;
134     RenderBox* thumb = toRenderBox(m_thumb->renderer());
135
136     thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth()));
137     thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight()));
138
139     double fraction = sliderPosition(static_cast<HTMLInputElement*>(node()));
140     IntRect contentRect = contentBoxRect();
141     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) {
142         thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2);
143         thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction)));
144     } else {
145         thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction));
146         thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2);
147     }
148
149     return thumbRect;
150 }
151
152 void RenderSlider::layout()
153 {
154     ASSERT(needsLayout());
155
156     RenderBox* thumb = m_thumb ? toRenderBox(m_thumb->renderer()) : 0;
157
158     IntSize baseSize(borderAndPaddingWidth(), borderAndPaddingHeight());
159
160     if (thumb) {
161         // Allow the theme to set the size of the thumb.
162         if (thumb->style()->hasAppearance()) {
163             // FIXME: This should pass the style, not the renderer, to the theme.
164             theme()->adjustSliderThumbSize(thumb);
165         }
166
167         baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0));
168     }
169
170     LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
171
172     IntSize oldSize = size();
173
174     setSize(baseSize);
175     computeLogicalWidth();
176     computeLogicalHeight();
177     updateLayerTransform();
178
179     if (thumb) {
180         if (oldSize != size())
181             thumb->setChildNeedsLayout(true, false);
182
183         LayoutStateMaintainer statePusher(view(), this, size(), style()->isFlippedBlocksWritingMode());
184
185         IntRect oldThumbRect = thumb->frameRect();
186
187         thumb->layoutIfNeeded();
188
189         IntRect rect = thumbRect();
190         thumb->setFrameRect(rect);
191         if (thumb->checkForRepaintDuringLayout())
192             thumb->repaintDuringLayoutIfMoved(oldThumbRect);
193
194         statePusher.pop();
195         addOverflowFromChild(thumb);
196     }
197
198     repainter.repaintAfterLayout();    
199
200     setNeedsLayout(false);
201 }
202
203 void RenderSlider::updateFromElement()
204 {
205     // Layout will take care of the thumb's size and position.
206     if (!m_thumb) {
207         m_thumb = SliderThumbElement::create(static_cast<HTMLElement*>(node()));
208         RefPtr<RenderStyle> thumbStyle = createThumbStyle(style());
209         m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get()));
210         m_thumb->renderer()->setStyle(thumbStyle.release());
211         m_thumb->setAttached();
212         m_thumb->setInDocument();
213         addChild(m_thumb->renderer());
214     }
215     setNeedsLayout(true);
216 }
217
218 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
219 {
220     if (!m_thumb || !m_thumb->renderer())
221         return false;
222
223 #if ENABLE(VIDEO)
224     if (style()->appearance() == MediaSliderPart || style()->appearance() == MediaVolumeSliderPart) {
225         MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node());
226         return sliderThumb->hitTest(evt->absoluteLocation());
227     }
228 #endif
229
230     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
231     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
232     return thumbBounds.contains(roundedIntPoint(localPoint));
233 }
234
235 FloatPoint RenderSlider::mouseEventOffsetToThumb(MouseEvent* evt)
236 {
237     ASSERT(m_thumb && m_thumb->renderer());
238     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
239     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
240     FloatPoint offset;
241     offset.setX(thumbBounds.x() + thumbBounds.width() / 2 - localPoint.x());
242     offset.setY(thumbBounds.y() + thumbBounds.height() / 2 - localPoint.y());
243     return offset;
244 }
245
246 void RenderSlider::setValueForPosition(int position)
247 {
248     if (!m_thumb || !m_thumb->renderer())
249         return;
250
251     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
252
253     // Calculate the new value based on the position, and send it to the element.
254     StepRange range(element);
255     double fraction = static_cast<double>(position) / trackSize();
256     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
257         fraction = 1 - fraction;
258     double value = range.clampValue(range.valueFromProportion(fraction));
259     element->setValueFromRenderer(serializeForNumberType(value));
260
261     // Also update the position if appropriate.
262     if (position != currentPosition()) {
263         setNeedsLayout(true);
264
265         // FIXME: It seems like this could send extra change events if the same value is set
266         // multiple times with no layout in between.
267         element->dispatchFormControlChangeEvent();
268     }
269 }
270
271 int RenderSlider::positionForOffset(const IntPoint& p)
272 {
273     if (!m_thumb || !m_thumb->renderer())
274         return 0;
275
276     int position;
277     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
278         position = p.y() - m_thumb->renderBox()->height() / 2;
279     else
280         position = p.x() - m_thumb->renderBox()->width() / 2;
281     
282     return max(0, min(position, trackSize()));
283 }
284
285 int RenderSlider::currentPosition()
286 {
287     ASSERT(m_thumb);
288     ASSERT(m_thumb->renderer());
289
290     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
291         return toRenderBox(m_thumb->renderer())->y() - contentBoxRect().y();
292     return toRenderBox(m_thumb->renderer())->x() - contentBoxRect().x();
293 }
294
295 int RenderSlider::trackSize()
296 {
297     ASSERT(m_thumb);
298     ASSERT(m_thumb->renderer());
299
300     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
301         return contentHeight() - m_thumb->renderBox()->height();
302     return contentWidth() - m_thumb->renderBox()->width();
303 }
304
305 void RenderSlider::forwardEvent(Event* event)
306 {
307     if (event->isMouseEvent()) {
308         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
309         if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) {
310             if (!mouseEventIsInThumb(mouseEvent)) {
311                 IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
312                 setValueForPosition(positionForOffset(eventOffset));
313             }
314         }
315     }
316
317     m_thumb->defaultEventHandler(event);
318 }
319
320 bool RenderSlider::inDragMode() const
321 {
322     return m_thumb && m_thumb->inDragMode();
323 }
324
325 } // namespace WebCore