/* * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved. * Distributed under the terms of the MIT License. */ #include #include #include #include #include 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(CalcFrame(target, horizontal, vertical, border), name, resizingMode, ModifyFlags(flags, border)), fTarget(target), fHorizontalScrollBar(NULL), fVerticalScrollBar(NULL), fBorder(border), fHighlighted(false) { BRect targetFrame; if (fTarget) { // layout target and add it fTarget->TargetedByScrollView(this); fTarget->MoveTo(B_ORIGIN); if (border != B_NO_BORDER) fTarget->MoveBy(BorderSize(border), BorderSize(border)); 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 (border == B_FANCY_BORDER) { targetFrame.bottom--; targetFrame.right--; } } if (horizontal) { BRect rect = targetFrame; rect.top = rect.bottom + 1; rect.bottom = rect.top + B_H_SCROLL_BAR_HEIGHT; if (border != B_NO_BORDER || vertical) { // extend scrollbar so that it overlaps one pixel with vertical scrollbar rect.right++; } if (border != B_NO_BORDER) { // the scrollbar draws part of the surrounding frame on the left rect.left--; } fHorizontalScrollBar = new BScrollBar(rect, "_HSB_", fTarget, 0, 1000, B_HORIZONTAL); AddChild(fHorizontalScrollBar); } if (vertical) { BRect rect = targetFrame; rect.left = rect.right + 1; rect.right = rect.left + B_V_SCROLL_BAR_WIDTH; if (border != B_NO_BORDER || horizontal) { // extend scrollbar so that it overlaps one pixel with vertical scrollbar rect.bottom++; } if (border != B_NO_BORDER) { // the scrollbar draws part of the surrounding frame on the left rect.top--; } fVerticalScrollBar = new BScrollBar(rect, "_VSB_", fTarget, 0, 1000, B_VERTICAL); AddChild(fVerticalScrollBar); } fPreviousWidth = uint16(Bounds().Width()); fPreviousHeight = uint16(Bounds().Height()); } 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(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() { } 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(fBorder) != windowBounds.right || bounds.bottom - BorderSize(fBorder) != 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 (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()); } } BScrollBar * BScrollView::ScrollBar(orientation posture) const { if (posture == B_HORIZONTAL) return fHorizontalScrollBar; return fVerticalScrollBar; } void BScrollView::SetBorder(border_style border) { if (fBorder == border) return; float offset = BorderSize(fBorder) - 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; /* The BeBook describes something like this: if (LockLooper()) { Draw(Bounds()); UnlockLooper(); } */ // but this is much cleaner, I think: BRect bounds = Bounds(); 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(fBorder), BorderSize(fBorder)); 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; } 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); } 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; // changes in width BRect bounds = Bounds(); float border = BorderSize(fBorder) - 1; 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()); } void BScrollView::ResizeToPreferred() { BView::ResizeToPreferred(); } void BScrollView::GetPreferredSize(float *_width, float *_height) { BRect frame = CalcFrame(fTarget, fHorizontalScrollBar, fVerticalScrollBar, fBorder); if (fTarget != NULL) { float width, height; fTarget->GetPreferredSize(&width, &height); frame.right += width - fTarget->Frame().Width(); frame.bottom += height - fTarget->Frame().Height(); } if (_width) *_width = frame.Width(); if (_height) *_height = frame.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. */ BRect BScrollView::CalcFrame(BView *target, bool horizontal, bool vertical, border_style border) { BRect frame = target != NULL ? target->Frame() : BRect(0, 0, 80, 80); 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; } /** This method returns the size of the specified border */ 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. */ 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); } void BScrollView::WindowActivated(bool active) { if (fHighlighted) Invalidate(); BView::WindowActivated(active); } void BScrollView::MakeFocus(bool state) { BView::MakeFocus(state); } 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); } BSize BScrollView::MinSize() { // TODO: This is not yet correct. BSize size = (fTarget ? fTarget->MinSize() : BSize(-1, -1)); if (fVerticalScrollBar) size.width += B_V_SCROLL_BAR_WIDTH; if (fHorizontalScrollBar) size.height += B_H_SCROLL_BAR_HEIGHT; float borderSize = BorderSize(fBorder); size.width += 2 * borderSize; size.height += 2 * borderSize; return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); } BSize BScrollView::PreferredSize() { // TODO: This is not yet correct. BSize size = (fTarget ? fTarget->PreferredSize() : BSize(-1, -1)); if (fVerticalScrollBar) size.width += B_V_SCROLL_BAR_WIDTH; if (fHorizontalScrollBar) size.height += B_H_SCROLL_BAR_HEIGHT; float borderSize = BorderSize(fBorder); size.width += 2 * borderSize; size.height += 2 * borderSize; return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); } status_t BScrollView::Perform(perform_code d, void *arg) { return BView::Perform(d, arg); } BScrollView & BScrollView::operator=(const BScrollView &) { return *this; } /** Although BScrollView::InitObject() was defined in the original ScrollView.h, * it is not exported by the R5 libbe.so, so we don't have to export it as well. */ #if 0 void BScrollView::InitObject() { } #endif // #pragma mark - // Reserved virtuals void BScrollView::_ReservedScrollView1() {} void BScrollView::_ReservedScrollView2() {} void BScrollView::_ReservedScrollView3() {} void BScrollView::_ReservedScrollView4() {}