xref: /haiku/src/kits/shared/DateTimeEdit.cpp (revision 6f80a9801fedbe7355c4360bd204ba746ec3ec2d)
1 /*
2  * Copyright 2004-2011, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		McCall <mccall@@digitalparadise.co.uk>
7  *		Mike Berg <mike@berg-net.us>
8  *		Julun <host.haiku@gmx.de>
9  *		Clemens <mail@Clemens-Zeidler.de>
10  *		Adrien Destugues <pulkomandy@pulkomandy.cx>
11  *		Hamish Morrison <hamish@lavabit.com>
12  */
13 
14 
15 #include "DateTimeEdit.h"
16 
17 #include <stdlib.h>
18 
19 #include <ControlLook.h>
20 #include <DateFormat.h>
21 #include <LayoutUtils.h>
22 #include <List.h>
23 #include <Locale.h>
24 #include <String.h>
25 #include <Window.h>
26 
27 
28 namespace BPrivate {
29 
30 
31 const uint32 kArrowAreaWidth = 16;
32 
33 
34 TimeEdit::TimeEdit(const char* name, uint32 sections, BMessage* message)
35 	:
36 	SectionEdit(name, sections, message),
37 	fLastKeyDownTime(0),
38 	fFields(NULL),
39 	fFieldCount(0),
40 	fFieldPositions(NULL),
41 	fFieldPosCount(0)
42 {
43 	InitView();
44 }
45 
46 
47 TimeEdit::~TimeEdit()
48 {
49 	free(fFieldPositions);
50 	free(fFields);
51 }
52 
53 
54 void
55 TimeEdit::KeyDown(const char* bytes, int32 numBytes)
56 {
57 	if (IsEnabled() == false)
58 		return;
59 	SectionEdit::KeyDown(bytes, numBytes);
60 
61 	// only accept valid input
62 	int32 number = atoi(bytes);
63 	if (number < 0 || bytes[0] < '0')
64 		return;
65 
66 	int32 section = FocusIndex();
67 	if (section < 0 || section > 2)
68 		return;
69 
70 	bigtime_t currentTime = system_time();
71 	if (currentTime - fLastKeyDownTime < 1000000) {
72 		int32 doubleDigit = number + fLastKeyDownInt * 10;
73 		if (_IsValidDoubleDigit(doubleDigit))
74 			number = doubleDigit;
75 		fLastKeyDownTime = 0;
76 	} else {
77 		fLastKeyDownTime = currentTime;
78 		fLastKeyDownInt = number;
79 	}
80 
81 	// update display value
82 	fHoldValue = number;
83 	_CheckRange();
84 	_UpdateFields();
85 
86 	// send message to change time
87 	Invoke();
88 }
89 
90 
91 void
92 TimeEdit::InitView()
93 {
94 	// make sure we call the base class method, as it
95 	// will create the arrow bitmaps and the section list
96 	fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME);
97 	_UpdateFields();
98 }
99 
100 
101 void
102 TimeEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus)
103 {
104 	if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount)
105 		return;
106 
107 	if (hasFocus)
108 		SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR),
109 			ViewColor(), 192));
110 	else
111 		SetLowColor(ViewColor());
112 
113 	BString field;
114 	fText.CopyCharsInto(field, fFieldPositions[index * 2],
115 		fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]);
116 
117 	BPoint point(bounds.LeftBottom());
118 	point.y -= bounds.Height() / 2.0 - 6.0;
119 	point.x += (bounds.Width() - StringWidth(field)) / 2;
120 	SetHighUIColor(B_PANEL_TEXT_COLOR);
121 	FillRect(bounds, B_SOLID_LOW);
122 	DrawString(field, point);
123 }
124 
125 
126 void
127 TimeEdit::DrawSeparator(uint32 index, BRect bounds)
128 {
129 	if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount)
130 		return;
131 
132 	BString field;
133 	fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1],
134 		fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]);
135 
136 	BPoint point(bounds.LeftBottom());
137 	point.y -= bounds.Height() / 2.0 - 6.0;
138 	point.x += (bounds.Width() - StringWidth(field)) / 2;
139 	SetHighUIColor(B_PANEL_TEXT_COLOR);
140 	DrawString(field, point);
141 }
142 
143 
144 float
145 TimeEdit::SeparatorWidth()
146 {
147 	return 10.0f;
148 }
149 
150 
151 float
152 TimeEdit::MinSectionWidth()
153 {
154 	return be_plain_font->StringWidth("00");
155 }
156 
157 
158 void
159 TimeEdit::SectionFocus(uint32 index)
160 {
161 	fLastKeyDownTime = 0;
162 	fFocus = index;
163 	fHoldValue = _SectionValue(index);
164 	Draw(Bounds());
165 }
166 
167 
168 void
169 TimeEdit::SetTime(int32 hour, int32 minute, int32 second)
170 {
171 	// make sure to update date upon overflow
172 	if (hour == 0 && minute == 0 && second == 0)
173 		fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME);
174 
175 	fTime.SetTime(BTime(hour, minute, second));
176 
177 	if (LockLooper()) {
178 		_UpdateFields();
179 		UnlockLooper();
180 	}
181 
182 	Invalidate(Bounds());
183 }
184 
185 
186 BTime
187 TimeEdit::GetTime()
188 {
189 	return fTime.Time();
190 }
191 
192 
193 void
194 TimeEdit::DoUpPress()
195 {
196 	if (fFocus == -1)
197 		SectionFocus(0);
198 
199 	// update displayed value
200 	fHoldValue += 1;
201 
202 	_CheckRange();
203 	_UpdateFields();
204 
205 	// send message to change time
206 	Invoke();
207 }
208 
209 
210 void
211 TimeEdit::DoDownPress()
212 {
213 	if (fFocus == -1)
214 		SectionFocus(0);
215 
216 	// update display value
217 	fHoldValue -= 1;
218 
219 	_CheckRange();
220 	_UpdateFields();
221 
222 	Invoke();
223 }
224 
225 
226 void
227 TimeEdit::PopulateMessage(BMessage* message)
228 {
229 	if (fFocus < 0 || fFocus >= fFieldCount)
230 		return;
231 
232 	message->AddBool("time", true);
233 	message->AddInt32("hour", fTime.Time().Hour());
234 	message->AddInt32("minute", fTime.Time().Minute());
235 	message->AddInt32("second", fTime.Time().Second());
236 }
237 
238 
239 void
240 TimeEdit::_UpdateFields()
241 {
242 	time_t time = fTime.Time_t();
243 
244 	if (fFieldPositions != NULL) {
245 		free(fFieldPositions);
246 		fFieldPositions = NULL;
247 	}
248 	fTimeFormat.Format(fText, fFieldPositions, fFieldPosCount, time,
249 		B_MEDIUM_TIME_FORMAT);
250 
251 	if (fFields != NULL) {
252 		free(fFields);
253 		fFields = NULL;
254 	}
255 	fTimeFormat.GetTimeFields(fFields, fFieldCount, B_MEDIUM_TIME_FORMAT);
256 }
257 
258 
259 void
260 TimeEdit::_CheckRange()
261 {
262 	if (fFocus < 0 || fFocus >= fFieldCount)
263 		return;
264 
265 	int32 value = fHoldValue;
266 	switch (fFields[fFocus]) {
267 		case B_DATE_ELEMENT_HOUR:
268 			if (value > 23)
269 				value = 0;
270 			else if (value < 0)
271 				value = 23;
272 
273 			fTime.SetTime(BTime(value, fTime.Time().Minute(),
274 				fTime.Time().Second()));
275 			break;
276 
277 		case B_DATE_ELEMENT_MINUTE:
278 			if (value> 59)
279 				value = 0;
280 			else if (value < 0)
281 				value = 59;
282 
283 			fTime.SetTime(BTime(fTime.Time().Hour(), value,
284 				fTime.Time().Second()));
285 			break;
286 
287 		case B_DATE_ELEMENT_SECOND:
288 			if (value > 59)
289 				value = 0;
290 			else if (value < 0)
291 				value = 59;
292 
293 			fTime.SetTime(BTime(fTime.Time().Hour(), fTime.Time().Minute(),
294 				value));
295 			break;
296 
297 		case B_DATE_ELEMENT_AM_PM:
298 			value = fTime.Time().Hour();
299 			if (value < 13)
300 				value += 12;
301 			else
302 				value -= 12;
303 			if (value == 24)
304 				value = 0;
305 
306 			// modify hour value to reflect change in am/ pm
307 			fTime.SetTime(BTime(value, fTime.Time().Minute(),
308 				fTime.Time().Second()));
309 			break;
310 
311 		default:
312 			return;
313 	}
314 
315 
316 	fHoldValue = value;
317 	Invalidate(Bounds());
318 }
319 
320 
321 bool
322 TimeEdit::_IsValidDoubleDigit(int32 value)
323 {
324 	if (fFocus < 0 || fFocus >= fFieldCount)
325 		return false;
326 
327 	bool isInRange = false;
328 	switch (fFields[fFocus]) {
329 		case B_DATE_ELEMENT_HOUR:
330 			if (value <= 23)
331 				isInRange = true;
332 			break;
333 
334 		case B_DATE_ELEMENT_MINUTE:
335 			if (value <= 59)
336 				isInRange = true;
337 			break;
338 
339 		case B_DATE_ELEMENT_SECOND:
340 			if (value <= 59)
341 				isInRange = true;
342 			break;
343 
344 		default:
345 			break;
346 	}
347 
348 	return isInRange;
349 }
350 
351 
352 int32
353 TimeEdit::_SectionValue(int32 index) const
354 {
355 	if (index < 0 || index >= fFieldCount)
356 		return 0;
357 
358 	int32 value;
359 	switch (fFields[index]) {
360 		case B_DATE_ELEMENT_HOUR:
361 			value = fTime.Time().Hour();
362 			break;
363 
364 		case B_DATE_ELEMENT_MINUTE:
365 			value = fTime.Time().Minute();
366 			break;
367 
368 		case B_DATE_ELEMENT_SECOND:
369 			value = fTime.Time().Second();
370 			break;
371 
372 		default:
373 			value = 0;
374 			break;
375 	}
376 
377 	return value;
378 }
379 
380 
381 float
382 TimeEdit::PreferredHeight()
383 {
384 	font_height fontHeight;
385 	GetFontHeight(&fontHeight);
386 	return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4);
387 }
388 
389 
390 // #pragma mark -
391 
392 
393 DateEdit::DateEdit(const char* name, uint32 sections, BMessage* message)
394 	:
395 	SectionEdit(name, sections, message),
396 	fFields(NULL),
397 	fFieldCount(0),
398 	fFieldPositions(NULL),
399 	fFieldPosCount(0)
400 {
401 	InitView();
402 }
403 
404 
405 DateEdit::~DateEdit()
406 {
407 	free(fFieldPositions);
408 	free(fFields);
409 }
410 
411 
412 void
413 DateEdit::KeyDown(const char* bytes, int32 numBytes)
414 {
415 	if (IsEnabled() == false)
416 		return;
417 	SectionEdit::KeyDown(bytes, numBytes);
418 
419 	// only accept valid input
420 	int32 number = atoi(bytes);
421 	if (number < 0 || bytes[0] < '0')
422 		return;
423 
424 	int32 section = FocusIndex();
425 	if (section < 0 || section > 2)
426 		return;
427 
428 	bigtime_t currentTime = system_time();
429 	if (currentTime - fLastKeyDownTime < 1000000) {
430 		int32 doubleDigit = number + fLastKeyDownInt * 10;
431 		if (_IsValidDoubleDigit(doubleDigit))
432 			number = doubleDigit;
433 		fLastKeyDownTime = 0;
434 	} else {
435 		fLastKeyDownTime = currentTime;
436 		fLastKeyDownInt = number;
437 	}
438 
439 	// if year add 2000
440 
441 	if (fFields[section] == B_DATE_ELEMENT_YEAR) {
442 		int32 oldCentury = int32(fHoldValue / 100) * 100;
443 		if (number < 10 && oldCentury == 1900)
444 			number += 70;
445 		number += oldCentury;
446 	}
447 	fHoldValue = number;
448 
449 	// update display value
450 	_CheckRange();
451 	_UpdateFields();
452 
453 	// send message to change time
454 	Invoke();
455 }
456 
457 
458 void
459 DateEdit::InitView()
460 {
461 	// make sure we call the base class method, as it
462 	// will create the arrow bitmaps and the section list
463 	fDate = BDate::CurrentDate(B_LOCAL_TIME);
464 	_UpdateFields();
465 }
466 
467 
468 void
469 DateEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus)
470 {
471 	if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount)
472 		return;
473 
474 	if (hasFocus)
475 		SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR),
476 			ViewColor(), 192));
477 	else
478 		SetLowColor(ViewColor());
479 
480 	BString field;
481 	fText.CopyCharsInto(field, fFieldPositions[index * 2],
482 		fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]);
483 
484 	BPoint point(bounds.LeftBottom());
485 	point.y -= bounds.Height() / 2.0 - 6.0;
486 	point.x += (bounds.Width() - StringWidth(field)) / 2;
487 	SetHighUIColor(B_PANEL_TEXT_COLOR);
488 	FillRect(bounds, B_SOLID_LOW);
489 	DrawString(field, point);
490 }
491 
492 
493 void
494 DateEdit::DrawSeparator(uint32 index, BRect bounds)
495 {
496 	if (index >= 2)
497 		return;
498 
499 	if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount)
500 		return;
501 
502 	BString field;
503 	fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1],
504 		fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]);
505 
506 	BPoint point(bounds.LeftBottom());
507 	point.y -= bounds.Height() / 2.0 - 6.0;
508 	point.x += (bounds.Width() - StringWidth(field)) / 2;
509 	SetHighUIColor(B_PANEL_TEXT_COLOR);
510 	DrawString(field, point);
511 }
512 
513 
514 void
515 DateEdit::SectionFocus(uint32 index)
516 {
517 	fLastKeyDownTime = 0;
518 	fFocus = index;
519 	fHoldValue = _SectionValue(index);
520 	Draw(Bounds());
521 }
522 
523 
524 float
525 DateEdit::MinSectionWidth()
526 {
527 	return be_plain_font->StringWidth("00");
528 }
529 
530 
531 float
532 DateEdit::SeparatorWidth()
533 {
534 	return 10.0f;
535 }
536 
537 
538 void
539 DateEdit::SetDate(int32 year, int32 month, int32 day)
540 {
541 	fDate.SetDate(year, month, day);
542 
543 	if (LockLooper()) {
544 		_UpdateFields();
545 		UnlockLooper();
546 	}
547 
548 	Invalidate(Bounds());
549 }
550 
551 
552 BDate
553 DateEdit::GetDate()
554 {
555 	return fDate;
556 }
557 
558 
559 void
560 DateEdit::DoUpPress()
561 {
562 	if (fFocus == -1)
563 		SectionFocus(0);
564 
565 	// update displayed value
566 	fHoldValue += 1;
567 
568 	_CheckRange();
569 	_UpdateFields();
570 
571 	// send message to change Date
572 	Invoke();
573 }
574 
575 
576 void
577 DateEdit::DoDownPress()
578 {
579 	if (fFocus == -1)
580 		SectionFocus(0);
581 
582 	// update display value
583 	fHoldValue -= 1;
584 
585 	_CheckRange();
586 	_UpdateFields();
587 
588 	// send message to change Date
589 	Invoke();
590 }
591 
592 
593 void
594 DateEdit::PopulateMessage(BMessage* message)
595 {
596 	if (fFocus < 0 || fFocus >= fFieldCount)
597 		return;
598 
599 	message->AddBool("time", false);
600 	message->AddInt32("year", fDate.Year());
601 	message->AddInt32("month", fDate.Month());
602 	message->AddInt32("day", fDate.Day());
603 }
604 
605 
606 void
607 DateEdit::_UpdateFields()
608 {
609 	time_t time = BDateTime(fDate, BTime()).Time_t();
610 
611 	if (fFieldPositions != NULL) {
612 		free(fFieldPositions);
613 		fFieldPositions = NULL;
614 	}
615 
616 	fDateFormat.Format(fText, fFieldPositions, fFieldPosCount, time,
617 		B_SHORT_DATE_FORMAT);
618 
619 	if (fFields != NULL) {
620 		free(fFields);
621 		fFields = NULL;
622 	}
623 	fDateFormat.GetFields(fFields, fFieldCount, B_SHORT_DATE_FORMAT);
624 }
625 
626 
627 void
628 DateEdit::_CheckRange()
629 {
630 	if (fFocus < 0 || fFocus >= fFieldCount)
631 		return;
632 
633 	int32 value = fHoldValue;
634 	switch (fFields[fFocus]) {
635 		case B_DATE_ELEMENT_DAY:
636 		{
637 			int32 days = fDate.DaysInMonth();
638 			if (value > days)
639 				value = 1;
640 			else if (value < 1)
641 				value = days;
642 
643 			fDate.SetDate(fDate.Year(), fDate.Month(), value);
644 			break;
645 		}
646 
647 		case B_DATE_ELEMENT_MONTH:
648 		{
649 			if (value > 12)
650 				value = 1;
651 			else if (value < 1)
652 				value = 12;
653 
654 			int32 day = fDate.Day();
655 			fDate.SetDate(fDate.Year(), value, 1);
656 
657 			// changing between months with differing amounts of days
658 			while (day > fDate.DaysInMonth())
659 				day--;
660 			fDate.SetDate(fDate.Year(), value, day);
661 			break;
662 		}
663 
664 		case B_DATE_ELEMENT_YEAR:
665 			fDate.SetDate(value, fDate.Month(), fDate.Day());
666 			break;
667 
668 		default:
669 			return;
670 	}
671 
672 	fHoldValue = value;
673 	Invalidate(Bounds());
674 }
675 
676 
677 bool
678 DateEdit::_IsValidDoubleDigit(int32 value)
679 {
680 	if (fFocus < 0 || fFocus >= fFieldCount)
681 		return false;
682 
683 	bool isInRange = false;
684 	switch (fFields[fFocus]) {
685 		case B_DATE_ELEMENT_DAY:
686 		{
687 			int32 days = fDate.DaysInMonth();
688 			if (value >= 1 && value <= days)
689 				isInRange = true;
690 			break;
691 		}
692 
693 		case B_DATE_ELEMENT_MONTH:
694 		{
695 			if (value >= 1 && value <= 12)
696 				isInRange = true;
697 			break;
698 		}
699 
700 		case B_DATE_ELEMENT_YEAR:
701 		{
702 			int32 year = int32(fHoldValue / 100) * 100 + value;
703 			if (year >= 2000)
704 				isInRange = true;
705 			break;
706 		}
707 
708 		default:
709 			break;
710 	}
711 
712 	return isInRange;
713 }
714 
715 
716 int32
717 DateEdit::_SectionValue(int32 index) const
718 {
719 	if (index < 0 || index >= fFieldCount)
720 		return 0;
721 
722 	int32 value = 0;
723 	switch (fFields[index]) {
724 		case B_DATE_ELEMENT_YEAR:
725 			value = fDate.Year();
726 			break;
727 
728 		case B_DATE_ELEMENT_MONTH:
729 			value = fDate.Month();
730 			break;
731 
732 		case B_DATE_ELEMENT_DAY:
733 			value = fDate.Day();
734 			break;
735 
736 		default:
737 			break;
738 	}
739 
740 	return value;
741 }
742 
743 
744 float
745 DateEdit::PreferredHeight()
746 {
747 	font_height fontHeight;
748 	GetFontHeight(&fontHeight);
749 	return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4);
750 }
751 
752 
753 // #pragma mark -
754 
755 
756 SectionEdit::SectionEdit(const char* name, uint32 sections, BMessage* message)
757 	:
758 	BControl(name, NULL, message, B_WILL_DRAW | B_NAVIGABLE),
759 	fFocus(-1),
760 	fSectionCount(sections),
761 	fHoldValue(0)
762 {
763 }
764 
765 
766 SectionEdit::~SectionEdit()
767 {
768 }
769 
770 
771 void
772 SectionEdit::AttachedToWindow()
773 {
774 	AdoptParentColors();
775 	BControl::AttachedToWindow();
776 }
777 
778 
779 void
780 SectionEdit::Draw(BRect updateRect)
781 {
782 	DrawBorder(updateRect);
783 
784 	for (uint32 idx = 0; idx < fSectionCount; idx++) {
785 		DrawSection(idx, FrameForSection(idx),
786 			((uint32)fFocus == idx) && IsFocus());
787 		if (idx < fSectionCount - 1)
788 			DrawSeparator(idx, FrameForSeparator(idx));
789 	}
790 }
791 
792 
793 void
794 SectionEdit::MouseDown(BPoint where)
795 {
796 	if (IsEnabled() == false)
797 		return;
798 
799 	MakeFocus(true);
800 
801 	if (fUpRect.Contains(where))
802 		DoUpPress();
803 	else if (fDownRect.Contains(where))
804 		DoDownPress();
805 	else if (fSectionCount > 0) {
806 		for (uint32 idx = 0; idx < fSectionCount; idx++) {
807 			if (FrameForSection(idx).Contains(where)) {
808 				SectionFocus(idx);
809 				return;
810 			}
811 		}
812 	}
813 }
814 
815 
816 BSize
817 SectionEdit::MaxSize()
818 {
819 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
820 		BSize(B_SIZE_UNLIMITED, PreferredHeight()));
821 }
822 
823 
824 BSize
825 SectionEdit::MinSize()
826 {
827 	BSize minSize;
828 	minSize.height = PreferredHeight();
829 	minSize.width = (SeparatorWidth() + MinSectionWidth())
830 		* fSectionCount;
831 	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
832 		minSize);
833 }
834 
835 
836 BSize
837 SectionEdit::PreferredSize()
838 {
839 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
840 		MinSize());
841 }
842 
843 
844 BRect
845 SectionEdit::FrameForSection(uint32 index)
846 {
847 	BRect area = SectionArea();
848 	float sepWidth = SeparatorWidth();
849 
850 	float width = (area.Width() -
851 		sepWidth * (fSectionCount - 1))
852 		/ fSectionCount;
853 	area.left += index * (width + sepWidth);
854 	area.right = area.left + width;
855 
856 	return area;
857 }
858 
859 
860 BRect
861 SectionEdit::FrameForSeparator(uint32 index)
862 {
863 	BRect area = SectionArea();
864 	float sepWidth = SeparatorWidth();
865 
866 	float width = (area.Width() -
867 		sepWidth * (fSectionCount - 1))
868 		/ fSectionCount;
869 	area.left += (index + 1) * width + index * sepWidth;
870 	area.right = area.left + sepWidth;
871 
872 	return area;
873 }
874 
875 
876 void
877 SectionEdit::MakeFocus(bool focused)
878 {
879 	if (focused == IsFocus())
880 		return;
881 
882 	BControl::MakeFocus(focused);
883 
884 	if (fFocus == -1)
885 		SectionFocus(0);
886 	else
887 		SectionFocus(fFocus);
888 }
889 
890 
891 void
892 SectionEdit::KeyDown(const char* bytes, int32 numbytes)
893 {
894 	if (IsEnabled() == false)
895 		return;
896 	if (fFocus == -1)
897 		SectionFocus(0);
898 
899 	switch (bytes[0]) {
900 		case B_LEFT_ARROW:
901 			fFocus -= 1;
902 			if (fFocus < 0)
903 				fFocus = fSectionCount - 1;
904 			SectionFocus(fFocus);
905 			break;
906 
907 		case B_RIGHT_ARROW:
908 			fFocus += 1;
909 			if ((uint32)fFocus >= fSectionCount)
910 				fFocus = 0;
911 			SectionFocus(fFocus);
912 			break;
913 
914 		case B_UP_ARROW:
915 			DoUpPress();
916 			break;
917 
918 		case B_DOWN_ARROW:
919 			DoDownPress();
920 			break;
921 
922 		default:
923 			BControl::KeyDown(bytes, numbytes);
924 			break;
925 	}
926 	Draw(Bounds());
927 }
928 
929 
930 status_t
931 SectionEdit::Invoke(BMessage* message)
932 {
933 	if (message == NULL)
934 		message = Message();
935 	if (message == NULL)
936 		return BControl::Invoke(NULL);
937 
938 	BMessage clone(*message);
939 	PopulateMessage(&clone);
940 	return BControl::Invoke(&clone);
941 }
942 
943 
944 uint32
945 SectionEdit::CountSections() const
946 {
947 	return fSectionCount;
948 }
949 
950 
951 int32
952 SectionEdit::FocusIndex() const
953 {
954 	return fFocus;
955 }
956 
957 
958 BRect
959 SectionEdit::SectionArea() const
960 {
961 	BRect sectionArea = Bounds().InsetByCopy(2, 2);
962 	sectionArea.right -= kArrowAreaWidth;
963 	return sectionArea;
964 }
965 
966 
967 void
968 SectionEdit::DrawBorder(const BRect& updateRect)
969 {
970 	BRect bounds(Bounds());
971 	bool showFocus = (IsFocus() && Window() && Window()->IsActive());
972 
973 	be_control_look->DrawBorder(this, bounds, updateRect, ViewColor(),
974 		B_FANCY_BORDER, showFocus ? BControlLook::B_FOCUSED : 0);
975 
976 	// draw up/down control
977 
978 	bounds.left = bounds.right - kArrowAreaWidth;
979 	bounds.right = Bounds().right - 2;
980 	fUpRect.Set(bounds.left + 3, bounds.top + 2, bounds.right,
981 		bounds.bottom / 2.0);
982 	fDownRect = fUpRect.OffsetByCopy(0, fUpRect.Height() + 2);
983 
984 	BPoint middle(floorf(fUpRect.left + fUpRect.Width() / 2),
985 		fUpRect.top + 1);
986 	BPoint left(fUpRect.left + 3, fUpRect.bottom - 1);
987 	BPoint right(left.x + 2 * (middle.x - left.x), fUpRect.bottom - 1);
988 
989 	SetPenSize(2);
990 	SetLowColor(ViewColor());
991 
992 	if (updateRect.Intersects(fUpRect)) {
993 		FillRect(fUpRect, B_SOLID_LOW);
994 		BeginLineArray(2);
995 			AddLine(left, middle, HighColor());
996 			AddLine(middle, right, HighColor());
997 		EndLineArray();
998 	}
999 	if (updateRect.Intersects(fDownRect)) {
1000 		middle.y = fDownRect.bottom - 1;
1001 		left.y = right.y = fDownRect.top + 1;
1002 
1003 		FillRect(fDownRect, B_SOLID_LOW);
1004 		BeginLineArray(2);
1005 			AddLine(left, middle, HighColor());
1006 			AddLine(middle, right, HighColor());
1007 		EndLineArray();
1008 	}
1009 
1010 	SetPenSize(1);
1011 }
1012 
1013 
1014 }	// namespace BPrivate
1015