2 * Copyright 2004-2009, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2009 Stephan Aßmus, superstippi@gmx.de.
4 * Copyright 2014 Haiku, Inc. All rights reserved.
5 * Distributed under the terms of the MIT License.
8 * Stephan Aßmus, superstippi@gmx.de
9 * Axel Dörfler, axeld@pinc-software.de
10 * John Scipione, jscpione@gmail.com
14 #include <ScrollView.h>
16 #include <ControlLook.h>
17 #include <LayoutUtils.h>
22 #include <binary_compatibility/Interface.h>
25 static const float kFancyBorderSize
= 2;
26 static const float kPlainBorderSize
= 1;
29 BScrollView::BScrollView(const char* name
, BView
* target
, uint32 resizingMode
,
30 uint32 flags
, bool horizontal
, bool vertical
, border_style border
)
32 BView(_ComputeFrame(target
, horizontal
, vertical
, border
), name
,
33 resizingMode
, _ModifyFlags(flags
, border
)),
37 _Init(horizontal
, vertical
);
41 BScrollView::BScrollView(const char* name
, BView
* target
, uint32 flags
,
42 bool horizontal
, bool vertical
, border_style border
)
44 BView(name
, _ModifyFlags(flags
, border
)),
48 _Init(horizontal
, vertical
);
52 BScrollView::BScrollView(BMessage
* archive
)
58 fBorder
= archive
->FindInt32("_style", &border
) == B_OK
?
59 (border_style
)border
: B_FANCY_BORDER
;
61 // in a shallow archive, we may not have a target anymore. We must
62 // be prepared for this case
64 // don't confuse our scroll bars with our (eventual) target
66 if (!archive
->FindBool("_no_target_")) {
72 // search for our scroll bars
73 // This will not work for managed archives (when the layout kit is used).
74 // In that case the children are attached later, and we perform the search
75 // again in the AllUnarchived method.
77 fHorizontalScrollBar
= NULL
;
78 fVerticalScrollBar
= NULL
;
81 while ((view
= ChildAt(firstBar
++)) != NULL
) {
82 BScrollBar
*bar
= dynamic_cast<BScrollBar
*>(view
);
86 if (bar
->Orientation() == B_HORIZONTAL
)
87 fHorizontalScrollBar
= bar
;
88 else if (bar
->Orientation() == B_VERTICAL
)
89 fVerticalScrollBar
= bar
;
92 fPreviousWidth
= uint16(Bounds().Width());
93 fPreviousHeight
= uint16(Bounds().Height());
98 BScrollView::~BScrollView()
103 // #pragma mark - Archiving
107 BScrollView::Instantiate(BMessage
* archive
)
109 if (validate_instantiation(archive
, "BScrollView"))
110 return new BScrollView(archive
);
117 BScrollView::Archive(BMessage
* archive
, bool deep
) const
119 status_t status
= BView::Archive(archive
, deep
);
123 // If this is a deep archive, the BView class will take care
126 if (status
== B_OK
&& fBorder
!= B_FANCY_BORDER
)
127 status
= archive
->AddInt32("_style", fBorder
);
128 if (status
== B_OK
&& fTarget
== NULL
)
129 status
= archive
->AddBool("_no_target_", true);
131 // The highlighted state is not archived, but since it is
132 // usually (or should be) used to indicate focus, this
133 // is probably the right thing to do.
140 BScrollView::AllUnarchived(const BMessage
* archive
)
142 status_t result
= BView::AllUnarchived(archive
);
146 // search for our scroll bars and target
149 while ((view
= ChildAt(firstBar
++)) != NULL
) {
150 BScrollBar
*bar
= dynamic_cast<BScrollBar
*>(view
);
151 // We assume that the first non-scrollbar child view is the target.
152 // So the target view can't be a BScrollBar, but who would do that?
154 // in a shallow archive, we may not have a target anymore. We must
155 // be prepared for this case
156 if (fTarget
== NULL
&& !archive
->FindBool("_no_target_"))
161 if (bar
->Orientation() == B_HORIZONTAL
)
162 fHorizontalScrollBar
= bar
;
163 else if (bar
->Orientation() == B_VERTICAL
)
164 fVerticalScrollBar
= bar
;
167 // Now connect the bars to the target, and make the target aware of them
168 if (fHorizontalScrollBar
)
169 fHorizontalScrollBar
->SetTarget(fTarget
);
170 if (fVerticalScrollBar
)
171 fVerticalScrollBar
->SetTarget(fTarget
);
174 fTarget
->TargetedByScrollView(this);
176 fPreviousWidth
= uint16(Bounds().Width());
177 fPreviousHeight
= uint16(Bounds().Height());
183 // #pragma mark - Hook methods
187 BScrollView::AttachedToWindow()
189 BView::AttachedToWindow();
191 if ((fHorizontalScrollBar
== NULL
&& fVerticalScrollBar
== NULL
)
192 || (fHorizontalScrollBar
!= NULL
&& fVerticalScrollBar
!= NULL
)
193 || Window()->Look() != B_DOCUMENT_WINDOW_LOOK
) {
197 // If we have only one bar, we need to check if we are in the
198 // bottom right edge of a window with the B_DOCUMENT_LOOK to
199 // adjust the size of the bar to acknowledge the resize knob.
201 BRect bounds
= ConvertToScreen(Bounds());
202 BRect windowBounds
= Window()->Frame();
204 if (bounds
.right
- _BorderSize() != windowBounds
.right
205 || bounds
.bottom
- _BorderSize() != windowBounds
.bottom
) {
209 if (fHorizontalScrollBar
)
210 fHorizontalScrollBar
->ResizeBy(-B_V_SCROLL_BAR_WIDTH
, 0);
211 else if (fVerticalScrollBar
)
212 fVerticalScrollBar
->ResizeBy(0, -B_H_SCROLL_BAR_HEIGHT
);
217 BScrollView::DetachedFromWindow()
219 BView::DetachedFromWindow();
224 BScrollView::AllAttached()
226 BView::AllAttached();
231 BScrollView::AllDetached()
233 BView::AllDetached();
238 BScrollView::Draw(BRect updateRect
)
241 if (fHighlighted
&& Window()->IsActive())
242 flags
|= BControlLook::B_FOCUSED
;
244 BRect
rect(Bounds());
245 rgb_color base
= ui_color(B_PANEL_BACKGROUND_COLOR
);
247 BRect
verticalScrollBarFrame(0, 0, -1, -1);
248 if (fVerticalScrollBar
)
249 verticalScrollBarFrame
= fVerticalScrollBar
->Frame();
251 BRect
horizontalScrollBarFrame(0, 0, -1, -1);
252 if (fHorizontalScrollBar
)
253 horizontalScrollBarFrame
= fHorizontalScrollBar
->Frame();
255 be_control_look
->DrawScrollViewFrame(this, rect
, updateRect
,
256 verticalScrollBarFrame
, horizontalScrollBarFrame
, base
, fBorder
,
262 BScrollView::FrameMoved(BPoint newPosition
)
264 BView::FrameMoved(newPosition
);
269 BScrollView::FrameResized(float newWidth
, float newHeight
)
271 BView::FrameResized(newWidth
, newHeight
);
273 if (fBorder
== B_NO_BORDER
)
276 BRect bounds
= Bounds();
277 float border
= _BorderSize() - 1;
279 if (be_control_look
&& fHorizontalScrollBar
&& fVerticalScrollBar
) {
280 BRect
scrollCorner(bounds
);
281 scrollCorner
.left
= min_c(
282 fPreviousWidth
- fVerticalScrollBar
->Frame().Height(),
283 fHorizontalScrollBar
->Frame().right
+ 1);
284 scrollCorner
.top
= min_c(
285 fPreviousHeight
- fHorizontalScrollBar
->Frame().Width(),
286 fVerticalScrollBar
->Frame().bottom
+ 1);
287 Invalidate(scrollCorner
);
290 // changes in newWidth
292 if (bounds
.Width() > fPreviousWidth
) {
293 // invalidate the region between the old and the new right border
295 rect
.left
+= fPreviousWidth
- border
;
298 } else if (bounds
.Width() < fPreviousWidth
) {
299 // invalidate the region of the new right border
301 rect
.left
= rect
.right
- border
;
305 // changes in newHeight
307 if (bounds
.Height() > fPreviousHeight
) {
308 // invalidate the region between the old and the new bottom border
310 rect
.top
+= fPreviousHeight
- border
;
313 } else if (bounds
.Height() < fPreviousHeight
) {
314 // invalidate the region of the new bottom border
316 rect
.top
= rect
.bottom
- border
;
320 fPreviousWidth
= uint16(bounds
.Width());
321 fPreviousHeight
= uint16(bounds
.Height());
326 BScrollView::MessageReceived(BMessage
* message
)
328 BView::MessageReceived(message
);
333 BScrollView::MouseDown(BPoint where
)
335 BView::MouseDown(where
);
340 BScrollView::MouseMoved(BPoint where
, uint32 code
,
341 const BMessage
* dragMessage
)
343 BView::MouseMoved(where
, code
, dragMessage
);
348 BScrollView::MouseUp(BPoint where
)
350 BView::MouseUp(where
);
355 BScrollView::WindowActivated(bool active
)
360 BView::WindowActivated(active
);
364 // #pragma mark - Size methods
368 BScrollView::GetPreferredSize(float* _width
, float* _height
)
370 BSize size
= PreferredSize();
373 *_width
= size
.width
;
376 *_height
= size
.height
;
381 BScrollView::ResizeToPreferred()
383 if (Window() == NULL
)
385 BView::ResizeToPreferred();
390 BScrollView::MakeFocus(bool focus
)
392 BView::MakeFocus(focus
);
397 BScrollView::MinSize()
399 BSize size
= _ComputeSize(fTarget
!= NULL
? fTarget
->MinSize()
402 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size
);
407 BScrollView::MaxSize()
409 BSize size
= _ComputeSize(fTarget
!= NULL
? fTarget
->MaxSize()
410 : BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
));
412 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size
);
417 BScrollView::PreferredSize()
419 BSize size
= _ComputeSize(fTarget
!= NULL
? fTarget
->PreferredSize()
422 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size
);
426 // #pragma mark - BScrollView methods
430 BScrollView::ScrollBar(orientation direction
) const
432 if (direction
== B_HORIZONTAL
)
433 return fHorizontalScrollBar
;
435 return fVerticalScrollBar
;
440 BScrollView::SetBorder(border_style border
)
442 if (fBorder
== border
)
445 if (Flags() & B_SUPPORTS_LAYOUT
) {
447 SetFlags(_ModifyFlags(Flags(), border
));
451 BRect
bounds(Bounds());
452 Invalidate(BRect(bounds
.LeftTop(), bounds
.RightBottom()));
453 Invalidate(BRect(bounds
.LeftBottom(), bounds
.RightBottom()));
457 float offset
= _BorderSize() - _BorderSize(border
);
458 float resize
= 2 * offset
;
460 float horizontalGap
= 0, verticalGap
= 0;
462 if (border
== B_NO_BORDER
|| fBorder
== B_NO_BORDER
) {
463 if (fHorizontalScrollBar
!= NULL
)
464 verticalGap
= border
!= B_NO_BORDER
? 1 : -1;
465 if (fVerticalScrollBar
!= NULL
)
466 horizontalGap
= border
!= B_NO_BORDER
? 1 : -1;
468 change
= border
!= B_NO_BORDER
? -1 : 1;
469 if (fHorizontalScrollBar
== NULL
|| fVerticalScrollBar
== NULL
)
475 int32 savedResizingMode
= 0;
476 if (fTarget
!= NULL
) {
477 savedResizingMode
= fTarget
->ResizingMode();
478 fTarget
->SetResizingMode(B_FOLLOW_NONE
);
481 MoveBy(offset
, offset
);
482 ResizeBy(-resize
- horizontalGap
, -resize
- verticalGap
);
484 if (fTarget
!= NULL
) {
485 fTarget
->MoveBy(-offset
, -offset
);
486 fTarget
->SetResizingMode(savedResizingMode
);
489 if (fHorizontalScrollBar
!= NULL
) {
490 fHorizontalScrollBar
->MoveBy(-offset
- verticalGap
, offset
+ verticalGap
);
491 fHorizontalScrollBar
->ResizeBy(resize
+ horizontalGap
- change
, 0);
493 if (fVerticalScrollBar
!= NULL
) {
494 fVerticalScrollBar
->MoveBy(offset
+ horizontalGap
, -offset
- horizontalGap
);
495 fVerticalScrollBar
->ResizeBy(0, resize
+ verticalGap
- change
);
498 SetFlags(_ModifyFlags(Flags(), border
));
503 BScrollView::Border() const
510 BScrollView::SetBorderHighlighted(bool highlight
)
512 if (fHighlighted
== highlight
)
515 if (fBorder
!= B_FANCY_BORDER
)
516 // highlighting only works for B_FANCY_BORDER
519 fHighlighted
= highlight
;
521 if (fHorizontalScrollBar
!= NULL
)
522 fHorizontalScrollBar
->SetBorderHighlighted(highlight
);
523 if (fVerticalScrollBar
!= NULL
)
524 fVerticalScrollBar
->SetBorderHighlighted(highlight
);
526 BRect bounds
= Bounds();
527 if (be_control_look
!= NULL
)
528 bounds
.InsetBy(1, 1);
530 Invalidate(BRect(bounds
.left
, bounds
.top
, bounds
.right
, bounds
.top
));
531 Invalidate(BRect(bounds
.left
, bounds
.top
+ 1, bounds
.left
,
533 Invalidate(BRect(bounds
.right
, bounds
.top
+ 1, bounds
.right
,
535 Invalidate(BRect(bounds
.left
, bounds
.bottom
, bounds
.right
, bounds
.bottom
));
542 BScrollView::IsBorderHighlighted() const
549 BScrollView::SetTarget(BView
* target
)
551 if (fTarget
== target
)
554 if (fTarget
!= NULL
) {
555 fTarget
->TargetedByScrollView(NULL
);
556 RemoveChild(fTarget
);
558 // we are not supposed to delete the view
562 if (fHorizontalScrollBar
!= NULL
)
563 fHorizontalScrollBar
->SetTarget(target
);
564 if (fVerticalScrollBar
!= NULL
)
565 fVerticalScrollBar
->SetTarget(target
);
567 if (target
!= NULL
) {
568 target
->MoveTo(_BorderSize(), _BorderSize());
569 BRect innerFrame
= _InnerFrame();
570 target
->ResizeTo(innerFrame
.Width() - 1, innerFrame
.Height() - 1);
571 target
->TargetedByScrollView(this);
573 AddChild(target
, ChildAt(0));
574 // This way, we are making sure that the target will
575 // be added top most in the list (which is important
582 BScrollView::Target() const
588 // #pragma mark - Scripting methods
593 BScrollView::ResolveSpecifier(BMessage
* message
, int32 index
,
594 BMessage
* specifier
, int32 what
, const char* property
)
596 return BView::ResolveSpecifier(message
, index
, specifier
, what
, property
);
601 BScrollView::GetSupportedSuites(BMessage
* message
)
603 return BView::GetSupportedSuites(message
);
607 // #pragma mark - Perform
611 BScrollView::Perform(perform_code code
, void* _data
)
614 case PERFORM_CODE_MIN_SIZE
:
615 ((perform_data_min_size
*)_data
)->return_value
616 = BScrollView::MinSize();
619 case PERFORM_CODE_MAX_SIZE
:
620 ((perform_data_max_size
*)_data
)->return_value
621 = BScrollView::MaxSize();
624 case PERFORM_CODE_PREFERRED_SIZE
:
625 ((perform_data_preferred_size
*)_data
)->return_value
626 = BScrollView::PreferredSize();
629 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
630 ((perform_data_layout_alignment
*)_data
)->return_value
631 = BScrollView::LayoutAlignment();
634 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
635 ((perform_data_has_height_for_width
*)_data
)->return_value
636 = BScrollView::HasHeightForWidth();
639 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
641 perform_data_get_height_for_width
* data
642 = (perform_data_get_height_for_width
*)_data
;
643 BScrollView::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
648 case PERFORM_CODE_SET_LAYOUT
:
650 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
651 BScrollView::SetLayout(data
->layout
);
655 case PERFORM_CODE_LAYOUT_INVALIDATED
:
657 perform_data_layout_invalidated
* data
658 = (perform_data_layout_invalidated
*)_data
;
659 BScrollView::LayoutInvalidated(data
->descendants
);
663 case PERFORM_CODE_DO_LAYOUT
:
665 BScrollView::DoLayout();
670 return BView::Perform(code
, _data
);
674 // #pragma mark - Protected methods
678 BScrollView::LayoutInvalidated(bool descendants
)
684 BScrollView::DoLayout()
686 if ((Flags() & B_SUPPORTS_LAYOUT
) == 0)
689 // If the user set a layout, we let the base class version call its hook.
690 if (GetLayout() != NULL
) {
695 BRect innerFrame
= _InnerFrame();
697 if (fTarget
!= NULL
) {
698 fTarget
->MoveTo(innerFrame
.left
, innerFrame
.top
);
699 fTarget
->ResizeTo(innerFrame
.Width(), innerFrame
.Height());
701 //BLayoutUtils::AlignInFrame(fTarget, fTarget->Bounds());
704 _AlignScrollBars(fHorizontalScrollBar
!= NULL
, fVerticalScrollBar
!= NULL
,
709 // #pragma mark - Private methods
713 BScrollView::_Init(bool horizontal
, bool vertical
)
715 fHorizontalScrollBar
= NULL
;
716 fVerticalScrollBar
= NULL
;
717 fHighlighted
= false;
719 if (be_control_look
!= NULL
)
720 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR
));
723 fHorizontalScrollBar
= new BScrollBar(BRect(0, 0, 14, 14), "_HSB_",
724 fTarget
, 0, 1000, B_HORIZONTAL
);
725 AddChild(fHorizontalScrollBar
);
729 fVerticalScrollBar
= new BScrollBar(BRect(0, 0, 14, 14), "_VSB_",
730 fTarget
, 0, 1000, B_VERTICAL
);
731 AddChild(fVerticalScrollBar
);
736 // layout target and add it
737 fTarget
->TargetedByScrollView(this);
738 fTarget
->MoveTo(B_ORIGIN
);
740 if (fBorder
!= B_NO_BORDER
)
741 fTarget
->MoveBy(_BorderSize(), _BorderSize());
744 targetFrame
= fTarget
->Frame();
746 // no target specified
747 targetFrame
= Bounds();
749 targetFrame
.bottom
-= B_H_SCROLL_BAR_HEIGHT
+ 1;
751 targetFrame
.right
-= B_V_SCROLL_BAR_WIDTH
+ 1;
752 if (fBorder
== B_FANCY_BORDER
) {
753 targetFrame
.bottom
--;
758 _AlignScrollBars(horizontal
, vertical
, targetFrame
);
760 fPreviousWidth
= uint16(Bounds().Width());
761 fPreviousHeight
= uint16(Bounds().Height());
766 BScrollView::_BorderSize() const
768 return _BorderSize(fBorder
);
773 BScrollView::_InnerFrame() const
775 BRect frame
= Bounds();
777 float borderSize
= _BorderSize();
778 frame
.InsetBy(borderSize
, borderSize
);
780 if (fHorizontalScrollBar
!= NULL
) {
781 frame
.bottom
-= B_H_SCROLL_BAR_HEIGHT
;
785 if (fVerticalScrollBar
!= NULL
) {
786 frame
.right
-= B_V_SCROLL_BAR_WIDTH
;
796 BScrollView::_ComputeSize(BSize targetSize
) const
798 BRect frame
= _ComputeFrame(
799 BRect(0, 0, targetSize
.width
, targetSize
.height
));
801 return BSize(frame
.Width(), frame
.Height());
806 BScrollView::_ComputeFrame(BRect targetRect
) const
808 return _ComputeFrame(targetRect
, fHorizontalScrollBar
!= NULL
,
809 fVerticalScrollBar
!= NULL
, fBorder
);
814 BScrollView::_AlignScrollBars(bool horizontal
, bool vertical
, BRect targetFrame
)
817 BRect rect
= targetFrame
;
818 rect
.top
= rect
.bottom
+ 1;
819 rect
.bottom
= rect
.top
+ B_H_SCROLL_BAR_HEIGHT
;
820 if (fBorder
!= B_NO_BORDER
|| vertical
) {
821 // extend scrollbar so that it overlaps one pixel with vertical
826 if (fBorder
!= B_NO_BORDER
) {
827 // the scrollbar draws part of the surrounding frame on the left
831 fHorizontalScrollBar
->MoveTo(rect
.left
, rect
.top
);
832 fHorizontalScrollBar
->ResizeTo(rect
.Width(), rect
.Height());
836 BRect rect
= targetFrame
;
837 rect
.left
= rect
.right
+ 1;
838 rect
.right
= rect
.left
+ B_V_SCROLL_BAR_WIDTH
;
839 if (fBorder
!= B_NO_BORDER
|| horizontal
) {
840 // extend scrollbar so that it overlaps one pixel with vertical
845 if (fBorder
!= B_NO_BORDER
) {
846 // the scrollbar draws part of the surrounding frame on the left
850 fVerticalScrollBar
->MoveTo(rect
.left
, rect
.top
);
851 fVerticalScrollBar
->ResizeTo(rect
.Width(), rect
.Height());
856 /*! This static method is used to calculate the frame that the
857 ScrollView will cover depending on the frame of its target
858 and which border style is used.
859 It is used in the constructor and at other places.
862 BScrollView::_ComputeFrame(BRect frame
, bool horizontal
, bool vertical
,
866 frame
.right
+= B_V_SCROLL_BAR_WIDTH
;
868 frame
.bottom
+= B_H_SCROLL_BAR_HEIGHT
;
870 float borderSize
= _BorderSize(border
);
871 frame
.InsetBy(-borderSize
, -borderSize
);
873 if (borderSize
== 0) {
885 BScrollView::_ComputeFrame(BView
*target
, bool horizontal
, bool vertical
,
888 return _ComputeFrame(target
!= NULL
? target
->Frame()
889 : BRect(0, 0, 16, 16), horizontal
, vertical
, border
);
893 /*! This method returns the size of the specified border.
896 BScrollView::_BorderSize(border_style border
)
898 if (border
== B_FANCY_BORDER
)
899 return kFancyBorderSize
;
900 if (border
== B_PLAIN_BORDER
)
901 return kPlainBorderSize
;
907 /*! This method changes the "flags" argument as passed on to
908 the BView constructor.
911 BScrollView::_ModifyFlags(int32 flags
, border_style border
)
913 // We either need B_FULL_UPDATE_ON_RESIZE or
914 // B_FRAME_EVENTS if we have to draw a border
915 if (border
!= B_NO_BORDER
)
916 return flags
| B_WILL_DRAW
| (flags
& B_FULL_UPDATE_ON_RESIZE
? 0 : B_FRAME_EVENTS
);
918 return flags
& ~(B_WILL_DRAW
| B_FRAME_EVENTS
| B_FULL_UPDATE_ON_RESIZE
);
922 // #pragma mark - FBC and forbidden
926 BScrollView::operator=(const BScrollView
&)
932 void BScrollView::_ReservedScrollView1() {}
933 void BScrollView::_ReservedScrollView2() {}
934 void BScrollView::_ReservedScrollView3() {}
935 void BScrollView::_ReservedScrollView4() {}
939 B_IF_GCC_2(InvalidateLayout__11BScrollViewb
,
940 _ZN11BScrollView16InvalidateLayoutEb
)(BScrollView
* view
, bool descendants
)
942 perform_data_layout_invalidated data
;
943 data
.descendants
= descendants
;
945 view
->Perform(PERFORM_CODE_LAYOUT_INVALIDATED
, &data
);