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