Home | History | Annotate | Download | only in shadow
      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 
     51 using namespace std;
     52 
     53 namespace WebCore {
     54 
     55 // FIXME: Find a way to cascade appearance (see the layout method) and get rid of this class.
     56 class RenderSliderThumb : public RenderBlock {
     57 public:
     58     RenderSliderThumb(Node*);
     59 
     60 private:
     61     virtual void layout();
     62 };
     63 
     64 RenderSliderThumb::RenderSliderThumb(Node* node)
     65     : RenderBlock(node)
     66 {
     67 }
     68 
     69 void RenderSliderThumb::layout()
     70 {
     71     // FIXME: Hard-coding this cascade of appearance is bad, because it's something
     72     // that CSS usually does. We need to find a way to express this in CSS.
     73     RenderStyle* parentStyle = parent()->style();
     74     if (parentStyle->appearance() == SliderVerticalPart)
     75         style()->setAppearance(SliderThumbVerticalPart);
     76     else if (parentStyle->appearance() == SliderHorizontalPart)
     77         style()->setAppearance(SliderThumbHorizontalPart);
     78     else if (parentStyle->appearance() == MediaSliderPart)
     79         style()->setAppearance(MediaSliderThumbPart);
     80     else if (parentStyle->appearance() == MediaVolumeSliderPart)
     81         style()->setAppearance(MediaVolumeSliderThumbPart);
     82 
     83     if (style()->hasAppearance()) {
     84         // FIXME: This should pass the style, not the renderer, to the theme.
     85         theme()->adjustSliderThumbSize(this);
     86     }
     87     RenderBlock::layout();
     88 }
     89 
     90 void SliderThumbElement::setPositionFromValue()
     91 {
     92     // Since today the code to calculate position is in the RenderSlider layout
     93     // path, we don't actually update the value here. Instead, we poke at the
     94     // renderer directly to trigger layout.
     95     // FIXME: Move the logic of positioning the thumb here.
     96     if (renderer())
     97         renderer()->setNeedsLayout(true);
     98 }
     99 
    100 RenderObject* SliderThumbElement::createRenderer(RenderArena* arena, RenderStyle*)
    101 {
    102     return new (arena) RenderSliderThumb(this);
    103 }
    104 
    105 void SliderThumbElement::dragFrom(const IntPoint& point)
    106 {
    107     setPositionFromPoint(point);
    108     startDragging();
    109 }
    110 
    111 void SliderThumbElement::setPositionFromPoint(const IntPoint& point)
    112 {
    113     HTMLInputElement* input = hostInput();
    114     ASSERT(input);
    115 
    116     if (!input->renderer() || !renderer())
    117         return;
    118 
    119     IntPoint offset = roundedIntPoint(input->renderer()->absoluteToLocal(point, false, true));
    120     RenderStyle* sliderStyle = input->renderer()->style();
    121     bool isVertical = sliderStyle->appearance() == SliderVerticalPart || sliderStyle->appearance() == MediaVolumeSliderPart;
    122 
    123     int trackSize;
    124     int position;
    125     int currentPosition;
    126     if (isVertical) {
    127         trackSize = input->renderBox()->contentHeight() - renderBox()->height();
    128         position = offset.y() - renderBox()->height() / 2;
    129         currentPosition = renderBox()->y() - input->renderBox()->contentBoxRect().y();
    130     } else {
    131         trackSize = input->renderBox()->contentWidth() - renderBox()->width();
    132         position = offset.x() - renderBox()->width() / 2;
    133         currentPosition = renderBox()->x() - input->renderBox()->contentBoxRect().x();
    134     }
    135     position = max(0, min(position, trackSize));
    136     if (position == currentPosition)
    137         return;
    138 
    139     StepRange range(input);
    140     double fraction = static_cast<double>(position) / trackSize;
    141     if (isVertical)
    142         fraction = 1 - fraction;
    143     double value = range.clampValue(range.valueFromProportion(fraction));
    144 
    145     // FIXME: This is no longer being set from renderer. Consider updating the method name.
    146     input->setValueFromRenderer(serializeForNumberType(value));
    147     renderer()->setNeedsLayout(true);
    148     input->dispatchFormControlChangeEvent();
    149 }
    150 
    151 void SliderThumbElement::startDragging()
    152 {
    153     if (Frame* frame = document()->frame()) {
    154         frame->eventHandler()->setCapturingMouseEventsNode(this);
    155 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    156         // Touch events come from Java to the main frame event handler, so we need
    157         // to flag we are capturing those events also on the main frame event
    158         // handler.
    159         frame->page()->mainFrame()->eventHandler()->setCapturingTouchEventsNode(this);
    160 #endif
    161         m_inDragMode = true;
    162     }
    163 }
    164 
    165 void SliderThumbElement::stopDragging()
    166 {
    167     if (!m_inDragMode)
    168         return;
    169 
    170     if (Frame* frame = document()->frame())
    171         frame->eventHandler()->setCapturingMouseEventsNode(0);
    172 
    173 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    174     if (Frame* frame = document()->frame())
    175         frame->page()->mainFrame()->eventHandler()->setCapturingTouchEventsNode(0);
    176 #endif
    177     m_inDragMode = false;
    178     if (renderer())
    179         renderer()->setNeedsLayout(true);
    180 }
    181 
    182 void SliderThumbElement::defaultEventHandler(Event* event)
    183 {
    184     if (!event->isMouseEvent()
    185 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    186         && !event->isTouchEvent()
    187 #endif
    188         ) {
    189         HTMLDivElement::defaultEventHandler(event);
    190         return;
    191     }
    192 
    193 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    194     bool isLeftButton = false;
    195 
    196     if (event->isMouseEvent()) {
    197         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
    198         isLeftButton = mouseEvent->button() == LeftButton;
    199     }
    200 #else
    201     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
    202     bool isLeftButton = mouseEvent->button() == LeftButton;
    203 #endif
    204     const AtomicString& eventType = event->type();
    205 
    206     if (eventType == eventNames().mousedownEvent && isLeftButton
    207 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    208             || eventType == eventNames().touchstartEvent
    209 #endif
    210             ) {
    211         startDragging();
    212         return;
    213     } else if (eventType == eventNames().mouseupEvent && isLeftButton
    214 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    215             || eventType == eventNames().touchendEvent
    216             || eventType == eventNames().touchcancelEvent
    217 #endif
    218             ) {
    219         stopDragging();
    220         return;
    221     } else if (eventType == eventNames().mousemoveEvent
    222 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    223             || eventType == eventNames().touchmoveEvent
    224 #endif
    225             ) {
    226         if (m_inDragMode)
    227 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    228         {
    229             if (event->isMouseEvent()) {
    230                 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
    231 #endif
    232             setPositionFromPoint(mouseEvent->absoluteLocation());
    233 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
    234             } else if (event->isTouchEvent()) {
    235                 TouchEvent* touchEvent = static_cast<TouchEvent*>(event);
    236                 if (touchEvent->touches() && touchEvent->touches()->item(0)) {
    237                     IntPoint curPoint;
    238                     curPoint.setX(touchEvent->touches()->item(0)->pageX());
    239                     curPoint.setY(touchEvent->touches()->item(0)->pageY());
    240                     setPositionFromPoint(curPoint);
    241                     // Tell the webview that webkit will handle the following move events
    242                     event->setDefaultPrevented(true);
    243                 }
    244             }
    245 
    246         }
    247 #endif
    248         return;
    249     }
    250 
    251     HTMLDivElement::defaultEventHandler(event);
    252 }
    253 
    254 void SliderThumbElement::detach()
    255 {
    256     if (m_inDragMode) {
    257         if (Frame* frame = document()->frame())
    258             frame->eventHandler()->setCapturingMouseEventsNode(0);
    259     }
    260     HTMLDivElement::detach();
    261 }
    262 
    263 HTMLInputElement* SliderThumbElement::hostInput()
    264 {
    265     ASSERT(parentNode());
    266     return static_cast<HTMLInputElement*>(parentNode()->shadowHost());
    267 }
    268 
    269 const AtomicString& SliderThumbElement::shadowPseudoId() const
    270 {
    271     DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb"));
    272     return sliderThumb;
    273 }
    274 
    275 }
    276 
    277