blob: 783eabcd09e17eeac16eaf0ca0d2778a11a64c46 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2011 Apple 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 "RangeInputType.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLParserIdioms.h"
#include "KeyboardEvent.h"
#include "MouseEvent.h"
#include "PlatformMouseEvent.h"
#include "RenderSlider.h"
#include "ShadowRoot.h"
#include "SliderThumbElement.h"
#include "StepRange.h"
#include <limits>
#include <wtf/MathExtras.h>
#include <wtf/PassOwnPtr.h>
#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
#include "TouchEvent.h"
#endif
namespace WebCore {
using namespace HTMLNames;
using namespace std;
static const double rangeDefaultMinimum = 0.0;
static const double rangeDefaultMaximum = 100.0;
static const double rangeDefaultStep = 1.0;
static const double rangeStepScaleFactor = 1.0;
PassOwnPtr<InputType> RangeInputType::create(HTMLInputElement* element)
{
return adoptPtr(new RangeInputType(element));
}
bool RangeInputType::isRangeControl() const
{
return true;
}
const AtomicString& RangeInputType::formControlType() const
{
return InputTypeNames::range();
}
double RangeInputType::valueAsNumber() const
{
return parseToDouble(element()->value(), numeric_limits<double>::quiet_NaN());
}
void RangeInputType::setValueAsNumber(double newValue, ExceptionCode&) const
{
element()->setValue(serialize(newValue));
}
bool RangeInputType::supportsRequired() const
{
return false;
}
bool RangeInputType::rangeUnderflow(const String& value) const
{
// Guaranteed by sanitization.
ASSERT_UNUSED(value, parseToDouble(value, numeric_limits<double>::quiet_NaN()) >= minimum());
return false;
}
bool RangeInputType::rangeOverflow(const String& value) const
{
// Guaranteed by sanitization.
ASSERT_UNUSED(value, parseToDouble(value, numeric_limits<double>::quiet_NaN()) <= maximum());
return false;
}
bool RangeInputType::supportsRangeLimitation() const
{
return true;
}
double RangeInputType::minimum() const
{
return parseToDouble(element()->fastGetAttribute(minAttr), rangeDefaultMinimum);
}
double RangeInputType::maximum() const
{
double max = parseToDouble(element()->fastGetAttribute(maxAttr), rangeDefaultMaximum);
// A remedy for the inconsistent min/max values.
// Sets the maximum to the default or the minimum value.
double min = minimum();
if (max < min)
max = std::max(min, rangeDefaultMaximum);
return max;
}
bool RangeInputType::stepMismatch(const String&, double) const
{
// stepMismatch doesn't occur for type=range. RenderSlider guarantees the
// value matches to step on user input, and sanitization takes care
// of the general case.
return false;
}
double RangeInputType::stepBase() const
{
return minimum();
}
double RangeInputType::defaultStep() const
{
return rangeDefaultStep;
}
double RangeInputType::stepScaleFactor() const
{
return rangeStepScaleFactor;
}
void RangeInputType::handleMouseDownEvent(MouseEvent* event)
{
if (event->button() != LeftButton || event->target() != element())
return;
if (SliderThumbElement* thumb = shadowSliderThumb())
thumb->dragFrom(event->absoluteLocation());
}
void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
{
if (element()->disabled() || element()->readOnly())
return;
const String& key = event->keyIdentifier();
if (key != "Up" && key != "Right" && key != "Down" && key != "Left")
return;
ExceptionCode ec;
if (equalIgnoringCase(element()->fastGetAttribute(stepAttr), "any")) {
double min = minimum();
double max = maximum();
// FIXME: We can't use stepUp() for the step value "any". So, we increase
// or decrease the value by 1/100 of the value range. Is it reasonable?
double step = (max - min) / 100;
double current = parseToDouble(element()->value(), numeric_limits<double>::quiet_NaN());
ASSERT(isfinite(current));
// Stepping-up and -down for step="any" are special cases for type="range" from renderer for convenient.
// No stepping normally for step="any". They cannot be handled by stepUp()/stepDown()/stepUpFromRenderer().
// So calculating values stepped-up or -down here.
double newValue;
if (key == "Up" || key == "Right") {
newValue = current + step;
if (newValue > max)
newValue = max;
} else {
newValue = current - step;
if (newValue < min)
newValue = min;
}
if (newValue != current) {
setValueAsNumber(newValue, ec);
element()->dispatchFormControlChangeEvent();
}
} else {
int stepMagnification = (key == "Up" || key == "Right") ? 1 : -1;
// Reasonable stepping-up/-down by stepUpFromRenderer() unless step="any"
element()->stepUpFromRenderer(stepMagnification);
}
event->setDefaultHandled();
}
#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
void RangeInputType::handleTouchStartEvent(TouchEvent* touchEvent)
{
if (SliderThumbElement* thumb = shadowSliderThumb()) {
if (touchEvent->touches() && touchEvent->touches()->item(0)) {
IntPoint curPoint;
curPoint.setX(touchEvent->touches()->item(0)->pageX());
curPoint.setY(touchEvent->touches()->item(0)->pageY());
thumb->dragFrom(curPoint);
touchEvent->setDefaultHandled();
touchEvent->setDefaultPrevented(true);
}
}
}
#endif
void RangeInputType::createShadowSubtree()
{
ExceptionCode ec = 0;
element()->ensureShadowRoot()->appendChild(SliderThumbElement::create(element()->document()), ec);
}
RenderObject* RangeInputType::createRenderer(RenderArena* arena, RenderStyle*) const
{
return new (arena) RenderSlider(element());
}
double RangeInputType::parseToDouble(const String& src, double defaultValue) const
{
double numberValue;
if (!parseToDoubleForNumberType(src, &numberValue))
return defaultValue;
ASSERT(isfinite(numberValue));
return numberValue;
}
String RangeInputType::serialize(double value) const
{
if (!isfinite(value))
return String();
return serializeForNumberType(value);
}
// FIXME: Could share this with BaseButtonInputType and BaseCheckableInputType if we had a common base class.
void RangeInputType::accessKeyAction(bool sendToAnyElement)
{
InputType::accessKeyAction(sendToAnyElement);
// Send mouse button events if the caller specified sendToAnyElement.
// FIXME: The comment above is no good. It says what we do, but not why.
element()->dispatchSimulatedClick(0, sendToAnyElement);
}
void RangeInputType::minOrMaxAttributeChanged()
{
InputType::minOrMaxAttributeChanged();
// Sanitize the value.
element()->setValue(element()->value());
element()->setNeedsStyleRecalc();
}
void RangeInputType::valueChanged()
{
shadowSliderThumb()->setPositionFromValue();
}
String RangeInputType::fallbackValue()
{
return serializeForNumberType(StepRange(element()).defaultValue());
}
String RangeInputType::sanitizeValue(const String& proposedValue)
{
// If the proposedValue is null than this is a reset scenario and we
// want the range input's value attribute to take priority over the
// calculated default (middle) value.
if (proposedValue.isNull())
return proposedValue;
return serializeForNumberType(StepRange(element()).clampValue(proposedValue));
}
bool RangeInputType::shouldRespectListAttribute()
{
return true;
}
SliderThumbElement* RangeInputType::shadowSliderThumb() const
{
Node* shadow = element()->shadowRoot();
return shadow ? toSliderThumbElement(shadow->firstChild()) : 0;
}
} // namespace WebCore