WebCore:
[WebKit-https.git] / WebCore / rendering / RenderSlider.cpp
1 /**
2  *
3  * Copyright (C) 2006, 2007, 2008 Apple Computer, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  */
21
22 #include "config.h"
23 #include "RenderSlider.h"
24
25 #include "CSSPropertyNames.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 "HTMLDivElement.h"
33 #include "HTMLNames.h"
34 #include "MouseEvent.h"
35 #include "RenderTheme.h"
36 #include <wtf/MathExtras.h>
37
38 using std::min;
39
40 namespace WebCore {
41
42 using namespace EventNames;
43 using namespace HTMLNames;
44
45 const int defaultTrackLength = 129;
46
47 class HTMLSliderThumbElement : public HTMLDivElement {
48 public:
49     HTMLSliderThumbElement(Document*, Node* shadowParent = 0);
50         
51     virtual void defaultEventHandler(Event*);
52     virtual bool isShadowNode() const { return true; }
53     virtual Node* shadowParentNode() { return m_shadowParent; }
54     
55     bool inDragMode() const { return m_inDragMode; }
56 private:
57     Node* m_shadowParent;
58     IntPoint m_initialClickPoint;
59     int m_initialPosition;
60     bool m_inDragMode;
61 };
62
63 HTMLSliderThumbElement::HTMLSliderThumbElement(Document* doc, Node* shadowParent)
64     : HTMLDivElement(doc)
65     , m_shadowParent(shadowParent)
66     , m_initialClickPoint(IntPoint())
67     , m_initialPosition(0)
68     , m_inDragMode(false)
69 {
70 }
71
72 void HTMLSliderThumbElement::defaultEventHandler(Event* event)
73 {
74     const AtomicString& eventType = event->type();
75     if (eventType == mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
76         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
77         if (document()->frame() && renderer() && renderer()->parent()
78                 && static_cast<RenderSlider*>(renderer()->parent())->mouseEventIsInThumb(mouseEvent)) {
79             // Cache the initial point where the mouse down occurred.
80             m_initialClickPoint = IntPoint(mouseEvent->pageX(), mouseEvent->pageY());
81             // Cache the initial position of the thumb.
82             m_initialPosition = static_cast<RenderSlider*>(renderer()->parent())->currentPosition();
83             m_inDragMode = true;
84             
85             document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
86             
87             event->setDefaultHandled();
88             return;
89         }
90     } else if (eventType == mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
91         if (m_inDragMode) {
92             if (Frame* frame = document()->frame())
93                 frame->eventHandler()->setCapturingMouseEventsNode(0);      
94             m_inDragMode = false;
95             event->setDefaultHandled();
96             return;
97         }
98     } else if (eventType == mousemoveEvent && event->isMouseEvent()) {
99         if (m_inDragMode && renderer() && renderer()->parent()) {
100             // Move the slider
101             MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
102             RenderSlider* slider = static_cast<RenderSlider*>(renderer()->parent());
103             int newPosition = slider->positionForOffset(
104                 IntPoint(m_initialPosition + mouseEvent->pageX() - m_initialClickPoint.x()
105                         + (renderer()->absoluteBoundingBoxRect().width() / 2), 
106                     m_initialPosition + mouseEvent->pageY() - m_initialClickPoint.y()
107                         + (renderer()->absoluteBoundingBoxRect().height() / 2)));
108             if (slider->currentPosition() != newPosition) {
109                 slider->setCurrentPosition(newPosition);
110                 slider->valueChanged();
111             }
112             event->setDefaultHandled();
113             return;
114         }
115     }
116
117     HTMLDivElement::defaultEventHandler(event);
118 }
119
120 RenderSlider::RenderSlider(HTMLInputElement* element)
121     : RenderBlock(element)
122     , m_thumb(0)
123 {
124 }
125
126 RenderSlider::~RenderSlider()
127 {
128     if (m_thumb)
129         m_thumb->detach();
130 }
131
132 short RenderSlider::baselinePosition(bool b, bool isRootLineBox) const
133 {
134     return height() + marginTop();
135 }
136
137 void RenderSlider::calcPrefWidths()
138 {
139     m_minPrefWidth = 0;
140     m_maxPrefWidth = 0;
141
142     if (style()->width().isFixed() && style()->width().value() > 0)
143         m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
144     else
145         m_maxPrefWidth = defaultTrackLength;
146
147     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
148         m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
149         m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
150     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
151         m_minPrefWidth = 0;
152     else
153         m_minPrefWidth = m_maxPrefWidth;
154     
155     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
156         m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
157         m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
158     }
159
160     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
161     m_minPrefWidth += toAdd;
162     m_maxPrefWidth += toAdd;
163
164     setPrefWidthsDirty(false); 
165 }
166
167 void RenderSlider::setStyle(RenderStyle* newStyle)
168 {
169     RenderBlock::setStyle(newStyle);
170     
171     if (m_thumb) {
172         RenderStyle* thumbStyle = createThumbStyle(newStyle, m_thumb->renderer()->style());
173         m_thumb->renderer()->setStyle(thumbStyle);
174     }
175         
176     setReplaced(isInline());
177 }
178
179 RenderStyle* RenderSlider::createThumbStyle(RenderStyle* parentStyle, RenderStyle* oldStyle)
180 {
181     RenderStyle* style;
182
183     RenderStyle* pseudoStyle = getPseudoStyle(RenderStyle::SLIDER_THUMB);
184     if (pseudoStyle)
185         // We may be sharing style with another slider, but we must not share the thumb style.
186         style = new (renderArena()) RenderStyle(*pseudoStyle);
187     else
188         style = new (renderArena()) RenderStyle();
189
190     if (parentStyle)
191         style->inheritFrom(parentStyle);
192
193     style->setDisplay(BLOCK);
194     style->setPosition(RelativePosition);
195     if (oldStyle) {
196         style->setLeft(oldStyle->left());
197         style->setTop(oldStyle->top());
198     }
199
200     if (parentStyle->appearance() == SliderVerticalAppearance)
201        style->setAppearance(SliderThumbVerticalAppearance);
202     else if (parentStyle->appearance() == SliderHorizontalAppearance)
203        style->setAppearance(SliderThumbHorizontalAppearance);
204     else if (parentStyle->appearance() == MediaSliderAppearance)
205         style->setAppearance(MediaSliderThumbAppearance);
206
207     return style;
208 }
209
210 void RenderSlider::layout()
211 {    
212     bool relayoutChildren = false;
213     
214     if (m_thumb && m_thumb->renderer()) {
215             
216         int oldWidth = m_width;
217         calcWidth();
218         int oldHeight = m_height;
219         calcHeight();
220         
221         if (oldWidth != m_width || oldHeight != m_height)
222             relayoutChildren = true;  
223
224         // Allow the theme to set the size of the thumb
225         if (m_thumb->renderer()->style()->hasAppearance())
226             theme()->adjustSliderThumbSize(m_thumb->renderer());
227
228         if (style()->appearance() == SliderVerticalAppearance) {
229             // FIXME: Handle percentage widths correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104
230             m_thumb->renderer()->style()->setLeft(Length(m_width / 2 - m_thumb->renderer()->style()->width().value() / 2, Fixed));
231         } else {
232             // FIXME: Handle percentage heights correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104
233             m_thumb->renderer()->style()->setTop(Length(m_height / 2 - m_thumb->renderer()->style()->height().value() / 2, Fixed));
234         }
235
236         if (relayoutChildren)
237             setPositionFromValue(true);
238     }
239
240     RenderBlock::layoutBlock(relayoutChildren);
241 }
242
243 void RenderSlider::updateFromElement()
244 {
245     if (!m_thumb) {
246         m_thumb = new HTMLSliderThumbElement(document(), node());
247         RenderStyle* thumbStyle = createThumbStyle(style());
248         m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle));
249         m_thumb->renderer()->setStyle(thumbStyle);
250         m_thumb->setAttached();
251         m_thumb->setInDocument(true);
252         addChild(m_thumb->renderer());
253     }
254     setPositionFromValue();
255     setNeedsLayout(true);
256 }
257
258 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
259 {
260     if (!m_thumb || !m_thumb->renderer())
261         return false;
262  
263     IntRect thumbBounds = m_thumb->renderer()->absoluteBoundingBoxRect();
264     return thumbBounds.contains(evt->pageX(), evt->pageY());
265 }
266
267 void RenderSlider::setValueForPosition(int position)
268 {
269     if (!m_thumb || !m_thumb->renderer())
270         return;
271     
272     const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr);
273     const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr);
274     const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr);
275     
276     double minVal = minStr.isNull() ? 0.0 : minStr.toDouble();
277     double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble();
278     minVal = min(minVal, maxVal); // Make sure the range is sane.
279     
280     // Calculate the new value based on the position
281     double factor = (double)position / (double)trackSize();
282     if (style()->appearance() == SliderVerticalAppearance)
283         factor = 1.0 - factor;
284     double val = minVal + factor * (maxVal - minVal);
285             
286     val = max(minVal, min(val, maxVal)); // Make sure val is within min/max.
287
288     // Force integer value if not float.
289     if (!equalIgnoringCase(precision, "float"))
290         val = lround(val);
291
292     static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
293     
294     if (position != currentPosition())
295         setCurrentPosition(position);
296 }
297
298 double RenderSlider::setPositionFromValue(bool inLayout)
299 {
300     if (!m_thumb || !m_thumb->renderer())
301         return 0;
302     
303     if (!inLayout)
304         document()->updateLayout();
305         
306     String value = static_cast<HTMLInputElement*>(node())->value();
307     const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr);
308     const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr);
309     const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr);
310     
311     double minVal = minStr.isNull() ? 0.0 : minStr.toDouble();
312     double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble();
313     minVal = min(minVal, maxVal); // Make sure the range is sane.
314     
315     double oldVal = value.isNull() ? (maxVal + minVal)/2.0 : value.toDouble();
316     double val = max(minVal, min(oldVal, maxVal)); // Make sure val is within min/max.
317         
318     // Force integer value if not float.
319     if (!equalIgnoringCase(precision, "float"))
320         val = lround(val);
321
322     // Calculate the new position based on the value
323     double factor = (val - minVal) / (maxVal - minVal);
324     if (style()->appearance() == SliderVerticalAppearance)
325         factor = 1.0 - factor;
326
327     setCurrentPosition((int)(factor * trackSize()));
328     
329     if (val != oldVal)
330         static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
331     
332     return val;
333 }
334
335 int RenderSlider::positionForOffset(const IntPoint& p)
336 {
337     if (!m_thumb || !m_thumb->renderer())
338         return 0;
339    
340     int position;
341     if (style()->appearance() == SliderVerticalAppearance) {
342         position = max(0, min(p.y() - (m_thumb->renderer()->absoluteBoundingBoxRect().height() / 2), 
343                               absoluteBoundingBoxRect().height() - m_thumb->renderer()->absoluteBoundingBoxRect().height()));
344     } else {
345         position = max(0, min(p.x() - (m_thumb->renderer()->absoluteBoundingBoxRect().width() / 2), 
346                               absoluteBoundingBoxRect().width() - m_thumb->renderer()->absoluteBoundingBoxRect().width()));
347     }
348     return position;
349 }
350
351 void RenderSlider::valueChanged()
352 {
353     setValueForPosition(currentPosition());
354 }
355
356 int RenderSlider::currentPosition()
357 {
358     if (!m_thumb || !m_thumb->renderer())
359         return 0;
360
361     if (style()->appearance() == SliderVerticalAppearance)
362         return m_thumb->renderer()->style()->top().value();
363     return m_thumb->renderer()->style()->left().value();
364 }
365
366 void RenderSlider::setCurrentPosition(int pos)
367 {
368     if (!m_thumb || !m_thumb->renderer())
369         return;
370
371     if (style()->appearance() == SliderVerticalAppearance)
372         m_thumb->renderer()->style()->setTop(Length(pos, Fixed));
373     else
374         m_thumb->renderer()->style()->setLeft(Length(pos, Fixed));
375
376     m_thumb->renderer()->layer()->updateLayerPosition();
377     repaint();
378     m_thumb->renderer()->repaint();
379 }
380
381 int RenderSlider::trackSize()
382 {
383     if (!m_thumb || !m_thumb->renderer())
384         return 0;
385
386     if (style()->appearance() == SliderVerticalAppearance)
387         return absoluteBoundingBoxRect().height() - m_thumb->renderer()->absoluteBoundingBoxRect().height();
388     return absoluteBoundingBoxRect().width() - m_thumb->renderer()->absoluteBoundingBoxRect().width();
389 }
390
391 void RenderSlider::forwardEvent(Event* evt)
392 {
393     m_thumb->defaultEventHandler(evt);
394 }
395
396 bool RenderSlider::inDragMode() const
397 {
398     return m_thumb->inDragMode();
399 }
400
401 } // namespace WebCore