Merge branch 'master' into jgrpp

# Conflicts:
#	src/console.cpp
#	src/os/os2/os2.cpp
#	src/os/unix/font_unix.cpp
#	src/strgen/strgen.h
#	src/strgen/strgen_base.cpp
#	src/table/settings/gui_settings.ini
This commit is contained in:
Jonathan G Rennison
2023-09-02 20:46:57 +01:00
24 changed files with 240 additions and 362 deletions

View File

@@ -255,8 +255,7 @@ std::string RemoveUnderscores(std::string name)
*/ */
static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT], const uint recurse_count) static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT], const uint recurse_count)
{ {
char alias_buffer[ICON_MAX_STREAMSIZE] = { '\0' }; std::string alias_buffer;
char *alias_stream = alias_buffer;
DEBUG(console, 6, "Requested command is an alias; parsing..."); DEBUG(console, 6, "Requested command is an alias; parsing...");
@@ -268,14 +267,13 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char
for (const char *cmdptr = alias->cmdline.c_str(); *cmdptr != '\0'; cmdptr++) { for (const char *cmdptr = alias->cmdline.c_str(); *cmdptr != '\0'; cmdptr++) {
switch (*cmdptr) { switch (*cmdptr) {
case '\'': // ' will double for "" case '\'': // ' will double for ""
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer)); alias_buffer += '\"';
break; break;
case ';': // Cmd separator; execute previous and start new command case ';': // Cmd separator; execute previous and start new command
IConsoleCmdExec(alias_buffer, recurse_count); IConsoleCmdExec(alias_buffer.c_str(), recurse_count);
alias_stream = alias_buffer; alias_buffer.clear();
*alias_stream = '\0'; // Make sure the new command is terminated.
cmdptr++; cmdptr++;
break; break;
@@ -285,21 +283,21 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char
switch (*cmdptr) { switch (*cmdptr) {
case '+': { // All parameters separated: "[param 1]" "[param 2]" case '+': { // All parameters separated: "[param 1]" "[param 2]"
for (uint i = 0; i != tokencount; i++) { for (uint i = 0; i != tokencount; i++) {
if (i != 0) alias_stream = strecpy(alias_stream, " ", lastof(alias_buffer)); if (i != 0) alias_buffer += ' ';
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer)); alias_buffer += '\"';
alias_stream = strecpy(alias_stream, tokens[i], lastof(alias_buffer)); alias_buffer += tokens[i];
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer)); alias_buffer += '\"';
} }
break; break;
} }
case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]" case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer)); alias_buffer += '\"';
for (uint i = 0; i != tokencount; i++) { for (uint i = 0; i != tokencount; i++) {
if (i != 0) alias_stream = strecpy(alias_stream, " ", lastof(alias_buffer)); if (i != 0) alias_buffer += " ";
alias_stream = strecpy(alias_stream, tokens[i], lastof(alias_buffer)); alias_buffer += tokens[i];
} }
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer)); alias_buffer += '\"';
break; break;
} }
@@ -312,27 +310,26 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char
return; return;
} }
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer)); alias_buffer += '\"';
alias_stream = strecpy(alias_stream, tokens[param], lastof(alias_buffer)); alias_buffer += tokens[param];
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer)); alias_buffer += '\"';
break; break;
} }
} }
break; break;
default: default:
*alias_stream++ = *cmdptr; alias_buffer += *cmdptr;
*alias_stream = '\0';
break; break;
} }
if (alias_stream >= lastof(alias_buffer) - 1) { if (alias_buffer.size() >= ICON_MAX_STREAMSIZE - 1) {
IConsoleError("Requested alias execution would overflow execution buffer"); IConsoleError("Requested alias execution would overflow execution buffer");
return; return;
} }
} }
IConsoleCmdExec(alias_buffer, recurse_count); IConsoleCmdExec(alias_buffer.c_str(), recurse_count);
} }
/** /**

View File

@@ -65,37 +65,4 @@ static inline int MemCmpT(const T *ptr1, const T *ptr2, size_t num = 1)
return memcmp(ptr1, ptr2, num * sizeof(T)); return memcmp(ptr1, ptr2, num * sizeof(T));
} }
/**
* Type safe memory reverse operation.
* Reverse a block of memory in steps given by the
* type of the pointers.
*
* @param ptr1 Start-pointer to the block of memory.
* @param ptr2 End-pointer to the block of memory.
*/
template <typename T>
static inline void MemReverseT(T *ptr1, T *ptr2)
{
assert(ptr1 != nullptr && ptr2 != nullptr);
assert(ptr1 < ptr2);
do {
Swap(*ptr1, *ptr2);
} while (++ptr1 < --ptr2);
}
/**
* Type safe memory reverse operation (overloaded)
*
* @param ptr Pointer to the block of memory.
* @param num The number of items we want to reverse.
*/
template <typename T>
static inline void MemReverseT(T *ptr, size_t num)
{
assert(ptr != nullptr);
MemReverseT(ptr, ptr + (num - 1));
}
#endif /* MEM_FUNC_HPP */ #endif /* MEM_FUNC_HPP */

View File

