vfs: check userland buffers before reading them.
[haiku.git] / src / preferences / time / DateTimeEdit.cpp
blob24fb1c4d2ffe747d0bbb5e57245144f93280bbe5
1 /*
2 * Copyright 2004-2011, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
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>
15 #include "DateTimeEdit.h"
17 #include <stdlib.h>
19 #include <DateFormat.h>
20 #include <List.h>
21 #include <Locale.h>
22 #include <String.h>
23 #include <Window.h>
26 using BPrivate::B_LOCAL_TIME;
29 TTimeEdit::TTimeEdit(const char* name, uint32 sections)
31 TSectionEdit(name, sections),
32 fLastKeyDownTime(0),
33 fFields(NULL),
34 fFieldCount(0),
35 fFieldPositions(NULL),
36 fFieldPosCount(0)
38 InitView();
42 TTimeEdit::~TTimeEdit()
44 free(fFieldPositions);
45 free(fFields);
49 void
50 TTimeEdit::KeyDown(const char* bytes, int32 numBytes)
52 TSectionEdit::KeyDown(bytes, numBytes);
54 // only accept valid input
55 int32 number = atoi(bytes);
56 if (number - 1 < 0)
57 return;
59 int32 section = FocusIndex();
60 if (section < 0 || section > 2)
61 return;
62 bigtime_t currentTime = system_time();
63 if (currentTime - fLastKeyDownTime < 1000000) {
64 int32 doubleDigit = number + fLastKeyDownInt * 10;
65 if (_IsValidDoubleDigit(doubleDigit))
66 number = doubleDigit;
67 fLastKeyDownTime = 0;
68 } else {
69 fLastKeyDownTime = currentTime;
70 fLastKeyDownInt = number;
73 // update display value
74 fHoldValue = number;
76 _CheckRange();
78 // send message to change time
79 DispatchMessage();
83 void
84 TTimeEdit::InitView()
86 // make sure we call the base class method, as it
87 // will create the arrow bitmaps and the section list
88 fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME);
89 _UpdateFields();
93 void
94 TTimeEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus)
96 if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount)
97 return;
99 if (hasFocus)
100 SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR),
101 ViewColor(), 192));
102 else
103 SetLowColor(ViewColor());
105 BString field;
106 fText.CopyCharsInto(field, fFieldPositions[index * 2],
107 fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]);
109 BPoint point(bounds.LeftBottom());
110 point.y -= bounds.Height() / 2.0 - 6.0;
111 point.x += (bounds.Width() - StringWidth(field)) / 2;
112 SetHighUIColor(B_PANEL_TEXT_COLOR);
113 FillRect(bounds, B_SOLID_LOW);
114 DrawString(field, point);
118 void
119 TTimeEdit::DrawSeparator(uint32 index, BRect bounds)
121 if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount)
122 return;
124 BString field;
125 fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1],
126 fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]);
128 BPoint point(bounds.LeftBottom());
129 point.y -= bounds.Height() / 2.0 - 6.0;
130 point.x += (bounds.Width() - StringWidth(field)) / 2;
131 SetHighUIColor(B_PANEL_TEXT_COLOR);
132 DrawString(field, point);
136 float
137 TTimeEdit::SeparatorWidth()
139 return 10.0f;
143 float
144 TTimeEdit::MinSectionWidth()
146 return be_plain_font->StringWidth("00");
150 void
151 TTimeEdit::SectionFocus(uint32 index)
153 fLastKeyDownTime = 0;
154 fFocus = index;
155 fHoldValue = _SectionValue(index);
156 Draw(Bounds());
160 void
161 TTimeEdit::SetTime(int32 hour, int32 minute, int32 second)
163 // make sure to update date upon overflow
164 if (hour == 0 && minute == 0 && second == 0)
165 fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME);
167 fTime.SetTime(BTime(hour, minute, second));
169 if (LockLooper()) {
170 _UpdateFields();
171 UnlockLooper();
174 Invalidate(Bounds());
178 void
179 TTimeEdit::DoUpPress()
181 if (fFocus == -1)
182 SectionFocus(0);
184 // update displayed value
185 fHoldValue += 1;
187 _CheckRange();
189 // send message to change time
190 DispatchMessage();
194 void
195 TTimeEdit::DoDownPress()
197 if (fFocus == -1)
198 SectionFocus(0);
200 // update display value
201 fHoldValue -= 1;
203 _CheckRange();
205 // send message to change time
206 DispatchMessage();
211 void
212 TTimeEdit::BuildDispatch(BMessage* message)
214 if (fFocus < 0 || fFocus >= fFieldCount)
215 return;
217 message->AddBool("time", true);
219 for (int32 index = 0; index < (int)fSectionCount; ++index) {
220 uint32 data = _SectionValue(index);
222 if (fFocus == index)
223 data = fHoldValue;
225 switch (fFields[index]) {
226 case B_DATE_ELEMENT_HOUR:
227 message->AddInt32("hour", data);
228 break;
230 case B_DATE_ELEMENT_MINUTE:
231 message->AddInt32("minute", data);
232 break;
234 case B_DATE_ELEMENT_SECOND:
235 message->AddInt32("second", data);
236 break;
238 default:
239 break;
245 void
246 TTimeEdit::_UpdateFields()
248 time_t time = fTime.Time_t();
250 if (fFieldPositions != NULL) {
251 free(fFieldPositions);
252 fFieldPositions = NULL;
254 fTimeFormat.Format(fText, fFieldPositions, fFieldPosCount, time,
255 B_MEDIUM_TIME_FORMAT);
257 if (fFields != NULL) {
258 free(fFields);
259 fFields = NULL;
261 fTimeFormat.GetTimeFields(fFields, fFieldCount, B_MEDIUM_TIME_FORMAT);
265 void
266 TTimeEdit::_CheckRange()
268 if (fFocus < 0 || fFocus >= fFieldCount)
269 return;
271 int32 value = fHoldValue;
272 switch (fFields[fFocus]) {
273 case B_DATE_ELEMENT_HOUR:
274 if (value > 23)
275 value = 0;
276 else if (value < 0)
277 value = 23;
279 fTime.SetTime(BTime(value, fTime.Time().Minute(),
280 fTime.Time().Second()));
281 break;
283 case B_DATE_ELEMENT_MINUTE:
284 if (value> 59)
285 value = 0;
286 else if (value < 0)
287 value = 59;
289 fTime.SetTime(BTime(fTime.Time().Hour(), value,
290 fTime.Time().Second()));
291 break;
293 case B_DATE_ELEMENT_SECOND:
294 if (value > 59)
295 value = 0;
296 else if (value < 0)
297 value = 59;
299 fTime.SetTime(BTime(fTime.Time().Hour(), fTime.Time().Minute(),
300 value));
301 break;
303 case B_DATE_ELEMENT_AM_PM:
304 value = fTime.Time().Hour();
305 if (value < 13)
306 value += 12;
307 else
308 value -= 12;
309 if (value == 24)
310 value = 0;
312 // modify hour value to reflect change in am/ pm
313 fTime.SetTime(BTime(value, fTime.Time().Minute(),
314 fTime.Time().Second()));
315 break;
317 default:
318 return;
321 fHoldValue = value;
322 Invalidate(Bounds());
326 bool
327 TTimeEdit::_IsValidDoubleDigit(int32 value)
329 if (fFocus < 0 || fFocus >= fFieldCount)
330 return false;
332 bool isInRange = false;
333 switch (fFields[fFocus]) {
334 case B_DATE_ELEMENT_HOUR:
335 if (value <= 23)
336 isInRange = true;
337 break;
339 case B_DATE_ELEMENT_MINUTE:
340 if (value <= 59)
341 isInRange = true;
342 break;
344 case B_DATE_ELEMENT_SECOND:
345 if (value <= 59)
346 isInRange = true;
347 break;
349 default:
350 break;
353 return isInRange;
357 int32
358 TTimeEdit::_SectionValue(int32 index) const
360 if (index < 0 || index >= fFieldCount)
361 return 0;
363 int32 value;
364 switch (fFields[index]) {
365 case B_DATE_ELEMENT_HOUR:
366 value = fTime.Time().Hour();
367 break;
369 case B_DATE_ELEMENT_MINUTE:
370 value = fTime.Time().Minute();
371 break;
373 case B_DATE_ELEMENT_SECOND:
374 value = fTime.Time().Second();
375 break;
377 default:
378 value = 0;
379 break;
382 return value;
386 float
387 TTimeEdit::PreferredHeight()
389 font_height fontHeight;
390 GetFontHeight(&fontHeight);
391 return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4);
395 // #pragma mark -
398 TDateEdit::TDateEdit(const char* name, uint32 sections)
400 TSectionEdit(name, sections),
401 fFields(NULL),
402 fFieldCount(0),
403 fFieldPositions(NULL),
404 fFieldPosCount(0)
406 InitView();
410 TDateEdit::~TDateEdit()
412 free(fFieldPositions);
413 free(fFields);
417 void
418 TDateEdit::KeyDown(const char* bytes, int32 numBytes)
420 TSectionEdit::KeyDown(bytes, numBytes);
422 // only accept valid input
423 int32 number = atoi(bytes);
424 if (number - 1 < 0)
425 return;
427 int32 section = FocusIndex();
428 if (section < 0 || section > 2)
429 return;
431 bigtime_t currentTime = system_time();
432 if (currentTime - fLastKeyDownTime < 1000000) {
433 int32 doubleDigit = number + fLastKeyDownInt * 10;
434 if (_IsValidDoubleDigit(doubleDigit))
435 number = doubleDigit;
436 fLastKeyDownTime = 0;
437 } else {
438 fLastKeyDownTime = currentTime;
439 fLastKeyDownInt = number;
442 // if year add 2000
444 if (fFields[section] == B_DATE_ELEMENT_YEAR) {
445 int32 oldCentury = int32(fHoldValue / 100) * 100;
446 if (number < 10 && oldCentury == 1900)
447 number += 70;
448 number += oldCentury;
451 // update display value
452 fHoldValue = number;
454 _CheckRange();
456 // send message to change time
457 DispatchMessage();
461 void
462 TDateEdit::InitView()
464 // make sure we call the base class method, as it
465 // will create the arrow bitmaps and the section list
466 fDate = BDate::CurrentDate(B_LOCAL_TIME);
467 _UpdateFields();
471 void
472 TDateEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus)
474 if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount)
475 return;
477 if (hasFocus)
478 SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR),
479 ViewColor(), 192));
480 else
481 SetLowColor(ViewColor());
483 BString field;
484 fText.CopyCharsInto(field, fFieldPositions[index * 2],
485 fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]);
487 BPoint point(bounds.LeftBottom());
488 point.y -= bounds.Height() / 2.0 - 6.0;
489 point.x += (bounds.Width() - StringWidth(field)) / 2;
490 SetHighUIColor(B_PANEL_TEXT_COLOR);
491 FillRect(bounds, B_SOLID_LOW);
492 DrawString(field, point);
496 void
497 TDateEdit::DrawSeparator(uint32 index, BRect bounds)
499 if (index >= 2)
500 return;
502 if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount)
503 return;
505 BString field;
506 fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1],
507 fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]);
509 BPoint point(bounds.LeftBottom());
510 point.y -= bounds.Height() / 2.0 - 6.0;
511 point.x += (bounds.Width() - StringWidth(field)) / 2;
512 SetHighUIColor(B_PANEL_TEXT_COLOR);
513 DrawString(field, point);
517 void
518 TDateEdit::SectionFocus(uint32 index)
520 fLastKeyDownTime = 0;
521 fFocus = index;
522 fHoldValue = _SectionValue(index);
523 Draw(Bounds());
527 float
528 TDateEdit::MinSectionWidth()
530 return be_plain_font->StringWidth("00");
534 float
535 TDateEdit::SeparatorWidth()
537 return 10.0f;
541 void
542 TDateEdit::SetDate(int32 year, int32 month, int32 day)
544 fDate.SetDate(year, month, day);
546 if (LockLooper()) {
547 _UpdateFields();
548 UnlockLooper();
551 Invalidate(Bounds());
555 void
556 TDateEdit::DoUpPress()
558 if (fFocus == -1)
559 SectionFocus(0);
561 // update displayed value
562 fHoldValue += 1;
564 _CheckRange();
566 // send message to change Date
567 DispatchMessage();
571 void
572 TDateEdit::DoDownPress()
574 if (fFocus == -1)
575 SectionFocus(0);
577 // update display value
578 fHoldValue -= 1;
580 _CheckRange();
582 // send message to change Date
583 DispatchMessage();
587 void
588 TDateEdit::BuildDispatch(BMessage* message)
590 if (fFocus < 0 || fFocus >= fFieldCount)
591 return;
593 message->AddBool("time", false);
595 for (int32 index = 0; index < (int)fSectionCount; ++index) {
596 uint32 data = _SectionValue(index);
598 if (fFocus == index)
599 data = fHoldValue;
601 switch (fFields[index]) {
602 case B_DATE_ELEMENT_MONTH:
603 message->AddInt32("month", data);
604 break;
606 case B_DATE_ELEMENT_DAY:
607 message->AddInt32("day", data);
608 break;
610 case B_DATE_ELEMENT_YEAR:
611 message->AddInt32("year", data);
612 break;
614 default:
615 break;
621 void
622 TDateEdit::_UpdateFields()
624 time_t time = BDateTime(fDate, BTime()).Time_t();
626 if (fFieldPositions != NULL) {
627 free(fFieldPositions);
628 fFieldPositions = NULL;
631 fDateFormat.Format(fText, fFieldPositions, fFieldPosCount, time,
632 B_SHORT_DATE_FORMAT);
634 if (fFields != NULL) {
635 free(fFields);
636 fFields = NULL;
638 fDateFormat.GetFields(fFields, fFieldCount, B_SHORT_DATE_FORMAT);
642 void
643 TDateEdit::_CheckRange()
645 if (fFocus < 0 || fFocus >= fFieldCount)
646 return;
648 int32 value = fHoldValue;
649 switch (fFields[fFocus]) {
650 case B_DATE_ELEMENT_DAY:
652 int32 days = fDate.DaysInMonth();
653 if (value > days)
654 value = 1;
655 else if (value < 1)
656 value = days;
658 fDate.SetDate(fDate.Year(), fDate.Month(), value);
659 break;
662 case B_DATE_ELEMENT_MONTH:
663 if (value > 12)
664 value = 1;
665 else if (value < 1)
666 value = 12;
668 fDate.SetDate(fDate.Year(), value, fDate.Day());
669 break;
671 case B_DATE_ELEMENT_YEAR:
672 // 2037 is the end of 32-bit UNIX time
673 if (value > 2037)
674 value = 2037;
675 else if (value < 1970)
676 value = 1970;
678 fDate.SetDate(value, fDate.Month(), fDate.Day());
679 break;
681 default:
682 return;
685 fHoldValue = value;
686 Draw(Bounds());
690 bool
691 TDateEdit::_IsValidDoubleDigit(int32 value)
693 if (fFocus < 0 || fFocus >= fFieldCount)
694 return false;
696 bool isInRange = false;
697 switch (fFields[fFocus]) {
698 case B_DATE_ELEMENT_DAY:
700 int32 days = fDate.DaysInMonth();
701 if (value >= 1 && value <= days)
702 isInRange = true;
703 break;
706 case B_DATE_ELEMENT_MONTH:
708 if (value >= 1 && value <= 12)
709 isInRange = true;
710 break;
713 case B_DATE_ELEMENT_YEAR:
715 int32 year = int32(fHoldValue / 100) * 100 + value;
716 if (year <= 2037 && year >= 1970)
717 isInRange = true;
718 break;
721 default:
722 break;
725 return isInRange;
729 int32
730 TDateEdit::_SectionValue(int32 index) const
732 if (index < 0 || index >= fFieldCount)
733 return 0;
735 int32 value = 0;
736 switch (fFields[index]) {
737 case B_DATE_ELEMENT_YEAR:
738 value = fDate.Year();
739 break;
741 case B_DATE_ELEMENT_MONTH:
742 value = fDate.Month();
743 break;
745 case B_DATE_ELEMENT_DAY:
746 value = fDate.Day();
747 break;
749 default:
750 break;
753 return value;
757 float
758 TDateEdit::PreferredHeight()
760 font_height fontHeight;
761 GetFontHeight(&fontHeight);
762 return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4);