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
commit c0598d34bf
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)
{
char alias_buffer[ICON_MAX_STREAMSIZE] = { '\0' };
char *alias_stream = alias_buffer;
std::string alias_buffer;
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++) {
switch (*cmdptr) {
case '\'': // ' will double for ""
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
alias_buffer += '\"';
break;
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_stream = '\0'; // Make sure the new command is terminated.
alias_buffer.clear();
cmdptr++;
break;
@ -285,21 +283,21 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char
switch (*cmdptr) {
case '+': { // All parameters separated: "[param 1]" "[param 2]"
for (uint i = 0; i != tokencount; i++) {
if (i != 0) alias_stream = strecpy(alias_stream, " ", lastof(alias_buffer));
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
alias_stream = strecpy(alias_stream, tokens[i], lastof(alias_buffer));
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
if (i != 0) alias_buffer += ' ';
alias_buffer += '\"';
alias_buffer += tokens[i];
alias_buffer += '\"';
}
break;
}
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++) {
if (i != 0) alias_stream = strecpy(alias_stream, " ", lastof(alias_buffer));
alias_stream = strecpy(alias_stream, tokens[i], lastof(alias_buffer));
if (i != 0) alias_buffer += " ";
alias_buffer += tokens[i];
}
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
alias_buffer += '\"';
break;
}
@ -312,27 +310,26 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char
return;
}
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
alias_stream = strecpy(alias_stream, tokens[param], lastof(alias_buffer));
alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
alias_buffer += '\"';
alias_buffer += tokens[param];
alias_buffer += '\"';
break;
}
}
break;
default:
*alias_stream++ = *cmdptr;
*alias_stream = '\0';
alias_buffer += *cmdptr;
break;
}
if (alias_stream >= lastof(alias_buffer) - 1) {
if (alias_buffer.size() >= ICON_MAX_STREAMSIZE - 1) {
IConsoleError("Requested alias execution would overflow execution buffer");
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));
}
/**
* 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 */

View File

@ -288,10 +288,9 @@ static void ExtractStringParams(const StringData &data, StringParamsList &params
if (ls != nullptr) {
StringParams &param = params.emplace_back();
ParsedCommandStruct pcs;
ExtractCommandString(&pcs, ls->english, false);
ParsedCommandStruct pcs = ExtractCommandString(ls->english.c_str(), false);
for (const CmdStruct *cs : pcs.cmd) {
for (const CmdStruct *cs : pcs.consuming_commands) {
if (cs == nullptr) break;
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_y How much change in the Y position.
*/
void CursorVars::UpdateCursorPositionRelative(int delta_x, int delta_y)
{
if (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;
assert(this->fix_at);
this->pos.x = Clamp(this->pos.x + delta_x, 0, _cur_resolution.width - 1);
this->pos.y = Clamp(this->pos.y + delta_y, 0, _cur_resolution.height - 1);
this->delta.x = last_position_x - this->pos.x;
this->delta.y = last_position_y - this->pos.y;
this->dirty = true;
}
this->delta.x = delta_x;
this->delta.y = delta_y;
}
/**
* Update cursor position on mouse movement.
* @param x New X 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.
*/
bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp)
bool CursorVars::UpdateCursorPosition(int x, int y)
{
/* Detecting relative mouse movement is somewhat tricky.
* - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
* - 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.
*/
this->delta.x = x - this->pos.x;
this->delta.y = y - this->pos.y;
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->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;
}
return this->delta.x != 0 || this->delta.y != 0;
} 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->pos.x = x;
this->pos.y = y;
}
return need_warp;
return false;
}
bool ChangeResInGame(int width, int height)

View File

