1/*
2 * Copyright (C) 2007 Kevin Ollivier <kevino@theolliviers.com>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ScrollView.h"
28
29#include "FloatRect.h"
30#include "IntRect.h"
31#include "NotImplemented.h"
32#include "PlatformWheelEvent.h"
33#include "Scrollbar.h"
34
35#include <algorithm>
36#include <stdio.h>
37
38#include <wx/defs.h>
39#include <wx/scrolbar.h>
40#include <wx/scrolwin.h>
41#include <wx/event.h>
42
43using namespace std;
44
45namespace WebCore {
46
47class ScrollView::ScrollViewPrivate : public wxEvtHandler {
48
49public:
50    ScrollViewPrivate(ScrollView* scrollView)
51        : wxEvtHandler()
52        , m_scrollView(scrollView)
53        , vScrollbarMode(ScrollbarAuto)
54        , hScrollbarMode(ScrollbarAuto)
55        , viewStart(0, 0)
56    {
57    }
58
59    void bindEvents(wxWindow* win)
60    {
61        // TODO: is there an easier way to Connect to a range of events? these are contiguous.
62        win->Connect(wxEVT_SCROLLWIN_TOP,          wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
63        win->Connect(wxEVT_SCROLLWIN_BOTTOM,       wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
64        win->Connect(wxEVT_SCROLLWIN_LINEUP,       wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
65        win->Connect(wxEVT_SCROLLWIN_LINEDOWN,     wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
66        win->Connect(wxEVT_SCROLLWIN_PAGEUP,       wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
67        win->Connect(wxEVT_SCROLLWIN_PAGEDOWN,     wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
68        win->Connect(wxEVT_SCROLLWIN_THUMBTRACK,   wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
69        win->Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
70    }
71
72    void OnScrollWinEvents(wxScrollWinEvent& e)
73    {
74        wxEventType scrollType(e.GetEventType());
75        bool horiz = e.GetOrientation() == wxHORIZONTAL;
76
77        wxPoint pos(viewStart);
78
79        if (scrollType == wxEVT_SCROLLWIN_THUMBTRACK || scrollType == wxEVT_SCROLLWIN_THUMBRELEASE) {
80            if (horiz)
81                pos.x = e.GetPosition();
82            else
83                pos.y = e.GetPosition();
84        }
85        else if (scrollType == wxEVT_SCROLLWIN_LINEDOWN) {
86            if (horiz)
87                pos.x += Scrollbar::pixelsPerLineStep();
88            else
89                pos.y += Scrollbar::pixelsPerLineStep();
90        }
91        else if (scrollType == wxEVT_SCROLLWIN_LINEUP) {
92            if (horiz)
93                pos.x -= Scrollbar::pixelsPerLineStep();
94            else
95                pos.y -= Scrollbar::pixelsPerLineStep();
96        }
97        else if (scrollType == wxEVT_SCROLLWIN_PAGEUP) {
98            if (horiz)
99                pos.x -= max<int>(m_scrollView->visibleWidth() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleWidth() - Scrollbar::maxOverlapBetweenPages());
100            else
101                pos.y -= max<int>(m_scrollView->visibleHeight() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleHeight() - Scrollbar::maxOverlapBetweenPages());
102        }
103        else if (scrollType == wxEVT_SCROLLWIN_PAGEDOWN) {
104            if (horiz)
105                pos.x += max<int>(m_scrollView->visibleWidth() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleWidth() - Scrollbar::maxOverlapBetweenPages());
106            else
107                pos.y += max<int>(m_scrollView->visibleHeight() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleHeight() - Scrollbar::maxOverlapBetweenPages());
108        }
109        else
110            return e.Skip();
111
112        m_scrollView->setScrollPosition(IntPoint(pos.x, pos.y));
113    }
114
115    ScrollView* m_scrollView;
116
117    ScrollbarMode vScrollbarMode;
118    ScrollbarMode hScrollbarMode;
119    wxPoint viewStart;
120};
121
122void ScrollView::platformInit()
123{
124    m_data = new ScrollViewPrivate(this);
125}
126
127
128void ScrollView::platformDestroy()
129{
130    delete m_data;
131}
132
133void ScrollView::setPlatformWidget(wxWindow* win)
134{
135    Widget::setPlatformWidget(win);
136    m_data->bindEvents(win);
137}
138
139void ScrollView::platformRepaintContentRectangle(const IntRect& updateRect, bool now)
140{
141    // we need to convert coordinates to scrolled position
142    wxRect contentsRect = updateRect;
143    contentsRect.Offset(-scrollX(), -scrollY());
144    wxWindow* win = platformWidget();
145    if (win) {
146        win->RefreshRect(contentsRect, true);
147        if (now)
148            win->Update();
149    }
150}
151
152IntRect ScrollView::platformVisibleContentRect(bool includeScrollbars) const
153{
154    wxWindow* win = platformWidget();
155    if (!win)
156        return IntRect();
157
158    int width, height;
159
160    if (includeScrollbars)
161        win->GetSize(&width, &height);
162    else
163        win->GetClientSize(&width, &height);
164
165    return IntRect(m_data->viewStart.x, m_data->viewStart.y, width, height);
166}
167
168IntSize ScrollView::platformContentsSize() const
169{
170    int width = 0;
171    int height = 0;
172    if (platformWidget()) {
173        platformWidget()->GetVirtualSize(&width, &height);
174        ASSERT(width >= 0 && height >= 0);
175    }
176    return IntSize(width, height);
177}
178
179void ScrollView::platformSetScrollPosition(const IntPoint& scrollPoint)
180{
181    wxWindow* win = platformWidget();
182
183    wxPoint scrollOffset = m_data->viewStart;
184    wxPoint orig(scrollOffset);
185    wxPoint newScrollOffset(scrollPoint);
186
187    wxRect vRect(win->GetVirtualSize());
188    wxRect cRect(win->GetClientSize());
189
190    // clamp to scroll area
191    if (newScrollOffset.x < 0)
192        newScrollOffset.x = 0;
193    else if (newScrollOffset.x + cRect.width > vRect.width)
194        newScrollOffset.x = max(0, vRect.width - cRect.width);
195
196    if (newScrollOffset.y < 0)
197        newScrollOffset.y = 0;
198    else if (newScrollOffset.y + cRect.height > vRect.height)
199        newScrollOffset.y = max(0, vRect.height - cRect.height);
200
201    if (newScrollOffset == scrollOffset)
202        return;
203
204    m_data->viewStart = newScrollOffset;
205
206    wxPoint delta(orig - newScrollOffset);
207
208    if (canBlitOnScroll())
209        win->ScrollWindow(delta.x, delta.y);
210    else
211        win->Refresh();
212
213    adjustScrollbars();
214}
215
216bool ScrollView::platformScroll(ScrollDirection, ScrollGranularity)
217{
218    notImplemented();
219    return true;
220}
221
222void ScrollView::platformSetContentsSize()
223{
224    wxWindow* win = platformWidget();
225    if (!win)
226        return;
227
228    win->SetVirtualSize(m_contentsSize.width(), m_contentsSize.height());
229    adjustScrollbars();
230}
231
232void ScrollView::adjustScrollbars(int x, int y, bool refresh)
233{
234    wxWindow* win = platformWidget();
235    if (!win)
236        return;
237
238    wxRect crect(win->GetClientRect()), vrect(win->GetVirtualSize());
239
240    if (x == -1) x = m_data->viewStart.x;
241    if (y == -1) y = m_data->viewStart.y;
242
243    long style = win->GetWindowStyle();
244
245    // by setting the wxALWAYS_SHOW_SB wxWindow flag before
246    // each SetScrollbar call, we can control the scrollbars
247    // visibility individually.
248
249    // horizontal scrollbar
250    switch (m_data->hScrollbarMode) {
251        case ScrollbarAlwaysOff:
252            win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
253            win->SetScrollbar(wxHORIZONTAL, 0, 0, 0, refresh);
254            break;
255
256        case ScrollbarAuto:
257            win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
258            win->SetScrollbar(wxHORIZONTAL, x, crect.width, vrect.width, refresh);
259            break;
260
261        default: // ScrollbarAlwaysOn
262            win->SetWindowStyleFlag(style | wxALWAYS_SHOW_SB);
263            win->SetScrollbar(wxHORIZONTAL, x, crect.width, vrect.width, refresh);
264            break;
265    }
266
267    // vertical scrollbar
268    switch (m_data->vScrollbarMode) {
269        case ScrollbarAlwaysOff:
270            win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
271            win->SetScrollbar(wxVERTICAL, 0, 0, 0, refresh);
272            break;
273
274        case ScrollbarAlwaysOn:
275            win->SetWindowStyleFlag(style | wxALWAYS_SHOW_SB);
276            win->SetScrollbar(wxVERTICAL, y, crect.height, vrect.height, refresh);
277            break;
278
279        default: // case ScrollbarAuto:
280            win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
281            win->SetScrollbar(wxVERTICAL, y, crect.height, vrect.height, refresh);
282    }
283}
284
285void ScrollView::platformSetScrollbarModes()
286{
287    bool needsAdjust = false;
288
289    if (m_data->hScrollbarMode != horizontalScrollbarMode() ) {
290        m_data->hScrollbarMode = horizontalScrollbarMode();
291        needsAdjust = true;
292    }
293
294    if (m_data->vScrollbarMode != verticalScrollbarMode() ) {
295        m_data->vScrollbarMode = verticalScrollbarMode();
296        needsAdjust = true;
297    }
298
299    if (needsAdjust)
300        adjustScrollbars();
301}
302
303void ScrollView::platformScrollbarModes(ScrollbarMode& horizontal, ScrollbarMode& vertical) const
304{
305    horizontal = m_data->hScrollbarMode;
306    vertical = m_data->vScrollbarMode;
307}
308
309void ScrollView::platformSetCanBlitOnScroll(bool canBlitOnScroll)
310{
311    m_canBlitOnScroll = canBlitOnScroll;
312}
313
314bool ScrollView::platformCanBlitOnScroll() const
315{
316    return m_canBlitOnScroll;
317}
318
319// used for subframes support
320void ScrollView::platformAddChild(Widget* widget)
321{
322    // NB: In all cases I'm aware of,
323    // by the time this is called the ScrollView is already a child
324    // of its parent Widget by wx port APIs, so I don't think
325    // we need to do anything here.
326}
327
328void ScrollView::platformRemoveChild(Widget* widget)
329{
330    if (platformWidget()) {
331        platformWidget()->RemoveChild(widget->platformWidget());
332        // FIXME: Is this the right place to do deletion? I see
333        // detachFromParent2/3/4, initiated by FrameLoader::detachFromParent,
334        // but I'm not sure if it's better to handle there or not.
335        widget->platformWidget()->Destroy();
336    }
337}
338
339IntRect ScrollView::platformContentsToScreen(const IntRect& rect) const
340{
341    if (platformWidget()) {
342        wxRect wxrect = rect;
343        platformWidget()->ClientToScreen(&wxrect.x, &wxrect.y);
344        return wxrect;
345    }
346    return IntRect();
347}
348
349IntPoint ScrollView::platformScreenToContents(const IntPoint& point) const
350{
351    if (platformWidget()) {
352        return platformWidget()->ScreenToClient(point);
353    }
354    return IntPoint();
355}
356
357bool ScrollView::platformIsOffscreen() const
358{
359    return !platformWidget() || !platformWidget()->IsShownOnScreen();
360}
361
362}
363