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