• 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 isIntegral;
56     double minimum;
57     double maximum;
58 
59     explicit SliderRange(HTMLInputElement*);
60     double clampValue(double value);
61 
62     // Map value into 0-1 range
proportionFromValueWebCore::SliderRange63     double proportionFromValue(double value)
64     {
65         if (minimum == maximum)
66             return 0;
67 
68         return (value - minimum) / (maximum - minimum);
69     }
70 
71     // Map from 0-1 range to value
valueFromProportionWebCore::SliderRange72     double valueFromProportion(double proportion)
73     {
74         return minimum + proportion * (maximum - minimum);
75     }
76 
77     double valueFromElement(HTMLInputElement*, bool* wasClamped = 0);
78 };
79 
SliderRange(HTMLInputElement * element)80 SliderRange::SliderRange(HTMLInputElement* element)
81 {
82     // FIXME: What's the right way to handle an integral range with non-integral minimum and maximum?
83     // Currently values are guaranteed to be integral but could be outside the range in that case.
84 
85     isIntegral = !equalIgnoringCase(element->getAttribute(precisionAttr), "float");
86 
87     // FIXME: This treats maximum strings that can't be parsed as 0, but perhaps 100 would be more appropriate.
88     const AtomicString& maxString = element->getAttribute(maxAttr);
89     maximum = maxString.isNull() ? 100.0 : maxString.toDouble();
90 
91     // If the maximum is smaller, use it as the minimum.
92     minimum = min(element->getAttribute(minAttr).toDouble(), maximum);
93 }
94 
clampValue(double value)95 double SliderRange::clampValue(double value)
96 {
97     double clampedValue = max(minimum, min(value, maximum));
98     return isIntegral ? round(clampedValue) : clampedValue;
99 }
100 
valueFromElement(HTMLInputElement * element,bool * wasClamped)101 double SliderRange::valueFromElement(HTMLInputElement* element, bool* wasClamped)
102 {
103     String valueString = element->value();
104     double oldValue = valueString.isNull() ? (minimum + maximum) / 2 : valueString.toDouble();
105     double newValue = clampValue(oldValue);
106 
107     if (wasClamped)
108         *wasClamped = valueString.isNull() || newValue != oldValue;
109 
110     return newValue;
111 }
112 
113 // Returns a value between 0 and 1.
114 // As with SliderRange, this could be on HTMLInputElement instead of here.
sliderPosition(HTMLInputElement * element)115 static double sliderPosition(HTMLInputElement* element)
116 {
117     SliderRange range(element);
118     return range.proportionFromValue(range.valueFromElement(element));
119 }
120 
121 class SliderThumbElement : public HTMLDivElement {
122 public:
123     SliderThumbElement(Document*, Node* shadowParent);
124 
inDragMode() const125     bool inDragMode() const { return m_inDragMode; }
126 
127     virtual void defaultEventHandler(Event*);
128     virtual void detach();
129 
130 private:
isShadowNode() const131     virtual bool isShadowNode() const { return true; }
shadowParentNode()132     virtual Node* shadowParentNode() { return m_shadowParent; }
133 
134     FloatPoint m_offsetToThumb;
135     Node* m_shadowParent;
136     bool m_inDragMode;
137 };
138 
SliderThumbElement(Document * document,Node * shadowParent)139 SliderThumbElement::SliderThumbElement(Document* document, Node* shadowParent)
140     : HTMLDivElement(divTag, document)
141     , m_shadowParent(shadowParent)
142     , m_inDragMode(false)
143 {
144 }
145 
defaultEventHandler(Event * event)146 void SliderThumbElement::defaultEventHandler(Event* event)
147 {
148     if (!event->isMouseEvent()) {
149         HTMLDivElement::defaultEventHandler(event);
150         return;
151     }
152 
153     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
154     bool isLeftButton = mouseEvent->button() == LeftButton;
155     const AtomicString& eventType = event->type();
156 
157     if (eventType == eventNames().mousedownEvent && isLeftButton) {
158         if (document()->frame() && renderer()) {
159             RenderSlider* slider = toRenderSlider(renderer()->parent());
160             if (slider) {
161                 if (slider->mouseEventIsInThumb(mouseEvent)) {
162                     // We selected the thumb, we want the cursor to always stay at
163                     // the same position relative to the thumb.
164                     m_offsetToThumb = slider->mouseEventOffsetToThumb(mouseEvent);
165                 } else {
166                     // We are outside the thumb, move the thumb to the point were
167                     // we clicked. We'll be exactly at the center of the thumb.
168                     m_offsetToThumb.setX(0);
169                     m_offsetToThumb.setY(0);
170                 }
171 
172                 m_inDragMode = true;
173                 document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
174                 event->setDefaultHandled();
175                 return;
176             }
177         }
178     } else if (eventType == eventNames().mouseupEvent && isLeftButton) {
179         if (m_inDragMode) {
180             if (Frame* frame = document()->frame())
181                 frame->eventHandler()->setCapturingMouseEventsNode(0);
182             m_inDragMode = false;
183             event->setDefaultHandled();
184             return;
185         }
186     } else if (eventType == eventNames().mousemoveEvent) {
187         if (m_inDragMode && renderer() && renderer()->parent()) {
188             RenderSlider* slider = toRenderSlider(renderer()->parent());
189             if (slider) {
190                 FloatPoint curPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true);
191                 IntPoint eventOffset(curPoint.x() + m_offsetToThumb.x(), curPoint.y() + m_offsetToThumb.y());
192                 slider->setValueForPosition(slider->positionForOffset(eventOffset));
193                 event->setDefaultHandled();
194                 return;
195             }
196         }
197     }
198 
199     HTMLDivElement::defaultEventHandler(event);
200 }
201 
detach()202 void SliderThumbElement::detach()
203 {
204     if (m_inDragMode) {
205         if (Frame* frame = document()->frame())
206             frame->eventHandler()->setCapturingMouseEventsNode(0);
207     }
208     HTMLDivElement::detach();
209 }
210 
RenderSlider(HTMLInputElement * element)211 RenderSlider::RenderSlider(HTMLInputElement* element)
212     : RenderBlock(element)
213 {
214 }
215 
~RenderSlider()216 RenderSlider::~RenderSlider()
217 {
218     if (m_thumb)
219         m_thumb->detach();
220 }
221 
baselinePosition(bool,bool) const222 int RenderSlider::baselinePosition(bool, bool) const
223 {
224     return height() + marginTop();
225 }
226 
calcPrefWidths()227 void RenderSlider::calcPrefWidths()
228 {
229     m_minPrefWidth = 0;
230     m_maxPrefWidth = 0;
231 
232     if (style()->width().isFixed() && style()->width().value() > 0)
233         m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
234     else
235         m_maxPrefWidth = defaultTrackLength * style()->effectiveZoom();
236 
237     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
238         m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
239         m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
240     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
241         m_minPrefWidth = 0;
242     else
243         m_minPrefWidth = m_maxPrefWidth;
244 
245     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
246         m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
247         m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
248     }
249 
250     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
251     m_minPrefWidth += toAdd;
252     m_maxPrefWidth += toAdd;
253 
254     setPrefWidthsDirty(false);
255 }
256 
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)257 void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
258 {
259     RenderBlock::styleDidChange(diff, oldStyle);
260 
261     if (m_thumb)
262         m_thumb->renderer()->setStyle(createThumbStyle(style()));
263 
264     setReplaced(isInline());
265 }
266 
createThumbStyle(const RenderStyle * parentStyle)267 PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle)
268 {
269     RefPtr<RenderStyle> style;
270     RenderStyle* pseudoStyle = getCachedPseudoStyle(SLIDER_THUMB);
271     if (pseudoStyle)
272         // We may be sharing style with another slider, but we must not share the thumb style.
273         style = RenderStyle::clone(pseudoStyle);
274     else
275         style = RenderStyle::create();
276 
277     if (parentStyle)
278         style->inheritFrom(parentStyle);
279 
280     style->setDisplay(BLOCK);
281 
282     if (parentStyle->appearance() == SliderVerticalPart)
283         style->setAppearance(SliderThumbVerticalPart);
284     else if (parentStyle->appearance() == SliderHorizontalPart)
285         style->setAppearance(SliderThumbHorizontalPart);
286     else if (parentStyle->appearance() == MediaSliderPart)
287         style->setAppearance(MediaSliderThumbPart);
288 
289     return style.release();
290 }
291 
thumbRect()292 IntRect RenderSlider::thumbRect()
293 {
294     if (!m_thumb)
295         return IntRect();
296 
297     IntRect thumbRect;
298     RenderBox* thumb = toRenderBox(m_thumb->renderer());
299 
300     thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth()));
301     thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight()));
302 
303     double fraction = sliderPosition(static_cast<HTMLInputElement*>(node()));
304     IntRect contentRect = contentBoxRect();
305     if (style()->appearance() == SliderVerticalPart) {
306         thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2);
307         thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction)));
308     } else {
309         thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction));
310         thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2);
311     }
312 
313     return thumbRect;
314 }
315 
layout()316 void RenderSlider::layout()
317 {
318     ASSERT(needsLayout());
319 
320     RenderBox* thumb = m_thumb ? toRenderBox(m_thumb->renderer()) : 0;
321 
322     IntSize baseSize(borderLeft() + paddingLeft() + paddingRight() + borderRight(),
323         borderTop() + paddingTop() + paddingBottom() + borderBottom());
324 
325     if (thumb) {
326         // Allow the theme to set the size of the thumb.
327         if (thumb->style()->hasAppearance()) {
328             // FIXME: This should pass the style, not the renderer, to the theme.
329             theme()->adjustSliderThumbSize(thumb);
330         }
331 
332         baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0));
333     }
334 
335     LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
336 
337     IntSize oldSize = size();
338 
339     setSize(baseSize);
340     calcWidth();
341     calcHeight();
342 
343     IntRect overflowRect(IntPoint(), size());
344 
345     if (thumb) {
346         if (oldSize != size())
347             thumb->setChildNeedsLayout(true, false);
348 
349         LayoutStateMaintainer statePusher(view(), this, size());
350 
351         IntRect oldThumbRect = thumb->frameRect();
352 
353         thumb->layoutIfNeeded();
354 
355         IntRect rect = thumbRect();
356         thumb->setFrameRect(rect);
357         if (thumb->checkForRepaintDuringLayout())
358             thumb->repaintDuringLayoutIfMoved(oldThumbRect);
359 
360         statePusher.pop();
361 
362         IntRect thumbOverflowRect = thumb->overflowRect();
363         thumbOverflowRect.move(thumb->x(), thumb->y());
364         overflowRect.unite(thumbOverflowRect);
365     }
366 
367     // FIXME: m_overflowWidth and m_overflowHeight should be renamed
368     // m_overflowRight and m_overflowBottom.
369     m_overflowLeft = overflowRect.x();
370     m_overflowTop = overflowRect.y();
371     m_overflowWidth = overflowRect.right();
372     m_overflowHeight = overflowRect.bottom();
373 
374     repainter.repaintAfterLayout();
375 
376     setNeedsLayout(false);
377 }
378 
updateFromElement()379 void RenderSlider::updateFromElement()
380 {
381     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
382 
383     // Send the value back to the element if the range changes it.
384     SliderRange range(element);
385     bool clamped;
386     double value = range.valueFromElement(element, &clamped);
387     if (clamped)
388         element->setValueFromRenderer(String::number(value));
389 
390     // Layout will take care of the thumb's size and position.
391     if (!m_thumb) {
392         m_thumb = new SliderThumbElement(document(), node());
393         RefPtr<RenderStyle> thumbStyle = createThumbStyle(style());
394         m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get()));
395         m_thumb->renderer()->setStyle(thumbStyle.release());
396         m_thumb->setAttached();
397         m_thumb->setInDocument(true);
398         addChild(m_thumb->renderer());
399     }
400     setNeedsLayout(true);
401 }
402 
mouseEventIsInThumb(MouseEvent * evt)403 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
404 {
405     if (!m_thumb || !m_thumb->renderer())
406         return false;
407 
408 #if ENABLE(VIDEO)
409     if (style()->appearance() == MediaSliderPart) {
410         MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node());
411         return sliderThumb->hitTest(evt->absoluteLocation());
412     }
413 #endif
414 
415     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
416     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
417     return thumbBounds.contains(roundedIntPoint(localPoint));
418 }
419 
mouseEventOffsetToThumb(MouseEvent * evt)420 FloatPoint RenderSlider::mouseEventOffsetToThumb(MouseEvent* evt)
421 {
422     ASSERT(m_thumb && m_thumb->renderer());
423     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
424     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
425     FloatPoint offset;
426     offset.setX(thumbBounds.x() + thumbBounds.width() / 2 - localPoint.x());
427     offset.setY(thumbBounds.y() + thumbBounds.height() / 2 - localPoint.y());
428     return offset;
429 }
430 
setValueForPosition(int position)431 void RenderSlider::setValueForPosition(int position)
432 {
433     if (!m_thumb || !m_thumb->renderer())
434         return;
435 
436     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
437 
438     // Calculate the new value based on the position, and send it to the element.
439     SliderRange range(element);
440     double fraction = static_cast<double>(position) / trackSize();
441     if (style()->appearance() == SliderVerticalPart)
442         fraction = 1 - fraction;
443     double value = range.clampValue(range.valueFromProportion(fraction));
444     element->setValueFromRenderer(String::number(value));
445 
446     // Also update the position if appropriate.
447     if (position != currentPosition()) {
448         setNeedsLayout(true);
449 
450         // FIXME: It seems like this could send extra change events if the same value is set
451         // multiple times with no layout in between.
452         element->dispatchFormControlChangeEvent();
453     }
454 }
455 
positionForOffset(const IntPoint & p)456 int RenderSlider::positionForOffset(const IntPoint& p)
457 {
458     if (!m_thumb || !m_thumb->renderer())
459         return 0;
460 
461     int position;
462     if (style()->appearance() == SliderVerticalPart)
463         position = p.y() - m_thumb->renderBox()->height() / 2;
464     else
465         position = p.x() - m_thumb->renderBox()->width() / 2;
466 
467     return max(0, min(position, trackSize()));
468 }
469 
currentPosition()470 int RenderSlider::currentPosition()
471 {
472     ASSERT(m_thumb);
473     ASSERT(m_thumb->renderer());
474 
475     if (style()->appearance() == SliderVerticalPart)
476         return toRenderBox(m_thumb->renderer())->y() - contentBoxRect().y();
477     return toRenderBox(m_thumb->renderer())->x() - contentBoxRect().x();
478 }
479 
trackSize()480 int RenderSlider::trackSize()
481 {
482     ASSERT(m_thumb);
483     ASSERT(m_thumb->renderer());
484 
485     if (style()->appearance() == SliderVerticalPart)
486         return contentHeight() - m_thumb->renderBox()->height();
487     return contentWidth() - m_thumb->renderBox()->width();
488 }
489 
forwardEvent(Event * event)490 void RenderSlider::forwardEvent(Event* event)
491 {
492     if (event->isMouseEvent()) {
493         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
494         if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) {
495             if (!mouseEventIsInThumb(mouseEvent)) {
496                 IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
497                 setValueForPosition(positionForOffset(eventOffset));
498             }
499         }
500     }
501 
502     m_thumb->defaultEventHandler(event);
503 }
504 
inDragMode() const505 bool RenderSlider::inDragMode() const
506 {
507     return m_thumb && m_thumb->inDragMode();
508 }
509 
510 } // namespace WebCore
511