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