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