OpenTTD Source 20250818-master-g1850ad1aa2
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
260static constexpr NWidgetPart _nested_build_industry_widgets[] = {
262 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
263 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetStringTip(STR_FUND_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
264 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
265 NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
266 NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
267 EndContainer(),
271 SetStringTip(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP),
273 SetStringTip(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES, STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP),
274 EndContainer(),
275 EndContainer(),
277 NWidget(WWT_MATRIX, COLOUR_DARK_GREEN, WID_DPI_MATRIX_WIDGET), SetMatrixDataTip(1, 0, STR_FUND_INDUSTRY_SELECTION_TOOLTIP), SetFill(1, 0), SetResize(1, 1), SetScrollbar(WID_DPI_SCROLLBAR),
278 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_DPI_SCROLLBAR),
279 EndContainer(),
280 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_DPI_INFOPANEL), SetResize(1, 0),
281 EndContainer(),
283 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_DISPLAY_WIDGET), SetFill(1, 0), SetResize(1, 0),
284 SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
285 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_FUND_WIDGET), SetFill(1, 0), SetResize(1, 0),
286 NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
287 EndContainer(),
288};
289
292 WDP_AUTO, "build_industry", 170, 212,
295 _nested_build_industry_widgets
296);
297
300 IndustryType selected_type = IT_INVALID;
301 std::vector<IndustryType> list{};
302 bool enabled = false;
303 Scrollbar *vscroll = nullptr;
305 GUIBadgeClasses badge_classes{};
306
308 static const int MAX_MINWIDTH_LINEHEIGHTS = 20;
309
310 void UpdateAvailability()
311 {
312 this->enabled = this->selected_type != IT_INVALID && (_game_mode == GM_EDITOR || GetIndustryProbabilityCallback(this->selected_type, IACT_USERCREATION, 1) > 0);
313 }
314
315 void SetupArrays()
316 {
317 this->list.clear();
318
319 /* Fill the arrays with industries.
320 * The tests performed after the enabled allow to load the industries
321 * In the same way they are inserted by grf (if any)
322 */
323 for (IndustryType ind : _sorted_industry_types) {
324 const IndustrySpec *indsp = GetIndustrySpec(ind);
325 if (indsp->enabled) {
326 /* Rule is that editor mode loads all industries.
327 * In game mode, all non raw industries are loaded too
328 * and raw ones are loaded only when setting allows it */
329 if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
330 /* Unselect if the industry is no longer in the list */
331 if (this->selected_type == ind) this->selected_type = IT_INVALID;
332 continue;
333 }
334
335 this->list.push_back(ind);
336 }
337 }
338
339 /* First industry type is selected if the current selection is invalid. */
340 if (this->selected_type == IT_INVALID && !this->list.empty()) this->selected_type = this->list[0];
341
342 this->UpdateAvailability();
343
344 this->vscroll->SetCount(this->list.size());
345 }
346
349 {
350 this->SetWidgetDisabledState(WID_DPI_FUND_WIDGET, this->selected_type != IT_INVALID && !this->enabled);
351 this->SetWidgetDisabledState(WID_DPI_DISPLAY_WIDGET, this->selected_type == IT_INVALID && this->enabled);
352 }
353
366 std::string MakeCargoListString(const std::span<const CargoType> cargolist, const std::span<const CargoSuffix> cargo_suffix, StringID prefixstr) const
367 {
368 assert(cargolist.size() == cargo_suffix.size());
369
370 std::string cargostring;
371 size_t numcargo = 0;
372 size_t firstcargo = 0;
373
374 for (size_t j = 0; j < cargolist.size(); j++) {
375 if (!IsValidCargoType(cargolist[j])) continue;
376 numcargo++;
377 if (numcargo == 1) {
378 firstcargo = j;
379 continue;
380 }
381 auto params = MakeParameters(CargoSpec::Get(cargolist[j])->name, cargo_suffix[j].text);
382 AppendStringWithArgsInPlace(cargostring, STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION, params);
383 }
384
385 if (numcargo > 0) {
386 cargostring = GetString(prefixstr, CargoSpec::Get(cargolist[firstcargo])->name, cargo_suffix[firstcargo].text) + cargostring;
387 } else {
388 cargostring = GetString(prefixstr, STR_JUST_NOTHING, ""sv);
389 }
390
391 return cargostring;
392 }
393
394public:
396 {
397 this->CreateNestedTree();
398 this->vscroll = this->GetScrollbar(WID_DPI_SCROLLBAR);
399 /* Show scenario editor tools in editor. */
400 if (_game_mode != GM_EDITOR) {
401 this->GetWidget<NWidgetStacked>(WID_DPI_SCENARIO_EDITOR_PANE)->SetDisplayedPlane(SZSP_HORIZONTAL);
402 }
403 this->FinishInitNested(0);
404
405 this->SetButtons();
406 }
407
408 void OnInit() override
409 {
410 this->badge_classes = GUIBadgeClasses{GSF_INDUSTRIES};
411
412 /* Width of the legend blob -- slightly larger than the smallmap legend blob. */
413 this->legend.height = GetCharacterHeight(FS_SMALL);
414 this->legend.width = this->legend.height * 9 / 6;
415
416 this->SetupArrays();
417 }
418
419 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
420 {
421 switch (widget) {
424 Dimension d{};
425 for (const auto &indtype : this->list) {
426 d = maxdim(d, GetStringBoundingBox(GetIndustrySpec(indtype)->name));
427 }
428 fill.height = resize.height = std::max<uint>({this->legend.height, d.height, count.height}) + padding.height;
429 d.width += this->badge_classes.GetTotalColumnsWidth() + this->legend.width + WidgetDimensions::scaled.hsep_wide + WidgetDimensions::scaled.hsep_normal + count.width + padding.width;
430 d.height = 5 * resize.height;
431 size = maxdim(size, d);
432 break;
433 }
434
435 case WID_DPI_INFOPANEL: {
436 /* Extra line for cost outside of editor. */
437 int height = 2 + (_game_mode == GM_EDITOR ? 0 : 1);
438 uint extra_lines_req = 0;
439 uint extra_lines_prd = 0;
440 uint extra_lines_newgrf = 0;
442 Dimension d = {0, 0};
443 for (const auto &indtype : this->list) {
444 const IndustrySpec *indsp = GetIndustrySpec(indtype);
445 CargoSuffix cargo_suffix[std::tuple_size_v<decltype(indsp->accepts_cargo)>];
446
447 /* Measure the accepted cargoes, if any. */
448 GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, indtype, indsp, indsp->accepts_cargo, cargo_suffix);
449 std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
450 Dimension strdim = GetStringBoundingBox(cargostring);
451 if (strdim.width > max_minwidth) {
452 extra_lines_req = std::max(extra_lines_req, strdim.width / max_minwidth + 1);
453 strdim.width = max_minwidth;
454 }
455 d = maxdim(d, strdim);
456
457 /* Measure the produced cargoes, if any. */
458 GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, indtype, indsp, indsp->produced_cargo, cargo_suffix);
459 cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
460 strdim = GetStringBoundingBox(cargostring);
461 if (strdim.width > max_minwidth) {
462 extra_lines_prd = std::max(extra_lines_prd, strdim.width / max_minwidth + 1);
463 strdim.width = max_minwidth;
464 }
465 d = maxdim(d, strdim);
466
467 if (indsp->grf_prop.HasGrfFile()) {
468 /* Reserve a few extra lines for text from an industry NewGRF. */
469 extra_lines_newgrf = 4;
470 }
471 }
472
473 /* Set it to something more sane :) */
474 height += extra_lines_prd + extra_lines_req + extra_lines_newgrf;
475 size.height = height * GetCharacterHeight(FS_NORMAL) + padding.height;
476 size.width = d.width + padding.width;
477 break;
478 }
479
480 case WID_DPI_FUND_WIDGET: {
481 Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
482 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY));
483 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY));
484 d.width += padding.width;
485 d.height += padding.height;
486 size = maxdim(size, d);
487 break;
488 }
489 }
490 }
491
492 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
493 {
494 switch (widget) {
496 /* Raw industries might be prospected. Show this fact by changing the string
497 * In Editor, you just build, while ingame, or you fund or you prospect */
498 if (_game_mode == GM_EDITOR) {
499 /* We've chosen many random industries but no industries have been specified */
500 return GetString(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
501 }
502 if (this->selected_type != IT_INVALID) {
503 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
504 return GetString((_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
505 }
506 return GetString(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
507
508 default:
509 return this->Window::GetWidgetString(widget, stringid);
510 }
511 }
512
513 void DrawWidget(const Rect &r, WidgetID widget) const override
514 {
515 switch (widget) {
517 bool rtl = _current_text_dir == TD_RTL;
519 Rect icon = text.WithWidth(this->legend.width, rtl);
520 text = text.Indent(this->legend.width + WidgetDimensions::scaled.hsep_wide, rtl);
521
522 /* Vertical offset for legend icon. */
523 icon.top = r.top + (this->resize.step_height - this->legend.height + 1) / 2;
524 icon.bottom = icon.top + this->legend.height - 1;
525
526 auto badge_column_widths = badge_classes.GetColumnWidths();
527
528 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->list);
529 for (auto it = first; it != last; ++it) {
530 IndustryType type = *it;
531 bool selected = this->selected_type == type;
532 const IndustrySpec *indsp = GetIndustrySpec(type);
533
534 Rect tr = text;
535 if (badge_column_widths.size() >= 1 && badge_column_widths[0] > 0) {
536 DrawBadgeColumn(tr.WithWidth(badge_column_widths[0], rtl), 0, this->badge_classes, indsp->badges, GSF_INDUSTRIES, std::nullopt, PAL_NONE);
537 tr = tr.Indent(badge_column_widths[0], rtl);
538 }
539 if (badge_column_widths.size() >= 2 && badge_column_widths[1] > 0) {
540 DrawBadgeColumn(tr.WithWidth(badge_column_widths[1], !rtl), 0, this->badge_classes, indsp->badges, GSF_INDUSTRIES, std::nullopt, PAL_NONE);
541 tr = tr.Indent(badge_column_widths[1], !rtl);
542 }
543
544 /* Draw the name of the industry in white is selected, otherwise, in orange */
545 DrawString(tr, indsp->name, selected ? TC_WHITE : TC_ORANGE);
546 GfxFillRect(icon, selected ? PC_WHITE : PC_BLACK);
548 DrawString(tr, GetString(STR_JUST_COMMA, Industry::GetIndustryTypeCount(type)), TC_BLACK, SA_RIGHT, false, FS_SMALL);
549
550 text = text.Translate(0, this->resize.step_height);
551 icon = icon.Translate(0, this->resize.step_height);
552 }
553 break;
554 }
555
556 case WID_DPI_INFOPANEL: {
557 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
558
559 if (this->selected_type == IT_INVALID) {
560 DrawStringMultiLine(ir, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
561 break;
562 }
563
564 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
565
566 if (_game_mode != GM_EDITOR) {
567 DrawString(ir, GetString(STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST, indsp->GetConstructionCost()));
568 ir.top += GetCharacterHeight(FS_NORMAL);
569 }
570
571 CargoSuffix cargo_suffix[std::tuple_size_v<decltype(indsp->accepts_cargo)>];
572
573 /* Draw the accepted cargoes, if any. Otherwise, will print "Nothing". */
574 GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, this->selected_type, indsp, indsp->accepts_cargo, cargo_suffix);
575 std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
576 ir.top = DrawStringMultiLine(ir, cargostring);
577
578 /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
579 GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, this->selected_type, indsp, indsp->produced_cargo, cargo_suffix);
580 cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
581 ir.top = DrawStringMultiLine(ir, cargostring);
582
583 ir.top = DrawBadgeNameList(ir, indsp->badges, GSF_INDUSTRIES);
584
585 /* Get the additional purchase info text, if it has not already been queried. */
587 std::array<int32_t, 16> regs100;
588 uint16_t callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, nullptr, this->selected_type, INVALID_TILE, regs100);
589 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
590 std::string str;
591 if (callback_res == 0x40F) {
592 str = GetGRFStringWithTextStack(indsp->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
593 } else if (callback_res > 0x400) {
595 } else {
596 str = GetGRFStringWithTextStack(indsp->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, regs100);
597 }
598 if (!str.empty()) {
599 DrawStringMultiLine(ir, str, TC_YELLOW);
600 }
601 }
602 }
603 break;
604 }
605 }
606 }
607
608 static void AskManyRandomIndustriesCallback(Window *, bool confirmed)
609 {
610 if (!confirmed) return;
611
612 if (Town::GetNumItems() == 0) {
613 ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_GENERATE_INDUSTRIES), GetEncodedString(STR_ERROR_MUST_FOUND_TOWN_FIRST), WL_INFO);
614 } else {
615 Backup<bool> old_generating_world(_generating_world, true);
619 old_generating_world.Restore();
620 }
621 }
622
623 static void AskRemoveAllIndustriesCallback(Window *, bool confirmed)
624 {
625 if (!confirmed) return;
626
627 for (Industry *industry : Industry::Iterate()) delete industry;
628
629 /* Clear farmland. */
630 for (const auto tile : Map::Iterate()) {
631 if (IsTileType(tile, MP_CLEAR) && GetClearGround(tile) == CLEAR_FIELDS) {
632 MakeClear(tile, CLEAR_GRASS, 3);
633 }
634 }
635
637 }
638
639 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
640 {
641 switch (widget) {
643 assert(_game_mode == GM_EDITOR);
645 ShowQuery(
646 GetEncodedString(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION),
647 GetEncodedString(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY),
648 nullptr, AskManyRandomIndustriesCallback);
649 break;
650 }
651
653 assert(_game_mode == GM_EDITOR);
655 ShowQuery(
656 GetEncodedString(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION),
657 GetEncodedString(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY),
658 nullptr, AskRemoveAllIndustriesCallback);
659 break;
660 }
661
663 auto it = this->vscroll->GetScrolledItemFromWidget(this->list, pt.y, this, WID_DPI_MATRIX_WIDGET);
664 if (it != this->list.end()) { // Is it within the boundaries of available data?
665 this->selected_type = *it;
666 this->UpdateAvailability();
667
668 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
669
670 this->SetDirty();
671
672 if (_thd.GetCallbackWnd() == this &&
673 ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != nullptr && indsp->IsRawIndustry()) || !this->enabled)) {
674 /* Reset the button state if going to prospecting or "build many industries" */
675 this->RaiseButtons();
677 }
678
679 this->SetButtons();
680 if (this->enabled && click_count > 1) this->OnClick(pt, WID_DPI_FUND_WIDGET, 1);
681 }
682 break;
683 }
684
686 if (this->selected_type != IT_INVALID) ShowIndustryCargoesWindow(this->selected_type);
687 break;
688
689 case WID_DPI_FUND_WIDGET: {
690 if (this->selected_type != IT_INVALID) {
691 if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
692 Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, TileIndex{}, this->selected_type, 0, false, InteractiveRandom());
694 } else {
695 HandlePlacePushButton(this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT);
696 }
697 }
698 break;
699 }
700 }
701 }
702
703 void OnResize() override
704 {
705 /* Adjust the number of items in the matrix depending of the resize */
706 this->vscroll->SetCapacityFromWidget(this, WID_DPI_MATRIX_WIDGET);
707 }
708
709 void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override
710 {
711 bool success = true;
712 /* We do not need to protect ourselves against "Random Many Industries" in this mode */
713 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
714 uint32_t seed = InteractiveRandom();
715 uint32_t layout_index = InteractiveRandomRange((uint32_t)indsp->layouts.size());
716
717 if (_game_mode == GM_EDITOR) {
718 /* Show error if no town exists at all */
719 if (Town::GetNumItems() == 0) {
720 ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_HERE, indsp->name),
721 GetEncodedString(STR_ERROR_MUST_FOUND_TOWN_FIRST), WL_INFO, pt.x, pt.y);
722 return;
723 }
724
725 AutoRestoreBackup backup_cur_company(_current_company, OWNER_NONE);
726 AutoRestoreBackup backup_generating_world(_generating_world, true);
727 AutoRestoreBackup backup_ignore_industry_restritions(_ignore_industry_restrictions, true);
728
729 Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
730 } else {
731 success = Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
732 }
733
734 /* If an industry has been built, just reset the cursor and the system */
736 }
737
738 const IntervalTimer<TimerWindow> update_interval = {std::chrono::seconds(3), [this](auto) {
739 if (_game_mode == GM_EDITOR) return;
740 if (this->selected_type == IT_INVALID) return;
741
742 bool enabled = this->enabled;
743 this->UpdateAvailability();
744 if (enabled != this->enabled) {
745 this->SetButtons();
746 this->SetDirty();
747 }
748 }};
749
750 void OnTimeout() override
751 {
752 this->RaiseButtons();
753 }
754
755 void OnPlaceObjectAbort() override
756 {
757 this->RaiseButtons();
758 }
759
765 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
766 {
767 if (!gui_scope) return;
768 this->SetupArrays();
769 this->SetButtons();
770 this->SetDirty();
771 }
772};
773
774void ShowBuildIndustryWindow()
775{
776 if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
779}
780
781static void UpdateIndustryProduction(Industry *i);
782
783static inline bool IsProductionAlterable(const Industry *i)
784{
785 const IndustrySpec *is = GetIndustrySpec(i->type);
786 bool has_prod = std::any_of(std::begin(is->production_rate), std::end(is->production_rate), [](auto rate) { return rate != 0; });
787 return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
788 (has_prod || is->IsRawIndustry()) &&
789 !_networking);
790}
791
793{
800
808
813 uint8_t clicked_button = 0;
815 int info_height = 0;
817
818public:
820 {
822 this->info_height = WidgetDimensions::scaled.framerect.Vertical() + 2 * GetCharacterHeight(FS_NORMAL); // Info panel has at least two lines text.
823
824 this->InitNested(window_number);
825 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
826 nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ScaleZoomGUI(ZoomLevel::Industry));
827
830
831 this->InvalidateData();
832 }
833
835 {
837 }
838
839 void OnInit() override
840 {
841 /* This only used when the cheat to alter industry production is enabled */
842 this->cheat_line_height = std::max(SETTING_BUTTON_HEIGHT + WidgetDimensions::scaled.vsep_normal, GetCharacterHeight(FS_NORMAL));
844 }
845
846 void OnPaint() override
847 {
848 this->DrawWidgets();
849
850 if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
851
852 const Rect r = this->GetWidget<NWidgetBase>(WID_IV_INFO)->GetCurrentRect();
853 int expected = this->DrawInfo(r);
854 if (expected != r.bottom) {
855 this->info_height = expected - r.top + 1;
856 this->ReInit();
857 return;
858 }
859 }
860
861 void DrawCargoIcon(const Rect &r, CargoType cargo_type) const
862 {
863 bool rtl = _current_text_dir == TD_RTL;
864 SpriteID icon = CargoSpec::Get(cargo_type)->GetCargoIcon();
865 Dimension d = GetSpriteSize(icon);
867 DrawSprite(icon, PAL_NONE, CentreBounds(ir.left, ir.right, d.width), CentreBounds(ir.top, ir.bottom, this->cargo_icon_size.height));
868 }
869
870 std::string GetAcceptedCargoString(const Industry::AcceptedCargo &ac, const CargoSuffix &suffix) const
871 {
872 auto params = MakeParameters(CargoSpec::Get(ac.cargo)->name, ac.cargo, ac.waiting, suffix.text);
873 switch (suffix.display) {
874 case CSD_CARGO_AMOUNT_TEXT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_SUFFIX, params);
875 case CSD_CARGO_TEXT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_SUFFIX, params);
876 case CSD_CARGO_AMOUNT: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_NOSUFFIX, params);
877 case CSD_CARGO: return GetStringWithArgs(STR_INDUSTRY_VIEW_ACCEPT_CARGO_NOSUFFIX, params);
878 default: NOT_REACHED();
879 }
880 }
881
887 int DrawInfo(const Rect &r)
888 {
889 bool rtl = _current_text_dir == TD_RTL;
891 const IndustrySpec *ind = GetIndustrySpec(i->type);
892 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
893 bool first = true;
894 bool has_accept = false;
895
896 /* Use all the available space past the rect, so that we can enlarge the window if needed. */
897 ir.bottom = INT_MAX;
898
899 if (i->prod_level == PRODLEVEL_CLOSURE) {
900 DrawString(ir, STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE);
902 }
903
904 const int label_indent = WidgetDimensions::scaled.hsep_normal + this->cargo_icon_size.width;
906
907 for (const auto &a : i->accepted) {
908 if (!IsValidCargoType(a.cargo)) continue;
909 has_accept = true;
910 if (first) {
911 DrawString(ir, STR_INDUSTRY_VIEW_REQUIRES);
912 ir.top += GetCharacterHeight(FS_NORMAL);
913 first = false;
914 }
915
916 DrawCargoIcon(ir, a.cargo);
917
918 CargoSuffix suffix;
919 GetCargoSuffix(CARGOSUFFIX_IN, CST_VIEW, i, i->type, ind, a.cargo, &a - i->accepted.data(), suffix);
920 /* if the industry is not stockpiling then don't show amount in the acceptance display. */
921 if (!stockpiling && suffix.display == CSD_CARGO_AMOUNT_TEXT) suffix.display = CSD_CARGO_TEXT;
922 if (!stockpiling && suffix.display == CSD_CARGO_AMOUNT) suffix.display = CSD_CARGO;
923
924 DrawString(ir.Indent(label_indent, rtl), this->GetAcceptedCargoString(a, suffix));
925 ir.top += GetCharacterHeight(FS_NORMAL);
926 }
927
928 int line_height = this->editable == EA_RATE ? this->cheat_line_height : GetCharacterHeight(FS_NORMAL);
929 int text_y_offset = (line_height - GetCharacterHeight(FS_NORMAL)) / 2;
930 int button_y_offset = (line_height - SETTING_BUTTON_HEIGHT) / 2;
931 first = true;
932 for (const auto &p : i->produced) {
933 if (!IsValidCargoType(p.cargo)) continue;
934 if (first) {
935 if (has_accept) ir.top += WidgetDimensions::scaled.vsep_wide;
936 DrawString(ir, TimerGameEconomy::UsingWallclockUnits() ? STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE : STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
937 ir.top += GetCharacterHeight(FS_NORMAL);
938 if (this->editable == EA_RATE) this->production_offset_y = ir.top;
939 first = false;
940 }
941
942 DrawCargoIcon(ir, p.cargo);
943
944 CargoSuffix suffix;
945 GetCargoSuffix(CARGOSUFFIX_OUT, CST_VIEW, i, i->type, ind, p.cargo, &p - i->produced.data(), suffix);
946
947 DrawString(ir.Indent(label_indent + (this->editable == EA_RATE ? SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_normal : 0), rtl).Translate(0, text_y_offset),
948 GetString(STR_INDUSTRY_VIEW_TRANSPORTED, p.cargo, p.history[LAST_MONTH].production, suffix.text, ToPercent8(p.history[LAST_MONTH].PctTransported())));
949 /* Let's put out those buttons.. */
950 if (this->editable == EA_RATE) {
951 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,
952 p.rate > 0, p.rate < 255);
953 }
954 ir.top += line_height;
955 }
956
957 /* Display production multiplier if editable */
958 if (this->editable == EA_MULTIPLIER) {
959 line_height = this->cheat_line_height;
960 text_y_offset = (line_height - GetCharacterHeight(FS_NORMAL)) / 2;
961 button_y_offset = (line_height - SETTING_BUTTON_HEIGHT) / 2;
963 this->production_offset_y = ir.top;
964 DrawString(ir.Indent(label_indent + SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_normal, rtl).Translate(0, text_y_offset),
965 GetString(STR_INDUSTRY_VIEW_PRODUCTION_LEVEL, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT)));
966 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,
968 ir.top += line_height;
969 }
970
971 /* Get the extra message for the GUI */
973 std::array<int32_t, 16> regs100;
974 uint16_t callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->location.tile, regs100);
975 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
976 std::string str;
977 if (callback_res == 0x40F) {
978 str = GetGRFStringWithTextStack(ind->grf_prop.grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
979 } else if (callback_res > 0x400) {
981 } else {
982 str = GetGRFStringWithTextStack(ind->grf_prop.grffile, GRFSTR_MISC_GRF_TEXT + callback_res, regs100);
983 }
984 if (!str.empty()) {
986 ir.top = DrawStringMultiLine(ir, str, TC_YELLOW);
987 }
988 }
989 }
990
991 if (!i->text.empty()) {
993 ir.top = DrawStringMultiLine(ir, i->text.GetDecodedString(), TC_BLACK);
994 }
995
996 /* Return required bottom position, the last pixel row plus some padding. */
997 return ir.top - 1 + WidgetDimensions::scaled.framerect.bottom;
998 }
999
1000 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1001 {
1002 if (widget == WID_IV_CAPTION) return GetString(STR_INDUSTRY_VIEW_CAPTION, this->window_number);
1003
1004 return this->Window::GetWidgetString(widget, stringid);
1005 }
1006
1007 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1008 {
1009 if (widget == WID_IV_INFO) size.height = this->info_height;
1010 }
1011
1012 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1013 {
1014 switch (widget) {
1015 case WID_IV_INFO: {
1017 InfoLine line = IL_NONE;
1018
1019 switch (this->editable) {
1020 case EA_NONE: break;
1021
1022 case EA_MULTIPLIER:
1023 if (IsInsideBS(pt.y, this->production_offset_y, this->cheat_line_height)) line = IL_MULTIPLIER;
1024 break;
1025
1026 case EA_RATE:
1027 if (pt.y >= this->production_offset_y) {
1028 int row = (pt.y - this->production_offset_y) / this->cheat_line_height;
1029 for (auto itp = std::begin(i->produced); itp != std::end(i->produced); ++itp) {
1030 if (!IsValidCargoType(itp->cargo)) continue;
1031 row--;
1032 if (row < 0) {
1033 line = (InfoLine)(IL_RATE1 + (itp - std::begin(i->produced)));
1034 break;
1035 }
1036 }
1037 }
1038 break;
1039 }
1040 if (line == IL_NONE) return;
1041
1042 bool rtl = _current_text_dir == TD_RTL;
1043 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect).Indent(this->cargo_icon_size.width + WidgetDimensions::scaled.hsep_normal, rtl);
1044
1045 if (r.WithWidth(SETTING_BUTTON_WIDTH, rtl).Contains(pt)) {
1046 /* Clicked buttons, decrease or increase production */
1047 bool decrease = r.WithWidth(SETTING_BUTTON_WIDTH / 2, rtl).Contains(pt);
1048 switch (this->editable) {
1049 case EA_MULTIPLIER:
1050 if (decrease) {
1051 if (i->prod_level <= PRODLEVEL_MINIMUM) return;
1052 i->prod_level = static_cast<uint8_t>(std::max<uint>(i->prod_level / 2, PRODLEVEL_MINIMUM));
1053 } else {
1054 if (i->prod_level >= PRODLEVEL_MAXIMUM) return;
1055 i->prod_level = static_cast<uint8_t>(std::min<uint>(i->prod_level * 2, PRODLEVEL_MAXIMUM));
1056 }
1057 break;
1058
1059 case EA_RATE:
1060 if (decrease) {
1061 if (i->produced[line - IL_RATE1].rate <= 0) return;
1062 i->produced[line - IL_RATE1].rate = std::max(i->produced[line - IL_RATE1].rate / 2, 0);
1063 } else {
1064 if (i->produced[line - IL_RATE1].rate >= 255) return;
1065 /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
1066 int new_prod = i->produced[line - IL_RATE1].rate == 0 ? 1 : i->produced[line - IL_RATE1].rate * 2;
1067 i->produced[line - IL_RATE1].rate = ClampTo<uint8_t>(new_prod);
1068 }
1069 break;
1070
1071 default: NOT_REACHED();
1072 }
1073
1074 UpdateIndustryProduction(i);
1075 this->SetDirty();
1076 this->SetTimeout();
1077 this->clicked_line = line;
1078 this->clicked_button = (decrease ^ rtl) ? 1 : 2;
1080 /* clicked the text */
1081 this->editbox_line = line;
1082 switch (this->editable) {
1083 case EA_MULTIPLIER:
1084 ShowQueryString(GetString(STR_JUST_INT, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT)), STR_CONFIG_GAME_PRODUCTION_LEVEL, 10, this, CS_ALPHANUMERAL, {});
1085 break;
1086
1087 case EA_RATE:
1088 ShowQueryString(GetString(STR_JUST_INT, i->produced[line - IL_RATE1].rate * 8), STR_CONFIG_GAME_PRODUCTION, 10, this, CS_ALPHANUMERAL, {});
1089 break;
1090
1091 default: NOT_REACHED();
1092 }
1093 }
1094 break;
1095 }
1096
1097 case WID_IV_GOTO: {
1099 if (_ctrl_pressed) {
1101 } else {
1103 }
1104 break;
1105 }
1106
1107 case WID_IV_DISPLAY: {
1110 break;
1111 }
1112
1113 case WID_IV_GRAPH:
1114 ShowIndustryProductionGraph(this->window_number);
1115 break;
1116 }
1117 }
1118
1119 void OnTimeout() override
1120 {
1121 this->clicked_line = IL_NONE;
1122 this->clicked_button = 0;
1123 this->SetDirty();
1124 }
1125
1126 void OnResize() override
1127 {
1128 if (this->viewport != nullptr) {
1129 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
1130 nvp->UpdateViewportCoordinates(this);
1131
1132 ScrollWindowToTile(Industry::Get(this->window_number)->location.GetCenterTile(), this, true); // Re-center viewport.
1133 }
1134 }
1135
1136 void OnMouseWheel(int wheel, WidgetID widget) override
1137 {
1138 if (widget != WID_IV_VIEWPORT) return;
1140 DoZoomInOutWindow(wheel < 0 ? ZOOM_IN : ZOOM_OUT, this);
1141 }
1142 }
1143
1144 void OnQueryTextFinished(std::optional<std::string> str) override
1145 {
1146 if (!str.has_value() || str->empty()) return;
1147
1149 auto value = ParseInteger(*str, 10, true);
1150 if (!value.has_value()) return;
1151 switch (this->editbox_line) {
1152 case IL_NONE: NOT_REACHED();
1153
1154 case IL_MULTIPLIER:
1156 break;
1157
1158 default:
1159 i->produced[this->editbox_line - IL_RATE1].rate = ClampU(RoundDivSU(*value, 8), 0, 255);
1160 break;
1161 }
1162 UpdateIndustryProduction(i);
1163 this->SetDirty();
1164 }
1165
1171 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1172 {
1173 if (!gui_scope) return;
1174 const Industry *i = Industry::Get(this->window_number);
1175 if (IsProductionAlterable(i)) {
1176 const IndustrySpec *ind = GetIndustrySpec(i->type);
1178 } else {
1179 this->editable = EA_NONE;
1180 }
1181 }
1182
1183 bool IsNewGRFInspectable() const override
1184 {
1185 return ::IsNewGRFInspectable(GSF_INDUSTRIES, this->window_number);
1186 }
1187
1188 void ShowNewGRFInspectWindow() const override
1189 {
1190 ::ShowNewGRFInspectWindow(GSF_INDUSTRIES, this->window_number);
1191 }
1192};
1193
1194static void UpdateIndustryProduction(Industry *i)
1195{
1196 const IndustrySpec *indspec = GetIndustrySpec(i->type);
1198
1199 for (auto &p : i->produced) {
1200 if (IsValidCargoType(p.cargo)) {
1201 p.history[LAST_MONTH].production = ScaleByCargoScale(8 * p.rate, false);
1202 }
1203 }
1204}
1205
1209 NWidget(WWT_CLOSEBOX, COLOUR_CREAM),
1210 NWidget(WWT_CAPTION, COLOUR_CREAM, WID_IV_CAPTION),
1211 NWidget(WWT_PUSHIMGBTN, COLOUR_CREAM, WID_IV_GOTO), SetAspect(WidgetDimensions::ASPECT_LOCATION), SetSpriteTip(SPR_GOTO_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
1212 NWidget(WWT_DEBUGBOX, COLOUR_CREAM),
1213 NWidget(WWT_SHADEBOX, COLOUR_CREAM),
1214 NWidget(WWT_DEFSIZEBOX, COLOUR_CREAM),
1215 NWidget(WWT_STICKYBOX, COLOUR_CREAM),
1216 EndContainer(),
1217 NWidget(WWT_PANEL, COLOUR_CREAM),
1218 NWidget(WWT_INSET, COLOUR_CREAM), SetPadding(2, 2, 2, 2),
1219 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_IV_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetResize(1, 1),
1220 EndContainer(),
1221 EndContainer(),
1222 NWidget(WWT_PANEL, COLOUR_CREAM, WID_IV_INFO), SetMinimalSize(260, 0), SetMinimalTextLines(2, WidgetDimensions::unscaled.framerect.Vertical()), SetResize(1, 0),
1223 EndContainer(),
1225 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
1226 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GRAPH), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_VIEW_CARGO_GRAPH, STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP),
1227 NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
1228 EndContainer(),
1229};
1230
1233 WDP_AUTO, "view_industry", 260, 120,
1235 {},
1237);
1238
1239void ShowIndustryViewWindow(IndustryID industry)
1240{
1241 AllocateWindowDescFront<IndustryViewWindow>(_industry_view_desc, industry);
1242}
1243
1247 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1248 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_ID_CAPTION),
1249 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1250 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1251 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1252 EndContainer(),
1256 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_ID_DROPDOWN_ORDER), SetStringTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1257 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_DROPDOWN_CRITERIA), SetToolTip(STR_TOOLTIP_SORT_CRITERIA),
1258 NWidget(WWT_EDITBOX, COLOUR_BROWN, WID_ID_FILTER), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
1259 EndContainer(),
1261 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_ACC_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA),
1262 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_PROD_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA),
1263 NWidget(WWT_PANEL, COLOUR_BROWN), SetResize(1, 0), EndContainer(),
1264 EndContainer(),
1265 NWidget(WWT_PANEL, COLOUR_BROWN, WID_ID_INDUSTRY_LIST), SetToolTip(STR_INDUSTRY_DIRECTORY_LIST_TOOLTIP), SetResize(1, 1), SetScrollbar(WID_ID_VSCROLLBAR),
1266 EndContainer(),
1267 EndContainer(),
1269 EndContainer(),
1272 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1273 EndContainer(),
1274};
1275
1277
1285static bool CargoFilter(const Industry * const *industry, const std::pair<CargoType, CargoType> &cargoes)
1286{
1287 auto accepted_cargo = cargoes.first;
1288 auto produced_cargo = cargoes.second;
1289
1290 bool accepted_cargo_matches;
1291
1292 switch (accepted_cargo) {
1294 accepted_cargo_matches = true;
1295 break;
1296
1298 accepted_cargo_matches = !(*industry)->IsCargoAccepted();
1299 break;
1300
1301 default:
1302 accepted_cargo_matches = (*industry)->IsCargoAccepted(accepted_cargo);
1303 break;
1304 }
1305
1306 bool produced_cargo_matches;
1307
1308 switch (produced_cargo) {
1310 produced_cargo_matches = true;
1311 break;
1312
1314 produced_cargo_matches = !(*industry)->IsCargoProduced();
1315 break;
1316
1317 default:
1318 produced_cargo_matches = (*industry)->IsCargoProduced(produced_cargo);
1319 break;
1320 }
1321
1322 return accepted_cargo_matches && produced_cargo_matches;
1323}
1324
1325static GUIIndustryList::FilterFunction * const _industry_filter_funcs[] = { &CargoFilter };
1326
1331protected:
1332 /* Runtime saved values */
1333 static Listing last_sorting;
1334
1335 /* Constants for sorting industries */
1336 static inline const StringID sorter_names[] = {
1337 STR_SORT_BY_NAME,
1338 STR_SORT_BY_TYPE,
1339 STR_SORT_BY_PRODUCTION,
1340 STR_SORT_BY_TRANSPORTED,
1341 };
1342 static const std::initializer_list<GUIIndustryList::SortFunction * const> sorter_funcs;
1343
1344 GUIIndustryList industries{IndustryDirectoryWindow::produced_cargo_filter};
1345 Scrollbar *vscroll{};
1346 Scrollbar *hscroll{};
1347
1350 static CargoType produced_cargo_filter;
1351
1352 const int MAX_FILTER_LENGTH = 16;
1355
1356 enum class SorterType : uint8_t {
1357 ByName,
1358 ByType,
1359 ByProduction,
1361 };
1362
1368 {
1369 if (this->produced_cargo_filter_criteria != cargo_type) {
1370 this->produced_cargo_filter_criteria = cargo_type;
1371 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1373
1374 this->industries.SetFilterState(is_filtering_necessary);
1375 this->industries.SetFilterType(0);
1376 this->industries.ForceRebuild();
1377 }
1378 }
1379
1385 {
1386 if (this->accepted_cargo_filter_criteria != cargo_type) {
1387 this->accepted_cargo_filter_criteria = cargo_type;
1388 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1390
1391 this->industries.SetFilterState(is_filtering_necessary);
1392 this->industries.SetFilterType(0);
1393 this->industries.ForceRebuild();
1394 }
1395 }
1396
1397 StringID GetCargoFilterLabel(CargoType cargo_type) const
1398 {
1399 switch (cargo_type) {
1400 case CargoFilterCriteria::CF_ANY: return STR_INDUSTRY_DIRECTORY_FILTER_ALL_TYPES;
1401 case CargoFilterCriteria::CF_NONE: return STR_INDUSTRY_DIRECTORY_FILTER_NONE;
1402 default: return CargoSpec::Get(cargo_type)->name;
1403 }
1404 }
1405
1410 {
1413
1414 this->industries.SetFilterFuncs(_industry_filter_funcs);
1415
1417
1418 this->industries.SetFilterState(is_filtering_necessary);
1419 }
1420
1426 {
1427 uint width = this->hscroll->GetCount();
1428 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1429 for (auto it = first; it != last; ++it) {
1430 width = std::max(width, GetStringBoundingBox(this->GetIndustryString(*it)).width);
1431 }
1432 return width;
1433 }
1434
1437 {
1438 if (this->industries.NeedRebuild()) {
1439 this->industries.clear();
1440 this->industries.reserve(Industry::GetNumItems());
1441
1442 for (const Industry *i : Industry::Iterate()) {
1443 if (this->string_filter.IsEmpty()) {
1444 this->industries.push_back(i);
1445 continue;
1446 }
1447 this->string_filter.ResetState();
1448 this->string_filter.AddLine(i->GetCachedName());
1449 if (this->string_filter.GetState()) this->industries.push_back(i);
1450 }
1451
1452 this->industries.RebuildDone();
1453
1454 auto filter = std::make_pair(this->accepted_cargo_filter_criteria, this->produced_cargo_filter_criteria);
1455
1456 this->industries.Filter(filter);
1457
1458 this->vscroll->SetCount(this->industries.size()); // Update scrollbar as well.
1459 }
1460
1461 IndustryDirectoryWindow::produced_cargo_filter = this->produced_cargo_filter_criteria;
1462 this->industries.Sort();
1463
1464 this->SetDirty();
1465 }
1466
1474 {
1475 if (!IsValidCargoType(p.cargo)) return -1;
1476 return ToPercent8(p.history[LAST_MONTH].PctTransported());
1477 }
1478
1487 {
1488 CargoType filter = IndustryDirectoryWindow::produced_cargo_filter;
1489 if (filter == CargoFilterCriteria::CF_NONE) return 0;
1490
1491 int percentage = 0, produced_cargo_count = 0;
1492 for (const auto &p : i->produced) {
1493 if (filter == CargoFilterCriteria::CF_ANY) {
1494 int transported = GetCargoTransportedPercentsIfValid(p);
1495 if (transported != -1) {
1496 produced_cargo_count++;
1497 percentage += transported;
1498 }
1499 if (produced_cargo_count == 0 && &p == &i->produced.back() && percentage == 0) {
1500 return transported;
1501 }
1502 } else if (filter == p.cargo) {
1504 }
1505 }
1506
1507 if (produced_cargo_count == 0) return percentage;
1508 return percentage / produced_cargo_count;
1509 }
1510
1512 static bool IndustryNameSorter(const Industry * const &a, const Industry * const &b, const CargoType &)
1513 {
1514 int r = StrNaturalCompare(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting).
1515 if (r == 0) return a->index < b->index;
1516 return r < 0;
1517 }
1518
1520 static bool IndustryTypeSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1521 {
1522 int it_a = 0;
1523 while (it_a != NUM_INDUSTRYTYPES && a->type != _sorted_industry_types[it_a]) it_a++;
1524 int it_b = 0;
1525 while (it_b != NUM_INDUSTRYTYPES && b->type != _sorted_industry_types[it_b]) it_b++;
1526 int r = it_a - it_b;
1527 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1528 }
1529
1531 static bool IndustryProductionSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1532 {
1533 if (filter == CargoFilterCriteria::CF_NONE) return IndustryTypeSorter(a, b, filter);
1534
1535 uint prod_a = 0, prod_b = 0;
1536 if (filter == CargoFilterCriteria::CF_ANY) {
1537 for (const auto &pa : a->produced) {
1538 if (IsValidCargoType(pa.cargo)) prod_a += pa.history[LAST_MONTH].production;
1539 }
1540 for (const auto &pb : b->produced) {
1541 if (IsValidCargoType(pb.cargo)) prod_b += pb.history[LAST_MONTH].production;
1542 }
1543 } else {
1544 if (auto ita = a->GetCargoProduced(filter); ita != std::end(a->produced)) prod_a = ita->history[LAST_MONTH].production;
1545 if (auto itb = b->GetCargoProduced(filter); itb != std::end(b->produced)) prod_b = itb->history[LAST_MONTH].production;
1546 }
1547 int r = prod_a - prod_b;
1548
1549 return (r == 0) ? IndustryTypeSorter(a, b, filter) : r < 0;
1550 }
1551
1553 static bool IndustryTransportedCargoSorter(const Industry * const &a, const Industry * const &b, const CargoType &filter)
1554 {
1556 return (r == 0) ? IndustryNameSorter(a, b, filter) : r < 0;
1557 }
1558
1559 StringID GetStringForNumCargo(size_t count) const
1560 {
1561 switch (count) {
1562 case 0: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1563 case 1: return STR_INDUSTRY_DIRECTORY_ITEM_PROD1;
1564 case 2: return STR_INDUSTRY_DIRECTORY_ITEM_PROD2;
1565 case 3: return STR_INDUSTRY_DIRECTORY_ITEM_PROD3;
1566 default: return STR_INDUSTRY_DIRECTORY_ITEM_PRODMORE;
1567 }
1568 }
1569
1575 std::string GetIndustryString(const Industry *i) const
1576 {
1577 const IndustrySpec *indsp = GetIndustrySpec(i->type);
1578
1579 /* Get industry productions (CargoType, production, suffix, transported) */
1580 struct CargoInfo {
1581 CargoType cargo_type;
1582 uint16_t production;
1583 uint transported;
1584 std::string suffix;
1585
1586 CargoInfo(CargoType cargo_type, uint16_t production, uint transported, std::string &&suffix) : cargo_type(cargo_type), production(production), transported(transported), suffix(std::move(suffix)) {}
1587 };
1588 std::vector<CargoInfo> cargos;
1589
1590 for (auto itp = std::begin(i->produced); itp != std::end(i->produced); ++itp) {
1591 if (!IsValidCargoType(itp->cargo)) continue;
1592 CargoSuffix cargo_suffix;
1593 GetCargoSuffix(CARGOSUFFIX_OUT, CST_DIR, i, i->type, indsp, itp->cargo, itp - std::begin(i->produced), cargo_suffix);
1594 cargos.emplace_back(itp->cargo, itp->history[LAST_MONTH].production, ToPercent8(itp->history[LAST_MONTH].PctTransported()), std::move(cargo_suffix.text));
1595 }
1596
1597 switch (static_cast<IndustryDirectoryWindow::SorterType>(this->industries.SortType())) {
1601 /* Sort by descending production, then descending transported */
1602 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1603 if (a.production != b.production) return a.production > b.production;
1604 return a.transported > b.transported;
1605 });
1606 break;
1607
1609 /* Sort by descending transported, then descending production */
1610 std::sort(cargos.begin(), cargos.end(), [](const CargoInfo &a, const CargoInfo &b) {
1611 if (a.transported != b.transported) return a.transported > b.transported;
1612 return a.production > b.production;
1613 });
1614 break;
1615 }
1616
1617 /* If the produced cargo filter is active then move the filtered cargo to the beginning of the list,
1618 * because this is the one the player interested in, and that way it is not hidden in the 'n' more cargos */
1619 const CargoType cargo_type = this->produced_cargo_filter_criteria;
1620 if (cargo_type != CargoFilterCriteria::CF_ANY && cargo_type != CargoFilterCriteria::CF_NONE) {
1621 auto filtered_ci = std::ranges::find(cargos, cargo_type, &CargoInfo::cargo_type);
1622 if (filtered_ci != cargos.end()) {
1623 std::rotate(cargos.begin(), filtered_ci, filtered_ci + 1);
1624 }
1625 }
1626
1627 static constexpr size_t MAX_DISPLAYED_CARGOES = 3;
1628 std::array<StringParameter, 2 + 5 * MAX_DISPLAYED_CARGOES> params{};
1629 auto it = params.begin();
1630
1631 /* Industry name */
1632 *it++ = i->index;
1633
1634 /* Display first MAX_DISPLAYED_CARGOES cargoes */
1635 for (CargoInfo &ci : cargos | std::views::take(MAX_DISPLAYED_CARGOES)) {
1636 *it++ = STR_INDUSTRY_DIRECTORY_ITEM_INFO;
1637 *it++ = ci.cargo_type;
1638 *it++ = ci.production;
1639 *it++ = std::move(ci.suffix);
1640 *it++ = ci.transported;
1641 }
1642
1643 /* Undisplayed cargos if any */
1644 if (std::size(cargos) > MAX_DISPLAYED_CARGOES) *it++ = std::size(cargos) - MAX_DISPLAYED_CARGOES;
1645
1646 return GetStringWithArgs(GetStringForNumCargo(std::size(cargos)), {params.begin(), it});
1647 }
1648
1649public:
1651 {
1652 this->CreateNestedTree();
1653 this->vscroll = this->GetScrollbar(WID_ID_VSCROLLBAR);
1654 this->hscroll = this->GetScrollbar(WID_ID_HSCROLLBAR);
1655
1656 this->industries.SetListing(this->last_sorting);
1657 this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1658 this->industries.ForceRebuild();
1659
1660 this->FinishInitNested(0);
1661
1663
1665 this->industry_editbox.cancel_button = QueryString::ACTION_CLEAR;
1666 }
1667
1669 {
1670 this->last_sorting = this->industries.GetListing();
1671 }
1672
1673 void OnInit() override
1674 {
1675 this->SetCargoFilterArray();
1676 this->hscroll->SetCount(0);
1677 }
1678
1679 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1680 {
1681 switch (widget) {
1682 case WID_ID_CAPTION:
1683 return GetString(STR_INDUSTRY_DIRECTORY_CAPTION, this->vscroll->GetCount(), Industry::GetNumItems());
1684
1686 return GetString(IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1687
1689 return GetString(STR_INDUSTRY_DIRECTORY_ACCEPTED_CARGO_FILTER, this->GetCargoFilterLabel(this->accepted_cargo_filter_criteria));
1690
1692 return GetString(STR_INDUSTRY_DIRECTORY_PRODUCED_CARGO_FILTER, this->GetCargoFilterLabel(this->produced_cargo_filter_criteria));
1693
1694 default:
1695 return this->Window::GetWidgetString(widget, stringid);
1696 }
1697 }
1698
1699 void DrawWidget(const Rect &r, WidgetID widget) const override
1700 {
1701 switch (widget) {
1703 this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1704 break;
1705
1706 case WID_ID_INDUSTRY_LIST: {
1707 Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
1708
1709 /* Setup a clipping rectangle... */
1710 DrawPixelInfo tmp_dpi;
1711 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
1712 /* ...but keep coordinates relative to the window. */
1713 tmp_dpi.left += ir.left;
1714 tmp_dpi.top += ir.top;
1715
1716 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
1717
1718 ir = ScrollRect(ir, *this->hscroll, 1);
1719
1720 if (this->industries.empty()) {
1721 DrawString(ir, STR_INDUSTRY_DIRECTORY_NONE);
1722 break;
1723 }
1724 const CargoType acf_cargo_type = this->accepted_cargo_filter_criteria;
1725 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
1726 for (auto it = first; it != last; ++it) {
1727 TextColour tc = TC_FROMSTRING;
1728 if (acf_cargo_type != CargoFilterCriteria::CF_ANY && acf_cargo_type != CargoFilterCriteria::CF_NONE) {
1729 Industry *ind = const_cast<Industry *>(*it);
1730 if (IndustryTemporarilyRefusesCargo(ind, acf_cargo_type)) {
1731 tc = TC_GREY | TC_FORCED;
1732 }
1733 }
1734 DrawString(ir, this->GetIndustryString(*it), tc);
1735
1736 ir.top += this->resize.step_height;
1737 }
1738 break;
1739 }
1740 }
1741 }
1742
1743 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1744 {
1745 switch (widget) {
1746 case WID_ID_DROPDOWN_ORDER: {
1747 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->GetString());
1748 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1749 d.height += padding.height;
1750 size = maxdim(size, d);
1751 break;
1752 }
1753
1755 Dimension d = GetStringListBoundingBox(IndustryDirectoryWindow::sorter_names);
1756 d.width += padding.width;
1757 d.height += padding.height;
1758 size = maxdim(size, d);
1759 break;
1760 }
1761
1762 case WID_ID_INDUSTRY_LIST: {
1763 Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1764 fill.height = resize.height = d.height;
1765 d.height *= 5;
1766 d.width += padding.width;
1767 d.height += padding.height;
1768 size = maxdim(size, d);
1769 break;
1770 }
1771 }
1772 }
1773
1774 DropDownList BuildCargoDropDownList() const
1775 {
1776 DropDownList list;
1777
1778 /* Add item for disabling filtering. */
1779 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_ANY), CargoFilterCriteria::CF_ANY));
1780 /* Add item for industries not producing anything, e.g. power plants */
1781 list.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_NONE), CargoFilterCriteria::CF_NONE));
1782
1783 /* Add cargos */
1785 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1786 list.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
1787 }
1788
1789 return list;
1790 }
1791
1792 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1793 {
1794 switch (widget) {
1796 this->industries.ToggleSortOrder();
1797 this->SetDirty();
1798 break;
1799
1801 ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1802 break;
1803
1804 case WID_ID_FILTER_BY_ACC_CARGO: // Cargo filter dropdown
1805 ShowDropDownList(this, this->BuildCargoDropDownList(), this->accepted_cargo_filter_criteria, widget);
1806 break;
1807
1808 case WID_ID_FILTER_BY_PROD_CARGO: // Cargo filter dropdown
1809 ShowDropDownList(this, this->BuildCargoDropDownList(), this->produced_cargo_filter_criteria, widget);
1810 break;
1811
1812 case WID_ID_INDUSTRY_LIST: {
1813 auto it = this->vscroll->GetScrolledItemFromWidget(this->industries, pt.y, this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.top);
1814 if (it != this->industries.end()) {
1815 if (_ctrl_pressed) {
1816 ShowExtraViewportWindow((*it)->location.tile);
1817 } else {
1818 ScrollMainWindowToTile((*it)->location.tile);
1819 }
1820 }
1821 break;
1822 }
1823 }
1824 }
1825
1826 void OnDropdownSelect(WidgetID widget, int index, int) override
1827 {
1828 switch (widget) {
1830 if (this->industries.SortType() != index) {
1831 this->industries.SetSortType(index);
1833 }
1834 break;
1835 }
1836
1838 this->SetAcceptedCargoFilter(index);
1840 break;
1841 }
1842
1844 this->SetProducedCargoFilter(index);
1846 break;
1847 }
1848 }
1849 }
1850
1851 void OnResize() override
1852 {
1853 this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Vertical());
1854 this->hscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.Horizontal());
1855 }
1856
1857 void OnEditboxChanged(WidgetID wid) override
1858 {
1859 if (wid == WID_ID_FILTER) {
1860 this->string_filter.SetFilterTerm(this->industry_editbox.text.GetText());
1861 this->InvalidateData(IDIWD_FORCE_REBUILD);
1862 }
1863 }
1864
1865 void OnPaint() override
1866 {
1867 if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1868 this->hscroll->SetCount(this->GetIndustryListWidth());
1869 this->DrawWidgets();
1870 }
1871
1873 const IntervalTimer<TimerWindow> rebuild_interval = {std::chrono::seconds(3), [this](auto) {
1874 this->industries.ForceResort();
1876 }};
1877
1883 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1884 {
1885 switch (data) {
1886 case IDIWD_FORCE_REBUILD:
1887 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1888 this->industries.ForceRebuild();
1889 break;
1890
1891 case IDIWD_PRODUCTION_CHANGE:
1892 if (this->industries.SortType() == 2) this->industries.ForceResort();
1893 break;
1894
1895 default:
1896 this->industries.ForceResort();
1897 break;
1898 }
1899 }
1900
1901 static inline HotkeyList hotkeys {"industrydirectory", {
1902 Hotkey('F', "focus_filter_box", WID_ID_FILTER),
1903 }};
1904};
1905
1906Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1907
1908/* Available station sorting functions. */
1909const std::initializer_list<GUIIndustryList::SortFunction * const> IndustryDirectoryWindow::sorter_funcs = {
1910 &IndustryNameSorter,
1911 &IndustryTypeSorter,
1912 &IndustryProductionSorter,
1913 &IndustryTransportedCargoSorter
1914};
1915
1916CargoType IndustryDirectoryWindow::produced_cargo_filter = CargoFilterCriteria::CF_ANY;
1917
1918
1921 WDP_AUTO, "list_industries", 428, 190,
1923 {},
1925 &IndustryDirectoryWindow::hotkeys
1926);
1927
1928void ShowIndustryDirectory()
1929{
1930 AllocateWindowDescFront<IndustryDirectoryWindow>(_industry_directory_desc, 0);
1931}
1932
1936 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1937 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION),
1938 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1939 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1940 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1941 EndContainer(),
1945 EndContainer(),
1947 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1948 SetStringTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1949 NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1950 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1951 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1952 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1953 SetStringTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1954 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1955 EndContainer(),
1956};
1957
1960 WDP_AUTO, "industry_cargoes", 300, 210,
1962 {},
1964);
1965
1975
1976static const uint MAX_CARGOES = 16;
1977
1981 static int blob_distance;
1982
1988
1991
1994 static int industry_width;
1995 static uint max_cargoes;
1996
1997 using Cargoes = uint16_t;
1998 static_assert(std::numeric_limits<Cargoes>::digits >= MAX_CARGOES);
1999
2001 union {
2002 struct {
2003 IndustryType ind_type;
2004 std::array<CargoType, MAX_CARGOES> other_produced;
2005 std::array<CargoType, MAX_CARGOES> other_accepted;
2007 struct {
2008 std::array<CargoType, MAX_CARGOES> vertical_cargoes;
2011 uint8_t num_cargoes;
2012 uint8_t top_end;
2013 uint8_t bottom_end;
2015 struct {
2016 std::array<CargoType, MAX_CARGOES> cargoes;
2020 } u{}; // Data for each type.
2021
2027 {
2028 this->type = type;
2029 }
2030
2036 void MakeIndustry(IndustryType ind_type)
2037 {
2038 this->type = CFT_INDUSTRY;
2039 this->u.industry.ind_type = ind_type;
2040 std::fill(std::begin(this->u.industry.other_accepted), std::end(this->u.industry.other_accepted), INVALID_CARGO);
2041 std::fill(std::begin(this->u.industry.other_produced), std::end(this->u.industry.other_produced), INVALID_CARGO);
2042 }
2043
2050 int ConnectCargo(CargoType cargo, bool producer)
2051 {
2052 assert(this->type == CFT_CARGO);
2053 if (!IsValidCargoType(cargo)) return -1;
2054
2055 /* Find the vertical cargo column carrying the cargo. */
2056 int column = -1;
2057 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2058 if (cargo == this->u.cargo.vertical_cargoes[i]) {
2059 column = i;
2060 break;
2061 }
2062 }
2063 if (column < 0) return -1;
2064
2065 if (producer) {
2066 assert(!HasBit(this->u.cargo.supp_cargoes, column));
2067 SetBit(this->u.cargo.supp_cargoes, column);
2068 } else {
2069 assert(!HasBit(this->u.cargo.cust_cargoes, column));
2070 SetBit(this->u.cargo.cust_cargoes, column);
2071 }
2072 return column;
2073 }
2074
2080 {
2081 assert(this->type == CFT_CARGO);
2082
2083 return this->u.cargo.supp_cargoes != 0 || this->u.cargo.cust_cargoes != 0;
2084 }
2085
2091 void MakeCargo(const std::span<const CargoType> cargoes)
2092 {
2093 this->type = CFT_CARGO;
2094 assert(std::size(cargoes) <= std::size(this->u.cargo.vertical_cargoes));
2095 auto insert = std::copy_if(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo.vertical_cargoes), IsValidCargoType);
2096 this->u.cargo.num_cargoes = static_cast<uint8_t>(std::distance(std::begin(this->u.cargo.vertical_cargoes), insert));
2097 CargoTypeComparator comparator;
2098 std::sort(std::begin(this->u.cargo.vertical_cargoes), insert, comparator);
2099 std::fill(insert, std::end(this->u.cargo.vertical_cargoes), INVALID_CARGO);
2100 this->u.cargo.top_end = false;
2101 this->u.cargo.bottom_end = false;
2102 this->u.cargo.supp_cargoes = 0;
2103 this->u.cargo.cust_cargoes = 0;
2104 }
2105
2111 void MakeCargoLabel(const std::span<const CargoType> cargoes, bool left_align)
2112 {
2113 this->type = CFT_CARGO_LABEL;
2114 assert(std::size(cargoes) <= std::size(this->u.cargo_label.cargoes));
2115 auto insert = std::copy(std::begin(cargoes), std::end(cargoes), std::begin(this->u.cargo_label.cargoes));
2116 std::fill(insert, std::end(this->u.cargo_label.cargoes), INVALID_CARGO);
2117 this->u.cargo_label.left_align = left_align;
2118 }
2119
2125 {
2126 this->type = CFT_HEADER;
2127 this->u.header = textid;
2128 }
2129
2135 int GetCargoBase(int xpos) const
2136 {
2137 assert(this->type == CFT_CARGO);
2138 int n = this->u.cargo.num_cargoes;
2139
2140 return xpos + cargo_field_width / 2 - (CargoesField::cargo_line.width * n + CargoesField::cargo_space.width * (n - 1)) / 2;
2141 }
2142
2148 void Draw(int xpos, int ypos) const
2149 {
2150 switch (this->type) {
2151 case CFT_EMPTY:
2152 case CFT_SMALL_EMPTY:
2153 break;
2154
2155 case CFT_HEADER:
2156 ypos += (small_height - GetCharacterHeight(FS_NORMAL)) / 2;
2157 DrawString(xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
2158 break;
2159
2160 case CFT_INDUSTRY: {
2161 int ypos1 = ypos + vert_inter_industry_space / 2;
2162 int ypos2 = ypos + normal_height - 1 - vert_inter_industry_space / 2;
2163 int xpos2 = xpos + industry_width - 1;
2164 DrawRectOutline({xpos, ypos1, xpos2, ypos2}, INDUSTRY_LINE_COLOUR);
2166 if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2167 const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
2168 DrawString(xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
2169
2170 /* Draw the industry legend. */
2171 int blob_left, blob_right;
2172 if (_current_text_dir == TD_RTL) {
2173 blob_right = xpos2 - blob_distance;
2174 blob_left = blob_right - CargoesField::legend.width;
2175 } else {
2176 blob_left = xpos + blob_distance;
2177 blob_right = blob_left + CargoesField::legend.width;
2178 }
2179 GfxFillRect(blob_left, ypos2 - blob_distance - CargoesField::legend.height, blob_right, ypos2 - blob_distance, PC_BLACK); // Border
2180 GfxFillRect(blob_left + 1, ypos2 - blob_distance - CargoesField::legend.height + 1, blob_right - 1, ypos2 - blob_distance - 1, indsp->map_colour);
2181 } else {
2182 DrawString(xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
2183 }
2184
2185 /* Draw the other_produced/other_accepted cargoes. */
2186 std::span<const CargoType> other_right, other_left;
2187 if (_current_text_dir == TD_RTL) {
2188 other_right = this->u.industry.other_accepted;
2189 other_left = this->u.industry.other_produced;
2190 } else {
2191 other_right = this->u.industry.other_produced;
2192 other_left = this->u.industry.other_accepted;
2193 }
2195 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2196 if (IsValidCargoType(other_right[i])) {
2197 const CargoSpec *csp = CargoSpec::Get(other_right[i]);
2198 int xp = xpos + industry_width + CargoesField::cargo_stub.width;
2199 DrawHorConnection(xpos + industry_width, xp - 1, ypos1, csp);
2200 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2201 }
2202 if (IsValidCargoType(other_left[i])) {
2203 const CargoSpec *csp = CargoSpec::Get(other_left[i]);
2204 int xp = xpos - CargoesField::cargo_stub.width;
2205 DrawHorConnection(xp + 1, xpos - 1, ypos1, csp);
2206 GfxDrawLine(xp, ypos1, xp, ypos1 + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2207 }
2209 }
2210 break;
2211 }
2212
2213 case CFT_CARGO: {
2214 int cargo_base = this->GetCargoBase(xpos);
2215 int top = ypos + (this->u.cargo.top_end ? vert_inter_industry_space / 2 + 1 : 0);
2216 int bot = ypos - (this->u.cargo.bottom_end ? vert_inter_industry_space / 2 + 1 : 0) + normal_height - 1;
2217 int colpos = cargo_base;
2218 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2219 if (this->u.cargo.top_end) GfxDrawLine(colpos, top - 1, colpos + CargoesField::cargo_line.width - 1, top - 1, CARGO_LINE_COLOUR);
2220 if (this->u.cargo.bottom_end) GfxDrawLine(colpos, bot + 1, colpos + CargoesField::cargo_line.width - 1, bot + 1, CARGO_LINE_COLOUR);
2221 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2222 colpos++;
2223 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
2224 GfxFillRect(colpos, top, colpos + CargoesField::cargo_line.width - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
2225 colpos += CargoesField::cargo_line.width - 2;
2226 GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2227 colpos += 1 + CargoesField::cargo_space.width;
2228 }
2229
2230 Cargoes hor_left, hor_right;
2231 if (_current_text_dir == TD_RTL) {
2232 hor_left = this->u.cargo.cust_cargoes;
2233 hor_right = this->u.cargo.supp_cargoes;
2234 } else {
2235 hor_left = this->u.cargo.supp_cargoes;
2236 hor_right = this->u.cargo.cust_cargoes;
2237 }
2239 for (uint i = 0; i < MAX_CARGOES; i++) {
2240 if (HasBit(hor_left, i)) {
2241 int col = i;
2242 int dx = 0;
2243 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2244 for (; col > 0; col--) {
2245 int lf = cargo_base + col * CargoesField::cargo_line.width + (col - 1) * CargoesField::cargo_space.width;
2246 DrawHorConnection(lf, lf + CargoesField::cargo_space.width - dx, ypos, csp);
2247 dx = 1;
2248 }
2249 DrawHorConnection(xpos, cargo_base - dx, ypos, csp);
2250 }
2251 if (HasBit(hor_right, i)) {
2252 int col = i;
2253 int dx = 0;
2254 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2255 for (; col < this->u.cargo.num_cargoes - 1; col++) {
2256 int lf = cargo_base + (col + 1) * CargoesField::cargo_line.width + col * CargoesField::cargo_space.width;
2257 DrawHorConnection(lf + dx - 1, lf + CargoesField::cargo_space.width - 1, ypos, csp);
2258 dx = 1;
2259 }
2260 DrawHorConnection(cargo_base + col * CargoesField::cargo_space.width + (col + 1) * CargoesField::cargo_line.width - 1 + dx, xpos + CargoesField::cargo_field_width - 1, ypos, csp);
2261 }
2263 }
2264 break;
2265 }
2266
2267 case CFT_CARGO_LABEL:
2269 for (uint i = 0; i < MAX_CARGOES; i++) {
2270 if (IsValidCargoType(this->u.cargo_label.cargoes[i])) {
2271 const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
2272 DrawString(xpos + WidgetDimensions::scaled.framerect.left, xpos + industry_width - 1 - WidgetDimensions::scaled.framerect.right, ypos, csp->name, TC_WHITE,
2273 (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
2274 }
2276 }
2277 break;
2278
2279 default:
2280 NOT_REACHED();
2281 }
2282 }
2283
2291 CargoType CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
2292 {
2293 assert(this->type == CFT_CARGO);
2294
2295 /* Vertical matching. */
2296 int cpos = this->GetCargoBase(0);
2297 uint col;
2298 for (col = 0; col < this->u.cargo.num_cargoes; col++) {
2299 if (pt.x < cpos) break;
2300 if (pt.x < cpos + (int)CargoesField::cargo_line.width) return this->u.cargo.vertical_cargoes[col];
2302 }
2303 /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
2304
2306 uint row;
2307 for (row = 0; row < MAX_CARGOES; row++) {
2308 if (pt.y < vpos) return INVALID_CARGO;
2309 if (pt.y < vpos + GetCharacterHeight(FS_NORMAL)) break;
2311 }
2312 if (row == MAX_CARGOES) return INVALID_CARGO;
2313
2314 /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
2315 if (col == 0) {
2316 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2317 if (left != nullptr) {
2318 if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
2319 if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
2320 }
2321 return INVALID_CARGO;
2322 }
2323 if (col == this->u.cargo.num_cargoes) {
2324 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2325 if (right != nullptr) {
2326 if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
2327 if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
2328 }
2329 return INVALID_CARGO;
2330 }
2331 if (row >= col) {
2332 /* Clicked somewhere in-between vertical cargo connection.
2333 * Since the horizontal connection is made in the same order as the vertical list, the above condition
2334 * ensures we are left-below the main diagonal, thus at the supplying side.
2335 */
2336 if (HasBit(this->u.cargo.supp_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2337 return INVALID_CARGO;
2338 }
2339 /* Clicked at a customer connection. */
2340 if (HasBit(this->u.cargo.cust_cargoes, row)) return this->u.cargo.vertical_cargoes[row];
2341 return INVALID_CARGO;
2342 }
2343
2350 {
2351 assert(this->type == CFT_CARGO_LABEL);
2352
2354 uint row;
2355 for (row = 0; row < MAX_CARGOES; row++) {
2356 if (pt.y < vpos) return INVALID_CARGO;
2357 if (pt.y < vpos + GetCharacterHeight(FS_NORMAL)) break;
2359 }
2360 if (row == MAX_CARGOES) return INVALID_CARGO;
2361 return this->u.cargo_label.cargoes[row];
2362 }
2363
2364private:
2372 static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
2373 {
2374 GfxDrawLine(left, top, right, top, CARGO_LINE_COLOUR);
2375 GfxFillRect(left, top + 1, right, top + CargoesField::cargo_line.height - 2, csp->legend_colour, FILLRECT_OPAQUE);
2376 GfxDrawLine(left, top + CargoesField::cargo_line.height - 1, right, top + CargoesField::cargo_line.height - 1, CARGO_LINE_COLOUR);
2377 }
2378};
2379
2380static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::produced_cargo)>);
2381static_assert(MAX_CARGOES >= std::tuple_size_v<decltype(IndustrySpec::accepts_cargo)>);
2382
2388
2395
2397
2400
2404
2410 {
2411 CargoesField *ind_fld = this->columns + column;
2412 CargoesField *cargo_fld = this->columns + column + 1;
2413 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2414
2415 std::fill(std::begin(ind_fld->u.industry.other_produced), std::end(ind_fld->u.industry.other_produced), INVALID_CARGO);
2416
2417 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2418 CargoType others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
2419 int other_count = 0;
2420
2421 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2422 assert(CargoesField::max_cargoes <= std::size(indsp->produced_cargo));
2423 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2424 int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
2425 if (col < 0) others[other_count++] = indsp->produced_cargo[i];
2426 }
2427
2428 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2429 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2430 if (HasBit(cargo_fld->u.cargo.supp_cargoes, i)) ind_fld->u.industry.other_produced[i] = others[--other_count];
2431 }
2432 } else {
2433 /* Houses only display cargo that towns produce. */
2434 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2435 CargoType cargo_type = cargo_fld->u.cargo.vertical_cargoes[i];
2437 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) cargo_fld->ConnectCargo(cargo_type, true);
2438 }
2439 }
2440 }
2441
2447 void MakeCargoLabel(int column, bool accepting)
2448 {
2449 CargoType cargoes[MAX_CARGOES];
2450 std::fill(std::begin(cargoes), std::end(cargoes), INVALID_CARGO);
2451
2452 CargoesField *label_fld = this->columns + column;
2453 CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2454
2455 assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2456 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2457 int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2458 if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2459 }
2460 label_fld->MakeCargoLabel(cargoes, accepting);
2461 }
2462
2463
2469 {
2470 CargoesField *ind_fld = this->columns + column;
2471 CargoesField *cargo_fld = this->columns + column - 1;
2472 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2473
2474 std::fill(std::begin(ind_fld->u.industry.other_accepted), std::end(ind_fld->u.industry.other_accepted), INVALID_CARGO);
2475
2476 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2477 CargoType others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2478 int other_count = 0;
2479
2480 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2481 assert(CargoesField::max_cargoes <= std::size(indsp->accepts_cargo));
2482 for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2483 int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2484 if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2485 }
2486
2487 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2488 for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2489 if (!HasBit(cargo_fld->u.cargo.cust_cargoes, i)) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2490 }
2491 } else {
2492 /* Houses only display what is demanded. */
2493 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2494 for (const auto &hs : HouseSpec::Specs()) {
2495 if (!hs.enabled) continue;
2496
2497 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2498 if (hs.cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs.accepts_cargo[j]) {
2499 cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2500 goto next_cargo;
2501 }
2502 }
2503 }
2504next_cargo: ;
2505 }
2506 }
2507 }
2508};
2509
2510
2539 typedef std::vector<CargoesRow> Fields;
2540
2541 Fields fields{};
2542 uint ind_cargo = 0;
2545 Scrollbar *vscroll = nullptr;
2546
2548 {
2549 this->OnInit();
2550 this->CreateNestedTree();
2551 this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2552 this->FinishInitNested(0);
2553 this->OnInvalidateData(id);
2554 }
2555
2556 void OnInit() override
2557 {
2558 /* Initialize static CargoesField size variables. */
2559 Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2560 d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2563 CargoesField::small_height = d.height;
2564
2565 /* Size of the legend blob -- slightly larger than the smallmap legend blob. */
2567 CargoesField::legend.width = CargoesField::legend.height * 9 / 6;
2568
2569 /* Size of cargo lines. */
2572
2573 /* Size of border between cargo lines and industry boxes. */
2576
2577 /* Size of space between cargo lines. */
2580
2581 /* Size of cargo stub (unconnected cargo line.) */
2583 CargoesField::cargo_stub.height = CargoesField::cargo_line.height; /* Unused */
2584
2587
2588 /* Decide about the size of the box holding the text of an industry type. */
2589 this->ind_textsize.width = 0;
2590 this->ind_textsize.height = 0;
2591 CargoesField::max_cargoes = 0;
2592 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2593 const IndustrySpec *indsp = GetIndustrySpec(it);
2594 if (!indsp->enabled) continue;
2596 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->accepts_cargo, IsValidCargoType));
2597 CargoesField::max_cargoes = std::max<uint>(CargoesField::max_cargoes, std::ranges::count_if(indsp->produced_cargo, IsValidCargoType));
2598 }
2599 d.width = std::max(d.width, this->ind_textsize.width);
2600 d.height = this->ind_textsize.height;
2601 this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2602
2603 /* Compute max size of the cargo texts. */
2604 this->cargo_textsize.width = 0;
2605 this->cargo_textsize.height = 0;
2606 for (const CargoSpec *csp : CargoSpec::Iterate()) {
2607 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(csp->name));
2608 }
2609 d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2610 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2611
2613 /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2615 d.height = std::max(d.height + WidgetDimensions::scaled.frametext.Vertical(), min_ind_height);
2616
2619
2620 /* Width of a #CFT_CARGO field. */
2622 }
2623
2624 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
2625 {
2626 switch (widget) {
2627 case WID_IC_PANEL:
2628 fill.height = resize.height = CargoesField::normal_height;
2631 break;
2632
2634 size.width = std::max(size.width, this->ind_textsize.width + padding.width);
2635 break;
2636
2638 size.width = std::max(size.width, this->cargo_textsize.width + padding.width);
2639 break;
2640 }
2641 }
2642
2643 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
2644 {
2645 if (widget != WID_IC_CAPTION) return this->Window::GetWidgetString(widget, stringid);
2646
2647 if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2648 const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2649 return GetString(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, indsp->name);
2650 } else {
2651 const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2652 return GetString(STR_INDUSTRY_CARGOES_CARGO_CAPTION, csp->name);
2653 }
2654 }
2655
2662 static bool HasCommonValidCargo(const std::span<const CargoType> cargoes1, const std::span<const CargoType> cargoes2)
2663 {
2664 for (const CargoType cargo_type1 : cargoes1) {
2665 if (!IsValidCargoType(cargo_type1)) continue;
2666 for (const CargoType cargo_type2 : cargoes2) {
2667 if (cargo_type1 == cargo_type2) return true;
2668 }
2669 }
2670 return false;
2671 }
2672
2678 static bool HousesCanSupply(const std::span<const CargoType> cargoes)
2679 {
2680 for (const CargoType cargo_type : cargoes) {
2681 if (!IsValidCargoType(cargo_type)) continue;
2683 if (tpe == TPE_PASSENGERS || tpe == TPE_MAIL) return true;
2684 }
2685 return false;
2686 }
2687
2693 static bool HousesCanAccept(const std::span<const CargoType> cargoes)
2694 {
2695 HouseZones climate_mask = GetClimateMaskForLandscape();
2696
2697 for (const CargoType cargo_type : cargoes) {
2698 if (!IsValidCargoType(cargo_type)) continue;
2699
2700 for (const auto &hs : HouseSpec::Specs()) {
2701 if (!hs.enabled || !hs.building_availability.Any(climate_mask)) continue;
2702
2703 for (uint j = 0; j < lengthof(hs.accepts_cargo); j++) {
2704 if (hs.cargo_acceptance[j] > 0 && cargo_type == hs.accepts_cargo[j]) return true;
2705 }
2706 }
2707 }
2708 return false;
2709 }
2710
2716 static int CountMatchingAcceptingIndustries(const std::span<const CargoType> cargoes)
2717 {
2718 int count = 0;
2719 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2720 const IndustrySpec *indsp = GetIndustrySpec(it);
2721 if (!indsp->enabled) continue;
2722
2723 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) count++;
2724 }
2725 return count;
2726 }
2727
2733 static int CountMatchingProducingIndustries(const std::span<const CargoType> cargoes)
2734 {
2735 int count = 0;
2736 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2737 const IndustrySpec *indsp = GetIndustrySpec(it);
2738 if (!indsp->enabled) continue;
2739
2740 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) count++;
2741 }
2742 return count;
2743 }
2744
2751 void ShortenCargoColumn(int column, int top, int bottom)
2752 {
2753 while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2754 this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2755 top++;
2756 }
2757 this->fields[top].columns[column].u.cargo.top_end = true;
2758
2759 while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2760 this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2761 bottom--;
2762 }
2763 this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2764 }
2765
2772 void PlaceIndustry(int row, int col, IndustryType it)
2773 {
2774 assert(this->fields[row].columns[col].type == CFT_EMPTY);
2775 this->fields[row].columns[col].MakeIndustry(it);
2776 if (col == 0) {
2777 this->fields[row].ConnectIndustryProduced(col);
2778 } else {
2779 this->fields[row].ConnectIndustryAccepted(col);
2780 }
2781 }
2782
2787 {
2788 if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2789
2790 /* Only notify the smallmap window if it exists. In particular, do not
2791 * bring it to the front to prevent messing up any nice layout of the user. */
2793 }
2794
2799 void ComputeIndustryDisplay(IndustryType displayed_it)
2800 {
2801 this->ind_cargo = displayed_it;
2802 _displayed_industries.reset();
2803 _displayed_industries.set(displayed_it);
2804
2805 this->fields.clear();
2806 CargoesRow &first_row = this->fields.emplace_back();
2807 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2808 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2809 first_row.columns[2].MakeEmpty(CFT_SMALL_EMPTY);
2810 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2811 first_row.columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2812
2813 const IndustrySpec *central_sp = GetIndustrySpec(displayed_it);
2814 bool houses_supply = HousesCanSupply(central_sp->accepts_cargo);
2815 bool houses_accept = HousesCanAccept(central_sp->produced_cargo);
2816 /* Make a field consisting of two cargo columns. */
2817 int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo) + houses_supply;
2818 int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo) + houses_accept;
2819 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.
2820 for (int i = 0; i < num_indrows; i++) {
2821 CargoesRow &row = this->fields.emplace_back();
2822 row.columns[0].MakeEmpty(CFT_EMPTY);
2823 row.columns[1].MakeCargo(central_sp->accepts_cargo);
2824 row.columns[2].MakeEmpty(CFT_EMPTY);
2825 row.columns[3].MakeCargo(central_sp->produced_cargo);
2826 row.columns[4].MakeEmpty(CFT_EMPTY);
2827 }
2828 /* Add central industry. */
2829 int central_row = 1 + num_indrows / 2;
2830 this->fields[central_row].columns[2].MakeIndustry(displayed_it);
2831 this->fields[central_row].ConnectIndustryProduced(2);
2832 this->fields[central_row].ConnectIndustryAccepted(2);
2833
2834 /* Add cargo labels. */
2835 this->fields[central_row - 1].MakeCargoLabel(2, true);
2836 this->fields[central_row + 1].MakeCargoLabel(2, false);
2837
2838 /* Add suppliers and customers of the 'it' industry. */
2839 int supp_count = 0;
2840 int cust_count = 0;
2841 for (IndustryType it : _sorted_industry_types) {
2842 const IndustrySpec *indsp = GetIndustrySpec(it);
2843 if (!indsp->enabled) continue;
2844
2845 if (HasCommonValidCargo(central_sp->accepts_cargo, indsp->produced_cargo)) {
2846 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2847 _displayed_industries.set(it);
2848 supp_count++;
2849 }
2850 if (HasCommonValidCargo(central_sp->produced_cargo, indsp->accepts_cargo)) {
2851 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2852 _displayed_industries.set(it);
2853 cust_count++;
2854 }
2855 }
2856 if (houses_supply) {
2857 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2858 supp_count++;
2859 }
2860 if (houses_accept) {
2861 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2862 cust_count++;
2863 }
2864
2865 this->ShortenCargoColumn(1, 1, num_indrows);
2866 this->ShortenCargoColumn(3, 1, num_indrows);
2867 this->vscroll->SetCount(num_indrows);
2868 this->SetDirty();
2869 this->NotifySmallmap();
2870 }
2871
2877 {
2878 this->ind_cargo = cargo_type + NUM_INDUSTRYTYPES;
2879 _displayed_industries.reset();
2880
2881 this->fields.clear();
2882 CargoesRow &first_row = this->fields.emplace_back();
2883 first_row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2884 first_row.columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2885 first_row.columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2886 first_row.columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2887 first_row.columns[4].MakeEmpty(CFT_SMALL_EMPTY);
2888
2889 auto cargoes = std::span(&cargo_type, 1);
2890 bool houses_supply = HousesCanSupply(cargoes);
2891 bool houses_accept = HousesCanAccept(cargoes);
2892 int num_supp = CountMatchingProducingIndustries(cargoes) + houses_supply + 1; // Ensure room for the cargo label.
2893 int num_cust = CountMatchingAcceptingIndustries(cargoes) + houses_accept;
2894 int num_indrows = std::max(num_supp, num_cust);
2895 for (int i = 0; i < num_indrows; i++) {
2896 CargoesRow &row = this->fields.emplace_back();
2897 row.columns[0].MakeEmpty(CFT_EMPTY);
2898 row.columns[1].MakeCargo(cargoes);
2899 row.columns[2].MakeEmpty(CFT_EMPTY);
2900 row.columns[3].MakeEmpty(CFT_EMPTY);
2901 row.columns[4].MakeEmpty(CFT_EMPTY);
2902 }
2903
2904 this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2905
2906 /* Add suppliers and customers of the cargo. */
2907 int supp_count = 0;
2908 int cust_count = 0;
2909 for (IndustryType it : _sorted_industry_types) {
2910 const IndustrySpec *indsp = GetIndustrySpec(it);
2911 if (!indsp->enabled) continue;
2912
2913 if (HasCommonValidCargo(cargoes, indsp->produced_cargo)) {
2914 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2915 _displayed_industries.set(it);
2916 supp_count++;
2917 }
2918 if (HasCommonValidCargo(cargoes, indsp->accepts_cargo)) {
2919 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2920 _displayed_industries.set(it);
2921 cust_count++;
2922 }
2923 }
2924 if (houses_supply) {
2925 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2926 supp_count++;
2927 }
2928 if (houses_accept) {
2929 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2930 cust_count++;
2931 }
2932
2933 this->ShortenCargoColumn(1, 1, num_indrows);
2934 this->vscroll->SetCount(num_indrows);
2935 this->SetDirty();
2936 this->NotifySmallmap();
2937 }
2938
2946 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
2947 {
2948 if (!gui_scope) return;
2949 if (data == NUM_INDUSTRYTYPES) {
2951 return;
2952 }
2953
2954 assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2955 this->ComputeIndustryDisplay(data);
2956 }
2957
2958 void DrawWidget(const Rect &r, WidgetID widget) const override
2959 {
2960 if (widget != WID_IC_PANEL) return;
2961
2962 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
2963 DrawPixelInfo tmp_dpi;
2964 if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
2965 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2966
2968 if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::cargo_field_width) / 2;
2969 int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2970
2971 const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2972 int vpos = WidgetDimensions::scaled.frametext.top - WidgetDimensions::scaled.bevel.top - this->vscroll->GetPosition() * nwp->resize_y;
2973 int row_height = CargoesField::small_height;
2974 for (const auto &field : this->fields) {
2975 if (vpos + row_height >= 0) {
2976 int xpos = left_pos;
2977 int col, dir;
2978 if (_current_text_dir == TD_RTL) {
2979 col = last_column;
2980 dir = -1;
2981 } else {
2982 col = 0;
2983 dir = 1;
2984 }
2985 while (col >= 0 && col <= last_column) {
2986 field.columns[col].Draw(xpos, vpos);
2988 col += dir;
2989 }
2990 }
2991 vpos += row_height;
2992 if (vpos >= height) break;
2993 row_height = CargoesField::normal_height;
2994 }
2995 }
2996
3005 {
3006 const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
3007 pt.x -= nw->pos_x;
3008 pt.y -= nw->pos_y;
3009
3010 int vpos = WidgetDimensions::scaled.frametext.top + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
3011 if (pt.y < vpos) return false;
3012
3013 int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
3014 if (row + 1 >= (int)this->fields.size()) return false;
3015 vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
3016 row++; // rebase row to match index of this->fields.
3017
3019 if (pt.x < xpos) return false;
3020 int column;
3021 for (column = 0; column <= 5; column++) {
3023 if (pt.x < xpos + width) break;
3024 xpos += width;
3025 }
3026 int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
3027 if (column > num_columns) return false;
3028 xpos = pt.x - xpos;
3029
3030 /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
3031 fieldxy->y = row;
3032 xy->y = vpos;
3033 if (_current_text_dir == TD_RTL) {
3034 fieldxy->x = num_columns - column;
3035 xy->x = ((column & 1) ? CargoesField::cargo_field_width : CargoesField::industry_width) - xpos;
3036 } else {
3037 fieldxy->x = column;
3038 xy->x = xpos;
3039 }
3040 return true;
3041 }
3042
3043 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
3044 {
3045 switch (widget) {
3046 case WID_IC_PANEL: {
3047 Point fieldxy, xy;
3048 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
3049
3050 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3051 switch (fld->type) {
3052 case CFT_INDUSTRY:
3054 break;
3055
3056 case CFT_CARGO: {
3057 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3058 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3059 CargoType cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3060 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3061 break;
3062 }
3063
3064 case CFT_CARGO_LABEL: {
3065 CargoType cargo_type = fld->CargoLabelClickedAt(xy);
3066 if (IsValidCargoType(cargo_type)) this->ComputeCargoDisplay(cargo_type);
3067 break;
3068 }
3069
3070 default:
3071 break;
3072 }
3073 break;
3074 }
3075
3076 case WID_IC_NOTIFY:
3079 SndClickBeep();
3080
3081 if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
3082 if (FindWindowByClass(WC_SMALLMAP) == nullptr) ShowSmallMap();
3083 this->NotifySmallmap();
3084 }
3085 break;
3086
3087 case WID_IC_CARGO_DROPDOWN: {
3088 DropDownList lst;
3090 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
3091 lst.push_back(MakeDropDownListIconItem(d, cs->GetCargoIcon(), PAL_NONE, cs->name, cs->Index()));
3092 }
3093 if (!lst.empty()) {
3094 int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
3095 ShowDropDownList(this, std::move(lst), selected, WID_IC_CARGO_DROPDOWN);
3096 }
3097 break;
3098 }
3099
3100 case WID_IC_IND_DROPDOWN: {
3101 DropDownList lst;
3102 for (IndustryType ind : _sorted_industry_types) {
3103 const IndustrySpec *indsp = GetIndustrySpec(ind);
3104 if (!indsp->enabled) continue;
3105 lst.push_back(MakeDropDownListStringItem(indsp->name, ind));
3106 }
3107 if (!lst.empty()) {
3108 int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
3109 ShowDropDownList(this, std::move(lst), selected, WID_IC_IND_DROPDOWN);
3110 }
3111 break;
3112 }
3113 }
3114 }
3115
3116 void OnDropdownSelect(WidgetID widget, int index, int) override
3117 {
3118 if (index < 0) return;
3119
3120 switch (widget) {
3122 this->ComputeCargoDisplay(index);
3123 break;
3124
3126 this->ComputeIndustryDisplay(index);
3127 break;
3128 }
3129 }
3130
3131 bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
3132 {
3133 if (widget != WID_IC_PANEL) return false;
3134
3135 Point fieldxy, xy;
3136 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return false;
3137
3138 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
3139 CargoType cargo_type = INVALID_CARGO;
3140 switch (fld->type) {
3141 case CFT_CARGO: {
3142 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
3143 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
3144 cargo_type = fld->CargoClickedAt(lft, rgt, xy);
3145 break;
3146 }
3147
3148 case CFT_CARGO_LABEL: {
3149 cargo_type = fld->CargoLabelClickedAt(xy);
3150 break;
3151 }
3152
3153 case CFT_INDUSTRY:
3154 if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
3155 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP), close_cond);
3156 }
3157 return true;
3158
3159 default:
3160 break;
3161 }
3162 if (IsValidCargoType(cargo_type) && (this->ind_cargo < NUM_INDUSTRYTYPES || cargo_type != this->ind_cargo - NUM_INDUSTRYTYPES)) {
3163 const CargoSpec *csp = CargoSpec::Get(cargo_type);
3164 GuiShowTooltips(this, GetEncodedString(STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, csp->name), close_cond);
3165 return true;
3166 }
3167
3168 return false;
3169 }
3170
3171 void OnResize() override
3172 {
3173 this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL, WidgetDimensions::scaled.framerect.Vertical() + CargoesField::small_height);
3174 }
3175};
3176
3181static void ShowIndustryCargoesWindow(IndustryType id)
3182{
3183 if (id >= NUM_INDUSTRYTYPES) {
3184 for (IndustryType ind : _sorted_industry_types) {
3185 const IndustrySpec *indsp = GetIndustrySpec(ind);
3186 if (indsp->enabled) {
3187 id = ind;
3188 break;
3189 }
3190 }
3191 if (id >= NUM_INDUSTRYTYPES) return;
3192 }
3193
3195 if (w != nullptr) {
3196 w->InvalidateData(id);
3197 return;
3198 }
3199 new IndustryCargoesWindow(id);
3200}
3201
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.
std::string GetDecodedString() const
Decode the encoded string.
Definition strings.cpp:207
uint GetTotalColumnsWidth() const
Get total width of all columns.
List template of 'things' T to sort in a GUI.
bool Filter(FilterFunction *decide, F filter_data)
Filter the list.
void RebuildDone()
Notify the sortlist that the rebuild is done.
void SetListing(Listing l)
Import sort conditions.
void SetFilterState(bool state)
Enable or disable the filter.
bool IsDescSortOrder() const
Check if the sort order is descending.
void ToggleSortOrder()
Toggle the sort order Since that is the worst condition for the sort function reverse the list here.
bool(const const Industry * *, const std::pair< CargoType, CargoType > &) FilterFunction
Signature of filter function.
void SetFilterFuncs(std::span< FilterFunction *const > n_funcs)
Hand the filter function pointers to the GUIList.
bool NeedRebuild() const
Check if a rebuild is needed.
void SetFilterType(uint8_t n_type)
Set the filtertype of the list.
void ForceRebuild()
Force that a rebuild is needed.
bool Sort(Comp compare)
Sort the list.
void ForceResort()
Force a resort next Sort call Reset the resort timer if used too.
uint8_t SortType() const
Get the sorttype of the list.
Listing GetListing() const
Export current sort conditions.
void SetSortFuncs(std::span< SortFunction *const > n_funcs)
Hand the sort function pointers to the GUIList.
void SetSortType(uint8_t n_type)
Set the sorttype of the list.
The list of industries.
const IntervalTimer< TimerWindow > rebuild_interval
Rebuild the industry list on a regular interval.
void OnEditboxChanged(WidgetID wid) override
The text in an editbox has been edited.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
static bool IndustryTypeSorter(const Industry *const &a, const Industry *const &b, const CargoType &filter)
Sort industries by type and name.
static int GetCargoTransportedSortValue(const Industry *i)
Returns value representing industry's transported cargo percentage for industry sorting.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
const int MAX_FILTER_LENGTH
The max length of the filter, in chars.
void SetCargoFilterArray()
Populate the filter list and set the cargo filter criteria.
void OnResize() override
Called after the window got resized.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
void SetProducedCargoFilter(CargoType cargo_type)
Set produced cargo filter for the industry list.
static int GetCargoTransportedPercentsIfValid(const Industry::ProducedCargo &p)
Returns percents of cargo transported if industry produces this cargo, else -1.
@ ByName
Sorter type to sort by name.
@ ByTransported
Sorter type to sort by transported percentage.
@ ByProduction
Sorter type to sort by production amount.
@ ByType
Sorter type to sort by type.
std::string GetIndustryString(const Industry *i) const
Get the StringID to draw and set the appropriate DParams.
uint GetIndustryListWidth() const
Get the width needed to draw the longest industry line.
static bool IndustryTransportedCargoSorter(const Industry *const &a, const Industry *const &b, const CargoType &filter)
Sort industries by transported cargo and name.
QueryString industry_editbox
Filter editbox.
StringFilter string_filter
Filter for industries.
static bool IndustryProductionSorter(const Industry *const &a, const Industry *const &b, const CargoType &filter)
Sort industries by production and name.
void BuildSortIndustriesList()
(Re)Build industries list
CargoType accepted_cargo_filter_criteria
Selected accepted cargo filter index.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
static bool IndustryNameSorter(const Industry *const &a, const Industry *const &b, const CargoType &)
Sort industries by name.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void SetAcceptedCargoFilter(CargoType cargo_type)
Set accepted cargo filter for the industry list.
void OnDropdownSelect(WidgetID widget, int index, int) override
A dropdown option associated to this window has been selected.
CargoType produced_cargo_filter_criteria
Selected produced cargo filter index.
void OnPaint() override
The window must be repainted.
void OnInit() override
Notification that the nested widget tree gets initialized.
void OnResize() override
Called after the window got resized.
int cheat_line_height
Height of each line for the WID_IV_INFO panel.
int production_offset_y
The offset of the production texts/buttons.
void OnPaint() override
The window must be repainted.
Editability editable
Mode for changing production.
InfoLine clicked_line
The line of the button that has been clicked.
void OnTimeout() override
Called when this window's timeout has been reached.
bool IsNewGRFInspectable() const override
Is the data related to this window NewGRF inspectable?
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
int info_height
Height needed for the WID_IV_INFO panel.
Dimension cargo_icon_size
Largest cargo icon dimension.
void OnMouseWheel(int wheel, WidgetID widget) override
The mouse wheel has been turned.
void ShowNewGRFInspectWindow() const override
Show the NewGRF inspection window.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void OnInit() override
Notification that the nested widget tree gets initialized.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
uint8_t clicked_button
The button that has been clicked (to raise)
void OnQueryTextFinished(std::optional< std::string > str) override
The query window opened from this window has closed.
InfoLine
Specific lines in the info panel.
@ IL_RATE2
Production rate of cargo 2.
@ IL_MULTIPLIER
Production multiplier.
@ IL_RATE1
Production rate of cargo 1.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
int DrawInfo(const Rect &r)
Draw the text in the WID_IV_INFO panel.
InfoLine editbox_line
The line clicked to open the edit box.
Editability
Modes for changing production.
@ EA_RATE
Allow changing the production rates.
@ EA_MULTIPLIER
Allow changing the production multiplier.
@ EA_NONE
Not alterable.
An interval timer will fire every interval, and will continue to fire until it is deleted.
Definition timer.h:76
Baseclass for nested widgets.
int pos_y
Vertical position of top-left corner of the widget in the window.
int pos_x
Horizontal position of top-left corner of the widget in the window.
uint resize_y
Vertical resize step (0 means not resizable).
Nested widget to display a viewport in a window.
void UpdateViewportCoordinates(Window *w)
Update the position and size of the viewport (after eg a resize).
Definition widget.cpp: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.
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:455
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:415
Functions related to the drop down widget.
Types related to the drop down widget.
std::vector< std::unique_ptr< const DropDownListItem > > DropDownList
A drop down list is a collection of drop down list items.
uint ScaleByCargoScale(uint num, bool town)
Scale a number by the cargo scale setting.
Functions related to errors.
@ WL_INFO
Used for DoCommand-like (and some non-fatal AI GUI) errors/information.
Definition error.h:24
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, CommandCost &cc)
Display an error message in a window.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:87
bool _generating_world
Whether we are generating the map or not.
Definition genworld.cpp:74
Functions related to world/map generation.
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
int CentreBounds(int min, int max, int size)
Determine where to position a centred object.
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:966
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:895
Dimension GetStringListBoundingBox(std::span< const StringID > list, FontSize fontsize)
Get maximum dimension of a list of strings.
Definition gfx.cpp:933
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:666
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:39
void DrawRectOutline(const Rect &r, PixelColour colour, int width, int dash)
Draw the outline of a Rect.
Definition gfx.cpp:461
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:1032
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:783
void GfxFillRect(int left, int top, int right, int bottom, const std::variant< PixelColour, PaletteID > &colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:116
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:1562
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:251
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:250
@ SA_LEFT
Left align the text.
Definition gfx_type.h:389
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:391
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:390
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:308
@ TC_FORCED
Ignore colour changes from strings.
Definition gfx_type.h:333
@ FILLRECT_OPAQUE
Fill rectangle with a single colour.
Definition gfx_type.h:346
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:957
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition gfx.cpp:1543
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.
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
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 constexpr PixelColour PC_YELLOW
Yellow palette colour.
static constexpr PixelColour PC_BLACK
Black palette colour.
static constexpr PixelColour PC_WHITE
White palette colour.
Base for the GUIs that have an edit box in them.
Pseudo random number generator.
A number of safeguards to prevent using unsafe methods.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
void DrawArrowButtons(int x, int y, Colours button_colour, uint8_t state, bool clickable_left, bool clickable_right)
Draw [<][>] boxes.
Functions for setting GUIs.
#define SETTING_BUTTON_WIDTH
Width of setting buttons.
#define SETTING_BUTTON_HEIGHT
Height of setting buttons.
@ SWS_OFF
Scroll wheel has no effect.
void ShowSmallMap()
Show the smallmap window.
Smallmap GUI functions.
Base types for having sorted lists in GUIs.
void SndClickBeep()
Play a beep sound for a click event if enabled in settings.
Definition sound.cpp:253
Functions related to sound.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
int StrNaturalCompare(std::string_view s1, std::string_view s2, bool ignore_garbage_at_front)
Compares two strings using case insensitive natural sort.
Definition string.cpp: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:336
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:218
Functions related to OTTD's strings.
auto MakeParameters(Args &&... args)
Helper to create the StringParameters with its own buffer with the given parameter values.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_RTL
Text is written right-to-left by default.
static const int MAX_CHAR_LENGTH
Max. length of UTF-8 encoded unicode character.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
Class to backup a specific variable and restore it later.
static void SwitchMode(PersistentStorageMode mode, bool ignore_prev_mode=false)
Clear temporary changes made since the last call to SwitchMode, and set whether subsequent changes sh...
Specification of a cargo type.
Definition cargotype.h:74
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:137
SpriteID GetCargoIcon() const
Get sprite for showing cargo of this type.
static IterateWrapper Iterate(size_t from=0)
Returns an iterable ensemble of all valid CargoSpec.
Definition cargotype.h:192
StringID name
Name of this type of cargo.
Definition cargotype.h:91
TownProductionEffect town_production_effect
The effect on town cargo production.
Definition cargotype.h:87
Transfer storage of cargo suffix information.
std::string text
Cargo suffix text.
CargoSuffixDisplay display
How to display the cargo and text.
Comparator to sort CargoType by according to desired order.
Definition cargotype.h:244
Data about a single field in the IndustryCargoesWindow panel.
static const PixelColour CARGO_LINE_COLOUR
Line colour around the cargo.
StringID header
Header text (for CFT_HEADER).
std::array< CargoType, MAX_CARGOES > cargoes
Cargoes to display (or INVALID_CARGO).
CargoesFieldType type
Type of field.
static int blob_distance
Distance of the industry legend colour from the edge of the industry box.
static Dimension cargo_border
Dimensions of border between cargo lines and industry boxes.
uint8_t top_end
Stop at the top of the vertical cargoes.
Cargoes cust_cargoes
Cargoes in vertical_cargoes leaving to the right.
int GetCargoBase(int xpos) const
For a CFT_CARGO, compute the left position of the left-most vertical cargo connection.
std::array< CargoType, MAX_CARGOES > other_accepted
Cargoes accepted but not used in this figure.
int ConnectCargo(CargoType cargo, bool producer)
Connect a cargo from an industry to the CFT_CARGO column.
static int small_height
Height of the header row.
CargoType CargoLabelClickedAt(Point pt) const
Decide what cargo the user clicked in the cargo label field.
static Dimension cargo_space
Dimensions of space between cargo lines.
static int normal_height
Height of the non-header rows.
static int cargo_field_width
Width of a cargo field.
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.
static const PixelColour INDUSTRY_LINE_COLOUR
Line colour of the industry type box.
CargoType CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
Decide which cargo was clicked at in a CFT_CARGO field.
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
GUISettings gui
settings related to the GUI
uint8_t raw_industry_construction
type of (raw) industry construction (none, "normal", prospecting)
T y
Y coordinate.
T x
X coordinate.
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h: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.
PixelColour map_colour
colour used for the small map
CargoType cargo
Cargo type.
Definition industry.h:86
uint16_t waiting
Amount of cargo waiting to processed.
Definition industry.h:87
CargoType cargo
Cargo type.
Definition industry.h:74
HistoryData< ProducedHistory > history
History of cargo produced and transported for this month and 24 previous months.
Definition industry.h:77
Defines the internal data of a functional industry.
Definition industry.h:62
IndustryType type
type of industry.
Definition industry.h:115
bool IsCargoAccepted() const
Test if this industry accepts any cargo.
Definition industry.h:223
uint8_t prod_level
general production level
Definition industry.h:112
void RecomputeProductionMultipliers()
Recompute #production_rate for current prod_level.
EncodedString text
General text with additional information.
Definition industry.h:132
ProducedCargoes::iterator GetCargoProduced(CargoType cargo)
Get produced cargo slot for a specific cargo type.
Definition industry.h:180
ProducedCargoes produced
produced cargo slots
Definition industry.h:110
AcceptedCargoes accepted
accepted cargo slots
Definition industry.h:111
static uint16_t GetIndustryTypeCount(IndustryType type)
Get the count of industries for this type.
Definition industry.h:264
TileArea location
Location of the industry.
Definition industry.h:106
bool IsCargoProduced() const
Test if this industry produces any cargo.
Definition industry.h:229
Data structure describing how to show the list (what sort direction and criteria).
Size related data of the map.
Definition map_func.h:206
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.
Colour for pixel/line drawing.
Definition gfx_type.h:406
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
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:969
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:1780
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:3207
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:556
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:504
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:1770
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:530
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:595
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1793
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:313
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
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:1184
Window * FindWindowByClass(WindowClass cls)
Find any window by its class.
Definition window.cpp:1157
Window * BringWindowToFrontById(WindowClass cls, WindowNumber number)
Find a window and make it the relative top-window on the screen.
Definition window.cpp:1267
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:3284
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.