OpenTTD Source 20260107-master-g88a467db19
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, MP_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<CMD_BUILD_INDUSTRY>::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<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
721 } else {
722 success = Command<CMD_BUILD_INDUSTRY>::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
1347 enum class SorterType : uint8_t {
1348 ByName,
1349 ByType,
1350 ByProduction,
1352 };
1353
1359 {
1360 if (this->produced_cargo_filter_criteria != cargo_type) {
1361 this->produced_cargo_filter_criteria = cargo_type;
1362 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1364
1365 this->industries.SetFilterState(is_filtering_necessary);
1366 this->industries.SetFilterType(0);
1367 this->industries.ForceRebuild();
1368 }
1369 }
1370
1376 {
1377 if (this->accepted_cargo_filter_criteria != cargo_type) {
1378 this->accepted_cargo_filter_criteria = cargo_type;
1379 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1381
1382 this->industries.SetFilterState(is_filtering_necessary);
1383 this->industries.SetFilterType(0);
1384 this->industries.ForceRebuild();
1385 }
1386 }
1387
1388 StringID GetCargoFilterLabel(CargoType cargo_type) const
1389 {
1390 switch (cargo_type) {
1391 case CargoFilterCriteria::CF_ANY: return STR_INDUSTRY_DIRECTORY_FILTER_ALL_TYPES;
1392 case CargoFilterCriteria::CF_NONE: return STR_INDUSTRY_DIRECTORY_FILTER_NONE;
1393 default: return CargoSpec::Get(cargo_type)->name;
1394 }
1395 }
1396
1401 {
1404
1405 this->industries.SetFilterFuncs(_industry_filter_funcs);
1406
1408
1409 this->industries.SetFilterState(is_filtering_necessary);
1410 }
1411
1417 {
1418 uint width = this->hscroll->GetCount();
1419 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1420 for (auto it = first; it != last; ++it) {
1421 width = std::max(width, GetStringBoundingBox(this->GetIndustryString(*it)).width);
1422 }
1423 return width;
1424 }
1425
1428 {
1429 if (this->industries.NeedRebuild()) {
1430 this->industries.clear();
1431 this->industries.reserve(Industry::GetNumItems());
1432
1433 for (const Industry *i : Industry::Iterate()) {
1434 if (this->string_filter.IsEmpty()) {
1435 this->industries.push_back(i);
1436 continue;
1437 }
1438 this->string_filter.ResetState();
1439 this->string_filter.AddLine(i->GetCachedName());
1440 if (this->string_filter.GetState()) this->industries.push_back(i);
1441 }
1442
1443 this->industries.RebuildDone();
1444
1445 auto filter = std::make_pair(this->accepted_cargo_filter_criteria, this->produced_cargo_filter_criteria);
1446
1447 this->industries.Filter(filter);
1448
1449 this->vscroll->SetCount(this->industries.size()); // Update scrollbar as well.
1450 }
1451
1452 IndustryDirectoryWindow::produced_cargo_filter = this->produced_cargo_filter_criteria;
1453 this->industries.Sort();
1454
1455 this->SetDirty();
1456 }
1457
1465 {
1466 if (!IsValidCargoType(p.cargo)) return -1;
1467 return ToPercent8(p.history[LAST_MONTH].PctTransported());
1468 }
1469
1478 {
1479 CargoType filter = IndustryDirectoryWindow::produced_cargo_filter;
1480 if (filter == CargoFilterCriteria::CF_NONE) return 0;
1481
1482 int percentage = 0, produced_cargo_count = 0;
1483 for (const auto &p : i->produced) {
1484 if (filter == CargoFilterCriteria::CF_ANY) {
1485 int transported = GetCargoTransportedPercentsIfValid(p);
1486 if (transported != -1) {
1487 produced_cargo_count++;
1488 percentage += transported;
1489 }
1490 if (produced_cargo_count == 0 && &p == &i->produced.back() && percentage == 0) {
1491 return transported;
1492 }
1493 } else if (filter == p.cargo) {
1495 }
1496 }
1497
1498 if (produced_cargo_count == 0) return percentage;
1499 return percentage / produced_cargo_count;
1500 }
1501
1503 static bool IndustryNameSorter(const Industry * const &a, const Industry * const &b, const CargoType &)
1504 {
1505 int r = StrNaturalCompare(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting).
1506 if (r == 0) return a->index < b->index;
1507 return r < 0;
1508 }
1509
1511 static bool IndustryTypeSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1512 {
1513 int it_a = 0;
1514 while (it_a != NUM_INDUSTRYTYPES && a->type != _sorted_industry_types[it_a]) it_a++;
1515 int it_b = 0;
1516 while (it_b != NUM_INDUSTRYTYPES && b->type != _sorted_industry_types[it_b]) it_b++;
1517 int r = it_a - it_b;
1518 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1519 }
1520
1522 static bool IndustryProductionSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1523 {
1524 if (filter == CargoFilterCriteria::CF_NONE) return IndustryTypeSorter(a, b, filter);
1525
1526 uint prod_a = 0, prod_b = 0;
1527 if (filter == CargoFilterCriteria::CF_ANY) {
1528 for (const auto &pa : a->produced) {
1529 if (IsValidCargoType(pa.cargo)) prod_a += pa.history[LAST_MONTH].production;
1530 }
1531 for (const auto &pb : b->produced) {
1532 if (IsValidCargoType(pb.cargo)) prod_b += pb.history[LAST_MONTH].production;
1533 }
1534 } else {
1535 if (auto ita = a->GetCargoProduced(filter); ita != std::end(a->produced)) prod_a = ita->history[LAST_MONTH].production;
1536 if (auto itb = b->GetCargoProduced(filter); itb != std::end(b->produced)) prod_b = itb->history[LAST_MONTH].production;
1537 }
1538 int r = prod_a - prod_b;
1539
1540 return (r == 0) ? IndustryTypeSorter(a, b, filter) : r < 0;
1541 }
1542
1544 static bool IndustryTransportedCargoSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1545 {
1547 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1548 }
1549
1550 StringID GetStringForNumCargo(size_t count) const
1551 {
1552 switch (count) {
1553 case 0: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1554 case 1: return STR_INDUSTRY_DIRECTORY_ITEM_PROD1;
1555 case 2: return STR_INDUSTRY_DIRECTORY_ITEM_PROD2;
1556 case 3: return STR_INDUSTRY_DIRECTORY_ITEM_PROD3;
1557 default: return STR_INDUSTRY_DIRECTORY_ITEM_PRODMORE;
1558 }
1559 }
1560
1566 std::string GetIndustryString(const Industry *i) const
1567 {
1568 const IndustrySpec *indsp = GetIndustrySpec(i->type);
1569
1570 /* Get industry productions (CargoType, production, suffix, transported) */
1571 struct CargoInfo {
1572 CargoType cargo_type;
1573 uint16_t production;
1574 uint transported;
1575 std::string suffix;
1576
1577 CargoInfo(CargoType cargo_type, uint16_t production, uint transported, std::string &&suffix) : cargo_type(cargo_type), production(production), transported(transported), suffix(std::move(suffix)) {}
1578 };
1579 std::vector<CargoInfo> cargos;
1580
1581 for (auto itp = std::begin(i->produced); itp != std::end(i->produced); ++itp) {
1582 if (!IsValidCargoType(itp->cargo)) continue;
1583 CargoSuffix cargo_suffix;
1584 GetCargoSuffix(CARGOSUFFIX_OUT, CST_DIR, i, i->type, indsp, itp->cargo, itp - std::begin(i->produced), cargo_suffix);
1585 cargos.emplace_back(itp->cargo, itp->history[LAST_MONTH].production, ToPercent8(itp->history[LAST_MONTH].PctTransported()), std::move(cargo_suffix.text));
1586 }
1587
1588 switch (static_cast<IndustryDirectoryWindow::SorterType>(this->industries.SortType())) {
1592 /* Sort by descending production, then descending transported */
1593 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1594 if (a.production != b.production) return a.production > b.production;
1595 return a.transported > b.transported;
1596 });
1597 break;
1598
1600 /* Sort by descending transported, then descending production */
1601 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1602 if (a.transported != b.transported) return a.transported > b.transported;
1603 return a.production > b.production;
1604 });
1605 break;
1606 }
1607
1608 /* If the produced cargo filter is active then move the filtered cargo to the beginning of the list,
1609 * because this is the one the player interested in, and that way it is not hidden in the 'n' more cargos */
1610 const CargoType cargo_type = this->produced_cargo_filter_criteria;
1611 if (cargo_type != CargoFilterCriteria::CF_ANY && cargo_type != CargoFilterCriteria::CF_NONE) {
1612 auto filtered_ci = std::ranges::find(cargos, cargo_type, &CargoInfo::cargo_type);
1613 if (filtered_ci != cargos.end()) {
1614 std::rotate(cargos.begin(), filtered_ci, filtered_ci + 1);
1615 }
1616 }
1617
1618 static constexpr size_t MAX_DISPLAYED_CARGOES = 3;
1619 std::array<StringParameter, 2 + 5 * MAX_DISPLAYED_CARGOES> params{};
1620 auto it = params.begin();
1621
1622 /* Industry name */
1623 *it++ = i->index;
1624
1625 /* Display first MAX_DISPLAYED_CARGOES cargoes */
1626 for (CargoInfo &ci : cargos | std::views::take(MAX_DISPLAYED_CARGOES)) {
1627 *it++ = STR_INDUSTRY_DIRECTORY_ITEM_INFO;
1628 *it++ = ci.cargo_type;
1629 *it++ = ci.production;
1630 *it++ = std::move(ci.suffix);
1631 *it++ = ci.transported;
1632 }
1633
1634 /* Undisplayed cargos if any */
1635 if (std::size(cargos) > MAX_DISPLAYED_CARGOES) *it++ = std::size(cargos) - MAX_DISPLAYED_CARGOES;
1636
1637 return GetStringWithArgs(GetStringForNumCargo(std::size(cargos)), {params.begin(), it});
1638 }
1639
1640public:
1642 {
1643 this->CreateNestedTree();
1644 this->vscroll = this->GetScrollbar(WID_ID_VSCROLLBAR);
1645 this->hscroll = this->GetScrollbar(WID_ID_HSCROLLBAR);
1646
1647 this->industries.SetListing(this->last_sorting);
1648 this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1649 this->industries.ForceRebuild();
1650
1651 this->FinishInitNested(0);
1652
1654
1656 this->industry_editbox.cancel_button = QueryString::ACTION_CLEAR;
1657 }
1658
1659 ~IndustryDirectoryWindow() override
1660 {
1661 this->last_sorting = this->industries.GetListing();
1662 }
1663
1664 void OnInit() override
1665 {
1666 this->SetCargoFilterArray();
1667 this->hscroll->SetCount(0);
1668 }
1669
1670 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1671 {
1672 switch (widget) {
1673 case WID_ID_CAPTION:
1674 return GetString(STR_INDUSTRY_DIRECTORY_CAPTION, this->vscroll->GetCount(), Industry::GetNumItems());
1675
1677 return GetString(IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1678
1680 return GetString(STR_INDUSTRY_DIRECTORY_ACCEPTED_CARGO_FILTER, this->GetCargoFilterLabel(this->accepted_cargo_filter_criteria));
1681
1683 return GetString(STR_INDUSTRY_DIRECTORY_PRODUCED_CARGO_FILTER, this->GetCargoFilterLabel(this->produced_cargo_filter_criteria));
1684
1685 default:
1686 return this->Window::GetWidgetString(widget, stringid);
1687 }
1688 }
1689
1690 void DrawWidget(const Rect &r, WidgetID widget) const override
1691 {
1692 switch (widget) {
1694 this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1695 break;
1696
1697 case WID_ID_INDUSTRY_LIST: {
1698 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
1699
1700 /* Setup a clipping rectangle... */
1701 DrawPixelInfo tmp_dpi;
1702 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
1703 /* ...but keep coordinates relative to the window. */
1704 tmp_dpi.left += ir.left;
1705 tmp_dpi.top += ir.top;
1706
1707 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
1708
1709 ir = ScrollRect(ir, *this->hscroll, 1);
1710
1711 if (this->industries.empty()) {
1712 DrawString(ir, STR_INDUSTRY_DIRECTORY_NONE);
1713 break;
1714 }
1715 const CargoType acf_cargo_type = this->accepted_cargo_filter_criteria;
1716 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1717 for (auto it = first; it != last; ++it) {
1718 TextColour tc = TC_FROMSTRING;
1719 if (acf_cargo_type != CargoFilterCriteria::CF_ANY && acf_cargo_type != CargoFilterCriteria::CF_NONE) {
1720 Industry *ind = const_cast<Industry *>(*it);
1721 if (IndustryTemporarilyRefusesCargo(ind, acf_cargo_type)) {
1722 tc = TC_GREY | TC_FORCED;
1723 }
1724 }
1725 DrawString(ir, this->GetIndustryString(*it), tc);
1726
1727 ir.top += this->resize.step_height;
1728 }
1729 break;
1730 }
1731 }
1732 }
1733
1734 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1735 {
1736 switch (widget) {
1737 case WID_ID_DROPDOWN_ORDER: {
1738 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->GetString());
1739 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1740 d.height += padding.height;
1741 size = maxdim(size, d);
1742 break;
1743 }
1744
1746 Dimension d = GetStringListBoundingBox(IndustryDirectoryWindow::sorter_names);
1747 d.width += padding.width;
1748 d.height += padding.height;
1749 size = maxdim(size, d);
1750 break;
1751 }
1752
1753 case WID_ID_INDUSTRY_LIST: {
1754 Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1755 fill.height = resize.height = d.height;
1756 d.height *= 5;
1757 d.width += padding.width;
1758 d.height += padding.height;
1759 size = maxdim(size, d);
1760 break;
1761 }
1762 }
1763 }
1764
1765 DropDownList BuildCargoDropDownList() const
1766 {
1767 DropDownList list;
1768
1769 /* Add item for disabling filtering. */
1770 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_ANY), CargoFilterCriteria::CF_ANY));
1771 /* Add item for industries not producing anything, e.g. power plants */
1772 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_NONE), CargoFilterCriteria::CF_NONE));
1773
1774 /* Add cargos */
1776 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1777 list.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
1778 }
1779
1780 return list;
1781 }
1782
1783 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1784 {
1785 switch (widget) {
1787 this->industries.ToggleSortOrder();
1788 this->SetDirty();
1789 break;
1790
1792 ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1793 break;
1794
1795 case WID_ID_FILTER_BY_ACC_CARGO: // Cargo filter dropdown
1796 ShowDropDownList(this, this->BuildCargoDropDownList(), this->accepted_cargo_filter_criteria, widget);
1797 break;
1798
1799 case WID_ID_FILTER_BY_PROD_CARGO: // Cargo filter dropdown
1800 ShowDropDownList(this, this->BuildCargoDropDownList(), this->produced_cargo_filter_criteria, widget);
1801 break;
1802
1803 case WID_ID_INDUSTRY_LIST: {
1804 auto it = this->vscroll->GetScrolledItemFromWidget(this->industries, pt.y, this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.top);
1805 if (it != this->industries.end()) {
1806 if (_ctrl_pressed) {
1807 ShowExtraViewportWindow((*it)->location.tile);
1808 } else {
1809 ScrollMainWindowToTile((*it)->location.tile);
1810 }
1811 }
1812 break;
1813 }
1814 }
1815 }
1816
1817 void OnDropdownSelect(WidgetID widget, int index, int) override
1818 {
1819 switch (widget) {
1821 if (this->industries.SortType() != index) {
1822 this->industries.SetSortType(index);
1824 }
1825 break;
1826 }
1827
1829 this->SetAcceptedCargoFilter(index);
1831 break;
1832 }
1833
1835 this->SetProducedCargoFilter(index);
1837 break;
1838 }
1839 }
1840 }
1841
1842 void OnResize() override
1843 {
1844 this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Vertical());
1845 this->hscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Horizontal());
1846 }
1847
1848 void OnEditboxChanged(WidgetID wid) override
1849 {
1850 if (wid == WID_ID_FILTER) {
1851 this->string_filter.SetFilterTerm(this->industry_editbox.text.GetText());
1852 this->InvalidateData(IDIWD_FORCE_REBUILD);
1853 }
1854 }
1855
1856 void OnPaint() override
1857 {
1858 if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1859 this->hscroll->SetCount(this->GetIndustryListWidth());
1860 this->DrawWidgets();
1861 }
1862
1864 const IntervalTimer<TimerWindow> rebuild_interval = {std::chrono::seconds(3), [this](auto) {
1865 this->industries.ForceResort();
1867 }};
1868
1874 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1875 {
1876 switch (data) {
1877 case IDIWD_FORCE_REBUILD:
1878 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1879 this->industries.ForceRebuild();
1880 break;
1881
1882 case IDIWD_PRODUCTION_CHANGE:
1883 if (this->industries.SortType() == 2) this->industries.ForceResort();
1884 break;
1885
1886 default:
1887 this->industries.ForceResort();
1888 break;
1889 }
1890 }
1891
1892 static inline HotkeyList hotkeys {"industrydirectory", {
1893 Hotkey('F', "focus_filter_box", WID_ID_FILTER),
1894 }};
1895};
1896
1897Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1898
1899/* Available station sorting functions. */
1900const std::initializer_list<GUIIndustryList::SortFunction * const> IndustryDirectoryWindow::sorter_funcs = {
1901 &IndustryNameSorter,
1902 &IndustryTypeSorter,
1903 &IndustryProductionSorter,
1904 &IndustryTransportedCargoSorter
1905};
1906
1907CargoType IndustryDirectoryWindow::produced_cargo_filter = CargoFilterCriteria::CF_ANY;
1908
1909
1912 WDP_AUTO, "list_industries", 428, 190,
1914 {},
1916 &IndustryDirectoryWindow::hotkeys
1917);
1918
1919void ShowIndustryDirectory()
1920{
1921 AllocateWindowDescFront<IndustryDirectoryWindow>(_industry_directory_desc, 0);
1922}
1923
1925static constexpr std::initializer_list<NWidgetPart> _nested_industry_cargoes_widgets = {
1927 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1928 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION),
1929 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1930 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1931 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1932 EndContainer(),
1936 EndContainer(),
1938 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1939 SetStringTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1940 NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1941 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1942 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1943 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1944 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1945 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1946 EndContainer(),
1947};
1948
1951 WDP_AUTO, "industry_cargoes", 300, 210,
1953 {},
1955);
1956
1966
1967static const uint MAX_CARGOES = 16;
1968
1972 static int blob_distance;
1973
1979
1982
1985 static int industry_width;
1986 static uint max_cargoes;
1987
1988 using Cargoes = uint16_t;
1989 static_assert(std::numeric_limits<Cargoes>::digits >= MAX_CARGOES);
1990
1992 union {
1993 struct {
1994 IndustryType ind_type;
1995 std::array<CargoType, MAX_CARGOES> other_produced;
1996 std::array<CargoType, MAX_CARGOES> other_accepted;
1998 struct {
1999 std::array<CargoType, MAX_CARGOES> vertical_cargoes;
2002 uint8_t num_cargoes;
2003 uint8_t top_end;
2004 uint8_t bottom_end;
2006 struct {
2007 std::array<CargoType, MAX_CARGOES> cargoes;
2011 } u{}; // Data for each type.
2012
2018 {
2019 this->type = type;
2020 }
2021
2027 void MakeIndustry(IndustryType ind_type)
2028 {
2029 this->type = CFT_INDUSTRY;
2030 this->u.industry.ind_type = ind_type;
2031 std::fill(std::begin(this->u.industry.other_accepted), std::end(this->u.industry.other_accepted), INVALID_CARGO);
2032 std::fill(std::begin(this->u.industry.other_produced), std::end(this->u.industry.other_produced), INVALID_CARGO);
2033 }
2034
2041 int ConnectCargo(CargoType cargo, bool producer)
2042 {
2043 assert(this->type == CFT_CARGO);
2044 if (!IsValidCargoType(cargo)) return -1;
2045
2046 /* Find the vertical cargo column carrying the cargo. */
2047 int column = -1;
2048 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2049 if (cargo == this->u.cargo.vertical_cargoes[i]) {
2050 column = i;
2051 break;
2052 }
2053 }
2054 if (column < 0) return -1;
2055
2056 if (producer) {
2057 assert(!HasBit(this->u.cargo.supp_cargoes, column));
2058 SetBit(this->u.cargo.supp_cargoes, column);
2059 } else {
2060 assert(!HasBit(this->u.cargo.cust_cargoes, column));
2061 SetBit(this->u.cargo.cust_cargoes, column);
2062 }
2063 return column;
2064 }
2065
2071 {
2072 assert(this->type == CFT_CARGO);
2073
2074 return this->u.cargo.supp_cargoes != 0 || this->u.cargo.cust_cargoes != 0;
2075 }
2076
2082 void MakeCargo(const std::span<const CargoType> cargoes)
2083 {
2084 this->type = CFT_CARGO;
2085 assert(std::size(cargoes) <= std::size(this->u.cargo.vertical_cargoes));
2086 auto insert = std::copy_if(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo.vertical_cargoes), IsValidCargoType);
2087 this->u.cargo.num_cargoes = static_cast<uint8_t>(std::distance(std::begin(this->u.cargo.vertical_cargoes), insert));
2088 CargoTypeComparator comparator;
2089 std::sort(std::begin(this->u.cargo.vertical_cargoes), insert, comparator);
2090 std::fill(insert, std::end(this->u.cargo.vertical_cargoes), INVALID_CARGO);
2091 this->u.cargo.top_end = false;
2092 this->u.cargo.bottom_end = false;
2093 this->u.cargo.supp_cargoes = 0;
2094 this->u.cargo.cust_cargoes = 0;
2095 }
2096
2102 void MakeCargoLabel(const std::span<const CargoType> cargoes, bool left_align)
2103 {
2104 this->type = CFT_CARGO_LABEL;
2105 assert(std::size(cargoes) <= std::size(this->u.cargo_label.cargoes));
2106 auto insert = std::copy(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo_label.cargoes));
2107 std::fill(insert, std::end(this->u.cargo_label.cargoes), INVALID_CARGO);
2108 this->u.cargo_label.left_align = left_align;
2109 }
2110
2116 {
2117 this->type = CFT_HEADER;
2118 this->u.header = textid;
2119 }
2120
2126 int GetCargoBase(int xpos) const
2127 {
2128 assert(this->type == CFT_CARGO);
2129 int n = this->u.cargo.num_cargoes;
2130
2131 return xpos + cargo_field_width / 2 - (CargoesField::cargo_line.width * n + CargoesField::cargo_space.width * (n - 1)) / 2;
2132 }
2133
2139 void Draw(int xpos, int ypos) const
2140 {
2141 switch (this->type) {
2142 case CFT_EMPTY:
2143 case CFT_SMALL_EMPTY:
2144 break;
2145
2146 case CFT_HEADER:
2147 ypos += (small_height - GetCharacterHeight(FS_NORMAL)) / 2;
2148 DrawString(xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
2149 break;
2150
2151 case CFT_INDUSTRY: {
2152 int ypos1 = ypos + vert_inter_industry_space / 2;
2153 int ypos2 = ypos + normal_height - 1 - vert_inter_industry_space / 2;
2154 int xpos2 = xpos + industry_width - 1;
2155 DrawRectOutline({xpos, ypos1, xpos2, ypos2}, INDUSTRY_LINE_COLOUR);
2157 if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2158 const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
2159 DrawString(xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
2160
2161 /* Draw the industry legend. */
2162 int blob_left, blob_right;
2163 if (_current_text_dir == TD_RTL) {
2164 blob_right = xpos2 - blob_distance;
2165 blob_left = blob_right - CargoesField::legend.width;
2166 } else {
2167 blob_left = xpos + blob_distance;
2168 blob_right = blob_left + CargoesField::legend.width;
2169 }
2170 GfxFillRect(blob_left, ypos2 - blob_distance - CargoesField::legend.height, blob_right, ypos2 - blob_distance, PC_BLACK); // Border
2171 GfxFillRect(blob_left + 1, ypos2 - blob_distance - CargoesField::legend.height + 1, blob_right - 1, ypos2 - blob_distance - 1, indsp->map_colour);
2172 } else {
2173 DrawString(xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
2174 }
2175
2176 /* Draw the other_produced/other_accepted cargoes. */
2177 std::span<const CargoType> other_right, other_left;
2178 if (_current_text_dir == TD_RTL) {
2179 other_right = this->u.industry.other_accepted;
2180 other_left = this->u.industry.other_produced;
2181 } else {
2182 other_right = this->u.industry.other_produced;
2183 other_left = this->u.industry.other_accepted;
2184 }
2186 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2187 if (IsValidCargoType(other_right[i])) {
2188 const CargoSpec *csp = CargoSpec::Get(other_right[i]);
2189 int xp = xpos + industry_width + CargoesField::cargo_stub.width;
2190 DrawHorConnection(xpos + industry_width, xp - 1, ypos1, csp);
2191 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2192 }
2193 if (IsValidCargoType(other_left[i])) {
2194 const CargoSpec *csp = CargoSpec::Get(other_left[i]);
2195 int xp = xpos - CargoesField::cargo_stub.width;
2196 DrawHorConnection(xp + 1, xpos - 1, ypos1, csp);
2197 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2198 }
2200 }
2201 break;
2202 }
2203
2204 case CFT_CARGO: {
2205 int cargo_base = this->GetCargoBase(xpos);
2206 int top = ypos + (this->u.cargo.top_end ? vert_inter_industry_space / 2 + 1 : 0);
2207 int bot = ypos - (this->u.cargo.bottom_end ? vert_inter_industry_space / 2 + 1 : 0) + normal_height - 1;
2208 int colpos = cargo_base;
2209 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2210 if (this->u.cargo.top_end) GfxDrawLine(colpos, top - 1, colpos + CargoesField::cargo_line.width - 1, top - 1, CARGO_LINE_COLOUR);
2211 if (this->u.cargo.bottom_end) GfxDrawLine(colpos, bot + 1, colpos + CargoesField::cargo_line.width - 1, bot + 1, CARGO_LINE_COLOUR);
2212 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2213 colpos++;
2214 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
2215 GfxFillRect(colpos, top, colpos + CargoesField::cargo_line.width - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
2216 colpos += CargoesField::cargo_line.width - 2;
2217 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2218 colpos += 1 + CargoesField::cargo_space.width;
2219 }
2220
2221 Cargoes hor_left, hor_right;
2222 if (_current_text_dir == TD_RTL) {
2223 hor_left = this->u.cargo.cust_cargoes;
2224 hor_right = this->u.cargo.supp_cargoes;
2225 } else {
2226 hor_left = this->u.cargo.supp_cargoes;
2227 hor_right = this->u.cargo.cust_cargoes;
2228 }
2230 for (uint i = 0; i < MAX_CARGOES; i++) {
2231 if (HasBit(hor_left, i)) {
2232 int col = i;
2233 int dx = 0;
2234 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2235 for (; col > 0; col--) {
2236 int lf = cargo_base + col * CargoesField::cargo_line.width + (col - 1) * CargoesField::cargo_space.width;
2237 DrawHorConnection(lf, lf + CargoesField::cargo_space.width - dx, ypos, csp);
2238 dx = 1;
2239 }
2240 DrawHorConnection(xpos, cargo_base - dx, ypos, csp);
2241 }
2242 if (HasBit(hor_right, i)) {
2243 int col = i;
2244 int dx = 0;
2245 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2246 for (; col < this->u.cargo.num_cargoes - 1; col++) {
2247 int lf = cargo_base + (col + 1) * CargoesField::cargo_line.width + col * CargoesField::cargo_space.width;
2248 DrawHorConnection(lf + dx - 1, lf + CargoesField::cargo_space.width - 1, ypos, csp);
2249 dx = 1;
2250 }
2251 DrawHorConnection(cargo_base + col * CargoesField::cargo_space.width + (col + 1) * CargoesField::cargo_line.width - 1 + dx, xpos + CargoesField::cargo_field_width - 1, ypos, csp);
2252 }
2254 }
2255 break;
2256 }
2257
2258 case CFT_CARGO_LABEL:
2260 for (uint i = 0; i < MAX_CARGOES; i++) {
2261 if (IsValidCargoType(this->u.cargo_label.cargoes[i])) {
2262 const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
2263 DrawString(xpos + WidgetDimensions::scaled.framerect.left, xpos + industry_width - 1 - WidgetDimensions::scaled.framerect.right, ypos, csp->name, TC_WHITE,
2264 (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
2265 }
2267 }
2268 break;
2269
2270 default:
2271 NOT_REACHED();
2272 }
2273 }
2274
2282 CargoType CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
2283 {
2284 assert(this->type == CFT_CARGO);
2285
2286 /* Vertical matching. */
2287 int cpos = this->GetCargoBase(0);
2288 uint col;
2289 for (col = 0; col < this->u.cargo.num_cargoes; col++) {
2290 if (pt.x < cpos) break;
2291 if (pt.x < cpos + static_cast<int>(CargoesField::cargo_line.width)) return this->u.cargo.vertical_cargoes[col];
2293 }
2294 /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
2295
2297 uint row;
2298 for (row = 0; row < MAX_CARGOES; row++) {
2299 if (pt.y < vpos) return INVALID_CARGO;
2300 if (pt.y < vpos + static_cast<int>(CargoesField::cargo_line.height)) break;
2302 }
2303 if (row == MAX_CARGOES) return INVALID_CARGO;
2304
2305 /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
2306 if (col == 0) {
2307 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2308 if (left != nullptr) {
2309 if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
2310 if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
2311 }
2312 return INVALID_CARGO;
2313 }
2314 if (col == this->u.cargo.num_cargoes) {
2315 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2316 if (right != nullptr) {
2317 if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
2318 if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
2319 }
2320 return INVALID_CARGO;
2321 }
2322 if (row >= col) {
2323 /* Clicked somewhere in-between vertical cargo connection.
2324 * Since the horizontal connection is made in the same order as the vertical list, the above condition
2325 * ensures we are left-below the main diagonal, thus at the supplying side.
2326 */
2327 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2328 return INVALID_CARGO;
2329 }
2330 /* Clicked at a customer connection. */
2331 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2332 return INVALID_CARGO;
2333 }
2334
2341 {
2342 assert(this->type == CFT_CARGO_LABEL);
2343
2345 uint row;
2346 for (row = 0; row < MAX_CARGOES; row++) {
2347 if (pt.y < vpos) return INVALID_CARGO;
2348 if (pt.y < vpos + GetCharacterHeight(FS_NORMAL)) break;
2350 }
2351 if (row == MAX_CARGOES) return INVALID_CARGO;
2352 return this->u.cargo_label.cargoes[row];
2353 }
2354
2355private:
2363 static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
2364 {
2365 GfxDrawLine(left, top, right, top, CARGO_LINE_COLOUR);
2366 GfxFillRect(left, top + 1, right, top + CargoesField::cargo_line.height - 2, csp->legend_colour, FILLRECT_OPAQUE);
2367 GfxDrawLine(left, top + CargoesField::cargo_line.height - 1, right, top + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2368 }
2369};
2370
2371static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::produced_cargo)>);
2372static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::accepts_cargo)>);
2373
2379
2386
2388
2391
2395
2401 {
2402 CargoesField *ind_fld = this->columns + column;
2403 CargoesField *cargo_fld = this->columns + column + 1;
2404 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2405
2406 std::fill(std::begin(ind_fld->u.industry.other_produced), std::end(ind_fld->u.industry.other_produced), INVALID_CARGO);
2407
2408 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2409 CargoType others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
2410 int other_count = 0;
2411
2412 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2413 assert(CargoesField::max_cargoes <= std::size(indsp->produced_cargo));
2414 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2415 int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
2416 if (col < 0) others[other_count++] = indsp->produced_cargo[i];
2417 }
2418
2419 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2420 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2421 if (HasBit(cargo_fld->u.cargo.supp_cargoes, i)) ind_fld->u.industry.other_produced[i] = others[--other_count];
2422 }
2423 } else {
2424 /* Houses only display cargo that towns produce. */
2425 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2426 CargoType cargo_type = cargo_fld->u.cargo.vertical_cargoes[i];
2428 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) cargo_fld->ConnectCargo(cargo_type, true);
2429 }
2430 }
2431 }
2432
2438 void MakeCargoLabel(int column, bool accepting)
2439 {
2440 CargoType cargoes[MAX_CARGOES];
2441 std::fill(std::begin(cargoes), std::end(cargoes), INVALID_CARGO);
2442
2443 CargoesField *label_fld = this->columns + column;
2444 CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2445
2446 assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2447 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2448 int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2449 if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2450 }
2451 label_fld->MakeCargoLabel(cargoes, accepting);
2452 }
2453
2454
2460 {
2461 CargoesField *ind_fld = this->columns + column;
2462 CargoesField *cargo_fld = this->columns + column - 1;
2463 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2464
2465 std::fill(std::begin(ind_fld->u.industry.other_accepted), std::end(ind_fld->u.industry.other_accepted), INVALID_CARGO);
2466
2467 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2468 CargoType others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2469 int other_count = 0;
2470
2471 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2472 assert(CargoesField::max_cargoes <= std::size(indsp->accepts_cargo));
2473 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2474 int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2475 if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2476 }
2477
2478 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2479 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2480 if (!HasBit(cargo_fld->u.cargo.cust_cargoes, i)) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2481 }
2482 } else {
2483 /* Houses only display what is demanded. */
2484 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2485 for (const auto &hs : HouseSpec::Specs()) {
2486 if (!hs.enabled) continue;
2487
2488 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2489 if (hs.cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs.accepts_cargo[j]) {
2490 cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2491 goto next_cargo;
2492 }
2493 }
2494 }
2495next_cargo: ;
2496 }
2497 }
2498 }
2499};
2500
2501
2530 typedef std::vector<CargoesRow> Fields;
2531
2532 Fields fields{};
2533 uint ind_cargo = 0;
2536 Scrollbar *vscroll = nullptr;
2537
2539 {
2540 this->OnInit();
2541 this->CreateNestedTree();
2542 this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2543 this->FinishInitNested(0);
2544 this->OnInvalidateData(id);
2545 }
2546
2547 void OnInit() override
2548 {
2549 /* Initialize static CargoesField size variables. */
2550 Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2551 d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2554 CargoesField::small_height = d.height;
2555
2556 /* Size of the legend blob -- slightly larger than the smallmap legend blob. */
2558 CargoesField::legend.width = CargoesField::legend.height * 9 / 6;
2559
2560 /* Size of cargo lines. */
2563
2564 /* Size of border between cargo lines and industry boxes. */
2567
2568 /* Size of space between cargo lines. */
2571
2572 /* Size of cargo stub (unconnected cargo line.) */
2574 CargoesField::cargo_stub.height = CargoesField::cargo_line.height; /* Unused */
2575
2578
2579 /* Decide about the size of the box holding the text of an industry type. */
2580 this->ind_textsize.width = 0;
2581 this->ind_textsize.height = 0;
2582 CargoesField::max_cargoes = 0;
2583 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2584 const IndustrySpec *indsp = GetIndustrySpec(it);
2585 if (!indsp->enabled) continue;
2587 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->accepts_cargo, IsValidCargoType));
2588 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->produced_cargo, IsValidCargoType));
2589 }
2590 d.width = std::max(d.width, this->ind_textsize.width);
2591 d.height = this->ind_textsize.height;
2592 this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2593
2594 /* Compute max size of the cargo texts. */
2595 this->cargo_textsize.width = 0;
2596 this->cargo_textsize.height = 0;
2597 for (const CargoSpec *csp : CargoSpec::Iterate()) {
2598 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(csp->name));
2599 }
2600 d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2601 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2602
2604 /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2606 d.height = std::max(d.height + WidgetDimensions::scaled.frametext.Vertical(), min_ind_height);
2607
2610
2611 /* Width of a #CFT_CARGO field. */
2613 }
2614
2615 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
2616 {
2617 switch (widget) {
2618 case WID_IC_PANEL:
2619 fill.height = resize.height = CargoesField::normal_height;
2622 break;
2623
2625 size.width = std::max(size.width, this->ind_textsize.width + padding.width);
2626 break;
2627
2629 size.width = std::max(size.width, this->cargo_textsize.width + padding.width);
2630 break;
2631 }
2632 }
2633
2634 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
2635 {
2636 if (widget != WID_IC_CAPTION) return this->Window::GetWidgetString(widget, stringid);
2637
2638 if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2639 const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2640 return GetString(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, indsp->name);
2641 } else {
2642 const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2643 return GetString(STR_INDUSTRY_CARGOES_CARGO_CAPTION, csp->name);
2644 }
2645 }
2646
2653 static bool HasCommonValidCargo(const std::span<const CargoType> cargoes1, const std::span<const CargoType> cargoes2)
2654 {
2655 for (const CargoType cargo_type1 : cargoes1) {
2656 if (!IsValidCargoType(cargo_type1)) continue;
2657 for (const CargoType cargo_type2 : cargoes2) {
2658 if (cargo_type1 == cargo_type2) return true;
2659 }
2660 }
2661 return false;
2662 }
2663
2669 static bool HousesCanSupply(const std::span<const CargoType> cargoes)
2670 {
2671 for (const CargoType cargo_type : cargoes) {
2672 if (!IsValidCargoType(cargo_type)) continue;
2674 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) return true;
2675 }
2676 return false;
2677 }
2678
2684 static bool HousesCanAccept(const std::span<const CargoType> cargoes)
2685 {
2686 HouseZones climate_mask = GetClimateMaskForLandscape();
2687
2688 for (const CargoType cargo_type : cargoes) {
2689 if (!IsValidCargoType(cargo_type)) continue;
2690
2691 for (const auto &hs : HouseSpec::Specs()) {
2692 if (!hs.enabled || !hs.building_availability.Any(climate_mask)) continue;
2693
2694 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2695 if (hs.cargo_acceptance[j] > 0 && cargo_type == hs.accepts_cargo[j]) return true;
2696 }
2697 }
2698 }
2699 return false;
2700 }
2701
2707 static int CountMatchingAcceptingIndustries(const std::span<const CargoType> cargoes)
2708 {
2709 int count = 0;
2710 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2711 const IndustrySpec *indsp = GetIndustrySpec(it);
2712 if (!indsp->enabled) continue;
2713
2714 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) count++;
2715 }
2716 return count;
2717 }
2718
2724 static int CountMatchingProducingIndustries(const std::span<const CargoType> cargoes)
2725 {
2726 int count = 0;
2727 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2728 const IndustrySpec *indsp = GetIndustrySpec(it);
2729 if (!indsp->enabled) continue;
2730
2731 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) count++;
2732 }
2733 return count;
2734 }
2735
2742 void ShortenCargoColumn(int column, int top, int bottom)
2743 {
2744 while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2745 this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2746 top++;
2747 }
2748 this->fields[top].columns[column].u.cargo.top_end = true;
2749
2750 while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2751 this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2752 bottom--;
2753 }
2754 this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2755 }
2756
2763 void PlaceIndustry(int row, int col, IndustryType it)
2764 {
2765 assert(this->fields[row].columns[col].type == CFT_EMPTY);
2766 this->fields[row].columns[col].MakeIndustry(it);
2767 if (col == 0) {
2768 this->fields[row].ConnectIndustryProduced(col);
2769 } else {
2770 this->fields[row].ConnectIndustryAccepted(col);
2771 }
2772 }
2773
2778 {
2779 if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2780
2781 /* Only notify the smallmap window if it exists. In particular, do not
2782 * bring it to the front to prevent messing up any nice layout of the user. */
2784 }
2785
2790 void ComputeIndustryDisplay(IndustryType displayed_it)
2791 {
2792 this->ind_cargo = displayed_it;
2793 _displayed_industries.reset();
2794 _displayed_industries.set(displayed_it);
2795
2796 this->fields.clear();
2797 CargoesRow &first_row = this->fields.emplace_back();
2798 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2799 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2800 first_row.columns[2].MakeEmpty(CFT_SMALL_EMPTY);
2801 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2802 first_row.columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2803
2804 const IndustrySpec *central_sp = GetIndustrySpec(displayed_it);
2805 bool houses_supply = HousesCanSupply(central_sp->accepts_cargo);
2806 bool houses_accept = HousesCanAccept(central_sp->produced_cargo);
2807 /* Make a field consisting of two cargo columns. */
2808 int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo) + houses_supply;
2809 int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo) + houses_accept;
2810 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.
2811 for (int i = 0; i < num_indrows; i++) {
2812 CargoesRow &row = this->fields.emplace_back();
2813 row.columns[0].MakeEmpty(CFT_EMPTY);
2814 row.columns[1].MakeCargo(central_sp->accepts_cargo);
2815 row.columns[2].MakeEmpty(CFT_EMPTY);
2816 row.columns[3].MakeCargo(central_sp->produced_cargo);
2817 row.columns[4].MakeEmpty(CFT_EMPTY);
2818 }
2819 /* Add central industry. */
2820 int central_row = 1 + num_indrows / 2;
2821 this->fields[central_row].columns[2].MakeIndustry(displayed_it);
2822 this->fields[central_row].ConnectIndustryProduced(2);
2823 this->fields[central_row].ConnectIndustryAccepted(2);
2824
2825 /* Add cargo labels. */
2826 this->fields[central_row - 1].MakeCargoLabel(2, true);
2827 this->fields[central_row + 1].MakeCargoLabel(2, false);
2828
2829 /* Add suppliers and customers of the 'it' industry. */
2830 int supp_count = 0;
2831 int cust_count = 0;
2832 for (IndustryType it : _sorted_industry_types) {
2833 const IndustrySpec *indsp = GetIndustrySpec(it);
2834 if (!indsp->enabled) continue;
2835
2836 if (HasCommonValidCargo(central_sp->accepts_cargo, indsp->produced_cargo)) {
2837 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2838 _displayed_industries.set(it);
2839 supp_count++;
2840 }
2841 if (HasCommonValidCargo(central_sp->produced_cargo, indsp->accepts_cargo)) {
2842 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2843 _displayed_industries.set(it);
2844 cust_count++;
2845 }
2846 }
2847 if (houses_supply) {
2848 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2849 supp_count++;
2850 }
2851 if (houses_accept) {
2852 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2853 cust_count++;
2854 }
2855
2856 this->ShortenCargoColumn(1, 1, num_indrows);
2857 this->ShortenCargoColumn(3, 1, num_indrows);
2858 this->vscroll->SetCount(num_indrows);
2859 this->SetDirty();
2860 this->NotifySmallmap();
2861 }
2862
2868 {
2869 this->ind_cargo = cargo_type + NUM_INDUSTRYTYPES;
2870 _displayed_industries.reset();
2871
2872 this->fields.clear();
2873 CargoesRow &first_row = this->fields.emplace_back();
2874 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2875 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2876 first_row.columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2877 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2878 first_row.columns[4].MakeEmpty(CFT_SMALL_EMPTY);
2879
2880 auto cargoes = std::span(&cargo_type, 1);
2881 bool houses_supply = HousesCanSupply(cargoes);
2882 bool houses_accept = HousesCanAccept(cargoes);
2883 int num_supp = CountMatchingProducingIndustries(cargoes) + houses_supply + 1; // Ensure room for the cargo label.
2884 int num_cust = CountMatchingAcceptingIndustries(cargoes) + houses_accept;
2885 int num_indrows = std::max(num_supp, num_cust);
2886 for (int i = 0; i < num_indrows; i++) {
2887 CargoesRow &row = this->fields.emplace_back();
2888 row.columns[0].MakeEmpty(CFT_EMPTY);
2889 row.columns[1].MakeCargo(cargoes);
2890 row.columns[2].MakeEmpty(CFT_EMPTY);
2891 row.columns[3].MakeEmpty(CFT_EMPTY);
2892 row.columns[4].MakeEmpty(CFT_EMPTY);
2893 }
2894
2895 this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2896
2897 /* Add suppliers and customers of the cargo. */
2898 int supp_count = 0;
2899 int cust_count = 0;
2900 for (IndustryType it : _sorted_industry_types) {
2901 const IndustrySpec *indsp = GetIndustrySpec(it);
2902 if (!indsp->enabled) continue;
2903
2904 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) {
2905 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2906 _displayed_industries.set(it);
2907 supp_count++;
2908 }
2909 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) {
2910 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2911 _displayed_industries.set(it);
2912 cust_count++;
2913 }
2914 }
2915 if (houses_supply) {
2916 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2917 supp_count++;
2918 }
2919 if (houses_accept) {
2920 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2921 cust_count++;
2922 }
2923
2924 this->ShortenCargoColumn(1, 1, num_indrows);
2925 this->vscroll->SetCount(num_indrows);
2926 this->SetDirty();
2927 this->NotifySmallmap();
2928 }
2929
2937 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
2938 {
2939 if (!gui_scope) return;
2940 if (data == NUM_INDUSTRYTYPES) {
2942 return;
2943 }
2944
2945 assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2946 this->ComputeIndustryDisplay(data);
2947 }
2948
2949 void DrawWidget(const Rect &r, WidgetID widget) const override
2950 {
2951 if (widget != WID_IC_PANEL) return;
2952
2953 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
2954 DrawPixelInfo tmp_dpi;
2955 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
2956 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2957
2959 if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::cargo_field_width) / 2;
2960 int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2961
2962 const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2963 int vpos = WidgetDimensions::scaled.frametext.top - WidgetDimensions::scaled.bevel.top - this->vscroll->GetPosition() * nwp->resize_y;
2964 int row_height = CargoesField::small_height;
2965 for (const auto &field : this->fields) {
2966 if (vpos + row_height >= 0) {
2967 int xpos = left_pos;
2968 int col, dir;
2969 if (_current_text_dir == TD_RTL) {
2970 col = last_column;
2971 dir = -1;
2972 } else {
2973 col = 0;
2974 dir = 1;
2975 }
2976 while (col >= 0 && col <= last_column) {
2977 field.columns[col].Draw(xpos, vpos);
2979 col += dir;
2980 }
2981 }
2982 vpos += row_height;
2983 if (vpos >= height) break;
2984 row_height = CargoesField::normal_height;
2985 }
2986 }
2987
2996 {
2997 const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2998 pt.x -= nw->pos_x;
2999 pt.y -= nw->pos_y;
3000
3001 int vpos = WidgetDimensions::scaled.frametext.top + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
3002 if (pt.y < vpos) return false;
3003
3004 int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
3005 if (row + 1 >= (int)this->fields.size()) return false;
3006 vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
3007 row++; // rebase row to match index of this->fields.
3008
3010 if (pt.x < xpos) return false;
3011 int column;
3012 for (column = 0; column <= 5; column++) {
3014 if (pt.x < xpos + width) break;
3015 xpos += width;
3016 }
3017 int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
3018 if (column > num_columns) return false;
3019 xpos = pt.x - xpos;
3020
3021 /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
3022 fieldxy->y = row;
3023 xy->y = vpos;
3024 if (_current_text_dir == TD_RTL) {
3025 fieldxy->x = num_columns - column;
3026 xy->x = ((column & 1) ? CargoesField::cargo_field_width : CargoesField::industry_width) - xpos;
3027 } else {
3028 fieldxy->x = column;
3029 xy->x = xpos;
3030 }
3031 return true;
3032 }
3033
3034 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
3035 {
3036 switch (widget) {
3037 case WID_IC_PANEL: {
3038 Point fieldxy, xy;
3039 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
3040
3041 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3042 switch (fld->type) {
3043 case CFT_INDUSTRY:
3045 break;
3046
3047 case CFT_CARGO: {
3048 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3049 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3050 CargoType cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3051 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3052 break;
3053 }
3054
3055 case CFT_CARGO_LABEL: {
3056 CargoType cargo_type = fld->CargoLabelClickedAt(xy);
3057 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3058 break;
3059 }
3060
3061 default:
3062 break;
3063 }
3064 break;
3065 }
3066
3067 case WID_IC_NOTIFY:
3070 SndClickBeep();
3071
3072 if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
3073 if (FindWindowByClass(WC_SMALLMAP) == nullptr) ShowSmallMap();
3074 this->NotifySmallmap();
3075 }
3076 break;
3077
3078 case WID_IC_CARGO_DROPDOWN: {
3079 DropDownList lst;
3081 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
3082 lst.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
3083 }
3084 if (!lst.empty()) {
3085 int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
3086 ShowDropDownList(this, std::move(lst), selected, WID_IC_CARGO_DROPDOWN);
3087 }
3088 break;
3089 }
3090
3091 case WID_IC_IND_DROPDOWN: {
3092 DropDownList lst;
3093 for (IndustryType ind : _sorted_industry_types) {
3094 const IndustrySpec *indsp = GetIndustrySpec(ind);
3095 if (!indsp->enabled) continue;
3096 lst.push_back(MakeDropDownListStringItem(indsp->name, ind));
3097 }
3098 if (!lst.empty()) {
3099 int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
3100 ShowDropDownList(this, std::move(lst), selected, WID_IC_IND_DROPDOWN);
3101 }
3102 break;
3103 }
3104 }
3105 }
3106
3107 void OnDropdownSelect(WidgetID widget, int index, int) override
3108 {
3109 if (index < 0) return;
3110
3111 switch (widget) {
3113 this->ComputeCargoDisplay(index);
3114 break;
3115
3117 this->ComputeIndustryDisplay(index);
3118 break;
3119 }
3120 }
3121
3122 bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
3123 {
3124 if (widget != WID_IC_PANEL) return false;
3125
3126 Point fieldxy, xy;
3127 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return false;
3128
3129 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3130 CargoType cargo_type = INVALID_CARGO;
3131 switch (fld->type) {
3132 case CFT_CARGO: {
3133 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3134 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3135 cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3136 break;
3137 }
3138
3139 case CFT_CARGO_LABEL: {
3140 cargo_type = fld->CargoLabelClickedAt(xy);
3141 break;
3142 }
3143
3144 case CFT_INDUSTRY:
3145 if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
3146 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP), close_cond);
3147 }
3148 return true;
3149
3150 default:
3151 break;
3152 }
3153 if (IsValidCargoType(cargo_type) && (this->ind_cargo < NUM_INDUSTRYTYPES || cargo_type != this->ind_cargo - NUM_INDUSTRYTYPES)) {
3154 const CargoSpec *csp = CargoSpec::Get(cargo_type);
3155 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, csp->name), close_cond);
3156 return true;
3157 }
3158
3159 return false;
3160 }
3161
3162 void OnResize() override
3163 {
3164 this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL, WidgetDimensions::scaled.framerect.Vertical() + CargoesField::small_height);
3165 }
3166};
3167
3172static void ShowIndustryCargoesWindow(IndustryType id)
3173{
3174 if (id >= NUM_INDUSTRYTYPES) {
3175 for (IndustryType ind : _sorted_industry_types) {
3176 const IndustrySpec *indsp = GetIndustrySpec(ind);
3177 if (indsp->enabled) {
3178 id = ind;
3179 break;
3180 }
3181 }
3182 if (id >= NUM_INDUSTRYTYPES) return;
3183 }
3184
3186 if (w != nullptr) {
3187 w->InvalidateData(id);
3188 return;
3189 }
3190 new IndustryCargoesWindow(id);
3191}
3192
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.
@ 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:247
ClearGround GetClearGround(Tile t)
Get the type of clear tile.
Definition clear_map.h:47
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.
@ SWS_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:136
bool persistent_buildingtools
keep the building tools active after usage
uint8_t 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:933
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:95
@ MP_CLEAR
A tile without any structures, i.e. grass, rocks, farm fields etc.
Definition tile_type.h:48
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.