@@ -288,10 +288,9 @@ static void ExtractStringParams(const StringData &data, StringParamsList &params
if (ls != nullptr) { if (ls != nullptr) {
StringParams &param = params.emplace_back(); StringParams &param = params.emplace_back();
ParsedCommandStruct pcs; ParsedCommandStruct pcs = ExtractCommandString(ls->english.c_str(), false);
ExtractCommandString(&pcs, ls->english, false);
for (const CmdStruct *cs : pcs.cmd) { for (const CmdStruct *cs : pcs.consuming_commands) {
if (cs == nullptr) break; if (cs == nullptr) break;
param.emplace_back(GetParamType(cs), cs->consumes); param.emplace_back(GetParamType(cs), cs->consumes);
} }

View File

@@ -2230,77 +2230,39 @@ void SetAnimatedMouseCursor(const AnimCursor *table)
} }
/** /**
* Update cursor position on mouse movement for relative modes. * Update cursor position based on a relative change.
*
* @param delta_x How much change in the X position. * @param delta_x How much change in the X position.
* @param delta_y How much change in the Y position. * @param delta_y How much change in the Y position.
*/ */
void CursorVars::UpdateCursorPositionRelative(int delta_x, int delta_y) void CursorVars::UpdateCursorPositionRelative(int delta_x, int delta_y)
{ {
if (this->fix_at) { assert(this->fix_at);
this->delta.x = delta_x;
this->delta.y = delta_y;
} else {
int last_position_x = this->pos.x;
int last_position_y = this->pos.y;
this->pos.x = Clamp(this->pos.x + delta_x, 0, _cur_resolution.width - 1); this->delta.x = delta_x;
this->pos.y = Clamp(this->pos.y + delta_y, 0, _cur_resolution.height - 1); this->delta.y = delta_y;
this->delta.x = last_position_x - this->pos.x;
this->delta.y = last_position_y - this->pos.y;
this->dirty = true;
}
} }
/** /**
* Update cursor position on mouse movement. * Update cursor position on mouse movement.
* @param x New X position. * @param x New X position.
* @param y New Y position. * @param y New Y position.
* @param queued_warp True, if the OS queues mouse warps after pending mouse movement events.
* False, if the warp applies instantaneous.
* @return true, if the OS cursor position should be warped back to this->pos. * @return true, if the OS cursor position should be warped back to this->pos.
*/ */
bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp) bool CursorVars::UpdateCursorPosition(int x, int y)
{ {
/* Detecting relative mouse movement is somewhat tricky. this->delta.x = x - this->pos.x;
* - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit). this->delta.y = y - this->pos.y;
* - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
*
* So, when this->fix_at is active, we use the following strategy:
* - The first movement triggers the warp to reset the mouse position.
* - Subsequent events have to compute movement relative to the previous event.
* - The relative movement is finished, when we receive the event matching the warp.
*/
if (x == this->pos.x && y == this->pos.y) {
/* Warp finished. */
this->queued_warp = false;
}
this->delta.x = x - (this->queued_warp ? this->last_position.x : this->pos.x);
this->delta.y = y - (this->queued_warp ? this->last_position.y : this->pos.y);
this->last_position.x = x;
this->last_position.y = y;
bool need_warp = false;
if (this->fix_at) { if (this->fix_at) {
if (this->delta.x != 0 || this->delta.y != 0) { return this->delta.x != 0 || this->delta.y != 0;
/* Trigger warp.
* Note: We also trigger warping again, if there is already a pending warp.
* This makes it more tolerant about the OS or other software in between
* botchering the warp. */
this->queued_warp = queued_warp;
need_warp = true;
}
} else if (this->pos.x != x || this->pos.y != y) { } else if (this->pos.x != x || this->pos.y != y) {
this->queued_warp = false; // Cancel warping, we are no longer confining the position.
this->dirty = true; this->dirty = true;
this->pos.x = x; this->pos.x = x;
this->pos.y = y; this->pos.y = y;
} }
return need_warp;
return false;
} }
bool ChangeResInGame(int width, int height) bool ChangeResInGame(int width, int height)

View File

@@ -145,11 +145,7 @@ struct CursorVars {
bool vehchain; ///< vehicle chain is dragged bool vehchain; ///< vehicle chain is dragged
void UpdateCursorPositionRelative(int delta_x, int delta_y); void UpdateCursorPositionRelative(int delta_x, int delta_y);
bool UpdateCursorPosition(int x, int y, bool queued_warp); bool UpdateCursorPosition(int x, int y);
private:
bool queued_warp;
Point last_position;
}; };
/** Data about how and where to blit pixels. */ /** Data about how and where to blit pixels. */

View File

@@ -270,6 +270,33 @@ static void SurveyGameScript(nlohmann::json &survey)
survey = fmt::format("{}.{}", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion()); survey = fmt::format("{}.{}", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion());
} }
/**
* Change the bytes of memory into a textual version rounded up to the biggest unit.
*
* For example, 16751108096 would become 16 GiB.
*
* @param memory The bytes of memory.
* @return std::string A textual representation.
*/
std::string SurveyMemoryToText(uint64_t memory)
{
memory = memory / 1024; // KiB
memory = CeilDiv(memory, 1024); // MiB
/* Anything above 512 MiB we represent in GiB. */
if (memory > 512) {
return fmt::format("{} GiB", CeilDiv(memory, 1024));
}
/* Anything above 64 MiB we represent in a multiplier of 128 MiB. */
if (memory > 64) {
return fmt::format("{} MiB", Ceil(memory, 128));
}
/* Anything else in a multiplier of 4 MiB. */
return fmt::format("{} MiB", Ceil(memory, 4));
}
#endif /* WITH_NLOHMANN_JSON */ #endif /* WITH_NLOHMANN_JSON */
/** /**
@@ -300,8 +327,6 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview)
{ {
auto &info = survey["info"]; auto &info = survey["info"];
SurveyOS(info["os"]); SurveyOS(info["os"]);
info["os"]["hardware_concurrency"] = std::thread::hardware_concurrency();
SurveyOpenTTD(info["openttd"]); SurveyOpenTTD(info["openttd"]);
SurveyConfiguration(info["configuration"]); SurveyConfiguration(info["configuration"]);
SurveyFont(info["font"]); SurveyFont(info["font"]);

View File

@@ -595,9 +595,9 @@ void MakeNewgameSettingsLive()
void OpenBrowser(const char *url) void OpenBrowser(const char *url)
{ {
/* Make sure we only accept urls that are sure to open a browser. */ /* Make sure we only accept urls that are sure to open a browser. */
if (strstr(url, "http://") != url && strstr(url, "https://") != url) return; if (StrStartsWith(url, "http://") || StrStartsWith(url, "https://")) {
OSOpenBrowser(url);
OSOpenBrowser(url); }
} }
/** Callback structure of statements to be executed after the NewGRF scan. */ /** Callback structure of statements to be executed after the NewGRF scan. */

View File

@@ -189,25 +189,21 @@ const char *GetCurrentLocale(const char *)
/** /**
* Return the contents of the clipboard (COCOA). * Return the contents of the clipboard (COCOA).
* *
* @param buffer Clipboard content. * @return The (optional) clipboard contents.
* @param last The pointer to the last element of the destination buffer
* @return Whether clipboard is empty or not.
*/ */
bool GetClipboardContents(char *buffer, const char *last) std::optional<std::string> GetClipboardContents()
{ {
NSPasteboard *pb = [ NSPasteboard generalPasteboard ]; NSPasteboard *pb = [ NSPasteboard generalPasteboard ];
NSArray *types = [ NSArray arrayWithObject:NSPasteboardTypeString ]; NSArray *types = [ NSArray arrayWithObject:NSPasteboardTypeString ];
NSString *bestType = [ pb availableTypeFromArray:types ]; NSString *bestType = [ pb availableTypeFromArray:types ];
/* Clipboard has no text data available. */ /* Clipboard has no text data available. */
if (bestType == nil) return false; if (bestType == nil) return std::nullopt;
NSString *string = [ pb stringForType:NSPasteboardTypeString ]; NSString *string = [ pb stringForType:NSPasteboardTypeString ];
if (string == nil || [ string length ] == 0) return false; if (string == nil || [ string length ] == 0) return std::nullopt;
strecpy(buffer, [ string UTF8String ], last); return [ string UTF8String ];
return true;
} }
/** Set the application's bundle directory. /** Set the application's bundle directory.

View File

@@ -16,9 +16,12 @@
#include <mach-o/arch.h> #include <mach-o/arch.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <thread>
#include "../../safeguards.h" #include "../../safeguards.h"
extern std::string SurveyMemoryToText(uint64_t memory);
void SurveyOS(nlohmann::json &json) void SurveyOS(nlohmann::json &json)
{ {
int ver_maj, ver_min, ver_bug; int ver_maj, ver_min, ver_bug;
@@ -32,7 +35,8 @@ void SurveyOS(nlohmann::json &json)
json["min_ver"] = MAC_OS_X_VERSION_MIN_REQUIRED; json["min_ver"] = MAC_OS_X_VERSION_MIN_REQUIRED;
json["max_ver"] = MAC_OS_X_VERSION_MAX_ALLOWED; json["max_ver"] = MAC_OS_X_VERSION_MAX_ALLOWED;
json["memory"] = MacOSGetPhysicalMemory(); json["memory"] = SurveyMemoryToText(MacOSGetPhysicalMemory());
json["hardware_concurrency"] = std::thread::hardware_concurrency();
} }
#endif /* WITH_NLOHMANN_JSON */ #endif /* WITH_NLOHMANN_JSON */

