xref: /haiku/src/kits/interface/AbstractSpinner.cpp (revision 632e56d8e514ba6ac41f582ce580e51a3cd8922e)
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 }
536 
537 
538 SpinnerTextView::~SpinnerTextView()
539 {
540 }
541 
542 
543 void
544 SpinnerTextView::AttachedToWindow()
545 {
546 	fParent = static_cast<BAbstractSpinner*>(Parent());
547 
548 	BTextView::AttachedToWindow();
549 }
550 
551 
552 void
553 SpinnerTextView::DetachedFromWindow()
554 {
555 	fParent = NULL;
556 
557 	BTextView::DetachedFromWindow();
558 }
559 
560 
561 void
562 SpinnerTextView::KeyDown(const char* bytes, int32 numBytes)
563 {
564 	if (fParent == NULL) {
565 		BTextView::KeyDown(bytes, numBytes);
566 		return;
567 	}
568 
569 	switch (bytes[0]) {
570 		case B_ENTER:
571 		case B_SPACE:
572 			fParent->SetValueFromText();
573 			break;
574 
575 		case B_TAB:
576 			fParent->KeyDown(bytes, numBytes);
577 			break;
578 
579 		case B_LEFT_ARROW:
580 			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
581 				&& (modifiers() & B_CONTROL_KEY) != 0) {
582 				// need to hold down control, otherwise can't move cursor
583 				fParent->Decrement();
584 			} else
585 				BTextView::KeyDown(bytes, numBytes);
586 			break;
587 
588 		case B_UP_ARROW:
589 			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
590 				fParent->Increment();
591 			else
592 				BTextView::KeyDown(bytes, numBytes);
593 			break;
594 
595 		case B_RIGHT_ARROW:
596 			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
597 				&& (modifiers() & B_CONTROL_KEY) != 0) {
598 				// need to hold down control, otherwise can't move cursor
599 				fParent->Increment();
600 			} else
601 				BTextView::KeyDown(bytes, numBytes);
602 			break;
603 
604 		case B_DOWN_ARROW:
605 			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
606 				fParent->Decrement();
607 			else
608 				BTextView::KeyDown(bytes, numBytes);
609 			break;
610 
611 		default:
612 			BTextView::KeyDown(bytes, numBytes);
613 			break;
614 	}
615 }
616 
617 
618 void
619 SpinnerTextView::MakeFocus(bool focus)
620 {
621 	BTextView::MakeFocus(focus);
622 
623 	if (fParent == NULL)
624 		return;
625 
626 	if (focus)
627 		SelectAll();
628 	else
629 		fParent->SetValueFromText();
630 
631 	fParent->_DrawTextView(fParent->Bounds());
632 }
633 
634 
635 //	#pragma mark - BAbstractSpinner::LabelLayoutItem
636 
637 
638 BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BAbstractSpinner* parent)
639 	:
640 	fParent(parent),
641 	fFrame()
642 {
643 }
644 
645 
646 BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BMessage* from)
647 	:
648 	BAbstractLayoutItem(from),
649 	fParent(NULL),
650 	fFrame()
651 {
652 	from->FindRect(kFrameField, &fFrame);
653 }
654 
655 
656 bool
657 BAbstractSpinner::LabelLayoutItem::IsVisible()
658 {
659 	return !fParent->IsHidden(fParent);
660 }
661 
662 
663 void
664 BAbstractSpinner::LabelLayoutItem::SetVisible(bool visible)
665 {
666 }
667 
668 
669 BRect
670 BAbstractSpinner::LabelLayoutItem::Frame()
671 {
672 	return fFrame;
673 }
674 
675 
676 void
677 BAbstractSpinner::LabelLayoutItem::SetFrame(BRect frame)
678 {
679 	fFrame = frame;
680 	fParent->_UpdateFrame();
681 }
682 
683 
684 void
685 BAbstractSpinner::LabelLayoutItem::SetParent(BAbstractSpinner* parent)
686 {
687 	fParent = parent;
688 }
689 
690 
691 BView*
692 BAbstractSpinner::LabelLayoutItem::View()
693 {
694 	return fParent;
695 }
696 
697 
698 BSize
699 BAbstractSpinner::LabelLayoutItem::BaseMinSize()
700 {
701 	fParent->_ValidateLayoutData();
702 
703 	if (fParent->Label() == NULL)
704 		return BSize(-1.0f, -1.0f);
705 
706 	return BSize(fParent->fLayoutData->label_width
707 			+ be_control_look->DefaultLabelSpacing(),
708 		fParent->fLayoutData->label_height);
709 }
710 
711 
712 BSize
713 BAbstractSpinner::LabelLayoutItem::BaseMaxSize()
714 {
715 	return BaseMinSize();
716 }
717 
718 
719 BSize
720 BAbstractSpinner::LabelLayoutItem::BasePreferredSize()
721 {
722 	return BaseMinSize();
723 }
724 
725 
726 BAlignment
727 BAbstractSpinner::LabelLayoutItem::BaseAlignment()
728 {
729 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
730 }
731 
732 
733 BRect
734 BAbstractSpinner::LabelLayoutItem::FrameInParent() const
735 {
736 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
737 }
738 
739 
740 status_t
741 BAbstractSpinner::LabelLayoutItem::Archive(BMessage* into, bool deep) const
742 {
743 	BArchiver archiver(into);
744 	status_t result = BAbstractLayoutItem::Archive(into, deep);
745 
746 	if (result == B_OK)
747 		result = into->AddRect(kFrameField, fFrame);
748 
749 	return archiver.Finish(result);
750 }
751 
752 
753 BArchivable*
754 BAbstractSpinner::LabelLayoutItem::Instantiate(BMessage* from)
755 {
756 	if (validate_instantiation(from, "BAbstractSpinner::LabelLayoutItem"))
757 		return new LabelLayoutItem(from);
758 
759 	return NULL;
760 }
761 
762 
763 //	#pragma mark - BAbstractSpinner::TextViewLayoutItem
764 
765 
766 BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BAbstractSpinner* parent)
767 	:
768 	fParent(parent),
769 	fFrame()
770 {
771 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
772 }
773 
774 
775 BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
776 	:
777 	BAbstractLayoutItem(from),
778 	fParent(NULL),
779 	fFrame()
780 {
781 	from->FindRect(kFrameField, &fFrame);
782 }
783 
784 
785 bool
786 BAbstractSpinner::TextViewLayoutItem::IsVisible()
787 {
788 	return !fParent->IsHidden(fParent);
789 }
790 
791 
792 void
793 BAbstractSpinner::TextViewLayoutItem::SetVisible(bool visible)
794 {
795 	// not allowed
796 }
797 
798 
799 BRect
800 BAbstractSpinner::TextViewLayoutItem::Frame()
801 {
802 	return fFrame;
803 }
804 
805 
806 void
807 BAbstractSpinner::TextViewLayoutItem::SetFrame(BRect frame)
808 {
809 	fFrame = frame;
810 	fParent->_UpdateFrame();
811 }
812 
813 
814 void
815 BAbstractSpinner::TextViewLayoutItem::SetParent(BAbstractSpinner* parent)
816 {
817 	fParent = parent;
818 }
819 
820 
821 BView*
822 BAbstractSpinner::TextViewLayoutItem::View()
823 {
824 	return fParent;
825 }
826 
827 
828 BSize
829 BAbstractSpinner::TextViewLayoutItem::BaseMinSize()
830 {
831 	fParent->_ValidateLayoutData();
832 
833 	BSize size(fParent->fLayoutData->text_view_width,
834 		fParent->fLayoutData->text_view_height);
835 
836 	return size;
837 }
838 
839 
840 BSize
841 BAbstractSpinner::TextViewLayoutItem::BaseMaxSize()
842 {
843 	return BaseMinSize();
844 }
845 
846 
847 BSize
848 BAbstractSpinner::TextViewLayoutItem::BasePreferredSize()
849 {
850 	return BaseMinSize();
851 }
852 
853 
854 BAlignment
855 BAbstractSpinner::TextViewLayoutItem::BaseAlignment()
856 {
857 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
858 }
859 
860 
861 BRect
862 BAbstractSpinner::TextViewLayoutItem::FrameInParent() const
863 {
864 	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
865 }
866 
867 
868 status_t
869 BAbstractSpinner::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
870 {
871 	BArchiver archiver(into);
872 	status_t result = BAbstractLayoutItem::Archive(into, deep);
873 
874 	if (result == B_OK)
875 		result = into->AddRect(kFrameField, fFrame);
876 
877 	return archiver.Finish(result);
878 }
879 
880 
881 BArchivable*
882 BAbstractSpinner::TextViewLayoutItem::Instantiate(BMessage* from)
883 {
884 	if (validate_instantiation(from, "BAbstractSpinner::TextViewLayoutItem"))
885 		return new LabelLayoutItem(from);
886 
887 	return NULL;
888 }
889 
890 
891 //	#pragma mark - BAbstractSpinner
892 
893 
894 BAbstractSpinner::BAbstractSpinner(BRect frame, const char* name, const char* label,
895 	BMessage* message, uint32 resizingMode, uint32 flags)
896 	:
897 	BControl(frame, name, label, message, resizingMode,
898 		flags | B_WILL_DRAW | B_FRAME_EVENTS)
899 {
900 	_InitObject();
901 }
902 
903 
904 BAbstractSpinner::BAbstractSpinner(const char* name, const char* label, BMessage* message,
905 	uint32 flags)
906 	:
907 	BControl(name, label, message, flags | B_WILL_DRAW | B_FRAME_EVENTS)
908 {
909 	_InitObject();
910 }
911 
912 
913 BAbstractSpinner::BAbstractSpinner(BMessage* data)
914 	:
915 	BControl(data),
916 	fButtonStyle(SPINNER_BUTTON_PLUS_MINUS)
917 {
918 	_InitObject();
919 
920 	if (data->FindInt32("_align") != B_OK)
921 		fAlignment = B_ALIGN_LEFT;
922 
923 	if (data->FindInt32("_button_style") != B_OK)
924 		fButtonStyle = SPINNER_BUTTON_PLUS_MINUS;
925 
926 	if (data->FindInt32("_divider") != B_OK)
927 		fDivider = 0.0f;
928 }
929 
930 
931 BAbstractSpinner::~BAbstractSpinner()
932 {
933 	delete fLayoutData;
934 	fLayoutData = NULL;
935 }
936 
937 
938 BArchivable*
939 BAbstractSpinner::Instantiate(BMessage* data)
940 {
941 	// cannot instantiate an abstract spinner
942 	return NULL;
943 }
944 
945 
946 status_t
947 BAbstractSpinner::Archive(BMessage* data, bool deep) const
948 {
949 	status_t status = BControl::Archive(data, deep);
950 	data->AddString("class", "Spinner");
951 
952 	if (status == B_OK)
953 		status = data->AddInt32("_align", fAlignment);
954 
955 	if (status == B_OK)
956 		data->AddInt32("_button_style", fButtonStyle);
957 
958 	if (status == B_OK)
959 		status = data->AddFloat("_divider", fDivider);
960 
961 	return status;
962 }
963 
964 
965 status_t
966 BAbstractSpinner::GetSupportedSuites(BMessage* message)
967 {
968 	message->AddString("suites", "suite/vnd.Haiku-spinner");
969 
970 	BPropertyInfo prop_info(sProperties);
971 	message->AddFlat("messages", &prop_info);
972 
973 	return BView::GetSupportedSuites(message);
974 }
975 
976 
977 BHandler*
978 BAbstractSpinner::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
979 	int32 form, const char* property)
980 {
981 	return BView::ResolveSpecifier(message, index, specifier, form,
982 		property);
983 }
984 
985 
986 void
987 BAbstractSpinner::AttachedToWindow()
988 {
989 	if (!Messenger().IsValid())
990 		SetTarget(Window());
991 
992 	BControl::SetValue(Value());
993 		// sets the text and enables or disables the arrows
994 
995 	_UpdateTextViewColors(IsEnabled());
996 	fTextView->MakeEditable(IsEnabled());
997 
998 	BView::AttachedToWindow();
999 }
1000 
1001 
1002 void
1003 BAbstractSpinner::Draw(BRect updateRect)
1004 {
1005 	_DrawLabel(updateRect);
1006 	_DrawTextView(updateRect);
1007 	fIncrement->Invalidate();
1008 	fDecrement->Invalidate();
1009 }
1010 
1011 
1012 void
1013 BAbstractSpinner::FrameResized(float width, float height)
1014 {
1015 	BView::FrameResized(width, height);
1016 
1017 	// TODO: this causes flickering still...
1018 
1019 	// changes in width
1020 
1021 	BRect bounds = Bounds();
1022 
1023 	if (bounds.Width() > fLayoutData->previous_width) {
1024 		// invalidate the region between the old and the new right border
1025 		BRect rect = bounds;
1026 		rect.left += fLayoutData->previous_width - kFrameMargin;
1027 		rect.right--;
1028 		Invalidate(rect);
1029 	} else if (bounds.Width() < fLayoutData->previous_width) {
1030 		// invalidate the region of the new right border
1031 		BRect rect = bounds;
1032 		rect.left = rect.right - kFrameMargin;
1033 		Invalidate(rect);
1034 	}
1035 
1036 	// changes in height
1037 
1038 	if (bounds.Height() > fLayoutData->previous_height) {
1039 		// invalidate the region between the old and the new bottom border
1040 		BRect rect = bounds;
1041 		rect.top += fLayoutData->previous_height - kFrameMargin;
1042 		rect.bottom--;
1043 		Invalidate(rect);
1044 		// invalidate label area
1045 		rect = bounds;
1046 		rect.right = fDivider;
1047 		Invalidate(rect);
1048 	} else if (bounds.Height() < fLayoutData->previous_height) {
1049 		// invalidate the region of the new bottom border
1050 		BRect rect = bounds;
1051 		rect.top = rect.bottom - kFrameMargin;
1052 		Invalidate(rect);
1053 		// invalidate label area
1054 		rect = bounds;
1055 		rect.right = fDivider;
1056 		Invalidate(rect);
1057 	}
1058 
1059 	fLayoutData->previous_width = bounds.Width();
1060 	fLayoutData->previous_height = bounds.Height();
1061 }
1062 
1063 
1064 void
1065 BAbstractSpinner::ValueChanged()
1066 {
1067 	// hook method - does nothing
1068 }
1069 
1070 
1071 void
1072 BAbstractSpinner::MessageReceived(BMessage* message)
1073 {
1074 	if (!IsEnabled() && message->what == B_COLORS_UPDATED)
1075 		_UpdateTextViewColors(false);
1076 
1077 	BControl::MessageReceived(message);
1078 }
1079 
1080 
1081 void
1082 BAbstractSpinner::MakeFocus(bool focus)
1083 {
1084 	fTextView->MakeFocus(focus);
1085 }
1086 
1087 
1088 void
1089 BAbstractSpinner::ResizeToPreferred()
1090 {
1091 	BView::ResizeToPreferred();
1092 
1093 	const char* label = Label();
1094 	if (label != NULL) {
1095 		fDivider = ceilf(StringWidth(label))
1096 			+ be_control_look->DefaultLabelSpacing();
1097 	} else
1098 		fDivider = 0.0f;
1099 
1100 	_LayoutTextView();
1101 }
1102 
1103 
1104 void
1105 BAbstractSpinner::SetFlags(uint32 flags)
1106 {
1107 	// If the textview is navigable, set it to not navigable if needed,
1108 	// else if it is not navigable, set it to navigable if needed
1109 	if (fTextView->Flags() & B_NAVIGABLE) {
1110 		if (!(flags & B_NAVIGABLE))
1111 			fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
1112 	} else {
1113 		if (flags & B_NAVIGABLE)
1114 			fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
1115 	}
1116 
1117 	// Don't make this one navigable
1118 	flags &= ~B_NAVIGABLE;
1119 
1120 	BView::SetFlags(flags);
1121 }
1122 
1123 
1124 void
1125 BAbstractSpinner::WindowActivated(bool active)
1126 {
1127 	_DrawTextView(fTextView->Frame());
1128 }
1129 
1130 
1131 void
1132 BAbstractSpinner::SetAlignment(alignment align)
1133 {
1134 	fAlignment = align;
1135 }
1136 
1137 
1138 void
1139 BAbstractSpinner::SetButtonStyle(spinner_button_style buttonStyle)
1140 {
1141 	fButtonStyle = buttonStyle;
1142 }
1143 
1144 
1145 void
1146 BAbstractSpinner::SetDivider(float position)
1147 {
1148 	position = roundf(position);
1149 
1150 	float delta = fDivider - position;
1151 	if (delta == 0.0f)
1152 		return;
1153 
1154 	fDivider = position;
1155 
1156 	if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
1157 		// We should never get here, since layout support means, we also
1158 		// layout the divider, and don't use this method at all.
1159 		Relayout();
1160 	} else {
1161 		_LayoutTextView();
1162 		Invalidate();
1163 	}
1164 }
1165 
1166 
1167 void
1168 BAbstractSpinner::SetEnabled(bool enable)
1169 {
1170 	if (IsEnabled() == enable)
1171 		return;
1172 
1173 	BControl::SetEnabled(enable);
1174 
1175 	fTextView->MakeEditable(enable);
1176 	if (enable)
1177 		fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
1178 	else
1179 		fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
1180 
1181 	_UpdateTextViewColors(enable);
1182 	fTextView->Invalidate();
1183 
1184 	_LayoutTextView();
1185 	Invalidate();
1186 	if (Window() != NULL)
1187 		Window()->UpdateIfNeeded();
1188 }
1189 
1190 
1191 void
1192 BAbstractSpinner::SetLabel(const char* label)
1193 {
1194 	BControl::SetLabel(label);
1195 
1196 	if (Window() != NULL)
1197 		Window()->UpdateIfNeeded();
1198 }
1199 
1200 
1201 bool
1202 BAbstractSpinner::IsDecrementEnabled() const
1203 {
1204 	return fDecrement->IsEnabled();
1205 }
1206 
1207 
1208 void
1209 BAbstractSpinner::SetDecrementEnabled(bool enable)
1210 {
1211 	if (IsDecrementEnabled() == enable)
1212 		return;
1213 
1214 	fDecrement->SetEnabled(enable);
1215 	fDecrement->Invalidate();
1216 }
1217 
1218 
1219 bool
1220 BAbstractSpinner::IsIncrementEnabled() const
1221 {
1222 	return fIncrement->IsEnabled();
1223 }
1224 
1225 
1226 void
1227 BAbstractSpinner::SetIncrementEnabled(bool enable)
1228 {
1229 	if (IsIncrementEnabled() == enable)
1230 		return;
1231 
1232 	fIncrement->SetEnabled(enable);
1233 	fIncrement->Invalidate();
1234 }
1235 
1236 
1237 BSize
1238 BAbstractSpinner::MinSize()
1239 {
1240 	_ValidateLayoutData();
1241 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
1242 }
1243 
1244 
1245 BSize
1246 BAbstractSpinner::MaxSize()
1247 {
1248 	_ValidateLayoutData();
1249 
1250 	BSize max = fLayoutData->min;
1251 	max.width = B_SIZE_UNLIMITED;
1252 
1253 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
1254 }
1255 
1256 
1257 BSize
1258 BAbstractSpinner::PreferredSize()
1259 {
1260 	_ValidateLayoutData();
1261 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
1262 		fLayoutData->min);
1263 }
1264 
1265 
1266 BAlignment
1267 BAbstractSpinner::LayoutAlignment()
1268 {
1269 	_ValidateLayoutData();
1270 	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
1271 		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_CENTER));
1272 }
1273 
1274 
1275 BLayoutItem*
1276 BAbstractSpinner::CreateLabelLayoutItem()
1277 {
1278 	if (fLayoutData->label_layout_item == NULL)
1279 		fLayoutData->label_layout_item = new LabelLayoutItem(this);
1280 
1281 	return fLayoutData->label_layout_item;
1282 }
1283 
1284 
1285 BLayoutItem*
1286 BAbstractSpinner::CreateTextViewLayoutItem()
1287 {
1288 	if (fLayoutData->text_view_layout_item == NULL)
1289 		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
1290 
1291 	return fLayoutData->text_view_layout_item;
1292 }
1293 
1294 
1295 BTextView*
1296 BAbstractSpinner::TextView() const
1297 {
1298 	return dynamic_cast<BTextView*>(fTextView);
1299 }
1300 
1301 
1302 //	#pragma mark - BAbstractSpinner protected methods
1303 
1304 
1305 status_t
1306 BAbstractSpinner::AllArchived(BMessage* into) const
1307 {
1308 	status_t result;
1309 	if ((result = BControl::AllArchived(into)) != B_OK)
1310 		return result;
1311 
1312 	BArchiver archiver(into);
1313 
1314 	BArchivable* textViewItem = fLayoutData->text_view_layout_item;
1315 	if (archiver.IsArchived(textViewItem))
1316 		result = archiver.AddArchivable(kTextViewItemField, textViewItem);
1317 
1318 	if (result != B_OK)
1319 		return result;
1320 
1321 	BArchivable* labelBarItem = fLayoutData->label_layout_item;
1322 	if (archiver.IsArchived(labelBarItem))
1323 		result = archiver.AddArchivable(kLabelItemField, labelBarItem);
1324 
1325 	return result;
1326 }
1327 
1328 
1329 status_t
1330 BAbstractSpinner::AllUnarchived(const BMessage* from)
1331 {
1332 	BUnarchiver unarchiver(from);
1333 
1334 	status_t result = B_OK;
1335 	if ((result = BControl::AllUnarchived(from)) != B_OK)
1336 		return result;
1337 
1338 	if (unarchiver.IsInstantiated(kTextViewItemField)) {
1339 		TextViewLayoutItem*& textViewItem
1340 			= fLayoutData->text_view_layout_item;
1341 		result = unarchiver.FindObject(kTextViewItemField,
1342 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, textViewItem);
1343 
1344 		if (result == B_OK)
1345 			textViewItem->SetParent(this);
1346 		else
1347 			return result;
1348 	}
1349 
1350 	if (unarchiver.IsInstantiated(kLabelItemField)) {
1351 		LabelLayoutItem*& labelItem = fLayoutData->label_layout_item;
1352 		result = unarchiver.FindObject(kLabelItemField,
1353 			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, labelItem);
1354 
1355 		if (result == B_OK)
1356 			labelItem->SetParent(this);
1357 	}
1358 
1359 	return result;
1360 }
1361 
1362 
1363 void
1364 BAbstractSpinner::DoLayout()
1365 {
1366 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
1367 		return;
1368 
1369 	if (GetLayout()) {
1370 		BControl::DoLayout();
1371 		return;
1372 	}
1373 
1374 	_ValidateLayoutData();
1375 
1376 	BSize size(Bounds().Size());
1377 	if (size.width < fLayoutData->min.width)
1378 		size.width = fLayoutData->min.width;
1379 
1380 	if (size.height < fLayoutData->min.height)
1381 		size.height = fLayoutData->min.height;
1382 
1383 	float divider = 0;
1384 	if (fLayoutData->label_layout_item != NULL
1385 		&& fLayoutData->text_view_layout_item != NULL
1386 		&& fLayoutData->label_layout_item->Frame().IsValid()
1387 		&& fLayoutData->text_view_layout_item->Frame().IsValid()) {
1388 		divider = fLayoutData->text_view_layout_item->Frame().left
1389 			- fLayoutData->label_layout_item->Frame().left;
1390 	} else if (fLayoutData->label_width > 0) {
1391 		divider = fLayoutData->label_width
1392 			+ be_control_look->DefaultLabelSpacing();
1393 	}
1394 	fDivider = divider;
1395 
1396 	BRect dirty(fTextView->Frame());
1397 	_LayoutTextView();
1398 
1399 	// invalidate dirty region
1400 	dirty = dirty | fTextView->Frame();
1401 	dirty = dirty | fIncrement->Frame();
1402 	dirty = dirty | fDecrement->Frame();
1403 
1404 	Invalidate(dirty);
1405 }
1406 
1407 
1408 void
1409 BAbstractSpinner::LayoutInvalidated(bool descendants)
1410 {
1411 	if (fLayoutData != NULL)
1412 		fLayoutData->valid = false;
1413 }
1414 
1415 
1416 //	#pragma mark - BAbstractSpinner private methods
1417 
1418 
1419 void
1420 BAbstractSpinner::_DrawLabel(BRect updateRect)
1421 {
1422 	BRect rect(Bounds());
1423 	rect.right = fDivider;
1424 	if (!rect.IsValid() || !rect.Intersects(updateRect))
1425 		return;
1426 
1427 	_ValidateLayoutData();
1428 
1429 	const char* label = Label();
1430 	if (label == NULL)
1431 		return;
1432 
1433 	// horizontal position
1434 	float x;
1435 	switch (fAlignment) {
1436 		case B_ALIGN_RIGHT:
1437 			x = fDivider - fLayoutData->label_width - 3.0f;
1438 			break;
1439 
1440 		case B_ALIGN_CENTER:
1441 			x = fDivider - roundf(fLayoutData->label_width / 2.0f);
1442 			break;
1443 
1444 		default:
1445 			x = 0.0f;
1446 			break;
1447 	}
1448 
1449 	// vertical position
1450 	font_height& fontHeight = fLayoutData->font_info;
1451 	float y = rect.top
1452 		+ roundf((rect.Height() + 1.0f - fontHeight.ascent
1453 			- fontHeight.descent) / 2.0f)
1454 		+ fontHeight.ascent + kFrameMargin * 2;
1455 
1456 	uint32 flags = 0;
1457 	if (!IsEnabled())
1458 		flags |= BControlLook::B_DISABLED;
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