xref: /haiku/src/kits/interface/AbstractSpinner.cpp (revision b2acee1cb986b696adfad7daabfe9279949a3e54)
1 /*
2  * Copyright 2004 DarkWyrm <darkwyrm@earthlink.net>
3  * Copyright 2013 FeemanLou
4  * Copyright 2014-2015 Haiku, Inc. All rights reserved.
5  *
6  * Distributed under the terms of the MIT license.
7  *
8  * Originally written by DarkWyrm <darkwyrm@earthlink.net>
9  * Updated by FreemanLou as part of Google GCI 2013
10  *
11  * Authors:
12  *		DarkWyrm, darkwyrm@earthlink.net
13  *		FeemanLou
14  *		John Scipione, jscipione@gmail.com
15  */
16 
17 
18 #include <AbstractSpinner.h>
19 
20 #include <algorithm>
21 
22 #include <AbstractLayoutItem.h>
23 #include <Alignment.h>
24 #include <ControlLook.h>
25 #include <Font.h>
26 #include <GradientLinear.h>
27 #include <LayoutItem.h>
28 #include <LayoutUtils.h>
29 #include <Message.h>
30 #include <MessageFilter.h>
31 #include <Point.h>
32 #include <PropertyInfo.h>
33 #include <TextView.h>
34 #include <View.h>
35 #include <Window.h>
36 
37 #include "Thread.h"
38 
39 
40 static const float kFrameMargin			= 2.0f;
41 
42 const char* const kFrameField			= "BAbstractSpinner:layoutItem:frame";
43 const char* const kLabelItemField		= "BAbstractSpinner:labelItem";
44 const char* const kTextViewItemField	= "BAbstractSpinner:textViewItem";
45 
46 
47 static property_info sProperties[] = {
48 	{
49 		"Align",
50 		{ B_GET_PROPERTY, 0 },
51 		{ B_DIRECT_SPECIFIER, 0 },
52 		"Returns the alignment of the spinner label.",
53 		0,
54 		{ B_INT32_TYPE }
55 	},
56 	{
57 		"Align",
58 		{ B_SET_PROPERTY, 0 },
59 		{ B_DIRECT_SPECIFIER, 0},
60 		"Sets the alignment of the spinner label.",
61 		0,
62 		{ B_INT32_TYPE }
63 	},
64 
65 	{
66 		"ButtonStyle",
67 		{ B_GET_PROPERTY, 0 },
68 		{ B_DIRECT_SPECIFIER, 0 },
69 		"Returns the style of the spinner buttons.",
70 		0,
71 		{ B_INT32_TYPE }
72 	},
73 	{
74 		"ButtonStyle",
75 		{ B_SET_PROPERTY, 0 },
76 		{ B_DIRECT_SPECIFIER, 0},
77 		"Sets the style of the spinner buttons.",
78 		0,
79 		{ B_INT32_TYPE }
80 	},
81 
82 	{
83 		"Divider",
84 		{ B_GET_PROPERTY, 0 },
85 		{ B_DIRECT_SPECIFIER, 0 },
86 		"Returns the divider position of the spinner.",
87 		0,
88 		{ B_FLOAT_TYPE }
89 	},
90 	{
91 		"Divider",
92 		{ B_SET_PROPERTY, 0 },
93 		{ B_DIRECT_SPECIFIER, 0},
94 		"Sets the divider position of the spinner.",
95 		0,
96 		{ B_FLOAT_TYPE }
97 	},
98 
99 	{
100 		"Enabled",
101 		{ B_GET_PROPERTY, 0 },
102 		{ B_DIRECT_SPECIFIER, 0 },
103 		"Returns whether or not the spinner is enabled.",
104 		0,
105 		{ B_BOOL_TYPE }
106 	},
107 	{
108 		"Enabled",
109 		{ B_SET_PROPERTY, 0 },
110 		{ B_DIRECT_SPECIFIER, 0},
111 		"Sets whether or not the spinner is enabled.",
112 		0,
113 		{ B_BOOL_TYPE }
114 	},
115 
116 	{
117 		"Label",
118 		{ B_GET_PROPERTY, 0 },
119 		{ B_DIRECT_SPECIFIER, 0 },
120 		"Returns the spinner label.",
121 		0,
122 		{ B_STRING_TYPE }
123 	},
124 	{
125 		"Label",
126 		{ B_SET_PROPERTY, 0 },
127 		{ B_DIRECT_SPECIFIER, 0},
128 		"Sets the spinner label.",
129 		0,
130 		{ B_STRING_TYPE }
131 	},
132 
133 	{
134 		"Message",
135 		{ B_GET_PROPERTY, 0 },
136 		{ B_DIRECT_SPECIFIER, 0 },
137 		"Returns the spinner invocation message.",
138 		0,
139 		{ B_MESSAGE_TYPE }
140 	},
141 	{
142 		"Message",
143 		{ B_SET_PROPERTY, 0 },
144 		{ B_DIRECT_SPECIFIER, 0},
145 		"Sets the spinner invocation message.",
146 		0,
147 		{ B_MESSAGE_TYPE }
148 	},
149 
150 	{ 0 }
151 };
152 
153 
154 typedef enum {
155 	SPINNER_INCREMENT,
156 	SPINNER_DECREMENT
157 } spinner_direction;
158 
159 
160 class SpinnerButton : public BView {
161 public:
162 								SpinnerButton(BRect frame, const char* name,
163 									spinner_direction direction);
164 	virtual						~SpinnerButton();
165 
166 	virtual	void				AttachedToWindow();
167 	virtual	void				DetachedFromWindow();
168 	virtual	void				Draw(BRect updateRect);
169 	virtual	void				MouseDown(BPoint where);
170 	virtual	void				MouseUp(BPoint where);
171 	virtual	void				MouseMoved(BPoint where, uint32 transit,
172 									const BMessage* message);
173 
174 			bool				IsEnabled() const { return fIsEnabled; }
175 	virtual	void				SetEnabled(bool enable) { fIsEnabled = enable; };
176 
177 private:
178 			void				_DoneTracking(BPoint where);
179 			void				_Track(BPoint where, uint32);
180 
181 			spinner_direction	fSpinnerDirection;
182 			BAbstractSpinner*	fParent;
183 			bool				fIsEnabled;
184 			bool				fIsMouseDown;
185 			bool				fIsMouseOver;
186 			bigtime_t			fRepeatDelay;
187 };
188 
189 
190 class SpinnerTextView : public BTextView {
191 public:
192 								SpinnerTextView(BRect rect, BRect textRect);
193 	virtual						~SpinnerTextView();
194 
195 	virtual	void				AttachedToWindow();
196 	virtual	void				DetachedFromWindow();
197 	virtual	void				KeyDown(const char* bytes, int32 numBytes);
198 	virtual	void				MakeFocus(bool focus);
199 
200 private:
201 			BAbstractSpinner*	fParent;
202 };
203 
204 
205 class BAbstractSpinner::LabelLayoutItem : public BAbstractLayoutItem {
206 public:
207 								LabelLayoutItem(BAbstractSpinner* parent);
208 								LabelLayoutItem(BMessage* archive);
209 
210 	virtual	bool				IsVisible();
211 	virtual	void				SetVisible(bool visible);
212 
213 	virtual	BRect				Frame();
214 	virtual	void				SetFrame(BRect frame);
215 
216 			void				SetParent(BAbstractSpinner* parent);
217 	virtual	BView*				View();
218 
219 	virtual	BSize				BaseMinSize();
220 	virtual	BSize				BaseMaxSize();
221 	virtual	BSize				BasePreferredSize();
222 	virtual	BAlignment			BaseAlignment();
223 
224 			BRect				FrameInParent() const;
225 
226 	virtual status_t			Archive(BMessage* into, bool deep = true) const;
227 	static	BArchivable*		Instantiate(BMessage* from);
228 
229 private:
230 			BAbstractSpinner*	fParent;
231 			BRect				fFrame;
232 };
233 
234 
235 class BAbstractSpinner::TextViewLayoutItem : public BAbstractLayoutItem {
236 public:
237 								TextViewLayoutItem(BAbstractSpinner* parent);
238 								TextViewLayoutItem(BMessage* archive);
239 
240 	virtual	bool				IsVisible();
241 	virtual	void				SetVisible(bool visible);
242 
243 	virtual	BRect				Frame();
244 	virtual	void				SetFrame(BRect frame);
245 
246 			void				SetParent(BAbstractSpinner* parent);
247 	virtual	BView*				View();
248 
249 	virtual	BSize				BaseMinSize();
250 	virtual	BSize				BaseMaxSize();
251 	virtual	BSize				BasePreferredSize();
252 	virtual	BAlignment			BaseAlignment();
253 
254 			BRect				FrameInParent() const;
255 
256 	virtual status_t			Archive(BMessage* into, bool deep = true) const;
257 	static	BArchivable*		Instantiate(BMessage* from);
258 
259 private:
260 			BAbstractSpinner*	fParent;
261 			BRect				fFrame;
262 };
263 
264 
265 struct BAbstractSpinner::LayoutData {
266 	LayoutData(float width, float height)
267 	:
268 	label_layout_item(NULL),
269 	text_view_layout_item(NULL),
270 	label_width(0),
271 	label_height(0),
272 	text_view_width(0),
273 	text_view_height(0),
274 	previous_width(width),
275 	previous_height(height),
276 	valid(false)
277 	{
278 	}
279 
280 	LabelLayoutItem* label_layout_item;
281 	TextViewLayoutItem* text_view_layout_item;
282 
283 	font_height font_info;
284 
285 	float label_width;
286 	float label_height;
287 	float text_view_width;
288 	float text_view_height;
289 
290 	float previous_width;
291 	float previous_height;
292 
293 	BSize min;
294 	BAlignment alignment;
295 
296 	bool valid;
297 };
298 
299 
300 //	#pragma mark - SpinnerButton
301 
302 
303 SpinnerButton::SpinnerButton(BRect frame, const char* name,
304 	spinner_direction direction)
305 	:
306 	BView(frame, name, B_FOLLOW_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW),
307 	fSpinnerDirection(direction),
308 	fParent(NULL),
309 	fIsEnabled(true),
310 	fIsMouseDown(false),
311 	fIsMouseOver(false),
312 	fRepeatDelay(100000)
313 {
314 }
315 
316 
317 SpinnerButton::~SpinnerButton()
318 {
319 }
320 
321 
322 void
323 SpinnerButton::AttachedToWindow()
324 {
325 	fParent = static_cast<BAbstractSpinner*>(Parent());
326 
327 	AdoptParentColors();
328 	BView::AttachedToWindow();
329 }
330 
331 
332 void
333 SpinnerButton::DetachedFromWindow()
334 {
335 	fParent = NULL;
336 
337 	BView::DetachedFromWindow();
338 }
339 
340 
341 void
342 SpinnerButton::Draw(BRect updateRect)
343 {
344 	BRect rect(Bounds());
345 	if (!rect.IsValid() || !rect.Intersects(updateRect))
346 		return;
347 
348 	BView::Draw(updateRect);
349 
350 	float frameTint = B_DARKEN_1_TINT;
351 
352 	float fgTint;
353 	if (!fIsEnabled)
354 		fgTint = B_DARKEN_1_TINT;
355 	else if (fIsMouseDown)
356 		fgTint = B_DARKEN_MAX_TINT;
357 	else
358 		fgTint = 1.777f;	// 216 --> 48.2 (48)
359 
360 	float bgTint;
361 	if (fIsEnabled && fIsMouseOver)
362 		bgTint = B_DARKEN_1_TINT;
363 	else
364 		bgTint = B_NO_TINT;
365 
366 	rgb_color bgColor = ui_color(B_PANEL_BACKGROUND_COLOR);
367 	if (bgColor.red + bgColor.green + bgColor.blue <= 128 * 3) {
368 		// if dark background make the tint lighter
369 		frameTint = 2.0f - frameTint;
370 		fgTint = 2.0f - fgTint;
371 		bgTint = 2.0f - bgTint;
372 	}
373 
374 	uint32 borders = be_control_look->B_TOP_BORDER
375 		| be_control_look->B_BOTTOM_BORDER;
376 
377 	if (fSpinnerDirection == SPINNER_INCREMENT)
378 		borders |= be_control_look->B_RIGHT_BORDER;
379 	else
380 		borders |= be_control_look->B_LEFT_BORDER;
381 
382 	uint32 flags = fIsMouseDown ? BControlLook::B_ACTIVATED : 0;
383 
384 	// draw the button
385 	be_control_look->DrawButtonFrame(this, rect, updateRect,
386 		tint_color(bgColor, frameTint), bgColor, flags, borders);
387 	be_control_look->DrawButtonBackground(this, rect, updateRect,
388 		tint_color(bgColor, bgTint), flags, borders);
389 
390 	switch (fParent->ButtonStyle()) {
391 		case SPINNER_BUTTON_HORIZONTAL_ARROWS:
392 		{
393 			int32 arrowDirection = fSpinnerDirection == SPINNER_INCREMENT
394 				? be_control_look->B_RIGHT_ARROW
395 				: be_control_look->B_LEFT_ARROW;
396 
397 			rect.InsetBy(0.0f, 1.0f);
398 			be_control_look->DrawArrowShape(this, rect, updateRect, bgColor,
399 				arrowDirection, 0, fgTint);
400 			break;
401 		}
402 
403 		case SPINNER_BUTTON_VERTICAL_ARROWS:
404 		{
405 			int32 arrowDirection = fSpinnerDirection == SPINNER_INCREMENT
406 				? be_control_look->B_UP_ARROW
407 				: be_control_look->B_DOWN_ARROW;
408 
409 			rect.InsetBy(0.0f, 1.0f);
410 			be_control_look->DrawArrowShape(this, rect, updateRect, bgColor,
411 				arrowDirection, 0, fgTint);
412 			break;
413 		}
414 
415 		default:
416 		case SPINNER_BUTTON_PLUS_MINUS:
417 		{
418 			BFont font;
419 			fParent->GetFont(&font);
420 			float inset = floorf(font.Size() / 4);
421 			rect.InsetBy(inset, inset);
422 
423 			if (rect.IntegerWidth() % 2 != 0)
424 				rect.right -= 1;
425 
426 			if (rect.IntegerHeight() % 2 != 0)
427 				rect.bottom -= 1;
428 
429 			SetHighColor(tint_color(bgColor, fgTint));
430 
431 			// draw the +/-
432 			float halfHeight = floorf(rect.Height() / 2);
433 			StrokeLine(BPoint(rect.left, rect.top + halfHeight),
434 				BPoint(rect.right, rect.top + halfHeight));
435 			if (fSpinnerDirection == SPINNER_INCREMENT) {
436 				float halfWidth = floorf(rect.Width() / 2);
437 				StrokeLine(BPoint(rect.left + halfWidth, rect.top + 1),
438 					BPoint(rect.left + halfWidth, rect.bottom - 1));
439 			}
440 		}
441 	}
442 }
443 
444 
445 void
446 SpinnerButton::MouseDown(BPoint where)
447 {
448 	if (fIsEnabled) {
449 		fIsMouseDown = true;
450 		Invalidate();
451 		fRepeatDelay = 100000;
452 		MouseDownThread<SpinnerButton>::TrackMouse(this,
453 			&SpinnerButton::_DoneTracking, &SpinnerButton::_Track);
454 	}
455 
456 	BView::MouseDown(where);
457 }
458 
459 
460 void
461 SpinnerButton::MouseMoved(BPoint where, uint32 transit,
462 	const BMessage* message)
463 {
464 	switch (transit) {
465 		case B_ENTERED_VIEW:
466 		case B_INSIDE_VIEW:
467 		{
468 			BPoint where;
469 			uint32 buttons;
470 			GetMouse(&where, &buttons);
471 			fIsMouseOver = Bounds().Contains(where) && buttons == 0;
472 			if (!fIsMouseDown)
473 				Invalidate();
474 
475 			break;
476 		}
477 
478 		case B_EXITED_VIEW:
479 		case B_OUTSIDE_VIEW:
480 			fIsMouseOver = false;
481 			MouseUp(Bounds().LeftTop());
482 			break;
483 	}
484 
485 	BView::MouseMoved(where, transit, message);
486 }
487 
488 
489 void
490 SpinnerButton::MouseUp(BPoint where)
491 {
492 	fIsMouseDown = false;
493 	Invalidate();
494 
495 	BView::MouseUp(where);
496 }
497 
498 
499 //	#pragma mark  - SpinnerButton private methods
500 
501 
502 void
503 SpinnerButton::_DoneTracking(BPoint where)
504 {
505 	if (fIsMouseDown || !Bounds().Contains(where))
506 		fIsMouseDown = false;
507 }
508 
509 
510 void
511 SpinnerButton::_Track(BPoint where, uint32)
512 {
513 	if (fParent == NULL || !Bounds().Contains(where)) {
514 		fIsMouseDown = false;
515 		return;
516 	}
517 	fIsMouseDown = true;
518 
519 	fSpinnerDirection == SPINNER_INCREMENT
520 		? fParent->Increment()
521 		: fParent->Decrement();
522 
523 	snooze(fRepeatDelay);
524 	fRepeatDelay = 10000;
525 }
526 
527 
528 //	#pragma mark - SpinnerTextView
529 
530 
531 SpinnerTextView::SpinnerTextView(BRect rect, BRect textRect)
532 	:
533 	BTextView(rect, "textview", textRect, B_FOLLOW_ALL,
534 		B_WILL_DRAW | B_NAVIGABLE),
535 	fParent(NULL)
536 {
537 	MakeResizable(true);
538 }
539 
540 
541 SpinnerTextView::~SpinnerTextView()
542 {
543 }
544 
545 
546 void
547 SpinnerTextView::AttachedToWindow()
548 {
549 	fParent = static_cast<BAbstractSpinner*>(Parent());
550 
551 	BTextView::AttachedToWindow();
552 }
553 
554 
555 void
556 SpinnerTextView::DetachedFromWindow()
557 {
558 	fParent = NULL;
559 
560 	BTextView::DetachedFromWindow();
561 }
562 
563 
564 void
565 SpinnerTextView::KeyDown(const char* bytes, int32 numBytes)
566 {
567 	if (fParent == NULL) {
568 		BTextView::KeyDown(bytes, numBytes);
569 		return;
570 	}
571 
572 	switch (bytes[0]) {
573 		case B_ENTER:
574 		case B_SPACE:
575 			fParent->SetValueFromText();
576 			break;
577 
578 		case B_TAB:
579 			fParent->KeyDown(bytes, numBytes);
580 			break;
581 
582 		case B_LEFT_ARROW:
583 			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
584 				&& (modifiers() & B_CONTROL_KEY) != 0) {
585 				// need to hold down control, otherwise can't move cursor
586 				fParent->Decrement();
587 			} else
588 				BTextView::KeyDown(bytes, numBytes);
589 			break;
590 
591 		case B_UP_ARROW:
592 			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
593 				fParent->Increment();
594 			else
595 				BTextView::KeyDown(bytes, numBytes);
596 			break;
597 
598 		case B_RIGHT_ARROW:
599 			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
600 				&& (modifiers() & B_CONTROL_KEY) != 0) {
601 				// need to hold down control, otherwise can't move cursor
602 				fParent->Increment();
603 			} else
604 				BTextView::KeyDown(bytes, numBytes);
605 			break;
606 
607 		case B_DOWN_ARROW:
608 			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
609 				fParent->Decrement();
610 			else
611 				BTextView::KeyDown(bytes, numBytes);
612 			break;
613 
614 		default:
615 			BTextView::KeyDown(bytes, numBytes);
616 			break;
617 	}
618 }
619 
620 
621 void
622 SpinnerTextView::MakeFocus(bool focus)
623 {
624 	BTextView::MakeFocus(focus);
625 
626 	if (fParent == NULL)
627 		return;
628 
629 	if (focus)
630 		SelectAll();
631 	else
632 		fParent->SetValueFromText();
633 
634 	fParent->_DrawTextView(fParent->Bounds());
635 }
636 
637 
638 //	#pragma mark - BAbstractSpinner::LabelLayoutItem
639 
640 
641 BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BAbstractSpinner* parent)
642 	:
643 	fParent(parent),
644 	fFrame()
645 {
646 }
647 
648 
649 BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BMessage* from)
650 	:
651 	BAbstractLayoutItem(from),
652 	fParent(NULL),
653 	fFrame()
654 {
655 	from->FindRect(kFrameField, &fFrame);
656 }
657 
658 
659 bool
660 BAbstractSpinner::LabelLayoutItem::IsVisible()
661 {
662 	return !fParent->IsHidden(fParent);
663 }
664 
665 
666 void
667 BAbstractSpinner::LabelLayoutItem::SetVisible(bool visible)
668 {
669 }
670 
671 
672 BRect
673 BAbstractSpinner::LabelLayoutItem::Frame()
674 {
675 	return fFrame;
676 }
677 
678 
679 void
680 BAbstractSpinner::LabelLayoutItem::SetFrame(BRect frame)
681 {
682 	fFrame = frame;
683 	fParent->_UpdateFrame();
684 }
685 
686 
687 void
688 BAbstractSpinner::LabelLayoutItem::SetParent(BAbstractSpinner* parent)
689 {
690 	fParent = parent;
691 }
692 
693 
694 BView*
695 BAbstractSpinner::LabelLayoutItem::View()
696 {
697 	return fParent;
698 }
699 
700 
701 BSize
702 BAbstractSpinner::LabelLayoutItem::BaseMinSize()
703 {
704 	fParent->_ValidateLayoutData();
705 
706 	if (fParent->Label() == NULL)
707 		return BSize(-1.0f, -1.0f);
708 
709 	return BSize(fParent->fLayoutData->label_width
710 			+ be_control_look->DefaultLabelSpacing(),
711 		fParent->fLayoutData->label_height);
712 }
713 
714 
715 BSize
716 BAbstractSpinner::LabelLayoutItem::BaseMaxSize()
717 {
718 	return BaseMinSize();
719 }
720 
721 
722 BSize
723 BAbstractSpinner::LabelLayoutItem::BasePreferredSize()
724 {
725 	return BaseMinSize();
726 }
727 
728 
729 BAlignment
730 BAbstractSpinner::LabelLayoutItem::BaseAlignment()
731 {
732 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
733 }
734 
735 
736 BRect
737 BAbstractSpinner::LabelLayoutItem::FrameInParent() const
738 {
739 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
740 }
741 
742 
743 status_t
744 BAbstractSpinner::LabelLayoutItem::Archive(BMessage* into, bool deep) const
745 {
746 	BArchiver archiver(into);
747 	status_t result = BAbstractLayoutItem::Archive(into, deep);
748 
749 	if (result == B_OK)
750 		result = into->AddRect(kFrameField, fFrame);
751 
752 	return archiver.Finish(result);
753 }
754 
755 
756 BArchivable*
757 BAbstractSpinner::LabelLayoutItem::Instantiate(BMessage* from)
758 {
759 	if (validate_instantiation(from, "BAbstractSpinner::LabelLayoutItem"))
760 		return new LabelLayoutItem(from);
761 
762 	return NULL;
763 }
764 
765 
766 //	#pragma mark - BAbstractSpinner::TextViewLayoutItem
767 
768 
769 BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BAbstractSpinner* parent)
770 	:
771 	fParent(parent),
772 	fFrame()
773 {
774 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
775 }
776 
777 
778 BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
779 	:
780 	BAbstractLayoutItem(from),
781 	fParent(NULL),
782 	fFrame()
783 {
784 	from->FindRect(kFrameField, &fFrame);
785 }
786 
787 
788 bool
789 BAbstractSpinner::TextViewLayoutItem::IsVisible()
790 {
791 	return !fParent->IsHidden(fParent);
792 }
793 
794 
795 void
796 BAbstractSpinner::TextViewLayoutItem::SetVisible(bool visible)
797 {
798 	// not allowed
799 }
800 
801 
802 BRect
803 BAbstractSpinner::TextViewLayoutItem::Frame()
804 {
805 	return fFrame;
806 }
807 
808 
809 void
810 BAbstractSpinner::TextViewLayoutItem::SetFrame(BRect frame)
811 {
812 	fFrame = frame;
813 	fParent->_UpdateFrame();
814 }
815 
816 
817 void
818 BAbstractSpinner::TextViewLayoutItem::SetParent(BAbstractSpinner* parent)
819 {
820 	fParent = parent;
821 }
822 
823 
824 BView*
825 BAbstractSpinner::TextViewLayoutItem::View()
826 {
827 	return fParent;
828 }
829 
830 
831 BSize
832 BAbstractSpinner::TextViewLayoutItem::BaseMinSize()
833 {
834 	fParent->_ValidateLayoutData();
835 
836 	BSize size(fParent->fLayoutData->text_view_width,
837 		fParent->fLayoutData->text_view_height);
838 
839 	return size;
840 }
841 
842 
843 BSize
844 BAbstractSpinner::TextViewLayoutItem::BaseMaxSize()
845 {
846 	return BaseMinSize();
847 }
848 
849 
850 BSize
851 BAbstractSpinner::TextViewLayoutItem::BasePreferredSize()
852 {
853 	return BaseMinSize();
854 }
855 
856 
857 BAlignment
858 BAbstractSpinner::TextViewLayoutItem::BaseAlignment()
859 {
860 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
861 }
862 
863 
864 BRect
865 BAbstractSpinner::TextViewLayoutItem::FrameInParent() const
866 {
867 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
868 }
869 
870 
871 status_t
872 BAbstractSpinner::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
873 {
874 	BArchiver archiver(into);
875 	status_t result = BAbstractLayoutItem::Archive(into, deep);
876 
877 	if (result == B_OK)
878 		result = into->AddRect(kFrameField, fFrame);
879 
880 	return archiver.Finish(result);
881 }
882 
883 
884 BArchivable*
885 BAbstractSpinner::TextViewLayoutItem::Instantiate(BMessage* from)
886 {
887 	if (validate_instantiation(from, "BAbstractSpinner::TextViewLayoutItem"))
888 		return new LabelLayoutItem(from);
889 
890 	return NULL;
891 }
892 
893 
894 //	#pragma mark - BAbstractSpinner
895 
896 
897 BAbstractSpinner::BAbstractSpinner(BRect frame, const char* name, const char* label,
898 	BMessage* message, uint32 resizingMode, uint32 flags)
899 	:
900 	BControl(frame, name, label, message, resizingMode,
901 		flags | B_WILL_DRAW | B_FRAME_EVENTS)
902 {
903 	_InitObject();
904 }
905 
906 
907 BAbstractSpinner::BAbstractSpinner(const char* name, const char* label, BMessage* message,
908 	uint32 flags)
909 	:
910 	BControl(name, label, message, flags | B_WILL_DRAW | B_FRAME_EVENTS)
911 {
912 	_InitObject();
913 }
914 
915 
916 BAbstractSpinner::BAbstractSpinner(BMessage* data)
917 	:
918 	BControl(data),
919 	fButtonStyle(SPINNER_BUTTON_PLUS_MINUS)
920 {
921 	_InitObject();
922 
923 	if (data->FindInt32("_align") != B_OK)
924 		fAlignment = B_ALIGN_LEFT;
925 
926 	if (data->FindInt32("_button_style") != B_OK)
927 		fButtonStyle = SPINNER_BUTTON_PLUS_MINUS;
928 
929 	if (data->FindInt32("_divider") != B_OK)
930 		fDivider = 0.0f;
931 }
932 
933 
934 BAbstractSpinner::~BAbstractSpinner()
935 {
936 	delete fLayoutData;
937 	fLayoutData = NULL;
938 }
939 
940 
941 BArchivable*
942 BAbstractSpinner::Instantiate(BMessage* data)
943 {
944 	// cannot instantiate an abstract spinner
945 	return NULL;
946 }
947 
948 
949 status_t
950 BAbstractSpinner::Archive(BMessage* data, bool deep) const
951 {
952 	status_t status = BControl::Archive(data, deep);
953 	data->AddString("class", "Spinner");
954 
955 	if (status == B_OK)
956 		status = data->AddInt32("_align", fAlignment);
957 
958 	if (status == B_OK)
959 		data->AddInt32("_button_style", fButtonStyle);
960 
961 	if (status == B_OK)
962 		status = data->AddFloat("_divider", fDivider);
963 
964 	return status;
965 }
966 
967 
968 status_t
969 BAbstractSpinner::GetSupportedSuites(BMessage* message)
970 {
971 	message->AddString("suites", "suite/vnd.Haiku-spinner");
972 
973 	BPropertyInfo prop_info(sProperties);
974 	message->AddFlat("messages", &prop_info);
975 
976 	return BView::GetSupportedSuites(message);
977 }
978 
979 
980 BHandler*
981 BAbstractSpinner::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
982 	int32 form, const char* property)
983 {
984 	return BView::ResolveSpecifier(message, index, specifier, form,
985 		property);
986 }
987 
988 
989 void
990 BAbstractSpinner::AttachedToWindow()
991 {
992 	if (!Messenger().IsValid())
993 		SetTarget(Window());
994 
995 	BControl::SetValue(Value());
996 		// sets the text and enables or disables the arrows
997 
998 	_UpdateTextViewColors(IsEnabled());
999 	fTextView->MakeEditable(IsEnabled());
1000 
1001 	BView::AttachedToWindow();
1002 }
1003 
1004 
1005 void
1006 BAbstractSpinner::Draw(BRect updateRect)
1007 {
1008 	_DrawLabel(updateRect);
1009 	_DrawTextView(updateRect);
1010 	fIncrement->Invalidate();
1011 	fDecrement->Invalidate();
1012 }
1013 
1014 
1015 void
1016 BAbstractSpinner::FrameResized(float width, float height)
1017 {
1018 	BView::FrameResized(width, height);
1019 
1020 	// TODO: this causes flickering still...
1021 
1022 	// changes in width
1023 
1024 	BRect bounds = Bounds();
1025 
1026 	if (bounds.Width() > fLayoutData->previous_width) {
1027 		// invalidate the region between the old and the new right border
1028 		BRect rect = bounds;
1029 		rect.left += fLayoutData->previous_width - kFrameMargin;
1030 		rect.right--;
1031 		Invalidate(rect);
1032 	} else if (bounds.Width() < fLayoutData->previous_width) {
1033 		// invalidate the region of the new right border
1034 		BRect rect = bounds;
1035 		rect.left = rect.right - kFrameMargin;
1036 		Invalidate(rect);
1037 	}
1038 
1039 	// changes in height
1040 
1041 	if (bounds.Height() > fLayoutData->previous_height) {
1042 		// invalidate the region between the old and the new bottom border
1043 		BRect rect = bounds;
1044 		rect.top += fLayoutData->previous_height - kFrameMargin;
1045 		rect.bottom--;
1046 		Invalidate(rect);
1047 		// invalidate label area
1048 		rect = bounds;
1049 		rect.right = fDivider;
1050 		Invalidate(rect);
1051 	} else if (bounds.Height() < fLayoutData->previous_height) {
1052 		// invalidate the region of the new bottom border
1053 		BRect rect = bounds;
1054 		rect.top = rect.bottom - kFrameMargin;
1055 		Invalidate(rect);
1056 		// invalidate label area
1057 		rect = bounds;
1058 		rect.right = fDivider;
1059 		Invalidate(rect);
1060 	}
1061 
1062 	fLayoutData->previous_width = bounds.Width();
1063 	fLayoutData->previous_height = bounds.Height();
1064 }
1065 
1066 
1067 void
1068 BAbstractSpinner::ValueChanged()
1069 {
1070 	// hook method - does nothing
1071 }
1072 
1073 
1074 void
1075 BAbstractSpinner::MessageReceived(BMessage* message)
1076 {
1077 	if (!IsEnabled() && message->what == B_COLORS_UPDATED)
1078 		_UpdateTextViewColors(false);
1079 
1080 	BControl::MessageReceived(message);
1081 }
1082 
1083 
1084 void
1085 BAbstractSpinner::MakeFocus(bool focus)
1086 {
1087 	fTextView->MakeFocus(focus);
1088 }
1089 
1090 
1091 void
1092 BAbstractSpinner::ResizeToPreferred()
1093 {
1094 	BView::ResizeToPreferred();
1095 
1096 	const char* label = Label();
1097 	if (label != NULL) {
1098 		fDivider = ceilf(StringWidth(label))
1099 			+ be_control_look->DefaultLabelSpacing();
1100 	} else
1101 		fDivider = 0.0f;
1102 
1103 	_LayoutTextView();
1104 }
1105 
1106 
1107 void
1108 BAbstractSpinner::SetFlags(uint32 flags)
1109 {
1110 	// If the textview is navigable, set it to not navigable if needed,
1111 	// else if it is not navigable, set it to navigable if needed
1112 	if (fTextView->Flags() & B_NAVIGABLE) {
1113 		if (!(flags & B_NAVIGABLE))
1114 			fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
1115 	} else {
1116 		if (flags & B_NAVIGABLE)
1117 			fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
1118 	}
1119 
1120 	// Don't make this one navigable
1121 	flags &= ~B_NAVIGABLE;
1122 
1123 	BView::SetFlags(flags);
1124 }
1125 
1126 
1127 void
1128 BAbstractSpinner::WindowActivated(bool active)
1129 {
1130 	_DrawTextView(fTextView->Frame());
1131 }
1132 
1133 
1134 void
1135 BAbstractSpinner::SetAlignment(alignment align)
1136 {
1137 	fAlignment = align;
1138 }
1139 
1140 
1141 void
1142 BAbstractSpinner::SetButtonStyle(spinner_button_style buttonStyle)
1143 {
1144 	fButtonStyle = buttonStyle;
1145 }
1146 
1147 
1148 void
1149 BAbstractSpinner::SetDivider(float position)
1150 {
1151 	position = roundf(position);
1152 
1153 	float delta = fDivider - position;
1154 	if (delta == 0.0f)
1155 		return;
1156 
1157 	fDivider = position;
1158 
1159 	if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
1160 		// We should never get here, since layout support means, we also
1161 		// layout the divider, and don't use this method at all.
1162 		Relayout();
1163 	} else {
1164 		_LayoutTextView();
1165 		Invalidate();
1166 	}
1167 }
1168 
1169 
1170 void
1171 BAbstractSpinner::SetEnabled(bool enable)
1172 {
1173 	if (IsEnabled() == enable)
1174 		return;
1175 
1176 	BControl::SetEnabled(enable);
1177 
1178 	fTextView->MakeEditable(enable);
1179 	if (enable)
1180 		fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
1181 	else
1182 		fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
1183 
1184 	_UpdateTextViewColors(enable);
1185 	fTextView->Invalidate();
1186 
1187 	_LayoutTextView();
1188 	Invalidate();
1189 	if (Window() != NULL)
1190 		Window()->UpdateIfNeeded();
1191 }
1192 
1193 
1194 void
1195 BAbstractSpinner::SetLabel(const char* label)
1196 {
1197 	BControl::SetLabel(label);
1198 
1199 	if (Window() != NULL)
1200 		Window()->UpdateIfNeeded();
1201 }
1202 
1203 
1204 bool
1205 BAbstractSpinner::IsDecrementEnabled() const
1206 {
1207 	return fDecrement->IsEnabled();
1208 }
1209 
1210 
1211 void
1212 BAbstractSpinner::SetDecrementEnabled(bool enable)
1213 {
1214 	if (IsDecrementEnabled() == enable)
1215 		return;
1216 
1217 	fDecrement->SetEnabled(enable);
1218 	fDecrement->Invalidate();
1219 }
1220 
1221 
1222 bool
1223 BAbstractSpinner::IsIncrementEnabled() const
1224 {
1225 	return fIncrement->IsEnabled();
1226 }
1227 
1228 
1229 void
1230 BAbstractSpinner::SetIncrementEnabled(bool enable)
1231 {
1232 	if (IsIncrementEnabled() == enable)
1233 		return;
1234 
1235 	fIncrement->SetEnabled(enable);
1236 	fIncrement->Invalidate();
1237 }
1238 
1239 
1240 BSize
1241 BAbstractSpinner::MinSize()
1242 {
1243 	_ValidateLayoutData();
1244 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
1245 }
1246 
1247 
1248 BSize
1249 BAbstractSpinner::MaxSize()
1250 {
1251 	_ValidateLayoutData();
1252 
1253 	BSize max = fLayoutData->min;
1254 	max.width = B_SIZE_UNLIMITED;
1255 
1256 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
1257 }
1258 
1259 
1260 BSize
1261 BAbstractSpinner::PreferredSize()
1262 {
1263 	_ValidateLayoutData();
1264 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
1265 		fLayoutData->min);
1266 }
1267 
1268 
1269 BAlignment
1270 BAbstractSpinner::LayoutAlignment()
1271 {
1272 	_ValidateLayoutData();
1273 	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
1274 		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_CENTER));
1275 }
1276 
1277 
1278 BLayoutItem*
1279 BAbstractSpinner::CreateLabelLayoutItem()
1280 {
1281 	if (fLayoutData->label_layout_item == NULL)
1282 		fLayoutData->label_layout_item = new LabelLayoutItem(this);
1283 
1284 	return fLayoutData->label_layout_item;
1285 }
1286 
1287 
1288 BLayoutItem*
1289 BAbstractSpinner::CreateTextViewLayoutItem()
1290 {
1291 	if (fLayoutData->text_view_layout_item == NULL)
1292 		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
1293 
1294 	return fLayoutData->text_view_layout_item;
1295 }
1296 
1297 
1298 BTextView*
1299 BAbstractSpinner::TextView() const
1300 {
1301 	return dynamic_cast<BTextView*>(fTextView);
1302 }
1303 
1304 
1305 //	#pragma mark - BAbstractSpinner protected methods
1306 
1307 
1308 status_t
1309 BAbstractSpinner::AllArchived(BMessage* into) const
1310 {
1311 	status_t result;
1312 	if ((result = BControl::AllArchived(into)) != B_OK)
1313 		return result;
1314 
1315 	BArchiver archiver(into);
1316 
1317 	BArchivable* textViewItem = fLayoutData->text_view_layout_item;
1318 	if (archiver.IsArchived(textViewItem))
1319 		result = archiver.AddArchivable(kTextViewItemField, textViewItem);
1320 
1321 	if (result != B_OK)
1322 		return result;
1323 
1324 	BArchivable* labelBarItem = fLayoutData->label_layout_item;
1325 	if (archiver.IsArchived(labelBarItem))
1326 		result = archiver.AddArchivable(kLabelItemField, labelBarItem);
1327 
1328 	return result;
1329 }
1330 
1331 
1332 status_t
1333 BAbstractSpinner::AllUnarchived(const BMessage* from)
1334 {
1335 	BUnarchiver unarchiver(from);
1336 
1337 	status_t result = B_OK;
1338 	if ((result = BControl::AllUnarchived(from)) != B_OK)
1339 		return result;
1340 
1341 	if (unarchiver.IsInstantiated(kTextViewItemField)) {
1342 		TextViewLayoutItem*& textViewItem
1343 			= fLayoutData->text_view_layout_item;
1344 		result = unarchiver.FindObject(kTextViewItemField,
1345 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, textViewItem);
1346 
1347 		if (result == B_OK)
1348 			textViewItem->SetParent(this);
1349 		else
1350 			return result;
1351 	}
1352 
1353 	if (unarchiver.IsInstantiated(kLabelItemField)) {
1354 		LabelLayoutItem*& labelItem = fLayoutData->label_layout_item;
1355 		result = unarchiver.FindObject(kLabelItemField,
1356 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, labelItem);
1357 
1358 		if (result == B_OK)
1359 			labelItem->SetParent(this);
1360 	}
1361 
1362 	return result;
1363 }
1364 
1365 
1366 void
1367 BAbstractSpinner::DoLayout()
1368 {
1369 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
1370 		return;
1371 
1372 	if (GetLayout()) {
1373 		BControl::DoLayout();
1374 		return;
1375 	}
1376 
1377 	_ValidateLayoutData();
1378 
1379 	BSize size(Bounds().Size());
1380 	if (size.width < fLayoutData->min.width)
1381 		size.width = fLayoutData->min.width;
1382 
1383 	if (size.height < fLayoutData->min.height)
1384 		size.height = fLayoutData->min.height;
1385 
1386 	float divider = 0;
1387 	if (fLayoutData->label_layout_item != NULL
1388 		&& fLayoutData->text_view_layout_item != NULL
1389 		&& fLayoutData->label_layout_item->Frame().IsValid()
1390 		&& fLayoutData->text_view_layout_item->Frame().IsValid()) {
1391 		divider = fLayoutData->text_view_layout_item->Frame().left
1392 			- fLayoutData->label_layout_item->Frame().left;
1393 	} else if (fLayoutData->label_width > 0) {
1394 		divider = fLayoutData->label_width
1395 			+ be_control_look->DefaultLabelSpacing();
1396 	}
1397 	fDivider = divider;
1398 
1399 	BRect dirty(fTextView->Frame());
1400 	_LayoutTextView();
1401 
1402 	// invalidate dirty region
1403 	dirty = dirty | fTextView->Frame();
1404 	dirty = dirty | fIncrement->Frame();
1405 	dirty = dirty | fDecrement->Frame();
1406 
1407 	Invalidate(dirty);
1408 }
1409 
1410 
1411 void
1412 BAbstractSpinner::LayoutInvalidated(bool descendants)
1413 {
1414 	if (fLayoutData != NULL)
1415 		fLayoutData->valid = false;
1416 }
1417 
1418 
1419 //	#pragma mark - BAbstractSpinner private methods
1420 
1421 
1422 void
1423 BAbstractSpinner::_DrawLabel(BRect updateRect)
1424 {
1425 	BRect rect(Bounds());
1426 	rect.right = fDivider;
1427 	if (!rect.IsValid() || !rect.Intersects(updateRect))
1428 		return;
1429 
1430 	_ValidateLayoutData();
1431 
1432 	const char* label = Label();
1433 	if (label == NULL)
1434 		return;
1435 
1436 	// horizontal position
1437 	float x;
1438 	switch (fAlignment) {
1439 		case B_ALIGN_RIGHT:
1440 			x = fDivider - fLayoutData->label_width - 3.0f;
1441 			break;
1442 
1443 		case B_ALIGN_CENTER:
1444 			x = fDivider - roundf(fLayoutData->label_width / 2.0f);
1445 			break;
1446 
1447 		default:
1448 			x = 0.0f;
1449 			break;
1450 	}
1451 
1452 	// vertical position
1453 	font_height& fontHeight = fLayoutData->font_info;
1454 	float y = rect.top
1455 		+ roundf((rect.Height() + 1.0f - fontHeight.ascent
1456 			- fontHeight.descent) / 2.0f)
1457 		+ fontHeight.ascent;
1458 
1459 	uint32 flags = be_control_look->Flags(this);
1460 
1461 	// erase the is control flag before drawing the label so that the label
1462 	// will get drawn using B_PANEL_TEXT_COLOR.
1463 	flags &= ~BControlLook::B_IS_CONTROL;
1464 
1465 	be_control_look->DrawLabel(this, label, LowColor(), flags, BPoint(x, y));
1466 }
1467 
1468 
1469 void
1470 BAbstractSpinner::_DrawTextView(BRect updateRect)
1471 {
1472 	BRect rect = fTextView->Frame();
1473 	rect.InsetBy(-kFrameMargin, -kFrameMargin);
1474 	if (!rect.IsValid() || !rect.Intersects(updateRect))
1475 		return;
1476 
1477 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1478 	uint32 flags = 0;
1479 	if (!IsEnabled())
1480 		flags |= BControlLook::B_DISABLED;
1481 
1482 	if (fTextView->IsFocus() && Window()->IsActive())
1483 		flags |= BControlLook::B_FOCUSED;
1484 
1485 	be_control_look->DrawTextControlBorder(this, rect, updateRect, base,
1486 		flags);
1487 }
1488 
1489 
1490 void
1491 BAbstractSpinner::_InitObject()
1492 {
1493 	fAlignment = B_ALIGN_LEFT;
1494 	fButtonStyle = SPINNER_BUTTON_PLUS_MINUS;
1495 
1496 	if (Label() != NULL) {
1497 		fDivider = StringWidth(Label())
1498 			+ be_control_look->DefaultLabelSpacing();
1499 	} else
1500 		fDivider = 0.0f;
1501 
1502 	BControl::SetEnabled(true);
1503 	BControl::SetValue(0);
1504 
1505 	BRect rect(Bounds());
1506 	fLayoutData = new LayoutData(rect.Width(), rect.Height());
1507 
1508 	rect.left = fDivider;
1509 	rect.InsetBy(kFrameMargin, kFrameMargin);
1510 	rect.right -= rect.Height() * 2 + kFrameMargin * 2 + 1.0f;
1511 	BRect textRect(rect.OffsetToCopy(B_ORIGIN));
1512 
1513 	fTextView = new SpinnerTextView(rect, textRect);
1514 	AddChild(fTextView);
1515 
1516 	rect.InsetBy(0.0f, -kFrameMargin);
1517 
1518 	rect.left = rect.right + kFrameMargin * 2;
1519 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1520 
1521 	fDecrement = new SpinnerButton(rect, "decrement", SPINNER_DECREMENT);
1522 	AddChild(fDecrement);
1523 
1524 	rect.left = rect.right + 1.0f;
1525 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1526 
1527 	fIncrement = new SpinnerButton(rect, "increment", SPINNER_INCREMENT);
1528 	AddChild(fIncrement);
1529 
1530 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
1531 	if (navigableFlags != 0)
1532 		BControl::SetFlags(Flags() & ~B_NAVIGABLE);
1533 }
1534 
1535 
1536 void
1537 BAbstractSpinner::_LayoutTextView()
1538 {
1539 	BRect rect;
1540 	if (fLayoutData->text_view_layout_item != NULL) {
1541 		rect = fLayoutData->text_view_layout_item->FrameInParent();
1542 	} else {
1543 		rect = Bounds();
1544 		rect.left = fDivider;
1545 	}
1546 	rect.InsetBy(kFrameMargin, kFrameMargin);
1547 	rect.right -= rect.Height() * 2 + kFrameMargin * 2 + 1.0f;
1548 
1549 	fTextView->MoveTo(rect.left, rect.top);
1550 	fTextView->ResizeTo(rect.Width(), rect.Height());
1551 	fTextView->SetTextRect(rect.OffsetToCopy(B_ORIGIN));
1552 
1553 	rect.InsetBy(0.0f, -kFrameMargin);
1554 
1555 	rect.left = rect.right + kFrameMargin * 2;
1556 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1557 
1558 	fDecrement->ResizeTo(rect.Width(), rect.Height());
1559 	fDecrement->MoveTo(rect.LeftTop());
1560 
1561 	rect.left = rect.right + 1.0f;
1562 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1563 
1564 	fIncrement->ResizeTo(rect.Width(), rect.Height());
1565 	fIncrement->MoveTo(rect.LeftTop());
1566 }
1567 
1568 
1569 void
1570 BAbstractSpinner::_UpdateFrame()
1571 {
1572 	if (fLayoutData->label_layout_item == NULL
1573 		|| fLayoutData->text_view_layout_item == NULL) {
1574 		return;
1575 	}
1576 
1577 	BRect labelFrame = fLayoutData->label_layout_item->Frame();
1578 	BRect textViewFrame = fLayoutData->text_view_layout_item->Frame();
1579 
1580 	if (!labelFrame.IsValid() || !textViewFrame.IsValid())
1581 		return;
1582 
1583 	// update divider
1584 	fDivider = textViewFrame.left - labelFrame.left;
1585 
1586 	BRect frame = textViewFrame | labelFrame;
1587 	MoveTo(frame.left, frame.top);
1588 	BSize oldSize = Bounds().Size();
1589 	ResizeTo(frame.Width(), frame.Height());
1590 	BSize newSize = Bounds().Size();
1591 
1592 	// If the size changes, ResizeTo() will trigger a relayout, otherwise
1593 	// we need to do that explicitly.
1594 	if (newSize != oldSize)
1595 		Relayout();
1596 }
1597 
1598 
1599 void
1600 BAbstractSpinner::_UpdateTextViewColors(bool enable)
1601 {
1602 	// Mimick BTextControl's appearance.
1603 	rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1604 
1605 	if (enable) {
1606 		fTextView->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
1607 		fTextView->SetLowUIColor(ViewUIColor());
1608 	} else {
1609 		rgb_color color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1610 		color = disable_color(ViewColor(), color);
1611 		textColor = disable_color(textColor, ViewColor());
1612 
1613 		fTextView->SetViewColor(color);
1614 		fTextView->SetLowColor(color);
1615 	}
1616 
1617 	BFont font;
1618 	fTextView->GetFontAndColor(0, &font);
1619 	fTextView->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1620 }
1621 
1622 
1623 void
1624 BAbstractSpinner::_ValidateLayoutData()
1625 {
1626 	if (fLayoutData->valid)
1627 		return;
1628 
1629 	font_height& fontHeight = fLayoutData->font_info;
1630 	GetFontHeight(&fontHeight);
1631 
1632 	if (Label() != NULL) {
1633 		fLayoutData->label_width = StringWidth(Label());
1634 		fLayoutData->label_height = ceilf(fontHeight.ascent
1635 			+ fontHeight.descent + fontHeight.leading);
1636 	} else {
1637 		fLayoutData->label_width = 0;
1638 		fLayoutData->label_height = 0;
1639 	}
1640 
1641 	float divider = 0;
1642 	if (fLayoutData->label_width > 0) {
1643 		divider = ceilf(fLayoutData->label_width
1644 			+ be_control_look->DefaultLabelSpacing());
1645 	}
1646 
1647 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
1648 		divider = std::max(divider, fDivider);
1649 
1650 	float minTextWidth = fTextView->StringWidth("99999");
1651 
1652 	float textViewHeight = fTextView->LineHeight(0) + kFrameMargin * 2;
1653 	float textViewWidth = minTextWidth + textViewHeight * 2;
1654 
1655 	fLayoutData->text_view_width = textViewWidth;
1656 	fLayoutData->text_view_height = textViewHeight;
1657 
1658 	BSize min(textViewWidth, textViewHeight);
1659 	if (divider > 0.0f)
1660 		min.width += divider;
1661 
1662 	if (fLayoutData->label_height > min.height)
1663 		min.height = fLayoutData->label_height;
1664 
1665 	fLayoutData->min = min;
1666 	fLayoutData->valid = true;
1667 
1668 	ResetLayoutInvalidation();
1669 }
1670 
1671 
1672 // FBC padding
1673 
1674 void BAbstractSpinner::_ReservedAbstractSpinner20() {}
1675 void BAbstractSpinner::_ReservedAbstractSpinner19() {}
1676 void BAbstractSpinner::_ReservedAbstractSpinner18() {}
1677 void BAbstractSpinner::_ReservedAbstractSpinner17() {}
1678 void BAbstractSpinner::_ReservedAbstractSpinner16() {}
1679 void BAbstractSpinner::_ReservedAbstractSpinner15() {}
1680 void BAbstractSpinner::_ReservedAbstractSpinner14() {}
1681 void BAbstractSpinner::_ReservedAbstractSpinner13() {}
1682 void BAbstractSpinner::_ReservedAbstractSpinner12() {}
1683 void BAbstractSpinner::_ReservedAbstractSpinner11() {}
1684 void BAbstractSpinner::_ReservedAbstractSpinner10() {}
1685 void BAbstractSpinner::_ReservedAbstractSpinner9() {}
1686 void BAbstractSpinner::_ReservedAbstractSpinner8() {}
1687 void BAbstractSpinner::_ReservedAbstractSpinner7() {}
1688 void BAbstractSpinner::_ReservedAbstractSpinner6() {}
1689 void BAbstractSpinner::_ReservedAbstractSpinner5() {}
1690 void BAbstractSpinner::_ReservedAbstractSpinner4() {}
1691 void BAbstractSpinner::_ReservedAbstractSpinner3() {}
1692 void BAbstractSpinner::_ReservedAbstractSpinner2() {}
1693 void BAbstractSpinner::_ReservedAbstractSpinner1() {}
1694