diff options
Diffstat (limited to 'src/corelib/text/qlocale_mac.mm')
-rw-r--r-- | src/corelib/text/qlocale_mac.mm | 186 |
1 files changed, 103 insertions, 83 deletions
diff --git a/src/corelib/text/qlocale_mac.mm b/src/corelib/text/qlocale_mac.mm index 7d75db31d0..ed4399e6a3 100644 --- a/src/corelib/text/qlocale_mac.mm +++ b/src/corelib/text/qlocale_mac.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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" @@ -50,12 +14,52 @@ #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(); @@ -152,23 +156,35 @@ static QVariant macDayName(int day, QSystemLocale::QueryType type) static QString macZeroDigit() { - QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); - QCFType<CFNumberFormatterRef> numberFormatter = - CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle); - const int zeroDigit = 0; - QCFType<CFStringRef> value - = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter, - kCFNumberIntType, &zeroDigit); - return QString::fromCFString(value); + 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, int minDigits, const QString &zero) +static QString zeroPad(QString &&number, qsizetype minDigits, const QString &zero) { // Need to pad with zeros, possibly after a sign. - int insert = -1, digits = 0; + qsizetype insert = -1, digits = 0; auto it = QStringIterator(number); while (it.hasNext()) { - int here = it.index(); + qsizetype here = it.index(); if (QChar::isDigit(it.next())) { if (insert < 0) insert = here; @@ -188,10 +204,10 @@ 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. - int first = -1, prev = -1, last = -1; + qsizetype first = -1, prev = -1, last = -1; auto it = QStringIterator(number); while (it.hasNext()) { - int here = it.index(); + qsizetype here = it.index(); if (QChar::isDigit(it.next())) { if (first == -1) last = first = here; @@ -222,12 +238,16 @@ static QString fourDigitYear(int year, const QString &zero) static QString macDateToStringImpl(QDate date, CFDateFormatterStyle style) { - QCFType<CFDateRef> myDate = date.startOfDay().toCFDate(); + // 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(0, myFormatter, myDate); + QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(nullptr, myFormatter, myDate); return QString::fromCFString(text); } @@ -235,12 +255,14 @@ static QVariant macDateToString(QDate date, bool short_format) { const int year = date.year(); QString fakeYear, trueYear; - if (year < 0) { + 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 > 0); + 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 @@ -253,7 +275,7 @@ static QVariant macDateToString(QDate date, bool short_format) QString text = macDateToStringImpl(date, short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle); - if (year < 0) { + if (year < 1583) { if (text.contains(fakeYear)) return std::move(text).replace(fakeYear, trueYear); // Cope with two-digit year: @@ -288,20 +310,20 @@ static QVariant macTimeToString(QTime time, bool short_format) 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.mid(i)); switch (c.unicode()) { // Qt does not support the following options @@ -322,17 +344,17 @@ static QVariant macToQtFormat(QStringView sys_fmt) 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"); + result += "yy"_L1; else - result += QLatin1String("yyyy"); + result += "yyyy"_L1; 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"); + result += "MMM"_L1; else - result += QString(repeat, QLatin1Char('M')); + result += QString(repeat, u'M'); break; case 'd': // Day of Month (1..2): 1..2 padded number result += QString(repeat, c); @@ -340,32 +362,32 @@ static QVariant macToQtFormat(QStringView sys_fmt) 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"); + result += "dddd"_L1; else - result += QLatin1String("ddd"); + result += "ddd"_L1; 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"); + result += "ddd"_L1; else - result += QString(repeat, QLatin1Char('d')); + result += QString(repeat, 'd'_L1); break; case 'a': // AM/PM (1): 1 = short // Translate to Qt uppercase AM/PM - result += QLatin1String("AP"); + 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 case 'j': // Local Hour [12 or 24] (1..2): 1..2 = padded number // Qt h is local hour - result += QString(repeat, QLatin1Char('h')); + 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, QLatin1Char('H')); + result += QString(repeat, 'H'_L1); break; case 'm': // Minutes (1..2): 1..2 = padded number case 's': // Seconds (1..2): 1..2 = padded number @@ -374,9 +396,9 @@ static QVariant macToQtFormat(QStringView sys_fmt) 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'); + result += u'z'; else - result += QLatin1String("zzz"); + result += "zzz"_L1; break; case 'z': // Time Zone (1..4) case 'Z': // Time Zone (1..5) @@ -385,16 +407,14 @@ static QVariant macToQtFormat(QStringView sys_fmt) case 'V': // Time Zone (1..4) case 'X': // Time Zone (1..5) case 'x': // Time Zone (1..5) - result += QLatin1Char('t'); + 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 < QLatin1Char('A') || c > QLatin1Char('z') || - (c > QLatin1Char('Z') && c < QLatin1Char('a'))) { + if (c < u'A' || c > u'z' || (c > u'Z' && c < u'a')) result += QString(repeat, c); - } break; } @@ -433,7 +453,7 @@ 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; @@ -553,7 +573,7 @@ static QLocale::Language codeToLanguage(QStringView s) return QLocalePrivate::codeToLanguage(s); } -QVariant QSystemLocale::query(QueryType type, QVariant in) const +QVariant QSystemLocale::query(QueryType type, QVariant &&in) const { QMacAutoReleasePool pool; @@ -621,13 +641,13 @@ QVariant QSystemLocale::query(QueryType type, QVariant in) const case CurrencySymbol: return macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt())); case CurrencyToString: - return macFormatCurrency(in.value<QSystemLocale::CurrencyToStringArgument>()); + return macFormatCurrency(in.value<CurrencyToStringArgument>()); case UILanguages: { QStringList result; QCFType<CFArrayRef> languages = CFLocaleCopyPreferredLanguages(); - const int cnt = CFArrayGetCount(languages); + const CFIndex cnt = CFArrayGetCount(languages); result.reserve(cnt); - for (int i = 0; i < cnt; ++i) { + for (CFIndex i = 0; i < cnt; ++i) { const QString lang = QString::fromCFString( static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages, i))); result.append(lang); |