/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include "private/qcore_symbian_p.h" QT_BEGIN_NAMESPACE // Located in qlocale.cpp extern void getLangAndCountry(const QString &name, QLocale::Language &lang, QLocale::Country &cntry); static TExtendedLocale _s60Locale; // Type definitions for runtime resolved function pointers typedef void (*FormatFunc)(TTime&, TDes&, const TDesC&, const TLocale&); typedef TPtrC (*FormatSpecFunc)(TExtendedLocale&); // Runtime resolved functions static FormatFunc ptrTimeFormatL = NULL; static FormatSpecFunc ptrGetTimeFormatSpec = NULL; static FormatSpecFunc ptrGetLongDateFormatSpec = NULL; static FormatSpecFunc ptrGetShortDateFormatSpec = NULL; // Default functions if functions cannot be resolved static void defaultTimeFormatL(TTime&, TDes& des, const TDesC&, const TLocale&) { des.Zero(); } static TPtrC defaultFormatSpec(TExtendedLocale&) { return TPtrC(KNullDesC); } /* Definition of struct for mapping Symbian to ISO locale */ struct symbianToISO { int symbian_language; char iso_name[8]; }; /* Mapping from Symbian to ISO locale */ static const symbianToISO symbian_to_iso_list[] = { { ELangEnglish, "en_GB" }, { ELangFrench, "fr_FR" }, { ELangGerman, "de_DE" }, { ELangSpanish, "es_ES" }, { ELangItalian, "it_IT" }, { ELangSwedish, "sv_SE" }, { ELangDanish, "da_DK" }, { ELangNorwegian, "no_NO" }, { ELangFinnish, "fi_FI" }, { ELangAmerican, "en_US" }, { ELangPortuguese, "pt_PT" }, { ELangTurkish, "tr_TR" }, { ELangIcelandic, "is_IS" }, { ELangRussian, "ru_RU" }, { ELangHungarian, "hu_HU" }, { ELangDutch, "nl_NL" }, { ELangBelgianFlemish, "nl_BE" }, { ELangCzech, "cs_CZ" }, { ELangSlovak, "sk_SK" }, { ELangPolish, "pl_PL" }, { ELangSlovenian, "sl_SI" }, { ELangTaiwanChinese, "zh_TW" }, { ELangHongKongChinese, "zh_HK" }, { ELangPrcChinese, "zh_CN" }, { ELangJapanese, "ja_JP" }, { ELangThai, "th_TH" }, { ELangArabic, "ar_AE" }, { ELangTagalog, "tl_PH" }, { ELangBulgarian, "bg_BG" }, { ELangCatalan, "ca_ES" }, { ELangCroatian, "hr_HR" }, { ELangEstonian, "et_EE" }, { ELangFarsi, "fa_IR" }, { ELangCanadianFrench, "fr_CA" }, { ELangGreek, "el_GR" }, { ELangHebrew, "he_IL" }, { ELangHindi, "hi_IN" }, { ELangIndonesian, "id_ID" }, { ELangLatvian, "lv_LV" }, { ELangLithuanian, "lt_LT" }, { ELangMalay, "ms_MY" }, { ELangBrazilianPortuguese, "pt_BR" }, { ELangRomanian, "ro_RO" }, { ELangSerbian, "sr_YU" }, { ELangLatinAmericanSpanish, "es" }, { ELangUkrainian, "uk_UA" }, { ELangUrdu, "ur_PK" }, // India/Pakistan { ELangVietnamese, "vi_VN" }, #ifdef __E32LANG_H__ // 5.0 { ELangBasque, "eu_ES" }, { ELangGalician, "gl_ES" }, #endif #if !defined(__SERIES60_31__) { ELangEnglish_Apac, "en" }, { ELangEnglish_Taiwan, "en_TW" }, { ELangEnglish_HongKong, "en_HK" }, { ELangEnglish_Prc, "en_CN" }, { ELangEnglish_Japan, "en_JP"}, { ELangEnglish_Thailand, "en_TH" }, { ELangMalay_Apac, "ms" } #endif }; /*! Returns ISO name corresponding to the Symbian locale code \a sys_fmt. */ QByteArray qt_symbianLocaleName(int code) { //Number of Symbian to ISO locale mappings static const int symbian_to_iso_count = sizeof(symbian_to_iso_list)/sizeof(symbianToISO); int cmp = code - symbian_to_iso_list[0].symbian_language; if (cmp < 0) return 0; if (cmp == 0) return symbian_to_iso_list[0].iso_name; int begin = 0; int end = symbian_to_iso_count; while (end - begin > 1) { uint mid = (begin + end)/2; const symbianToISO *elt = symbian_to_iso_list + mid; int cmp = code - elt->symbian_language; if (cmp < 0) end = mid; else if (cmp > 0) begin = mid; else return elt->iso_name; } return 0; } // order is: normal, abbr, nmode, nmode+abbr static const char *us_locale_dep[] = { "MM", "dd", "yyyy", "MM", "dd", "M", "d", "yy", "M", "d", "MMMM", "dd", "yyyy", "MMMM", "dd", "MMM", "d", "yy", "MMM", "d" }; static const char *eu_locale_dep[] = { "dd", "MM", "yyyy", "dd", "MM", "d", "M", "yy", "d", "M", "dd", "MMMM", "yyyy", "dd", "MMMM", "d", "MMM", "yy", "d", "MMM" }; static const char *jp_locale_dep[] = { "yyyy", "MM", "dd", "MM", "dd", "yy", "M", "d", "M", "d", "yyyy", "MMMM", "dd", "MMMM", "dd", "yy", "MMM", "d", "MMM", "d" }; /*! Returns a Qt version of the given \a sys_fmt Symbian locale format string. */ static QString s60ToQtFormat(const QString &sys_fmt) { TLocale *locale = _s60Locale.GetLocale(); QString result; QString other; QString qtformatchars = QString::fromLatin1("adhmsyzAHM"); QChar c; int i = 0; bool open_escape = false; bool abbrev_next = false; bool locale_indep_ordering = false; bool minus_mode = false; bool plus_mode = false; bool n_mode = false; TTimeFormat tf = locale->TimeFormat(); while (i < sys_fmt.size()) { c = sys_fmt.at(i); // let formatting thru if (c.unicode() == '%') { // if we have gathered string, concat it if (!other.isEmpty()) { result += other; other.clear(); } // if we have open escape, end it if (open_escape) { result += QLatin1Char('\''); open_escape = false; } ++i; if (i >= sys_fmt.size()) break; c = sys_fmt.at(i); // process specials abbrev_next = c.unicode() == '*'; plus_mode = c.unicode() == '+'; minus_mode = c.unicode() == '-'; if (abbrev_next || plus_mode || minus_mode) { ++i; if (i >= sys_fmt.size()) break; c = sys_fmt.at(i); if (plus_mode || minus_mode) { // break on undefined plus/minus mode if (c.unicode() != 'A' && c.unicode() != 'B') break; } } switch (c.unicode()) { case 'F': { // locale indep mode on locale_indep_ordering = true; break; } case '/': { // date sep 0-3 ++i; if (i >= sys_fmt.size()) break; c = sys_fmt.at(i); if (c.isDigit() && c.digitValue() <= 3) { TChar s = locale->DateSeparator(c.digitValue()); TUint val = s; // some indexes return zero for empty if (val > 0) result += QChar(val); } break; } case 'D': { if (!locale_indep_ordering) break; if (!abbrev_next) result += QLatin1String("dd"); else result += QLatin1Char('d'); break; } case 'M': { if (!locale_indep_ordering) break; if (!n_mode) { if (!abbrev_next) result += QLatin1String("MM"); else result += QLatin1String("M"); } else { if (!abbrev_next) result += QLatin1String("MMMM"); else result += QLatin1String("MMM"); } break; } case 'N': { n_mode = true; if (!locale_indep_ordering) break; if (!abbrev_next) result += QLatin1String("MMMM"); else result += QLatin1String("MMM"); break; } case 'Y': { if (!locale_indep_ordering) break; if (!abbrev_next) result += QLatin1String("yyyy"); else result += QLatin1String("yy"); break; } case 'E': { if (!abbrev_next) result += QLatin1String("dddd"); else result += QLatin1String("ddd"); break; } case ':': { // timesep 0-3 ++i; if (i >= sys_fmt.size()) break; c = sys_fmt.at(i); if (c.isDigit() && c.digitValue() <= 3) { TChar s = locale->TimeSeparator(c.digitValue()); TUint val = s; // some indexes return zero for empty if (val > 0) result += QChar(val); } break; } case 'J': { if (tf == ETime24 && !abbrev_next) result += QLatin1String("hh"); else result += QLatin1Char('h'); break; } case 'H': { if (!abbrev_next) result += QLatin1String("hh"); else result += QLatin1Char('h'); break; } case 'I': { result += QLatin1Char('h'); break; } case 'T': { if (!abbrev_next) result += QLatin1String("mm"); else result += QLatin1Char('m'); break; } case 'S': { if (!abbrev_next) result += QLatin1String("ss"); else result += QLatin1Char('s'); break; } case 'B': { // only done for 12h clock if (tf == ETime24) break; } // fallthru to A case 'A': { // quickie to get capitalization, can't use s60 string as is because Qt 'hh' format's am/pm logic TAmPmName ampm = TAmPmName(); TChar first(ampm[0]); QString qtampm = QString::fromLatin1(first.IsUpper() ? "AP" : "ap"); int pos = locale->AmPmSymbolPosition(); if ((minus_mode && pos != ELocaleBefore) || (plus_mode && pos != ELocaleAfter)) break; if (!abbrev_next && locale->AmPmSpaceBetween()) { if (pos == ELocaleBefore) qtampm.append(QLatin1Char(' ')); else qtampm.prepend(QLatin1Char(' ')); } result += qtampm; } break; case '.': { // decimal sep TChar s = locale->DecimalSeparator(); TUint val = s; if (val > 0) result += QChar(val); } break; case 'C': { // six digits in s60, three digits in qt if (!abbrev_next) { result += QLatin1String("zzz"); } else { // next char is number from 0-6, how many digits to display ++i; if (i >= sys_fmt.size()) break; c = sys_fmt.at(i); if (c.isDigit()) { // try to match wanted digits QChar val(c.digitValue()); if (val >= 3) { result += QLatin1String("zzz"); } else if (val > 0) { result += QLatin1Char('z'); } } } break; } // these cases fallthru case '1': case '2': case '3': case '4': case '5': { // shouldn't parse these with %F if (locale_indep_ordering) break; TDateFormat df = locale->DateFormat(); const char **locale_dep; switch (df) { default: // fallthru to american case EDateAmerican: locale_dep = us_locale_dep; break; case EDateEuropean: locale_dep = eu_locale_dep; break; case EDateJapanese: locale_dep = jp_locale_dep; break; } int offset = 0; if (abbrev_next) offset += 5; if (n_mode) offset += 10; result += QLatin1String(locale_dep[offset + (c.digitValue()-1)]); break; } case '%': // fallthru percent { // any junk gets copied as is } default: { result += c; break; } case 'Z': // Qt doesn't support these :( case 'X': case 'W': { break; } } } else { // double any single quotes, don't begin escape if (c.unicode() == '\'') { // end open escape if (open_escape) { result += other; other.clear(); result += QLatin1Char('\''); open_escape = false; } other += c; } // gather chars and escape them in one go if any format chars are found if (!open_escape && qtformatchars.indexOf(c) != -1) { result += QLatin1Char('\''); open_escape = true; } other += c; } ++i; } if (!other.isEmpty()) result += other; if (open_escape) result += QLatin1Char('\''); return result; } /*! Retrieves Symbian locale decimal separator. */ static QString symbianDecimalPoint() { TLocale *locale = _s60Locale.GetLocale(); TChar decPoint = locale->DecimalSeparator(); int val = decPoint; return QChar(val); } /*! Retrieves Symbian locale group separator. */ static QString symbianGroupSeparator() { TLocale *locale = _s60Locale.GetLocale(); TChar grpSep = locale->ThousandsSeparator(); int val = grpSep; return QChar(val); } /*! Retrieves Symbian locale zero digit. */ static QString symbianZeroDigit() { TLocale *locale = _s60Locale.GetLocale(); // TDigitType enumeration value returned by TLocale // will always correspond to zero digit unicode value. TDigitType digit = locale->DigitType(); return QChar(digit); } /*! Retrieves a day name from Symbian locale. The \a day is an integer from 1 to 7. When \a short_format is true the method returns the day in short format. Otherwise it returns the day in a long format. */ static QString symbianDayName(int day, bool short_format) { day -= 1; if (day < 0 || day > 6) return QString(); if (short_format) { return qt_TDes2QString(TDayNameAbb(TDay(day))); } else { return qt_TDes2QString(TDayName(TDay(day))); } } /*! Retrieves a month name from Symbian locale. The \a month is an integer from 1 to 12. When \a short_format is true the method returns the month in short format. Otherwise it returns the month in a long format. */ static QString symbianMonthName(int month, bool short_format) { month -= 1; if (month < 0 || month > 11) return QString(); if (short_format) { return qt_TDes2QString(TMonthNameAbb(TMonth(month))); } else { return qt_TDes2QString(TMonthName(TMonth(month))); } } /*! Retrieves date format from Symbian locale and transforms it to Qt format. When \a short_format is true the method returns short date format. Otherwise it returns the long format. */ static QString symbianDateFormat(bool short_format) { TPtrC dateFormat; if (short_format) { dateFormat.Set(ptrGetShortDateFormatSpec(_s60Locale)); } else { dateFormat.Set(ptrGetLongDateFormatSpec(_s60Locale)); } return s60ToQtFormat(qt_TDesC2QString(dateFormat)); } /*! Retrieves time format from Symbian locale and transforms it to Qt format. */ static QString symbianTimeFormat() { return s60ToQtFormat(qt_TDesC2QString(ptrGetTimeFormatSpec(_s60Locale))); } /*! Returns localized string representation of given \a date formatted with Symbian locale date format. If \a short_format is true the format will be a short version. Otherwise it uses a longer version. */ static QString symbianDateToString(const QDate &date, bool short_format) { int month = date.month() - 1; int day = date.day() - 1; int year = date.year(); TDateTime dateTime; dateTime.Set(year, TMonth(month), day, 0, 0, 0, 0); TTime timeStr(dateTime); TBuf buffer; TPtrC dateFormat; if (short_format) { dateFormat.Set(ptrGetShortDateFormatSpec(_s60Locale)); } else { dateFormat.Set(ptrGetLongDateFormatSpec(_s60Locale)); } TRAPD(err, ptrTimeFormatL(timeStr, buffer, dateFormat, *_s60Locale.GetLocale());) if (err == KErrNone) return qt_TDes2QString(buffer); else return QString(); } /*! Returns localized string representation of given \a time formatted with Symbian locale time format. */ static QString symbianTimeToString(const QTime &time) { int hour = time.hour(); int minute = time.minute(); int second = time.second(); int milliseconds = 0; TDateTime dateTime; dateTime.Set(0, TMonth(0), 0, hour, minute, second, milliseconds); TTime timeStr(dateTime); TBuf buffer; TRAPD(err, ptrTimeFormatL( timeStr, buffer, ptrGetTimeFormatSpec(_s60Locale), *_s60Locale.GetLocale()); ) if (err == KErrNone) return qt_TDes2QString(buffer); else return QString(); } /*! Returns the measurement system stored in Symbian locale \sa QLocale::MeasurementSystem */ static QLocale::MeasurementSystem symbianMeasurementSystem() { TLocale *locale = _s60Locale.GetLocale(); TUnitsFormat unitFormat = locale->UnitsGeneral(); if (unitFormat == EUnitsImperial) return QLocale::ImperialSystem; else return QLocale::MetricSystem; } QLocale QSystemLocale::fallbackLocale() const { // load system data before query calls static QBasicAtomicInt initDone = Q_BASIC_ATOMIC_INITIALIZER(0); if (initDone.testAndSetRelaxed(0, 1)) { _s60Locale.LoadSystemSettings(); // Initialize platform version dependent function pointers ptrTimeFormatL = reinterpret_cast (qt_resolveS60PluginFunc(S60Plugin_TimeFormatL)); ptrGetTimeFormatSpec = reinterpret_cast (qt_resolveS60PluginFunc(S60Plugin_GetTimeFormatSpec)); ptrGetLongDateFormatSpec = reinterpret_cast (qt_resolveS60PluginFunc(S60Plugin_GetLongDateFormatSpec)); ptrGetShortDateFormatSpec = reinterpret_cast (qt_resolveS60PluginFunc(S60Plugin_GetShortDateFormatSpec)); if (!ptrTimeFormatL) ptrTimeFormatL = &defaultTimeFormatL; if (!ptrGetTimeFormatSpec) ptrGetTimeFormatSpec = &defaultFormatSpec; if (!ptrGetLongDateFormatSpec) ptrGetLongDateFormatSpec = &defaultFormatSpec; if (!ptrGetShortDateFormatSpec) ptrGetShortDateFormatSpec = &defaultFormatSpec; bool ret = initDone.testAndSetRelease(1, 2); Q_ASSERT(ret); Q_UNUSED(ret); } while(initDone != 2) QThread::yieldCurrentThread(); TLanguage lang = User::Language(); QString locale = QLatin1String(qt_symbianLocaleName(lang)); return QLocale(locale); } /*! Generic query method for locale data. Provides indirection. Denotes the \a type of the query with \a in as input data depending on the query. \sa QSystemLocale::QueryType */ QVariant QSystemLocale::query(QueryType type, QVariant in = QVariant()) const { switch(type) { case DecimalPoint: return symbianDecimalPoint(); case GroupSeparator: return symbianGroupSeparator(); case ZeroDigit: return symbianZeroDigit(); case DayNameLong: case DayNameShort: return symbianDayName(in.toInt(), (type == DayNameShort) ); case MonthNameLong: case MonthNameShort: return symbianMonthName(in.toInt(), (type == MonthNameShort) ); case DateFormatLong: case DateFormatShort: return symbianDateFormat( (type == DateFormatShort) ); case TimeFormatLong: case TimeFormatShort: return symbianTimeFormat(); case DateTimeFormatLong: case DateTimeFormatShort: return symbianDateFormat( (type == DateTimeFormatShort) ) + QLatin1Char(' ') + symbianTimeFormat(); case DateToStringShort: case DateToStringLong: return symbianDateToString(in.toDate(), (type == DateToStringShort) ); case TimeToStringShort: case TimeToStringLong: return symbianTimeToString(in.toTime()); case DateTimeToStringShort: case DateTimeToStringLong: { const QDateTime dt = in.toDateTime(); return symbianDateToString(dt.date(), (type == DateTimeToStringShort) ) + QLatin1Char(' ') + symbianTimeToString(dt.time()); } case MeasurementSystem: return static_cast(symbianMeasurementSystem()); case LanguageId: case CountryId: { TLanguage language = User::Language(); QString locale = QLatin1String(qt_symbianLocaleName(language)); QLocale::Language lang; QLocale::Country cntry; getLangAndCountry(locale, lang, cntry); if (type == LanguageId) return lang; // few iso codes have no country and will use this if (cntry == QLocale::AnyCountry) return fallbackLocale().country(); return cntry; } case NegativeSign: case PositiveSign: break; case AMText: return qt_TDes2QString(TAmPmName(TAmPm(EAm))); case PMText: return qt_TDes2QString(TAmPmName(TAmPm(EPm))); default: break; } return QVariant(); } QT_END_NAMESPACE