diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -156,7 +156,7 @@ static bool EngineNameSorter(const GUIEn GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1])); } - int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting). + int r = StrNaturalCompare(last_name[0], last_name[1]); // Sort by name (natural sorting). /* Use EngineID to sort instead since we want consistent sorting */ if (r == 0) return EngineNumberSorter(a, b); diff --git a/src/cargotype.cpp b/src/cargotype.cpp --- a/src/cargotype.cpp +++ b/src/cargotype.cpp @@ -156,13 +156,10 @@ span _sorted_standard /** Sort cargo specifications by their name. */ static bool CargoSpecNameSorter(const CargoSpec * const &a, const CargoSpec * const &b) { - static char a_name[64]; - static char b_name[64]; + std::string a_name = GetString(a->name); + std::string b_name = GetString(b->name); - GetString(a_name, a->name, lastof(a_name)); - GetString(b_name, b->name, lastof(b_name)); - - int res = strnatcmp(a_name, b_name); // Sort by name (natural sorting). + int res = StrNaturalCompare(a_name, b_name); // Sort by name (natural sorting). /* If the names are equal, sort by cargo bitnum. */ return (res != 0) ? res < 0 : (a->bitnum < b->bitnum); diff --git a/src/company_gui.cpp b/src/company_gui.cpp --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -735,7 +735,7 @@ private: GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1])); } - int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting). + int r = StrNaturalCompare(last_name[0], last_name[1]); // Sort by name (natural sorting). if (r == 0) return a->index < b->index; return r < 0; }); diff --git a/src/fios.cpp b/src/fios.cpp --- a/src/fios.cpp +++ b/src/fios.cpp @@ -58,7 +58,7 @@ bool FiosItem::operator< (const FiosItem if ((_savegame_sort_order & SORT_BY_NAME) == 0 && (*this).mtime != other.mtime) { r = (*this).mtime - other.mtime; } else { - r = strnatcmp((*this).title, other.title); + r = StrNaturalCompare((*this).title, other.title); } if (r == 0) return false; return (_savegame_sort_order & SORT_DESCENDING) ? r > 0 : r < 0; diff --git a/src/group_gui.cpp b/src/group_gui.cpp --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -191,7 +191,7 @@ private: GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1])); } - int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting). + int r = StrNaturalCompare(last_name[0], last_name[1]); // Sort by name (natural sorting). if (r == 0) return a->index < b->index; return r < 0; }); diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -202,7 +202,7 @@ static bool IndustryTypeNameSorter(const const IndustrySpec *indsp2 = GetIndustrySpec(b); GetString(industry_name[1], indsp2->name, lastof(industry_name[1])); - int r = strnatcmp(industry_name[0], industry_name[1]); // Sort by name (natural sorting). + int r = StrNaturalCompare(industry_name[0], industry_name[1]); // Sort by name (natural sorting). /* If the names are equal, sort by industry type. */ return (r != 0) ? r < 0 : (a < b); @@ -1498,7 +1498,7 @@ protected: /** Sort industries by name */ static bool IndustryNameSorter(const Industry * const &a, const Industry * const &b) { - int r = strnatcmp(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting). + int r = StrNaturalCompare(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting). if (r == 0) return a->index < b->index; return r < 0; } diff --git a/src/network/network_content_gui.cpp b/src/network/network_content_gui.cpp --- a/src/network/network_content_gui.cpp +++ b/src/network/network_content_gui.cpp @@ -432,7 +432,7 @@ class NetworkContentListWindow : public /** Sort content by name. */ static bool NameSorter(const ContentInfo * const &a, const ContentInfo * const &b) { - return strnatcmp(a->name.c_str(), b->name.c_str(), true) < 0; // Sort by name (natural sorting). + return StrNaturalCompare(a->name, b->name, true) < 0; // Sort by name (natural sorting). } /** Sort content by type. */ @@ -440,7 +440,7 @@ class NetworkContentListWindow : public { int r = 0; if (a->type != b->type) { - r = strnatcmp(content_type_strs[a->type], content_type_strs[b->type]); + r = StrNaturalCompare(content_type_strs[a->type], content_type_strs[b->type]); } if (r == 0) return NameSorter(a, b); return r < 0; diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -289,7 +289,7 @@ protected: /** Sort servers by name. */ static bool NGameNameSorter(NetworkGameList * const &a, NetworkGameList * const &b) { - int r = strnatcmp(a->info.server_name.c_str(), b->info.server_name.c_str(), true); // Sort by name (natural sorting). + int r = StrNaturalCompare(a->info.server_name, b->info.server_name, true); // Sort by name (natural sorting). if (r == 0) r = a->connection_string.compare(b->connection_string); return r < 0; diff --git a/src/newgrf_config.cpp b/src/newgrf_config.cpp --- a/src/newgrf_config.cpp +++ b/src/newgrf_config.cpp @@ -670,7 +670,7 @@ bool GRFFileScanner::AddFile(const std:: */ static bool GRFSorter(GRFConfig * const &c1, GRFConfig * const &c2) { - return strnatcmp(c1->GetName(), c2->GetName()) < 0; + return StrNaturalCompare(c1->GetName(), c2->GetName()) < 0; } /** diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp --- a/src/newgrf_gui.cpp +++ b/src/newgrf_gui.cpp @@ -1428,7 +1428,7 @@ private: /** Sort grfs by name. */ static bool NameSorter(const GRFConfig * const &a, const GRFConfig * const &b) { - int i = strnatcmp(a->GetName(), b->GetName(), true); // Sort by name (natural sorting). + int i = StrNaturalCompare(a->GetName(), b->GetName(), true); // Sort by name (natural sorting). if (i != 0) return i < 0; i = a->version - b->version; diff --git a/src/os/macosx/string_osx.cpp b/src/os/macosx/string_osx.cpp --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -322,15 +322,15 @@ void MacOSSetCurrentLocaleName(const cha * @param s2 Second string to compare. * @return 1 if s1 < s2, 2 if s1 == s2, 3 if s1 > s2, or 0 if not supported by the OS. */ -int MacOSStringCompare(const char *s1, const char *s2) +int MacOSStringCompare(std::string_view s1, std::string_view s2) { static bool supported = MacOSVersionIsAtLeast(10, 5, 0); if (!supported) return 0; CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering; - CFAutoRelease cf1(CFStringCreateWithCString(kCFAllocatorDefault, s1, kCFStringEncodingUTF8)); - CFAutoRelease cf2(CFStringCreateWithCString(kCFAllocatorDefault, s2, kCFStringEncodingUTF8)); + CFAutoRelease cf1(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)s1.data(), s1.size(), kCFStringEncodingUTF8, false)); + CFAutoRelease cf2(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)s2.data(), s2.size(), kCFStringEncodingUTF8, false)); /* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */ if (cf1 == nullptr || cf2 == nullptr) return 0; diff --git a/src/os/macosx/string_osx.h b/src/os/macosx/string_osx.h --- a/src/os/macosx/string_osx.h +++ b/src/os/macosx/string_osx.h @@ -83,7 +83,7 @@ public: void MacOSResetScriptCache(FontSize size); void MacOSSetCurrentLocaleName(const char *iso_code); -int MacOSStringCompare(const char *s1, const char *s2); +int MacOSStringCompare(std::string_view s1, std::string_view s2); void MacOSRegisterExternalFont(const char *file_path); diff --git a/src/os/windows/win32.cpp b/src/os/windows/win32.cpp --- a/src/os/windows/win32.cpp +++ b/src/os/windows/win32.cpp @@ -567,7 +567,7 @@ void Win32SetCurrentLocaleName(const cha MultiByteToWideChar(CP_UTF8, 0, iso, -1, _cur_iso_locale, lengthof(_cur_iso_locale)); } -int OTTDStringCompare(const char *s1, const char *s2) +int OTTDStringCompare(std::string_view s1, std::string_view s2) { typedef int (WINAPI *PFNCOMPARESTRINGEX)(LPCWSTR, DWORD, LPCWCH, int, LPCWCH, int, LPVOID, LPVOID, LPARAM); static PFNCOMPARESTRINGEX _CompareStringEx = nullptr; @@ -588,15 +588,15 @@ int OTTDStringCompare(const char *s1, co if (_CompareStringEx != nullptr) { /* CompareStringEx takes UTF-16 strings, even in ANSI-builds. */ - int len_s1 = MultiByteToWideChar(CP_UTF8, 0, s1, -1, nullptr, 0); - int len_s2 = MultiByteToWideChar(CP_UTF8, 0, s2, -1, nullptr, 0); + int len_s1 = MultiByteToWideChar(CP_UTF8, 0, s1.data(), (int)s1.size(), nullptr, 0); + int len_s2 = MultiByteToWideChar(CP_UTF8, 0, s2.data(), (int)s2.size(), nullptr, 0); if (len_s1 != 0 && len_s2 != 0) { std::wstring str_s1(len_s1, L'\0'); // len includes terminating null std::wstring str_s2(len_s2, L'\0'); - MultiByteToWideChar(CP_UTF8, 0, s1, -1, str_s1.data(), len_s1); - MultiByteToWideChar(CP_UTF8, 0, s2, -1, str_s2.data(), len_s2); + MultiByteToWideChar(CP_UTF8, 0, s1.data(), (int)s1.size(), str_s1.data(), len_s1); + MultiByteToWideChar(CP_UTF8, 0, s2.data(), (int)s2.size(), str_s2.data(), len_s2); int result = _CompareStringEx(_cur_iso_locale, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, str_s1.c_str(), -1, str_s2.c_str(), -1, nullptr, nullptr, 0); if (result != 0) return result; diff --git a/src/os/windows/win32.h b/src/os/windows/win32.h --- a/src/os/windows/win32.h +++ b/src/os/windows/win32.h @@ -59,6 +59,6 @@ char *convert_from_fs(const wchar_t *nam wchar_t *convert_to_fs(const std::string_view name, wchar_t *utf16_buf, size_t buflen); void Win32SetCurrentLocaleName(const char *iso_code); -int OTTDStringCompare(const char *s1, const char *s2); +int OTTDStringCompare(std::string_view s1, std::string_view s2); #endif /* WIN32_H */ diff --git a/src/signs_gui.cpp b/src/signs_gui.cpp --- a/src/signs_gui.cpp +++ b/src/signs_gui.cpp @@ -47,7 +47,7 @@ struct SignList { StringFilter string_filter; ///< The match string to be used when the GUIList is (re)-sorted. static bool match_case; ///< Should case sensitive matching be used? - static char default_name[64]; ///< Default sign name, used if Sign::name is nullptr. + static std::string default_name; ///< Default sign name, used if Sign::name is nullptr. /** * Creates a SignList with filtering disabled by default. @@ -79,10 +79,10 @@ struct SignList { * a lot of them. Therefore a worthwhile performance gain can be made by * directly comparing Sign::name instead of going through the string * system for each comparison. */ - const char *a_name = a->name.empty() ? SignList::default_name : a->name.c_str(); - const char *b_name = b->name.empty() ? SignList::default_name : b->name.c_str(); + const std::string &a_name = a->name.empty() ? SignList::default_name : a->name; + const std::string &b_name = b->name.empty() ? SignList::default_name : b->name; - int r = strnatcmp(a_name, b_name); // Sort by name (natural sorting). + int r = StrNaturalCompare(a_name, b_name); // Sort by name (natural sorting). return r != 0 ? r < 0 : (a->index < b->index); } @@ -96,10 +96,10 @@ struct SignList { static bool CDECL SignNameFilter(const Sign * const *a, StringFilter &filter) { /* Same performance benefit as above for sorting. */ - const char *a_name = (*a)->name.empty() ? SignList::default_name : (*a)->name.c_str(); + const std::string &a_name = (*a)->name.empty() ? SignList::default_name : (*a)->name; filter.ResetState(); - filter.AddLine(a_name); + filter.AddLine(a_name.c_str()); return filter.GetState(); } @@ -130,7 +130,7 @@ struct SignList { }; bool SignList::match_case = false; -char SignList::default_name[64]; +std::string SignList::default_name; /** Enum referring to the Hotkeys in the sign list window */ enum SignListHotkeys { @@ -165,7 +165,7 @@ struct SignListWindow : Window, SignList void OnInit() override { /* Default sign name, used if Sign::name is nullptr. */ - GetString(SignList::default_name, STR_DEFAULT_SIGN_NAME, lastof(SignList::default_name)); + SignList::default_name = GetString(STR_DEFAULT_SIGN_NAME); this->signs.ForceResort(); this->SortSignsList(); this->SetDirty(); diff --git a/src/station_gui.cpp b/src/station_gui.cpp --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -258,7 +258,7 @@ protected: /** Sort stations by their name */ static bool StationNameSorter(const Station * const &a, const Station * const &b) { - int r = strnatcmp(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting). + int r = StrNaturalCompare(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting). if (r == 0) return a->index < b->index; return r < 0; } @@ -1186,7 +1186,7 @@ bool CargoSorter::SortStation(StationID return order == SO_DESCENDING; } - int res = strnatcmp(Station::Get(st1)->GetCachedName(), Station::Get(st2)->GetCachedName()); // Sort by name (natural sorting). + int res = StrNaturalCompare(Station::Get(st1)->GetCachedName(), Station::Get(st2)->GetCachedName()); // Sort by name (natural sorting). if (res == 0) { return this->SortId(st1, st2); } else { diff --git a/src/string.cpp b/src/string.cpp --- a/src/string.cpp +++ b/src/string.cpp @@ -36,7 +36,7 @@ #endif #ifdef WITH_ICU_I18N -/* Required by strnatcmp. */ +/* Required by StrNaturalCompare. */ # include # include "language.h" # include "gfx_func.h" @@ -766,9 +766,9 @@ char *strcasestr(const char *haystack, c * @param str The string to skip the initial garbage of. * @return The string with the garbage skipped. */ -static const char *SkipGarbage(const char *str) +static std::string_view SkipGarbage(std::string_view str) { - while (*str != '\0' && (*str < '0' || IsInsideMM(*str, ';', '@' + 1) || IsInsideMM(*str, '[', '`' + 1) || IsInsideMM(*str, '{', '~' + 1))) str++; + while (str.size() != 0 && (str[0] < '0' || IsInsideMM(str[0], ';', '@' + 1) || IsInsideMM(str[0], '[', '`' + 1) || IsInsideMM(str[0], '{', '~' + 1))) str.remove_prefix(1); return str; } @@ -780,7 +780,7 @@ static const char *SkipGarbage(const cha * @param ignore_garbage_at_front Skip punctuation characters in the front * @return Less than zero if s1 < s2, zero if s1 == s2, greater than zero if s1 > s2. */ -int strnatcmp(const char *s1, const char *s2, bool ignore_garbage_at_front) +int StrNaturalCompare(std::string_view s1, std::string_view s2, bool ignore_garbage_at_front) { if (ignore_garbage_at_front) { s1 = SkipGarbage(s1); diff --git a/src/string_func.h b/src/string_func.h --- a/src/string_func.h +++ b/src/string_func.h @@ -58,6 +58,7 @@ void StrTrimInPlace(std::string &str); [[nodiscard]] int StrCompareIgnoreCase(const std::string_view str1, const std::string_view str2); [[nodiscard]] bool StrEqualsIgnoreCase(const std::string_view str1, const std::string_view str2); +[[nodiscard]] int StrNaturalCompare(std::string_view s1, std::string_view s2, bool ignore_garbage_at_front = false); /** * Check if a string buffer is empty. @@ -277,6 +278,4 @@ static inline bool IsWhitespace(WChar c) char *strcasestr(const char *haystack, const char *needle); #endif /* strcasestr is available */ -int strnatcmp(const char *s1, const char *s2, bool ignore_garbage_at_front = false); - #endif /* STRING_FUNC_H */ diff --git a/src/strings.cpp b/src/strings.cpp --- a/src/strings.cpp +++ b/src/strings.cpp @@ -1956,12 +1956,10 @@ const char *GetCurrentLocale(const char bool StringIDSorter(const StringID &a, const StringID &b) { - char stra[512]; - char strb[512]; - GetString(stra, a, lastof(stra)); - GetString(strb, b, lastof(strb)); + std::string stra = GetString(a); + std::string strb = GetString(b); - return strnatcmp(stra, strb) < 0; + return StrNaturalCompare(stra, strb) < 0; } /** diff --git a/src/town_gui.cpp b/src/town_gui.cpp --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -746,7 +746,7 @@ private: /** Sort by town name */ static bool TownNameSorter(const Town * const &a, const Town * const &b) { - return strnatcmp(a->GetCachedName(), b->GetCachedName()) < 0; // Sort by name (natural sorting). + return StrNaturalCompare(a->GetCachedName(), b->GetCachedName()) < 0; // Sort by name (natural sorting). } /** Sort by population (default descending, as big towns are of the most interest). */ diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -1345,7 +1345,7 @@ static bool VehicleNameSorter(const Vehi GetString(last_name[1], STR_VEHICLE_NAME, lastof(last_name[1])); } - int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting). + int r = StrNaturalCompare(last_name[0], last_name[1]); // Sort by name (natural sorting). return (r != 0) ? r < 0: VehicleNumberSorter(a, b); } diff --git a/src/widgets/dropdown.cpp b/src/widgets/dropdown.cpp --- a/src/widgets/dropdown.cpp +++ b/src/widgets/dropdown.cpp @@ -52,10 +52,9 @@ void DropDownListStringItem::Draw(const */ /* static */ bool DropDownListStringItem::NatSortFunc(std::unique_ptr const &first, std::unique_ptr const &second) { - char buffer1[512], buffer2[512]; - GetString(buffer1, static_cast(first.get())->String(), lastof(buffer1)); - GetString(buffer2, static_cast(second.get())->String(), lastof(buffer2)); - return strnatcmp(buffer1, buffer2) < 0; + std::string str1 = GetString(static_cast(first.get())->String()); + std::string str2 = GetString(static_cast(second.get())->String()); + return StrNaturalCompare(str1, str2) < 0; } StringID DropDownListParamStringItem::String() const