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