From a9aa206b7b8ac4e69f8c46233b4080e00e845ff5 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Mon, 27 May 2019 19:13:54 +0200 Subject: Move text-related code out of corelib/tools/ to corelib/text/ This includes byte array, string, char, unicode, locale, collation and regular expressions. Change-Id: I8b125fa52c8c513eb57a0f1298b91910e5a0d786 Reviewed-by: Volker Hilsheimer --- src/corelib/text/qlocale_mac.mm | 508 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 508 insertions(+) create mode 100644 src/corelib/text/qlocale_mac.mm (limited to 'src/corelib/text/qlocale_mac.mm') diff --git a/src/corelib/text/qlocale_mac.mm b/src/corelib/text/qlocale_mac.mm new file mode 100644 index 0000000000..9719278426 --- /dev/null +++ b/src/corelib/text/qlocale_mac.mm @@ -0,0 +1,508 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocale_p.h" + +#include "qstringlist.h" +#include "qvariant.h" +#include "qdatetime.h" + +#ifdef Q_OS_DARWIN +#include "private/qcore_mac_p.h" +#include +#endif + +QT_BEGIN_NAMESPACE + +/****************************************************************************** +** Wrappers for Mac locale system functions +*/ + +static QByteArray envVarLocale() +{ + static QByteArray lang = 0; +#ifdef Q_OS_UNIX + lang = qgetenv("LC_ALL"); + if (lang.isEmpty()) + lang = qgetenv("LC_NUMERIC"); + if (lang.isEmpty()) +#endif + lang = qgetenv("LANG"); + return lang; +} + +static QByteArray getMacLocaleName() +{ + QByteArray result = envVarLocale(); + + QString lang, script, cntry; + if (result.isEmpty() + || (result != "C" && !qt_splitLocaleName(QString::fromLocal8Bit(result), lang, script, cntry))) { + QCFType l = CFLocaleCopyCurrent(); + CFStringRef locale = CFLocaleGetIdentifier(l); + result = QString::fromCFString(locale).toUtf8(); + } + return result; +} + +static QString macMonthName(int month, bool short_format) +{ + month -= 1; + if (month < 0 || month > 11) + return QString(); + + QCFType formatter + = CFDateFormatterCreate(0, QCFType(CFLocaleCopyCurrent()), + kCFDateFormatterNoStyle, kCFDateFormatterNoStyle); + QCFType values + = static_cast(CFDateFormatterCopyProperty(formatter, + short_format ? kCFDateFormatterShortMonthSymbols + : kCFDateFormatterMonthSymbols)); + if (values != 0) { + CFStringRef cfstring = static_cast(CFArrayGetValueAtIndex(values, month)); + return QString::fromCFString(cfstring); + } + return QString(); +} + +static QString macDayName(int day, bool short_format) +{ + if (day < 1 || day > 7) + return QString(); + + QCFType formatter + = CFDateFormatterCreate(0, QCFType(CFLocaleCopyCurrent()), + kCFDateFormatterNoStyle, kCFDateFormatterNoStyle); + QCFType values = static_cast(CFDateFormatterCopyProperty(formatter, + short_format ? kCFDateFormatterShortWeekdaySymbols + : kCFDateFormatterWeekdaySymbols)); + if (values != 0) { + CFStringRef cfstring = static_cast(CFArrayGetValueAtIndex(values, day % 7)); + return QString::fromCFString(cfstring); + } + return QString(); +} + +static QString macDateToString(const QDate &date, bool short_format) +{ + QCFType myDate = QDateTime(date, QTime()).toCFDate(); + QCFType mylocale = CFLocaleCopyCurrent(); + CFDateFormatterStyle style = short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle; + QCFType myFormatter + = CFDateFormatterCreate(kCFAllocatorDefault, + mylocale, style, + kCFDateFormatterNoStyle); + return QCFString(CFDateFormatterCreateStringWithDate(0, myFormatter, myDate)); +} + +static QString macTimeToString(const QTime &time, bool short_format) +{ + QCFType myDate = QDateTime(QDate::currentDate(), time).toCFDate(); + QCFType mylocale = CFLocaleCopyCurrent(); + CFDateFormatterStyle style = short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle; + QCFType myFormatter = CFDateFormatterCreate(kCFAllocatorDefault, + mylocale, + kCFDateFormatterNoStyle, + style); + return QCFString(CFDateFormatterCreateStringWithDate(0, myFormatter, myDate)); +} + +// Mac uses the Unicode CLDR format codes +// http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table +// See also qtbase/util/locale_database/dateconverter.py +// Makes the assumption that input formats are always well formed and consecutive letters +// never exceed the maximum for the format code. +static QString macToQtFormat(QStringView sys_fmt) +{ + QString result; + int i = 0; + + while (i < sys_fmt.size()) { + if (sys_fmt.at(i).unicode() == '\'') { + QString text = qt_readEscapedFormatString(sys_fmt, &i); + if (text == QLatin1String("'")) + result += QLatin1String("''"); + else + result += QLatin1Char('\'') + text + QLatin1Char('\''); + continue; + } + + QChar c = sys_fmt.at(i); + int repeat = qt_repeatCount(sys_fmt.mid(i)); + + switch (c.unicode()) { + // Qt does not support the following options + case 'G': // Era (1..5): 4 = long, 1..3 = short, 5 = narrow + case 'Y': // Year of Week (1..n): 1..n = padded number + case 'U': // Cyclic Year Name (1..5): 4 = long, 1..3 = short, 5 = narrow + case 'Q': // Quarter (1..4): 4 = long, 3 = short, 1..2 = padded number + case 'q': // Standalone Quarter (1..4): 4 = long, 3 = short, 1..2 = padded number + case 'w': // Week of Year (1..2): 1..2 = padded number + case 'W': // Week of Month (1): 1 = number + case 'D': // Day of Year (1..3): 1..3 = padded number + case 'F': // Day of Week in Month (1): 1 = number + case 'g': // Modified Julian Day (1..n): 1..n = padded number + case 'A': // Milliseconds in Day (1..n): 1..n = padded number + break; + + case 'y': // Year (1..n): 2 = short year, 1 & 3..n = padded number + case 'u': // Extended Year (1..n): 2 = short year, 1 & 3..n = padded number + // Qt only supports long (4) or short (2) year, use long for all others + if (repeat == 2) + result += QLatin1String("yy"); + else + result += QLatin1String("yyyy"); + break; + case 'M': // Month (1..5): 4 = long, 3 = short, 1..2 = number, 5 = narrow + case 'L': // Standalone Month (1..5): 4 = long, 3 = short, 1..2 = number, 5 = narrow + // Qt only supports long, short and number, use short for narrow + if (repeat == 5) + result += QLatin1String("MMM"); + else + result += QString(repeat, QLatin1Char('M')); + break; + case 'd': // Day of Month (1..2): 1..2 padded number + result += QString(repeat, c); + break; + case 'E': // Day of Week (1..6): 4 = long, 1..3 = short, 5..6 = narrow + // Qt only supports long, short and padded number, use short for narrow + if (repeat == 4) + result += QLatin1String("dddd"); + else + result += QLatin1String("ddd"); + break; + case 'e': // Local Day of Week (1..6): 4 = long, 3 = short, 5..6 = narrow, 1..2 padded number + case 'c': // Standalone Local Day of Week (1..6): 4 = long, 3 = short, 5..6 = narrow, 1..2 padded number + // Qt only supports long, short and padded number, use short for narrow + if (repeat >= 5) + result += QLatin1String("ddd"); + else + result += QString(repeat, QLatin1Char('d')); + break; + case 'a': // AM/PM (1): 1 = short + // Translate to Qt uppercase AM/PM + result += QLatin1String("AP"); + break; + case 'h': // Hour [1..12] (1..2): 1..2 = padded number + case 'K': // Hour [0..11] (1..2): 1..2 = padded number + case 'j': // Local Hour [12 or 24] (1..2): 1..2 = padded number + // Qt h is local hour + result += QString(repeat, QLatin1Char('h')); + break; + case 'H': // Hour [0..23] (1..2): 1..2 = padded number + case 'k': // Hour [1..24] (1..2): 1..2 = padded number + // Qt H is 0..23 hour + result += QString(repeat, QLatin1Char('H')); + break; + case 'm': // Minutes (1..2): 1..2 = padded number + case 's': // Seconds (1..2): 1..2 = padded number + result += QString(repeat, c); + break; + case 'S': // Fractional second (1..n): 1..n = truncates to decimal places + // Qt uses msecs either unpadded or padded to 3 places + if (repeat < 3) + result += QLatin1Char('z'); + else + result += QLatin1String("zzz"); + break; + case 'z': // Time Zone (1..4) + case 'Z': // Time Zone (1..5) + case 'O': // Time Zone (1, 4) + case 'v': // Time Zone (1, 4) + case 'V': // Time Zone (1..4) + case 'X': // Time Zone (1..5) + case 'x': // Time Zone (1..5) + result += QLatin1Char('t'); + break; + default: + // a..z and A..Z are reserved for format codes, so any occurrence of these not + // already processed are not known and so unsupported formats to be ignored. + // All other chars are allowed as literals. + if (c < QLatin1Char('A') || c > QLatin1Char('z') || + (c > QLatin1Char('Z') && c < QLatin1Char('a'))) { + result += QString(repeat, c); + } + break; + } + + i += repeat; + } + + return result; +} + +QString getMacDateFormat(CFDateFormatterStyle style) +{ + QCFType l = CFLocaleCopyCurrent(); + QCFType formatter = CFDateFormatterCreate(kCFAllocatorDefault, + l, style, kCFDateFormatterNoStyle); + return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter))); +} + +static QString getMacTimeFormat(CFDateFormatterStyle style) +{ + QCFType l = CFLocaleCopyCurrent(); + QCFType formatter = CFDateFormatterCreate(kCFAllocatorDefault, + l, kCFDateFormatterNoStyle, style); + return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter))); +} + +static QString getCFLocaleValue(CFStringRef key) +{ + QCFType locale = CFLocaleCopyCurrent(); + CFTypeRef value = CFLocaleGetValue(locale, key); + return QString::fromCFString(CFStringRef(static_cast(value))); +} + +static QLocale::MeasurementSystem macMeasurementSystem() +{ + QCFType locale = CFLocaleCopyCurrent(); + CFStringRef system = static_cast(CFLocaleGetValue(locale, kCFLocaleMeasurementSystem)); + if (QString::fromCFString(system) == QLatin1String("Metric")) { + return QLocale::MetricSystem; + } else { + return QLocale::ImperialSystem; + } +} + + +static quint8 macFirstDayOfWeek() +{ + QCFType calendar = CFCalendarCopyCurrent(); + quint8 day = static_cast(CFCalendarGetFirstWeekday(calendar))-1; + if (day == 0) + day = 7; + return day; +} + +static QString macCurrencySymbol(QLocale::CurrencySymbolFormat format) +{ + QCFType locale = CFLocaleCopyCurrent(); + switch (format) { + case QLocale::CurrencyIsoCode: + return QString::fromCFString(static_cast(CFLocaleGetValue(locale, kCFLocaleCurrencyCode))); + case QLocale::CurrencySymbol: + return QString::fromCFString(static_cast(CFLocaleGetValue(locale, kCFLocaleCurrencySymbol))); + case QLocale::CurrencyDisplayName: { + CFStringRef code = static_cast(CFLocaleGetValue(locale, kCFLocaleCurrencyCode)); + QCFType value = CFLocaleCopyDisplayNameForPropertyValue(locale, kCFLocaleCurrencyCode, code); + return QString::fromCFString(value); + } + default: + break; + } + return QString(); +} + +static QString macZeroDigit() +{ + QCFType locale = CFLocaleCopyCurrent(); + QCFType numberFormatter = + CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle); + static const int zeroDigit = 0; + QCFType value = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter, + kCFNumberIntType, &zeroDigit); + return QString::fromCFString(value); +} + +#ifndef QT_NO_SYSTEMLOCALE +static QString macFormatCurrency(const QSystemLocale::CurrencyToStringArgument &arg) +{ + QCFType value; + switch (arg.value.type()) { + case QVariant::Int: + case QVariant::UInt: { + int v = arg.value.toInt(); + value = CFNumberCreate(NULL, kCFNumberIntType, &v); + break; + } + case QVariant::Double: { + double v = arg.value.toDouble(); + value = CFNumberCreate(NULL, kCFNumberDoubleType, &v); + break; + } + case QVariant::LongLong: + case QVariant::ULongLong: { + qint64 v = arg.value.toLongLong(); + value = CFNumberCreate(NULL, kCFNumberLongLongType, &v); + break; + } + default: + return QString(); + } + + QCFType locale = CFLocaleCopyCurrent(); + QCFType currencyFormatter = + CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterCurrencyStyle); + if (!arg.symbol.isEmpty()) { + CFNumberFormatterSetProperty(currencyFormatter, kCFNumberFormatterCurrencySymbol, + arg.symbol.toCFString()); + } + QCFType result = CFNumberFormatterCreateStringWithNumber(NULL, currencyFormatter, value); + return QString::fromCFString(result); +} + +static QVariant macQuoteString(QSystemLocale::QueryType type, const QStringRef &str) +{ + QString begin, end; + QCFType locale = CFLocaleCopyCurrent(); + switch (type) { + case QSystemLocale::StringToStandardQuotation: + begin = QString::fromCFString(static_cast(CFLocaleGetValue(locale, kCFLocaleQuotationBeginDelimiterKey))); + end = QString::fromCFString(static_cast(CFLocaleGetValue(locale, kCFLocaleQuotationEndDelimiterKey))); + return QString(begin % str % end); + case QSystemLocale::StringToAlternateQuotation: + begin = QString::fromCFString(static_cast(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationBeginDelimiterKey))); + end = QString::fromCFString(static_cast(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationEndDelimiterKey))); + return QString(begin % str % end); + default: + break; + } + return QVariant(); +} +#endif //QT_NO_SYSTEMLOCALE + +#ifndef QT_NO_SYSTEMLOCALE + +QLocale QSystemLocale::fallbackUiLocale() const +{ + return QLocale(QString::fromUtf8(getMacLocaleName().constData())); +} + +QVariant QSystemLocale::query(QueryType type, QVariant in = QVariant()) const +{ + QMacAutoReleasePool pool; + switch(type) { +// case Name: +// return getMacLocaleName(); + case DecimalPoint: { + QString value = getCFLocaleValue(kCFLocaleDecimalSeparator); + return value.isEmpty() ? QVariant() : value; + } + case GroupSeparator: { + QString value = getCFLocaleValue(kCFLocaleGroupingSeparator); + return value.isEmpty() ? QVariant() : value; + } + case DateFormatLong: + case DateFormatShort: + return getMacDateFormat(type == DateFormatShort + ? kCFDateFormatterShortStyle + : kCFDateFormatterLongStyle); + case TimeFormatLong: + case TimeFormatShort: + return getMacTimeFormat(type == TimeFormatShort + ? kCFDateFormatterShortStyle + : kCFDateFormatterLongStyle); + case DayNameLong: + case DayNameShort: + return macDayName(in.toInt(), (type == DayNameShort)); + case MonthNameLong: + case MonthNameShort: + case StandaloneMonthNameLong: + case StandaloneMonthNameShort: + return macMonthName(in.toInt(), (type == MonthNameShort || type == StandaloneMonthNameShort)); + case DateToStringShort: + case DateToStringLong: + return macDateToString(in.toDate(), (type == DateToStringShort)); + case TimeToStringShort: + case TimeToStringLong: + return macTimeToString(in.toTime(), (type == TimeToStringShort)); + + case NegativeSign: + case PositiveSign: + break; + case ZeroDigit: + return QVariant(macZeroDigit()); + + case MeasurementSystem: + return QVariant(static_cast(macMeasurementSystem())); + + case AMText: + case PMText: { + QCFType locale = CFLocaleCopyCurrent(); + QCFType formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterLongStyle, kCFDateFormatterLongStyle); + QCFType value = static_cast(CFDateFormatterCopyProperty(formatter, + (type == AMText ? kCFDateFormatterAMSymbol : kCFDateFormatterPMSymbol))); + return QString::fromCFString(value); + } + case FirstDayOfWeek: + return QVariant(macFirstDayOfWeek()); + case CurrencySymbol: + return QVariant(macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()))); + case CurrencyToString: + return macFormatCurrency(in.value()); + case UILanguages: { + QCFType languages = CFPreferencesCopyValue( + CFSTR("AppleLanguages"), + kCFPreferencesAnyApplication, + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost); + QStringList result; + if (!languages) + return QVariant(result); + + CFTypeID typeId = CFGetTypeID(languages); + if (typeId == CFArrayGetTypeID()) { + const int cnt = CFArrayGetCount(languages.as()); + result.reserve(cnt); + for (int i = 0; i < cnt; ++i) { + const QString lang = QString::fromCFString( + static_cast(CFArrayGetValueAtIndex(languages.as(), i))); + result.append(lang); + } + } else if (typeId == CFStringGetTypeID()) { + result = QStringList(QString::fromCFString(languages.as())); + } else { + qWarning("QLocale::uiLanguages(): CFPreferencesCopyValue returned unhandled type \"%ls\"; please report to http://bugreports.qt.io", + qUtf16Printable(QString::fromCFString(CFCopyTypeIDDescription(typeId)))); + } + return QVariant(result); + } + case StringToStandardQuotation: + case StringToAlternateQuotation: + return macQuoteString(type, in.value()); + default: + break; + } + return QVariant(); +} + +#endif // QT_NO_SYSTEMLOCALE + +QT_END_NAMESPACE -- cgit v1.2.3