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