/*
* 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 <wtf/MathExtras.h>
#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<double>(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<MouseEvent*>(event);
isLeftButton = mouseEvent->button() == LeftButton;
}
#else
MouseEvent* mouseEvent = static_cast<MouseEvent*>(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<MouseEvent*>(event);
#endif
setPositionFromPoint(mouseEvent->absoluteLocation());
#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
} else if (event->isTouchEvent()) {
TouchEvent* touchEvent = static_cast<TouchEvent*>(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<HTMLInputElement*>(parentNode()->shadowHost());
}
const AtomicString& SliderThumbElement::shadowPseudoId() const
{
DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb"));
return sliderThumb;
}
}