1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "WebScrollbarImpl.h"
33
34#include "GraphicsContext.h"
35#include "KeyboardCodes.h"
36#include "painting/GraphicsContextBuilder.h"
37#include "Scrollbar.h"
38#include "ScrollbarTheme.h"
39#include "ScrollTypes.h"
40#include "WebCanvas.h"
41#include "WebInputEvent.h"
42#include "WebInputEventConversion.h"
43#include "WebRect.h"
44#include "WebScrollbarClient.h"
45#include "WebVector.h"
46#include "WebViewImpl.h"
47
48using namespace std;
49using namespace WebCore;
50
51namespace WebKit {
52
53WebScrollbar* WebScrollbar::create(WebScrollbarClient* client, Orientation orientation)
54{
55    return new WebScrollbarImpl(client, orientation);
56}
57
58int WebScrollbar::defaultThickness()
59{
60    return ScrollbarTheme::nativeTheme()->scrollbarThickness();
61}
62
63WebScrollbarImpl::WebScrollbarImpl(WebScrollbarClient* client, Orientation orientation)
64    : m_client(client)
65    , m_scrollOffset(0)
66{
67    m_scrollbar = Scrollbar::createNativeScrollbar(
68        static_cast<ScrollableArea*>(this),
69        static_cast<ScrollbarOrientation>(orientation),
70        RegularScrollbar);
71}
72
73WebScrollbarImpl::~WebScrollbarImpl()
74{
75}
76
77void WebScrollbarImpl::setLocation(const WebRect& rect)
78{
79    IntRect oldRect = m_scrollbar->frameRect();
80    m_scrollbar->setFrameRect(rect);
81    if (WebRect(oldRect) != rect)
82      m_scrollbar->invalidate();
83
84    int length = m_scrollbar->orientation() == HorizontalScrollbar ? m_scrollbar->width() : m_scrollbar->height();
85    int pageStep = max(max(static_cast<int>(static_cast<float>(length) * Scrollbar::minFractionToStepWhenPaging()), length - Scrollbar::maxOverlapBetweenPages()), 1);
86    m_scrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
87    m_scrollbar->setEnabled(m_scrollbar->totalSize() > length);
88    m_scrollbar->setProportion(length, m_scrollbar->totalSize());
89}
90
91int WebScrollbarImpl::value() const
92{
93    return m_scrollOffset;
94}
95
96void WebScrollbarImpl::setValue(int position)
97{
98    ScrollableArea::scrollToOffsetWithoutAnimation(m_scrollbar->orientation(), static_cast<float>(position));
99}
100
101void WebScrollbarImpl::setDocumentSize(int size)
102{
103    int length = m_scrollbar->orientation() == HorizontalScrollbar ? m_scrollbar->width() : m_scrollbar->height();
104    m_scrollbar->setEnabled(size > length);
105    m_scrollbar->setProportion(length, size);
106}
107
108void WebScrollbarImpl::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
109{
110    WebCore::ScrollDirection dir;
111    bool horizontal = m_scrollbar->orientation() == HorizontalScrollbar;
112    if (direction == ScrollForward)
113        dir = horizontal ? ScrollRight : ScrollDown;
114    else
115        dir = horizontal ? ScrollLeft : ScrollUp;
116
117    WebCore::ScrollableArea::scroll(dir, static_cast<WebCore::ScrollGranularity>(granularity), multiplier);
118}
119
120void WebScrollbarImpl::paint(WebCanvas* canvas, const WebRect& rect)
121{
122    m_scrollbar->paint(&GraphicsContextBuilder(canvas).context(), rect);
123}
124
125bool WebScrollbarImpl::handleInputEvent(const WebInputEvent& event)
126{
127    switch (event.type) {
128    case WebInputEvent::MouseDown:
129        return onMouseDown(event);
130    case WebInputEvent::MouseUp:
131        return onMouseUp(event);
132    case WebInputEvent::MouseMove:
133        return onMouseMove(event);
134    case WebInputEvent::MouseLeave:
135        return onMouseLeave(event);
136    case WebInputEvent::MouseWheel:
137        return onMouseWheel(event);
138    case WebInputEvent::KeyDown:
139        return onKeyDown(event);
140    case WebInputEvent::Undefined:
141    case WebInputEvent::MouseEnter:
142    case WebInputEvent::RawKeyDown:
143    case WebInputEvent::KeyUp:
144    case WebInputEvent::Char:
145    case WebInputEvent::TouchStart:
146    case WebInputEvent::TouchMove:
147    case WebInputEvent::TouchEnd:
148    case WebInputEvent::TouchCancel:
149    default:
150         break;
151    }
152    return false;
153}
154
155bool WebScrollbarImpl::onMouseDown(const WebInputEvent& event)
156{
157    WebMouseEvent mousedown = *static_cast<const WebMouseEvent*>(&event);
158    if (!m_scrollbar->frameRect().contains(mousedown.x, mousedown.y))
159        return false;
160
161    mousedown.x -= m_scrollbar->x();
162    mousedown.y -= m_scrollbar->y();
163    m_scrollbar->mouseDown(PlatformMouseEventBuilder(m_scrollbar.get(), mousedown));
164    return true;
165}
166
167bool WebScrollbarImpl::onMouseUp(const WebInputEvent& event)
168{
169    if (m_scrollbar->pressedPart() == NoPart)
170        return false;
171
172    return m_scrollbar->mouseUp();
173}
174
175bool WebScrollbarImpl::onMouseMove(const WebInputEvent& event)
176{
177    WebMouseEvent mousemove = *static_cast<const WebMouseEvent*>(&event);
178    if (m_scrollbar->frameRect().contains(mousemove.x, mousemove.y)
179        || m_scrollbar->pressedPart() != NoPart) {
180        mousemove.x -= m_scrollbar->x();
181        mousemove.y -= m_scrollbar->y();
182        return m_scrollbar->mouseMoved(PlatformMouseEventBuilder(m_scrollbar.get(), mousemove));
183    }
184
185    if (m_scrollbar->hoveredPart() != NoPart)
186        m_scrollbar->mouseExited();
187    return false;
188}
189
190bool WebScrollbarImpl::onMouseLeave(const WebInputEvent& event)
191{
192    if (m_scrollbar->hoveredPart() == NoPart)
193        return false;
194
195    return m_scrollbar->mouseExited();
196}
197
198bool WebScrollbarImpl::onMouseWheel(const WebInputEvent& event)
199{
200    // Same logic as in Scrollview.cpp.  If we can move at all, we'll accept the event.
201    WebMouseWheelEvent mousewheel = *static_cast<const WebMouseWheelEvent*>(&event);
202    int maxScrollDelta = m_scrollbar->maximum() - m_scrollbar->value();
203    float delta = m_scrollbar->orientation() == HorizontalScrollbar ? mousewheel.deltaX : mousewheel.deltaY;
204    if ((delta < 0 && maxScrollDelta > 0) || (delta > 0 && m_scrollbar->value() > 0)) {
205        if (mousewheel.scrollByPage) {
206            ASSERT(m_scrollbar->orientation() == VerticalScrollbar);
207            bool negative = delta < 0;
208            delta = max(max(static_cast<float>(m_scrollbar->visibleSize()) * Scrollbar::minFractionToStepWhenPaging(), static_cast<float>(m_scrollbar->visibleSize() - Scrollbar::maxOverlapBetweenPages())), 1.0f);
209            if (negative)
210                delta *= -1;
211        }
212        ScrollableArea::scroll((m_scrollbar->orientation() == HorizontalScrollbar) ? WebCore::ScrollLeft : WebCore::ScrollUp, WebCore::ScrollByPixel, delta);
213        return true;
214    }
215
216    return false;
217    }
218
219bool WebScrollbarImpl::onKeyDown(const WebInputEvent& event)
220{
221    WebKeyboardEvent keyboard = *static_cast<const WebKeyboardEvent*>(&event);
222    int keyCode;
223    // We have to duplicate this logic from WebViewImpl because there it uses
224    // Char and RawKeyDown events, which don't exist at this point.
225    if (keyboard.windowsKeyCode == VKEY_SPACE)
226        keyCode = ((keyboard.modifiers & WebInputEvent::ShiftKey) ? VKEY_PRIOR : VKEY_NEXT);
227    else {
228        if (keyboard.modifiers == WebInputEvent::ControlKey) {
229            // Match FF behavior in the sense that Ctrl+home/end are the only Ctrl
230            // key combinations which affect scrolling. Safari is buggy in the
231            // sense that it scrolls the page for all Ctrl+scrolling key
232            // combinations. For e.g. Ctrl+pgup/pgdn/up/down, etc.
233            switch (keyboard.windowsKeyCode) {
234            case VKEY_HOME:
235            case VKEY_END:
236                break;
237            default:
238                return false;
239            }
240        }
241
242        if (keyboard.isSystemKey || (keyboard.modifiers & WebInputEvent::ShiftKey))
243            return false;
244
245        keyCode = keyboard.windowsKeyCode;
246    }
247    WebCore::ScrollDirection scrollDirection;
248    WebCore::ScrollGranularity scrollGranularity;
249    if (WebViewImpl::mapKeyCodeForScroll(keyCode, &scrollDirection, &scrollGranularity)) {
250        // Will return false if scroll direction wasn't compatible with this scrollbar.
251        return ScrollableArea::scroll(scrollDirection, scrollGranularity);
252    }
253    return false;
254}
255
256int WebScrollbarImpl::scrollSize(WebCore::ScrollbarOrientation orientation) const
257{
258    return (orientation == m_scrollbar->orientation()) ? (m_scrollbar->totalSize() - m_scrollbar->visibleSize()) : 0;
259}
260
261int WebScrollbarImpl::scrollPosition(Scrollbar*) const
262{
263    return m_scrollOffset;
264}
265
266void WebScrollbarImpl::setScrollOffset(const IntPoint& offset)
267{
268    if (m_scrollbar->orientation() == HorizontalScrollbar)
269        m_scrollOffset = offset.x();
270    else
271        m_scrollOffset = offset.y();
272
273    m_client->valueChanged(this);
274}
275
276void WebScrollbarImpl::invalidateScrollbarRect(Scrollbar*, const IntRect& rect)
277{
278    WebRect webrect(rect);
279    webrect.x += m_scrollbar->x();
280    webrect.y += m_scrollbar->y();
281    m_client->invalidateScrollbarRect(this, webrect);
282}
283
284void WebScrollbarImpl::invalidateScrollCornerRect(const IntRect&)
285{
286}
287
288bool WebScrollbarImpl::isActive() const
289{
290    return true;
291}
292
293bool WebScrollbarImpl::isScrollCornerVisible() const
294{
295    return false;
296}
297
298void WebScrollbarImpl::getTickmarks(Vector<IntRect>& tickmarks) const
299{
300    WebVector<WebRect> ticks;
301    m_client->getTickmarks(const_cast<WebScrollbarImpl*>(this), &ticks);
302    tickmarks.resize(ticks.size());
303    for (size_t i = 0; i < ticks.size(); ++i)
304        tickmarks[i] = ticks[i];
305}
306
307Scrollbar* WebScrollbarImpl::horizontalScrollbar() const
308{
309    return m_scrollbar->orientation() == HorizontalScrollbar ? m_scrollbar.get() : 0;
310}
311
312Scrollbar* WebScrollbarImpl::verticalScrollbar() const
313{
314    return m_scrollbar->orientation() == VerticalScrollbar ? m_scrollbar.get() : 0;
315}
316
317} // namespace WebKit
318