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