* Copyright 2006-2009, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <bonefish@cs.tu-berlin.de>
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "ScrollView.h"
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <Bitmap.h>
#ifdef __HAIKU__
# include <LayoutUtils.h>
#endif
#include <Message.h>
#include <ScrollBar.h>
#include <Window.h>
#include "Scrollable.h"
#include "ScrollCornerBitmaps.h"
using namespace std;
class InternalScrollBar : public BScrollBar {
public:
InternalScrollBar(ScrollView* scrollView,
BRect frame,
orientation posture);
virtual ~InternalScrollBar();
virtual void ValueChanged(float value);
virtual void MouseDown(BPoint where);
virtual void MouseUp(BPoint where);
private:
ScrollView* fScrollView;
};
InternalScrollBar::InternalScrollBar(ScrollView* scrollView, BRect frame,
orientation posture)
: BScrollBar(frame, NULL, NULL, 0, 0, posture),
fScrollView(scrollView)
{
}
InternalScrollBar::~InternalScrollBar()
{
}
void
InternalScrollBar::ValueChanged(float value)
{
if (fScrollView)
fScrollView->_ScrollValueChanged(this, value);
}
void
InternalScrollBar::MouseDown(BPoint where)
{
if (fScrollView)
fScrollView->_SetScrolling(true);
BScrollBar::MouseDown(where);
}
void
InternalScrollBar::MouseUp(BPoint where)
{
BScrollBar::MouseUp(where);
if (fScrollView)
fScrollView->_SetScrolling(false);
}
class ScrollCorner : public BView {
public:
ScrollCorner(ScrollView* scrollView);
virtual ~ScrollCorner();
virtual void MouseDown(BPoint point);
virtual void MouseUp(BPoint point);
virtual void MouseMoved(BPoint point, uint32 transit,
const BMessage* message);
virtual void Draw(BRect updateRect);
virtual void WindowActivated(bool active);
void SetActive(bool active);
inline bool IsActive() const
{ return fState & STATE_ACTIVE; }
private:
ScrollView* fScrollView;
uint32 fState;
BPoint fStartPoint;
BPoint fStartScrollOffset;
BBitmap* fBitmaps[3];
inline bool IsEnabled() const
{ return ((fState & STATE_ENABLED) ==
STATE_ENABLED); }
void SetDragging(bool dragging);
inline bool IsDragging() const
{ return (fState & STATE_DRAGGING); }
enum {
STATE_DRAGGING = 0x01,
STATE_WINDOW_ACTIVE = 0x02,
STATE_ACTIVE = 0x04,
STATE_ENABLED = STATE_WINDOW_ACTIVE | STATE_ACTIVE,
};
};
ScrollCorner::ScrollCorner(ScrollView* scrollView)
: BView(BRect(0.0, 0.0, B_V_SCROLL_BAR_WIDTH - 1.0f, B_H_SCROLL_BAR_HEIGHT - 1.0f), NULL,
0, B_WILL_DRAW),
fScrollView(scrollView),
fState(0),
fStartPoint(0, 0),
fStartScrollOffset(0, 0)
{
SetViewColor(B_TRANSPARENT_32_BIT);
fBitmaps[0] = new BBitmap(BRect(0.0f, 0.0f, sBitmapWidth - 1,
sBitmapHeight - 1), sColorSpace);
char* bits = (char*)fBitmaps[0]->Bits();
int32 bpr = fBitmaps[0]->BytesPerRow();
for (int i = 0; i < sBitmapHeight; i++, bits += bpr) {
memcpy(bits, &sScrollCornerNormalBits[i * sBitmapHeight * 4],
sBitmapWidth * 4);
}
fBitmaps[1] = new BBitmap(BRect(0.0f, 0.0f, sBitmapWidth - 1,
sBitmapHeight - 1), sColorSpace);
bits = (char*)fBitmaps[1]->Bits();
bpr = fBitmaps[1]->BytesPerRow();
for (int i = 0; i < sBitmapHeight; i++, bits += bpr) {
memcpy(bits, &sScrollCornerPushedBits[i * sBitmapHeight * 4],
sBitmapWidth * 4);
}
fBitmaps[2] = new BBitmap(BRect(0.0f, 0.0f, sBitmapWidth - 1,
sBitmapHeight - 1), sColorSpace);
bits = (char*)fBitmaps[2]->Bits();
bpr = fBitmaps[2]->BytesPerRow();
for (int i = 0; i < sBitmapHeight; i++, bits += bpr) {
memcpy(bits, &sScrollCornerDisabledBits[i * sBitmapHeight * 4],
sBitmapWidth * 4);
}
}
ScrollCorner::~ScrollCorner()
{
for (int i = 0; i < 3; i++)
delete fBitmaps[i];
}
void
ScrollCorner::MouseDown(BPoint point)
{
BView::MouseDown(point);
uint32 buttons = 0;
Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons);
if (buttons & B_PRIMARY_MOUSE_BUTTON) {
SetMouseEventMask(B_POINTER_EVENTS);
if (fScrollView && IsEnabled() && Bounds().Contains(point)) {
SetDragging(true);
fStartPoint = point;
fStartScrollOffset = fScrollView->ScrollOffset();
}
}
}
void
ScrollCorner::MouseUp(BPoint point)
{
BView::MouseUp(point);
uint32 buttons = 0;
Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons);
if (!(buttons & B_PRIMARY_MOUSE_BUTTON))
SetDragging(false);
}
void
ScrollCorner::MouseMoved(BPoint point, uint32 transit, const BMessage* message)
{
BView::MouseMoved(point, transit, message);
if (IsDragging()) {
uint32 buttons = 0;
Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons);
if (buttons & B_PRIMARY_MOUSE_BUTTON) {
BPoint diff = point - fStartPoint;
if (fScrollView) {
fScrollView->_ScrollCornerValueChanged(fStartScrollOffset
- diff);
}
} else
SetDragging(false);
}
}
void
ScrollCorner::Draw(BRect updateRect)
{
if (IsEnabled()) {
if (IsDragging())
DrawBitmap(fBitmaps[1], BPoint(0.0f, 0.0f));
else
DrawBitmap(fBitmaps[0], BPoint(0.0f, 0.0f));
}
else
DrawBitmap(fBitmaps[2], BPoint(0.0f, 0.0f));
}
void
ScrollCorner::WindowActivated(bool active)
{
if (active != (fState & STATE_WINDOW_ACTIVE)) {
bool enabled = IsEnabled();
if (active)
fState |= STATE_WINDOW_ACTIVE;
else
fState &= ~STATE_WINDOW_ACTIVE;
if (enabled != IsEnabled())
Invalidate();
}
}
void
ScrollCorner::SetActive(bool active)
{
if (active != IsActive()) {
bool enabled = IsEnabled();
if (active)
fState |= STATE_ACTIVE;
else
fState &= ~STATE_ACTIVE;
if (enabled != IsEnabled())
Invalidate();
}
}
void
ScrollCorner::SetDragging(bool dragging)
{
if (dragging != IsDragging()) {
if (dragging)
fState |= STATE_DRAGGING;
else
fState &= ~STATE_DRAGGING;
Invalidate();
}
}
ScrollView::ScrollView(BView* child, uint32 scrollingFlags, BRect frame,
const char* name, uint32 resizingMode, uint32 viewFlags,
uint32 borderStyle, uint32 borderFlags)
: BView(frame, name, resizingMode,
viewFlags | B_FRAME_EVENTS | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
Scroller()
{
_Init(child, scrollingFlags, borderStyle, borderFlags);
}
#ifdef __HAIKU__
ScrollView::ScrollView(BView* child, uint32 scrollingFlags, const char* name,
uint32 viewFlags, uint32 borderStyle, uint32 borderFlags)
: BView(name, viewFlags | B_FRAME_EVENTS | B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE),
Scroller()
{
_Init(child, scrollingFlags, borderStyle, borderFlags);
}
#endif
ScrollView::~ScrollView()
{
}
void
ScrollView::AllAttached()
{
_Layout(_UpdateScrollBarVisibility());
}
void ScrollView::Draw(BRect updateRect)
{
if (fBorderStyle == B_NO_BORDER)
return;
rgb_color keyboardFocus = keyboard_navigation_color();
rgb_color light = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_LIGHTEN_MAX_TINT);
rgb_color shadow = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DARKEN_1_TINT);
rgb_color darkShadow = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DARKEN_2_TINT);
BRect r = Bounds();
if (fChildFocused && fWindowActive) {
SetHighColor(keyboardFocus);
StrokeRect(r);
} else {
if (fBorderStyle == B_PLAIN_BORDER) {
SetHighColor(darkShadow);
StrokeRect(r);
} else {
BeginLineArray(4);
AddLine(BPoint(r.left, r.bottom),
BPoint(r.left, r.top), shadow);
AddLine(BPoint(r.left + 1.0, r.top),
BPoint(r.right, r.top), shadow);
AddLine(BPoint(r.right, r.top + 1.0),
BPoint(r.right, r.bottom), light);
AddLine(BPoint(r.right - 1.0, r.bottom),
BPoint(r.left + 1.0, r.bottom), light);
EndLineArray();
}
}
if (fBorderStyle == B_PLAIN_BORDER)
return;
r.InsetBy(1, 1);
SetHighColor(darkShadow);
StrokeRect(r);
}
void
ScrollView::FrameResized(float width, float height)
{
_Layout(0);
}
void ScrollView::WindowActivated(bool activated)
{
fWindowActive = activated;
if (fChildFocused)
Invalidate();
}
#ifdef __HAIKU__
BSize
ScrollView::MinSize()
{
BSize size = (fChild ? fChild->MinSize() : BSize(-1, -1));
return _Size(size);
}
BSize
ScrollView::PreferredSize()
{
BSize size = (fChild ? fChild->PreferredSize() : BSize(-1, -1));
return _Size(size);
}
#endif
uint32
ScrollView::ScrollingFlags() const
{
return fScrollingFlags;
}
void
ScrollView::SetVisibleRectIsChildBounds(bool flag)
{
if (flag != VisibleRectIsChildBounds()) {
if (flag)
fScrollingFlags |= SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS;
else
fScrollingFlags &= ~SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS;
if (fChild && _UpdateScrollBarVisibility())
_Layout(0);
}
}
bool
ScrollView::VisibleRectIsChildBounds() const
{
return (fScrollingFlags & SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS);
}
BView*
ScrollView::Child() const
{
return fChild;
}
void
ScrollView::ChildFocusChanged(bool focused)
{
if (fChildFocused != focused) {
fChildFocused = focused;
Invalidate();
}
}
BScrollBar*
ScrollView::HScrollBar() const
{
return fHScrollBar;
}
BScrollBar*
ScrollView::VScrollBar() const
{
return fVScrollBar;
}
BView*
ScrollView::HVScrollCorner() const
{
return fScrollCorner;
}
void
ScrollView::SetHSmallStep(float hStep)
{
SetSmallSteps(hStep, fVSmallStep);
}
void
ScrollView::SetVSmallStep(float vStep)
{
SetSmallSteps(fHSmallStep, vStep);
}
void
ScrollView::SetSmallSteps(float hStep, float vStep)
{
if (fHSmallStep != hStep || fVSmallStep != vStep) {
fHSmallStep = hStep;
fVSmallStep = vStep;
_UpdateScrollBars();
}
}
void
ScrollView::GetSmallSteps(float* hStep, float* vStep) const
{
*hStep = fHSmallStep;
*vStep = fVSmallStep;
}
float
ScrollView::HSmallStep() const
{
return fHSmallStep;
}
float
ScrollView::VSmallStep() const
{
return fVSmallStep;
}
bool
ScrollView::IsScrolling() const
{
return fScrolling;
}
void
ScrollView::SetScrollingEnabled(bool enabled)
{
Scroller::SetScrollingEnabled(enabled);
if (IsScrollingEnabled())
SetScrollOffset(ScrollOffset());
}
void
ScrollView::DataRectChanged(BRect , BRect )
{
if (ScrollTarget()) {
if (_UpdateScrollBarVisibility())
_Layout(0);
else
_UpdateScrollBars();
}
}
void
ScrollView::ScrollOffsetChanged(BPoint , BPoint newOffset)
{
if (fHScrollBar && fHScrollBar->Value() != newOffset.x)
fHScrollBar->SetValue(newOffset.x);
if (fVScrollBar && fVScrollBar->Value() != newOffset.y)
fVScrollBar->SetValue(newOffset.y);
}
void
ScrollView::VisibleSizeChanged(float , float ,
float , float )
{
if (ScrollTarget()) {
if (_UpdateScrollBarVisibility())
_Layout(0);
else
_UpdateScrollBars();
}
}
void
ScrollView::ScrollTargetChanged(Scrollable* ,
Scrollable* newTarget)
{
if (fChild)
RemoveChild(fChild);
// add the new child
BView* view = dynamic_cast<BView*>(newTarget);
fChild = view;
if (view)
AddChild(view);
else if (newTarget) // set the scroll target to NULL, if it isn't a BView
SetScrollTarget(NULL);
*/
}
void
ScrollView::_Init(BView* child, uint32 scrollingFlags, uint32 borderStyle,
uint32 borderFlags)
{
fChild = NULL;
fScrollingFlags = scrollingFlags;
fHScrollBar = NULL;
fVScrollBar = NULL;
fScrollCorner = NULL;
fHVisible = true;
fVVisible = true;
fCornerVisible = true;
fWindowActive = false;
fChildFocused = false;
fScrolling = false;
fHSmallStep = 1;
fVSmallStep = 1;
fBorderStyle = borderStyle;
fBorderFlags = borderFlags;
SetViewColor(B_TRANSPARENT_32_BIT);
if (fScrollingFlags & (SCROLL_HORIZONTAL | SCROLL_HORIZONTAL_MAGIC)) {
fHScrollBar = new InternalScrollBar(this,
BRect(0.0, 0.0, 100.0, B_H_SCROLL_BAR_HEIGHT), B_HORIZONTAL);
AddChild(fHScrollBar);
}
if (fScrollingFlags & (SCROLL_VERTICAL | SCROLL_VERTICAL_MAGIC)) {
fVScrollBar = new InternalScrollBar(this,
BRect(0.0, 0.0, B_V_SCROLL_BAR_WIDTH, 100.0), B_VERTICAL);
AddChild(fVScrollBar);
}
if (fHScrollBar && fVScrollBar) {
fScrollCorner = new ScrollCorner(this);
AddChild(fScrollCorner);
}
if (child) {
fChild = child;
AddChild(child);
if (Scrollable* scrollable = dynamic_cast<Scrollable*>(child))
SetScrollTarget(scrollable);
}
}
void
ScrollView::_ScrollValueChanged(InternalScrollBar* scrollBar, float value)
{
if (!IsScrollingEnabled())
return;
switch (scrollBar->Orientation()) {
case B_HORIZONTAL:
if (fHScrollBar)
SetScrollOffset(BPoint(value, ScrollOffset().y));
break;
case B_VERTICAL:
if (fVScrollBar)
SetScrollOffset(BPoint(ScrollOffset().x, value));
break;
default:
break;
}
}
void
ScrollView::_ScrollCornerValueChanged(BPoint offset)
{
SetScrollOffset(offset);
}
void
ScrollView::_Layout(uint32 flags)
{
bool hbar = (fHScrollBar && fHVisible);
bool vbar = (fVScrollBar && fVVisible);
bool corner = (fScrollCorner && fCornerVisible);
BRect childRect(_ChildRect());
float innerWidth = childRect.Width();
float innerHeight = childRect.Height();
BPoint scrollLT(_InnerRect().LeftTop());
scrollLT.x--;
scrollLT.y--;
BPoint scrollRB(childRect.RightBottom() + BPoint(1.0f, 1.0f));
if (corner) {
fHScrollBar->MoveTo(scrollLT.x, scrollRB.y);
fHScrollBar->ResizeTo(innerWidth + 2.0, B_H_SCROLL_BAR_HEIGHT);
fVScrollBar->MoveTo(scrollRB.x, scrollLT.y);
fVScrollBar->ResizeTo(B_V_SCROLL_BAR_WIDTH, innerHeight + 2.0);
fScrollCorner->MoveTo(childRect.right + 2.0, childRect.bottom + 2.0);
} else if (hbar) {
fHScrollBar->MoveTo(scrollLT.x, scrollRB.y);
fHScrollBar->ResizeTo(innerWidth + 2.0, B_H_SCROLL_BAR_HEIGHT);
} else if (vbar) {
fVScrollBar->MoveTo(scrollRB.x, scrollLT.y);
fVScrollBar->ResizeTo(B_V_SCROLL_BAR_WIDTH, innerHeight + 2.0);
}
if (fChild) {
fChild->MoveTo(childRect.LeftTop());
fChild->ResizeTo(innerWidth, innerHeight);
if (VisibleRectIsChildBounds())
SetVisibleSize(innerWidth, innerHeight);
if (fChild->Window()) {
if (flags & SCROLL_HORIZONTAL && !fHVisible)
fChild->Invalidate(fHScrollBar->Frame());
if (flags & SCROLL_VERTICAL && !fVVisible)
fChild->Invalidate(fVScrollBar->Frame());
}
}
}
void
ScrollView::_UpdateScrollBars()
{
BRect dataRect = DataRect();
BRect visibleBounds = VisibleBounds();
if (!fScrollTarget) {
dataRect.Set(0.0, 0.0, 0.0, 0.0);
visibleBounds.Set(0.0, 0.0, 0.0, 0.0);
}
float hProportion = min_c(1.0f, (visibleBounds.Width() + 1.0f)
/ (dataRect.Width() + 1.0f));
float hMaxValue = max_c(dataRect.left,
dataRect.right - visibleBounds.Width());
float vProportion = min_c(1.0f, (visibleBounds.Height() + 1.0f)
/ (dataRect.Height() + 1.0f));
float vMaxValue = max_c(dataRect.top,
dataRect.bottom - visibleBounds.Height());
if (fHScrollBar) {
fHScrollBar->SetProportion(hProportion);
fHScrollBar->SetRange(dataRect.left, hMaxValue);
fHScrollBar->SetValue(fHScrollBar->Value());
fHScrollBar->SetSteps(fHSmallStep, visibleBounds.Width());
}
if (fVScrollBar) {
fVScrollBar->SetProportion(vProportion);
fVScrollBar->SetRange(dataRect.top, vMaxValue);
fVScrollBar->SetValue(fVScrollBar->Value());
fVScrollBar->SetSteps(fVSmallStep, visibleBounds.Height());
}
if (fScrollCorner) {
fScrollCorner->SetActive(hProportion < 1.0f || vProportion < 1.0f);
}
}
static inline
bool
set_visible_state(BView* view, bool visible, bool* currentlyVisible)
{
bool changed = false;
if (*currentlyVisible != visible) {
if (visible)
view->Show();
else
view->Hide();
*currentlyVisible = visible;
changed = true;
}
return changed;
}
uint32
ScrollView::_UpdateScrollBarVisibility()
{
uint32 changed = 0;
BRect childRect(_MaxVisibleRect());
float width = childRect.Width();
float height = childRect.Height();
BRect dataRect = DataRect();
float dataWidth = dataRect.Width();
float dataHeight = dataRect.Height();
bool hbar = (fScrollingFlags & SCROLL_HORIZONTAL_MAGIC);
bool vbar = (fScrollingFlags & SCROLL_VERTICAL_MAGIC);
if (!ScrollTarget()) {
if (hbar) {
if (set_visible_state(fHScrollBar, false, &fHVisible))
changed |= SCROLL_HORIZONTAL;
}
if (vbar) {
if (set_visible_state(fVScrollBar, false, &fVVisible))
changed |= SCROLL_VERTICAL;
}
} else if (hbar && width >= dataWidth && vbar && height >= dataHeight) {
if (set_visible_state(fHScrollBar, false, &fHVisible))
changed |= SCROLL_HORIZONTAL;
if (set_visible_state(fVScrollBar, false, &fVVisible))
changed |= SCROLL_VERTICAL;
} else {
BRect innerRect(_GuessVisibleRect(fHScrollBar, fVScrollBar));
float innerWidth = innerRect.Width();
float innerHeight = innerRect.Height();
if (hbar) {
if (innerWidth >= dataWidth) {
if (set_visible_state(fHScrollBar, false, &fHVisible))
changed |= SCROLL_HORIZONTAL;
} else {
if (set_visible_state(fHScrollBar, true, &fHVisible))
changed |= SCROLL_HORIZONTAL;
}
}
if (vbar) {
if (innerHeight >= dataHeight) {
if (set_visible_state(fVScrollBar, false, &fVVisible))
changed |= SCROLL_VERTICAL;
} else {
if (set_visible_state(fVScrollBar, true, &fVVisible))
changed |= SCROLL_VERTICAL;
}
}
}
if (changed && fScrollCorner)
set_visible_state(fScrollCorner, fHVisible && fVVisible, &fCornerVisible);
return changed;
}
BRect
ScrollView::_InnerRect() const
{
BRect r = Bounds();
float borderWidth = 0;
switch (fBorderStyle) {
case B_NO_BORDER:
break;
case B_PLAIN_BORDER:
borderWidth = 1;
break;
case B_FANCY_BORDER:
default:
borderWidth = 2;
break;
}
if (fBorderFlags & BORDER_LEFT)
r.left += borderWidth;
if (fBorderFlags & BORDER_TOP)
r.top += borderWidth;
if (fBorderFlags & BORDER_RIGHT)
r.right -= borderWidth;
if (fBorderFlags & BORDER_BOTTOM)
r.bottom -= borderWidth;
return r;
}
BRect
ScrollView::_ChildRect() const
{
return _ChildRect(fHScrollBar && fHVisible, fVScrollBar && fVVisible);
}
BRect
ScrollView::_ChildRect(bool hbar, bool vbar) const
{
BRect rect(_InnerRect());
if (vbar)
rect.right -= B_V_SCROLL_BAR_WIDTH;
if (hbar)
rect.bottom -= B_H_SCROLL_BAR_HEIGHT;
return rect;
}
BRect
ScrollView::_GuessVisibleRect(bool hbar, bool vbar) const
{
if (VisibleRectIsChildBounds())
return _ChildRect(hbar, vbar).OffsetToCopy(ScrollOffset());
return VisibleRect();
}
BRect
ScrollView::_MaxVisibleRect() const
{
return _GuessVisibleRect(true, true);
}
#ifdef __HAIKU__
BSize
ScrollView::_Size(BSize size)
{
if (fVVisible)
size.width += B_V_SCROLL_BAR_WIDTH;
if (fHVisible)
size.height += B_H_SCROLL_BAR_HEIGHT;
switch (fBorderStyle) {
case B_NO_BORDER:
if (fBorderFlags & BORDER_RIGHT)
size.width += fVVisible ? -1 : 0;
if (fBorderFlags & BORDER_BOTTOM)
size.height += fHVisible ? -1 : 0;
break;
case B_PLAIN_BORDER:
if (fBorderFlags & BORDER_LEFT)
size.width += 1;
if (fBorderFlags & BORDER_TOP)
size.height += 1;
if (fBorderFlags & BORDER_RIGHT)
size.width += fVVisible ? 0 : 1;
if (fBorderFlags & BORDER_BOTTOM)
size.height += fHVisible ? 0 : 1;
break;
case B_FANCY_BORDER:
default:
if (fBorderFlags & BORDER_LEFT)
size.width += 2;
if (fBorderFlags & BORDER_TOP)
size.height += 2;
if (fBorderFlags & BORDER_RIGHT)
size.width += fVVisible ? 1 : 2;
if (fBorderFlags & BORDER_BOTTOM)
size.height += fHVisible ? 1 : 2;
break;
}
return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
}
#endif
void
ScrollView::_SetScrolling(bool scrolling)
{
fScrolling = scrolling;
}