3 * Copyright (C) 2006, 2007, 2008 Apple Computer, Inc.
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.
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.
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.
23 #include "RenderSlider.h"
25 #include "CSSPropertyNames.h"
28 #include "EventHandler.h"
29 #include "EventNames.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>
44 using namespace HTMLNames;
46 const int defaultTrackLength = 129;
48 class HTMLSliderThumbElement : public HTMLDivElement {
50 HTMLSliderThumbElement(Document*, Node* shadowParent = 0);
52 virtual void defaultEventHandler(Event*);
53 virtual bool isShadowNode() const { return true; }
54 virtual Node* shadowParentNode() { return m_shadowParent; }
56 bool inDragMode() const { return m_inDragMode; }
59 FloatPoint m_initialClickPoint; // initial click point in RenderSlider-local coordinates
60 int m_initialPosition;
64 HTMLSliderThumbElement::HTMLSliderThumbElement(Document* doc, Node* shadowParent)
65 : HTMLDivElement(divTag, doc)
66 , m_shadowParent(shadowParent)
67 , m_initialClickPoint(IntPoint())
68 , m_initialPosition(0)
73 void HTMLSliderThumbElement::defaultEventHandler(Event* event)
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);
79 if (document()->frame() && renderer() && renderer()->parent() &&
80 (slider = static_cast<RenderSlider*>(renderer()->parent())) &&
81 slider->mouseEventIsInThumb(mouseEvent)) {
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();
89 document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
91 event->setDefaultHandled();
94 } else if (eventType == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
96 if (Frame* frame = document()->frame())
97 frame->eventHandler()->setCapturingMouseEventsNode(0);
99 event->setDefaultHandled();
102 } else if (eventType == eventNames().mousemoveEvent && event->isMouseEvent()) {
103 if (m_inDragMode && renderer() && renderer()->parent()) {
105 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
106 RenderSlider* slider = static_cast<RenderSlider*>(renderer()->parent());
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();
118 event->setDefaultHandled();
123 HTMLDivElement::defaultEventHandler(event);
126 RenderSlider::RenderSlider(HTMLInputElement* element)
127 : RenderBlock(element)
132 RenderSlider::~RenderSlider()
138 int RenderSlider::baselinePosition(bool, bool) const
140 return height() + marginTop();
143 void RenderSlider::calcPrefWidths()
148 if (style()->width().isFixed() && style()->width().value() > 0)
149 m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
151 m_maxPrefWidth = defaultTrackLength * style()->effectiveZoom();
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()))
159 m_minPrefWidth = m_maxPrefWidth;
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()));
166 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
167 m_minPrefWidth += toAdd;
168 m_maxPrefWidth += toAdd;
170 setPrefWidthsDirty(false);
173 void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
175 RenderBlock::styleDidChange(diff, oldStyle);
178 m_thumb->renderer()->setStyle(createThumbStyle(style(), m_thumb->renderer()->style()));
180 setReplaced(isInline());
183 PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle, const RenderStyle* oldStyle)
185 RefPtr<RenderStyle> style;
186 RenderStyle* pseudoStyle = getCachedPseudoStyle(SLIDER_THUMB);
188 // We may be sharing style with another slider, but we must not share the thumb style.
189 style = RenderStyle::clone(pseudoStyle);
191 style = RenderStyle::create();
194 style->inheritFrom(parentStyle);
196 style->setDisplay(BLOCK);
197 style->setPosition(RelativePosition);
199 style->setLeft(oldStyle->left());
200 style->setTop(oldStyle->top());
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);
210 return style.release();
213 void RenderSlider::layout()
215 bool relayoutChildren = false;
217 if (m_thumb && m_thumb->renderer()) {
219 int oldWidth = width();
221 int oldHeight = height();
224 if (oldWidth != width() || oldHeight != height())
225 relayoutChildren = true;
227 // Allow the theme to set the size of the thumb
228 if (m_thumb->renderer()->style()->hasAppearance())
229 theme()->adjustSliderThumbSize(m_thumb->renderer());
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));
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));
239 if (relayoutChildren)
240 setPositionFromValue(true);
243 RenderBlock::layoutBlock(relayoutChildren);
246 void RenderSlider::updateFromElement()
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());
257 setPositionFromValue();
258 setNeedsLayout(true, false);
261 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
263 if (!m_thumb || !m_thumb->renderer())
267 if (style()->appearance() == MediaSliderPart) {
268 MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node());
269 return sliderThumb->hitTest(evt->absoluteLocation());
273 FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
274 IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
275 return thumbBounds.contains(roundedIntPoint(localPoint));
278 void RenderSlider::setValueForPosition(int position)
280 if (!m_thumb || !m_thumb->renderer())
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);
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.
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);
297 val = max(minVal, min(val, maxVal)); // Make sure val is within min/max.
299 // Force integer value if not float.
300 if (!equalIgnoringCase(precision, "float"))
303 static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
305 if (position != currentPosition()) {
306 setCurrentPosition(position);
307 static_cast<HTMLInputElement*>(node())->onChange();
311 double RenderSlider::setPositionFromValue(bool inLayout)
313 if (!m_thumb || !m_thumb->renderer())
317 document()->updateLayout();
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);
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.
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.
331 // Force integer value if not float.
332 if (!equalIgnoringCase(precision, "float"))
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;
340 setCurrentPosition((int)(factor * trackSize()));
342 if (value.isNull() || val != oldVal)
343 static_cast<HTMLInputElement*>(node())->setValueFromRenderer(String::number(val));
348 int RenderSlider::positionForOffset(const IntPoint& p)
350 if (!m_thumb || !m_thumb->renderer())
354 if (style()->appearance() == SliderVerticalPart)
355 position = p.y() - m_thumb->renderBox()->height() / 2;
357 position = p.x() - m_thumb->renderBox()->width() / 2;
359 return max(0, min(position, trackSize()));
362 void RenderSlider::valueChanged()
364 setValueForPosition(currentPosition());
365 static_cast<HTMLInputElement*>(node())->onChange();
368 int RenderSlider::currentPosition()
370 if (!m_thumb || !m_thumb->renderer())
373 if (style()->appearance() == SliderVerticalPart)
374 return m_thumb->renderer()->style()->top().value();
375 return m_thumb->renderer()->style()->left().value();
378 void RenderSlider::setCurrentPosition(int pos)
380 if (!m_thumb || !m_thumb->renderer())
383 if (style()->appearance() == SliderVerticalPart)
384 m_thumb->renderer()->style()->setTop(Length(pos, Fixed));
386 m_thumb->renderer()->style()->setLeft(Length(pos, Fixed));
388 m_thumb->renderBox()->layer()->updateLayerPosition();
390 m_thumb->renderer()->repaint();
393 int RenderSlider::trackSize()
395 if (!m_thumb || !m_thumb->renderer())
398 if (style()->appearance() == SliderVerticalPart)
399 return contentHeight() - m_thumb->renderBox()->height();
400 return contentWidth() - m_thumb->renderBox()->width();
403 void RenderSlider::forwardEvent(Event* evt)
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));
415 m_thumb->defaultEventHandler(evt);
418 bool RenderSlider::inDragMode() const
420 return m_thumb->inDragMode();
423 } // namespace WebCore