/* * Copyright 2004-2011, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * McCall * Mike Berg * Julun * Clemens * Adrien Destugues * Hamish Morrison */ #include "DateTimeEdit.h" #include #include #include #include #include #include #include #include namespace BPrivate { const uint32 kArrowAreaWidth = 16; TimeEdit::TimeEdit(const char* name, uint32 sections, BMessage* message) : SectionEdit(name, sections, message), fLastKeyDownTime(0), fFields(NULL), fFieldCount(0), fFieldPositions(NULL), fFieldPosCount(0) { InitView(); } TimeEdit::~TimeEdit() { free(fFieldPositions); free(fFields); } void TimeEdit::KeyDown(const char* bytes, int32 numBytes) { if (IsEnabled() == false) return; SectionEdit::KeyDown(bytes, numBytes); // only accept valid input int32 number = atoi(bytes); if (number < 0 || bytes[0] < '0') return; int32 section = FocusIndex(); if (section < 0 || section > 2) return; bigtime_t currentTime = system_time(); if (currentTime - fLastKeyDownTime < 1000000) { int32 doubleDigit = number + fLastKeyDownInt * 10; if (_IsValidDoubleDigit(doubleDigit)) number = doubleDigit; fLastKeyDownTime = 0; } else { fLastKeyDownTime = currentTime; fLastKeyDownInt = number; } // update display value fHoldValue = number; _CheckRange(); _UpdateFields(); // send message to change time Invoke(); } void TimeEdit::InitView() { // make sure we call the base class method, as it // will create the arrow bitmaps and the section list fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME); _UpdateFields(); } void TimeEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus) { if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount) return; if (hasFocus) SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR), ui_color(B_DOCUMENT_BACKGROUND_COLOR), 192)); else SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR); BString field; fText.CopyCharsInto(field, fFieldPositions[index * 2], fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]); BPoint point(bounds.LeftBottom()); point.y -= bounds.Height() / 2.0 - 6.0; point.x += (bounds.Width() - StringWidth(field)) / 2; FillRect(bounds, B_SOLID_LOW); DrawString(field, point); } void TimeEdit::DrawSeparator(uint32 index, BRect bounds) { if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount) return; BString field; fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1], fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]); BPoint point(bounds.LeftBottom()); point.y -= bounds.Height() / 2.0 - 6.0; point.x += (bounds.Width() - StringWidth(field)) / 2; DrawString(field, point); } float TimeEdit::SeparatorWidth() { return 10.0f; } float TimeEdit::MinSectionWidth() { return be_plain_font->StringWidth("00"); } void TimeEdit::SectionFocus(uint32 index) { fLastKeyDownTime = 0; fFocus = index; fHoldValue = _SectionValue(index); Draw(Bounds()); } void TimeEdit::SetTime(int32 hour, int32 minute, int32 second) { // make sure to update date upon overflow if (hour == 0 && minute == 0 && second == 0) fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME); fTime.SetTime(BTime(hour, minute, second)); if (LockLooper()) { _UpdateFields(); UnlockLooper(); } Invalidate(Bounds()); } BTime TimeEdit::GetTime() { return fTime.Time(); } void TimeEdit::DoUpPress() { if (fFocus == -1) SectionFocus(0); // update displayed value fHoldValue += 1; _CheckRange(); _UpdateFields(); // send message to change time Invoke(); } void TimeEdit::DoDownPress() { if (fFocus == -1) SectionFocus(0); // update display value fHoldValue -= 1; _CheckRange(); _UpdateFields(); Invoke(); } void TimeEdit::PopulateMessage(BMessage* message) { if (fFocus < 0 || fFocus >= fFieldCount) return; message->AddBool("time", true); message->AddInt32("hour", fTime.Time().Hour()); message->AddInt32("minute", fTime.Time().Minute()); message->AddInt32("second", fTime.Time().Second()); } void TimeEdit::_UpdateFields() { time_t time = fTime.Time_t(); if (fFieldPositions != NULL) { free(fFieldPositions); fFieldPositions = NULL; } fTimeFormat.Format(fText, fFieldPositions, fFieldPosCount, time, B_MEDIUM_TIME_FORMAT); if (fFields != NULL) { free(fFields); fFields = NULL; } fTimeFormat.GetTimeFields(fFields, fFieldCount, B_MEDIUM_TIME_FORMAT); } void TimeEdit::_CheckRange() { if (fFocus < 0 || fFocus >= fFieldCount) return; int32 value = fHoldValue; switch (fFields[fFocus]) { case B_DATE_ELEMENT_HOUR: if (value > 23) value = 0; else if (value < 0) value = 23; fTime.SetTime(BTime(value, fTime.Time().Minute(), fTime.Time().Second())); break; case B_DATE_ELEMENT_MINUTE: if (value> 59) value = 0; else if (value < 0) value = 59; fTime.SetTime(BTime(fTime.Time().Hour(), value, fTime.Time().Second())); break; case B_DATE_ELEMENT_SECOND: if (value > 59) value = 0; else if (value < 0) value = 59; fTime.SetTime(BTime(fTime.Time().Hour(), fTime.Time().Minute(), value)); break; case B_DATE_ELEMENT_AM_PM: value = fTime.Time().Hour(); if (value < 13) value += 12; else value -= 12; if (value == 24) value = 0; // modify hour value to reflect change in am/ pm fTime.SetTime(BTime(value, fTime.Time().Minute(), fTime.Time().Second())); break; default: return; } fHoldValue = value; Invalidate(Bounds()); } bool TimeEdit::_IsValidDoubleDigit(int32 value) { if (fFocus < 0 || fFocus >= fFieldCount) return false; bool isInRange = false; switch (fFields[fFocus]) { case B_DATE_ELEMENT_HOUR: if (value <= 23) isInRange = true; break; case B_DATE_ELEMENT_MINUTE: if (value <= 59) isInRange = true; break; case B_DATE_ELEMENT_SECOND: if (value <= 59) isInRange = true; break; default: break; } return isInRange; } int32 TimeEdit::_SectionValue(int32 index) const { if (index < 0 || index >= fFieldCount) return 0; int32 value; switch (fFields[index]) { case B_DATE_ELEMENT_HOUR: value = fTime.Time().Hour(); break; case B_DATE_ELEMENT_MINUTE: value = fTime.Time().Minute(); break; case B_DATE_ELEMENT_SECOND: value = fTime.Time().Second(); break; default: value = 0; break; } return value; } float TimeEdit::PreferredHeight() { font_height fontHeight; GetFontHeight(&fontHeight); return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4); } // #pragma mark - DateEdit::DateEdit(const char* name, uint32 sections, BMessage* message) : SectionEdit(name, sections, message), fFields(NULL), fFieldCount(0), fFieldPositions(NULL), fFieldPosCount(0) { InitView(); } DateEdit::~DateEdit() { free(fFieldPositions); free(fFields); } void DateEdit::KeyDown(const char* bytes, int32 numBytes) { if (IsEnabled() == false) return; SectionEdit::KeyDown(bytes, numBytes); // only accept valid input int32 number = atoi(bytes); if (number < 0 || bytes[0] < '0') return; int32 section = FocusIndex(); if (section < 0 || section > 2) return; bigtime_t currentTime = system_time(); if (currentTime - fLastKeyDownTime < 1000000) { int32 doubleDigit = number + fLastKeyDownInt * 10; if (_IsValidDoubleDigit(doubleDigit)) number = doubleDigit; fLastKeyDownTime = 0; } else { fLastKeyDownTime = currentTime; fLastKeyDownInt = number; } // if year add 2000 if (fFields[section] == B_DATE_ELEMENT_YEAR) { int32 oldCentury = int32(fHoldValue / 100) * 100; if (number < 10 && oldCentury == 1900) number += 70; number += oldCentury; } fHoldValue = number; // update display value _CheckRange(); _UpdateFields(); // send message to change time Invoke(); } void DateEdit::InitView() { // make sure we call the base class method, as it // will create the arrow bitmaps and the section list fDate = BDate::CurrentDate(B_LOCAL_TIME); _UpdateFields(); } void DateEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus) { if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount) return; if (hasFocus) SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR), ui_color(B_DOCUMENT_BACKGROUND_COLOR), 192)); else SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR); BString field; fText.CopyCharsInto(field, fFieldPositions[index * 2], fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]); BPoint point(bounds.LeftBottom()); point.y -= bounds.Height() / 2.0 - 6.0; point.x += (bounds.Width() - StringWidth(field)) / 2; FillRect(bounds, B_SOLID_LOW); DrawString(field, point); } void DateEdit::DrawSeparator(uint32 index, BRect bounds) { if (index >= 2) return; if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount) return; BString field; fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1], fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]); BPoint point(bounds.LeftBottom()); point.y -= bounds.Height() / 2.0 - 6.0; point.x += (bounds.Width() - StringWidth(field)) / 2; DrawString(field, point); } void DateEdit::SectionFocus(uint32 index) { fLastKeyDownTime = 0; fFocus = index; fHoldValue = _SectionValue(index); Draw(Bounds()); } float DateEdit::MinSectionWidth() { return be_plain_font->StringWidth("00"); } float DateEdit::SeparatorWidth() { return 10.0f; } void DateEdit::SetDate(int32 year, int32 month, int32 day) { fDate.SetDate(year, month, day); if (LockLooper()) { _UpdateFields(); UnlockLooper(); } Invalidate(Bounds()); } BDate DateEdit::GetDate() { return fDate; } void DateEdit::DoUpPress() { if (fFocus == -1) SectionFocus(0); // update displayed value fHoldValue += 1; _CheckRange(); _UpdateFields(); // send message to change Date Invoke(); } void DateEdit::DoDownPress() { if (fFocus == -1) SectionFocus(0); // update display value fHoldValue -= 1; _CheckRange(); _UpdateFields(); // send message to change Date Invoke(); } void DateEdit::PopulateMessage(BMessage* message) { if (fFocus < 0 || fFocus >= fFieldCount) return; message->AddBool("time", false); message->AddInt32("year", fDate.Year()); message->AddInt32("month", fDate.Month()); message->AddInt32("day", fDate.Day()); } void DateEdit::_UpdateFields() { time_t time = BDateTime(fDate, BTime()).Time_t(); if (fFieldPositions != NULL) { free(fFieldPositions); fFieldPositions = NULL; } fDateFormat.Format(fText, fFieldPositions, fFieldPosCount, time, B_SHORT_DATE_FORMAT); if (fFields != NULL) { free(fFields); fFields = NULL; } fDateFormat.GetFields(fFields, fFieldCount, B_SHORT_DATE_FORMAT); } void DateEdit::_CheckRange() { if (fFocus < 0 || fFocus >= fFieldCount) return; int32 value = fHoldValue; switch (fFields[fFocus]) { case B_DATE_ELEMENT_DAY: { int32 days = fDate.DaysInMonth(); if (value > days) value = 1; else if (value < 1) value = days; fDate.SetDate(fDate.Year(), fDate.Month(), value); break; } case B_DATE_ELEMENT_MONTH: { if (value > 12) value = 1; else if (value < 1) value = 12; int32 day = fDate.Day(); fDate.SetDate(fDate.Year(), value, 1); // changing between months with differing amounts of days while (day > fDate.DaysInMonth()) day--; fDate.SetDate(fDate.Year(), value, day); break; } case B_DATE_ELEMENT_YEAR: fDate.SetDate(value, fDate.Month(), fDate.Day()); break; default: return; } fHoldValue = value; Invalidate(Bounds()); } bool DateEdit::_IsValidDoubleDigit(int32 value) { if (fFocus < 0 || fFocus >= fFieldCount) return false; bool isInRange = false; switch (fFields[fFocus]) { case B_DATE_ELEMENT_DAY: { int32 days = fDate.DaysInMonth(); if (value >= 1 && value <= days) isInRange = true; break; } case B_DATE_ELEMENT_MONTH: { if (value >= 1 && value <= 12) isInRange = true; break; } case B_DATE_ELEMENT_YEAR: { int32 year = int32(fHoldValue / 100) * 100 + value; if (year >= 2000) isInRange = true; break; } default: break; } return isInRange; } int32 DateEdit::_SectionValue(int32 index) const { if (index < 0 || index >= fFieldCount) return 0; int32 value = 0; switch (fFields[index]) { case B_DATE_ELEMENT_YEAR: value = fDate.Year(); break; case B_DATE_ELEMENT_MONTH: value = fDate.Month(); break; case B_DATE_ELEMENT_DAY: value = fDate.Day(); break; default: break; } return value; } float DateEdit::PreferredHeight() { font_height fontHeight; GetFontHeight(&fontHeight); return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4); } // #pragma mark - SectionEdit::SectionEdit(const char* name, uint32 sections, BMessage* message) : BControl(name, NULL, message, B_WILL_DRAW | B_NAVIGABLE), fFocus(-1), fSectionCount(sections), fHoldValue(0) { } SectionEdit::~SectionEdit() { } void SectionEdit::AttachedToWindow() { BControl::AttachedToWindow(); // Low colors are set in Draw() methods. SetHighUIColor(B_DOCUMENT_TEXT_COLOR); } void SectionEdit::Draw(BRect updateRect) { DrawBorder(updateRect); for (uint32 idx = 0; idx < fSectionCount; idx++) { DrawSection(idx, FrameForSection(idx), ((uint32)fFocus == idx) && IsFocus()); if (idx < fSectionCount - 1) DrawSeparator(idx, FrameForSeparator(idx)); } } void SectionEdit::MouseDown(BPoint where) { if (IsEnabled() == false) return; MakeFocus(true); if (fUpRect.Contains(where)) DoUpPress(); else if (fDownRect.Contains(where)) DoDownPress(); else if (fSectionCount > 0) { for (uint32 idx = 0; idx < fSectionCount; idx++) { if (FrameForSection(idx).Contains(where)) { SectionFocus(idx); return; } } } } BSize SectionEdit::MaxSize() { return BLayoutUtils::ComposeSize(ExplicitMaxSize(), BSize(B_SIZE_UNLIMITED, PreferredHeight())); } BSize SectionEdit::MinSize() { BSize minSize; minSize.height = PreferredHeight(); minSize.width = (SeparatorWidth() + MinSectionWidth()) * fSectionCount; return BLayoutUtils::ComposeSize(ExplicitMinSize(), minSize); } BSize SectionEdit::PreferredSize() { return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize()); } BRect SectionEdit::FrameForSection(uint32 index) { BRect area = SectionArea(); float sepWidth = SeparatorWidth(); float width = (area.Width() - sepWidth * (fSectionCount - 1)) / fSectionCount; area.left += index * (width + sepWidth); area.right = area.left + width; return area; } BRect SectionEdit::FrameForSeparator(uint32 index) { BRect area = SectionArea(); float sepWidth = SeparatorWidth(); float width = (area.Width() - sepWidth * (fSectionCount - 1)) / fSectionCount; area.left += (index + 1) * width + index * sepWidth; area.right = area.left + sepWidth; return area; } void SectionEdit::MakeFocus(bool focused) { if (focused == IsFocus()) return; BControl::MakeFocus(focused); if (fFocus == -1) SectionFocus(0); else SectionFocus(fFocus); } void SectionEdit::KeyDown(const char* bytes, int32 numbytes) { if (IsEnabled() == false) return; if (fFocus == -1) SectionFocus(0); switch (bytes[0]) { case B_LEFT_ARROW: fFocus -= 1; if (fFocus < 0) fFocus = fSectionCount - 1; SectionFocus(fFocus); break; case B_RIGHT_ARROW: fFocus += 1; if ((uint32)fFocus >= fSectionCount) fFocus = 0; SectionFocus(fFocus); break; case B_UP_ARROW: DoUpPress(); break; case B_DOWN_ARROW: DoDownPress(); break; default: BControl::KeyDown(bytes, numbytes); break; } Draw(Bounds()); } status_t SectionEdit::Invoke(BMessage* message) { if (message == NULL) message = Message(); if (message == NULL) return BControl::Invoke(NULL); BMessage clone(*message); PopulateMessage(&clone); return BControl::Invoke(&clone); } uint32 SectionEdit::CountSections() const { return fSectionCount; } int32 SectionEdit::FocusIndex() const { return fFocus; } BRect SectionEdit::SectionArea() const { BRect sectionArea = Bounds().InsetByCopy(2, 2); sectionArea.right -= kArrowAreaWidth; return sectionArea; } void SectionEdit::DrawBorder(const BRect& updateRect) { BRect bounds(Bounds()); bool showFocus = (IsFocus() && Window() && Window()->IsActive()); be_control_look->DrawTextControlBorder(this, bounds, updateRect, ViewColor(), showFocus ? BControlLook::B_FOCUSED : 0); SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR); FillRect(bounds, B_SOLID_LOW); // draw up/down control bounds.left = bounds.right - kArrowAreaWidth; bounds.right = Bounds().right - 2; fUpRect.Set(bounds.left + 3, bounds.top + 2, bounds.right, bounds.bottom / 2.0); fDownRect = fUpRect.OffsetByCopy(0, fUpRect.Height() + 2); BPoint middle(floorf(fUpRect.left + fUpRect.Width() / 2), fUpRect.top + 1); BPoint left(fUpRect.left + 3, fUpRect.bottom - 1); BPoint right(left.x + 2 * (middle.x - left.x), fUpRect.bottom - 1); SetPenSize(2); if (updateRect.Intersects(fUpRect)) { FillRect(fUpRect, B_SOLID_LOW); BeginLineArray(2); AddLine(left, middle, HighColor()); AddLine(middle, right, HighColor()); EndLineArray(); } if (updateRect.Intersects(fDownRect)) { middle.y = fDownRect.bottom - 1; left.y = right.y = fDownRect.top + 1; FillRect(fDownRect, B_SOLID_LOW); BeginLineArray(2); AddLine(left, middle, HighColor()); AddLine(middle, right, HighColor()); EndLineArray(); } SetPenSize(1); } } // namespace BPrivate