View File

@@ -174,27 +174,25 @@ int CDECL main(int argc, char *argv[])
return openttd_main(argc, argv); return openttd_main(argc, argv);
} }
bool GetClipboardContents(char *buffer, const char *last) std::optional<std::string> GetClipboardContents()
{ {
/* XXX -- Currently no clipboard support implemented with GCC */ /* XXX -- Currently no clipboard support implemented with GCC */
#ifndef __INNOTEK_LIBC__ #ifndef __INNOTEK_LIBC__
HAB hab = 0; HAB hab = 0;
if (WinOpenClipbrd(hab)) if (WinOpenClipbrd(hab)) {
{ const char *text = (const char *)WinQueryClipbrdData(hab, CF_TEXT);
const char *text = (const char*)WinQueryClipbrdData(hab, CF_TEXT);
if (text != nullptr) if (text != nullptr) {
{ std::string result = text;
strecpy(buffer, text, last);
WinCloseClipbrd(hab); WinCloseClipbrd(hab);
return true; return result;
} }
WinCloseClipbrd(hab); WinCloseClipbrd(hab);
} }
#endif #endif
return false; return std::nullopt;
} }

View File

@@ -13,10 +13,13 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <sys/utsname.h> #include <sys/utsname.h>
#include <thread>
#include <unistd.h> #include <unistd.h>
#include "../../safeguards.h" #include "../../safeguards.h"
extern std::string SurveyMemoryToText(uint64_t memory);
void SurveyOS(nlohmann::json &json) void SurveyOS(nlohmann::json &json)
{ {
struct utsname name; struct utsname name;
@@ -32,7 +35,8 @@ void SurveyOS(nlohmann::json &json)
long pages = sysconf(_SC_PHYS_PAGES); long pages = sysconf(_SC_PHYS_PAGES);
long page_size = sysconf(_SC_PAGE_SIZE); long page_size = sysconf(_SC_PAGE_SIZE);
json["memory"] = pages * page_size; json["memory"] = SurveyMemoryToText(pages * page_size);
json["hardware_concurrency"] = std::thread::hardware_concurrency();
} }
#endif /* WITH_NLOHMANN_JSON */ #endif /* WITH_NLOHMANN_JSON */

View File

@@ -270,22 +270,20 @@ int CDECL main(int argc, char *argv[])
} }
#ifndef WITH_COCOA #ifndef WITH_COCOA
bool GetClipboardContents(char *buffer, const char *last) std::optional<std::string> GetClipboardContents()
{ {
#ifdef WITH_SDL2 #ifdef WITH_SDL2
if (SDL_HasClipboardText() == SDL_FALSE) { if (SDL_HasClipboardText() == SDL_FALSE) return std::nullopt;
return false;
}
char *clip = SDL_GetClipboardText(); char *clip = SDL_GetClipboardText();
if (clip != nullptr) { if (clip != nullptr) {
strecpy(buffer, clip, last); std::string result = clip;
SDL_free(clip); SDL_free(clip);
return true; return result;
} }
#endif #endif
return false; return std::nullopt;
} }
#endif #endif

View File

@@ -14,10 +14,13 @@
#include "../../core/format.hpp" #include "../../core/format.hpp"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <thread>
#include <windows.h> #include <windows.h>
#include "../../safeguards.h" #include "../../safeguards.h"
extern std::string SurveyMemoryToText(uint64_t memory);
void SurveyOS(nlohmann::json &json) void SurveyOS(nlohmann::json &json)
{ {
_OSVERSIONINFOA os; _OSVERSIONINFOA os;
@@ -31,7 +34,8 @@ void SurveyOS(nlohmann::json &json)
status.dwLength = sizeof(status); status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status); GlobalMemoryStatusEx(&status);
json["memory"] = status.ullTotalPhys; json["memory"] = SurveyMemoryToText(status.ullTotalPhys);
json["hardware_concurrency"] = std::thread::hardware_concurrency();
} }
#endif /* WITH_NLOHMANN_JSON */ #endif /* WITH_NLOHMANN_JSON */

View File

@@ -549,26 +549,19 @@ void DetermineBasePaths(const char *exe)
} }
bool GetClipboardContents(char *buffer, const char *last) std::optional<std::string> GetClipboardContents()
{ {
HGLOBAL cbuf; if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return std::nullopt;
const char *ptr;
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { OpenClipboard(nullptr);
OpenClipboard(nullptr); HGLOBAL cbuf = GetClipboardData(CF_UNICODETEXT);
cbuf = GetClipboardData(CF_UNICODETEXT);
ptr = (const char*)GlobalLock(cbuf); std::string result = FS2OTTD(static_cast<LPCWSTR>(GlobalLock(cbuf)));
int out_len = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)ptr, -1, buffer, (last - buffer) + 1, nullptr, nullptr); GlobalUnlock(cbuf);
GlobalUnlock(cbuf); CloseClipboard();
CloseClipboard();
if (out_len == 0) return false; if (result.empty()) return std::nullopt;
} else { return result;
return false;
}
return true;
} }

View File

