1 /*
2 * Copyright 2001-2020 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * Stefano Ceccherini, burton666@libero.it
8 * DarkWyrm, bpmagic@columbus.rr.com
9 * Marc Flerackers, mflerackers@androme.be
10 * John Scipione, jscipione@gmail.com
11 */
12
13
14 #include <ScrollBar.h>
15
16 #include <algorithm>
17
18 #include <math.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <ControlLook.h>
24 #include <LayoutUtils.h>
25 #include <Message.h>
26 #include <OS.h>
27 #include <Shape.h>
28 #include <Window.h>
29
30 #include <binary_compatibility/Interface.h>
31
32
33 //#define TRACE_SCROLLBAR
34 #ifdef TRACE_SCROLLBAR
35 # define TRACE(x...) printf(x)
36 #else
37 # define TRACE(x...)
38 #endif
39
40
41 typedef enum {
42 ARROW_LEFT = 0,
43 ARROW_RIGHT,
44 ARROW_UP,
45 ARROW_DOWN,
46 ARROW_NONE
47 } arrow_direction;
48
49
50 #define SCROLL_BAR_MAXIMUM_KNOB_SIZE 50
51 #define SCROLL_BAR_MINIMUM_KNOB_SIZE 9
52
53 #define SBC_SCROLLBYVALUE 0
54 #define SBC_SETDOUBLE 1
55 #define SBC_SETPROPORTIONAL 2
56 #define SBC_SETSTYLE 3
57
58 // Quick constants for determining which arrow is down and are defined with
59 // respect to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of
60 // arrows and ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up
61 // and ARROW4 points right/down.
62 #define ARROW1 0
63 #define ARROW2 1
64 #define ARROW3 2
65 #define ARROW4 3
66 #define THUMB 4
67 #define NOARROW -1
68
69
70 static const bigtime_t kRepeatDelay = 300000;
71
72
73 // Because the R5 version kept a lot of data on server-side, we need to kludge
74 // our way into binary compatibility
75 class BScrollBar::Private {
76 public:
Private(BScrollBar * scrollBar)77 Private(BScrollBar* scrollBar)
78 :
79 fScrollBar(scrollBar),
80 fEnabled(true),
81 fRepeaterThread(-1),
82 fExitRepeater(false),
83 fRepeaterDelay(0),
84 fThumbFrame(0.0, 0.0, -1.0, -1.0),
85 fDoRepeat(false),
86 fClickOffset(0.0, 0.0),
87 fThumbInc(0.0),
88 fStopValue(0.0),
89 fUpArrowsEnabled(true),
90 fDownArrowsEnabled(true),
91 fBorderHighlighted(false),
92 fButtonDown(NOARROW)
93 {
94 #ifdef TEST_MODE
95 fScrollBarInfo.proportional = true;
96 fScrollBarInfo.double_arrows = true;
97 fScrollBarInfo.knob = 0;
98 fScrollBarInfo.min_knob_size = 15;
99 #else
100 get_scroll_bar_info(&fScrollBarInfo);
101 #endif
102
103 fScrollBarInfo.min_knob_size = (int32)(fScrollBarInfo.min_knob_size *
104 (be_plain_font->Size() / 12.0f));
105 }
106
~Private()107 ~Private()
108 {
109 if (fRepeaterThread >= 0) {
110 status_t dummy;
111 fExitRepeater = true;
112 wait_for_thread(fRepeaterThread, &dummy);
113 }
114 }
115
116 void DrawScrollBarButton(BScrollBar* owner, arrow_direction direction,
117 BRect frame, bool down = false);
118
119 static int32 button_repeater_thread(void* data);
120
121 int32 ButtonRepeaterThread();
122
123 BScrollBar* fScrollBar;
124 bool fEnabled;
125
126 // TODO: This should be a static, initialized by
127 // _init_interface_kit() at application startup-time,
128 // like BMenu::sMenuInfo
129 scroll_bar_info fScrollBarInfo;
130
131 thread_id fRepeaterThread;
132 volatile bool fExitRepeater;
133 bigtime_t fRepeaterDelay;
134
135 BRect fThumbFrame;
136 volatile bool fDoRepeat;
137 BPoint fClickOffset;
138
139 float fThumbInc;
140 float fStopValue;
141
142 bool fUpArrowsEnabled;
143 bool fDownArrowsEnabled;
144
145 bool fBorderHighlighted;
146
147 int8 fButtonDown;
148 };
149
150
151 // This thread is spawned when a button is initially pushed and repeatedly scrolls
152 // the scrollbar by a little bit after a short delay
153 int32
button_repeater_thread(void * data)154 BScrollBar::Private::button_repeater_thread(void* data)
155 {
156 BScrollBar::Private* privateData = (BScrollBar::Private*)data;
157 return privateData->ButtonRepeaterThread();
158 }
159
160
161 int32
ButtonRepeaterThread()162 BScrollBar::Private::ButtonRepeaterThread()
163 {
164 // Wait a bit before auto scrolling starts. As long as the user releases
165 // and presses the button again while the repeat delay has not yet
166 // triggered, the value is pushed into the future, so we need to loop such
167 // that repeating starts at exactly the correct delay after the last
168 // button press.
169 while (fRepeaterDelay > system_time() && !fExitRepeater)
170 snooze_until(fRepeaterDelay, B_SYSTEM_TIMEBASE);
171
172 // repeat loop
173 while (!fExitRepeater) {
174 if (fScrollBar->LockLooper()) {
175 if (fDoRepeat) {
176 float value = fScrollBar->Value() + fThumbInc;
177 if (fButtonDown == NOARROW) {
178 // in this case we want to stop when we're under the mouse
179 if (fThumbInc > 0.0 && value <= fStopValue)
180 fScrollBar->SetValue(value);
181 if (fThumbInc < 0.0 && value >= fStopValue)
182 fScrollBar->SetValue(value);
183 } else
184 fScrollBar->SetValue(value);
185 }
186
187 fScrollBar->UnlockLooper();
188 }
189
190 snooze(25000);
191 }
192
193 // tell scrollbar we're gone
194 if (fScrollBar->LockLooper()) {
195 fRepeaterThread = -1;
196 fScrollBar->UnlockLooper();
197 }
198
199 return 0;
200 }
201
202
203 // #pragma mark - BScrollBar
204
205
BScrollBar(BRect frame,const char * name,BView * target,float min,float max,orientation direction)206 BScrollBar::BScrollBar(BRect frame, const char* name, BView* target,
207 float min, float max, orientation direction)
208 :
209 BView(frame, name, B_FOLLOW_NONE,
210 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
211 fMin(min),
212 fMax(max),
213 fSmallStep(1.0f),
214 fLargeStep(10.0f),
215 fValue(0),
216 fProportion(0.0f),
217 fTarget(NULL),
218 fOrientation(direction)
219 {
220 SetViewColor(B_TRANSPARENT_COLOR);
221
222 fPrivateData = new BScrollBar::Private(this);
223
224 SetTarget(target);
225 SetEventMask(B_NO_POINTER_HISTORY);
226
227 _UpdateThumbFrame();
228 _UpdateArrowButtons();
229
230 SetResizingMode(direction == B_VERTICAL
231 ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT
232 : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
233 }
234
235
BScrollBar(const char * name,BView * target,float min,float max,orientation direction)236 BScrollBar::BScrollBar(const char* name, BView* target,
237 float min, float max, orientation direction)
238 :
239 BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
240 fMin(min),
241 fMax(max),
242 fSmallStep(1.0f),
243 fLargeStep(10.0f),
244 fValue(0),
245 fProportion(0.0f),
246 fTarget(NULL),
247 fOrientation(direction)
248 {
249 SetViewColor(B_TRANSPARENT_COLOR);
250
251 fPrivateData = new BScrollBar::Private(this);
252
253 SetTarget(target);
254 SetEventMask(B_NO_POINTER_HISTORY);
255
256 _UpdateThumbFrame();
257 _UpdateArrowButtons();
258 }
259
260
BScrollBar(BMessage * data)261 BScrollBar::BScrollBar(BMessage* data)
262 :
263 BView(data),
264 fTarget(NULL)
265 {
266 fPrivateData = new BScrollBar::Private(this);
267
268 // TODO: Does the BeOS implementation try to find the target
269 // by name again? Does it archive the name at all?
270 if (data->FindFloat("_range", 0, &fMin) < B_OK)
271 fMin = 0.0f;
272
273 if (data->FindFloat("_range", 1, &fMax) < B_OK)
274 fMax = 0.0f;
275
276 if (data->FindFloat("_steps", 0, &fSmallStep) < B_OK)
277 fSmallStep = 1.0f;
278
279 if (data->FindFloat("_steps", 1, &fLargeStep) < B_OK)
280 fLargeStep = 10.0f;
281
282 if (data->FindFloat("_val", &fValue) < B_OK)
283 fValue = 0.0;
284
285 int32 orientation;
286 if (data->FindInt32("_orient", &orientation) < B_OK) {
287 fOrientation = B_VERTICAL;
288 } else
289 fOrientation = (enum orientation)orientation;
290
291 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) {
292 // just to make sure
293 SetResizingMode(fOrientation == B_VERTICAL
294 ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT
295 : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
296 }
297
298 if (data->FindFloat("_prop", &fProportion) < B_OK)
299 fProportion = 0.0;
300
301 _UpdateThumbFrame();
302 _UpdateArrowButtons();
303 }
304
305
~BScrollBar()306 BScrollBar::~BScrollBar()
307 {
308 SetTarget((BView*)NULL);
309 delete fPrivateData;
310 }
311
312
313 BArchivable*
Instantiate(BMessage * data)314 BScrollBar::Instantiate(BMessage* data)
315 {
316 if (validate_instantiation(data, "BScrollBar"))
317 return new BScrollBar(data);
318 return NULL;
319 }
320
321
322 status_t
Archive(BMessage * data,bool deep) const323 BScrollBar::Archive(BMessage* data, bool deep) const
324 {
325 status_t err = BView::Archive(data, deep);
326 if (err != B_OK)
327 return err;
328
329 err = data->AddFloat("_range", fMin);
330 if (err != B_OK)
331 return err;
332
333 err = data->AddFloat("_range", fMax);
334 if (err != B_OK)
335 return err;
336
337 err = data->AddFloat("_steps", fSmallStep);
338 if (err != B_OK)
339 return err;
340
341 err = data->AddFloat("_steps", fLargeStep);
342 if (err != B_OK)
343 return err;
344
345 err = data->AddFloat("_val", fValue);
346 if (err != B_OK)
347 return err;
348
349 err = data->AddInt32("_orient", (int32)fOrientation);
350 if (err != B_OK)
351 return err;
352
353 err = data->AddFloat("_prop", fProportion);
354
355 return err;
356 }
357
358
359 void
AllAttached()360 BScrollBar::AllAttached()
361 {
362 BView::AllAttached();
363 }
364
365
366 void
AllDetached()367 BScrollBar::AllDetached()
368 {
369 BView::AllDetached();
370 }
371
372
373 void
AttachedToWindow()374 BScrollBar::AttachedToWindow()
375 {
376 BView::AttachedToWindow();
377 }
378
379
380 void
DetachedFromWindow()381 BScrollBar::DetachedFromWindow()
382 {
383 BView::DetachedFromWindow();
384 }
385
386
387 void
Draw(BRect updateRect)388 BScrollBar::Draw(BRect updateRect)
389 {
390 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
391
392 uint32 flags = 0;
393 bool scrollingEnabled = fMin < fMax
394 && fProportion >= 0.0f && fProportion < 1.0f;
395 if (scrollingEnabled)
396 flags |= BControlLook::B_PARTIALLY_ACTIVATED;
397
398 if (!fPrivateData->fEnabled || !scrollingEnabled)
399 flags |= BControlLook::B_DISABLED;
400
401 bool isFocused = fPrivateData->fBorderHighlighted;
402 if (isFocused)
403 flags |= BControlLook::B_FOCUSED;
404
405 bool doubleArrows = _DoubleArrows();
406
407 // draw border
408 BRect rect = Bounds();
409 be_control_look->DrawScrollBarBorder(this, rect, updateRect, base, flags,
410 fOrientation);
411
412 // inset past border
413 rect.InsetBy(1, 1);
414
415 // draw arrows buttons
416 BRect thumbBG = rect;
417 if (fOrientation == B_HORIZONTAL) {
418 BRect buttonFrame(rect.left, rect.top,
419 rect.left + rect.Height(), rect.bottom);
420
421 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
422 base, flags | (fPrivateData->fButtonDown == ARROW1
423 ? BControlLook::B_ACTIVATED : 0),
424 BControlLook::B_LEFT_ARROW, fOrientation,
425 fPrivateData->fButtonDown == ARROW1);
426
427 if (doubleArrows) {
428 buttonFrame.OffsetBy(rect.Height() + 1, 0.0f);
429 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
430 base, flags | (fPrivateData->fButtonDown == ARROW2
431 ? BControlLook::B_ACTIVATED : 0),
432 BControlLook::B_RIGHT_ARROW, fOrientation,
433 fPrivateData->fButtonDown == ARROW2);
434
435 buttonFrame.OffsetTo(rect.right - ((rect.Height() * 2) + 1),
436 rect.top);
437 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
438 base, flags | (fPrivateData->fButtonDown == ARROW3
439 ? BControlLook::B_ACTIVATED : 0),
440 BControlLook::B_LEFT_ARROW, fOrientation,
441 fPrivateData->fButtonDown == ARROW3);
442
443 thumbBG.left += rect.Height() * 2 + 2;
444 thumbBG.right -= rect.Height() * 2 + 2;
445 } else {
446 thumbBG.left += rect.Height() + 1;
447 thumbBG.right -= rect.Height() + 1;
448 }
449
450 buttonFrame.OffsetTo(rect.right - rect.Height(), rect.top);
451 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
452 base, flags | (fPrivateData->fButtonDown == ARROW4
453 ? BControlLook::B_ACTIVATED : 0),
454 BControlLook::B_RIGHT_ARROW, fOrientation,
455 fPrivateData->fButtonDown == ARROW4);
456 } else {
457 BRect buttonFrame(rect.left, rect.top, rect.right,
458 rect.top + rect.Width());
459
460 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
461 base, flags | (fPrivateData->fButtonDown == ARROW1
462 ? BControlLook::B_ACTIVATED : 0),
463 BControlLook::B_UP_ARROW, fOrientation,
464 fPrivateData->fButtonDown == ARROW1);
465
466 if (doubleArrows) {
467 buttonFrame.OffsetBy(0, rect.Width() + 1);
468 be_control_look->DrawScrollBarButton(this, buttonFrame,
469 updateRect, base, flags | (fPrivateData->fButtonDown == ARROW2
470 ? BControlLook::B_ACTIVATED : 0),
471 BControlLook::B_DOWN_ARROW, fOrientation,
472 fPrivateData->fButtonDown == ARROW2);
473
474 buttonFrame.OffsetTo(rect.left, rect.bottom
475 - ((rect.Width() * 2) + 1));
476 be_control_look->DrawScrollBarButton(this, buttonFrame,
477 updateRect, base, flags | (fPrivateData->fButtonDown == ARROW3
478 ? BControlLook::B_ACTIVATED : 0),
479 BControlLook::B_UP_ARROW, fOrientation,
480 fPrivateData->fButtonDown == ARROW3);
481
482 thumbBG.top += rect.Width() * 2 + 2;
483 thumbBG.bottom -= rect.Width() * 2 + 2;
484 } else {
485 thumbBG.top += rect.Width() + 1;
486 thumbBG.bottom -= rect.Width() + 1;
487 }
488
489 buttonFrame.OffsetTo(rect.left, rect.bottom - rect.Width());
490 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
491 base, flags | (fPrivateData->fButtonDown == ARROW4
492 ? BControlLook::B_ACTIVATED : 0),
493 BControlLook::B_DOWN_ARROW, fOrientation,
494 fPrivateData->fButtonDown == ARROW4);
495 }
496
497 // fill background besides the thumb
498 rect = fPrivateData->fThumbFrame;
499 if (fOrientation == B_HORIZONTAL) {
500 BRect leftOfThumb(thumbBG.left, thumbBG.top,
501 rect.left - 1, thumbBG.bottom);
502 BRect rightOfThumb(rect.right + 1, thumbBG.top,
503 thumbBG.right, thumbBG.bottom);
504 be_control_look->DrawScrollBarBackground(this, leftOfThumb,
505 rightOfThumb, updateRect, base, flags, fOrientation);
506 } else {
507 BRect topOfThumb(thumbBG.left, thumbBG.top,
508 thumbBG.right, rect.top - 1);
509 BRect bottomOfThumb(thumbBG.left, rect.bottom + 1,
510 thumbBG.right, thumbBG.bottom);
511 be_control_look->DrawScrollBarBackground(this, topOfThumb,
512 bottomOfThumb, updateRect, base, flags, fOrientation);
513 }
514
515 // draw thumb
516 rect = fPrivateData->fThumbFrame;
517 be_control_look->DrawScrollBarThumb(this, rect, updateRect,
518 ui_color(B_SCROLL_BAR_THUMB_COLOR), flags, fOrientation,
519 fPrivateData->fScrollBarInfo.knob);
520 }
521
522
523 void
FrameMoved(BPoint newPosition)524 BScrollBar::FrameMoved(BPoint newPosition)
525 {
526 BView::FrameMoved(newPosition);
527 }
528
529
530 void
FrameResized(float newWidth,float newHeight)531 BScrollBar::FrameResized(float newWidth, float newHeight)
532 {
533 _UpdateThumbFrame();
534 }
535
536
537 void
MessageReceived(BMessage * message)538 BScrollBar::MessageReceived(BMessage* message)
539 {
540 switch(message->what) {
541 case B_VALUE_CHANGED:
542 {
543 int32 value;
544 if (message->FindInt32("value", &value) == B_OK)
545 ValueChanged(value);
546
547 break;
548 }
549
550 case B_MOUSE_WHEEL_CHANGED:
551 {
552 // Must handle this here since BView checks for the existence of
553 // scrollbars, which a scrollbar itself does not have
554 float deltaX = 0.0f;
555 float deltaY = 0.0f;
556 message->FindFloat("be:wheel_delta_x", &deltaX);
557 message->FindFloat("be:wheel_delta_y", &deltaY);
558
559 if (deltaX == 0.0f && deltaY == 0.0f)
560 break;
561
562 if (deltaX != 0.0f && deltaY == 0.0f)
563 deltaY = deltaX;
564
565 ScrollWithMouseWheelDelta(this, deltaY);
566 }
567
568 default:
569 BView::MessageReceived(message);
570 }
571 }
572
573
574 void
MouseDown(BPoint where)575 BScrollBar::MouseDown(BPoint where)
576 {
577 if (!fPrivateData->fEnabled || fMin == fMax)
578 return;
579
580 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
581
582 int32 buttons;
583 if (Looper() == NULL || Looper()->CurrentMessage() == NULL
584 || Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) {
585 buttons = B_PRIMARY_MOUSE_BUTTON;
586 }
587
588 if (buttons & B_SECONDARY_MOUSE_BUTTON) {
589 // special absolute scrolling: move thumb to where we clicked
590 fPrivateData->fButtonDown = THUMB;
591 fPrivateData->fClickOffset
592 = fPrivateData->fThumbFrame.LeftTop() - where;
593 if (Orientation() == B_HORIZONTAL) {
594 fPrivateData->fClickOffset.x
595 = -fPrivateData->fThumbFrame.Width() / 2;
596 } else {
597 fPrivateData->fClickOffset.y
598 = -fPrivateData->fThumbFrame.Height() / 2;
599 }
600
601 SetValue(_ValueFor(where + fPrivateData->fClickOffset));
602 return;
603 }
604
605 // hit test for the thumb
606 if (fPrivateData->fThumbFrame.Contains(where)) {
607 fPrivateData->fButtonDown = THUMB;
608 fPrivateData->fClickOffset
609 = fPrivateData->fThumbFrame.LeftTop() - where;
610 Invalidate(fPrivateData->fThumbFrame);
611 return;
612 }
613
614 // hit test for arrows or empty area
615 float scrollValue = 0.0;
616
617 // pressing the shift key scrolls faster
618 float buttonStepSize
619 = (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep;
620
621 fPrivateData->fButtonDown = _ButtonFor(where);
622 switch (fPrivateData->fButtonDown) {
623 case ARROW1:
624 scrollValue = -buttonStepSize;
625 break;
626
627 case ARROW2:
628 scrollValue = buttonStepSize;
629 break;
630
631 case ARROW3:
632 scrollValue = -buttonStepSize;
633 break;
634
635 case ARROW4:
636 scrollValue = buttonStepSize;
637 break;
638
639 case NOARROW:
640 // we hit the empty area, figure out which side of the thumb
641 if (fOrientation == B_VERTICAL) {
642 if (where.y < fPrivateData->fThumbFrame.top)
643 scrollValue = -fLargeStep;
644 else
645 scrollValue = fLargeStep;
646 } else {
647 if (where.x < fPrivateData->fThumbFrame.left)
648 scrollValue = -fLargeStep;
649 else
650 scrollValue = fLargeStep;
651 }
652 _UpdateTargetValue(where);
653 break;
654 }
655 if (scrollValue != 0.0) {
656 SetValue(fValue + scrollValue);
657 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
658
659 // launch the repeat thread
660 if (fPrivateData->fRepeaterThread == -1) {
661 fPrivateData->fExitRepeater = false;
662 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
663 fPrivateData->fThumbInc = scrollValue;
664 fPrivateData->fDoRepeat = true;
665 fPrivateData->fRepeaterThread = spawn_thread(
666 fPrivateData->button_repeater_thread, "scroll repeater",
667 B_NORMAL_PRIORITY, fPrivateData);
668 resume_thread(fPrivateData->fRepeaterThread);
669 } else {
670 fPrivateData->fExitRepeater = false;
671 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
672 fPrivateData->fDoRepeat = true;
673 }
674 }
675 }
676
677
678 void
MouseMoved(BPoint where,uint32 code,const BMessage * dragMessage)679 BScrollBar::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
680 {
681 if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f
682 || fProportion < 0.0f) {
683 return;
684 }
685
686 if (fPrivateData->fButtonDown != NOARROW) {
687 if (fPrivateData->fButtonDown == THUMB) {
688 SetValue(_ValueFor(where + fPrivateData->fClickOffset));
689 } else {
690 // suspend the repeating if the mouse is not over the button
691 bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains(
692 where);
693 if (fPrivateData->fDoRepeat != repeat) {
694 fPrivateData->fDoRepeat = repeat;
695 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
696 }
697 }
698 } else {
699 // update the value at which we want to stop repeating
700 if (fPrivateData->fDoRepeat) {
701 _UpdateTargetValue(where);
702 // we might have to turn arround
703 if ((fValue < fPrivateData->fStopValue
704 && fPrivateData->fThumbInc < 0)
705 || (fValue > fPrivateData->fStopValue
706 && fPrivateData->fThumbInc > 0)) {
707 fPrivateData->fThumbInc = -fPrivateData->fThumbInc;
708 }
709 }
710 }
711 }
712
713
714 void
MouseUp(BPoint where)715 BScrollBar::MouseUp(BPoint where)
716 {
717 if (fPrivateData->fButtonDown == THUMB)
718 Invalidate(fPrivateData->fThumbFrame);
719 else
720 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
721
722 fPrivateData->fButtonDown = NOARROW;
723 fPrivateData->fExitRepeater = true;
724 fPrivateData->fDoRepeat = false;
725 }
726
727
728 void
WindowActivated(bool active)729 BScrollBar::WindowActivated(bool active)
730 {
731 fPrivateData->fEnabled = active;
732 Invalidate();
733 }
734
735
736 void
SetValue(float value)737 BScrollBar::SetValue(float value)
738 {
739 if (value > fMax)
740 value = fMax;
741 else if (value < fMin)
742 value = fMin;
743 else if (isnan(value) || isinf(value))
744 return;
745
746 value = roundf(value);
747 if (value == fValue)
748 return;
749
750 TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value);
751
752 fValue = value;
753
754 _UpdateThumbFrame();
755 _UpdateArrowButtons();
756
757 ValueChanged(fValue);
758 }
759
760
761 float
Value() const762 BScrollBar::Value() const
763 {
764 return fValue;
765 }
766
767
768 void
ValueChanged(float newValue)769 BScrollBar::ValueChanged(float newValue)
770 {
771 TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue);
772
773 if (fTarget != NULL) {
774 // cache target bounds
775 BRect targetBounds = fTarget->Bounds();
776 // if vertical, check bounds top and scroll if different from newValue
777 if (fOrientation == B_VERTICAL && targetBounds.top != newValue)
778 fTarget->ScrollBy(0.0, newValue - targetBounds.top);
779
780 // if horizontal, check bounds left and scroll if different from newValue
781 if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue)
782 fTarget->ScrollBy(newValue - targetBounds.left, 0.0);
783 }
784
785 TRACE(" -> %.1f\n", newValue);
786
787 SetValue(newValue);
788 }
789
790
791 void
SetProportion(float value)792 BScrollBar::SetProportion(float value)
793 {
794 if (value < 0.0f)
795 value = 0.0f;
796 else if (value > 1.0f)
797 value = 1.0f;
798
799 if (value == fProportion)
800 return;
801
802 TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value);
803
804 bool oldEnabled = fPrivateData->fEnabled && fMin < fMax
805 && fProportion < 1.0f && fProportion >= 0.0f;
806
807 fProportion = value;
808
809 bool newEnabled = fPrivateData->fEnabled && fMin < fMax
810 && fProportion < 1.0f && fProportion >= 0.0f;
811
812 _UpdateThumbFrame();
813
814 if (oldEnabled != newEnabled)
815 Invalidate();
816 }
817
818
819 float
Proportion() const820 BScrollBar::Proportion() const
821 {
822 return fProportion;
823 }
824
825
826 void
SetRange(float min,float max)827 BScrollBar::SetRange(float min, float max)
828 {
829 if (min > max || isnanf(min) || isnanf(max)
830 || isinff(min) || isinff(max)) {
831 min = 0.0f;
832 max = 0.0f;
833 }
834
835 min = roundf(min);
836 max = roundf(max);
837
838 if (fMin == min && fMax == max)
839 return;
840
841 TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max);
842
843 fMin = min;
844 fMax = max;
845
846 if (fValue < fMin || fValue > fMax)
847 SetValue(fValue);
848 else {
849 _UpdateThumbFrame();
850 Invalidate();
851 }
852 }
853
854
855 void
GetRange(float * min,float * max) const856 BScrollBar::GetRange(float* min, float* max) const
857 {
858 if (min != NULL)
859 *min = fMin;
860
861 if (max != NULL)
862 *max = fMax;
863 }
864
865
866 void
SetSteps(float smallStep,float largeStep)867 BScrollBar::SetSteps(float smallStep, float largeStep)
868 {
869 // Under R5, steps can be set only after being attached to a window,
870 // probably because the data is kept server-side. We'll just remove
871 // that limitation... :P
872
873 // The BeBook also says that we need to specify an integer value even
874 // though the step values are floats. For the moment, we'll just make
875 // sure that they are integers
876 smallStep = roundf(smallStep);
877 largeStep = roundf(largeStep);
878 if (fSmallStep == smallStep && fLargeStep == largeStep)
879 return;
880
881 TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(),
882 smallStep, largeStep);
883
884 fSmallStep = smallStep;
885 fLargeStep = largeStep;
886
887 if (fProportion == 0.0) {
888 // special case, proportion is based on fLargeStep if it was never
889 // set, so it means we need to invalidate here
890 _UpdateThumbFrame();
891 Invalidate();
892 }
893
894 // TODO: test use of fractional values and make them work properly if
895 // they don't
896 }
897
898
899 void
GetSteps(float * smallStep,float * largeStep) const900 BScrollBar::GetSteps(float* smallStep, float* largeStep) const
901 {
902 if (smallStep != NULL)
903 *smallStep = fSmallStep;
904
905 if (largeStep != NULL)
906 *largeStep = fLargeStep;
907 }
908
909
910 void
SetTarget(BView * target)911 BScrollBar::SetTarget(BView* target)
912 {
913 if (fTarget) {
914 // unset the previous target's scrollbar pointer
915 if (fOrientation == B_VERTICAL)
916 fTarget->fVerScroller = NULL;
917 else
918 fTarget->fHorScroller = NULL;
919 }
920
921 fTarget = target;
922 if (fTarget) {
923 if (fOrientation == B_VERTICAL)
924 fTarget->fVerScroller = this;
925 else
926 fTarget->fHorScroller = this;
927 }
928 }
929
930
931 void
SetTarget(const char * targetName)932 BScrollBar::SetTarget(const char* targetName)
933 {
934 // NOTE 1: BeOS implementation crashes for targetName == NULL
935 // NOTE 2: BeOS implementation also does not modify the target
936 // if it can't be found
937 if (targetName == NULL)
938 return;
939
940 if (Window() == NULL)
941 debugger("Method requires window and doesn't have one");
942
943 BView* target = Window()->FindView(targetName);
944 if (target != NULL)
945 SetTarget(target);
946 }
947
948
949 BView*
Target() const950 BScrollBar::Target() const
951 {
952 return fTarget;
953 }
954
955
956 void
SetOrientation(orientation direction)957 BScrollBar::SetOrientation(orientation direction)
958 {
959 if (fOrientation == direction)
960 return;
961
962 fOrientation = direction;
963 InvalidateLayout();
964 Invalidate();
965 }
966
967
968 orientation
Orientation() const969 BScrollBar::Orientation() const
970 {
971 return fOrientation;
972 }
973
974
975 status_t
SetBorderHighlighted(bool highlight)976 BScrollBar::SetBorderHighlighted(bool highlight)
977 {
978 if (fPrivateData->fBorderHighlighted == highlight)
979 return B_OK;
980
981 fPrivateData->fBorderHighlighted = highlight;
982
983 BRect dirty(Bounds());
984 if (fOrientation == B_HORIZONTAL)
985 dirty.bottom = dirty.top;
986 else
987 dirty.right = dirty.left;
988
989 Invalidate(dirty);
990
991 return B_OK;
992 }
993
994
995 void
GetPreferredSize(float * _width,float * _height)996 BScrollBar::GetPreferredSize(float* _width, float* _height)
997 {
998 if (fOrientation == B_VERTICAL) {
999 if (_width)
1000 *_width = be_control_look->GetScrollBarWidth(B_VERTICAL);
1001
1002 if (_height)
1003 *_height = _MinSize().Height();
1004 } else if (fOrientation == B_HORIZONTAL) {
1005 if (_width)
1006 *_width = _MinSize().Width();
1007
1008 if (_height)
1009 *_height = be_control_look->GetScrollBarWidth(B_HORIZONTAL);
1010 }
1011 }
1012
1013
1014 void
ResizeToPreferred()1015 BScrollBar::ResizeToPreferred()
1016 {
1017 BView::ResizeToPreferred();
1018 }
1019
1020
1021
1022 void
MakeFocus(bool focus)1023 BScrollBar::MakeFocus(bool focus)
1024 {
1025 BView::MakeFocus(focus);
1026 }
1027
1028
1029 BSize
MinSize()1030 BScrollBar::MinSize()
1031 {
1032 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize());
1033 }
1034
1035
1036 BSize
MaxSize()1037 BScrollBar::MaxSize()
1038 {
1039 BSize maxSize;
1040 GetPreferredSize(&maxSize.width, &maxSize.height);
1041 if (fOrientation == B_HORIZONTAL)
1042 maxSize.width = B_SIZE_UNLIMITED;
1043 else
1044 maxSize.height = B_SIZE_UNLIMITED;
1045 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize);
1046 }
1047
1048
1049 BSize
PreferredSize()1050 BScrollBar::PreferredSize()
1051 {
1052 BSize preferredSize;
1053 GetPreferredSize(&preferredSize.width, &preferredSize.height);
1054 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize);
1055 }
1056
1057
1058 status_t
GetSupportedSuites(BMessage * message)1059 BScrollBar::GetSupportedSuites(BMessage* message)
1060 {
1061 return BView::GetSupportedSuites(message);
1062 }
1063
1064
1065 BHandler*
ResolveSpecifier(BMessage * message,int32 index,BMessage * specifier,int32 what,const char * property)1066 BScrollBar::ResolveSpecifier(BMessage* message, int32 index,
1067 BMessage* specifier, int32 what, const char* property)
1068 {
1069 return BView::ResolveSpecifier(message, index, specifier, what, property);
1070 }
1071
1072
1073 status_t
Perform(perform_code code,void * _data)1074 BScrollBar::Perform(perform_code code, void* _data)
1075 {
1076 switch (code) {
1077 case PERFORM_CODE_MIN_SIZE:
1078 ((perform_data_min_size*)_data)->return_value
1079 = BScrollBar::MinSize();
1080
1081 return B_OK;
1082
1083 case PERFORM_CODE_MAX_SIZE:
1084 ((perform_data_max_size*)_data)->return_value
1085 = BScrollBar::MaxSize();
1086
1087 return B_OK;
1088
1089 case PERFORM_CODE_PREFERRED_SIZE:
1090 ((perform_data_preferred_size*)_data)->return_value
1091 = BScrollBar::PreferredSize();
1092
1093 return B_OK;
1094
1095 case PERFORM_CODE_LAYOUT_ALIGNMENT:
1096 ((perform_data_layout_alignment*)_data)->return_value
1097 = BScrollBar::LayoutAlignment();
1098
1099 return B_OK;
1100
1101 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1102 ((perform_data_has_height_for_width*)_data)->return_value
1103 = BScrollBar::HasHeightForWidth();
1104
1105 return B_OK;
1106
1107 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1108 {
1109 perform_data_get_height_for_width* data
1110 = (perform_data_get_height_for_width*)_data;
1111 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max,
1112 &data->preferred);
1113
1114 return B_OK;
1115 }
1116
1117 case PERFORM_CODE_SET_LAYOUT:
1118 {
1119 perform_data_set_layout* data = (perform_data_set_layout*)_data;
1120 BScrollBar::SetLayout(data->layout);
1121
1122 return B_OK;
1123 }
1124
1125 case PERFORM_CODE_LAYOUT_INVALIDATED:
1126 {
1127 perform_data_layout_invalidated* data
1128 = (perform_data_layout_invalidated*)_data;
1129 BScrollBar::LayoutInvalidated(data->descendants);
1130
1131 return B_OK;
1132 }
1133
1134 case PERFORM_CODE_DO_LAYOUT:
1135 {
1136 BScrollBar::DoLayout();
1137
1138 return B_OK;
1139 }
1140 }
1141
1142 return BView::Perform(code, _data);
1143 }
1144
1145
_ReservedScrollBar1()1146 void BScrollBar::_ReservedScrollBar1() {}
_ReservedScrollBar2()1147 void BScrollBar::_ReservedScrollBar2() {}
_ReservedScrollBar3()1148 void BScrollBar::_ReservedScrollBar3() {}
_ReservedScrollBar4()1149 void BScrollBar::_ReservedScrollBar4() {}
1150
1151
1152 BScrollBar&
operator =(const BScrollBar &)1153 BScrollBar::operator=(const BScrollBar&)
1154 {
1155 return *this;
1156 }
1157
1158
1159 bool
_DoubleArrows() const1160 BScrollBar::_DoubleArrows() const
1161 {
1162 if (!fPrivateData->fScrollBarInfo.double_arrows)
1163 return false;
1164
1165 // if there is not enough room, switch to single arrows even though
1166 // double arrows is specified
1167 if (fOrientation == B_HORIZONTAL) {
1168 return Bounds().Width() > (Bounds().Height() + 1) * 4
1169 + fPrivateData->fScrollBarInfo.min_knob_size * 2;
1170 } else {
1171 return Bounds().Height() > (Bounds().Width() + 1) * 4
1172 + fPrivateData->fScrollBarInfo.min_knob_size * 2;
1173 }
1174 }
1175
1176
1177 void
_UpdateThumbFrame()1178 BScrollBar::_UpdateThumbFrame()
1179 {
1180 BRect bounds = Bounds();
1181 bounds.InsetBy(1.0, 1.0);
1182
1183 BRect oldFrame = fPrivateData->fThumbFrame;
1184 fPrivateData->fThumbFrame = bounds;
1185 float minSize = fPrivateData->fScrollBarInfo.min_knob_size;
1186 float maxSize;
1187 float buttonSize;
1188
1189 // assume square buttons
1190 if (fOrientation == B_VERTICAL) {
1191 maxSize = bounds.Height();
1192 buttonSize = bounds.Width() + 1.0;
1193 } else {
1194 maxSize = bounds.Width();
1195 buttonSize = bounds.Height() + 1.0;
1196 }
1197
1198 if (_DoubleArrows()) {
1199 // subtract the size of four buttons
1200 maxSize -= buttonSize * 4;
1201 } else {
1202 // subtract the size of two buttons
1203 maxSize -= buttonSize * 2;
1204 }
1205 // visual adjustments (room for darker line between thumb and buttons)
1206 maxSize--;
1207
1208 float thumbSize;
1209 if (fPrivateData->fScrollBarInfo.proportional) {
1210 float proportion = fProportion;
1211 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0)
1212 proportion = 1.0;
1213
1214 if (proportion == 0.0) {
1215 // Special case a proportion of 0.0, use the large step value
1216 // in that case (NOTE: fMin == fMax already handled above)
1217 // This calculation is based on the assumption that "large step"
1218 // scrolls by one "page size".
1219 proportion = fLargeStep / (2 * (fMax - fMin));
1220 if (proportion > 1.0)
1221 proportion = 1.0;
1222 }
1223 thumbSize = maxSize * proportion;
1224 if (thumbSize < minSize)
1225 thumbSize = minSize;
1226 } else
1227 thumbSize = minSize;
1228
1229 thumbSize = floorf(thumbSize + 0.5);
1230 thumbSize--;
1231
1232 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0"
1233 float offset = 0.0;
1234 if (fMax > fMin) {
1235 offset = floorf(((fValue - fMin) / (fMax - fMin))
1236 * (maxSize - thumbSize - 1.0));
1237 }
1238
1239 if (_DoubleArrows()) {
1240 offset += buttonSize * 2;
1241 } else
1242 offset += buttonSize;
1243
1244 // visual adjustments (room for darker line between thumb and buttons)
1245 offset++;
1246
1247 if (fOrientation == B_VERTICAL) {
1248 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top
1249 + thumbSize;
1250 fPrivateData->fThumbFrame.OffsetBy(0.0, offset);
1251 } else {
1252 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left
1253 + thumbSize;
1254 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0);
1255 }
1256
1257 if (Window() != NULL) {
1258 BRect invalid = oldFrame.IsValid()
1259 ? oldFrame | fPrivateData->fThumbFrame
1260 : fPrivateData->fThumbFrame;
1261 // account for those two dark lines
1262 if (fOrientation == B_HORIZONTAL)
1263 invalid.InsetBy(-2.0, 0.0);
1264 else
1265 invalid.InsetBy(0.0, -2.0);
1266
1267 Invalidate(invalid);
1268 }
1269 }
1270
1271
1272 float
_ValueFor(BPoint where) const1273 BScrollBar::_ValueFor(BPoint where) const
1274 {
1275 BRect bounds = Bounds();
1276 bounds.InsetBy(1.0f, 1.0f);
1277
1278 float offset;
1279 float thumbSize;
1280 float maxSize;
1281 float buttonSize;
1282
1283 if (fOrientation == B_VERTICAL) {
1284 offset = where.y;
1285 thumbSize = fPrivateData->fThumbFrame.Height();
1286 maxSize = bounds.Height();
1287 buttonSize = bounds.Width() + 1.0f;
1288 } else {
1289 offset = where.x;
1290 thumbSize = fPrivateData->fThumbFrame.Width();
1291 maxSize = bounds.Width();
1292 buttonSize = bounds.Height() + 1.0f;
1293 }
1294
1295 if (_DoubleArrows()) {
1296 // subtract the size of four buttons
1297 maxSize -= buttonSize * 4;
1298 // convert point to inside of area between buttons
1299 offset -= buttonSize * 2;
1300 } else {
1301 // subtract the size of two buttons
1302 maxSize -= buttonSize * 2;
1303 // convert point to inside of area between buttons
1304 offset -= buttonSize;
1305 }
1306 // visual adjustments (room for darker line between thumb and buttons)
1307 maxSize--;
1308 offset++;
1309
1310 return roundf(fMin + (offset / (maxSize - thumbSize)
1311 * (fMax - fMin + 1.0f)));
1312 }
1313
1314
1315 int32
_ButtonFor(BPoint where) const1316 BScrollBar::_ButtonFor(BPoint where) const
1317 {
1318 BRect bounds = Bounds();
1319 bounds.InsetBy(1.0f, 1.0f);
1320
1321 float buttonSize = fOrientation == B_VERTICAL
1322 ? bounds.Width() + 1.0f
1323 : bounds.Height() + 1.0f;
1324
1325 BRect rect(bounds.left, bounds.top,
1326 bounds.left + buttonSize, bounds.top + buttonSize);
1327
1328 if (fOrientation == B_VERTICAL) {
1329 if (rect.Contains(where))
1330 return ARROW1;
1331
1332 if (_DoubleArrows()) {
1333 rect.OffsetBy(0.0, buttonSize);
1334 if (rect.Contains(where))
1335 return ARROW2;
1336
1337 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
1338 if (rect.Contains(where))
1339 return ARROW3;
1340 }
1341 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
1342 if (rect.Contains(where))
1343 return ARROW4;
1344 } else {
1345 if (rect.Contains(where))
1346 return ARROW1;
1347
1348 if (_DoubleArrows()) {
1349 rect.OffsetBy(buttonSize, 0.0);
1350 if (rect.Contains(where))
1351 return ARROW2;
1352
1353 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
1354 if (rect.Contains(where))
1355 return ARROW3;
1356 }
1357 rect.OffsetTo(bounds.right - buttonSize, bounds.top);
1358 if (rect.Contains(where))
1359 return ARROW4;
1360 }
1361
1362 return NOARROW;
1363 }
1364
1365
1366 BRect
_ButtonRectFor(int32 button) const1367 BScrollBar::_ButtonRectFor(int32 button) const
1368 {
1369 BRect bounds = Bounds();
1370 bounds.InsetBy(1.0f, 1.0f);
1371
1372 float buttonSize = fOrientation == B_VERTICAL
1373 ? bounds.Width() + 1.0f
1374 : bounds.Height() + 1.0f;
1375
1376 BRect rect(bounds.left, bounds.top,
1377 bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f);
1378
1379 if (fOrientation == B_VERTICAL) {
1380 switch (button) {
1381 case ARROW1:
1382 break;
1383
1384 case ARROW2:
1385 rect.OffsetBy(0.0, buttonSize);
1386 break;
1387
1388 case ARROW3:
1389 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1);
1390 break;
1391
1392 case ARROW4:
1393 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1);
1394 break;
1395 }
1396 } else {
1397 switch (button) {
1398 case ARROW1:
1399 break;
1400
1401 case ARROW2:
1402 rect.OffsetBy(buttonSize, 0.0);
1403 break;
1404
1405 case ARROW3:
1406 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top);
1407 break;
1408
1409 case ARROW4:
1410 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top);
1411 break;
1412 }
1413 }
1414
1415 return rect;
1416 }
1417
1418
1419 void
_UpdateTargetValue(BPoint where)1420 BScrollBar::_UpdateTargetValue(BPoint where)
1421 {
1422 if (fOrientation == B_VERTICAL) {
1423 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y
1424 - fPrivateData->fThumbFrame.Height() / 2.0));
1425 } else {
1426 fPrivateData->fStopValue = _ValueFor(BPoint(where.x
1427 - fPrivateData->fThumbFrame.Width() / 2.0, where.y));
1428 }
1429 }
1430
1431
1432 void
_UpdateArrowButtons()1433 BScrollBar::_UpdateArrowButtons()
1434 {
1435 bool upEnabled = fValue > fMin;
1436 if (fPrivateData->fUpArrowsEnabled != upEnabled) {
1437 fPrivateData->fUpArrowsEnabled = upEnabled;
1438 Invalidate(_ButtonRectFor(ARROW1));
1439 if (_DoubleArrows())
1440 Invalidate(_ButtonRectFor(ARROW3));
1441 }
1442
1443 bool downEnabled = fValue < fMax;
1444 if (fPrivateData->fDownArrowsEnabled != downEnabled) {
1445 fPrivateData->fDownArrowsEnabled = downEnabled;
1446 Invalidate(_ButtonRectFor(ARROW4));
1447 if (_DoubleArrows())
1448 Invalidate(_ButtonRectFor(ARROW2));
1449 }
1450 }
1451
1452
1453 status_t
control_scrollbar(scroll_bar_info * info,BScrollBar * bar)1454 control_scrollbar(scroll_bar_info* info, BScrollBar* bar)
1455 {
1456 if (bar == NULL || info == NULL)
1457 return B_BAD_VALUE;
1458
1459 if (bar->fPrivateData->fScrollBarInfo.double_arrows
1460 != info->double_arrows) {
1461 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
1462
1463 int8 multiplier = (info->double_arrows) ? 1 : -1;
1464
1465 if (bar->fOrientation == B_VERTICAL) {
1466 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier
1467 * B_H_SCROLL_BAR_HEIGHT);
1468 } else {
1469 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier
1470 * B_V_SCROLL_BAR_WIDTH, 0);
1471 }
1472 }
1473
1474 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
1475
1476 // TODO: Figure out how proportional relates to the size of the thumb
1477
1478 // TODO: Add redraw code to reflect the changes
1479
1480 if (info->knob >= 0 && info->knob <= 2)
1481 bar->fPrivateData->fScrollBarInfo.knob = info->knob;
1482 else
1483 return B_BAD_VALUE;
1484
1485 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE
1486 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) {
1487 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
1488 } else
1489 return B_BAD_VALUE;
1490
1491 return B_OK;
1492 }
1493
1494
1495 BSize
_MinSize() const1496 BScrollBar::_MinSize() const
1497 {
1498 BSize minSize;
1499 if (fOrientation == B_HORIZONTAL) {
1500 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH
1501 + 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1502 minSize.height = B_H_SCROLL_BAR_HEIGHT;
1503 } else {
1504 minSize.width = B_V_SCROLL_BAR_WIDTH;
1505 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT
1506 + 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1507 }
1508
1509 return minSize;
1510 }
1511