OpenTTD Source 20260129-master-g2bb01bd0e4
industry_gui.cpp
Go to the documentation of this file.
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
10#include "stdafx.h"
11#include <ranges>
12#include "error.h"
13#include "gui.h"
14#include "settings_gui.h"
15#include "sound_func.h"
16#include "window_func.h"
17#include "textbuf_gui.h"
18#include "command_func.h"
19#include "viewport_func.h"
20#include "industry.h"
21#include "town.h"
22#include "cheat_type.h"
23#include "newgrf_badge.h"
24#include "newgrf_badge_gui.h"
25#include "newgrf_industries.h"
26#include "newgrf_text.h"
27#include "newgrf_debug.h"
28#include "network/network.h"
29#include "strings_func.h"
30#include "company_func.h"
31#include "tilehighlight_func.h"
32#include "string_func.h"
33#include "sortlist_type.h"
34#include "dropdown_func.h"
35#include "company_base.h"
37#include "core/random_func.hpp"
38#include "core/backup_type.hpp"
39#include "genworld.h"
40#include "smallmap_gui.h"
41#include "dropdown_type.h"
42#include "clear_map.h"
43#include "zoom_func.h"
44#include "industry_cmd.h"
45#include "graph_gui.h"
46#include "querystring_gui.h"
47#include "stringfilter_type.h"
48#include "timer/timer.h"
49#include "timer/timer_window.h"
50#include "hotkeys.h"
52
54
55#include "table/strings.h"
56
57#include <bitset>
58
59#include "safeguards.h"
60
61bool _ignore_industry_restrictions;
62std::bitset<NUM_INDUSTRYTYPES> _displayed_industries;
63
70
78
84
85extern void GenerateIndustries();
86static void ShowIndustryCargoesWindow(IndustryType id);
87
97static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, CargoSuffix &suffix)
98{
99 suffix.text.clear();
100 suffix.display = CSD_CARGO_AMOUNT;
101
103 TileIndex t = (cst != CST_FUND) ? ind->location.tile : INVALID_TILE;
104 std::array<int32_t, 16> regs100;
105 uint16_t callback = GetIndustryCallback(CBID_INDUSTRY_CARGO_SUFFIX, 0, (cst << 8) | cargo, const_cast<Industry *>(ind), ind_type, t, regs100);
106 if (callback == CALLBACK_FAILED) return;
107
108 if (indspec->grf_prop.grffile->grf_version < 8) {
109 if (GB(callback, 0, 8) == 0xFF) return;
110 if (callback < 0x400) {
111 suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback, regs100);
113 return;
114 }
116 return;
117
118 } else { // GRF version 8 or higher.
119 switch (callback) {
120 case 0x400:
121 return;
122 case 0x401:
123 suffix.display = CSD_CARGO;
124 return;
125 case 0x40E:
126 suffix.display = CSD_CARGO_TEXT;
127 suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
128 return;
129 case 0x40F:
131 suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
132 return;
133 default:
134 break;
135 }
136 if (callback < 0x400) {
137 suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback, regs100);
139 return;
140 }
141 if (callback >= 0x800 && callback < 0xC00) {
142 suffix.text = GetGRFStringWithTextStack(indspec->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback - 0x800, regs100);
143 suffix.display = CSD_CARGO_TEXT;
144 return;
145 }
147 return;
148 }
149 }
150}
151
152enum CargoSuffixInOut : uint8_t {
153 CARGOSUFFIX_OUT = 0,
154 CARGOSUFFIX_IN = 1,
155};
156
167template <typename TC, typename TS>
168static inline void GetAllCargoSuffixes(CargoSuffixInOut use_input, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, const TC &cargoes, TS &suffixes)
169{
170 static_assert(std::tuple_size_v<std::remove_reference_t<decltype(cargoes)>> <= lengthof(suffixes));
171
173 /* Reworked behaviour with new many-in-many-out scheme */
174 for (uint j = 0; j < lengthof(suffixes); j++) {
175 if (IsValidCargoType(cargoes[j])) {
176 uint8_t local_id = indspec->grf_prop.grffile->cargo_map[cargoes[j]]; // should we check the value for valid?
177 uint cargotype = local_id << 16 | use_input;
178 GetCargoSuffix(cargotype, cst, ind, ind_type, indspec, suffixes[j]);
179 } else {
180 suffixes[j].text.clear();
181 suffixes[j].display = CSD_CARGO;
182 }
183 }
184 } else {
185 /* Compatible behaviour with old 3-in-2-out scheme */
186 for (uint j = 0; j < lengthof(suffixes); j++) {
187 suffixes[j].text.clear();
188 suffixes[j].display = CSD_CARGO;
189 }
190 switch (use_input) {
191 case CARGOSUFFIX_OUT:
192 /* Handle INDUSTRY_ORIGINAL_NUM_OUTPUTS cargoes */
193 if (IsValidCargoType(cargoes[0])) GetCargoSuffix(3, cst, ind, ind_type, indspec, suffixes[0]);
194 if (IsValidCargoType(cargoes[1])) GetCargoSuffix(4, cst, ind, ind_type, indspec, suffixes[1]);
195 break;
196 case CARGOSUFFIX_IN:
197 /* Handle INDUSTRY_ORIGINAL_NUM_INPUTS cargoes */
198 if (IsValidCargoType(cargoes[0])) GetCargoSuffix(0, cst, ind, ind_type, indspec, suffixes[0]);
199 if (IsValidCargoType(cargoes[1])) GetCargoSuffix(1, cst, ind, ind_type, indspec, suffixes[1]);
200 if (IsValidCargoType(cargoes[2])) GetCargoSuffix(2, cst, ind, ind_type, indspec, suffixes[2]);
201 break;
202 default:
203 NOT_REACHED();
204 }
205 }
206}
207
219void GetCargoSuffix(CargoSuffixInOut use_input, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, CargoType cargo, uint8_t slot, CargoSuffix &suffix)
220{
221 suffix.text.clear();
222 suffix.display = CSD_CARGO;
223 if (!IsValidCargoType(cargo)) return;
225 uint8_t local_id = indspec->grf_prop.grffile->cargo_map[cargo]; // should we check the value for valid?
226 uint cargotype = local_id << 16 | use_input;
227 GetCargoSuffix(cargotype, cst, ind, ind_type, indspec, suffix);
228 } else if (use_input == CARGOSUFFIX_IN) {
229 if (slot < INDUSTRY_ORIGINAL_NUM_INPUTS) GetCargoSuffix(slot, cst, ind, ind_type, indspec, suffix);
230 } else if (use_input == CARGOSUFFIX_OUT) {
231 if (slot < INDUSTRY_ORIGINAL_NUM_OUTPUTS) GetCargoSuffix(slot + INDUSTRY_ORIGINAL_NUM_INPUTS, cst, ind, ind_type, indspec, suffix);
232 }
233}
234
235std::array<IndustryType, NUM_INDUSTRYTYPES> _sorted_industry_types;
236
238static bool IndustryTypeNameSorter(const IndustryType &a, const IndustryType &b)
239{
240 int r = StrNaturalCompare(GetString(GetIndustrySpec(a)->name), GetString(GetIndustrySpec(b)->name)); // Sort by name (natural sorting).
241
242 /* If the names are equal, sort by industry type. */
243 return (r != 0) ? r < 0 : (a < b);
244}
245
250{
251 /* Add each industry type to the list. */
252 for (IndustryType i = 0; i < NUM_INDUSTRYTYPES; i++) {
254 }
255
256 /* Sort industry types by name. */
258}
259
260static constexpr std::initializer_list<NWidgetPart> _nested_build_industry_widgets = {
262 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
263 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetStringTip(STR_FUND_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
264 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
265 NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
266 NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
267 EndContainer(),
271 SetStringTip(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP),
273 SetStringTip(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES, STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP),
274 EndContainer(),
275 EndContainer(),
277 NWidget(WWT_MATRIX, COLOUR_DARK_GREEN, WID_DPI_MATRIX_WIDGET), SetMatrixDataTip(1, 0, STR_FUND_INDUSTRY_SELECTION_TOOLTIP), SetFill(1, 0), SetResize(1, 1), SetScrollbar(WID_DPI_SCROLLBAR),
278 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_DPI_SCROLLBAR),
279 EndContainer(),
280 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_DPI_INFOPANEL), SetResize(1, 0),
281 EndContainer(),
283 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_DISPLAY_WIDGET), SetFill(1, 0), SetResize(1, 0),
284 SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
285 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_FUND_WIDGET), SetFill(1, 0), SetResize(1, 0),
286 NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
287 EndContainer(),
288};
289
292 WDP_AUTO, "build_industry", 170, 212,
295 _nested_build_industry_widgets
296);
297
300 IndustryType selected_type = IT_INVALID;
301 std::vector<IndustryType> list{};
302 bool enabled = false;
303 Scrollbar *vscroll = nullptr;
305 GUIBadgeClasses badge_classes{};
306
308 static const int MAX_MINWIDTH_LINEHEIGHTS = 20;
309
310 void UpdateAvailability()
311 {
312 this->enabled = this->selected_type != IT_INVALID && (_game_mode == GM_EDITOR || GetIndustryProbabilityCallback(this->selected_type, IACT_USERCREATION, 1) > 0);
313 }
314
315 void SetupArrays()
316 {
317 this->list.clear();
318
319 /* Fill the arrays with industries.
320 * The tests performed after the enabled allow to load the industries
321 * In the same way they are inserted by grf (if any)
322 */
323 for (IndustryType ind : _sorted_industry_types) {
324 const IndustrySpec *indsp = GetIndustrySpec(ind);
325 if (indsp->enabled) {
326 /* Rule is that editor mode loads all industries.
327 * In game mode, all non raw industries are loaded too
328 * and raw ones are loaded only when setting allows it */
329 if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
330 /* Unselect if the industry is no longer in the list */
331 if (this->selected_type == ind) this->selected_type = IT_INVALID;
332 continue;
333 }
334
335 this->list.push_back(ind);
336 }
337 }
338
339 /* First industry type is selected if the current selection is invalid. */
340 if (this->selected_type == IT_INVALID && !this->list.empty()) this->selected_type = this->list[0];
341
342 this->UpdateAvailability();
343
344 this->vscroll->SetCount(this->list.size());
345 }
346
349 {
350 this->SetWidgetDisabledState(WID_DPI_FUND_WIDGET, this->selected_type != IT_INVALID && !this->enabled);
351 this->SetWidgetDisabledState(WID_DPI_DISPLAY_WIDGET, this->selected_type == IT_INVALID && this->enabled);
352 }
353
366 std::string MakeCargoListString(const std::span<const CargoType> cargolist, const std::span<const CargoSuffix> cargo_suffix, StringID prefixstr) const
367 {
368 assert(cargolist.size() == cargo_suffix.size());
369
370 std::string cargostring;
371 std::string_view list_separator = GetListSeparator();
372
373 for (size_t j = 0; j < cargolist.size(); j++) {
374 if (!IsValidCargoType(cargolist[j])) continue;
375
376 if (!cargostring.empty()) cargostring += list_separator;
377 auto params = MakeParameters(CargoSpec::Get(cargolist[j])->name, cargo_suffix[j].text);
378 AppendStringWithArgsInPlace(cargostring, STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION, params);
379 }
380
381 if (cargostring.empty()) AppendStringInPlace(cargostring, STR_JUST_NOTHING);
382 return GetString(prefixstr, cargostring);
383 }
384
385public:
387 {
388 this->CreateNestedTree();
389 this->vscroll = this->GetScrollbar(WID_DPI_SCROLLBAR);
390 /* Show scenario editor tools in editor. */
391 if (_game_mode != GM_EDITOR) {
392 this->GetWidget<NWidgetStacked>(WID_DPI_SCENARIO_EDITOR_PANE)->SetDisplayedPlane(SZSP_HORIZONTAL);
393 }
394 this->FinishInitNested(0);
395
396 this->SetButtons();
397 }
398
399 void OnInit() override
400 {
401 this->badge_classes = GUIBadgeClasses{GSF_INDUSTRIES};
402
403 /* Width of the legend blob -- slightly larger than the smallmap legend blob. */
404 this->legend.height = GetCharacterHeight(FS_SMALL);
405 this->legend.width = this->legend.height * 9 / 6;
406
407 this->SetupArrays();
408 }
409
410 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
411 {
412 switch (widget) {
415 Dimension d{};
416 for (const auto &indtype : this->list) {
417 d = maxdim(d, GetStringBoundingBox(GetIndustrySpec(indtype)->name));
418 }
419 fill.height = resize.height = std::max<uint>({this->legend.height, d.height, count.height}) + padding.height;
420 d.width += this->badge_classes.GetTotalColumnsWidth() + this->legend.width + WidgetDimensions::scaled.hsep_wide + WidgetDimensions::scaled.hsep_normal + count.width + padding.width;
421 d.height = 5 * resize.height;
422 size = maxdim(size, d);
423 break;
424 }
425
426 case WID_DPI_INFOPANEL: {
427 /* Extra line for cost outside of editor. */
428 int height = 2 + (_game_mode == GM_EDITOR ? 0 : 1);
429 uint extra_lines_req = 0;
430 uint extra_lines_prd = 0;
431 uint extra_lines_newgrf = 0;
433 Dimension d = {0, 0};
434 for (const auto &indtype : this->list) {
435 const IndustrySpec *indsp = GetIndustrySpec(indtype);
436 CargoSuffix cargo_suffix[std::tuple_size_v<decltype(indsp->accepts_cargo)>];
437
438 /* Measure the accepted cargoes, if any. */
439 GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, indtype, indsp, indsp->accepts_cargo, cargo_suffix);
440 std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
441 Dimension strdim = GetStringBoundingBox(cargostring);
442 if (strdim.width > max_minwidth) {
443 extra_lines_req = std::max(extra_lines_req, strdim.width / max_minwidth + 1);
444 strdim.width = max_minwidth;
445 }
446 d = maxdim(d, strdim);
447
448 /* Measure the produced cargoes, if any. */
449 GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, indtype, indsp, indsp->produced_cargo, cargo_suffix);
450 cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
451 strdim = GetStringBoundingBox(cargostring);
452 if (strdim.width > max_minwidth) {
453 extra_lines_prd = std::max(extra_lines_prd, strdim.width / max_minwidth + 1);
454 strdim.width = max_minwidth;
455 }
456 d = maxdim(d, strdim);
457
458 if (indsp->grf_prop.HasGrfFile()) {
459 /* Reserve a few extra lines for text from an industry NewGRF. */
460 extra_lines_newgrf = 4;
461 }
462 }
463
464 /* Set it to something more sane :) */
465 height += extra_lines_prd + extra_lines_req + extra_lines_newgrf;
466 size.height = height * GetCharacterHeight(FS_NORMAL) + padding.height;
467 size.width = d.width + padding.width;
468 break;
469 }
470
471 case WID_DPI_FUND_WIDGET: {
472 Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
473 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY));
474 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY));
475 d.width += padding.width;
476 d.height += padding.height;
477 size = maxdim(size, d);
478 break;
479 }
480 }
481 }
482
483 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
484 {
485 switch (widget) {
487 /* Raw industries might be prospected. Show this fact by changing the string
488 * In Editor, you just build, while ingame, or you fund or you prospect */
489 if (_game_mode == GM_EDITOR) {
490 /* We've chosen many random industries but no industries have been specified */
491 return GetString(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
492 }
493 if (this->selected_type != IT_INVALID) {
494 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
495 return GetString((_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
496 }
497 return GetString(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
498
499 default:
500 return this->Window::GetWidgetString(widget, stringid);
501 }
502 }
503
504 void DrawWidget(const Rect &r, WidgetID widget) const override
505 {
506 switch (widget) {
508 bool rtl = _current_text_dir == TD_RTL;
510 Rect icon = text.WithWidth(this->legend.width, rtl);
511 text = text.Indent(this->legend.width + WidgetDimensions::scaled.hsep_wide, rtl);
512
513 /* Vertical offset for legend icon. */
514 icon.top = r.top + (this->resize.step_height - this->legend.height + 1) / 2;
515 icon.bottom = icon.top + this->legend.height - 1;
516
517 auto badge_column_widths = badge_classes.GetColumnWidths();
518
519 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->list);
520 for (auto it = first; it != last; ++it) {
521 IndustryType type = *it;
522 bool selected = this->selected_type == type;
523 const IndustrySpec *indsp = GetIndustrySpec(type);
524
525 Rect tr = text;
526 if (badge_column_widths.size() >= 1 && badge_column_widths[0] > 0) {
527 DrawBadgeColumn(tr.WithWidth(badge_column_widths[0], rtl), 0, this->badge_classes, indsp->badges, GSF_INDUSTRIES, std::nullopt, PAL_NONE);
528 tr = tr.Indent(badge_column_widths[0], rtl);
529 }
530 if (badge_column_widths.size() >= 2 && badge_column_widths[1] > 0) {
531 DrawBadgeColumn(tr.WithWidth(badge_column_widths[1], !rtl), 0, this->badge_classes, indsp->badges, GSF_INDUSTRIES, std::nullopt, PAL_NONE);
532 tr = tr.Indent(badge_column_widths[1], !rtl);
533 }
534
535 /* Draw the name of the industry in white is selected, otherwise, in orange */
536 DrawString(tr, indsp->name, selected ? TC_WHITE : TC_ORANGE);
537 GfxFillRect(icon, selected ? PC_WHITE : PC_BLACK);
539 DrawString(tr, GetString(STR_JUST_COMMA, Industry::GetIndustryTypeCount(type)), TC_BLACK, SA_RIGHT, false, FS_SMALL);
540
541 text = text.Translate(0, this->resize.step_height);
542 icon = icon.Translate(0, this->resize.step_height);
543 }
544 break;
545 }
546
547 case WID_DPI_INFOPANEL: {
548 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
549
550 if (this->selected_type == IT_INVALID) {
551 DrawStringMultiLine(ir, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
552 break;
553 }
554
555 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
556
557 if (_game_mode != GM_EDITOR) {
558 DrawString(ir, GetString(STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST, indsp->GetConstructionCost()));
559 ir.top += GetCharacterHeight(FS_NORMAL);
560 }
561
562 CargoSuffix cargo_suffix[std::tuple_size_v<decltype(indsp->accepts_cargo)>];
563
564 /* Draw the accepted cargoes, if any. Otherwise, will print "Nothing". */
565 GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, this->selected_type, indsp, indsp->accepts_cargo, cargo_suffix);
566 std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
567 ir.top = DrawStringMultiLine(ir, cargostring);
568
569 /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
570 GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, this->selected_type, indsp, indsp->produced_cargo, cargo_suffix);
571 cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
572 ir.top = DrawStringMultiLine(ir, cargostring);
573
574 ir.top = DrawBadgeNameList(ir, indsp->badges, GSF_INDUSTRIES);
575
576 /* Get the additional purchase info text, if it has not already been queried. */
578 std::array<int32_t, 16> regs100;
579 uint16_t callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, nullptr, this->selected_type, INVALID_TILE, regs100);
580 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
581 std::string str;
582 if (callback_res == 0x40F) {
583 str = GetGRFStringWithTextStack(indsp->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
584 } else if (callback_res > 0x400) {
586 } else {
587 str = GetGRFStringWithTextStack(indsp->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, regs100);
588 }
589 if (!str.empty()) {
590 DrawStringMultiLine(ir, str, TC_YELLOW);
591 }
592 }
593 }
594 break;
595 }
596 }
597 }
598
599 static void AskManyRandomIndustriesCallback(Window *, bool confirmed)
600 {
601 if (!confirmed) return;
602
603 if (Town::GetNumItems() == 0) {
604 ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_GENERATE_INDUSTRIES), GetEncodedString(STR_ERROR_MUST_FOUND_TOWN_FIRST), WL_INFO);
605 } else {
606 Backup<bool> old_generating_world(_generating_world, true);
610 old_generating_world.Restore();
611 }
612 }
613
614 static void AskRemoveAllIndustriesCallback(Window *, bool confirmed)
615 {
616 if (!confirmed) return;
617
618 for (Industry *industry : Industry::Iterate()) delete industry;
619
620 /* Clear farmland. */
621 for (const auto tile : Map::Iterate()) {
622 if (IsTileType(tile, TileType::Clear) && GetClearGround(tile) == CLEAR_FIELDS) {
623 MakeClear(tile, CLEAR_GRASS, 3);
624 }
625 }
626
628 }
629
630 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
631 {
632 switch (widget) {
634 assert(_game_mode == GM_EDITOR);
636 ShowQuery(
637 GetEncodedString(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION),
638 GetEncodedString(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY),
639 nullptr, AskManyRandomIndustriesCallback);
640 break;
641 }
642
644 assert(_game_mode == GM_EDITOR);
646 ShowQuery(
647 GetEncodedString(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION),
648 GetEncodedString(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY),
649 nullptr, AskRemoveAllIndustriesCallback);
650 break;
651 }
652
654 auto it = this->vscroll->GetScrolledItemFromWidget(this->list, pt.y, this, WID_DPI_MATRIX_WIDGET);
655 if (it != this->list.end()) { // Is it within the boundaries of available data?
656 this->selected_type = *it;
657 this->UpdateAvailability();
658
659 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
660
661 this->SetDirty();
662
663 if (_thd.GetCallbackWnd() == this &&
664 ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != nullptr && indsp->IsRawIndustry()) || !this->enabled)) {
665 /* Reset the button state if going to prospecting or "build many industries" */
666 this->RaiseButtons();
668 }
669
670 this->SetButtons();
671 if (this->enabled && click_count > 1) this->OnClick(pt, WID_DPI_FUND_WIDGET, 1);
672 }
673 break;
674 }
675
677 if (this->selected_type != IT_INVALID) ShowIndustryCargoesWindow(this->selected_type);
678 break;
679
680 case WID_DPI_FUND_WIDGET: {
681 if (this->selected_type != IT_INVALID) {
682 if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
683 Command<Commands::BuildIndustry>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, TileIndex{}, this->selected_type, 0, false, InteractiveRandom());
685 } else {
686 HandlePlacePushButton(this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT);
687 }
688 }
689 break;
690 }
691 }
692 }
693
694 void OnResize() override
695 {
696 /* Adjust the number of items in the matrix depending of the resize */
697 this->vscroll->SetCapacityFromWidget(this, WID_DPI_MATRIX_WIDGET);
698 }
699
700 void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override
701 {
702 bool success = true;
703 /* We do not need to protect ourselves against "Random Many Industries" in this mode */
704 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
705 uint32_t seed = InteractiveRandom();
706 uint32_t layout_index = InteractiveRandomRange((uint32_t)indsp->layouts.size());
707
708 if (_game_mode == GM_EDITOR) {
709 /* Show error if no town exists at all */
710 if (Town::GetNumItems() == 0) {
711 ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_HERE, indsp->name),
712 GetEncodedString(STR_ERROR_MUST_FOUND_TOWN_FIRST), WL_INFO, pt.x, pt.y);
713 return;
714 }
715
716 AutoRestoreBackup backup_cur_company(_current_company, OWNER_NONE);
717 AutoRestoreBackup backup_generating_world(_generating_world, true);
718 AutoRestoreBackup backup_ignore_industry_restritions(_ignore_industry_restrictions, true);
719
720 Command<Commands::BuildIndustry>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
721 } else {
722 success = Command<Commands::BuildIndustry>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
723 }
724
725 /* If an industry has been built, just reset the cursor and the system */
727 }
728
729 const IntervalTimer<TimerWindow> update_interval = {std::chrono::seconds(3), [this](auto) {
730 if (_game_mode == GM_EDITOR) return;
731 if (this->selected_type == IT_INVALID) return;
732
733 bool enabled = this->enabled;
734 this->UpdateAvailability();
735 if (enabled != this->enabled) {
736 this->SetButtons();
737 this->SetDirty();
738 }
739 }};
740
741 void OnTimeout() override
742 {
743 this->RaiseButtons();
744 }
745
746 void OnPlaceObjectAbort() override
747 {
748 this->RaiseButtons();
749 }
750
756 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
757 {
758 if (!gui_scope) return;
759 this->SetupArrays();
760 this->SetButtons();
761 this->SetDirty();
762 }
763};
764
765void ShowBuildIndustryWindow()
766{
767 if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
770}
771
772static void UpdateIndustryProduction(Industry *i);
773
774static inline bool IsProductionAlterable(const Industry *i)
775{
776 const IndustrySpec *is = GetIndustrySpec(i->type);
777 bool has_prod = std::any_of(std::begin(is->production_rate), std::end(is->production_rate), [](auto rate) { return rate != 0; });
778 return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
779 (has_prod || is->IsRawIndustry()) &&
780 !_networking);
781}
782
784{
791
799
804 uint8_t clicked_button = 0;
806 int info_height = 0;
808
809public:
811 {
813 this->info_height = WidgetDimensions::scaled.framerect.Vertical() + 2 * GetCharacterHeight(FS_NORMAL); // Info panel has at least two lines text.
814
815 this->InitNested(window_number);
816 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
817 nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ScaleZoomGUI(ZoomLevel::Industry));
818
821
822 this->InvalidateData();
823 }
824
825 ~IndustryViewWindow() override
826 {
828 }
829
830 void OnInit() override
831 {
832 /* This only used when the cheat to alter industry production is enabled */
833 this->cheat_line_height = std::max(SETTING_BUTTON_HEIGHT + WidgetDimensions::scaled.vsep_normal, GetCharacterHeight(FS_NORMAL));
835 }
836
837 void OnPaint() override
838 {
839 this->DrawWidgets();
840
841 if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
842
843 const Rect r = this->GetWidget<NWidgetBase>(WID_IV_INFO)->GetCurrentRect();
844 int expected = this->DrawInfo(r);
845 if (expected != r.bottom) {
846 this->info_height = expected - r.top + 1;
847 this->ReInit();
848 return;
849 }
850 }
851
852 void DrawCargoIcon(const Rect &r, CargoType cargo_type) const
853 {
854 bool rtl = _current_text_dir == TD_RTL;
855 SpriteID icon = CargoSpec::Get(cargo_type)->GetCargoIcon();
856 Dimension d = GetSpriteSize(icon);
858 DrawSprite(icon, PAL_NONE, CentreBounds(ir.left, ir.right, d.width), CentreBounds(ir.top, ir.bottom, this->cargo_icon_size.height));
859 }
860
861 std::string GetAcceptedCargoString(const Industry::AcceptedCargo &ac, const CargoSuffix &suffix) const
862 {
863 auto params = MakeParameters(CargoSpec::Get(ac.cargo)->name, ac.cargo, ac.waiting, suffix.text);
864 switch (suffix.display) {
865 case CSD_CARGO_AMOUNT_TEXT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_SUFFIX, params);
866 case CSD_CARGO_TEXT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_SUFFIX, params);
867 case CSD_CARGO_AMOUNT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_NOSUFFIX, params);
868 case CSD_CARGO: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_NOSUFFIX, params);
869 default: NOT_REACHED();
870 }
871 }
872
878 int DrawInfo(const Rect &r)
879 {
880 bool rtl = _current_text_dir == TD_RTL;
882 const IndustrySpec *ind = GetIndustrySpec(i->type);
883 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
884 bool first = true;
885 bool has_accept = false;
886
887 /* Use all the available space past the rect, so that we can enlarge the window if needed. */
888 ir.bottom = INT_MAX;
889
890 if (i->prod_level == PRODLEVEL_CLOSURE) {
891 DrawString(ir, STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE);
893 }
894
895 const int label_indent = WidgetDimensions::scaled.hsep_normal + this->cargo_icon_size.width;
897
898 for (const auto &a : i->accepted) {
899 if (!IsValidCargoType(a.cargo)) continue;
900 has_accept = true;
901 if (first) {
902 DrawString(ir, STR_INDUSTRY_VIEW_REQUIRES);
903 ir.top += GetCharacterHeight(FS_NORMAL);
904 first = false;
905 }
906
907 DrawCargoIcon(ir, a.cargo);
908
909 CargoSuffix suffix;
910 GetCargoSuffix(CARGOSUFFIX_IN, CST_VIEW, i, i->type, ind, a.cargo, &a - i->accepted.data(), suffix);
911 /* if the industry is not stockpiling then don't show amount in the acceptance display. */
912 if (!stockpiling && suffix.display == CSD_CARGO_AMOUNT_TEXT) suffix.display = CSD_CARGO_TEXT;
913 if (!stockpiling && suffix.display == CSD_CARGO_AMOUNT) suffix.display = CSD_CARGO;
914
915 DrawString(ir.Indent(label_indent, rtl), this->GetAcceptedCargoString(a, suffix));
916 ir.top += GetCharacterHeight(FS_NORMAL);
917 }
918
919 int line_height = this->editable == EA_RATE ? this->cheat_line_height : GetCharacterHeight(FS_NORMAL);
920 int text_y_offset = (line_height - GetCharacterHeight(FS_NORMAL)) / 2;
921 int button_y_offset = (line_height - SETTING_BUTTON_HEIGHT) / 2;
922 first = true;
923 for (const auto &p : i->produced) {
924 if (!IsValidCargoType(p.cargo)) continue;
925 if (first) {
926 if (has_accept) ir.top += WidgetDimensions::scaled.vsep_wide;
927 DrawString(ir, TimerGameEconomy::UsingWallclockUnits() ? STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE : STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
928 ir.top += GetCharacterHeight(FS_NORMAL);
929 if (this->editable == EA_RATE) this->production_offset_y = ir.top;
930 first = false;
931 }
932
933 DrawCargoIcon(ir, p.cargo);
934
935 CargoSuffix suffix;
936 GetCargoSuffix(CARGOSUFFIX_OUT, CST_VIEW, i, i->type, ind, p.cargo, &p - i->produced.data(), suffix);
937
938 DrawString(ir.Indent(label_indent + (this->editable == EA_RATE ? SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_normal : 0), rtl).Translate(0, text_y_offset),
939 GetString(STR_INDUSTRY_VIEW_TRANSPORTED, p.cargo, p.history[LAST_MONTH].production, suffix.text, ToPercent8(p.history[LAST_MONTH].PctTransported())));
940 /* Let's put out those buttons.. */
941 if (this->editable == EA_RATE) {
942 DrawArrowButtons(ir.Indent(label_indent, rtl).WithWidth(SETTING_BUTTON_WIDTH, rtl).left, ir.top + button_y_offset, COLOUR_YELLOW, (this->clicked_line == IL_RATE1 + (&p - i->produced.data())) ? this->clicked_button : 0,
943 p.rate > 0, p.rate < 255);
944 }
945 ir.top += line_height;
946 }
947
948 /* Display production multiplier if editable */
949 if (this->editable == EA_MULTIPLIER) {
950 line_height = this->cheat_line_height;
951 text_y_offset = (line_height - GetCharacterHeight(FS_NORMAL)) / 2;
952 button_y_offset = (line_height - SETTING_BUTTON_HEIGHT) / 2;
954 this->production_offset_y = ir.top;
955 DrawString(ir.Indent(label_indent + SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_normal, rtl).Translate(0, text_y_offset),
956 GetString(STR_INDUSTRY_VIEW_PRODUCTION_LEVEL, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT)));
957 DrawArrowButtons(ir.Indent(label_indent, rtl).WithWidth(SETTING_BUTTON_WIDTH, rtl).left, ir.top + button_y_offset, COLOUR_YELLOW, (this->clicked_line == IL_MULTIPLIER) ? this->clicked_button : 0,
959 ir.top += line_height;
960 }
961
962 /* Get the extra message for the GUI */
964 std::array<int32_t, 16> regs100;
965 uint16_t callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->location.tile, regs100);
966 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
967 std::string str;
968 if (callback_res == 0x40F) {
969 str = GetGRFStringWithTextStack(ind->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
970 } else if (callback_res > 0x400) {
972 } else {
973 str = GetGRFStringWithTextStack(ind->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, regs100);
974 }
975 if (!str.empty()) {
977 ir.top = DrawStringMultiLine(ir, str, TC_YELLOW);
978 }
979 }
980 }
981
982 if (!i->text.empty()) {
984 ir.top = DrawStringMultiLine(ir, i->text.GetDecodedString(), TC_BLACK);
985 }
986
987 /* Return required bottom position, the last pixel row plus some padding. */
988 return ir.top - 1 + WidgetDimensions::scaled.framerect.bottom;
989 }
990
991 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
992 {
993 if (widget == WID_IV_CAPTION) return GetString(STR_INDUSTRY_VIEW_CAPTION, this->window_number);
994
995 return this->Window::GetWidgetString(widget, stringid);
996 }
997
998 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
999 {
1000 if (widget == WID_IV_INFO) size.height = this->info_height;
1001 }
1002
1003 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1004 {
1005 switch (widget) {
1006 case WID_IV_INFO: {
1008 InfoLine line = IL_NONE;
1009
1010 switch (this->editable) {
1011 case EA_NONE: break;
1012
1013 case EA_MULTIPLIER:
1014 if (IsInsideBS(pt.y, this->production_offset_y, this->cheat_line_height)) line = IL_MULTIPLIER;
1015 break;
1016
1017 case EA_RATE:
1018 if (pt.y >= this->production_offset_y) {
1019 int row = (pt.y - this->production_offset_y) / this->cheat_line_height;
1020 for (auto itp = std::begin(i->produced); itp != std::end(i->produced); ++itp) {
1021 if (!IsValidCargoType(itp->cargo)) continue;
1022 row--;
1023 if (row < 0) {
1024 line = (InfoLine)(IL_RATE1 + (itp - std::begin(i->produced)));
1025 break;
1026 }
1027 }
1028 }
1029 break;
1030 }
1031 if (line == IL_NONE) return;
1032
1033 bool rtl = _current_text_dir == TD_RTL;
1034 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect).Indent(this->cargo_icon_size.width + WidgetDimensions::scaled.hsep_normal, rtl);
1035
1036 if (r.WithWidth(SETTING_BUTTON_WIDTH, rtl).Contains(pt)) {
1037 /* Clicked buttons, decrease or increase production */
1038 bool decrease = r.WithWidth(SETTING_BUTTON_WIDTH / 2, rtl).Contains(pt);
1039 switch (this->editable) {
1040 case EA_MULTIPLIER:
1041 if (decrease) {
1042 if (i->prod_level <= PRODLEVEL_MINIMUM) return;
1043 i->prod_level = static_cast<uint8_t>(std::max<uint>(i->prod_level / 2, PRODLEVEL_MINIMUM));
1044 } else {
1045 if (i->prod_level >= PRODLEVEL_MAXIMUM) return;
1046 i->prod_level = static_cast<uint8_t>(std::min<uint>(i->prod_level * 2, PRODLEVEL_MAXIMUM));
1047 }
1048 break;
1049
1050 case EA_RATE:
1051 if (decrease) {
1052 if (i->produced[line - IL_RATE1].rate <= 0) return;
1053 i->produced[line - IL_RATE1].rate = std::max(i->produced[line - IL_RATE1].rate / 2, 0);
1054 } else {
1055 if (i->produced[line - IL_RATE1].rate >= 255) return;
1056 /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
1057 int new_prod = i->produced[line - IL_RATE1].rate == 0 ? 1 : i->produced[line - IL_RATE1].rate * 2;
1058 i->produced[line - IL_RATE1].rate = ClampTo<uint8_t>(new_prod);
1059 }
1060 break;
1061
1062 default: NOT_REACHED();
1063 }
1064
1065 UpdateIndustryProduction(i);
1066 this->SetDirty();
1067 this->SetTimeout();
1068 this->clicked_line = line;
1069 this->clicked_button = (decrease ^ rtl) ? 1 : 2;
1071 /* clicked the text */
1072 this->editbox_line = line;
1073 switch (this->editable) {
1074 case EA_MULTIPLIER:
1075 ShowQueryString(GetString(STR_JUST_INT, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT)), STR_CONFIG_GAME_PRODUCTION_LEVEL, 10, this, CS_ALPHANUMERAL, {});
1076 break;
1077
1078 case EA_RATE:
1079 ShowQueryString(GetString(STR_JUST_INT, i->produced[line - IL_RATE1].rate * 8), STR_CONFIG_GAME_PRODUCTION, 10, this, CS_ALPHANUMERAL, {});
1080 break;
1081
1082 default: NOT_REACHED();
1083 }
1084 }
1085 break;
1086 }
1087
1088 case WID_IV_GOTO: {
1090 if (_ctrl_pressed) {
1092 } else {
1094 }
1095 break;
1096 }
1097
1098 case WID_IV_DISPLAY: {
1101 break;
1102 }
1103
1104 case WID_IV_GRAPH:
1105 ShowIndustryProductionGraph(this->window_number);
1106 break;
1107 }
1108 }
1109
1110 void OnTimeout() override
1111 {
1112 this->clicked_line = IL_NONE;
1113 this->clicked_button = 0;
1114 this->SetDirty();
1115 }
1116
1117 void OnResize() override
1118 {
1119 if (this->viewport != nullptr) {
1120 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
1121 nvp->UpdateViewportCoordinates(this);
1122
1123 ScrollWindowToTile(Industry::Get(this->window_number)->location.GetCenterTile(), this, true); // Re-center viewport.
1124 }
1125 }
1126
1127 void OnMouseWheel(int wheel, WidgetID widget) override
1128 {
1129 if (widget != WID_IV_VIEWPORT) return;
1131 DoZoomInOutWindow(wheel < 0 ? ZOOM_IN : ZOOM_OUT, this);
1132 }
1133 }
1134
1135 void OnQueryTextFinished(std::optional<std::string> str) override
1136 {
1137 if (!str.has_value() || str->empty()) return;
1138
1140 auto value = ParseInteger(*str, 10, true);
1141 if (!value.has_value()) return;
1142 switch (this->editbox_line) {
1143 case IL_NONE: NOT_REACHED();
1144
1145 case IL_MULTIPLIER:
1147 break;
1148
1149 default:
1150 i->produced[this->editbox_line - IL_RATE1].rate = ClampU(RoundDivSU(*value, 8), 0, 255);
1151 break;
1152 }
1153 UpdateIndustryProduction(i);
1154 this->SetDirty();
1155 }
1156
1162 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1163 {
1164 if (!gui_scope) return;
1165 const Industry *i = Industry::Get(this->window_number);
1166 if (IsProductionAlterable(i)) {
1167 const IndustrySpec *ind = GetIndustrySpec(i->type);
1169 } else {
1170 this->editable = EA_NONE;
1171 }
1172 }
1173
1174 bool IsNewGRFInspectable() const override
1175 {
1176 return ::IsNewGRFInspectable(GSF_INDUSTRIES, this->window_number);
1177 }
1178
1179 void ShowNewGRFInspectWindow() const override
1180 {
1181 ::ShowNewGRFInspectWindow(GSF_INDUSTRIES, this->window_number);
1182 }
1183};
1184
1185static void UpdateIndustryProduction(Industry *i)
1186{
1187 const IndustrySpec *indspec = GetIndustrySpec(i->type);
1189
1190 for (auto &p : i->produced) {
1191 if (IsValidCargoType(p.cargo)) {
1192 p.history[LAST_MONTH].production = ScaleByCargoScale(8 * p.rate, false);
1193 }
1194 }
1195}
1196
1198static constexpr std::initializer_list<NWidgetPart> _nested_industry_view_widgets = {
1200 NWidget(WWT_CLOSEBOX, COLOUR_CREAM),
1201 NWidget(WWT_CAPTION, COLOUR_CREAM, WID_IV_CAPTION),
1202 NWidget(WWT_PUSHIMGBTN, COLOUR_CREAM, WID_IV_GOTO), SetAspect(WidgetDimensions::ASPECT_LOCATION), SetSpriteTip(SPR_GOTO_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
1203 NWidget(WWT_DEBUGBOX, COLOUR_CREAM),
1204 NWidget(WWT_SHADEBOX, COLOUR_CREAM),
1205 NWidget(WWT_DEFSIZEBOX, COLOUR_CREAM),
1206 NWidget(WWT_STICKYBOX, COLOUR_CREAM),
1207 EndContainer(),
1208 NWidget(WWT_PANEL, COLOUR_CREAM),
1209 NWidget(WWT_INSET, COLOUR_CREAM), SetPadding(2, 2, 2, 2),
1210 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_IV_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetResize(1, 1),
1211 EndContainer(),
1212 EndContainer(),
1213 NWidget(WWT_PANEL, COLOUR_CREAM, WID_IV_INFO), SetMinimalSize(260, 0), SetMinimalTextLines(2, WidgetDimensions::unscaled.framerect.Vertical()), SetResize(1, 0),
1214 EndContainer(),
1216 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
1217 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GRAPH), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_VIEW_CARGO_GRAPH, STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP),
1218 NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
1219 EndContainer(),
1220};
1221
1224 WDP_AUTO, "view_industry", 260, 120,
1226 {},
1228);
1229
1230void ShowIndustryViewWindow(IndustryID industry)
1231{
1232 AllocateWindowDescFront<IndustryViewWindow>(_industry_view_desc, industry);
1233}
1234
1236static constexpr std::initializer_list<NWidgetPart> _nested_industry_directory_widgets = {
1238 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1239 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_ID_CAPTION),
1240 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1241 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1242 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1243 EndContainer(),
1247 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_ID_DROPDOWN_ORDER), SetStringTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1248 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_DROPDOWN_CRITERIA), SetToolTip(STR_TOOLTIP_SORT_CRITERIA),
1249 NWidget(WWT_EDITBOX, COLOUR_BROWN, WID_ID_FILTER), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
1250 EndContainer(),
1252 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_ACC_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA),
1253 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_PROD_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA),
1254 NWidget(WWT_PANEL, COLOUR_BROWN), SetResize(1, 0), EndContainer(),
1255 EndContainer(),
1256 NWidget(WWT_PANEL, COLOUR_BROWN, WID_ID_INDUSTRY_LIST), SetToolTip(STR_INDUSTRY_DIRECTORY_LIST_TOOLTIP), SetResize(1, 1), SetScrollbar(WID_ID_VSCROLLBAR),
1257 EndContainer(),
1258 EndContainer(),
1260 EndContainer(),
1263 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1264 EndContainer(),
1265};
1266
1268
1276static bool CargoFilter(const Industry * const *industry, const std::pair<CargoType, CargoType> &cargoes)
1277{
1278 auto accepted_cargo = cargoes.first;
1279 auto produced_cargo = cargoes.second;
1280
1281 bool accepted_cargo_matches;
1282
1283 switch (accepted_cargo) {
1285 accepted_cargo_matches = true;
1286 break;
1287
1289 accepted_cargo_matches = !(*industry)->IsCargoAccepted();
1290 break;
1291
1292 default:
1293 accepted_cargo_matches = (*industry)->IsCargoAccepted(accepted_cargo);
1294 break;
1295 }
1296
1297 bool produced_cargo_matches;
1298
1299 switch (produced_cargo) {
1301 produced_cargo_matches = true;
1302 break;
1303
1305 produced_cargo_matches = !(*industry)->IsCargoProduced();
1306 break;
1307
1308 default:
1309 produced_cargo_matches = (*industry)->IsCargoProduced(produced_cargo);
1310 break;
1311 }
1312
1313 return accepted_cargo_matches && produced_cargo_matches;
1314}
1315
1316static GUIIndustryList::FilterFunction * const _industry_filter_funcs[] = { &CargoFilter };
1317
1322protected:
1323 /* Runtime saved values */
1324 static Listing last_sorting;
1325
1326 /* Constants for sorting industries */
1327 static inline const StringID sorter_names[] = {
1328 STR_SORT_BY_NAME,
1329 STR_SORT_BY_TYPE,
1330 STR_SORT_BY_PRODUCTION,
1331 STR_SORT_BY_TRANSPORTED,
1332 };
1333 static const std::initializer_list<GUIIndustryList::SortFunction * const> sorter_funcs;
1334
1335 GUIIndustryList industries{IndustryDirectoryWindow::produced_cargo_filter};
1336 Scrollbar *vscroll{};
1337 Scrollbar *hscroll{};
1338
1341 static CargoType produced_cargo_filter;
1342
1343 const int MAX_FILTER_LENGTH = 16;
1346
1348 enum class SorterType : uint8_t {
1349 ByName,
1350 ByType,
1351 ByProduction,
1353 };
1354
1360 {
1361 if (this->produced_cargo_filter_criteria != cargo_type) {
1362 this->produced_cargo_filter_criteria = cargo_type;
1363 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1365
1366 this->industries.SetFilterState(is_filtering_necessary);
1367 this->industries.SetFilterType(0);
1368 this->industries.ForceRebuild();
1369 }
1370 }
1371
1377 {
1378 if (this->accepted_cargo_filter_criteria != cargo_type) {
1379 this->accepted_cargo_filter_criteria = cargo_type;
1380 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1382
1383 this->industries.SetFilterState(is_filtering_necessary);
1384 this->industries.SetFilterType(0);
1385 this->industries.ForceRebuild();
1386 }
1387 }
1388
1389 StringID GetCargoFilterLabel(CargoType cargo_type) const
1390 {
1391 switch (cargo_type) {
1392 case CargoFilterCriteria::CF_ANY: return STR_INDUSTRY_DIRECTORY_FILTER_ALL_TYPES;
1393 case CargoFilterCriteria::CF_NONE: return STR_INDUSTRY_DIRECTORY_FILTER_NONE;
1394 default: return CargoSpec::Get(cargo_type)->name;
1395 }
1396 }
1397
1402 {
1405
1406 this->industries.SetFilterFuncs(_industry_filter_funcs);
1407
1409
1410 this->industries.SetFilterState(is_filtering_necessary);
1411 }
1412
1418 {
1419 uint width = this->hscroll->GetCount();
1420 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1421 for (auto it = first; it != last; ++it) {
1422 width = std::max(width, GetStringBoundingBox(this->GetIndustryString(*it)).width);
1423 }
1424 return width;
1425 }
1426
1429 {
1430 if (this->industries.NeedRebuild()) {
1431 this->industries.clear();
1432 this->industries.reserve(Industry::GetNumItems());
1433
1434 for (const Industry *i : Industry::Iterate()) {
1435 if (this->string_filter.IsEmpty()) {
1436 this->industries.push_back(i);
1437 continue;
1438 }
1439 this->string_filter.ResetState();
1440 this->string_filter.AddLine(i->GetCachedName());
1441 if (this->string_filter.GetState()) this->industries.push_back(i);
1442 }
1443
1444 this->industries.RebuildDone();
1445
1446 auto filter = std::make_pair(this->accepted_cargo_filter_criteria, this->produced_cargo_filter_criteria);
1447
1448 this->industries.Filter(filter);
1449
1450 this->vscroll->SetCount(this->industries.size()); // Update scrollbar as well.
1451 }
1452
1453 IndustryDirectoryWindow::produced_cargo_filter = this->produced_cargo_filter_criteria;
1454 this->industries.Sort();
1455
1456 this->SetDirty();
1457 }
1458
1466 {
1467 if (!IsValidCargoType(p.cargo)) return -1;
1468 return ToPercent8(p.history[LAST_MONTH].PctTransported());
1469 }
1470
1479 {
1480 CargoType filter = IndustryDirectoryWindow::produced_cargo_filter;
1481 if (filter == CargoFilterCriteria::CF_NONE) return 0;
1482
1483 int percentage = 0, produced_cargo_count = 0;
1484 for (const auto &p : i->produced) {
1485 if (filter == CargoFilterCriteria::CF_ANY) {
1486 int transported = GetCargoTransportedPercentsIfValid(p);
1487 if (transported != -1) {
1488 produced_cargo_count++;
1489 percentage += transported;
1490 }
1491 if (produced_cargo_count == 0 && &p == &i->produced.back() && percentage == 0) {
1492 return transported;
1493 }
1494 } else if (filter == p.cargo) {
1496 }
1497 }
1498
1499 if (produced_cargo_count == 0) return percentage;
1500 return percentage / produced_cargo_count;
1501 }
1502
1504 static bool IndustryNameSorter(const Industry * const &a, const Industry * const &b, const CargoType &)
1505 {
1506 int r = StrNaturalCompare(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting).
1507 if (r == 0) return a->index < b->index;
1508 return r < 0;
1509 }
1510
1512 static bool IndustryTypeSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1513 {
1514 int it_a = 0;
1515 while (it_a != NUM_INDUSTRYTYPES && a->type != _sorted_industry_types[it_a]) it_a++;
1516 int it_b = 0;
1517 while (it_b != NUM_INDUSTRYTYPES && b->type != _sorted_industry_types[it_b]) it_b++;
1518 int r = it_a - it_b;
1519 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1520 }
1521
1523 static bool IndustryProductionSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1524 {
1525 if (filter == CargoFilterCriteria::CF_NONE) return IndustryTypeSorter(a, b, filter);
1526
1527 uint prod_a = 0, prod_b = 0;
1528 if (filter == CargoFilterCriteria::CF_ANY) {
1529 for (const auto &pa : a->produced) {
1530 if (IsValidCargoType(pa.cargo)) prod_a += pa.history[LAST_MONTH].production;
1531 }
1532 for (const auto &pb : b->produced) {
1533 if (IsValidCargoType(pb.cargo)) prod_b += pb.history[LAST_MONTH].production;
1534 }
1535 } else {
1536 if (auto ita = a->GetCargoProduced(filter); ita != std::end(a->produced)) prod_a = ita->history[LAST_MONTH].production;
1537 if (auto itb = b->GetCargoProduced(filter); itb != std::end(b->produced)) prod_b = itb->history[LAST_MONTH].production;
1538 }
1539 int r = prod_a - prod_b;
1540
1541 return (r == 0) ? IndustryTypeSorter(a, b, filter) : r < 0;
1542 }
1543
1545 static bool IndustryTransportedCargoSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1546 {
1548 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1549 }
1550
1551 StringID GetStringForNumCargo(size_t count) const
1552 {
1553 switch (count) {
1554 case 0: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1555 case 1: return STR_INDUSTRY_DIRECTORY_ITEM_PROD1;
1556 case 2: return STR_INDUSTRY_DIRECTORY_ITEM_PROD2;
1557 case 3: return STR_INDUSTRY_DIRECTORY_ITEM_PROD3;
1558 default: return STR_INDUSTRY_DIRECTORY_ITEM_PRODMORE;
1559 }
1560 }
1561
1567 std::string GetIndustryString(const Industry *i) const
1568 {
1569 const IndustrySpec *indsp = GetIndustrySpec(i->type);
1570
1571 /* Get industry productions (CargoType, production, suffix, transported) */
1572 struct CargoInfo {
1573 CargoType cargo_type;
1574 uint16_t production;
1575 uint transported;
1576 std::string suffix;
1577
1578 CargoInfo(CargoType cargo_type, uint16_t production, uint transported, std::string &&suffix) : cargo_type(cargo_type), production(production), transported(transported), suffix(std::move(suffix)) {}
1579 };
1580 std::vector<CargoInfo> cargos;
1581
1582 for (auto itp = std::begin(i->produced); itp != std::end(i->produced); ++itp) {
1583 if (!IsValidCargoType(itp->cargo)) continue;
1584 CargoSuffix cargo_suffix;
1585 GetCargoSuffix(CARGOSUFFIX_OUT, CST_DIR, i, i->type, indsp, itp->cargo, itp - std::begin(i->produced), cargo_suffix);
1586 cargos.emplace_back(itp->cargo, itp->history[LAST_MONTH].production, ToPercent8(itp->history[LAST_MONTH].PctTransported()), std::move(cargo_suffix.text));
1587 }
1588
1589 switch (static_cast<IndustryDirectoryWindow::SorterType>(this->industries.SortType())) {
1593 /* Sort by descending production, then descending transported */
1594 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1595 if (a.production != b.production) return a.production > b.production;
1596 return a.transported > b.transported;
1597 });
1598 break;
1599
1601 /* Sort by descending transported, then descending production */
1602 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1603 if (a.transported != b.transported) return a.transported > b.transported;
1604 return a.production > b.production;
1605 });
1606 break;
1607 }
1608
1609 /* If the produced cargo filter is active then move the filtered cargo to the beginning of the list,
1610 * because this is the one the player interested in, and that way it is not hidden in the 'n' more cargos */
1611 const CargoType cargo_type = this->produced_cargo_filter_criteria;
1612 if (cargo_type != CargoFilterCriteria::CF_ANY && cargo_type != CargoFilterCriteria::CF_NONE) {
1613 auto filtered_ci = std::ranges::find(cargos, cargo_type, &CargoInfo::cargo_type);
1614 if (filtered_ci != cargos.end()) {
1615 std::rotate(cargos.begin(), filtered_ci, filtered_ci + 1);
1616 }
1617 }
1618
1619 static constexpr size_t MAX_DISPLAYED_CARGOES = 3;
1620 std::array<StringParameter, 2 + 5 * MAX_DISPLAYED_CARGOES> params{};
1621 auto it = params.begin();
1622
1623 /* Industry name */
1624 *it++ = i->index;
1625
1626 /* Display first MAX_DISPLAYED_CARGOES cargoes */
1627 for (CargoInfo &ci : cargos | std::views::take(MAX_DISPLAYED_CARGOES)) {
1628 *it++ = STR_INDUSTRY_DIRECTORY_ITEM_INFO;
1629 *it++ = ci.cargo_type;
1630 *it++ = ci.production;
1631 *it++ = std::move(ci.suffix);
1632 *it++ = ci.transported;
1633 }
1634
1635 /* Undisplayed cargos if any */
1636 if (std::size(cargos) > MAX_DISPLAYED_CARGOES) *it++ = std::size(cargos) - MAX_DISPLAYED_CARGOES;
1637
1638 return GetStringWithArgs(GetStringForNumCargo(std::size(cargos)), {params.begin(), it});
1639 }
1640
1641public:
1643 {
1644 this->CreateNestedTree();
1645 this->vscroll = this->GetScrollbar(WID_ID_VSCROLLBAR);
1646 this->hscroll = this->GetScrollbar(WID_ID_HSCROLLBAR);
1647
1648 this->industries.SetListing(this->last_sorting);
1649 this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1650 this->industries.ForceRebuild();
1651
1652 this->FinishInitNested(0);
1653
1655
1657 this->industry_editbox.cancel_button = QueryString::ACTION_CLEAR;
1658 }
1659
1660 ~IndustryDirectoryWindow() override
1661 {
1662 this->last_sorting = this->industries.GetListing();
1663 }
1664
1665 void OnInit() override
1666 {
1667 this->SetCargoFilterArray();
1668 this->hscroll->SetCount(0);
1669 }
1670
1671 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1672 {
1673 switch (widget) {
1674 case WID_ID_CAPTION:
1675 return GetString(STR_INDUSTRY_DIRECTORY_CAPTION, this->vscroll->GetCount(), Industry::GetNumItems());
1676
1678 return GetString(IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1679
1681 return GetString(STR_INDUSTRY_DIRECTORY_ACCEPTED_CARGO_FILTER, this->GetCargoFilterLabel(this->accepted_cargo_filter_criteria));
1682
1684 return GetString(STR_INDUSTRY_DIRECTORY_PRODUCED_CARGO_FILTER, this->GetCargoFilterLabel(this->produced_cargo_filter_criteria));
1685
1686 default:
1687 return this->Window::GetWidgetString(widget, stringid);
1688 }
1689 }
1690
1691 void DrawWidget(const Rect &r, WidgetID widget) const override
1692 {
1693 switch (widget) {
1695 this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1696 break;
1697
1698 case WID_ID_INDUSTRY_LIST: {
1699 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
1700
1701 /* Setup a clipping rectangle... */
1702 DrawPixelInfo tmp_dpi;
1703 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
1704 /* ...but keep coordinates relative to the window. */
1705 tmp_dpi.left += ir.left;
1706 tmp_dpi.top += ir.top;
1707
1708 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
1709
1710 ir = ScrollRect(ir, *this->hscroll, 1);
1711
1712 if (this->industries.empty()) {
1713 DrawString(ir, STR_INDUSTRY_DIRECTORY_NONE);
1714 break;
1715 }
1716 const CargoType acf_cargo_type = this->accepted_cargo_filter_criteria;
1717 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1718 for (auto it = first; it != last; ++it) {
1719 TextColour tc = TC_FROMSTRING;
1720 if (acf_cargo_type != CargoFilterCriteria::CF_ANY && acf_cargo_type != CargoFilterCriteria::CF_NONE) {
1721 Industry *ind = const_cast<Industry *>(*it);
1722 if (IndustryTemporarilyRefusesCargo(ind, acf_cargo_type)) {
1723 tc = TC_GREY | TC_FORCED;
1724 }
1725 }
1726 DrawString(ir, this->GetIndustryString(*it), tc);
1727
1728 ir.top += this->resize.step_height;
1729 }
1730 break;
1731 }
1732 }
1733 }
1734
1735 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1736 {
1737 switch (widget) {
1738 case WID_ID_DROPDOWN_ORDER: {
1739 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->GetString());
1740 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1741 d.height += padding.height;
1742 size = maxdim(size, d);
1743 break;
1744 }
1745
1747 Dimension d = GetStringListBoundingBox(IndustryDirectoryWindow::sorter_names);
1748 d.width += padding.width;
1749 d.height += padding.height;
1750 size = maxdim(size, d);
1751 break;
1752 }
1753
1754 case WID_ID_INDUSTRY_LIST: {
1755 Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1756 fill.height = resize.height = d.height;
1757 d.height *= 5;
1758 d.width += padding.width;
1759 d.height += padding.height;
1760 size = maxdim(size, d);
1761 break;
1762 }
1763 }
1764 }
1765
1766 DropDownList BuildCargoDropDownList() const
1767 {
1768 DropDownList list;
1769
1770 /* Add item for disabling filtering. */
1771 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_ANY), CargoFilterCriteria::CF_ANY));
1772 /* Add item for industries not producing anything, e.g. power plants */
1773 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_NONE), CargoFilterCriteria::CF_NONE));
1774
1775 /* Add cargos */
1777 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1778 list.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
1779 }
1780
1781 return list;
1782 }
1783
1784 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1785 {
1786 switch (widget) {
1788 this->industries.ToggleSortOrder();
1789 this->SetDirty();
1790 break;
1791
1793 ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1794 break;
1795
1796 case WID_ID_FILTER_BY_ACC_CARGO: // Cargo filter dropdown
1797 ShowDropDownList(this, this->BuildCargoDropDownList(), this->accepted_cargo_filter_criteria, widget);
1798 break;
1799
1800 case WID_ID_FILTER_BY_PROD_CARGO: // Cargo filter dropdown
1801 ShowDropDownList(this, this->BuildCargoDropDownList(), this->produced_cargo_filter_criteria, widget);
1802 break;
1803
1804 case WID_ID_INDUSTRY_LIST: {
1805 auto it = this->vscroll->GetScrolledItemFromWidget(this->industries, pt.y, this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.top);
1806 if (it != this->industries.end()) {
1807 if (_ctrl_pressed) {
1808 ShowExtraViewportWindow((*it)->location.tile);
1809 } else {
1810 ScrollMainWindowToTile((*it)->location.tile);
1811 }
1812 }
1813 break;
1814 }
1815 }
1816 }
1817
1818 void OnDropdownSelect(WidgetID widget, int index, int) override
1819 {
1820 switch (widget) {
1822 if (this->industries.SortType() != index) {
1823 this->industries.SetSortType(index);
1825 }
1826 break;
1827 }
1828
1830 this->SetAcceptedCargoFilter(index);
1832 break;
1833 }
1834
1836 this->SetProducedCargoFilter(index);
1838 break;
1839 }
1840 }
1841 }
1842
1843 void OnResize() override
1844 {
1845 this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Vertical());
1846 this->hscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Horizontal());
1847 }
1848
1849 void OnEditboxChanged(WidgetID wid) override
1850 {
1851 if (wid == WID_ID_FILTER) {
1852 this->string_filter.SetFilterTerm(this->industry_editbox.text.GetText());
1853 this->InvalidateData(IDIWD_FORCE_REBUILD);
1854 }
1855 }
1856
1857 void OnPaint() override
1858 {
1859 if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1860 this->hscroll->SetCount(this->GetIndustryListWidth());
1861 this->DrawWidgets();
1862 }
1863
1865 const IntervalTimer<TimerWindow> rebuild_interval = {std::chrono::seconds(3), [this](auto) {
1866 this->industries.ForceResort();
1868 }};
1869
1875 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1876 {
1877 switch (data) {
1878 case IDIWD_FORCE_REBUILD:
1879 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1880 this->industries.ForceRebuild();
1881 break;
1882
1883 case IDIWD_PRODUCTION_CHANGE:
1884 if (this->industries.SortType() == 2) this->industries.ForceResort();
1885 break;
1886
1887 default:
1888 this->industries.ForceResort();
1889 break;
1890 }
1891 }
1892
1893 static inline HotkeyList hotkeys {"industrydirectory", {
1894 Hotkey('F', "focus_filter_box", WID_ID_FILTER),
1895 }};
1896};
1897
1898Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1899
1900/* Available station sorting functions. */
1901const std::initializer_list<GUIIndustryList::SortFunction * const> IndustryDirectoryWindow::sorter_funcs = {
1902 &IndustryNameSorter,
1903 &IndustryTypeSorter,
1904 &IndustryProductionSorter,
1905 &IndustryTransportedCargoSorter
1906};
1907
1908CargoType IndustryDirectoryWindow::produced_cargo_filter = CargoFilterCriteria::CF_ANY;
1909
1910
1913 WDP_AUTO, "list_industries", 428, 190,
1915 {},
1917 &IndustryDirectoryWindow::hotkeys
1918);
1919
1920void ShowIndustryDirectory()
1921{
1922 AllocateWindowDescFront<IndustryDirectoryWindow>(_industry_directory_desc, 0);
1923}
1924
1926static constexpr std::initializer_list<NWidgetPart> _nested_industry_cargoes_widgets = {
1928 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1929 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION),
1930 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1931 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1932 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1933 EndContainer(),
1937 EndContainer(),
1939 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1940 SetStringTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1941 NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1942 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1943 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1944 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1945 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1946 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1947 EndContainer(),
1948};
1949
1952 WDP_AUTO, "industry_cargoes", 300, 210,
1954 {},
1956);
1957
1967
1968static const uint MAX_CARGOES = 16;
1969
1973 static int blob_distance;
1974
1980
1983
1986 static int industry_width;
1987 static uint max_cargoes;
1988
1989 using Cargoes = uint16_t;
1990 static_assert(std::numeric_limits<Cargoes>::digits >= MAX_CARGOES);
1991
1993 union {
1994 struct {
1995 IndustryType ind_type;
1996 std::array<CargoType, MAX_CARGOES> other_produced;
1997 std::array<CargoType, MAX_CARGOES> other_accepted;
1999 struct {
2000 std::array<CargoType, MAX_CARGOES> vertical_cargoes;
2003 uint8_t num_cargoes;
2004 uint8_t top_end;
2005 uint8_t bottom_end;
2007 struct {
2008 std::array<CargoType, MAX_CARGOES> cargoes;
2012 } u{}; // Data for each type.
2013
2019 {
2020 this->type = type;
2021 }
2022
2028 void MakeIndustry(IndustryType ind_type)
2029 {
2030 this->type = CFT_INDUSTRY;
2031 this->u.industry.ind_type = ind_type;
2032 std::fill(std::begin(this->u.industry.other_accepted), std::end(this->u.industry.other_accepted), INVALID_CARGO);
2033 std::fill(std::begin(this->u.industry.other_produced), std::end(this->u.industry.other_produced), INVALID_CARGO);
2034 }
2035
2042 int ConnectCargo(CargoType cargo, bool producer)
2043 {
2044 assert(this->type == CFT_CARGO);
2045 if (!IsValidCargoType(cargo)) return -1;
2046
2047 /* Find the vertical cargo column carrying the cargo. */
2048 int column = -1;
2049 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2050 if (cargo == this->u.cargo.vertical_cargoes[i]) {
2051 column = i;
2052 break;
2053 }
2054 }
2055 if (column < 0) return -1;
2056
2057 if (producer) {
2058 assert(!HasBit(this->u.cargo.supp_cargoes, column));
2059 SetBit(this->u.cargo.supp_cargoes, column);
2060 } else {
2061 assert(!HasBit(this->u.cargo.cust_cargoes, column));
2062 SetBit(this->u.cargo.cust_cargoes, column);
2063 }
2064 return column;
2065 }
2066
2072 {
2073 assert(this->type == CFT_CARGO);
2074
2075 return this->u.cargo.supp_cargoes != 0 || this->u.cargo.cust_cargoes != 0;
2076 }
2077
2083 void MakeCargo(const std::span<const CargoType> cargoes)
2084 {
2085 this->type = CFT_CARGO;
2086 assert(std::size(cargoes) <= std::size(this->u.cargo.vertical_cargoes));
2087 auto insert = std::copy_if(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo.vertical_cargoes), IsValidCargoType);
2088 this->u.cargo.num_cargoes = static_cast<uint8_t>(std::distance(std::begin(this->u.cargo.vertical_cargoes), insert));
2089 CargoTypeComparator comparator;
2090 std::sort(std::begin(this->u.cargo.vertical_cargoes), insert, comparator);
2091 std::fill(insert, std::end(this->u.cargo.vertical_cargoes), INVALID_CARGO);
2092 this->u.cargo.top_end = false;
2093 this->u.cargo.bottom_end = false;
2094 this->u.cargo.supp_cargoes = 0;
2095 this->u.cargo.cust_cargoes = 0;
2096 }
2097
2103 void MakeCargoLabel(const std::span<const CargoType> cargoes, bool left_align)
2104 {
2105 this->type = CFT_CARGO_LABEL;
2106 assert(std::size(cargoes) <= std::size(this->u.cargo_label.cargoes));
2107 auto insert = std::copy(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo_label.cargoes));
2108 std::fill(insert, std::end(this->u.cargo_label.cargoes), INVALID_CARGO);
2109 this->u.cargo_label.left_align = left_align;
2110 }
2111
2117 {
2118 this->type = CFT_HEADER;
2119 this->u.header = textid;
2120 }
2121
2127 int GetCargoBase(int xpos) const
2128 {
2129 assert(this->type == CFT_CARGO);
2130 int n = this->u.cargo.num_cargoes;
2131
2132 return xpos + cargo_field_width / 2 - (CargoesField::cargo_line.width * n + CargoesField::cargo_space.width * (n - 1)) / 2;
2133 }
2134
2140 void Draw(int xpos, int ypos) const
2141 {
2142 switch (this->type) {
2143 case CFT_EMPTY:
2144 case CFT_SMALL_EMPTY:
2145 break;
2146
2147 case CFT_HEADER:
2148 ypos += (small_height - GetCharacterHeight(FS_NORMAL)) / 2;
2149 DrawString(xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
2150 break;
2151
2152 case CFT_INDUSTRY: {
2153 int ypos1 = ypos + vert_inter_industry_space / 2;
2154 int ypos2 = ypos + normal_height - 1 - vert_inter_industry_space / 2;
2155 int xpos2 = xpos + industry_width - 1;
2156 DrawRectOutline({xpos, ypos1, xpos2, ypos2}, INDUSTRY_LINE_COLOUR);
2158 if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2159 const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
2160 DrawString(xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
2161
2162 /* Draw the industry legend. */
2163 int blob_left, blob_right;
2164 if (_current_text_dir == TD_RTL) {
2165 blob_right = xpos2 - blob_distance;
2166 blob_left = blob_right - CargoesField::legend.width;
2167 } else {
2168 blob_left = xpos + blob_distance;
2169 blob_right = blob_left + CargoesField::legend.width;
2170 }
2171 GfxFillRect(blob_left, ypos2 - blob_distance - CargoesField::legend.height, blob_right, ypos2 - blob_distance, PC_BLACK); // Border
2172 GfxFillRect(blob_left + 1, ypos2 - blob_distance - CargoesField::legend.height + 1, blob_right - 1, ypos2 - blob_distance - 1, indsp->map_colour);
2173 } else {
2174 DrawString(xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
2175 }
2176
2177 /* Draw the other_produced/other_accepted cargoes. */
2178 std::span<const CargoType> other_right, other_left;
2179 if (_current_text_dir == TD_RTL) {
2180 other_right = this->u.industry.other_accepted;
2181 other_left = this->u.industry.other_produced;
2182 } else {
2183 other_right = this->u.industry.other_produced;
2184 other_left = this->u.industry.other_accepted;
2185 }
2187 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2188 if (IsValidCargoType(other_right[i])) {
2189 const CargoSpec *csp = CargoSpec::Get(other_right[i]);
2190 int xp = xpos + industry_width + CargoesField::cargo_stub.width;
2191 DrawHorConnection(xpos + industry_width, xp - 1, ypos1, csp);
2192 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2193 }
2194 if (IsValidCargoType(other_left[i])) {
2195 const CargoSpec *csp = CargoSpec::Get(other_left[i]);
2196 int xp = xpos - CargoesField::cargo_stub.width;
2197 DrawHorConnection(xp + 1, xpos - 1, ypos1, csp);
2198 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2199 }
2201 }
2202 break;
2203 }
2204
2205 case CFT_CARGO: {
2206 int cargo_base = this->GetCargoBase(xpos);
2207 int top = ypos + (this->u.cargo.top_end ? vert_inter_industry_space / 2 + 1 : 0);
2208 int bot = ypos - (this->u.cargo.bottom_end ? vert_inter_industry_space / 2 + 1 : 0) + normal_height - 1;
2209 int colpos = cargo_base;
2210 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2211 if (this->u.cargo.top_end) GfxDrawLine(colpos, top - 1, colpos + CargoesField::cargo_line.width - 1, top - 1, CARGO_LINE_COLOUR);
2212 if (this->u.cargo.bottom_end) GfxDrawLine(colpos, bot + 1, colpos + CargoesField::cargo_line.width - 1, bot + 1, CARGO_LINE_COLOUR);
2213 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2214 colpos++;
2215 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
2216 GfxFillRect(colpos, top, colpos + CargoesField::cargo_line.width - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
2217 colpos += CargoesField::cargo_line.width - 2;
2218 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2219 colpos += 1 + CargoesField::cargo_space.width;
2220 }
2221
2222 Cargoes hor_left, hor_right;
2223 if (_current_text_dir == TD_RTL) {
2224 hor_left = this->u.cargo.cust_cargoes;
2225 hor_right = this->u.cargo.supp_cargoes;
2226 } else {
2227 hor_left = this->u.cargo.supp_cargoes;
2228 hor_right = this->u.cargo.cust_cargoes;
2229 }
2231 for (uint i = 0; i < MAX_CARGOES; i++) {
2232 if (HasBit(hor_left, i)) {
2233 int col = i;
2234 int dx = 0;
2235 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2236 for (; col > 0; col--) {
2237 int lf = cargo_base + col * CargoesField::cargo_line.width + (col - 1) * CargoesField::cargo_space.width;
2238 DrawHorConnection(lf, lf + CargoesField::cargo_space.width - dx, ypos, csp);
2239 dx = 1;
2240 }
2241 DrawHorConnection(xpos, cargo_base - dx, ypos, csp);
2242 }
2243 if (HasBit(hor_right, i)) {
2244 int col = i;
2245 int dx = 0;
2246 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2247 for (; col < this->u.cargo.num_cargoes - 1; col++) {
2248 int lf = cargo_base + (col + 1) * CargoesField::cargo_line.width + col * CargoesField::cargo_space.width;
2249 DrawHorConnection(lf + dx - 1, lf + CargoesField::cargo_space.width - 1, ypos, csp);
2250 dx = 1;
2251 }
2252 DrawHorConnection(cargo_base + col * CargoesField::cargo_space.width + (col + 1) * CargoesField::cargo_line.width - 1 + dx, xpos + CargoesField::cargo_field_width - 1, ypos, csp);
2253 }
2255 }
2256 break;
2257 }
2258
2259 case CFT_CARGO_LABEL:
2261 for (uint i = 0; i < MAX_CARGOES; i++) {
2262 if (IsValidCargoType(this->u.cargo_label.cargoes[i])) {
2263 const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
2264 DrawString(xpos + WidgetDimensions::scaled.framerect.left, xpos + industry_width - 1 - WidgetDimensions::scaled.framerect.right, ypos, csp->name, TC_WHITE,
2265 (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
2266 }
2268 }
2269 break;
2270
2271 default:
2272 NOT_REACHED();
2273 }
2274 }
2275
2283 CargoType CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
2284 {
2285 assert(this->type == CFT_CARGO);
2286
2287 /* Vertical matching. */
2288 int cpos = this->GetCargoBase(0);
2289 uint col;
2290 for (col = 0; col < this->u.cargo.num_cargoes; col++) {
2291 if (pt.x < cpos) break;
2292 if (pt.x < cpos + static_cast<int>(CargoesField::cargo_line.width)) return this->u.cargo.vertical_cargoes[col];
2294 }
2295 /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
2296
2298 uint row;
2299 for (row = 0; row < MAX_CARGOES; row++) {
2300 if (pt.y < vpos) return INVALID_CARGO;
2301 if (pt.y < vpos + static_cast<int>(CargoesField::cargo_line.height)) break;
2303 }
2304 if (row == MAX_CARGOES) return INVALID_CARGO;
2305
2306 /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
2307 if (col == 0) {
2308 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2309 if (left != nullptr) {
2310 if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
2311 if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
2312 }
2313 return INVALID_CARGO;
2314 }
2315 if (col == this->u.cargo.num_cargoes) {
2316 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2317 if (right != nullptr) {
2318 if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
2319 if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
2320 }
2321 return INVALID_CARGO;
2322 }
2323 if (row >= col) {
2324 /* Clicked somewhere in-between vertical cargo connection.
2325 * Since the horizontal connection is made in the same order as the vertical list, the above condition
2326 * ensures we are left-below the main diagonal, thus at the supplying side.
2327 */
2328 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2329 return INVALID_CARGO;
2330 }
2331 /* Clicked at a customer connection. */
2332 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2333 return INVALID_CARGO;
2334 }
2335
2342 {
2343 assert(this->type == CFT_CARGO_LABEL);
2344
2346 uint row;
2347 for (row = 0; row < MAX_CARGOES; row++) {
2348 if (pt.y < vpos) return INVALID_CARGO;
2349 if (pt.y < vpos + GetCharacterHeight(FS_NORMAL)) break;
2351 }
2352 if (row == MAX_CARGOES) return INVALID_CARGO;
2353 return this->u.cargo_label.cargoes[row];
2354 }
2355
2356private:
2364 static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
2365 {
2366 GfxDrawLine(left, top, right, top, CARGO_LINE_COLOUR);
2367 GfxFillRect(left, top + 1, right, top + CargoesField::cargo_line.height - 2, csp->legend_colour, FILLRECT_OPAQUE);
2368 GfxDrawLine(left, top + CargoesField::cargo_line.height - 1, right, top + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2369 }
2370};
2371
2372static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::produced_cargo)>);
2373static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::accepts_cargo)>);
2374
2380
2387
2389
2392
2396
2402 {
2403 CargoesField *ind_fld = this->columns + column;
2404 CargoesField *cargo_fld = this->columns + column + 1;
2405 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2406
2407 std::fill(std::begin(ind_fld->u.industry.other_produced), std::end(ind_fld->u.industry.other_produced), INVALID_CARGO);
2408
2409 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2410 CargoType others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
2411 int other_count = 0;
2412
2413 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2414 assert(CargoesField::max_cargoes <= std::size(indsp->produced_cargo));
2415 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2416 int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
2417 if (col < 0) others[other_count++] = indsp->produced_cargo[i];
2418 }
2419
2420 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2421 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2422 if (HasBit(cargo_fld->u.cargo.supp_cargoes, i)) ind_fld->u.industry.other_produced[i] = others[--other_count];
2423 }
2424 } else {
2425 /* Houses only display cargo that towns produce. */
2426 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2427 CargoType cargo_type = cargo_fld->u.cargo.vertical_cargoes[i];
2429 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) cargo_fld->ConnectCargo(cargo_type, true);
2430 }
2431 }
2432 }
2433
2439 void MakeCargoLabel(int column, bool accepting)
2440 {
2441 CargoType cargoes[MAX_CARGOES];
2442 std::fill(std::begin(cargoes), std::end(cargoes), INVALID_CARGO);
2443
2444 CargoesField *label_fld = this->columns + column;
2445 CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2446
2447 assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2448 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2449 int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2450 if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2451 }
2452 label_fld->MakeCargoLabel(cargoes, accepting);
2453 }
2454
2455
2461 {
2462 CargoesField *ind_fld = this->columns + column;
2463 CargoesField *cargo_fld = this->columns + column - 1;
2464 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2465
2466 std::fill(std::begin(ind_fld->u.industry.other_accepted), std::end(ind_fld->u.industry.other_accepted), INVALID_CARGO);
2467
2468 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2469 CargoType others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2470 int other_count = 0;
2471
2472 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2473 assert(CargoesField::max_cargoes <= std::size(indsp->accepts_cargo));
2474 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2475 int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2476 if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2477 }
2478
2479 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2480 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2481 if (!HasBit(cargo_fld->u.cargo.cust_cargoes, i)) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2482 }
2483 } else {
2484 /* Houses only display what is demanded. */
2485 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2486 for (const auto &hs : HouseSpec::Specs()) {
2487 if (!hs.enabled) continue;
2488
2489 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2490 if (hs.cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs.accepts_cargo[j]) {
2491 cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2492 goto next_cargo;
2493 }
2494 }
2495 }
2496next_cargo: ;
2497 }
2498 }
2499 }
2500};
2501
2502
2531 typedef std::vector<CargoesRow> Fields;
2532
2533 Fields fields{};
2534 uint ind_cargo = 0;
2537 Scrollbar *vscroll = nullptr;
2538
2540 {
2541 this->OnInit();
2542 this->CreateNestedTree();
2543 this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2544 this->FinishInitNested(0);
2545 this->OnInvalidateData(id);
2546 }
2547
2548 void OnInit() override
2549 {
2550 /* Initialize static CargoesField size variables. */
2551 Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2552 d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2555 CargoesField::small_height = d.height;
2556
2557 /* Size of the legend blob -- slightly larger than the smallmap legend blob. */
2559 CargoesField::legend.width = CargoesField::legend.height * 9 / 6;
2560
2561 /* Size of cargo lines. */
2564
2565 /* Size of border between cargo lines and industry boxes. */
2568
2569 /* Size of space between cargo lines. */
2572
2573 /* Size of cargo stub (unconnected cargo line.) */
2575 CargoesField::cargo_stub.height = CargoesField::cargo_line.height; /* Unused */
2576
2579
2580 /* Decide about the size of the box holding the text of an industry type. */
2581 this->ind_textsize.width = 0;
2582 this->ind_textsize.height = 0;
2583 CargoesField::max_cargoes = 0;
2584 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2585 const IndustrySpec *indsp = GetIndustrySpec(it);
2586 if (!indsp->enabled) continue;
2588 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->accepts_cargo, IsValidCargoType));
2589 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->produced_cargo, IsValidCargoType));
2590 }
2591 d.width = std::max(d.width, this->ind_textsize.width);
2592 d.height = this->ind_textsize.height;
2593 this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2594
2595 /* Compute max size of the cargo texts. */
2596 this->cargo_textsize.width = 0;
2597 this->cargo_textsize.height = 0;
2598 for (const CargoSpec *csp : CargoSpec::Iterate()) {
2599 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(csp->name));
2600 }
2601 d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2602 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2603
2605 /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2607 d.height = std::max(d.height + WidgetDimensions::scaled.frametext.Vertical(), min_ind_height);
2608
2611
2612 /* Width of a #CFT_CARGO field. */
2614 }
2615
2616 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
2617 {
2618 switch (widget) {
2619 case WID_IC_PANEL:
2620 fill.height = resize.height = CargoesField::normal_height;
2623 break;
2624
2626 size.width = std::max(size.width, this->ind_textsize.width + padding.width);
2627 break;
2628
2630 size.width = std::max(size.width, this->cargo_textsize.width + padding.width);
2631 break;
2632 }
2633 }
2634
2635 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
2636 {
2637 if (widget != WID_IC_CAPTION) return this->Window::GetWidgetString(widget, stringid);
2638
2639 if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2640 const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2641 return GetString(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, indsp->name);
2642 } else {
2643 const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2644 return GetString(STR_INDUSTRY_CARGOES_CARGO_CAPTION, csp->name);
2645 }
2646 }
2647
2654 static bool HasCommonValidCargo(const std::span<const CargoType> cargoes1, const std::span<const CargoType> cargoes2)
2655 {
2656 for (const CargoType cargo_type1 : cargoes1) {
2657 if (!IsValidCargoType(cargo_type1)) continue;
2658 for (const CargoType cargo_type2 : cargoes2) {
2659 if (cargo_type1 == cargo_type2) return true;
2660 }
2661 }
2662 return false;
2663 }
2664
2670 static bool HousesCanSupply(const std::span<const CargoType> cargoes)
2671 {
2672 for (const CargoType cargo_type : cargoes) {
2673 if (!IsValidCargoType(cargo_type)) continue;
2675 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) return true;
2676 }
2677 return false;
2678 }
2679
2685 static bool HousesCanAccept(const std::span<const CargoType> cargoes)
2686 {
2687 HouseZones climate_mask = GetClimateMaskForLandscape();
2688
2689 for (const CargoType cargo_type : cargoes) {
2690 if (!IsValidCargoType(cargo_type)) continue;
2691
2692 for (const auto &hs : HouseSpec::Specs()) {
2693 if (!hs.enabled || !hs.building_availability.Any(climate_mask)) continue;
2694
2695 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2696 if (hs.cargo_acceptance[j] > 0 && cargo_type == hs.accepts_cargo[j]) return true;
2697 }
2698 }
2699 }
2700 return false;
2701 }
2702
2708 static int CountMatchingAcceptingIndustries(const std::span<const CargoType> cargoes)
2709 {
2710 int count = 0;
2711 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2712 const IndustrySpec *indsp = GetIndustrySpec(it);
2713 if (!indsp->enabled) continue;
2714
2715 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) count++;
2716 }
2717 return count;
2718 }
2719
2725 static int CountMatchingProducingIndustries(const std::span<const CargoType> cargoes)
2726 {
2727 int count = 0;
2728 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2729 const IndustrySpec *indsp = GetIndustrySpec(it);
2730 if (!indsp->enabled) continue;
2731
2732 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) count++;
2733 }
2734 return count;
2735 }
2736
2743 void ShortenCargoColumn(int column, int top, int bottom)
2744 {
2745 while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2746 this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2747 top++;
2748 }
2749 this->fields[top].columns[column].u.cargo.top_end = true;
2750
2751 while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2752 this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2753 bottom--;
2754 }
2755 this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2756 }
2757
2764 void PlaceIndustry(int row, int col, IndustryType it)
2765 {
2766 assert(this->fields[row].columns[col].type == CFT_EMPTY);
2767 this->fields[row].columns[col].MakeIndustry(it);
2768 if (col == 0) {
2769 this->fields[row].ConnectIndustryProduced(col);
2770 } else {
2771 this->fields[row].ConnectIndustryAccepted(col);
2772 }
2773 }
2774
2779 {
2780 if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2781
2782 /* Only notify the smallmap window if it exists. In particular, do not
2783 * bring it to the front to prevent messing up any nice layout of the user. */
2785 }
2786
2791 void ComputeIndustryDisplay(IndustryType displayed_it)
2792 {
2793 this->ind_cargo = displayed_it;
2794 _displayed_industries.reset();
2795 _displayed_industries.set(displayed_it);
2796
2797 this->fields.clear();
2798 CargoesRow &first_row = this->fields.emplace_back();
2799 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2800 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2801 first_row.columns[2].MakeEmpty(CFT_SMALL_EMPTY);
2802 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2803 first_row.columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2804
2805 const IndustrySpec *central_sp = GetIndustrySpec(displayed_it);
2806 bool houses_supply = HousesCanSupply(central_sp->accepts_cargo);
2807 bool houses_accept = HousesCanAccept(central_sp->produced_cargo);
2808 /* Make a field consisting of two cargo columns. */
2809 int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo) + houses_supply;
2810 int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo) + houses_accept;
2811 int num_indrows = std::max(3, std::max(num_supp, num_cust)); // One is needed for the 'it' industry, and 2 for the cargo labels.
2812 for (int i = 0; i < num_indrows; i++) {
2813 CargoesRow &row = this->fields.emplace_back();
2814 row.columns[0].MakeEmpty(CFT_EMPTY);
2815 row.columns[1].MakeCargo(central_sp->accepts_cargo);
2816 row.columns[2].MakeEmpty(CFT_EMPTY);
2817 row.columns[3].MakeCargo(central_sp->produced_cargo);
2818 row.columns[4].MakeEmpty(CFT_EMPTY);
2819 }
2820 /* Add central industry. */
2821 int central_row = 1 + num_indrows / 2;
2822 this->fields[central_row].columns[2].MakeIndustry(displayed_it);
2823 this->fields[central_row].ConnectIndustryProduced(2);
2824 this->fields[central_row].ConnectIndustryAccepted(2);
2825
2826 /* Add cargo labels. */
2827 this->fields[central_row - 1].MakeCargoLabel(2, true);
2828 this->fields[central_row + 1].MakeCargoLabel(2, false);
2829
2830 /* Add suppliers and customers of the 'it' industry. */
2831 int supp_count = 0;
2832 int cust_count = 0;
2833 for (IndustryType it : _sorted_industry_types) {
2834 const IndustrySpec *indsp = GetIndustrySpec(it);
2835 if (!indsp->enabled) continue;
2836
2837 if (HasCommonValidCargo(central_sp->accepts_cargo, indsp->produced_cargo)) {
2838 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2839 _displayed_industries.set(it);
2840 supp_count++;
2841 }
2842 if (HasCommonValidCargo(central_sp->produced_cargo, indsp->accepts_cargo)) {
2843 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2844 _displayed_industries.set(it);
2845 cust_count++;
2846 }
2847 }
2848 if (houses_supply) {
2849 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2850 supp_count++;
2851 }
2852 if (houses_accept) {
2853 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2854 cust_count++;
2855 }
2856
2857 this->ShortenCargoColumn(1, 1, num_indrows);
2858 this->ShortenCargoColumn(3, 1, num_indrows);
2859 this->vscroll->SetCount(num_indrows);
2860 this->SetDirty();
2861 this->NotifySmallmap();
2862 }
2863
2869 {
2870 this->ind_cargo = cargo_type + NUM_INDUSTRYTYPES;
2871 _displayed_industries.reset();
2872
2873 this->fields.clear();
2874 CargoesRow &first_row = this->fields.emplace_back();
2875 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2876 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2877 first_row.columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2878 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2879 first_row.columns[4].MakeEmpty(CFT_SMALL_EMPTY);
2880
2881 auto cargoes = std::span(&cargo_type, 1);
2882 bool houses_supply = HousesCanSupply(cargoes);
2883 bool houses_accept = HousesCanAccept(cargoes);
2884 int num_supp = CountMatchingProducingIndustries(cargoes) + houses_supply + 1; // Ensure room for the cargo label.
2885 int num_cust = CountMatchingAcceptingIndustries(cargoes) + houses_accept;
2886 int num_indrows = std::max(num_supp, num_cust);
2887 for (int i = 0; i < num_indrows; i++) {
2888 CargoesRow &row = this->fields.emplace_back();
2889 row.columns[0].MakeEmpty(CFT_EMPTY);
2890 row.columns[1].MakeCargo(cargoes);
2891 row.columns[2].MakeEmpty(CFT_EMPTY);
2892 row.columns[3].MakeEmpty(CFT_EMPTY);
2893 row.columns[4].MakeEmpty(CFT_EMPTY);
2894 }
2895
2896 this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2897
2898 /* Add suppliers and customers of the cargo. */
2899 int supp_count = 0;
2900 int cust_count = 0;
2901 for (IndustryType it : _sorted_industry_types) {
2902 const IndustrySpec *indsp = GetIndustrySpec(it);
2903 if (!indsp->enabled) continue;
2904
2905 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) {
2906 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2907 _displayed_industries.set(it);
2908 supp_count++;
2909 }
2910 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) {
2911 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2912 _displayed_industries.set(it);
2913 cust_count++;
2914 }
2915 }
2916 if (houses_supply) {
2917 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2918 supp_count++;
2919 }
2920 if (houses_accept) {
2921 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2922 cust_count++;
2923 }
2924
2925 this->ShortenCargoColumn(1, 1, num_indrows);
2926 this->vscroll->SetCount(num_indrows);
2927 this->SetDirty();
2928 this->NotifySmallmap();
2929 }
2930
2938 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
2939 {
2940 if (!gui_scope) return;
2941 if (data == NUM_INDUSTRYTYPES) {
2943 return;
2944 }
2945
2946 assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2947 this->ComputeIndustryDisplay(data);
2948 }
2949
2950 void DrawWidget(const Rect &r, WidgetID widget) const override
2951 {
2952 if (widget != WID_IC_PANEL) return;
2953
2954 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
2955 DrawPixelInfo tmp_dpi;
2956 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
2957 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2958
2960 if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::cargo_field_width) / 2;
2961 int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2962
2963 const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2964 int vpos = WidgetDimensions::scaled.frametext.top - WidgetDimensions::scaled.bevel.top - this->vscroll->GetPosition() * nwp->resize_y;
2965 int row_height = CargoesField::small_height;
2966 for (const auto &field : this->fields) {
2967 if (vpos + row_height >= 0) {
2968 int xpos = left_pos;
2969 int col, dir;
2970 if (_current_text_dir == TD_RTL) {
2971 col = last_column;
2972 dir = -1;
2973 } else {
2974 col = 0;
2975 dir = 1;
2976 }
2977 while (col >= 0 && col <= last_column) {
2978 field.columns[col].Draw(xpos, vpos);
2980 col += dir;
2981 }
2982 }
2983 vpos += row_height;
2984 if (vpos >= height) break;
2985 row_height = CargoesField::normal_height;
2986 }
2987 }
2988
2997 {
2998 const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2999 pt.x -= nw->pos_x;
3000 pt.y -= nw->pos_y;
3001
3002 int vpos = WidgetDimensions::scaled.frametext.top + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
3003 if (pt.y < vpos) return false;
3004
3005 int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
3006 if (row + 1 >= (int)this->fields.size()) return false;
3007 vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
3008 row++; // rebase row to match index of this->fields.
3009
3011 if (pt.x < xpos) return false;
3012 int column;
3013 for (column = 0; column <= 5; column++) {
3015 if (pt.x < xpos + width) break;
3016 xpos += width;
3017 }
3018 int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
3019 if (column > num_columns) return false;
3020 xpos = pt.x - xpos;
3021
3022 /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
3023 fieldxy->y = row;
3024 xy->y = vpos;
3025 if (_current_text_dir == TD_RTL) {
3026 fieldxy->x = num_columns - column;
3027 xy->x = ((column & 1) ? CargoesField::cargo_field_width : CargoesField::industry_width) - xpos;
3028 } else {
3029 fieldxy->x = column;
3030 xy->x = xpos;
3031 }
3032 return true;
3033 }
3034
3035 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
3036 {
3037 switch (widget) {
3038 case WID_IC_PANEL: {
3039 Point fieldxy, xy;
3040 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
3041
3042 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3043 switch (fld->type) {
3044 case CFT_INDUSTRY:
3046 break;
3047
3048 case CFT_CARGO: {
3049 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3050 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3051 CargoType cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3052 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3053 break;
3054 }
3055
3056 case CFT_CARGO_LABEL: {
3057 CargoType cargo_type = fld->CargoLabelClickedAt(xy);
3058 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3059 break;
3060 }
3061
3062 default:
3063 break;
3064 }
3065 break;
3066 }
3067
3068 case WID_IC_NOTIFY:
3071 SndClickBeep();
3072
3073 if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
3074 if (FindWindowByClass(WC_SMALLMAP) == nullptr) ShowSmallMap();
3075 this->NotifySmallmap();
3076 }
3077 break;
3078
3079 case WID_IC_CARGO_DROPDOWN: {
3080 DropDownList lst;
3082 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
3083 lst.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
3084 }
3085 if (!lst.empty()) {
3086 int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
3087 ShowDropDownList(this, std::move(lst), selected, WID_IC_CARGO_DROPDOWN);
3088 }
3089 break;
3090 }
3091
3092 case WID_IC_IND_DROPDOWN: {
3093 DropDownList lst;
3094 for (IndustryType ind : _sorted_industry_types) {
3095 const IndustrySpec *indsp = GetIndustrySpec(ind);
3096 if (!indsp->enabled) continue;
3097 lst.push_back(MakeDropDownListStringItem(indsp->name, ind));
3098 }
3099 if (!lst.empty()) {
3100 int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
3101 ShowDropDownList(this, std::move(lst), selected, WID_IC_IND_DROPDOWN);
3102 }
3103 break;
3104 }
3105 }
3106 }
3107
3108 void OnDropdownSelect(WidgetID widget, int index, int) override
3109 {
3110 if (index < 0) return;
3111
3112 switch (widget) {
3114 this->ComputeCargoDisplay(index);
3115 break;
3116
3118 this->ComputeIndustryDisplay(index);
3119 break;
3120 }
3121 }
3122
3123 bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
3124 {
3125 if (widget != WID_IC_PANEL) return false;
3126
3127 Point fieldxy, xy;
3128 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return false;
3129
3130 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3131 CargoType cargo_type = INVALID_CARGO;
3132 switch (fld->type) {
3133 case CFT_CARGO: {
3134 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3135 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3136 cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3137 break;
3138 }
3139
3140 case CFT_CARGO_LABEL: {
3141 cargo_type = fld->CargoLabelClickedAt(xy);
3142 break;
3143 }
3144
3145 case CFT_INDUSTRY:
3146 if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
3147 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP), close_cond);
3148 }
3149 return true;
3150
3151 default:
3152 break;
3153 }
3154 if (IsValidCargoType(cargo_type) && (this->ind_cargo < NUM_INDUSTRYTYPES || cargo_type != this->ind_cargo - NUM_INDUSTRYTYPES)) {
3155 const CargoSpec *csp = CargoSpec::Get(cargo_type);
3156 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, csp->name), close_cond);
3157 return true;
3158 }
3159
3160 return false;
3161 }
3162
3163 void OnResize() override
3164 {
3165 this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL, WidgetDimensions::scaled.framerect.Vertical() + CargoesField::small_height);
3166 }
3167};
3168
3173static void ShowIndustryCargoesWindow(IndustryType id)
3174{
3175 if (id >= NUM_INDUSTRYTYPES) {
3176 for (IndustryType ind : _sorted_industry_types) {
3177 const IndustrySpec *indsp = GetIndustrySpec(ind);
3178 if (indsp->enabled) {
3179 id = ind;
3180 break;
3181 }
3182 }
3183 if (id >= NUM_INDUSTRYTYPES) return;
3184 }
3185
3187 if (w != nullptr) {
3188 w->InvalidateData(id);
3189 return;
3190 }
3191 new IndustryCargoesWindow(id);
3192}
3193
Class for backupping variables and making sure they are restored later.
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:21
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:104
Dimension GetLargestCargoIconSize()
Get dimensions of largest cargo icon.
std::span< const CargoSpec * > _sorted_standard_cargo_specs
Standard cargo specifications sorted alphabetically by name.
TownProductionEffect
Town effect when producing cargo.
Definition cargotype.h:35
@ TPE_PASSENGERS
Cargo behaves passenger-like for production.
Definition cargotype.h:37
@ TPE_MAIL
Cargo behaves mail-like for production.
Definition cargotype.h:38
Cheats _cheats
All the cheats.
Definition cheat.cpp:16
Types related to cheating.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Timpl & Set()
Set all bits.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
Build (fund or prospect) a new industry,.
IndustryType selected_type
industry corresponding to the above index
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
Dimension legend
Dimension of the legend 'blob'.
void OnInit() override
Notification that the nested widget tree gets initialized.
void SetButtons()
Update status of the fund and display-chain widgets.
static const int MAX_MINWIDTH_LINEHEIGHTS
The largest allowed minimum-width of the window, given in line heights.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void OnPlaceObject(Point pt, TileIndex tile) override
The user clicked some place on the map when a tile highlight mode has been set.
void OnPlaceObjectAbort() override
The user cancelled a tile highlight mode that has been set.
std::string MakeCargoListString(const std::span< const CargoType > cargolist, const std::span< const CargoSuffix > cargo_suffix, StringID prefixstr) const
Build a string of cargo names with suffixes attached.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void OnTimeout() override
Called when this window's timeout has been reached.
void OnResize() override
Called after the window got resized.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
std::vector< IndustryType > list
List of industries.
bool enabled
Availability state of the selected industry.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
std::string GetDecodedString() const
Decode the encoded string.
Definition strings.cpp:207
uint GetTotalColumnsWidth() const
Get total width of all columns.
List template of 'things' T to sort in a GUI.
bool Filter(FilterFunction *decide, F filter_data)
Filter the list.
void RebuildDone()
Notify the sortlist that the rebuild is done.
void SetListing(Listing l)
Import sort conditions.
void SetFilterState(bool state)
Enable or disable the filter.
bool IsDescSortOrder() const
Check if the sort order is descending.
void ToggleSortOrder()
Toggle the sort order Since that is the worst condition for the sort function reverse the list here.
bool(const const Industry * *, const std::pair< CargoType, CargoType > &) FilterFunction
Signature of filter function.
void SetFilterFuncs(std::span< FilterFunction *const > n_funcs)
Hand the filter function pointers to the GUIList.
bool NeedRebuild() const
Check if a rebuild is needed.
void SetFilterType(uint8_t n_type)
Set the filtertype of the list.
void ForceRebuild()
Force that a rebuild is needed.
bool Sort(Comp compare)
Sort the list.
void ForceResort()
Force a resort next Sort call Reset the resort timer if used too.
uint8_t SortType() const
Get the sorttype of the list.
Listing GetListing() const
Export current sort conditions.
void SetSortFuncs(std::span< SortFunction *const > n_funcs)
Hand the sort function pointers to the GUIList.
void SetSortType(uint8_t n_type)
Set the sorttype of the list.
The list of industries.
const IntervalTimer< TimerWindow > rebuild_interval
Rebuild the industry list on a regular interval.
void OnEditboxChanged(WidgetID wid) override
The text in an editbox has been edited.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
static bool IndustryTypeSorter(const Industry *const &a, const Industry *const &b, const CargoType &filter)
Sort industries by type and name.
static int GetCargoTransportedSortValue(const Industry *i)
Returns value representing industry's transported cargo percentage for industry sorting.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
const int MAX_FILTER_LENGTH
The max length of the filter, in chars.
void SetCargoFilterArray()
Populate the filter list and set the cargo filter criteria.
void OnResize() override
Called after the window got resized.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
void SetProducedCargoFilter(CargoType cargo_type)
Set produced cargo filter for the industry list.
static int GetCargoTransportedPercentsIfValid(const Industry::ProducedCargo &p)
Returns percents of cargo transported if industry produces this cargo, else -1.
SorterType
Ways to sort industries.
@ ByName
Sorter type to sort by name.
@ ByTransported
Sorter type to sort by transported percentage.
@ ByProduction
Sorter type to sort by production amount.
@ ByType
Sorter type to sort by type.
std::string GetIndustryString(const Industry *i) const
Get the StringID to draw and set the appropriate DParams.
uint GetIndustryListWidth() const
Get the width needed to draw the longest industry line.
static bool IndustryTransportedCargoSorter(const Industry *const &a, const Industry *const &b, const CargoType &filter)
Sort industries by transported cargo and name.
QueryString industry_editbox
Filter editbox.
StringFilter string_filter
Filter for industries.
static bool IndustryProductionSorter(const Industry *const &a, const Industry *const &b, const CargoType &filter)
Sort industries by production and name.
void BuildSortIndustriesList()
(Re)Build industries list
CargoType accepted_cargo_filter_criteria
Selected accepted cargo filter index.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
static bool IndustryNameSorter(const Industry *const &a, const Industry *const &b, const CargoType &)
Sort industries by name.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void SetAcceptedCargoFilter(CargoType cargo_type)
Set accepted cargo filter for the industry list.
void OnDropdownSelect(WidgetID widget, int index, int) override
A dropdown option associated to this window has been selected.
CargoType produced_cargo_filter_criteria
Selected produced cargo filter index.
void OnPaint() override
The window must be repainted.
void OnInit() override
Notification that the nested widget tree gets initialized.
void OnResize() override
Called after the window got resized.
int cheat_line_height
Height of each line for the WID_IV_INFO panel.
int production_offset_y
The offset of the production texts/buttons.
void OnPaint() override
The window must be repainted.
Editability editable
Mode for changing production.
InfoLine clicked_line
The line of the button that has been clicked.
void OnTimeout() override
Called when this window's timeout has been reached.
bool IsNewGRFInspectable() const override
Is the data related to this window NewGRF inspectable?
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
int info_height
Height needed for the WID_IV_INFO panel.
Dimension cargo_icon_size
Largest cargo icon dimension.
void OnMouseWheel(int wheel, WidgetID widget) override
The mouse wheel has been turned.
void ShowNewGRFInspectWindow() const override
Show the NewGRF inspection window.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void OnInit() override
Notification that the nested widget tree gets initialized.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
uint8_t clicked_button
The button that has been clicked (to raise)
void OnQueryTextFinished(std::optional< std::string > str) override
The query window opened from this window has closed.
InfoLine
Specific lines in the info panel.
@ IL_RATE2
Production rate of cargo 2.
@ IL_MULTIPLIER
Production multiplier.
@ IL_RATE1
Production rate of cargo 1.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
int DrawInfo(const Rect &r)
Draw the text in the WID_IV_INFO panel.
InfoLine editbox_line
The line clicked to open the edit box.
Editability
Modes for changing production.
@ EA_RATE
Allow changing the production rates.
@ EA_MULTIPLIER
Allow changing the production multiplier.
@ EA_NONE
Not alterable.
An interval timer will fire every interval, and will continue to fire until it is deleted.
Definition timer.h:76
Baseclass for nested widgets.
int pos_y
Vertical position of top-left corner of the widget in the window.
int pos_x
Horizontal position of top-left corner of the widget in the window.
uint resize_y
Vertical resize step (0 means not resizable).
Nested widget to display a viewport in a window.
void UpdateViewportCoordinates(Window *w)
Update the position and size of the viewport (after eg a resize).
Definition widget.cpp:2402
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2393
Scrollbar data structure.
void SetCount(size_t num)
Sets the number of elements in the list.
auto GetScrolledItemFromWidget(Tcontainer &container, int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Return an iterator pointing to the element of a scrolled widget that a user clicked in.
void SetCapacityFromWidget(Window *w, WidgetID widget, int padding=0)
Set capacity of visible elements from the size and resize properties of a widget.
Definition widget.cpp:2499
size_type GetCount() const
Gets the number of elements in the list.
auto GetVisibleRangeIterators(Tcontainer &container) const
Get a pair of iterators for the range of visible elements in a container.
size_type GetPosition() const
Gets the position of the first visible element in the list.
static bool UsingWallclockUnits(bool newgame=false)
Check if we are using wallclock units.
RectPadding framerect
Standard padding inside many panels.
Definition window_gui.h:40
RectPadding frametext
Padding inside frame with text.
Definition window_gui.h:41
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
int vsep_wide
Wide vertical spacing.
Definition window_gui.h:60
int hsep_wide
Wide horizontal spacing.
Definition window_gui.h:62
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
int hsep_normal
Normal horizontal spacing.
Definition window_gui.h:61
RectPadding bevel
Bevel thickness, affected by "scaled bevels" game option.
Definition window_gui.h:38
Map accessors for 'clear' tiles.
@ CLEAR_GRASS
0-3
Definition clear_map.h:20
@ CLEAR_FIELDS
3
Definition clear_map.h:23
void MakeClear(Tile t, ClearGround g, uint density)
Make a clear tile.
Definition clear_map.h:246
ClearGround GetClearGround(Tile t)
Get the type of clear tile.
Definition clear_map.h:46
Functions related to commands.
Definition of stuff that is very close to a company, like the company struct itself.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
CompanyID _current_company
Company currently doing an action.
Functions related to companies.
static constexpr Owner OWNER_NONE
The tile has no ownership.
void ShowDropDownMenu(Window *w, std::span< const StringID > strings, int selected, WidgetID button, uint32_t disabled_mask, uint32_t hidden_mask, uint width)
Show a dropdown menu window near a widget of the parent window.
Definition dropdown.cpp:458
void ShowDropDownList(Window *w, DropDownList &&list, int selected, WidgetID button, uint width, DropDownOptions options)
Show a drop down list.
Definition dropdown.cpp:418
Functions related to the drop down widget.
Types related to the drop down widget.
std::vector< std::unique_ptr< const DropDownListItem > > DropDownList
A drop down list is a collection of drop down list items.
uint ScaleByCargoScale(uint num, bool town)
Scale a number by the cargo scale setting.
Functions related to errors.
@ WL_INFO
Used for DoCommand-like (and some non-fatal AI GUI) errors/information.
Definition error.h:24
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, CommandCost &cc)
Display an error message in a window.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:87
bool _generating_world
Whether we are generating the map or not.
Definition genworld.cpp:74
Functions related to world/map generation.
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
int CentreBounds(int min, int max, int size)
Determine where to position a centred object.
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:968
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:897
Dimension GetStringListBoundingBox(std::span< const StringID > list, FontSize fontsize)
Get maximum dimension of a list of strings.
Definition gfx.cpp:935
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:668
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:38
void DrawRectOutline(const Rect &r, PixelColour colour, int width, int dash)
Draw the outline of a Rect.
Definition gfx.cpp:463
void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
Draw a sprite, not in a viewport.
Definition gfx.cpp:1034
int DrawStringMultiLine(int left, int right, int top, int bottom, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly over multiple lines.
Definition gfx.cpp:785
void GfxFillRect(int left, int top, int right, int bottom, const std::variant< PixelColour, PaletteID > &colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
Set up a clipping area for only drawing into a certain area.
Definition gfx.cpp:1568
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:250
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:249
@ SA_LEFT
Left align the text.
Definition gfx_type.h:388
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:390
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:389
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:307
@ TC_FORCED
Ignore colour changes from strings.
Definition gfx_type.h:332
@ FILLRECT_OPAQUE
Fill rectangle with a single colour.
Definition gfx_type.h:345
Graph GUI functions.
constexpr NWidgetPart SetMatrixDataTip(uint32_t cols, uint32_t rows, StringID tip={})
Widget part function for setting the data and tooltip of WWT_MATRIX widgets.
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
constexpr NWidgetPart SetSpriteTip(SpriteID sprite, StringID tip={})
Widget part function for setting the sprite and tooltip.
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
constexpr NWidgetPart SetPadding(uint8_t top, uint8_t right, uint8_t bottom, uint8_t left)
Widget part function for setting additional space around a widget.
constexpr NWidgetPart SetStringTip(StringID string, StringID tip={})
Widget part function for setting the string and tooltip.
constexpr NWidgetPart SetAspect(float ratio, AspectFlags flags=AspectFlag::ResizeX)
Widget part function for setting the aspect ratio.
constexpr NWidgetPart SetMinimalSize(int16_t x, int16_t y)
Widget part function for setting the minimal size.
constexpr NWidgetPart SetToolTip(StringID tip)
Widget part function for setting tooltip and clearing the widget data.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=INVALID_WIDGET)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart SetMinimalTextLines(uint8_t lines, uint8_t spacing, FontSize size=FS_NORMAL)
Widget part function for setting the minimal text lines.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:968
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition gfx.cpp:1549
GUI functions that shouldn't be here.
void ShowExtraViewportWindow(TileIndex tile=INVALID_TILE)
Show a new Extra Viewport window.
Hotkey related functions.
HouseZones GetClimateMaskForLandscape()
Get the HouseZones climate mask for the current landscape type.
Base of all industries.
static constexpr uint8_t PRODLEVEL_MAXIMUM
the industry is running at full speed
Definition industry.h:38
static constexpr uint8_t PRODLEVEL_DEFAULT
default level set when the industry is created
Definition industry.h:37
static constexpr uint8_t PRODLEVEL_MINIMUM
below this level, the industry is set to be closing
Definition industry.h:36
static constexpr uint8_t PRODLEVEL_CLOSURE
signal set to actually close the industry
Definition industry.h:35
const IndustrySpec * GetIndustrySpec(IndustryType thistype)
Accessor for array _industry_specs.
Command definitions related to industries.
static WindowDesc _build_industry_desc(WDP_AUTO, "build_industry", 170, 212, WC_BUILD_INDUSTRY, WC_NONE, WindowDefaultFlag::Construction, _nested_build_industry_widgets)
Window definition of the dynamic place industries gui.
static constexpr std::initializer_list< NWidgetPart > _nested_industry_directory_widgets
Widget definition of the industry directory gui.
void ShowIndustryCargoesWindow()
Open the industry and cargoes window with an industry.
static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, CargoSuffix &suffix)
Gets the string to display after the cargo name (using callback 37)
CargoSuffixType
Cargo suffix type (for which window is it requested)
@ CST_DIR
Industry-directory window.
@ CST_FUND
Fund-industry window.
@ CST_VIEW
View-industry window.
static const uint MAX_CARGOES
Maximum number of cargoes carried in a CFT_CARGO field in CargoesField.
static WindowDesc _industry_directory_desc(WDP_AUTO, "list_industries", 428, 190, WC_INDUSTRY_DIRECTORY, WC_NONE, {}, _nested_industry_directory_widgets, &IndustryDirectoryWindow::hotkeys)
Window definition of the industry directory gui.
static WindowDesc _industry_cargoes_desc(WDP_AUTO, "industry_cargoes", 300, 210, WC_INDUSTRY_CARGOES, WC_NONE, {}, _nested_industry_cargoes_widgets)
Window description for the industry cargoes window.
static bool CargoFilter(const Industry *const *industry, const std::pair< CargoType, CargoType > &cargoes)
Cargo filter functions.
static void GetAllCargoSuffixes(CargoSuffixInOut use_input, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, const TC &cargoes, TS &suffixes)
Gets all strings to display after the cargoes of industries (using callback 37)
static WindowDesc _industry_view_desc(WDP_AUTO, "view_industry", 260, 120, WC_INDUSTRY_VIEW, WC_NONE, {}, _nested_industry_view_widgets)
Window definition of the view industry gui.
void GenerateIndustries()
This function will create random industries during game creation.
static bool IndustryTypeNameSorter(const IndustryType &a, const IndustryType &b)
Sort industry types by their name.
CargoSuffixDisplay
Ways of displaying the cargo.
@ CSD_CARGO_TEXT
Display then cargo and supplied string (cb37 result 800-BFF).
@ CSD_CARGO
Display the cargo without sub-type (cb37 result 401).
@ CSD_CARGO_AMOUNT_TEXT
Display then cargo, amount, and string (cb37 result 000-3FF).
@ CSD_CARGO_AMOUNT
Display the cargo and amount (if useful), but no sub-type (cb37 result 400 or fail).
std::array< IndustryType, NUM_INDUSTRYTYPES > _sorted_industry_types
Industry types sorted by name.
std::bitset< NUM_INDUSTRYTYPES > _displayed_industries
Communication from the industry chain window to the smallmap window about what industries to display.
static constexpr std::initializer_list< NWidgetPart > _nested_industry_view_widgets
Widget definition of the view industry gui.
CargoesFieldType
Available types of field.
@ CFT_SMALL_EMPTY
Empty small field (for the header).
@ CFT_EMPTY
Empty field.
@ CFT_INDUSTRY
Display industry.
@ CFT_CARGO
Display cargo connections.
@ CFT_HEADER
Header text.
@ CFT_CARGO_LABEL
Display cargo labels.
void SortIndustryTypes()
Initialize the list of sorted industry types.
static constexpr std::initializer_list< NWidgetPart > _nested_industry_cargoes_widgets
Widgets of the industry cargoes window.
static const int INDUSTRY_ORIGINAL_NUM_INPUTS
Original number of accepted cargo types.
static const IndustryType NUM_INDUSTRYTYPES
total number of industry types, new and old; limited to 240 because we need some special ids like IT_...
static const int INDUSTRY_ORIGINAL_NUM_OUTPUTS
Original number of produced cargo types.
Types related to the industry widgets.
@ WID_DPI_MATRIX_WIDGET
Matrix of the industries.
@ WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET
Remove all industries button.
@ WID_DPI_INFOPANEL
Info panel about the industry.
@ WID_DPI_SCENARIO_EDITOR_PANE
Pane containing SE-only widgets.
@ WID_DPI_SCROLLBAR
Scrollbar of the matrix.
@ WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET
Create random industries button.
@ WID_DPI_FUND_WIDGET
Fund button.
@ WID_DPI_DISPLAY_WIDGET
Display chain button.
@ WID_ID_HSCROLLBAR
Horizontal scrollbar of the list.
@ WID_ID_INDUSTRY_LIST
Industry list.
@ WID_ID_DROPDOWN_ORDER
Dropdown for the order of the sort.
@ WID_ID_FILTER_BY_PROD_CARGO
Produced cargo filter dropdown list.
@ WID_ID_DROPDOWN_CRITERIA
Dropdown for the criteria of the sort.
@ WID_ID_CAPTION
Caption of the window.
@ WID_ID_FILTER
Textbox to filter industry name.
@ WID_ID_FILTER_BY_ACC_CARGO
Accepted cargo filter dropdown list.
@ WID_ID_VSCROLLBAR
Vertical scrollbar of the list.
@ WID_IC_SCROLLBAR
Scrollbar of the panel.
@ WID_IC_NOTIFY
Row of buttons at the bottom.
@ WID_IC_IND_DROPDOWN
Select industry dropdown.
@ WID_IC_CAPTION
Caption of the window.
@ WID_IC_PANEL
Panel that shows the chain.
@ WID_IC_CARGO_DROPDOWN
Select cargo dropdown.
@ WID_IV_INFO
Info of the industry.
@ WID_IV_CAPTION
Caption of the window.
@ WID_IV_GRAPH
Production history button.
@ WID_IV_DISPLAY
Display chain button.
@ WID_IV_GOTO
Goto button.
@ WID_IV_VIEWPORT
Viewport of the industry.
@ CargoTypesUnlimited
Allow produced/accepted cargoes callbacks to supply more than 2 and 3 types.
bool DoZoomInOutWindow(ZoomStateChange how, Window *w)
Zooms a viewport in a window in or out.
Definition main_gui.cpp:93
bool HandlePlacePushButton(Window *w, WidgetID widget, CursorID cursor, HighLightStyle mode)
This code is shared for the majority of the pushbuttons.
Definition main_gui.cpp:63
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr int RoundDivSU(int a, uint b)
Computes round(a / b) for signed a and unsigned b.
constexpr uint ToPercent8(uint i)
Converts a "fract" value 0..255 to "percent" value 0..100.
constexpr uint ClampU(const uint a, const uint min, const uint max)
Clamp an unsigned integer between an interval.
void GuiShowTooltips(Window *parent, EncodedString &&text, TooltipCloseCondition close_tooltip)
Shows a tooltip.
Definition misc_gui.cpp:693
void ShowQuery(EncodedString &&caption, EncodedString &&message, Window *parent, QueryCallbackProc *callback, bool focus)
Show a confirmation window with standard 'yes' and 'no' buttons The window is aligned to the centre o...
void ShowQueryString(std::string_view str, StringID caption, uint maxsize, Window *parent, CharSetFilter afilter, QueryStringFlags flags)
Show a query popup window with a textbox in it.
static constexpr CargoType CF_NONE
Show only items which do not carry cargo (e.g. train engines)
Definition cargo_type.h:94
static constexpr CargoType CF_ANY
Show all items independent of carried cargo (i.e. no filtering)
Definition cargo_type.h:93
bool _networking
are we in networking mode?
Definition network.cpp:66
Basic functions/variables used all over the place.
Functions related to NewGRF badges.
int DrawBadgeNameList(Rect r, std::span< const BadgeID > badges, GrfSpecFeature)
Draw names for a list of badge labels.
void DrawBadgeColumn(Rect r, int column_group, const GUIBadgeClasses &gui_classes, std::span< const BadgeID > badges, GrfSpecFeature feature, std::optional< TimerGameCalendar::Date > introduction_date, PaletteID remap)
Draw a badge column group.
GUI functions related to NewGRF badges.
@ WindowMoreText
additional text in industry window
@ FundMoreText
additional text in fund window
@ Production256Ticks
call production callback every 256 ticks
@ ProductionCargoArrival
call production callback when cargo arrives at the industry
@ CargoSuffix
cargo sub-type display
@ CBID_INDUSTRY_WINDOW_MORE_TEXT
Called to determine more text in the industry window.
@ CBID_INDUSTRY_CARGO_SUFFIX
Called to determine text to display after cargo name.
@ CBID_INDUSTRY_FUND_MORE_TEXT
Called to determine more text in the fund industry window.
static const uint CALLBACK_FAILED
Different values for Callback result evaluations.
void ErrorUnknownCallbackResult(uint32_t grfid, uint16_t cbid, uint16_t cb_res)
Record that a NewGRF returned an unknown/invalid callback result.
Functions/types related to NewGRF debugging.
uint16_t GetIndustryCallback(CallbackID callback, uint32_t param1, uint32_t param2, Industry *industry, IndustryType type, TileIndex tile, std::span< int32_t > regs100)
Perform an industry callback.
uint32_t GetIndustryProbabilityCallback(IndustryType type, IndustryAvailabilityCallType creation_type, uint32_t default_prob)
Check with callback CBID_INDUSTRY_PROBABILITY whether the industry can be built.
bool IndustryTemporarilyRefusesCargo(Industry *ind, CargoType cargo_type)
Check whether an industry temporarily refuses to accept a certain cargo.
Functions for NewGRF industries.
@ IACT_USERCREATION
from the Fund/build window
@ PSM_ENTER_GAMELOOP
Enter the gameloop, changes will be permanent.
@ PSM_LEAVE_GAMELOOP
Leave the gameloop, changes will be temporary.
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span< const int32_t > textstack)
Format a GRF string using the text ref stack for parameters.
Header of Action 04 "universal holder" structure and functions.
static constexpr GRFStringID GRFSTR_MISC_GRF_TEXT
Miscellaneous GRF text range.
static constexpr PixelColour PC_YELLOW
Yellow palette colour.
static constexpr PixelColour PC_BLACK
Black palette colour.
static constexpr PixelColour PC_WHITE
White palette colour.
Base for the GUIs that have an edit box in them.
Pseudo random number generator.
A number of safeguards to prevent using unsafe methods.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
void DrawArrowButtons(int x, int y, Colours button_colour, uint8_t state, bool clickable_left, bool clickable_right)
Draw [<][>] boxes.
Functions for setting GUIs.
#define SETTING_BUTTON_WIDTH
Width of setting buttons.
#define SETTING_BUTTON_HEIGHT
Height of setting buttons.
@ Off
Scroll wheel has no effect.
void ShowSmallMap()
Show the smallmap window.
Smallmap GUI functions.
Base types for having sorted lists in GUIs.
void SndClickBeep()
Play a beep sound for a click event if enabled in settings.
Definition sound.cpp:253
Functions related to sound.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
int StrNaturalCompare(std::string_view s1, std::string_view s2, bool ignore_garbage_at_front)
Compares two strings using case insensitive natural sort.
Definition string.cpp:427
Parse strings.
static std::optional< T > ParseInteger(std::string_view arg, int base=10, bool clamp=false)
Change a string into its number representation.
Functions related to low-level strings.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition string_type.h:25
Searching and filtering using a stringterm.
void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
Get a parsed string with most special stringcodes replaced by the string parameters.
Definition strings.cpp:336
std::string_view GetListSeparator()
Get the list separator string for the current language.
Definition strings.cpp:299
void AppendStringInPlace(std::string &result, StringID string)
Resolve the given StringID and append in place into an existing std::string with formatting but no pa...
Definition strings.cpp:434
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:218
Functions related to OTTD's strings.
auto MakeParameters(Args &&... args)
Helper to create the StringParameters with its own buffer with the given parameter values.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_RTL
Text is written right-to-left by default.
static const int MAX_CHAR_LENGTH
Max. length of UTF-8 encoded unicode character.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
Class to backup a specific variable and restore it later.
static void SwitchMode(PersistentStorageMode mode, bool ignore_prev_mode=false)
Clear temporary changes made since the last call to SwitchMode, and set whether subsequent changes sh...
Specification of a cargo type.
Definition cargotype.h:74
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:137
SpriteID GetCargoIcon() const
Get sprite for showing cargo of this type.
static IterateWrapper Iterate(size_t from=0)
Returns an iterable ensemble of all valid CargoSpec.
Definition cargotype.h:192
StringID name
Name of this type of cargo.
Definition cargotype.h:91
TownProductionEffect town_production_effect
The effect on town cargo production.
Definition cargotype.h:87
Transfer storage of cargo suffix information.
std::string text
Cargo suffix text.
CargoSuffixDisplay display
How to display the cargo and text.
Comparator to sort CargoType by according to desired order.
Definition cargotype.h:244
Data about a single field in the IndustryCargoesWindow panel.
static const PixelColour CARGO_LINE_COLOUR
Line colour around the cargo.
StringID header
Header text (for CFT_HEADER).
std::array< CargoType, MAX_CARGOES > cargoes
Cargoes to display (or INVALID_CARGO).
CargoesFieldType type
Type of field.
static int blob_distance
Distance of the industry legend colour from the edge of the industry box.
static Dimension cargo_border
Dimensions of border between cargo lines and industry boxes.
uint8_t top_end
Stop at the top of the vertical cargoes.
Cargoes cust_cargoes
Cargoes in vertical_cargoes leaving to the right.
int GetCargoBase(int xpos) const
For a CFT_CARGO, compute the left position of the left-most vertical cargo connection.
std::array< CargoType, MAX_CARGOES > other_accepted
Cargoes accepted but not used in this figure.
int ConnectCargo(CargoType cargo, bool producer)
Connect a cargo from an industry to the CFT_CARGO column.
static int small_height
Height of the header row.
CargoType CargoLabelClickedAt(Point pt) const
Decide what cargo the user clicked in the cargo label field.
static Dimension cargo_space
Dimensions of space between cargo lines.
static int normal_height
Height of the non-header rows.
static int cargo_field_width
Width of a cargo field.
struct CargoesField::@8::@11 cargo_label
Label data (for CFT_CARGO_LABEL).
bool left_align
Align all cargo texts to the left (else align to the right).
static uint max_cargoes
Largest number of cargoes actually on any industry.
bool HasConnection()
Does this CFT_CARGO field have a horizontal connection?
static Dimension cargo_line
Dimensions of cargo lines.
void MakeIndustry(IndustryType ind_type)
Make an industry type field.
static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
Draw a horizontal cargo connection.
IndustryType ind_type
Industry type (NUM_INDUSTRYTYPES means 'houses').
static int vert_inter_industry_space
Amount of space between two industries in a column.
static Dimension cargo_stub
Dimensions of cargo stub (unconnected cargo line.)
static Dimension legend
Dimension of the legend blob.
void MakeCargo(const std::span< const CargoType > cargoes)
Make a piece of cargo column.
Cargoes supp_cargoes
Cargoes in vertical_cargoes entering from the left.
static const PixelColour INDUSTRY_LINE_COLOUR
Line colour of the industry type box.
CargoType CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
Decide which cargo was clicked at in a CFT_CARGO field.
struct CargoesField::@8::@10 cargo
Cargo data (for CFT_CARGO).
void Draw(int xpos, int ypos) const
Draw the field.
std::array< CargoType, MAX_CARGOES > other_produced
Cargoes produced but not used in this figure.
struct CargoesField::@8::@9 industry
Industry data (for CFT_INDUSTRY).
static int industry_width
Width of an industry field.
std::array< CargoType, MAX_CARGOES > vertical_cargoes
Cargoes running from top to bottom (cargo type or INVALID_CARGO).
uint8_t num_cargoes
Number of cargoes.
void MakeHeader(StringID textid)
Make a header above an industry column.
uint8_t bottom_end
Stop at the bottom of the vertical cargoes.
void MakeCargoLabel(const std::span< const CargoType > cargoes, bool left_align)
Make a field displaying cargo type names.
void MakeEmpty(CargoesFieldType type)
Make one of the empty fields (CFT_EMPTY or CFT_SMALL_EMPTY).
A single row of CargoesField.
CargoesField columns[5]
One row of fields.
void ConnectIndustryProduced(int column)
Connect industry production cargoes to the cargo column after it.
void ConnectIndustryAccepted(int column)
Connect industry accepted cargoes to the cargo column before it.
void MakeCargoLabel(int column, bool accepting)
Construct a CFT_CARGO_LABEL field.
bool value
tells if the bool cheat is active or not
Definition cheat_type.h:18
Cheat setup_prod
setup raw-material production in game
Definition cheat_type.h:33
GUISettings gui
settings related to the GUI
uint8_t raw_industry_construction
type of (raw) industry construction (none, "normal", prospecting)
T y
Y coordinate.
T x
X coordinate.
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h:157
const struct GRFFile * grffile
grf file that introduced this entity
uint32_t grfid
grfid that introduced this entity.
bool HasGrfFile() const
Test if this entity was introduced by NewGRF.
std::array< uint8_t, NUM_CARGO > cargo_map
Inverse cargo translation table (CargoType -> local ID)
Definition newgrf.h:137
bool persistent_buildingtools
keep the building tools active after usage
ScrollWheelScrolling scrollwheel_scrolling
scrolling using the scroll wheel?
ConstructionSettings construction
construction of things in-game
List of hotkeys for a window.
Definition hotkeys.h:37
All data for a single hotkey.
Definition hotkeys.h:21
static std::vector< HouseSpec > & Specs()
Get a reference to all HouseSpecs.
Window displaying the cargo connections around an industry (or cargo).
Dimension cargo_textsize
Size to hold any cargo text, as well as STR_INDUSTRY_CARGOES_SELECT_CARGO.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void OnInit() override
Notification that the nested widget tree gets initialized.
static bool HousesCanSupply(const std::span< const CargoType > cargoes)
Can houses be used to supply one of the cargoes?
bool OnTooltip(Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
Event to display a custom tooltip.
Dimension ind_textsize
Size to hold any industry type text, as well as STR_INDUSTRY_CARGOES_SELECT_INDUSTRY.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void ShortenCargoColumn(int column, int top, int bottom)
Shorten the cargo column to just the part between industries.
static int CountMatchingAcceptingIndustries(const std::span< const CargoType > cargoes)
Count how many industries have accepted cargoes in common with one of the supplied set.
void PlaceIndustry(int row, int col, IndustryType it)
Place an industry in the fields.
void OnResize() override
Called after the window got resized.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
Fields fields
Fields to display in the WID_IC_PANEL.
static int CountMatchingProducingIndustries(const std::span< const CargoType > cargoes)
Count how many industries have produced cargoes in common with one of the supplied set.
void ComputeIndustryDisplay(IndustryType displayed_it)
Compute what and where to display for industry type it.
void NotifySmallmap()
Notify smallmap that new displayed industries have been selected (in _displayed_industries).
bool CalculatePositionInWidget(Point pt, Point *fieldxy, Point *xy)
Calculate in which field was clicked, and within the field, at what position.
void ComputeCargoDisplay(CargoType cargo_type)
Compute what and where to display for cargo type cargo_type.
uint ind_cargo
If less than NUM_INDUSTRYTYPES, an industry type, else a cargo type + NUM_INDUSTRYTYPES.
static bool HasCommonValidCargo(const std::span< const CargoType > cargoes1, const std::span< const CargoType > cargoes2)
Do the two sets of cargoes have a valid cargo in common?
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
static bool HousesCanAccept(const std::span< const CargoType > cargoes)
Can houses be used as customers of the produced cargoes?
void OnDropdownSelect(WidgetID widget, int index, int) override
A dropdown option associated to this window has been selected.
Defines the data structure for constructing industry.
IndustryCallbackMasks callback_mask
Bitmask of industry callbacks that have to be called.
std::array< CargoType, INDUSTRY_NUM_INPUTS > accepts_cargo
16 accepted cargoes.
bool UsesOriginalEconomy() const
Determines whether this industrytype uses standard/newgrf production changes.
SubstituteGRFFileProps grf_prop
properties related to the grf file
StringID name
Displayed name of the industry.
bool IsRawIndustry() const
Is an industry with the spec a raw industry?
IndustryBehaviours behaviour
How this industry will behave, and how others entities can use it.
std::vector< IndustryTileLayout > layouts
List of possible tile layouts for the industry.
bool enabled
entity still available (by default true).newgrf can disable it, though
Money GetConstructionCost() const
Get the cost for constructing this industry.
PixelColour map_colour
colour used for the small map
CargoType cargo
Cargo type.
Definition industry.h:86
uint16_t waiting
Amount of cargo waiting to processed.
Definition industry.h:87
CargoType cargo
Cargo type.
Definition industry.h:74
HistoryData< ProducedHistory > history
History of cargo produced and transported for this month and 24 previous months.
Definition industry.h:77
Defines the internal data of a functional industry.
Definition industry.h:62
IndustryType type
type of industry.
Definition industry.h:115
bool IsCargoAccepted() const
Test if this industry accepts any cargo.
Definition industry.h:223
uint8_t prod_level
general production level
Definition industry.h:112
void RecomputeProductionMultipliers()
Recompute #production_rate for current prod_level.
EncodedString text
General text with additional information.
Definition industry.h:132
ProducedCargoes::iterator GetCargoProduced(CargoType cargo)
Get produced cargo slot for a specific cargo type.
Definition industry.h:180
ProducedCargoes produced
produced cargo slots
Definition industry.h:110
AcceptedCargoes accepted
accepted cargo slots
Definition industry.h:111
static uint16_t GetIndustryTypeCount(IndustryType type)
Get the count of industries for this type.
Definition industry.h:264
TileArea location
Location of the industry.
Definition industry.h:106
bool IsCargoProduced() const
Test if this industry produces any cargo.
Definition industry.h:229
Data structure describing how to show the list (what sort direction and criteria).
Size related data of the map.
Definition map_func.h:206
TileIndex GetCenterTile() const
Get the center tile.
TileIndex tile
The base tile of the area.
Colour for pixel/line drawing.
Definition gfx_type.h:405
Templated helper to make a PoolID a single POD value.
Definition pool_type.hpp:47
static Pool::IterateWrapper< Titem > Iterate(size_t from=0)
Returns an iterable ensemble of all valid Titem.
static Titem * Get(auto index)
Returns Titem with given index.
static size_t GetNumItems()
Returns number of valid items in the pool.
static bool IsValidID(auto index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
const Tindex index
Index of this pool item.
Data stored about a string that can be modified in the GUI.
int cancel_button
Widget button of parent window to simulate when pressing CANCEL in OSK.
static const int ACTION_CLEAR
Clear editbox.
constexpr uint Horizontal() const
Get total horizontal padding of RectPadding.
constexpr uint Vertical() const
Get total vertical padding of RectPadding.
Specification of a rectangle with absolute coordinates of all edges.
Rect WithWidth(int width, bool end) const
Copy Rect and set its width.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect WithHeight(int height, bool end=false) const
Copy Rect and set its height.
Rect Indent(int indent, bool end) const
Copy Rect and indent it from its position.
Rect Translate(int x, int y) const
Copy and translate Rect by x,y pixels.
bool Contains(const Point &pt) const
Test if a point falls inside this Rect.
uint step_height
Step-size of height resize changes.
Definition window_gui.h:213
String filter and state.
bool IsEmpty() const
Check whether any filter words were entered.
void SetFilterTerm(std::string_view str)
Set the term to filter on.
void ResetState()
Reset the matching state to process a new item.
bool GetState() const
Get the matching state of the current item.
std::string_view GetText() const
Get the current text.
Definition textbuf.cpp:284
Window * GetCallbackWnd()
Get the window that started the current highlighting.
High level window description.
Definition window_gui.h:168
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:274
void ReInit(int rx=0, int ry=0, bool reposition=false)
Re-initialize a window, and optionally change its size.
Definition window.cpp:980
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:815
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1809
std::map< WidgetID, QueryString * > querystrings
QueryString associated to WWT_EDITBOX widgets.
Definition window_gui.h:321
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:766
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
Definition window.cpp:3240
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:558
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:319
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:506
void RaiseWidgetWhenLowered(WidgetID widget_index)
Marks a widget as raised and dirty (redraw), when it is marked as lowered.
Definition window_gui.h:479
void DrawSortButtonState(WidgetID widget, SortButtonState state) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:798
ResizeInfo resize
Resize information.
Definition window_gui.h:315
void DisableWidget(WidgetID widget_index)
Sets a widget to disabled.
Definition window_gui.h:392
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1799
bool IsWidgetLowered(WidgetID widget_index) const
Gets the lowered state of a widget.
Definition window_gui.h:492
void RaiseButtons(bool autoraise=false)
Raise the buttons of the window.
Definition window.cpp:532
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:560
void SetTimeout()
Set the timeout flag of the window and initiate the timer.
Definition window_gui.h:356
int top
y position of top edge of the window
Definition window_gui.h:311
void HandleButtonClick(WidgetID widget)
Do all things to make a button look clicked and mark it to be unclicked in a few ticks.
Definition window.cpp:597
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1822
WindowFlags flags
Window flags.
Definition window_gui.h:301
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:315
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:382
AllWindows< false > Iterate
Iterate all windows in whatever order is easiest.
Definition window_gui.h:934
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:313
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:312
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:451
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:303
Stuff related to the text buffer GUI.
static bool IsTileType(Tile tile, TileType type)
Checks if a tile is a given tiletype.
Definition tile_map.h:150
constexpr TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition tile_type.h:100
@ Clear
A tile without any structures, i.e. grass, rocks, farm fields etc.
Functions related to tile highlights.
void ResetObjectToPlace()
Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows).
@ HT_RECT
rectangle (stations, depots, ...)
Definition of Interval and OneShot timers.
Definition of the Window system.
Base of the town class.
bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
Scrolls the viewport in a window to a given location.
bool ScrollMainWindowToTile(TileIndex tile, bool instant)
Scrolls the viewport of the main window to a given location.
Functions related to (drawing on) viewports.
@ ZOOM_IN
Zoom in (get more detailed view).
@ ZOOM_OUT
Zoom out (get helicopter view).
Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
Apply 'scroll' to a rect to be drawn in.
Definition widget.cpp:2516
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:49
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ WWT_INSET
Pressed (inset) panel, most commonly used as combo box text area.
Definition widget_type.h:40
@ WWT_PUSHIMGBTN
Normal push-button (no toggle button) with image caption.
@ WWT_EDITBOX
a textbox for typing
Definition widget_type.h:62
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:66
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:44
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:39
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:57
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:50
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:55
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:52
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:76
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:68
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:60
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:75
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:59
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:56
@ NWID_VIEWPORT
Nested widget containing a viewport.
Definition widget_type.h:73
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:61
@ WWT_DEBUGBOX
NewGRF debug box (at top-right of a window, between WWT_CAPTION and WWT_SHADEBOX)
Definition widget_type.h:54
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition widget_type.h:71
@ SZSP_HORIZONTAL
Display plane with zero size vertically, and filling and resizing horizontally.
void CloseWindowById(WindowClass cls, WindowNumber number, bool force, int data)
Close a window by its class and window number (if it is open).
Definition window.cpp:1195
Window * FindWindowByClass(WindowClass cls)
Find any window by its class.
Definition window.cpp:1168
Window * BringWindowToFrontById(WindowClass cls, WindowNumber number)
Find a window and make it the relative top-window on the screen.
Definition window.cpp:1278
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
Definition window.cpp:3318
Window functions not directly related to making/drawing windows.
@ Construction
This window is used for construction; close it whenever changing company.
@ DisableVpScroll
Window does not do autoscroll,.
@ SBS_DOWN
Sort ascending.
Definition window_gui.h:219
@ SBS_UP
Sort descending.
Definition window_gui.h:220
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h:144
int WidgetID
Widget ID.
Definition window_type.h:20
@ WC_INDUSTRY_DIRECTORY
Industry directory; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:50
@ WC_INDUSTRY_CARGOES
Industry cargoes chain; Window numbers:
@ WC_INDUSTRY_PRODUCTION
Industry production history graph; Window numbers:
@ WC_INDUSTRY_VIEW
Industry view; Window numbers:
@ WC_SMALLMAP
Small map; Window numbers:
@ WC_BUILD_INDUSTRY
Build industry; Window numbers:
Functions related to zooming.
ZoomLevel ScaleZoomGUI(ZoomLevel value)
Scale zoom level relative to GUI zoom.
Definition zoom_func.h:87
@ Industry
Default zoom level for the industry view.