@ -145,11 +145,7 @@ struct CursorVars {
bool vehchain; ///< vehicle chain is dragged
void UpdateCursorPositionRelative(int delta_x, int delta_y);
bool UpdateCursorPosition(int x, int y, bool queued_warp);
private:
bool queued_warp;
Point last_position;
bool UpdateCursorPosition(int x, int y);
};
/** 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());
}
/**
* 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 */
/**
@ -300,8 +327,6 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview)
{
auto &info = survey["info"];
SurveyOS(info["os"]);
info["os"]["hardware_concurrency"] = std::thread::hardware_concurrency();
SurveyOpenTTD(info["openttd"]);
SurveyConfiguration(info["configuration"]);
SurveyFont(info["font"]);

View File

@ -595,9 +595,9 @@ void MakeNewgameSettingsLive()
void OpenBrowser(const char *url)
{
/* Make sure we only accept urls that are sure to open a browser. */
if (strstr(url, "http://") != url && strstr(url, "https://") != url) return;
OSOpenBrowser(url);
if (StrStartsWith(url, "http://") || StrStartsWith(url, "https://")) {
OSOpenBrowser(url);
}
}
/** 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).
*
* @param buffer Clipboard content.
* @param last The pointer to the last element of the destination buffer
* @return Whether clipboard is empty or not.
* @return The (optional) clipboard contents.
*/
bool GetClipboardContents(char *buffer, const char *last)
std::optional<std::string> GetClipboardContents()
{
NSPasteboard *pb = [ NSPasteboard generalPasteboard ];
NSArray *types = [ NSArray arrayWithObject:NSPasteboardTypeString ];
NSString *bestType = [ pb availableTypeFromArray:types ];
/* Clipboard has no text data available. */
if (bestType == nil) return false;
if (bestType == nil) return std::nullopt;
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 true;
return [ string UTF8String ];
}
/** Set the application's bundle directory.

View File

@ -16,9 +16,12 @@
#include <mach-o/arch.h>
#include <nlohmann/json.hpp>
#include <thread>
#include "../../safeguards.h"
extern std::string SurveyMemoryToText(uint64_t memory);
void SurveyOS(nlohmann::json &json)
{
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["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 */

View File

@ -174,27 +174,25 @@ int CDECL main(int argc, char *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 */
#ifndef __INNOTEK_LIBC__
HAB hab = 0;
if (WinOpenClipbrd(hab))
{
const char *text = (const char*)WinQueryClipbrdData(hab, CF_TEXT);
if (WinOpenClipbrd(hab)) {
const char *text = (const char *)WinQueryClipbrdData(hab, CF_TEXT);
if (text != nullptr)
{
strecpy(buffer, text, last);
if (text != nullptr) {
std::string result = text;
WinCloseClipbrd(hab);
return true;
return result;
}
WinCloseClipbrd(hab);
}
#endif
return false;
return std::nullopt;
}

View File

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

View File

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

View File

@ -14,10 +14,13 @@
#include "../../core/format.hpp"
#include <nlohmann/json.hpp>
#include <thread>
#include <windows.h>
#include "../../safeguards.h"
extern std::string SurveyMemoryToText(uint64_t memory);
void SurveyOS(nlohmann::json &json)
{
_OSVERSIONINFOA os;
@ -31,7 +34,8 @@ void SurveyOS(nlohmann::json &json)
status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status);
json["memory"] = status.ullTotalPhys;
json["memory"] = SurveyMemoryToText(status.ullTotalPhys);
json["hardware_concurrency"] = std::thread::hardware_concurrency();
}
#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;
const char *ptr;
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return std::nullopt;
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
OpenClipboard(nullptr);
cbuf = GetClipboardData(CF_UNICODETEXT);
OpenClipboard(nullptr);
HGLOBAL cbuf = GetClipboardData(CF_UNICODETEXT);
ptr = (const char*)GlobalLock(cbuf);
int out_len = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)ptr, -1, buffer, (last - buffer) + 1, nullptr, nullptr);
GlobalUnlock(cbuf);
CloseClipboard();
std::string result = FS2OTTD(static_cast<LPCWSTR>(GlobalLock(cbuf)));
GlobalUnlock(cbuf);
CloseClipboard();
if (out_len == 0) return false;
} else {
return false;
}
return true;
if (result.empty()) return std::nullopt;
return result;
}

View File

