2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file industry_gui.cpp GUIs related to industries. */
13 #include "settings_gui.h"
14 #include "sound_func.h"
15 #include "window_func.h"
16 #include "textbuf_gui.h"
17 #include "command_func.h"
18 #include "viewport_func.h"
21 #include "cheat_type.h"
22 #include "newgrf_industries.h"
23 #include "newgrf_text.h"
24 #include "newgrf_debug.h"
25 #include "network/network.h"
26 #include "strings_func.h"
27 #include "company_func.h"
28 #include "tilehighlight_func.h"
29 #include "string_func.h"
30 #include "sortlist_type.h"
31 #include "dropdown_func.h"
32 #include "company_base.h"
33 #include "core/geometry_func.hpp"
34 #include "core/random_func.hpp"
35 #include "core/backup_type.hpp"
37 #include "smallmap_gui.h"
38 #include "dropdown_type.h"
39 #include "clear_map.h"
40 #include "zoom_func.h"
41 #include "industry_cmd.h"
42 #include "graph_gui.h"
43 #include "querystring_gui.h"
44 #include "stringfilter_type.h"
45 #include "timer/timer.h"
46 #include "timer/timer_window.h"
49 #include "widgets/industry_widget.h"
51 #include "table/strings.h"
55 #include "safeguards.h"
57 bool _ignore_restrictions
;
58 std::bitset
<NUM_INDUSTRYTYPES
> _displayed_industries
; ///< Communication from the industry chain window to the smallmap window about what industries to display.
60 /** Cargo suffix type (for which window is it requested) */
61 enum CargoSuffixType
{
62 CST_FUND
, ///< Fund-industry window
63 CST_VIEW
, ///< View-industry window
64 CST_DIR
, ///< Industry-directory window
67 /** Ways of displaying the cargo. */
68 enum CargoSuffixDisplay
{
69 CSD_CARGO
, ///< Display the cargo without sub-type (cb37 result 401).
70 CSD_CARGO_AMOUNT
, ///< Display the cargo and amount (if useful), but no sub-type (cb37 result 400 or fail).
71 CSD_CARGO_TEXT
, ///< Display then cargo and supplied string (cb37 result 800-BFF).
72 CSD_CARGO_AMOUNT_TEXT
, ///< Display then cargo, amount, and string (cb37 result 000-3FF).
75 /** Transfer storage of cargo suffix information. */
77 CargoSuffixDisplay display
; ///< How to display the cargo and text.
78 std::string text
; ///< Cargo suffix text.
81 extern void GenerateIndustries();
82 static void ShowIndustryCargoesWindow(IndustryType id
);
85 * Gets the string to display after the cargo name (using callback 37)
86 * @param cargo the cargo for which the suffix is requested, meaning depends on presence of flag 18 in prop 1A
87 * @param cst the cargo suffix type (for which window is it requested). @see CargoSuffixType
88 * @param ind the industry (nullptr if in fund window)
89 * @param ind_type the industry type
90 * @param indspec the industry spec
91 * @param suffix is filled with the string to display
93 static void GetCargoSuffix(uint cargo
, CargoSuffixType cst
, const Industry
*ind
, IndustryType ind_type
, const IndustrySpec
*indspec
, CargoSuffix
&suffix
)
96 suffix
.display
= CSD_CARGO_AMOUNT
;
98 if (HasBit(indspec
->callback_mask
, CBM_IND_CARGO_SUFFIX
)) {
99 TileIndex t
= (cst
!= CST_FUND
) ? ind
->location
.tile
: INVALID_TILE
;
100 uint16_t callback
= GetIndustryCallback(CBID_INDUSTRY_CARGO_SUFFIX
, 0, (cst
<< 8) | cargo
, const_cast<Industry
*>(ind
), ind_type
, t
);
101 if (callback
== CALLBACK_FAILED
) return;
103 if (indspec
->grf_prop
.grffile
->grf_version
< 8) {
104 if (GB(callback
, 0, 8) == 0xFF) return;
105 if (callback
< 0x400) {
106 StartTextRefStackUsage(indspec
->grf_prop
.grffile
, 6);
107 suffix
.text
= GetString(GetGRFStringID(indspec
->grf_prop
.grffile
->grfid
, 0xD000 + callback
));
108 StopTextRefStackUsage();
109 suffix
.display
= CSD_CARGO_AMOUNT_TEXT
;
112 ErrorUnknownCallbackResult(indspec
->grf_prop
.grffile
->grfid
, CBID_INDUSTRY_CARGO_SUFFIX
, callback
);
115 } else { // GRF version 8 or higher.
116 if (callback
== 0x400) return;
117 if (callback
== 0x401) {
118 suffix
.display
= CSD_CARGO
;
121 if (callback
< 0x400) {
122 StartTextRefStackUsage(indspec
->grf_prop
.grffile
, 6);
123 suffix
.text
= GetString(GetGRFStringID(indspec
->grf_prop
.grffile
->grfid
, 0xD000 + callback
));
124 StopTextRefStackUsage();
125 suffix
.display
= CSD_CARGO_AMOUNT_TEXT
;
128 if (callback
>= 0x800 && callback
< 0xC00) {
129 StartTextRefStackUsage(indspec
->grf_prop
.grffile
, 6);
130 suffix
.text
= GetString(GetGRFStringID(indspec
->grf_prop
.grffile
->grfid
, 0xD000 - 0x800 + callback
));
131 StopTextRefStackUsage();
132 suffix
.display
= CSD_CARGO_TEXT
;
135 ErrorUnknownCallbackResult(indspec
->grf_prop
.grffile
->grfid
, CBID_INDUSTRY_CARGO_SUFFIX
, callback
);
141 enum CargoSuffixInOut
{
147 * Gets all strings to display after the cargoes of industries (using callback 37)
148 * @param use_input get suffixes for output cargoes or input cargoes?
149 * @param cst the cargo suffix type (for which window is it requested). @see CargoSuffixType
150 * @param ind the industry (nullptr if in fund window)
151 * @param ind_type the industry type
152 * @param indspec the industry spec
153 * @param cargoes array with cargotypes. for INVALID_CARGO no suffix will be determined
154 * @param suffixes is filled with the suffixes
156 template <typename TC
, typename TS
>
157 static inline void GetAllCargoSuffixes(CargoSuffixInOut use_input
, CargoSuffixType cst
, const Industry
*ind
, IndustryType ind_type
, const IndustrySpec
*indspec
, const TC
&cargoes
, TS
&suffixes
)
159 static_assert(std::tuple_size_v
<std::remove_reference_t
<decltype(cargoes
)>> <= lengthof(suffixes
));
161 if (indspec
->behaviour
& INDUSTRYBEH_CARGOTYPES_UNLIMITED
) {
162 /* Reworked behaviour with new many-in-many-out scheme */
163 for (uint j
= 0; j
< lengthof(suffixes
); j
++) {
164 if (IsValidCargoID(cargoes
[j
])) {
165 uint8_t local_id
= indspec
->grf_prop
.grffile
->cargo_map
[cargoes
[j
]]; // should we check the value for valid?
166 uint cargotype
= local_id
<< 16 | use_input
;
167 GetCargoSuffix(cargotype
, cst
, ind
, ind_type
, indspec
, suffixes
[j
]);
169 suffixes
[j
].text
.clear();
170 suffixes
[j
].display
= CSD_CARGO
;
174 /* Compatible behaviour with old 3-in-2-out scheme */
175 for (uint j
= 0; j
< lengthof(suffixes
); j
++) {
176 suffixes
[j
].text
.clear();
177 suffixes
[j
].display
= CSD_CARGO
;
180 case CARGOSUFFIX_OUT
:
181 // Handle INDUSTRY_ORIGINAL_NUM_OUTPUTS cargoes
182 if (IsValidCargoID(cargoes
[0])) GetCargoSuffix(3, cst
, ind
, ind_type
, indspec
, suffixes
[0]);
183 if (IsValidCargoID(cargoes
[1])) GetCargoSuffix(4, cst
, ind
, ind_type
, indspec
, suffixes
[1]);
186 // Handle INDUSTRY_ORIGINAL_NUM_INPUTS cargoes
187 if (IsValidCargoID(cargoes
[0])) GetCargoSuffix(0, cst
, ind
, ind_type
, indspec
, suffixes
[0]);
188 if (IsValidCargoID(cargoes
[1])) GetCargoSuffix(1, cst
, ind
, ind_type
, indspec
, suffixes
[1]);
189 if (IsValidCargoID(cargoes
[2])) GetCargoSuffix(2, cst
, ind
, ind_type
, indspec
, suffixes
[2]);
198 * Gets the strings to display after the cargo of industries (using callback 37)
199 * @param use_input get suffixes for output cargo or input cargo?
200 * @param cst the cargo suffix type (for which window is it requested). @see CargoSuffixType
201 * @param ind the industry (nullptr if in fund window)
202 * @param ind_type the industry type
203 * @param indspec the industry spec
204 * @param cargo cargotype. for INVALID_CARGO no suffix will be determined
205 * @param slot accepts/produced slot number, used for old-style 3-in/2-out industries.
206 * @param suffix is filled with the suffix
208 void GetCargoSuffix(CargoSuffixInOut use_input
, CargoSuffixType cst
, const Industry
*ind
, IndustryType ind_type
, const IndustrySpec
*indspec
, CargoID cargo
, uint8_t slot
, CargoSuffix
&suffix
)
211 suffix
.display
= CSD_CARGO
;
212 if (!IsValidCargoID(cargo
)) return;
213 if (indspec
->behaviour
& INDUSTRYBEH_CARGOTYPES_UNLIMITED
) {
214 uint8_t local_id
= indspec
->grf_prop
.grffile
->cargo_map
[cargo
]; // should we check the value for valid?
215 uint cargotype
= local_id
<< 16 | use_input
;
216 GetCargoSuffix(cargotype
, cst
, ind
, ind_type
, indspec
, suffix
);
217 } else if (use_input
== CARGOSUFFIX_IN
) {
218 if (slot
< INDUSTRY_ORIGINAL_NUM_INPUTS
) GetCargoSuffix(slot
, cst
, ind
, ind_type
, indspec
, suffix
);
219 } else if (use_input
== CARGOSUFFIX_OUT
) {
220 if (slot
< INDUSTRY_ORIGINAL_NUM_OUTPUTS
) GetCargoSuffix(slot
+ INDUSTRY_ORIGINAL_NUM_INPUTS
, cst
, ind
, ind_type
, indspec
, suffix
);
224 std::array
<IndustryType
, NUM_INDUSTRYTYPES
> _sorted_industry_types
; ///< Industry types sorted by name.
226 /** Sort industry types by their name. */
227 static bool IndustryTypeNameSorter(const IndustryType
&a
, const IndustryType
&b
)
229 int r
= StrNaturalCompare(GetString(GetIndustrySpec(a
)->name
), GetString(GetIndustrySpec(b
)->name
)); // Sort by name (natural sorting).
231 /* If the names are equal, sort by industry type. */
232 return (r
!= 0) ? r
< 0 : (a
< b
);
236 * Initialize the list of sorted industry types.
238 void SortIndustryTypes()
240 /* Add each industry type to the list. */
241 for (IndustryType i
= 0; i
< NUM_INDUSTRYTYPES
; i
++) {
242 _sorted_industry_types
[i
] = i
;
245 /* Sort industry types by name. */
246 std::sort(_sorted_industry_types
.begin(), _sorted_industry_types
.end(), IndustryTypeNameSorter
);
250 * Command callback. In case of failure to build an industry, show an error message.
251 * @param result Result of the command.
252 * @param tile Tile where the industry is placed.
253 * @param indtype Industry type.
255 void CcBuildIndustry(Commands
, const CommandCost
&result
, TileIndex tile
, IndustryType indtype
, uint32_t, bool, uint32_t)
257 if (result
.Succeeded()) return;
259 if (indtype
< NUM_INDUSTRYTYPES
) {
260 const IndustrySpec
*indsp
= GetIndustrySpec(indtype
);
261 if (indsp
->enabled
) {
262 SetDParam(0, indsp
->name
);
263 ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE
, result
.GetErrorMessage(), WL_INFO
, TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
);
268 static constexpr NWidgetPart _nested_build_industry_widgets
[] = {
269 NWidget(NWID_HORIZONTAL
),
270 NWidget(WWT_CLOSEBOX
, COLOUR_DARK_GREEN
),
271 NWidget(WWT_CAPTION
, COLOUR_DARK_GREEN
), SetDataTip(STR_FUND_INDUSTRY_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
272 NWidget(WWT_SHADEBOX
, COLOUR_DARK_GREEN
),
273 NWidget(WWT_DEFSIZEBOX
, COLOUR_DARK_GREEN
),
274 NWidget(WWT_STICKYBOX
, COLOUR_DARK_GREEN
),
276 NWidget(NWID_SELECTION
, COLOUR_DARK_GREEN
, WID_DPI_SCENARIO_EDITOR_PANE
),
277 NWidget(NWID_VERTICAL
),
278 NWidget(WWT_TEXTBTN
, COLOUR_DARK_GREEN
, WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET
), SetMinimalSize(0, 12), SetFill(1, 0), SetResize(1, 0),
279 SetDataTip(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES
, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP
),
280 NWidget(WWT_TEXTBTN
, COLOUR_DARK_GREEN
, WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET
), SetMinimalSize(0, 12), SetFill(1, 0), SetResize(1, 0),
281 SetDataTip(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES
, STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP
),
284 NWidget(NWID_HORIZONTAL
),
285 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
),
286 NWidget(NWID_VSCROLLBAR
, COLOUR_DARK_GREEN
, WID_DPI_SCROLLBAR
),
288 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
, WID_DPI_INFOPANEL
), SetResize(1, 0),
290 NWidget(NWID_HORIZONTAL
),
291 NWidget(WWT_TEXTBTN
, COLOUR_DARK_GREEN
, WID_DPI_DISPLAY_WIDGET
), SetFill(1, 0), SetResize(1, 0),
292 SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN
, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP
),
293 NWidget(WWT_TEXTBTN
, COLOUR_DARK_GREEN
, WID_DPI_FUND_WIDGET
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING
, STR_NULL
),
294 NWidget(WWT_RESIZEBOX
, COLOUR_DARK_GREEN
),
298 /** Window definition of the dynamic place industries gui */
299 static WindowDesc
_build_industry_desc(
300 WDP_AUTO
, "build_industry", 170, 212,
301 WC_BUILD_INDUSTRY
, WC_NONE
,
303 _nested_build_industry_widgets
306 /** Build (fund or prospect) a new industry, */
307 class BuildIndustryWindow
: public Window
{
308 IndustryType selected_type
; ///< industry corresponding to the above index
309 std::vector
<IndustryType
> list
; ///< List of industries.
310 bool enabled
; ///< Availability state of the selected industry.
312 Dimension legend
; ///< Dimension of the legend 'blob'.
314 /** The largest allowed minimum-width of the window, given in line heights */
315 static const int MAX_MINWIDTH_LINEHEIGHTS
= 20;
317 void UpdateAvailability()
319 this->enabled
= this->selected_type
!= INVALID_INDUSTRYTYPE
&& (_game_mode
== GM_EDITOR
|| GetIndustryProbabilityCallback(this->selected_type
, IACT_USERCREATION
, 1) > 0);
326 /* Fill the arrays with industries.
327 * The tests performed after the enabled allow to load the industries
328 * In the same way they are inserted by grf (if any)
330 for (IndustryType ind
: _sorted_industry_types
) {
331 const IndustrySpec
*indsp
= GetIndustrySpec(ind
);
332 if (indsp
->enabled
) {
333 /* Rule is that editor mode loads all industries.
334 * In game mode, all non raw industries are loaded too
335 * and raw ones are loaded only when setting allows it */
336 if (_game_mode
!= GM_EDITOR
&& indsp
->IsRawIndustry() && _settings_game
.construction
.raw_industry_construction
== 0) {
337 /* Unselect if the industry is no longer in the list */
338 if (this->selected_type
== ind
) this->selected_type
= INVALID_INDUSTRYTYPE
;
342 this->list
.push_back(ind
);
346 /* First industry type is selected if the current selection is invalid. */
347 if (this->selected_type
== INVALID_INDUSTRYTYPE
&& !this->list
.empty()) this->selected_type
= this->list
[0];
349 this->UpdateAvailability();
351 this->vscroll
->SetCount(this->list
.size());
354 /** Update status of the fund and display-chain widgets. */
357 this->SetWidgetDisabledState(WID_DPI_FUND_WIDGET
, this->selected_type
!= INVALID_INDUSTRYTYPE
&& !this->enabled
);
358 this->SetWidgetDisabledState(WID_DPI_DISPLAY_WIDGET
, this->selected_type
== INVALID_INDUSTRYTYPE
&& this->enabled
);
362 * Build a string of cargo names with suffixes attached.
363 * This is distinct from the CARGO_LIST string formatting code in two ways:
364 * - This cargo list uses the order defined by the industry, rather than alphabetic.
365 * - NewGRF-supplied suffix strings can be attached to each cargo.
367 * @param cargolist Array of CargoID to display
368 * @param cargo_suffix Array of suffixes to attach to each cargo
369 * @param cargolistlen Length of arrays
370 * @param prefixstr String to use for the first item
371 * @return A formatted raw string
373 std::string
MakeCargoListString(const std::span
<const CargoID
> cargolist
, const std::span
<const CargoSuffix
> cargo_suffix
, StringID prefixstr
) const
375 assert(cargolist
.size() == cargo_suffix
.size());
377 std::string cargostring
;
379 size_t firstcargo
= 0;
381 for (size_t j
= 0; j
< cargolist
.size(); j
++) {
382 if (!IsValidCargoID(cargolist
[j
])) continue;
388 SetDParam(0, CargoSpec::Get(cargolist
[j
])->name
);
389 SetDParamStr(1, cargo_suffix
[j
].text
);
390 AppendStringInPlace(cargostring
, STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION
);
394 SetDParam(0, CargoSpec::Get(cargolist
[firstcargo
])->name
);
395 SetDParamStr(1, cargo_suffix
[firstcargo
].text
);
396 cargostring
= GetString(prefixstr
) + cargostring
;
398 SetDParam(0, STR_JUST_NOTHING
);
400 cargostring
= GetString(prefixstr
);
407 BuildIndustryWindow() : Window(_build_industry_desc
)
409 this->selected_type
= INVALID_INDUSTRYTYPE
;
411 this->CreateNestedTree();
412 this->vscroll
= this->GetScrollbar(WID_DPI_SCROLLBAR
);
413 /* Show scenario editor tools in editor. */
414 if (_game_mode
!= GM_EDITOR
) {
415 this->GetWidget
<NWidgetStacked
>(WID_DPI_SCENARIO_EDITOR_PANE
)->SetDisplayedPlane(SZSP_HORIZONTAL
);
417 this->FinishInitNested(0);
422 void OnInit() override
424 /* Width of the legend blob -- slightly larger than the smallmap legend blob. */
425 this->legend
.height
= GetCharacterHeight(FS_SMALL
);
426 this->legend
.width
= this->legend
.height
* 9 / 6;
431 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
434 case WID_DPI_MATRIX_WIDGET
: {
435 SetDParamMaxDigits(0, 4);
436 Dimension count
= GetStringBoundingBox(STR_JUST_COMMA
, FS_SMALL
);
438 for (const auto &indtype
: this->list
) {
439 d
= maxdim(d
, GetStringBoundingBox(GetIndustrySpec(indtype
)->name
));
441 resize
.height
= std::max
<uint
>({this->legend
.height
, d
.height
, count
.height
}) + padding
.height
;
442 d
.width
+= this->legend
.width
+ WidgetDimensions::scaled
.hsep_wide
+ WidgetDimensions::scaled
.hsep_normal
+ count
.width
+ padding
.width
;
443 d
.height
= 5 * resize
.height
;
444 size
= maxdim(size
, d
);
448 case WID_DPI_INFOPANEL
: {
449 /* Extra line for cost outside of editor. */
450 int height
= 2 + (_game_mode
== GM_EDITOR
? 0 : 1);
451 uint extra_lines_req
= 0;
452 uint extra_lines_prd
= 0;
453 uint extra_lines_newgrf
= 0;
454 uint max_minwidth
= GetCharacterHeight(FS_NORMAL
) * MAX_MINWIDTH_LINEHEIGHTS
;
455 Dimension d
= {0, 0};
456 for (const auto &indtype
: this->list
) {
457 const IndustrySpec
*indsp
= GetIndustrySpec(indtype
);
458 CargoSuffix cargo_suffix
[std::tuple_size_v
<decltype(indsp
->accepts_cargo
)>];
460 /* Measure the accepted cargoes, if any. */
461 GetAllCargoSuffixes(CARGOSUFFIX_IN
, CST_FUND
, nullptr, indtype
, indsp
, indsp
->accepts_cargo
, cargo_suffix
);
462 std::string cargostring
= this->MakeCargoListString(indsp
->accepts_cargo
, cargo_suffix
, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO
);
463 Dimension strdim
= GetStringBoundingBox(cargostring
);
464 if (strdim
.width
> max_minwidth
) {
465 extra_lines_req
= std::max(extra_lines_req
, strdim
.width
/ max_minwidth
+ 1);
466 strdim
.width
= max_minwidth
;
468 d
= maxdim(d
, strdim
);
470 /* Measure the produced cargoes, if any. */
471 GetAllCargoSuffixes(CARGOSUFFIX_OUT
, CST_FUND
, nullptr, indtype
, indsp
, indsp
->produced_cargo
, cargo_suffix
);
472 cargostring
= this->MakeCargoListString(indsp
->produced_cargo
, cargo_suffix
, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO
);
473 strdim
= GetStringBoundingBox(cargostring
);
474 if (strdim
.width
> max_minwidth
) {
475 extra_lines_prd
= std::max(extra_lines_prd
, strdim
.width
/ max_minwidth
+ 1);
476 strdim
.width
= max_minwidth
;
478 d
= maxdim(d
, strdim
);
480 if (indsp
->grf_prop
.grffile
!= nullptr) {
481 /* Reserve a few extra lines for text from an industry NewGRF. */
482 extra_lines_newgrf
= 4;
486 /* Set it to something more sane :) */
487 height
+= extra_lines_prd
+ extra_lines_req
+ extra_lines_newgrf
;
488 size
.height
= height
* GetCharacterHeight(FS_NORMAL
) + padding
.height
;
489 size
.width
= d
.width
+ padding
.width
;
493 case WID_DPI_FUND_WIDGET
: {
494 Dimension d
= GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY
);
495 d
= maxdim(d
, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY
));
496 d
= maxdim(d
, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY
));
497 d
.width
+= padding
.width
;
498 d
.height
+= padding
.height
;
499 size
= maxdim(size
, d
);
505 void SetStringParameters(WidgetID widget
) const override
508 case WID_DPI_FUND_WIDGET
:
509 /* Raw industries might be prospected. Show this fact by changing the string
510 * In Editor, you just build, while ingame, or you fund or you prospect */
511 if (_game_mode
== GM_EDITOR
) {
512 /* We've chosen many random industries but no industries have been specified */
513 SetDParam(0, STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY
);
515 if (this->selected_type
!= INVALID_INDUSTRYTYPE
) {
516 const IndustrySpec
*indsp
= GetIndustrySpec(this->selected_type
);
517 SetDParam(0, (_settings_game
.construction
.raw_industry_construction
== 2 && indsp
->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY
: STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY
);
519 SetDParam(0, STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY
);
526 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
529 case WID_DPI_MATRIX_WIDGET
: {
530 bool rtl
= _current_text_dir
== TD_RTL
;
531 Rect text
= r
.WithHeight(this->resize
.step_height
).Shrink(WidgetDimensions::scaled
.matrix
);
532 Rect icon
= text
.WithWidth(this->legend
.width
, rtl
);
533 text
= text
.Indent(this->legend
.width
+ WidgetDimensions::scaled
.hsep_wide
, rtl
);
535 /* Vertical offset for legend icon. */
536 icon
.top
= r
.top
+ (this->resize
.step_height
- this->legend
.height
+ 1) / 2;
537 icon
.bottom
= icon
.top
+ this->legend
.height
- 1;
539 auto [first
, last
] = this->vscroll
->GetVisibleRangeIterators(this->list
);
540 for (auto it
= first
; it
!= last
; ++it
) {
541 IndustryType type
= *it
;
542 bool selected
= this->selected_type
== type
;
543 const IndustrySpec
*indsp
= GetIndustrySpec(type
);
545 /* Draw the name of the industry in white is selected, otherwise, in orange */
546 DrawString(text
, indsp
->name
, selected
? TC_WHITE
: TC_ORANGE
);
547 GfxFillRect(icon
, selected
? PC_WHITE
: PC_BLACK
);
548 GfxFillRect(icon
.Shrink(WidgetDimensions::scaled
.bevel
), indsp
->map_colour
);
549 SetDParam(0, Industry::GetIndustryTypeCount(type
));
550 DrawString(text
, STR_JUST_COMMA
, TC_BLACK
, SA_RIGHT
, false, FS_SMALL
);
552 text
= text
.Translate(0, this->resize
.step_height
);
553 icon
= icon
.Translate(0, this->resize
.step_height
);
558 case WID_DPI_INFOPANEL
: {
559 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.framerect
);
561 if (this->selected_type
== INVALID_INDUSTRYTYPE
) {
562 DrawStringMultiLine(ir
, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP
);
566 const IndustrySpec
*indsp
= GetIndustrySpec(this->selected_type
);
568 if (_game_mode
!= GM_EDITOR
) {
569 SetDParam(0, indsp
->GetConstructionCost());
570 DrawString(ir
, STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST
);
571 ir
.top
+= GetCharacterHeight(FS_NORMAL
);
574 CargoSuffix cargo_suffix
[std::tuple_size_v
<decltype(indsp
->accepts_cargo
)>];
576 /* Draw the accepted cargoes, if any. Otherwise, will print "Nothing". */
577 GetAllCargoSuffixes(CARGOSUFFIX_IN
, CST_FUND
, nullptr, this->selected_type
, indsp
, indsp
->accepts_cargo
, cargo_suffix
);
578 std::string cargostring
= this->MakeCargoListString(indsp
->accepts_cargo
, cargo_suffix
, STR_INDUSTRY_VIEW_REQUIRES_N_CARGO
);
579 ir
.top
= DrawStringMultiLine(ir
, cargostring
);
581 /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
582 GetAllCargoSuffixes(CARGOSUFFIX_OUT
, CST_FUND
, nullptr, this->selected_type
, indsp
, indsp
->produced_cargo
, cargo_suffix
);
583 cargostring
= this->MakeCargoListString(indsp
->produced_cargo
, cargo_suffix
, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO
);
584 ir
.top
= DrawStringMultiLine(ir
, cargostring
);
586 /* Get the additional purchase info text, if it has not already been queried. */
587 if (HasBit(indsp
->callback_mask
, CBM_IND_FUND_MORE_TEXT
)) {
588 uint16_t callback_res
= GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT
, 0, 0, nullptr, this->selected_type
, INVALID_TILE
);
589 if (callback_res
!= CALLBACK_FAILED
&& callback_res
!= 0x400) {
590 if (callback_res
> 0x400) {
591 ErrorUnknownCallbackResult(indsp
->grf_prop
.grffile
->grfid
, CBID_INDUSTRY_FUND_MORE_TEXT
, callback_res
);
593 StringID str
= GetGRFStringID(indsp
->grf_prop
.grffile
->grfid
, 0xD000 + callback_res
); // No. here's the new string
594 if (str
!= STR_UNDEFINED
) {
595 StartTextRefStackUsage(indsp
->grf_prop
.grffile
, 6);
596 DrawStringMultiLine(ir
, str
, TC_YELLOW
);
597 StopTextRefStackUsage();
607 static void AskManyRandomIndustriesCallback(Window
*, bool confirmed
)
609 if (!confirmed
) return;
611 if (Town::GetNumItems() == 0) {
612 ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES
, STR_ERROR_MUST_FOUND_TOWN_FIRST
, WL_INFO
);
614 Backup
<bool> old_generating_world(_generating_world
, true);
615 BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP
);
616 GenerateIndustries();
617 BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP
);
618 old_generating_world
.Restore();
622 static void AskRemoveAllIndustriesCallback(Window
*, bool confirmed
)
624 if (!confirmed
) return;
626 for (Industry
*industry
: Industry::Iterate()) delete industry
;
628 /* Clear farmland. */
629 for (TileIndex tile
= 0; tile
< Map::Size(); tile
++) {
630 if (IsTileType(tile
, MP_CLEAR
) && GetRawClearGround(tile
) == CLEAR_FIELDS
) {
631 MakeClear(tile
, CLEAR_GRASS
, 3);
635 MarkWholeScreenDirty();
638 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
641 case WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET
: {
642 assert(_game_mode
== GM_EDITOR
);
643 this->HandleButtonClick(WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET
);
644 ShowQuery(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION
, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY
, nullptr, AskManyRandomIndustriesCallback
);
648 case WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET
: {
649 assert(_game_mode
== GM_EDITOR
);
650 this->HandleButtonClick(WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET
);
651 ShowQuery(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION
, STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY
, nullptr, AskRemoveAllIndustriesCallback
);
655 case WID_DPI_MATRIX_WIDGET
: {
656 auto it
= this->vscroll
->GetScrolledItemFromWidget(this->list
, pt
.y
, this, WID_DPI_MATRIX_WIDGET
);
657 if (it
!= this->list
.end()) { // Is it within the boundaries of available data?
658 this->selected_type
= *it
;
659 this->UpdateAvailability();
661 const IndustrySpec
*indsp
= GetIndustrySpec(this->selected_type
);
665 if (_thd
.GetCallbackWnd() == this &&
666 ((_game_mode
!= GM_EDITOR
&& _settings_game
.construction
.raw_industry_construction
== 2 && indsp
!= nullptr && indsp
->IsRawIndustry()) || !this->enabled
)) {
667 /* Reset the button state if going to prospecting or "build many industries" */
668 this->RaiseButtons();
669 ResetObjectToPlace();
673 if (this->enabled
&& click_count
> 1) this->OnClick(pt
, WID_DPI_FUND_WIDGET
, 1);
678 case WID_DPI_DISPLAY_WIDGET
:
679 if (this->selected_type
!= INVALID_INDUSTRYTYPE
) ShowIndustryCargoesWindow(this->selected_type
);
682 case WID_DPI_FUND_WIDGET
: {
683 if (this->selected_type
!= INVALID_INDUSTRYTYPE
) {
684 if (_game_mode
!= GM_EDITOR
&& _settings_game
.construction
.raw_industry_construction
== 2 && GetIndustrySpec(this->selected_type
)->IsRawIndustry()) {
685 Command
<CMD_BUILD_INDUSTRY
>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY
, 0, this->selected_type
, 0, false, InteractiveRandom());
686 this->HandleButtonClick(WID_DPI_FUND_WIDGET
);
688 HandlePlacePushButton(this, WID_DPI_FUND_WIDGET
, SPR_CURSOR_INDUSTRY
, HT_RECT
);
696 void OnResize() override
698 /* Adjust the number of items in the matrix depending of the resize */
699 this->vscroll
->SetCapacityFromWidget(this, WID_DPI_MATRIX_WIDGET
);
702 void OnPlaceObject([[maybe_unused
]] Point pt
, TileIndex tile
) override
705 /* We do not need to protect ourselves against "Random Many Industries" in this mode */
706 const IndustrySpec
*indsp
= GetIndustrySpec(this->selected_type
);
707 uint32_t seed
= InteractiveRandom();
708 uint32_t layout_index
= InteractiveRandomRange((uint32_t)indsp
->layouts
.size());
710 if (_game_mode
== GM_EDITOR
) {
711 /* Show error if no town exists at all */
712 if (Town::GetNumItems() == 0) {
713 SetDParam(0, indsp
->name
);
714 ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE
, STR_ERROR_MUST_FOUND_TOWN_FIRST
, WL_INFO
, pt
.x
, pt
.y
);
718 Backup
<CompanyID
> cur_company(_current_company
, OWNER_NONE
);
719 Backup
<bool> old_generating_world(_generating_world
, true);
720 _ignore_restrictions
= true;
722 Command
<CMD_BUILD_INDUSTRY
>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY
, &CcBuildIndustry
, tile
, this->selected_type
, layout_index
, false, seed
);
724 cur_company
.Restore();
725 old_generating_world
.Restore();
726 _ignore_restrictions
= false;
728 success
= Command
<CMD_BUILD_INDUSTRY
>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY
, tile
, this->selected_type
, layout_index
, false, seed
);
731 /* If an industry has been built, just reset the cursor and the system */
732 if (success
&& !_settings_client
.gui
.persistent_buildingtools
) ResetObjectToPlace();
735 IntervalTimer
<TimerWindow
> update_interval
= {std::chrono::seconds(3), [this](auto) {
736 if (_game_mode
== GM_EDITOR
) return;
737 if (this->selected_type
== INVALID_INDUSTRYTYPE
) return;
739 bool enabled
= this->enabled
;
740 this->UpdateAvailability();
741 if (enabled
!= this->enabled
) {
747 void OnTimeout() override
749 this->RaiseButtons();
752 void OnPlaceObjectAbort() override
754 this->RaiseButtons();
758 * Some data on this window has become invalid.
759 * @param data Information about the changed data.
760 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
762 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
764 if (!gui_scope
) return;
771 void ShowBuildIndustryWindow()
773 if (_game_mode
!= GM_EDITOR
&& !Company::IsValidID(_local_company
)) return;
774 if (BringWindowToFrontById(WC_BUILD_INDUSTRY
, 0)) return;
775 new BuildIndustryWindow();
778 static void UpdateIndustryProduction(Industry
*i
);
780 static inline bool IsProductionAlterable(const Industry
*i
)
782 const IndustrySpec
*is
= GetIndustrySpec(i
->type
);
783 bool has_prod
= std::any_of(std::begin(is
->production_rate
), std::end(is
->production_rate
), [](auto rate
) { return rate
!= 0; });
784 return ((_game_mode
== GM_EDITOR
|| _cheats
.setup_prod
.value
) &&
785 (has_prod
|| is
->IsRawIndustry()) &&
789 class IndustryViewWindow
: public Window
791 /** Modes for changing production */
793 EA_NONE
, ///< Not alterable
794 EA_MULTIPLIER
, ///< Allow changing the production multiplier
795 EA_RATE
, ///< Allow changing the production rates
798 /** Specific lines in the info panel */
800 IL_NONE
, ///< No line
801 IL_MULTIPLIER
, ///< Production multiplier
802 IL_RATE1
, ///< Production rate of cargo 1
803 IL_RATE2
, ///< Production rate of cargo 2
806 Dimension cargo_icon_size
; ///< Largest cargo icon dimension.
807 Editability editable
; ///< Mode for changing production
808 InfoLine editbox_line
; ///< The line clicked to open the edit box
809 InfoLine clicked_line
; ///< The line of the button that has been clicked
810 uint8_t clicked_button
; ///< The button that has been clicked (to raise)
811 int production_offset_y
; ///< The offset of the production texts/buttons
812 int info_height
; ///< Height needed for the #WID_IV_INFO panel
813 int cheat_line_height
; ///< Height of each line for the #WID_IV_INFO panel
816 IndustryViewWindow(WindowDesc
&desc
, WindowNumber window_number
) : Window(desc
)
818 this->flags
|= WF_DISABLE_VP_SCROLL
;
819 this->editbox_line
= IL_NONE
;
820 this->clicked_line
= IL_NONE
;
821 this->clicked_button
= 0;
822 this->info_height
= WidgetDimensions::scaled
.framerect
.Vertical() + 2 * GetCharacterHeight(FS_NORMAL
); // Info panel has at least two lines text.
824 this->InitNested(window_number
);
825 NWidgetViewport
*nvp
= this->GetWidget
<NWidgetViewport
>(WID_IV_VIEWPORT
);
826 nvp
->InitializeViewport(this, Industry::Get(window_number
)->location
.GetCenterTile(), ScaleZoomGUI(ZOOM_LVL_INDUSTRY
));
828 const Industry
*i
= Industry::Get(window_number
);
829 if (!i
->IsCargoProduced()) this->DisableWidget(WID_IV_GRAPH
);
831 this->InvalidateData();
834 ~IndustryViewWindow()
836 CloseWindowById(WC_INDUSTRY_PRODUCTION
, this->window_number
, false);
839 void OnInit() override
841 /* This only used when the cheat to alter industry production is enabled */
842 this->cheat_line_height
= std::max(SETTING_BUTTON_HEIGHT
+ WidgetDimensions::scaled
.vsep_normal
, GetCharacterHeight(FS_NORMAL
));
843 this->cargo_icon_size
= GetLargestCargoIconSize();
846 void OnPaint() override
850 if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
852 const Rect r
= this->GetWidget
<NWidgetBase
>(WID_IV_INFO
)->GetCurrentRect();
853 int expected
= this->DrawInfo(r
);
854 if (expected
!= r
.bottom
) {
855 this->info_height
= expected
- r
.top
+ 1;
861 void DrawCargoIcon(const Rect
&r
, CargoID cid
) const
863 bool rtl
= _current_text_dir
== TD_RTL
;
864 SpriteID icon
= CargoSpec::Get(cid
)->GetCargoIcon();
865 Dimension d
= GetSpriteSize(icon
);
866 Rect ir
= r
.WithWidth(this->cargo_icon_size
.width
, rtl
).WithHeight(GetCharacterHeight(FS_NORMAL
));
867 DrawSprite(icon
, PAL_NONE
, CenterBounds(ir
.left
, ir
.right
, d
.width
), CenterBounds(ir
.top
, ir
.bottom
, this->cargo_icon_size
.height
));
871 * Draw the text in the #WID_IV_INFO panel.
872 * @param r Rectangle of the panel.
873 * @return Expected position of the bottom edge of the panel.
875 int DrawInfo(const Rect
&r
)
877 bool rtl
= _current_text_dir
== TD_RTL
;
878 Industry
*i
= Industry::Get(this->window_number
);
879 const IndustrySpec
*ind
= GetIndustrySpec(i
->type
);
880 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.framerect
);
882 bool has_accept
= false;
884 if (i
->prod_level
== PRODLEVEL_CLOSURE
) {
885 DrawString(ir
, STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE
);
886 ir
.top
+= GetCharacterHeight(FS_NORMAL
) + WidgetDimensions::scaled
.vsep_wide
;
889 const int label_indent
= WidgetDimensions::scaled
.hsep_normal
+ this->cargo_icon_size
.width
;
890 bool stockpiling
= HasBit(ind
->callback_mask
, CBM_IND_PRODUCTION_CARGO_ARRIVAL
) || HasBit(ind
->callback_mask
, CBM_IND_PRODUCTION_256_TICKS
);
892 for (const auto &a
: i
->accepted
) {
893 if (!IsValidCargoID(a
.cargo
)) continue;
896 DrawString(ir
, STR_INDUSTRY_VIEW_REQUIRES
);
897 ir
.top
+= GetCharacterHeight(FS_NORMAL
);
901 DrawCargoIcon(ir
, a
.cargo
);
904 GetCargoSuffix(CARGOSUFFIX_IN
, CST_VIEW
, i
, i
->type
, ind
, a
.cargo
, &a
- i
->accepted
.data(), suffix
);
906 SetDParam(0, CargoSpec::Get(a
.cargo
)->name
);
907 SetDParam(1, a
.cargo
);
908 SetDParam(2, a
.waiting
);
910 StringID str
= STR_NULL
;
911 switch (suffix
.display
) {
912 case CSD_CARGO_AMOUNT_TEXT
:
913 SetDParamStr(3, suffix
.text
);
915 case CSD_CARGO_AMOUNT
:
916 str
= stockpiling
? STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT
: STR_INDUSTRY_VIEW_ACCEPT_CARGO
;
920 SetDParamStr(3, suffix
.text
);
923 str
= STR_INDUSTRY_VIEW_ACCEPT_CARGO
;
929 DrawString(ir
.Indent(label_indent
, rtl
), str
);
930 ir
.top
+= GetCharacterHeight(FS_NORMAL
);
933 int line_height
= this->editable
== EA_RATE
? this->cheat_line_height
: GetCharacterHeight(FS_NORMAL
);
934 int text_y_offset
= (line_height
- GetCharacterHeight(FS_NORMAL
)) / 2;
935 int button_y_offset
= (line_height
- SETTING_BUTTON_HEIGHT
) / 2;
937 for (const auto &p
: i
->produced
) {
938 if (!IsValidCargoID(p
.cargo
)) continue;
940 if (has_accept
) ir
.top
+= WidgetDimensions::scaled
.vsep_wide
;
941 DrawString(ir
, TimerGameEconomy::UsingWallclockUnits() ? STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE
: STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE
);
942 ir
.top
+= GetCharacterHeight(FS_NORMAL
);
943 if (this->editable
== EA_RATE
) this->production_offset_y
= ir
.top
;
947 DrawCargoIcon(ir
, p
.cargo
);
950 GetCargoSuffix(CARGOSUFFIX_OUT
, CST_VIEW
, i
, i
->type
, ind
, p
.cargo
, &p
- i
->produced
.data(), suffix
);
952 SetDParam(0, p
.cargo
);
953 SetDParam(1, p
.history
[LAST_MONTH
].production
);
954 SetDParamStr(2, suffix
.text
);
955 SetDParam(3, ToPercent8(p
.history
[LAST_MONTH
].PctTransported()));
956 DrawString(ir
.Indent(label_indent
+ (this->editable
== EA_RATE
? SETTING_BUTTON_WIDTH
+ WidgetDimensions::scaled
.hsep_normal
: 0), rtl
).Translate(0, text_y_offset
), STR_INDUSTRY_VIEW_TRANSPORTED
);
957 /* Let's put out those buttons.. */
958 if (this->editable
== EA_RATE
) {
959 DrawArrowButtons(ir
.Indent(label_indent
, rtl
).WithWidth(SETTING_BUTTON_WIDTH
, rtl
).left
, ir
.top
+ button_y_offset
, COLOUR_YELLOW
, (this->clicked_line
== IL_RATE1
+ (&p
- i
->produced
.data())) ? this->clicked_button
: 0,
960 p
.rate
> 0, p
.rate
< 255);
962 ir
.top
+= line_height
;
965 /* Display production multiplier if editable */
966 if (this->editable
== EA_MULTIPLIER
) {
967 line_height
= this->cheat_line_height
;
968 text_y_offset
= (line_height
- GetCharacterHeight(FS_NORMAL
)) / 2;
969 button_y_offset
= (line_height
- SETTING_BUTTON_HEIGHT
) / 2;
970 ir
.top
+= WidgetDimensions::scaled
.vsep_wide
;
971 this->production_offset_y
= ir
.top
;
972 SetDParam(0, RoundDivSU(i
->prod_level
* 100, PRODLEVEL_DEFAULT
));
973 DrawString(ir
.Indent(label_indent
+ SETTING_BUTTON_WIDTH
+ WidgetDimensions::scaled
.hsep_normal
, rtl
).Translate(0, text_y_offset
), STR_INDUSTRY_VIEW_PRODUCTION_LEVEL
);
974 DrawArrowButtons(ir
.Indent(label_indent
, rtl
).WithWidth(SETTING_BUTTON_WIDTH
, rtl
).left
, ir
.top
+ button_y_offset
, COLOUR_YELLOW
, (this->clicked_line
== IL_MULTIPLIER
) ? this->clicked_button
: 0,
975 i
->prod_level
> PRODLEVEL_MINIMUM
, i
->prod_level
< PRODLEVEL_MAXIMUM
);
976 ir
.top
+= line_height
;
979 /* Get the extra message for the GUI */
980 if (HasBit(ind
->callback_mask
, CBM_IND_WINDOW_MORE_TEXT
)) {
981 uint16_t callback_res
= GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT
, 0, 0, i
, i
->type
, i
->location
.tile
);
982 if (callback_res
!= CALLBACK_FAILED
&& callback_res
!= 0x400) {
983 if (callback_res
> 0x400) {
984 ErrorUnknownCallbackResult(ind
->grf_prop
.grffile
->grfid
, CBID_INDUSTRY_WINDOW_MORE_TEXT
, callback_res
);
986 StringID message
= GetGRFStringID(ind
->grf_prop
.grffile
->grfid
, 0xD000 + callback_res
);
987 if (message
!= STR_NULL
&& message
!= STR_UNDEFINED
) {
988 ir
.top
+= WidgetDimensions::scaled
.vsep_wide
;
990 StartTextRefStackUsage(ind
->grf_prop
.grffile
, 6);
991 /* Use all the available space left from where we stand up to the
992 * end of the window. We ALSO enlarge the window if needed, so we
993 * can 'go' wild with the bottom of the window. */
994 ir
.top
= DrawStringMultiLine(ir
.left
, ir
.right
, ir
.top
, UINT16_MAX
, message
, TC_BLACK
);
995 StopTextRefStackUsage();
1001 if (!i
->text
.empty()) {
1002 SetDParamStr(0, i
->text
);
1003 ir
.top
+= WidgetDimensions::scaled
.vsep_wide
;
1004 ir
.top
= DrawStringMultiLine(ir
.left
, ir
.right
, ir
.top
, UINT16_MAX
, STR_JUST_RAW_STRING
, TC_BLACK
);
1007 /* Return required bottom position, the last pixel row plus some padding. */
1008 return ir
.top
- 1 + WidgetDimensions::scaled
.framerect
.bottom
;
1011 void SetStringParameters(WidgetID widget
) const override
1013 if (widget
== WID_IV_CAPTION
) SetDParam(0, this->window_number
);
1016 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
1018 if (widget
== WID_IV_INFO
) size
.height
= this->info_height
;
1021 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
1025 Industry
*i
= Industry::Get(this->window_number
);
1026 InfoLine line
= IL_NONE
;
1028 switch (this->editable
) {
1029 case EA_NONE
: break;
1032 if (IsInsideBS(pt
.y
, this->production_offset_y
, this->cheat_line_height
)) line
= IL_MULTIPLIER
;
1036 if (pt
.y
>= this->production_offset_y
) {
1037 int row
= (pt
.y
- this->production_offset_y
) / this->cheat_line_height
;
1038 for (auto itp
= std::begin(i
->produced
); itp
!= std::end(i
->produced
); ++itp
) {
1039 if (!IsValidCargoID(itp
->cargo
)) continue;
1042 line
= (InfoLine
)(IL_RATE1
+ (itp
- std::begin(i
->produced
)));
1049 if (line
== IL_NONE
) return;
1051 bool rtl
= _current_text_dir
== TD_RTL
;
1052 Rect r
= this->GetWidget
<NWidgetBase
>(widget
)->GetCurrentRect().Shrink(WidgetDimensions::scaled
.framerect
).Indent(this->cargo_icon_size
.width
+ WidgetDimensions::scaled
.hsep_normal
, rtl
);
1054 if (r
.WithWidth(SETTING_BUTTON_WIDTH
, rtl
).Contains(pt
)) {
1055 /* Clicked buttons, decrease or increase production */
1056 bool decrease
= r
.WithWidth(SETTING_BUTTON_WIDTH
/ 2, rtl
).Contains(pt
);
1057 switch (this->editable
) {
1060 if (i
->prod_level
<= PRODLEVEL_MINIMUM
) return;
1061 i
->prod_level
= static_cast<uint8_t>(std::max
<uint
>(i
->prod_level
/ 2, PRODLEVEL_MINIMUM
));
1063 if (i
->prod_level
>= PRODLEVEL_MAXIMUM
) return;
1064 i
->prod_level
= static_cast<uint8_t>(std::min
<uint
>(i
->prod_level
* 2, PRODLEVEL_MAXIMUM
));
1070 if (i
->produced
[line
- IL_RATE1
].rate
<= 0) return;
1071 i
->produced
[line
- IL_RATE1
].rate
= std::max(i
->produced
[line
- IL_RATE1
].rate
/ 2, 0);
1073 if (i
->produced
[line
- IL_RATE1
].rate
>= 255) return;
1074 /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
1075 int new_prod
= i
->produced
[line
- IL_RATE1
].rate
== 0 ? 1 : i
->produced
[line
- IL_RATE1
].rate
* 2;
1076 i
->produced
[line
- IL_RATE1
].rate
= ClampTo
<uint8_t>(new_prod
);
1080 default: NOT_REACHED();
1083 UpdateIndustryProduction(i
);
1086 this->clicked_line
= line
;
1087 this->clicked_button
= (decrease
^ rtl
) ? 1 : 2;
1088 } else if (r
.Indent(SETTING_BUTTON_WIDTH
+ WidgetDimensions::scaled
.hsep_normal
, rtl
).Contains(pt
)) {
1089 /* clicked the text */
1090 this->editbox_line
= line
;
1091 switch (this->editable
) {
1093 SetDParam(0, RoundDivSU(i
->prod_level
* 100, PRODLEVEL_DEFAULT
));
1094 ShowQueryString(STR_JUST_INT
, STR_CONFIG_GAME_PRODUCTION_LEVEL
, 10, this, CS_ALPHANUMERAL
, QSF_NONE
);
1098 SetDParam(0, i
->produced
[line
- IL_RATE1
].rate
* 8);
1099 ShowQueryString(STR_JUST_INT
, STR_CONFIG_GAME_PRODUCTION
, 10, this, CS_ALPHANUMERAL
, QSF_NONE
);
1102 default: NOT_REACHED();
1109 Industry
*i
= Industry::Get(this->window_number
);
1110 if (_ctrl_pressed
) {
1111 ShowExtraViewportWindow(i
->location
.GetCenterTile());
1113 ScrollMainWindowToTile(i
->location
.GetCenterTile());
1118 case WID_IV_DISPLAY
: {
1119 Industry
*i
= Industry::Get(this->window_number
);
1120 ShowIndustryCargoesWindow(i
->type
);
1125 ShowIndustryProductionGraph(this->window_number
);
1130 void OnTimeout() override
1132 this->clicked_line
= IL_NONE
;
1133 this->clicked_button
= 0;
1137 void OnResize() override
1139 if (this->viewport
!= nullptr) {
1140 NWidgetViewport
*nvp
= this->GetWidget
<NWidgetViewport
>(WID_IV_VIEWPORT
);
1141 nvp
->UpdateViewportCoordinates(this);
1143 ScrollWindowToTile(Industry::Get(this->window_number
)->location
.GetCenterTile(), this, true); // Re-center viewport.
1147 void OnMouseWheel(int wheel
) override
1149 if (_settings_client
.gui
.scrollwheel_scrolling
!= SWS_OFF
) {
1150 DoZoomInOutWindow(wheel
< 0 ? ZOOM_IN
: ZOOM_OUT
, this);
1154 void OnQueryTextFinished(std::optional
<std::string
> str
) override
1156 if (!str
.has_value() || str
->empty()) return;
1158 Industry
*i
= Industry::Get(this->window_number
);
1159 uint value
= atoi(str
->c_str());
1160 switch (this->editbox_line
) {
1161 case IL_NONE
: NOT_REACHED();
1164 i
->prod_level
= ClampU(RoundDivSU(value
* PRODLEVEL_DEFAULT
, 100), PRODLEVEL_MINIMUM
, PRODLEVEL_MAXIMUM
);
1168 i
->produced
[this->editbox_line
- IL_RATE1
].rate
= ClampU(RoundDivSU(value
, 8), 0, 255);
1171 UpdateIndustryProduction(i
);
1176 * Some data on this window has become invalid.
1177 * @param data Information about the changed data.
1178 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
1180 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
1182 if (!gui_scope
) return;
1183 const Industry
*i
= Industry::Get(this->window_number
);
1184 if (IsProductionAlterable(i
)) {
1185 const IndustrySpec
*ind
= GetIndustrySpec(i
->type
);
1186 this->editable
= ind
->UsesOriginalEconomy() ? EA_MULTIPLIER
: EA_RATE
;
1188 this->editable
= EA_NONE
;
1192 bool IsNewGRFInspectable() const override
1194 return ::IsNewGRFInspectable(GSF_INDUSTRIES
, this->window_number
);
1197 void ShowNewGRFInspectWindow() const override
1199 ::ShowNewGRFInspectWindow(GSF_INDUSTRIES
, this->window_number
);
1203 static void UpdateIndustryProduction(Industry
*i
)
1205 const IndustrySpec
*indspec
= GetIndustrySpec(i
->type
);
1206 if (indspec
->UsesOriginalEconomy()) i
->RecomputeProductionMultipliers();
1208 for (auto &p
: i
->produced
) {
1209 if (IsValidCargoID(p
.cargo
)) {
1210 p
.history
[LAST_MONTH
].production
= ScaleByCargoScale(8 * p
.rate
, false);
1215 /** Widget definition of the view industry gui */
1216 static constexpr NWidgetPart _nested_industry_view_widgets
[] = {
1217 NWidget(NWID_HORIZONTAL
),
1218 NWidget(WWT_CLOSEBOX
, COLOUR_CREAM
),
1219 NWidget(WWT_CAPTION
, COLOUR_CREAM
, WID_IV_CAPTION
), SetDataTip(STR_INDUSTRY_VIEW_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
1220 NWidget(WWT_PUSHIMGBTN
, COLOUR_CREAM
, WID_IV_GOTO
), SetAspect(WidgetDimensions::ASPECT_LOCATION
), SetDataTip(SPR_GOTO_LOCATION
, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP
),
1221 NWidget(WWT_DEBUGBOX
, COLOUR_CREAM
),
1222 NWidget(WWT_SHADEBOX
, COLOUR_CREAM
),
1223 NWidget(WWT_DEFSIZEBOX
, COLOUR_CREAM
),
1224 NWidget(WWT_STICKYBOX
, COLOUR_CREAM
),
1226 NWidget(WWT_PANEL
, COLOUR_CREAM
),
1227 NWidget(WWT_INSET
, COLOUR_CREAM
), SetPadding(2, 2, 2, 2),
1228 NWidget(NWID_VIEWPORT
, INVALID_COLOUR
, WID_IV_VIEWPORT
), SetMinimalSize(254, 86), SetFill(1, 0), SetResize(1, 1),
1231 NWidget(WWT_PANEL
, COLOUR_CREAM
, WID_IV_INFO
), SetMinimalSize(260, 0), SetMinimalTextLines(2, WidgetDimensions::unscaled
.framerect
.Vertical()), SetResize(1, 0),
1233 NWidget(NWID_HORIZONTAL
),
1234 NWidget(WWT_PUSHTXTBTN
, COLOUR_CREAM
, WID_IV_DISPLAY
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN
, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP
),
1235 NWidget(WWT_PUSHTXTBTN
, COLOUR_CREAM
, WID_IV_GRAPH
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_INDUSTRY_VIEW_PRODUCTION_GRAPH
, STR_INDUSTRY_VIEW_PRODUCTION_GRAPH_TOOLTIP
),
1236 NWidget(WWT_RESIZEBOX
, COLOUR_CREAM
),
1240 /** Window definition of the view industry gui */
1241 static WindowDesc
_industry_view_desc(
1242 WDP_AUTO
, "view_industry", 260, 120,
1243 WC_INDUSTRY_VIEW
, WC_NONE
,
1245 _nested_industry_view_widgets
1248 void ShowIndustryViewWindow(int industry
)
1250 AllocateWindowDescFront
<IndustryViewWindow
>(_industry_view_desc
, industry
);
1253 /** Widget definition of the industry directory gui */
1254 static constexpr NWidgetPart _nested_industry_directory_widgets
[] = {
1255 NWidget(NWID_HORIZONTAL
),
1256 NWidget(WWT_CLOSEBOX
, COLOUR_BROWN
),
1257 NWidget(WWT_CAPTION
, COLOUR_BROWN
, WID_ID_CAPTION
), SetDataTip(STR_INDUSTRY_DIRECTORY_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
1258 NWidget(WWT_SHADEBOX
, COLOUR_BROWN
),
1259 NWidget(WWT_DEFSIZEBOX
, COLOUR_BROWN
),
1260 NWidget(WWT_STICKYBOX
, COLOUR_BROWN
),
1262 NWidget(NWID_HORIZONTAL
),
1263 NWidget(NWID_VERTICAL
),
1264 NWidget(NWID_HORIZONTAL
),
1265 NWidget(WWT_TEXTBTN
, COLOUR_BROWN
, WID_ID_DROPDOWN_ORDER
), SetDataTip(STR_BUTTON_SORT_BY
, STR_TOOLTIP_SORT_ORDER
),
1266 NWidget(WWT_DROPDOWN
, COLOUR_BROWN
, WID_ID_DROPDOWN_CRITERIA
), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_SORT_CRITERIA
),
1267 NWidget(WWT_EDITBOX
, COLOUR_BROWN
, WID_ID_FILTER
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE
, STR_LIST_FILTER_TOOLTIP
),
1269 NWidget(NWID_HORIZONTAL
),
1270 NWidget(WWT_DROPDOWN
, COLOUR_BROWN
, WID_ID_FILTER_BY_ACC_CARGO
), SetMinimalSize(225, 12), SetFill(0, 1), SetDataTip(STR_INDUSTRY_DIRECTORY_ACCEPTED_CARGO_FILTER
, STR_TOOLTIP_FILTER_CRITERIA
),
1271 NWidget(WWT_DROPDOWN
, COLOUR_BROWN
, WID_ID_FILTER_BY_PROD_CARGO
), SetMinimalSize(225, 12), SetFill(0, 1), SetDataTip(STR_INDUSTRY_DIRECTORY_PRODUCED_CARGO_FILTER
, STR_TOOLTIP_FILTER_CRITERIA
),
1272 NWidget(WWT_PANEL
, COLOUR_BROWN
), SetResize(1, 0), EndContainer(),
1274 NWidget(WWT_PANEL
, COLOUR_BROWN
, WID_ID_INDUSTRY_LIST
), SetDataTip(0x0, STR_INDUSTRY_DIRECTORY_LIST_CAPTION
), SetResize(1, 1), SetScrollbar(WID_ID_VSCROLLBAR
),
1277 NWidget(NWID_VSCROLLBAR
, COLOUR_BROWN
, WID_ID_VSCROLLBAR
),
1279 NWidget(NWID_HORIZONTAL
),
1280 NWidget(NWID_HSCROLLBAR
, COLOUR_BROWN
, WID_ID_HSCROLLBAR
),
1281 NWidget(WWT_RESIZEBOX
, COLOUR_BROWN
),
1285 typedef GUIList
<const Industry
*, const CargoID
&, const std::pair
<CargoID
, CargoID
> &> GUIIndustryList
;
1287 /** Cargo filter functions */
1289 * Check whether an industry accepts and produces a certain cargo pair.
1290 * @param industry The industry whose cargoes will being checked.
1291 * @param cargoes The accepted and produced cargo pair to look for.
1292 * @return bool Whether the given cargoes accepted and produced by the industry.
1294 static bool CargoFilter(const Industry
* const *industry
, const std::pair
<CargoID
, CargoID
> &cargoes
)
1296 auto accepted_cargo
= cargoes
.first
;
1297 auto produced_cargo
= cargoes
.second
;
1299 bool accepted_cargo_matches
;
1301 switch (accepted_cargo
) {
1302 case CargoFilterCriteria::CF_ANY
:
1303 accepted_cargo_matches
= true;
1306 case CargoFilterCriteria::CF_NONE
:
1307 accepted_cargo_matches
= !(*industry
)->IsCargoAccepted();
1311 accepted_cargo_matches
= (*industry
)->IsCargoAccepted(accepted_cargo
);
1315 bool produced_cargo_matches
;
1317 switch (produced_cargo
) {
1318 case CargoFilterCriteria::CF_ANY
:
1319 produced_cargo_matches
= true;
1322 case CargoFilterCriteria::CF_NONE
:
1323 produced_cargo_matches
= !(*industry
)->IsCargoProduced();
1327 produced_cargo_matches
= (*industry
)->IsCargoProduced(produced_cargo
);
1331 return accepted_cargo_matches
&& produced_cargo_matches
;
1334 static GUIIndustryList::FilterFunction
* const _industry_filter_funcs
[] = { &CargoFilter
};
1336 /** Enum referring to the Hotkeys in the industry directory window */
1337 enum IndustryDirectoryHotkeys
{
1338 IDHK_FOCUS_FILTER_BOX
, ///< Focus the filter box
1341 * The list of industries.
1343 class IndustryDirectoryWindow
: public Window
{
1345 /* Runtime saved values */
1346 static Listing last_sorting
;
1348 /* Constants for sorting industries */
1349 static inline const StringID sorter_names
[] = {
1352 STR_SORT_BY_PRODUCTION
,
1353 STR_SORT_BY_TRANSPORTED
,
1355 static const std::initializer_list
<GUIIndustryList::SortFunction
* const> sorter_funcs
;
1357 GUIIndustryList industries
{IndustryDirectoryWindow::produced_cargo_filter
};
1361 CargoID produced_cargo_filter_criteria
; ///< Selected produced cargo filter index
1362 CargoID accepted_cargo_filter_criteria
; ///< Selected accepted cargo filter index
1363 static CargoID produced_cargo_filter
;
1365 const int MAX_FILTER_LENGTH
= 16; ///< The max length of the filter, in chars
1366 StringFilter string_filter
; ///< Filter for industries
1367 QueryString industry_editbox
; ///< Filter editbox
1369 enum class SorterType
: uint8_t {
1370 ByName
, ///< Sorter type to sort by name
1371 ByType
, ///< Sorter type to sort by type
1372 ByProduction
, ///< Sorter type to sort by production amount
1373 ByTransported
, ///< Sorter type to sort by transported percentage
1377 * Set produced cargo filter for the industry list.
1378 * @param cid The cargo to be set
1380 void SetProducedCargoFilter(CargoID cid
)
1382 if (this->produced_cargo_filter_criteria
!= cid
) {
1383 this->produced_cargo_filter_criteria
= cid
;
1384 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1385 bool is_filtering_necessary
= this->produced_cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
|| this->accepted_cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
;
1387 this->industries
.SetFilterState(is_filtering_necessary
);
1388 this->industries
.SetFilterType(0);
1389 this->industries
.ForceRebuild();
1394 * Set accepted cargo filter for the industry list.
1395 * @param index The cargo to be set
1397 void SetAcceptedCargoFilter(CargoID cid
)
1399 if (this->accepted_cargo_filter_criteria
!= cid
) {
1400 this->accepted_cargo_filter_criteria
= cid
;
1401 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1402 bool is_filtering_necessary
= this->produced_cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
|| this->accepted_cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
;
1404 this->industries
.SetFilterState(is_filtering_necessary
);
1405 this->industries
.SetFilterType(0);
1406 this->industries
.ForceRebuild();
1410 StringID
GetCargoFilterLabel(CargoID cid
) const
1413 case CargoFilterCriteria::CF_ANY
: return STR_INDUSTRY_DIRECTORY_FILTER_ALL_TYPES
;
1414 case CargoFilterCriteria::CF_NONE
: return STR_INDUSTRY_DIRECTORY_FILTER_NONE
;
1415 default: return CargoSpec::Get(cid
)->name
;
1420 * Populate the filter list and set the cargo filter criteria.
1422 void SetCargoFilterArray()
1424 this->produced_cargo_filter_criteria
= CargoFilterCriteria::CF_ANY
;
1425 this->accepted_cargo_filter_criteria
= CargoFilterCriteria::CF_ANY
;
1427 this->industries
.SetFilterFuncs(_industry_filter_funcs
);
1429 bool is_filtering_necessary
= this->produced_cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
|| this->accepted_cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
;
1431 this->industries
.SetFilterState(is_filtering_necessary
);
1435 * Get the width needed to draw the longest industry line.
1436 * @return Returns width of the longest industry line, including padding.
1438 uint
GetIndustryListWidth() const
1441 for (const Industry
*i
: this->industries
) {
1442 width
= std::max(width
, GetStringBoundingBox(this->GetIndustryString(i
)).width
);
1444 return width
+ WidgetDimensions::scaled
.framerect
.Horizontal();
1447 /** (Re)Build industries list */
1448 void BuildSortIndustriesList()
1450 if (this->industries
.NeedRebuild()) {
1451 this->industries
.clear();
1452 this->industries
.reserve(Industry::GetNumItems());
1454 for (const Industry
*i
: Industry::Iterate()) {
1455 if (this->string_filter
.IsEmpty()) {
1456 this->industries
.push_back(i
);
1459 this->string_filter
.ResetState();
1460 this->string_filter
.AddLine(i
->GetCachedName());
1461 if (this->string_filter
.GetState()) this->industries
.push_back(i
);
1464 this->industries
.RebuildDone();
1466 auto filter
= std::make_pair(this->accepted_cargo_filter_criteria
, this->produced_cargo_filter_criteria
);
1468 this->industries
.Filter(filter
);
1470 this->hscroll
->SetCount(this->GetIndustryListWidth());
1471 this->vscroll
->SetCount(this->industries
.size()); // Update scrollbar as well.
1474 IndustryDirectoryWindow::produced_cargo_filter
= this->produced_cargo_filter_criteria
;
1475 this->industries
.Sort();
1481 * Returns percents of cargo transported if industry produces this cargo, else -1
1483 * @param i industry to check
1484 * @param id cargo slot
1485 * @return percents of cargo transported, or -1 if industry doesn't use this cargo slot
1487 static inline int GetCargoTransportedPercentsIfValid(const Industry::ProducedCargo
&p
)
1489 if (!IsValidCargoID(p
.cargo
)) return -1;
1490 return ToPercent8(p
.history
[LAST_MONTH
].PctTransported());
1494 * Returns value representing industry's transported cargo
1495 * percentage for industry sorting
1497 * @param i industry to check
1498 * @return value used for sorting
1500 static int GetCargoTransportedSortValue(const Industry
*i
)
1502 CargoID filter
= IndustryDirectoryWindow::produced_cargo_filter
;
1503 if (filter
== CargoFilterCriteria::CF_NONE
) return 0;
1505 int percentage
= 0, produced_cargo_count
= 0;
1506 for (const auto &p
: i
->produced
) {
1507 if (filter
== CargoFilterCriteria::CF_ANY
) {
1508 int transported
= GetCargoTransportedPercentsIfValid(p
);
1509 if (transported
!= -1) {
1510 produced_cargo_count
++;
1511 percentage
+= transported
;
1513 if (produced_cargo_count
== 0 && &p
== &i
->produced
.back() && percentage
== 0) {
1516 } else if (filter
== p
.cargo
) {
1517 return GetCargoTransportedPercentsIfValid(p
);
1521 if (produced_cargo_count
== 0) return percentage
;
1522 return percentage
/ produced_cargo_count
;
1525 /** Sort industries by name */
1526 static bool IndustryNameSorter(const Industry
* const &a
, const Industry
* const &b
, const CargoID
&)
1528 int r
= StrNaturalCompare(a
->GetCachedName(), b
->GetCachedName()); // Sort by name (natural sorting).
1529 if (r
== 0) return a
->index
< b
->index
;
1533 /** Sort industries by type and name */
1534 static bool IndustryTypeSorter(const Industry
* const &a
, const Industry
* const &b
, const CargoID
&filter
)
1537 while (it_a
!= NUM_INDUSTRYTYPES
&& a
->type
!= _sorted_industry_types
[it_a
]) it_a
++;
1539 while (it_b
!= NUM_INDUSTRYTYPES
&& b
->type
!= _sorted_industry_types
[it_b
]) it_b
++;
1540 int r
= it_a
- it_b
;
1541 return (r
== 0) ? IndustryNameSorter(a
, b
, filter
) : r
< 0;
1544 /** Sort industries by production and name */
1545 static bool IndustryProductionSorter(const Industry
* const &a
, const Industry
* const &b
, const CargoID
&filter
)
1547 if (filter
== CargoFilterCriteria::CF_NONE
) return IndustryTypeSorter(a
, b
, filter
);
1549 uint prod_a
= 0, prod_b
= 0;
1550 if (filter
== CargoFilterCriteria::CF_ANY
) {
1551 for (const auto &pa
: a
->produced
) {
1552 if (IsValidCargoID(pa
.cargo
)) prod_a
+= pa
.history
[LAST_MONTH
].production
;
1554 for (const auto &pb
: b
->produced
) {
1555 if (IsValidCargoID(pb
.cargo
)) prod_b
+= pb
.history
[LAST_MONTH
].production
;
1558 if (auto ita
= a
->GetCargoProduced(filter
); ita
!= std::end(a
->produced
)) prod_a
= ita
->history
[LAST_MONTH
].production
;
1559 if (auto itb
= b
->GetCargoProduced(filter
); itb
!= std::end(b
->produced
)) prod_b
= itb
->history
[LAST_MONTH
].production
;
1561 int r
= prod_a
- prod_b
;
1563 return (r
== 0) ? IndustryTypeSorter(a
, b
, filter
) : r
< 0;
1566 /** Sort industries by transported cargo and name */
1567 static bool IndustryTransportedCargoSorter(const Industry
* const &a
, const Industry
* const &b
, const CargoID
&filter
)
1569 int r
= GetCargoTransportedSortValue(a
) - GetCargoTransportedSortValue(b
);
1570 return (r
== 0) ? IndustryNameSorter(a
, b
, filter
) : r
< 0;
1574 * Get the StringID to draw and set the appropriate DParams.
1575 * @param i the industry to get the StringID of.
1576 * @return the StringID.
1578 StringID
GetIndustryString(const Industry
*i
) const
1580 const IndustrySpec
*indsp
= GetIndustrySpec(i
->type
);
1584 SetDParam(p
++, i
->index
);
1586 /* Get industry productions (CargoID, production, suffix, transported) */
1588 CargoID cargo_id
; ///< Cargo ID.
1589 uint16_t production
; ///< Production last month.
1590 uint transported
; ///< Percent transported last month.
1591 std::string suffix
; ///< Cargo suffix.
1593 CargoInfo(CargoID cargo_id
, uint16_t production
, uint transported
, std::string
&&suffix
) : cargo_id(cargo_id
), production(production
), transported(transported
), suffix(std::move(suffix
)) {}
1595 std::vector
<CargoInfo
> cargos
;
1597 for (auto itp
= std::begin(i
->produced
); itp
!= std::end(i
->produced
); ++itp
) {
1598 if (!IsValidCargoID(itp
->cargo
)) continue;
1599 CargoSuffix cargo_suffix
;
1600 GetCargoSuffix(CARGOSUFFIX_OUT
, CST_DIR
, i
, i
->type
, indsp
, itp
->cargo
, itp
- std::begin(i
->produced
), cargo_suffix
);
1601 cargos
.emplace_back(itp
->cargo
, itp
->history
[LAST_MONTH
].production
, ToPercent8(itp
->history
[LAST_MONTH
].PctTransported()), std::move(cargo_suffix
.text
));
1604 switch (static_cast<IndustryDirectoryWindow::SorterType
>(this->industries
.SortType())) {
1605 case IndustryDirectoryWindow::SorterType::ByName
:
1606 case IndustryDirectoryWindow::SorterType::ByType
:
1607 case IndustryDirectoryWindow::SorterType::ByProduction
:
1608 /* Sort by descending production, then descending transported */
1609 std::sort(cargos
.begin(), cargos
.end(), [](const CargoInfo
&a
, const CargoInfo
&b
) {
1610 if (a
.production
!= b
.production
) return a
.production
> b
.production
;
1611 return a
.transported
> b
.transported
;
1615 case IndustryDirectoryWindow::SorterType::ByTransported
:
1616 /* Sort by descending transported, then descending production */
1617 std::sort(cargos
.begin(), cargos
.end(), [](const CargoInfo
&a
, const CargoInfo
&b
) {
1618 if (a
.transported
!= b
.transported
) return a
.transported
> b
.transported
;
1619 return a
.production
> b
.production
;
1624 /* If the produced cargo filter is active then move the filtered cargo to the beginning of the list,
1625 * because this is the one the player interested in, and that way it is not hidden in the 'n' more cargos */
1626 const CargoID cid
= this->produced_cargo_filter_criteria
;
1627 if (cid
!= CargoFilterCriteria::CF_ANY
&& cid
!= CargoFilterCriteria::CF_NONE
) {
1628 auto filtered_ci
= std::find_if(cargos
.begin(), cargos
.end(), [cid
](const CargoInfo
&ci
) -> bool {
1629 return ci
.cargo_id
== cid
;
1631 if (filtered_ci
!= cargos
.end()) {
1632 std::rotate(cargos
.begin(), filtered_ci
, filtered_ci
+ 1);
1636 /* Display first 3 cargos */
1637 for (size_t j
= 0; j
< std::min
<size_t>(3, cargos
.size()); j
++) {
1638 CargoInfo
&ci
= cargos
[j
];
1639 SetDParam(p
++, STR_INDUSTRY_DIRECTORY_ITEM_INFO
);
1640 SetDParam(p
++, ci
.cargo_id
);
1641 SetDParam(p
++, ci
.production
);
1642 SetDParamStr(p
++, std::move(ci
.suffix
));
1643 SetDParam(p
++, ci
.transported
);
1646 /* Undisplayed cargos if any */
1647 SetDParam(p
++, cargos
.size() - 3);
1649 /* Drawing the right string */
1650 switch (cargos
.size()) {
1651 case 0: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD
;
1652 case 1: return STR_INDUSTRY_DIRECTORY_ITEM_PROD1
;
1653 case 2: return STR_INDUSTRY_DIRECTORY_ITEM_PROD2
;
1654 case 3: return STR_INDUSTRY_DIRECTORY_ITEM_PROD3
;
1655 default: return STR_INDUSTRY_DIRECTORY_ITEM_PRODMORE
;
1660 IndustryDirectoryWindow(WindowDesc
&desc
, WindowNumber
) : Window(desc
), industry_editbox(MAX_FILTER_LENGTH
* MAX_CHAR_LENGTH
, MAX_FILTER_LENGTH
)
1662 this->CreateNestedTree();
1663 this->vscroll
= this->GetScrollbar(WID_ID_VSCROLLBAR
);
1664 this->hscroll
= this->GetScrollbar(WID_ID_HSCROLLBAR
);
1666 this->industries
.SetListing(this->last_sorting
);
1667 this->industries
.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs
);
1668 this->industries
.ForceRebuild();
1670 this->FinishInitNested(0);
1672 this->BuildSortIndustriesList();
1674 this->querystrings
[WID_ID_FILTER
] = &this->industry_editbox
;
1675 this->industry_editbox
.cancel_button
= QueryString::ACTION_CLEAR
;
1678 ~IndustryDirectoryWindow()
1680 this->last_sorting
= this->industries
.GetListing();
1683 void OnInit() override
1685 this->SetCargoFilterArray();
1688 void SetStringParameters(WidgetID widget
) const override
1691 case WID_ID_CAPTION
:
1692 SetDParam(0, this->vscroll
->GetCount());
1693 SetDParam(1, Industry::GetNumItems());
1696 case WID_ID_DROPDOWN_CRITERIA
:
1697 SetDParam(0, IndustryDirectoryWindow::sorter_names
[this->industries
.SortType()]);
1700 case WID_ID_FILTER_BY_ACC_CARGO
:
1701 SetDParam(0, this->GetCargoFilterLabel(this->accepted_cargo_filter_criteria
));
1704 case WID_ID_FILTER_BY_PROD_CARGO
:
1705 SetDParam(0, this->GetCargoFilterLabel(this->produced_cargo_filter_criteria
));
1710 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
1713 case WID_ID_DROPDOWN_ORDER
:
1714 this->DrawSortButtonState(widget
, this->industries
.IsDescSortOrder() ? SBS_DOWN
: SBS_UP
);
1717 case WID_ID_INDUSTRY_LIST
: {
1718 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.framerect
);
1720 /* Setup a clipping rectangle... */
1721 DrawPixelInfo tmp_dpi
;
1722 if (!FillDrawPixelInfo(&tmp_dpi
, ir
)) return;
1723 /* ...but keep coordinates relative to the window. */
1724 tmp_dpi
.left
+= ir
.left
;
1725 tmp_dpi
.top
+= ir
.top
;
1727 AutoRestoreBackup
dpi_backup(_cur_dpi
, &tmp_dpi
);
1729 ir
= ScrollRect(ir
, *this->hscroll
, 1);
1731 if (this->industries
.empty()) {
1732 DrawString(ir
, STR_INDUSTRY_DIRECTORY_NONE
);
1735 const CargoID acf_cid
= this->accepted_cargo_filter_criteria
;
1736 auto [first
, last
] = this->vscroll
->GetVisibleRangeIterators(this->industries
);
1737 for (auto it
= first
; it
!= last
; ++it
) {
1738 TextColour tc
= TC_FROMSTRING
;
1739 if (acf_cid
!= CargoFilterCriteria::CF_ANY
&& acf_cid
!= CargoFilterCriteria::CF_NONE
) {
1740 Industry
*ind
= const_cast<Industry
*>(*it
);
1741 if (IndustryTemporarilyRefusesCargo(ind
, acf_cid
)) {
1742 tc
= TC_GREY
| TC_FORCED
;
1745 DrawString(ir
, this->GetIndustryString(*it
), tc
);
1747 ir
.top
+= this->resize
.step_height
;
1754 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
1757 case WID_ID_DROPDOWN_ORDER
: {
1758 Dimension d
= GetStringBoundingBox(this->GetWidget
<NWidgetCore
>(widget
)->widget_data
);
1759 d
.width
+= padding
.width
+ Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1760 d
.height
+= padding
.height
;
1761 size
= maxdim(size
, d
);
1765 case WID_ID_DROPDOWN_CRITERIA
: {
1766 Dimension d
= GetStringListBoundingBox(IndustryDirectoryWindow::sorter_names
);
1767 d
.width
+= padding
.width
;
1768 d
.height
+= padding
.height
;
1769 size
= maxdim(size
, d
);
1773 case WID_ID_INDUSTRY_LIST
: {
1774 Dimension d
= GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE
);
1775 resize
.height
= d
.height
;
1777 d
.width
+= padding
.width
;
1778 d
.height
+= padding
.height
;
1779 size
= maxdim(size
, d
);
1785 DropDownList
BuildCargoDropDownList() const
1789 /* Add item for disabling filtering. */
1790 list
.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_ANY
), CargoFilterCriteria::CF_ANY
));
1791 /* Add item for industries not producing anything, e.g. power plants */
1792 list
.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_NONE
), CargoFilterCriteria::CF_NONE
));
1795 Dimension d
= GetLargestCargoIconSize();
1796 for (const CargoSpec
*cs
: _sorted_standard_cargo_specs
) {
1797 list
.push_back(MakeDropDownListIconItem(d
, cs
->GetCargoIcon(), PAL_NONE
, cs
->name
, cs
->Index()));
1803 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
1806 case WID_ID_DROPDOWN_ORDER
:
1807 this->industries
.ToggleSortOrder();
1811 case WID_ID_DROPDOWN_CRITERIA
:
1812 ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names
, this->industries
.SortType(), WID_ID_DROPDOWN_CRITERIA
, 0, 0);
1815 case WID_ID_FILTER_BY_ACC_CARGO
: // Cargo filter dropdown
1816 ShowDropDownList(this, this->BuildCargoDropDownList(), this->accepted_cargo_filter_criteria
, widget
);
1819 case WID_ID_FILTER_BY_PROD_CARGO
: // Cargo filter dropdown
1820 ShowDropDownList(this, this->BuildCargoDropDownList(), this->produced_cargo_filter_criteria
, widget
);
1823 case WID_ID_INDUSTRY_LIST
: {
1824 auto it
= this->vscroll
->GetScrolledItemFromWidget(this->industries
, pt
.y
, this, WID_ID_INDUSTRY_LIST
, WidgetDimensions::scaled
.framerect
.top
);
1825 if (it
!= this->industries
.end()) {
1826 if (_ctrl_pressed
) {
1827 ShowExtraViewportWindow((*it
)->location
.tile
);
1829 ScrollMainWindowToTile((*it
)->location
.tile
);
1837 void OnDropdownSelect(WidgetID widget
, int index
) override
1840 case WID_ID_DROPDOWN_CRITERIA
: {
1841 if (this->industries
.SortType() != index
) {
1842 this->industries
.SetSortType(index
);
1843 this->BuildSortIndustriesList();
1848 case WID_ID_FILTER_BY_ACC_CARGO
: {
1849 this->SetAcceptedCargoFilter(index
);
1850 this->BuildSortIndustriesList();
1854 case WID_ID_FILTER_BY_PROD_CARGO
: {
1855 this->SetProducedCargoFilter(index
);
1856 this->BuildSortIndustriesList();
1862 void OnResize() override
1864 this->vscroll
->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST
, WidgetDimensions::scaled
.framerect
.Vertical());
1865 this->hscroll
->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST
, WidgetDimensions::scaled
.framerect
.Horizontal());
1868 void OnEditboxChanged(WidgetID wid
) override
1870 if (wid
== WID_ID_FILTER
) {
1871 this->string_filter
.SetFilterTerm(this->industry_editbox
.text
.buf
);
1872 this->InvalidateData(IDIWD_FORCE_REBUILD
);
1876 void OnPaint() override
1878 if (this->industries
.NeedRebuild()) this->BuildSortIndustriesList();
1879 this->DrawWidgets();
1882 /** Rebuild the industry list on a regular interval. */
1883 IntervalTimer
<TimerWindow
> rebuild_interval
= {std::chrono::seconds(3), [this](auto) {
1884 this->industries
.ForceResort();
1885 this->BuildSortIndustriesList();
1889 * Some data on this window has become invalid.
1890 * @param data Information about the changed data.
1891 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
1893 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
1896 case IDIWD_FORCE_REBUILD
:
1897 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1898 this->industries
.ForceRebuild();
1901 case IDIWD_PRODUCTION_CHANGE
:
1902 if (this->industries
.SortType() == 2) this->industries
.ForceResort();
1906 this->industries
.ForceResort();
1911 EventState
OnHotkey(int hotkey
) override
1914 case IDHK_FOCUS_FILTER_BOX
:
1915 this->SetFocusedWidget(WID_ID_FILTER
);
1916 SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused.
1919 return ES_NOT_HANDLED
;
1924 static inline HotkeyList hotkeys
{"industrydirectory", {
1925 Hotkey('F', "focus_filter_box", IDHK_FOCUS_FILTER_BOX
),
1929 Listing
IndustryDirectoryWindow::last_sorting
= {false, 0};
1931 /* Available station sorting functions. */
1932 const std::initializer_list
<GUIIndustryList::SortFunction
* const> IndustryDirectoryWindow::sorter_funcs
= {
1933 &IndustryNameSorter
,
1934 &IndustryTypeSorter
,
1935 &IndustryProductionSorter
,
1936 &IndustryTransportedCargoSorter
1939 CargoID
IndustryDirectoryWindow::produced_cargo_filter
= CargoFilterCriteria::CF_ANY
;
1942 /** Window definition of the industry directory gui */
1943 static WindowDesc
_industry_directory_desc(
1944 WDP_AUTO
, "list_industries", 428, 190,
1945 WC_INDUSTRY_DIRECTORY
, WC_NONE
,
1947 _nested_industry_directory_widgets
,
1948 &IndustryDirectoryWindow::hotkeys
1951 void ShowIndustryDirectory()
1953 AllocateWindowDescFront
<IndustryDirectoryWindow
>(_industry_directory_desc
, 0);
1956 /** Widgets of the industry cargoes window. */
1957 static constexpr NWidgetPart _nested_industry_cargoes_widgets
[] = {
1958 NWidget(NWID_HORIZONTAL
),
1959 NWidget(WWT_CLOSEBOX
, COLOUR_BROWN
),
1960 NWidget(WWT_CAPTION
, COLOUR_BROWN
, WID_IC_CAPTION
), SetDataTip(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
1961 NWidget(WWT_SHADEBOX
, COLOUR_BROWN
),
1962 NWidget(WWT_DEFSIZEBOX
, COLOUR_BROWN
),
1963 NWidget(WWT_STICKYBOX
, COLOUR_BROWN
),
1965 NWidget(NWID_HORIZONTAL
),
1966 NWidget(WWT_PANEL
, COLOUR_BROWN
, WID_IC_PANEL
), SetResize(1, 10), SetScrollbar(WID_IC_SCROLLBAR
), EndContainer(),
1967 NWidget(NWID_VSCROLLBAR
, COLOUR_BROWN
, WID_IC_SCROLLBAR
),
1969 NWidget(NWID_HORIZONTAL
),
1970 NWidget(WWT_TEXTBTN
, COLOUR_BROWN
, WID_IC_NOTIFY
),
1971 SetDataTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP
, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP
),
1972 NWidget(WWT_PANEL
, COLOUR_BROWN
), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1973 NWidget(WWT_DROPDOWN
, COLOUR_BROWN
, WID_IC_IND_DROPDOWN
), SetFill(0, 0), SetResize(0, 0),
1974 SetDataTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY
, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP
),
1975 NWidget(WWT_DROPDOWN
, COLOUR_BROWN
, WID_IC_CARGO_DROPDOWN
), SetFill(0, 0), SetResize(0, 0),
1976 SetDataTip(STR_INDUSTRY_CARGOES_SELECT_CARGO
, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP
),
1977 NWidget(WWT_RESIZEBOX
, COLOUR_BROWN
),
1981 /** Window description for the industry cargoes window. */
1982 static WindowDesc
_industry_cargoes_desc(
1983 WDP_AUTO
, "industry_cargoes", 300, 210,
1984 WC_INDUSTRY_CARGOES
, WC_NONE
,
1986 _nested_industry_cargoes_widgets
1989 /** Available types of field. */
1990 enum CargoesFieldType
{
1991 CFT_EMPTY
, ///< Empty field.
1992 CFT_SMALL_EMPTY
, ///< Empty small field (for the header).
1993 CFT_INDUSTRY
, ///< Display industry.
1994 CFT_CARGO
, ///< Display cargo connections.
1995 CFT_CARGO_LABEL
, ///< Display cargo labels.
1996 CFT_HEADER
, ///< Header text.
1999 static const uint MAX_CARGOES
= 16; ///< Maximum number of cargoes carried in a #CFT_CARGO field in #CargoesField.
2001 /** Data about a single field in the #IndustryCargoesWindow panel. */
2002 struct CargoesField
{
2003 static int vert_inter_industry_space
;
2004 static int blob_distance
;
2006 static Dimension legend
;
2007 static Dimension cargo_border
;
2008 static Dimension cargo_line
;
2009 static Dimension cargo_space
;
2010 static Dimension cargo_stub
;
2012 static const int INDUSTRY_LINE_COLOUR
;
2013 static const int CARGO_LINE_COLOUR
;
2015 static int small_height
, normal_height
;
2016 static int cargo_field_width
;
2017 static int industry_width
;
2018 static uint max_cargoes
;
2020 using Cargoes
= uint16_t;
2021 static_assert(std::numeric_limits
<Cargoes
>::digits
>= MAX_CARGOES
);
2023 CargoesFieldType type
; ///< Type of field.
2026 IndustryType ind_type
; ///< Industry type (#NUM_INDUSTRYTYPES means 'houses').
2027 CargoID other_produced
[MAX_CARGOES
]; ///< Cargoes produced but not used in this figure.
2028 CargoID other_accepted
[MAX_CARGOES
]; ///< Cargoes accepted but not used in this figure.
2029 } industry
; ///< Industry data (for #CFT_INDUSTRY).
2031 CargoID vertical_cargoes
[MAX_CARGOES
]; ///< Cargoes running from top to bottom (cargo ID or #INVALID_CARGO).
2032 Cargoes supp_cargoes
; ///< Cargoes in \c vertical_cargoes entering from the left.
2033 Cargoes cust_cargoes
; ///< Cargoes in \c vertical_cargoes leaving to the right.
2034 uint8_t num_cargoes
; ///< Number of cargoes.
2035 uint8_t top_end
; ///< Stop at the top of the vertical cargoes.
2036 uint8_t bottom_end
; ///< Stop at the bottom of the vertical cargoes.
2037 } cargo
; ///< Cargo data (for #CFT_CARGO).
2039 CargoID cargoes
[MAX_CARGOES
]; ///< Cargoes to display (or #INVALID_CARGO).
2040 bool left_align
; ///< Align all cargo texts to the left (else align to the right).
2041 } cargo_label
; ///< Label data (for #CFT_CARGO_LABEL).
2042 StringID header
; ///< Header text (for #CFT_HEADER).
2043 } u
; // Data for each type.
2046 * Make one of the empty fields (#CFT_EMPTY or #CFT_SMALL_EMPTY).
2047 * @param type Type of empty field.
2049 void MakeEmpty(CargoesFieldType type
)
2055 * Make an industry type field.
2056 * @param ind_type Industry type (#NUM_INDUSTRYTYPES means 'houses').
2057 * @note #other_accepted and #other_produced should be filled later.
2059 void MakeIndustry(IndustryType ind_type
)
2061 this->type
= CFT_INDUSTRY
;
2062 this->u
.industry
.ind_type
= ind_type
;
2063 std::fill(std::begin(this->u
.industry
.other_accepted
), std::end(this->u
.industry
.other_accepted
), INVALID_CARGO
);
2064 std::fill(std::begin(this->u
.industry
.other_produced
), std::end(this->u
.industry
.other_produced
), INVALID_CARGO
);
2068 * Connect a cargo from an industry to the #CFT_CARGO column.
2069 * @param cargo Cargo to connect.
2070 * @param producer Cargo is produced (if \c false, cargo is assumed to be accepted).
2071 * @return Horizontal connection index, or \c -1 if not accepted at all.
2073 int ConnectCargo(CargoID cargo
, bool producer
)
2075 assert(this->type
== CFT_CARGO
);
2076 if (!IsValidCargoID(cargo
)) return -1;
2078 /* Find the vertical cargo column carrying the cargo. */
2080 for (int i
= 0; i
< this->u
.cargo
.num_cargoes
; i
++) {
2081 if (cargo
== this->u
.cargo
.vertical_cargoes
[i
]) {
2086 if (column
< 0) return -1;
2089 assert(!HasBit(this->u
.cargo
.supp_cargoes
, column
));
2090 SetBit(this->u
.cargo
.supp_cargoes
, column
);
2092 assert(!HasBit(this->u
.cargo
.cust_cargoes
, column
));
2093 SetBit(this->u
.cargo
.cust_cargoes
, column
);
2099 * Does this #CFT_CARGO field have a horizontal connection?
2100 * @return \c true if a horizontal connection exists, \c false otherwise.
2102 bool HasConnection()
2104 assert(this->type
== CFT_CARGO
);
2106 return this->u
.cargo
.supp_cargoes
!= 0 || this->u
.cargo
.cust_cargoes
!= 0;
2110 * Make a piece of cargo column.
2111 * @param cargoes Span of #CargoID (may contain #INVALID_CARGO).
2112 * @note #supp_cargoes and #cust_cargoes should be filled in later.
2114 void MakeCargo(const std::span
<const CargoID
> cargoes
)
2116 this->type
= CFT_CARGO
;
2117 assert(std::size(cargoes
) <= std::size(this->u
.cargo
.vertical_cargoes
));
2118 auto insert
= std::copy_if(std::begin(cargoes
), std::end(cargoes
), std::begin(this->u
.cargo
.vertical_cargoes
), IsValidCargoID
);
2119 this->u
.cargo
.num_cargoes
= static_cast<uint8_t>(std::distance(std::begin(this->u
.cargo
.vertical_cargoes
), insert
));
2120 CargoIDComparator comparator
;
2121 std::sort(std::begin(this->u
.cargo
.vertical_cargoes
), insert
, comparator
);
2122 std::fill(insert
, std::end(this->u
.cargo
.vertical_cargoes
), INVALID_CARGO
);
2123 this->u
.cargo
.top_end
= false;
2124 this->u
.cargo
.bottom_end
= false;
2125 this->u
.cargo
.supp_cargoes
= 0;
2126 this->u
.cargo
.cust_cargoes
= 0;
2130 * Make a field displaying cargo type names.
2131 * @param cargoes Span of #CargoID (may contain #INVALID_CARGO).
2132 * @param left_align ALign texts to the left (else to the right).
2134 void MakeCargoLabel(const std::span
<const CargoID
> cargoes
, bool left_align
)
2136 this->type
= CFT_CARGO_LABEL
;
2137 assert(std::size(cargoes
) <= std::size(this->u
.cargo_label
.cargoes
));
2138 auto insert
= std::copy(std::begin(cargoes
), std::end(cargoes
), std::begin(this->u
.cargo_label
.cargoes
));
2139 std::fill(insert
, std::end(this->u
.cargo_label
.cargoes
), INVALID_CARGO
);
2140 this->u
.cargo_label
.left_align
= left_align
;
2144 * Make a header above an industry column.
2145 * @param textid Text to display.
2147 void MakeHeader(StringID textid
)
2149 this->type
= CFT_HEADER
;
2150 this->u
.header
= textid
;
2154 * For a #CFT_CARGO, compute the left position of the left-most vertical cargo connection.
2155 * @param xpos Left position of the field.
2156 * @return Left position of the left-most vertical cargo column.
2158 int GetCargoBase(int xpos
) const
2160 assert(this->type
== CFT_CARGO
);
2161 int n
= this->u
.cargo
.num_cargoes
;
2163 return xpos
+ cargo_field_width
/ 2 - (CargoesField::cargo_line
.width
* n
+ CargoesField::cargo_space
.width
* (n
- 1)) / 2;
2168 * @param xpos Position of the left edge.
2169 * @param ypos Position of the top edge.
2171 void Draw(int xpos
, int ypos
) const
2173 switch (this->type
) {
2175 case CFT_SMALL_EMPTY
:
2179 ypos
+= (small_height
- GetCharacterHeight(FS_NORMAL
)) / 2;
2180 DrawString(xpos
, xpos
+ industry_width
, ypos
, this->u
.header
, TC_WHITE
, SA_HOR_CENTER
);
2183 case CFT_INDUSTRY
: {
2184 int ypos1
= ypos
+ vert_inter_industry_space
/ 2;
2185 int ypos2
= ypos
+ normal_height
- 1 - vert_inter_industry_space
/ 2;
2186 int xpos2
= xpos
+ industry_width
- 1;
2187 DrawRectOutline({xpos
, ypos1
, xpos2
, ypos2
}, INDUSTRY_LINE_COLOUR
);
2188 ypos
+= (normal_height
- GetCharacterHeight(FS_NORMAL
)) / 2;
2189 if (this->u
.industry
.ind_type
< NUM_INDUSTRYTYPES
) {
2190 const IndustrySpec
*indsp
= GetIndustrySpec(this->u
.industry
.ind_type
);
2191 DrawString(xpos
, xpos2
, ypos
, indsp
->name
, TC_WHITE
, SA_HOR_CENTER
);
2193 /* Draw the industry legend. */
2194 int blob_left
, blob_right
;
2195 if (_current_text_dir
== TD_RTL
) {
2196 blob_right
= xpos2
- blob_distance
;
2197 blob_left
= blob_right
- CargoesField::legend
.width
;
2199 blob_left
= xpos
+ blob_distance
;
2200 blob_right
= blob_left
+ CargoesField::legend
.width
;
2202 GfxFillRect(blob_left
, ypos2
- blob_distance
- CargoesField::legend
.height
, blob_right
, ypos2
- blob_distance
, PC_BLACK
); // Border
2203 GfxFillRect(blob_left
+ 1, ypos2
- blob_distance
- CargoesField::legend
.height
+ 1, blob_right
- 1, ypos2
- blob_distance
- 1, indsp
->map_colour
);
2205 DrawString(xpos
, xpos2
, ypos
, STR_INDUSTRY_CARGOES_HOUSES
, TC_FROMSTRING
, SA_HOR_CENTER
);
2208 /* Draw the other_produced/other_accepted cargoes. */
2209 std::span
<const CargoID
> other_right
, other_left
;
2210 if (_current_text_dir
== TD_RTL
) {
2211 other_right
= this->u
.industry
.other_accepted
;
2212 other_left
= this->u
.industry
.other_produced
;
2214 other_right
= this->u
.industry
.other_produced
;
2215 other_left
= this->u
.industry
.other_accepted
;
2217 ypos1
+= CargoesField::cargo_border
.height
+ (GetCharacterHeight(FS_NORMAL
) - CargoesField::cargo_line
.height
) / 2;
2218 for (uint i
= 0; i
< CargoesField::max_cargoes
; i
++) {
2219 if (IsValidCargoID(other_right
[i
])) {
2220 const CargoSpec
*csp
= CargoSpec::Get(other_right
[i
]);
2221 int xp
= xpos
+ industry_width
+ CargoesField::cargo_stub
.width
;
2222 DrawHorConnection(xpos
+ industry_width
, xp
- 1, ypos1
, csp
);
2223 GfxDrawLine(xp
, ypos1
, xp
, ypos1
+ CargoesField::cargo_line
.height
- 1, CARGO_LINE_COLOUR
);
2225 if (IsValidCargoID(other_left
[i
])) {
2226 const CargoSpec
*csp
= CargoSpec::Get(other_left
[i
]);
2227 int xp
= xpos
- CargoesField::cargo_stub
.width
;
2228 DrawHorConnection(xp
+ 1, xpos
- 1, ypos1
, csp
);
2229 GfxDrawLine(xp
, ypos1
, xp
, ypos1
+ CargoesField::cargo_line
.height
- 1, CARGO_LINE_COLOUR
);
2231 ypos1
+= GetCharacterHeight(FS_NORMAL
) + CargoesField::cargo_space
.height
;
2237 int cargo_base
= this->GetCargoBase(xpos
);
2238 int top
= ypos
+ (this->u
.cargo
.top_end
? vert_inter_industry_space
/ 2 + 1 : 0);
2239 int bot
= ypos
- (this->u
.cargo
.bottom_end
? vert_inter_industry_space
/ 2 + 1 : 0) + normal_height
- 1;
2240 int colpos
= cargo_base
;
2241 for (int i
= 0; i
< this->u
.cargo
.num_cargoes
; i
++) {
2242 if (this->u
.cargo
.top_end
) GfxDrawLine(colpos
, top
- 1, colpos
+ CargoesField::cargo_line
.width
- 1, top
- 1, CARGO_LINE_COLOUR
);
2243 if (this->u
.cargo
.bottom_end
) GfxDrawLine(colpos
, bot
+ 1, colpos
+ CargoesField::cargo_line
.width
- 1, bot
+ 1, CARGO_LINE_COLOUR
);
2244 GfxDrawLine(colpos
, top
, colpos
, bot
, CARGO_LINE_COLOUR
);
2246 const CargoSpec
*csp
= CargoSpec::Get(this->u
.cargo
.vertical_cargoes
[i
]);
2247 GfxFillRect(colpos
, top
, colpos
+ CargoesField::cargo_line
.width
- 2, bot
, csp
->legend_colour
, FILLRECT_OPAQUE
);
2248 colpos
+= CargoesField::cargo_line
.width
- 2;
2249 GfxDrawLine(colpos
, top
, colpos
, bot
, CARGO_LINE_COLOUR
);
2250 colpos
+= 1 + CargoesField::cargo_space
.width
;
2253 Cargoes hor_left
, hor_right
;
2254 if (_current_text_dir
== TD_RTL
) {
2255 hor_left
= this->u
.cargo
.cust_cargoes
;
2256 hor_right
= this->u
.cargo
.supp_cargoes
;
2258 hor_left
= this->u
.cargo
.supp_cargoes
;
2259 hor_right
= this->u
.cargo
.cust_cargoes
;
2261 ypos
+= CargoesField::cargo_border
.height
+ vert_inter_industry_space
/ 2 + (GetCharacterHeight(FS_NORMAL
) - CargoesField::cargo_line
.height
) / 2;
2262 for (uint i
= 0; i
< MAX_CARGOES
; i
++) {
2263 if (HasBit(hor_left
, i
)) {
2266 const CargoSpec
*csp
= CargoSpec::Get(this->u
.cargo
.vertical_cargoes
[col
]);
2267 for (; col
> 0; col
--) {
2268 int lf
= cargo_base
+ col
* CargoesField::cargo_line
.width
+ (col
- 1) * CargoesField::cargo_space
.width
;
2269 DrawHorConnection(lf
, lf
+ CargoesField::cargo_space
.width
- dx
, ypos
, csp
);
2272 DrawHorConnection(xpos
, cargo_base
- dx
, ypos
, csp
);
2274 if (HasBit(hor_right
, i
)) {
2277 const CargoSpec
*csp
= CargoSpec::Get(this->u
.cargo
.vertical_cargoes
[col
]);
2278 for (; col
< this->u
.cargo
.num_cargoes
- 1; col
++) {
2279 int lf
= cargo_base
+ (col
+ 1) * CargoesField::cargo_line
.width
+ col
* CargoesField::cargo_space
.width
;
2280 DrawHorConnection(lf
+ dx
- 1, lf
+ CargoesField::cargo_space
.width
- 1, ypos
, csp
);
2283 DrawHorConnection(cargo_base
+ col
* CargoesField::cargo_space
.width
+ (col
+ 1) * CargoesField::cargo_line
.width
- 1 + dx
, xpos
+ CargoesField::cargo_field_width
- 1, ypos
, csp
);
2285 ypos
+= GetCharacterHeight(FS_NORMAL
) + CargoesField::cargo_space
.height
;
2290 case CFT_CARGO_LABEL
:
2291 ypos
+= CargoesField::cargo_border
.height
+ vert_inter_industry_space
/ 2;
2292 for (uint i
= 0; i
< MAX_CARGOES
; i
++) {
2293 if (IsValidCargoID(this->u
.cargo_label
.cargoes
[i
])) {
2294 const CargoSpec
*csp
= CargoSpec::Get(this->u
.cargo_label
.cargoes
[i
]);
2295 DrawString(xpos
+ WidgetDimensions::scaled
.framerect
.left
, xpos
+ industry_width
- 1 - WidgetDimensions::scaled
.framerect
.right
, ypos
, csp
->name
, TC_WHITE
,
2296 (this->u
.cargo_label
.left_align
) ? SA_LEFT
: SA_RIGHT
);
2298 ypos
+= GetCharacterHeight(FS_NORMAL
) + CargoesField::cargo_space
.height
;
2308 * Decide which cargo was clicked at in a #CFT_CARGO field.
2309 * @param left Left industry neighbour if available (else \c nullptr should be supplied).
2310 * @param right Right industry neighbour if available (else \c nullptr should be supplied).
2311 * @param pt Click position in the cargo field.
2312 * @return Cargo clicked at, or #INVALID_CARGO if none.
2314 CargoID
CargoClickedAt(const CargoesField
*left
, const CargoesField
*right
, Point pt
) const
2316 assert(this->type
== CFT_CARGO
);
2318 /* Vertical matching. */
2319 int cpos
= this->GetCargoBase(0);
2321 for (col
= 0; col
< this->u
.cargo
.num_cargoes
; col
++) {
2322 if (pt
.x
< cpos
) break;
2323 if (pt
.x
< cpos
+ (int)CargoesField::cargo_line
.width
) return this->u
.cargo
.vertical_cargoes
[col
];
2324 cpos
+= CargoesField::cargo_line
.width
+ CargoesField::cargo_space
.width
;
2326 /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
2328 int vpos
= vert_inter_industry_space
/ 2 + CargoesField::cargo_border
.width
;
2330 for (row
= 0; row
< MAX_CARGOES
; row
++) {
2331 if (pt
.y
< vpos
) return INVALID_CARGO
;
2332 if (pt
.y
< vpos
+ GetCharacterHeight(FS_NORMAL
)) break;
2333 vpos
+= GetCharacterHeight(FS_NORMAL
) + CargoesField::cargo_space
.width
;
2335 if (row
== MAX_CARGOES
) return INVALID_CARGO
;
2337 /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
2339 if (HasBit(this->u
.cargo
.supp_cargoes
, row
)) return this->u
.cargo
.vertical_cargoes
[row
];
2340 if (left
!= nullptr) {
2341 if (left
->type
== CFT_INDUSTRY
) return left
->u
.industry
.other_produced
[row
];
2342 if (left
->type
== CFT_CARGO_LABEL
&& !left
->u
.cargo_label
.left_align
) return left
->u
.cargo_label
.cargoes
[row
];
2344 return INVALID_CARGO
;
2346 if (col
== this->u
.cargo
.num_cargoes
) {
2347 if (HasBit(this->u
.cargo
.cust_cargoes
, row
)) return this->u
.cargo
.vertical_cargoes
[row
];
2348 if (right
!= nullptr) {
2349 if (right
->type
== CFT_INDUSTRY
) return right
->u
.industry
.other_accepted
[row
];
2350 if (right
->type
== CFT_CARGO_LABEL
&& right
->u
.cargo_label
.left_align
) return right
->u
.cargo_label
.cargoes
[row
];
2352 return INVALID_CARGO
;
2355 /* Clicked somewhere in-between vertical cargo connection.
2356 * Since the horizontal connection is made in the same order as the vertical list, the above condition
2357 * ensures we are left-below the main diagonal, thus at the supplying side.
2359 if (HasBit(this->u
.cargo
.supp_cargoes
, row
)) return this->u
.cargo
.vertical_cargoes
[row
];
2360 return INVALID_CARGO
;
2362 /* Clicked at a customer connection. */
2363 if (HasBit(this->u
.cargo
.cust_cargoes
, row
)) return this->u
.cargo
.vertical_cargoes
[row
];
2364 return INVALID_CARGO
;
2368 * Decide what cargo the user clicked in the cargo label field.
2369 * @param pt Click position in the cargo label field.
2370 * @return Cargo clicked at, or #INVALID_CARGO if none.
2372 CargoID
CargoLabelClickedAt(Point pt
) const
2374 assert(this->type
== CFT_CARGO_LABEL
);
2376 int vpos
= vert_inter_industry_space
/ 2 + CargoesField::cargo_border
.height
;
2378 for (row
= 0; row
< MAX_CARGOES
; row
++) {
2379 if (pt
.y
< vpos
) return INVALID_CARGO
;
2380 if (pt
.y
< vpos
+ GetCharacterHeight(FS_NORMAL
)) break;
2381 vpos
+= GetCharacterHeight(FS_NORMAL
) + CargoesField::cargo_space
.height
;
2383 if (row
== MAX_CARGOES
) return INVALID_CARGO
;
2384 return this->u
.cargo_label
.cargoes
[row
];
2389 * Draw a horizontal cargo connection.
2390 * @param left Left-most coordinate to draw.
2391 * @param right Right-most coordinate to draw.
2392 * @param top Top coordinate of the cargo connection.
2393 * @param csp Cargo to draw.
2395 static void DrawHorConnection(int left
, int right
, int top
, const CargoSpec
*csp
)
2397 GfxDrawLine(left
, top
, right
, top
, CARGO_LINE_COLOUR
);
2398 GfxFillRect(left
, top
+ 1, right
, top
+ CargoesField::cargo_line
.height
- 2, csp
->legend_colour
, FILLRECT_OPAQUE
);
2399 GfxDrawLine(left
, top
+ CargoesField::cargo_line
.height
- 1, right
, top
+ CargoesField::cargo_line
.height
- 1, CARGO_LINE_COLOUR
);
2403 static_assert(MAX_CARGOES
>= std::tuple_size_v
<decltype(IndustrySpec::produced_cargo
)>);
2404 static_assert(MAX_CARGOES
>= std::tuple_size_v
<decltype(IndustrySpec::accepts_cargo
)>);
2406 Dimension
CargoesField::legend
; ///< Dimension of the legend blob.
2407 Dimension
CargoesField::cargo_border
; ///< Dimensions of border between cargo lines and industry boxes.
2408 Dimension
CargoesField::cargo_line
; ///< Dimensions of cargo lines.
2409 Dimension
CargoesField::cargo_space
; ///< Dimensions of space between cargo lines.
2410 Dimension
CargoesField::cargo_stub
; ///< Dimensions of cargo stub (unconnected cargo line.)
2412 int CargoesField::small_height
; ///< Height of the header row.
2413 int CargoesField::normal_height
; ///< Height of the non-header rows.
2414 int CargoesField::industry_width
; ///< Width of an industry field.
2415 int CargoesField::cargo_field_width
; ///< Width of a cargo field.
2416 uint
CargoesField::max_cargoes
; ///< Largest number of cargoes actually on any industry.
2417 int CargoesField::vert_inter_industry_space
; ///< Amount of space between two industries in a column.
2419 int CargoesField::blob_distance
; ///< Distance of the industry legend colour from the edge of the industry box.
2421 const int CargoesField::INDUSTRY_LINE_COLOUR
= PC_YELLOW
; ///< Line colour of the industry type box.
2422 const int CargoesField::CARGO_LINE_COLOUR
= PC_YELLOW
; ///< Line colour around the cargo.
2424 /** A single row of #CargoesField. */
2426 CargoesField columns
[5]; ///< One row of fields.
2429 * Connect industry production cargoes to the cargo column after it.
2430 * @param column Column of the industry.
2432 void ConnectIndustryProduced(int column
)
2434 CargoesField
*ind_fld
= this->columns
+ column
;
2435 CargoesField
*cargo_fld
= this->columns
+ column
+ 1;
2436 assert(ind_fld
->type
== CFT_INDUSTRY
&& cargo_fld
->type
== CFT_CARGO
);
2438 std::fill(std::begin(ind_fld
->u
.industry
.other_produced
), std::end(ind_fld
->u
.industry
.other_produced
), INVALID_CARGO
);
2440 if (ind_fld
->u
.industry
.ind_type
< NUM_INDUSTRYTYPES
) {
2441 CargoID others
[MAX_CARGOES
]; // Produced cargoes not carried in the cargo column.
2442 int other_count
= 0;
2444 const IndustrySpec
*indsp
= GetIndustrySpec(ind_fld
->u
.industry
.ind_type
);
2445 assert(CargoesField::max_cargoes
<= std::size(indsp
->produced_cargo
));
2446 for (uint i
= 0; i
< CargoesField::max_cargoes
; i
++) {
2447 int col
= cargo_fld
->ConnectCargo(indsp
->produced_cargo
[i
], true);
2448 if (col
< 0) others
[other_count
++] = indsp
->produced_cargo
[i
];
2451 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2452 for (uint i
= 0; i
< CargoesField::max_cargoes
&& other_count
> 0; i
++) {
2453 if (HasBit(cargo_fld
->u
.cargo
.supp_cargoes
, i
)) ind_fld
->u
.industry
.other_produced
[i
] = others
[--other_count
];
2456 /* Houses only display cargo that towns produce. */
2457 for (uint i
= 0; i
< cargo_fld
->u
.cargo
.num_cargoes
; i
++) {
2458 CargoID cid
= cargo_fld
->u
.cargo
.vertical_cargoes
[i
];
2459 TownProductionEffect tpe
= CargoSpec::Get(cid
)->town_production_effect
;
2460 if (tpe
== TPE_PASSENGERS
|| tpe
== TPE_MAIL
) cargo_fld
->ConnectCargo(cid
, true);
2466 * Construct a #CFT_CARGO_LABEL field.
2467 * @param column Column to create the new field.
2468 * @param accepting Display accepted cargo (if \c false, display produced cargo).
2470 void MakeCargoLabel(int column
, bool accepting
)
2472 CargoID cargoes
[MAX_CARGOES
];
2473 std::fill(std::begin(cargoes
), std::end(cargoes
), INVALID_CARGO
);
2475 CargoesField
*label_fld
= this->columns
+ column
;
2476 CargoesField
*cargo_fld
= this->columns
+ (accepting
? column
- 1 : column
+ 1);
2478 assert(cargo_fld
->type
== CFT_CARGO
&& label_fld
->type
== CFT_EMPTY
);
2479 for (uint i
= 0; i
< cargo_fld
->u
.cargo
.num_cargoes
; i
++) {
2480 int col
= cargo_fld
->ConnectCargo(cargo_fld
->u
.cargo
.vertical_cargoes
[i
], !accepting
);
2481 if (col
>= 0) cargoes
[col
] = cargo_fld
->u
.cargo
.vertical_cargoes
[i
];
2483 label_fld
->MakeCargoLabel(cargoes
, accepting
);
2488 * Connect industry accepted cargoes to the cargo column before it.
2489 * @param column Column of the industry.
2491 void ConnectIndustryAccepted(int column
)
2493 CargoesField
*ind_fld
= this->columns
+ column
;
2494 CargoesField
*cargo_fld
= this->columns
+ column
- 1;
2495 assert(ind_fld
->type
== CFT_INDUSTRY
&& cargo_fld
->type
== CFT_CARGO
);
2497 std::fill(std::begin(ind_fld
->u
.industry
.other_accepted
), std::end(ind_fld
->u
.industry
.other_accepted
), INVALID_CARGO
);
2499 if (ind_fld
->u
.industry
.ind_type
< NUM_INDUSTRYTYPES
) {
2500 CargoID others
[MAX_CARGOES
]; // Accepted cargoes not carried in the cargo column.
2501 int other_count
= 0;
2503 const IndustrySpec
*indsp
= GetIndustrySpec(ind_fld
->u
.industry
.ind_type
);
2504 assert(CargoesField::max_cargoes
<= std::size(indsp
->accepts_cargo
));
2505 for (uint i
= 0; i
< CargoesField::max_cargoes
; i
++) {
2506 int col
= cargo_fld
->ConnectCargo(indsp
->accepts_cargo
[i
], false);
2507 if (col
< 0) others
[other_count
++] = indsp
->accepts_cargo
[i
];
2510 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2511 for (uint i
= 0; i
< CargoesField::max_cargoes
&& other_count
> 0; i
++) {
2512 if (!HasBit(cargo_fld
->u
.cargo
.cust_cargoes
, i
)) ind_fld
->u
.industry
.other_accepted
[i
] = others
[--other_count
];
2515 /* Houses only display what is demanded. */
2516 for (uint i
= 0; i
< cargo_fld
->u
.cargo
.num_cargoes
; i
++) {
2517 for (const auto &hs
: HouseSpec::Specs()) {
2518 if (!hs
.enabled
) continue;
2520 for (uint j
= 0; j
< lengthof(hs
.accepts_cargo
); j
++) {
2521 if (hs
.cargo_acceptance
[j
] > 0 && cargo_fld
->u
.cargo
.vertical_cargoes
[i
] == hs
.accepts_cargo
[j
]) {
2522 cargo_fld
->ConnectCargo(cargo_fld
->u
.cargo
.vertical_cargoes
[i
], false);
2535 * Window displaying the cargo connections around an industry (or cargo).
2537 * The main display is constructed from 'fields', rectangles that contain an industry, piece of the cargo connection, cargo labels, or headers.
2538 * For a nice display, the following should be kept in mind:
2539 * - A #CFT_HEADER is always at the top of an column of #CFT_INDUSTRY fields.
2540 * - A #CFT_CARGO_LABEL field is also always put in a column of #CFT_INDUSTRY fields.
2541 * - The top row contains #CFT_HEADER and #CFT_SMALL_EMPTY fields.
2542 * - Cargo connections have a column of their own (#CFT_CARGO fields).
2543 * - Cargo accepted or produced by an industry, but not carried in a cargo connection, is drawn in the space of a cargo column attached to the industry.
2544 * The information however is part of the industry.
2546 * This results in the following invariants:
2547 * - Width of a #CFT_INDUSTRY column is large enough to hold all industry type labels, all cargo labels, and all header texts.
2548 * - Height of a #CFT_INDUSTRY is large enough to hold a header line, or a industry type line, \c N cargo labels
2549 * (where \c N is the maximum number of cargoes connected between industries), \c N connections of cargo types, and space
2550 * between two industry types (1/2 above it, and 1/2 underneath it).
2551 * - Width of a cargo field (#CFT_CARGO) is large enough to hold \c N vertical columns (one for each type of cargo).
2552 * Also, space is needed between an industry and the leftmost/rightmost column to draw the non-carried cargoes.
2553 * - Height of a #CFT_CARGO field is equally high as the height of the #CFT_INDUSTRY.
2554 * - A field at the top (#CFT_HEADER or #CFT_SMALL_EMPTY) match the width of the fields below them (#CFT_INDUSTRY respectively
2555 * #CFT_CARGO), the height should be sufficient to display the header text.
2557 * When displaying the cargoes around an industry type, five columns are needed (supplying industries, accepted cargoes, the industry,
2558 * produced cargoes, customer industries). Displaying the industries around a cargo needs three columns (supplying industries, the cargo,
2559 * customer industries). The remaining two columns are set to #CFT_EMPTY with a width equal to the average of a cargo and an industry column.
2561 struct IndustryCargoesWindow
: public Window
{
2562 typedef std::vector
<CargoesRow
> Fields
;
2564 Fields fields
; ///< Fields to display in the #WID_IC_PANEL.
2565 uint ind_cargo
; ///< If less than #NUM_INDUSTRYTYPES, an industry type, else a cargo id + NUM_INDUSTRYTYPES.
2566 Dimension cargo_textsize
; ///< Size to hold any cargo text, as well as STR_INDUSTRY_CARGOES_SELECT_CARGO.
2567 Dimension ind_textsize
; ///< Size to hold any industry type text, as well as STR_INDUSTRY_CARGOES_SELECT_INDUSTRY.
2570 IndustryCargoesWindow(int id
) : Window(_industry_cargoes_desc
)
2573 this->CreateNestedTree();
2574 this->vscroll
= this->GetScrollbar(WID_IC_SCROLLBAR
);
2575 this->FinishInitNested(0);
2576 this->OnInvalidateData(id
);
2579 void OnInit() override
2581 /* Initialize static CargoesField size variables. */
2582 Dimension d
= GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS
);
2583 d
= maxdim(d
, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS
));
2584 d
.width
+= WidgetDimensions::scaled
.frametext
.Horizontal();
2585 d
.height
+= WidgetDimensions::scaled
.frametext
.Vertical();
2586 CargoesField::small_height
= d
.height
;
2588 /* Size of the legend blob -- slightly larger than the smallmap legend blob. */
2589 CargoesField::legend
.height
= GetCharacterHeight(FS_SMALL
);
2590 CargoesField::legend
.width
= CargoesField::legend
.height
* 9 / 6;
2592 /* Size of cargo lines. */
2593 CargoesField::cargo_line
.width
= ScaleGUITrad(6);
2594 CargoesField::cargo_line
.height
= CargoesField::cargo_line
.width
;
2596 /* Size of border between cargo lines and industry boxes. */
2597 CargoesField::cargo_border
.width
= CargoesField::cargo_line
.width
* 3 / 2;
2598 CargoesField::cargo_border
.height
= CargoesField::cargo_line
.width
/ 2;
2600 /* Size of space between cargo lines. */
2601 CargoesField::cargo_space
.width
= CargoesField::cargo_line
.width
/ 2;
2602 CargoesField::cargo_space
.height
= CargoesField::cargo_line
.height
/ 2;
2604 /* Size of cargo stub (unconnected cargo line.) */
2605 CargoesField::cargo_stub
.width
= CargoesField::cargo_line
.width
/ 2;
2606 CargoesField::cargo_stub
.height
= CargoesField::cargo_line
.height
; /* Unused */
2608 CargoesField::vert_inter_industry_space
= WidgetDimensions::scaled
.vsep_wide
;
2609 CargoesField::blob_distance
= WidgetDimensions::scaled
.hsep_normal
;
2611 /* Decide about the size of the box holding the text of an industry type. */
2612 this->ind_textsize
.width
= 0;
2613 this->ind_textsize
.height
= 0;
2614 CargoesField::max_cargoes
= 0;
2615 for (IndustryType it
= 0; it
< NUM_INDUSTRYTYPES
; it
++) {
2616 const IndustrySpec
*indsp
= GetIndustrySpec(it
);
2617 if (!indsp
->enabled
) continue;
2618 this->ind_textsize
= maxdim(this->ind_textsize
, GetStringBoundingBox(indsp
->name
));
2619 CargoesField::max_cargoes
= std::max
<uint
>(CargoesField::max_cargoes
, std::count_if(std::begin(indsp
->accepts_cargo
), std::end(indsp
->accepts_cargo
), IsValidCargoID
));
2620 CargoesField::max_cargoes
= std::max
<uint
>(CargoesField::max_cargoes
, std::count_if(std::begin(indsp
->produced_cargo
), std::end(indsp
->produced_cargo
), IsValidCargoID
));
2622 d
.width
= std::max(d
.width
, this->ind_textsize
.width
);
2623 d
.height
= this->ind_textsize
.height
;
2624 this->ind_textsize
= maxdim(this->ind_textsize
, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY
));
2626 /* Compute max size of the cargo texts. */
2627 this->cargo_textsize
.width
= 0;
2628 this->cargo_textsize
.height
= 0;
2629 for (const CargoSpec
*csp
: CargoSpec::Iterate()) {
2630 if (!csp
->IsValid()) continue;
2631 this->cargo_textsize
= maxdim(this->cargo_textsize
, GetStringBoundingBox(csp
->name
));
2633 d
= maxdim(d
, this->cargo_textsize
); // Box must also be wide enough to hold any cargo label.
2634 this->cargo_textsize
= maxdim(this->cargo_textsize
, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO
));
2636 d
.width
+= WidgetDimensions::scaled
.frametext
.Horizontal();
2637 /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2638 uint min_ind_height
= CargoesField::cargo_border
.height
* 2 + CargoesField::max_cargoes
* GetCharacterHeight(FS_NORMAL
) + (CargoesField::max_cargoes
- 1) * CargoesField::cargo_space
.height
;
2639 d
.height
= std::max(d
.height
+ WidgetDimensions::scaled
.frametext
.Vertical(), min_ind_height
);
2641 CargoesField::industry_width
= d
.width
;
2642 CargoesField::normal_height
= d
.height
+ CargoesField::vert_inter_industry_space
;
2644 /* Width of a #CFT_CARGO field. */
2645 CargoesField::cargo_field_width
= CargoesField::cargo_border
.width
* 2 + CargoesField::cargo_line
.width
* CargoesField::max_cargoes
+ CargoesField::cargo_space
.width
* (CargoesField::max_cargoes
- 1);
2648 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
2652 resize
.height
= CargoesField::normal_height
;
2653 size
.width
= CargoesField::industry_width
* 3 + CargoesField::cargo_field_width
* 2 + WidgetDimensions::scaled
.frametext
.Horizontal();
2654 size
.height
= CargoesField::small_height
+ 2 * resize
.height
+ WidgetDimensions::scaled
.frametext
.Vertical();
2657 case WID_IC_IND_DROPDOWN
:
2658 size
.width
= std::max(size
.width
, this->ind_textsize
.width
+ padding
.width
);
2661 case WID_IC_CARGO_DROPDOWN
:
2662 size
.width
= std::max(size
.width
, this->cargo_textsize
.width
+ padding
.width
);
2667 void SetStringParameters(WidgetID widget
) const override
2669 if (widget
!= WID_IC_CAPTION
) return;
2671 if (this->ind_cargo
< NUM_INDUSTRYTYPES
) {
2672 const IndustrySpec
*indsp
= GetIndustrySpec(this->ind_cargo
);
2673 SetDParam(0, indsp
->name
);
2675 const CargoSpec
*csp
= CargoSpec::Get(this->ind_cargo
- NUM_INDUSTRYTYPES
);
2676 SetDParam(0, csp
->name
);
2681 * Do the two sets of cargoes have a valid cargo in common?
2682 * @param cargoes1 Span of the first cargo list.
2683 * @param cargoes2 Span of the second cargo list.
2684 * @return Arrays have at least one valid cargo in common.
2686 static bool HasCommonValidCargo(const std::span
<const CargoID
> cargoes1
, const std::span
<const CargoID
> cargoes2
)
2688 for (const CargoID cid1
: cargoes1
) {
2689 if (!IsValidCargoID(cid1
)) continue;
2690 for (const CargoID cid2
: cargoes2
) {
2691 if (cid1
== cid2
) return true;
2698 * Can houses be used to supply one of the cargoes?
2699 * @param cargoes Span of cargo list.
2700 * @return Houses can supply at least one of the cargoes.
2702 static bool HousesCanSupply(const std::span
<const CargoID
> cargoes
)
2704 for (const CargoID cid
: cargoes
) {
2705 if (!IsValidCargoID(cid
)) continue;
2706 TownProductionEffect tpe
= CargoSpec::Get(cid
)->town_production_effect
;
2707 if (tpe
== TPE_PASSENGERS
|| tpe
== TPE_MAIL
) return true;
2713 * Can houses be used as customers of the produced cargoes?
2714 * @param cargoes Span of cargo list.
2715 * @return Houses can accept at least one of the cargoes.
2717 static bool HousesCanAccept(const std::span
<const CargoID
> cargoes
)
2719 HouseZones climate_mask
;
2720 switch (_settings_game
.game_creation
.landscape
) {
2721 case LT_TEMPERATE
: climate_mask
= HZ_TEMP
; break;
2722 case LT_ARCTIC
: climate_mask
= HZ_SUBARTC_ABOVE
| HZ_SUBARTC_BELOW
; break;
2723 case LT_TROPIC
: climate_mask
= HZ_SUBTROPIC
; break;
2724 case LT_TOYLAND
: climate_mask
= HZ_TOYLND
; break;
2725 default: NOT_REACHED();
2727 for (const CargoID cid
: cargoes
) {
2728 if (!IsValidCargoID(cid
)) continue;
2730 for (const auto &hs
: HouseSpec::Specs()) {
2731 if (!hs
.enabled
|| !(hs
.building_availability
& climate_mask
)) continue;
2733 for (uint j
= 0; j
< lengthof(hs
.accepts_cargo
); j
++) {
2734 if (hs
.cargo_acceptance
[j
] > 0 && cid
== hs
.accepts_cargo
[j
]) return true;
2742 * Count how many industries have accepted cargoes in common with one of the supplied set.
2743 * @param cargoes Cargoes to search.
2744 * @return Number of industries that have an accepted cargo in common with the supplied set.
2746 static int CountMatchingAcceptingIndustries(const std::span
<const CargoID
> cargoes
)
2749 for (IndustryType it
= 0; it
< NUM_INDUSTRYTYPES
; it
++) {
2750 const IndustrySpec
*indsp
= GetIndustrySpec(it
);
2751 if (!indsp
->enabled
) continue;
2753 if (HasCommonValidCargo(cargoes
, indsp
->accepts_cargo
)) count
++;
2759 * Count how many industries have produced cargoes in common with one of the supplied set.
2760 * @param cargoes Cargoes to search.
2761 * @return Number of industries that have a produced cargo in common with the supplied set.
2763 static int CountMatchingProducingIndustries(const std::span
<const CargoID
> cargoes
)
2766 for (IndustryType it
= 0; it
< NUM_INDUSTRYTYPES
; it
++) {
2767 const IndustrySpec
*indsp
= GetIndustrySpec(it
);
2768 if (!indsp
->enabled
) continue;
2770 if (HasCommonValidCargo(cargoes
, indsp
->produced_cargo
)) count
++;
2776 * Shorten the cargo column to just the part between industries.
2777 * @param column Column number of the cargo column.
2778 * @param top Current top row.
2779 * @param bottom Current bottom row.
2781 void ShortenCargoColumn(int column
, int top
, int bottom
)
2783 while (top
< bottom
&& !this->fields
[top
].columns
[column
].HasConnection()) {
2784 this->fields
[top
].columns
[column
].MakeEmpty(CFT_EMPTY
);
2787 this->fields
[top
].columns
[column
].u
.cargo
.top_end
= true;
2789 while (bottom
> top
&& !this->fields
[bottom
].columns
[column
].HasConnection()) {
2790 this->fields
[bottom
].columns
[column
].MakeEmpty(CFT_EMPTY
);
2793 this->fields
[bottom
].columns
[column
].u
.cargo
.bottom_end
= true;
2797 * Place an industry in the fields.
2798 * @param row Row of the new industry.
2799 * @param col Column of the new industry.
2800 * @param it Industry to place.
2802 void PlaceIndustry(int row
, int col
, IndustryType it
)
2804 assert(this->fields
[row
].columns
[col
].type
== CFT_EMPTY
);
2805 this->fields
[row
].columns
[col
].MakeIndustry(it
);
2807 this->fields
[row
].ConnectIndustryProduced(col
);
2809 this->fields
[row
].ConnectIndustryAccepted(col
);
2814 * Notify smallmap that new displayed industries have been selected (in #_displayed_industries).
2816 void NotifySmallmap()
2818 if (!this->IsWidgetLowered(WID_IC_NOTIFY
)) return;
2820 /* Only notify the smallmap window if it exists. In particular, do not
2821 * bring it to the front to prevent messing up any nice layout of the user. */
2822 InvalidateWindowClassesData(WC_SMALLMAP
, 0);
2826 * Compute what and where to display for industry type \a it.
2827 * @param displayed_it Industry type to display.
2829 void ComputeIndustryDisplay(IndustryType displayed_it
)
2831 this->GetWidget
<NWidgetCore
>(WID_IC_CAPTION
)->widget_data
= STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION
;
2832 this->ind_cargo
= displayed_it
;
2833 _displayed_industries
.reset();
2834 _displayed_industries
.set(displayed_it
);
2836 this->fields
.clear();
2837 CargoesRow
&first_row
= this->fields
.emplace_back();
2838 first_row
.columns
[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS
);
2839 first_row
.columns
[1].MakeEmpty(CFT_SMALL_EMPTY
);
2840 first_row
.columns
[2].MakeEmpty(CFT_SMALL_EMPTY
);
2841 first_row
.columns
[3].MakeEmpty(CFT_SMALL_EMPTY
);
2842 first_row
.columns
[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS
);
2844 const IndustrySpec
*central_sp
= GetIndustrySpec(displayed_it
);
2845 bool houses_supply
= HousesCanSupply(central_sp
->accepts_cargo
);
2846 bool houses_accept
= HousesCanAccept(central_sp
->produced_cargo
);
2847 /* Make a field consisting of two cargo columns. */
2848 int num_supp
= CountMatchingProducingIndustries(central_sp
->accepts_cargo
) + houses_supply
;
2849 int num_cust
= CountMatchingAcceptingIndustries(central_sp
->produced_cargo
) + houses_accept
;
2850 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.
2851 for (int i
= 0; i
< num_indrows
; i
++) {
2852 CargoesRow
&row
= this->fields
.emplace_back();
2853 row
.columns
[0].MakeEmpty(CFT_EMPTY
);
2854 row
.columns
[1].MakeCargo(central_sp
->accepts_cargo
);
2855 row
.columns
[2].MakeEmpty(CFT_EMPTY
);
2856 row
.columns
[3].MakeCargo(central_sp
->produced_cargo
);
2857 row
.columns
[4].MakeEmpty(CFT_EMPTY
);
2859 /* Add central industry. */
2860 int central_row
= 1 + num_indrows
/ 2;
2861 this->fields
[central_row
].columns
[2].MakeIndustry(displayed_it
);
2862 this->fields
[central_row
].ConnectIndustryProduced(2);
2863 this->fields
[central_row
].ConnectIndustryAccepted(2);
2865 /* Add cargo labels. */
2866 this->fields
[central_row
- 1].MakeCargoLabel(2, true);
2867 this->fields
[central_row
+ 1].MakeCargoLabel(2, false);
2869 /* Add suppliers and customers of the 'it' industry. */
2872 for (IndustryType it
: _sorted_industry_types
) {
2873 const IndustrySpec
*indsp
= GetIndustrySpec(it
);
2874 if (!indsp
->enabled
) continue;
2876 if (HasCommonValidCargo(central_sp
->accepts_cargo
, indsp
->produced_cargo
)) {
2877 this->PlaceIndustry(1 + supp_count
* num_indrows
/ num_supp
, 0, it
);
2878 _displayed_industries
.set(it
);
2881 if (HasCommonValidCargo(central_sp
->produced_cargo
, indsp
->accepts_cargo
)) {
2882 this->PlaceIndustry(1 + cust_count
* num_indrows
/ num_cust
, 4, it
);
2883 _displayed_industries
.set(it
);
2887 if (houses_supply
) {
2888 this->PlaceIndustry(1 + supp_count
* num_indrows
/ num_supp
, 0, NUM_INDUSTRYTYPES
);
2891 if (houses_accept
) {
2892 this->PlaceIndustry(1 + cust_count
* num_indrows
/ num_cust
, 4, NUM_INDUSTRYTYPES
);
2896 this->ShortenCargoColumn(1, 1, num_indrows
);
2897 this->ShortenCargoColumn(3, 1, num_indrows
);
2898 this->vscroll
->SetCount(num_indrows
);
2900 this->NotifySmallmap();
2904 * Compute what and where to display for cargo id \a cid.
2905 * @param cid Cargo id to display.
2907 void ComputeCargoDisplay(CargoID cid
)
2909 this->GetWidget
<NWidgetCore
>(WID_IC_CAPTION
)->widget_data
= STR_INDUSTRY_CARGOES_CARGO_CAPTION
;
2910 this->ind_cargo
= cid
+ NUM_INDUSTRYTYPES
;
2911 _displayed_industries
.reset();
2913 this->fields
.clear();
2914 CargoesRow
&first_row
= this->fields
.emplace_back();
2915 first_row
.columns
[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS
);
2916 first_row
.columns
[1].MakeEmpty(CFT_SMALL_EMPTY
);
2917 first_row
.columns
[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS
);
2918 first_row
.columns
[3].MakeEmpty(CFT_SMALL_EMPTY
);
2919 first_row
.columns
[4].MakeEmpty(CFT_SMALL_EMPTY
);
2921 auto cargoes
= std::span(&cid
, 1);
2922 bool houses_supply
= HousesCanSupply(cargoes
);
2923 bool houses_accept
= HousesCanAccept(cargoes
);
2924 int num_supp
= CountMatchingProducingIndustries(cargoes
) + houses_supply
+ 1; // Ensure room for the cargo label.
2925 int num_cust
= CountMatchingAcceptingIndustries(cargoes
) + houses_accept
;
2926 int num_indrows
= std::max(num_supp
, num_cust
);
2927 for (int i
= 0; i
< num_indrows
; i
++) {
2928 CargoesRow
&row
= this->fields
.emplace_back();
2929 row
.columns
[0].MakeEmpty(CFT_EMPTY
);
2930 row
.columns
[1].MakeCargo(cargoes
);
2931 row
.columns
[2].MakeEmpty(CFT_EMPTY
);
2932 row
.columns
[3].MakeEmpty(CFT_EMPTY
);
2933 row
.columns
[4].MakeEmpty(CFT_EMPTY
);
2936 this->fields
[num_indrows
].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2938 /* Add suppliers and customers of the cargo. */
2941 for (IndustryType it
: _sorted_industry_types
) {
2942 const IndustrySpec
*indsp
= GetIndustrySpec(it
);
2943 if (!indsp
->enabled
) continue;
2945 if (HasCommonValidCargo(cargoes
, indsp
->produced_cargo
)) {
2946 this->PlaceIndustry(1 + supp_count
* num_indrows
/ num_supp
, 0, it
);
2947 _displayed_industries
.set(it
);
2950 if (HasCommonValidCargo(cargoes
, indsp
->accepts_cargo
)) {
2951 this->PlaceIndustry(1 + cust_count
* num_indrows
/ num_cust
, 2, it
);
2952 _displayed_industries
.set(it
);
2956 if (houses_supply
) {
2957 this->PlaceIndustry(1 + supp_count
* num_indrows
/ num_supp
, 0, NUM_INDUSTRYTYPES
);
2960 if (houses_accept
) {
2961 this->PlaceIndustry(1 + cust_count
* num_indrows
/ num_cust
, 2, NUM_INDUSTRYTYPES
);
2965 this->ShortenCargoColumn(1, 1, num_indrows
);
2966 this->vscroll
->SetCount(num_indrows
);
2968 this->NotifySmallmap();
2972 * Some data on this window has become invalid.
2973 * @param data Information about the changed data.
2974 * - data = 0 .. NUM_INDUSTRYTYPES - 1: Display the chain around the given industry.
2975 * - data = NUM_INDUSTRYTYPES: Stop sending updates to the smallmap window.
2976 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
2978 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
2980 if (!gui_scope
) return;
2981 if (data
== NUM_INDUSTRYTYPES
) {
2982 this->RaiseWidgetWhenLowered(WID_IC_NOTIFY
);
2986 assert(data
>= 0 && data
< NUM_INDUSTRYTYPES
);
2987 this->ComputeIndustryDisplay(data
);
2990 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
2992 if (widget
!= WID_IC_PANEL
) return;
2994 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.bevel
);
2995 DrawPixelInfo tmp_dpi
;
2996 if (!FillDrawPixelInfo(&tmp_dpi
, ir
)) return;
2997 AutoRestoreBackup
dpi_backup(_cur_dpi
, &tmp_dpi
);
2999 int left_pos
= WidgetDimensions::scaled
.frametext
.left
- WidgetDimensions::scaled
.bevel
.left
;
3000 if (this->ind_cargo
>= NUM_INDUSTRYTYPES
) left_pos
+= (CargoesField::industry_width
+ CargoesField::cargo_field_width
) / 2;
3001 int last_column
= (this->ind_cargo
< NUM_INDUSTRYTYPES
) ? 4 : 2;
3003 const NWidgetBase
*nwp
= this->GetWidget
<NWidgetBase
>(WID_IC_PANEL
);
3004 int vpos
= WidgetDimensions::scaled
.frametext
.top
- WidgetDimensions::scaled
.bevel
.top
- this->vscroll
->GetPosition() * nwp
->resize_y
;
3005 int row_height
= CargoesField::small_height
;
3006 for (const auto &field
: this->fields
) {
3007 if (vpos
+ row_height
>= 0) {
3008 int xpos
= left_pos
;
3010 if (_current_text_dir
== TD_RTL
) {
3017 while (col
>= 0 && col
<= last_column
) {
3018 field
.columns
[col
].Draw(xpos
, vpos
);
3019 xpos
+= (col
& 1) ? CargoesField::cargo_field_width
: CargoesField::industry_width
;
3024 if (vpos
>= height
) break;
3025 row_height
= CargoesField::normal_height
;
3030 * Calculate in which field was clicked, and within the field, at what position.
3031 * @param pt Clicked position in the #WID_IC_PANEL widget.
3032 * @param fieldxy If \c true is returned, field x/y coordinate of \a pt.
3033 * @param xy If \c true is returned, x/y coordinate with in the field.
3034 * @return Clicked at a valid position.
3036 bool CalculatePositionInWidget(Point pt
, Point
*fieldxy
, Point
*xy
)
3038 const NWidgetBase
*nw
= this->GetWidget
<NWidgetBase
>(WID_IC_PANEL
);
3042 int vpos
= WidgetDimensions::scaled
.frametext
.top
+ CargoesField::small_height
- this->vscroll
->GetPosition() * nw
->resize_y
;
3043 if (pt
.y
< vpos
) return false;
3045 int row
= (pt
.y
- vpos
) / CargoesField::normal_height
; // row is relative to row 1.
3046 if (row
+ 1 >= (int)this->fields
.size()) return false;
3047 vpos
= pt
.y
- vpos
- row
* CargoesField::normal_height
; // Position in the row + 1 field
3048 row
++; // rebase row to match index of this->fields.
3050 int xpos
= 2 * WidgetDimensions::scaled
.frametext
.left
+ ((this->ind_cargo
< NUM_INDUSTRYTYPES
) ? 0 : (CargoesField::industry_width
+ CargoesField::cargo_field_width
) / 2);
3051 if (pt
.x
< xpos
) return false;
3053 for (column
= 0; column
<= 5; column
++) {
3054 int width
= (column
& 1) ? CargoesField::cargo_field_width
: CargoesField::industry_width
;
3055 if (pt
.x
< xpos
+ width
) break;
3058 int num_columns
= (this->ind_cargo
< NUM_INDUSTRYTYPES
) ? 4 : 2;
3059 if (column
> num_columns
) return false;
3062 /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
3065 if (_current_text_dir
== TD_RTL
) {
3066 fieldxy
->x
= num_columns
- column
;
3067 xy
->x
= ((column
& 1) ? CargoesField::cargo_field_width
: CargoesField::industry_width
) - xpos
;
3069 fieldxy
->x
= column
;
3075 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
3078 case WID_IC_PANEL
: {
3080 if (!CalculatePositionInWidget(pt
, &fieldxy
, &xy
)) return;
3082 const CargoesField
*fld
= this->fields
[fieldxy
.y
].columns
+ fieldxy
.x
;
3083 switch (fld
->type
) {
3085 if (fld
->u
.industry
.ind_type
< NUM_INDUSTRYTYPES
) this->ComputeIndustryDisplay(fld
->u
.industry
.ind_type
);
3089 CargoesField
*lft
= (fieldxy
.x
> 0) ? this->fields
[fieldxy
.y
].columns
+ fieldxy
.x
- 1 : nullptr;
3090 CargoesField
*rgt
= (fieldxy
.x
< 4) ? this->fields
[fieldxy
.y
].columns
+ fieldxy
.x
+ 1 : nullptr;
3091 CargoID cid
= fld
->CargoClickedAt(lft
, rgt
, xy
);
3092 if (IsValidCargoID(cid
)) this->ComputeCargoDisplay(cid
);
3096 case CFT_CARGO_LABEL
: {
3097 CargoID cid
= fld
->CargoLabelClickedAt(xy
);
3098 if (IsValidCargoID(cid
)) this->ComputeCargoDisplay(cid
);
3109 this->ToggleWidgetLoweredState(WID_IC_NOTIFY
);
3110 this->SetWidgetDirty(WID_IC_NOTIFY
);
3111 if (_settings_client
.sound
.click_beep
) SndPlayFx(SND_15_BEEP
);
3113 if (this->IsWidgetLowered(WID_IC_NOTIFY
)) {
3114 if (FindWindowByClass(WC_SMALLMAP
) == nullptr) ShowSmallMap();
3115 this->NotifySmallmap();
3119 case WID_IC_CARGO_DROPDOWN
: {
3121 Dimension d
= GetLargestCargoIconSize();
3122 for (const CargoSpec
*cs
: _sorted_standard_cargo_specs
) {
3123 lst
.push_back(MakeDropDownListIconItem(d
, cs
->GetCargoIcon(), PAL_NONE
, cs
->name
, cs
->Index()));
3126 int selected
= (this->ind_cargo
>= NUM_INDUSTRYTYPES
) ? (int)(this->ind_cargo
- NUM_INDUSTRYTYPES
) : -1;
3127 ShowDropDownList(this, std::move(lst
), selected
, WID_IC_CARGO_DROPDOWN
);
3132 case WID_IC_IND_DROPDOWN
: {
3134 for (IndustryType ind
: _sorted_industry_types
) {
3135 const IndustrySpec
*indsp
= GetIndustrySpec(ind
);
3136 if (!indsp
->enabled
) continue;
3137 lst
.push_back(MakeDropDownListStringItem(indsp
->name
, ind
));
3140 int selected
= (this->ind_cargo
< NUM_INDUSTRYTYPES
) ? (int)this->ind_cargo
: -1;
3141 ShowDropDownList(this, std::move(lst
), selected
, WID_IC_IND_DROPDOWN
);
3148 void OnDropdownSelect(WidgetID widget
, int index
) override
3150 if (index
< 0) return;
3153 case WID_IC_CARGO_DROPDOWN
:
3154 this->ComputeCargoDisplay(index
);
3157 case WID_IC_IND_DROPDOWN
:
3158 this->ComputeIndustryDisplay(index
);
3163 bool OnTooltip([[maybe_unused
]] Point pt
, WidgetID widget
, TooltipCloseCondition close_cond
) override
3165 if (widget
!= WID_IC_PANEL
) return false;
3168 if (!CalculatePositionInWidget(pt
, &fieldxy
, &xy
)) return false;
3170 const CargoesField
*fld
= this->fields
[fieldxy
.y
].columns
+ fieldxy
.x
;
3171 CargoID cid
= INVALID_CARGO
;
3172 switch (fld
->type
) {
3174 CargoesField
*lft
= (fieldxy
.x
> 0) ? this->fields
[fieldxy
.y
].columns
+ fieldxy
.x
- 1 : nullptr;
3175 CargoesField
*rgt
= (fieldxy
.x
< 4) ? this->fields
[fieldxy
.y
].columns
+ fieldxy
.x
+ 1 : nullptr;
3176 cid
= fld
->CargoClickedAt(lft
, rgt
, xy
);
3180 case CFT_CARGO_LABEL
: {
3181 cid
= fld
->CargoLabelClickedAt(xy
);
3186 if (fld
->u
.industry
.ind_type
< NUM_INDUSTRYTYPES
&& (this->ind_cargo
>= NUM_INDUSTRYTYPES
|| fieldxy
.x
!= 2)) {
3187 GuiShowTooltips(this, STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP
, close_cond
);
3194 if (IsValidCargoID(cid
) && (this->ind_cargo
< NUM_INDUSTRYTYPES
|| cid
!= this->ind_cargo
- NUM_INDUSTRYTYPES
)) {
3195 const CargoSpec
*csp
= CargoSpec::Get(cid
);
3196 SetDParam(0, csp
->name
);
3197 GuiShowTooltips(this, STR_INDUSTRY_CARGOES_CARGO_TOOLTIP
, close_cond
, 1);
3204 void OnResize() override
3206 this->vscroll
->SetCapacityFromWidget(this, WID_IC_PANEL
, WidgetDimensions::scaled
.framerect
.Vertical() + CargoesField::small_height
);
3211 * Open the industry and cargoes window.
3212 * @param id Industry type to display, \c NUM_INDUSTRYTYPES selects a default industry type.
3214 static void ShowIndustryCargoesWindow(IndustryType id
)
3216 if (id
>= NUM_INDUSTRYTYPES
) {
3217 for (IndustryType ind
: _sorted_industry_types
) {
3218 const IndustrySpec
*indsp
= GetIndustrySpec(ind
);
3219 if (indsp
->enabled
) {
3224 if (id
>= NUM_INDUSTRYTYPES
) return;
3227 Window
*w
= BringWindowToFrontById(WC_INDUSTRY_CARGOES
, 0);
3229 w
->InvalidateData(id
);
3232 new IndustryCargoesWindow(id
);
3235 /** Open the industry and cargoes window with an industry. */
3236 void ShowIndustryCargoesWindow()
3238 ShowIndustryCargoesWindow(NUM_INDUSTRYTYPES
);