/* * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "SliderThumbElement.h" #include "Event.h" #include "Frame.h" #include "HTMLInputElement.h" #include "HTMLParserIdioms.h" #include "MouseEvent.h" #include "RenderSlider.h" #include "RenderTheme.h" #include "StepRange.h" #include #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) #include "Page.h" #include "TouchEvent.h" #endif using namespace std; namespace WebCore { // FIXME: Find a way to cascade appearance (see the layout method) and get rid of this class. class RenderSliderThumb : public RenderBlock { public: RenderSliderThumb(Node*); private: virtual void layout(); }; RenderSliderThumb::RenderSliderThumb(Node* node) : RenderBlock(node) { } void RenderSliderThumb::layout() { // FIXME: Hard-coding this cascade of appearance is bad, because it's something // that CSS usually does. We need to find a way to express this in CSS. RenderStyle* parentStyle = parent()->style(); if (parentStyle->appearance() == SliderVerticalPart) style()->setAppearance(SliderThumbVerticalPart); else if (parentStyle->appearance() == SliderHorizontalPart) style()->setAppearance(SliderThumbHorizontalPart); else if (parentStyle->appearance() == MediaSliderPart) style()->setAppearance(MediaSliderThumbPart); else if (parentStyle->appearance() == MediaVolumeSliderPart) style()->setAppearance(MediaVolumeSliderThumbPart); if (style()->hasAppearance()) { // FIXME: This should pass the style, not the renderer, to the theme. theme()->adjustSliderThumbSize(this); } RenderBlock::layout(); } void SliderThumbElement::setPositionFromValue() { // Since today the code to calculate position is in the RenderSlider layout // path, we don't actually update the value here. Instead, we poke at the // renderer directly to trigger layout. // FIXME: Move the logic of positioning the thumb here. if (renderer()) renderer()->setNeedsLayout(true); } RenderObject* SliderThumbElement::createRenderer(RenderArena* arena, RenderStyle*) { return new (arena) RenderSliderThumb(this); } void SliderThumbElement::dragFrom(const IntPoint& point) { setPositionFromPoint(point); startDragging(); } void SliderThumbElement::setPositionFromPoint(const IntPoint& point) { HTMLInputElement* input = hostInput(); ASSERT(input); if (!input->renderer() || !renderer()) return; IntPoint offset = roundedIntPoint(input->renderer()->absoluteToLocal(point, false, true)); RenderStyle* sliderStyle = input->renderer()->style(); bool isVertical = sliderStyle->appearance() == SliderVerticalPart || sliderStyle->appearance() == MediaVolumeSliderPart; int trackSize; int position; int currentPosition; if (isVertical) { trackSize = input->renderBox()->contentHeight() - renderBox()->height(); position = offset.y() - renderBox()->height() / 2; currentPosition = renderBox()->y() - input->renderBox()->contentBoxRect().y(); } else { trackSize = input->renderBox()->contentWidth() - renderBox()->width(); position = offset.x() - renderBox()->width() / 2; currentPosition = renderBox()->x() - input->renderBox()->contentBoxRect().x(); } position = max(0, min(position, trackSize)); if (position == currentPosition) return; StepRange range(input); double fraction = static_cast(position) / trackSize; if (isVertical) fraction = 1 - fraction; double value = range.clampValue(range.valueFromProportion(fraction)); // FIXME: This is no longer being set from renderer. Consider updating the method name. input->setValueFromRenderer(serializeForNumberType(value)); renderer()->setNeedsLayout(true); input->dispatchFormControlChangeEvent(); } void SliderThumbElement::startDragging() { if (Frame* frame = document()->frame()) { frame->eventHandler()->setCapturingMouseEventsNode(this); #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) // Touch events come from Java to the main frame event handler, so we need // to flag we are capturing those events also on the main frame event // handler. frame->page()->mainFrame()->eventHandler()->setCapturingTouchEventsNode(this); #endif m_inDragMode = true; } } void SliderThumbElement::stopDragging() { if (!m_inDragMode) return; if (Frame* frame = document()->frame()) frame->eventHandler()->setCapturingMouseEventsNode(0); #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) if (Frame* frame = document()->frame()) frame->page()->mainFrame()->eventHandler()->setCapturingTouchEventsNode(0); #endif m_inDragMode = false; if (renderer()) renderer()->setNeedsLayout(true); } void SliderThumbElement::defaultEventHandler(Event* event) { if (!event->isMouseEvent() #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) && !event->isTouchEvent() #endif ) { HTMLDivElement::defaultEventHandler(event); return; } #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) bool isLeftButton = false; if (event->isMouseEvent()) { MouseEvent* mouseEvent = static_cast(event); isLeftButton = mouseEvent->button() == LeftButton; } #else MouseEvent* mouseEvent = static_cast(event); bool isLeftButton = mouseEvent->button() == LeftButton; #endif const AtomicString& eventType = event->type(); if (eventType == eventNames().mousedownEvent && isLeftButton #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) || eventType == eventNames().touchstartEvent #endif ) { startDragging(); return; } else if (eventType == eventNames().mouseupEvent && isLeftButton #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) || eventType == eventNames().touchendEvent || eventType == eventNames().touchcancelEvent #endif ) { stopDragging(); return; } else if (eventType == eventNames().mousemoveEvent #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) || eventType == eventNames().touchmoveEvent #endif ) { if (m_inDragMode) #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) { if (event->isMouseEvent()) { MouseEvent* mouseEvent = static_cast(event); #endif setPositionFromPoint(mouseEvent->absoluteLocation()); #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) } else if (event->isTouchEvent()) { TouchEvent* touchEvent = static_cast(event); if (touchEvent->touches() && touchEvent->touches()->item(0)) { IntPoint curPoint; curPoint.setX(touchEvent->touches()->item(0)->pageX()); curPoint.setY(touchEvent->touches()->item(0)->pageY()); setPositionFromPoint(curPoint); // Tell the webview that webkit will handle the following move events event->setDefaultPrevented(true); } } } #endif return; } HTMLDivElement::defaultEventHandler(event); } #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS) void SliderThumbElement::attach() { HTMLDivElement::attach(); document()->addListenerTypeIfNeeded(eventNames().touchstartEvent); } #endif void SliderThumbElement::detach() { if (m_inDragMode) { if (Frame* frame = document()->frame()) frame->eventHandler()->setCapturingMouseEventsNode(0); } HTMLDivElement::detach(); } HTMLInputElement* SliderThumbElement::hostInput() { ASSERT(parentNode()); return static_cast(parentNode()->shadowHost()); } const AtomicString& SliderThumbElement::shadowPseudoId() const { DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb")); return sliderThumb; } }