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
28namespace BPrivate {
29
30
31const uint32 kArrowAreaWidth = 16;
32
33
34TimeEdit::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
47TimeEdit::~TimeEdit()
48{
49	free(fFieldPositions);
50	free(fFields);
51}
52
53
54void
55TimeEdit::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
91void
92TimeEdit::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
101void
102TimeEdit::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
126void
127TimeEdit::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
144float
145TimeEdit::SeparatorWidth()
146{
147	return 10.0f;
148}
149
150
151float
152TimeEdit::MinSectionWidth()
153{
154	return be_plain_font->StringWidth("00");
155}
156
157
158void
159TimeEdit::SectionFocus(uint32 index)
160{
161	fLastKeyDownTime = 0;
162	fFocus = index;
163	fHoldValue = _SectionValue(index);
164	Draw(Bounds());
165}
166
167
168void
169TimeEdit::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
186BTime
187TimeEdit::GetTime()
188{
189	return fTime.Time();
190}
191
192
193void
194TimeEdit::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
210void
211TimeEdit::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
226void
227TimeEdit::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
239void
240TimeEdit::_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
259void
260TimeEdit::_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
321bool
322TimeEdit::_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
352int32
353TimeEdit::_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
381float
382TimeEdit::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
393DateEdit::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
405DateEdit::~DateEdit()
406{
407	free(fFieldPositions);
408	free(fFields);
409}
410
411
412void
413DateEdit::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
458void
459DateEdit::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
468void
469DateEdit::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
493void
494DateEdit::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
514void
515DateEdit::SectionFocus(uint32 index)
516{
517	fLastKeyDownTime = 0;
518	fFocus = index;
519	fHoldValue = _SectionValue(index);
520	Draw(Bounds());
521}
522
523
524float
525DateEdit::MinSectionWidth()
526{
527	return be_plain_font->StringWidth("00");
528}
529
530
531float
532DateEdit::SeparatorWidth()
533{
534	return 10.0f;
535}
536
537
538void
539DateEdit::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
552BDate
553DateEdit::GetDate()
554{
555	return fDate;
556}
557
558
559void
560DateEdit::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
576void
577DateEdit::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
593void
594DateEdit::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
606void
607DateEdit::_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
627void
628DateEdit::_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
677bool
678DateEdit::_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
716int32
717DateEdit::_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
744float
745DateEdit::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
756SectionEdit::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
766SectionEdit::~SectionEdit()
767{
768}
769
770
771void
772SectionEdit::AttachedToWindow()
773{
774	AdoptParentColors();
775	BControl::AttachedToWindow();
776}
777
778
779void
780SectionEdit::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
793void
794SectionEdit::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
816BSize
817SectionEdit::MaxSize()
818{
819	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
820		BSize(B_SIZE_UNLIMITED, PreferredHeight()));
821}
822
823
824BSize
825SectionEdit::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
836BSize
837SectionEdit::PreferredSize()
838{
839	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
840		MinSize());
841}
842
843
844BRect
845SectionEdit::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
860BRect
861SectionEdit::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
876void
877SectionEdit::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
891void
892SectionEdit::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
930status_t
931SectionEdit::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
944uint32
945SectionEdit::CountSections() const
946{
947	return fSectionCount;
948}
949
950
951int32
952SectionEdit::FocusIndex() const
953{
954	return fFocus;
955}
956
957
958BRect
959SectionEdit::SectionArea() const
960{
961	BRect sectionArea = Bounds().InsetByCopy(2, 2);
962	sectionArea.right -= kArrowAreaWidth;
963	return sectionArea;
964}
965
966
967void
968SectionEdit::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