From 37a23e1ab1d4fd254379f41b65dcc49befa1a8c9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 14 Aug 2021 17:42:29 +0100 Subject: [PATCH] Move platform-specific parts of wxLocale::Init() to wxUILocale This is tidier than using #ifdefs in the same common file and also ensures that initializing wxLocale affects wxUILocale too, which will be important for compatibility when the code elsewhere is modified to use wxUILocale::GetInfo() instead of wxLocale::GetInfo() in the upcoming commits. This commit is best viewed with --color-moved git option. --- include/wx/msw/private/uilocale.h | 28 ++++ include/wx/private/uilocale.h | 9 ++ include/wx/uilocale.h | 6 + include/wx/unix/private/uilocale.h | 33 +++++ src/common/intl.cpp | 199 +++-------------------------- src/common/uilocale.cpp | 12 ++ src/msw/uilocale.cpp | 60 +++++++-- src/osx/core/uilocale.cpp | 21 ++- src/unix/uilocale.cpp | 95 ++++++++++++++ 9 files changed, 270 insertions(+), 193 deletions(-) create mode 100644 include/wx/msw/private/uilocale.h create mode 100644 include/wx/unix/private/uilocale.h diff --git a/include/wx/msw/private/uilocale.h b/include/wx/msw/private/uilocale.h new file mode 100644 index 0000000000..8684213610 --- /dev/null +++ b/include/wx/msw/private/uilocale.h @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/msw/private/uilocale.h +// Purpose: MSW-specific locale-related helpers +// Author: Vadim Zeitlin +// Created: 2021-08-14 +// Copyright: (c) 2021 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_MSW_PRIVATE_UILOCALE_H_ +#define _WX_MSW_PRIVATE_UILOCALE_H_ + +#include "wx/msw/private.h" // Include to get LCID. + +#ifndef LOCALE_SNAME +#define LOCALE_SNAME 0x5c +#endif +#ifndef LOCALE_CUSTOM_UI_DEFAULT +#define LOCALE_CUSTOM_UI_DEFAULT 0x1400 +#endif + +// Use the specific LCID for the current thread. +void wxUseLCID(LCID lcid); + +// This function is defined in src/common/intl.cpp +wxString wxGetInfoFromLCID(LCID lcid, wxLocaleInfo index, wxLocaleCategory cat); + +#endif // _WX_MSW_PRIVATE_UILOCALE_H_ diff --git a/include/wx/private/uilocale.h b/include/wx/private/uilocale.h index 89ce17a63c..bfa6f318ea 100644 --- a/include/wx/private/uilocale.h +++ b/include/wx/private/uilocale.h @@ -33,6 +33,15 @@ public: // It may return NULL in case of failure. static wxUILocaleImpl* CreateUserDefault(); + // This function exists only for wxLocale compatibility and sets the locale + // corresponding to the given language. + // + // The language passed to this function is a valid language, i.e. neither + // wxLANGUAGE_UNKNOWN nor wxLANGUAGE_DEFAULT. + // + // It may return NULL in case of failure. + static wxUILocaleImpl* CreateForLanguage(const wxLanguageInfo& info); + // Functions corresponding to wxUILocale ones. virtual wxString GetName() const = 0; virtual wxString GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const = 0; diff --git a/include/wx/uilocale.h b/include/wx/uilocale.h index f3aa9a2bb3..465baa3ffe 100644 --- a/include/wx/uilocale.h +++ b/include/wx/uilocale.h @@ -29,6 +29,12 @@ public: // Configure the UI to use the default user locale. static bool UseDefault(); + // Use the locale corresponding to the given language. + // + // This is a compatibility function used by wxWidgets itself, don't use it + // in the new code. + static bool UseLanguage(const wxLanguageInfo& info); + // Get the object corresponding to the currently used locale. static const wxUILocale& GetCurrent(); diff --git a/include/wx/unix/private/uilocale.h b/include/wx/unix/private/uilocale.h new file mode 100644 index 0000000000..7a882810d0 --- /dev/null +++ b/include/wx/unix/private/uilocale.h @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/unix/private/uilocale.h +// Purpose: Various locale-related helpers used under Unix systems only +// Author: Vadim Zeitlin +// Created: 2021-08-14 (extracted from src/common/intl.cpp) +// Copyright: (c) 2021 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_UNIX_PRIVATE_UILOCALE_H_ +#define _WX_UNIX_PRIVATE_UILOCALE_H_ + +#include "wx/string.h" + +// get just the language part ("en" in "en_GB") +inline wxString ExtractLang(const wxString& langFull) +{ + return langFull.BeforeFirst('_'); +} + +// get everything else (including the leading '_') +inline wxString ExtractNotLang(const wxString& langFull) +{ + size_t pos = langFull.find('_'); + if ( pos != wxString::npos ) + return langFull.substr(pos); + else + return wxString(); +} + +const char *wxSetlocaleTryAll(int c, const wxString& lc); + +#endif // _WX_UNIX_PRIVATE_UILOCALE_H_ diff --git a/src/common/intl.cpp b/src/common/intl.cpp index e0966452e3..5f2a74788f 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -43,18 +43,6 @@ #include #endif -#ifdef __WIN32__ - #include "wx/dynlib.h" - #include "wx/msw/private.h" - - #ifndef LOCALE_SNAME - #define LOCALE_SNAME 0x5c - #endif - #ifndef LOCALE_CUSTOM_UI_DEFAULT - #define LOCALE_CUSTOM_UI_DEFAULT 0x1400 - #endif -#endif - #include "wx/file.h" #include "wx/filename.h" #include "wx/tokenzr.h" @@ -63,13 +51,18 @@ #include "wx/apptrait.h" #include "wx/stdpaths.h" #include "wx/hashset.h" +#include "wx/uilocale.h" -#if defined(__WXOSX__) +#ifdef __WIN32__ + #include "wx/msw/private/uilocale.h" +#elif defined(__WXOSX__) #include "wx/osx/core/cfref.h" #include "wx/osx/core/cfstring.h" #include #include #include +#elif defined(__UNIX__) + #include "wx/unix/private/uilocale.h" #endif // ---------------------------------------------------------------------------- @@ -88,31 +81,6 @@ static wxLocale *wxSetLocale(wxLocale *pLocale); -namespace -{ - -#if defined(__UNIX__) - -// get just the language part ("en" in "en_GB") -inline wxString ExtractLang(const wxString& langFull) -{ - return langFull.BeforeFirst('_'); -} - -// get everything else (including the leading '_') -inline wxString ExtractNotLang(const wxString& langFull) -{ - size_t pos = langFull.find('_'); - if ( pos != wxString::npos ) - return langFull.substr(pos); - else - return wxString(); -} - -#endif // __UNIX__ - -} // anonymous namespace - // ---------------------------------------------------------------------------- // wxLanguageInfo // ---------------------------------------------------------------------------- @@ -396,89 +364,6 @@ bool wxLocale::DoCommonPostInit(bool success, return success; } -#if defined(__UNIX__) - -// Helper of wxSetlocaleTryAll() below which tries setting the given locale -// with and without UTF-8 suffix. Don't use this one directly. -static const char *wxSetlocaleTryUTF8(int c, const wxString& lc) -{ - const char *l = NULL; - - // NB: We prefer to set UTF-8 locale if it's possible and only fall back to - // non-UTF-8 locale if it fails, but this is not necessary under the - // supported macOS versions where xx_YY locales are just aliases to - // xx_YY.UTF-8 anyhow. -#if wxUSE_UNICODE && !defined(__WXMAC__) - if ( !lc.empty() ) - { - wxString buf(lc); - wxString buf2; - buf2 = buf + wxS(".UTF-8"); - l = wxSetlocale(c, buf2); - if ( !l ) - { - buf2 = buf + wxS(".utf-8"); - l = wxSetlocale(c, buf2); - } - if ( !l ) - { - buf2 = buf + wxS(".UTF8"); - l = wxSetlocale(c, buf2); - } - if ( !l ) - { - buf2 = buf + wxS(".utf8"); - l = wxSetlocale(c, buf2); - } - } - - // if we can't set UTF-8 locale, try non-UTF-8 one: - if ( !l ) -#endif // wxUSE_UNICODE && !__WXMAC__ - l = wxSetlocale(c, lc); - - return l; -} - -// Try setting all possible versions of the given locale, i.e. with and without -// UTF-8 encoding, and with or without the "_territory" part. -static const char *wxSetlocaleTryAll(int c, const wxString& lc) -{ - const char* l = wxSetlocaleTryUTF8(c, lc); - if ( !l ) - { - const wxString& lcOnlyLang = ExtractLang(lc); - if ( lcOnlyLang != lc ) - l = wxSetlocaleTryUTF8(c, lcOnlyLang); - } - - return l; -} - -#endif // __UNIX__ - -#ifdef __WIN32__ - -// Trivial wrapper for ::SetThreadUILanguage(). -// -// TODO-XP: Drop this when we don't support XP any longer. -static void wxMSWSetThreadUILanguage(LANGID langid) -{ - // SetThreadUILanguage() is available on XP, but with unclear - // behavior, so avoid calling it there. - if ( wxGetWinVersion() >= wxWinVersion_Vista ) - { - wxLoadedDLL dllKernel32(wxS("kernel32.dll")); - typedef LANGID(WINAPI *SetThreadUILanguage_t)(LANGID); - SetThreadUILanguage_t pfnSetThreadUILanguage = NULL; - wxDL_INIT_FUNC(pfn, SetThreadUILanguage, dllKernel32); - if (pfnSetThreadUILanguage) - pfnSetThreadUILanguage(langid); - } -} - -#endif // __WIN32__ - bool wxLocale::Init(int lang, int flags) { #if WXWIN_COMPATIBILITY_2_8 @@ -522,62 +407,19 @@ bool wxLocale::Init(int lang, int flags) #if defined(__UNIX__) || defined(__WIN32__) + bool ok = lang == wxLANGUAGE_DEFAULT ? wxUILocale::UseDefault() + : wxUILocale::UseLanguage(*info); + + // Under (non-Darwn) Unix wxUILocale already set the C locale, but under + // the other platforms we still have to do it here. +#if defined(__WIN32__) || defined(__WXOSX__) + // We prefer letting the CRT to set its locale on its own when using // default locale, as it does a better job of it than we do. We also have // to do this when we didn't recognize the default language at all. const char *retloc = lang == wxLANGUAGE_DEFAULT ? wxSetlocale(LC_ALL, "") - : NULL; + : info->TrySetLocale(); -#if defined(__UNIX__) - if ( !retloc ) - retloc = wxSetlocaleTryAll(LC_ALL, shortName); - - if ( !retloc ) - { - // Some C libraries (namely glibc) still use old ISO 639, - // so will translate the abbrev for them - wxString localeAlt; - const wxString& langOnly = ExtractLang(shortName); - if ( langOnly == wxS("he") ) - localeAlt = wxS("iw") + ExtractNotLang(shortName); - else if ( langOnly == wxS("id") ) - localeAlt = wxS("in") + ExtractNotLang(shortName); - else if ( langOnly == wxS("yi") ) - localeAlt = wxS("ji") + ExtractNotLang(shortName); - else if ( langOnly == wxS("nb") ) - localeAlt = wxS("no_NO"); - else if ( langOnly == wxS("nn") ) - localeAlt = wxS("no_NY"); - - if ( !localeAlt.empty() ) - retloc = wxSetlocaleTryAll(LC_ALL, localeAlt); - } -#elif defined(__WIN32__) - if ( lang == wxLANGUAGE_DEFAULT ) - { - ::SetThreadLocale(LOCALE_USER_DEFAULT); - wxMSWSetThreadUILanguage(LANG_USER_DEFAULT); - - // CRT locale already set above. - } - else if ( info->WinLang == 0 ) - { - wxLogWarning(wxS("Locale '%s' not supported by OS."), name); - - retloc = "C"; - } - else // language supported by Windows - { - const wxUint32 lcid = info->GetLCID(); - - // change locale used by Windows functions - ::SetThreadLocale(lcid); - - wxMSWSetThreadUILanguage(LANGIDFROMLCID(lcid)); - - // and also call setlocale() to change locale used by the CRT - retloc = info->TrySetLocale(); - } #if wxUSE_UNICODE && (defined(__VISUALC__) || defined(__MINGW32__)) // VC++ setlocale() (also used by Mingw) can't set locale to languages that // can only be written using Unicode, therefore wxSetlocale() call fails @@ -593,13 +435,15 @@ bool wxLocale::Init(int lang, int flags) } } #endif // CRT not handling Unicode-only languages -#else - #error "Unsupported platform" -#endif + + if ( !retloc ) + ok = false; + +#endif // __WIN32__ return DoCommonPostInit ( - retloc != NULL, + ok, name, // wxLANGUAGE_DEFAULT needs to be passed to wxTranslations as "" // for correct detection of user's preferred language(s) @@ -1123,8 +967,7 @@ wxLocale::~wxLocale() } #ifdef __WIN32__ - ::SetThreadLocale(m_oldLCID); - wxMSWSetThreadUILanguage(LANGIDFROMLCID(m_oldLCID)); + wxUseLCID(m_oldLCID); #endif } diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index bf79b77189..4843f823e5 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -53,6 +53,18 @@ bool wxUILocale::UseDefault() return true; } +/* static */ +bool wxUILocale::UseLanguage(const wxLanguageInfo& info) +{ + wxUILocaleImpl* const impl = wxUILocaleImpl::CreateForLanguage(info); + if ( !impl ) + return false; + + ms_current.SetImpl(impl); + + return true; +} + /* static */ const wxUILocale& wxUILocale::GetCurrent() { diff --git a/src/msw/uilocale.cpp b/src/msw/uilocale.cpp index 21070de0c0..6916fefe52 100644 --- a/src/msw/uilocale.cpp +++ b/src/msw/uilocale.cpp @@ -22,22 +22,48 @@ #include "wx/private/uilocale.h" +#include "wx/msw/private/uilocale.h" + #include "wx/dynlib.h" -#include "wx/msw/private.h" - -#ifndef LOCALE_SNAME -#define LOCALE_SNAME 0x5c -#endif - -// This function is defined in src/common/intl.cpp, just declare it here for -// now before refactoring the code. -wxString wxGetInfoFromLCID(LCID lcid, wxLocaleInfo index, wxLocaleCategory cat); - // ============================================================================ // implementation // ============================================================================ +// ---------------------------------------------------------------------------- +// helper functions +// ---------------------------------------------------------------------------- + +namespace +{ + +// Trivial wrapper for ::SetThreadUILanguage(). +// +// TODO-XP: Drop this when we don't support XP any longer. +static void wxMSWSetThreadUILanguage(LANGID langid) +{ + // SetThreadUILanguage() is available on XP, but with unclear + // behavior, so avoid calling it there. + if ( wxGetWinVersion() >= wxWinVersion_Vista ) + { + wxLoadedDLL dllKernel32(wxS("kernel32.dll")); + typedef LANGID(WINAPI *SetThreadUILanguage_t)(LANGID); + SetThreadUILanguage_t pfnSetThreadUILanguage = NULL; + wxDL_INIT_FUNC(pfn, SetThreadUILanguage, dllKernel32); + if (pfnSetThreadUILanguage) + pfnSetThreadUILanguage(langid); + } +} + +} // anonymous namespace + +void wxUseLCID(LCID lcid) +{ + ::SetThreadLocale(lcid); + + wxMSWSetThreadUILanguage(LANGIDFROMLCID(lcid)); +} + // ---------------------------------------------------------------------------- // wxUILocale implementation for MSW // ---------------------------------------------------------------------------- @@ -50,6 +76,7 @@ public: explicit wxUILocaleImplLCID(LCID lcid) : m_lcid(lcid) { + wxUseLCID(lcid); } wxString GetName() const wxOVERRIDE @@ -98,4 +125,17 @@ wxUILocaleImpl* wxUILocaleImpl::CreateUserDefault() return new wxUILocaleImplLCID(LOCALE_USER_DEFAULT); } +/* static */ +wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info) +{ + if ( info.WinLang == 0 ) + { + wxLogWarning(wxS("Locale '%s' not supported by OS."), info.Description); + + return NULL; + } + + return new wxUILocaleImplLCID(info.GetLCID()); +} + #endif // wxUSE_INTL diff --git a/src/osx/core/uilocale.cpp b/src/osx/core/uilocale.cpp index 3309343739..96a0058f5a 100644 --- a/src/osx/core/uilocale.cpp +++ b/src/osx/core/uilocale.cpp @@ -46,6 +46,15 @@ public: { } + static wxUILocaleImplCF* Create(const wxString& name) + { + CFLocaleRef cfloc = CFLocaleCreate(kCFAllocatorDefault, wxCFStringRef(name)); + if ( !cfloc ) + return NULL; + + return new wxUILocaleImplCF(cfloc); + } + wxString GetName() const wxOVERRIDE; wxString GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const wxOVERRIDE; @@ -76,11 +85,7 @@ wxUILocaleImplCF::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateStdC() { - CFLocaleRef cfloc = CFLocaleCreate(kCFAllocatorDefault, wxCFStringRef("C")); - if ( !cfloc ) - return NULL; - - return new wxUILocaleImplCF(cfloc); + return wxUILocaleImplCF::Create("C"); } /* static */ @@ -89,4 +94,10 @@ wxUILocaleImpl* wxUILocaleImpl::CreateUserDefault() return new wxUILocaleImplCF(CFLocaleCopyCurrent()); } +/* static */ +wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info) +{ + return wxUILocaleImplCF::Create(info.CanonicalName); +} + #endif // wxUSE_INTL diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index a817ad899d..f4b86fa0a0 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -22,6 +22,8 @@ #include "wx/private/uilocale.h" +#include "wx/unix/private/uilocale.h" + #include "wx/intl.h" #include @@ -52,6 +54,67 @@ private: // implementation // ============================================================================ +// Helper of wxSetlocaleTryAll() below which tries setting the given locale +// with and without UTF-8 suffix. Don't use this one directly. +static const char *wxSetlocaleTryUTF8(int c, const wxString& lc) +{ + const char *l = NULL; + + // NB: We prefer to set UTF-8 locale if it's possible and only fall back to + // non-UTF-8 locale if it fails, but this is not necessary under the + // supported macOS versions where xx_YY locales are just aliases to + // xx_YY.UTF-8 anyhow. +#if wxUSE_UNICODE && !defined(__WXMAC__) + if ( !lc.empty() ) + { + wxString buf(lc); + wxString buf2; + buf2 = buf + wxS(".UTF-8"); + l = wxSetlocale(c, buf2); + if ( !l ) + { + buf2 = buf + wxS(".utf-8"); + l = wxSetlocale(c, buf2); + } + if ( !l ) + { + buf2 = buf + wxS(".UTF8"); + l = wxSetlocale(c, buf2); + } + if ( !l ) + { + buf2 = buf + wxS(".utf8"); + l = wxSetlocale(c, buf2); + } + } + + // if we can't set UTF-8 locale, try non-UTF-8 one: + if ( !l ) +#endif // wxUSE_UNICODE && !__WXMAC__ + l = wxSetlocale(c, lc); + + return l; +} + +// Try setting all possible versions of the given locale, i.e. with and without +// UTF-8 encoding, and with or without the "_territory" part. +const char *wxSetlocaleTryAll(int c, const wxString& lc) +{ + const char* l = wxSetlocaleTryUTF8(c, lc); + if ( !l ) + { + const wxString& lcOnlyLang = ExtractLang(lc); + if ( lcOnlyLang != lc ) + l = wxSetlocaleTryUTF8(c, lcOnlyLang); + } + + return l; +} + +// ---------------------------------------------------------------------------- +// wxUILocale implementation for Unix +// ---------------------------------------------------------------------------- + wxUILocaleImplUnix::wxUILocaleImplUnix(const char* locale) { if ( locale ) @@ -88,4 +151,36 @@ wxUILocaleImpl* wxUILocaleImpl::CreateUserDefault() return new wxUILocaleImplUnix(""); } +/* static */ +wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info) +{ + // Set the locale before creating the wxUILocaleImplUnix object in order to + // check if we succeed in doing it. + + const wxString& shortName = info.CanonicalName; + + if ( !wxSetlocaleTryAll(LC_ALL, shortName) ) + { + // Some C libraries (namely glibc) still use old ISO 639, + // so will translate the abbrev for them + wxString localeAlt; + const wxString& langOnly = ExtractLang(shortName); + if ( langOnly == wxS("he") ) + localeAlt = wxS("iw") + ExtractNotLang(shortName); + else if ( langOnly == wxS("id") ) + localeAlt = wxS("in") + ExtractNotLang(shortName); + else if ( langOnly == wxS("yi") ) + localeAlt = wxS("ji") + ExtractNotLang(shortName); + else if ( langOnly == wxS("nb") ) + localeAlt = wxS("no_NO"); + else if ( langOnly == wxS("nn") ) + localeAlt = wxS("no_NY"); + + if ( localeAlt.empty() || !wxSetlocaleTryAll(LC_ALL, localeAlt) ) + return NULL; + } + + return new wxUILocaleImplUnix(NULL); +} + #endif // wxUSE_INTL