Add wxUILocale::CompareStrings() function

This function allows comparing strings using the sort order of the
specified locale, represented by the new wxLocaleIdent class.

It is implemented using CompareStringEx()[1] under MSW and
NSString::compare:options:range:locale:[2] under macOS, generic
implementation for the other platforms is upcoming.

[1]: https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringex
[2]: https://developer.apple.com/documentation/foundation/nsstring/1414561-compare?language=objc
This commit is contained in:
Alexander Koshelev 2021-08-27 17:54:02 +03:00 committed by Vadim Zeitlin
parent 9f43ec03e6
commit c8269210a2
6 changed files with 343 additions and 0 deletions

View File

@ -108,6 +108,63 @@ struct WXDLLIMPEXP_BASE wxLanguageInfo
const char* TrySetLocale() const;
};
// ----------------------------------------------------------------------------
// wxLocaleIdent: allows to fully identify a locale under all platforms
// ----------------------------------------------------------------------------
class WXDLLIMPEXP_BASE wxLocaleIdent
{
public:
// Leave language empty
wxLocaleIdent() { }
// Construct name from language
wxLocaleIdent(const wxString& language) : m_language(language) { }
// Set language
wxLocaleIdent& Language(const wxString& language)
{
m_language = language;
return *this;
}
// Set region
wxLocaleIdent& Region(const wxString& region)
{
m_region = region;
return *this;
}
// Set script
wxLocaleIdent& Script(const wxString& script)
{
m_script = script;
return *this;
}
// Set modifier
wxLocaleIdent& Modifier(const wxString& modifier)
{
m_modifier = modifier;
return *this;
}
// Construct platform dependent name
wxString GetName() const;
// Empty language represents user's default language
bool IsDefault() const
{
return m_language.empty();
}
private:
wxString m_language;
wxString m_region;
wxString m_script;
wxString m_modifier;
};
#endif // wxUSE_INTL
#endif // _WX_LOCALEDEFS_H_

View File

@ -45,6 +45,10 @@ public:
wxString GetInfo(wxLocaleInfo index,
wxLocaleCategory cat = wxLOCALE_CAT_DEFAULT) const;
// Compares two strings, for a locale specified by wxLocaleIdent.
static int CompareStrings(const wxString& lhs, const wxString& rhs,
const wxLocaleIdent& localeId = wxLocaleIdent());
// Note that this class is not supposed to be used polymorphically, hence
// its dtor is not virtual.
~wxUILocale();

View File

@ -80,6 +80,24 @@ public:
*/
static const wxUILocale& GetCurrent();
/**
Compares two strings using comparison rules of the given locale.
@param lhs
First comparing string.
@param rhs
Second comparing string.
@param localeId
Represents platform dependent language name.
@see wxLocaleIdent for details.
@return
-1 if lhs less than rhs.
0 if lhs equal to rhs.
1 if lhs greater than rhs.
*/
static int CompareStrings(const wxString& lhs, const wxString& rhs,
const wxLocaleIdent& localeId = wxLocaleIdent());
/**
Get the platform-dependent name of the current locale.
@ -119,3 +137,93 @@ public:
@since 3.1.6
*/
wxString wxGetUIDateFormat();
/**
Allows to construct the full locale identifier in a portable way.
Parts of the locale not supported by the current platform (e.g. modifier under non-Unix platforms) are ignored.
The remaining parts are used to construct a string uniquely identifying the locale in a platform-specific name.
Usage example:
@code
auto loc = wxLocaleIdent("fr").Region("BE").Modifier("euro");
#if defined(__WINDOWS__) || defined(__WXOSX__)
wxASSERT( loc.GetName() == "fr_BE" );
#elif defined(__UNIX__)
wxASSERT( loc.GetName() == "fr_BE@euro" );
#endif
@endcode
@since 3.1.6
*/
class wxLocaleIdent
{
public:
/**
This is the default constructor and it leaves language empty.
*/
wxLocaleIdent();
/**
Constructor with language.
@param language
ISO 639 language code.
See Language() for more detailed info.
*/
wxLocaleIdent(const wxString& language);
/**
Set language.
Return reference to @this for method chaining.
@param language
It is a lowercase ISO 639 language code.
The codes from ISO 639-1 are used when available.
Otherwise, codes from ISO 639-2/T are used.
*/
wxLocaleIdent& Language(const wxString& language);
/**
Set region.
Return reference to @this for method chaining.
@param region
It specifies an uppercase ISO 3166-1 country/region identifier.
*/
wxLocaleIdent& Region(const wxString& region);
/**
Set script.
Return reference to @this for method chaining.
@param script
It is an initial-uppercase ISO 15924 script code.
*/
wxLocaleIdent& Script(const wxString& script);
/**
Set modifier.
Return reference to @this for method chaining.
@param modifier
Modifier is defined by ISO/IEC 15897.
It is a semi-colon separated list of identifiers, or name=value pairs.
*/
wxLocaleIdent& Modifier(const wxString& modifier);
/**
Construct platform dependent name.
Format:
Windows: <language>-<script>-<REGION>
Unix: <language>_<REGION>@<modifier>
MacOS: <language>-<script>_<REGION>
*/
wxString GetName() const;
/**
Empty language represents user's default language.
@return @true if language is empty, @false otherwise.
*/
bool IsDefault() const;
};

View File

