/*
* Copyright (C) 2007 Kevin Ollivier <kevino@theolliviers.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 "ScrollView.h"
#include "FloatRect.h"
#include "IntRect.h"
#include "NotImplemented.h"
#include "PlatformWheelEvent.h"
#include "Scrollbar.h"
#include <algorithm>
#include <stdio.h>
#include <wx/defs.h>
#include <wx/scrolbar.h>
#include <wx/scrolwin.h>
#include <wx/event.h>
using namespace std;
namespace WebCore {
class ScrollView::ScrollViewPrivate : public wxEvtHandler {
public:
ScrollViewPrivate(ScrollView* scrollView)
: wxEvtHandler()
, m_scrollView(scrollView)
, vScrollbarMode(ScrollbarAuto)
, hScrollbarMode(ScrollbarAuto)
, viewStart(0, 0)
{
}
void bindEvents(wxWindow* win)
{
// TODO: is there an easier way to Connect to a range of events? these are contiguous.
win->Connect(wxEVT_SCROLLWIN_TOP, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
win->Connect(wxEVT_SCROLLWIN_BOTTOM, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
win->Connect(wxEVT_SCROLLWIN_LINEUP, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
win->Connect(wxEVT_SCROLLWIN_LINEDOWN, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
win->Connect(wxEVT_SCROLLWIN_PAGEUP, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
win->Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
win->Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
win->Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxScrollWinEventHandler(ScrollViewPrivate::OnScrollWinEvents), NULL, this);
}
void OnScrollWinEvents(wxScrollWinEvent& e)
{
wxEventType scrollType(e.GetEventType());
bool horiz = e.GetOrientation() == wxHORIZONTAL;
wxPoint pos(viewStart);
if (scrollType == wxEVT_SCROLLWIN_THUMBTRACK || scrollType == wxEVT_SCROLLWIN_THUMBRELEASE) {
if (horiz)
pos.x = e.GetPosition();
else
pos.y = e.GetPosition();
}
else if (scrollType == wxEVT_SCROLLWIN_LINEDOWN) {
if (horiz)
pos.x += Scrollbar::pixelsPerLineStep();
else
pos.y += Scrollbar::pixelsPerLineStep();
}
else if (scrollType == wxEVT_SCROLLWIN_LINEUP) {
if (horiz)
pos.x -= Scrollbar::pixelsPerLineStep();
else
pos.y -= Scrollbar::pixelsPerLineStep();
}
else if (scrollType == wxEVT_SCROLLWIN_PAGEUP) {
if (horiz)
pos.x -= max<int>(m_scrollView->visibleWidth() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleWidth() - Scrollbar::maxOverlapBetweenPages());
else
pos.y -= max<int>(m_scrollView->visibleHeight() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleHeight() - Scrollbar::maxOverlapBetweenPages());
}
else if (scrollType == wxEVT_SCROLLWIN_PAGEDOWN) {
if (horiz)
pos.x += max<int>(m_scrollView->visibleWidth() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleWidth() - Scrollbar::maxOverlapBetweenPages());
else
pos.y += max<int>(m_scrollView->visibleHeight() * Scrollbar::minFractionToStepWhenPaging(), m_scrollView->visibleHeight() - Scrollbar::maxOverlapBetweenPages());
}
else
return e.Skip();
m_scrollView->setScrollPosition(IntPoint(pos.x, pos.y));
}
ScrollView* m_scrollView;
ScrollbarMode vScrollbarMode;
ScrollbarMode hScrollbarMode;
wxPoint viewStart;
};
void ScrollView::platformInit()
{
m_data = new ScrollViewPrivate(this);
}
void ScrollView::platformDestroy()
{
delete m_data;
}
void ScrollView::setPlatformWidget(wxWindow* win)
{
Widget::setPlatformWidget(win);
m_data->bindEvents(win);
}
void ScrollView::platformRepaintContentRectangle(const IntRect& updateRect, bool now)
{
// we need to convert coordinates to scrolled position
wxRect contentsRect = updateRect;
contentsRect.Offset(-scrollX(), -scrollY());
wxWindow* win = platformWidget();
if (win) {
win->RefreshRect(contentsRect, true);
if (now)
win->Update();
}
}
IntRect ScrollView::platformVisibleContentRect(bool includeScrollbars) const
{
wxWindow* win = platformWidget();
if (!win)
return IntRect();
int width, height;
if (includeScrollbars)
win->GetSize(&width, &height);
else
win->GetClientSize(&width, &height);
return IntRect(m_data->viewStart.x, m_data->viewStart.y, width, height);
}
IntSize ScrollView::platformContentsSize() const
{
int width = 0;
int height = 0;
if (platformWidget()) {
platformWidget()->GetVirtualSize(&width, &height);
ASSERT(width >= 0 && height >= 0);
}
return IntSize(width, height);
}
void ScrollView::platformSetScrollPosition(const IntPoint& scrollPoint)
{
wxWindow* win = platformWidget();
wxPoint scrollOffset = m_data->viewStart;
wxPoint orig(scrollOffset);
wxPoint newScrollOffset(scrollPoint);
wxRect vRect(win->GetVirtualSize());
wxRect cRect(win->GetClientSize());
// clamp to scroll area
if (newScrollOffset.x < 0)
newScrollOffset.x = 0;
else if (newScrollOffset.x + cRect.width > vRect.width)
newScrollOffset.x = max(0, vRect.width - cRect.width);
if (newScrollOffset.y < 0)
newScrollOffset.y = 0;
else if (newScrollOffset.y + cRect.height > vRect.height)
newScrollOffset.y = max(0, vRect.height - cRect.height);
if (newScrollOffset == scrollOffset)
return;
m_data->viewStart = newScrollOffset;
wxPoint delta(orig - newScrollOffset);
if (canBlitOnScroll())
win->ScrollWindow(delta.x, delta.y);
else
win->Refresh();
adjustScrollbars();
}
bool ScrollView::platformScroll(ScrollDirection, ScrollGranularity)
{
notImplemented();
return true;
}
void ScrollView::platformSetContentsSize()
{
wxWindow* win = platformWidget();
if (!win)
return;
win->SetVirtualSize(m_contentsSize.width(), m_contentsSize.height());
adjustScrollbars();
}
void ScrollView::adjustScrollbars(int x, int y, bool refresh)
{
wxWindow* win = platformWidget();
if (!win)
return;
wxRect crect(win->GetClientRect()), vrect(win->GetVirtualSize());
if (x == -1) x = m_data->viewStart.x;
if (y == -1) y = m_data->viewStart.y;
long style = win->GetWindowStyle();
// by setting the wxALWAYS_SHOW_SB wxWindow flag before
// each SetScrollbar call, we can control the scrollbars
// visibility individually.
// horizontal scrollbar
switch (m_data->hScrollbarMode) {
case ScrollbarAlwaysOff:
win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
win->SetScrollbar(wxHORIZONTAL, 0, 0, 0, refresh);
break;
case ScrollbarAuto:
win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
win->SetScrollbar(wxHORIZONTAL, x, crect.width, vrect.width, refresh);
break;
default: // ScrollbarAlwaysOn
win->SetWindowStyleFlag(style | wxALWAYS_SHOW_SB);
win->SetScrollbar(wxHORIZONTAL, x, crect.width, vrect.width, refresh);
break;
}
// vertical scrollbar
switch (m_data->vScrollbarMode) {
case ScrollbarAlwaysOff:
win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
win->SetScrollbar(wxVERTICAL, 0, 0, 0, refresh);
break;
case ScrollbarAlwaysOn:
win->SetWindowStyleFlag(style | wxALWAYS_SHOW_SB);
win->SetScrollbar(wxVERTICAL, y, crect.height, vrect.height, refresh);
break;
default: // case ScrollbarAuto:
win->SetWindowStyleFlag(style & ~wxALWAYS_SHOW_SB);
win->SetScrollbar(wxVERTICAL, y, crect.height, vrect.height, refresh);
}
}
void ScrollView::platformSetScrollbarModes()
{
bool needsAdjust = false;
if (m_data->hScrollbarMode != horizontalScrollbarMode() ) {
m_data->hScrollbarMode = horizontalScrollbarMode();
needsAdjust = true;
}
if (m_data->vScrollbarMode != verticalScrollbarMode() ) {
m_data->vScrollbarMode = verticalScrollbarMode();
needsAdjust = true;
}
if (needsAdjust)
adjustScrollbars();
}
void ScrollView::platformScrollbarModes(ScrollbarMode& horizontal, ScrollbarMode& vertical) const
{
horizontal = m_data->hScrollbarMode;
vertical = m_data->vScrollbarMode;
}
void ScrollView::platformSetCanBlitOnScroll(bool canBlitOnScroll)
{
m_canBlitOnScroll = canBlitOnScroll;
}
bool ScrollView::platformCanBlitOnScroll() const
{
return m_canBlitOnScroll;
}
// used for subframes support
void ScrollView::platformAddChild(Widget* widget)
{
// NB: In all cases I'm aware of,
// by the time this is called the ScrollView is already a child
// of its parent Widget by wx port APIs, so I don't think
// we need to do anything here.
}
void ScrollView::platformRemoveChild(Widget* widget)
{
if (platformWidget()) {
platformWidget()->RemoveChild(widget->platformWidget());
// FIXME: Is this the right place to do deletion? I see
// detachFromParent2/3/4, initiated by FrameLoader::detachFromParent,
// but I'm not sure if it's better to handle there or not.
widget->platformWidget()->Destroy();
}
}
IntRect ScrollView::platformContentsToScreen(const IntRect& rect) const
{
if (platformWidget()) {
wxRect wxrect = rect;
platformWidget()->ClientToScreen(&wxrect.x, &wxrect.y);
return wxrect;
}
return IntRect();
}
IntPoint ScrollView::platformScreenToContents(const IntPoint& point) const
{
if (platformWidget()) {
return platformWidget()->ScreenToClient(point);
}
return IntPoint();
}
bool ScrollView::platformIsOffscreen() const
{
return !platformWidget() || !platformWidget()->IsShownOnScreen();
}
}