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