• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20 
21 #include "config.h"
22 #include "RenderSlider.h"
23 
24 #include "CSSPropertyNames.h"
25 #include "Document.h"
26 #include "Event.h"
27 #include "EventHandler.h"
28 #include "EventNames.h"
29 #include "Frame.h"
30 #include "HTMLInputElement.h"
31 #include "HTMLDivElement.h"
32 #include "HTMLNames.h"
33 #include "MediaControlElements.h"
34 #include "MouseEvent.h"
35 #include "RenderLayer.h"
36 #include "RenderTheme.h"
37 #include "RenderView.h"
38 #include <wtf/MathExtras.h>
39 
40 #ifdef ANDROID_LAYOUT
41 #include "Settings.h"
42 #endif
43 
44 using std::min;
45 
46 namespace WebCore {
47 
48 using namespace HTMLNames;
49 
50 static const int defaultTrackLength = 129;
51 
52 // FIXME: The SliderRange class and functions are entirely based on the DOM,
53 // and could be put with HTMLInputElement (possibly with a new name) instead of here.
54 struct SliderRange {
55     bool hasStep;
56     double step;
57     double minimum;
58     double maximum;  // maximum must be >= minimum.
59 
60     explicit SliderRange(HTMLInputElement*);
61     double clampValue(double value);
62 
63     // Map value into 0-1 range
proportionFromValueWebCore::SliderRange64     double proportionFromValue(double value)
65     {
66         if (minimum == maximum)
67             return 0;
68 
69         return (value - minimum) / (maximum - minimum);
70     }
71 
72     // Map from 0-1 range to value
valueFromProportionWebCore::SliderRange73     double valueFromProportion(double proportion)
74     {
75         return minimum + proportion * (maximum - minimum);
76     }
77 
78     double valueFromElement(HTMLInputElement*, bool* wasClamped = 0);
79 };
80 
SliderRange(HTMLInputElement * element)81 SliderRange::SliderRange(HTMLInputElement* element)
82 {
83     if (element->hasAttribute(precisionAttr)) {
84         step = 1.0;
85         hasStep = !equalIgnoringCase(element->getAttribute(precisionAttr), "float");
86     } else
87         hasStep = element->getAllowedValueStep(&step);
88 
89     maximum = element->maximum();
90     minimum = element->minimum();
91 }
92 
clampValue(double value)93 double SliderRange::clampValue(double value)
94 {
95     double clampedValue = max(minimum, min(value, maximum));
96     if (!hasStep)
97         return clampedValue;
98     // Rounds clampedValue to minimum + N * step.
99     clampedValue = minimum + round((clampedValue - minimum) / step) * step;
100     if (clampedValue > maximum)
101        clampedValue -= step;
102     ASSERT(clampedValue >= minimum);
103     ASSERT(clampedValue <= maximum);
104     return clampedValue;
105 }
106 
valueFromElement(HTMLInputElement * element,bool * wasClamped)107 double SliderRange::valueFromElement(HTMLInputElement* element, bool* wasClamped)
108 {
109     double oldValue;
110     bool parseSuccess = HTMLInputElement::formStringToDouble(element->value(), &oldValue);
111     if (!parseSuccess)
112         oldValue = (minimum + maximum) / 2;
113     double newValue = clampValue(oldValue);
114 
115     if (wasClamped)
116         *wasClamped = !parseSuccess || newValue != oldValue;
117 
118     return newValue;
119 }
120 
121 // Returns a value between 0 and 1.
122 // As with SliderRange, this could be on HTMLInputElement instead of here.
sliderPosition(HTMLInputElement * element)123 static double sliderPosition(HTMLInputElement* element)
124 {
125     SliderRange range(element);
126     return range.proportionFromValue(range.valueFromElement(element));
127 }
128 
129 class SliderThumbElement : public HTMLDivElement {
130 public:
131     SliderThumbElement(Document*, Node* shadowParent);
132 
inDragMode() const133     bool inDragMode() const { return m_inDragMode; }
134 
135     virtual void defaultEventHandler(Event*);
136     virtual void detach();
137 
138 private:
isShadowNode() const139     virtual bool isShadowNode() const { return true; }
shadowParentNode()140     virtual Node* shadowParentNode() { return m_shadowParent; }
141 
142     FloatPoint m_offsetToThumb;
143     Node* m_shadowParent;
144     bool m_inDragMode;
145 };
146 
SliderThumbElement(Document * document,Node * shadowParent)147 SliderThumbElement::SliderThumbElement(Document* document, Node* shadowParent)
148     : HTMLDivElement(divTag, document)
149     , m_shadowParent(shadowParent)
150     , m_inDragMode(false)
151 {
152 }
153 
defaultEventHandler(Event * event)154 void SliderThumbElement::defaultEventHandler(Event* event)
155 {
156     if (!event->isMouseEvent()) {
157         HTMLDivElement::defaultEventHandler(event);
158         return;
159     }
160 
161     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
162     bool isLeftButton = mouseEvent->button() == LeftButton;
163     const AtomicString& eventType = event->type();
164 
165     if (eventType == eventNames().mousedownEvent && isLeftButton) {
166         if (document()->frame() && renderer()) {
167             RenderSlider* slider = toRenderSlider(renderer()->parent());
168             if (slider) {
169                 if (slider->mouseEventIsInThumb(mouseEvent)) {
170                     // We selected the thumb, we want the cursor to always stay at
171                     // the same position relative to the thumb.
172                     m_offsetToThumb = slider->mouseEventOffsetToThumb(mouseEvent);
173                 } else {
174                     // We are outside the thumb, move the thumb to the point were
175                     // we clicked. We'll be exactly at the center of the thumb.
176                     m_offsetToThumb.setX(0);
177                     m_offsetToThumb.setY(0);
178                 }
179 
180                 m_inDragMode = true;
181                 document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
182                 event->setDefaultHandled();
183                 return;
184             }
185         }
186     } else if (eventType == eventNames().mouseupEvent && isLeftButton) {
187         if (m_inDragMode) {
188             if (Frame* frame = document()->frame())
189                 frame->eventHandler()->setCapturingMouseEventsNode(0);
190             m_inDragMode = false;
191             event->setDefaultHandled();
192             return;
193         }
194     } else if (eventType == eventNames().mousemoveEvent) {
195         if (m_inDragMode && renderer() && renderer()->parent()) {
196             RenderSlider* slider = toRenderSlider(renderer()->parent());
197             if (slider) {
198                 FloatPoint curPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true);
199                 IntPoint eventOffset(curPoint.x() + m_offsetToThumb.x(), curPoint.y() + m_offsetToThumb.y());
200                 slider->setValueForPosition(slider->positionForOffset(eventOffset));
201                 event->setDefaultHandled();
202                 return;
203             }
204         }
205     }
206 
207     HTMLDivElement::defaultEventHandler(event);
208 }
209 
detach()210 void SliderThumbElement::detach()
211 {
212     if (m_inDragMode) {
213         if (Frame* frame = document()->frame())
214             frame->eventHandler()->setCapturingMouseEventsNode(0);
215     }
216     HTMLDivElement::detach();
217 }
218 
RenderSlider(HTMLInputElement * element)219 RenderSlider::RenderSlider(HTMLInputElement* element)
220     : RenderBlock(element)
221 {
222 }
223 
~RenderSlider()224 RenderSlider::~RenderSlider()
225 {
226     if (m_thumb)
227         m_thumb->detach();
228 }
229 
baselinePosition(bool,bool) const230 int RenderSlider::baselinePosition(bool, bool) const
231 {
232     return height() + marginTop();
233 }
234 
calcPrefWidths()235 void RenderSlider::calcPrefWidths()
236 {
237     m_minPrefWidth = 0;
238     m_maxPrefWidth = 0;
239 
240     if (style()->width().isFixed() && style()->width().value() > 0)
241         m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
242     else
243         m_maxPrefWidth = defaultTrackLength * style()->effectiveZoom();
244 
245     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
246         m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
247         m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
248     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
249         m_minPrefWidth = 0;
250     else
251         m_minPrefWidth = m_maxPrefWidth;
252 
253     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
254         m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
255         m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
256     }
257 
258     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
259     m_minPrefWidth += toAdd;
260     m_maxPrefWidth += toAdd;
261 
262     setPrefWidthsDirty(false);
263 }
264 
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)265 void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
266 {
267     RenderBlock::styleDidChange(diff, oldStyle);
268 
269     if (m_thumb)
270         m_thumb->renderer()->setStyle(createThumbStyle(style()));
271 
272     setReplaced(isInline());
273 }
274 
createThumbStyle(const RenderStyle * parentStyle)275 PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle)
276 {
277     RefPtr<RenderStyle> style;
278     RenderStyle* pseudoStyle = getCachedPseudoStyle(SLIDER_THUMB);
279     if (pseudoStyle)
280         // We may be sharing style with another slider, but we must not share the thumb style.
281         style = RenderStyle::clone(pseudoStyle);
282     else
283         style = RenderStyle::create();
284 
285     if (parentStyle)
286         style->inheritFrom(parentStyle);
287 
288     style->setDisplay(BLOCK);
289 
290     if (parentStyle->appearance() == SliderVerticalPart)
291         style->setAppearance(SliderThumbVerticalPart);
292     else if (parentStyle->appearance() == SliderHorizontalPart)
293         style->setAppearance(SliderThumbHorizontalPart);
294     else if (parentStyle->appearance() == MediaSliderPart)
295         style->setAppearance(MediaSliderThumbPart);
296     else if (parentStyle->appearance() == MediaVolumeSliderPart)
297         style->setAppearance(MediaVolumeSliderThumbPart);
298 
299     return style.release();
300 }
301 
thumbRect()302 IntRect RenderSlider::thumbRect()
303 {
304     if (!m_thumb)
305         return IntRect();
306 
307     IntRect thumbRect;
308     RenderBox* thumb = toRenderBox(m_thumb->renderer());
309 
310     thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth()));
311     thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight()));
312 
313     double fraction = sliderPosition(static_cast<HTMLInputElement*>(node()));
314     IntRect contentRect = contentBoxRect();
315     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) {
316         thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2);
317         thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction)));
318     } else {
319         thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction));
320         thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2);
321     }
322 
323     return thumbRect;
324 }
325 
layout()326 void RenderSlider::layout()
327 {
328     ASSERT(needsLayout());
329 
330     RenderBox* thumb = m_thumb ? toRenderBox(m_thumb->renderer()) : 0;
331 
332     IntSize baseSize(borderLeft() + paddingLeft() + paddingRight() + borderRight(),
333         borderTop() + paddingTop() + paddingBottom() + borderBottom());
334 
335     if (thumb) {
336         // Allow the theme to set the size of the thumb.
337         if (thumb->style()->hasAppearance()) {
338             // FIXME: This should pass the style, not the renderer, to the theme.
339             theme()->adjustSliderThumbSize(thumb);
340         }
341 
342         baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0));
343     }
344 
345     LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
346 
347     IntSize oldSize = size();
348 
349     setSize(baseSize);
350     calcWidth();
351     calcHeight();
352 
353     if (thumb) {
354         if (oldSize != size())
355             thumb->setChildNeedsLayout(true, false);
356 
357         LayoutStateMaintainer statePusher(view(), this, size());
358 
359         IntRect oldThumbRect = thumb->frameRect();
360 
361         thumb->layoutIfNeeded();
362 
363         IntRect rect = thumbRect();
364         thumb->setFrameRect(rect);
365         if (thumb->checkForRepaintDuringLayout())
366             thumb->repaintDuringLayoutIfMoved(oldThumbRect);
367 
368         statePusher.pop();
369         addOverflowFromChild(thumb);
370     }
371 
372     repainter.repaintAfterLayout();
373 
374     setNeedsLayout(false);
375 }
376 
updateFromElement()377 void RenderSlider::updateFromElement()
378 {
379     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
380 
381     // Send the value back to the element if the range changes it.
382     SliderRange range(element);
383     bool clamped;
384     double value = range.valueFromElement(element, &clamped);
385     if (clamped)
386         element->setValueFromRenderer(HTMLInputElement::formStringFromDouble(value));
387 
388     // Layout will take care of the thumb's size and position.
389     if (!m_thumb) {
390         m_thumb = new SliderThumbElement(document(), node());
391         RefPtr<RenderStyle> thumbStyle = createThumbStyle(style());
392         m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get()));
393         m_thumb->renderer()->setStyle(thumbStyle.release());
394         m_thumb->setAttached();
395         m_thumb->setInDocument(true);
396         addChild(m_thumb->renderer());
397     }
398     setNeedsLayout(true);
399 }
400 
mouseEventIsInThumb(MouseEvent * evt)401 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
402 {
403     if (!m_thumb || !m_thumb->renderer())
404         return false;
405 
406 #if ENABLE(VIDEO)
407     if (style()->appearance() == MediaSliderPart || style()->appearance() == MediaVolumeSliderPart) {
408         MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node());
409         return sliderThumb->hitTest(evt->absoluteLocation());
410     }
411 #endif
412 
413     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
414     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
415     return thumbBounds.contains(roundedIntPoint(localPoint));
416 }
417 
mouseEventOffsetToThumb(MouseEvent * evt)418 FloatPoint RenderSlider::mouseEventOffsetToThumb(MouseEvent* evt)
419 {
420     ASSERT(m_thumb && m_thumb->renderer());
421     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
422     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
423     FloatPoint offset;
424     offset.setX(thumbBounds.x() + thumbBounds.width() / 2 - localPoint.x());
425     offset.setY(thumbBounds.y() + thumbBounds.height() / 2 - localPoint.y());
426     return offset;
427 }
428 
setValueForPosition(int position)429 void RenderSlider::setValueForPosition(int position)
430 {
431     if (!m_thumb || !m_thumb->renderer())
432         return;
433 
434     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
435 
436     // Calculate the new value based on the position, and send it to the element.
437     SliderRange range(element);
438     double fraction = static_cast<double>(position) / trackSize();
439     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
440         fraction = 1 - fraction;
441     double value = range.clampValue(range.valueFromProportion(fraction));
442     element->setValueFromRenderer(HTMLInputElement::formStringFromDouble(value));
443 
444     // Also update the position if appropriate.
445     if (position != currentPosition()) {
446         setNeedsLayout(true);
447 
448         // FIXME: It seems like this could send extra change events if the same value is set
449         // multiple times with no layout in between.
450         element->dispatchFormControlChangeEvent();
451     }
452 }
453 
positionForOffset(const IntPoint & p)454 int RenderSlider::positionForOffset(const IntPoint& p)
455 {
456     if (!m_thumb || !m_thumb->renderer())
457         return 0;
458 
459     int position;
460     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
461         position = p.y() - m_thumb->renderBox()->height() / 2;
462     else
463         position = p.x() - m_thumb->renderBox()->width() / 2;
464 
465     return max(0, min(position, trackSize()));
466 }
467 
currentPosition()468 int RenderSlider::currentPosition()
469 {
470     ASSERT(m_thumb);
471     ASSERT(m_thumb->renderer());
472 
473     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
474         return toRenderBox(m_thumb->renderer())->y() - contentBoxRect().y();
475     return toRenderBox(m_thumb->renderer())->x() - contentBoxRect().x();
476 }
477 
trackSize()478 int RenderSlider::trackSize()
479 {
480     ASSERT(m_thumb);
481     ASSERT(m_thumb->renderer());
482 
483     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
484         return contentHeight() - m_thumb->renderBox()->height();
485     return contentWidth() - m_thumb->renderBox()->width();
486 }
487 
forwardEvent(Event * event)488 void RenderSlider::forwardEvent(Event* event)
489 {
490     if (event->isMouseEvent()) {
491         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
492         if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) {
493             if (!mouseEventIsInThumb(mouseEvent)) {
494                 IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
495                 setValueForPosition(positionForOffset(eventOffset));
496             }
497         }
498     }
499 
500     m_thumb->defaultEventHandler(event);
501 }
502 
inDragMode() const503 bool RenderSlider::inDragMode() const
504 {
505     return m_thumb && m_thumb->inDragMode();
506 }
507 
508 } // namespace WebCore
509