SliderThumbElement.cpp revision 6fbaea61d241814b015fd7e022796e68d8ef3e8e
1/*
2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32
33#include "config.h"
34#include "SliderThumbElement.h"
35
36#include "Event.h"
37#include "Frame.h"
38#include "HTMLInputElement.h"
39#include "HTMLParserIdioms.h"
40#include "MouseEvent.h"
41#include "RenderSlider.h"
42#include "RenderTheme.h"
43#include "StepRange.h"
44#include <wtf/MathExtras.h>
45
46#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
47#include "Page.h"
48#include "TouchEvent.h"
49#endif
50
51using namespace std;
52
53namespace WebCore {
54
55// FIXME: Find a way to cascade appearance (see the layout method) and get rid of this class.
56class RenderSliderThumb : public RenderBlock {
57public:
58    RenderSliderThumb(Node*);
59    virtual void layout();
60};
61
62RenderSliderThumb::RenderSliderThumb(Node* node)
63    : RenderBlock(node)
64{
65}
66
67void RenderSliderThumb::layout()
68{
69    // FIXME: Hard-coding this cascade of appearance is bad, because it's something
70    // that CSS usually does. We need to find a way to express this in CSS.
71    RenderStyle* parentStyle = parent()->style();
72    if (parentStyle->appearance() == SliderVerticalPart)
73        style()->setAppearance(SliderThumbVerticalPart);
74    else if (parentStyle->appearance() == SliderHorizontalPart)
75        style()->setAppearance(SliderThumbHorizontalPart);
76    else if (parentStyle->appearance() == MediaSliderPart)
77        style()->setAppearance(MediaSliderThumbPart);
78    else if (parentStyle->appearance() == MediaVolumeSliderPart)
79        style()->setAppearance(MediaVolumeSliderThumbPart);
80
81    if (style()->hasAppearance()) {
82        // FIXME: This should pass the style, not the renderer, to the theme.
83        theme()->adjustSliderThumbSize(this);
84    }
85    RenderBlock::layout();
86}
87
88RenderObject* SliderThumbElement::createRenderer(RenderArena* arena, RenderStyle*)
89{
90    return new (arena) RenderSliderThumb(this);
91}
92
93void SliderThumbElement::dragFrom(const IntPoint& point)
94{
95    setPosition(point);
96    startDragging();
97}
98
99void SliderThumbElement::setPosition(const IntPoint& point)
100{
101    HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowHost());
102    ASSERT(input);
103
104    if (!input->renderer() || !renderer())
105        return;
106
107    IntPoint offset = roundedIntPoint(input->renderer()->absoluteToLocal(point, false, true));
108    RenderStyle* sliderStyle = input->renderer()->style();
109    bool isVertical = sliderStyle->appearance() == SliderVerticalPart || sliderStyle->appearance() == MediaVolumeSliderPart;
110
111    int trackSize;
112    int position;
113    int currentPosition;
114    if (isVertical) {
115        trackSize = input->renderBox()->contentHeight() - renderBox()->height();
116        position = offset.y() - renderBox()->height() / 2;
117        currentPosition = renderBox()->y() - input->renderBox()->contentBoxRect().y();
118    } else {
119        trackSize = input->renderBox()->contentWidth() - renderBox()->width();
120        position = offset.x() - renderBox()->width() / 2;
121        currentPosition = renderBox()->x() - input->renderBox()->contentBoxRect().x();
122    }
123    position = max(0, min(position, trackSize));
124    if (position == currentPosition)
125        return;
126
127    StepRange range(input);
128    double fraction = static_cast<double>(position) / trackSize;
129    if (isVertical)
130        fraction = 1 - fraction;
131    double value = range.clampValue(range.valueFromProportion(fraction));
132
133    // FIXME: This is no longer being set from renderer. Consider updating the method name.
134    input->setValueFromRenderer(serializeForNumberType(value));
135    renderer()->setNeedsLayout(true);
136    input->dispatchFormControlChangeEvent();
137}
138
139void SliderThumbElement::startDragging()
140{
141    if (Frame* frame = document()->frame()) {
142        frame->eventHandler()->setCapturingMouseEventsNode(this);
143#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
144        // Touch events come from Java to the main frame event handler, so we need
145        // to flag we are capturing those events also on the main frame event
146        // handler.
147        frame->page()->mainFrame()->eventHandler()->setCapturingTouchEventsNode(this);
148#endif
149        m_inDragMode = true;
150    }
151}
152
153void SliderThumbElement::stopDragging()
154{
155    if (!m_inDragMode)
156        return;
157
158    if (Frame* frame = document()->frame())
159        frame->eventHandler()->setCapturingMouseEventsNode(0);
160
161#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
162    if (Frame* frame = document()->frame())
163        frame->page()->mainFrame()->eventHandler()->setCapturingTouchEventsNode(0);
164#endif
165    m_inDragMode = false;
166}
167
168void SliderThumbElement::defaultEventHandler(Event* event)
169{
170    if (!event->isMouseEvent()
171#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
172        && !event->isTouchEvent()
173#endif
174        ) {
175        HTMLDivElement::defaultEventHandler(event);
176        return;
177    }
178
179#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
180    bool isLeftButton = false;
181
182    if (event->isMouseEvent()) {
183        MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
184        isLeftButton = mouseEvent->button() == LeftButton;
185    }
186#else
187    MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
188    bool isLeftButton = mouseEvent->button() == LeftButton;
189#endif
190    const AtomicString& eventType = event->type();
191
192    if (eventType == eventNames().mousedownEvent && isLeftButton
193#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
194            || eventType == eventNames().touchstartEvent
195#endif
196            ) {
197        startDragging();
198        return;
199    } else if (eventType == eventNames().mouseupEvent && isLeftButton
200#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
201            || eventType == eventNames().touchendEvent
202            || eventType == eventNames().touchcancelEvent
203#endif
204            ) {
205        stopDragging();
206        return;
207    } else if (eventType == eventNames().mousemoveEvent
208#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
209            || eventType == eventNames().touchmoveEvent
210#endif
211            ) {
212        if (m_inDragMode)
213#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
214        {
215            if (event->isMouseEvent()) {
216                MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
217#endif
218            setPosition(mouseEvent->absoluteLocation());
219#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
220            } else if (event->isTouchEvent()) {
221                TouchEvent* touchEvent = static_cast<TouchEvent*>(event);
222                if (touchEvent->touches() && touchEvent->touches()->item(0)) {
223                    IntPoint curPoint;
224                    curPoint.setX(touchEvent->touches()->item(0)->pageX());
225                    curPoint.setY(touchEvent->touches()->item(0)->pageY());
226                    setPosition(curPoint);
227                    // Tell the webview that webkit will handle the following move events
228                    event->setDefaultPrevented(true);
229                }
230            }
231
232        }
233#endif
234        return;
235    }
236
237    HTMLDivElement::defaultEventHandler(event);
238}
239
240void SliderThumbElement::detach()
241{
242    if (m_inDragMode) {
243        if (Frame* frame = document()->frame())
244            frame->eventHandler()->setCapturingMouseEventsNode(0);
245    }
246    HTMLDivElement::detach();
247}
248
249}
250
251