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
39static const float kFrameMargin			= 2.0f;
40
41const char* const kFrameField			= "BAbstractSpinner:layoutItem:frame";
42const char* const kLabelItemField		= "BAbstractSpinner:labelItem";
43const char* const kTextViewItemField	= "BAbstractSpinner:textViewItem";
44
45
46static 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
153typedef enum {
154	SPINNER_INCREMENT,
155	SPINNER_DECREMENT
156} spinner_direction;
157
158
159class SpinnerButton : public BView {
160public:
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
177private:
178			spinner_direction	fSpinnerDirection;
179			BAbstractSpinner*	fParent;
180			bool				fIsEnabled;
181			bool				fIsMouseDown;
182			bool				fIsMouseOver;
183			BMessageRunner*		fRepeater;
184};
185
186
187class SpinnerTextView : public BTextView {
188public:
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
197private:
198			BAbstractSpinner*	fParent;
199};
200
201
202class BAbstractSpinner::LabelLayoutItem : public BAbstractLayoutItem {
203public:
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
226private:
227			BAbstractSpinner*	fParent;
228			BRect				fFrame;
229};
230
231
232class BAbstractSpinner::TextViewLayoutItem : public BAbstractLayoutItem {
233public:
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
256private:
257			BAbstractSpinner*	fParent;
258			BRect				fFrame;
259};
260
261
262struct 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
300SpinnerButton::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
314SpinnerButton::~SpinnerButton()
315{
316	delete fRepeater;
317}
318
319
320void
321SpinnerButton::AttachedToWindow()
322{
323	fParent = static_cast<BAbstractSpinner*>(Parent());
324
325	AdoptParentColors();
326	BView::AttachedToWindow();
327}
328
329
330void
331SpinnerButton::DetachedFromWindow()
332{
333	fParent = NULL;
334
335	BView::DetachedFromWindow();
336}
337
338
339void
340SpinnerButton::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
444void
445SpinnerButton::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
463void
464SpinnerButton::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
490void
491SpinnerButton::MouseUp(BPoint where)
492{
493	fIsMouseDown = false;
494	delete fRepeater;
495	fRepeater = NULL;
496	Invalidate();
497
498	BView::MouseUp(where);
499}
500
501
502void
503SpinnerButton::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
526SpinnerTextView::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
536SpinnerTextView::~SpinnerTextView()
537{
538}
539
540
541void
542SpinnerTextView::AttachedToWindow()
543{
544	fParent = static_cast<BAbstractSpinner*>(Parent());
545
546	BTextView::AttachedToWindow();
547}
548
549
550void
551SpinnerTextView::DetachedFromWindow()
552{
553	fParent = NULL;
554
555	BTextView::DetachedFromWindow();
556}
557
558
559void
560SpinnerTextView::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
616void
617SpinnerTextView::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
636BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BAbstractSpinner* parent)
637	:
638	fParent(parent),
639	fFrame()
640{
641}
642
643
644BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BMessage* from)
645	:
646	BAbstractLayoutItem(from),
647	fParent(NULL),
648	fFrame()
649{
650	from->FindRect(kFrameField, &fFrame);
651}
652
653
654bool
655BAbstractSpinner::LabelLayoutItem::IsVisible()
656{
657	return !fParent->IsHidden(fParent);
658}
659
660
661void
662BAbstractSpinner::LabelLayoutItem::SetVisible(bool visible)
663{
664}
665
666
667BRect
668BAbstractSpinner::LabelLayoutItem::Frame()
669{
670	return fFrame;
671}
672
673
674void
675BAbstractSpinner::LabelLayoutItem::SetFrame(BRect frame)
676{
677	fFrame = frame;
678	fParent->_UpdateFrame();
679}
680
681
682void
683BAbstractSpinner::LabelLayoutItem::SetParent(BAbstractSpinner* parent)
684{
685	fParent = parent;
686}
687
688
689BView*
690BAbstractSpinner::LabelLayoutItem::View()
691{
692	return fParent;
693}
694
695
696BSize
697BAbstractSpinner::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
710BSize
711BAbstractSpinner::LabelLayoutItem::BaseMaxSize()
712{
713	return BaseMinSize();
714}
715
716
717BSize
718BAbstractSpinner::LabelLayoutItem::BasePreferredSize()
719{
720	return BaseMinSize();
721}
722
723
724BAlignment
725BAbstractSpinner::LabelLayoutItem::BaseAlignment()
726{
727	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
728}
729
730
731BRect
732BAbstractSpinner::LabelLayoutItem::FrameInParent() const
733{
734	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
735}
736
737
738status_t
739BAbstractSpinner::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
751BArchivable*
752BAbstractSpinner::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
764BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BAbstractSpinner* parent)
765	:
766	fParent(parent),
767	fFrame()
768{
769	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
770}
771
772
773BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
774	:
775	BAbstractLayoutItem(from),
776	fParent(NULL),
777	fFrame()
778{
779	from->FindRect(kFrameField, &fFrame);
780}
781
782
783bool
784BAbstractSpinner::TextViewLayoutItem::IsVisible()
785{
786	return !fParent->IsHidden(fParent);
787}
788
789
790void
791BAbstractSpinner::TextViewLayoutItem::SetVisible(bool visible)
792{
793	// not allowed
794}
795
796
797BRect
798BAbstractSpinner::TextViewLayoutItem::Frame()
799{
800	return fFrame;
801}
802
803
804void
805BAbstractSpinner::TextViewLayoutItem::SetFrame(BRect frame)
806{
807	fFrame = frame;
808	fParent->_UpdateFrame();
809}
810
811
812void
813BAbstractSpinner::TextViewLayoutItem::SetParent(BAbstractSpinner* parent)
814{
815	fParent = parent;
816}
817
818
819BView*
820BAbstractSpinner::TextViewLayoutItem::View()
821{
822	return fParent;
823}
824
825
826BSize
827BAbstractSpinner::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
838BSize
839BAbstractSpinner::TextViewLayoutItem::BaseMaxSize()
840{
841	return BaseMinSize();
842}
843
844
845BSize
846BAbstractSpinner::TextViewLayoutItem::BasePreferredSize()
847{
848	return BaseMinSize();
849}
850
851
852BAlignment
853BAbstractSpinner::TextViewLayoutItem::BaseAlignment()
854{
855	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
856}
857
858
859BRect
860BAbstractSpinner::TextViewLayoutItem::FrameInParent() const
861{
862	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
863}
864
865
866status_t
867BAbstractSpinner::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
879BArchivable*
880BAbstractSpinner::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
892BAbstractSpinner::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
902BAbstractSpinner::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
911BAbstractSpinner::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
929BAbstractSpinner::~BAbstractSpinner()
930{
931	delete fLayoutData;
932	fLayoutData = NULL;
933}
934
935
936BArchivable*
937BAbstractSpinner::Instantiate(BMessage* data)
938{
939	// cannot instantiate an abstract spinner
940	return NULL;
941}
942
943
944status_t
945BAbstractSpinner::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
963status_t
964BAbstractSpinner::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
975BHandler*
976BAbstractSpinner::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
984void
985BAbstractSpinner::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
1000void
1001BAbstractSpinner::Draw(BRect updateRect)
1002{
1003	_DrawLabel(updateRect);
1004	_DrawTextView(updateRect);
1005	fIncrement->Invalidate();
1006	fDecrement->Invalidate();
1007}
1008
1009
1010void
1011BAbstractSpinner::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
1062void
1063BAbstractSpinner::ValueChanged()
1064{
1065	// hook method - does nothing
1066}
1067
1068
1069void
1070BAbstractSpinner::MessageReceived(BMessage* message)
1071{
1072	if (!IsEnabled() && message->what == B_COLORS_UPDATED)
1073		_UpdateTextViewColors(false);
1074
1075	BControl::MessageReceived(message);
1076}
1077
1078
1079void
1080BAbstractSpinner::MakeFocus(bool focus)
1081{
1082	fTextView->MakeFocus(focus);
1083}
1084
1085
1086void
1087BAbstractSpinner::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
1102void
1103BAbstractSpinner::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
1122void
1123BAbstractSpinner::WindowActivated(bool active)
1124{
1125	_DrawTextView(fTextView->Frame());
1126}
1127
1128
1129void
1130BAbstractSpinner::SetAlignment(alignment align)
1131{
1132	fAlignment = align;
1133}
1134
1135
1136void
1137BAbstractSpinner::SetButtonStyle(spinner_button_style buttonStyle)
1138{
1139	fButtonStyle = buttonStyle;
1140}
1141
1142
1143void
1144BAbstractSpinner::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
1165void
1166BAbstractSpinner::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
1189void
1190BAbstractSpinner::SetLabel(const char* label)
1191{
1192	BControl::SetLabel(label);
1193
1194	if (Window() != NULL)
1195		Window()->UpdateIfNeeded();
1196}
1197
1198
1199bool
1200BAbstractSpinner::IsDecrementEnabled() const
1201{
1202	return fDecrement->IsEnabled();
1203}
1204
1205
1206void
1207BAbstractSpinner::SetDecrementEnabled(bool enable)
1208{
1209	if (IsDecrementEnabled() == enable)
1210		return;
1211
1212	fDecrement->SetEnabled(enable);
1213	fDecrement->Invalidate();
1214}
1215
1216
1217bool
1218BAbstractSpinner::IsIncrementEnabled() const
1219{
1220	return fIncrement->IsEnabled();
1221}
1222
1223
1224void
1225BAbstractSpinner::SetIncrementEnabled(bool enable)
1226{
1227	if (IsIncrementEnabled() == enable)
1228		return;
1229
1230	fIncrement->SetEnabled(enable);
1231	fIncrement->Invalidate();
1232}
1233
1234
1235BSize
1236BAbstractSpinner::MinSize()
1237{
1238	_ValidateLayoutData();
1239	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
1240}
1241
1242
1243BSize
1244BAbstractSpinner::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
1255BSize
1256BAbstractSpinner::PreferredSize()
1257{
1258	_ValidateLayoutData();
1259	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
1260		fLayoutData->min);
1261}
1262
1263
1264BAlignment
1265BAbstractSpinner::LayoutAlignment()
1266{
1267	_ValidateLayoutData();
1268	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
1269		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_CENTER));
1270}
1271
1272
1273BLayoutItem*
1274BAbstractSpinner::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
1283BLayoutItem*
1284BAbstractSpinner::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
1293BTextView*
1294BAbstractSpinner::TextView() const
1295{
1296	return dynamic_cast<BTextView*>(fTextView);
1297}
1298
1299
1300//	#pragma mark - BAbstractSpinner protected methods
1301
1302
1303status_t
1304BAbstractSpinner::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
1327status_t
1328BAbstractSpinner::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
1361void
1362BAbstractSpinner::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
1406void
1407BAbstractSpinner::LayoutInvalidated(bool descendants)
1408{
1409	if (fLayoutData != NULL)
1410		fLayoutData->valid = false;
1411}
1412
1413
1414//	#pragma mark - BAbstractSpinner private methods
1415
1416
1417void
1418BAbstractSpinner::_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
1464void
1465BAbstractSpinner::_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
1485void
1486BAbstractSpinner::_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
1531void
1532BAbstractSpinner::_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
1564void
1565BAbstractSpinner::_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
1594void
1595BAbstractSpinner::_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
1618void
1619BAbstractSpinner::_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
1669void BAbstractSpinner::_ReservedAbstractSpinner20() {}
1670void BAbstractSpinner::_ReservedAbstractSpinner19() {}
1671void BAbstractSpinner::_ReservedAbstractSpinner18() {}
1672void BAbstractSpinner::_ReservedAbstractSpinner17() {}
1673void BAbstractSpinner::_ReservedAbstractSpinner16() {}
1674void BAbstractSpinner::_ReservedAbstractSpinner15() {}
1675void BAbstractSpinner::_ReservedAbstractSpinner14() {}
1676void BAbstractSpinner::_ReservedAbstractSpinner13() {}
1677void BAbstractSpinner::_ReservedAbstractSpinner12() {}
1678void BAbstractSpinner::_ReservedAbstractSpinner11() {}
1679void BAbstractSpinner::_ReservedAbstractSpinner10() {}
1680void BAbstractSpinner::_ReservedAbstractSpinner9() {}
1681void BAbstractSpinner::_ReservedAbstractSpinner8() {}
1682void BAbstractSpinner::_ReservedAbstractSpinner7() {}
1683void BAbstractSpinner::_ReservedAbstractSpinner6() {}
1684void BAbstractSpinner::_ReservedAbstractSpinner5() {}
1685void BAbstractSpinner::_ReservedAbstractSpinner4() {}
1686void BAbstractSpinner::_ReservedAbstractSpinner3() {}
1687void BAbstractSpinner::_ReservedAbstractSpinner2() {}
1688void BAbstractSpinner::_ReservedAbstractSpinner1() {}
1689