OpenTTD Source 20250717-master-g55605ae8f2
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 <http://www.gnu.org/licenses/>.
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
266void CcBuildIndustry(Commands, const CommandCost &result, TileIndex tile, IndustryType indtype, uint32_t, bool, uint32_t)
267{
268 if (result.Succeeded()) return;
269
270 if (indtype < NUM_INDUSTRYTYPES) {
271 const IndustrySpec *indsp = GetIndustrySpec(indtype);
272 if (indsp->enabled) {
273 ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_HERE, indsp->name),
275 }
276 }
277}
278
279static constexpr NWidgetPart _nested_build_industry_widgets[] = {
281 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
282 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetStringTip(STR_FUND_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
283 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
284 NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
285 NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
286 EndContainer(),
290 SetStringTip(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP),
292 SetStringTip(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES, STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP),
293 EndContainer(),
294 EndContainer(),
296 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),
297 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_DPI_SCROLLBAR),
298 EndContainer(),
299 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_DPI_INFOPANEL), SetResize(1, 0),
300 EndContainer(),
302 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_DISPLAY_WIDGET), SetFill(1, 0), SetResize(1, 0),
303 SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
304 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_FUND_WIDGET), SetFill(1, 0), SetResize(1, 0),
305 NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
306 EndContainer(),
307};
308
311 WDP_AUTO, "build_industry", 170, 212,
314 _nested_build_industry_widgets
315);
316
319 IndustryType selected_type = IT_INVALID;
320 std::vector<IndustryType> list{};
321 bool enabled = false;
322 Scrollbar *vscroll = nullptr;
324 GUIBadgeClasses badge_classes{};
325
327 static const int MAX_MINWIDTH_LINEHEIGHTS = 20;
328
329 void UpdateAvailability()
330 {
331 this->enabled = this->selected_type != IT_INVALID && (_game_mode == GM_EDITOR || GetIndustryProbabilityCallback(this->selected_type, IACT_USERCREATION, 1) > 0);
332 }
333
334 void SetupArrays()
335 {
336 this->list.clear();
337
338 /* Fill the arrays with industries.
339 * The tests performed after the enabled allow to load the industries
340 * In the same way they are inserted by grf (if any)
341 */
342 for (IndustryType ind : _sorted_industry_types) {
343 const IndustrySpec *indsp = GetIndustrySpec(ind);
344 if (indsp->enabled) {
345 /* Rule is that editor mode loads all industries.
346 * In game mode, all non raw industries are loaded too
347 * and raw ones are loaded only when setting allows it */
348 if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
349 /* Unselect if the industry is no longer in the list */
350 if (this->selected_type == ind) this->selected_type = IT_INVALID;
351 continue;
352 }
353
354 this->list.push_back(ind);
355 }
356 }
357
358 /* First industry type is selected if the current selection is invalid. */
359 if (this->selected_type == IT_INVALID && !this->list.empty()) this->selected_type = this->list[0];
360
361 this->UpdateAvailability();
362
363 this->vscroll->SetCount(this->list.size());
364 }
365
368 {
369 this->SetWidgetDisabledState(WID_DPI_FUND_WIDGET, this->selected_type != IT_INVALID && !this->enabled);
370 this->SetWidgetDisabledState(WID_DPI_DISPLAY_WIDGET, this->selected_type == IT_INVALID && this->enabled);
371 }
372
385 std::string MakeCargoListString(const std::span<const CargoType> cargolist, const std::span<const CargoSuffix> cargo_suffix, StringID prefixstr) const
386 {
387 assert(cargolist.size() == cargo_suffix.size());
388
389 std::string cargostring;
390 size_t numcargo = 0;
391 size_t firstcargo = 0;
392
393 for (size_t j = 0; j < cargolist.size(); j++) {
394 if (!IsValidCargoType(cargolist[j])) continue;
395 numcargo++;
396 if (numcargo == 1) {
397 firstcargo = j;
398 continue;
399 }
400 auto params = MakeParameters(CargoSpec::Get(cargolist[j])->name, cargo_suffix[j].text);
401 AppendStringWithArgsInPlace(cargostring, STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION, params);
402 }
403
404 if (numcargo > 0) {
405 cargostring = GetString(prefixstr, CargoSpec::Get(cargolist[firstcargo])->name, cargo_suffix[firstcargo].text) + cargostring;
406 } else {
407 cargostring = GetString(prefixstr, STR_JUST_NOTHING, ""sv);
408 }
409
410 return cargostring;
411 }
412
413public:
415 {
416 this->CreateNestedTree();
417 this->vscroll = this->GetScrollbar(WID_DPI_SCROLLBAR);
418 /* Show scenario editor tools in editor. */
419 if (_game_mode != GM_EDITOR) {
420 this->GetWidget<NWidgetStacked>(WID_DPI_SCENARIO_EDITOR_PANE)->SetDisplayedPlane(SZSP_HORIZONTAL);
421 }
422 this->FinishInitNested(0);
423
424 this->SetButtons();
425 }
426
427 void OnInit() override
428 {
429 this->badge_classes = GUIBadgeClasses{GSF_INDUSTRIES};
430
431 /* Width of the legend blob -- slightly larger than the smallmap legend blob. */
432 this->legend.height = GetCharacterHeight(FS_SMALL);
433 this->legend.width = this->legend.height * 9 / 6;
434
435 this->SetupArrays();
436 }
437
438 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
439 {
440 switch (widget) {
443 Dimension d{};
444 for (const auto &indtype : this->list) {
445 d = maxdim(d, GetStringBoundingBox(GetIndustrySpec(indtype)->name));
446 }
447 fill.height = resize.height = std::max<uint>({this->legend.height, d.height, count.height}) + padding.height;
448 d.width += this->badge_classes.GetTotalColumnsWidth() + this->legend.width + WidgetDimensions::scaled.hsep_wide + WidgetDimensions::scaled.hsep_normal + count.width + padding.width;
449 d.height = 5 * resize.height;
450 size = maxdim(size, d);
451 break;
452 }
453
454 case WID_DPI_INFOPANEL: {
455 /* Extra line for cost outside of editor. */
456 int height = 2 + (_game_mode == GM_EDITOR ? 0 : 1);
457 uint extra_lines_req = 0;
458 uint extra_lines_prd = 0;
459 uint extra_lines_newgrf = 0;
461 Dimension d = {0, 0};
462 for (const auto &indtype : this->list) {
463 const IndustrySpec *indsp = GetIndustrySpec(indtype);
464 CargoSuffix cargo_suffix[std::tuple_size_v<decltype(indsp->accepts_cargo)>];
465
466 /* Measure the accepted cargoes, if any. */
467 GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, indtype, indsp, indsp->accepts_cargo, cargo_suffix);
468 std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
469 Dimension strdim = GetStringBoundingBox(cargostring);
470 if (strdim.width > max_minwidth) {
471 extra_lines_req = std::max(extra_lines_req, strdim.width / max_minwidth + 1);
472 strdim.width = max_minwidth;
473 }
474 d = maxdim(d, strdim);
475
476 /* Measure the produced cargoes, if any. */
477 GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, indtype, indsp, indsp->produced_cargo, cargo_suffix);
478 cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
479 strdim = GetStringBoundingBox(cargostring);
480 if (strdim.width > max_minwidth) {
481 extra_lines_prd = std::max(extra_lines_prd, strdim.width / max_minwidth + 1);
482 strdim.width = max_minwidth;
483 }
484 d = maxdim(d, strdim);
485
486 if (indsp->grf_prop.HasGrfFile()) {
487 /* Reserve a few extra lines for text from an industry NewGRF. */
488 extra_lines_newgrf = 4;
489 }
490 }
491
492 /* Set it to something more sane :) */
493 height += extra_lines_prd + extra_lines_req + extra_lines_newgrf;
494 size.height = height * GetCharacterHeight(FS_NORMAL) + padding.height;
495 size.width = d.width + padding.width;
496 break;
497 }
498
499 case WID_DPI_FUND_WIDGET: {
500 Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
501 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY));
502 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY));
503 d.width += padding.width;
504 d.height += padding.height;
505 size = maxdim(size, d);
506 break;
507 }
508 }
509 }
510
511 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
512 {
513 switch (widget) {
515 /* Raw industries might be prospected. Show this fact by changing the string
516 * In Editor, you just build, while ingame, or you fund or you prospect */
517 if (_game_mode == GM_EDITOR) {
518 /* We've chosen many random industries but no industries have been specified */
519 return GetString(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
520 }
521 if (this->selected_type != IT_INVALID) {
522 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
523 return GetString((_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
524 }
525 return GetString(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
526
527 default:
528 return this->Window::GetWidgetString(widget, stringid);
529 }
530 }
531
532 void DrawWidget(const Rect &r, WidgetID widget) const override
533 {
534 switch (widget) {
536 bool rtl = _current_text_dir == TD_RTL;
538 Rect icon = text.WithWidth(this->legend.width, rtl);
539 text = text.Indent(this->legend.width + WidgetDimensions::scaled.hsep_wide, rtl);
540
541 /* Vertical offset for legend icon. */
542 icon.top = r.top + (this->resize.step_height - this->legend.height + 1) / 2;
543 icon.bottom = icon.top + this->legend.height - 1;
544
545 auto badge_column_widths = badge_classes.GetColumnWidths();
546
547 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->list);
548 for (auto it = first; it != last; ++it) {
549 IndustryType type = *it;
550 bool selected = this->selected_type == type;
551 const IndustrySpec *indsp = GetIndustrySpec(type);
552
553 Rect tr = text;
554 if (badge_column_widths.size() >= 1 && badge_column_widths[0] > 0) {
555 DrawBadgeColumn(tr.WithWidth(badge_column_widths[0], rtl), 0, this->badge_classes, indsp->badges, GSF_INDUSTRIES, std::nullopt, PAL_NONE);
556 tr = tr.Indent(badge_column_widths[0], rtl);
557 }
558 if (badge_column_widths.size() >= 2 && badge_column_widths[1] > 0) {
559 DrawBadgeColumn(tr.WithWidth(badge_column_widths[1], !rtl), 0, this->badge_classes, indsp->badges, GSF_INDUSTRIES, std::nullopt, PAL_NONE);
560 tr = tr.Indent(badge_column_widths[1], !rtl);
561 }
562
563 /* Draw the name of the industry in white is selected, otherwise, in orange */
564 DrawString(tr, indsp->name, selected ? TC_WHITE : TC_ORANGE);
565 GfxFillRect(icon, selected ? PC_WHITE : PC_BLACK);
567 DrawString(tr, GetString(STR_JUST_COMMA, Industry::GetIndustryTypeCount(type)), TC_BLACK, SA_RIGHT, false, FS_SMALL);
568
569 text = text.Translate(0, this->resize.step_height);
570 icon = icon.Translate(0, this->resize.step_height);
571 }
572 break;
573 }
574
575 case WID_DPI_INFOPANEL: {
576 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
577
578 if (this->selected_type == IT_INVALID) {
579 DrawStringMultiLine(ir, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
580 break;
581 }
582
583 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
584
585 if (_game_mode != GM_EDITOR) {
586 DrawString(ir, GetString(STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST, indsp->GetConstructionCost()));
587 ir.top += GetCharacterHeight(FS_NORMAL);
588 }
589
590 CargoSuffix cargo_suffix[std::tuple_size_v<decltype(indsp->accepts_cargo)>];
591
592 /* Draw the accepted cargoes, if any. Otherwise, will print "Nothing". */
593 GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, this->selected_type, indsp, indsp->accepts_cargo, cargo_suffix);
594 std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
595 ir.top = DrawStringMultiLine(ir, cargostring);
596
597 /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
598 GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, this->selected_type, indsp, indsp->produced_cargo, cargo_suffix);
599 cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
600 ir.top = DrawStringMultiLine(ir, cargostring);
601
602 ir.top = DrawBadgeNameList(ir, indsp->badges, GSF_INDUSTRIES);
603
604 /* Get the additional purchase info text, if it has not already been queried. */
606 std::array<int32_t, 16> regs100;
607 uint16_t callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, nullptr, this->selected_type, INVALID_TILE, regs100);
608 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
609 std::string str;
610 if (callback_res == 0x40F) {
611 str = GetGRFStringWithTextStack(indsp->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
612 } else if (callback_res > 0x400) {
614 } else {
615 str = GetGRFStringWithTextStack(indsp->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, regs100);
616 }
617 if (!str.empty()) {
618 DrawStringMultiLine(ir, str, TC_YELLOW);
619 }
620 }
621 }
622 break;
623 }
624 }
625 }
626
627 static void AskManyRandomIndustriesCallback(Window *, bool confirmed)
628 {
629 if (!confirmed) return;
630
631 if (Town::GetNumItems() == 0) {
632 ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_GENERATE_INDUSTRIES), GetEncodedString(STR_ERROR_MUST_FOUND_TOWN_FIRST), WL_INFO);
633 } else {
634 Backup<bool> old_generating_world(_generating_world, true);
638 old_generating_world.Restore();
639 }
640 }
641
642 static void AskRemoveAllIndustriesCallback(Window *, bool confirmed)
643 {
644 if (!confirmed) return;
645
646 for (Industry *industry : Industry::Iterate()) delete industry;
647
648 /* Clear farmland. */
649 for (const auto tile : Map::Iterate()) {
650 if (IsTileType(tile, MP_CLEAR) && GetClearGround(tile) == CLEAR_FIELDS) {
651 MakeClear(tile, CLEAR_GRASS, 3);
652 }
653 }
654
656 }
657
658 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
659 {
660 switch (widget) {
662 assert(_game_mode == GM_EDITOR);
664 ShowQuery(
665 GetEncodedString(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION),
666 GetEncodedString(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY),
667 nullptr, AskManyRandomIndustriesCallback);
668 break;
669 }
670
672 assert(_game_mode == GM_EDITOR);
674 ShowQuery(
675 GetEncodedString(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION),
676 GetEncodedString(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY),
677 nullptr, AskRemoveAllIndustriesCallback);
678 break;
679 }
680
682 auto it = this->vscroll->GetScrolledItemFromWidget(this->list, pt.y, this, WID_DPI_MATRIX_WIDGET);
683 if (it != this->list.end()) { // Is it within the boundaries of available data?
684 this->selected_type = *it;
685 this->UpdateAvailability();
686
687 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
688
689 this->SetDirty();
690
691 if (_thd.GetCallbackWnd() == this &&
692 ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != nullptr && indsp->IsRawIndustry()) || !this->enabled)) {
693 /* Reset the button state if going to prospecting or "build many industries" */
694 this->RaiseButtons();
696 }
697
698 this->SetButtons();
699 if (this->enabled && click_count > 1) this->OnClick(pt, WID_DPI_FUND_WIDGET, 1);
700 }
701 break;
702 }
703
705 if (this->selected_type != IT_INVALID) ShowIndustryCargoesWindow(this->selected_type);
706 break;
707
708 case WID_DPI_FUND_WIDGET: {
709 if (this->selected_type != IT_INVALID) {
710 if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
711 Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, TileIndex{}, this->selected_type, 0, false, InteractiveRandom());
713 } else {
714 HandlePlacePushButton(this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT);
715 }
716 }
717 break;
718 }
719 }
720 }
721
722 void OnResize() override
723 {
724 /* Adjust the number of items in the matrix depending of the resize */
725 this->vscroll->SetCapacityFromWidget(this, WID_DPI_MATRIX_WIDGET);
726 }
727
728 void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override
729 {
730 bool success = true;
731 /* We do not need to protect ourselves against "Random Many Industries" in this mode */
732 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
733 uint32_t seed = InteractiveRandom();
734 uint32_t layout_index = InteractiveRandomRange((uint32_t)indsp->layouts.size());
735
736 if (_game_mode == GM_EDITOR) {
737 /* Show error if no town exists at all */
738 if (Town::GetNumItems() == 0) {
739 ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_HERE, indsp->name),
740 GetEncodedString(STR_ERROR_MUST_FOUND_TOWN_FIRST), WL_INFO, pt.x, pt.y);
741 return;
742 }
743
744 AutoRestoreBackup backup_cur_company(_current_company, OWNER_NONE);
745 AutoRestoreBackup backup_generating_world(_generating_world, true);
746 AutoRestoreBackup backup_ignore_industry_restritions(_ignore_industry_restrictions, true);
747
748 Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, &CcBuildIndustry, tile, this->selected_type, layout_index, false, seed);
749 } else {
750 success = Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
751 }
752
753 /* If an industry has been built, just reset the cursor and the system */
755 }
756
757 const IntervalTimer<TimerWindow> update_interval = {std::chrono::seconds(3), [this](auto) {
758 if (_game_mode == GM_EDITOR) return;
759 if (this->selected_type == IT_INVALID) return;
760
761 bool enabled = this->enabled;
762 this->UpdateAvailability();
763 if (enabled != this->enabled) {
764 this->SetButtons();
765 this->SetDirty();
766 }
767 }};
768
769 void OnTimeout() override
770 {
771 this->RaiseButtons();
772 }
773
774 void OnPlaceObjectAbort() override
775 {
776 this->RaiseButtons();
777 }
778
784 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
785 {
786 if (!gui_scope) return;
787 this->SetupArrays();
788 this->SetButtons();
789 this->SetDirty();
790 }
791};
792
793void ShowBuildIndustryWindow()
794{
795 if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
798}
799
800static void UpdateIndustryProduction(Industry *i);
801
802static inline bool IsProductionAlterable(const Industry *i)
803{
804 const IndustrySpec *is = GetIndustrySpec(i->type);
805 bool has_prod = std::any_of(std::begin(is->production_rate), std::end(is->production_rate), [](auto rate) { return rate != 0; });
806 return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
807 (has_prod || is->IsRawIndustry()) &&
808 !_networking);
809}
810
812{
819
827
832 uint8_t clicked_button = 0;
834 int info_height = 0;
836
837public:
839 {
841 this->info_height = WidgetDimensions::scaled.framerect.Vertical() + 2 * GetCharacterHeight(FS_NORMAL); // Info panel has at least two lines text.
842
843 this->InitNested(window_number);
844 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
845 nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ScaleZoomGUI(ZoomLevel::Industry));
846
849
850 this->InvalidateData();
851 }
852
854 {
856 }
857
858 void OnInit() override
859 {
860 /* This only used when the cheat to alter industry production is enabled */
861 this->cheat_line_height = std::max(SETTING_BUTTON_HEIGHT + WidgetDimensions::scaled.vsep_normal, GetCharacterHeight(FS_NORMAL));
863 }
864
865 void OnPaint() override
866 {
867 this->DrawWidgets();
868
869 if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
870
871 const Rect r = this->GetWidget<NWidgetBase>(WID_IV_INFO)->GetCurrentRect();
872 int expected = this->DrawInfo(r);
873 if (expected != r.bottom) {
874 this->info_height = expected - r.top + 1;
875 this->ReInit();
876 return;
877 }
878 }
879
880 void DrawCargoIcon(const Rect &r, CargoType cargo_type) const
881 {
882 bool rtl = _current_text_dir == TD_RTL;
883 SpriteID icon = CargoSpec::Get(cargo_type)->GetCargoIcon();
884 Dimension d = GetSpriteSize(icon);
886 DrawSprite(icon, PAL_NONE, CentreBounds(ir.left, ir.right, d.width), CentreBounds(ir.top, ir.bottom, this->cargo_icon_size.height));
887 }
888
889 std::string GetAcceptedCargoString(const Industry::AcceptedCargo &ac, const CargoSuffix &suffix) const
890 {
891 auto params = MakeParameters(CargoSpec::Get(ac.cargo)->name, ac.cargo, ac.waiting, suffix.text);
892 switch (suffix.display) {
893 case CSD_CARGO_AMOUNT_TEXT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_SUFFIX, params);
894 case CSD_CARGO_TEXT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_SUFFIX, params);
895 case CSD_CARGO_AMOUNT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_NOSUFFIX, params);
896 case CSD_CARGO: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_NOSUFFIX, params);
897 default: NOT_REACHED();
898 }
899 }
900
906 int DrawInfo(const Rect &r)
907 {
908 bool rtl = _current_text_dir == TD_RTL;
910 const IndustrySpec *ind = GetIndustrySpec(i->type);
911 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
912 bool first = true;
913 bool has_accept = false;
914
915 /* Use all the available space past the rect, so that we can enlarge the window if needed. */
916 ir.bottom = INT_MAX;
917
918 if (i->prod_level == PRODLEVEL_CLOSURE) {
919 DrawString(ir, STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE);
921 }
922
923 const int label_indent = WidgetDimensions::scaled.hsep_normal + this->cargo_icon_size.width;
925
926 for (const auto &a : i->accepted) {
927 if (!IsValidCargoType(a.cargo)) continue;
928 has_accept = true;
929 if (first) {
930 DrawString(ir, STR_INDUSTRY_VIEW_REQUIRES);
931 ir.top += GetCharacterHeight(FS_NORMAL);
932 first = false;
933 }
934
935 DrawCargoIcon(ir, a.cargo);
936
937 CargoSuffix suffix;
938 GetCargoSuffix(CARGOSUFFIX_IN, CST_VIEW, i, i->type, ind, a.cargo, &a - i->accepted.data(), suffix);
939 /* if the industry is not stockpiling then don't show amount in the acceptance display. */
940 if (!stockpiling && suffix.display == CSD_CARGO_AMOUNT_TEXT) suffix.display = CSD_CARGO_TEXT;
941 if (!stockpiling && suffix.display == CSD_CARGO_AMOUNT) suffix.display = CSD_CARGO;
942
943 DrawString(ir.Indent(label_indent, rtl), this->GetAcceptedCargoString(a, suffix));
944 ir.top += GetCharacterHeight(FS_NORMAL);
945 }
946
947 int line_height = this->editable == EA_RATE ? this->cheat_line_height : GetCharacterHeight(FS_NORMAL);
948 int text_y_offset = (line_height - GetCharacterHeight(FS_NORMAL)) / 2;
949 int button_y_offset = (line_height - SETTING_BUTTON_HEIGHT) / 2;
950 first = true;
951 for (const auto &p : i->produced) {
952 if (!IsValidCargoType(p.cargo)) continue;
953 if (first) {
954 if (has_accept) ir.top += WidgetDimensions::scaled.vsep_wide;
955 DrawString(ir, TimerGameEconomy::UsingWallclockUnits() ? STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE : STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
956 ir.top += GetCharacterHeight(FS_NORMAL);
957 if (this->editable == EA_RATE) this->production_offset_y = ir.top;
958 first = false;
959 }
960
961 DrawCargoIcon(ir, p.cargo);
962
963 CargoSuffix suffix;
964 GetCargoSuffix(CARGOSUFFIX_OUT, CST_VIEW, i, i->type, ind, p.cargo, &p - i->produced.data(), suffix);
965
966 DrawString(ir.Indent(label_indent + (this->editable == EA_RATE ? SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_normal : 0), rtl).Translate(0, text_y_offset),
967 GetString(STR_INDUSTRY_VIEW_TRANSPORTED, p.cargo, p.history[LAST_MONTH].production, suffix.text, ToPercent8(p.history[LAST_MONTH].PctTransported())));
968 /* Let's put out those buttons.. */
969 if (this->editable == EA_RATE) {
970 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,
971 p.rate > 0, p.rate < 255);
972 }
973 ir.top += line_height;
974 }
975
976 /* Display production multiplier if editable */
977 if (this->editable == EA_MULTIPLIER) {
978 line_height = this->cheat_line_height;
979 text_y_offset = (line_height - GetCharacterHeight(FS_NORMAL)) / 2;
980 button_y_offset = (line_height - SETTING_BUTTON_HEIGHT) / 2;
982 this->production_offset_y = ir.top;
983 DrawString(ir.Indent(label_indent + SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_normal, rtl).Translate(0, text_y_offset),
984 GetString(STR_INDUSTRY_VIEW_PRODUCTION_LEVEL, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT)));
985 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,
987 ir.top += line_height;
988 }
989
990 /* Get the extra message for the GUI */
992 std::array<int32_t, 16> regs100;
993 uint16_t callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->location.tile, regs100);
994 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
995 std::string str;
996 if (callback_res == 0x40F) {
997 str = GetGRFStringWithTextStack(ind->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
998 } else if (callback_res > 0x400) {
1000 } else {
1001 str = GetGRFStringWithTextStack(ind->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, regs100);
1002 }
1003 if (!str.empty()) {
1005 ir.top = DrawStringMultiLine(ir, str, TC_YELLOW);
1006 }
1007 }
1008 }
1009
1010 if (!i->text.empty()) {
1012 ir.top = DrawStringMultiLine(ir, i->text.GetDecodedString(), TC_BLACK);
1013 }
1014
1015 /* Return required bottom position, the last pixel row plus some padding. */
1016 return ir.top - 1 + WidgetDimensions::scaled.framerect.bottom;
1017 }
1018
1019 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1020 {
1021 if (widget == WID_IV_CAPTION) return GetString(STR_INDUSTRY_VIEW_CAPTION, this->window_number);
1022
1023 return this->Window::GetWidgetString(widget, stringid);
1024 }
1025
1026 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1027 {
1028 if (widget == WID_IV_INFO) size.height = this->info_height;
1029 }
1030
1031 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1032 {
1033 switch (widget) {
1034 case WID_IV_INFO: {
1036 InfoLine line = IL_NONE;
1037
1038 switch (this->editable) {
1039 case EA_NONE: break;
1040
1041 case EA_MULTIPLIER:
1042 if (IsInsideBS(pt.y, this->production_offset_y, this->cheat_line_height)) line = IL_MULTIPLIER;
1043 break;
1044
1045 case EA_RATE:
1046 if (pt.y >= this->production_offset_y) {
1047 int row = (pt.y - this->production_offset_y) / this->cheat_line_height;
1048 for (auto itp = std::begin(i->produced); itp != std::end(i->produced); ++itp) {
1049 if (!IsValidCargoType(itp->cargo)) continue;
1050 row--;
1051 if (row < 0) {
1052 line = (InfoLine)(IL_RATE1 + (itp - std::begin(i->produced)));
1053 break;
1054 }
1055 }
1056 }
1057 break;
1058 }
1059 if (line == IL_NONE) return;
1060
1061 bool rtl = _current_text_dir == TD_RTL;
1062 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect).Indent(this->cargo_icon_size.width + WidgetDimensions::scaled.hsep_normal, rtl);
1063
1064 if (r.WithWidth(SETTING_BUTTON_WIDTH, rtl).Contains(pt)) {
1065 /* Clicked buttons, decrease or increase production */
1066 bool decrease = r.WithWidth(SETTING_BUTTON_WIDTH / 2, rtl).Contains(pt);
1067 switch (this->editable) {
1068 case EA_MULTIPLIER:
1069 if (decrease) {
1070 if (i->prod_level <= PRODLEVEL_MINIMUM) return;
1071 i->prod_level = static_cast<uint8_t>(std::max<uint>(i->prod_level / 2, PRODLEVEL_MINIMUM));
1072 } else {
1073 if (i->prod_level >= PRODLEVEL_MAXIMUM) return;
1074 i->prod_level = static_cast<uint8_t>(std::min<uint>(i->prod_level * 2, PRODLEVEL_MAXIMUM));
1075 }
1076 break;
1077
1078 case EA_RATE:
1079 if (decrease) {
1080 if (i->produced[line - IL_RATE1].rate <= 0) return;
1081 i->produced[line - IL_RATE1].rate = std::max(i->produced[line - IL_RATE1].rate / 2, 0);
1082 } else {
1083 if (i->produced[line - IL_RATE1].rate >= 255) return;
1084 /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
1085 int new_prod = i->produced[line - IL_RATE1].rate == 0 ? 1 : i->produced[line - IL_RATE1].rate * 2;
1086 i->produced[line - IL_RATE1].rate = ClampTo<uint8_t>(new_prod);
1087 }
1088 break;
1089
1090 default: NOT_REACHED();
1091 }
1092
1093 UpdateIndustryProduction(i);
1094 this->SetDirty();
1095 this->SetTimeout();
1096 this->clicked_line = line;
1097 this->clicked_button = (decrease ^ rtl) ? 1 : 2;
1099 /* clicked the text */
1100 this->editbox_line = line;
1101 switch (this->editable) {
1102 case EA_MULTIPLIER:
1103 ShowQueryString(GetString(STR_JUST_INT, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT)), STR_CONFIG_GAME_PRODUCTION_LEVEL, 10, this, CS_ALPHANUMERAL, {});
1104 break;
1105
1106 case EA_RATE:
1107 ShowQueryString(GetString(STR_JUST_INT, i->produced[line - IL_RATE1].rate * 8), STR_CONFIG_GAME_PRODUCTION, 10, this, CS_ALPHANUMERAL, {});
1108 break;
1109
1110 default: NOT_REACHED();
1111 }
1112 }
1113 break;
1114 }
1115
1116 case WID_IV_GOTO: {
1118 if (_ctrl_pressed) {
1120 } else {
1122 }
1123 break;
1124 }
1125
1126 case WID_IV_DISPLAY: {
1129 break;
1130 }
1131
1132 case WID_IV_GRAPH:
1133 ShowIndustryProductionGraph(this->window_number);
1134 break;
1135 }
1136 }
1137
1138 void OnTimeout() override
1139 {
1140 this->clicked_line = IL_NONE;
1141 this->clicked_button = 0;
1142 this->SetDirty();
1143 }
1144
1145 void OnResize() override
1146 {
1147 if (this->viewport != nullptr) {
1148 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
1149 nvp->UpdateViewportCoordinates(this);
1150
1151 ScrollWindowToTile(Industry::Get(this->window_number)->location.GetCenterTile(), this, true); // Re-center viewport.
1152 }
1153 }
1154
1155 void OnMouseWheel(int wheel, WidgetID widget) override
1156 {
1157 if (widget != WID_IV_VIEWPORT) return;
1159 DoZoomInOutWindow(wheel < 0 ? ZOOM_IN : ZOOM_OUT, this);
1160 }
1161 }
1162
1163 void OnQueryTextFinished(std::optional<std::string> str) override
1164 {
1165 if (!str.has_value() || str->empty()) return;
1166
1168 auto value = ParseInteger(*str, 10, true);
1169 if (!value.has_value()) return;
1170 switch (this->editbox_line) {
1171 case IL_NONE: NOT_REACHED();
1172
1173 case IL_MULTIPLIER:
1175 break;
1176
1177 default:
1178 i->produced[this->editbox_line - IL_RATE1].rate = ClampU(RoundDivSU(*value, 8), 0, 255);
1179 break;
1180 }
1181 UpdateIndustryProduction(i);
1182 this->SetDirty();
1183 }
1184
1190 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1191 {
1192 if (!gui_scope) return;
1193 const Industry *i = Industry::Get(this->window_number);
1194 if (IsProductionAlterable(i)) {
1195 const IndustrySpec *ind = GetIndustrySpec(i->type);
1197 } else {
1198 this->editable = EA_NONE;
1199 }
1200 }
1201
1202 bool IsNewGRFInspectable() const override
1203 {
1204 return ::IsNewGRFInspectable(GSF_INDUSTRIES, this->window_number);
1205 }
1206
1207 void ShowNewGRFInspectWindow() const override
1208 {
1209 ::ShowNewGRFInspectWindow(GSF_INDUSTRIES, this->window_number);
1210 }
1211};
1212
1213static void UpdateIndustryProduction(Industry *i)
1214{
1215 const IndustrySpec *indspec = GetIndustrySpec(i->type);
1217
1218 for (auto &p : i->produced) {
1219 if (IsValidCargoType(p.cargo)) {
1220 p.history[LAST_MONTH].production = ScaleByCargoScale(8 * p.rate, false);
1221 }
1222 }
1223}
1224
1228 NWidget(WWT_CLOSEBOX, COLOUR_CREAM),
1229 NWidget(WWT_CAPTION, COLOUR_CREAM, WID_IV_CAPTION),
1230 NWidget(WWT_PUSHIMGBTN, COLOUR_CREAM, WID_IV_GOTO), SetAspect(WidgetDimensions::ASPECT_LOCATION), SetSpriteTip(SPR_GOTO_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
1231 NWidget(WWT_DEBUGBOX, COLOUR_CREAM),
1232 NWidget(WWT_SHADEBOX, COLOUR_CREAM),
1233 NWidget(WWT_DEFSIZEBOX, COLOUR_CREAM),
1234 NWidget(WWT_STICKYBOX, COLOUR_CREAM),
1235 EndContainer(),
1236 NWidget(WWT_PANEL, COLOUR_CREAM),
1237 NWidget(WWT_INSET, COLOUR_CREAM), SetPadding(2, 2, 2, 2),
1238 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_IV_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetResize(1, 1),
1239 EndContainer(),
1240 EndContainer(),
1241 NWidget(WWT_PANEL, COLOUR_CREAM, WID_IV_INFO), SetMinimalSize(260, 0), SetMinimalTextLines(2, WidgetDimensions::unscaled.framerect.Vertical()), SetResize(1, 0),
1242 EndContainer(),
1244 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
1245 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GRAPH), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_VIEW_PRODUCTION_GRAPH, STR_INDUSTRY_VIEW_PRODUCTION_GRAPH_TOOLTIP),
1246 NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
1247 EndContainer(),
1248};
1249
1252 WDP_AUTO, "view_industry", 260, 120,
1254 {},
1256);
1257
1258void ShowIndustryViewWindow(IndustryID industry)
1259{
1260 AllocateWindowDescFront<IndustryViewWindow>(_industry_view_desc, industry);
1261}
1262
1266 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1267 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_ID_CAPTION),
1268 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1269 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1270 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1271 EndContainer(),
1275 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_ID_DROPDOWN_ORDER), SetStringTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1276 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_DROPDOWN_CRITERIA), SetToolTip(STR_TOOLTIP_SORT_CRITERIA),
1277 NWidget(WWT_EDITBOX, COLOUR_BROWN, WID_ID_FILTER), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
1278 EndContainer(),
1280 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_ACC_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA),
1281 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_PROD_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA),
1282 NWidget(WWT_PANEL, COLOUR_BROWN), SetResize(1, 0), EndContainer(),
1283 EndContainer(),
1284 NWidget(WWT_PANEL, COLOUR_BROWN, WID_ID_INDUSTRY_LIST), SetToolTip(STR_INDUSTRY_DIRECTORY_LIST_TOOLTIP), SetResize(1, 1), SetScrollbar(WID_ID_VSCROLLBAR),
1285 EndContainer(),
1286 EndContainer(),
1288 EndContainer(),
1291 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1292 EndContainer(),
1293};
1294
1296
1304static bool CargoFilter(const Industry * const *industry, const std::pair<CargoType, CargoType> &cargoes)
1305{
1306 auto accepted_cargo = cargoes.first;
1307 auto produced_cargo = cargoes.second;
1308
1309 bool accepted_cargo_matches;
1310
1311 switch (accepted_cargo) {
1313 accepted_cargo_matches = true;
1314 break;
1315
1317 accepted_cargo_matches = !(*industry)->IsCargoAccepted();
1318 break;
1319
1320 default:
1321 accepted_cargo_matches = (*industry)->IsCargoAccepted(accepted_cargo);
1322 break;
1323 }
1324
1325 bool produced_cargo_matches;
1326
1327 switch (produced_cargo) {
1329 produced_cargo_matches = true;
1330 break;
1331
1333 produced_cargo_matches = !(*industry)->IsCargoProduced();
1334 break;
1335
1336 default:
1337 produced_cargo_matches = (*industry)->IsCargoProduced(produced_cargo);
1338 break;
1339 }
1340
1341 return accepted_cargo_matches && produced_cargo_matches;
1342}
1343
1344static GUIIndustryList::FilterFunction * const _industry_filter_funcs[] = { &CargoFilter };
1345
1350protected:
1351 /* Runtime saved values */
1352 static Listing last_sorting;
1353
1354 /* Constants for sorting industries */
1355 static inline const StringID sorter_names[] = {
1356 STR_SORT_BY_NAME,
1357 STR_SORT_BY_TYPE,
1358 STR_SORT_BY_PRODUCTION,
1359 STR_SORT_BY_TRANSPORTED,
1360 };
1361 static const std::initializer_list<GUIIndustryList::SortFunction * const> sorter_funcs;
1362
1363 GUIIndustryList industries{IndustryDirectoryWindow::produced_cargo_filter};
1364 Scrollbar *vscroll{};
1365 Scrollbar *hscroll{};
1366
1369 static CargoType produced_cargo_filter;
1370
1371 const int MAX_FILTER_LENGTH = 16;
1374
1375 enum class SorterType : uint8_t {
1376 ByName,
1377 ByType,
1378 ByProduction,
1380 };
1381
1387 {
1388 if (this->produced_cargo_filter_criteria != cargo_type) {
1389 this->produced_cargo_filter_criteria = cargo_type;
1390 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1392
1393 this->industries.SetFilterState(is_filtering_necessary);
1394 this->industries.SetFilterType(0);
1395 this->industries.ForceRebuild();
1396 }
1397 }
1398
1404 {
1405 if (this->accepted_cargo_filter_criteria != cargo_type) {
1406 this->accepted_cargo_filter_criteria = cargo_type;
1407 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1409
1410 this->industries.SetFilterState(is_filtering_necessary);
1411 this->industries.SetFilterType(0);
1412 this->industries.ForceRebuild();
1413 }
1414 }
1415
1416 StringID GetCargoFilterLabel(CargoType cargo_type) const
1417 {
1418 switch (cargo_type) {
1419 case CargoFilterCriteria::CF_ANY: return STR_INDUSTRY_DIRECTORY_FILTER_ALL_TYPES;
1420 case CargoFilterCriteria::CF_NONE: return STR_INDUSTRY_DIRECTORY_FILTER_NONE;
1421 default: return CargoSpec::Get(cargo_type)->name;
1422 }
1423 }
1424
1429 {
1432
1433 this->industries.SetFilterFuncs(_industry_filter_funcs);
1434
1436
1437 this->industries.SetFilterState(is_filtering_necessary);
1438 }
1439
1445 {
1446 uint width = this->hscroll->GetCount();
1447 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1448 for (auto it = first; it != last; ++it) {
1449 width = std::max(width, GetStringBoundingBox(this->GetIndustryString(*it)).width);
1450 }
1451 return width;
1452 }
1453
1456 {
1457 if (this->industries.NeedRebuild()) {
1458 this->industries.clear();
1459 this->industries.reserve(Industry::GetNumItems());
1460
1461 for (const Industry *i : Industry::Iterate()) {
1462 if (this->string_filter.IsEmpty()) {
1463 this->industries.push_back(i);
1464 continue;
1465 }
1466 this->string_filter.ResetState();
1467 this->string_filter.AddLine(i->GetCachedName());
1468 if (this->string_filter.GetState()) this->industries.push_back(i);
1469 }
1470
1471 this->industries.RebuildDone();
1472
1473 auto filter = std::make_pair(this->accepted_cargo_filter_criteria, this->produced_cargo_filter_criteria);
1474
1475 this->industries.Filter(filter);
1476
1477 this->vscroll->SetCount(this->industries.size()); // Update scrollbar as well.
1478 }
1479
1480 IndustryDirectoryWindow::produced_cargo_filter = this->produced_cargo_filter_criteria;
1481 this->industries.Sort();
1482
1483 this->SetDirty();
1484 }
1485
1493 {
1494 if (!IsValidCargoType(p.cargo)) return -1;
1495 return ToPercent8(p.history[LAST_MONTH].PctTransported());
1496 }
1497
1506 {
1507 CargoType filter = IndustryDirectoryWindow::produced_cargo_filter;
1508 if (filter == CargoFilterCriteria::CF_NONE) return 0;
1509
1510 int percentage = 0, produced_cargo_count = 0;
1511 for (const auto &p : i->produced) {
1512 if (filter == CargoFilterCriteria::CF_ANY) {
1513 int transported = GetCargoTransportedPercentsIfValid(p);
1514 if (transported != -1) {
1515 produced_cargo_count++;
1516 percentage += transported;
1517 }
1518 if (produced_cargo_count == 0 && &p == &i->produced.back() && percentage == 0) {
1519 return transported;
1520 }
1521 } else if (filter == p.cargo) {
1523 }
1524 }
1525
1526 if (produced_cargo_count == 0) return percentage;
1527 return percentage / produced_cargo_count;
1528 }
1529
1531 static bool IndustryNameSorter(const Industry * const &a, const Industry * const &b, const CargoType &)
1532 {
1533 int r = StrNaturalCompare(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting).
1534 if (r == 0) return a->index < b->index;
1535 return r < 0;
1536 }
1537
1539 static bool IndustryTypeSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1540 {
1541 int it_a = 0;
1542 while (it_a != NUM_INDUSTRYTYPES && a->type != _sorted_industry_types[it_a]) it_a++;
1543 int it_b = 0;
1544 while (it_b != NUM_INDUSTRYTYPES && b->type != _sorted_industry_types[it_b]) it_b++;
1545 int r = it_a - it_b;
1546 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1547 }
1548
1550 static bool IndustryProductionSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1551 {
1552 if (filter == CargoFilterCriteria::CF_NONE) return IndustryTypeSorter(a, b, filter);
1553
1554 uint prod_a = 0, prod_b = 0;
1555 if (filter == CargoFilterCriteria::CF_ANY) {
1556 for (const auto &pa : a->produced) {
1557 if (IsValidCargoType(pa.cargo)) prod_a += pa.history[LAST_MONTH].production;
1558 }
1559 for (const auto &pb : b->produced) {
1560 if (IsValidCargoType(pb.cargo)) prod_b += pb.history[LAST_MONTH].production;
1561 }
1562 } else {
1563 if (auto ita = a->GetCargoProduced(filter); ita != std::end(a->produced)) prod_a = ita->history[LAST_MONTH].production;
1564 if (auto itb = b->GetCargoProduced(filter); itb != std::end(b->produced)) prod_b = itb->history[LAST_MONTH].production;
1565 }
1566 int r = prod_a - prod_b;
1567
1568 return (r == 0) ? IndustryTypeSorter(a, b, filter) : r < 0;
1569 }
1570
1572 static bool IndustryTransportedCargoSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1573 {
1575 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1576 }
1577
1578 StringID GetStringForNumCargo(size_t count) const
1579 {
1580 switch (count) {
1581 case 0: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1582 case 1: return STR_INDUSTRY_DIRECTORY_ITEM_PROD1;
1583 case 2: return STR_INDUSTRY_DIRECTORY_ITEM_PROD2;
1584 case 3: return STR_INDUSTRY_DIRECTORY_ITEM_PROD3;
1585 default: return STR_INDUSTRY_DIRECTORY_ITEM_PRODMORE;
1586 }
1587 }
1588
1594 std::string GetIndustryString(const Industry *i) const
1595 {
1596 const IndustrySpec *indsp = GetIndustrySpec(i->type);
1597
1598 /* Get industry productions (CargoType, production, suffix, transported) */
1599 struct CargoInfo {
1600 CargoType cargo_type;
1601 uint16_t production;
1602 uint transported;
1603 std::string suffix;
1604
1605 CargoInfo(CargoType cargo_type, uint16_t production, uint transported, std::string &&suffix) : cargo_type(cargo_type), production(production), transported(transported), suffix(std::move(suffix)) {}
1606 };
1607 std::vector<CargoInfo> cargos;
1608
1609 for (auto itp = std::begin(i->produced); itp != std::end(i->produced); ++itp) {
1610 if (!IsValidCargoType(itp->cargo)) continue;
1611 CargoSuffix cargo_suffix;
1612 GetCargoSuffix(CARGOSUFFIX_OUT, CST_DIR, i, i->type, indsp, itp->cargo, itp - std::begin(i->produced), cargo_suffix);
1613 cargos.emplace_back(itp->cargo, itp->history[LAST_MONTH].production, ToPercent8(itp->history[LAST_MONTH].PctTransported()), std::move(cargo_suffix.text));
1614 }
1615
1616 switch (static_cast<IndustryDirectoryWindow::SorterType>(this->industries.SortType())) {
1620 /* Sort by descending production, then descending transported */
1621 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1622 if (a.production != b.production) return a.production > b.production;
1623 return a.transported > b.transported;
1624 });
1625 break;
1626
1628 /* Sort by descending transported, then descending production */
1629 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1630 if (a.transported != b.transported) return a.transported > b.transported;
1631 return a.production > b.production;
1632 });
1633 break;
1634 }
1635
1636 /* If the produced cargo filter is active then move the filtered cargo to the beginning of the list,
1637 * because this is the one the player interested in, and that way it is not hidden in the 'n' more cargos */
1638 const CargoType cargo_type = this->produced_cargo_filter_criteria;
1639 if (cargo_type != CargoFilterCriteria::CF_ANY && cargo_type != CargoFilterCriteria::CF_NONE) {
1640 auto filtered_ci = std::ranges::find(cargos, cargo_type, &CargoInfo::cargo_type);
1641 if (filtered_ci != cargos.end()) {
1642 std::rotate(cargos.begin(), filtered_ci, filtered_ci + 1);
1643 }
1644 }
1645
1646 static constexpr size_t MAX_DISPLAYED_CARGOES = 3;
1647 std::array<StringParameter, 2 + 5 * MAX_DISPLAYED_CARGOES> params{};
1648 auto it = params.begin();
1649
1650 /* Industry name */
1651 *it++ = i->index;
1652
1653 /* Display first MAX_DISPLAYED_CARGOES cargoes */
1654 for (CargoInfo &ci : cargos | std::views::take(MAX_DISPLAYED_CARGOES)) {
1655 *it++ = STR_INDUSTRY_DIRECTORY_ITEM_INFO;
1656 *it++ = ci.cargo_type;
1657 *it++ = ci.production;
1658 *it++ = std::move(ci.suffix);
1659 *it++ = ci.transported;
1660 }
1661
1662 /* Undisplayed cargos if any */
1663 if (std::size(cargos) > MAX_DISPLAYED_CARGOES) *it++ = std::size(cargos) - MAX_DISPLAYED_CARGOES;
1664
1665 return GetStringWithArgs(GetStringForNumCargo(std::size(cargos)), {params.begin(), it});
1666 }
1667
1668public:
1670 {
1671 this->CreateNestedTree();
1672 this->vscroll = this->GetScrollbar(WID_ID_VSCROLLBAR);
1673 this->hscroll = this->GetScrollbar(WID_ID_HSCROLLBAR);
1674
1675 this->industries.SetListing(this->last_sorting);
1676 this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1677 this->industries.ForceRebuild();
1678
1679 this->FinishInitNested(0);
1680
1682
1684 this->industry_editbox.cancel_button = QueryString::ACTION_CLEAR;
1685 }
1686
1688 {
1689 this->last_sorting = this->industries.GetListing();
1690 }
1691
1692 void OnInit() override
1693 {
1694 this->SetCargoFilterArray();
1695 this->hscroll->SetCount(0);
1696 }
1697
1698 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1699 {
1700 switch (widget) {
1701 case WID_ID_CAPTION:
1702 return GetString(STR_INDUSTRY_DIRECTORY_CAPTION, this->vscroll->GetCount(), Industry::GetNumItems());
1703
1705 return GetString(IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1706
1708 return GetString(STR_INDUSTRY_DIRECTORY_ACCEPTED_CARGO_FILTER, this->GetCargoFilterLabel(this->accepted_cargo_filter_criteria));
1709
1711 return GetString(STR_INDUSTRY_DIRECTORY_PRODUCED_CARGO_FILTER, this->GetCargoFilterLabel(this->produced_cargo_filter_criteria));
1712
1713 default:
1714 return this->Window::GetWidgetString(widget, stringid);
1715 }
1716 }
1717
1718 void DrawWidget(const Rect &r, WidgetID widget) const override
1719 {
1720 switch (widget) {
1722 this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1723 break;
1724
1725 case WID_ID_INDUSTRY_LIST: {
1726 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
1727
1728 /* Setup a clipping rectangle... */
1729 DrawPixelInfo tmp_dpi;
1730 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
1731 /* ...but keep coordinates relative to the window. */
1732 tmp_dpi.left += ir.left;
1733 tmp_dpi.top += ir.top;
1734
1735 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
1736
1737 ir = ScrollRect(ir, *this->hscroll, 1);
1738
1739 if (this->industries.empty()) {
1740 DrawString(ir, STR_INDUSTRY_DIRECTORY_NONE);
1741 break;
1742 }
1743 const CargoType acf_cargo_type = this->accepted_cargo_filter_criteria;
1744 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1745 for (auto it = first; it != last; ++it) {
1746 TextColour tc = TC_FROMSTRING;
1747 if (acf_cargo_type != CargoFilterCriteria::CF_ANY && acf_cargo_type != CargoFilterCriteria::CF_NONE) {
1748 Industry *ind = const_cast<Industry *>(*it);
1749 if (IndustryTemporarilyRefusesCargo(ind, acf_cargo_type)) {
1750 tc = TC_GREY | TC_FORCED;
1751 }
1752 }
1753 DrawString(ir, this->GetIndustryString(*it), tc);
1754
1755 ir.top += this->resize.step_height;
1756 }
1757 break;
1758 }
1759 }
1760 }
1761
1762 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1763 {
1764 switch (widget) {
1765 case WID_ID_DROPDOWN_ORDER: {
1766 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->GetString());
1767 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1768 d.height += padding.height;
1769 size = maxdim(size, d);
1770 break;
1771 }
1772
1774 Dimension d = GetStringListBoundingBox(IndustryDirectoryWindow::sorter_names);
1775 d.width += padding.width;
1776 d.height += padding.height;
1777 size = maxdim(size, d);
1778 break;
1779 }
1780
1781 case WID_ID_INDUSTRY_LIST: {
1782 Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1783 fill.height = resize.height = d.height;
1784 d.height *= 5;
1785 d.width += padding.width;
1786 d.height += padding.height;
1787 size = maxdim(size, d);
1788 break;
1789 }
1790 }
1791 }
1792
1793 DropDownList BuildCargoDropDownList() const
1794 {
1795 DropDownList list;
1796
1797 /* Add item for disabling filtering. */
1798 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_ANY), CargoFilterCriteria::CF_ANY));
1799 /* Add item for industries not producing anything, e.g. power plants */
1800 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_NONE), CargoFilterCriteria::CF_NONE));
1801
1802 /* Add cargos */
1804 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1805 list.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
1806 }
1807
1808 return list;
1809 }
1810
1811 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1812 {
1813 switch (widget) {
1815 this->industries.ToggleSortOrder();
1816 this->SetDirty();
1817 break;
1818
1820 ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1821 break;
1822
1823 case WID_ID_FILTER_BY_ACC_CARGO: // Cargo filter dropdown
1824 ShowDropDownList(this, this->BuildCargoDropDownList(), this->accepted_cargo_filter_criteria, widget);
1825 break;
1826
1827 case WID_ID_FILTER_BY_PROD_CARGO: // Cargo filter dropdown
1828 ShowDropDownList(this, this->BuildCargoDropDownList(), this->produced_cargo_filter_criteria, widget);
1829 break;
1830
1831 case WID_ID_INDUSTRY_LIST: {
1832 auto it = this->vscroll->GetScrolledItemFromWidget(this->industries, pt.y, this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.top);
1833 if (it != this->industries.end()) {
1834 if (_ctrl_pressed) {
1835 ShowExtraViewportWindow((*it)->location.tile);
1836 } else {
1837 ScrollMainWindowToTile((*it)->location.tile);
1838 }
1839 }
1840 break;
1841 }
1842 }
1843 }
1844
1845 void OnDropdownSelect(WidgetID widget, int index, int) override
1846 {
1847 switch (widget) {
1849 if (this->industries.SortType() != index) {
1850 this->industries.SetSortType(index);
1852 }
1853 break;
1854 }
1855
1857 this->SetAcceptedCargoFilter(index);
1859 break;
1860 }
1861
1863 this->SetProducedCargoFilter(index);
1865 break;
1866 }
1867 }
1868 }
1869
1870 void OnResize() override
1871 {
1872 this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Vertical());
1873 this->hscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Horizontal());
1874 }
1875
1876 void OnEditboxChanged(WidgetID wid) override
1877 {
1878 if (wid == WID_ID_FILTER) {
1879 this->string_filter.SetFilterTerm(this->industry_editbox.text.GetText());
1880 this->InvalidateData(IDIWD_FORCE_REBUILD);
1881 }
1882 }
1883
1884 void OnPaint() override
1885 {
1886 if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1887 this->hscroll->SetCount(this->GetIndustryListWidth());
1888 this->DrawWidgets();
1889 }
1890
1892 const IntervalTimer<TimerWindow> rebuild_interval = {std::chrono::seconds(3), [this](auto) {
1893 this->industries.ForceResort();
1895 }};
1896
1902 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1903 {
1904 switch (data) {
1905 case IDIWD_FORCE_REBUILD:
1906 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1907 this->industries.ForceRebuild();
1908 break;
1909
1910 case IDIWD_PRODUCTION_CHANGE:
1911 if (this->industries.SortType() == 2) this->industries.ForceResort();
1912 break;
1913
1914 default:
1915 this->industries.ForceResort();
1916 break;
1917 }
1918 }
1919
1920 static inline HotkeyList hotkeys {"industrydirectory", {
1921 Hotkey('F', "focus_filter_box", WID_ID_FILTER),
1922 }};
1923};
1924
1925Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1926
1927/* Available station sorting functions. */
1928const std::initializer_list<GUIIndustryList::SortFunction * const> IndustryDirectoryWindow::sorter_funcs = {
1929 &IndustryNameSorter,
1930 &IndustryTypeSorter,
1931 &IndustryProductionSorter,
1932 &IndustryTransportedCargoSorter
1933};
1934
1935CargoType IndustryDirectoryWindow::produced_cargo_filter = CargoFilterCriteria::CF_ANY;
1936
1937
1940 WDP_AUTO, "list_industries", 428, 190,
1942 {},
1944 &IndustryDirectoryWindow::hotkeys
1945);
1946
1947void ShowIndustryDirectory()
1948{
1949 AllocateWindowDescFront<IndustryDirectoryWindow>(_industry_directory_desc, 0);
1950}
1951
1955 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1956 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION),
1957 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1958 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1959 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1960 EndContainer(),
1964 EndContainer(),
1966 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1967 SetStringTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1968 NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1969 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1970 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1971 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1972 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1973 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1974 EndContainer(),
1975};
1976
1979 WDP_AUTO, "industry_cargoes", 300, 210,
1981 {},
1983);
1984
1994
1995static const uint MAX_CARGOES = 16;
1996
2000 static int blob_distance;
2001
2007
2008 static const int INDUSTRY_LINE_COLOUR;
2009 static const int CARGO_LINE_COLOUR;
2010
2013 static int industry_width;
2014 static uint max_cargoes;
2015
2016 using Cargoes = uint16_t;
2017 static_assert(std::numeric_limits<Cargoes>::digits >= MAX_CARGOES);
2018
2020 union {
2021 struct {
2022 IndustryType ind_type;
2023 std::array<CargoType, MAX_CARGOES> other_produced;
2024 std::array<CargoType, MAX_CARGOES> other_accepted;
2026 struct {
2027 std::array<CargoType, MAX_CARGOES> vertical_cargoes;
2030 uint8_t num_cargoes;
2031 uint8_t top_end;
2032 uint8_t bottom_end;
2034 struct {
2035 std::array<CargoType, MAX_CARGOES> cargoes;
2039 } u{}; // Data for each type.
2040
2046 {
2047 this->type = type;
2048 }
2049
2055 void MakeIndustry(IndustryType ind_type)
2056 {
2057 this->type = CFT_INDUSTRY;
2058 this->u.industry.ind_type = ind_type;
2059 std::fill(std::begin(this->u.industry.other_accepted), std::end(this->u.industry.other_accepted), INVALID_CARGO);
2060 std::fill(std::begin(this->u.industry.other_produced), std::end(this->u.industry.other_produced), INVALID_CARGO);
2061 }
2062
2069 int ConnectCargo(CargoType cargo, bool producer)
2070 {
2071 assert(this->type == CFT_CARGO);
2072 if (!IsValidCargoType(cargo)) return -1;
2073
2074 /* Find the vertical cargo column carrying the cargo. */
2075 int column = -1;
2076 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2077 if (cargo == this->u.cargo.vertical_cargoes[i]) {
2078 column = i;
2079 break;
2080 }
2081 }
2082 if (column < 0) return -1;
2083
2084 if (producer) {
2085 assert(!HasBit(this->u.cargo.supp_cargoes, column));
2086 SetBit(this->u.cargo.supp_cargoes, column);
2087 } else {
2088 assert(!HasBit(this->u.cargo.cust_cargoes, column));
2089 SetBit(this->u.cargo.cust_cargoes, column);
2090 }
2091 return column;
2092 }
2093
2099 {
2100 assert(this->type == CFT_CARGO);
2101
2102 return this->u.cargo.supp_cargoes != 0 || this->u.cargo.cust_cargoes != 0;
2103 }
2104
2110 void MakeCargo(const std::span<const CargoType> cargoes)
2111 {
2112 this->type = CFT_CARGO;
2113 assert(std::size(cargoes) <= std::size(this->u.cargo.vertical_cargoes));
2114 auto insert = std::copy_if(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo.vertical_cargoes), IsValidCargoType);
2115 this->u.cargo.num_cargoes = static_cast<uint8_t>(std::distance(std::begin(this->u.cargo.vertical_cargoes), insert));
2116 CargoTypeComparator comparator;
2117 std::sort(std::begin(this->u.cargo.vertical_cargoes), insert, comparator);
2118 std::fill(insert, std::end(this->u.cargo.vertical_cargoes), INVALID_CARGO);
2119 this->u.cargo.top_end = false;
2120 this->u.cargo.bottom_end = false;
2121 this->u.cargo.supp_cargoes = 0;
2122 this->u.cargo.cust_cargoes = 0;
2123 }
2124
2130 void MakeCargoLabel(const std::span<const CargoType> cargoes, bool left_align)
2131 {
2132 this->type = CFT_CARGO_LABEL;
2133 assert(std::size(cargoes) <= std::size(this->u.cargo_label.cargoes));
2134 auto insert = std::copy(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo_label.cargoes));
2135 std::fill(insert, std::end(this->u.cargo_label.cargoes), INVALID_CARGO);
2136 this->u.cargo_label.left_align = left_align;
2137 }
2138
2144 {
2145 this->type = CFT_HEADER;
2146 this->u.header = textid;
2147 }
2148
2154 int GetCargoBase(int xpos) const
2155 {
2156 assert(this->type == CFT_CARGO);
2157 int n = this->u.cargo.num_cargoes;
2158
2159 return xpos + cargo_field_width / 2 - (CargoesField::cargo_line.width * n + CargoesField::cargo_space.width * (n - 1)) / 2;
2160 }
2161
2167 void Draw(int xpos, int ypos) const
2168 {
2169 switch (this->type) {
2170 case CFT_EMPTY:
2171 case CFT_SMALL_EMPTY:
2172 break;
2173
2174 case CFT_HEADER:
2175 ypos += (small_height - GetCharacterHeight(FS_NORMAL)) / 2;
2176 DrawString(xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
2177 break;
2178
2179 case CFT_INDUSTRY: {
2180 int ypos1 = ypos + vert_inter_industry_space / 2;
2181 int ypos2 = ypos + normal_height - 1 - vert_inter_industry_space / 2;
2182 int xpos2 = xpos + industry_width - 1;
2183 DrawRectOutline({xpos, ypos1, xpos2, ypos2}, INDUSTRY_LINE_COLOUR);
2185 if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2186 const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
2187 DrawString(xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
2188
2189 /* Draw the industry legend. */
2190 int blob_left, blob_right;
2191 if (_current_text_dir == TD_RTL) {
2192 blob_right = xpos2 - blob_distance;
2193 blob_left = blob_right - CargoesField::legend.width;
2194 } else {
2195 blob_left = xpos + blob_distance;
2196 blob_right = blob_left + CargoesField::legend.width;
2197 }
2198 GfxFillRect(blob_left, ypos2 - blob_distance - CargoesField::legend.height, blob_right, ypos2 - blob_distance, PC_BLACK); // Border
2199 GfxFillRect(blob_left + 1, ypos2 - blob_distance - CargoesField::legend.height + 1, blob_right - 1, ypos2 - blob_distance - 1, indsp->map_colour);
2200 } else {
2201 DrawString(xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
2202 }
2203
2204 /* Draw the other_produced/other_accepted cargoes. */
2205 std::span<const CargoType> other_right, other_left;
2206 if (_current_text_dir == TD_RTL) {
2207 other_right = this->u.industry.other_accepted;
2208 other_left = this->u.industry.other_produced;
2209 } else {
2210 other_right = this->u.industry.other_produced;
2211 other_left = this->u.industry.other_accepted;
2212 }
2214 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2215 if (IsValidCargoType(other_right[i])) {
2216 const CargoSpec *csp = CargoSpec::Get(other_right[i]);
2217 int xp = xpos + industry_width + CargoesField::cargo_stub.width;
2218 DrawHorConnection(xpos + industry_width, xp - 1, ypos1, csp);
2219 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2220 }
2221 if (IsValidCargoType(other_left[i])) {
2222 const CargoSpec *csp = CargoSpec::Get(other_left[i]);
2223 int xp = xpos - CargoesField::cargo_stub.width;
2224 DrawHorConnection(xp + 1, xpos - 1, ypos1, csp);
2225 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2226 }
2228 }
2229 break;
2230 }
2231
2232 case CFT_CARGO: {
2233 int cargo_base = this->GetCargoBase(xpos);
2234 int top = ypos + (this->u.cargo.top_end ? vert_inter_industry_space / 2 + 1 : 0);
2235 int bot = ypos - (this->u.cargo.bottom_end ? vert_inter_industry_space / 2 + 1 : 0) + normal_height - 1;
2236 int colpos = cargo_base;
2237 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2238 if (this->u.cargo.top_end) GfxDrawLine(colpos, top - 1, colpos + CargoesField::cargo_line.width - 1, top - 1, CARGO_LINE_COLOUR);
2239 if (this->u.cargo.bottom_end) GfxDrawLine(colpos, bot + 1, colpos + CargoesField::cargo_line.width - 1, bot + 1, CARGO_LINE_COLOUR);
2240 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2241 colpos++;
2242 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
2243 GfxFillRect(colpos, top, colpos + CargoesField::cargo_line.width - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
2244 colpos += CargoesField::cargo_line.width - 2;
2245 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2246 colpos += 1 + CargoesField::cargo_space.width;
2247 }
2248
2249 Cargoes hor_left, hor_right;
2250 if (_current_text_dir == TD_RTL) {
2251 hor_left = this->u.cargo.cust_cargoes;
2252 hor_right = this->u.cargo.supp_cargoes;
2253 } else {
2254 hor_left = this->u.cargo.supp_cargoes;
2255 hor_right = this->u.cargo.cust_cargoes;
2256 }
2258 for (uint i = 0; i < MAX_CARGOES; i++) {
2259 if (HasBit(hor_left, i)) {
2260 int col = i;
2261 int dx = 0;
2262 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2263 for (; col > 0; col--) {
2264 int lf = cargo_base + col * CargoesField::cargo_line.width + (col - 1) * CargoesField::cargo_space.width;
2265 DrawHorConnection(lf, lf + CargoesField::cargo_space.width - dx, ypos, csp);
2266 dx = 1;
2267 }
2268 DrawHorConnection(xpos, cargo_base - dx, ypos, csp);
2269 }
2270 if (HasBit(hor_right, i)) {
2271 int col = i;
2272 int dx = 0;
2273 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2274 for (; col < this->u.cargo.num_cargoes - 1; col++) {
2275 int lf = cargo_base + (col + 1) * CargoesField::cargo_line.width + col * CargoesField::cargo_space.width;
2276 DrawHorConnection(lf + dx - 1, lf + CargoesField::cargo_space.width - 1, ypos, csp);
2277 dx = 1;
2278 }
2279 DrawHorConnection(cargo_base + col * CargoesField::cargo_space.width + (col + 1) * CargoesField::cargo_line.width - 1 + dx, xpos + CargoesField::cargo_field_width - 1, ypos, csp);
2280 }
2282 }
2283 break;
2284 }
2285
2286 case CFT_CARGO_LABEL:
2288 for (uint i = 0; i < MAX_CARGOES; i++) {
2289 if (IsValidCargoType(this->u.cargo_label.cargoes[i])) {
2290 const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
2291 DrawString(xpos + WidgetDimensions::scaled.framerect.left, xpos + industry_width - 1 - WidgetDimensions::scaled.framerect.right, ypos, csp->name, TC_WHITE,
2292 (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
2293 }
2295 }
2296 break;
2297
2298 default:
2299 NOT_REACHED();
2300 }
2301 }
2302
2310 CargoType CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
2311 {
2312 assert(this->type == CFT_CARGO);
2313
2314 /* Vertical matching. */
2315 int cpos = this->GetCargoBase(0);
2316 uint col;
2317 for (col = 0; col < this->u.cargo.num_cargoes; col++) {
2318 if (pt.x < cpos) break;
2319 if (pt.x < cpos + (int)CargoesField::cargo_line.width) return this->u.cargo.vertical_cargoes[col];
2321 }
2322 /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
2323
2325 uint row;
2326 for (row = 0; row < MAX_CARGOES; row++) {
2327 if (pt.y < vpos) return INVALID_CARGO;
2328 if (pt.y < vpos + GetCharacterHeight(FS_NORMAL)) break;
2330 }
2331 if (row == MAX_CARGOES) return INVALID_CARGO;
2332
2333 /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
2334 if (col == 0) {
2335 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2336 if (left != nullptr) {
2337 if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
2338 if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
2339 }
2340 return INVALID_CARGO;
2341 }
2342 if (col == this->u.cargo.num_cargoes) {
2343 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2344 if (right != nullptr) {
2345 if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
2346 if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
2347 }
2348 return INVALID_CARGO;
2349 }
2350 if (row >= col) {
2351 /* Clicked somewhere in-between vertical cargo connection.
2352 * Since the horizontal connection is made in the same order as the vertical list, the above condition
2353 * ensures we are left-below the main diagonal, thus at the supplying side.
2354 */
2355 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2356 return INVALID_CARGO;
2357 }
2358 /* Clicked at a customer connection. */
2359 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2360 return INVALID_CARGO;
2361 }
2362
2369 {
2370 assert(this->type == CFT_CARGO_LABEL);
2371
2373 uint row;
2374 for (row = 0; row < MAX_CARGOES; row++) {
2375 if (pt.y < vpos) return INVALID_CARGO;
2376 if (pt.y < vpos + GetCharacterHeight(FS_NORMAL)) break;
2378 }
2379 if (row == MAX_CARGOES) return INVALID_CARGO;
2380 return this->u.cargo_label.cargoes[row];
2381 }
2382
2383private:
2391 static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
2392 {
2393 GfxDrawLine(left, top, right, top, CARGO_LINE_COLOUR);
2394 GfxFillRect(left, top + 1, right, top + CargoesField::cargo_line.height - 2, csp->legend_colour, FILLRECT_OPAQUE);
2395 GfxDrawLine(left, top + CargoesField::cargo_line.height - 1, right, top + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2396 }
2397};
2398
2399static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::produced_cargo)>);
2400static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::accepts_cargo)>);
2401
2407
2414
2416
2419
2423
2429 {
2430 CargoesField *ind_fld = this->columns + column;
2431 CargoesField *cargo_fld = this->columns + column + 1;
2432 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2433
2434 std::fill(std::begin(ind_fld->u.industry.other_produced), std::end(ind_fld->u.industry.other_produced), INVALID_CARGO);
2435
2436 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2437 CargoType others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
2438 int other_count = 0;
2439
2440 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2441 assert(CargoesField::max_cargoes <= std::size(indsp->produced_cargo));
2442 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2443 int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
2444 if (col < 0) others[other_count++] = indsp->produced_cargo[i];
2445 }
2446
2447 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2448 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2449 if (HasBit(cargo_fld->u.cargo.supp_cargoes, i)) ind_fld->u.industry.other_produced[i] = others[--other_count];
2450 }
2451 } else {
2452 /* Houses only display cargo that towns produce. */
2453 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2454 CargoType cargo_type = cargo_fld->u.cargo.vertical_cargoes[i];
2456 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) cargo_fld->ConnectCargo(cargo_type, true);
2457 }
2458 }
2459 }
2460
2466 void MakeCargoLabel(int column, bool accepting)
2467 {
2468 CargoType cargoes[MAX_CARGOES];
2469 std::fill(std::begin(cargoes), std::end(cargoes), INVALID_CARGO);
2470
2471 CargoesField *label_fld = this->columns + column;
2472 CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2473
2474 assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2475 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2476 int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2477 if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2478 }
2479 label_fld->MakeCargoLabel(cargoes, accepting);
2480 }
2481
2482
2488 {
2489 CargoesField *ind_fld = this->columns + column;
2490 CargoesField *cargo_fld = this->columns + column - 1;
2491 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2492
2493 std::fill(std::begin(ind_fld->u.industry.other_accepted), std::end(ind_fld->u.industry.other_accepted), INVALID_CARGO);
2494
2495 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2496 CargoType others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2497 int other_count = 0;
2498
2499 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2500 assert(CargoesField::max_cargoes <= std::size(indsp->accepts_cargo));
2501 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2502 int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2503 if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2504 }
2505
2506 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2507 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2508 if (!HasBit(cargo_fld->u.cargo.cust_cargoes, i)) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2509 }
2510 } else {
2511 /* Houses only display what is demanded. */
2512 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2513 for (const auto &hs : HouseSpec::Specs()) {
2514 if (!hs.enabled) continue;
2515
2516 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2517 if (hs.cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs.accepts_cargo[j]) {
2518 cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2519 goto next_cargo;
2520 }
2521 }
2522 }
2523next_cargo: ;
2524 }
2525 }
2526 }
2527};
2528
2529
2558 typedef std::vector<CargoesRow> Fields;
2559
2560 Fields fields{};
2561 uint ind_cargo = 0;
2564 Scrollbar *vscroll = nullptr;
2565
2567 {
2568 this->OnInit();
2569 this->CreateNestedTree();
2570 this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2571 this->FinishInitNested(0);
2572 this->OnInvalidateData(id);
2573 }
2574
2575 void OnInit() override
2576 {
2577 /* Initialize static CargoesField size variables. */
2578 Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2579 d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2582 CargoesField::small_height = d.height;
2583
2584 /* Size of the legend blob -- slightly larger than the smallmap legend blob. */
2586 CargoesField::legend.width = CargoesField::legend.height * 9 / 6;
2587
2588 /* Size of cargo lines. */
2591
2592 /* Size of border between cargo lines and industry boxes. */
2595
2596 /* Size of space between cargo lines. */
2599
2600 /* Size of cargo stub (unconnected cargo line.) */
2602 CargoesField::cargo_stub.height = CargoesField::cargo_line.height; /* Unused */
2603
2606
2607 /* Decide about the size of the box holding the text of an industry type. */
2608 this->ind_textsize.width = 0;
2609 this->ind_textsize.height = 0;
2610 CargoesField::max_cargoes = 0;
2611 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2612 const IndustrySpec *indsp = GetIndustrySpec(it);
2613 if (!indsp->enabled) continue;
2615 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->accepts_cargo, IsValidCargoType));
2616 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->produced_cargo, IsValidCargoType));
2617 }
2618 d.width = std::max(d.width, this->ind_textsize.width);
2619 d.height = this->ind_textsize.height;
2620 this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2621
2622 /* Compute max size of the cargo texts. */
2623 this->cargo_textsize.width = 0;
2624 this->cargo_textsize.height = 0;
2625 for (const CargoSpec *csp : CargoSpec::Iterate()) {
2626 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(csp->name));
2627 }
2628 d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2629 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2630
2632 /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2634 d.height = std::max(d.height + WidgetDimensions::scaled.frametext.Vertical(), min_ind_height);
2635
2638
2639 /* Width of a #CFT_CARGO field. */
2641 }
2642
2643 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
2644 {
2645 switch (widget) {
2646 case WID_IC_PANEL:
2647 fill.height = resize.height = CargoesField::normal_height;
2650 break;
2651
2653 size.width = std::max(size.width, this->ind_textsize.width + padding.width);
2654 break;
2655
2657 size.width = std::max(size.width, this->cargo_textsize.width + padding.width);
2658 break;
2659 }
2660 }
2661
2662 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
2663 {
2664 if (widget != WID_IC_CAPTION) return this->Window::GetWidgetString(widget, stringid);
2665
2666 if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2667 const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2668 return GetString(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, indsp->name);
2669 } else {
2670 const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2671 return GetString(STR_INDUSTRY_CARGOES_CARGO_CAPTION, csp->name);
2672 }
2673 }
2674
2681 static bool HasCommonValidCargo(const std::span<const CargoType> cargoes1, const std::span<const CargoType> cargoes2)
2682 {
2683 for (const CargoType cargo_type1 : cargoes1) {
2684 if (!IsValidCargoType(cargo_type1)) continue;
2685 for (const CargoType cargo_type2 : cargoes2) {
2686 if (cargo_type1 == cargo_type2) return true;
2687 }
2688 }
2689 return false;
2690 }
2691
2697 static bool HousesCanSupply(const std::span<const CargoType> cargoes)
2698 {
2699 for (const CargoType cargo_type : cargoes) {
2700 if (!IsValidCargoType(cargo_type)) continue;
2702 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) return true;
2703 }
2704 return false;
2705 }
2706
2712 static bool HousesCanAccept(const std::span<const CargoType> cargoes)
2713 {
2714 HouseZones climate_mask = GetClimateMaskForLandscape();
2715
2716 for (const CargoType cargo_type : cargoes) {
2717 if (!IsValidCargoType(cargo_type)) continue;
2718
2719 for (const auto &hs : HouseSpec::Specs()) {
2720 if (!hs.enabled || !hs.building_availability.Any(climate_mask)) continue;
2721
2722 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2723 if (hs.cargo_acceptance[j] > 0 && cargo_type == hs.accepts_cargo[j]) return true;
2724 }
2725 }
2726 }
2727 return false;
2728 }
2729
2735 static int CountMatchingAcceptingIndustries(const std::span<const CargoType> cargoes)
2736 {
2737 int count = 0;
2738 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2739 const IndustrySpec *indsp = GetIndustrySpec(it);
2740 if (!indsp->enabled) continue;
2741
2742 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) count++;
2743 }
2744 return count;
2745 }
2746
2752 static int CountMatchingProducingIndustries(const std::span<const CargoType> cargoes)
2753 {
2754 int count = 0;
2755 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2756 const IndustrySpec *indsp = GetIndustrySpec(it);
2757 if (!indsp->enabled) continue;
2758
2759 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) count++;
2760 }
2761 return count;
2762 }
2763
2770 void ShortenCargoColumn(int column, int top, int bottom)
2771 {
2772 while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2773 this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2774 top++;
2775 }
2776 this->fields[top].columns[column].u.cargo.top_end = true;
2777
2778 while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2779 this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2780 bottom--;
2781 }
2782 this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2783 }
2784
2791 void PlaceIndustry(int row, int col, IndustryType it)
2792 {
2793 assert(this->fields[row].columns[col].type == CFT_EMPTY);
2794 this->fields[row].columns[col].MakeIndustry(it);
2795 if (col == 0) {
2796 this->fields[row].ConnectIndustryProduced(col);
2797 } else {
2798 this->fields[row].ConnectIndustryAccepted(col);
2799 }
2800 }
2801
2806 {
2807 if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2808
2809 /* Only notify the smallmap window if it exists. In particular, do not
2810 * bring it to the front to prevent messing up any nice layout of the user. */
2812 }
2813
2818 void ComputeIndustryDisplay(IndustryType displayed_it)
2819 {
2820 this->ind_cargo = displayed_it;
2821 _displayed_industries.reset();
2822 _displayed_industries.set(displayed_it);
2823
2824 this->fields.clear();
2825 CargoesRow &first_row = this->fields.emplace_back();
2826 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2827 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2828 first_row.columns[2].MakeEmpty(CFT_SMALL_EMPTY);
2829 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2830 first_row.columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2831
2832 const IndustrySpec *central_sp = GetIndustrySpec(displayed_it);
2833 bool houses_supply = HousesCanSupply(central_sp->accepts_cargo);
2834 bool houses_accept = HousesCanAccept(central_sp->produced_cargo);
2835 /* Make a field consisting of two cargo columns. */
2836 int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo) + houses_supply;
2837 int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo) + houses_accept;
2838 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.
2839 for (int i = 0; i < num_indrows; i++) {
2840 CargoesRow &row = this->fields.emplace_back();
2841 row.columns[0].MakeEmpty(CFT_EMPTY);
2842 row.columns[1].MakeCargo(central_sp->accepts_cargo);
2843 row.columns[2].MakeEmpty(CFT_EMPTY);
2844 row.columns[3].MakeCargo(central_sp->produced_cargo);
2845 row.columns[4].MakeEmpty(CFT_EMPTY);
2846 }
2847 /* Add central industry. */
2848 int central_row = 1 + num_indrows / 2;
2849 this->fields[central_row].columns[2].MakeIndustry(displayed_it);
2850 this->fields[central_row].ConnectIndustryProduced(2);
2851 this->fields[central_row].ConnectIndustryAccepted(2);
2852
2853 /* Add cargo labels. */
2854 this->fields[central_row - 1].MakeCargoLabel(2, true);
2855 this->fields[central_row + 1].MakeCargoLabel(2, false);
2856
2857 /* Add suppliers and customers of the 'it' industry. */
2858 int supp_count = 0;
2859 int cust_count = 0;
2860 for (IndustryType it : _sorted_industry_types) {
2861 const IndustrySpec *indsp = GetIndustrySpec(it);
2862 if (!indsp->enabled) continue;
2863
2864 if (HasCommonValidCargo(central_sp->accepts_cargo, indsp->produced_cargo)) {
2865 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2866 _displayed_industries.set(it);
2867 supp_count++;
2868 }
2869 if (HasCommonValidCargo(central_sp->produced_cargo, indsp->accepts_cargo)) {
2870 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2871 _displayed_industries.set(it);
2872 cust_count++;
2873 }
2874 }
2875 if (houses_supply) {
2876 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2877 supp_count++;
2878 }
2879 if (houses_accept) {
2880 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2881 cust_count++;
2882 }
2883
2884 this->ShortenCargoColumn(1, 1, num_indrows);
2885 this->ShortenCargoColumn(3, 1, num_indrows);
2886 this->vscroll->SetCount(num_indrows);
2887 this->SetDirty();
2888 this->NotifySmallmap();
2889 }
2890
2896 {
2897 this->ind_cargo = cargo_type + NUM_INDUSTRYTYPES;
2898 _displayed_industries.reset();
2899
2900 this->fields.clear();
2901 CargoesRow &first_row = this->fields.emplace_back();
2902 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2903 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2904 first_row.columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2905 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2906 first_row.columns[4].MakeEmpty(CFT_SMALL_EMPTY);
2907
2908 auto cargoes = std::span(&cargo_type, 1);
2909 bool houses_supply = HousesCanSupply(cargoes);
2910 bool houses_accept = HousesCanAccept(cargoes);
2911 int num_supp = CountMatchingProducingIndustries(cargoes) + houses_supply + 1; // Ensure room for the cargo label.
2912 int num_cust = CountMatchingAcceptingIndustries(cargoes) + houses_accept;
2913 int num_indrows = std::max(num_supp, num_cust);
2914 for (int i = 0; i < num_indrows; i++) {
2915 CargoesRow &row = this->fields.emplace_back();
2916 row.columns[0].MakeEmpty(CFT_EMPTY);
2917 row.columns[1].MakeCargo(cargoes);
2918 row.columns[2].MakeEmpty(CFT_EMPTY);
2919 row.columns[3].MakeEmpty(CFT_EMPTY);
2920 row.columns[4].MakeEmpty(CFT_EMPTY);
2921 }
2922
2923 this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2924
2925 /* Add suppliers and customers of the cargo. */
2926 int supp_count = 0;
2927 int cust_count = 0;
2928 for (IndustryType it : _sorted_industry_types) {
2929 const IndustrySpec *indsp = GetIndustrySpec(it);
2930 if (!indsp->enabled) continue;
2931
2932 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) {
2933 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2934 _displayed_industries.set(it);
2935 supp_count++;
2936 }
2937 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) {
2938 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2939 _displayed_industries.set(it);
2940 cust_count++;
2941 }
2942 }
2943 if (houses_supply) {
2944 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2945 supp_count++;
2946 }
2947 if (houses_accept) {
2948 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2949 cust_count++;
2950 }
2951
2952 this->ShortenCargoColumn(1, 1, num_indrows);
2953 this->vscroll->SetCount(num_indrows);
2954 this->SetDirty();
2955 this->NotifySmallmap();
2956 }
2957
2965 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
2966 {
2967 if (!gui_scope) return;
2968 if (data == NUM_INDUSTRYTYPES) {
2970 return;
2971 }
2972
2973 assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2974 this->ComputeIndustryDisplay(data);
2975 }
2976
2977 void DrawWidget(const Rect &r, WidgetID widget) const override
2978 {
2979 if (widget != WID_IC_PANEL) return;
2980
2981 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
2982 DrawPixelInfo tmp_dpi;
2983 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
2984 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2985
2987 if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::cargo_field_width) / 2;
2988 int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2989
2990 const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2991 int vpos = WidgetDimensions::scaled.frametext.top - WidgetDimensions::scaled.bevel.top - this->vscroll->GetPosition() * nwp->resize_y;
2992 int row_height = CargoesField::small_height;
2993 for (const auto &field : this->fields) {
2994 if (vpos + row_height >= 0) {
2995 int xpos = left_pos;
2996 int col, dir;
2997 if (_current_text_dir == TD_RTL) {
2998 col = last_column;
2999 dir = -1;
3000 } else {
3001 col = 0;
3002 dir = 1;
3003 }
3004 while (col >= 0 && col <= last_column) {
3005 field.columns[col].Draw(xpos, vpos);
3007 col += dir;
3008 }
3009 }
3010 vpos += row_height;
3011 if (vpos >= height) break;
3012 row_height = CargoesField::normal_height;
3013 }
3014 }
3015
3024 {
3025 const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
3026 pt.x -= nw->pos_x;
3027 pt.y -= nw->pos_y;
3028
3029 int vpos = WidgetDimensions::scaled.frametext.top + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
3030 if (pt.y < vpos) return false;
3031
3032 int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
3033 if (row + 1 >= (int)this->fields.size()) return false;
3034 vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
3035 row++; // rebase row to match index of this->fields.
3036
3038 if (pt.x < xpos) return false;
3039 int column;
3040 for (column = 0; column <= 5; column++) {
3042 if (pt.x < xpos + width) break;
3043 xpos += width;
3044 }
3045 int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
3046 if (column > num_columns) return false;
3047 xpos = pt.x - xpos;
3048
3049 /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
3050 fieldxy->y = row;
3051 xy->y = vpos;
3052 if (_current_text_dir == TD_RTL) {
3053 fieldxy->x = num_columns - column;
3054 xy->x = ((column & 1) ? CargoesField::cargo_field_width : CargoesField::industry_width) - xpos;
3055 } else {
3056 fieldxy->x = column;
3057 xy->x = xpos;
3058 }
3059 return true;
3060 }
3061
3062 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
3063 {
3064 switch (widget) {
3065 case WID_IC_PANEL: {
3066 Point fieldxy, xy;
3067 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
3068
3069 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3070 switch (fld->type) {
3071 case CFT_INDUSTRY:
3073 break;
3074
3075 case CFT_CARGO: {
3076 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3077 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3078 CargoType cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3079 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3080 break;
3081 }
3082
3083 case CFT_CARGO_LABEL: {
3084 CargoType cargo_type = fld->CargoLabelClickedAt(xy);
3085 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3086 break;
3087 }
3088
3089 default:
3090 break;
3091 }
3092 break;
3093 }
3094
3095 case WID_IC_NOTIFY:
3099
3100 if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
3101 if (FindWindowByClass(WC_SMALLMAP) == nullptr) ShowSmallMap();
3102 this->NotifySmallmap();
3103 }
3104 break;
3105
3106 case WID_IC_CARGO_DROPDOWN: {
3107 DropDownList lst;
3109 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
3110 lst.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
3111 }
3112 if (!lst.empty()) {
3113 int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
3114 ShowDropDownList(this, std::move(lst), selected, WID_IC_CARGO_DROPDOWN);
3115 }
3116 break;
3117 }
3118
3119 case WID_IC_IND_DROPDOWN: {
3120 DropDownList lst;
3121 for (IndustryType ind : _sorted_industry_types) {
3122 const IndustrySpec *indsp = GetIndustrySpec(ind);
3123 if (!indsp->enabled) continue;
3124 lst.push_back(MakeDropDownListStringItem(indsp->name, ind));
3125 }
3126 if (!lst.empty()) {
3127 int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
3128 ShowDropDownList(this, std::move(lst), selected, WID_IC_IND_DROPDOWN);
3129 }
3130 break;
3131 }
3132 }
3133 }
3134
3135 void OnDropdownSelect(WidgetID widget, int index, int) override
3136 {
3137 if (index < 0) return;
3138
3139 switch (widget) {
3141 this->ComputeCargoDisplay(index);
3142 break;
3143
3145 this->ComputeIndustryDisplay(index);
3146 break;
3147 }
3148 }
3149
3150 bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
3151 {
3152 if (widget != WID_IC_PANEL) return false;
3153
3154 Point fieldxy, xy;
3155 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return false;
3156
3157 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3158 CargoType cargo_type = INVALID_CARGO;
3159 switch (fld->type) {
3160 case CFT_CARGO: {
3161 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3162 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3163 cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3164 break;
3165 }
3166
3167 case CFT_CARGO_LABEL: {
3168 cargo_type = fld->CargoLabelClickedAt(xy);
3169 break;
3170 }
3171
3172 case CFT_INDUSTRY:
3173 if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
3174 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP), close_cond);
3175 }
3176 return true;
3177
3178 default:
3179 break;
3180 }
3181 if (IsValidCargoType(cargo_type) && (this->ind_cargo < NUM_INDUSTRYTYPES || cargo_type != this->ind_cargo - NUM_INDUSTRYTYPES)) {
3182 const CargoSpec *csp = CargoSpec::Get(cargo_type);
3183 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, csp->name), close_cond);
3184 return true;
3185 }
3186
3187 return false;
3188 }
3189
3190 void OnResize() override
3191 {
3192 this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL, WidgetDimensions::scaled.framerect.Vertical() + CargoesField::small_height);
3193 }
3194};
3195
3200static void ShowIndustryCargoesWindow(IndustryType id)
3201{
3202 if (id >= NUM_INDUSTRYTYPES) {
3203 for (IndustryType ind : _sorted_industry_types) {
3204 const IndustrySpec *indsp = GetIndustrySpec(ind);
3205 if (indsp->enabled) {
3206 id = ind;
3207 break;
3208 }
3209 }
3210 if (id >= NUM_INDUSTRYTYPES) return;
3211 }
3212
3214 if (w != nullptr) {
3215 w->InvalidateData(id);
3216 return;
3217 }
3218 new IndustryCargoesWindow(id);
3219}
3220
Class for backupping variables and making sure they are restored later.
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:106
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.
Common return value for all commands.
bool Succeeded() const
Did this command succeed?
StringID GetErrorMessage() const
Returns the error message of a command.
std::string GetDecodedString() const
Decode the encoded string.
Definition strings.cpp:208
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:2413
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2404
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:2510
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.
Commands
List of 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:451
void ShowDropDownList(Window *w, DropDownList &&list, int selected, WidgetID button, uint width, bool instant_close, bool persist)
Show a drop down list.
Definition dropdown.cpp:414
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:77
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:958
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:887
void DrawRectOutline(const Rect &r, int colour, int width, int dash)
Draw the outline of a Rect.
Definition gfx.cpp:457
Dimension GetStringListBoundingBox(std::span< const StringID > list, FontSize fontsize)
Get maximum dimension of a list of strings.
Definition gfx.cpp:925
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:658
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:38
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
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:1024
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:775
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:1554
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:252
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:251
@ SA_LEFT
Left align the text.
Definition gfx_type.h:383
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:385
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:384
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:302
@ TC_FORCED
Ignore colour changes from strings.
Definition gfx_type.h:327
@ FILLRECT_OPAQUE
Fill rectangle with a single colour.
Definition gfx_type.h:340
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 NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
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 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:955
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition gfx.cpp:1535
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.
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.
static constexpr NWidgetPart _nested_industry_view_widgets[]
Widget definition of the view industry gui.
std::bitset< NUM_INDUSTRYTYPES > _displayed_industries
Communication from the industry chain window to the smallmap window about what industries to display.
void CcBuildIndustry(Commands, const CommandCost &result, TileIndex tile, IndustryType indtype, uint32_t, bool, uint32_t)
Command callback.
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.
static constexpr NWidgetPart _nested_industry_directory_widgets[]
Widget definition of the industry directory gui.
static constexpr NWidgetPart _nested_industry_cargoes_widgets[]
Widgets of the industry cargoes window.
void SortIndustryTypes()
Initialize the list of sorted industry types.
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
static debug_inline uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition map_func.h:424
static debug_inline uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:414
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:688
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:96
static constexpr CargoType CF_ANY
Show all items independent of carried cargo (i.e. no filtering)
Definition cargo_type.h:95
bool _networking
are we in networking mode?
Definition network.cpp:67
Basic functions/variables used all over the place.
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 const uint8_t PC_WHITE
White palette colour.
static const uint8_t PC_BLACK
Black palette colour.
static const uint8_t PC_YELLOW
Yellow 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.
Functions related to sound.
@ SND_15_BEEP
19 == 0x13 GUI button click
Definition sound_type.h:66
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:425
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:337
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:91
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:425
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:57
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:219
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.
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.
static const int INDUSTRY_LINE_COLOUR
Line colour of the industry type box.
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.
static const int CARGO_LINE_COLOUR
Line colour around the cargo.
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.
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.
struct CargoesField::@9::@12 cargo_label
Label data (for CFT_CARGO_LABEL).
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.
CargoType CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
Decide which cargo was clicked at in a CFT_CARGO field.
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::@9::@11 cargo
Cargo data (for CFT_CARGO).
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).
struct CargoesField::@9::@10 industry
Industry data (for CFT_INDUSTRY).
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
SoundSettings sound
sound effect settings
GUISettings gui
settings related to the GUI
uint8_t raw_industry_construction
type of (raw) industry construction (none, "normal", prospecting)
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h:158
const struct GRFFile * grffile
grf file that introduced this entity
uint32_t grfid
grfid that introduced this entity.
bool HasGrfFile() const
Test if this entity was introduced by NewGRF.
std::array< uint8_t, NUM_CARGO > cargo_map
Inverse cargo translation table (CargoType -> local ID)
Definition newgrf.h:137
bool persistent_buildingtools
keep the building tools active after usage
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.
uint8_t map_colour
colour used for the small map
CargoType cargo
Cargo type.
Definition industry.h:81
uint16_t waiting
Amount of cargo waiting to processed.
Definition industry.h:82
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:98
uint8_t prod_level
general production level
Definition industry.h:95
void RecomputeProductionMultipliers()
Recompute #production_rate for current prod_level.
EncodedString text
General text with additional information.
Definition industry.h:115
ProducedCargoes::iterator GetCargoProduced(CargoType cargo)
Get produced cargo slot for a specific cargo type.
Definition industry.h:163
ProducedCargoes produced
produced cargo slots
Definition industry.h:93
AcceptedCargoes accepted
accepted cargo slots
Definition industry.h:94
static uint16_t GetIndustryTypeCount(IndustryType type)
Get the count of industries for this type.
Definition industry.h:247
TileArea location
Location of the industry.
Definition industry.h:89
bool IsCargoProduced() const
Test if this industry produces any cargo.
Definition industry.h:212
Data structure describing how to show the list (what sort direction and criteria).
Size related data of the map.
Definition map_func.h:206
Partial widget specification to allow NWidgets to be written nested.
TileIndex GetCenterTile() const
Get the center tile.
TileIndex tile
The base tile of the area.
Coordinates of a point in 2D.
Templated helper to make a PoolID a single POD value.
Definition pool_type.hpp:43
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.
Tindex index
Index of this pool item.
static bool IsValidID(auto index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
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:212
bool click_beep
Beep on a random selection of buttons.
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:167
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:273
void ReInit(int rx=0, int ry=0, bool reposition=false)
Re-initialize a window, and optionally change its size.
Definition window.cpp:967
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:826
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1778
std::map< WidgetID, QueryString * > querystrings
QueryString associated to WWT_EDITBOX widgets.
Definition window_gui.h:320
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:777
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
Definition window.cpp:3205
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:555
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:318
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:503
void RaiseWidgetWhenLowered(WidgetID widget_index)
Marks a widget as raised and dirty (redraw), when it is marked as lowered.
Definition window_gui.h:478
void DrawSortButtonState(WidgetID widget, SortButtonState state) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:809
ResizeInfo resize
Resize information.
Definition window_gui.h:314
void DisableWidget(WidgetID widget_index)
Sets a widget to disabled.
Definition window_gui.h:391
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1768
bool IsWidgetLowered(WidgetID widget_index) const
Gets the lowered state of a widget.
Definition window_gui.h:491
void RaiseButtons(bool autoraise=false)
Raise the buttons of the window.
Definition window.cpp:529
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:559
void SetTimeout()
Set the timeout flag of the window and initiate the timer.
Definition window_gui.h:355
int top
y position of top edge of the window
Definition window_gui.h:310
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:594
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1791
WindowFlags flags
Window flags.
Definition window_gui.h:300
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:312
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:381
AllWindows< false > Iterate
Iterate all windows in whatever order is easiest.
Definition window_gui.h:932
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:450
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:302
Stuff related to the text buffer GUI.
static debug_inline bool IsTileType(Tile tile, TileType type)
Checks if a tile is a given tiletype.
Definition tile_map.h:150
static const uint TILE_SIZE
Tile size in world coordinates.
Definition tile_type.h:15
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:2527
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:41
@ WWT_PUSHIMGBTN
Normal push-button (no toggle button) with image caption.
@ WWT_EDITBOX
a textbox for typing
Definition widget_type.h:63
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:67
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:45
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:58
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:51
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:53
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:77
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:69
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:61
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:76
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:60
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:57
@ NWID_VIEWPORT
Nested widget containing a viewport.
Definition widget_type.h:74
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:62
@ WWT_DEBUGBOX
NewGRF debug box (at top-right of a window, between WWT_CAPTION and WWT_SHADEBOX)
Definition widget_type.h:55
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition widget_type.h:72
@ 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:1182
Window * FindWindowByClass(WindowClass cls)
Find any window by its class.
Definition window.cpp:1155
Window * BringWindowToFrontById(WindowClass cls, WindowNumber number)
Find a window and make it the relative top-window on the screen.
Definition window.cpp:1265
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:3282
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:218
@ SBS_UP
Sort descending.
Definition window_gui.h:219
@ 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:47
@ 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.