Update: Translations from eints
[openttd-github.git] / src / game / game_gui.cpp
blob22461404b08ac29ccac8478a96d846dc29642b03
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file game_gui.cpp %Window for configuring the Game Script */
10 #include "../stdafx.h"
11 #include "../error.h"
12 #include "../settings_gui.h"
13 #include "../querystring_gui.h"
14 #include "../window_func.h"
15 #include "../network/network.h"
16 #include "../network/network_content.h"
17 #include "../dropdown_type.h"
18 #include "../dropdown_func.h"
19 #include "../timer/timer.h"
20 #include "../timer/timer_window.h"
22 #include "game.hpp"
23 #include "game_gui.hpp"
24 #include "game_config.hpp"
25 #include "game_info.hpp"
26 #include "../script/script_gui.h"
27 #include "../script_config.hpp"
28 #include "../table/strings.h"
30 #include "../safeguards.h"
33 /** Widgets for the configure GS window. */
34 static constexpr NWidgetPart _nested_gs_config_widgets[] = {
35 NWidget(NWID_HORIZONTAL),
36 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
37 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_CAPTION_GAMESCRIPT, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
38 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
39 EndContainer(),
40 NWidget(WWT_PANEL, COLOUR_MAUVE, WID_GSC_BACKGROUND),
41 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0), SetPadding(WidgetDimensions::unscaled.sparse_resize),
42 NWidget(WWT_FRAME, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_GAMESCRIPT, STR_NULL), SetFill(1, 0), SetResize(1, 0),
43 NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_GSC_GSLIST), SetMinimalSize(288, 14), SetFill(1, 1), SetResize(1, 0), SetMatrixDataTip(1, 1, STR_AI_CONFIG_GAMELIST_TOOLTIP),
44 EndContainer(),
45 NWidget(WWT_FRAME, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_GAMESCRIPT_PARAM, STR_NULL), SetFill(1, 1), SetResize(1, 0), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
46 NWidget(NWID_HORIZONTAL),
47 NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_GSC_SETTINGS), SetFill(1, 0), SetResize(1, 1), SetMinimalSize(188, 182), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_GSC_SCROLLBAR),
48 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GSC_SCROLLBAR),
49 EndContainer(),
50 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_GSC_RESET), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_AI_SETTINGS_RESET, STR_NULL),
51 EndContainer(),
52 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
53 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
54 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_GSC_CHANGE), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_AI_CONFIG_CHANGE_GAMESCRIPT, STR_AI_CONFIG_CHANGE_TOOLTIP),
55 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_GSC_CONTENT_DOWNLOAD), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
56 EndContainer(),
57 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
58 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
59 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_GSC_OPEN_URL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_CONTENT_OPEN_URL, STR_CONTENT_OPEN_URL_TOOLTIP),
60 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_GSC_TEXTFILE + TFT_README), SetFill(1, 1), SetResize(1, 0), SetMinimalSize(93, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_TEXTFILE_VIEW_README_TOOLTIP),
61 EndContainer(),
62 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
63 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_GSC_TEXTFILE + TFT_CHANGELOG), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_TEXTFILE_VIEW_CHANGELOG_TOOLTIP),
64 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_GSC_TEXTFILE + TFT_LICENSE), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_TEXTFILE_VIEW_LICENCE_TOOLTIP),
65 EndContainer(),
66 EndContainer(),
67 EndContainer(),
68 EndContainer(),
69 NWidget(NWID_HORIZONTAL),
70 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
71 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE), SetDataTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
72 EndContainer(),
73 EndContainer(),
76 /** Window definition for the configure GS window. */
77 static WindowDesc _gs_config_desc(
78 WDP_CENTER, "settings_gs_config", 500, 350,
79 WC_GAME_OPTIONS, WC_NONE,
81 _nested_gs_config_widgets
84 /**
85 * Window to configure which GSs will start.
87 struct GSConfigWindow : public Window {
88 ScriptConfig *gs_config; ///< The configuration we're modifying.
89 int line_height; ///< Height of a single GS-name line.
90 int clicked_button; ///< The button we clicked.
91 bool clicked_increase; ///< Whether we clicked the increase or decrease button.
92 bool clicked_dropdown; ///< Whether the dropdown is open.
93 bool closing_dropdown; ///< True, if the dropdown list is currently closing.
94 int clicked_row; ///< The clicked row of settings.
95 Scrollbar *vscroll; ///< Cache of the vertical scrollbar.
96 typedef std::vector<const ScriptConfigItem *> VisibleSettingsList; ///< typdef for a vector of script settings
97 VisibleSettingsList visible_settings; ///< List of visible GS settings
99 GSConfigWindow() : Window(_gs_config_desc),
100 clicked_button(-1),
101 clicked_dropdown(false),
102 closing_dropdown(false)
104 this->gs_config = GameConfig::GetConfig();
106 this->CreateNestedTree(); // Initializes 'this->line_height' as a side effect.
107 this->vscroll = this->GetScrollbar(WID_GSC_SCROLLBAR);
108 this->FinishInitNested(WN_GAME_OPTIONS_GS);
109 this->OnInvalidateData(0);
111 this->RebuildVisibleSettings();
114 void Close([[maybe_unused]] int data = 0) override
116 CloseWindowByClass(WC_SCRIPT_LIST);
117 this->Window::Close();
121 * Rebuilds the list of visible settings. GS settings with the flag
122 * GSCONFIG_GS_DEVELOPER set will only be visible if the game setting
123 * gui.ai_developer_tools is enabled.
125 void RebuildVisibleSettings()
127 visible_settings.clear();
129 for (const auto &item : *this->gs_config->GetConfigList()) {
130 bool no_hide = (item.flags & SCRIPTCONFIG_DEVELOPER) == 0;
131 if (no_hide || _settings_client.gui.ai_developer_tools) {
132 visible_settings.push_back(&item);
136 this->vscroll->SetCount(this->visible_settings.size());
139 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
141 switch (widget) {
142 case WID_GSC_SETTINGS:
143 this->line_height = std::max(SETTING_BUTTON_HEIGHT, GetCharacterHeight(FS_NORMAL)) + padding.height;
144 resize.width = 1;
145 resize.height = this->line_height;
146 size.height = 5 * this->line_height;
147 break;
149 case WID_GSC_GSLIST:
150 this->line_height = GetCharacterHeight(FS_NORMAL) + padding.height;
151 size.height = 1 * this->line_height;
152 break;
157 * Can the GS config be edited?
158 * @return True if the given GS Config slot can be edited, otherwise false.
160 static bool IsEditable()
162 return _game_mode != GM_NORMAL || Game::GetInstance() != nullptr;
165 void DrawWidget(const Rect &r, WidgetID widget) const override
167 switch (widget) {
168 case WID_GSC_GSLIST: {
169 StringID text = STR_AI_CONFIG_NONE;
171 if (GameConfig::GetConfig()->GetInfo() != nullptr) {
172 SetDParamStr(0, GameConfig::GetConfig()->GetInfo()->GetName());
173 text = STR_JUST_RAW_STRING;
176 /* There is only one slot, unlike with the GS GUI, so it should never be white */
177 DrawString(r.Shrink(WidgetDimensions::scaled.matrix), text, (IsEditable() ? TC_ORANGE : TC_SILVER));
178 break;
180 case WID_GSC_SETTINGS: {
181 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
182 bool rtl = _current_text_dir == TD_RTL;
183 Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl);
184 Rect tr = ir.Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl);
186 int y = r.top;
187 int button_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
188 int text_y_offset = (this->line_height - GetCharacterHeight(FS_NORMAL)) / 2;
190 const auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->visible_settings);
191 for (auto it = first; it != last; ++it) {
192 const ScriptConfigItem &config_item = **it;
193 int current_value = this->gs_config->GetSetting(config_item.name);
194 bool editable = this->IsEditableItem(config_item);
196 StringID str;
197 TextColour colour;
198 uint idx = 0;
199 if (config_item.description.empty()) {
200 str = STR_JUST_STRING1;
201 colour = TC_ORANGE;
202 } else {
203 str = STR_AI_SETTINGS_SETTING;
204 colour = TC_LIGHT_BLUE;
205 SetDParamStr(idx++, config_item.description);
208 if ((config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0) {
209 DrawBoolButton(br.left, y + button_y_offset, current_value != 0, editable);
210 SetDParam(idx++, current_value == 0 ? STR_CONFIG_SETTING_OFF : STR_CONFIG_SETTING_ON);
211 } else {
212 int i = static_cast<int>(std::distance(std::begin(this->visible_settings), it));
213 if (config_item.complete_labels) {
214 DrawDropDownButton(br.left, y + button_y_offset, COLOUR_YELLOW, this->clicked_row == i && clicked_dropdown, editable);
215 } else {
216 DrawArrowButtons(br.left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value);
219 auto config_iterator = config_item.labels.find(current_value);
220 if (config_iterator != config_item.labels.end()) {
221 SetDParam(idx++, STR_JUST_RAW_STRING);
222 SetDParamStr(idx++, config_iterator->second);
223 } else {
224 SetDParam(idx++, STR_JUST_INT);
225 SetDParam(idx++, current_value);
229 DrawString(tr.left, tr.right, y + text_y_offset, str, colour);
230 y += this->line_height;
232 break;
237 void OnPaint() override
239 if (this->closing_dropdown) {
240 this->closing_dropdown = false;
241 this->clicked_dropdown = false;
243 this->DrawWidgets();
246 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
248 if (widget >= WID_GSC_TEXTFILE && widget < WID_GSC_TEXTFILE + TFT_CONTENT_END) {
249 if (GameConfig::GetConfig() == nullptr) return;
251 ShowScriptTextfileWindow((TextfileType)(widget - WID_GSC_TEXTFILE), (CompanyID)OWNER_DEITY);
252 return;
255 switch (widget) {
256 case WID_GSC_GSLIST: {
257 this->InvalidateData();
258 if (click_count > 1 && _game_mode != GM_NORMAL) ShowScriptListWindow((CompanyID)OWNER_DEITY, _ctrl_pressed);
259 break;
262 case WID_GSC_CHANGE: // choose other Game Script
263 ShowScriptListWindow((CompanyID)OWNER_DEITY, _ctrl_pressed);
264 break;
266 case WID_GSC_CONTENT_DOWNLOAD:
267 if (!_network_available) {
268 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
269 } else {
270 ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_GAME);
272 break;
274 case WID_GSC_SETTINGS: {
275 auto it = this->vscroll->GetScrolledItemFromWidget(this->visible_settings, pt.y, this, widget);
276 if (it == this->visible_settings.end()) break;
278 const ScriptConfigItem &config_item = **it;
279 if (!this->IsEditableItem(config_item)) return;
281 int num = it - this->visible_settings.begin();
282 if (this->clicked_row != num) {
283 this->CloseChildWindows(WC_QUERY_STRING);
284 this->CloseChildWindows(WC_DROPDOWN_MENU);
285 this->clicked_row = num;
286 this->clicked_dropdown = false;
289 bool bool_item = (config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0;
291 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero);
292 int x = pt.x - r.left;
293 if (_current_text_dir == TD_RTL) x = r.Width() - 1 - x;
295 /* One of the arrows is clicked (or green/red rect in case of bool value) */
296 int old_val = this->gs_config->GetSetting(config_item.name);
297 if (!bool_item && IsInsideMM(x, 0, SETTING_BUTTON_WIDTH) && config_item.complete_labels) {
298 if (this->clicked_dropdown) {
299 /* unclick the dropdown */
300 this->CloseChildWindows(WC_DROPDOWN_MENU);
301 this->clicked_dropdown = false;
302 this->closing_dropdown = false;
303 } else {
304 int rel_y = (pt.y - r.top) % this->line_height;
306 Rect wi_rect;
307 wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
308 wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
309 wi_rect.top = pt.y - rel_y + (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
310 wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
312 /* If the mouse is still held but dragged outside of the dropdown list, keep the dropdown open */
313 if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
314 this->clicked_dropdown = true;
315 this->closing_dropdown = false;
317 DropDownList list;
318 for (int i = config_item.min_value; i <= config_item.max_value; i++) {
319 list.push_back(MakeDropDownListStringItem(config_item.labels.find(i)->second, i));
322 ShowDropDownListAt(this, std::move(list), old_val, WID_GSC_SETTING_DROPDOWN, wi_rect, COLOUR_ORANGE);
325 } else if (IsInsideMM(x, 0, SETTING_BUTTON_WIDTH)) {
326 int new_val = old_val;
327 if (bool_item) {
328 new_val = !new_val;
329 } else if (x >= SETTING_BUTTON_WIDTH / 2) {
330 /* Increase button clicked */
331 new_val += config_item.step_size;
332 if (new_val > config_item.max_value) new_val = config_item.max_value;
333 this->clicked_increase = true;
334 } else {
335 /* Decrease button clicked */
336 new_val -= config_item.step_size;
337 if (new_val < config_item.min_value) new_val = config_item.min_value;
338 this->clicked_increase = false;
341 if (new_val != old_val) {
342 this->gs_config->SetSetting(config_item.name, new_val);
343 this->clicked_button = num;
344 this->unclick_timeout.Reset();
346 } else if (!bool_item && !config_item.complete_labels) {
347 /* Display a query box so users can enter a custom value. */
348 SetDParam(0, old_val);
349 ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, INT32_DIGITS_WITH_SIGN_AND_TERMINATION, this, CS_NUMERAL_SIGNED, QSF_NONE);
351 this->SetDirty();
352 break;
355 case WID_GSC_OPEN_URL: {
356 const GameConfig *config = GameConfig::GetConfig();
357 if (config == nullptr || config->GetInfo() == nullptr) return;
358 OpenBrowser(config->GetInfo()->GetURL());
359 break;
362 case WID_GSC_RESET:
363 this->gs_config->ResetEditableSettings(_game_mode == GM_MENU);
364 this->SetDirty();
365 break;
369 void OnQueryTextFinished(std::optional<std::string> str) override
371 if (!str.has_value() || str->empty()) return;
372 int32_t value = atoi(str->c_str());
373 SetValue(value);
376 void OnDropdownSelect(WidgetID widget, int index) override
378 if (widget != WID_GSC_SETTING_DROPDOWN) return;
379 assert(this->clicked_dropdown);
380 SetValue(index);
383 void OnDropdownClose(Point, WidgetID widget, int, bool) override
385 if (widget != WID_GSC_SETTING_DROPDOWN) return;
386 /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
387 * the same dropdown button was clicked again, and then not open the dropdown again.
388 * So, we only remember that it was closed, and process it on the next OnPaint, which is
389 * after OnClick. */
390 assert(this->clicked_dropdown);
391 this->closing_dropdown = true;
392 this->SetDirty();
395 void OnResize() override
397 this->vscroll->SetCapacityFromWidget(this, WID_GSC_SETTINGS);
400 /** When reset, unclick the button after a small timeout. */
401 TimeoutTimer<TimerWindow> unclick_timeout = {std::chrono::milliseconds(150), [this]() {
402 this->clicked_button = -1;
403 this->SetDirty();
407 * Some data on this window has become invalid.
408 * @param data Information about the changed data.
409 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
411 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
413 if (!gui_scope) return;
415 this->SetWidgetDisabledState(WID_GSC_CHANGE, (_game_mode == GM_NORMAL) || !IsEditable());
417 const GameConfig *config = GameConfig::GetConfig();
418 this->SetWidgetDisabledState(WID_GSC_OPEN_URL, config->GetInfo() == nullptr || config->GetInfo()->GetURL().empty());
419 for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
420 this->SetWidgetDisabledState(WID_GSC_TEXTFILE + tft, !config->GetTextfile(tft, (CompanyID)OWNER_DEITY).has_value());
422 this->RebuildVisibleSettings();
423 this->CloseChildWindows(WC_DROPDOWN_MENU);
424 this->CloseChildWindows(WC_QUERY_STRING);
426 private:
427 bool IsEditableItem(const ScriptConfigItem &config_item) const
429 return _game_mode == GM_MENU
430 || _game_mode == GM_EDITOR
431 || (config_item.flags & SCRIPTCONFIG_INGAME) != 0
432 || _settings_client.gui.ai_developer_tools;
435 void SetValue(int value)
437 const ScriptConfigItem &config_item = *this->visible_settings[this->clicked_row];
438 if (_game_mode == GM_NORMAL && (config_item.flags & SCRIPTCONFIG_INGAME) == 0) return;
439 this->gs_config->SetSetting(config_item.name, value);
440 this->SetDirty();
444 /** Open the GS config window. */
445 void ShowGSConfigWindow()
447 CloseWindowByClass(WC_GAME_OPTIONS);
448 new GSConfigWindow();