Also scroll tile separators in the train depot
[openttd/fttd.git] / src / industry_gui.cpp
blobe404daa0403bfe2b0ad58e9f62e81a46d9426aad
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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 */
10 /** @file industry_gui.cpp GUIs related to industries. */
12 #include "stdafx.h"
13 #include "error.h"
14 #include "gui.h"
15 #include "settings_gui.h"
16 #include "sound_func.h"
17 #include "window_func.h"
18 #include "textbuf_gui.h"
19 #include "command_func.h"
20 #include "viewport_func.h"
21 #include "industry.h"
22 #include "town.h"
23 #include "cheat_type.h"
24 #include "newgrf_industries.h"
25 #include "newgrf_text.h"
26 #include "newgrf_debug.h"
27 #include "network/network.h"
28 #include "strings_func.h"
29 #include "company_func.h"
30 #include "tilehighlight_func.h"
31 #include "string.h"
32 #include "sortlist_type.h"
33 #include "widgets/dropdown_func.h"
34 #include "company_base.h"
35 #include "core/geometry_func.hpp"
36 #include "core/random_func.hpp"
37 #include "core/backup_type.hpp"
38 #include "genworld.h"
39 #include "smallmap_gui.h"
40 #include "widgets/dropdown_type.h"
41 #include "widgets/industry_widget.h"
43 #include "table/strings.h"
45 #include <bitset>
47 bool _ignore_restrictions;
48 std::bitset<NUM_INDUSTRYTYPES> _displayed_industries; ///< Communication from the industry chain window to the smallmap window about what industries to display.
50 /** Cargo suffix type (for which window is it requested) */
51 enum CargoSuffixType {
52 CST_FUND, ///< Fund-industry window
53 CST_VIEW, ///< View-industry window
54 CST_DIR, ///< Industry-directory window
57 static void ShowIndustryCargoesWindow(IndustryType id);
59 /** Option flags for displaying accepted cargo in the industry view window. */
60 enum CargoSuffixFlags {
61 CARGO_SUFFIX_FLAG_AMOUNT, ///< Display the amount of cargo.
62 CARGO_SUFFIX_FLAG_TEXT, ///< Display extra text.
65 /** Options for displaying accepted cargo in the industry view window. */
66 enum CargoSuffix {
67 CARGO_SUFFIX_NONE = 0,
68 CARGO_SUFFIX_AMOUNT = (1 << CARGO_SUFFIX_FLAG_AMOUNT),
69 CARGO_SUFFIX_TEXT = (1 << CARGO_SUFFIX_FLAG_TEXT),
70 CARGO_SUFFIX_AMOUNT_TEXT = CARGO_SUFFIX_AMOUNT | CARGO_SUFFIX_TEXT,
73 /**
74 * Gets the string to display after the cargo name for a given callback result.
75 * @param suffix Buffer to which to append the suffix.
76 * @param grffile GRF that defined the industry.
77 * @param callback Result of callback 37.
78 * @return Display option for the industry view window.
80 static uint GetCargoSuffix (stringb *suffix, const GRFFile *grffile, uint16 callback)
82 if (callback == CALLBACK_FAILED) return CARGO_SUFFIX_AMOUNT;
84 bool valid;
85 if (grffile->grf_version < 8) {
86 if (GB(callback, 0, 8) == 0xFF) return CARGO_SUFFIX_AMOUNT;
87 valid = (callback < 0x400);
88 } else {
89 if (callback == 0x400) return CARGO_SUFFIX_AMOUNT;
90 if (callback == 0x401) return CARGO_SUFFIX_NONE;
91 valid = ((callback & ~0x800) < 0x400);
94 if (valid) {
95 StartTextRefStackUsage (grffile, 6);
96 GetString (suffix, GetGRFStringID (grffile->grfid, 0xD000 + (callback & ~0x800)));
97 StopTextRefStackUsage();
98 return (callback & 0x800) != 0 ? CARGO_SUFFIX_TEXT : CARGO_SUFFIX_AMOUNT_TEXT;
99 } else {
100 ErrorUnknownCallbackResult (grffile->grfid, CBID_INDUSTRY_CARGO_SUFFIX, callback);
101 return CARGO_SUFFIX_AMOUNT;
106 * Gets the string to display after the cargo name (using callback 37)
107 * @param cargo the cargo for which the suffix is requested
108 * - 00 - first accepted cargo type
109 * - 01 - second accepted cargo type
110 * - 02 - third accepted cargo type
111 * - 03 - first produced cargo type
112 * - 04 - second produced cargo type
113 * @param cst the cargo suffix type (for which window is it requested). @see CargoSuffixType
114 * @param ind the industry (NULL if in fund window)
115 * @param ind_type the industry type
116 * @param indspec the industry spec
117 * @param suffix is filled with the string to display
118 * @return Display option for the industry view window.
120 static inline uint GetCargoSuffix (uint cargo, CargoSuffixType cst,
121 const Industry *ind, IndustryType ind_type,
122 const IndustrySpec *indspec, stringb *suffix)
124 suffix->clear();
126 if (!HasBit(indspec->callback_mask, CBM_IND_CARGO_SUFFIX)) {
127 return CARGO_SUFFIX_AMOUNT;
130 uint16 callback = GetIndustryCallback (CBID_INDUSTRY_CARGO_SUFFIX, 0,
131 (cst << 8) | cargo, const_cast<Industry *>(ind), ind_type,
132 (cst != CST_FUND) ? ind->location.tile : INVALID_TILE);
133 return GetCargoSuffix (suffix, indspec->grf_prop.grffile, callback);
137 * Set up the string parameters for an array of cargoes.
138 * @param buffer The buffer where to store the cargo suffixes, if any.
139 * @param i The industry (NULL if in fund window).
140 * @param type The industry type.
141 * @param spec The industry spec.
142 * @param n The number of cargoes in the array.
143 * @param c The array of cargoes.
144 * @param cst The cargo suffix type (for which window it is requested).
145 * @param offset Cargo offset for the callback (0 for accepts, 3 for production).
146 * @return The number of valid cargoes found.
148 static uint SetupCargoArrayParams (sstring<512> (&buffer) [3],
149 const Industry *i, IndustryType type, const IndustrySpec *spec,
150 uint n, const CargoID *c, CargoSuffixType cst, uint offset)
152 assert (n <= lengthof(buffer));
153 uint p = 0;
154 for (uint j = 0; j < n; j++) {
155 if (c[j] == CT_INVALID) continue;
156 GetCargoSuffix (j + offset, cst, i, type, spec, &buffer[j]);
157 SetDParam (p++, CargoSpec::Get(c[j])->name);
158 SetDParamStr (p++, buffer[j].c_str());
160 return p / 2;
164 * Set up the string parameters for an array of cargoes.
165 * @param buffer The buffer where to store the cargo suffixes, if any.
166 * @param i The industry (NULL if in fund window).
167 * @param type The industry type.
168 * @param spec The industry spec.
169 * @param c The array of cargoes.
170 * @param cst The cargo suffix type (for which window it is requested).
171 * @param offset Cargo offset for the callback (0 for accepts, 3 for production).
172 * @return The number of valid cargoes found.
174 template <uint N>
175 static inline uint SetupCargoArrayParams (sstring<512> (&buffer) [3],
176 const Industry *i, IndustryType type, const IndustrySpec *spec,
177 const CargoID (&c) [N], CargoSuffixType cst, uint offset)
179 return SetupCargoArrayParams (buffer, i, type, spec, N, &c[0],
180 cst, offset);
184 * Set up the string parameters for the accepted cargoes of an industry.
185 * @param buffer The buffer where to store the cargo suffixes, if any.
186 * @param type The industry type.
187 * @param spec The industry spec.
188 * @return The string to use.
190 static StringID SetupAcceptedCargoParams (sstring<512> (&buffer) [3],
191 IndustryType type, const IndustrySpec *spec)
193 static const StringID str = STR_INDUSTRY_VIEW_REQUIRES_CARGO;
195 uint p = SetupCargoArrayParams (buffer, NULL, type, spec,
196 spec->accepts_cargo, CST_FUND, 0);
197 if (p > 0) return str + p - 1;
199 SetDParam (0, STR_JUST_NOTHING);
200 SetDParamStr (1, "");
201 return str;
205 * Set up the string parameters for the produced cargoes of an industry.
206 * @param buffer The buffer where to store the cargo suffixes, if any.
207 * @param type The industry type.
208 * @param spec The industry spec.
209 * @return The string to use.
211 static StringID SetupProducedCargoParams (sstring<512> (&buffer) [3],
212 IndustryType type, const IndustrySpec *spec)
214 static const StringID str = STR_INDUSTRY_VIEW_PRODUCES_CARGO;
216 uint p = SetupCargoArrayParams (buffer, NULL, type, spec,
217 spec->produced_cargo, CST_FUND, 3);
218 if (p > 0) return str + p - 1;
220 SetDParam (0, STR_JUST_NOTHING);
221 SetDParamStr (1, "");
222 return str;
225 IndustryType _sorted_industry_types[NUM_INDUSTRYTYPES]; ///< Industry types sorted by name.
227 /** Sort industry types by their name. */
228 static int CDECL IndustryTypeNameSorter(const IndustryType *a, const IndustryType *b)
230 static char industry_name[2][64];
232 const IndustrySpec *indsp1 = GetIndustrySpec(*a);
233 GetString (industry_name[0], indsp1->name);
235 const IndustrySpec *indsp2 = GetIndustrySpec(*b);
236 GetString (industry_name[1], indsp2->name);
238 int r = strnatcmp(industry_name[0], industry_name[1]); // Sort by name (natural sorting).
240 /* If the names are equal, sort by industry type. */
241 return (r != 0) ? r : (*a - *b);
245 * Initialize the list of sorted industry types.
247 void SortIndustryTypes()
249 /* Add each industry type to the list. */
250 for (IndustryType i = 0; i < NUM_INDUSTRYTYPES; i++) {
251 _sorted_industry_types[i] = i;
254 /* Sort industry types by name. */
255 QSortT(_sorted_industry_types, NUM_INDUSTRYTYPES, &IndustryTypeNameSorter);
259 * Command callback. In case of failure to build an industry, show an error message.
260 * @param result Result of the command.
261 * @param tile Tile where the industry is placed.
262 * @param p1 Additional data of the #CMD_BUILD_INDUSTRY command.
263 * @param p2 Additional data of the #CMD_BUILD_INDUSTRY command.
265 void CcBuildIndustry(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
267 if (result.Succeeded()) return;
269 if (_game_mode != GM_EDITOR) return;
271 uint8 indtype = GB(p1, 0, 8);
272 if (indtype < NUM_INDUSTRYTYPES) {
273 const IndustrySpec *indsp = GetIndustrySpec(indtype);
274 if (indsp->enabled) {
275 SetDParam(0, indsp->name);
276 ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, result.GetErrorMessage(), WL_INFO, TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE);
281 static const NWidgetPart _nested_build_industry_widgets[] = {
282 NWidget(NWID_HORIZONTAL),
283 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
284 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_FUND_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
285 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
286 NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
287 NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
288 EndContainer(),
289 NWidget(NWID_HORIZONTAL),
290 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),
291 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_DPI_SCROLLBAR),
292 EndContainer(),
293 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_DPI_INFOPANEL), SetResize(1, 0),
294 EndContainer(),
295 NWidget(NWID_HORIZONTAL),
296 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_DISPLAY_WIDGET), SetFill(1, 0), SetResize(1, 0),
297 SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
298 NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_FUND_WIDGET), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
299 NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
300 EndContainer(),
303 /** Window preferences of the dynamic place industries gui */
304 static WindowDesc::Prefs _build_industry_prefs ("build_industry");
306 /** Window definition of the dynamic place industries gui */
307 static const WindowDesc _build_industry_desc(
308 WDP_AUTO, 170, 212,
309 WC_BUILD_INDUSTRY, WC_NONE,
310 WDF_CONSTRUCTION,
311 _nested_build_industry_widgets, lengthof(_nested_build_industry_widgets),
312 &_build_industry_prefs
315 /** Build (fund or prospect) a new industry, */
316 class BuildIndustryWindow : public Window {
317 int selected_index; ///< index of the element in the matrix
318 IndustryType selected_type; ///< industry corresponding to the above index
319 uint16 callback_timer; ///< timer counter for callback eventual verification
320 bool timer_enabled; ///< timer can be used
321 uint16 count; ///< How many industries are loaded
322 IndustryType index[NUM_INDUSTRYTYPES + 1]; ///< Type of industry, in the order it was loaded
323 bool enabled[NUM_INDUSTRYTYPES + 1]; ///< availability state, coming from CBID_INDUSTRY_PROBABILITY (if ever)
324 Scrollbar *vscroll;
326 /** The offset for the text in the matrix. */
327 static const int MATRIX_TEXT_OFFSET = 17;
329 void SetupArrays()
331 this->count = 0;
333 for (uint i = 0; i < lengthof(this->index); i++) {
334 this->index[i] = INVALID_INDUSTRYTYPE;
335 this->enabled[i] = false;
338 if (_game_mode == GM_EDITOR) { // give room for the Many Random "button"
339 this->index[this->count] = INVALID_INDUSTRYTYPE;
340 this->enabled[this->count] = true;
341 this->count++;
342 this->timer_enabled = false;
344 /* Fill the arrays with industries.
345 * The tests performed after the enabled allow to load the industries
346 * In the same way they are inserted by grf (if any)
348 for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
349 IndustryType ind = _sorted_industry_types[i];
350 const IndustrySpec *indsp = GetIndustrySpec(ind);
351 if (indsp->enabled) {
352 /* Rule is that editor mode loads all industries.
353 * In game mode, all non raw industries are loaded too
354 * and raw ones are loaded only when setting allows it */
355 if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
356 /* Unselect if the industry is no longer in the list */
357 if (this->selected_type == ind) this->selected_index = -1;
358 continue;
360 this->index[this->count] = ind;
361 this->enabled[this->count] = (_game_mode == GM_EDITOR) || GetIndustryProbabilityCallback(ind, IACT_USERCREATION, 1) > 0;
362 /* Keep the selection to the correct line */
363 if (this->selected_type == ind) this->selected_index = this->count;
364 this->count++;
368 /* first industry type is selected if the current selection is invalid.
369 * I'll be damned if there are none available ;) */
370 if (this->selected_index == -1) {
371 this->selected_index = 0;
372 this->selected_type = this->index[0];
375 this->vscroll->SetCount(this->count);
378 /** Update status of the fund and display-chain widgets. */
379 void SetButtons()
381 this->SetWidgetDisabledState(WID_DPI_FUND_WIDGET, this->selected_type != INVALID_INDUSTRYTYPE && !this->enabled[this->selected_index]);
382 this->SetWidgetDisabledState(WID_DPI_DISPLAY_WIDGET, this->selected_type == INVALID_INDUSTRYTYPE && this->enabled[this->selected_index]);
385 public:
386 BuildIndustryWindow() : Window (&_build_industry_desc),
387 selected_index (-1),
388 selected_type (INVALID_INDUSTRYTYPE),
389 callback_timer (DAY_TICKS),
390 timer_enabled (_loaded_newgrf_features.has_newindustries),
391 count (0), vscroll (NULL)
393 memset (this->index, 0, sizeof(this->index));
394 memset (this->enabled, 0, sizeof(this->enabled));
396 this->CreateNestedTree();
397 this->vscroll = this->GetScrollbar(WID_DPI_SCROLLBAR);
398 this->InitNested(0);
400 this->SetButtons();
403 virtual void OnInit()
405 this->SetupArrays();
408 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
410 switch (widget) {
411 case WID_DPI_MATRIX_WIDGET: {
412 Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES);
413 for (byte i = 0; i < this->count; i++) {
414 if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
415 d = maxdim(d, GetStringBoundingBox(GetIndustrySpec(this->index[i])->name));
417 resize->height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
418 d.width += MATRIX_TEXT_OFFSET + padding.width;
419 d.height = 5 * resize->height;
420 *size = maxdim(*size, d);
421 break;
424 case WID_DPI_INFOPANEL: {
425 /* Extra line for cost outside of editor + extra lines for 'extra' information for NewGRFs. */
426 int height = 2 + (_game_mode == GM_EDITOR ? 0 : 1) + (_loaded_newgrf_features.has_newindustries ? 4 : 0);
427 Dimension d = {0, 0};
428 for (byte i = 0; i < this->count; i++) {
429 if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
431 const IndustrySpec *indsp = GetIndustrySpec(this->index[i]);
433 sstring<512> cargo_suffix [3];
434 StringID str = SetupAcceptedCargoParams (cargo_suffix,
435 this->index[i], indsp);
436 d = maxdim(d, GetStringBoundingBox(str));
438 /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
439 str = SetupProducedCargoParams (cargo_suffix,
440 this->index[i], indsp);
441 d = maxdim(d, GetStringBoundingBox(str));
444 /* Set it to something more sane :) */
445 size->height = height * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
446 size->width = d.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
447 break;
450 case WID_DPI_FUND_WIDGET: {
451 Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
452 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY));
453 d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY));
454 d.width += padding.width;
455 d.height += padding.height;
456 *size = maxdim(*size, d);
457 break;
462 virtual void SetStringParameters(int widget) const
464 switch (widget) {
465 case WID_DPI_FUND_WIDGET:
466 /* Raw industries might be prospected. Show this fact by changing the string
467 * In Editor, you just build, while ingame, or you fund or you prospect */
468 if (_game_mode == GM_EDITOR) {
469 /* We've chosen many random industries but no industries have been specified */
470 SetDParam(0, STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
471 } else {
472 const IndustrySpec *indsp = GetIndustrySpec(this->index[this->selected_index]);
473 SetDParam(0, (_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
475 break;
479 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
481 switch (widget) {
482 case WID_DPI_MATRIX_WIDGET: {
483 uint text_left, text_right, icon_left, icon_right;
484 if (_current_text_dir == TD_RTL) {
485 icon_right = r.right - WD_MATRIX_RIGHT;
486 icon_left = icon_right - 10;
487 text_right = icon_right - BuildIndustryWindow::MATRIX_TEXT_OFFSET;
488 text_left = r.left + WD_MATRIX_LEFT;
489 } else {
490 icon_left = r.left + WD_MATRIX_LEFT;
491 icon_right = icon_left + 10;
492 text_left = icon_left + BuildIndustryWindow::MATRIX_TEXT_OFFSET;
493 text_right = r.right - WD_MATRIX_RIGHT;
496 for (byte i = 0; i < this->vscroll->GetCapacity() && i + this->vscroll->GetPosition() < this->count; i++) {
497 int y = r.top + WD_MATRIX_TOP + i * this->resize.step_height;
498 bool selected = this->selected_index == i + this->vscroll->GetPosition();
500 if (this->index[i + this->vscroll->GetPosition()] == INVALID_INDUSTRYTYPE) {
501 DrawString (dpi, text_left, text_right, y, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, selected ? TC_WHITE : TC_ORANGE);
502 continue;
504 const IndustrySpec *indsp = GetIndustrySpec(this->index[i + this->vscroll->GetPosition()]);
506 /* Draw the name of the industry in white is selected, otherwise, in orange */
507 DrawString (dpi, text_left, text_right, y, indsp->name, selected ? TC_WHITE : TC_ORANGE);
508 GfxFillRect (dpi, icon_left, y + 1, icon_right, y + 7, selected ? PC_WHITE : PC_BLACK);
509 GfxFillRect (dpi, icon_left + 1, y + 2, icon_right - 1, y + 6, indsp->map_colour);
511 break;
514 case WID_DPI_INFOPANEL: {
515 int y = r.top + WD_FRAMERECT_TOP;
516 int bottom = r.bottom - WD_FRAMERECT_BOTTOM;
517 int left = r.left + WD_FRAMERECT_LEFT;
518 int right = r.right - WD_FRAMERECT_RIGHT;
520 if (this->selected_type == INVALID_INDUSTRYTYPE) {
521 DrawStringMultiLine (dpi, left, right, y, bottom, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
522 break;
525 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
527 if (_game_mode != GM_EDITOR) {
528 SetDParam(0, indsp->GetConstructionCost());
529 DrawString (dpi, left, right, y, STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST);
530 y += FONT_HEIGHT_NORMAL;
533 /* Draw the accepted cargoes, if any. Otherwise, will print "Nothing". */
534 sstring<512> cargo_suffix [3];
535 StringID str = SetupAcceptedCargoParams (cargo_suffix,
536 this->selected_type, indsp);
537 DrawString (dpi, left, right, y, str);
538 y += FONT_HEIGHT_NORMAL;
540 /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
541 str = SetupProducedCargoParams (cargo_suffix,
542 this->selected_type, indsp);
543 DrawString (dpi, left, right, y, str);
544 y += FONT_HEIGHT_NORMAL;
546 /* Get the additional purchase info text, if it has not already been queried. */
547 str = STR_NULL;
548 if (HasBit(indsp->callback_mask, CBM_IND_FUND_MORE_TEXT)) {
549 uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, NULL, this->selected_type, INVALID_TILE);
550 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
551 if (callback_res > 0x400) {
552 ErrorUnknownCallbackResult(indsp->grf_prop.grffile->grfid, CBID_INDUSTRY_FUND_MORE_TEXT, callback_res);
553 } else {
554 str = GetGRFStringID(indsp->grf_prop.grffile->grfid, 0xD000 + callback_res); // No. here's the new string
555 if (str != STR_UNDEFINED) {
556 StartTextRefStackUsage(indsp->grf_prop.grffile, 6);
557 DrawStringMultiLine (dpi, left, right, y, bottom, str, TC_YELLOW);
558 StopTextRefStackUsage();
563 break;
568 virtual void OnClick(Point pt, int widget, int click_count)
570 switch (widget) {
571 case WID_DPI_MATRIX_WIDGET: {
572 int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_DPI_MATRIX_WIDGET);
573 if (y < this->count) { // Is it within the boundaries of available data?
574 this->selected_index = y;
575 this->selected_type = this->index[y];
576 const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
578 this->SetDirty();
580 if (_thd.GetCallbackWnd() == this &&
581 ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != NULL && indsp->IsRawIndustry()) ||
582 this->selected_type == INVALID_INDUSTRYTYPE ||
583 !this->enabled[this->selected_index])) {
584 /* Reset the button state if going to prospecting or "build many industries" */
585 this->RaiseButtons();
586 ResetPointerMode();
589 this->SetButtons();
590 if (this->enabled[this->selected_index] && click_count > 1) this->OnClick(pt, WID_DPI_FUND_WIDGET, 1);
592 break;
595 case WID_DPI_DISPLAY_WIDGET:
596 if (this->selected_type != INVALID_INDUSTRYTYPE) ShowIndustryCargoesWindow(this->selected_type);
597 break;
599 case WID_DPI_FUND_WIDGET: {
600 if (this->selected_type == INVALID_INDUSTRYTYPE) {
601 this->HandleButtonClick(WID_DPI_FUND_WIDGET);
603 if (Town::GetNumItems() == 0) {
604 ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO);
605 } else {
606 extern void GenerateIndustries();
607 _generating_world = true;
608 GenerateIndustries();
609 _generating_world = false;
611 } else if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
612 DoCommandP(0, this->selected_type, InteractiveRandom(), CMD_BUILD_INDUSTRY);
613 this->HandleButtonClick(WID_DPI_FUND_WIDGET);
614 } else {
615 HandlePlacePushButton (this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, POINTER_TILE);
617 break;
622 virtual void OnResize()
624 /* Adjust the number of items in the matrix depending of the resize */
625 this->vscroll->SetCapacityFromWidget(this, WID_DPI_MATRIX_WIDGET);
628 virtual void OnPlaceObject(Point pt, TileIndex tile)
630 bool success = true;
631 /* We do not need to protect ourselves against "Random Many Industries" in this mode */
632 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
633 uint32 seed = InteractiveRandom();
635 if (_game_mode == GM_EDITOR) {
636 /* Show error if no town exists at all */
637 if (Town::GetNumItems() == 0) {
638 SetDParam(0, indsp->name);
639 ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO, pt.x, pt.y);
640 return;
643 Backup<CompanyByte> cur_company(_current_company, OWNER_NONE, FILE_LINE);
644 _generating_world = true;
645 _ignore_restrictions = true;
647 DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY);
649 cur_company.Restore();
650 _ignore_restrictions = false;
651 _generating_world = false;
652 } else {
653 success = DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY);
656 /* If an industry has been built, just reset the cursor and the system */
657 if (success && !_settings_client.gui.persistent_buildingtools) ResetPointerMode();
660 virtual void OnTick()
662 if (_pause_mode != PM_UNPAUSED) return;
663 if (!this->timer_enabled) return;
664 if (--this->callback_timer == 0) {
665 /* We have just passed another day.
666 * See if we need to update availability of currently selected industry */
667 this->callback_timer = DAY_TICKS; // restart counter
669 const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
671 if (indsp->enabled) {
672 bool call_back_result = GetIndustryProbabilityCallback(this->selected_type, IACT_USERCREATION, 1) > 0;
674 /* Only if result does match the previous state would it require a redraw. */
675 if (call_back_result != this->enabled[this->selected_index]) {
676 this->enabled[this->selected_index] = call_back_result;
677 this->SetButtons();
678 this->SetDirty();
684 virtual void OnTimeout()
686 this->RaiseButtons();
689 virtual void OnPlaceObjectAbort()
691 this->RaiseButtons();
695 * Some data on this window has become invalid.
696 * @param data Information about the changed data.
697 * @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.
699 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
701 if (!gui_scope) return;
702 this->SetupArrays();
704 const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
705 if (indsp == NULL) this->enabled[this->selected_index] = _settings_game.difficulty.industry_density != ID_FUND_ONLY;
706 this->SetButtons();
710 void ShowBuildIndustryWindow()
712 if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
713 if (BringWindowToFrontById(WC_BUILD_INDUSTRY, 0)) return;
714 new BuildIndustryWindow();
717 static void UpdateIndustryProduction(Industry *i);
719 static inline bool IsProductionAlterable(const Industry *i)
721 const IndustrySpec *is = GetIndustrySpec(i->type);
722 return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
723 (is->production_rate[0] != 0 || is->production_rate[1] != 0 || is->IsRawIndustry()) &&
724 !_networking);
727 class IndustryViewWindow : public Window
729 /** Modes for changing production */
730 enum Editability {
731 EA_NONE, ///< Not alterable
732 EA_MULTIPLIER, ///< Allow changing the production multiplier
733 EA_RATE, ///< Allow changing the production rates
736 /** Specific lines in the info panel */
737 enum InfoLine {
738 IL_NONE, ///< No line
739 IL_MULTIPLIER, ///< Production multiplier
740 IL_RATE1, ///< Production rate of cargo 1
741 IL_RATE2, ///< Production rate of cargo 2
744 Editability editable; ///< Mode for changing production
745 InfoLine editbox_line; ///< The line clicked to open the edit box
746 InfoLine clicked_line; ///< The line of the button that has been clicked
747 byte clicked_button; ///< The button that has been clicked (to raise)
748 int production_offset_y; ///< The offset of the production texts/buttons
749 int info_height; ///< Height needed for the #WID_IV_INFO panel
751 public:
752 IndustryViewWindow (const WindowDesc *desc, WindowNumber window_number)
753 : Window (desc), editable (EA_NONE),
754 editbox_line (IL_NONE), clicked_line (IL_NONE),
755 clicked_button (0), production_offset_y (0),
756 info_height (WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM + 1) // Info panel has at least two lines text.
758 this->flags |= WF_DISABLE_VP_SCROLL;
760 this->InitNested(window_number);
761 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
762 nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ZOOM_LVL_INDUSTRY);
764 this->InvalidateData();
767 void OnPaint (BlitArea *dpi) OVERRIDE
769 this->DrawWidgets (dpi);
771 if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
773 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_IV_INFO);
774 uint expected = this->DrawInfo (dpi, nwi->pos_x, nwi->pos_x + nwi->current_x - 1, nwi->pos_y) - nwi->pos_y;
775 if (expected > nwi->current_y - 1) {
776 this->info_height = expected + 1;
777 this->ReInit();
778 return;
783 * Draw the text in the #WID_IV_INFO panel.
784 * @param dpi Area to draw on.
785 * @param left Left edge of the panel.
786 * @param right Right edge of the panel.
787 * @param top Top edge of the panel.
788 * @return Expected position of the bottom edge of the panel.
790 int DrawInfo (BlitArea *dpi, uint left, uint right, uint top)
792 Industry *i = Industry::Get(this->window_number);
793 const IndustrySpec *ind = GetIndustrySpec(i->type);
794 int y = top + WD_FRAMERECT_TOP;
795 bool has_accept = false;
796 sstring<512> cargo_suffix [3];
798 if (i->prod_level == PRODLEVEL_CLOSURE) {
799 DrawString (dpi, left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE);
800 y += 2 * FONT_HEIGHT_NORMAL;
803 bool stockpiling = HasBit(ind->callback_mask, CBM_IND_PRODUCTION_CARGO_ARRIVAL)
804 || HasBit(ind->callback_mask, CBM_IND_PRODUCTION_256_TICKS);
805 bool has_suffix = HasBit(ind->callback_mask, CBM_IND_CARGO_SUFFIX);
807 if (stockpiling || has_suffix) {
808 for (byte j = 0; j < lengthof(i->accepts_cargo); j++) {
809 if (i->accepts_cargo[j] == CT_INVALID) continue;
810 if (!has_accept) {
811 DrawString (dpi, left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_REQUIRES);
812 y += FONT_HEIGHT_NORMAL;
813 has_accept = true;
816 uint opt;
817 if (!has_suffix) {
818 opt = CARGO_SUFFIX_AMOUNT;
819 } else {
820 opt = GetCargoSuffix (j, CST_VIEW, i, i->type, ind, &cargo_suffix[j]);
821 if ((opt == CARGO_SUFFIX_AMOUNT) && !stockpiling) {
822 opt = CARGO_SUFFIX_NONE;
826 byte p;
827 if (HasBit (opt, CARGO_SUFFIX_FLAG_AMOUNT)) {
828 SetDParam (0, i->accepts_cargo[j]);
829 SetDParam (1, i->incoming_cargo_waiting[j]);
830 p = 2;
831 } else {
832 SetDParam (0, CargoSpec::Get(i->accepts_cargo[j])->name);
833 p = 1;
835 if (HasBit (opt, CARGO_SUFFIX_FLAG_TEXT)) {
836 SetDParamStr (p, cargo_suffix[j].c_str());
839 assert_compile (STR_INDUSTRY_VIEW_ACCEPT_CARGO + CARGO_SUFFIX_NONE == STR_INDUSTRY_VIEW_ACCEPT_CARGO);
840 assert_compile (STR_INDUSTRY_VIEW_ACCEPT_CARGO + CARGO_SUFFIX_AMOUNT == STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT);
841 assert_compile (STR_INDUSTRY_VIEW_ACCEPT_CARGO + CARGO_SUFFIX_TEXT == STR_INDUSTRY_VIEW_ACCEPT_CARGO_TEXT);
842 assert_compile (STR_INDUSTRY_VIEW_ACCEPT_CARGO + CARGO_SUFFIX_AMOUNT_TEXT == STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_TEXT);
844 StringID str = STR_INDUSTRY_VIEW_ACCEPT_CARGO + opt;
845 DrawString (dpi, left + WD_FRAMETEXT_LEFT, right - WD_FRAMERECT_RIGHT, y, str);
846 y += FONT_HEIGHT_NORMAL;
848 } else {
849 uint p = SetupCargoArrayParams (cargo_suffix, i, i->type,
850 ind, i->accepts_cargo, CST_VIEW, 0);
851 has_accept = (p > 0);
852 if (has_accept) {
853 StringID str = STR_INDUSTRY_VIEW_REQUIRES_CARGO + p - 1;
854 DrawString (dpi, left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, str);
855 y += FONT_HEIGHT_NORMAL;
859 bool first = true;
860 assert_compile(lengthof(i->produced_cargo) <= lengthof(cargo_suffix));
861 for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
862 if (i->produced_cargo[j] == CT_INVALID) continue;
863 if (first) {
864 if (has_accept) y += WD_PAR_VSEP_WIDE;
865 DrawString (dpi, left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
866 y += FONT_HEIGHT_NORMAL;
867 if (this->editable == EA_RATE) this->production_offset_y = y;
868 first = false;
871 GetCargoSuffix (j + 3, CST_VIEW, i, i->type, ind, &cargo_suffix[j]);
872 SetDParam(0, i->produced_cargo[j]);
873 SetDParam(1, i->last_month_production[j]);
874 SetDParamStr(2, cargo_suffix[j].c_str());
875 SetDParam(3, ToPercent8(i->last_month_pct_transported[j]));
876 uint x = left + WD_FRAMETEXT_LEFT + (this->editable == EA_RATE ? SETTING_BUTTON_WIDTH + 10 : 0);
877 DrawString (dpi, x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_TRANSPORTED);
878 /* Let's put out those buttons.. */
879 if (this->editable == EA_RATE) {
880 DrawArrowButtons (dpi, left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_RATE1 + j) ? this->clicked_button : 0,
881 i->production_rate[j] > 0, i->production_rate[j] < 255);
883 y += FONT_HEIGHT_NORMAL;
886 /* Display production multiplier if editable */
887 if (this->editable == EA_MULTIPLIER) {
888 y += WD_PAR_VSEP_WIDE;
889 this->production_offset_y = y;
890 SetDParam(0, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT));
891 uint x = left + WD_FRAMETEXT_LEFT + SETTING_BUTTON_WIDTH + 10;
892 DrawString (dpi, x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LEVEL);
893 DrawArrowButtons (dpi, left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_MULTIPLIER) ? this->clicked_button : 0,
894 i->prod_level > PRODLEVEL_MINIMUM, i->prod_level < PRODLEVEL_MAXIMUM);
895 y += FONT_HEIGHT_NORMAL;
898 /* Get the extra message for the GUI */
899 if (HasBit(ind->callback_mask, CBM_IND_WINDOW_MORE_TEXT)) {
900 uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->location.tile);
901 if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
902 if (callback_res > 0x400) {
903 ErrorUnknownCallbackResult(ind->grf_prop.grffile->grfid, CBID_INDUSTRY_WINDOW_MORE_TEXT, callback_res);
904 } else {
905 StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res);
906 if (message != STR_NULL && message != STR_UNDEFINED) {
907 y += WD_PAR_VSEP_WIDE;
909 StartTextRefStackUsage(ind->grf_prop.grffile, 6);
910 /* Use all the available space left from where we stand up to the
911 * end of the window. We ALSO enlarge the window if needed, so we
912 * can 'go' wild with the bottom of the window. */
913 y = DrawStringMultiLine (dpi, left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, UINT16_MAX, message, TC_BLACK);
914 StopTextRefStackUsage();
919 return y + WD_FRAMERECT_BOTTOM;
922 virtual void SetStringParameters(int widget) const
924 if (widget == WID_IV_CAPTION) SetDParam(0, this->window_number);
927 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
929 if (widget == WID_IV_INFO) size->height = this->info_height;
932 virtual void OnClick(Point pt, int widget, int click_count)
934 switch (widget) {
935 case WID_IV_INFO: {
936 Industry *i = Industry::Get(this->window_number);
937 InfoLine line = IL_NONE;
939 switch (this->editable) {
940 case EA_NONE: break;
942 case EA_MULTIPLIER:
943 if (IsInsideBS(pt.y, this->production_offset_y, FONT_HEIGHT_NORMAL)) line = IL_MULTIPLIER;
944 break;
946 case EA_RATE:
947 if (pt.y >= this->production_offset_y) {
948 int row = (pt.y - this->production_offset_y) / FONT_HEIGHT_NORMAL;
949 for (uint j = 0; j < lengthof(i->produced_cargo); j++) {
950 if (i->produced_cargo[j] == CT_INVALID) continue;
951 row--;
952 if (row < 0) {
953 line = (InfoLine)(IL_RATE1 + j);
954 break;
958 break;
960 if (line == IL_NONE) return;
962 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget);
963 int left = nwi->pos_x + WD_FRAMETEXT_LEFT;
964 int right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT;
965 if (IsInsideMM(pt.x, left, left + SETTING_BUTTON_WIDTH)) {
966 /* Clicked buttons, decrease or increase production */
967 byte button = (pt.x < left + SETTING_BUTTON_WIDTH / 2) ? 1 : 2;
968 switch (this->editable) {
969 case EA_MULTIPLIER:
970 if (button == 1) {
971 if (i->prod_level <= PRODLEVEL_MINIMUM) return;
972 i->prod_level = max<uint>(i->prod_level / 2, PRODLEVEL_MINIMUM);
973 } else {
974 if (i->prod_level >= PRODLEVEL_MAXIMUM) return;
975 i->prod_level = minu(i->prod_level * 2, PRODLEVEL_MAXIMUM);
977 break;
979 case EA_RATE:
980 if (button == 1) {
981 if (i->production_rate[line - IL_RATE1] <= 0) return;
982 i->production_rate[line - IL_RATE1] = max(i->production_rate[line - IL_RATE1] / 2, 0);
983 } else {
984 if (i->production_rate[line - IL_RATE1] >= 255) return;
985 /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
986 int new_prod = i->production_rate[line - IL_RATE1] == 0 ? 1 : i->production_rate[line - IL_RATE1] * 2;
987 i->production_rate[line - IL_RATE1] = minu(new_prod, 255);
989 break;
991 default: NOT_REACHED();
994 UpdateIndustryProduction(i);
995 this->SetDirty();
996 this->SetTimeout();
997 this->clicked_line = line;
998 this->clicked_button = button;
999 } else if (IsInsideMM(pt.x, left + SETTING_BUTTON_WIDTH + 10, right)) {
1000 /* clicked the text */
1001 this->editbox_line = line;
1002 switch (this->editable) {
1003 case EA_MULTIPLIER:
1004 SetDParam(0, RoundDivSU(i->prod_level * 100, PRODLEVEL_DEFAULT));
1005 ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION_LEVEL, 10, this, CS_ALPHANUMERAL, QSF_NONE);
1006 break;
1008 case EA_RATE:
1009 SetDParam(0, i->production_rate[line - IL_RATE1] * 8);
1010 ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION, 10, this, CS_ALPHANUMERAL, QSF_NONE);
1011 break;
1013 default: NOT_REACHED();
1016 break;
1019 case WID_IV_GOTO: {
1020 Industry *i = Industry::Get(this->window_number);
1021 if (_ctrl_pressed) {
1022 ShowExtraViewPortWindow(i->location.GetCenterTile());
1023 } else {
1024 ScrollMainWindowToTile(i->location.GetCenterTile());
1026 break;
1029 case WID_IV_DISPLAY: {
1030 Industry *i = Industry::Get(this->window_number);
1031 ShowIndustryCargoesWindow(i->type);
1032 break;
1037 virtual void OnTimeout()
1039 this->clicked_line = IL_NONE;
1040 this->clicked_button = 0;
1041 this->SetDirty();
1044 virtual void OnResize()
1046 if (this->viewport != NULL) {
1047 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
1048 nvp->UpdateViewportCoordinates(this);
1050 ScrollWindowToTile(Industry::Get(this->window_number)->location.GetCenterTile(), this, true); // Re-center viewport.
1054 virtual void OnQueryTextFinished(char *str)
1056 if (StrEmpty(str)) return;
1058 Industry *i = Industry::Get(this->window_number);
1059 uint value = atoi(str);
1060 switch (this->editbox_line) {
1061 case IL_NONE: NOT_REACHED();
1063 case IL_MULTIPLIER:
1064 i->prod_level = ClampU(RoundDivSU(value * PRODLEVEL_DEFAULT, 100), PRODLEVEL_MINIMUM, PRODLEVEL_MAXIMUM);
1065 break;
1067 default:
1068 i->production_rate[this->editbox_line - IL_RATE1] = ClampU(RoundDivSU(value, 8), 0, 255);
1069 break;
1071 UpdateIndustryProduction(i);
1072 this->SetDirty();
1076 * Some data on this window has become invalid.
1077 * @param data Information about the changed data.
1078 * @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.
1080 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1082 if (!gui_scope) return;
1083 const Industry *i = Industry::Get(this->window_number);
1084 if (IsProductionAlterable(i)) {
1085 const IndustrySpec *ind = GetIndustrySpec(i->type);
1086 this->editable = ind->UsesSmoothEconomy() ? EA_RATE : EA_MULTIPLIER;
1087 } else {
1088 this->editable = EA_NONE;
1092 virtual bool IsNewGRFInspectable() const
1094 return ::IsNewGRFInspectable(GSF_INDUSTRIES, this->window_number);
1097 virtual void ShowNewGRFInspectWindow() const
1099 ::ShowNewGRFInspectWindow(GSF_INDUSTRIES, this->window_number);
1103 static void UpdateIndustryProduction(Industry *i)
1105 const IndustrySpec *indspec = GetIndustrySpec(i->type);
1106 if (!indspec->UsesSmoothEconomy()) i->RecomputeProductionMultipliers();
1108 for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1109 if (i->produced_cargo[j] != CT_INVALID) {
1110 i->last_month_production[j] = 8 * i->production_rate[j];
1115 /** Widget definition of the view industry gui */
1116 static const NWidgetPart _nested_industry_view_widgets[] = {
1117 NWidget(NWID_HORIZONTAL),
1118 NWidget(WWT_CLOSEBOX, COLOUR_CREAM),
1119 NWidget(WWT_CAPTION, COLOUR_CREAM, WID_IV_CAPTION), SetDataTip(STR_INDUSTRY_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1120 NWidget(WWT_DEBUGBOX, COLOUR_CREAM),
1121 NWidget(WWT_SHADEBOX, COLOUR_CREAM),
1122 NWidget(WWT_DEFSIZEBOX, COLOUR_CREAM),
1123 NWidget(WWT_STICKYBOX, COLOUR_CREAM),
1124 EndContainer(),
1125 NWidget(WWT_PANEL, COLOUR_CREAM),
1126 NWidget(WWT_INSET, COLOUR_CREAM), SetPadding(2, 2, 2, 2),
1127 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_IV_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetPadding(1, 1, 1, 1), SetResize(1, 1),
1128 EndContainer(),
1129 EndContainer(),
1130 NWidget(WWT_PANEL, COLOUR_CREAM, WID_IV_INFO), SetMinimalSize(260, 2), SetResize(1, 0),
1131 EndContainer(),
1132 NWidget(NWID_HORIZONTAL),
1133 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GOTO), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_BUTTON_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
1134 NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
1135 NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
1136 EndContainer(),
1139 /** Window preferences of the view industry gui */
1140 static WindowDesc::Prefs _industry_view_prefs ("view_industry");
1142 /** Window definition of the view industry gui */
1143 static const WindowDesc _industry_view_desc(
1144 WDP_AUTO, 260, 120,
1145 WC_INDUSTRY_VIEW, WC_NONE,
1147 _nested_industry_view_widgets, lengthof(_nested_industry_view_widgets),
1148 &_industry_view_prefs
1151 void ShowIndustryViewWindow(int industry)
1153 AllocateWindowDescFront<IndustryViewWindow>(&_industry_view_desc, industry);
1156 /** Widget definition of the industry directory gui */
1157 static const NWidgetPart _nested_industry_directory_widgets[] = {
1158 NWidget(NWID_HORIZONTAL),
1159 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1160 NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_INDUSTRY_DIRECTORY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1161 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1162 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1163 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1164 EndContainer(),
1165 NWidget(NWID_HORIZONTAL),
1166 NWidget(NWID_VERTICAL),
1167 NWidget(NWID_HORIZONTAL),
1168 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_ID_DROPDOWN_ORDER), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1169 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_DROPDOWN_CRITERIA), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
1170 NWidget(WWT_PANEL, COLOUR_BROWN), SetResize(1, 0), EndContainer(),
1171 EndContainer(),
1172 NWidget(WWT_PANEL, COLOUR_BROWN, WID_ID_INDUSTRY_LIST), SetDataTip(0x0, STR_INDUSTRY_DIRECTORY_LIST_CAPTION), SetResize(1, 1), SetScrollbar(WID_ID_SCROLLBAR), EndContainer(),
1173 EndContainer(),
1174 NWidget(NWID_VERTICAL),
1175 NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_ID_SCROLLBAR),
1176 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1177 EndContainer(),
1178 EndContainer(),
1181 typedef GUIList<const Industry*> GUIIndustryList;
1185 * The list of industries.
1187 class IndustryDirectoryWindow : public Window {
1188 protected:
1189 /* Runtime saved values */
1190 static Listing last_sorting;
1191 static const Industry *last_industry;
1193 /* Constants for sorting stations */
1194 static const StringID sorter_names[];
1195 static GUIIndustryList::SortFunction * const sorter_funcs[];
1197 GUIIndustryList industries;
1198 Scrollbar *vscroll;
1200 /** (Re)Build industries list */
1201 void BuildSortIndustriesList()
1203 if (this->industries.NeedRebuild()) {
1204 this->industries.Clear();
1206 const Industry *i;
1207 FOR_ALL_INDUSTRIES(i) {
1208 *this->industries.Append() = i;
1211 this->industries.Compact();
1212 this->industries.RebuildDone();
1213 this->vscroll->SetCount(this->industries.Length()); // Update scrollbar as well.
1216 if (!this->industries.Sort()) return;
1217 IndustryDirectoryWindow::last_industry = NULL; // Reset name sorter sort cache
1218 this->SetWidgetDirty(WID_ID_INDUSTRY_LIST); // Set the modified widget dirty
1222 * Returns percents of cargo transported if industry produces this cargo, else -1
1224 * @param i industry to check
1225 * @param id cargo slot
1226 * @return percents of cargo transported, or -1 if industry doesn't use this cargo slot
1228 static inline int GetCargoTransportedPercentsIfValid(const Industry *i, uint id)
1230 assert(id < lengthof(i->produced_cargo));
1232 if (i->produced_cargo[id] == CT_INVALID) return 101;
1233 return ToPercent8(i->last_month_pct_transported[id]);
1237 * Returns value representing industry's transported cargo
1238 * percentage for industry sorting
1240 * @param i industry to check
1241 * @return value used for sorting
1243 static int GetCargoTransportedSortValue(const Industry *i)
1245 int p1 = GetCargoTransportedPercentsIfValid(i, 0);
1246 int p2 = GetCargoTransportedPercentsIfValid(i, 1);
1248 if (p1 > p2) Swap(p1, p2); // lower value has higher priority
1250 return (p1 << 8) + p2;
1253 /** Sort industries by name */
1254 static int CDECL IndustryNameSorter(const Industry * const *a, const Industry * const *b)
1256 static char buf_cache[96];
1257 static char buf[96];
1259 SetDParam(0, (*a)->index);
1260 GetString (buf, STR_INDUSTRY_NAME);
1262 if (*b != last_industry) {
1263 last_industry = *b;
1264 SetDParam(0, (*b)->index);
1265 GetString (buf_cache, STR_INDUSTRY_NAME);
1268 return strnatcmp(buf, buf_cache); // Sort by name (natural sorting).
1271 /** Sort industries by type and name */
1272 static int CDECL IndustryTypeSorter(const Industry * const *a, const Industry * const *b)
1274 int it_a = 0;
1275 while (it_a != NUM_INDUSTRYTYPES && (*a)->type != _sorted_industry_types[it_a]) it_a++;
1276 int it_b = 0;
1277 while (it_b != NUM_INDUSTRYTYPES && (*b)->type != _sorted_industry_types[it_b]) it_b++;
1278 int r = it_a - it_b;
1279 return (r == 0) ? IndustryNameSorter(a, b) : r;
1282 /** Sort industries by production and name */
1283 static int CDECL IndustryProductionSorter(const Industry * const *a, const Industry * const *b)
1285 uint prod_a = 0, prod_b = 0;
1286 for (uint i = 0; i < lengthof((*a)->produced_cargo); i++) {
1287 if ((*a)->produced_cargo[i] != CT_INVALID) prod_a += (*a)->last_month_production[i];
1288 if ((*b)->produced_cargo[i] != CT_INVALID) prod_b += (*b)->last_month_production[i];
1290 int r = prod_a - prod_b;
1292 return (r == 0) ? IndustryTypeSorter(a, b) : r;
1295 /** Sort industries by transported cargo and name */
1296 static int CDECL IndustryTransportedCargoSorter(const Industry * const *a, const Industry * const *b)
1298 int r = GetCargoTransportedSortValue(*a) - GetCargoTransportedSortValue(*b);
1299 return (r == 0) ? IndustryNameSorter(a, b) : r;
1303 * Get the StringID to draw and set the appropriate DParams.
1304 * @param i the industry to get the StringID of.
1305 * @return the StringID.
1307 StringID GetIndustryString(const Industry *i) const
1309 const IndustrySpec *indsp = GetIndustrySpec(i->type);
1310 byte p = 0;
1312 /* Industry name */
1313 SetDParam(p++, i->index);
1315 static sstring<512> cargo_suffix [lengthof(i->produced_cargo)];
1317 /* Industry productions */
1318 for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1319 if (i->produced_cargo[j] == CT_INVALID) continue;
1320 GetCargoSuffix (j + 3, CST_DIR, i, i->type, indsp, &cargo_suffix[j]);
1321 SetDParam(p++, i->produced_cargo[j]);
1322 SetDParam(p++, i->last_month_production[j]);
1323 SetDParamStr(p++, cargo_suffix[j].c_str());
1326 /* Transported productions */
1327 for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1328 if (i->produced_cargo[j] == CT_INVALID) continue;
1329 SetDParam(p++, ToPercent8(i->last_month_pct_transported[j]));
1332 /* Drawing the right string */
1333 switch (p) {
1334 case 1: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1335 case 5: return STR_INDUSTRY_DIRECTORY_ITEM;
1336 default: return STR_INDUSTRY_DIRECTORY_ITEM_TWO;
1340 public:
1341 IndustryDirectoryWindow (const WindowDesc *desc, WindowNumber number)
1342 : Window (desc), industries(), vscroll (NULL)
1344 this->CreateNestedTree();
1345 this->vscroll = this->GetScrollbar(WID_ID_SCROLLBAR);
1347 this->industries.SetListing(this->last_sorting);
1348 this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1349 this->industries.ForceRebuild();
1350 this->BuildSortIndustriesList();
1352 this->InitNested(0);
1355 void OnDelete (void) FINAL_OVERRIDE
1357 this->last_sorting = this->industries.GetListing();
1360 virtual void SetStringParameters(int widget) const
1362 if (widget == WID_ID_DROPDOWN_CRITERIA) SetDParam(0, IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1365 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
1367 switch (widget) {
1368 case WID_ID_DROPDOWN_ORDER:
1369 this->DrawSortButtonState (dpi, widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1370 break;
1372 case WID_ID_INDUSTRY_LIST: {
1373 int n = 0;
1374 int y = r.top + WD_FRAMERECT_TOP;
1375 if (this->industries.Length() == 0) {
1376 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_DIRECTORY_NONE);
1377 break;
1379 for (uint i = this->vscroll->GetPosition(); i < this->industries.Length(); i++) {
1380 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, this->GetIndustryString(this->industries[i]));
1382 y += this->resize.step_height;
1383 if (++n == this->vscroll->GetCapacity()) break; // max number of industries in 1 window
1385 break;
1390 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1392 switch (widget) {
1393 case WID_ID_DROPDOWN_ORDER: {
1394 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1395 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1396 d.height += padding.height;
1397 *size = maxdim(*size, d);
1398 break;
1401 case WID_ID_DROPDOWN_CRITERIA: {
1402 Dimension d = {0, 0};
1403 for (uint i = 0; IndustryDirectoryWindow::sorter_names[i] != INVALID_STRING_ID; i++) {
1404 d = maxdim(d, GetStringBoundingBox(IndustryDirectoryWindow::sorter_names[i]));
1406 d.width += padding.width;
1407 d.height += padding.height;
1408 *size = maxdim(*size, d);
1409 break;
1412 case WID_ID_INDUSTRY_LIST: {
1413 Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1414 for (uint i = 0; i < this->industries.Length(); i++) {
1415 d = maxdim(d, GetStringBoundingBox(this->GetIndustryString(this->industries[i])));
1417 resize->height = d.height;
1418 d.height *= 5;
1419 d.width += padding.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1420 d.height += padding.height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
1421 *size = maxdim(*size, d);
1422 break;
1428 virtual void OnClick(Point pt, int widget, int click_count)
1430 switch (widget) {
1431 case WID_ID_DROPDOWN_ORDER:
1432 this->industries.ToggleSortOrder();
1433 this->SetDirty();
1434 break;
1436 case WID_ID_DROPDOWN_CRITERIA:
1437 ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1438 break;
1440 case WID_ID_INDUSTRY_LIST: {
1441 uint p = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_ID_INDUSTRY_LIST, WD_FRAMERECT_TOP);
1442 if (p < this->industries.Length()) {
1443 if (_ctrl_pressed) {
1444 ShowExtraViewPortWindow(this->industries[p]->location.tile);
1445 } else {
1446 ScrollMainWindowToTile(this->industries[p]->location.tile);
1449 break;
1454 virtual void OnDropdownSelect(int widget, int index)
1456 if (this->industries.SortType() != index) {
1457 this->industries.SetSortType(index);
1458 this->BuildSortIndustriesList();
1462 virtual void OnResize()
1464 this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST);
1467 void OnPaint (BlitArea *dpi) OVERRIDE
1469 if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1470 this->DrawWidgets (dpi);
1473 virtual void OnHundredthTick()
1475 this->industries.ForceResort();
1476 this->BuildSortIndustriesList();
1480 * Some data on this window has become invalid.
1481 * @param data Information about the changed data.
1482 * @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.
1484 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1486 if (data == 0) {
1487 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1488 this->industries.ForceRebuild();
1489 } else {
1490 this->industries.ForceResort();
1495 Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1496 const Industry *IndustryDirectoryWindow::last_industry = NULL;
1498 /* Available station sorting functions. */
1499 GUIIndustryList::SortFunction * const IndustryDirectoryWindow::sorter_funcs[] = {
1500 &IndustryNameSorter,
1501 &IndustryTypeSorter,
1502 &IndustryProductionSorter,
1503 &IndustryTransportedCargoSorter
1506 /* Names of the sorting functions */
1507 const StringID IndustryDirectoryWindow::sorter_names[] = {
1508 STR_SORT_BY_NAME,
1509 STR_SORT_BY_TYPE,
1510 STR_SORT_BY_PRODUCTION,
1511 STR_SORT_BY_TRANSPORTED,
1512 INVALID_STRING_ID
1516 /** Window preferences of the industry directory gui */
1517 static WindowDesc::Prefs _industry_directory_prefs ("list_industries");
1519 /** Window definition of the industry directory gui */
1520 static const WindowDesc _industry_directory_desc(
1521 WDP_AUTO, 428, 190,
1522 WC_INDUSTRY_DIRECTORY, WC_NONE,
1524 _nested_industry_directory_widgets, lengthof(_nested_industry_directory_widgets),
1525 &_industry_directory_prefs
1528 void ShowIndustryDirectory()
1530 AllocateWindowDescFront<IndustryDirectoryWindow>(&_industry_directory_desc, 0);
1533 /** Widgets of the industry cargoes window. */
1534 static const NWidgetPart _nested_industry_cargoes_widgets[] = {
1535 NWidget(NWID_HORIZONTAL),
1536 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1537 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION), SetDataTip(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1538 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1539 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1540 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1541 EndContainer(),
1542 NWidget(NWID_HORIZONTAL),
1543 NWidget(NWID_VERTICAL),
1544 NWidget(WWT_PANEL, COLOUR_BROWN, WID_IC_PANEL), SetResize(1, 10), SetMinimalSize(200, 90), SetScrollbar(WID_IC_SCROLLBAR), EndContainer(),
1545 NWidget(NWID_HORIZONTAL),
1546 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1547 SetDataTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1548 NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1549 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1550 SetDataTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1551 NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1552 SetDataTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1553 EndContainer(),
1554 EndContainer(),
1555 NWidget(NWID_VERTICAL),
1556 NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_IC_SCROLLBAR),
1557 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1558 EndContainer(),
1559 EndContainer(),
1562 /** Window preferences for the industry cargoes window. */
1563 static WindowDesc::Prefs _industry_cargoes_prefs ("industry_cargoes");
1565 /** Window description for the industry cargoes window. */
1566 static const WindowDesc _industry_cargoes_desc(
1567 WDP_AUTO, 300, 210,
1568 WC_INDUSTRY_CARGOES, WC_NONE,
1570 _nested_industry_cargoes_widgets, lengthof(_nested_industry_cargoes_widgets),
1571 &_industry_cargoes_prefs
1574 /** Available types of field. */
1575 enum CargoesFieldType {
1576 CFT_EMPTY, ///< Empty field.
1577 CFT_SMALL_EMPTY, ///< Empty small field (for the header).
1578 CFT_INDUSTRY, ///< Display industry.
1579 CFT_CARGO, ///< Display cargo connections.
1580 CFT_CARGO_LABEL, ///< Display cargo labels.
1581 CFT_HEADER, ///< Header text.
1584 static const uint MAX_CARGOES = 3; ///< Maximum number of cargoes carried in a #CFT_CARGO field in #CargoesField.
1586 /** Data about a single field in the #IndustryCargoesWindow panel. */
1587 struct CargoesField {
1588 static const int VERT_INTER_INDUSTRY_SPACE;
1589 static const int HOR_CARGO_BORDER_SPACE;
1590 static const int CARGO_STUB_WIDTH;
1591 static const int HOR_CARGO_WIDTH, HOR_CARGO_SPACE;
1592 static const int CARGO_FIELD_WIDTH;
1593 static const int VERT_CARGO_SPACE, VERT_CARGO_EDGE;
1594 static const int BLOB_DISTANCE, BLOB_WIDTH, BLOB_HEIGHT;
1596 static const int INDUSTRY_LINE_COLOUR;
1597 static const int CARGO_LINE_COLOUR;
1599 static int small_height, normal_height;
1600 static int industry_width;
1602 CargoesFieldType type; ///< Type of field.
1603 union {
1604 struct {
1605 IndustryType ind_type; ///< Industry type (#NUM_INDUSTRYTYPES means 'houses').
1606 CargoID other_produced[MAX_CARGOES]; ///< Cargoes produced but not used in this figure.
1607 CargoID other_accepted[MAX_CARGOES]; ///< Cargoes accepted but not used in this figure.
1608 } industry; ///< Industry data (for #CFT_INDUSTRY).
1609 struct {
1610 CargoID vertical_cargoes[MAX_CARGOES]; ///< Cargoes running from top to bottom (cargo ID or #INVALID_CARGO).
1611 byte num_cargoes; ///< Number of cargoes.
1612 CargoID supp_cargoes[MAX_CARGOES]; ///< Cargoes entering from the left (index in #vertical_cargoes, or #INVALID_CARGO).
1613 byte top_end; ///< Stop at the top of the vertical cargoes.
1614 CargoID cust_cargoes[MAX_CARGOES]; ///< Cargoes leaving to the right (index in #vertical_cargoes, or #INVALID_CARGO).
1615 byte bottom_end; ///< Stop at the bottom of the vertical cargoes.
1616 } cargo; ///< Cargo data (for #CFT_CARGO).
1617 struct {
1618 CargoID cargoes[MAX_CARGOES]; ///< Cargoes to display (or #INVALID_CARGO).
1619 bool left_align; ///< Align all cargo texts to the left (else align to the right).
1620 } cargo_label; ///< Label data (for #CFT_CARGO_LABEL).
1621 StringID header; ///< Header text (for #CFT_HEADER).
1622 } u; // Data for each type.
1625 * Make one of the empty fields (#CFT_EMPTY or #CFT_SMALL_EMPTY).
1626 * @param type Type of empty field.
1628 void MakeEmpty(CargoesFieldType type)
1630 this->type = type;
1634 * Make an industry type field.
1635 * @param ind_type Industry type (#NUM_INDUSTRYTYPES means 'houses').
1636 * @note #other_accepted and #other_produced should be filled later.
1638 void MakeIndustry(IndustryType ind_type)
1640 this->type = CFT_INDUSTRY;
1641 this->u.industry.ind_type = ind_type;
1642 MemSetT(this->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
1643 MemSetT(this->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
1647 * Connect a cargo from an industry to the #CFT_CARGO column.
1648 * @param cargo Cargo to connect.
1649 * @param produced Cargo is produced (if \c false, cargo is assumed to be accepted).
1650 * @return Horizontal connection index, or \c -1 if not accepted at all.
1652 int ConnectCargo(CargoID cargo, bool producer)
1654 assert(this->type == CFT_CARGO);
1655 if (cargo == INVALID_CARGO) return -1;
1657 /* Find the vertical cargo column carrying the cargo. */
1658 int column = -1;
1659 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
1660 if (cargo == this->u.cargo.vertical_cargoes[i]) {
1661 column = i;
1662 break;
1665 if (column < 0) return -1;
1667 if (producer) {
1668 assert(this->u.cargo.supp_cargoes[column] == INVALID_CARGO);
1669 this->u.cargo.supp_cargoes[column] = column;
1670 } else {
1671 assert(this->u.cargo.cust_cargoes[column] == INVALID_CARGO);
1672 this->u.cargo.cust_cargoes[column] = column;
1674 return column;
1678 * Does this #CFT_CARGO field have a horizontal connection?
1679 * @return \c true if a horizontal connection exists, \c false otherwise.
1681 bool HasConnection()
1683 assert(this->type == CFT_CARGO);
1685 for (uint i = 0; i < MAX_CARGOES; i++) {
1686 if (this->u.cargo.supp_cargoes[i] != INVALID_CARGO) return true;
1687 if (this->u.cargo.cust_cargoes[i] != INVALID_CARGO) return true;
1689 return false;
1693 * Make a piece of cargo column.
1694 * @param cargoes Array of #CargoID (may contain #INVALID_CARGO).
1695 * @param length Number of cargoes in \a cargoes.
1696 * @param count Number of cargoes to display (should be at least the number of valid cargoes, or \c -1 to let the method compute it).
1697 * @param top_end This is the first cargo field of this column.
1698 * @param bottom_end This is the last cargo field of this column.
1699 * @note #supp_cargoes and #cust_cargoes should be filled in later.
1701 void MakeCargo(const CargoID *cargoes, uint length, int count = -1, bool top_end = false, bool bottom_end = false)
1703 this->type = CFT_CARGO;
1704 uint i;
1705 uint num = 0;
1706 for (i = 0; i < MAX_CARGOES && i < length; i++) {
1707 if (cargoes[i] != INVALID_CARGO) {
1708 this->u.cargo.vertical_cargoes[num] = cargoes[i];
1709 num++;
1712 this->u.cargo.num_cargoes = (count < 0) ? num : count;
1713 for (; num < MAX_CARGOES; num++) this->u.cargo.vertical_cargoes[num] = INVALID_CARGO;
1714 this->u.cargo.top_end = top_end;
1715 this->u.cargo.bottom_end = bottom_end;
1716 MemSetT(this->u.cargo.supp_cargoes, INVALID_CARGO, MAX_CARGOES);
1717 MemSetT(this->u.cargo.cust_cargoes, INVALID_CARGO, MAX_CARGOES);
1721 * Make a field displaying cargo type names.
1722 * @param cargoes Array of #CargoID (may contain #INVALID_CARGO).
1723 * @param length Number of cargoes in \a cargoes.
1724 * @param left_align ALign texts to the left (else to the right).
1726 void MakeCargoLabel(const CargoID *cargoes, uint length, bool left_align)
1728 this->type = CFT_CARGO_LABEL;
1729 uint i;
1730 for (i = 0; i < MAX_CARGOES && i < length; i++) this->u.cargo_label.cargoes[i] = cargoes[i];
1731 for (; i < MAX_CARGOES; i++) this->u.cargo_label.cargoes[i] = INVALID_CARGO;
1732 this->u.cargo_label.left_align = left_align;
1736 * Make a header above an industry column.
1737 * @param textid Text to display.
1739 void MakeHeader(StringID textid)
1741 this->type = CFT_HEADER;
1742 this->u.header = textid;
1746 * For a #CFT_CARGO, compute the left position of the left-most vertical cargo connection.
1747 * @param xpos Left position of the field.
1748 * @return Left position of the left-most vertical cargo column.
1750 int GetCargoBase(int xpos) const
1752 assert(this->type == CFT_CARGO);
1754 switch (this->u.cargo.num_cargoes) {
1755 case 0: return xpos + CARGO_FIELD_WIDTH / 2;
1756 case 1: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH / 2;
1757 case 2: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH - HOR_CARGO_SPACE / 2;
1758 case 3: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH - HOR_CARGO_SPACE - HOR_CARGO_WIDTH / 2;
1759 default: NOT_REACHED();
1764 * Draw the field.
1765 * @param dpi Area to draw on.
1766 * @param xpos Position of the left edge.
1767 * @param vpos Position of the top edge.
1769 void Draw (BlitArea *dpi, int xpos, int ypos) const
1771 switch (this->type) {
1772 case CFT_EMPTY:
1773 case CFT_SMALL_EMPTY:
1774 break;
1776 case CFT_HEADER:
1777 ypos += (small_height - FONT_HEIGHT_NORMAL) / 2;
1778 DrawString (dpi, xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
1779 break;
1781 case CFT_INDUSTRY: {
1782 int ypos1 = ypos + VERT_INTER_INDUSTRY_SPACE / 2;
1783 int ypos2 = ypos + normal_height - 1 - VERT_INTER_INDUSTRY_SPACE / 2;
1784 int xpos2 = xpos + industry_width - 1;
1785 GfxDrawLine (dpi, xpos, ypos1, xpos2, ypos1, INDUSTRY_LINE_COLOUR);
1786 GfxDrawLine (dpi, xpos, ypos1, xpos, ypos2, INDUSTRY_LINE_COLOUR);
1787 GfxDrawLine (dpi, xpos, ypos2, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1788 GfxDrawLine (dpi, xpos2, ypos1, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1789 ypos += (normal_height - FONT_HEIGHT_NORMAL) / 2;
1790 if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
1791 const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
1792 DrawString (dpi, xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
1794 /* Draw the industry legend. */
1795 int blob_left, blob_right;
1796 if (_current_text_dir == TD_RTL) {
1797 blob_right = xpos2 - BLOB_DISTANCE;
1798 blob_left = blob_right - BLOB_WIDTH;
1799 } else {
1800 blob_left = xpos + BLOB_DISTANCE;
1801 blob_right = blob_left + BLOB_WIDTH;
1803 GfxFillRect (dpi, blob_left, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT, blob_right, ypos2 - BLOB_DISTANCE, PC_BLACK); // Border
1804 GfxFillRect (dpi, blob_left + 1, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT + 1, blob_right - 1, ypos2 - BLOB_DISTANCE - 1, indsp->map_colour);
1805 } else {
1806 DrawString (dpi, xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
1809 /* Draw the other_produced/other_accepted cargoes. */
1810 const CargoID *other_right, *other_left;
1811 if (_current_text_dir == TD_RTL) {
1812 other_right = this->u.industry.other_accepted;
1813 other_left = this->u.industry.other_produced;
1814 } else {
1815 other_right = this->u.industry.other_produced;
1816 other_left = this->u.industry.other_accepted;
1818 ypos1 += VERT_CARGO_EDGE;
1819 for (uint i = 0; i < MAX_CARGOES; i++) {
1820 if (other_right[i] != INVALID_CARGO) {
1821 const CargoSpec *csp = CargoSpec::Get(other_right[i]);
1822 int xp = xpos + industry_width + CARGO_STUB_WIDTH;
1823 DrawHorConnection (dpi, xpos + industry_width, xp - 1, ypos1, csp);
1824 GfxDrawLine (dpi, xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1826 if (other_left[i] != INVALID_CARGO) {
1827 const CargoSpec *csp = CargoSpec::Get(other_left[i]);
1828 int xp = xpos - CARGO_STUB_WIDTH;
1829 DrawHorConnection (dpi, xp + 1, xpos - 1, ypos1, csp);
1830 GfxDrawLine (dpi, xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1832 ypos1 += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
1834 break;
1837 case CFT_CARGO: {
1838 int cargo_base = this->GetCargoBase(xpos);
1839 int top = ypos + (this->u.cargo.top_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0);
1840 int bot = ypos - (this->u.cargo.bottom_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0) + normal_height - 1;
1841 int colpos = cargo_base;
1842 for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
1843 if (this->u.cargo.top_end) GfxDrawLine (dpi, colpos, top - 1, colpos + HOR_CARGO_WIDTH - 1, top - 1, CARGO_LINE_COLOUR);
1844 if (this->u.cargo.bottom_end) GfxDrawLine (dpi, colpos, bot + 1, colpos + HOR_CARGO_WIDTH - 1, bot + 1, CARGO_LINE_COLOUR);
1845 GfxDrawLine (dpi, colpos, top, colpos, bot, CARGO_LINE_COLOUR);
1846 colpos++;
1847 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
1848 GfxFillRect (dpi, colpos, top, colpos + HOR_CARGO_WIDTH - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
1849 colpos += HOR_CARGO_WIDTH - 2;
1850 GfxDrawLine (dpi, colpos, top, colpos, bot, CARGO_LINE_COLOUR);
1851 colpos += 1 + HOR_CARGO_SPACE;
1854 const CargoID *hor_left, *hor_right;
1855 if (_current_text_dir == TD_RTL) {
1856 hor_left = this->u.cargo.cust_cargoes;
1857 hor_right = this->u.cargo.supp_cargoes;
1858 } else {
1859 hor_left = this->u.cargo.supp_cargoes;
1860 hor_right = this->u.cargo.cust_cargoes;
1862 ypos += VERT_CARGO_EDGE + VERT_INTER_INDUSTRY_SPACE / 2;
1863 for (uint i = 0; i < MAX_CARGOES; i++) {
1864 if (hor_left[i] != INVALID_CARGO) {
1865 int col = hor_left[i];
1866 int dx = 0;
1867 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
1868 for (; col > 0; col--) {
1869 int lf = cargo_base + col * HOR_CARGO_WIDTH + (col - 1) * HOR_CARGO_SPACE;
1870 DrawHorConnection (dpi, lf, lf + HOR_CARGO_SPACE - dx, ypos, csp);
1871 dx = 1;
1873 DrawHorConnection (dpi, xpos, cargo_base - dx, ypos, csp);
1875 if (hor_right[i] != INVALID_CARGO) {
1876 int col = hor_right[i];
1877 int dx = 0;
1878 const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
1879 for (; col < this->u.cargo.num_cargoes - 1; col++) {
1880 int lf = cargo_base + (col + 1) * HOR_CARGO_WIDTH + col * HOR_CARGO_SPACE;
1881 DrawHorConnection (dpi, lf + dx - 1, lf + HOR_CARGO_SPACE - 1, ypos, csp);
1882 dx = 1;
1884 DrawHorConnection (dpi, cargo_base + col * HOR_CARGO_SPACE + (col + 1) * HOR_CARGO_WIDTH - 1 + dx, xpos + CARGO_FIELD_WIDTH - 1, ypos, csp);
1886 ypos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
1888 break;
1891 case CFT_CARGO_LABEL:
1892 ypos += VERT_CARGO_EDGE + VERT_INTER_INDUSTRY_SPACE / 2;
1893 for (uint i = 0; i < MAX_CARGOES; i++) {
1894 if (this->u.cargo_label.cargoes[i] != INVALID_CARGO) {
1895 const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
1896 DrawString (dpi, xpos + WD_FRAMERECT_LEFT, xpos + industry_width - 1 - WD_FRAMERECT_RIGHT, ypos, csp->name, TC_WHITE,
1897 (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
1899 ypos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
1901 break;
1903 default:
1904 NOT_REACHED();
1909 * Decide which cargo was clicked at in a #CFT_CARGO field.
1910 * @param left Left industry neighbour if available (else \c NULL should be supplied).
1911 * @param right Right industry neighbour if available (else \c NULL should be supplied).
1912 * @param pt Click position in the cargo field.
1913 * @return Cargo clicked at, or #INVALID_CARGO if none.
1915 CargoID CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
1917 assert(this->type == CFT_CARGO);
1919 /* Vertical matching. */
1920 int cpos = this->GetCargoBase(0);
1921 uint col;
1922 for (col = 0; col < this->u.cargo.num_cargoes; col++) {
1923 if (pt.x < cpos) break;
1924 if (pt.x < cpos + CargoesField::HOR_CARGO_WIDTH) return this->u.cargo.vertical_cargoes[col];
1925 cpos += CargoesField::HOR_CARGO_WIDTH + CargoesField::HOR_CARGO_SPACE;
1927 /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
1929 int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
1930 uint row;
1931 for (row = 0; row < MAX_CARGOES; row++) {
1932 if (pt.y < vpos) return INVALID_CARGO;
1933 if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
1934 vpos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
1936 if (row == MAX_CARGOES) return INVALID_CARGO;
1938 /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
1939 if (col == 0) {
1940 if (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]];
1941 if (left != NULL) {
1942 if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
1943 if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
1945 return INVALID_CARGO;
1947 if (col == this->u.cargo.num_cargoes) {
1948 if (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]];
1949 if (right != NULL) {
1950 if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
1951 if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
1953 return INVALID_CARGO;
1955 if (row >= col) {
1956 /* Clicked somewhere in-between vertical cargo connection.
1957 * Since the horizontal connection is made in the same order as the vertical list, the above condition
1958 * ensures we are left-below the main diagonal, thus at the supplying side.
1960 return (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]] : INVALID_CARGO;
1961 } else {
1962 /* Clicked at a customer connection. */
1963 return (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]] : INVALID_CARGO;
1968 * Decide what cargo the user clicked in the cargo label field.
1969 * @param pt Click position in the cargo label field.
1970 * @return Cargo clicked at, or #INVALID_CARGO if none.
1972 CargoID CargoLabelClickedAt(Point pt) const
1974 assert(this->type == CFT_CARGO_LABEL);
1976 int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
1977 uint row;
1978 for (row = 0; row < MAX_CARGOES; row++) {
1979 if (pt.y < vpos) return INVALID_CARGO;
1980 if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
1981 vpos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
1983 if (row == MAX_CARGOES) return INVALID_CARGO;
1984 return this->u.cargo_label.cargoes[row];
1987 private:
1989 * Draw a horizontal cargo connection.
1990 * @param dpi Area to draw on.
1991 * @param left Left-most coordinate to draw.
1992 * @param right Right-most coordinate to draw.
1993 * @param top Top coordinate of the cargo connection.
1994 * @param csp Cargo to draw.
1996 static void DrawHorConnection (BlitArea *dpi,
1997 int left, int right, int top, const CargoSpec *csp)
1999 GfxDrawLine (dpi, left, top, right, top, CARGO_LINE_COLOUR);
2000 GfxFillRect (dpi, left, top + 1, right, top + FONT_HEIGHT_NORMAL - 2, csp->legend_colour, FILLRECT_OPAQUE);
2001 GfxDrawLine (dpi, left, top + FONT_HEIGHT_NORMAL - 1, right, top + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
2005 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, produced_cargo));
2006 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, accepts_cargo));
2008 int CargoesField::small_height; ///< Height of the header row.
2009 int CargoesField::normal_height; ///< Height of the non-header rows.
2010 int CargoesField::industry_width; ///< Width of an industry field.
2011 const int CargoesField::VERT_INTER_INDUSTRY_SPACE = 6; ///< Amount of space between two industries in a column.
2013 const int CargoesField::HOR_CARGO_BORDER_SPACE = 15; ///< Amount of space between the left/right edge of a #CFT_CARGO field, and the left/right most vertical cargo.
2014 const int CargoesField::CARGO_STUB_WIDTH = 10; ///< Width of a cargo not carried in the column (should be less than #HOR_CARGO_BORDER_SPACE).
2015 const int CargoesField::HOR_CARGO_WIDTH = 15; ///< Width of a vertical cargo column (inclusive the border line).
2016 const int CargoesField::HOR_CARGO_SPACE = 5; ///< Amount of horizontal space between two vertical cargoes.
2017 const int CargoesField::VERT_CARGO_EDGE = 4; ///< Amount of vertical space between top/bottom and the top/bottom connected cargo at an industry.
2018 const int CargoesField::VERT_CARGO_SPACE = 4; ///< Amount of vertical space between two connected cargoes at an industry.
2020 const int CargoesField::BLOB_DISTANCE = 5; ///< Distance of the industry legend colour from the edge of the industry box.
2021 const int CargoesField::BLOB_WIDTH = 12; ///< Width of the industry legend colour, including border.
2022 const int CargoesField::BLOB_HEIGHT = 9; ///< Height of the industry legend colour, including border
2024 /** Width of a #CFT_CARGO field. */
2025 const int CargoesField::CARGO_FIELD_WIDTH = HOR_CARGO_BORDER_SPACE * 2 + HOR_CARGO_WIDTH * MAX_CARGOES + HOR_CARGO_SPACE * (MAX_CARGOES - 1);
2027 const int CargoesField::INDUSTRY_LINE_COLOUR = PC_YELLOW; ///< Line colour of the industry type box.
2028 const int CargoesField::CARGO_LINE_COLOUR = PC_YELLOW; ///< Line colour around the cargo.
2030 /** A single row of #CargoesField. */
2031 struct CargoesRow {
2032 CargoesField columns[5]; ///< One row of fields.
2035 * Connect industry production cargoes to the cargo column after it.
2036 * @param column Column of the industry.
2038 void ConnectIndustryProduced(int column)
2040 CargoesField *ind_fld = this->columns + column;
2041 CargoesField *cargo_fld = this->columns + column + 1;
2042 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2044 MemSetT(ind_fld->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
2046 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2047 CargoID others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
2048 int other_count = 0;
2050 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2051 for (uint i = 0; i < lengthof(indsp->produced_cargo); i++) {
2052 int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
2053 if (col < 0) others[other_count++] = indsp->produced_cargo[i];
2056 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2057 for (uint i = 0; i < MAX_CARGOES && other_count > 0; i++) {
2058 if (cargo_fld->u.cargo.supp_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_produced[i] = others[--other_count];
2060 } else {
2061 /* Houses only display what is demanded. */
2062 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2063 CargoID cid = cargo_fld->u.cargo.vertical_cargoes[i];
2064 if (cid == CT_PASSENGERS || cid == CT_MAIL) cargo_fld->ConnectCargo(cid, true);
2070 * Construct a #CFT_CARGO_LABEL field.
2071 * @param column Column to create the new field.
2072 * @param accepting Display accepted cargo (if \c false, display produced cargo).
2074 void MakeCargoLabel(int column, bool accepting)
2076 CargoID cargoes[MAX_CARGOES];
2077 MemSetT(cargoes, INVALID_CARGO, lengthof(cargoes));
2079 CargoesField *label_fld = this->columns + column;
2080 CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2082 assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2083 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2084 int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2085 if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2087 label_fld->MakeCargoLabel(cargoes, lengthof(cargoes), accepting);
2092 * Connect industry accepted cargoes to the cargo column before it.
2093 * @param column Column of the industry.
2095 void ConnectIndustryAccepted(int column)
2097 CargoesField *ind_fld = this->columns + column;
2098 CargoesField *cargo_fld = this->columns + column - 1;
2099 assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2101 MemSetT(ind_fld->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
2103 if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2104 CargoID others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2105 int other_count = 0;
2107 const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2108 for (uint i = 0; i < lengthof(indsp->accepts_cargo); i++) {
2109 int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2110 if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2113 /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2114 for (uint i = 0; i < MAX_CARGOES && other_count > 0; i++) {
2115 if (cargo_fld->u.cargo.cust_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2117 } else {
2118 /* Houses only display what is demanded. */
2119 for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2120 for (uint h = 0; h < NUM_HOUSES; h++) {
2121 HouseSpec *hs = HouseSpec::Get(h);
2122 if (!hs->enabled) continue;
2124 for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2125 if (hs->cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs->accepts_cargo[j]) {
2126 cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2127 goto next_cargo;
2131 next_cargo: ;
2139 * Window displaying the cargo connections around an industry (or cargo).
2141 * The main display is constructed from 'fields', rectangles that contain an industry, piece of the cargo connection, cargo labels, or headers.
2142 * For a nice display, the following should be kept in mind:
2143 * - A #CFT_HEADER is always at the top of an column of #CFT_INDUSTRY fields.
2144 * - A #CFT_CARGO_LABEL field is also always put in a column of #CFT_INDUSTRY fields.
2145 * - The top row contains #CFT_HEADER and #CFT_SMALL_EMPTY fields.
2146 * - Cargo connections have a column of their own (#CFT_CARGO fields).
2147 * - 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.
2148 * The information however is part of the industry.
2150 * This results in the following invariants:
2151 * - Width of a #CFT_INDUSTRY column is large enough to hold all industry type labels, all cargo labels, and all header texts.
2152 * - Height of a #CFT_INDUSTRY is large enough to hold a header line, or a industry type line, \c N cargo labels
2153 * (where \c N is the maximum number of cargoes connected between industries), \c N connections of cargo types, and space
2154 * between two industry types (1/2 above it, and 1/2 underneath it).
2155 * - Width of a cargo field (#CFT_CARGO) is large enough to hold \c N vertical columns (one for each type of cargo).
2156 * Also, space is needed between an industry and the leftmost/rightmost column to draw the non-carried cargoes.
2157 * - Height of a #CFT_CARGO field is equally high as the height of the #CFT_INDUSTRY.
2158 * - A field at the top (#CFT_HEADER or #CFT_SMALL_EMPTY) match the width of the fields below them (#CFT_INDUSTRY respectively
2159 * #CFT_CARGO), the height should be sufficient to display the header text.
2161 * When displaying the cargoes around an industry type, five columns are needed (supplying industries, accepted cargoes, the industry,
2162 * produced cargoes, customer industries). Displaying the industries around a cargo needs three columns (supplying industries, the cargo,
2163 * 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.
2165 struct IndustryCargoesWindow : public Window {
2166 static const int HOR_TEXT_PADDING, VERT_TEXT_PADDING;
2168 typedef SmallVector<CargoesRow, 4> Fields;
2170 Fields fields; ///< Fields to display in the #WID_IC_PANEL.
2171 uint ind_cargo; ///< If less than #NUM_INDUSTRYTYPES, an industry type, else a cargo id + NUM_INDUSTRYTYPES.
2172 Dimension cargo_textsize; ///< Size to hold any cargo text, as well as STR_INDUSTRY_CARGOES_SELECT_CARGO.
2173 Dimension ind_textsize; ///< Size to hold any industry type text, as well as STR_INDUSTRY_CARGOES_SELECT_INDUSTRY.
2174 Scrollbar *vscroll;
2176 IndustryCargoesWindow (int id) : Window (&_industry_cargoes_desc),
2177 fields(), ind_cargo (0), vscroll (NULL), type (CFT_EMPTY)
2179 this->cargo_textsize.width = 0;
2180 this->cargo_textsize.height = 0;
2181 this->ind_textsize.width = 0;
2182 this->ind_textsize.height = 0;
2184 this->OnInit();
2185 this->CreateNestedTree();
2186 this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2187 this->InitNested(0);
2188 this->OnInvalidateData(id);
2191 virtual void OnInit()
2193 /* Initialize static CargoesField size variables. */
2194 Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2195 d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2196 d.width += WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
2197 d.height += WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
2198 CargoesField::small_height = d.height;
2200 /* Decide about the size of the box holding the text of an industry type. */
2201 this->ind_textsize.width = 0;
2202 this->ind_textsize.height = 0;
2203 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2204 const IndustrySpec *indsp = GetIndustrySpec(it);
2205 if (!indsp->enabled) continue;
2206 this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(indsp->name));
2208 d.width = max(d.width, this->ind_textsize.width);
2209 d.height = this->ind_textsize.height;
2210 this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2212 /* Compute max size of the cargo texts. */
2213 this->cargo_textsize.width = 0;
2214 this->cargo_textsize.height = 0;
2215 for (uint i = 0; i < NUM_CARGO; i++) {
2216 const CargoSpec *csp = CargoSpec::Get(i);
2217 if (!csp->IsValid()) continue;
2218 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(csp->name));
2220 d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2221 this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2223 d.width += 2 * HOR_TEXT_PADDING;
2224 /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2225 uint min_ind_height = CargoesField::VERT_CARGO_EDGE * 2 + MAX_CARGOES * FONT_HEIGHT_NORMAL + (MAX_CARGOES - 1) * CargoesField::VERT_CARGO_SPACE;
2226 d.height = max(d.height + 2 * VERT_TEXT_PADDING, min_ind_height);
2228 CargoesField::industry_width = d.width;
2229 CargoesField::normal_height = d.height + CargoesField::VERT_INTER_INDUSTRY_SPACE;
2232 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2234 switch (widget) {
2235 case WID_IC_PANEL:
2236 size->width = WD_FRAMETEXT_LEFT + CargoesField::industry_width * 3 + CargoesField::CARGO_FIELD_WIDTH * 2 + WD_FRAMETEXT_RIGHT;
2237 break;
2239 case WID_IC_IND_DROPDOWN:
2240 size->width = max(size->width, this->ind_textsize.width + padding.width);
2241 break;
2243 case WID_IC_CARGO_DROPDOWN:
2244 size->width = max(size->width, this->cargo_textsize.width + padding.width);
2245 break;
2250 CargoesFieldType type; ///< Type of field.
2251 virtual void SetStringParameters (int widget) const
2253 if (widget != WID_IC_CAPTION) return;
2255 if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2256 const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2257 SetDParam(0, indsp->name);
2258 } else {
2259 const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2260 SetDParam(0, csp->name);
2265 * Do the two sets of cargoes have a valid cargo in common?
2266 * @param cargoes1 Base address of the first cargo array.
2267 * @param length1 Number of cargoes in the first cargo array.
2268 * @param cargoes2 Base address of the second cargo array.
2269 * @param length2 Number of cargoes in the second cargo array.
2270 * @return Arrays have at least one valid cargo in common.
2272 static bool HasCommonValidCargo(const CargoID *cargoes1, uint length1, const CargoID *cargoes2, uint length2)
2274 while (length1 > 0) {
2275 if (*cargoes1 != INVALID_CARGO) {
2276 for (uint i = 0; i < length2; i++) if (*cargoes1 == cargoes2[i]) return true;
2278 cargoes1++;
2279 length1--;
2281 return false;
2285 * Can houses be used to supply one of the cargoes?
2286 * @param cargoes Base address of the cargo array.
2287 * @param length Number of cargoes in the array.
2288 * @return Houses can supply at least one of the cargoes.
2290 static bool HousesCanSupply(const CargoID *cargoes, uint length)
2292 for (uint i = 0; i < length; i++) {
2293 if (cargoes[i] == INVALID_CARGO) continue;
2294 if (cargoes[i] == CT_PASSENGERS || cargoes[i] == CT_MAIL) return true;
2296 return false;
2300 * Can houses be used as customers of the produced cargoes?
2301 * @param cargoes Base address of the cargo array.
2302 * @param length Number of cargoes in the array.
2303 * @return Houses can accept at least one of the cargoes.
2305 static bool HousesCanAccept(const CargoID *cargoes, uint length)
2307 HouseZones climate_mask;
2308 switch (_settings_game.game_creation.landscape) {
2309 case LT_TEMPERATE: climate_mask = HZ_TEMP; break;
2310 case LT_ARCTIC: climate_mask = HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW; break;
2311 case LT_TROPIC: climate_mask = HZ_SUBTROPIC; break;
2312 case LT_TOYLAND: climate_mask = HZ_TOYLND; break;
2313 default: NOT_REACHED();
2315 for (uint i = 0; i < length; i++) {
2316 if (cargoes[i] == INVALID_CARGO) continue;
2318 for (uint h = 0; h < NUM_HOUSES; h++) {
2319 HouseSpec *hs = HouseSpec::Get(h);
2320 if (!hs->enabled || !(hs->building_availability & climate_mask)) continue;
2322 for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2323 if (hs->cargo_acceptance[j] > 0 && cargoes[i] == hs->accepts_cargo[j]) return true;
2327 return false;
2331 * Count how many industries have accepted cargoes in common with one of the supplied set.
2332 * @param cargoes Cargoes to search.
2333 * @param length Number of cargoes in \a cargoes.
2334 * @return Number of industries that have an accepted cargo in common with the supplied set.
2336 static int CountMatchingAcceptingIndustries(const CargoID *cargoes, uint length)
2338 int count = 0;
2339 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2340 const IndustrySpec *indsp = GetIndustrySpec(it);
2341 if (!indsp->enabled) continue;
2343 if (HasCommonValidCargo(cargoes, length, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) count++;
2345 return count;
2349 * Count how many industries have produced cargoes in common with one of the supplied set.
2350 * @param cargoes Cargoes to search.
2351 * @param length Number of cargoes in \a cargoes.
2352 * @return Number of industries that have a produced cargo in common with the supplied set.
2354 static int CountMatchingProducingIndustries(const CargoID *cargoes, uint length)
2356 int count = 0;
2357 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2358 const IndustrySpec *indsp = GetIndustrySpec(it);
2359 if (!indsp->enabled) continue;
2361 if (HasCommonValidCargo(cargoes, length, indsp->produced_cargo, lengthof(indsp->produced_cargo))) count++;
2363 return count;
2367 * Shorten the cargo column to just the part between industries.
2368 * @param column Column number of the cargo column.
2369 * @param top Current top row.
2370 * @param bottom Current bottom row.
2372 void ShortenCargoColumn(int column, int top, int bottom)
2374 while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2375 this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2376 top++;
2378 this->fields[top].columns[column].u.cargo.top_end = true;
2380 while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2381 this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2382 bottom--;
2384 this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2388 * Place an industry in the fields.
2389 * @param row Row of the new industry.
2390 * @param col Column of the new industry.
2391 * @param it Industry to place.
2393 void PlaceIndustry(int row, int col, IndustryType it)
2395 assert(this->fields[row].columns[col].type == CFT_EMPTY);
2396 this->fields[row].columns[col].MakeIndustry(it);
2397 if (col == 0) {
2398 this->fields[row].ConnectIndustryProduced(col);
2399 } else {
2400 this->fields[row].ConnectIndustryAccepted(col);
2405 * Notify smallmap that new displayed industries have been selected (in #_displayed_industries).
2407 void NotifySmallmap()
2409 if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2411 /* Only notify the smallmap window if it exists. In particular, do not
2412 * bring it to the front to prevent messing up any nice layout of the user. */
2413 InvalidateWindowClassesData(WC_SMALLMAP, 0);
2417 * Compute what and where to display for industry type \a it.
2418 * @param it Industry type to display.
2420 void ComputeIndustryDisplay(IndustryType it)
2422 this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION;
2423 this->ind_cargo = it;
2424 _displayed_industries.reset();
2425 _displayed_industries.set(it);
2427 this->fields.Clear();
2428 CargoesRow *row = this->fields.Append();
2429 row->columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2430 row->columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2431 row->columns[2].MakeEmpty(CFT_SMALL_EMPTY);
2432 row->columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2433 row->columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2435 const IndustrySpec *central_sp = GetIndustrySpec(it);
2436 bool houses_supply = HousesCanSupply(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2437 bool houses_accept = HousesCanAccept(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2438 /* Make a field consisting of two cargo columns. */
2439 int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo)) + houses_supply;
2440 int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo, lengthof(central_sp->produced_cargo)) + houses_accept;
2441 int num_indrows = max(3, max(num_supp, num_cust)); // One is needed for the 'it' industry, and 2 for the cargo labels.
2442 for (int i = 0; i < num_indrows; i++) {
2443 CargoesRow *row = this->fields.Append();
2444 row->columns[0].MakeEmpty(CFT_EMPTY);
2445 row->columns[1].MakeCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2446 row->columns[2].MakeEmpty(CFT_EMPTY);
2447 row->columns[3].MakeCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2448 row->columns[4].MakeEmpty(CFT_EMPTY);
2450 /* Add central industry. */
2451 int central_row = 1 + num_indrows / 2;
2452 this->fields[central_row].columns[2].MakeIndustry(it);
2453 this->fields[central_row].ConnectIndustryProduced(2);
2454 this->fields[central_row].ConnectIndustryAccepted(2);
2456 /* Add cargo labels. */
2457 this->fields[central_row - 1].MakeCargoLabel(2, true);
2458 this->fields[central_row + 1].MakeCargoLabel(2, false);
2460 /* Add suppliers and customers of the 'it' industry. */
2461 int supp_count = 0;
2462 int cust_count = 0;
2463 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2464 const IndustrySpec *indsp = GetIndustrySpec(it);
2465 if (!indsp->enabled) continue;
2467 if (HasCommonValidCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo), indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2468 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2469 _displayed_industries.set(it);
2470 supp_count++;
2472 if (HasCommonValidCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo), indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2473 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2474 _displayed_industries.set(it);
2475 cust_count++;
2478 if (houses_supply) {
2479 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2480 supp_count++;
2482 if (houses_accept) {
2483 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2484 cust_count++;
2487 this->ShortenCargoColumn(1, 1, num_indrows);
2488 this->ShortenCargoColumn(3, 1, num_indrows);
2489 const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2490 this->vscroll->SetCount(CeilDiv(WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + CargoesField::small_height + num_indrows * CargoesField::normal_height, nwp->resize_y));
2491 this->SetDirty();
2492 this->NotifySmallmap();
2496 * Compute what and where to display for cargo id \a cid.
2497 * @param cid Cargo id to display.
2499 void ComputeCargoDisplay(CargoID cid)
2501 this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_CARGO_CAPTION;
2502 this->ind_cargo = cid + NUM_INDUSTRYTYPES;
2503 _displayed_industries.reset();
2505 this->fields.Clear();
2506 CargoesRow *row = this->fields.Append();
2507 row->columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2508 row->columns[1].MakeEmpty(CFT_SMALL_EMPTY);
2509 row->columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2510 row->columns[3].MakeEmpty(CFT_SMALL_EMPTY);
2511 row->columns[4].MakeEmpty(CFT_SMALL_EMPTY);
2513 bool houses_supply = HousesCanSupply(&cid, 1);
2514 bool houses_accept = HousesCanAccept(&cid, 1);
2515 int num_supp = CountMatchingProducingIndustries(&cid, 1) + houses_supply + 1; // Ensure room for the cargo label.
2516 int num_cust = CountMatchingAcceptingIndustries(&cid, 1) + houses_accept;
2517 int num_indrows = max(num_supp, num_cust);
2518 for (int i = 0; i < num_indrows; i++) {
2519 CargoesRow *row = this->fields.Append();
2520 row->columns[0].MakeEmpty(CFT_EMPTY);
2521 row->columns[1].MakeCargo(&cid, 1);
2522 row->columns[2].MakeEmpty(CFT_EMPTY);
2523 row->columns[3].MakeEmpty(CFT_EMPTY);
2524 row->columns[4].MakeEmpty(CFT_EMPTY);
2527 this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2529 /* Add suppliers and customers of the cargo. */
2530 int supp_count = 0;
2531 int cust_count = 0;
2532 for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2533 const IndustrySpec *indsp = GetIndustrySpec(it);
2534 if (!indsp->enabled) continue;
2536 if (HasCommonValidCargo(&cid, 1, indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2537 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2538 _displayed_industries.set(it);
2539 supp_count++;
2541 if (HasCommonValidCargo(&cid, 1, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2542 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2543 _displayed_industries.set(it);
2544 cust_count++;
2547 if (houses_supply) {
2548 this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2549 supp_count++;
2551 if (houses_accept) {
2552 this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2553 cust_count++;
2556 this->ShortenCargoColumn(1, 1, num_indrows);
2557 const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2558 this->vscroll->SetCount(CeilDiv(WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + CargoesField::small_height + num_indrows * CargoesField::normal_height, nwp->resize_y));
2559 this->SetDirty();
2560 this->NotifySmallmap();
2564 * Some data on this window has become invalid.
2565 * @param data Information about the changed data.
2566 * - data = 0 .. NUM_INDUSTRYTYPES - 1: Display the chain around the given industry.
2567 * - data = NUM_INDUSTRYTYPES: Stop sending updates to the smallmap window.
2568 * @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.
2570 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2572 if (!gui_scope) return;
2573 if (data == NUM_INDUSTRYTYPES) {
2574 if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2575 this->RaiseWidget(WID_IC_NOTIFY);
2576 this->SetWidgetDirty(WID_IC_NOTIFY);
2578 return;
2581 assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2582 this->ComputeIndustryDisplay(data);
2585 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
2587 if (widget != WID_IC_PANEL) return;
2589 BlitArea tmp_dpi;
2590 int width = r.right - r.left + 1;
2591 int height = r.bottom - r.top + 1 - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM;
2592 if (!InitBlitArea (dpi, &tmp_dpi, r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP, width, height)) return;
2594 int left_pos = WD_FRAMERECT_LEFT;
2595 if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::CARGO_FIELD_WIDTH) / 2;
2596 int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2598 const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2599 int vpos = -this->vscroll->GetPosition() * nwp->resize_y;
2600 for (uint i = 0; i < this->fields.Length(); i++) {
2601 int row_height = (i == 0) ? CargoesField::small_height : CargoesField::normal_height;
2602 if (vpos + row_height >= 0) {
2603 int xpos = left_pos;
2604 int col, dir;
2605 if (_current_text_dir == TD_RTL) {
2606 col = last_column;
2607 dir = -1;
2608 } else {
2609 col = 0;
2610 dir = 1;
2612 while (col >= 0 && col <= last_column) {
2613 this->fields[i].columns[col].Draw (&tmp_dpi, xpos, vpos);
2614 xpos += (col & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width;
2615 col += dir;
2618 vpos += row_height;
2619 if (vpos >= height) break;
2624 * Calculate in which field was clicked, and within the field, at what position.
2625 * @param pt Clicked position in the #WID_IC_PANEL widget.
2626 * @param fieldxy If \c true is returned, field x/y coordinate of \a pt.
2627 * @param xy If \c true is returned, x/y coordinate with in the field.
2628 * @return Clicked at a valid position.
2630 bool CalculatePositionInWidget(Point pt, Point *fieldxy, Point *xy)
2632 const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2633 pt.x -= nw->pos_x;
2634 pt.y -= nw->pos_y;
2636 int vpos = WD_FRAMERECT_TOP + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
2637 if (pt.y < vpos) return false;
2639 int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
2640 if (row + 1 >= (int)this->fields.Length()) return false;
2641 vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
2642 row++; // rebase row to match index of this->fields.
2644 int xpos = 2 * WD_FRAMERECT_LEFT + ((this->ind_cargo < NUM_INDUSTRYTYPES) ? 0 : (CargoesField::industry_width + CargoesField::CARGO_FIELD_WIDTH) / 2);
2645 if (pt.x < xpos) return false;
2646 int column;
2647 for (column = 0; column <= 5; column++) {
2648 int width = (column & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width;
2649 if (pt.x < xpos + width) break;
2650 xpos += width;
2652 int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2653 if (column > num_columns) return false;
2654 xpos = pt.x - xpos;
2656 /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
2657 fieldxy->y = row;
2658 xy->y = vpos;
2659 if (_current_text_dir == TD_RTL) {
2660 fieldxy->x = num_columns - column;
2661 xy->x = ((column & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width) - xpos;
2662 } else {
2663 fieldxy->x = column;
2664 xy->x = xpos;
2666 return true;
2669 virtual void OnClick(Point pt, int widget, int click_count)
2671 switch (widget) {
2672 case WID_IC_PANEL: {
2673 Point fieldxy, xy;
2674 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
2676 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2677 switch (fld->type) {
2678 case CFT_INDUSTRY:
2679 if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES) this->ComputeIndustryDisplay(fld->u.industry.ind_type);
2680 break;
2682 case CFT_CARGO: {
2683 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : NULL;
2684 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : NULL;
2685 CargoID cid = fld->CargoClickedAt(lft, rgt, xy);
2686 if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2687 break;
2690 case CFT_CARGO_LABEL: {
2691 CargoID cid = fld->CargoLabelClickedAt(xy);
2692 if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2693 break;
2696 default:
2697 break;
2699 break;
2702 case WID_IC_NOTIFY:
2703 this->ToggleWidgetLoweredState(WID_IC_NOTIFY);
2704 this->SetWidgetDirty(WID_IC_NOTIFY);
2705 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
2707 if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2708 if (FindWindowByClass(WC_SMALLMAP) == NULL) ShowSmallMap();
2709 this->NotifySmallmap();
2711 break;
2713 case WID_IC_CARGO_DROPDOWN: {
2714 DropDownList *lst = new DropDownList;
2715 const CargoSpec *cs;
2716 FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) {
2717 *lst->Append() = new DropDownListStringItem(cs->name, cs->Index(), false);
2719 if (lst->Length() == 0) {
2720 delete lst;
2721 break;
2723 int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
2724 ShowDropDownList(this, lst, selected, WID_IC_CARGO_DROPDOWN, 0, true);
2725 break;
2728 case WID_IC_IND_DROPDOWN: {
2729 DropDownList *lst = new DropDownList;
2730 for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
2731 IndustryType ind = _sorted_industry_types[i];
2732 const IndustrySpec *indsp = GetIndustrySpec(ind);
2733 if (!indsp->enabled) continue;
2734 *lst->Append() = new DropDownListStringItem(indsp->name, ind, false);
2736 if (lst->Length() == 0) {
2737 delete lst;
2738 break;
2740 int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
2741 ShowDropDownList(this, lst, selected, WID_IC_IND_DROPDOWN, 0, true);
2742 break;
2747 virtual void OnDropdownSelect(int widget, int index)
2749 if (index < 0) return;
2751 switch (widget) {
2752 case WID_IC_CARGO_DROPDOWN:
2753 this->ComputeCargoDisplay(index);
2754 break;
2756 case WID_IC_IND_DROPDOWN:
2757 this->ComputeIndustryDisplay(index);
2758 break;
2762 virtual void OnHover(Point pt, int widget)
2764 if (widget != WID_IC_PANEL) return;
2766 Point fieldxy, xy;
2767 if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
2769 const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2770 CargoID cid = INVALID_CARGO;
2771 switch (fld->type) {
2772 case CFT_CARGO: {
2773 CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : NULL;
2774 CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : NULL;
2775 cid = fld->CargoClickedAt(lft, rgt, xy);
2776 break;
2779 case CFT_CARGO_LABEL: {
2780 cid = fld->CargoLabelClickedAt(xy);
2781 break;
2784 case CFT_INDUSTRY:
2785 if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
2786 GuiShowTooltips(this, STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP, 0, NULL, TCC_HOVER);
2788 return;
2790 default:
2791 break;
2793 if (cid != INVALID_CARGO && (this->ind_cargo < NUM_INDUSTRYTYPES || cid != this->ind_cargo - NUM_INDUSTRYTYPES)) {
2794 const CargoSpec *csp = CargoSpec::Get(cid);
2795 uint64 params[5];
2796 params[0] = csp->name;
2797 GuiShowTooltips(this, STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, 1, params, TCC_HOVER);
2801 virtual void OnResize()
2803 this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL);
2807 const int IndustryCargoesWindow::HOR_TEXT_PADDING = 5; ///< Horizontal padding around the industry type text.
2808 const int IndustryCargoesWindow::VERT_TEXT_PADDING = 5; ///< Vertical padding around the industry type text.
2811 * Open the industry and cargoes window.
2812 * @param id Industry type to display, \c NUM_INDUSTRYTYPES selects a default industry type.
2814 static void ShowIndustryCargoesWindow(IndustryType id)
2816 if (id >= NUM_INDUSTRYTYPES) {
2817 for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
2818 const IndustrySpec *indsp = GetIndustrySpec(_sorted_industry_types[i]);
2819 if (indsp->enabled) {
2820 id = _sorted_industry_types[i];
2821 break;
2824 if (id >= NUM_INDUSTRYTYPES) return;
2827 Window *w = BringWindowToFrontById(WC_INDUSTRY_CARGOES, 0);
2828 if (w != NULL) {
2829 w->InvalidateData(id);
2830 return;
2832 new IndustryCargoesWindow(id);
2835 /** Open the industry and cargoes window with an industry. */
2836 void ShowIndustryCargoesWindow()
2838 ShowIndustryCargoesWindow(NUM_INDUSTRYTYPES);