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