xref: /haiku/src/kits/interface/AbstractSpinner.cpp (revision 6aff37d1c79e20748c683ae224bd629f88a5b0be)
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 	// draw the button
383 	be_control_look->DrawButtonFrame(this, rect, updateRect,
384 		tint_color(bgColor, frameTint), bgColor, 0, borders);
385 	be_control_look->DrawButtonBackground(this, rect, updateRect,
386 		tint_color(bgColor, bgTint), 0, borders);
387 
388 	switch (fParent->ButtonStyle()) {
389 		case SPINNER_BUTTON_HORIZONTAL_ARROWS:
390 		{
391 			int32 arrowDirection = fSpinnerDirection == SPINNER_INCREMENT
392 				? be_control_look->B_RIGHT_ARROW
393 				: be_control_look->B_LEFT_ARROW;
394 
395 			rect.InsetBy(0.0f, 1.0f);
396 			be_control_look->DrawArrowShape(this, rect, updateRect, bgColor,
397 				arrowDirection, 0, fgTint);
398 			break;
399 		}
400 
401 		case SPINNER_BUTTON_VERTICAL_ARROWS:
402 		{
403 			int32 arrowDirection = fSpinnerDirection == SPINNER_INCREMENT
404 				? be_control_look->B_UP_ARROW
405 				: be_control_look->B_DOWN_ARROW;
406 
407 			rect.InsetBy(0.0f, 1.0f);
408 			be_control_look->DrawArrowShape(this, rect, updateRect, bgColor,
409 				arrowDirection, 0, fgTint);
410 			break;
411 		}
412 
413 		default:
414 		case SPINNER_BUTTON_PLUS_MINUS:
415 		{
416 			BFont font;
417 			fParent->GetFont(&font);
418 			float inset = floorf(font.Size() / 4);
419 			rect.InsetBy(inset, inset);
420 
421 			if (rect.IntegerWidth() % 2 != 0)
422 				rect.right -= 1;
423 
424 			if (rect.IntegerHeight() % 2 != 0)
425 				rect.bottom -= 1;
426 
427 			SetHighColor(tint_color(bgColor, fgTint));
428 
429 			// draw the +/-
430 			float halfHeight = floorf(rect.Height() / 2);
431 			StrokeLine(BPoint(rect.left, rect.top + halfHeight),
432 				BPoint(rect.right, rect.top + halfHeight));
433 			if (fSpinnerDirection == SPINNER_INCREMENT) {
434 				float halfWidth = floorf(rect.Width() / 2);
435 				StrokeLine(BPoint(rect.left + halfWidth, rect.top),
436 					BPoint(rect.left + halfWidth, rect.bottom));
437 			}
438 		}
439 	}
440 }
441 
442 
443 void
444 SpinnerButton::MouseDown(BPoint where)
445 {
446 	if (fIsEnabled) {
447 		fIsMouseDown = true;
448 		Invalidate();
449 		fRepeatDelay = 100000;
450 		MouseDownThread<SpinnerButton>::TrackMouse(this,
451 			&SpinnerButton::_DoneTracking, &SpinnerButton::_Track);
452 	}
453 
454 	BView::MouseDown(where);
455 }
456 
457 
458 void
459 SpinnerButton::MouseMoved(BPoint where, uint32 transit,
460 	const BMessage* message)
461 {
462 	switch (transit) {
463 		case B_ENTERED_VIEW:
464 		case B_INSIDE_VIEW:
465 		{
466 			BPoint where;
467 			uint32 buttons;
468 			GetMouse(&where, &buttons);
469 			fIsMouseOver = Bounds().Contains(where) && buttons == 0;
470 			if (!fIsMouseDown)
471 				Invalidate();
472 
473 			break;
474 		}
475 
476 		case B_EXITED_VIEW:
477 		case B_OUTSIDE_VIEW:
478 			fIsMouseOver = false;
479 			MouseUp(Bounds().LeftTop());
480 			break;
481 	}
482 
483 	BView::MouseMoved(where, transit, message);
484 }
485 
486 
487 void
488 SpinnerButton::MouseUp(BPoint where)
489 {
490 	fIsMouseDown = false;
491 	Invalidate();
492 
493 	BView::MouseUp(where);
494 }
495 
496 
497 //	#pragma mark  - SpinnerButton private methods
498 
499 
500 void
501 SpinnerButton::_DoneTracking(BPoint where)
502 {
503 	if (fIsMouseDown || !Bounds().Contains(where))
504 		fIsMouseDown = false;
505 }
506 
507 
508 void
509 SpinnerButton::_Track(BPoint where, uint32)
510 {
511 	if (fParent == NULL || !Bounds().Contains(where)) {
512 		fIsMouseDown = false;
513 		return;
514 	}
515 	fIsMouseDown = true;
516 
517 	fSpinnerDirection == SPINNER_INCREMENT
518 		? fParent->Increment()
519 		: fParent->Decrement();
520 
521 	snooze(fRepeatDelay);
522 	fRepeatDelay = 10000;
523 }
524 
525 
526 //	#pragma mark - SpinnerTextView
527 
528 
529 SpinnerTextView::SpinnerTextView(BRect rect, BRect textRect)
530 	:
531 	BTextView(rect, "textview", textRect, B_FOLLOW_ALL,
532 		B_WILL_DRAW | B_NAVIGABLE),
533 	fParent(NULL)
534 {
535 	MakeResizable(true);
536 }
537 
538 
539 SpinnerTextView::~SpinnerTextView()
540 {
541 }
542 
543 
544 void
545 SpinnerTextView::AttachedToWindow()
546 {
547 	fParent = static_cast<BAbstractSpinner*>(Parent());
548 
549 	BTextView::AttachedToWindow();
550 }
551 
552 
553 void
554 SpinnerTextView::DetachedFromWindow()
555 {
556 	fParent = NULL;
557 
558 	BTextView::DetachedFromWindow();
559 }
560 
561 
562 void
563 SpinnerTextView::KeyDown(const char* bytes, int32 numBytes)
564 {
565 	if (fParent == NULL) {
566 		BTextView::KeyDown(bytes, numBytes);
567 		return;
568 	}
569 
570 	switch (bytes[0]) {
571 		case B_ENTER:
572 		case B_SPACE:
573 			fParent->SetValueFromText();
574 			break;
575 
576 		case B_TAB:
577 			fParent->KeyDown(bytes, numBytes);
578 			break;
579 
580 		case B_LEFT_ARROW:
581 			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
582 				&& (modifiers() & B_CONTROL_KEY) != 0) {
583 				// need to hold down control, otherwise can't move cursor
584 				fParent->Decrement();
585 			} else
586 				BTextView::KeyDown(bytes, numBytes);
587 			break;
588 
589 		case B_UP_ARROW:
590 			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
591 				fParent->Increment();
592 			else
593 				BTextView::KeyDown(bytes, numBytes);
594 			break;
595 
596 		case B_RIGHT_ARROW:
597 			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
598 				&& (modifiers() & B_CONTROL_KEY) != 0) {
599 				// need to hold down control, otherwise can't move cursor
600 				fParent->Increment();
601 			} else
602 				BTextView::KeyDown(bytes, numBytes);
603 			break;
604 
605 		case B_DOWN_ARROW:
606 			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
607 				fParent->Decrement();
608 			else
609 				BTextView::KeyDown(bytes, numBytes);
610 			break;
611 
612 		default:
613 			BTextView::KeyDown(bytes, numBytes);
614 			break;
615 	}
616 }
617 
618 
619 void
620 SpinnerTextView::MakeFocus(bool focus)
621 {
622 	BTextView::MakeFocus(focus);
623 
624 	if (fParent == NULL)
625 		return;
626 
627 	if (focus)
628 		SelectAll();
629 	else
630 		fParent->SetValueFromText();
631 
632 	fParent->_DrawTextView(fParent->Bounds());
633 }
634 
635 
636 //	#pragma mark - BAbstractSpinner::LabelLayoutItem
637 
638 
639 BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BAbstractSpinner* parent)
640 	:
641 	fParent(parent),
642 	fFrame()
643 {
644 }
645 
646 
647 BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BMessage* from)
648 	:
649 	BAbstractLayoutItem(from),
650 	fParent(NULL),
651 	fFrame()
652 {
653 	from->FindRect(kFrameField, &fFrame);
654 }
655 
656 
657 bool
658 BAbstractSpinner::LabelLayoutItem::IsVisible()
659 {
660 	return !fParent->IsHidden(fParent);
661 }
662 
663 
664 void
665 BAbstractSpinner::LabelLayoutItem::SetVisible(bool visible)
666 {
667 }
668 
669 
670 BRect
671 BAbstractSpinner::LabelLayoutItem::Frame()
672 {
673 	return fFrame;
674 }
675 
676 
677 void
678 BAbstractSpinner::LabelLayoutItem::SetFrame(BRect frame)
679 {
680 	fFrame = frame;
681 	fParent->_UpdateFrame();
682 }
683 
684 
685 void
686 BAbstractSpinner::LabelLayoutItem::SetParent(BAbstractSpinner* parent)
687 {
688 	fParent = parent;
689 }
690 
691 
692 BView*
693 BAbstractSpinner::LabelLayoutItem::View()
694 {
695 	return fParent;
696 }
697 
698 
699 BSize
700 BAbstractSpinner::LabelLayoutItem::BaseMinSize()
701 {
702 	fParent->_ValidateLayoutData();
703 
704 	if (fParent->Label() == NULL)
705 		return BSize(-1.0f, -1.0f);
706 
707 	return BSize(fParent->fLayoutData->label_width
708 			+ be_control_look->DefaultLabelSpacing(),
709 		fParent->fLayoutData->label_height);
710 }
711 
712 
713 BSize
714 BAbstractSpinner::LabelLayoutItem::BaseMaxSize()
715 {
716 	return BaseMinSize();
717 }
718 
719 
720 BSize
721 BAbstractSpinner::LabelLayoutItem::BasePreferredSize()
722 {
723 	return BaseMinSize();
724 }
725 
726 
727 BAlignment
728 BAbstractSpinner::LabelLayoutItem::BaseAlignment()
729 {
730 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
731 }
732 
733 
734 BRect
735 BAbstractSpinner::LabelLayoutItem::FrameInParent() const
736 {
737 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
738 }
739 
740 
741 status_t
742 BAbstractSpinner::LabelLayoutItem::Archive(BMessage* into, bool deep) const
743 {
744 	BArchiver archiver(into);
745 	status_t result = BAbstractLayoutItem::Archive(into, deep);
746 
747 	if (result == B_OK)
748 		result = into->AddRect(kFrameField, fFrame);
749 
750 	return archiver.Finish(result);
751 }
752 
753 
754 BArchivable*
755 BAbstractSpinner::LabelLayoutItem::Instantiate(BMessage* from)
756 {
757 	if (validate_instantiation(from, "BAbstractSpinner::LabelLayoutItem"))
758 		return new LabelLayoutItem(from);
759 
760 	return NULL;
761 }
762 
763 
764 //	#pragma mark - BAbstractSpinner::TextViewLayoutItem
765 
766 
767 BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BAbstractSpinner* parent)
768 	:
769 	fParent(parent),
770 	fFrame()
771 {
772 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
773 }
774 
775 
776 BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
777 	:
778 	BAbstractLayoutItem(from),
779 	fParent(NULL),
780 	fFrame()
781 {
782 	from->FindRect(kFrameField, &fFrame);
783 }
784 
785 
786 bool
787 BAbstractSpinner::TextViewLayoutItem::IsVisible()
788 {
789 	return !fParent->IsHidden(fParent);
790 }
791 
792 
793 void
794 BAbstractSpinner::TextViewLayoutItem::SetVisible(bool visible)
795 {
796 	// not allowed
797 }
798 
799 
800 BRect
801 BAbstractSpinner::TextViewLayoutItem::Frame()
802 {
803 	return fFrame;
804 }
805 
806 
807 void
808 BAbstractSpinner::TextViewLayoutItem::SetFrame(BRect frame)
809 {
810 	fFrame = frame;
811 	fParent->_UpdateFrame();
812 }
813 
814 
815 void
816 BAbstractSpinner::TextViewLayoutItem::SetParent(BAbstractSpinner* parent)
817 {
818 	fParent = parent;
819 }
820 
821 
822 BView*
823 BAbstractSpinner::TextViewLayoutItem::View()
824 {
825 	return fParent;
826 }
827 
828 
829 BSize
830 BAbstractSpinner::TextViewLayoutItem::BaseMinSize()
831 {
832 	fParent->_ValidateLayoutData();
833 
834 	BSize size(fParent->fLayoutData->text_view_width,
835 		fParent->fLayoutData->text_view_height);
836 
837 	return size;
838 }
839 
840 
841 BSize
842 BAbstractSpinner::TextViewLayoutItem::BaseMaxSize()
843 {
844 	return BaseMinSize();
845 }
846 
847 
848 BSize
849 BAbstractSpinner::TextViewLayoutItem::BasePreferredSize()
850 {
851 	return BaseMinSize();
852 }
853 
854 
855 BAlignment
856 BAbstractSpinner::TextViewLayoutItem::BaseAlignment()
857 {
858 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
859 }
860 
861 
862 BRect
863 BAbstractSpinner::TextViewLayoutItem::FrameInParent() const
864 {
865 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
866 }
867 
868 
869 status_t
870 BAbstractSpinner::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
871 {
872 	BArchiver archiver(into);
873 	status_t result = BAbstractLayoutItem::Archive(into, deep);
874 
875 	if (result == B_OK)
876 		result = into->AddRect(kFrameField, fFrame);
877 
878 	return archiver.Finish(result);
879 }
880 
881 
882 BArchivable*
883 BAbstractSpinner::TextViewLayoutItem::Instantiate(BMessage* from)
884 {
885 	if (validate_instantiation(from, "BAbstractSpinner::TextViewLayoutItem"))
886 		return new LabelLayoutItem(from);
887 
888 	return NULL;
889 }
890 
891 
892 //	#pragma mark - BAbstractSpinner
893 
894 
895 BAbstractSpinner::BAbstractSpinner(BRect frame, const char* name, const char* label,
896 	BMessage* message, uint32 resizingMode, uint32 flags)
897 	:
898 	BControl(frame, name, label, message, resizingMode,
899 		flags | B_WILL_DRAW | B_FRAME_EVENTS)
900 {
901 	_InitObject();
902 }
903 
904 
905 BAbstractSpinner::BAbstractSpinner(const char* name, const char* label, BMessage* message,
906 	uint32 flags)
907 	:
908 	BControl(name, label, message, flags | B_WILL_DRAW | B_FRAME_EVENTS)
909 {
910 	_InitObject();
911 }
912 
913 
914 BAbstractSpinner::BAbstractSpinner(BMessage* data)
915 	:
916 	BControl(data),
917 	fButtonStyle(SPINNER_BUTTON_PLUS_MINUS)
918 {
919 	_InitObject();
920 
921 	if (data->FindInt32("_align") != B_OK)
922 		fAlignment = B_ALIGN_LEFT;
923 
924 	if (data->FindInt32("_button_style") != B_OK)
925 		fButtonStyle = SPINNER_BUTTON_PLUS_MINUS;
926 
927 	if (data->FindInt32("_divider") != B_OK)
928 		fDivider = 0.0f;
929 }
930 
931 
932 BAbstractSpinner::~BAbstractSpinner()
933 {
934 	delete fLayoutData;
935 	fLayoutData = NULL;
936 }
937 
938 
939 BArchivable*
940 BAbstractSpinner::Instantiate(BMessage* data)
941 {
942 	// cannot instantiate an abstract spinner
943 	return NULL;
944 }
945 
946 
947 status_t
948 BAbstractSpinner::Archive(BMessage* data, bool deep) const
949 {
950 	status_t status = BControl::Archive(data, deep);
951 	data->AddString("class", "Spinner");
952 
953 	if (status == B_OK)
954 		status = data->AddInt32("_align", fAlignment);
955 
956 	if (status == B_OK)
957 		data->AddInt32("_button_style", fButtonStyle);
958 
959 	if (status == B_OK)
960 		status = data->AddFloat("_divider", fDivider);
961 
962 	return status;
963 }
964 
965 
966 status_t
967 BAbstractSpinner::GetSupportedSuites(BMessage* message)
968 {
969 	message->AddString("suites", "suite/vnd.Haiku-spinner");
970 
971 	BPropertyInfo prop_info(sProperties);
972 	message->AddFlat("messages", &prop_info);
973 
974 	return BView::GetSupportedSuites(message);
975 }
976 
977 
978 BHandler*
979 BAbstractSpinner::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
980 	int32 form, const char* property)
981 {
982 	return BView::ResolveSpecifier(message, index, specifier, form,
983 		property);
984 }
985 
986 
987 void
988 BAbstractSpinner::AttachedToWindow()
989 {
990 	if (!Messenger().IsValid())
991 		SetTarget(Window());
992 
993 	BControl::SetValue(Value());
994 		// sets the text and enables or disables the arrows
995 
996 	_UpdateTextViewColors(IsEnabled());
997 	fTextView->MakeEditable(IsEnabled());
998 
999 	BView::AttachedToWindow();
1000 }
1001 
1002 
1003 void
1004 BAbstractSpinner::Draw(BRect updateRect)
1005 {
1006 	_DrawLabel(updateRect);
1007 	_DrawTextView(updateRect);
1008 	fIncrement->Invalidate();
1009 	fDecrement->Invalidate();
1010 }
1011 
1012 
1013 void
1014 BAbstractSpinner::FrameResized(float width, float height)
1015 {
1016 	BView::FrameResized(width, height);
1017 
1018 	// TODO: this causes flickering still...
1019 
1020 	// changes in width
1021 
1022 	BRect bounds = Bounds();
1023 
1024 	if (bounds.Width() > fLayoutData->previous_width) {
1025 		// invalidate the region between the old and the new right border
1026 		BRect rect = bounds;
1027 		rect.left += fLayoutData->previous_width - kFrameMargin;
1028 		rect.right--;
1029 		Invalidate(rect);
1030 	} else if (bounds.Width() < fLayoutData->previous_width) {
1031 		// invalidate the region of the new right border
1032 		BRect rect = bounds;
1033 		rect.left = rect.right - kFrameMargin;
1034 		Invalidate(rect);
1035 	}
1036 
1037 	// changes in height
1038 
1039 	if (bounds.Height() > fLayoutData->previous_height) {
1040 		// invalidate the region between the old and the new bottom border
1041 		BRect rect = bounds;
1042 		rect.top += fLayoutData->previous_height - kFrameMargin;
1043 		rect.bottom--;
1044 		Invalidate(rect);
1045 		// invalidate label area
1046 		rect = bounds;
1047 		rect.right = fDivider;
1048 		Invalidate(rect);
1049 	} else if (bounds.Height() < fLayoutData->previous_height) {
1050 		// invalidate the region of the new bottom border
1051 		BRect rect = bounds;
1052 		rect.top = rect.bottom - kFrameMargin;
1053 		Invalidate(rect);
1054 		// invalidate label area
1055 		rect = bounds;
1056 		rect.right = fDivider;
1057 		Invalidate(rect);
1058 	}
1059 
1060 	fLayoutData->previous_width = bounds.Width();
1061 	fLayoutData->previous_height = bounds.Height();
1062 }
1063 
1064 
1065 void
1066 BAbstractSpinner::ValueChanged()
1067 {
1068 	// hook method - does nothing
1069 }
1070 
1071 
1072 void
1073 BAbstractSpinner::MessageReceived(BMessage* message)
1074 {
1075 	if (!IsEnabled() && message->what == B_COLORS_UPDATED)
1076 		_UpdateTextViewColors(false);
1077 
1078 	BControl::MessageReceived(message);
1079 }
1080 
1081 
1082 void
1083 BAbstractSpinner::MakeFocus(bool focus)
1084 {
1085 	fTextView->MakeFocus(focus);
1086 }
1087 
1088 
1089 void
1090 BAbstractSpinner::ResizeToPreferred()
1091 {
1092 	BView::ResizeToPreferred();
1093 
1094 	const char* label = Label();
1095 	if (label != NULL) {
1096 		fDivider = ceilf(StringWidth(label))
1097 			+ be_control_look->DefaultLabelSpacing();
1098 	} else
1099 		fDivider = 0.0f;
1100 
1101 	_LayoutTextView();
1102 }
1103 
1104 
1105 void
1106 BAbstractSpinner::SetFlags(uint32 flags)
1107 {
1108 	// If the textview is navigable, set it to not navigable if needed,
1109 	// else if it is not navigable, set it to navigable if needed
1110 	if (fTextView->Flags() & B_NAVIGABLE) {
1111 		if (!(flags & B_NAVIGABLE))
1112 			fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
1113 	} else {
1114 		if (flags & B_NAVIGABLE)
1115 			fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
1116 	}
1117 
1118 	// Don't make this one navigable
1119 	flags &= ~B_NAVIGABLE;
1120 
1121 	BView::SetFlags(flags);
1122 }
1123 
1124 
1125 void
1126 BAbstractSpinner::WindowActivated(bool active)
1127 {
1128 	_DrawTextView(fTextView->Frame());
1129 }
1130 
1131 
1132 void
1133 BAbstractSpinner::SetAlignment(alignment align)
1134 {
1135 	fAlignment = align;
1136 }
1137 
1138 
1139 void
1140 BAbstractSpinner::SetButtonStyle(spinner_button_style buttonStyle)
1141 {
1142 	fButtonStyle = buttonStyle;
1143 }
1144 
1145 
1146 void
1147 BAbstractSpinner::SetDivider(float position)
1148 {
1149 	position = roundf(position);
1150 
1151 	float delta = fDivider - position;
1152 	if (delta == 0.0f)
1153 		return;
1154 
1155 	fDivider = position;
1156 
1157 	if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
1158 		// We should never get here, since layout support means, we also
1159 		// layout the divider, and don't use this method at all.
1160 		Relayout();
1161 	} else {
1162 		_LayoutTextView();
1163 		Invalidate();
1164 	}
1165 }
1166 
1167 
1168 void
1169 BAbstractSpinner::SetEnabled(bool enable)
1170 {
1171 	if (IsEnabled() == enable)
1172 		return;
1173 
1174 	BControl::SetEnabled(enable);
1175 
1176 	fTextView->MakeEditable(enable);
1177 	if (enable)
1178 		fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
1179 	else
1180 		fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
1181 
1182 	_UpdateTextViewColors(enable);
1183 	fTextView->Invalidate();
1184 
1185 	_LayoutTextView();
1186 	Invalidate();
1187 	if (Window() != NULL)
1188 		Window()->UpdateIfNeeded();
1189 }
1190 
1191 
1192 void
1193 BAbstractSpinner::SetLabel(const char* label)
1194 {
1195 	BControl::SetLabel(label);
1196 
1197 	if (Window() != NULL)
1198 		Window()->UpdateIfNeeded();
1199 }
1200 
1201 
1202 bool
1203 BAbstractSpinner::IsDecrementEnabled() const
1204 {
1205 	return fDecrement->IsEnabled();
1206 }
1207 
1208 
1209 void
1210 BAbstractSpinner::SetDecrementEnabled(bool enable)
1211 {
1212 	if (IsDecrementEnabled() == enable)
1213 		return;
1214 
1215 	fDecrement->SetEnabled(enable);
1216 	fDecrement->Invalidate();
1217 }
1218 
1219 
1220 bool
1221 BAbstractSpinner::IsIncrementEnabled() const
1222 {
1223 	return fIncrement->IsEnabled();
1224 }
1225 
1226 
1227 void
1228 BAbstractSpinner::SetIncrementEnabled(bool enable)
1229 {
1230 	if (IsIncrementEnabled() == enable)
1231 		return;
1232 
1233 	fIncrement->SetEnabled(enable);
1234 	fIncrement->Invalidate();
1235 }
1236 
1237 
1238 BSize
1239 BAbstractSpinner::MinSize()
1240 {
1241 	_ValidateLayoutData();
1242 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
1243 }
1244 
1245 
1246 BSize
1247 BAbstractSpinner::MaxSize()
1248 {
1249 	_ValidateLayoutData();
1250 
1251 	BSize max = fLayoutData->min;
1252 	max.width = B_SIZE_UNLIMITED;
1253 
1254 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
1255 }
1256 
1257 
1258 BSize
1259 BAbstractSpinner::PreferredSize()
1260 {
1261 	_ValidateLayoutData();
1262 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
1263 		fLayoutData->min);
1264 }
1265 
1266 
1267 BAlignment
1268 BAbstractSpinner::LayoutAlignment()
1269 {
1270 	_ValidateLayoutData();
1271 	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
1272 		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_CENTER));
1273 }
1274 
1275 
1276 BLayoutItem*
1277 BAbstractSpinner::CreateLabelLayoutItem()
1278 {
1279 	if (fLayoutData->label_layout_item == NULL)
1280 		fLayoutData->label_layout_item = new LabelLayoutItem(this);
1281 
1282 	return fLayoutData->label_layout_item;
1283 }
1284 
1285 
1286 BLayoutItem*
1287 BAbstractSpinner::CreateTextViewLayoutItem()
1288 {
1289 	if (fLayoutData->text_view_layout_item == NULL)
1290 		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
1291 
1292 	return fLayoutData->text_view_layout_item;
1293 }
1294 
1295 
1296 BTextView*
1297 BAbstractSpinner::TextView() const
1298 {
1299 	return dynamic_cast<BTextView*>(fTextView);
1300 }
1301 
1302 
1303 //	#pragma mark - BAbstractSpinner protected methods
1304 
1305 
1306 status_t
1307 BAbstractSpinner::AllArchived(BMessage* into) const
1308 {
1309 	status_t result;
1310 	if ((result = BControl::AllArchived(into)) != B_OK)
1311 		return result;
1312 
1313 	BArchiver archiver(into);
1314 
1315 	BArchivable* textViewItem = fLayoutData->text_view_layout_item;
1316 	if (archiver.IsArchived(textViewItem))
1317 		result = archiver.AddArchivable(kTextViewItemField, textViewItem);
1318 
1319 	if (result != B_OK)
1320 		return result;
1321 
1322 	BArchivable* labelBarItem = fLayoutData->label_layout_item;
1323 	if (archiver.IsArchived(labelBarItem))
1324 		result = archiver.AddArchivable(kLabelItemField, labelBarItem);
1325 
1326 	return result;
1327 }
1328 
1329 
1330 status_t
1331 BAbstractSpinner::AllUnarchived(const BMessage* from)
1332 {
1333 	BUnarchiver unarchiver(from);
1334 
1335 	status_t result = B_OK;
1336 	if ((result = BControl::AllUnarchived(from)) != B_OK)
1337 		return result;
1338 
1339 	if (unarchiver.IsInstantiated(kTextViewItemField)) {
1340 		TextViewLayoutItem*& textViewItem
1341 			= fLayoutData->text_view_layout_item;
1342 		result = unarchiver.FindObject(kTextViewItemField,
1343 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, textViewItem);
1344 
1345 		if (result == B_OK)
1346 			textViewItem->SetParent(this);
1347 		else
1348 			return result;
1349 	}
1350 
1351 	if (unarchiver.IsInstantiated(kLabelItemField)) {
1352 		LabelLayoutItem*& labelItem = fLayoutData->label_layout_item;
1353 		result = unarchiver.FindObject(kLabelItemField,
1354 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, labelItem);
1355 
1356 		if (result == B_OK)
1357 			labelItem->SetParent(this);
1358 	}
1359 
1360 	return result;
1361 }
1362 
1363 
1364 void
1365 BAbstractSpinner::DoLayout()
1366 {
1367 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
1368 		return;
1369 
1370 	if (GetLayout()) {
1371 		BControl::DoLayout();
1372 		return;
1373 	}
1374 
1375 	_ValidateLayoutData();
1376 
1377 	BSize size(Bounds().Size());
1378 	if (size.width < fLayoutData->min.width)
1379 		size.width = fLayoutData->min.width;
1380 
1381 	if (size.height < fLayoutData->min.height)
1382 		size.height = fLayoutData->min.height;
1383 
1384 	float divider = 0;
1385 	if (fLayoutData->label_layout_item != NULL
1386 		&& fLayoutData->text_view_layout_item != NULL
1387 		&& fLayoutData->label_layout_item->Frame().IsValid()
1388 		&& fLayoutData->text_view_layout_item->Frame().IsValid()) {
1389 		divider = fLayoutData->text_view_layout_item->Frame().left
1390 			- fLayoutData->label_layout_item->Frame().left;
1391 	} else if (fLayoutData->label_width > 0) {
1392 		divider = fLayoutData->label_width
1393 			+ be_control_look->DefaultLabelSpacing();
1394 	}
1395 	fDivider = divider;
1396 
1397 	BRect dirty(fTextView->Frame());
1398 	_LayoutTextView();
1399 
1400 	// invalidate dirty region
1401 	dirty = dirty | fTextView->Frame();
1402 	dirty = dirty | fIncrement->Frame();
1403 	dirty = dirty | fDecrement->Frame();
1404 
1405 	Invalidate(dirty);
1406 }
1407 
1408 
1409 void
1410 BAbstractSpinner::LayoutInvalidated(bool descendants)
1411 {
1412 	if (fLayoutData != NULL)
1413 		fLayoutData->valid = false;
1414 }
1415 
1416 
1417 //	#pragma mark - BAbstractSpinner private methods
1418 
1419 
1420 void
1421 BAbstractSpinner::_DrawLabel(BRect updateRect)
1422 {
1423 	BRect rect(Bounds());
1424 	rect.right = fDivider;
1425 	if (!rect.IsValid() || !rect.Intersects(updateRect))
1426 		return;
1427 
1428 	_ValidateLayoutData();
1429 
1430 	const char* label = Label();
1431 	if (label == NULL)
1432 		return;
1433 
1434 	// horizontal position
1435 	float x;
1436 	switch (fAlignment) {
1437 		case B_ALIGN_RIGHT:
1438 			x = fDivider - fLayoutData->label_width - 3.0f;
1439 			break;
1440 
1441 		case B_ALIGN_CENTER:
1442 			x = fDivider - roundf(fLayoutData->label_width / 2.0f);
1443 			break;
1444 
1445 		default:
1446 			x = 0.0f;
1447 			break;
1448 	}
1449 
1450 	// vertical position
1451 	font_height& fontHeight = fLayoutData->font_info;
1452 	float y = rect.top
1453 		+ roundf((rect.Height() + 1.0f - fontHeight.ascent
1454 			- fontHeight.descent) / 2.0f)
1455 		+ fontHeight.ascent;
1456 
1457 	uint32 flags = be_control_look->Flags(this);
1458 
1459 	// erase the is control flag before drawing the label so that the label
1460 	// will get drawn using B_PANEL_TEXT_COLOR.
1461 	flags &= ~BControlLook::B_IS_CONTROL;
1462 
1463 	be_control_look->DrawLabel(this, label, LowColor(), flags, BPoint(x, y));
1464 }
1465 
1466 
1467 void
1468 BAbstractSpinner::_DrawTextView(BRect updateRect)
1469 {
1470 	BRect rect = fTextView->Frame();
1471 	rect.InsetBy(-kFrameMargin, -kFrameMargin);
1472 	if (!rect.IsValid() || !rect.Intersects(updateRect))
1473 		return;
1474 
1475 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1476 	uint32 flags = 0;
1477 	if (!IsEnabled())
1478 		flags |= BControlLook::B_DISABLED;
1479 
1480 	if (fTextView->IsFocus() && Window()->IsActive())
1481 		flags |= BControlLook::B_FOCUSED;
1482 
1483 	be_control_look->DrawTextControlBorder(this, rect, updateRect, base,
1484 		flags);
1485 }
1486 
1487 
1488 void
1489 BAbstractSpinner::_InitObject()
1490 {
1491 	fAlignment = B_ALIGN_LEFT;
1492 	fButtonStyle = SPINNER_BUTTON_PLUS_MINUS;
1493 
1494 	if (Label() != NULL) {
1495 		fDivider = StringWidth(Label())
1496 			+ be_control_look->DefaultLabelSpacing();
1497 	} else
1498 		fDivider = 0.0f;
1499 
1500 	BControl::SetEnabled(true);
1501 	BControl::SetValue(0);
1502 
1503 	BRect rect(Bounds());
1504 	fLayoutData = new LayoutData(rect.Width(), rect.Height());
1505 
1506 	rect.left = fDivider;
1507 	rect.InsetBy(kFrameMargin, kFrameMargin);
1508 	rect.right -= rect.Height() * 2 + kFrameMargin * 2 + 1.0f;
1509 	BRect textRect(rect.OffsetToCopy(B_ORIGIN));
1510 
1511 	fTextView = new SpinnerTextView(rect, textRect);
1512 	AddChild(fTextView);
1513 
1514 	rect.InsetBy(0.0f, -kFrameMargin);
1515 
1516 	rect.left = rect.right + kFrameMargin * 2;
1517 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1518 
1519 	fDecrement = new SpinnerButton(rect, "decrement", SPINNER_DECREMENT);
1520 	AddChild(fDecrement);
1521 
1522 	rect.left = rect.right + 1.0f;
1523 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1524 
1525 	fIncrement = new SpinnerButton(rect, "increment", SPINNER_INCREMENT);
1526 	AddChild(fIncrement);
1527 
1528 	uint32 navigableFlags = Flags() & B_NAVIGABLE;
1529 	if (navigableFlags != 0)
1530 		BControl::SetFlags(Flags() & ~B_NAVIGABLE);
1531 }
1532 
1533 
1534 void
1535 BAbstractSpinner::_LayoutTextView()
1536 {
1537 	BRect rect;
1538 	if (fLayoutData->text_view_layout_item != NULL) {
1539 		rect = fLayoutData->text_view_layout_item->FrameInParent();
1540 	} else {
1541 		rect = Bounds();
1542 		rect.left = fDivider;
1543 	}
1544 	rect.InsetBy(kFrameMargin, kFrameMargin);
1545 	rect.right -= rect.Height() * 2 + kFrameMargin * 2 + 1.0f;
1546 
1547 	fTextView->MoveTo(rect.left, rect.top);
1548 	fTextView->ResizeTo(rect.Width(), rect.Height());
1549 	fTextView->SetTextRect(rect.OffsetToCopy(B_ORIGIN));
1550 
1551 	rect.InsetBy(0.0f, -kFrameMargin);
1552 
1553 	rect.left = rect.right + kFrameMargin * 2;
1554 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1555 
1556 	fDecrement->ResizeTo(rect.Width(), rect.Height());
1557 	fDecrement->MoveTo(rect.LeftTop());
1558 
1559 	rect.left = rect.right + 1.0f;
1560 	rect.right = rect.left + rect.Height() - kFrameMargin * 2;
1561 
1562 	fIncrement->ResizeTo(rect.Width(), rect.Height());
1563 	fIncrement->MoveTo(rect.LeftTop());
1564 }
1565 
1566 
1567 void
1568 BAbstractSpinner::_UpdateFrame()
1569 {
1570 	if (fLayoutData->label_layout_item == NULL
1571 		|| fLayoutData->text_view_layout_item == NULL) {
1572 		return;
1573 	}
1574 
1575 	BRect labelFrame = fLayoutData->label_layout_item->Frame();
1576 	BRect textViewFrame = fLayoutData->text_view_layout_item->Frame();
1577 
1578 	if (!labelFrame.IsValid() || !textViewFrame.IsValid())
1579 		return;
1580 
1581 	// update divider
1582 	fDivider = textViewFrame.left - labelFrame.left;
1583 
1584 	BRect frame = textViewFrame | labelFrame;
1585 	MoveTo(frame.left, frame.top);
1586 	BSize oldSize = Bounds().Size();
1587 	ResizeTo(frame.Width(), frame.Height());
1588 	BSize newSize = Bounds().Size();
1589 
1590 	// If the size changes, ResizeTo() will trigger a relayout, otherwise
1591 	// we need to do that explicitly.
1592 	if (newSize != oldSize)
1593 		Relayout();
1594 }
1595 
1596 
1597 void
1598 BAbstractSpinner::_UpdateTextViewColors(bool enable)
1599 {
1600 	// Mimick BTextControl's appearance.
1601 	rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
1602 
1603 	if (enable) {
1604 		fTextView->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
1605 		fTextView->SetLowUIColor(ViewUIColor());
1606 	} else {
1607 		rgb_color color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
1608 		color = disable_color(ViewColor(), color);
1609 		textColor = disable_color(textColor, ViewColor());
1610 
1611 		fTextView->SetViewColor(color);
1612 		fTextView->SetLowColor(color);
1613 	}
1614 
1615 	BFont font;
1616 	fTextView->GetFontAndColor(0, &font);
1617 	fTextView->SetFontAndColor(&font, B_FONT_ALL, &textColor);
1618 }
1619 
1620 
1621 void
1622 BAbstractSpinner::_ValidateLayoutData()
1623 {
1624 	if (fLayoutData->valid)
1625 		return;
1626 
1627 	font_height& fontHeight = fLayoutData->font_info;
1628 	GetFontHeight(&fontHeight);
1629 
1630 	if (Label() != NULL) {
1631 		fLayoutData->label_width = StringWidth(Label());
1632 		fLayoutData->label_height = ceilf(fontHeight.ascent
1633 			+ fontHeight.descent + fontHeight.leading);
1634 	} else {
1635 		fLayoutData->label_width = 0;
1636 		fLayoutData->label_height = 0;
1637 	}
1638 
1639 	float divider = 0;
1640 	if (fLayoutData->label_width > 0) {
1641 		divider = ceilf(fLayoutData->label_width
1642 			+ be_control_look->DefaultLabelSpacing());
1643 	}
1644 
1645 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
1646 		divider = std::max(divider, fDivider);
1647 
1648 	float minTextWidth = fTextView->StringWidth("99999");
1649 
1650 	float textViewHeight = fTextView->LineHeight(0) + kFrameMargin * 2;
1651 	float textViewWidth = minTextWidth + textViewHeight * 2;
1652 
1653 	fLayoutData->text_view_width = textViewWidth;
1654 	fLayoutData->text_view_height = textViewHeight;
1655 
1656 	BSize min(textViewWidth, textViewHeight);
1657 	if (divider > 0.0f)
1658 		min.width += divider;
1659 
1660 	if (fLayoutData->label_height > min.height)
1661 		min.height = fLayoutData->label_height;
1662 
1663 	fLayoutData->min = min;
1664 	fLayoutData->valid = true;
1665 
1666 	ResetLayoutInvalidation();
1667 }
1668 
1669 
1670 // FBC padding
1671 
1672 void BAbstractSpinner::_ReservedAbstractSpinner20() {}
1673 void BAbstractSpinner::_ReservedAbstractSpinner19() {}
1674 void BAbstractSpinner::_ReservedAbstractSpinner18() {}
1675 void BAbstractSpinner::_ReservedAbstractSpinner17() {}
1676 void BAbstractSpinner::_ReservedAbstractSpinner16() {}
1677 void BAbstractSpinner::_ReservedAbstractSpinner15() {}
1678 void BAbstractSpinner::_ReservedAbstractSpinner14() {}
1679 void BAbstractSpinner::_ReservedAbstractSpinner13() {}
1680 void BAbstractSpinner::_ReservedAbstractSpinner12() {}
1681 void BAbstractSpinner::_ReservedAbstractSpinner11() {}
1682 void BAbstractSpinner::_ReservedAbstractSpinner10() {}
1683 void BAbstractSpinner::_ReservedAbstractSpinner9() {}
1684 void BAbstractSpinner::_ReservedAbstractSpinner8() {}
1685 void BAbstractSpinner::_ReservedAbstractSpinner7() {}
1686 void BAbstractSpinner::_ReservedAbstractSpinner6() {}
1687 void BAbstractSpinner::_ReservedAbstractSpinner5() {}
1688 void BAbstractSpinner::_ReservedAbstractSpinner4() {}
1689 void BAbstractSpinner::_ReservedAbstractSpinner3() {}
1690 void BAbstractSpinner::_ReservedAbstractSpinner2() {}
1691 void BAbstractSpinner::_ReservedAbstractSpinner1() {}
1692