* 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
This commit is contained in:
Adrien Destugues
2010-07-17 12:24:57 +00:00
parent a636d1e365
commit 7af097b973
3 changed files with 255 additions and 123 deletions
+13 -8
View File
@@ -21,11 +21,14 @@ enum {
}; };
typedef enum { typedef enum {
B_INVALID = B_BAD_DATA, B_DATE_ELEMENT_INVALID = B_BAD_DATA,
B_AM_PM = 0, B_DATE_ELEMENT_YEAR = 0,
B_HOUR, B_DATE_ELEMENT_MONTH,
B_MINUTE, B_DATE_ELEMENT_DAY,
B_SECOND B_DATE_ELEMENT_AM_PM,
B_DATE_ELEMENT_HOUR,
B_DATE_ELEMENT_MINUTE,
B_DATE_ELEMENT_SECOND
} BDateElement; } BDateElement;
@@ -47,6 +50,9 @@ class BCountry {
bool longFormat); bool longFormat);
virtual void FormatDate(BString* string, time_t time, virtual void FormatDate(BString* string, time_t time,
bool longFormat); 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, virtual void FormatTime(char* string, size_t maxSize, time_t time,
bool longFormat); bool longFormat);
virtual void FormatTime(BString* string, time_t time, virtual void FormatTime(BString* string, time_t time,
@@ -55,6 +61,8 @@ class BCountry {
int& fieldCount, time_t time, bool longFormat); int& fieldCount, time_t time, bool longFormat);
status_t TimeFields(BDateElement*& fields, int& fieldCount, status_t TimeFields(BDateElement*& fields, int& fieldCount,
bool longFormat); bool longFormat);
status_t DateFields(BDateElement*& fields, int& fieldCount,
bool longFormat);
bool DateFormat(BString&, bool longFormat) const; bool DateFormat(BString&, bool longFormat) const;
void SetDateFormat(const char* formatString, void SetDateFormat(const char* formatString,
@@ -62,8 +70,6 @@ class BCountry {
void SetTimeFormat(const char* formatString, void SetTimeFormat(const char* formatString,
bool longFormat = true); bool longFormat = true);
bool TimeFormat(BString&, bool longFormat) const; bool TimeFormat(BString&, bool longFormat) const;
const char* DateSeparator() const;
const char* TimeSeparator() const;
int StartOfWeek(); int StartOfWeek();
@@ -103,7 +109,6 @@ class BCountry {
icu_44::DateFormat* fICUShortDateFormatter; icu_44::DateFormat* fICUShortDateFormatter;
icu_44::DateFormat* fICULongTimeFormatter; icu_44::DateFormat* fICULongTimeFormatter;
icu_44::DateFormat* fICUShortTimeFormatter; icu_44::DateFormat* fICUShortTimeFormatter;
const char** fStrings;
icu_44::Locale* fICULocale; icu_44::Locale* fICULocale;
}; };
+91 -59
View File
@@ -35,29 +35,7 @@ using BPrivate::B_WEEK_START_MONDAY;
using BPrivate::B_WEEK_START_SUNDAY; 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) BCountry::BCountry(const char* languageCode, const char* countryCode)
:
fStrings(gStrings)
{ {
fICULocale = new ICU_VERSION::Locale(languageCode, countryCode); fICULocale = new ICU_VERSION::Locale(languageCode, countryCode);
fICULongDateFormatter = DateFormat::createDateInstance( fICULongDateFormatter = DateFormat::createDateInstance(
@@ -72,8 +50,6 @@ BCountry::BCountry(const char* languageCode, const char* countryCode)
BCountry::BCountry(const char* languageAndCountryCode) BCountry::BCountry(const char* languageAndCountryCode)
:
fStrings(gStrings)
{ {
fICULocale = new ICU_VERSION::Locale(languageAndCountryCode); fICULocale = new ICU_VERSION::Locale(languageAndCountryCode);
fICULongDateFormatter = DateFormat::createDateInstance( 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 void
BCountry::FormatDate(char* string, size_t maxSize, time_t time, bool longFormat) 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<int> 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 void
BCountry::FormatTime(char* string, size_t maxSize, time_t time, bool longFormat) 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_HOUR_OF_DAY0_FIELD:
case UDAT_HOUR1_FIELD: case UDAT_HOUR1_FIELD:
case UDAT_HOUR0_FIELD: case UDAT_HOUR0_FIELD:
fields[i] = B_HOUR; fields[i] = B_DATE_ELEMENT_HOUR;
break; break;
case UDAT_MINUTE_FIELD: case UDAT_MINUTE_FIELD:
fields[i] = B_MINUTE; fields[i] = B_DATE_ELEMENT_MINUTE;
break; break;
case UDAT_SECOND_FIELD: case UDAT_SECOND_FIELD:
fields[i] = B_SECOND; fields[i] = B_DATE_ELEMENT_SECOND;
break; break;
case UDAT_AM_PM_FIELD: case UDAT_AM_PM_FIELD:
fields[i] = B_AM_PM; fields[i] = B_DATE_ELEMENT_AM_PM;
break; break;
default: default:
std::cout << "invalid field id " << fieldPosStorage[i] fields[i] = B_DATE_ELEMENT_INVALID;
<< std::endl; break;
fields[i] = B_INVALID; }
}
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<int> 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; 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 void
BCountry::FormatNumber(char* string, size_t maxSize, double value) BCountry::FormatNumber(char* string, size_t maxSize, double value)
{ {
+151 -56
View File
@@ -121,7 +121,7 @@ TTimeEdit::DrawSection(uint32 index, bool hasFocus)
free(fieldPositions); free(fieldPositions);
// calc and center text in section rect // 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, BPoint offset(-((bounds.Width()- width) / 2.0) -1.0,
bounds.Height() / 2.0 -6.0); bounds.Height() / 2.0 -6.0);
@@ -280,13 +280,13 @@ TTimeEdit::BuildDispatch(BMessage *message)
data = fHoldValue; data = fHoldValue;
switch(dateFormat[index]) { switch(dateFormat[index]) {
case B_HOUR: case B_DATE_ELEMENT_HOUR:
message->AddInt32(fields[0], data); message->AddInt32(fields[0], data);
break; break;
case B_MINUTE: case B_DATE_ELEMENT_MINUTE:
message->AddInt32(fields[1], data); message->AddInt32(fields[1], data);
break; break;
case B_SECOND: case B_DATE_ELEMENT_SECOND:
message->AddInt32(fields[2], data); message->AddInt32(fields[2], data);
default: default:
break; break;
@@ -311,7 +311,7 @@ TTimeEdit::_CheckRange()
return; return;
} }
switch (fields[fFocus]) { switch (fields[fFocus]) {
case B_HOUR: case B_DATE_ELEMENT_HOUR:
if (value > 23) if (value > 23)
value = 0; value = 0;
else if (value < 0) else if (value < 0)
@@ -320,7 +320,7 @@ TTimeEdit::_CheckRange()
fTime.SetTime(value, fTime.Minute(), fTime.Second()); fTime.SetTime(value, fTime.Minute(), fTime.Second());
break; break;
case B_MINUTE: case B_DATE_ELEMENT_MINUTE:
if (value> 59) if (value> 59)
value = 0; value = 0;
else if (value < 0) else if (value < 0)
@@ -329,7 +329,7 @@ TTimeEdit::_CheckRange()
fTime.SetTime(fTime.Hour(), value, fTime.Second()); fTime.SetTime(fTime.Hour(), value, fTime.Second());
break; break;
case B_SECOND: case B_DATE_ELEMENT_SECOND:
if (value > 59) if (value > 59)
value = 0; value = 0;
else if (value < 0) else if (value < 0)
@@ -338,7 +338,7 @@ TTimeEdit::_CheckRange()
fTime.SetTime(fTime.Hour(), fTime.Minute(), value); fTime.SetTime(fTime.Hour(), fTime.Minute(), value);
break; break;
case B_AM_PM: case B_DATE_ELEMENT_AM_PM:
value = fTime.Hour(); value = fTime.Hour();
if (value < 13) if (value < 13)
value += 12; value += 12;
@@ -377,17 +377,17 @@ TTimeEdit::_IsValidDoubleDigi(int32 value)
return false; return false;
} }
switch (fields[fFocus]) { switch (fields[fFocus]) {
case B_HOUR: case B_DATE_ELEMENT_HOUR:
if (value <= 23) if (value <= 23)
isInRange = true; isInRange = true;
break; break;
case B_MINUTE: case B_DATE_ELEMENT_MINUTE:
if (value <= 59) if (value <= 59)
isInRange = true; isInRange = true;
break; break;
case B_SECOND: case B_DATE_ELEMENT_SECOND:
if (value <= 59) if (value <= 59)
isInRange = true; isInRange = true;
break; break;
@@ -416,15 +416,15 @@ TTimeEdit::_SectionValue(int32 index) const
return 0; return 0;
} }
switch (fields[index]) { switch (fields[index]) {
case B_HOUR: case B_DATE_ELEMENT_HOUR:
value = fTime.Hour(); value = fTime.Hour();
break; break;
case B_MINUTE: case B_DATE_ELEMENT_MINUTE:
value = fTime.Minute(); value = fTime.Minute();
break; break;
case B_SECOND: case B_DATE_ELEMENT_SECOND:
value = fTime.Second(); value = fTime.Second();
break; break;
@@ -465,7 +465,7 @@ TDateEdit::KeyDown(const char* bytes, int32 numBytes)
return; return;
int32 section = FocusIndex(); int32 section = FocusIndex();
if (section < 1 || section > 2) if (section < 0 || section > 2)
return; return;
bigtime_t currentTime = system_time(); bigtime_t currentTime = system_time();
@@ -480,12 +480,22 @@ TDateEdit::KeyDown(const char* bytes, int32 numBytes)
} }
// if year add 2000 // 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; int32 oldCentury = int32(fHoldValue / 100) * 100;
if (number < 10 && oldCentury == 1900) if (number < 10 && oldCentury == 1900)
number += 70; number += 70;
number += oldCentury; number += oldCentury;
} }
free(dateFormat);
// update display value // update display value
fHoldValue = number; fHoldValue = number;
@@ -509,7 +519,6 @@ TDateEdit::InitView()
void void
TDateEdit::DrawSection(uint32 index, bool hasFocus) TDateEdit::DrawSection(uint32 index, bool hasFocus)
{ {
// user defined section drawing
TSection *section = NULL; TSection *section = NULL;
section = static_cast<TSection*> (fSectionList->ItemAt(index)); section = static_cast<TSection*> (fSectionList->ItemAt(index));
@@ -517,29 +526,42 @@ TDateEdit::DrawSection(uint32 index, bool hasFocus)
return; return;
BRect bounds = section->Frame(); BRect bounds = section->Frame();
uint32 value = _SectionValue(index); BDateTime dateTime(fDate, BTime());
SetLowColor(ViewColor()); SetLowColor(ViewColor());
if (hasFocus) { BString field;
if (hasFocus)
SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
value = fHoldValue;
}
BString text; BString text;
if (index != 0) { int* fieldPositions;
if (value < 10) text << "0"; int fieldCount;
text << value;
} else BCountry* country;
text.SetTo(fDate.LongMonthName(value)); 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 // 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, BPoint offset(-(bounds.Width() - width) / 2.0 - 1.0,
(bounds.Height() / 2.0 - 6.0)); (bounds.Height() / 2.0 - 6.0));
SetHighColor(0, 0, 0, 255); SetHighColor(0, 0, 0, 255);
FillRect(bounds, B_SOLID_LOW); 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(); BRect bounds = section->Frame();
float sepWidth = SeparatorWidth(); 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); 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 void
TDateEdit::SetSections(BRect area) 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 // create sections
for (uint32 idx = 0; idx < fSectionCount; idx++) for (uint32 idx = 0; idx < fSectionCount; idx++)
fSectionList->AddItem(new TSection(area)); fSectionList->AddItem(new TSection(area));
@@ -664,15 +710,37 @@ TDateEdit::BuildDispatch(BMessage *message)
message->AddBool("time", false); message->AddBool("time", false);
int32 value; BDateElement* dateFormat;
for (int32 index = 0; index < fSectionList->CountItems(); ++index) { int fieldCount;
value = _SectionValue(index); BCountry* here;
be_locale_roster->GetDefaultCountry(&here);
if (index == fFocus) here->DateFields(dateFormat, fieldCount, false);
value = fHoldValue; if (fFocus > fieldCount) {
free(dateFormat);
message->AddInt32(fields[index], value); 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() TDateEdit::_CheckRange()
{ {
int32 value = fHoldValue; 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) { switch (fields[fFocus]) {
case 0: case B_DATE_ELEMENT_MONTH:
// month
if (value > 12) if (value > 12)
value = 1; value = 1;
else if (value < 1) else if (value < 1)
@@ -692,9 +768,8 @@ TDateEdit::_CheckRange()
fDate.SetDate(fDate.Year(), value, fDate.Day()); fDate.SetDate(fDate.Year(), value, fDate.Day());
break; break;
case 1: case B_DATE_ELEMENT_DAY:
{ {
// day
int32 days = fDate.DaysInMonth(); int32 days = fDate.DaysInMonth();
if (value > days) if (value > days)
value = 1; value = 1;
@@ -705,8 +780,8 @@ TDateEdit::_CheckRange()
break; break;
} }
case 2: case B_DATE_ELEMENT_YEAR:
// year // 2037 is the end of 32-bit UNIX time
if (value > 2037) if (value > 2037)
value = 2037; value = 2037;
else if (value < 1970) else if (value < 1970)
@@ -716,9 +791,11 @@ TDateEdit::_CheckRange()
break; break;
default: default:
free(fields);
return; return;
} }
free(fields);
fHoldValue = value; fHoldValue = value;
Draw(Bounds()); Draw(Bounds());
} }
@@ -728,20 +805,27 @@ bool
TDateEdit::_IsValidDoubleDigi(int32 value) TDateEdit::_IsValidDoubleDigi(int32 value)
{ {
bool isInRange = false; 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; int32 year = 0;
switch (fFocus) { switch (fields[fFocus]) {
case 1: case B_DATE_ELEMENT_DAY:
{ {
// day
int32 days = fDate.DaysInMonth(); int32 days = fDate.DaysInMonth();
if (value <= days) if (value <= days)
isInRange = true; isInRange = true;
break; break;
} }
case 2: case B_DATE_ELEMENT_YEAR:
{ {
// year
year = int32(fHoldValue / 100) * 100 + value; year = int32(fHoldValue / 100) * 100 + value;
if (year <= 2037 && year >= 1970) if (year <= 2037 && year >= 1970)
isInRange = true; isInRange = true;
@@ -749,9 +833,11 @@ TDateEdit::_IsValidDoubleDigi(int32 value)
} }
default: default:
free(fields);
return isInRange; return isInRange;
} }
free(fields);
return isInRange; return isInRange;
} }
@@ -760,12 +846,21 @@ int32
TDateEdit::_SectionValue(int32 index) const TDateEdit::_SectionValue(int32 index) const
{ {
int32 value = 0; int32 value = 0;
switch (index) { BDateElement* fields;
case 0: 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(); value = fDate.Month();
break; break;
case 1: case B_DATE_ELEMENT_DAY:
value = fDate.Day(); value = fDate.Day();
break; break;