xref: /haiku/src/kits/interface/ScrollView.cpp (revision 3cb015b1ee509d69c643506e8ff573808c86dcfc)
1 /*
2  * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <stdio.h>
7 
8 #include <LayoutUtils.h>
9 #include <ScrollView.h>
10 #include <Message.h>
11 #include <Window.h>
12 
13 
14 static const float kFancyBorderSize = 2;
15 static const float kPlainBorderSize = 1;
16 
17 
18 BScrollView::BScrollView(const char *name, BView *target, uint32 resizingMode,
19 	uint32 flags, bool horizontal, bool vertical, border_style border)
20 	: BView(CalcFrame(target, horizontal, vertical, border), name,
21 		resizingMode, ModifyFlags(flags, border)),
22 	fTarget(target),
23 	fHorizontalScrollBar(NULL),
24 	fVerticalScrollBar(NULL),
25 	fBorder(border),
26 	fHighlighted(false)
27 {
28 	BRect targetFrame;
29 	if (fTarget) {
30 		// layout target and add it
31 		fTarget->TargetedByScrollView(this);
32 		fTarget->MoveTo(B_ORIGIN);
33 
34 		if (border != B_NO_BORDER)
35 			fTarget->MoveBy(BorderSize(border), BorderSize(border));
36 
37 		AddChild(fTarget);
38 		targetFrame = fTarget->Frame();
39 	} else {
40 		// no target specified
41 		targetFrame = Bounds();
42 		if (horizontal)
43 			targetFrame.bottom -= B_H_SCROLL_BAR_HEIGHT + 1;
44 		if (vertical)
45 			targetFrame.right -= B_V_SCROLL_BAR_WIDTH + 1;
46 		if (border == B_FANCY_BORDER) {
47 			targetFrame.bottom--;
48 			targetFrame.right--;
49 		}
50 	}
51 
52 	if (horizontal) {
53 		BRect rect = targetFrame;
54 		rect.top = rect.bottom + 1;
55 		rect.bottom = rect.top + B_H_SCROLL_BAR_HEIGHT;
56 		if (border != B_NO_BORDER || vertical) {
57 			// extend scrollbar so that it overlaps one pixel with vertical scrollbar
58 			rect.right++;
59 		}
60 		if (border != B_NO_BORDER) {
61 			// the scrollbar draws part of the surrounding frame on the left
62 			rect.left--;
63 		}
64 		fHorizontalScrollBar = new BScrollBar(rect, "_HSB_", fTarget, 0, 1000, B_HORIZONTAL);
65 		AddChild(fHorizontalScrollBar);
66 	}
67 
68 	if (vertical) {
69 		BRect rect = targetFrame;
70 		rect.left = rect.right + 1;
71 		rect.right = rect.left + B_V_SCROLL_BAR_WIDTH;
72 		if (border != B_NO_BORDER || horizontal) {
73 			// extend scrollbar so that it overlaps one pixel with vertical scrollbar
74 			rect.bottom++;
75 		}
76 		if (border != B_NO_BORDER) {
77 			// the scrollbar draws part of the surrounding frame on the left
78 			rect.top--;
79 		}
80 		fVerticalScrollBar = new BScrollBar(rect, "_VSB_", fTarget, 0, 1000, B_VERTICAL);
81 		AddChild(fVerticalScrollBar);
82 	}
83 
84 	fPreviousWidth = uint16(Bounds().Width());
85 	fPreviousHeight = uint16(Bounds().Height());
86 }
87 
88 
89 BScrollView::BScrollView(BMessage *archive)
90 	: BView(archive),
91 	fHighlighted(false)
92 {
93 	int32 border;
94 	fBorder = archive->FindInt32("_style", &border) == B_OK ?
95 		(border_style)border : B_FANCY_BORDER;
96 
97 	// In a shallow archive, we may not have a target anymore. We must
98 	// be prepared for this case
99 
100 	// don't confuse our scroll bars with our (eventual) target
101 	int32 firstBar = 0;
102 	if (!archive->FindBool("_no_target_")) {
103 		fTarget = ChildAt(0);
104 		firstBar++;
105 	} else
106 		fTarget = NULL;
107 
108 	// search for our scroll bars
109 
110 	fHorizontalScrollBar = NULL;
111 	fVerticalScrollBar = NULL;
112 
113 	BView *view;
114 	while ((view = ChildAt(firstBar++)) != NULL) {
115 		BScrollBar *bar = dynamic_cast<BScrollBar *>(view);
116 		if (bar == NULL)
117 			continue;
118 
119 		if (bar->Orientation() == B_HORIZONTAL)
120 			fHorizontalScrollBar = bar;
121 		else if (bar->Orientation() == B_VERTICAL)
122 			fVerticalScrollBar = bar;
123 	}
124 
125 	fPreviousWidth = uint16(Bounds().Width());
126 	fPreviousHeight = uint16(Bounds().Height());
127 }
128 
129 
130 BScrollView::~BScrollView()
131 {
132 }
133 
134 
135 BArchivable *
136 BScrollView::Instantiate(BMessage *archive)
137 {
138 	if (validate_instantiation(archive, "BScrollView"))
139 		return new BScrollView(archive);
140 
141 	return NULL;
142 }
143 
144 
145 status_t
146 BScrollView::Archive(BMessage *archive, bool deep) const
147 {
148 	status_t status = BView::Archive(archive, deep);
149 	if (status != B_OK)
150 		return status;
151 
152 	// If this is a deep archive, the BView class will take care
153 	// of our children.
154 
155 	if (status == B_OK && fBorder != B_FANCY_BORDER)
156 		status = archive->AddInt32("_style", fBorder);
157 	if (status == B_OK && fTarget == NULL)
158 		status = archive->AddBool("_no_target_", true);
159 
160 	// The highlighted state is not archived, but since it is
161 	// usually (or should be) used to indicate focus, this
162 	// is probably the right thing to do.
163 
164 	return status;
165 }
166 
167 
168 void
169 BScrollView::AttachedToWindow()
170 {
171 	BView::AttachedToWindow();
172 
173 	if ((fHorizontalScrollBar == NULL && fVerticalScrollBar == NULL)
174 		|| (fHorizontalScrollBar != NULL && fVerticalScrollBar != NULL)
175 		|| Window()->Look() != B_DOCUMENT_WINDOW_LOOK)
176 		return;
177 
178 	// If we have only one bar, we need to check if we are in the
179 	// bottom right edge of a window with the B_DOCUMENT_LOOK to
180 	// adjust the size of the bar to acknowledge the resize knob.
181 
182 	BRect bounds = ConvertToScreen(Bounds());
183 	BRect windowBounds = Window()->Frame();
184 
185 	if (bounds.right - BorderSize(fBorder) != windowBounds.right
186 		|| bounds.bottom - BorderSize(fBorder) != windowBounds.bottom)
187 		return;
188 
189 	if (fHorizontalScrollBar)
190 		fHorizontalScrollBar->ResizeBy(-B_V_SCROLL_BAR_WIDTH, 0);
191 	else if (fVerticalScrollBar)
192 		fVerticalScrollBar->ResizeBy(0, -B_H_SCROLL_BAR_HEIGHT);
193 }
194 
195 
196 void
197 BScrollView::DetachedFromWindow()
198 {
199 	BView::DetachedFromWindow();
200 }
201 
202 
203 void
204 BScrollView::AllAttached()
205 {
206 	BView::AllAttached();
207 }
208 
209 
210 void
211 BScrollView::AllDetached()
212 {
213 	BView::AllDetached();
214 }
215 
216 
217 void
218 BScrollView::Draw(BRect updateRect)
219 {
220 	if (fBorder == B_PLAIN_BORDER) {
221 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_2_TINT));
222 		StrokeRect(Bounds());
223 		return;
224 	} else if (fBorder != B_FANCY_BORDER)
225 		return;
226 
227 	BRect bounds = Bounds();
228 	SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_2_TINT));
229 	StrokeRect(bounds.InsetByCopy(1, 1));
230 
231 	if (fHighlighted && Window()->IsActive()) {
232 		SetHighColor(ui_color(B_NAVIGATION_BASE_COLOR));
233 		StrokeRect(bounds);
234 	} else {
235 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT));
236 		StrokeLine(bounds.LeftBottom(), bounds.LeftTop());
237 		bounds.left++;
238 		StrokeLine(bounds.LeftTop(), bounds.RightTop());
239 
240 		SetHighColor(ui_color(B_SHINE_COLOR));
241 		StrokeLine(bounds.LeftBottom(), bounds.RightBottom());
242 		bounds.top++;
243 		bounds.bottom--;
244 		StrokeLine(bounds.RightBottom(), bounds.RightTop());
245 	}
246 }
247 
248 
249 BScrollBar *
250 BScrollView::ScrollBar(orientation posture) const
251 {
252 	if (posture == B_HORIZONTAL)
253 		return fHorizontalScrollBar;
254 
255 	return fVerticalScrollBar;
256 }
257 
258 
259 void
260 BScrollView::SetBorder(border_style border)
261 {
262 	if (fBorder == border)
263 		return;
264 
265 	float offset = BorderSize(fBorder) - BorderSize(border);
266 	float resize = 2 * offset;
267 
268 	float horizontalGap = 0, verticalGap = 0;
269 	float change = 0;
270 	if (border == B_NO_BORDER || fBorder == B_NO_BORDER) {
271 		if (fHorizontalScrollBar != NULL)
272 			verticalGap = border != B_NO_BORDER ? 1 : -1;
273 		if (fVerticalScrollBar != NULL)
274 			horizontalGap = border != B_NO_BORDER ? 1 : -1;
275 
276 		change = border != B_NO_BORDER ? -1 : 1;
277 		if (fHorizontalScrollBar == NULL || fVerticalScrollBar == NULL)
278 			change *= 2;
279 	}
280 
281 	fBorder = border;
282 
283 	int32 savedResizingMode = 0;
284 	if (fTarget != NULL) {
285 		savedResizingMode = fTarget->ResizingMode();
286 		fTarget->SetResizingMode(B_FOLLOW_NONE);
287 	}
288 
289 	MoveBy(offset, offset);
290 	ResizeBy(-resize - horizontalGap, -resize - verticalGap);
291 
292 	if (fTarget != NULL) {
293 		fTarget->MoveBy(-offset, -offset);
294 		fTarget->SetResizingMode(savedResizingMode);
295 	}
296 
297 	if (fHorizontalScrollBar != NULL) {
298 		fHorizontalScrollBar->MoveBy(-offset - verticalGap, offset + verticalGap);
299 		fHorizontalScrollBar->ResizeBy(resize + horizontalGap - change, 0);
300 	}
301 	if (fVerticalScrollBar != NULL) {
302 		fVerticalScrollBar->MoveBy(offset + horizontalGap, -offset - horizontalGap);
303 		fVerticalScrollBar->ResizeBy(0, resize + verticalGap - change);
304 	}
305 
306 	SetFlags(ModifyFlags(Flags(), border));
307 }
308 
309 
310 border_style
311 BScrollView::Border() const
312 {
313 	return fBorder;
314 }
315 
316 
317 status_t
318 BScrollView::SetBorderHighlighted(bool state)
319 {
320 	if (fHighlighted == state)
321 		return B_OK;
322 
323 	if (fBorder != B_FANCY_BORDER)
324 		// highlighting only works for B_FANCY_BORDER
325 		return B_ERROR;
326 
327 	fHighlighted = state;
328 
329 	/* The BeBook describes something like this:
330 
331 	if (LockLooper()) {
332 		Draw(Bounds());
333 		UnlockLooper();
334 	}
335 	*/
336 
337 	// but this is much cleaner, I think:
338 	BRect bounds = Bounds();
339 	Invalidate(BRect(bounds.left, bounds.top, bounds.right, bounds.top));
340 	Invalidate(BRect(bounds.left, bounds.top + 1, bounds.left, bounds.bottom - 1));
341 	Invalidate(BRect(bounds.right, bounds.top + 1, bounds.right, bounds.bottom - 1));
342 	Invalidate(BRect(bounds.left, bounds.bottom, bounds.right, bounds.bottom));
343 
344 	return B_OK;
345 }
346 
347 
348 bool
349 BScrollView::IsBorderHighlighted() const
350 {
351 	return fHighlighted;
352 }
353 
354 
355 void
356 BScrollView::SetTarget(BView *target)
357 {
358 	if (fTarget == target)
359 		return;
360 
361 	if (fTarget != NULL) {
362 		fTarget->TargetedByScrollView(NULL);
363 		RemoveChild(fTarget);
364 
365 		// we are not supposed to delete the view
366 	}
367 
368 	fTarget = target;
369 	if (fHorizontalScrollBar != NULL)
370 		fHorizontalScrollBar->SetTarget(target);
371 	if (fVerticalScrollBar != NULL)
372 		fVerticalScrollBar->SetTarget(target);
373 
374 	if (target != NULL) {
375 		target->MoveTo(BorderSize(fBorder), BorderSize(fBorder));
376 		target->TargetedByScrollView(this);
377 
378 		AddChild(target, ChildAt(0));
379 			// This way, we are making sure that the target will
380 			// be added top most in the list (which is important
381 			// for unarchiving)
382 	}
383 }
384 
385 
386 BView *
387 BScrollView::Target() const
388 {
389 	return fTarget;
390 }
391 
392 
393 void
394 BScrollView::MessageReceived(BMessage *message)
395 {
396 	switch (message->what) {
397 		default:
398 			BView::MessageReceived(message);
399 	}
400 }
401 
402 
403 void
404 BScrollView::MouseDown(BPoint point)
405 {
406 	BView::MouseDown(point);
407 }
408 
409 
410 void
411 BScrollView::MouseUp(BPoint point)
412 {
413 	BView::MouseUp(point);
414 }
415 
416 
417 void
418 BScrollView::MouseMoved(BPoint point, uint32 code, const BMessage *dragMessage)
419 {
420 	BView::MouseMoved(point, code, dragMessage);
421 }
422 
423 
424 void
425 BScrollView::FrameMoved(BPoint position)
426 {
427 	BView::FrameMoved(position);
428 }
429 
430 
431 void
432 BScrollView::FrameResized(float width, float height)
433 {
434 	BView::FrameResized(width, height);
435 
436 	if (fBorder == B_NO_BORDER)
437 		return;
438 
439 	// changes in width
440 
441 	BRect bounds = Bounds();
442 	float border = BorderSize(fBorder) - 1;
443 
444 	if (bounds.Width() > fPreviousWidth) {
445 		// invalidate the region between the old and the new right border
446 		BRect rect = bounds;
447 		rect.left += fPreviousWidth - border;
448 		rect.right--;
449 		Invalidate(rect);
450 	} else if (bounds.Width() < fPreviousWidth) {
451 		// invalidate the region of the new right border
452 		BRect rect = bounds;
453 		rect.left = rect.right - border;
454 		Invalidate(rect);
455 	}
456 
457 	// changes in height
458 
459 	if (bounds.Height() > fPreviousHeight) {
460 		// invalidate the region between the old and the new bottom border
461 		BRect rect = bounds;
462 		rect.top += fPreviousHeight - border;
463 		rect.bottom--;
464 		Invalidate(rect);
465 	} else if (bounds.Height() < fPreviousHeight) {
466 		// invalidate the region of the new bottom border
467 		BRect rect = bounds;
468 		rect.top = rect.bottom - border;
469 		Invalidate(rect);
470 	}
471 
472 	fPreviousWidth = uint16(bounds.Width());
473 	fPreviousHeight = uint16(bounds.Height());
474 }
475 
476 
477 void
478 BScrollView::ResizeToPreferred()
479 {
480 	BView::ResizeToPreferred();
481 }
482 
483 
484 void
485 BScrollView::GetPreferredSize(float *_width, float *_height)
486 {
487 	BRect frame = CalcFrame(fTarget, fHorizontalScrollBar, fVerticalScrollBar, fBorder);
488 
489 	if (fTarget != NULL) {
490 		float width, height;
491 		fTarget->GetPreferredSize(&width, &height);
492 
493 		frame.right += width - fTarget->Frame().Width();
494 		frame.bottom += height - fTarget->Frame().Height();
495 	}
496 
497 	if (_width)
498 		*_width = frame.Width();
499 	if (_height)
500 		*_height = frame.Height();
501 }
502 
503 
504 
505 
506 /** This static method is used to calculate the frame that the
507  *	ScrollView will cover depending on the frame of its target
508  *	and which border style is used.
509  *	It is used in the constructor and at other places.
510  */
511 
512 BRect
513 BScrollView::CalcFrame(BView *target, bool horizontal, bool vertical, border_style border)
514 {
515 	BRect frame = target != NULL ? target->Frame() : BRect(0, 0, 80, 80);
516 
517 	if (vertical)
518 		frame.right += B_V_SCROLL_BAR_WIDTH;
519 	if (horizontal)
520 		frame.bottom += B_H_SCROLL_BAR_HEIGHT;
521 
522 	float borderSize = BorderSize(border);
523 	frame.InsetBy(-borderSize, -borderSize);
524 
525 	if (borderSize == 0) {
526 		if (vertical)
527 			frame.right++;
528 		if (horizontal)
529 			frame.bottom++;
530 	}
531 
532 	return frame;
533 }
534 
535 
536 /** This method returns the size of the specified border
537  */
538 
539 float
540 BScrollView::BorderSize(border_style border)
541 {
542 	if (border == B_FANCY_BORDER)
543 		return kFancyBorderSize;
544 	if (border == B_PLAIN_BORDER)
545 		return kPlainBorderSize;
546 
547 	return 0;
548 }
549 
550 
551 /** This method changes the "flags" argument as passed on to
552  *	the BView constructor.
553  */
554 
555 int32
556 BScrollView::ModifyFlags(int32 flags, border_style border)
557 {
558 	// We either need B_FULL_UPDATE_ON_RESIZE or
559 	// B_FRAME_EVENTS if we have to draw a border
560 	if (border != B_NO_BORDER)
561 		return flags | B_WILL_DRAW | (flags & B_FULL_UPDATE_ON_RESIZE ? 0 : B_FRAME_EVENTS);
562 
563 	return flags & ~(B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE);
564 }
565 
566 
567 void
568 BScrollView::WindowActivated(bool active)
569 {
570 	if (fHighlighted)
571 		Invalidate();
572 
573 	BView::WindowActivated(active);
574 }
575 
576 
577 void
578 BScrollView::MakeFocus(bool state)
579 {
580 	BView::MakeFocus(state);
581 }
582 
583 
584 BHandler *
585 BScrollView::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 form, const char *property)
586 {
587 	return BView::ResolveSpecifier(msg, index, specifier, form, property);
588 }
589 
590 
591 status_t
592 BScrollView::GetSupportedSuites(BMessage *data)
593 {
594 	return BView::GetSupportedSuites(data);
595 }
596 
597 
598 BSize
599 BScrollView::MinSize()
600 {
601 // TODO: This is not yet correct.
602 	BSize size = (fTarget ? fTarget->MinSize() : BSize(-1, -1));
603 
604 	if (fVerticalScrollBar)
605 		size.width += B_V_SCROLL_BAR_WIDTH;
606 	if (fHorizontalScrollBar)
607 		size.height += B_H_SCROLL_BAR_HEIGHT;
608 
609 	float borderSize = BorderSize(fBorder);
610 	size.width += 2 * borderSize;
611 	size.height += 2 * borderSize;
612 
613 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
614 }
615 
616 
617 BSize
618 BScrollView::PreferredSize()
619 {
620 // TODO: This is not yet correct.
621 	BSize size = (fTarget ? fTarget->PreferredSize() : BSize(-1, -1));
622 
623 	if (fVerticalScrollBar)
624 		size.width += B_V_SCROLL_BAR_WIDTH;
625 	if (fHorizontalScrollBar)
626 		size.height += B_H_SCROLL_BAR_HEIGHT;
627 
628 	float borderSize = BorderSize(fBorder);
629 	size.width += 2 * borderSize;
630 	size.height += 2 * borderSize;
631 
632 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
633 }
634 
635 
636 status_t
637 BScrollView::Perform(perform_code d, void *arg)
638 {
639 	return BView::Perform(d, arg);
640 }
641 
642 
643 BScrollView &
644 BScrollView::operator=(const BScrollView &)
645 {
646 	return *this;
647 }
648 
649 
650 /** Although BScrollView::InitObject() was defined in the original ScrollView.h,
651  *	it is not exported by the R5 libbe.so, so we don't have to export it as well.
652  */
653 
654 #if 0
655 void
656 BScrollView::InitObject()
657 {
658 }
659 #endif
660 
661 
662 //	#pragma mark -
663 //	Reserved virtuals
664 
665 
666 void BScrollView::_ReservedScrollView1() {}
667 void BScrollView::_ReservedScrollView2() {}
668 void BScrollView::_ReservedScrollView3() {}
669 void BScrollView::_ReservedScrollView4() {}
670 
671