* 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

View File

@ -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;
};

View File

@ -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<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
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<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;
}
}
@ -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)
{

View File

@ -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<TSection*> (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;