Files
haiku-beta6/src/kits/interface/ScrollView.cpp
T

912 lines
19 KiB
C++

/*
* Copyright 2004-2009, Axel Dörfler, [email protected].
* Distributed under the terms of the MIT License.
*/
#include <ScrollView.h>
#include <ControlLook.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <Region.h>
#include <Window.h>
#include <binary_compatibility/Interface.h>
static const float kFancyBorderSize = 2;
static const float kPlainBorderSize = 1;
BScrollView::BScrollView(const char *name, BView *target, uint32 resizingMode,
uint32 flags, bool horizontal, bool vertical, border_style border)
: BView(_ComputeFrame(target, horizontal, vertical, border), name,
resizingMode, _ModifyFlags(flags, border)),
fTarget(target),
fBorder(border)
{
_Init(horizontal, vertical);
}
BScrollView::BScrollView(const char* name, BView* target, uint32 flags,
bool horizontal, bool vertical, border_style border)
: BView(name, _ModifyFlags(flags, border)),
fTarget(target),
fBorder(border)
{
_Init(horizontal, vertical);
}
BScrollView::BScrollView(BMessage *archive)
: BView(archive),
fHighlighted(false)
{
int32 border;
fBorder = archive->FindInt32("_style", &border) == B_OK ?
(border_style)border : B_FANCY_BORDER;
// In a shallow archive, we may not have a target anymore. We must
// be prepared for this case
// don't confuse our scroll bars with our (eventual) target
int32 firstBar = 0;
if (!archive->FindBool("_no_target_")) {
fTarget = ChildAt(0);
firstBar++;
} else
fTarget = NULL;
// search for our scroll bars
fHorizontalScrollBar = NULL;
fVerticalScrollBar = NULL;
BView *view;
while ((view = ChildAt(firstBar++)) != NULL) {
BScrollBar *bar = dynamic_cast<BScrollBar *>(view);
if (bar == NULL)
continue;
if (bar->Orientation() == B_HORIZONTAL)
fHorizontalScrollBar = bar;
else if (bar->Orientation() == B_VERTICAL)
fVerticalScrollBar = bar;
}
fPreviousWidth = uint16(Bounds().Width());
fPreviousHeight = uint16(Bounds().Height());
}
BScrollView::~BScrollView()
{
}
// #pragma mark -
BArchivable*
BScrollView::Instantiate(BMessage* archive)
{
if (validate_instantiation(archive, "BScrollView"))
return new BScrollView(archive);
return NULL;
}
status_t
BScrollView::Archive(BMessage* archive, bool deep) const
{
status_t status = BView::Archive(archive, deep);
if (status != B_OK)
return status;
// If this is a deep archive, the BView class will take care
// of our children.
if (status == B_OK && fBorder != B_FANCY_BORDER)
status = archive->AddInt32("_style", fBorder);
if (status == B_OK && fTarget == NULL)
status = archive->AddBool("_no_target_", true);
// The highlighted state is not archived, but since it is
// usually (or should be) used to indicate focus, this
// is probably the right thing to do.
return status;
}
void
BScrollView::AttachedToWindow()
{
BView::AttachedToWindow();
if ((fHorizontalScrollBar == NULL && fVerticalScrollBar == NULL)
|| (fHorizontalScrollBar != NULL && fVerticalScrollBar != NULL)
|| Window()->Look() != B_DOCUMENT_WINDOW_LOOK)
return;
// If we have only one bar, we need to check if we are in the
// bottom right edge of a window with the B_DOCUMENT_LOOK to
// adjust the size of the bar to acknowledge the resize knob.
BRect bounds = ConvertToScreen(Bounds());
BRect windowBounds = Window()->Frame();
if (bounds.right - _BorderSize() != windowBounds.right
|| bounds.bottom - _BorderSize() != windowBounds.bottom)
return;
if (fHorizontalScrollBar)
fHorizontalScrollBar->ResizeBy(-B_V_SCROLL_BAR_WIDTH, 0);
else if (fVerticalScrollBar)
fVerticalScrollBar->ResizeBy(0, -B_H_SCROLL_BAR_HEIGHT);
}
void
BScrollView::DetachedFromWindow()
{
BView::DetachedFromWindow();
}
void
BScrollView::AllAttached()
{
BView::AllAttached();
}
void
BScrollView::AllDetached()
{
BView::AllDetached();
}
void
BScrollView::Draw(BRect updateRect)
{
if (be_control_look != NULL) {
uint32 flags = 0;
if (fHighlighted && Window()->IsActive())
flags |= BControlLook::B_FOCUSED;
BRect rect(Bounds());
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
BRect verticalScrollBarFrame(0, 0, -1, -1);
if (fVerticalScrollBar)
verticalScrollBarFrame = fVerticalScrollBar->Frame();
BRect horizontalScrollBarFrame(0, 0, -1, -1);
if (fHorizontalScrollBar)
horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
be_control_look->DrawScrollViewFrame(this, rect, updateRect,
verticalScrollBarFrame, horizontalScrollBarFrame, base, fBorder,
flags);
return;
}
if (fBorder == B_PLAIN_BORDER) {
SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
StrokeRect(Bounds());
return;
} else if (fBorder != B_FANCY_BORDER)
return;
BRect bounds = Bounds();
SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
StrokeRect(bounds.InsetByCopy(1, 1));
if (fHighlighted && Window()->IsActive()) {
SetHighColor(ui_color(B_NAVIGATION_BASE_COLOR));
StrokeRect(bounds);
} else {
SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DARKEN_1_TINT));
StrokeLine(bounds.LeftBottom(), bounds.LeftTop());
bounds.left++;
StrokeLine(bounds.LeftTop(), bounds.RightTop());
SetHighColor(ui_color(B_SHINE_COLOR));
StrokeLine(bounds.LeftBottom(), bounds.RightBottom());
bounds.top++;
bounds.bottom--;
StrokeLine(bounds.RightBottom(), bounds.RightTop());
}
}
// #pragma mark -
void
BScrollView::WindowActivated(bool active)
{
if (fHighlighted)
Invalidate();
BView::WindowActivated(active);
}
void
BScrollView::MakeFocus(bool state)
{
BView::MakeFocus(state);
}
// #pragma mark -
void
BScrollView::GetPreferredSize(float *_width, float *_height)
{
BSize size = PreferredSize();
if (_width)
*_width = size.width;
if (_height)
*_height = size.height;
}
BSize
BScrollView::MinSize()
{
BSize size = _ComputeSize(fTarget != NULL ? fTarget->MinSize()
: BSize(16, 16));
return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
}
BSize
BScrollView::MaxSize()
{
BSize size = _ComputeSize(fTarget != NULL ? fTarget->MaxSize()
: BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
}
BSize
BScrollView::PreferredSize()
{
BSize size = _ComputeSize(fTarget != NULL ? fTarget->PreferredSize()
: BSize(32, 32));
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
}
void
BScrollView::ResizeToPreferred()
{
if (Window() == NULL)
return;
BView::ResizeToPreferred();
}
void
BScrollView::FrameMoved(BPoint position)
{
BView::FrameMoved(position);
}
void
BScrollView::FrameResized(float width, float height)
{
BView::FrameResized(width, height);
if (fBorder == B_NO_BORDER)
return;
BRect bounds = Bounds();
float border = _BorderSize() - 1;
if (be_control_look && fHorizontalScrollBar && fVerticalScrollBar) {
BRect scrollCorner(bounds);
scrollCorner.left = min_c(
fPreviousWidth - fVerticalScrollBar->Frame().Height(),
fHorizontalScrollBar->Frame().right + 1);
scrollCorner.top = min_c(
fPreviousHeight - fHorizontalScrollBar->Frame().Width(),
fVerticalScrollBar->Frame().bottom + 1);
Invalidate(scrollCorner);
}
// changes in width
if (bounds.Width() > fPreviousWidth) {
// invalidate the region between the old and the new right border
BRect rect = bounds;
rect.left += fPreviousWidth - border;
rect.right--;
Invalidate(rect);
} else if (bounds.Width() < fPreviousWidth) {
// invalidate the region of the new right border
BRect rect = bounds;
rect.left = rect.right - border;
Invalidate(rect);
}
// changes in height
if (bounds.Height() > fPreviousHeight) {
// invalidate the region between the old and the new bottom border
BRect rect = bounds;
rect.top += fPreviousHeight - border;
rect.bottom--;
Invalidate(rect);
} else if (bounds.Height() < fPreviousHeight) {
// invalidate the region of the new bottom border
BRect rect = bounds;
rect.top = rect.bottom - border;
Invalidate(rect);
}
fPreviousWidth = uint16(bounds.Width());
fPreviousHeight = uint16(bounds.Height());
}
// #pragma mark -
void
BScrollView::MessageReceived(BMessage* message)
{
switch (message->what) {
default:
BView::MessageReceived(message);
}
}
void
BScrollView::MouseDown(BPoint point)
{
BView::MouseDown(point);
}
void
BScrollView::MouseUp(BPoint point)
{
BView::MouseUp(point);
}
void
BScrollView::MouseMoved(BPoint point, uint32 code, const BMessage *dragMessage)
{
BView::MouseMoved(point, code, dragMessage);
}
// #pragma mark -
BScrollBar*
BScrollView::ScrollBar(orientation posture) const
{
if (posture == B_HORIZONTAL)
return fHorizontalScrollBar;
return fVerticalScrollBar;
}
void
BScrollView::SetBorder(border_style border)
{
if (fBorder == border)
return;
if (Flags() & B_SUPPORTS_LAYOUT) {
fBorder = border;
SetFlags(_ModifyFlags(Flags(), border));
DoLayout();
BRect bounds(Bounds());
Invalidate(BRect(bounds.LeftTop(), bounds.RightBottom()));
Invalidate(BRect(bounds.LeftBottom(), bounds.RightBottom()));
return;
}
float offset = _BorderSize() - _BorderSize(border);
float resize = 2 * offset;
float horizontalGap = 0, verticalGap = 0;
float change = 0;
if (border == B_NO_BORDER || fBorder == B_NO_BORDER) {
if (fHorizontalScrollBar != NULL)
verticalGap = border != B_NO_BORDER ? 1 : -1;
if (fVerticalScrollBar != NULL)
horizontalGap = border != B_NO_BORDER ? 1 : -1;
change = border != B_NO_BORDER ? -1 : 1;
if (fHorizontalScrollBar == NULL || fVerticalScrollBar == NULL)
change *= 2;
}
fBorder = border;
int32 savedResizingMode = 0;
if (fTarget != NULL) {
savedResizingMode = fTarget->ResizingMode();
fTarget->SetResizingMode(B_FOLLOW_NONE);
}
MoveBy(offset, offset);
ResizeBy(-resize - horizontalGap, -resize - verticalGap);
if (fTarget != NULL) {
fTarget->MoveBy(-offset, -offset);
fTarget->SetResizingMode(savedResizingMode);
}
if (fHorizontalScrollBar != NULL) {
fHorizontalScrollBar->MoveBy(-offset - verticalGap, offset + verticalGap);
fHorizontalScrollBar->ResizeBy(resize + horizontalGap - change, 0);
}
if (fVerticalScrollBar != NULL) {
fVerticalScrollBar->MoveBy(offset + horizontalGap, -offset - horizontalGap);
fVerticalScrollBar->ResizeBy(0, resize + verticalGap - change);
}
SetFlags(_ModifyFlags(Flags(), border));
}
border_style
BScrollView::Border() const
{
return fBorder;
}
status_t
BScrollView::SetBorderHighlighted(bool state)
{
if (fHighlighted == state)
return B_OK;
if (fBorder != B_FANCY_BORDER)
// highlighting only works for B_FANCY_BORDER
return B_ERROR;
fHighlighted = state;
if (fHorizontalScrollBar != NULL)
fHorizontalScrollBar->SetBorderHighlighted(state);
if (fVerticalScrollBar != NULL)
fVerticalScrollBar->SetBorderHighlighted(state);
BRect bounds = Bounds();
if (be_control_look != NULL)
bounds.InsetBy(1, 1);
Invalidate(BRect(bounds.left, bounds.top, bounds.right, bounds.top));
Invalidate(BRect(bounds.left, bounds.top + 1, bounds.left,
bounds.bottom - 1));
Invalidate(BRect(bounds.right, bounds.top + 1, bounds.right,
bounds.bottom - 1));
Invalidate(BRect(bounds.left, bounds.bottom, bounds.right, bounds.bottom));
return B_OK;
}
bool
BScrollView::IsBorderHighlighted() const
{
return fHighlighted;
}
void
BScrollView::SetTarget(BView *target)
{
if (fTarget == target)
return;
if (fTarget != NULL) {
fTarget->TargetedByScrollView(NULL);
RemoveChild(fTarget);
// we are not supposed to delete the view
}
fTarget = target;
if (fHorizontalScrollBar != NULL)
fHorizontalScrollBar->SetTarget(target);
if (fVerticalScrollBar != NULL)
fVerticalScrollBar->SetTarget(target);
if (target != NULL) {
target->MoveTo(_BorderSize(), _BorderSize());
BRect innerFrame = _InnerFrame();
target->ResizeTo(innerFrame.Width() - 1, innerFrame.Height() - 1);
target->TargetedByScrollView(this);
AddChild(target, ChildAt(0));
// This way, we are making sure that the target will
// be added top most in the list (which is important
// for unarchiving)
}
}
BView*
BScrollView::Target() const
{
return fTarget;
}
// #pragma mark -
BHandler*
BScrollView::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
int32 form, const char* property)
{
return BView::ResolveSpecifier(msg, index, specifier, form, property);
}
status_t
BScrollView::GetSupportedSuites(BMessage *data)
{
return BView::GetSupportedSuites(data);
}
status_t
BScrollView::Perform(perform_code code, void* _data)
{
switch (code) {
case PERFORM_CODE_MIN_SIZE:
((perform_data_min_size*)_data)->return_value
= BScrollView::MinSize();
return B_OK;
case PERFORM_CODE_MAX_SIZE:
((perform_data_max_size*)_data)->return_value
= BScrollView::MaxSize();
return B_OK;
case PERFORM_CODE_PREFERRED_SIZE:
((perform_data_preferred_size*)_data)->return_value
= BScrollView::PreferredSize();
return B_OK;
case PERFORM_CODE_LAYOUT_ALIGNMENT:
((perform_data_layout_alignment*)_data)->return_value
= BScrollView::LayoutAlignment();
return B_OK;
case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
((perform_data_has_height_for_width*)_data)->return_value
= BScrollView::HasHeightForWidth();
return B_OK;
case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
{
perform_data_get_height_for_width* data
= (perform_data_get_height_for_width*)_data;
BScrollView::GetHeightForWidth(data->width, &data->min, &data->max,
&data->preferred);
return B_OK;
}
case PERFORM_CODE_SET_LAYOUT:
{
perform_data_set_layout* data = (perform_data_set_layout*)_data;
BScrollView::SetLayout(data->layout);
return B_OK;
}
case PERFORM_CODE_LAYOUT_INVALIDATED:
{
perform_data_layout_invalidated* data
= (perform_data_layout_invalidated*)_data;
BScrollView::LayoutInvalidated(data->descendants);
return B_OK;
}
case PERFORM_CODE_DO_LAYOUT:
{
BScrollView::DoLayout();
return B_OK;
}
}
return BView::Perform(code, _data);
}
void
BScrollView::LayoutInvalidated(bool descendants)
{
}
void
BScrollView::DoLayout()
{
if (!(Flags() & B_SUPPORTS_LAYOUT))
return;
// If the user set a layout, we let the base class version call its hook.
if (GetLayout()) {
BView::DoLayout();
return;
}
BRect innerFrame = _InnerFrame();
if (fTarget != NULL) {
fTarget->MoveTo(innerFrame.left, innerFrame.top);
fTarget->ResizeTo(innerFrame.Width(), innerFrame.Height());
//BLayoutUtils::AlignInFrame(fTarget, fTarget->Bounds());
}
_AlignScrollBars(fHorizontalScrollBar != NULL, fVerticalScrollBar != NULL,
innerFrame);
}
// #pragma mark -
void
BScrollView::_Init(bool horizontal, bool vertical)
{
fHorizontalScrollBar = NULL;
fVerticalScrollBar = NULL;
fHighlighted = false;
if (be_control_look != NULL)
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
if (horizontal) {
fHorizontalScrollBar = new BScrollBar(BRect(0, 0, 14, 14), "_HSB_",
fTarget, 0, 1000, B_HORIZONTAL);
AddChild(fHorizontalScrollBar);
}
if (vertical) {
fVerticalScrollBar = new BScrollBar(BRect(0, 0, 14, 14), "_VSB_",
fTarget, 0, 1000, B_VERTICAL);
AddChild(fVerticalScrollBar);
}
BRect targetFrame;
if (fTarget) {
// layout target and add it
fTarget->TargetedByScrollView(this);
fTarget->MoveTo(B_ORIGIN);
if (fBorder != B_NO_BORDER)
fTarget->MoveBy(_BorderSize(), _BorderSize());
AddChild(fTarget);
targetFrame = fTarget->Frame();
} else {
// no target specified
targetFrame = Bounds();
if (horizontal)
targetFrame.bottom -= B_H_SCROLL_BAR_HEIGHT + 1;
if (vertical)
targetFrame.right -= B_V_SCROLL_BAR_WIDTH + 1;
if (fBorder == B_FANCY_BORDER) {
targetFrame.bottom--;
targetFrame.right--;
}
}
_AlignScrollBars(horizontal, vertical, targetFrame);
fPreviousWidth = uint16(Bounds().Width());
fPreviousHeight = uint16(Bounds().Height());
}
float
BScrollView::_BorderSize() const
{
return _BorderSize(fBorder);
}
BRect
BScrollView::_InnerFrame() const
{
BRect frame = Bounds();
float borderSize = _BorderSize();
frame.InsetBy(borderSize, borderSize);
if (fHorizontalScrollBar != NULL) {
frame.bottom -= B_H_SCROLL_BAR_HEIGHT;
if (borderSize == 0)
frame.bottom--;
}
if (fVerticalScrollBar != NULL) {
frame.right -= B_V_SCROLL_BAR_WIDTH;
if (borderSize == 0)
frame.right--;
}
return frame;
}
BSize
BScrollView::_ComputeSize(BSize targetSize) const
{
BRect frame = _ComputeFrame(
BRect(0, 0, targetSize.width, targetSize.height));
return BSize(frame.Width(), frame.Height());
}
BRect
BScrollView::_ComputeFrame(BRect targetRect) const
{
return _ComputeFrame(targetRect, fHorizontalScrollBar != NULL,
fVerticalScrollBar != NULL, fBorder);
}
void
BScrollView::_AlignScrollBars(bool horizontal, bool vertical, BRect targetFrame)
{
if (horizontal) {
BRect rect = targetFrame;
rect.top = rect.bottom + 1;
rect.bottom = rect.top + B_H_SCROLL_BAR_HEIGHT;
if (fBorder != B_NO_BORDER || vertical) {
// extend scrollbar so that it overlaps one pixel with vertical
// scrollbar
rect.right++;
}
if (fBorder != B_NO_BORDER) {
// the scrollbar draws part of the surrounding frame on the left
rect.left--;
}
fHorizontalScrollBar->MoveTo(rect.left, rect.top);
fHorizontalScrollBar->ResizeTo(rect.Width(), rect.Height());
}
if (vertical) {
BRect rect = targetFrame;
rect.left = rect.right + 1;
rect.right = rect.left + B_V_SCROLL_BAR_WIDTH;
if (fBorder != B_NO_BORDER || horizontal) {
// extend scrollbar so that it overlaps one pixel with vertical
// scrollbar
rect.bottom++;
}
if (fBorder != B_NO_BORDER) {
// the scrollbar draws part of the surrounding frame on the left
rect.top--;
}
fVerticalScrollBar->MoveTo(rect.left, rect.top);
fVerticalScrollBar->ResizeTo(rect.Width(), rect.Height());
}
}
/*! This static method is used to calculate the frame that the
ScrollView will cover depending on the frame of its target
and which border style is used.
It is used in the constructor and at other places.
*/
/*static*/ BRect
BScrollView::_ComputeFrame(BRect frame, bool horizontal, bool vertical,
border_style border)
{
if (vertical)
frame.right += B_V_SCROLL_BAR_WIDTH;
if (horizontal)
frame.bottom += B_H_SCROLL_BAR_HEIGHT;
float borderSize = _BorderSize(border);
frame.InsetBy(-borderSize, -borderSize);
if (borderSize == 0) {
if (vertical)
frame.right++;
if (horizontal)
frame.bottom++;
}
return frame;
}
/*static*/ BRect
BScrollView::_ComputeFrame(BView *target, bool horizontal, bool vertical,
border_style border)
{
return _ComputeFrame(target != NULL ? target->Frame()
: BRect(0, 0, 16, 16), horizontal, vertical, border);
}
/*! This method returns the size of the specified border.
*/
/*static*/ float
BScrollView::_BorderSize(border_style border)
{
if (border == B_FANCY_BORDER)
return kFancyBorderSize;
if (border == B_PLAIN_BORDER)
return kPlainBorderSize;
return 0;
}
/*! This method changes the "flags" argument as passed on to
the BView constructor.
*/
/*static*/ int32
BScrollView::_ModifyFlags(int32 flags, border_style border)
{
// We either need B_FULL_UPDATE_ON_RESIZE or
// B_FRAME_EVENTS if we have to draw a border
if (border != B_NO_BORDER)
return flags | B_WILL_DRAW | (flags & B_FULL_UPDATE_ON_RESIZE ? 0 : B_FRAME_EVENTS);
return flags & ~(B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE);
}
// #pragma mark -
BScrollView &
BScrollView::operator=(const BScrollView &)
{
return *this;
}
void BScrollView::_ReservedScrollView1() {}
void BScrollView::_ReservedScrollView2() {}
void BScrollView::_ReservedScrollView3() {}
void BScrollView::_ReservedScrollView4() {}
#if __GNUC__ == 2
extern "C" void
InvalidateLayout__11BScrollViewb(BScrollView* view)
{
view->Perform(PERFORM_CODE_LAYOUT_CHANGED, NULL);
}
#endif