668 lines
26 KiB
Plaintext
668 lines
26 KiB
Plaintext
// Copyright (C) 2021 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
#include "qlocale_p.h"
|
|
|
|
#include "qstringlist.h"
|
|
#include "qvariant.h"
|
|
#include "qdatetime.h"
|
|
|
|
#include "private/qstringiterator_p.h"
|
|
#include "private/qgregoriancalendar_p.h"
|
|
#ifdef Q_OS_DARWIN
|
|
#include "private/qcore_mac_p.h"
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#endif
|
|
|
|
#include <QtCore/qloggingcategory.h>
|
|
#include <QtCore/qcoreapplication.h>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
/******************************************************************************
|
|
** Wrappers for Mac locale system functions
|
|
*/
|
|
|
|
Q_LOGGING_CATEGORY(lcLocale, "qt.core.locale")
|
|
|
|
static void printLocalizationInformation()
|
|
{
|
|
if (!lcLocale().isDebugEnabled())
|
|
return;
|
|
|
|
#if defined(Q_OS_MACOS)
|
|
// Trigger initialization of standard user defaults, so that Foundation picks
|
|
// up -AppleLanguages and -AppleLocale passed on the command line.
|
|
Q_UNUSED(NSUserDefaults.standardUserDefaults);
|
|
#endif
|
|
|
|
auto singleLineDescription = [](NSArray *array) {
|
|
NSString *str = [array description];
|
|
str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
|
|
return [str stringByReplacingOccurrencesOfString:@" " withString:@""];
|
|
};
|
|
|
|
bool allowMixedLocalizations = [NSBundle.mainBundle.infoDictionary[@"CFBundleAllowMixedLocalizations"] boolValue];
|
|
|
|
NSBundle *foundation = [NSBundle bundleForClass:NSBundle.class];
|
|
qCDebug(lcLocale).nospace() << "Launched with locale \"" << NSLocale.currentLocale.localeIdentifier
|
|
<< "\" based on user's preferred languages " << singleLineDescription(NSLocale.preferredLanguages)
|
|
<< ", main bundle localizations " << singleLineDescription(NSBundle.mainBundle.localizations)
|
|
<< ", and allowing mixed localizations " << allowMixedLocalizations
|
|
<< "; resulting in main bundle preferred localizations "
|
|
<< singleLineDescription(NSBundle.mainBundle.preferredLocalizations)
|
|
<< " and Foundation preferred localizations "
|
|
<< singleLineDescription(foundation.preferredLocalizations);
|
|
qCDebug(lcLocale) << "Reflected by Qt as system locale"
|
|
<< QLocale::system() << "with UI languges " << QLocale::system().uiLanguages();
|
|
}
|
|
Q_COREAPP_STARTUP_FUNCTION(printLocalizationInformation);
|
|
|
|
static QString getMacLocaleName()
|
|
{
|
|
QCFType<CFLocaleRef> l = CFLocaleCopyCurrent();
|
|
CFStringRef locale = CFLocaleGetIdentifier(l);
|
|
return QString::fromCFString(locale);
|
|
}
|
|
|
|
static QVariant macMonthName(int month, QSystemLocale::QueryType type)
|
|
{
|
|
month -= 1;
|
|
if (month < 0 || month > 11)
|
|
return {};
|
|
|
|
QCFType<CFDateFormatterRef> formatter
|
|
= CFDateFormatterCreate(0, QCFType<CFLocaleRef>(CFLocaleCopyCurrent()),
|
|
kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
|
|
|
|
CFDateFormatterKey formatterType;
|
|
switch (type) {
|
|
case QSystemLocale::MonthNameLong:
|
|
formatterType = kCFDateFormatterMonthSymbols;
|
|
break;
|
|
case QSystemLocale::MonthNameShort:
|
|
formatterType = kCFDateFormatterShortMonthSymbols;
|
|
break;
|
|
case QSystemLocale::MonthNameNarrow:
|
|
formatterType = kCFDateFormatterVeryShortMonthSymbols;
|
|
break;
|
|
case QSystemLocale::StandaloneMonthNameLong:
|
|
formatterType = kCFDateFormatterStandaloneMonthSymbols;
|
|
break;
|
|
case QSystemLocale::StandaloneMonthNameShort:
|
|
formatterType = kCFDateFormatterShortStandaloneMonthSymbols;
|
|
break;
|
|
case QSystemLocale::StandaloneMonthNameNarrow:
|
|
formatterType = kCFDateFormatterVeryShortStandaloneMonthSymbols;
|
|
break;
|
|
default:
|
|
qWarning("macMonthName: Unsupported query type %d", type);
|
|
return {};
|
|
}
|
|
QCFType<CFArrayRef> values
|
|
= static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, formatterType));
|
|
|
|
if (values != 0) {
|
|
CFStringRef cfstring = static_cast<CFStringRef>(CFArrayGetValueAtIndex(values, month));
|
|
return QString::fromCFString(cfstring);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static QVariant macDayName(int day, QSystemLocale::QueryType type)
|
|
{
|
|
if (day < 1 || day > 7)
|
|
return {};
|
|
|
|
QCFType<CFDateFormatterRef> formatter
|
|
= CFDateFormatterCreate(0, QCFType<CFLocaleRef>(CFLocaleCopyCurrent()),
|
|
kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
|
|
|
|
CFDateFormatterKey formatterType;
|
|
switch (type) {
|
|
case QSystemLocale::DayNameLong:
|
|
formatterType = kCFDateFormatterWeekdaySymbols;
|
|
break;
|
|
case QSystemLocale::DayNameShort:
|
|
formatterType = kCFDateFormatterShortWeekdaySymbols;
|
|
break;
|
|
case QSystemLocale::DayNameNarrow:
|
|
formatterType = kCFDateFormatterVeryShortWeekdaySymbols;
|
|
break;
|
|
case QSystemLocale::StandaloneDayNameLong:
|
|
formatterType = kCFDateFormatterStandaloneWeekdaySymbols;
|
|
break;
|
|
case QSystemLocale::StandaloneDayNameShort:
|
|
formatterType = kCFDateFormatterShortStandaloneWeekdaySymbols;
|
|
break;
|
|
case QSystemLocale::StandaloneDayNameNarrow:
|
|
formatterType = kCFDateFormatterVeryShortStandaloneWeekdaySymbols;
|
|
break;
|
|
default:
|
|
qWarning("macDayName: Unsupported query type %d", type);
|
|
return {};
|
|
}
|
|
QCFType<CFArrayRef> values =
|
|
static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, formatterType));
|
|
|
|
if (values != 0) {
|
|
CFStringRef cfstring = static_cast<CFStringRef>(CFArrayGetValueAtIndex(values, day % 7));
|
|
return QString::fromCFString(cfstring);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static QString macZeroDigit()
|
|
{
|
|
static QString cachedZeroDigit;
|
|
|
|
if (cachedZeroDigit.isNull()) {
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
QCFType<CFNumberFormatterRef> numberFormatter =
|
|
CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle);
|
|
const int zeroDigit = 0;
|
|
QCFType<CFStringRef> value
|
|
= CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter,
|
|
kCFNumberIntType, &zeroDigit);
|
|
cachedZeroDigit = QString::fromCFString(value);
|
|
}
|
|
|
|
static QMacNotificationObserver localeChangeObserver = QMacNotificationObserver(
|
|
nil, NSCurrentLocaleDidChangeNotification, [&] {
|
|
qCDebug(lcLocale) << "System locale changed";
|
|
cachedZeroDigit = QString();
|
|
});
|
|
|
|
return cachedZeroDigit;
|
|
}
|
|
|
|
static QString zeroPad(QString &&number, qsizetype minDigits, const QString &zero)
|
|
{
|
|
// Need to pad with zeros, possibly after a sign.
|
|
qsizetype insert = -1, digits = 0;
|
|
auto it = QStringIterator(number);
|
|
while (it.hasNext()) {
|
|
qsizetype here = it.index();
|
|
if (QChar::isDigit(it.next())) {
|
|
if (insert < 0)
|
|
insert = here;
|
|
++digits;
|
|
} // else: assume we're stepping over a sign (or maybe grouping separator)
|
|
}
|
|
Q_ASSERT(digits > 0);
|
|
Q_ASSERT(insert >= 0);
|
|
while (digits++ < minDigits)
|
|
number.insert(insert, zero);
|
|
|
|
return std::move(number);
|
|
}
|
|
|
|
static QString trimTwoDigits(QString &&number)
|
|
{
|
|
// Retain any sign, but remove all but the last two digits.
|
|
// We know number has at least four digits - it came from fourDigitYear().
|
|
// Note that each digit might be a surrogate pair.
|
|
qsizetype first = -1, prev = -1, last = -1;
|
|
auto it = QStringIterator(number);
|
|
while (it.hasNext()) {
|
|
qsizetype here = it.index();
|
|
if (QChar::isDigit(it.next())) {
|
|
if (first == -1)
|
|
last = first = here;
|
|
else if (last != -1)
|
|
prev = std::exchange(last, here);
|
|
}
|
|
}
|
|
Q_ASSERT(first >= 0);
|
|
Q_ASSERT(prev > first);
|
|
Q_ASSERT(last > prev);
|
|
number.remove(first, prev - first);
|
|
return std::move(number);
|
|
}
|
|
|
|
static QString fourDigitYear(int year, const QString &zero)
|
|
{
|
|
// Return year formatted as an (at least) four digit number:
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
QCFType<CFNumberFormatterRef> numberFormatter =
|
|
CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle);
|
|
QCFType<CFStringRef> value = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter,
|
|
kCFNumberIntType, &year);
|
|
auto text = QString::fromCFString(value);
|
|
if (year > -1000 && year < 1000)
|
|
text = zeroPad(std::move(text), 4, zero);
|
|
return text;
|
|
}
|
|
|
|
static QString macDateToStringImpl(QDate date, CFDateFormatterStyle style)
|
|
{
|
|
// Use noon on the given date, to avoid complications that can arise for
|
|
// dates before 1900 (see QTBUG-54955) using different UTC offset than
|
|
// QDateTime extrapolates backwards from time_t functions that only work
|
|
// back to 1900. (Alaska and Phillipines may still be borked, though.)
|
|
QCFType<CFDateRef> myDate = QDateTime(date, QTime(12, 0)).toCFDate();
|
|
QCFType<CFLocaleRef> mylocale = CFLocaleCopyCurrent();
|
|
QCFType<CFDateFormatterRef> myFormatter
|
|
= CFDateFormatterCreate(kCFAllocatorDefault, mylocale, style,
|
|
kCFDateFormatterNoStyle);
|
|
QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(nullptr, myFormatter, myDate);
|
|
return QString::fromCFString(text);
|
|
}
|
|
|
|
static QVariant macDateToString(QDate date, bool short_format)
|
|
{
|
|
const int year = date.year();
|
|
QString fakeYear, trueYear;
|
|
if (year < 1583) {
|
|
// System API (in macOS 11.0, at least) discards sign :-(
|
|
// Simply negating the year won't do as the resulting year typically has
|
|
// a different pattern of week-days.
|
|
// Furthermore (see QTBUG-54955), Darwin uses the Julian calendar for
|
|
// dates before 1582-10-15, leading to discrepancies.
|
|
int matcher = QGregorianCalendar::yearSharingWeekDays(date);
|
|
Q_ASSERT(matcher >= 1583);
|
|
Q_ASSERT(matcher % 100 != date.month());
|
|
Q_ASSERT(matcher % 100 != date.day());
|
|
// i.e. there can't be any confusion between the two-digit year and
|
|
// month or day-of-month in the formatted date.
|
|
QString zero = macZeroDigit();
|
|
fakeYear = fourDigitYear(matcher, zero);
|
|
trueYear = fourDigitYear(year, zero);
|
|
date = QDate(matcher, date.month(), date.day());
|
|
}
|
|
QString text = macDateToStringImpl(date, short_format
|
|
? kCFDateFormatterShortStyle
|
|
: kCFDateFormatterLongStyle);
|
|
if (year < 1583) {
|
|
if (text.contains(fakeYear))
|
|
return std::move(text).replace(fakeYear, trueYear);
|
|
// Cope with two-digit year:
|
|
fakeYear = trimTwoDigits(std::move(fakeYear));
|
|
trueYear = trimTwoDigits(std::move(trueYear));
|
|
if (text.contains(fakeYear))
|
|
return std::move(text).replace(fakeYear, trueYear);
|
|
// That should have worked.
|
|
qWarning("Failed to fix up year when formatting a date in year %d", year);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
static QVariant macTimeToString(QTime time, bool short_format)
|
|
{
|
|
QCFType<CFDateRef> myDate = QDateTime(QDate::currentDate(), time).toCFDate();
|
|
QCFType<CFLocaleRef> mylocale = CFLocaleCopyCurrent();
|
|
CFDateFormatterStyle style = short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle;
|
|
QCFType<CFDateFormatterRef> myFormatter = CFDateFormatterCreate(kCFAllocatorDefault,
|
|
mylocale,
|
|
kCFDateFormatterNoStyle,
|
|
style);
|
|
QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(0, myFormatter, myDate);
|
|
return QString::fromCFString(text);
|
|
}
|
|
|
|
// 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 QVariant macToQtFormat(QStringView sys_fmt)
|
|
{
|
|
QString result;
|
|
qsizetype i = 0;
|
|
|
|
while (i < sys_fmt.size()) {
|
|
if (sys_fmt.at(i).unicode() == '\'') {
|
|
QString text = qt_readEscapedFormatString(sys_fmt, &i);
|
|
if (text == "'"_L1)
|
|
result += "''"_L1;
|
|
else
|
|
result += u'\'' + text + u'\'';
|
|
continue;
|
|
}
|
|
|
|
QChar c = sys_fmt.at(i);
|
|
qsizetype repeat = qt_repeatCount(sys_fmt.sliced(i));
|
|
|
|
switch (c.unicode()) {
|
|
// Qt does not support the following options
|
|
case 'A': // Milliseconds in Day (1..n): 1..n = padded number
|
|
case 'C': // Input skeleton symbol.
|
|
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 'G': // Era (1..5): 4 = long, 1..3 = short, 5 = narrow
|
|
case 'j': // Input skeleton symbol.
|
|
case 'J': // Input skeleton symbol.
|
|
case 'l': // Deprecated Chinese leap month indicator.
|
|
case 'q': // Standalone Quarter (1..4): 4 = long, 3 = short, 1,2 = padded number
|
|
case 'Q': // Quarter (1..4): 4 = long, 3 = short, 1,2 = padded number
|
|
case 'U': // Cyclic Year Name (1..5): 4 = long, 1..3 = short, 5 = narrow
|
|
case 'w': // Week of Year (1,2): 1,2 = padded number
|
|
case 'W': // Week of Month (1): 1 = number
|
|
case 'Y': // Year for Week-of-year calendars (1..n): 1..n = padded number
|
|
break;
|
|
|
|
case 'u': // Extended Year (1..n): 2 = short year, 1 & 3..n = padded number
|
|
case 'y': // 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 += "yy"_L1;
|
|
else
|
|
result += "yyyy"_L1;
|
|
break;
|
|
case 'L': // Standalone Month (1..5): 4 = long, 3 = short, 1,2 = number, 5 = narrow
|
|
case 'M': // 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 += "MMM"_L1;
|
|
else
|
|
result += QString(repeat, u'M');
|
|
break;
|
|
case 'd': // Day of Month (1,2): 1,2 padded number
|
|
result += QString(repeat, c);
|
|
break;
|
|
case 'c': // Standalone version of 'e'
|
|
case 'e': // Local Day of Week (1..6): 4 = long, 3 = short, 5,6 = narrow, 1,2 padded number
|
|
// "Local" only affects numeric form: depends on locale's start-day of the week.
|
|
case 'E': // Day of Week (1..6): 4 = long, 1..3 = short, 5,6 = narrow
|
|
// Qt only supports long, short: use short for narrow and padded number.
|
|
if (repeat == 4)
|
|
result += "dddd"_L1;
|
|
else
|
|
result += "ddd"_L1;
|
|
break;
|
|
case 'a': // AM/PM (1..n): Qt supports no distinctions
|
|
case 'b': // Like a, but also distinguishing noon, midnight (ignore difference).
|
|
case 'B': // Flexible day period (at night, &c.)
|
|
// Translate to Qt AM/PM, using locale-appropriate case:
|
|
result += "Ap"_L1;
|
|
break;
|
|
case 'h': // Hour [1..12] (1,2): 1,2 = padded number
|
|
case 'K': // Hour [0..11] (1,2): 1,2 = padded number
|
|
result += QString(repeat, 'h'_L1);
|
|
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, 'H'_L1);
|
|
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 += u'z';
|
|
else
|
|
result += "zzz"_L1;
|
|
break;
|
|
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)
|
|
case 'z': // Time Zone (1..4)
|
|
case 'Z': // Time Zone (1..5)
|
|
result += u'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 < u'A' || c > u'z' || (c > u'Z' && c < u'a'))
|
|
result += QString(repeat, c);
|
|
break;
|
|
}
|
|
|
|
i += repeat;
|
|
}
|
|
|
|
return !result.isEmpty() ? QVariant::fromValue(result) : QVariant();
|
|
}
|
|
|
|
static QVariant getMacDateFormat(CFDateFormatterStyle style)
|
|
{
|
|
QCFType<CFLocaleRef> l = CFLocaleCopyCurrent();
|
|
QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault,
|
|
l, style, kCFDateFormatterNoStyle);
|
|
return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter)));
|
|
}
|
|
|
|
static QVariant getMacTimeFormat(CFDateFormatterStyle style)
|
|
{
|
|
QCFType<CFLocaleRef> l = CFLocaleCopyCurrent();
|
|
QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault,
|
|
l, kCFDateFormatterNoStyle, style);
|
|
return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter)));
|
|
}
|
|
|
|
static QVariant getCFLocaleValue(CFStringRef key)
|
|
{
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
CFTypeRef value = CFLocaleGetValue(locale, key);
|
|
if (!value)
|
|
return QVariant();
|
|
return QString::fromCFString(CFStringRef(static_cast<CFTypeRef>(value)));
|
|
}
|
|
|
|
static QVariant macMeasurementSystem()
|
|
{
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
CFStringRef system = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleMeasurementSystem));
|
|
if (QString::fromCFString(system) == "Metric"_L1) {
|
|
return QLocale::MetricSystem;
|
|
} else {
|
|
return QLocale::ImperialSystem;
|
|
}
|
|
}
|
|
|
|
|
|
static quint8 macFirstDayOfWeek()
|
|
{
|
|
QCFType<CFCalendarRef> calendar = CFCalendarCopyCurrent();
|
|
quint8 day = static_cast<quint8>(CFCalendarGetFirstWeekday(calendar))-1;
|
|
if (day == 0)
|
|
day = 7;
|
|
return day;
|
|
}
|
|
|
|
static QVariant macCurrencySymbol(QLocale::CurrencySymbolFormat format)
|
|
{
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
switch (format) {
|
|
case QLocale::CurrencyIsoCode:
|
|
return QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencyCode)));
|
|
case QLocale::CurrencySymbol:
|
|
return QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencySymbol)));
|
|
case QLocale::CurrencyDisplayName: {
|
|
CFStringRef code = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencyCode));
|
|
QCFType<CFStringRef> value = CFLocaleCopyDisplayNameForPropertyValue(locale, kCFLocaleCurrencyCode, code);
|
|
return QString::fromCFString(value);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
#ifndef QT_NO_SYSTEMLOCALE
|
|
static QVariant macFormatCurrency(const QSystemLocale::CurrencyToStringArgument &arg)
|
|
{
|
|
QCFType<CFNumberRef> value;
|
|
switch (arg.value.metaType().id()) {
|
|
case QMetaType::Int:
|
|
case QMetaType::UInt: {
|
|
int v = arg.value.toInt();
|
|
value = CFNumberCreate(NULL, kCFNumberIntType, &v);
|
|
break;
|
|
}
|
|
case QMetaType::Double: {
|
|
double v = arg.value.toDouble();
|
|
value = CFNumberCreate(NULL, kCFNumberDoubleType, &v);
|
|
break;
|
|
}
|
|
case QMetaType::LongLong:
|
|
case QMetaType::ULongLong: {
|
|
qint64 v = arg.value.toLongLong();
|
|
value = CFNumberCreate(NULL, kCFNumberLongLongType, &v);
|
|
break;
|
|
}
|
|
default:
|
|
return {};
|
|
}
|
|
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
QCFType<CFNumberFormatterRef> currencyFormatter =
|
|
CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterCurrencyStyle);
|
|
if (!arg.symbol.isEmpty()) {
|
|
CFNumberFormatterSetProperty(currencyFormatter, kCFNumberFormatterCurrencySymbol,
|
|
arg.symbol.toCFString());
|
|
}
|
|
QCFType<CFStringRef> result = CFNumberFormatterCreateStringWithNumber(NULL, currencyFormatter, value);
|
|
return QString::fromCFString(result);
|
|
}
|
|
|
|
static QVariant macQuoteString(QSystemLocale::QueryType type, QStringView str)
|
|
{
|
|
QString begin, end;
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
switch (type) {
|
|
case QSystemLocale::StringToStandardQuotation:
|
|
begin = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleQuotationBeginDelimiterKey)));
|
|
end = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleQuotationEndDelimiterKey)));
|
|
return QString(begin % str % end);
|
|
case QSystemLocale::StringToAlternateQuotation:
|
|
begin = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationBeginDelimiterKey)));
|
|
end = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationEndDelimiterKey)));
|
|
return QString(begin % str % end);
|
|
default:
|
|
break;
|
|
}
|
|
return QVariant();
|
|
}
|
|
#endif //QT_NO_SYSTEMLOCALE
|
|
|
|
#ifndef QT_NO_SYSTEMLOCALE
|
|
|
|
QLocale QSystemLocale::fallbackLocale() const
|
|
{
|
|
return QLocale(getMacLocaleName());
|
|
}
|
|
|
|
template <auto CodeToValueFunction>
|
|
static QVariant getLocaleValue(CFStringRef key)
|
|
{
|
|
if (auto code = getCFLocaleValue(key); !code.isNull()) {
|
|
// If an invalid locale is requested with -AppleLocale, the system APIs
|
|
// will report invalid or empty locale values back to us, which codeToLanguage()
|
|
// and friends will fail to parse, resulting in returning QLocale::Any{L/C/S}.
|
|
// If this is the case, we fall down and return a null-variant, which
|
|
// QLocale's updateSystemPrivate() will interpret to use fallback logic.
|
|
if (auto value = CodeToValueFunction(code.toString()))
|
|
return value;
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
static QLocale::Language codeToLanguage(QStringView s)
|
|
{
|
|
return QLocalePrivate::codeToLanguage(s);
|
|
}
|
|
|
|
QVariant QSystemLocale::query(QueryType type, QVariant &&in) const
|
|
{
|
|
QMacAutoReleasePool pool;
|
|
|
|
switch(type) {
|
|
case LanguageId:
|
|
return getLocaleValue<codeToLanguage>(kCFLocaleLanguageCode);
|
|
case TerritoryId:
|
|
return getLocaleValue<QLocalePrivate::codeToTerritory>(kCFLocaleCountryCode);
|
|
case ScriptId:
|
|
return getLocaleValue<QLocalePrivate::codeToScript>(kCFLocaleScriptCode);
|
|
case DecimalPoint:
|
|
return getCFLocaleValue(kCFLocaleDecimalSeparator);
|
|
case GroupSeparator:
|
|
return getCFLocaleValue(kCFLocaleGroupingSeparator);
|
|
case DateFormatLong:
|
|
case DateFormatShort:
|
|
return getMacDateFormat(type == DateFormatShort
|
|
? kCFDateFormatterShortStyle
|
|
: kCFDateFormatterLongStyle);
|
|
case TimeFormatLong:
|
|
case TimeFormatShort:
|
|
return getMacTimeFormat(type == TimeFormatShort
|
|
? kCFDateFormatterShortStyle
|
|
: kCFDateFormatterLongStyle);
|
|
case DayNameLong:
|
|
case DayNameShort:
|
|
case DayNameNarrow:
|
|
case StandaloneDayNameLong:
|
|
case StandaloneDayNameShort:
|
|
case StandaloneDayNameNarrow:
|
|
return macDayName(in.toInt(), type);
|
|
case MonthNameLong:
|
|
case MonthNameShort:
|
|
case MonthNameNarrow:
|
|
case StandaloneMonthNameLong:
|
|
case StandaloneMonthNameShort:
|
|
case StandaloneMonthNameNarrow:
|
|
return macMonthName(in.toInt(), type);
|
|
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 macZeroDigit();
|
|
|
|
case MeasurementSystem:
|
|
return macMeasurementSystem();
|
|
|
|
case AMText:
|
|
case PMText: {
|
|
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
|
|
QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterLongStyle, kCFDateFormatterLongStyle);
|
|
QCFType<CFStringRef> value = static_cast<CFStringRef>(CFDateFormatterCopyProperty(formatter,
|
|
(type == AMText ? kCFDateFormatterAMSymbol : kCFDateFormatterPMSymbol)));
|
|
return QString::fromCFString(value);
|
|
}
|
|
case FirstDayOfWeek:
|
|
return QVariant(macFirstDayOfWeek());
|
|
case CurrencySymbol:
|
|
return macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()));
|
|
case CurrencyToString:
|
|
return macFormatCurrency(in.value<CurrencyToStringArgument>());
|
|
case UILanguages: {
|
|
QStringList result;
|
|
QCFType<CFArrayRef> languages = CFLocaleCopyPreferredLanguages();
|
|
const CFIndex cnt = CFArrayGetCount(languages);
|
|
result.reserve(cnt);
|
|
for (CFIndex i = 0; i < cnt; ++i) {
|
|
const QString lang = QString::fromCFString(
|
|
static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages, i)));
|
|
result.append(lang);
|
|
}
|
|
return QVariant(result);
|
|
}
|
|
case StringToStandardQuotation:
|
|
case StringToAlternateQuotation:
|
|
return macQuoteString(type, in.value<QStringView>());
|
|
default:
|
|
break;
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
#endif // QT_NO_SYSTEMLOCALE
|
|
|
|
QT_END_NAMESPACE
|