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