Files
wxWidgets/src/msw/uilocale.cpp
Vadim Zeitlin aa4cce8f7d Leave NULL impl pointer in wxUILocale if locale is not supported
This finally seems better than always creating some kind of impl object,
even if the locale isn't supported at all and makes all ports behave
consistently, as previously CreateForLocale() only returned NULL in
wxOSX implementation if the locale was unrecognized, but now all ports
do this (at least when locale_t and related functions are available
under Unix).

Also stop returning bool from Use(), as this resulted in initializing
wxLocale with any non-default language to fail under Mac: just ignore
the error here, as we always did it until now, because there is nothing
we can do about it anyhow.
2021-09-01 18:11:40 +02:00

427 lines
13 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/msw/uilocale.cpp
// Purpose: wxUILocale implementation for MSW
// Author: Vadim Zeitlin
// Created: 2021-07-31
// Copyright: (c) 2021 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#if wxUSE_INTL
#include "wx/uilocale.h"
#include "wx/private/uilocale.h"
#include "wx/msw/private/uilocale.h"
#include "wx/dynlib.h"
#ifndef LOCALE_NAME_USER_DEFAULT
#define LOCALE_NAME_USER_DEFAULT NULL
#endif
#ifndef MUI_LANGUAGE_NAME
#define MUI_LANGUAGE_NAME 8
#endif
// ============================================================================
// 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));
}
// ----------------------------------------------------------------------------
// wxLocaleIdent::GetName() implementation for MSW
// ----------------------------------------------------------------------------
wxString wxLocaleIdent::GetName() const
{
// Construct name in right format:
// Windows: <language>-<script>-<REGION>
wxString name;
if ( !m_language.empty() )
{
name << m_language;
if ( !m_script.empty() )
{
name << "-" << m_script;
}
if ( !m_region.empty() )
{
name << "-" << m_region;
}
}
return name;
}
// ----------------------------------------------------------------------------
// LCID-based wxUILocale implementation for MSW
// ----------------------------------------------------------------------------
// TODO-XP: Replace with wxUILocaleImplName when we don't support XP any longer.
class wxUILocaleImplLCID : public wxUILocaleImpl
{
public:
explicit wxUILocaleImplLCID(LCID lcid)
: m_lcid(lcid)
{
}
void Use() wxOVERRIDE
{
wxUseLCID(m_lcid);
}
wxString GetName() const wxOVERRIDE
{
wxChar buf[256];
buf[0] = wxT('\0');
// Try using newer constant available since Vista which produces names
// more similar to the other platforms.
if ( wxGetWinVersion() >= wxWinVersion_Vista )
{
::GetLocaleInfo(m_lcid, LOCALE_SNAME, buf, WXSIZEOF(buf));
}
else // TODO-XP: Drop this branch.
{
// This name constant is available under all systems, including
// pre-Vista ones.
::GetLocaleInfo(m_lcid, LOCALE_SENGLANGUAGE, buf, WXSIZEOF(buf));
}
return buf;
}
wxString GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const wxOVERRIDE
{
return wxGetInfoFromLCID(m_lcid, index, cat);
}
int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags) const wxOVERRIDE
{
// Can't be really implemented on the OS versions where this class is
// used.
return flags & wxCompare_CaseInsensitive
? lhs.CmpNoCase(rhs)
: lhs.Cmp(rhs) ;
}
private:
const LCID m_lcid;
wxDECLARE_NO_COPY_CLASS(wxUILocaleImplLCID);
};
// ----------------------------------------------------------------------------
// Name-based wxUILocale implementation for MSW
// ----------------------------------------------------------------------------
class wxUILocaleImplName : public wxUILocaleImpl
{
public:
// TODO-XP: Get rid of this function and all the code branches handling the
// return value of "false" from it and just always use this class.
static bool CanUse()
{
static int s_canUse = -1;
if ( s_canUse == -1 )
{
// One time only initialization.
s_canUse = 0;
wxLoadedDLL dllKernel32(wxS("kernel32.dll"));
wxDL_INIT_FUNC(ms_, GetLocaleInfoEx, dllKernel32);
if ( !ms_GetLocaleInfoEx )
return false;
wxDL_INIT_FUNC(ms_, SetThreadPreferredUILanguages, dllKernel32);
if ( !ms_SetThreadPreferredUILanguages )
return false;
wxDL_INIT_FUNC(ms_, CompareStringEx, dllKernel32);
if ( !ms_CompareStringEx )
return false;
s_canUse = 1;
}
return s_canUse == 1;
}
// Create object corresponding to the default user locale.
static wxUILocaleImplName* CreateDefault()
{
return new wxUILocaleImplName(LOCALE_NAME_USER_DEFAULT);
}
// Create object corresponding to the given locale, return NULL if not
// supported.
static wxUILocaleImplName* Create(const wchar_t* name)
{
// Getting the locale name seems to be the simplest way to see if it's
// really supported: unknown locale result in an error here.
if ( !ms_GetLocaleInfoEx(name, LOCALE_SNAME, NULL, 0) )
return NULL;
return new wxUILocaleImplName(name);
}
~wxUILocaleImplName() wxOVERRIDE
{
free(const_cast<wchar_t*>(m_name));
}
void Use() wxOVERRIDE
{
// Construct a double NUL-terminated buffer.
wchar_t buf[256];
if ( m_name )
wxStrlcpy(buf, m_name, WXSIZEOF(buf) - 1);
else
buf[0] = L'\0';
buf[wxWcslen(buf) + 1] = L'\0';
ULONG num = 1;
if ( !ms_SetThreadPreferredUILanguages(MUI_LANGUAGE_NAME, buf, &num) )
wxLogLastError(wxT("SetThreadPreferredUILanguages"));
}
wxString GetName() const wxOVERRIDE
{
return DoGetInfo(LOCALE_SNAME);
}
wxString GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const wxOVERRIDE
{
// TODO-XP: This duplicates code from in wxGetInfoFromLCID(), but
// it's only temporary because we will drop all code using LCID soon.
wxString str;
switch ( index )
{
case wxLOCALE_THOUSANDS_SEP:
str = DoGetInfo(LOCALE_STHOUSAND);
break;
case wxLOCALE_DECIMAL_POINT:
str = DoGetInfo(cat == wxLOCALE_CAT_MONEY
? LOCALE_SMONDECIMALSEP
: LOCALE_SDECIMAL);
break;
case wxLOCALE_SHORT_DATE_FMT:
case wxLOCALE_LONG_DATE_FMT:
case wxLOCALE_TIME_FMT:
str = DoGetInfo(wxGetLCTYPEFormatFromLocalInfo(index));
if ( !str.empty() )
str = wxTranslateFromUnicodeFormat(str);
break;
case wxLOCALE_DATE_TIME_FMT:
// there doesn't seem to be any specific setting for this, so just
// combine date and time ones
//
// we use the short date because this is what "%c" uses by default
// ("%#c" uses long date but we have no way to specify the
// alternate representation here)
str << GetInfo(wxLOCALE_SHORT_DATE_FMT, cat)
<< wxS(' ')
<< GetInfo(wxLOCALE_TIME_FMT, cat);
break;
default:
wxFAIL_MSG( "unknown wxLocaleInfo" );
}
return str;
}
int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags) const wxOVERRIDE
{
DWORD dwFlags = 0;
if ( flags & wxCompare_CaseInsensitive )
dwFlags |= NORM_IGNORECASE;
const int ret = ms_CompareStringEx
(
m_name,
dwFlags,
lhs.wc_str(), -1,
rhs.wc_str(), -1,
NULL, // [out] version information -- not needed
wxRESERVED_PARAM,
wxRESERVED_PARAM
);
switch ( ret )
{
case CSTR_LESS_THAN:
return -1;
case CSTR_EQUAL:
return 0;
case CSTR_GREATER_THAN:
return 1;
}
wxFAIL_MSG(wxS("Unreachable"));
return 0;
}
private:
typedef int (WINAPI *GetLocaleInfoEx_t)(LPCWSTR, LCTYPE, LPWSTR, int);
static GetLocaleInfoEx_t ms_GetLocaleInfoEx;
typedef BOOL (WINAPI *SetThreadPreferredUILanguages_t)(DWORD, CONST WCHAR*, ULONG*);
static SetThreadPreferredUILanguages_t ms_SetThreadPreferredUILanguages;
// Note: we currently don't use NLSVERSIONINFO output parameter and so we
// don't bother dealing with the different sizes of this struct under
// different OS versions and define the function type as using "void*" to
// avoid using this parameter accidentally. If we ever really need to use
// it, we'd need to check the OS version/struct size during run-time.
typedef int (WINAPI *CompareStringEx_t)(LPCWSTR, DWORD,
CONST WCHAR*, int,
CONST WCHAR*, int,
void*, // actually LPNLSVERSIONINFO
void*,
LPARAM);
static CompareStringEx_t ms_CompareStringEx;
// Ctor is private, use CreateDefault() or Create() instead.
//
// Note that "name" can be NULL here (LOCALE_NAME_USER_DEFAULT).
explicit wxUILocaleImplName(const wchar_t* name)
: m_name(name ? wxStrdup(name) : NULL)
{
}
wxString DoGetInfo(LCTYPE lctype) const
{
wchar_t buf[256];
if ( !ms_GetLocaleInfoEx(m_name, lctype, buf, WXSIZEOF(buf)) )
{
wxLogLastError(wxT("GetLocaleInfoEx"));
return wxString();
}
return buf;
}
const wchar_t* const m_name;
wxDECLARE_NO_COPY_CLASS(wxUILocaleImplName);
};
wxUILocaleImplName::GetLocaleInfoEx_t wxUILocaleImplName::ms_GetLocaleInfoEx;
wxUILocaleImplName::SetThreadPreferredUILanguages_t wxUILocaleImplName::ms_SetThreadPreferredUILanguages;
wxUILocaleImplName::CompareStringEx_t wxUILocaleImplName::ms_CompareStringEx;
// ----------------------------------------------------------------------------
// wxUILocaleImpl implementation
// ----------------------------------------------------------------------------
/* static */
wxUILocaleImpl* wxUILocaleImpl::CreateStdC()
{
if ( !wxUILocaleImplName::CanUse() )
{
// There is no LCID for "C" locale, but US English is basically the same.
LCID lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
return new wxUILocaleImplLCID(lcid);
}
return wxUILocaleImplName::Create(L"en-US");
}
/* static */
wxUILocaleImpl* wxUILocaleImpl::CreateUserDefault()
{
if ( !wxUILocaleImplName::CanUse() )
return new wxUILocaleImplLCID(LOCALE_USER_DEFAULT);
return wxUILocaleImplName::CreateDefault();
}
/* 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());
}
/* static */
wxUILocaleImpl* wxUILocaleImpl::CreateForLocale(const wxLocaleIdent& locId)
{
if ( !wxUILocaleImplName::CanUse() )
{
// We could try finding the LCID matching the name, but support for XP
// will be dropped soon, so it just doesn't seem worth to do it (note
// that LocaleNameToLCID() itself is not available in XP neither, so we
// can't just use it here).
return NULL;
}
return wxUILocaleImplName::Create(locId.GetName().wc_str());
}
#endif // wxUSE_INTL