@@ -242,7 +242,7 @@ public:
{ {
this->flags ^= VL_DESC; this->flags ^= VL_DESC;
if (this->IsSortable()) MemReverseT(std::vector<T>::data(), std::vector<T>::size()); if (this->IsSortable()) std::reverse(std::vector<T>::begin(), std::vector<T>::end());
} }
/** /**

View File

@@ -16,40 +16,39 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <unordered_map>
#include <array>
/** Container for the different cases of a string. */ /** Container for the different cases of a string. */
struct Case { struct Case {
int caseidx; ///< The index of the case. int caseidx; ///< The index of the case.
char *string; ///< The translation of the case. std::string string; ///< The translation of the case.
Case *next; ///< The next, chained, case.
Case(int caseidx, const char *string, Case *next); Case(int caseidx, std::string string);
~Case();
}; };
/** Information about a single string. */ /** Information about a single string. */
struct LangString { struct LangString {
char *name; ///< Name of the string. std::string name; ///< Name of the string.
char *english; ///< English text. std::string english; ///< English text.
char *translated; ///< Translated text. std::string translated; ///< Translated text.
LangString *hash_next; ///< Next hash entry. int index; ///< The index in the language file.
int index; ///< The index in the language file. int line; ///< Line of string in source-file.
int line; ///< Line of string in source-file. std::vector<Case> translated_cases; ///< Cases of the translation.
Case *translated_case; ///< Cases of the translation.
std::unique_ptr<LangString> chain_before; std::unique_ptr<LangString> chain_before;
std::unique_ptr<LangString> chain_after; std::unique_ptr<LangString> chain_after;
bool no_translate_mode = false; bool no_translate_mode = false;
LangString *default_translation = nullptr; LangString *default_translation = nullptr;
LangString(const char *name, const char *english, int index, int line); LangString(std::string name, std::string english, size_t index, int line);
void ReplaceDefinition(const char *name, const char *english, int line); void ReplaceDefinition(std::string english, int line);
~LangString();
void FreeTranslation(); void FreeTranslation();
}; };
/** Information about the currently known strings. */ /** Information about the currently known strings. */
struct StringData { struct StringData {
LangString **strings; ///< Array of all known strings. std::vector<LangString *> strings; ///< List of all known strings.
LangString **hash_heads; ///< Hash table for the strings. std::unordered_map<std::string_view, LangString *> name_to_string; ///< Lookup table for the strings.
size_t tabs; ///< The number of 'tabs' of strings. size_t tabs; ///< The number of 'tabs' of strings.
size_t max_strings; ///< The maximum number of strings. size_t max_strings; ///< The maximum number of strings.
int next_string_id; ///< The next string ID to allocate. int next_string_id; ///< The next string ID to allocate.
@@ -62,11 +61,8 @@ struct StringData {
LangString *default_translation = nullptr; LangString *default_translation = nullptr;
StringData(size_t tabs); StringData(size_t tabs);
~StringData();
void FreeTranslation(); void FreeTranslation();
uint HashStr(const char *s) const; LangString *Find(const std::string_view s);
void Add(const char *s, LangString *ls);
LangString *Find(const char *s);
uint VersionHashStr(uint hash, const char *s) const; uint VersionHashStr(uint hash, const char *s) const;
uint Version() const; uint Version() const;
uint CountInUse(uint tab) const; uint CountInUse(uint tab) const;
@@ -79,8 +75,8 @@ struct StringReader {
bool master; ///< Are we reading the master file? bool master; ///< Are we reading the master file?
bool translation; ///< Are we reading a translation, implies !master. However, the base translation will have this false. bool translation; ///< Are we reading a translation, implies !master. However, the base translation will have this false.
StringReader(StringData &data, const char *file, bool master, bool translation); StringReader(StringData &data, std::string file, bool master, bool translation);
virtual ~StringReader(); virtual ~StringReader() {}
void HandleString(char *str); void HandleString(char *str);
/** /**
@@ -157,18 +153,17 @@ struct LanguageWriter {
struct CmdStruct; struct CmdStruct;
struct CmdPair { struct CmdPair {
const CmdStruct *a; const CmdStruct *cmd;
const char *v; std::string param;
}; };
struct ParsedCommandStruct { struct ParsedCommandStruct {
uint np; std::vector<CmdPair> non_consuming_commands;
CmdPair pairs[32]; std::array<const CmdStruct*, 32> consuming_commands{ nullptr }; // ordered by param #
const CmdStruct *cmd[32]; // ordered by param #
}; };
const CmdStruct *TranslateCmdForCompare(const CmdStruct *a); const CmdStruct *TranslateCmdForCompare(const CmdStruct *a);
void ExtractCommandString(ParsedCommandStruct *p, const char *s, bool warnings); ParsedCommandStruct ExtractCommandString(const char *s, bool warnings);
void CDECL strgen_warning(const char *s, ...) WARN_FORMAT(1, 2); void CDECL strgen_warning(const char *s, ...) WARN_FORMAT(1, 2);
void CDECL strgen_error(const char *s, ...) WARN_FORMAT(1, 2); void CDECL strgen_error(const char *s, ...) WARN_FORMAT(1, 2);

View File

@@ -37,20 +37,12 @@ static const CmdStruct *ParseCommandString(const char **str, char *param, int *a
* Create a new case. * Create a new case.
* @param caseidx The index of the case. * @param caseidx The index of the case.
* @param string The translation of the case. * @param string The translation of the case.
* @param next The next chained case.
*/ */
Case::Case(int caseidx, const char *string, Case *next) : Case::Case(int caseidx, std::string string) :
caseidx(caseidx), string(stredup(string)), next(next) caseidx(caseidx), string(std::move(string))
{ {
} }
/** Free everything we allocated. */
Case::~Case()
{
free(this->string);
delete this->next;
}
/** /**
* Create a new string. * Create a new string.
* @param name The name of the string. * @param name The name of the string.
@@ -58,38 +50,22 @@ Case::~Case()
* @param index The index in the string table. * @param index The index in the string table.
* @param line The line this string was found on. * @param line The line this string was found on.
*/ */
LangString::LangString(const char *name, const char *english, int index, int line) : LangString::LangString(std::string name, std::string english, size_t index, int line) :
name(stredup(name)), english(stredup(english)), translated(nullptr), name(std::move(name)), english(std::move(english)), index(index), line(line)
hash_next(0), index(index), line(line), translated_case(nullptr)
{ {
} }
/** Free everything we allocated. */ void LangString::ReplaceDefinition(std::string english, int line)
LangString::~LangString()
{ {
free(this->name); this->english = std::move(english);
free(this->english);
free(this->translated);
delete this->translated_case;
}
void LangString::ReplaceDefinition(const char *name, const char *english, int line)
{
free(this->name);
free(this->english);
this->name = stredup(name);
this->english = stredup(english);
this->line = line; this->line = line;
} }
/** Free all data related to the translation. */ /** Free all data related to the translation. */
void LangString::FreeTranslation() void LangString::FreeTranslation()
{ {
free(this->translated); this->translated.clear();
this->translated = nullptr; this->translated_cases.clear();
delete this->translated_case;
this->translated_case = nullptr;
} }
/** /**
@@ -98,18 +74,10 @@ void LangString::FreeTranslation()
*/ */
StringData::StringData(size_t tabs) : tabs(tabs), max_strings(tabs * TAB_SIZE) StringData::StringData(size_t tabs) : tabs(tabs), max_strings(tabs * TAB_SIZE)
{ {
this->strings = CallocT<LangString *>(max_strings); this->strings.resize(max_strings);
this->hash_heads = CallocT<LangString *>(max_strings);
this->next_string_id = 0; this->next_string_id = 0;
} }
/** Free everything we allocated. */
StringData::~StringData()
{
free(this->strings);
free(this->hash_heads);
}
/** Free all data related to the translation. */ /** Free all data related to the translation. */
void StringData::FreeTranslation() void StringData::FreeTranslation()
{ {
@@ -119,45 +87,17 @@ void StringData::FreeTranslation()
} }
} }
/**
* Create a hash of the string for finding them back quickly.
* @param s The string to hash.
* @return The hashed string.
*/
uint StringData::HashStr(const char *s) const
{
uint hash = 0;
for (; *s != '\0'; s++) hash = ROL(hash, 3) ^ *s;
return hash % this->max_strings;
}
/**
* Add a newly created LangString.
* @param s The name of the string.
* @param ls The string to add.
*/
void StringData::Add(const char *s, LangString *ls)
{
uint hash = this->HashStr(s);
ls->hash_next = this->hash_heads[hash];
/* Off-by-one for hash find. */
this->hash_heads[hash] = ls;
}
/** /**
* Find a LangString based on the string name. * Find a LangString based on the string name.
* @param s The string name to search on. * @param s The string name to search on.
* @return The LangString or nullptr if it is not known. * @return The LangString or nullptr if it is not known.
*/ */
LangString *StringData::Find(const char *s) LangString *StringData::Find(const std::string_view s)
{ {
LangString *ls = this->hash_heads[this->HashStr(s)]; auto it = this->name_to_string.find(s);
if (it == this->name_to_string.end()) return nullptr;
while (ls != nullptr) { return it->second;
if (strcmp(ls->name, s) == 0) return ls;
ls = ls->hash_next;
}
return nullptr;
} }
/** /**
@@ -193,12 +133,12 @@ uint StringData::Version() const
int argno; int argno;
int casei; int casei;
s = ls->name; s = ls->name.c_str();
hash ^= i * 0x717239; hash ^= i * 0x717239;
hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1); hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
hash = this->VersionHashStr(hash, s + 1); hash = this->VersionHashStr(hash, s + 1);
s = ls->english; s = ls->english.c_str();
while ((cs = ParseCommandString(&s, buf, &argno, &casei)) != nullptr) { while ((cs = ParseCommandString(&s, buf, &argno, &casei)) != nullptr) {
if (cs->flags & C_DONTCOUNT) continue; if (cs->flags & C_DONTCOUNT) continue;
@@ -390,7 +330,7 @@ void EmitPlural(Buffer *buffer, char *buf, int value)
/* Parse out the number, if one exists. Otherwise default to prev arg. */ /* Parse out the number, if one exists. Otherwise default to prev arg. */
if (!ParseRelNum(&buf, &argidx, &offset)) argidx--; if (!ParseRelNum(&buf, &argidx, &offset)) argidx--;
const CmdStruct *cmd = _cur_pcs.cmd[argidx]; const CmdStruct *cmd = _cur_pcs.consuming_commands[argidx];
if (offset == -1) { if (offset == -1) {
/* Use default offset */ /* Use default offset */
if (cmd == nullptr || cmd->default_plural_offset < 0) { if (cmd == nullptr || cmd->default_plural_offset < 0) {
@@ -455,7 +395,7 @@ void EmitGender(Buffer *buffer, char *buf, int value)
* If no relative number exists, default to +0 */ * If no relative number exists, default to +0 */
ParseRelNum(&buf, &argidx, &offset); ParseRelNum(&buf, &argidx, &offset);
const CmdStruct *cmd = _cur_pcs.cmd[argidx]; const CmdStruct *cmd = _cur_pcs.consuming_commands[argidx];
if (cmd == nullptr || (cmd->flags & C_GENDER) == 0) { if (cmd == nullptr || (cmd->flags & C_GENDER) == 0) {
strgen_fatal("Command '%s' can't have a gender", cmd == nullptr ? "<empty>" : cmd->cmd); strgen_fatal("Command '%s' can't have a gender", cmd == nullptr ? "<empty>" : cmd->cmd);
} }
@@ -579,24 +519,19 @@ static const CmdStruct *ParseCommandString(const char **str, char *param, int *a
* @param master Are we reading the master file? * @param master Are we reading the master file?
* @param translation Are we reading a translation? * @param translation Are we reading a translation?
*/ */
StringReader::StringReader(StringData &data, const char *file, bool master, bool translation) : StringReader::StringReader(StringData &data, std::string file, bool master, bool translation) :
data(data), file(file), master(master), translation(translation) data(data), file(std::move(file)), master(master), translation(translation)
{ {
} }
/** Make sure the right reader gets freed. */ ParsedCommandStruct ExtractCommandString(const char *s, bool warnings)
StringReader::~StringReader()
{
}
void ExtractCommandString(ParsedCommandStruct *p, const char *s, bool warnings)
{ {
char param[MAX_COMMAND_PARAM_SIZE]; char param[MAX_COMMAND_PARAM_SIZE];
int argno; int argno;
int argidx = 0; int argidx = 0;
int casei; int casei;
memset(p, 0, sizeof(*p)); ParsedCommandStruct p;
for (;;) { for (;;) {
/* read until next command from a. */ /* read until next command from a. */
@@ -609,17 +544,16 @@ void ExtractCommandString(ParsedCommandStruct *p, const char *s, bool warnings)
if (ar->consumes) { if (ar->consumes) {
if (argno != -1) argidx = argno; if (argno != -1) argidx = argno;
if (argidx < 0 || (uint)argidx >= lengthof(p->cmd)) strgen_fatal("invalid param idx %d", argidx); if (argidx < 0 || (uint)argidx >= p.consuming_commands.max_size()) strgen_fatal("invalid param idx %d", argidx);
if (p->cmd[argidx] != nullptr && p->cmd[argidx] != ar) strgen_fatal("duplicate param idx %d", argidx); if (p.consuming_commands[argidx] != nullptr && p.consuming_commands[argidx] != ar) strgen_fatal("duplicate param idx %d", argidx);
p->cmd[argidx++] = ar; p.consuming_commands[argidx++] = ar;
} else if (!(ar->flags & C_DONTCOUNT)) { // Ignore some of them } else if (!(ar->flags & C_DONTCOUNT)) { // Ignore some of them
if (p->np >= lengthof(p->pairs)) strgen_fatal("too many commands in string, max " PRINTF_SIZE, lengthof(p->pairs)); p.non_consuming_commands.emplace_back(CmdPair{ar, param});
p->pairs[p->np].a = ar;
p->pairs[p->np].v = param[0] != '\0' ? stredup(param) : "";
p->np++;
} }
} }
return p;
} }
@@ -643,7 +577,7 @@ const CmdStruct *TranslateCmdForCompare(const CmdStruct *a)
} }
static bool CheckCommandsMatch(char *a, char *b, const char *name) static bool CheckCommandsMatch(const char *a, const char *b, const char *name)
{ {
/* If we're not translating, i.e. we're compiling the base language, /* If we're not translating, i.e. we're compiling the base language,
* it is pointless to do all these checks as it'll always be correct. * it is pointless to do all these checks as it'll always be correct.
@@ -651,45 +585,42 @@ static bool CheckCommandsMatch(char *a, char *b, const char *name)
*/ */
if (!_translation) return true; if (!_translation) return true;
ParsedCommandStruct templ;
ParsedCommandStruct lang;
bool result = true; bool result = true;
ExtractCommandString(&templ, b, true); ParsedCommandStruct templ = ExtractCommandString(b, true);
ExtractCommandString(&lang, a, true); ParsedCommandStruct lang = ExtractCommandString(a, true);
/* For each string in templ, see if we find it in lang */ /* For each string in templ, see if we find it in lang */
if (templ.np != lang.np) { if (templ.non_consuming_commands.max_size() != lang.non_consuming_commands.max_size()) {
strgen_warning("%s: template string and language string have a different # of commands", name); strgen_warning("%s: template string and language string have a different # of commands", name);
result = false; result = false;
} }
for (uint i = 0; i < templ.np; i++) { for (auto &templ_nc : templ.non_consuming_commands) {
/* see if we find it in lang, and zero it out */ /* see if we find it in lang, and zero it out */
bool found = false; bool found = false;
for (uint j = 0; j < lang.np; j++) { for (auto &lang_nc : lang.non_consuming_commands) {
if (templ.pairs[i].a == lang.pairs[j].a && if (templ_nc.cmd == lang_nc.cmd && templ_nc.param == lang_nc.param) {
strcmp(templ.pairs[i].v, lang.pairs[j].v) == 0) {
/* it was found in both. zero it out from lang so we don't find it again */ /* it was found in both. zero it out from lang so we don't find it again */
lang.pairs[j].a = nullptr; lang_nc.cmd = nullptr;
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
strgen_warning("%s: command '%s' exists in template file but not in language file", name, templ.pairs[i].a->cmd); strgen_warning("%s: command '%s' exists in template file but not in language file", name, templ_nc.cmd->cmd);
result = false; result = false;
} }
} }
/* if we reach here, all non consumer commands match up. /* if we reach here, all non consumer commands match up.
* Check if the non consumer commands match up also. */ * Check if the non consumer commands match up also. */
for (uint i = 0; i < lengthof(templ.cmd); i++) { for (uint i = 0; i < templ.consuming_commands.max_size(); i++) {
if (TranslateCmdForCompare(templ.cmd[i]) != lang.cmd[i]) { if (TranslateCmdForCompare(templ.consuming_commands[i]) != lang.consuming_commands[i]) {
strgen_warning("%s: Param idx #%d '%s' doesn't match with template command '%s'", name, i, strgen_warning("%s: Param idx #%d '%s' doesn't match with template command '%s'", name, i,
lang.cmd[i] == nullptr ? "<empty>" : TranslateCmdForCompare(lang.cmd[i])->cmd, lang.consuming_commands[i] == nullptr ? "<empty>" : TranslateCmdForCompare(lang.consuming_commands[i])->cmd,
templ.cmd[i] == nullptr ? "<empty>" : templ.cmd[i]->cmd); templ.consuming_commands[i] == nullptr ? "<empty>" : templ.consuming_commands[i]->cmd);
result = false; result = false;
} }
} }
@@ -754,7 +685,7 @@ void StringReader::HandleString(char *str)
if (ent != nullptr) { if (ent != nullptr) {
if (this->data.override_mode) { if (this->data.override_mode) {
ent->ReplaceDefinition(str, s, _cur_line); ent->ReplaceDefinition(s, _cur_line);
return; return;
} }
strgen_error("String name '%s' is used multiple times", str); strgen_error("String name '%s' is used multiple times", str);
@@ -772,7 +703,7 @@ void StringReader::HandleString(char *str)
std::unique_ptr<LangString> ls(new LangString(str, s, this->data.next_string_id, _cur_line)); std::unique_ptr<LangString> ls(new LangString(str, s, this->data.next_string_id, _cur_line));
if (this->data.no_translate_mode) ls->no_translate_mode = true; if (this->data.no_translate_mode) ls->no_translate_mode = true;
this->data.next_string_id = -1; this->data.next_string_id = -1;
this->data.Add(str, ls.get()); this->data.name_to_string[ls->name] = ls.get();
if (this->data.default_translation != nullptr) { if (this->data.default_translation != nullptr) {
ls->default_translation = this->data.default_translation; ls->default_translation = this->data.default_translation;
@@ -802,10 +733,9 @@ void StringReader::HandleString(char *str)
return; return;
} }
if (ent->translated && casep == nullptr) { if (!ent->translated.empty() && casep == nullptr) {
if (this->data.override_mode) { if (this->data.override_mode) {
free(ent->translated); ent->translated.clear();
ent->translated = nullptr;
} else { } else {
strgen_error("String name '%s' is used multiple times", str); strgen_error("String name '%s' is used multiple times", str);
return; return;
@@ -813,12 +743,12 @@ void StringReader::HandleString(char *str)
} }
/* make sure that the commands match */ /* make sure that the commands match */
if (!CheckCommandsMatch(s, ent->english, str)) return; if (!CheckCommandsMatch(s, ent->english.c_str(), str)) return;
if (casep != nullptr) { if (casep != nullptr) {
ent->translated_case = new Case(ResolveCaseName(casep, strlen(casep)), s, ent->translated_case); ent->translated_cases.emplace_back(ResolveCaseName(casep, strlen(casep)), s);
} else { } else {
ent->translated = stredup(s); ent->translated = s;
/* If the string was translated, use the line from the /* If the string was translated, use the line from the
* translated language so errors in the translated file * translated language so errors in the translated file
* are properly referenced to. */ * are properly referenced to. */
@@ -895,7 +825,7 @@ void StringReader::AssignIDs(size_t &next_id, LangString *ls)
strgen_error("Too many strings, maximum allowed is " PRINTF_SIZE, this->data.max_strings); strgen_error("Too many strings, maximum allowed is " PRINTF_SIZE, this->data.max_strings);
return; return;
} else if (this->data.strings[ls->index] != nullptr) { } else if (this->data.strings[ls->index] != nullptr) {
strgen_error("String ID 0x%X for '%s' already in use by '%s'", (uint)ls->index, ls->name, this->data.strings[ls->index]->name); strgen_error("String ID 0x%X for '%s' already in use by '%s'", (uint)ls->index, ls->name.c_str(), this->data.strings[ls->index]->name.c_str());
return; return;
} else { } else {
this->data.strings[ls->index] = ls; this->data.strings[ls->index] = ls;
@@ -915,7 +845,7 @@ void HeaderWriter::WriteHeader(const StringData &data)
int last = 0; int last = 0;
for (size_t i = 0; i < data.max_strings; i++) { for (size_t i = 0; i < data.max_strings; i++) {
if (data.strings[i] != nullptr) { if (data.strings[i] != nullptr) {
this->WriteStringID(data.strings[i]->name, (int)i); this->WriteStringID(data.strings[i]->name.c_str(), (int)i);
last = (int)i; last = (int)i;
} }
} }
@@ -927,20 +857,20 @@ static int TranslateArgumentIdx(int argidx, int offset)
{ {
int sum; int sum;
if (argidx < 0 || (uint)argidx >= lengthof(_cur_pcs.cmd)) { if (argidx < 0 || (uint)argidx >= _cur_pcs.consuming_commands.max_size()) {
strgen_fatal("invalid argidx %d", argidx); strgen_fatal("invalid argidx %d", argidx);
} }
const CmdStruct *cs = _cur_pcs.cmd[argidx]; const CmdStruct *cs = _cur_pcs.consuming_commands[argidx];
if (cs != nullptr && cs->consumes <= offset) { if (cs != nullptr && cs->consumes <= offset) {
strgen_fatal("invalid argidx offset %d:%d", argidx, offset); strgen_fatal("invalid argidx offset %d:%d", argidx, offset);
} }
if (_cur_pcs.cmd[argidx] == nullptr) { if (_cur_pcs.consuming_commands[argidx] == nullptr) {
strgen_fatal("no command for this argidx %d", argidx); strgen_fatal("no command for this argidx %d", argidx);
} }
for (int i = sum = 0; i < argidx; i++) { for (int i = sum = 0; i < argidx; i++) {
cs = _cur_pcs.cmd[i]; cs = _cur_pcs.consuming_commands[i];
sum += (cs != nullptr) ? cs->consumes : 1; sum += (cs != nullptr) ? cs->consumes : 1;
} }
@@ -986,7 +916,7 @@ static void PutCommandString(Buffer *buffer, const char *str)
} }
/* Output the one from the master string... it's always accurate. */ /* Output the one from the master string... it's always accurate. */
cs = _cur_pcs.cmd[_cur_argidx++]; cs = _cur_pcs.consuming_commands[_cur_argidx++];
if (cs == nullptr) { if (cs == nullptr) {
strgen_fatal("%s: No argument exists at position %d", _cur_ident, _cur_argidx - 1); strgen_fatal("%s: No argument exists at position %d", _cur_ident, _cur_argidx - 1);
} }
@@ -1030,7 +960,7 @@ void LanguageWriter::WriteLang(const StringData &data)
for (uint j = 0; j != in_use[tab]; j++) { for (uint j = 0; j != in_use[tab]; j++) {
const LangString *ls = data.strings[(tab * TAB_SIZE) + j]; const LangString *ls = data.strings[(tab * TAB_SIZE) + j];
if (ls != nullptr && ls->translated == nullptr && ls->default_translation == nullptr && !ls->no_translate_mode) { if (ls != nullptr && ls->translated.empty() && ls->default_translation == nullptr && !ls->no_translate_mode) {
_lang.missing++; _lang.missing++;
} }
} }
@@ -1047,8 +977,7 @@ void LanguageWriter::WriteLang(const StringData &data)
for (size_t tab = 0; tab < data.tabs; tab++) { for (size_t tab = 0; tab < data.tabs; tab++) {
for (uint j = 0; j != in_use[tab]; j++) { for (uint j = 0; j != in_use[tab]; j++) {
const LangString *ls = data.strings[(tab * TAB_SIZE) + j]; const LangString *ls = data.strings[(tab * TAB_SIZE) + j];
const Case *casep; const std::string *cmdp;
const char *cmdp;
/* For undefined strings, just set that it's an empty string */ /* For undefined strings, just set that it's an empty string */
if (ls == nullptr) { if (ls == nullptr) {
@@ -1056,13 +985,13 @@ void LanguageWriter::WriteLang(const StringData &data)
continue; continue;
} }
_cur_ident = ls->name; _cur_ident = ls->name.c_str();
_cur_line = ls->line; _cur_line = ls->line;
/* Produce a message if a string doesn't have a translation. */ /* Produce a message if a string doesn't have a translation. */
if (_show_todo > 0 && ls->translated == nullptr) { if (_show_todo > 0 && ls->translated.empty()) {
if ((_show_todo & 2) != 0) { if ((_show_todo & 2) != 0) {
strgen_warning("'%s' is untranslated", ls->name); strgen_warning("'%s' is untranslated", ls->name.c_str());
} }
if ((_show_todo & 1) != 0) { if ((_show_todo & 1) != 0) {
const char *s = "<TODO> "; const char *s = "<TODO> ";
@@ -1071,43 +1000,36 @@ void LanguageWriter::WriteLang(const StringData &data)
} }
/* Extract the strings and stuff from the english command string */ /* Extract the strings and stuff from the english command string */
ExtractCommandString(&_cur_pcs, ls->english, false); _cur_pcs = ExtractCommandString(ls->english.c_str(), false);
if (ls->translated_case != nullptr || ls->translated != nullptr) { if (!ls->translated_cases.empty() || !ls->translated.empty()) {
casep = ls->translated_case; cmdp = &ls->translated;
cmdp = ls->translated;
} else { } else {
casep = nullptr; cmdp = &ls->english;
cmdp = ls->english; if (ls->default_translation != nullptr && !ls->default_translation->translated.empty()) {
if (ls->default_translation != nullptr && ls->default_translation->translated != nullptr) { cmdp = &ls->default_translation->translated;
cmdp = ls->default_translation->translated;
} }
} }
_translated = cmdp != ls->english; _translated = cmdp != &ls->english;
if (casep != nullptr) {
const Case *c;
uint num;
if (!ls->translated_cases.empty()) {
/* Need to output a case-switch. /* Need to output a case-switch.
* It has this format * It has this format
* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT> * <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
* Each LEN is printed using 2 bytes in big endian order. */ * Each LEN is printed using 2 bytes in big endian order. */
buffer.AppendUtf8(SCC_SWITCH_CASE); buffer.AppendUtf8(SCC_SWITCH_CASE);
/* Count the number of cases */ buffer.AppendByte((byte)ls->translated_cases.size());
for (num = 0, c = casep; c; c = c->next) num++;
buffer.AppendByte(num);
/* Write each case */ /* Write each case */
for (c = casep; c != nullptr; c = c->next) { for (const Case &c : ls->translated_cases) {
buffer.AppendByte(c->caseidx); buffer.AppendByte(c.caseidx);
/* Make some space for the 16-bit length */ /* Make some space for the 16-bit length */
uint pos = (uint)buffer.size(); uint pos = (uint)buffer.size();
buffer.AppendByte(0); buffer.AppendByte(0);
buffer.AppendByte(0); buffer.AppendByte(0);
/* Write string */ /* Write string */
PutCommandString(&buffer, c->string); PutCommandString(&buffer, c.string.c_str());
buffer.AppendByte(0); // terminate with a zero buffer.AppendByte(0); // terminate with a zero
/* Fill in the length */ /* Fill in the length */
uint size = (uint)buffer.size() - (pos + 2); uint size = (uint)buffer.size() - (pos + 2);
@@ -1116,7 +1038,7 @@ void LanguageWriter::WriteLang(const StringData &data)
} }
} }
if (cmdp != nullptr) PutCommandString(&buffer, cmdp); if (!cmdp->empty()) PutCommandString(&buffer, cmdp->c_str());
this->WriteLength((uint)buffer.size()); this->WriteLength((uint)buffer.size());
this->Write(buffer.data(), buffer.size()); this->Write(buffer.data(), buffer.size());

View File

@@ -4474,7 +4474,7 @@ strval = STR_CONFIG_SETTING_AUTOSCROLL_DISABLED
cat = SC_BASIC cat = SC_BASIC
[SDTC_VAR] [SDTC_VAR]
ifdef = __EMSCRIPTEN__ ifdef = UNIX
var = gui.scroll_mode var = gui.scroll_mode
type = SLE_UINT8 type = SLE_UINT8
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_GUI_DROPDOWN flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_GUI_DROPDOWN
@@ -4487,7 +4487,7 @@ strval = STR_CONFIG_SETTING_SCROLLMODE_DEFAULT
cat = SC_BASIC cat = SC_BASIC
[SDTC_VAR] [SDTC_VAR]
ifndef = __EMSCRIPTEN__ ifndef = UNIX
var = gui.scroll_mode var = gui.scroll_mode
type = SLE_UINT8 type = SLE_UINT8
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_GUI_DROPDOWN flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_GUI_DROPDOWN

View File

@@ -24,11 +24,9 @@
* Try to retrieve the current clipboard contents. * Try to retrieve the current clipboard contents.
* *
* @note OS-specific function. * @note OS-specific function.
* @param buffer Clipboard content. * @return The (optional) clipboard contents.
* @param last The pointer to the last element of the destination buffer
* @return True if some text could be retrieved.
*/ */
bool GetClipboardContents(char *buffer, const char *last); std::optional<std::string> GetClipboardContents();
int _caret_timer; int _caret_timer;
@@ -225,11 +223,10 @@ bool Textbuf::InsertString(const char *str, bool marked, const char *caret, cons
*/ */
bool Textbuf::InsertClipboard() bool Textbuf::InsertClipboard()
{ {
char utf8_buf[512]; auto contents = GetClipboardContents();
if (!contents.has_value()) return false;
if (!GetClipboardContents(utf8_buf, lastof(utf8_buf))) return false; return this->InsertString(contents.value().c_str(), false);
return this->InsertString(utf8_buf, false);
} }
/** /**

View File

@@ -390,7 +390,7 @@ bool VideoDriver_Allegro::PollEvent()
} }
/* Mouse movement */ /* Mouse movement */
if (_cursor.UpdateCursorPosition(mouse_x, mouse_y, false)) { if (_cursor.UpdateCursorPosition(mouse_x, mouse_y)) {
position_mouse(_cursor.pos.x, _cursor.pos.y); position_mouse(_cursor.pos.x, _cursor.pos.y);
} }
if (_cursor.delta.x != 0 || _cursor.delta.y) mouse_action = true; if (_cursor.delta.x != 0 || _cursor.delta.y) mouse_action = true;

View File

@@ -661,7 +661,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel
_cursor.UpdateCursorPositionRelative(event.deltaX * self.getContentsScale, event.deltaY * self.getContentsScale); _cursor.UpdateCursorPositionRelative(event.deltaX * self.getContentsScale, event.deltaY * self.getContentsScale);
} else { } else {
NSPoint pt = [ self mousePositionFromEvent:event ]; NSPoint pt = [ self mousePositionFromEvent:event ];
_cursor.UpdateCursorPosition(pt.x, pt.y, false); _cursor.UpdateCursorPosition(pt.x, pt.y);
} }
HandleMouseEvents(); HandleMouseEvents();

View File

@@ -653,12 +653,25 @@ bool VideoDriver_SDL_Base::PollEvent()
if (!SDL_PollEvent(&ev)) return false; if (!SDL_PollEvent(&ev)) return false;
switch (ev.type) { switch (ev.type) {
case SDL_MOUSEMOTION: case SDL_MOUSEMOTION: {
if (_cursor.UpdateCursorPosition(ev.motion.x, ev.motion.y, true)) { int32_t x = ev.motion.x;
int32_t y = ev.motion.y;
if (_cursor.fix_at) {
/* Get all queued mouse events now in case we have to warp the cursor. In the
* end, we only care about the current mouse position and not bygone events. */
while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION)) {
x = ev.motion.x;
y = ev.motion.y;
}
}
if (_cursor.UpdateCursorPosition(x, y)) {
SDL_WarpMouseInWindow(this->sdl_window, _cursor.pos.x, _cursor.pos.y); SDL_WarpMouseInWindow(this->sdl_window, _cursor.pos.x, _cursor.pos.y);
} }
HandleMouseEvents(); HandleMouseEvents();
break; break;
}
case SDL_MOUSEWHEEL: case SDL_MOUSEWHEEL:
if (ev.wheel.y > 0) { if (ev.wheel.y > 0) {
@@ -790,10 +803,8 @@ bool VideoDriver_SDL_Base::PollEvent()
} else if (ev.window.event == SDL_WINDOWEVENT_ENTER) { } else if (ev.window.event == SDL_WINDOWEVENT_ENTER) {
// mouse entered the window, enable cursor // mouse entered the window, enable cursor
_cursor.in_window = true; _cursor.in_window = true;
#ifdef __EMSCRIPTEN__
/* Ensure pointer lock will not occur. */ /* Ensure pointer lock will not occur. */
SDL_SetRelativeMouseMode(SDL_FALSE); SDL_SetRelativeMouseMode(SDL_FALSE);
#endif
} else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) { } else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) {
// mouse left the window, undraw cursor // mouse left the window, undraw cursor
UndrawMouseCursor(); UndrawMouseCursor();
@@ -832,9 +843,6 @@ static const char *InitializeSDL()
* UpdateWindowSurface() to update the window's texture instead of * UpdateWindowSurface() to update the window's texture instead of
* its surface. */ * its surface. */
SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "0"); SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "0");
#ifndef __EMSCRIPTEN__
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1");
#endif
/* Check if the video-driver is already initialized. */ /* Check if the video-driver is already initialized. */
if (SDL_WasInit(SDL_INIT_VIDEO) != 0) return nullptr; if (SDL_WasInit(SDL_INIT_VIDEO) != 0) return nullptr;

View File

@@ -484,12 +484,25 @@ bool VideoDriver_SDL::PollEvent()
if (!SDL_PollEvent(&ev)) return false; if (!SDL_PollEvent(&ev)) return false;
switch (ev.type) { switch (ev.type) {
case SDL_MOUSEMOTION: case SDL_MOUSEMOTION: {
if (_cursor.UpdateCursorPosition(ev.motion.x, ev.motion.y, true)) { int32_t x = ev.motion.x;
int32_t y = ev.motion.y;
if (_cursor.fix_at) {
/* Get all queued mouse events now in case we have to warp the cursor. In the
* end, we only care about the current mouse position and not bygone events. */
while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT, SDL_MOUSEMOTION)) {
x = ev.motion.x;
y = ev.motion.y;
}
}
if (_cursor.UpdateCursorPosition(x, y)) {
SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y); SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y);
} }
HandleMouseEvents(); HandleMouseEvents();
break; break;
}
case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONDOWN:
if (_rightclick_emulate && SDL_GetModState() & KMOD_CTRL) { if (_rightclick_emulate && SDL_GetModState() & KMOD_CTRL) {

View File

@@ -491,7 +491,7 @@ LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
} }
} }
if (_cursor.UpdateCursorPosition(x, y, false)) { if (_cursor.UpdateCursorPosition(x, y)) {
POINT pt; POINT pt;
pt.x = _cursor.pos.x; pt.x = _cursor.pos.x;
pt.y = _cursor.pos.y; pt.y = _cursor.pos.y;