@ -242,7 +242,7 @@ public:
{
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 <vector>
#include <unordered_map>
#include <array>
/** Container for the different cases of a string. */
struct Case {
int caseidx; ///< The index of the case.
char *string; ///< The translation of the case.
Case *next; ///< The next, chained, case.
int caseidx; ///< The index of the case.
std::string string; ///< The translation of the case.
Case(int caseidx, const char *string, Case *next);
~Case();
Case(int caseidx, std::string string);
};
/** Information about a single string. */
struct LangString {
char *name; ///< Name of the string.
char *english; ///< English text.
char *translated; ///< Translated text.
LangString *hash_next; ///< Next hash entry.
int index; ///< The index in the language file.
int line; ///< Line of string in source-file.
Case *translated_case; ///< Cases of the translation.
std::string name; ///< Name of the string.
std::string english; ///< English text.
std::string translated; ///< Translated text.
int index; ///< The index in the language file.
int line; ///< Line of string in source-file.
std::vector<Case> translated_cases; ///< Cases of the translation.
std::unique_ptr<LangString> chain_before;
std::unique_ptr<LangString> chain_after;
bool no_translate_mode = false;
LangString *default_translation = nullptr;
LangString(const char *name, const char *english, int index, int line);
void ReplaceDefinition(const char *name, const char *english, int line);
~LangString();
LangString(std::string name, std::string english, size_t index, int line);
void ReplaceDefinition(std::string english, int line);
void FreeTranslation();
};
/** Information about the currently known strings. */
struct StringData {
LangString **strings; ///< Array of all known strings.
LangString **hash_heads; ///< Hash table for the strings.
std::vector<LangString *> strings; ///< List of all known 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 max_strings; ///< The maximum number of strings.
int next_string_id; ///< The next string ID to allocate.
@ -62,11 +61,8 @@ struct StringData {
LangString *default_translation = nullptr;
StringData(size_t tabs);
~StringData();
void FreeTranslation();
uint HashStr(const char *s) const;
void Add(const char *s, LangString *ls);
LangString *Find(const char *s);
LangString *Find(const std::string_view s);
uint VersionHashStr(uint hash, const char *s) const;
uint Version() const;
uint CountInUse(uint tab) const;
@ -79,8 +75,8 @@ struct StringReader {
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.
StringReader(StringData &data, const char *file, bool master, bool translation);
virtual ~StringReader();
StringReader(StringData &data, std::string file, bool master, bool translation);
virtual ~StringReader() {}
void HandleString(char *str);
/**
@ -157,18 +153,17 @@ struct LanguageWriter {
struct CmdStruct;
struct CmdPair {
const CmdStruct *a;
const char *v;
const CmdStruct *cmd;
std::string param;
};
struct ParsedCommandStruct {
uint np;
CmdPair pairs[32];
const CmdStruct *cmd[32]; // ordered by param #
std::vector<CmdPair> non_consuming_commands;
std::array<const CmdStruct*, 32> consuming_commands{ nullptr }; // ordered by param #
};
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_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.
* @param caseidx The index 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) :
caseidx(caseidx), string(stredup(string)), next(next)
Case::Case(int caseidx, std::string string) :
caseidx(caseidx), string(std::move(string))
{
}
/** Free everything we allocated. */
Case::~Case()
{
free(this->string);
delete this->next;
}
/**
* Create a new string.
* @param name The name of the string.
@ -58,38 +50,22 @@ Case::~Case()
* @param index The index in the string table.
* @param line The line this string was found on.
*/
LangString::LangString(const char *name, const char *english, int index, int line) :
name(stredup(name)), english(stredup(english)), translated(nullptr),
hash_next(0), index(index), line(line), translated_case(nullptr)
LangString::LangString(std::string name, std::string english, size_t index, int line) :
name(std::move(name)), english(std::move(english)), index(index), line(line)
{
}
/** Free everything we allocated. */
LangString::~LangString()
void LangString::ReplaceDefinition(std::string english, int line)
{
free(this->name);
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->english = std::move(english);
this->line = line;
}
/** Free all data related to the translation. */
void LangString::FreeTranslation()
{
free(this->translated);
this->translated = nullptr;
delete this->translated_case;
this->translated_case = nullptr;
this->translated.clear();
this->translated_cases.clear();
}
/**
@ -98,18 +74,10 @@ void LangString::FreeTranslation()
*/
StringData::StringData(size_t tabs) : tabs(tabs), max_strings(tabs * TAB_SIZE)
{
this->strings = CallocT<LangString *>(max_strings);
this->hash_heads = CallocT<LangString *>(max_strings);
this->strings.resize(max_strings);
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. */
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.
* @param s The string name to search on.
* @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) {
if (strcmp(ls->name, s) == 0) return ls;
ls = ls->hash_next;
}
return nullptr;
return it->second;
}
/**
@ -193,12 +133,12 @@ uint StringData::Version() const
int argno;
int casei;
s = ls->name;
s = ls->name.c_str();
hash ^= i * 0x717239;
hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
hash = this->VersionHashStr(hash, s + 1);
s = ls->english;
s = ls->english.c_str();
while ((cs = ParseCommandString(&s, buf, &argno, &casei)) != nullptr) {
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. */
if (!ParseRelNum(&buf, &argidx, &offset)) argidx--;
const CmdStruct *cmd = _cur_pcs.cmd[argidx];
const CmdStruct *cmd = _cur_pcs.consuming_commands[argidx];
if (offset == -1) {
/* Use default offset */
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 */
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) {
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 translation Are we reading a translation?
*/
StringReader::StringReader(StringData &data, const char *file, bool master, bool translation) :
data(data), file(file), master(master), translation(translation)
StringReader::StringReader(StringData &data, std::string file, bool master, bool translation) :
data(data), file(std::move(file)), master(master), translation(translation)
{
}
/** Make sure the right reader gets freed. */
StringReader::~StringReader()
{
}
void ExtractCommandString(ParsedCommandStruct *p, const char *s, bool warnings)
ParsedCommandStruct ExtractCommandString(const char *s, bool warnings)
{
char param[MAX_COMMAND_PARAM_SIZE];
int argno;
int argidx = 0;
int casei;
memset(p, 0, sizeof(*p));
ParsedCommandStruct p;
for (;;) {
/* read until next command from a. */
@ -609,17 +544,16 @@ void ExtractCommandString(ParsedCommandStruct *p, const char *s, bool warnings)
if (ar->consumes) {
if (argno != -1) argidx = argno;
if (argidx < 0 || (uint)argidx >= lengthof(p->cmd)) strgen_fatal("invalid param idx %d", argidx);
if (p->cmd[argidx] != nullptr && p->cmd[argidx] != ar) strgen_fatal("duplicate param idx %d", argidx);
if (argidx < 0 || (uint)argidx >= p.consuming_commands.max_size()) strgen_fatal("invalid 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
if (p->np >= lengthof(p->pairs)) strgen_fatal("too many commands in string, max " PRINTF_SIZE, lengthof(p->pairs));
p->pairs[p->np].a = ar;
p->pairs[p->np].v = param[0] != '\0' ? stredup(param) : "";
p->np++;
p.non_consuming_commands.emplace_back(CmdPair{ar, param});
}
}
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,
* 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;
ParsedCommandStruct templ;
ParsedCommandStruct lang;
bool result = true;
ExtractCommandString(&templ, b, true);
ExtractCommandString(&lang, a, true);
ParsedCommandStruct templ = ExtractCommandString(b, true);
ParsedCommandStruct lang = ExtractCommandString(a, true);
/* 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);
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 */
bool found = false;
for (uint j = 0; j < lang.np; j++) {
if (templ.pairs[i].a == lang.pairs[j].a &&
strcmp(templ.pairs[i].v, lang.pairs[j].v) == 0) {
for (auto &lang_nc : lang.non_consuming_commands) {
if (templ_nc.cmd == lang_nc.cmd && templ_nc.param == lang_nc.param) {
/* 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;
break;
}
}
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;
}
}
/* if we reach here, all non consumer commands match up.
* Check if the non consumer commands match up also. */
for (uint i = 0; i < lengthof(templ.cmd); i++) {
if (TranslateCmdForCompare(templ.cmd[i]) != lang.cmd[i]) {
for (uint i = 0; i < templ.consuming_commands.max_size(); 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,
lang.cmd[i] == nullptr ? "<empty>" : TranslateCmdForCompare(lang.cmd[i])->cmd,
templ.cmd[i] == nullptr ? "<empty>" : templ.cmd[i]->cmd);
lang.consuming_commands[i] == nullptr ? "<empty>" : TranslateCmdForCompare(lang.consuming_commands[i])->cmd,
templ.consuming_commands[i] == nullptr ? "<empty>" : templ.consuming_commands[i]->cmd);
result = false;
}
}
@ -754,7 +685,7 @@ void StringReader::HandleString(char *str)
if (ent != nullptr) {
if (this->data.override_mode) {
ent->ReplaceDefinition(str, s, _cur_line);
ent->ReplaceDefinition(s, _cur_line);
return;
}
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));
if (this->data.no_translate_mode) ls->no_translate_mode = true;
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) {
ls->default_translation = this->data.default_translation;
@ -802,10 +733,9 @@ void StringReader::HandleString(char *str)
return;
}
if (ent->translated && casep == nullptr) {
if (!ent->translated.empty() && casep == nullptr) {
if (this->data.override_mode) {
free(ent->translated);
ent->translated = nullptr;
ent->translated.clear();
} else {
strgen_error("String name '%s' is used multiple times", str);
return;
@ -813,12 +743,12 @@ void StringReader::HandleString(char *str)
}
/* 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) {
ent->translated_case = new Case(ResolveCaseName(casep, strlen(casep)), s, ent->translated_case);
ent->translated_cases.emplace_back(ResolveCaseName(casep, strlen(casep)), s);
} else {
ent->translated = stredup(s);
ent->translated = s;
/* If the string was translated, use the line from the
* translated language so errors in the translated file
* 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);
return;
} 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;
} else {
this->data.strings[ls->index] = ls;
@ -915,7 +845,7 @@ void HeaderWriter::WriteHeader(const StringData &data)
int last = 0;
for (size_t i = 0; i < data.max_strings; i++) {
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;
}
}
@ -927,20 +857,20 @@ static int TranslateArgumentIdx(int argidx, int offset)
{
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);
}
const CmdStruct *cs = _cur_pcs.cmd[argidx];
const CmdStruct *cs = _cur_pcs.consuming_commands[argidx];
if (cs != nullptr && cs->consumes <= 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);
}
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;
}
@ -986,7 +916,7 @@ static void PutCommandString(Buffer *buffer, const char *str)
}
/* 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) {
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++) {
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++;
}
}
@ -1047,8 +977,7 @@ void LanguageWriter::WriteLang(const StringData &data)
for (size_t tab = 0; tab < data.tabs; tab++) {
for (uint j = 0; j != in_use[tab]; j++) {
const LangString *ls = data.strings[(tab * TAB_SIZE) + j];
const Case *casep;
const char *cmdp;
const std::string *cmdp;
/* For undefined strings, just set that it's an empty string */
if (ls == nullptr) {
@ -1056,13 +985,13 @@ void LanguageWriter::WriteLang(const StringData &data)
continue;
}
_cur_ident = ls->name;
_cur_ident = ls->name.c_str();
_cur_line = ls->line;
/* 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) {
strgen_warning("'%s' is untranslated", ls->name);
strgen_warning("'%s' is untranslated", ls->name.c_str());
}
if ((_show_todo & 1) != 0) {
const char *s = "<TODO> ";
@ -1071,43 +1000,36 @@ void LanguageWriter::WriteLang(const StringData &data)
}
/* 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) {
casep = ls->translated_case;
cmdp = ls->translated;
if (!ls->translated_cases.empty() || !ls->translated.empty()) {
cmdp = &ls->translated;
} else {
casep = nullptr;
cmdp = ls->english;
if (ls->default_translation != nullptr && ls->default_translation->translated != nullptr) {
cmdp = ls->default_translation->translated;
cmdp = &ls->english;
if (ls->default_translation != nullptr && !ls->default_translation->translated.empty()) {
cmdp = &ls->default_translation->translated;
}
}
_translated = cmdp != ls->english;
if (casep != nullptr) {
const Case *c;
uint num;
_translated = cmdp != &ls->english;
if (!ls->translated_cases.empty()) {
/* Need to output a case-switch.
* It has this format
* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
* Each LEN is printed using 2 bytes in big endian order. */
buffer.AppendUtf8(SCC_SWITCH_CASE);
/* Count the number of cases */
for (num = 0, c = casep; c; c = c->next) num++;
buffer.AppendByte(num);
buffer.AppendByte((byte)ls->translated_cases.size());
/* Write each case */
for (c = casep; c != nullptr; c = c->next) {
buffer.AppendByte(c->caseidx);
for (const Case &c : ls->translated_cases) {
buffer.AppendByte(c.caseidx);
/* Make some space for the 16-bit length */
uint pos = (uint)buffer.size();
buffer.AppendByte(0);
buffer.AppendByte(0);
/* Write string */
PutCommandString(&buffer, c->string);
PutCommandString(&buffer, c.string.c_str());
buffer.AppendByte(0); // terminate with a zero
/* Fill in the length */
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->Write(buffer.data(), buffer.size());

View File

@ -4474,7 +4474,7 @@ strval = STR_CONFIG_SETTING_AUTOSCROLL_DISABLED
cat = SC_BASIC
[SDTC_VAR]
ifdef = __EMSCRIPTEN__
ifdef = UNIX
var = gui.scroll_mode
type = SLE_UINT8
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
[SDTC_VAR]
ifndef = __EMSCRIPTEN__
ifndef = UNIX
var = gui.scroll_mode
type = SLE_UINT8
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.
*
* @note OS-specific function.
* @param buffer Clipboard content.
* @param last The pointer to the last element of the destination buffer
* @return True if some text could be retrieved.
* @return The (optional) clipboard contents.
*/
bool GetClipboardContents(char *buffer, const char *last);
std::optional<std::string> GetClipboardContents();
int _caret_timer;
@ -225,11 +223,10 @@ bool Textbuf::InsertString(const char *str, bool marked, const char *caret, cons
*/
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(utf8_buf, false);
return this->InsertString(contents.value().c_str(), false);
}
/**

View File

@ -390,7 +390,7 @@ bool VideoDriver_Allegro::PollEvent()
}
/* 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);
}
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);
} else {
NSPoint pt = [ self mousePositionFromEvent:event ];
_cursor.UpdateCursorPosition(pt.x, pt.y, false);
_cursor.UpdateCursorPosition(pt.x, pt.y);
}
HandleMouseEvents();

View File

@ -653,12 +653,25 @@ bool VideoDriver_SDL_Base::PollEvent()
if (!SDL_PollEvent(&ev)) return false;
switch (ev.type) {
case SDL_MOUSEMOTION:
if (_cursor.UpdateCursorPosition(ev.motion.x, ev.motion.y, true)) {
case SDL_MOUSEMOTION: {
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);
}
HandleMouseEvents();
break;
}
case SDL_MOUSEWHEEL:
if (ev.wheel.y > 0) {
@ -790,10 +803,8 @@ bool VideoDriver_SDL_Base::PollEvent()
} else if (ev.window.event == SDL_WINDOWEVENT_ENTER) {
// mouse entered the window, enable cursor
_cursor.in_window = true;
#ifdef __EMSCRIPTEN__
/* Ensure pointer lock will not occur. */
SDL_SetRelativeMouseMode(SDL_FALSE);
#endif
} else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) {
// mouse left the window, undraw cursor
UndrawMouseCursor();
@ -832,9 +843,6 @@ static const char *InitializeSDL()
* UpdateWindowSurface() to update the window's texture instead of
* its surface. */
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. */
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;
switch (ev.type) {
case SDL_MOUSEMOTION:
if (_cursor.UpdateCursorPosition(ev.motion.x, ev.motion.y, true)) {
case SDL_MOUSEMOTION: {
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);
}
HandleMouseEvents();
break;
}
case SDL_MOUSEBUTTONDOWN:
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;
pt.x = _cursor.pos.x;
pt.y = _cursor.pos.y;