Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  *
     19  */
     20 
     21 #include "config.h"
     22 #include "RenderSlider.h"
     23 
     24 #include "CSSPropertyNames.h"
     25 #include "Document.h"
     26 #include "Event.h"
     27 #include "EventHandler.h"
     28 #include "EventNames.h"
     29 #include "Frame.h"
     30 #include "HTMLInputElement.h"
     31 #include "HTMLDivElement.h"
     32 #include "HTMLNames.h"
     33 #include "MediaControlElements.h"
     34 #include "MouseEvent.h"
     35 #include "RenderLayer.h"
     36 #include "RenderTheme.h"
     37 #include "RenderView.h"
     38 #include <wtf/MathExtras.h>
     39 
     40 #ifdef ANDROID_LAYOUT
     41 #include "Settings.h"
     42 #endif
     43 
     44 using std::min;
     45 
     46 namespace WebCore {
     47 
     48 using namespace HTMLNames;
     49 
     50 static const int defaultTrackLength = 129;
     51 
     52 // FIXME: The SliderRange class and functions are entirely based on the DOM,
     53 // and could be put with HTMLInputElement (possibly with a new name) instead of here.
     54 struct SliderRange {
     55     bool hasStep;
     56     double step;
     57     double minimum;
     58     double maximum;  // maximum must be >= minimum.
     59 
     60     explicit SliderRange(HTMLInputElement*);
     61     double clampValue(double value);
     62 
     63     // Map value into 0-1 range
     64     double proportionFromValue(double value)
     65     {
     66         if (minimum == maximum)
     67             return 0;
     68 
     69         return (value - minimum) / (maximum - minimum);
     70     }
     71 
     72     // Map from 0-1 range to value
     73     double valueFromProportion(double proportion)
     74     {
     75         return minimum + proportion * (maximum - minimum);
     76     }
     77 
     78     double valueFromElement(HTMLInputElement*, bool* wasClamped = 0);
     79 };
     80 
     81 SliderRange::SliderRange(HTMLInputElement* element)
     82 {
     83     if (element->hasAttribute(precisionAttr)) {
     84         step = 1.0;
     85         hasStep = !equalIgnoringCase(element->getAttribute(precisionAttr), "float");
     86     } else
     87         hasStep = element->getAllowedValueStep(&step);
     88 
     89     maximum = element->maximum();
     90     minimum = element->minimum();
     91 }
     92 
     93 double SliderRange::clampValue(double value)
     94 {
     95     double clampedValue = max(minimum, min(value, maximum));
     96     if (!hasStep)
     97         return clampedValue;
     98     // Rounds clampedValue to minimum + N * step.
     99     clampedValue = minimum + round((clampedValue - minimum) / step) * step;
    100     if (clampedValue > maximum)
    101        clampedValue -= step;
    102     ASSERT(clampedValue >= minimum);
    103     ASSERT(clampedValue <= maximum);
    104     return clampedValue;
    105 }
    106 
    107 double SliderRange::valueFromElement(HTMLInputElement* element, bool* wasClamped)
    108 {
    109     double oldValue;
    110     bool parseSuccess = HTMLInputElement::formStringToDouble(element->value(), &oldValue);
    111     if (!parseSuccess)
    112         oldValue = (minimum + maximum) / 2;
    113     double newValue = clampValue(oldValue);
    114 
    115     if (wasClamped)
    116         *wasClamped = !parseSuccess || newValue != oldValue;
    117 
    118     return newValue;
    119 }
    120 
    121 // Returns a value between 0 and 1.
    122 // As with SliderRange, this could be on HTMLInputElement instead of here.
    123 static double sliderPosition(HTMLInputElement* element)
    124 {
    125     SliderRange range(element);
    126     return range.proportionFromValue(range.valueFromElement(element));
    127 }
    128 
    129 class SliderThumbElement : public HTMLDivElement {
    130 public:
    131     SliderThumbElement(Document*, Node* shadowParent);
    132 
    133     bool inDragMode() const { return m_inDragMode; }
    134 
    135     virtual void defaultEventHandler(Event*);
    136     virtual void detach();
    137 
    138 private:
    139     virtual bool isShadowNode() const { return true; }
    140     virtual Node* shadowParentNode() { return m_shadowParent; }
    141 
    142     FloatPoint m_offsetToThumb;
    143     Node* m_shadowParent;
    144     bool m_inDragMode;
    145 };
    146 
    147 SliderThumbElement::SliderThumbElement(Document* document, Node* shadowParent)
    148     : HTMLDivElement(divTag, document)
    149     , m_shadowParent(shadowParent)
    150     , m_inDragMode(false)
    151 {
    152 }
    153 
    154 void SliderThumbElement::defaultEventHandler(Event* event)
    155 {
    156     if (!event->isMouseEvent()) {
    157         HTMLDivElement::defaultEventHandler(event);
    158         return;
    159     }
    160 
    161     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
    162     bool isLeftButton = mouseEvent->button() == LeftButton;
    163     const AtomicString& eventType = event->type();
    164 
    165     if (eventType == eventNames().mousedownEvent && isLeftButton) {
    166         if (document()->frame() && renderer()) {
    167             RenderSlider* slider = toRenderSlider(renderer()->parent());
    168             if (slider) {
    169                 if (slider->mouseEventIsInThumb(mouseEvent)) {
    170                     // We selected the thumb, we want the cursor to always stay at
    171                     // the same position relative to the thumb.
    172                     m_offsetToThumb = slider->mouseEventOffsetToThumb(mouseEvent);
    173                 } else {
    174                     // We are outside the thumb, move the thumb to the point were
    175                     // we clicked. We'll be exactly at the center of the thumb.
    176                     m_offsetToThumb.setX(0);
    177                     m_offsetToThumb.setY(0);
    178                 }
    179 
    180                 m_inDragMode = true;
    181                 document()->frame()->eventHandler()->setCapturingMouseEventsNode(m_shadowParent);
    182                 event->setDefaultHandled();
    183                 return;
    184             }
    185         }
    186     } else if (eventType == eventNames().mouseupEvent && isLeftButton) {
    187         if (m_inDragMode) {
    188             if (Frame* frame = document()->frame())
    189                 frame->eventHandler()->setCapturingMouseEventsNode(0);
    190             m_inDragMode = false;
    191             event->setDefaultHandled();
    192             return;
    193         }
    194     } else if (eventType == eventNames().mousemoveEvent) {
    195         if (m_inDragMode && renderer() && renderer()->parent()) {
    196             RenderSlider* slider = toRenderSlider(renderer()->parent());
    197             if (slider) {
    198                 FloatPoint curPoint = slider->absoluteToLocal(mouseEvent->absoluteLocation(), false, true);
    199                 IntPoint eventOffset(curPoint.x() + m_offsetToThumb.x(), curPoint.y() + m_offsetToThumb.y());
    200                 slider->setValueForPosition(slider->positionForOffset(eventOffset));
    201                 event->setDefaultHandled();
    202                 return;
    203             }
    204         }
    205     }
    206 
    207     HTMLDivElement::defaultEventHandler(event);
    208 }
    209 
    210 void SliderThumbElement::detach()
    211 {
    212     if (m_inDragMode) {
    213         if (Frame* frame = document()->frame())
    214             frame->eventHandler()->setCapturingMouseEventsNode(0);
    215     }
    216     HTMLDivElement::detach();
    217 }
    218 
    219 RenderSlider::RenderSlider(HTMLInputElement* element)
    220     : RenderBlock(element)
    221 {
    222 }
    223 
    224 RenderSlider::~RenderSlider()
    225 {
    226     if (m_thumb)
    227         m_thumb->detach();
    228 }
    229 
    230 int RenderSlider::baselinePosition(bool, bool) const
    231 {
    232     return height() + marginTop();
    233 }
    234 
    235 void RenderSlider::calcPrefWidths()
    236 {
    237     m_minPrefWidth = 0;
    238     m_maxPrefWidth = 0;
    239 
    240     if (style()->width().isFixed() && style()->width().value() > 0)
    241         m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
    242     else
    243         m_maxPrefWidth = defaultTrackLength * style()->effectiveZoom();
    244 
    245     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
    246         m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
    247         m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
    248     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
    249         m_minPrefWidth = 0;
    250     else
    251         m_minPrefWidth = m_maxPrefWidth;
    252 
    253     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
    254         m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
    255         m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
    256     }
    257 
    258     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
    259     m_minPrefWidth += toAdd;
    260     m_maxPrefWidth += toAdd;
    261 
    262     setPrefWidthsDirty(false);
    263 }
    264 
    265 void RenderSlider::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
    266 {
    267     RenderBlock::styleDidChange(diff, oldStyle);
    268 
    269     if (m_thumb)
    270         m_thumb->renderer()->setStyle(createThumbStyle(style()));
    271 
    272     setReplaced(isInline());
    273 }
    274 
    275 PassRefPtr<RenderStyle> RenderSlider::createThumbStyle(const RenderStyle* parentStyle)
    276 {
    277     RefPtr<RenderStyle> style;
    278     RenderStyle* pseudoStyle = getCachedPseudoStyle(SLIDER_THUMB);
    279     if (pseudoStyle)
    280         // We may be sharing style with another slider, but we must not share the thumb style.
    281         style = RenderStyle::clone(pseudoStyle);
    282     else
    283         style = RenderStyle::create();
    284 
    285     if (parentStyle)
    286         style->inheritFrom(parentStyle);
    287 
    288     style->setDisplay(BLOCK);
    289 
    290     if (parentStyle->appearance() == SliderVerticalPart)
    291         style->setAppearance(SliderThumbVerticalPart);
    292     else if (parentStyle->appearance() == SliderHorizontalPart)
    293         style->setAppearance(SliderThumbHorizontalPart);
    294     else if (parentStyle->appearance() == MediaSliderPart)
    295         style->setAppearance(MediaSliderThumbPart);
    296     else if (parentStyle->appearance() == MediaVolumeSliderPart)
    297         style->setAppearance(MediaVolumeSliderThumbPart);
    298 
    299     return style.release();
    300 }
    301 
    302 IntRect RenderSlider::thumbRect()
    303 {
    304     if (!m_thumb)
    305         return IntRect();
    306 
    307     IntRect thumbRect;
    308     RenderBox* thumb = toRenderBox(m_thumb->renderer());
    309 
    310     thumbRect.setWidth(thumb->style()->width().calcMinValue(contentWidth()));
    311     thumbRect.setHeight(thumb->style()->height().calcMinValue(contentHeight()));
    312 
    313     double fraction = sliderPosition(static_cast<HTMLInputElement*>(node()));
    314     IntRect contentRect = contentBoxRect();
    315     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart) {
    316         thumbRect.setX(contentRect.x() + (contentRect.width() - thumbRect.width()) / 2);
    317         thumbRect.setY(contentRect.y() + static_cast<int>(nextafter((contentRect.height() - thumbRect.height()) + 1, 0) * (1 - fraction)));
    318     } else {
    319         thumbRect.setX(contentRect.x() + static_cast<int>(nextafter((contentRect.width() - thumbRect.width()) + 1, 0) * fraction));
    320         thumbRect.setY(contentRect.y() + (contentRect.height() - thumbRect.height()) / 2);
    321     }
    322 
    323     return thumbRect;
    324 }
    325 
    326 void RenderSlider::layout()
    327 {
    328     ASSERT(needsLayout());
    329 
    330     RenderBox* thumb = m_thumb ? toRenderBox(m_thumb->renderer()) : 0;
    331 
    332     IntSize baseSize(borderLeft() + paddingLeft() + paddingRight() + borderRight(),
    333         borderTop() + paddingTop() + paddingBottom() + borderBottom());
    334 
    335     if (thumb) {
    336         // Allow the theme to set the size of the thumb.
    337         if (thumb->style()->hasAppearance()) {
    338             // FIXME: This should pass the style, not the renderer, to the theme.
    339             theme()->adjustSliderThumbSize(thumb);
    340         }
    341 
    342         baseSize.expand(thumb->style()->width().calcMinValue(0), thumb->style()->height().calcMinValue(0));
    343     }
    344 
    345     LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
    346 
    347     IntSize oldSize = size();
    348 
    349     setSize(baseSize);
    350     calcWidth();
    351     calcHeight();
    352 
    353     if (thumb) {
    354         if (oldSize != size())
    355             thumb->setChildNeedsLayout(true, false);
    356 
    357         LayoutStateMaintainer statePusher(view(), this, size());
    358 
    359         IntRect oldThumbRect = thumb->frameRect();
    360 
    361         thumb->layoutIfNeeded();
    362 
    363         IntRect rect = thumbRect();
    364         thumb->setFrameRect(rect);
    365         if (thumb->checkForRepaintDuringLayout())
    366             thumb->repaintDuringLayoutIfMoved(oldThumbRect);
    367 
    368         statePusher.pop();
    369         addOverflowFromChild(thumb);
    370     }
    371 
    372     repainter.repaintAfterLayout();
    373 
    374     setNeedsLayout(false);
    375 }
    376 
    377 void RenderSlider::updateFromElement()
    378 {
    379     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
    380 
    381     // Send the value back to the element if the range changes it.
    382     SliderRange range(element);
    383     bool clamped;
    384     double value = range.valueFromElement(element, &clamped);
    385     if (clamped)
    386         element->setValueFromRenderer(HTMLInputElement::formStringFromDouble(value));
    387 
    388     // Layout will take care of the thumb's size and position.
    389     if (!m_thumb) {
    390         m_thumb = new SliderThumbElement(document(), node());
    391         RefPtr<RenderStyle> thumbStyle = createThumbStyle(style());
    392         m_thumb->setRenderer(m_thumb->createRenderer(renderArena(), thumbStyle.get()));
    393         m_thumb->renderer()->setStyle(thumbStyle.release());
    394         m_thumb->setAttached();
    395         m_thumb->setInDocument(true);
    396         addChild(m_thumb->renderer());
    397     }
    398     setNeedsLayout(true);
    399 }
    400 
    401 bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt)
    402 {
    403     if (!m_thumb || !m_thumb->renderer())
    404         return false;
    405 
    406 #if ENABLE(VIDEO)
    407     if (style()->appearance() == MediaSliderPart || style()->appearance() == MediaVolumeSliderPart) {
    408         MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node());
    409         return sliderThumb->hitTest(evt->absoluteLocation());
    410     }
    411 #endif
    412 
    413     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
    414     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
    415     return thumbBounds.contains(roundedIntPoint(localPoint));
    416 }
    417 
    418 FloatPoint RenderSlider::mouseEventOffsetToThumb(MouseEvent* evt)
    419 {
    420     ASSERT(m_thumb && m_thumb->renderer());
    421     FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true);
    422     IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect();
    423     FloatPoint offset;
    424     offset.setX(thumbBounds.x() + thumbBounds.width() / 2 - localPoint.x());
    425     offset.setY(thumbBounds.y() + thumbBounds.height() / 2 - localPoint.y());
    426     return offset;
    427 }
    428 
    429 void RenderSlider::setValueForPosition(int position)
    430 {
    431     if (!m_thumb || !m_thumb->renderer())
    432         return;
    433 
    434     HTMLInputElement* element = static_cast<HTMLInputElement*>(node());
    435 
    436     // Calculate the new value based on the position, and send it to the element.
    437     SliderRange range(element);
    438     double fraction = static_cast<double>(position) / trackSize();
    439     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
    440         fraction = 1 - fraction;
    441     double value = range.clampValue(range.valueFromProportion(fraction));
    442     element->setValueFromRenderer(HTMLInputElement::formStringFromDouble(value));
    443 
    444     // Also update the position if appropriate.
    445     if (position != currentPosition()) {
    446         setNeedsLayout(true);
    447 
    448         // FIXME: It seems like this could send extra change events if the same value is set
    449         // multiple times with no layout in between.
    450         element->dispatchFormControlChangeEvent();
    451     }
    452 }
    453 
    454 int RenderSlider::positionForOffset(const IntPoint& p)
    455 {
    456     if (!m_thumb || !m_thumb->renderer())
    457         return 0;
    458 
    459     int position;
    460     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
    461         position = p.y() - m_thumb->renderBox()->height() / 2;
    462     else
    463         position = p.x() - m_thumb->renderBox()->width() / 2;
    464 
    465     return max(0, min(position, trackSize()));
    466 }
    467 
    468 int RenderSlider::currentPosition()
    469 {
    470     ASSERT(m_thumb);
    471     ASSERT(m_thumb->renderer());
    472 
    473     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
    474         return toRenderBox(m_thumb->renderer())->y() - contentBoxRect().y();
    475     return toRenderBox(m_thumb->renderer())->x() - contentBoxRect().x();
    476 }
    477 
    478 int RenderSlider::trackSize()
    479 {
    480     ASSERT(m_thumb);
    481     ASSERT(m_thumb->renderer());
    482 
    483     if (style()->appearance() == SliderVerticalPart || style()->appearance() == MediaVolumeSliderPart)
    484         return contentHeight() - m_thumb->renderBox()->height();
    485     return contentWidth() - m_thumb->renderBox()->width();
    486 }
    487 
    488 void RenderSlider::forwardEvent(Event* event)
    489 {
    490     if (event->isMouseEvent()) {
    491         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
    492         if (event->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) {
    493             if (!mouseEventIsInThumb(mouseEvent)) {
    494                 IntPoint eventOffset = roundedIntPoint(absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
    495                 setValueForPosition(positionForOffset(eventOffset));
    496             }
    497         }
    498     }
    499 
    500     m_thumb->defaultEventHandler(event);
    501 }
    502 
    503 bool RenderSlider::inDragMode() const
    504 {
    505     return m_thumb && m_thumb->inDragMode();
    506 }
    507 
    508 } // namespace WebCore
    509