Fix: Don't allow right-click to close world generation progress window. (#13084)
[openttd-github.git] / src / league_gui.cpp
blobfb62d49375dd36463f16519dfdf744042b46a38a
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 league_gui.cpp GUI for league tables. */
10 #include "stdafx.h"
11 #include "league_gui.h"
12 #include "company_base.h"
13 #include "company_gui.h"
14 #include "gui.h"
15 #include "industry.h"
16 #include "league_base.h"
17 #include "sortlist_type.h"
18 #include "story_base.h"
19 #include "strings_func.h"
20 #include "tile_map.h"
21 #include "town.h"
22 #include "viewport_func.h"
23 #include "window_gui.h"
25 #include "widgets/league_widget.h"
27 #include "table/strings.h"
28 #include "table/sprites.h"
30 #include "safeguards.h"
33 static const StringID _performance_titles[] = {
34 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER,
35 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER,
36 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER,
37 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER,
38 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR,
39 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR,
40 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR,
41 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR,
42 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR,
43 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR,
44 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE,
45 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE,
46 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN,
47 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN,
48 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_PRESIDENT,
49 STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TYCOON,
52 static inline StringID GetPerformanceTitleFromValue(uint value)
54 return _performance_titles[std::min(value, 1000u) >> 6];
57 class PerformanceLeagueWindow : public Window {
58 private:
59 GUIList<const Company *> companies;
60 uint ordinal_width; ///< The width of the ordinal number
61 uint text_width; ///< The width of the actual text
62 int line_height; ///< Height of the text lines
63 Dimension icon; ///< Dimension of the company icon.
65 /**
66 * (Re)Build the company league list
68 void BuildCompanyList()
70 if (!this->companies.NeedRebuild()) return;
72 this->companies.clear();
73 this->companies.reserve(Company::GetNumItems());
75 for (const Company *c : Company::Iterate()) {
76 this->companies.push_back(c);
79 this->companies.RebuildDone();
82 /** Sort the company league by performance history */
83 static bool PerformanceSorter(const Company * const &c1, const Company * const &c2)
85 return c2->old_economy[0].performance_history < c1->old_economy[0].performance_history;
88 public:
89 PerformanceLeagueWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc)
91 this->InitNested(window_number);
92 this->companies.ForceRebuild();
93 this->companies.NeedResort();
96 void OnPaint() override
98 this->BuildCompanyList();
99 this->companies.Sort(&PerformanceSorter);
101 this->DrawWidgets();
104 void DrawWidget(const Rect &r, WidgetID widget) const override
106 if (widget != WID_PLT_BACKGROUND) return;
108 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
109 int icon_y_offset = (this->line_height - this->icon.height) / 2;
110 int text_y_offset = (this->line_height - GetCharacterHeight(FS_NORMAL)) / 2;
112 bool rtl = _current_text_dir == TD_RTL;
113 Rect ordinal = ir.WithWidth(this->ordinal_width, rtl);
114 uint icon_left = ir.Indent(rtl ? this->text_width : this->ordinal_width, rtl).left;
115 Rect text = ir.WithWidth(this->text_width, !rtl);
117 for (uint i = 0; i != this->companies.size(); i++) {
118 const Company *c = this->companies[i];
119 SetDParam(0, i + 1);
120 DrawString(ordinal.left, ordinal.right, ir.top + text_y_offset, STR_COMPANY_LEAGUE_COMPANY_RANK);
122 DrawCompanyIcon(c->index, icon_left, ir.top + icon_y_offset);
124 SetDParam(0, c->index);
125 SetDParam(1, c->index);
126 SetDParam(2, GetPerformanceTitleFromValue(c->old_economy[0].performance_history));
127 DrawString(text.left, text.right, ir.top + text_y_offset, STR_COMPANY_LEAGUE_COMPANY_NAME);
128 ir.top += this->line_height;
132 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
134 if (widget != WID_PLT_BACKGROUND) return;
136 this->ordinal_width = 0;
137 for (uint i = 0; i < MAX_COMPANIES; i++) {
138 SetDParam(0, i + 1);
139 this->ordinal_width = std::max(this->ordinal_width, GetStringBoundingBox(STR_COMPANY_LEAGUE_COMPANY_RANK).width);
141 this->ordinal_width += WidgetDimensions::scaled.hsep_wide; // Keep some extra spacing
143 uint widest_width = 0;
144 StringID widest_title = STR_NULL;
145 for (auto title : _performance_titles) {
146 uint width = GetStringBoundingBox(title).width;
147 if (width > widest_width) {
148 widest_title = title;
149 widest_width = width;
153 this->icon = GetSpriteSize(SPR_COMPANY_ICON);
154 this->line_height = std::max<int>(this->icon.height + WidgetDimensions::scaled.vsep_normal, GetCharacterHeight(FS_NORMAL));
156 for (const Company *c : Company::Iterate()) {
157 SetDParam(0, c->index);
158 SetDParam(1, c->index);
159 SetDParam(2, widest_title);
160 widest_width = std::max(widest_width, GetStringBoundingBox(STR_COMPANY_LEAGUE_COMPANY_NAME).width);
163 this->text_width = widest_width + WidgetDimensions::scaled.hsep_indent * 3; // Keep some extra spacing
165 size.width = WidgetDimensions::scaled.framerect.Horizontal() + this->ordinal_width + this->icon.width + this->text_width + WidgetDimensions::scaled.hsep_wide;
166 size.height = this->line_height * MAX_COMPANIES + WidgetDimensions::scaled.framerect.Vertical();
169 void OnGameTick() override
171 if (this->companies.NeedResort()) {
172 this->SetDirty();
177 * Some data on this window has become invalid.
178 * @param data Information about the changed data.
179 * @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.
181 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
183 if (data == 0) {
184 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
185 this->companies.ForceRebuild();
186 } else {
187 this->companies.ForceResort();
192 static constexpr NWidgetPart _nested_performance_league_widgets[] = {
193 NWidget(NWID_HORIZONTAL),
194 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
195 NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_COMPANY_LEAGUE_TABLE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
196 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
197 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
198 EndContainer(),
199 NWidget(WWT_PANEL, COLOUR_BROWN, WID_PLT_BACKGROUND), SetMinimalSize(400, 0), SetMinimalTextLines(15, WidgetDimensions::unscaled.framerect.Vertical()),
200 EndContainer(),
203 static WindowDesc _performance_league_desc(
204 WDP_AUTO, "performance_league", 0, 0,
205 WC_COMPANY_LEAGUE, WC_NONE,
207 _nested_performance_league_widgets
210 void ShowPerformanceLeagueTable()
212 AllocateWindowDescFront<PerformanceLeagueWindow>(_performance_league_desc, 0);
215 static void HandleLinkClick(Link link)
217 TileIndex xy;
218 switch (link.type) {
219 case LT_NONE: return;
221 case LT_TILE:
222 if (!IsValidTile(link.target)) return;
223 xy = link.target;
224 break;
226 case LT_INDUSTRY:
227 if (!Industry::IsValidID(link.target)) return;
228 xy = Industry::Get(link.target)->location.tile;
229 break;
231 case LT_TOWN:
232 if (!Town::IsValidID(link.target)) return;
233 xy = Town::Get(link.target)->xy;
234 break;
236 case LT_COMPANY:
237 ShowCompany((CompanyID)link.target);
238 return;
240 case LT_STORY_PAGE: {
241 if (!StoryPage::IsValidID(link.target)) return;
242 CompanyID story_company = StoryPage::Get(link.target)->company;
243 ShowStoryBook(story_company, link.target);
244 return;
247 default: NOT_REACHED();
250 if (_ctrl_pressed) {
251 ShowExtraViewportWindow(xy);
252 } else {
253 ScrollMainWindowToTile(xy);
258 class ScriptLeagueWindow : public Window {
259 private:
260 LeagueTableID table;
261 std::vector<std::pair<uint, const LeagueTableElement *>> rows;
262 uint rank_width; ///< The width of the rank ordinal
263 uint text_width; ///< The width of the actual text
264 uint score_width; ///< The width of the score text
265 uint header_height; ///< Height of the table header
266 int line_height; ///< Height of the text lines
267 Dimension icon_size; ///< Dimenion of the company icon.
268 std::string title;
271 * Rebuild the company league list
273 void BuildTable()
275 this->rows.clear();
276 this->title = std::string{};
277 auto lt = LeagueTable::GetIfValid(this->table);
278 if (lt == nullptr) return;
280 /* We store title in the window class so we can safely reference the string later */
281 this->title = lt->title;
283 std::vector<const LeagueTableElement *> elements;
284 for (LeagueTableElement *lte : LeagueTableElement::Iterate()) {
285 if (lte->table == this->table) {
286 elements.push_back(lte);
289 std::sort(elements.begin(), elements.end(), [](auto a, auto b) { return a->rating > b->rating; });
291 /* Calculate rank, companies with the same rating share the ranks */
292 uint rank = 0;
293 for (uint i = 0; i != elements.size(); i++) {
294 auto *lte = elements[i];
295 if (i > 0 && elements[i - 1]->rating != lte->rating) rank = i;
296 this->rows.emplace_back(rank, lte);
300 public:
301 ScriptLeagueWindow(WindowDesc &desc, LeagueTableID table) : Window(desc)
303 this->table = table;
304 this->BuildTable();
305 this->InitNested(table);
308 void SetStringParameters(WidgetID widget) const override
310 if (widget != WID_SLT_CAPTION) return;
311 SetDParamStr(0, this->title);
314 void OnPaint() override
316 this->DrawWidgets();
319 void DrawWidget(const Rect &r, WidgetID widget) const override
321 if (widget != WID_SLT_BACKGROUND) return;
323 auto lt = LeagueTable::GetIfValid(this->table);
324 if (lt == nullptr) return;
326 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
328 if (!lt->header.empty()) {
329 SetDParamStr(0, lt->header);
330 ir.top = DrawStringMultiLine(ir.left, ir.right, ir.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK) + WidgetDimensions::scaled.vsep_wide;
333 int icon_y_offset = (this->line_height - this->icon_size.height) / 2;
334 int text_y_offset = (this->line_height - GetCharacterHeight(FS_NORMAL)) / 2;
336 /* Calculate positions.of the columns */
337 bool rtl = _current_text_dir == TD_RTL;
338 int spacer = WidgetDimensions::scaled.hsep_wide;
339 Rect rank_rect = ir.WithWidth(this->rank_width, rtl);
340 Rect icon_rect = ir.Indent(this->rank_width + (rtl ? 0 : spacer), rtl).WithWidth(this->icon_size.width, rtl);
341 Rect text_rect = ir.Indent(this->rank_width + spacer + this->icon_size.width, rtl).WithWidth(this->text_width, rtl);
342 Rect score_rect = ir.Indent(this->rank_width + 2 * spacer + this->icon_size.width + this->text_width, rtl).WithWidth(this->score_width, rtl);
344 for (auto [rank, lte] : this->rows) {
345 SetDParam(0, rank + 1);
346 DrawString(rank_rect.left, rank_rect.right, ir.top + text_y_offset, STR_COMPANY_LEAGUE_COMPANY_RANK);
347 if (this->icon_size.width > 0 && lte->company != INVALID_COMPANY) DrawCompanyIcon(lte->company, icon_rect.left, ir.top + icon_y_offset);
348 SetDParamStr(0, lte->text);
349 DrawString(text_rect.left, text_rect.right, ir.top + text_y_offset, STR_JUST_RAW_STRING, TC_BLACK);
350 SetDParamStr(0, lte->score);
351 DrawString(score_rect.left, score_rect.right, ir.top + text_y_offset, STR_JUST_RAW_STRING, TC_BLACK, SA_RIGHT);
352 ir.top += this->line_height;
355 if (!lt->footer.empty()) {
356 ir.top += WidgetDimensions::scaled.vsep_wide;
357 SetDParamStr(0, lt->footer);
358 ir.top = DrawStringMultiLine(ir.left, ir.right, ir.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK);
362 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
364 if (widget != WID_SLT_BACKGROUND) return;
366 auto lt = LeagueTable::GetIfValid(this->table);
367 if (lt == nullptr) return;
369 this->icon_size = GetSpriteSize(SPR_COMPANY_ICON);
370 this->line_height = std::max<int>(this->icon_size.height + WidgetDimensions::scaled.fullbevel.Vertical(), GetCharacterHeight(FS_NORMAL));
372 /* Calculate maximum width of every column */
373 this->rank_width = this->text_width = this->score_width = 0;
374 bool show_icon_column = false;
375 for (auto [rank, lte] : this->rows) {
376 SetDParam(0, rank + 1);
377 this->rank_width = std::max(this->rank_width, GetStringBoundingBox(STR_COMPANY_LEAGUE_COMPANY_RANK).width);
378 SetDParamStr(0, lte->text);
379 this->text_width = std::max(this->text_width, GetStringBoundingBox(STR_JUST_RAW_STRING).width);
380 SetDParamStr(0, lte->score);
381 this->score_width = std::max(this->score_width, GetStringBoundingBox(STR_JUST_RAW_STRING).width);
382 if (lte->company != INVALID_COMPANY) show_icon_column = true;
385 if (!show_icon_column) this->icon_size.width = 0;
386 else this->icon_size.width += WidgetDimensions::scaled.hsep_wide;
388 size.width = this->rank_width + this->icon_size.width + this->text_width + this->score_width + WidgetDimensions::scaled.framerect.Horizontal() + WidgetDimensions::scaled.hsep_wide * 2;
389 size.height = this->line_height * std::max<uint>(3u, (unsigned)this->rows.size()) + WidgetDimensions::scaled.framerect.Vertical();
391 if (!lt->header.empty()) {
392 SetDParamStr(0, lt->header);
393 this->header_height = GetStringHeight(STR_JUST_RAW_STRING, size.width - WidgetDimensions::scaled.framerect.Horizontal()) + WidgetDimensions::scaled.vsep_wide;
394 size.height += header_height;
395 } else this->header_height = 0;
397 if (!lt->footer.empty()) {
398 SetDParamStr(0, lt->footer);
399 size.height += GetStringHeight(STR_JUST_RAW_STRING, size.width - WidgetDimensions::scaled.framerect.Horizontal()) + WidgetDimensions::scaled.vsep_wide;
403 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
405 if (widget != WID_SLT_BACKGROUND) return;
407 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_SLT_BACKGROUND);
408 int index = (pt.y - WidgetDimensions::scaled.framerect.top - wid->pos_y - this->header_height) / this->line_height;
409 if (index >= 0 && (uint)index < this->rows.size()) {
410 auto lte = this->rows[index].second;
411 HandleLinkClick(lte->link);
416 * Some data on this window has become invalid.
417 * @param data Information about the changed data.
418 * @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.
420 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
422 this->BuildTable();
423 this->ReInit();
427 static constexpr NWidgetPart _nested_script_league_widgets[] = {
428 NWidget(NWID_HORIZONTAL),
429 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
430 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_SLT_CAPTION), SetDataTip(STR_JUST_RAW_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
431 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
432 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
433 EndContainer(),
434 NWidget(WWT_PANEL, COLOUR_BROWN, WID_SLT_BACKGROUND), SetMinimalSize(400, 0), SetMinimalTextLines(15, WidgetDimensions::unscaled.framerect.Vertical()),
435 EndContainer(),
438 static WindowDesc _script_league_desc(
439 WDP_AUTO, "script_league", 0, 0,
440 WC_COMPANY_LEAGUE, WC_NONE,
442 _nested_script_league_widgets
445 void ShowScriptLeagueTable(LeagueTableID table)
447 if (!LeagueTable::IsValidID(table)) return;
448 AllocateWindowDescFront<ScriptLeagueWindow>(_script_league_desc, table);
451 void ShowFirstLeagueTable()
453 auto it = LeagueTable::Iterate();
454 if (!it.empty()) {
455 ShowScriptLeagueTable((*it.begin())->index);
456 } else {
457 ShowPerformanceLeagueTable();