@ -20,6 +20,7 @@
#if wxUSE_INTL
#include "wx/uilocale.h"
#include "wx/private/uilocale.h"
#include "wx/msw/private/uilocale.h"
@ -55,6 +56,43 @@ static void wxMSWSetThreadUILanguage(LANGID langid)
}
}
// Trivial wrapper for ::CompareStringEx().
//
// TODO-XP: Drop this when we don't support XP any longer.
static int wxMSWCompareStringEx(LPCWSTR lpLocaleName,
DWORD dwCmpFlags,
LPCWSTR lpString1, //_In_NLS_string_(cchCount1)LPCWCH lpString1,
int cchCount1,
LPCWSTR lpString2, //_In_NLS_string_(cchCount2)LPCWCH lpString2,
int cchCount2,
LPNLSVERSIONINFO lpVersionInformation,
LPVOID lpReserved,
LPARAM lParam)
{
typedef int(WINAPI *CompareStringEx_t)(LPCWSTR,DWORD,LPCWSTR,int,LPCWSTR,int,LPNLSVERSIONINFO,LPVOID,LPARAM);
static const CompareStringEx_t INVALID_FUNC_PTR = (CompareStringEx_t)-1;
static CompareStringEx_t pfnCompareStringEx = INVALID_FUNC_PTR;
if (pfnCompareStringEx == INVALID_FUNC_PTR)
{
// Avoid calling CompareStringEx() on XP.
if (wxGetWinVersion() >= wxWinVersion_Vista)
{
wxLoadedDLL dllKernel32(wxS("kernel32.dll"));
wxDL_INIT_FUNC(pfn, CompareStringEx, dllKernel32);
}
}
if (pfnCompareStringEx)
{
return pfnCompareStringEx(lpLocaleName, dwCmpFlags, lpString1, cchCount1, lpString2,
cchCount2, lpVersionInformation, lpReserved, lParam);
}
return 0;
}
} // anonymous namespace
void wxUseLCID(LCID lcid)
@ -64,6 +102,34 @@ void wxUseLCID(LCID 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;
}
// ----------------------------------------------------------------------------
// wxUILocale implementation for MSW
// ----------------------------------------------------------------------------
@ -138,4 +204,30 @@ wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info)
return new wxUILocaleImplLCID(info.GetLCID());
}
/* static */
int wxUILocale::CompareStrings(const wxString& lhs, const wxString& rhs, const wxLocaleIdent& locale_id)
{
int ret = wxMSWCompareStringEx(
locale_id.IsDefault() ? LOCALE_NAME_USER_DEFAULT
: static_cast<LPCWSTR>(
locale_id.GetName().wc_str()
),
0, // Maybe we need LINGUISTIC_IGNORECASE here
static_cast<LPCWSTR>(lhs.wc_str()), -1,
static_cast<LPCWSTR>(rhs.wc_str()), -1,
NULL, NULL, 0);
switch (ret)
{
case CSTR_LESS_THAN:
return -1;
case CSTR_EQUAL:
return 0;
case CSTR_GREATER_THAN:
return 1;
}
return 0;
}
#endif // wxUSE_INTL

View File

@ -20,6 +20,7 @@
#if wxUSE_INTL
#include "wx/uilocale.h"
#include "wx/private/uilocale.h"
#include "wx/osx/core/cfref.h"
@ -28,9 +29,40 @@
#include <CoreFoundation/CFLocale.h>
#include <CoreFoundation/CFString.h>
#import <Foundation/NSString.h>
#import <Foundation/NSLocale.h>
extern wxString
wxGetInfoFromCFLocale(CFLocaleRef cfloc, wxLocaleInfo index, wxLocaleCategory cat);
// ----------------------------------------------------------------------------
// wxLocaleIdent::GetName() implementation using Foundation
// ----------------------------------------------------------------------------
wxString wxLocaleIdent::GetName() const
{
// Construct name in right format:
// MacOS: <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;
}
namespace
{
@ -100,4 +132,34 @@ wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info)
return wxUILocaleImplCF::Create(info.CanonicalName);
}
/* static */
int wxUILocale::CompareStrings(const wxString& lhs, const wxString& rhs, const wxLocaleIdent& locale_id)
{
NSString *ns_lhs = [NSString stringWithCString:lhs.ToStdString(wxConvUTF8).c_str()
encoding:NSUTF8StringEncoding];
NSString *ns_rhs = [NSString stringWithCString:rhs.ToStdString(wxConvUTF8).c_str()
encoding:NSUTF8StringEncoding];
NSString *ns_locale_id = [NSString stringWithCString:locale_id.GetName().ToStdString(wxConvUTF8).c_str()
encoding:NSUTF8StringEncoding];
NSInteger options = NSCaseInsensitiveSearch; // Maybe also NSDiacriticInsensitiveSearch?
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:ns_locale_id];
NSComparisonResult ret = [ns_lhs compare:ns_rhs
options:options
range:(NSRange){0, [ns_lhs length]}
locale:locale];
switch (ret)
{
case NSOrderedAscending:
return -1;
case NSOrderedSame:
return 0;
case NSOrderedDescending:
return 1;
}
return 0;
}
#endif // wxUSE_INTL

View File

@ -54,6 +54,26 @@ private:
// implementation
// ============================================================================
wxString wxLocaleIdent::GetName() const
{
// Construct name in the standard Unix format:
// language[_territory][.codeset][@modifier]
wxString name;
if ( !m_language.empty() )
{
name << m_language;
if ( !m_region.empty() )
name << "_" << m_region;
if ( !m_modifier.empty() )
name << "@" << m_modifier;
}
return name;
}
// 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)