[Forms] Introduce SpinButtonElement.{cpp,h} into build
[WebKit-https.git] / Source / WebCore / html / shadow / SpinButtonElement.cpp
1 /*
2  * Copyright (C) 2006, 2008, 2010 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
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "SpinButtonElement.h"
29
30 #include "EventHandler.h"
31 #include "EventNames.h"
32 #include "Frame.h"
33 #include "HTMLInputElement.h"
34 #include "HTMLNames.h"
35 #include "MouseEvent.h"
36 #include "RenderBox.h"
37 #include "ScrollbarTheme.h"
38 #include "WheelEvent.h"
39
40 namespace WebCore {
41
42 using namespace HTMLNames;
43
44 inline SpinButtonElement::SpinButtonElement(Document* document, StepActionHandler& stepActionHandler)
45     : HTMLDivElement(divTag, document)
46     , m_stepActionHandler(&stepActionHandler)
47     , m_capturing(false)
48     , m_upDownState(Indeterminate)
49     , m_pressStartingState(Indeterminate)
50     , m_repeatingTimer(this, &SpinButtonElement::repeatingTimerFired)
51 {
52 }
53
54 PassRefPtr<SpinButtonElement> SpinButtonElement::create(Document* document, StepActionHandler& stepActionHandler)
55 {
56     return adoptRef(new SpinButtonElement(document, stepActionHandler));
57 }
58
59 const AtomicString& SpinButtonElement::shadowPseudoId() const
60 {
61     DEFINE_STATIC_LOCAL(AtomicString, innerPseudoId, ("-webkit-inner-spin-button"));
62     return innerPseudoId;
63 }
64
65 void SpinButtonElement::detach()
66 {
67     releaseCapture();
68     HTMLDivElement::detach();
69 }
70
71 void SpinButtonElement::defaultEventHandler(Event* event)
72 {
73     if (!event->isMouseEvent()) {
74         if (!event->defaultHandled())
75             HTMLDivElement::defaultEventHandler(event);
76         return;
77     }
78
79     RenderBox* box = renderBox();
80     if (!box) {
81         if (!event->defaultHandled())
82             HTMLDivElement::defaultEventHandler(event);
83         return;
84     }
85
86     RefPtr<HTMLInputElement> input(static_cast<HTMLInputElement*>(shadowHost()));
87     if (input->disabled() || input->readOnly()) {
88         if (!event->defaultHandled())
89             HTMLDivElement::defaultEventHandler(event);
90         return;
91     }
92
93     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
94     IntPoint local = roundedIntPoint(box->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
95     if (mouseEvent->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) {
96         if (box->pixelSnappedBorderBoxRect().contains(local)) {
97             // The following functions of HTMLInputElement may run JavaScript
98             // code which detaches this shadow node. We need to take a reference
99             // and check renderer() after such function calls.
100             RefPtr<Node> protector(this);
101             input->focus();
102             input->select();
103             if (renderer()) {
104                 if (m_upDownState != Indeterminate) {
105                     doStepAction(m_upDownState == Up ? 1 : -1);
106                     if (renderer())
107                         startRepeatingTimer();
108                 }
109             }
110             event->setDefaultHandled();
111         }
112     } else if (mouseEvent->type() == eventNames().mouseupEvent && mouseEvent->button() == LeftButton)
113         stopRepeatingTimer();
114     else if (event->type() == eventNames().mousemoveEvent) {
115         if (box->pixelSnappedBorderBoxRect().contains(local)) {
116             if (!m_capturing) {
117                 if (Frame* frame = document()->frame()) {
118                     frame->eventHandler()->setCapturingMouseEventsNode(this);
119                     m_capturing = true;
120                 }
121             }
122             UpDownState oldUpDownState = m_upDownState;
123             m_upDownState = local.y() < box->height() / 2 ? Up : Down;
124             if (m_upDownState != oldUpDownState)
125                 renderer()->repaint();
126         } else {
127             releaseCapture();
128             m_upDownState = Indeterminate;
129         }
130     }
131
132     if (!event->defaultHandled())
133         HTMLDivElement::defaultEventHandler(event);
134 }
135
136 void SpinButtonElement::forwardEvent(Event* event)
137 {
138     if (!renderBox())
139         return;
140
141     if (!event->hasInterface(eventNames().interfaceForWheelEvent))
142         return;
143
144     HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowHost());
145     if (input->disabled() || input->readOnly() || !input->focused())
146         return;
147
148     doStepAction(static_cast<WheelEvent*>(event)->wheelDeltaY());
149     event->setDefaultHandled();
150 }
151
152 bool SpinButtonElement::willRespondToMouseMoveEvents()
153 {
154     const HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowHost());
155     if (renderBox() && !input->disabled() && !input->readOnly())
156         return true;
157
158     return HTMLDivElement::willRespondToMouseMoveEvents();
159 }
160
161 bool SpinButtonElement::willRespondToMouseClickEvents()
162 {
163     const HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowHost());
164     if (renderBox() && !input->disabled() && !input->readOnly())
165         return true;
166
167     return HTMLDivElement::willRespondToMouseClickEvents();
168 }
169
170 void SpinButtonElement::doStepAction(int amount)
171 {
172     if (!m_stepActionHandler)
173         return;
174
175     if (amount > 0)
176         m_stepActionHandler->spinButtonStepUp();
177     else if (amount < 0)
178         m_stepActionHandler->spinButtonStepDown();
179 }
180
181 void SpinButtonElement::releaseCapture()
182 {
183     stopRepeatingTimer();
184     if (m_capturing) {
185         if (Frame* frame = document()->frame()) {
186             frame->eventHandler()->setCapturingMouseEventsNode(0);
187             m_capturing = false;
188         }
189     }
190 }
191
192 bool SpinButtonElement::shouldMatchReadOnlySelector() const
193 {
194     return shadowHost()->shouldMatchReadOnlySelector();
195 }
196
197 bool SpinButtonElement::shouldMatchReadWriteSelector() const
198 {
199     return shadowHost()->shouldMatchReadWriteSelector();
200 }
201
202 void SpinButtonElement::startRepeatingTimer()
203 {
204     m_pressStartingState = m_upDownState;
205     ScrollbarTheme* theme = ScrollbarTheme::theme();
206     m_repeatingTimer.start(theme->initialAutoscrollTimerDelay(), theme->autoscrollTimerDelay());
207 }
208
209 void SpinButtonElement::stopRepeatingTimer()
210 {
211     m_repeatingTimer.stop();
212 }
213
214 void SpinButtonElement::step(int amount)
215 {
216     HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowHost());
217     if (input->disabled() || input->readOnly())
218         return;
219     // On Mac OS, NSStepper updates the value for the button under the mouse
220     // cursor regardless of the button pressed at the beginning. So the
221     // following check is not needed for Mac OS.
222 #if !OS(MAC_OS_X)
223     if (m_upDownState != m_pressStartingState)
224         return;
225 #endif
226     doStepAction(amount);
227 }
228     
229 void SpinButtonElement::repeatingTimerFired(Timer<SpinButtonElement>*)
230 {
231     if (m_upDownState != Indeterminate)
232         step(m_upDownState == Up ? 1 : -1);
233 }
234
235 void SpinButtonElement::setHovered(bool flag)
236 {
237     if (!flag)
238         m_upDownState = Indeterminate;
239     HTMLDivElement::setHovered(flag);
240 }
241
242 }