From 7af097b973b6939efe12e585cfc1c10f445d30b3 Mon Sep 17 00:00:00 2001 From: Adrien Destugues Date: Sat, 17 Jul 2010 12:24:57 +0000 Subject: [PATCH] * Add FormatDate variant returning fields positions and associated DateFields returning their identifiers * Use them in the time preflet to display and edit the date in a local-aware way git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@37551 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/os/locale/Country.h | 21 ++- src/kits/locale/Country.cpp | 150 +++++++++++-------- src/preferences/time/DateTimeEdit.cpp | 207 +++++++++++++++++++------- 3 files changed, 255 insertions(+), 123 deletions(-) diff --git a/headers/os/locale/Country.h b/headers/os/locale/Country.h index 7b86dcd41e..2a10b432d4 100644 --- a/headers/os/locale/Country.h +++ b/headers/os/locale/Country.h @@ -21,11 +21,14 @@ enum { }; typedef enum { - B_INVALID = B_BAD_DATA, - B_AM_PM = 0, - B_HOUR, - B_MINUTE, - B_SECOND + B_DATE_ELEMENT_INVALID = B_BAD_DATA, + B_DATE_ELEMENT_YEAR = 0, + B_DATE_ELEMENT_MONTH, + B_DATE_ELEMENT_DAY, + B_DATE_ELEMENT_AM_PM, + B_DATE_ELEMENT_HOUR, + B_DATE_ELEMENT_MINUTE, + B_DATE_ELEMENT_SECOND } BDateElement; @@ -47,6 +50,9 @@ class BCountry { bool longFormat); virtual void FormatDate(BString* string, time_t time, bool longFormat); + status_t FormatDate(BString* string, int*& fieldPositions, + int& fieldCount, time_t time, bool longFormat); + virtual void FormatTime(char* string, size_t maxSize, time_t time, bool longFormat); virtual void FormatTime(BString* string, time_t time, @@ -55,6 +61,8 @@ class BCountry { int& fieldCount, time_t time, bool longFormat); status_t TimeFields(BDateElement*& fields, int& fieldCount, bool longFormat); + status_t DateFields(BDateElement*& fields, int& fieldCount, + bool longFormat); bool DateFormat(BString&, bool longFormat) const; void SetDateFormat(const char* formatString, @@ -62,8 +70,6 @@ class BCountry { void SetTimeFormat(const char* formatString, bool longFormat = true); bool TimeFormat(BString&, bool longFormat) const; - const char* DateSeparator() const; - const char* TimeSeparator() const; int StartOfWeek(); @@ -103,7 +109,6 @@ class BCountry { icu_44::DateFormat* fICUShortDateFormatter; icu_44::DateFormat* fICULongTimeFormatter; icu_44::DateFormat* fICUShortTimeFormatter; - const char** fStrings; icu_44::Locale* fICULocale; }; diff --git a/src/kits/locale/Country.cpp b/src/kits/locale/Country.cpp index b22f1344ad..2b029ee6f4 100644 --- a/src/kits/locale/Country.cpp +++ b/src/kits/locale/Country.cpp @@ -35,29 +35,7 @@ using BPrivate::B_WEEK_START_MONDAY; using BPrivate::B_WEEK_START_SUNDAY; -const char* gStrings[] = { - // date/time format - "", - "", - // short date/time format - "", - "", - // am/pm string - "AM", - "PM", - // separators - ".", - ":", - - // currency/monetary - "." - "," -}; - - BCountry::BCountry(const char* languageCode, const char* countryCode) - : - fStrings(gStrings) { fICULocale = new ICU_VERSION::Locale(languageCode, countryCode); fICULongDateFormatter = DateFormat::createDateInstance( @@ -72,8 +50,6 @@ BCountry::BCountry(const char* languageCode, const char* countryCode) BCountry::BCountry(const char* languageAndCountryCode) - : - fStrings(gStrings) { fICULocale = new ICU_VERSION::Locale(languageAndCountryCode); fICULongDateFormatter = DateFormat::createDateInstance( @@ -138,17 +114,6 @@ BCountry::GetIcon(BBitmap* result) } -// TODO use ICU backend keywords instead -const char* -BCountry::GetString(uint32 id) const -{ - if (id < B_COUNTRY_STRINGS_BASE || id >= B_NUM_COUNTRY_STRINGS) - return NULL; - - return gStrings[id - B_COUNTRY_STRINGS_BASE]; -} - - void BCountry::FormatDate(char* string, size_t maxSize, time_t time, bool longFormat) { @@ -175,6 +140,45 @@ BCountry::FormatDate(BString *string, time_t time, bool longFormat) } +status_t +BCountry::FormatDate(BString* string, int*& fieldPositions, int& fieldCount, + time_t time, bool longFormat) +{ + fieldPositions = NULL; + UErrorCode error = U_ZERO_ERROR; + ICU_VERSION::DateFormat* dateFormatter; + ICU_VERSION::FieldPositionIterator positionIterator; + dateFormatter = longFormat ? fICULongDateFormatter : fICUShortDateFormatter; + UnicodeString ICUString; + ICUString = dateFormatter->format((UDate)time * 1000, ICUString, + &positionIterator, error); + + if (error != U_ZERO_ERROR) + return B_ERROR; + + ICU_VERSION::FieldPosition field; + std::vector fieldPosStorage; + fieldCount = 0; + while (positionIterator.next(field)) { + fieldPosStorage.push_back(field.getBeginIndex()); + fieldPosStorage.push_back(field.getEndIndex()); + fieldCount += 2; + } + + fieldPositions = (int*) malloc(fieldCount * sizeof(int)); + + for (int i = 0 ; i < fieldCount ; i++ ) + fieldPositions[i] = fieldPosStorage[i]; + + string->Truncate(0); + BStringByteSink stringConverter(string); + + ICUString.toUTF8(stringConverter); + + return B_OK; +} + + void BCountry::FormatTime(char* string, size_t maxSize, time_t time, bool longFormat) { @@ -272,21 +276,66 @@ BCountry::TimeFields(BDateElement*& fields, int& fieldCount, bool longFormat) case UDAT_HOUR_OF_DAY0_FIELD: case UDAT_HOUR1_FIELD: case UDAT_HOUR0_FIELD: - fields[i] = B_HOUR; + fields[i] = B_DATE_ELEMENT_HOUR; break; case UDAT_MINUTE_FIELD: - fields[i] = B_MINUTE; + fields[i] = B_DATE_ELEMENT_MINUTE; break; case UDAT_SECOND_FIELD: - fields[i] = B_SECOND; + fields[i] = B_DATE_ELEMENT_SECOND; break; case UDAT_AM_PM_FIELD: - fields[i] = B_AM_PM; + fields[i] = B_DATE_ELEMENT_AM_PM; break; default: - std::cout << "invalid field id " << fieldPosStorage[i] - << std::endl; - fields[i] = B_INVALID; + fields[i] = B_DATE_ELEMENT_INVALID; + break; + } + } + + return B_OK; +} + + +status_t +BCountry::DateFields(BDateElement*& fields, int& fieldCount, bool longFormat) +{ + fields = NULL; + UErrorCode error = U_ZERO_ERROR; + ICU_VERSION::DateFormat* dateFormatter; + ICU_VERSION::FieldPositionIterator positionIterator; + dateFormatter = longFormat ? fICULongDateFormatter : fICUShortDateFormatter; + UnicodeString ICUString; + time_t now; + ICUString = dateFormatter->format((UDate)time(&now) * 1000, ICUString, + &positionIterator, error); + + if (error != U_ZERO_ERROR) + return B_ERROR; + + ICU_VERSION::FieldPosition field; + std::vector fieldPosStorage; + fieldCount = 0; + while (positionIterator.next(field)) { + fieldPosStorage.push_back(field.getField()); + fieldCount ++; + } + + fields = (BDateElement*) malloc(fieldCount * sizeof(BDateElement)); + + for (int i = 0 ; i < fieldCount ; i++ ) { + switch (fieldPosStorage[i]) { + case UDAT_YEAR_FIELD: + fields[i] = B_DATE_ELEMENT_YEAR; + break; + case UDAT_MONTH_FIELD: + fields[i] = B_DATE_ELEMENT_MONTH; + break; + case UDAT_DATE_FIELD: + fields[i] = B_DATE_ELEMENT_DAY; + break; + default: + fields[i] = B_DATE_ELEMENT_INVALID; break; } } @@ -376,23 +425,6 @@ BCountry::StartOfWeek() } -// TODO find how to get it from ICU (setting it is ok, we use the pattern-string -// for that) -// Or remove this function ? -const char* -BCountry::DateSeparator() const -{ - return fStrings[B_DATE_SEPARATOR]; -} - - -const char* -BCountry::TimeSeparator() const -{ - return fStrings[B_TIME_SEPARATOR]; -} - - void BCountry::FormatNumber(char* string, size_t maxSize, double value) { diff --git a/src/preferences/time/DateTimeEdit.cpp b/src/preferences/time/DateTimeEdit.cpp index c61dd85c01..bdad0e9c00 100644 --- a/src/preferences/time/DateTimeEdit.cpp +++ b/src/preferences/time/DateTimeEdit.cpp @@ -121,7 +121,7 @@ TTimeEdit::DrawSection(uint32 index, bool hasFocus) free(fieldPositions); // calc and center text in section rect - float width = be_plain_font->StringWidth(field.String()); + float width = be_plain_font->StringWidth(field); BPoint offset(-((bounds.Width()- width) / 2.0) -1.0, bounds.Height() / 2.0 -6.0); @@ -280,13 +280,13 @@ TTimeEdit::BuildDispatch(BMessage *message) data = fHoldValue; switch(dateFormat[index]) { - case B_HOUR: + case B_DATE_ELEMENT_HOUR: message->AddInt32(fields[0], data); break; - case B_MINUTE: + case B_DATE_ELEMENT_MINUTE: message->AddInt32(fields[1], data); break; - case B_SECOND: + case B_DATE_ELEMENT_SECOND: message->AddInt32(fields[2], data); default: break; @@ -311,7 +311,7 @@ TTimeEdit::_CheckRange() return; } switch (fields[fFocus]) { - case B_HOUR: + case B_DATE_ELEMENT_HOUR: if (value > 23) value = 0; else if (value < 0) @@ -320,7 +320,7 @@ TTimeEdit::_CheckRange() fTime.SetTime(value, fTime.Minute(), fTime.Second()); break; - case B_MINUTE: + case B_DATE_ELEMENT_MINUTE: if (value> 59) value = 0; else if (value < 0) @@ -329,7 +329,7 @@ TTimeEdit::_CheckRange() fTime.SetTime(fTime.Hour(), value, fTime.Second()); break; - case B_SECOND: + case B_DATE_ELEMENT_SECOND: if (value > 59) value = 0; else if (value < 0) @@ -338,7 +338,7 @@ TTimeEdit::_CheckRange() fTime.SetTime(fTime.Hour(), fTime.Minute(), value); break; - case B_AM_PM: + case B_DATE_ELEMENT_AM_PM: value = fTime.Hour(); if (value < 13) value += 12; @@ -377,17 +377,17 @@ TTimeEdit::_IsValidDoubleDigi(int32 value) return false; } switch (fields[fFocus]) { - case B_HOUR: + case B_DATE_ELEMENT_HOUR: if (value <= 23) isInRange = true; break; - case B_MINUTE: + case B_DATE_ELEMENT_MINUTE: if (value <= 59) isInRange = true; break; - case B_SECOND: + case B_DATE_ELEMENT_SECOND: if (value <= 59) isInRange = true; break; @@ -416,15 +416,15 @@ TTimeEdit::_SectionValue(int32 index) const return 0; } switch (fields[index]) { - case B_HOUR: + case B_DATE_ELEMENT_HOUR: value = fTime.Hour(); break; - case B_MINUTE: + case B_DATE_ELEMENT_MINUTE: value = fTime.Minute(); break; - case B_SECOND: + case B_DATE_ELEMENT_SECOND: value = fTime.Second(); break; @@ -465,7 +465,7 @@ TDateEdit::KeyDown(const char* bytes, int32 numBytes) return; int32 section = FocusIndex(); - if (section < 1 || section > 2) + if (section < 0 || section > 2) return; bigtime_t currentTime = system_time(); @@ -480,12 +480,22 @@ TDateEdit::KeyDown(const char* bytes, int32 numBytes) } // if year add 2000 - if (section == 2) { + + BDateElement* dateFormat; + int fieldCount; + BCountry* here; + be_locale_roster->GetDefaultCountry(&here); + here->DateFields(dateFormat, fieldCount, false); + + if (dateFormat[section] == B_DATE_ELEMENT_YEAR) { int32 oldCentury = int32(fHoldValue / 100) * 100; if (number < 10 && oldCentury == 1900) number += 70; number += oldCentury; } + + free(dateFormat); + // update display value fHoldValue = number; @@ -509,7 +519,6 @@ TDateEdit::InitView() void TDateEdit::DrawSection(uint32 index, bool hasFocus) { - // user defined section drawing TSection *section = NULL; section = static_cast (fSectionList->ItemAt(index)); @@ -517,29 +526,42 @@ TDateEdit::DrawSection(uint32 index, bool hasFocus) return; BRect bounds = section->Frame(); - uint32 value = _SectionValue(index); + BDateTime dateTime(fDate, BTime()); SetLowColor(ViewColor()); - if (hasFocus) { + BString field; + if (hasFocus) SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); - value = fHoldValue; - } BString text; - if (index != 0) { - if (value < 10) text << "0"; - text << value; - } else - text.SetTo(fDate.LongMonthName(value)); + int* fieldPositions; + int fieldCount; + + BCountry* country; + be_locale_roster->GetDefaultCountry(&country); + country->FormatDate(&text, fieldPositions, fieldCount, dateTime.Time_t(), + false); + // TODO : this should be cached somehow to not redo it for each field + + if (index * 2 + 1 > fieldCount) { + free(fieldPositions); + return; + } + + text.CopyCharsInto(field, fieldPositions[index * 2], + fieldPositions[index * 2 + 1] - fieldPositions[index * 2]); + + free(fieldPositions); + // calc and center text in section rect - float width = StringWidth(text.String()); + float width = StringWidth(field); BPoint offset(-(bounds.Width() - width) / 2.0 - 1.0, (bounds.Height() / 2.0 - 6.0)); SetHighColor(0, 0, 0, 255); FillRect(bounds, B_SOLID_LOW); - DrawString(text.String(), bounds.LeftBottom() - offset); + DrawString(field, bounds.LeftBottom() - offset); } @@ -554,19 +576,43 @@ TDateEdit::DrawSeparator(uint32 index) BRect bounds = section->Frame(); float sepWidth = SeparatorWidth(); - float width = be_plain_font->StringWidth("/"); - - BPoint offset(-(sepWidth / 2.0 - width / 2.0) -1.0, - bounds.Height() / 2.0 -6.0); SetHighColor(0, 0, 0, 255); - DrawString("/", bounds.RightBottom() - offset); + + BString text; + int* fieldPositions; + int fieldCount; + + BCountry* country; + be_locale_roster->GetDefaultCountry(&country); + BDateTime dateTime(fDate, BTime()); + country->FormatDate(&text, fieldPositions, fieldCount, dateTime.Time_t(), + false); + // TODO : this should be cached somehow to not redo it for each field + + if (index * 2 + 2 > fieldCount) { + free(fieldPositions); + return; + } + + BString field; + text.CopyCharsInto(field, fieldPositions[index * 2 + 1], + fieldPositions[index * 2 + 2] - fieldPositions[index * 2 + 1]); + + free(fieldPositions); + + float width = be_plain_font->StringWidth(field); + BPoint offset(-((sepWidth - width) / 2.0) -1.0, bounds.Height() / 2.0 -6.0); + DrawString(field, bounds.RightBottom() - offset); } void TDateEdit::SetSections(BRect area) { + // TODO : we have to be more clever here, as the fields can move and have + // different sizes dependin on the locale + // create sections for (uint32 idx = 0; idx < fSectionCount; idx++) fSectionList->AddItem(new TSection(area)); @@ -664,15 +710,37 @@ TDateEdit::BuildDispatch(BMessage *message) message->AddBool("time", false); - int32 value; - for (int32 index = 0; index < fSectionList->CountItems(); ++index) { - value = _SectionValue(index); - - if (index == fFocus) - value = fHoldValue; - - message->AddInt32(fields[index], value); + BDateElement* dateFormat; + int fieldCount; + BCountry* here; + be_locale_roster->GetDefaultCountry(&here); + here->DateFields(dateFormat, fieldCount, false); + if (fFocus > fieldCount) { + free(dateFormat); + return; } + for (int32 index = 0; index < fSectionList->CountItems(); ++index) { + uint32 data = _SectionValue(index); + + if (fFocus == index) + data = fHoldValue; + + switch(dateFormat[index]) { + case B_DATE_ELEMENT_MONTH: + message->AddInt32(fields[0], data); + break; + case B_DATE_ELEMENT_DAY: + message->AddInt32(fields[1], data); + break; + case B_DATE_ELEMENT_YEAR: + message->AddInt32(fields[2], data); + break; + default: + break; + } + } + + free(dateFormat); } @@ -680,10 +748,18 @@ void TDateEdit::_CheckRange() { int32 value = fHoldValue; + BDateElement* fields; + int fieldCount; + BCountry* here; + be_locale_roster->GetDefaultCountry(&here); + here->DateFields(fields, fieldCount, false); + if (fFocus > fieldCount) { + free(fields); + return; + } - switch (fFocus) { - case 0: - // month + switch (fields[fFocus]) { + case B_DATE_ELEMENT_MONTH: if (value > 12) value = 1; else if (value < 1) @@ -692,9 +768,8 @@ TDateEdit::_CheckRange() fDate.SetDate(fDate.Year(), value, fDate.Day()); break; - case 1: + case B_DATE_ELEMENT_DAY: { - // day int32 days = fDate.DaysInMonth(); if (value > days) value = 1; @@ -705,8 +780,8 @@ TDateEdit::_CheckRange() break; } - case 2: - // year + case B_DATE_ELEMENT_YEAR: + // 2037 is the end of 32-bit UNIX time if (value > 2037) value = 2037; else if (value < 1970) @@ -716,9 +791,11 @@ TDateEdit::_CheckRange() break; default: + free(fields); return; } + free(fields); fHoldValue = value; Draw(Bounds()); } @@ -728,20 +805,27 @@ bool TDateEdit::_IsValidDoubleDigi(int32 value) { bool isInRange = false; + BDateElement* fields; + int fieldCount; + BCountry* here; + be_locale_roster->GetDefaultCountry(&here); + here->DateFields(fields, fieldCount, false); + if (fFocus > fieldCount) { + free(fields); + return false; + } int32 year = 0; - switch (fFocus) { - case 1: + switch (fields[fFocus]) { + case B_DATE_ELEMENT_DAY: { - // day int32 days = fDate.DaysInMonth(); if (value <= days) isInRange = true; break; } - case 2: + case B_DATE_ELEMENT_YEAR: { - // year year = int32(fHoldValue / 100) * 100 + value; if (year <= 2037 && year >= 1970) isInRange = true; @@ -749,9 +833,11 @@ TDateEdit::_IsValidDoubleDigi(int32 value) } default: + free(fields); return isInRange; } + free(fields); return isInRange; } @@ -760,12 +846,21 @@ int32 TDateEdit::_SectionValue(int32 index) const { int32 value = 0; - switch (index) { - case 0: + BDateElement* fields; + int fieldCount; + BCountry* here; + be_locale_roster->GetDefaultCountry(&here); + here->DateFields(fields, fieldCount, false); + if (index > fieldCount) { + free(fields); + return 0; + } + switch (fields[index]) { + case B_DATE_ELEMENT_MONTH: value = fDate.Month(); break; - case 1: + case B_DATE_ELEMENT_DAY: value = fDate.Day(); break;