diff options
Diffstat (limited to 'src/corelib/text/qlocale_mac.mm')
-rw-r--r-- | src/corelib/text/qlocale_mac.mm | 632 |
1 files changed, 401 insertions, 231 deletions
diff --git a/src/corelib/text/qlocale_mac.mm b/src/corelib/text/qlocale_mac.mm index 95c8769096..89339be2eb 100644 --- a/src/corelib/text/qlocale_mac.mm +++ b/src/corelib/text/qlocale_mac.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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$ -** -****************************************************************************/ +// 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" @@ -43,95 +7,289 @@ #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 */ -static QByteArray envVarLocale() +Q_LOGGING_CATEGORY(lcLocale, "qt.core.locale") + +static void printLocalizationInformation() { - QByteArray -#ifdef Q_OS_UNIX - lang = qgetenv("LC_ALL"); - if (lang.isEmpty()) - lang = qgetenv("LC_NUMERIC"); - if (lang.isEmpty()) + 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 - lang = qgetenv("LANG"); - return lang; + + 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() { - QString result = QString::fromLocal8Bit(envVarLocale()); - - QString lang, script, cntry; - if (result.isEmpty() - || (result != QLatin1String("C") && !qt_splitLocaleName(result, lang, script, cntry))) { - QCFType<CFLocaleRef> l = CFLocaleCopyCurrent(); - CFStringRef locale = CFLocaleGetIdentifier(l); - result = QString::fromCFString(locale); - } - return result; + QCFType<CFLocaleRef> l = CFLocaleCopyCurrent(); + CFStringRef locale = CFLocaleGetIdentifier(l); + return QString::fromCFString(locale); } -static QString macMonthName(int month, bool short_format) +static QVariant macMonthName(int month, QSystemLocale::QueryType type) { month -= 1; if (month < 0 || month > 11) - return QString(); + 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, - short_format ? kCFDateFormatterShortMonthSymbols - : kCFDateFormatterMonthSymbols)); + = static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, formatterType)); + if (values != 0) { CFStringRef cfstring = static_cast<CFStringRef>(CFArrayGetValueAtIndex(values, month)); return QString::fromCFString(cfstring); } - return QString(); + return {}; } -static QString macDayName(int day, bool short_format) +static QVariant macDayName(int day, QSystemLocale::QueryType type) { if (day < 1 || day > 7) - return QString(); + return {}; QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(0, QCFType<CFLocaleRef>(CFLocaleCopyCurrent()), kCFDateFormatterNoStyle, kCFDateFormatterNoStyle); - QCFType<CFArrayRef> values = static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, - short_format ? kCFDateFormatterShortWeekdaySymbols - : kCFDateFormatterWeekdaySymbols)); + + 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 QString(); + return {}; } -static QString macDateToString(QDate date, bool short_format) +static QString macZeroDigit() { - QCFType<CFDateRef> myDate = QDateTime(date, QTime()).toCFDate(); + 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(); - CFDateFormatterStyle style = short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle; QCFType<CFDateFormatterRef> myFormatter - = CFDateFormatterCreate(kCFAllocatorDefault, - mylocale, style, + = CFDateFormatterCreate(kCFAllocatorDefault, mylocale, style, kCFDateFormatterNoStyle); - return QCFString(CFDateFormatterCreateStringWithDate(0, myFormatter, myDate)); + QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(nullptr, myFormatter, myDate); + return QString::fromCFString(text); } -static QString macTimeToString(QTime time, bool short_format) +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(); @@ -140,7 +298,8 @@ static QString macTimeToString(QTime time, bool short_format) mylocale, kCFDateFormatterNoStyle, style); - return QCFString(CFDateFormatterCreateStringWithDate(0, myFormatter, myDate)); + QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(0, myFormatter, myDate); + return QString::fromCFString(text); } // Mac uses the Unicode CLDR format codes @@ -148,126 +307,132 @@ static QString macTimeToString(QTime time, bool short_format) // 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) +static QVariant macToQtFormat(QStringView sys_fmt) { QString result; - int i = 0; + qsizetype 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("''"); + if (text == "'"_L1) + result += "''"_L1; else - result += QLatin1Char('\'') + text + QLatin1Char('\''); + result += u'\'' + text + u'\''; continue; } QChar c = sys_fmt.at(i); - int repeat = qt_repeatCount(sys_fmt.mid(i)); + qsizetype repeat = qt_repeatCount(sys_fmt.sliced(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 + 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), padded number. + // Explicitly has no special case for 'uu' as only the last two digits. + result += "yyyy"_L1; + break; + 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) + result += u't'; + break; + case 'v': // Time Zone (1, 4) + case 'V': // Time Zone (1..4) + result += "tttt"_L1; + break; + case 'x': // Time Zone (1..5) + case 'X': // Time Zone (1..5) + result += (repeat > 1 && (repeat & 1)) ? "ttt"_L1 : "tt"_L1; + break; + case 'z': // Time Zone (1..4) + case 'Z': // Time Zone (1..5) + result += repeat < 4 ? "tt"_L1 : repeat > 4 ? "ttt"_L1 : "t"_L1; + 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; - 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; + break; } i += repeat; } - return result; + return !result.isEmpty() ? QVariant::fromValue(result) : QVariant(); } -QString getMacDateFormat(CFDateFormatterStyle style) +static QVariant getMacDateFormat(CFDateFormatterStyle style) { QCFType<CFLocaleRef> l = CFLocaleCopyCurrent(); QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault, @@ -275,7 +440,7 @@ QString getMacDateFormat(CFDateFormatterStyle style) return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter))); } -static QString getMacTimeFormat(CFDateFormatterStyle style) +static QVariant getMacTimeFormat(CFDateFormatterStyle style) { QCFType<CFLocaleRef> l = CFLocaleCopyCurrent(); QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault, @@ -292,11 +457,11 @@ static QVariant getCFLocaleValue(CFStringRef key) return QString::fromCFString(CFStringRef(static_cast<CFTypeRef>(value))); } -static QLocale::MeasurementSystem macMeasurementSystem() +static QVariant macMeasurementSystem() { QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); CFStringRef system = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleMeasurementSystem)); - if (QString::fromCFString(system) == QLatin1String("Metric")) { + if (QString::fromCFString(system) == "Metric"_L1) { return QLocale::MetricSystem; } else { return QLocale::ImperialSystem; @@ -313,7 +478,7 @@ static quint8 macFirstDayOfWeek() return day; } -static QString macCurrencySymbol(QLocale::CurrencySymbolFormat format) +static QVariant macCurrencySymbol(QLocale::CurrencySymbolFormat format) { QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); switch (format) { @@ -329,44 +494,33 @@ static QString macCurrencySymbol(QLocale::CurrencySymbolFormat format) default: break; } - return QString(); -} - -static QString macZeroDigit() -{ - QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); - QCFType<CFNumberFormatterRef> numberFormatter = - CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle); - static const int zeroDigit = 0; - QCFType<CFStringRef> value = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter, - kCFNumberIntType, &zeroDigit); - return QString::fromCFString(value); + return {}; } #ifndef QT_NO_SYSTEMLOCALE -static QString macFormatCurrency(const QSystemLocale::CurrencyToStringArgument &arg) +static QVariant macFormatCurrency(const QSystemLocale::CurrencyToStringArgument &arg) { QCFType<CFNumberRef> value; - switch (arg.value.type()) { - case QVariant::Int: - case QVariant::UInt: { + switch (arg.value.metaType().id()) { + case QMetaType::Int: + case QMetaType::UInt: { int v = arg.value.toInt(); value = CFNumberCreate(NULL, kCFNumberIntType, &v); break; } - case QVariant::Double: { + case QMetaType::Double: { double v = arg.value.toDouble(); value = CFNumberCreate(NULL, kCFNumberDoubleType, &v); break; } - case QVariant::LongLong: - case QVariant::ULongLong: { + case QMetaType::LongLong: + case QMetaType::ULongLong: { qint64 v = arg.value.toLongLong(); value = CFNumberCreate(NULL, kCFNumberLongLongType, &v); break; } default: - return QString(); + return {}; } QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); @@ -402,17 +556,42 @@ static QVariant macQuoteString(QSystemLocale::QueryType type, QStringView str) #ifndef QT_NO_SYSTEMLOCALE -QLocale QSystemLocale::fallbackUiLocale() const +QLocale QSystemLocale::fallbackLocale() const { return QLocale(getMacLocaleName()); } -QVariant QSystemLocale::query(QueryType type, QVariant in) const +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 Name: -// return getMacLocaleName(); + 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: @@ -429,12 +608,18 @@ QVariant QSystemLocale::query(QueryType type, QVariant in) const : kCFDateFormatterLongStyle); case DayNameLong: case DayNameShort: - return macDayName(in.toInt(), (type == DayNameShort)); + case DayNameNarrow: + case StandaloneDayNameLong: + case StandaloneDayNameShort: + case StandaloneDayNameNarrow: + return macDayName(in.toInt(), type); case MonthNameLong: case MonthNameShort: + case MonthNameNarrow: case StandaloneMonthNameLong: case StandaloneMonthNameShort: - return macMonthName(in.toInt(), (type == MonthNameShort || type == StandaloneMonthNameShort)); + case StandaloneMonthNameNarrow: + return macMonthName(in.toInt(), type); case DateToStringShort: case DateToStringLong: return macDateToString(in.toDate(), (type == DateToStringShort)); @@ -446,10 +631,10 @@ QVariant QSystemLocale::query(QueryType type, QVariant in) const case PositiveSign: break; case ZeroDigit: - return QVariant(macZeroDigit()); + return macZeroDigit(); case MeasurementSystem: - return QVariant(static_cast<int>(macMeasurementSystem())); + return macMeasurementSystem(); case AMText: case PMText: { @@ -462,33 +647,18 @@ QVariant QSystemLocale::query(QueryType type, QVariant in) const case FirstDayOfWeek: return QVariant(macFirstDayOfWeek()); case CurrencySymbol: - return QVariant(macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()))); + return macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt())); case CurrencyToString: - return macFormatCurrency(in.value<QSystemLocale::CurrencyToStringArgument>()); + return macFormatCurrency(in.value<CurrencyToStringArgument>()); case UILanguages: { - QCFType<CFPropertyListRef> 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<CFArrayRef>()); - result.reserve(cnt); - for (int i = 0; i < cnt; ++i) { - const QString lang = QString::fromCFString( - static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages.as<CFArrayRef>(), i))); - result.append(lang); - } - } else if (typeId == CFStringGetTypeID()) { - result = QStringList(QString::fromCFString(languages.as<CFStringRef>())); - } else { - qWarning("QLocale::uiLanguages(): CFPreferencesCopyValue returned unhandled type \"%ls\"; please report to http://bugreports.qt.io", - qUtf16Printable(QString::fromCFString(CFCopyTypeIDDescription(typeId)))); + 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); } |