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