d8ad152b272bd1b7dc9c47d6dbfae9bc40c1486d
[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 "MediaControlElements.h"
35 #include "MouseEvent.h"
36 #include "RenderLayer.h"
37 #include "RenderTheme.h"
38 #include <wtf/MathExtras.h>
39
40 using std::min;
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 const int defaultTrackLength = 129;
47
48 class HTMLSliderThumbElement : public HTMLDivElement {
49 public:
50     HTMLSliderThumbElement(Document*, Node* shadowParent = 0);
51         
52     virtual void defaultEventHandler(Event*);
53     virtual bool isShadowNode() const { return true; }
54     virtual Node* shadowParentNode() { return m_shadowParent; }
55     
56     bool inDragMode() const { return m_inDragMode; }
57 private:
58     Node* m_shadowParent;
59     FloatPoint m_initialClickPoint;       // initial click point in RenderSlider-local coordinates
60     int m_initialPosition;
61     bool m_inDragMode;
62 };
63
64 HTMLSliderThumbElement::HTMLSliderThumbElement(Document* doc, Node* shadowParent)
65     : HTMLDivElement(divTag, doc)
66     , m_shadowParent(shadowParent)
67     , m_initialClickPoint(IntPoint())
68     , m_initialPosition(0)
69     , m_inDragMode(false)
70 {
71 }
72
73 void HTMLSliderThumbElement::defaultEventHandler(Event* event)
74 {
75     const AtomicString& eventType = event->type();
76     if (eventType == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
77         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
78         RenderSlider* slider;
79         if (document()->frame() && renderer() && renderer()->parent() &&
80                 (slider = static_cast<RenderSlider*>(renderer()->parent())) &&
81                 slider->mouseEventIsInThumb(mouseEvent)) {
82             
83             // Cache the initial point where the mouse down occurred, in slider coordinates
84             m_initialClickPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true);
85             // Cache the initial position of the thumb.
86             m_initialPosition = slider->currentPosition();
87             m_inDragMode = true;
88             
89             document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
90             
91             event->setDefaultHandled();
92             return;
93         }
94     } else if (eventType == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
95         if (m_inDragMode) {
96             if (Frame* frame = document()->frame())
97                 frame->eventHandler()->setCapturingMouseEventsNode(0);      
98             m_inDragMode = false;
99             event->setDefaultHandled();
100             return;
101         }
102     } else if (eventType == eventNames().mousemoveEvent && event->isMouseEvent()) {
103         if (m_inDragMode && renderer() && renderer()->parent()) {
104             // Move the slider
105             MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
106             RenderSlider* slider = static_cast<RenderSlider*>(renderer()->parent());
107
108             FloatPoint curPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true);
109             int newPosition = slider->positionForOffset(
110                 IntPoint(m_initialPosition + curPoint.x() - m_initialClickPoint.x()
111                         + (renderBox()->width() / 2), 
112                     m_initialPosition + curPoint.y() - m_initialClickPoint.y()
113                         + (renderBox()->height() / 2)));
114             if (slider->currentPosition() != newPosition) {
115                 slider->setCurrentPosition(newPosition);
116                 slider->valueChanged();
117             }
118             event->setDefaultHandled();
119             return;
120         }
121     }
122
123     HTMLDivElement::defaultEventHandler(event);
124 }
125
126 RenderSlider::RenderSlider(HTMLInputElement* element)
127     : RenderBlock(element)
128     , m_thumb(0)
129 {
130 }
131
132 RenderSlider::~RenderSlider()
133 {
134     if (m_thumb)
135         m_thumb->detach();
136 }
137
138 int RenderSlider::baselinePosition(bool, bool) const
139 {
140     return height() + marginTop();
141 }
142
143 void RenderSlider::calcPrefWidths()
144 {
145     m_minPrefWidth = 0;
146     m_maxPrefWidth = 0;
147
148     if (style()->width().isFixed() && style()->width().value() > 0)
149         m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
150     else
151         m_maxPrefWidth = defaultTrackLength * style()->effectiveZoom();
152
153     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
154         m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
155         m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
156     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
157         m_minPrefWidth = 0;
158     else
159         m_minPrefWidth = m_maxPrefWidth;
160     
161     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
162         m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
163         m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
164     }
165
166     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
167     m_minPrefWidth += toAdd;
168     m_maxPrefWidth += toAdd;
169
170     setPrefWidthsDirty(false); 
171 }
172
173 void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
174 {
175     RenderBlock::styleDidChange(diff, oldStyle);
176     
177     if (m_thumb)
178         m_thumb->renderer()->setStyle(createThumbStyle(style(), m_thumb->renderer()->style()));
179         
180     setReplaced(isInline());
181 }
182
183 PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle, const RenderStyle* oldStyle)
184 {
185     RefPtr<RenderStyle> style;
186     RenderStyle* pseudoStyle = getCachedPseudoStyle(SLIDER_THUMB);
187     if (pseudoStyle)
188         // We may be sharing style with another slider, but we must not share the thumb style.
189         style = RenderStyle::clone(pseudoStyle);
190     else
191         style = RenderStyle::create();
192
193     if (parentStyle)
194         style->inheritFrom(parentStyle);
195
196     style->setDisplay(BLOCK);
197     style->setPosition(RelativePosition);
198     if (oldStyle) {
199         style->setLeft(oldStyle->left());
200         style->setTop(oldStyle->top());
201     }
202
203     if (parentStyle->appearance() == SliderVerticalPart)
204        style->setAppearance(SliderThumbVerticalPart);
205     else if (parentStyle->appearance() == SliderHorizontalPart)
206        style->setAppearance(SliderThumbHorizontalPart);
207     else if (parentStyle->appearance() == MediaSliderPart)
208         style->setAppearance(MediaSliderThumbPart);
209
210     return style.release();
211 }
212
213 void RenderSlider::layout()
214 {    
215     bool relayoutChildren = false;
216     
217     if (m_thumb && m_thumb->renderer()) {
218             
219         int oldWidth = width();
220         calcWidth();
221         int oldHeight = height();
222         calcHeight();
223         
224         if (oldWidth != width() || oldHeight != height())
225             relayoutChildren = true;  
226
227         // Allow the theme to set the size of the thumb
228         if (m_thumb->renderer()->style()->hasAppearance())
229             theme()->adjustSliderThumbSize(m_thumb->renderer());
230
231         if (style()->appearance() == SliderVerticalPart) {
232             // FIXME: Handle percentage widths correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104
233             m_thumb->renderer()->style()->setLeft(Length(contentWidth() / 2 - m_thumb->renderer()->style()->width().value() / 2, Fixed));
234         } else {
235             // FIXME: Handle percentage heights correctly. See http://bugs.webkit.org/show_bug.cgi?id=12104
236             m_thumb->renderer()->style()->setTop(Length(contentHeight() / 2 - m_thumb->renderer()->style()->height().value() / 2, Fixed));
237         }
238
239         if (relayoutChildren)
240             setPositionFromValue(true);
241     }
242
243     RenderBlock::layoutBlock(relayoutChildren);
244 }
245
246 void RenderSlider::updateFromElement()
247 {
248     if (!m_thumb) {
249         m_thumb = new HTMLSliderThumbElement(document(), node());
250         RefPtr<RenderStyle> thumbStyle = createThumbStyle(style());
251         m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get()));
252         m_thumb->renderer()->setStyle(thumbStyle.release());
253         m_thumb->setAttached();
254         m_thumb->setInDocument(true);
255         addChild(m_thumb->renderer());
256     }
257     setPositionFromValue();
258     setNeedsLayout(true, false);
259 }
260
261 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
262 {
263     if (!m_thumb || !m_thumb->renderer())
264         return false;
265
266 #if ENABLE(VIDEO)
267     if (style()->appearance() == MediaSliderPart) {
268         MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node());
269         return sliderThumb->hitTest(evt->absoluteLocation());
270     }
271 #endif
272
273     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
274     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
275     return thumbBounds.contains(roundedIntPoint(localPoint));
276 }
277
278 void RenderSlider::setValueForPosition(int position)
279 {
280     if (!m_thumb || !m_thumb->renderer())
281         return;
282     
283     const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr);
284     const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr);
285     const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr);
286     
287     double minVal = minStr.isNull() ? 0.0 : minStr.toDouble();
288     double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble();
289     minVal = min(minVal, maxVal); // Make sure the range is sane.
290     
291     // Calculate the new value based on the position
292     double factor = (double)position / (double)trackSize();
293     if (style()->appearance() == SliderVerticalPart)
294         factor = 1.0 - factor;
295     double val = minVal + factor * (maxVal - minVal);
296             
297     val = max(minVal, min(val, maxVal)); // Make sure val is within min/max.
298
299     // Force integer value if not float.
300     if (!equalIgnoringCase(precision, "float"))
301         val = lround(val);
302
303     static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
304     
305     if (position != currentPosition()) {
306         setCurrentPosition(position);
307         static_cast<HTMLInputElement*>(node())->onChange();
308     }
309 }
310
311 double RenderSlider::setPositionFromValue(bool inLayout)
312 {
313     if (!m_thumb || !m_thumb->renderer())
314         return 0;
315     
316     if (!inLayout)
317         document()->updateLayout();
318         
319     String value = static_cast<HTMLInputElement*>(node())->value();
320     const AtomicString& minStr = static_cast<HTMLInputElement*>(node())->getAttribute(minAttr);
321     const AtomicString& maxStr = static_cast<HTMLInputElement*>(node())->getAttribute(maxAttr);
322     const AtomicString& precision = static_cast<HTMLInputElement*>(node())->getAttribute(precisionAttr);
323     
324     double minVal = minStr.isNull() ? 0.0 : minStr.toDouble();
325     double maxVal = maxStr.isNull() ? 100.0 : maxStr.toDouble();
326     minVal = min(minVal, maxVal); // Make sure the range is sane.
327     
328     double oldVal = value.isNull() ? (maxVal + minVal)/2.0 : value.toDouble();
329     double val = max(minVal, min(oldVal, maxVal)); // Make sure val is within min/max.
330         
331     // Force integer value if not float.
332     if (!equalIgnoringCase(precision, "float"))
333         val = lround(val);
334
335     // Calculate the new position based on the value
336     double factor = (val - minVal) / (maxVal - minVal);
337     if (style()->appearance() == SliderVerticalPart)
338         factor = 1.0 - factor;
339
340     setCurrentPosition((int)(factor * trackSize()));
341     
342     if (value.isNull() || val != oldVal)
343         static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
344     
345     return val;
346 }
347
348 int RenderSlider::positionForOffset(const IntPoint& p)
349 {
350     if (!m_thumb || !m_thumb->renderer())
351         return 0;
352    
353     int position;
354     if (style()->appearance() == SliderVerticalPart)
355         position = p.y() - m_thumb->renderBox()->height() / 2;
356     else
357         position = p.x() - m_thumb->renderBox()->width() / 2;
358     
359     return max(0, min(position, trackSize()));
360 }
361
362 void RenderSlider::valueChanged()
363 {
364     setValueForPosition(currentPosition());
365     static_cast<HTMLInputElement*>(node())->onChange();
366 }
367
368 int RenderSlider::currentPosition()
369 {
370     if (!m_thumb || !m_thumb->renderer())
371         return 0;
372
373     if (style()->appearance() == SliderVerticalPart)
374         return m_thumb->renderer()->style()->top().value();
375     return m_thumb->renderer()->style()->left().value();
376 }
377
378 void RenderSlider::setCurrentPosition(int pos)
379 {
380     if (!m_thumb || !m_thumb->renderer())
381         return;
382
383     if (style()->appearance() == SliderVerticalPart)
384         m_thumb->renderer()->style()->setTop(Length(pos, Fixed));
385     else
386         m_thumb->renderer()->style()->setLeft(Length(pos, Fixed));
387
388     m_thumb->renderBox()->layer()->updateLayerPosition();
389     repaint();
390     m_thumb->renderer()->repaint();
391 }
392
393 int RenderSlider::trackSize()
394 {
395     if (!m_thumb || !m_thumb->renderer())
396         return 0;
397
398     if (style()->appearance() == SliderVerticalPart)
399         return contentHeight() - m_thumb->renderBox()->height();
400     return contentWidth() - m_thumb->renderBox()->width();
401 }
402
403 void RenderSlider::forwardEvent(Event* evt)
404 {
405     if (evt->isMouseEvent()) {
406         MouseEvent* mouseEvt = static_cast<MouseEvent*>(evt);
407         if (evt->type() == eventNames().mousedownEvent && mouseEvt->button() == LeftButton) {
408             if (!mouseEventIsInThumb(mouseEvt)) {
409                 IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvt->absoluteLocation(), false, true));
410                 setValueForPosition(positionForOffset(eventOffset));
411             }
412         }
413     }
414
415     m_thumb->defaultEventHandler(evt);
416 }
417
418 bool RenderSlider::inDragMode() const
419 {
420     return m_thumb->inDragMode();
421 }
422
423 } // namespace WebCore