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

1272 lines
31 KiB
C++

//------------------------------------------------------------------------------
// Copyright (c) 2001-2005 Haiku
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// File Name: ScrollBar.cpp
// Author: Marc Flerackers ([email protected])
// DarkWyrm ([email protected])
// Description: Client-side class for scrolling
//
//------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Message.h>
#include <OS.h>
#include <Window.h>
#include <ScrollBar.h>
//#define TEST_MODE
typedef enum {
ARROW_LEFT = 0,
ARROW_RIGHT,
ARROW_UP,
ARROW_DOWN,
ARROW_NONE
} arrow_direction;
#define SBC_SCROLLBYVALUE 0
#define SBC_SETDOUBLE 1
#define SBC_SETPROPORTIONAL 2
#define SBC_SETSTYLE 3
// Quick constants for determining which arrow is down and are defined with respect
// to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of arrows and
// ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up and ARROW4
// points right/down.
#define ARROW1 0
#define ARROW2 1
#define ARROW3 2
#define ARROW4 3
#define THUMB 4
#define NOARROW -1
// Because the R5 version kept a lot of data on server-side, we need to kludge our way
// into binary compatibility
class BScrollBar::Private {
public:
Private(BScrollBar* scrollBar)
:
fScrollBar(scrollBar),
fEnabled(true),
fRepeaterThread(-1),
fExitRepeater(false),
fThumbFrame(0.0, 0.0, -1.0, -1.0),
fDoRepeat(false),
fClickOffset(0.0, 0.0),
fThumbInc(0.0),
fStopValue(0.0),
fUpArrowsEnabled(true),
fDownArrowsEnabled(true),
fButtonDown(NOARROW)
{
#ifdef TEST_MODE
fScrollBarInfo.proportional = true;
fScrollBarInfo.double_arrows = true;
fScrollBarInfo.knob = 0;
fScrollBarInfo.min_knob_size = 15;
#else
get_scroll_bar_info(&fScrollBarInfo);
#endif
}
~Private()
{
if (fRepeaterThread >= 0) {
status_t dummy;
fExitRepeater = true;
wait_for_thread(fRepeaterThread, &dummy);
}
}
void DrawScrollBarButton(BScrollBar *owner, arrow_direction direction,
BRect frame, bool down = false);
static int32 button_repeater_thread(void* data);
int32 ButtonRepeaterThread();
BScrollBar* fScrollBar;
bool fEnabled;
// TODO: This should be a static, initialized by
// _init_interface_kit() at application startup-time,
// like BMenu::sMenuInfo
scroll_bar_info fScrollBarInfo;
thread_id fRepeaterThread;
volatile bool fExitRepeater;
BRect fThumbFrame;
volatile bool fDoRepeat;
BPoint fClickOffset;
float fThumbInc;
float fStopValue;
bool fUpArrowsEnabled;
bool fDownArrowsEnabled;
int8 fButtonDown;
};
// This thread is spawned when a button is initially pushed and repeatedly scrolls
// the scrollbar by a little bit after a short delay
int32
BScrollBar::Private::button_repeater_thread(void *data)
{
BScrollBar::Private* privateData = (BScrollBar::Private*)data;
return privateData->ButtonRepeaterThread();
}
int32
BScrollBar::Private::ButtonRepeaterThread()
{
// wait a bit before auto scrolling starts
snooze(500000);
// repeat loop
while (!fExitRepeater) {
if (fScrollBar->LockLooper()) {
if (fDoRepeat) {
float value = fScrollBar->Value() + fThumbInc;
if (fButtonDown == NOARROW) {
// in this case we want to stop when we're under the mouse
if (fThumbInc > 0.0 && value <= fStopValue)
fScrollBar->SetValue(value);
if (fThumbInc < 0.0 && value >= fStopValue)
fScrollBar->SetValue(value);
} else {
fScrollBar->SetValue(value);
}
}
fScrollBar->UnlockLooper();
}
snooze(25000);
}
// tell scrollbar we're gone
if (fScrollBar->LockLooper()) {
fRepeaterThread = -1;
fScrollBar->UnlockLooper();
}
return 0;
}
BScrollBar::BScrollBar(BRect frame, const char* name, BView *target,
float min, float max, orientation direction)
: BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
fMin(min),
fMax(max),
fSmallStep(1),
fLargeStep(10),
fValue(0),
fProportion(0.0),
fTarget(NULL),
fOrientation(direction),
fTargetName(NULL)
{
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
fPrivateData = new BScrollBar::Private(this);
SetTarget(target);
_UpdateThumbFrame();
_UpdateArrowButtons();
SetResizingMode((direction == B_VERTICAL) ?
B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT :
B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM );
}
BScrollBar::BScrollBar(BMessage *data)
: BView(data)
{
}
BScrollBar::~BScrollBar()
{
delete fPrivateData;
free(fTargetName);
// TODO: Disconnect from target
}
BArchivable *
BScrollBar::Instantiate(BMessage *data)
{
// TODO: Implement
return NULL;
}
status_t
BScrollBar::Archive(BMessage *data, bool deep) const
{
BView::Archive(data,deep);
data->AddFloat("_range",fMin);
data->AddFloat("_range",fMax);
data->AddFloat("_steps",fSmallStep);
data->AddFloat("_steps",fLargeStep);
data->AddFloat("_val",fValue);
data->AddInt32("_orient",(int32)fOrientation);
data->AddInt32("_prop",fProportion);
return B_OK;
}
void
BScrollBar::AttachedToWindow()
{
// R5's SB contacts the server if fValue!=0. I *think* we don't need to do anything here...
}
void
BScrollBar::SetValue(float value)
{
if (value > fMax)
value = fMax;
if (value < fMin)
value = fMin;
if (fValue != value) {
fValue = value;
ValueChanged(fValue);
}
}
// Value
float
BScrollBar::Value() const
{
return fValue;
}
// SetProportion
void
BScrollBar::SetProportion(float value)
{
if (value < 0.0)
value = 0.0;
if (value > 1.0)
value = 1.0;
if (value != fProportion) {
fProportion = value;
_UpdateThumbFrame();
}
}
float
BScrollBar::Proportion() const
{
return fProportion;
}
void
BScrollBar::ValueChanged(float newValue)
{
/*
From the BeBook:
Responds to a notification that the value of the scroll bar has changed to
newValue. For a horizontal scroll bar, this function interprets newValue
as the coordinate value that should be at the left side of the target
view's bounds rectangle. For a vertical scroll bar, it interprets
newValue as the coordinate value that should be at the top of the rectangle.
It calls ScrollTo() to scroll the target's contents into position, unless
they have already been scrolled.
ValueChanged() is called as the result both of user actions
(B_VALUE_CHANGED messages received from the Application Server) and of
programmatic ones. Programmatically, scrolling can be initiated by the
target view (calling ScrollTo()) or by the BScrollBar
(calling SetValue() or SetRange()).
In all these cases, the target view and the scroll bars need to be kept
in synch. This is done by a chain of function calls: ValueChanged() calls
ScrollTo(), which in turn calls SetValue(), which then calls
ValueChanged() again. It's up to ValueChanged() to get off this
merry-go-round, which it does by checking the target view's bounds
rectangle. If newValue already matches the left or top side of the
bounds rectangle, if forgoes calling ScrollTo().
ValueChanged() does nothing if a target BView hasn't been set—or
if the target has been set by name, but the name doesn't correspond to
an actual BView within the scroll bar's window.
*/
if (fTarget) {
// cache target bounds
BRect targetBounds = fTarget->Bounds();
// if vertical, check bounds top and scroll if different from newValue
if (fOrientation == B_VERTICAL && targetBounds.top != newValue) {
fTarget->ScrollTo(targetBounds.left, newValue);
}
// if horizontal, check bounds left and scroll if different from newValue
if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue) {
fTarget->ScrollTo(newValue, targetBounds.top);
}
}
_UpdateThumbFrame();
_UpdateArrowButtons();
}
void
BScrollBar::SetRange(float min, float max)
{
fMin = min;
fMax = max;
if (fValue > fMax)
fValue = fMax;
else if (fValue < fMin)
fValue = fMin;
Invalidate();
// Just a sort-of hack for now. ValueChanged is called, but with
// what value??
ValueChanged(fValue);
}
void
BScrollBar::GetRange(float *min, float *max) const
{
if (min != NULL)
*min = fMin;
if (max != NULL)
*max = fMax;
}
void
BScrollBar::SetSteps(float smallStep, float largeStep)
{
// Under R5, steps can be set only after being attached to a window, probably because
// the data is kept server-side. We'll just remove that limitation... :P
// The BeBook also says that we need to specify an integer value even though the step
// values are floats. For the moment, we'll just make sure that they are integers
fSmallStep = (int32)smallStep;
fLargeStep = (int32)largeStep;
// TODO: test use of fractional values and make them work properly if they don't
}
void
BScrollBar::GetSteps(float *smallStep, float *largeStep) const
{
if (smallStep != NULL)
*smallStep = fSmallStep;
if (largeStep)
*largeStep = fLargeStep;
}
void
BScrollBar::SetTarget(BView *target)
{
fTarget = target;
free(fTargetName);
if (fTarget) {
fTargetName = strdup(target->Name());
if (fOrientation == B_VERTICAL)
fTarget->fVerScroller = this;
else
fTarget->fHorScroller = this;
} else
fTargetName = NULL;
}
void
BScrollBar::SetTarget(const char *targetName)
{
if (!targetName)
return;
if (!Window())
debugger("Method requires window and doesn't have one");
BView *target = Window()->FindView(targetName);
if (target)
SetTarget(target);
}
BView *
BScrollBar::Target() const
{
return fTarget;
}
orientation
BScrollBar::Orientation() const
{
return fOrientation;
}
void
BScrollBar::MessageReceived(BMessage *msg)
{
switch(msg->what) {
case B_VALUE_CHANGED:
{
int32 value;
if (msg->FindInt32("value", &value) == B_OK)
ValueChanged(value);
break;
}
default:
BView::MessageReceived(msg);
break;
}
}
void
BScrollBar::MouseDown(BPoint where)
{
if (!fPrivateData->fEnabled || fMin == fMax)
return;
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
// hit test for the thumb
if (fPrivateData->fThumbFrame.Contains(where)) {
fPrivateData->fButtonDown = THUMB;
fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where;
// Invalidate(fPrivateData->fThumbFrame);
return;
}
// hit test for arrows or empty area
float scrollValue = 0.0;
fPrivateData->fButtonDown = _ButtonFor(where);
switch (fPrivateData->fButtonDown) {
case ARROW1:
scrollValue = -fSmallStep;
break;
case ARROW2:
scrollValue = fSmallStep;
break;
case ARROW3:
scrollValue = -fSmallStep;
break;
case ARROW4:
scrollValue = fSmallStep;
break;
case NOARROW:
// we hit the empty area, figure out which side of the thumb
if (fOrientation == B_VERTICAL) {
if (where.y < fPrivateData->fThumbFrame.top)
scrollValue = -fLargeStep;
else
scrollValue = fLargeStep;
} else {
if (where.x < fPrivateData->fThumbFrame.left)
scrollValue = -fLargeStep;
else
scrollValue = fLargeStep;
}
_UpdateTargetValue(where);
break;
}
if (scrollValue != 0.0) {
SetValue(fValue + scrollValue);
Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
// launch the repeat thread
if (fPrivateData->fRepeaterThread == -1) {
fPrivateData->fExitRepeater = false;
fPrivateData->fThumbInc = scrollValue;
fPrivateData->fDoRepeat = true;
fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->button_repeater_thread,
"scroll repeater", B_NORMAL_PRIORITY, fPrivateData);
resume_thread(fPrivateData->fRepeaterThread);
}
}
}
// MouseUp
void
BScrollBar::MouseUp(BPoint pt)
{
fPrivateData->fButtonDown = NOARROW;
fPrivateData->fExitRepeater = true;
fPrivateData->fDoRepeat = false;
Invalidate();
}
// MouseMoved
void
BScrollBar::MouseMoved(BPoint where, uint32 transit, const BMessage* message)
{
if (!fPrivateData->fEnabled || fMin == fMax)
return;
if (fPrivateData->fButtonDown != NOARROW) {
if (fPrivateData->fButtonDown == THUMB) {
SetValue(_ValueFor(where + fPrivateData->fClickOffset));
} else {
// suspend the repeating if the mouse is not over the button
bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains(where);
if (fPrivateData->fDoRepeat != repeat) {
fPrivateData->fDoRepeat = repeat;
Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
}
}
} else {
// update the value at which we want to stop repeating
if (fPrivateData->fDoRepeat) {
_UpdateTargetValue(where);
// we might have to turn arround
if ((fValue < fPrivateData->fStopValue && fPrivateData->fThumbInc < 0) ||
(fValue > fPrivateData->fStopValue && fPrivateData->fThumbInc > 0))
fPrivateData->fThumbInc = -fPrivateData->fThumbInc;
}
}
}
// DetachedFromWindow
void
BScrollBar::DetachedFromWindow()
{
fTarget = NULL;
free(fTargetName);
fTargetName = NULL;
}
// Draw
void
BScrollBar::Draw(BRect updateRect)
{
BRect bounds = Bounds();
rgb_color normal = ui_color(B_PANEL_BACKGROUND_COLOR);
// stroke a dark frame arround the entire scrollbar (independent of enabled state)
SetHighColor(tint_color(normal, B_DARKEN_2_TINT));
StrokeRect(bounds);
bounds.InsetBy(1.0, 1.0);
rgb_color light, light1, dark, dark1, dark2;
if (fPrivateData->fEnabled) {
light = tint_color(normal, B_LIGHTEN_MAX_TINT);
light1 = tint_color(normal, B_LIGHTEN_1_TINT);
dark = tint_color(normal, B_DARKEN_3_TINT);
dark1 = tint_color(normal, B_DARKEN_1_TINT);
dark2 = tint_color(normal, B_DARKEN_2_TINT);
} else {
light = tint_color(normal, B_LIGHTEN_1_TINT);
light1 = normal;
dark = tint_color(normal, B_DARKEN_2_TINT);
dark1 = normal;
dark2 = tint_color(normal, B_DARKEN_1_TINT);
}
SetDrawingMode(B_OP_OVER);
BRect thumbBG = bounds;
// Draw arrows
if (fOrientation == B_HORIZONTAL) {
BRect buttonFrame(bounds.left, bounds.top, bounds.left + bounds.Height(), bounds.bottom);
_DrawArrowButton(ARROW_LEFT, buttonFrame,
fPrivateData->fButtonDown == ARROW1);
if (_DoubleArrows()) {
buttonFrame.OffsetBy(bounds.Height() + 1, 0.0);
_DrawArrowButton(ARROW_RIGHT, buttonFrame,
fPrivateData->fButtonDown == ARROW2);
buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1), bounds.top);
_DrawArrowButton(ARROW_LEFT, buttonFrame,
fPrivateData->fButtonDown == ARROW3);
thumbBG.left += bounds.Height() * 2 + 2;
thumbBG.right -= bounds.Height() * 2 + 2;
} else {
thumbBG.left += bounds.Height() + 1;
thumbBG.right -= bounds.Height() + 1;
}
buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top);
_DrawArrowButton(ARROW_RIGHT, buttonFrame,
fPrivateData->fButtonDown == ARROW4);
} else {
BRect buttonFrame(bounds.left, bounds.top, bounds.right, bounds.top + bounds.Width());
_DrawArrowButton(ARROW_UP, buttonFrame,
fPrivateData->fButtonDown == ARROW1);
if (_DoubleArrows()) {
buttonFrame.OffsetBy(0.0, bounds.Width() + 1);
_DrawArrowButton(ARROW_DOWN, buttonFrame,
fPrivateData->fButtonDown == ARROW2);
buttonFrame.OffsetTo(bounds.left, bounds.bottom - ((bounds.Width() * 2) + 1));
_DrawArrowButton(ARROW_UP, buttonFrame,
fPrivateData->fButtonDown == ARROW3);
thumbBG.top += bounds.Width() * 2 + 2;
thumbBG.bottom -= bounds.Width() * 2 + 2;
} else {
thumbBG.top += bounds.Width() + 1;
thumbBG.bottom -= bounds.Width() + 1;
}
buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width());
_DrawArrowButton(ARROW_DOWN, buttonFrame,
fPrivateData->fButtonDown == ARROW4);
}
SetDrawingMode(B_OP_COPY);
// background for thumb area
// frame
BeginLineArray(4);
if (fOrientation == B_HORIZONTAL) {
AddLine(BPoint(thumbBG.left, thumbBG.bottom),
BPoint(thumbBG.left, thumbBG.top), dark);
AddLine(BPoint(thumbBG.left + 1, thumbBG.top),
BPoint(thumbBG.right - 1, thumbBG.top), dark2);
AddLine(BPoint(thumbBG.right, thumbBG.top),
BPoint(thumbBG.right, thumbBG.bottom), dark);
AddLine(BPoint(thumbBG.right - 1, thumbBG.bottom),
BPoint(thumbBG.left + 1, thumbBG.bottom), normal);
} else {
AddLine(BPoint(thumbBG.left, thumbBG.bottom - 1),
BPoint(thumbBG.left, thumbBG.top - 1), dark2);
AddLine(BPoint(thumbBG.left, thumbBG.top),
BPoint(thumbBG.right, thumbBG.top), dark);
AddLine(BPoint(thumbBG.right, thumbBG.top + 1),
BPoint(thumbBG.right, thumbBG.bottom - 1), normal);
AddLine(BPoint(thumbBG.right, thumbBG.bottom),
BPoint(thumbBG.left, thumbBG.bottom), dark);
}
EndLineArray();
thumbBG.InsetBy(1.0, 1.0);
SetHighColor(dark1);
// Draw scroll thumb
if (fPrivateData->fEnabled) {
// fill and additional dark lines
BRect rect(fPrivateData->fThumbFrame);
if (fOrientation == B_HORIZONTAL) {
BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1, thumbBG.bottom);
if (leftOfThumb.IsValid())
FillRect(leftOfThumb);
BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right, thumbBG.bottom);
if (rightOfThumb.IsValid())
FillRect(rightOfThumb);
if (rect.left > thumbBG.left) {
// dark line before thumb
SetHighColor(dark);
StrokeLine(BPoint(rect.left - 1, rect.top), BPoint(rect.left - 1, rect.bottom));
/* if (rect.left > thumbBG.left + 1) {
// shadow line behind left arrows
SetHighColor(dark2);
StrokeLine(BPoint(thumbBG.left + 1, thumbBG.top), BPoint(thumbBG.left + 1, thumbBG.bottom));
}*/
}
if (rect.right < thumbBG.right) {
// dark line behind thumb
SetHighColor(dark);
StrokeLine(BPoint(rect.right + 1, rect.top), BPoint(rect.right + 1, rect.bottom));
/* if (rect.right < thumbBG.right - 1) {
// shadow line behind thumb
SetHighColor(dark2);
StrokeLine(BPoint(rect.right + 2, rect.top), BPoint(rect.right + 2, rect.bottom));
}*/
}
} else {
BRect topOfThumb(thumbBG.left, thumbBG.top, thumbBG.right, rect.top - 1);
if (topOfThumb.IsValid())
FillRect(topOfThumb);
BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, thumbBG.right, thumbBG.bottom);
if (bottomOfThumb.IsValid())
FillRect(bottomOfThumb);
// dark lines before and after thumb
SetHighColor(dark);
if (rect.top > thumbBG.top)
StrokeLine(BPoint(rect.left, rect.top - 1), BPoint(rect.right, rect.top - 1));
if (rect.bottom < thumbBG.bottom)
StrokeLine(BPoint(rect.left, rect.bottom + 1), BPoint(rect.right, rect.bottom + 1));
}
BeginLineArray(4);
AddLine(BPoint(rect.left, rect.bottom),
BPoint(rect.left, rect.top), light);
AddLine(BPoint(rect.left + 1, rect.top),
BPoint(rect.right, rect.top), light);
AddLine(BPoint(rect.right, rect.top + 1),
BPoint(rect.right, rect.bottom), dark1);
AddLine(BPoint(rect.right - 1, rect.bottom),
BPoint(rect.left + 1, rect.bottom), dark1);
EndLineArray();
// fill
rect.InsetBy(1.0, 1.0);
/*if (fPrivateData->fButtonDown == THUMB)
SetHighColor(tint_color(normal, B_DARKEN_1_TINT));
else*/
SetHighColor(normal);
FillRect(rect);
// TODO: Add the other thumb styles - dots and lines
} else {
FillRect(thumbBG);
}
}
void
BScrollBar::FrameMoved(BPoint new_position)
{
}
void
BScrollBar::FrameResized(float new_width, float new_height)
{
_UpdateThumbFrame();
Invalidate();
}
BHandler *
BScrollBar::ResolveSpecifier(BMessage *msg,int32 index,
BMessage *specifier,int32 form,const char *property)
{
// TODO: Implement
return NULL;
}
void
BScrollBar::ResizeToPreferred()
{
// TODO: Implement
}
void
BScrollBar::GetPreferredSize(float *width, float *height)
{
if (fOrientation == B_VERTICAL)
*width = B_V_SCROLL_BAR_WIDTH;
else if (fOrientation == B_HORIZONTAL)
*height = B_H_SCROLL_BAR_HEIGHT;
}
void
BScrollBar::MakeFocus(bool state)
{
// TODO: ?!? Really?
if (fTarget)
fTarget->MakeFocus(state);
}
void
BScrollBar::AllAttached()
{
}
void
BScrollBar::AllDetached()
{
}
status_t
BScrollBar::GetSupportedSuites(BMessage *data)
{
return B_ERROR;
}
status_t
BScrollBar::Perform(perform_code d, void *arg)
{
return BView::Perform(d, arg);
}
void BScrollBar::_ReservedScrollBar1() {}
void BScrollBar::_ReservedScrollBar2() {}
void BScrollBar::_ReservedScrollBar3() {}
void BScrollBar::_ReservedScrollBar4() {}
BScrollBar &
BScrollBar::operator=(const BScrollBar &)
{
return *this;
}
// _DoubleArrows
bool
BScrollBar::_DoubleArrows() const
{
if (!fPrivateData->fScrollBarInfo.double_arrows)
return false;
// if there is not enough room, switch to single arrows even though
// double arrows is specified
if (fOrientation == B_HORIZONTAL)
return Bounds().Width() > (Bounds().Height() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2;
else
return Bounds().Height() > (Bounds().Width() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2;
}
// _UpdateThumbFrame
void
BScrollBar::_UpdateThumbFrame()
{
BRect bounds = Bounds();
bounds.InsetBy(1.0, 1.0);
BRect oldFrame = fPrivateData->fThumbFrame;
fPrivateData->fThumbFrame = bounds;
float minSize = fPrivateData->fScrollBarInfo.min_knob_size;
float maxSize;
float buttonSize;
// assume square buttons
if (fOrientation == B_VERTICAL) {
maxSize = bounds.Height();
buttonSize = bounds.Width() + 1.0;
} else {
maxSize = bounds.Width();
buttonSize = bounds.Height() + 1.0;
}
if (_DoubleArrows()) {
// subtract the size of four buttons
maxSize -= buttonSize * 4;
} else {
// subtract the size of two buttons
maxSize -= buttonSize * 2;
}
float thumbSize = minSize;
if (fPrivateData->fScrollBarInfo.proportional)
thumbSize += (maxSize - minSize) * fProportion;
// the thumb can be scrolled within the remaining area "maxSize - thumbSize"
float offset = floorf(((fValue - fMin) / (fMax - fMin + 1.0)) * (maxSize - thumbSize) + 0.5);
if (_DoubleArrows()) {
offset += buttonSize * 2;
} else {
offset += buttonSize;
}
if (fOrientation == B_VERTICAL) {
fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top + thumbSize;
fPrivateData->fThumbFrame.OffsetBy(0.0, offset);
} else {
fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left + thumbSize;
fPrivateData->fThumbFrame.OffsetBy(offset, 0.0);
}
if (Window()) {
BRect invalid = oldFrame.IsValid() ? oldFrame | fPrivateData->fThumbFrame : fPrivateData->fThumbFrame;
// account for those two dark lines
if (fOrientation == B_HORIZONTAL)
invalid.InsetBy(-1.0, 0.0);
else
invalid.InsetBy(0.0, -1.0);
Invalidate(invalid);
}
}
// _ValueFor
float
BScrollBar::_ValueFor(BPoint where) const
{
BRect bounds = Bounds();
bounds.InsetBy(1.0, 1.0);
float offset;
float thumbSize;
float maxSize;
float buttonSize;
if (fOrientation == B_VERTICAL) {
offset = where.y;
thumbSize = fPrivateData->fThumbFrame.Height();
maxSize = bounds.Height();
buttonSize = bounds.Width() + 1.0;
} else {
offset = where.x;
thumbSize = fPrivateData->fThumbFrame.Width();
maxSize = bounds.Width();
buttonSize = bounds.Height() + 1.0;
}
if (_DoubleArrows()) {
// subtract the size of four buttons
maxSize -= buttonSize * 4;
// convert point to inside of area between buttons
offset -= buttonSize * 2;
} else {
// subtract the size of two buttons
maxSize -= buttonSize * 2;
// convert point to inside of area between buttons
offset -= buttonSize;
}
float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0));
if (value >= 0.0)
return floorf(value + 0.5);
else
return ceilf(value - 0.5);
}
// _ButtonFor
int32
BScrollBar::_ButtonFor(BPoint where) const
{
BRect bounds = Bounds();
bounds.InsetBy(1.0, 1.0);
float buttonSize;
if (fOrientation == B_VERTICAL) {
buttonSize = bounds.Width() + 1.0;
} else {
buttonSize = bounds.Height() + 1.0;
}
BRect rect(bounds.left, bounds.top,
bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0);
if (fOrientation == B_VERTICAL) {
if (rect.Contains(where))
return ARROW1;
if (_DoubleArrows()) {
rect.OffsetBy(0.0, buttonSize);
if (rect.Contains(where))
return ARROW2;
rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
if (rect.Contains(where))
return ARROW3;
}
rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
if (rect.Contains(where))
return ARROW4;
} else {
if (rect.Contains(where))
return ARROW1;
if (_DoubleArrows()) {
rect.OffsetBy(buttonSize, 0.0);
if (rect.Contains(where))
return ARROW2;
rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
if (rect.Contains(where))
return ARROW3;
}
rect.OffsetTo(bounds.right - buttonSize, bounds.top);
if (rect.Contains(where))
return ARROW4;
}
return NOARROW;
}
// _ButtonRectFor
BRect
BScrollBar::_ButtonRectFor(int32 button) const
{
BRect bounds = Bounds();
bounds.InsetBy(1.0, 1.0);
float buttonSize;
if (fOrientation == B_VERTICAL) {
buttonSize = bounds.Width() + 1.0;
} else {
buttonSize = bounds.Height() + 1.0;
}
BRect rect(bounds.left, bounds.top,
bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0);
if (fOrientation == B_VERTICAL) {
switch (button) {
case ARROW1:
break;
case ARROW2:
rect.OffsetBy(0.0, buttonSize);
break;
case ARROW3:
rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
break;
case ARROW4:
rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
break;
}
} else {
switch (button) {
case ARROW1:
break;
case ARROW2:
rect.OffsetBy(buttonSize, 0.0);
break;
case ARROW3:
rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
break;
case ARROW4:
rect.OffsetTo(bounds.right - buttonSize, bounds.top);
break;
}
}
return rect;
}
// _UpdateTargetValue
void
BScrollBar::_UpdateTargetValue(BPoint where)
{
if (fOrientation == B_VERTICAL)
fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y - fPrivateData->fThumbFrame.Height() / 2.0));
else
fPrivateData->fStopValue = _ValueFor(BPoint(where.x - fPrivateData->fThumbFrame.Width() / 2.0, where.y));
}
// _UpdateArrowButtons
void
BScrollBar::_UpdateArrowButtons()
{
bool upEnabled = fValue > fMin;
if (fPrivateData->fUpArrowsEnabled != upEnabled) {
fPrivateData->fUpArrowsEnabled = upEnabled;
Invalidate(_ButtonRectFor(ARROW1));
if (_DoubleArrows())
Invalidate(_ButtonRectFor(ARROW3));
}
bool downEnabled = fValue < fMax;
if (fPrivateData->fDownArrowsEnabled != downEnabled) {
fPrivateData->fDownArrowsEnabled = downEnabled;
Invalidate(_ButtonRectFor(ARROW4));
if (_DoubleArrows())
Invalidate(_ButtonRectFor(ARROW2));
}
}
// control_scrollbar
status_t
control_scrollbar(scroll_bar_info *info, BScrollBar *bar)
{
if (!bar || !info)
return B_BAD_VALUE;
if (bar->fPrivateData->fScrollBarInfo.double_arrows != info->double_arrows) {
bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
int8 multiplier = (info->double_arrows) ? 1 : -1;
if (bar->fOrientation == B_VERTICAL)
bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier * B_H_SCROLL_BAR_HEIGHT);
else
bar->fPrivateData->fThumbFrame.OffsetBy(multiplier * B_V_SCROLL_BAR_WIDTH, 0);
}
bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
// TODO: Figure out how proportional relates to the size of the thumb
// TODO: Add redraw code to reflect the changes
if (info->knob >= 0 && info->knob <= 2)
bar->fPrivateData->fScrollBarInfo.knob = info->knob;
else
return B_BAD_VALUE;
if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE)
bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
else
return B_BAD_VALUE;
return B_OK;
}
void
BScrollBar::_DrawArrowButton(int32 direction, BRect r, bool down)
{
// Another hack for code size
rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR);
rgb_color light, dark, normal,arrow,arrow2;
if (down && fPrivateData->fDoRepeat) {
light = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0);
arrow2 = dark = tint_color(c, B_LIGHTEN_MAX_TINT);
normal = tint_color(c, B_DARKEN_1_TINT);
arrow = tint_color(c, B_DARKEN_MAX_TINT);
} else {
bool use_enabled_colors = fPrivateData->fEnabled;
// Add a usability perk - disable buttons if they would not do anything -
// like a left arrow if the value==fMin
// NOTE: disabled because of too much visual noise/distraction
/* if ((direction == ARROW_LEFT || direction == ARROW_UP) && (fValue == fMin) )
use_enabled_colors = false;
else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN) && (fValue == fMax) )
use_enabled_colors = false;*/
if (use_enabled_colors) {
arrow2 = light = tint_color(c, B_LIGHTEN_MAX_TINT);
dark = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0);
normal = c;
arrow = tint_color(c, (B_DARKEN_MAX_TINT + B_DARKEN_4_TINT) / 2.0);
} else {
arrow2 = light = tint_color(c, B_LIGHTEN_1_TINT);
dark = tint_color(c, B_DARKEN_1_TINT);
normal = c;
arrow = tint_color(c, B_DARKEN_1_TINT);
}
}
BPoint tri1, tri2, tri3;
switch (direction) {
case ARROW_LEFT:
{
tri1.Set(r.left + 3, (r.top + r.bottom) /2 );
tri2.Set(r.right - 3, r.top + 3);
tri3.Set(r.right - 3, r.bottom - 3);
break;
}
case ARROW_RIGHT:
{
tri1.Set(r.left + 3, r.bottom - 3);
tri2.Set(r.left + 3, r.top + 3);
tri3.Set(r.right-3, (r.top + r.bottom) / 2);
break;
}
case ARROW_UP:
{
tri1.Set(r.left + 3, r.bottom - 3);
tri2.Set((r.left + r.right) / 2, r.top + 3);
tri3.Set(r.right - 3, r.bottom - 3);
break;
}
default:
{
tri1.Set(r.left + 3, r.top + 3);
tri2.Set(r.right - 3, r.top + 3);
tri3.Set((r.left + r.right) / 2, r.bottom - 3);
break;
}
}
// offset triangle if down
if (down && fPrivateData->fDoRepeat) {
BPoint offset(1.0, 1.0);
tri1 = tri1 + offset;
tri2 = tri2 + offset;
tri3 = tri3 + offset;
}
r.InsetBy(1, 1);
SetHighColor(normal);
FillRect(r);
SetHighColor(arrow);
FillTriangle(tri1, tri2, tri3);
/* SetHighColor(dark);
StrokeLine(tri2, tri3);
StrokeLine(tri1, tri3);
SetHighColor(arrow2);
StrokeLine(tri1, tri2);*/
r.InsetBy(-1, -1);
BeginLineArray(4);
if (direction == ARROW_LEFT || direction == ARROW_RIGHT) {
AddLine(BPoint(r.left, r.bottom),
BPoint(r.left, r.top), light);
AddLine(BPoint(r.left + 1, r.top),
BPoint(r.right - 1, r.top), light);
AddLine(BPoint(r.right, r.top),
BPoint(r.right, r.bottom), dark);
AddLine(BPoint(r.right - 1, r.bottom),
BPoint(r.left + 1, r.bottom), dark);
} else {
AddLine(BPoint(r.left, r.bottom - 1),
BPoint(r.left, r.top), light);
AddLine(BPoint(r.left + 1, r.top),
BPoint(r.right, r.top), light);
AddLine(BPoint(r.right, r.top + 1),
BPoint(r.right, r.bottom), dark);
AddLine(BPoint(r.right - 1, r.bottom),
BPoint(r.left, r.bottom), dark);
}
EndLineArray();
}