OpenTTD Source  1.10.0-RC1
industry_gui.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
10 #include "stdafx.h"
11 #include "error.h"
12 #include "gui.h"
13 #include "settings_gui.h"
14 #include "sound_func.h"
15 #include "window_func.h"
16 #include "textbuf_gui.h"
17 #include "command_func.h"
18 #include "viewport_func.h"
19 #include "industry.h"
20 #include "town.h"
21 #include "cheat_type.h"
22 #include "newgrf_industries.h"
23 #include "newgrf_text.h"
24 #include "newgrf_debug.h"
25 #include "network/network.h"
26 #include "strings_func.h"
27 #include "company_func.h"
28 #include "tilehighlight_func.h"
29 #include "string_func.h"
30 #include "sortlist_type.h"
31 #include "widgets/dropdown_func.h"
32 #include "company_base.h"
33 #include "core/geometry_func.hpp"
34 #include "core/random_func.hpp"
35 #include "core/backup_type.hpp"
36 #include "genworld.h"
37 #include "smallmap_gui.h"
38 #include "widgets/dropdown_type.h"
40 
41 #include "table/strings.h"
42 
43 #include <bitset>
44 
45 #include "safeguards.h"
46 
47 bool _ignore_restrictions;
48 std::bitset<NUM_INDUSTRYTYPES> _displayed_industries;
49 
55 };
56 
63 };
64 
66 struct CargoSuffix {
68  char text[512];
69 };
70 
71 static void ShowIndustryCargoesWindow(IndustryType id);
72 
82 static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, CargoSuffix &suffix)
83 {
84  suffix.text[0] = '\0';
85  suffix.display = CSD_CARGO_AMOUNT;
86 
87  if (HasBit(indspec->callback_mask, CBM_IND_CARGO_SUFFIX)) {
88  TileIndex t = (cst != CST_FUND) ? ind->location.tile : INVALID_TILE;
89  uint16 callback = GetIndustryCallback(CBID_INDUSTRY_CARGO_SUFFIX, 0, (cst << 8) | cargo, const_cast<Industry *>(ind), ind_type, t);
90  if (callback == CALLBACK_FAILED) return;
91 
92  if (indspec->grf_prop.grffile->grf_version < 8) {
93  if (GB(callback, 0, 8) == 0xFF) return;
94  if (callback < 0x400) {
96  GetString(suffix.text, GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 + callback), lastof(suffix.text));
99  return;
100  }
102  return;
103 
104  } else { // GRF version 8 or higher.
105  if (callback == 0x400) return;
106  if (callback == 0x401) {
107  suffix.display = CSD_CARGO;
108  return;
109  }
110  if (callback < 0x400) {
112  GetString(suffix.text, GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 + callback), lastof(suffix.text));
115  return;
116  }
117  if (callback >= 0x800 && callback < 0xC00) {
119  GetString(suffix.text, GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 - 0x800 + callback), lastof(suffix.text));
121  suffix.display = CSD_CARGO_TEXT;
122  return;
123  }
125  return;
126  }
127  }
128 }
129 
130 enum CargoSuffixInOut {
131  CARGOSUFFIX_OUT = 0,
132  CARGOSUFFIX_IN = 1,
133 };
134 
145 template <typename TC, typename TS>
146 static inline void GetAllCargoSuffixes(CargoSuffixInOut use_input, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, const TC &cargoes, TS &suffixes)
147 {
148  assert_compile(lengthof(cargoes) <= lengthof(suffixes));
149 
151  /* Reworked behaviour with new many-in-many-out scheme */
152  for (uint j = 0; j < lengthof(suffixes); j++) {
153  if (cargoes[j] != CT_INVALID) {
154  byte local_id = indspec->grf_prop.grffile->cargo_map[cargoes[j]]; // should we check the value for valid?
155  uint cargotype = local_id << 16 | use_input;
156  GetCargoSuffix(cargotype, cst, ind, ind_type, indspec, suffixes[j]);
157  } else {
158  suffixes[j].text[0] = '\0';
159  suffixes[j].display = CSD_CARGO;
160  }
161  }
162  } else {
163  /* Compatible behaviour with old 3-in-2-out scheme */
164  for (uint j = 0; j < lengthof(suffixes); j++) {
165  suffixes[j].text[0] = '\0';
166  suffixes[j].display = CSD_CARGO;
167  }
168  switch (use_input) {
169  case CARGOSUFFIX_OUT:
170  if (cargoes[0] != CT_INVALID) GetCargoSuffix(3, cst, ind, ind_type, indspec, suffixes[0]);
171  if (cargoes[1] != CT_INVALID) GetCargoSuffix(4, cst, ind, ind_type, indspec, suffixes[1]);
172  break;
173  case CARGOSUFFIX_IN:
174  if (cargoes[0] != CT_INVALID) GetCargoSuffix(0, cst, ind, ind_type, indspec, suffixes[0]);
175  if (cargoes[1] != CT_INVALID) GetCargoSuffix(1, cst, ind, ind_type, indspec, suffixes[1]);
176  if (cargoes[2] != CT_INVALID) GetCargoSuffix(2, cst, ind, ind_type, indspec, suffixes[2]);
177  break;
178  default:
179  NOT_REACHED();
180  }
181  }
182 }
183 
184 std::array<IndustryType, NUM_INDUSTRYTYPES> _sorted_industry_types;
185 
187 static bool IndustryTypeNameSorter(const IndustryType &a, const IndustryType &b)
188 {
189  static char industry_name[2][64];
190 
191  const IndustrySpec *indsp1 = GetIndustrySpec(a);
192  GetString(industry_name[0], indsp1->name, lastof(industry_name[0]));
193 
194  const IndustrySpec *indsp2 = GetIndustrySpec(b);
195  GetString(industry_name[1], indsp2->name, lastof(industry_name[1]));
196 
197  int r = strnatcmp(industry_name[0], industry_name[1]); // Sort by name (natural sorting).
198 
199  /* If the names are equal, sort by industry type. */
200  return (r != 0) ? r < 0 : (a < b);
201 }
202 
207 {
208  /* Add each industry type to the list. */
209  for (IndustryType i = 0; i < NUM_INDUSTRYTYPES; i++) {
210  _sorted_industry_types[i] = i;
211  }
212 
213  /* Sort industry types by name. */
215 }
216 
225 void CcBuildIndustry(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
226 {
227  if (result.Succeeded()) return;
228 
229  uint8 indtype = GB(p1, 0, 8);
230  if (indtype < NUM_INDUSTRYTYPES) {
231  const IndustrySpec *indsp = GetIndustrySpec(indtype);
232  if (indsp->enabled) {
233  SetDParam(0, indsp->name);
234  ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, result.GetErrorMessage(), WL_INFO, TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE);
235  }
236  }
237 }
238 
239 static const NWidgetPart _nested_build_industry_widgets[] = {
241  NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
242  NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_FUND_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
243  NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
244  NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
245  NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
246  EndContainer(),
248  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),
249  NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_DPI_SCROLLBAR),
250  EndContainer(),
251  NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_DPI_INFOPANEL), SetResize(1, 0),
252  EndContainer(),
254  NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_DISPLAY_WIDGET), SetFill(1, 0), SetResize(1, 0),
255  SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
256  NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_FUND_WIDGET), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
257  NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
258  EndContainer(),
259 };
260 
263  WDP_AUTO, "build_industry", 170, 212,
266  _nested_build_industry_widgets, lengthof(_nested_build_industry_widgets)
267 );
268 
270 class BuildIndustryWindow : public Window {
272  IndustryType selected_type;
273  uint16 callback_timer;
275  uint16 count;
276  IndustryType index[NUM_INDUSTRYTYPES + 1];
277  bool enabled[NUM_INDUSTRYTYPES + 1];
278  Scrollbar *vscroll;
279 
281  static const int MATRIX_TEXT_OFFSET = 17;
283  static const int MAX_MINWIDTH_LINEHEIGHTS = 20;
284 
285  void SetupArrays()
286  {
287  this->count = 0;
288 
289  for (uint i = 0; i < lengthof(this->index); i++) {
290  this->index[i] = INVALID_INDUSTRYTYPE;
291  this->enabled[i] = false;
292  }
293 
294  if (_game_mode == GM_EDITOR) { // give room for the Many Random "button"
295  this->index[this->count] = INVALID_INDUSTRYTYPE;
296  this->enabled[this->count] = true;
297  this->count++;
298  this->timer_enabled = false;
299  }
300  /* Fill the arrays with industries.
301  * The tests performed after the enabled allow to load the industries
302  * In the same way they are inserted by grf (if any)
303  */
304  for (IndustryType ind : _sorted_industry_types) {
305  const IndustrySpec *indsp = GetIndustrySpec(ind);
306  if (indsp->enabled) {
307  /* Rule is that editor mode loads all industries.
308  * In game mode, all non raw industries are loaded too
309  * and raw ones are loaded only when setting allows it */
310  if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
311  /* Unselect if the industry is no longer in the list */
312  if (this->selected_type == ind) this->selected_index = -1;
313  continue;
314  }
315  this->index[this->count] = ind;
316  this->enabled[this->count] = (_game_mode == GM_EDITOR) || GetIndustryProbabilityCallback(ind, IACT_USERCREATION, 1) > 0;
317  /* Keep the selection to the correct line */
318  if (this->selected_type == ind) this->selected_index = this->count;
319  this->count++;
320  }
321  }
322 
323  /* first industry type is selected if the current selection is invalid.
324  * I'll be damned if there are none available ;) */
325  if (this->selected_index == -1) {
326  this->selected_index = 0;
327  this->selected_type = this->index[0];
328  }
329 
330  this->vscroll->SetCount(this->count);
331  }
332 
334  void SetButtons()
335  {
336  this->SetWidgetDisabledState(WID_DPI_FUND_WIDGET, this->selected_type != INVALID_INDUSTRYTYPE && !this->enabled[this->selected_index]);
337  this->SetWidgetDisabledState(WID_DPI_DISPLAY_WIDGET, this->selected_type == INVALID_INDUSTRYTYPE && this->enabled[this->selected_index]);
338  }
339 
352  std::string MakeCargoListString(const CargoID *cargolist, const CargoSuffix *cargo_suffix, int cargolistlen, StringID prefixstr) const
353  {
354  std::string cargostring;
355  char buf[1024];
356  int numcargo = 0;
357  int firstcargo = -1;
358 
359  for (byte j = 0; j < cargolistlen; j++) {
360  if (cargolist[j] == CT_INVALID) continue;
361  numcargo++;
362  if (firstcargo < 0) {
363  firstcargo = j;
364  continue;
365  }
366  SetDParam(0, CargoSpec::Get(cargolist[j])->name);
367  SetDParamStr(1, cargo_suffix[j].text);
368  GetString(buf, STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION, lastof(buf));
369  cargostring += buf;
370  }
371 
372  if (numcargo > 0) {
373  SetDParam(0, CargoSpec::Get(cargolist[firstcargo])->name);
374  SetDParamStr(1, cargo_suffix[firstcargo].text);
375  GetString(buf, prefixstr, lastof(buf));
376  cargostring = std::string(buf) + cargostring;
377  } else {
378  SetDParam(0, STR_JUST_NOTHING);
379  SetDParamStr(1, "");
380  GetString(buf, prefixstr, lastof(buf));
381  cargostring = std::string(buf);
382  }
383 
384  return cargostring;
385  }
386 
387 public:
388  BuildIndustryWindow() : Window(&_build_industry_desc)
389  {
390  this->timer_enabled = _loaded_newgrf_features.has_newindustries;
391 
392  this->selected_index = -1;
393  this->selected_type = INVALID_INDUSTRYTYPE;
394 
395  this->callback_timer = DAY_TICKS;
396 
397  this->CreateNestedTree();
398  this->vscroll = this->GetScrollbar(WID_DPI_SCROLLBAR);
399  this->FinishInitNested(0);
400 
401  this->SetButtons();
402  }
403 
404  void OnInit() override
405  {
406  this->SetupArrays();
407  }
408 
409  void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
410  {
411  switch (widget) {
412  case WID_DPI_MATRIX_WIDGET: {
413  Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES);
414  for (byte i = 0; i < this->count; i++) {
415  if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
416  d = maxdim(d, GetStringBoundingBox(GetIndustrySpec(this->index[i])->name));
417  }
419  d.width += MATRIX_TEXT_OFFSET + padding.width;
420  d.height = 5 * resize->height;
421  *size = maxdim(*size, d);
422  break;
423  }
424 
425  case WID_DPI_INFOPANEL: {
426  /* Extra line for cost outside of editor + extra lines for 'extra' information for NewGRFs. */
427  int height = 2 + (_game_mode == GM_EDITOR ? 0 : 1) + (_loaded_newgrf_features.has_newindustries ? 4 : 0);
428  uint extra_lines_req = 0;
429  uint extra_lines_prd = 0;
430  uint max_minwidth = FONT_HEIGHT_NORMAL * MAX_MINWIDTH_LINEHEIGHTS;
431  Dimension d = {0, 0};
432  for (byte i = 0; i < this->count; i++) {
433  if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
434 
435  const IndustrySpec *indsp = GetIndustrySpec(this->index[i]);
436  CargoSuffix cargo_suffix[lengthof(indsp->accepts_cargo)];
437 
438  /* Measure the accepted cargoes, if any. */
439  GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, this->index[i], indsp, indsp->accepts_cargo, cargo_suffix);
440  std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, lengthof(indsp->accepts_cargo), STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
441  Dimension strdim = GetStringBoundingBox(cargostring.c_str());
442  if (strdim.width > max_minwidth) {
443  extra_lines_req = max(extra_lines_req, strdim.width / max_minwidth + 1);
444  strdim.width = max_minwidth;
445  }
446  d = maxdim(d, strdim);
447 
448  /* Measure the produced cargoes, if any. */
449  GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, this->index[i], indsp, indsp->produced_cargo, cargo_suffix);
450  cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, lengthof(indsp->produced_cargo), STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
451  strdim = GetStringBoundingBox(cargostring.c_str());
452  if (strdim.width > max_minwidth) {
453  extra_lines_prd = max(extra_lines_prd, strdim.width / max_minwidth + 1);
454  strdim.width = max_minwidth;
455  }
456  d = maxdim(d, strdim);
457  }
458 
459  /* Set it to something more sane :) */
460  height += extra_lines_prd + extra_lines_req;
461  size->height = height * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
462  size->width = d.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
463  break;
464  }
465 
466  case WID_DPI_FUND_WIDGET: {
467  Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
468  d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY));
469  d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY));
470  d.width += padding.width;
471  d.height += padding.height;
472  *size = maxdim(*size, d);
473  break;
474  }
475  }
476  }
477 
478  void SetStringParameters(int widget) const override
479  {
480  switch (widget) {
481  case WID_DPI_FUND_WIDGET:
482  /* Raw industries might be prospected. Show this fact by changing the string
483  * In Editor, you just build, while ingame, or you fund or you prospect */
484  if (_game_mode == GM_EDITOR) {
485  /* We've chosen many random industries but no industries have been specified */
486  SetDParam(0, STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
487  } else {
488  const IndustrySpec *indsp = GetIndustrySpec(this->index[this->selected_index]);
489  SetDParam(0, (_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
490  }
491  break;
492  }
493  }
494 
495  void DrawWidget(const Rect &r, int widget) const override
496  {
497  switch (widget) {
498  case WID_DPI_MATRIX_WIDGET: {
499  uint text_left, text_right, icon_left, icon_right;
500  if (_current_text_dir == TD_RTL) {
501  icon_right = r.right - WD_MATRIX_RIGHT;
502  icon_left = icon_right - 10;
503  text_right = icon_right - BuildIndustryWindow::MATRIX_TEXT_OFFSET;
504  text_left = r.left + WD_MATRIX_LEFT;
505  } else {
506  icon_left = r.left + WD_MATRIX_LEFT;
507  icon_right = icon_left + 10;
508  text_left = icon_left + BuildIndustryWindow::MATRIX_TEXT_OFFSET;
509  text_right = r.right - WD_MATRIX_RIGHT;
510  }
511 
512  for (byte i = 0; i < this->vscroll->GetCapacity() && i + this->vscroll->GetPosition() < this->count; i++) {
513  int y = r.top + WD_MATRIX_TOP + i * this->resize.step_height;
514  bool selected = this->selected_index == i + this->vscroll->GetPosition();
515 
516  if (this->index[i + this->vscroll->GetPosition()] == INVALID_INDUSTRYTYPE) {
517  DrawString(text_left, text_right, y, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, selected ? TC_WHITE : TC_ORANGE);
518  continue;
519  }
520  const IndustrySpec *indsp = GetIndustrySpec(this->index[i + this->vscroll->GetPosition()]);
521 
522  /* Draw the name of the industry in white is selected, otherwise, in orange */
523  DrawString(text_left, text_right, y, indsp->name, selected ? TC_WHITE : TC_ORANGE);
524  GfxFillRect(icon_left, y + 1, icon_right, y + 7, selected ? PC_WHITE : PC_BLACK);
525  GfxFillRect(icon_left + 1, y + 2, icon_right - 1, y + 6, indsp->map_colour);
526  }
527  break;
528  }
529 
530  case WID_DPI_INFOPANEL: {
531  int y = r.top + WD_FRAMERECT_TOP;
532  int bottom = r.bottom - WD_FRAMERECT_BOTTOM;
533  int left = r.left + WD_FRAMERECT_LEFT;
534  int right = r.right - WD_FRAMERECT_RIGHT;
535 
536  if (this->selected_type == INVALID_INDUSTRYTYPE) {
537  DrawStringMultiLine(left, right, y, bottom, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
538  break;
539  }
540 
541  const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
542 
543  if (_game_mode != GM_EDITOR) {
544  SetDParam(0, indsp->GetConstructionCost());
545  DrawString(left, right, y, STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST);
546  y += FONT_HEIGHT_NORMAL;
547  }
548 
549  CargoSuffix cargo_suffix[lengthof(indsp->accepts_cargo)];
550 
551  /* Draw the accepted cargoes, if any. Otherwise, will print "Nothing". */
552  GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_FUND, nullptr, this->selected_type, indsp, indsp->accepts_cargo, cargo_suffix);
553  std::string cargostring = this->MakeCargoListString(indsp->accepts_cargo, cargo_suffix, lengthof(indsp->accepts_cargo), STR_INDUSTRY_VIEW_REQUIRES_N_CARGO);
554  y = DrawStringMultiLine(left, right, y, bottom, cargostring.c_str());
555 
556  /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
557  GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_FUND, nullptr, this->selected_type, indsp, indsp->produced_cargo, cargo_suffix);
558  cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, lengthof(indsp->produced_cargo), STR_INDUSTRY_VIEW_PRODUCES_N_CARGO);
559  y = DrawStringMultiLine(left, right, y, bottom, cargostring.c_str());
560 
561  /* Get the additional purchase info text, if it has not already been queried. */
563  uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, nullptr, this->selected_type, INVALID_TILE);
564  if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
565  if (callback_res > 0x400) {
567  } else {
568  StringID str = GetGRFStringID(indsp->grf_prop.grffile->grfid, 0xD000 + callback_res); // No. here's the new string
569  if (str != STR_UNDEFINED) {
571  DrawStringMultiLine(left, right, y, bottom, str, TC_YELLOW);
573  }
574  }
575  }
576  }
577  break;
578  }
579  }
580  }
581 
582  void OnClick(Point pt, int widget, int click_count) override
583  {
584  switch (widget) {
585  case WID_DPI_MATRIX_WIDGET: {
586  int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_DPI_MATRIX_WIDGET);
587  if (y < this->count) { // Is it within the boundaries of available data?
588  this->selected_index = y;
589  this->selected_type = this->index[y];
590  const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? nullptr : GetIndustrySpec(this->selected_type);
591 
592  this->SetDirty();
593 
594  if (_thd.GetCallbackWnd() == this &&
595  ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != nullptr && indsp->IsRawIndustry()) ||
596  this->selected_type == INVALID_INDUSTRYTYPE ||
597  !this->enabled[this->selected_index])) {
598  /* Reset the button state if going to prospecting or "build many industries" */
599  this->RaiseButtons();
601  }
602 
603  this->SetButtons();
604  if (this->enabled[this->selected_index] && click_count > 1) this->OnClick(pt, WID_DPI_FUND_WIDGET, 1);
605  }
606  break;
607  }
608 
610  if (this->selected_type != INVALID_INDUSTRYTYPE) ShowIndustryCargoesWindow(this->selected_type);
611  break;
612 
613  case WID_DPI_FUND_WIDGET: {
614  if (this->selected_type == INVALID_INDUSTRYTYPE) {
615  this->HandleButtonClick(WID_DPI_FUND_WIDGET);
616 
617  if (Town::GetNumItems() == 0) {
618  ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO);
619  } else {
620  extern void GenerateIndustries();
621  _generating_world = true;
623  _generating_world = false;
624  }
625  } else if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
626  DoCommandP(0, this->selected_type, InteractiveRandom(), CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY));
627  this->HandleButtonClick(WID_DPI_FUND_WIDGET);
628  } else {
629  HandlePlacePushButton(this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT);
630  }
631  break;
632  }
633  }
634  }
635 
636  void OnResize() override
637  {
638  /* Adjust the number of items in the matrix depending of the resize */
639  this->vscroll->SetCapacityFromWidget(this, WID_DPI_MATRIX_WIDGET);
640  }
641 
642  void OnPlaceObject(Point pt, TileIndex tile) override
643  {
644  bool success = true;
645  /* We do not need to protect ourselves against "Random Many Industries" in this mode */
646  const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
647  uint32 seed = InteractiveRandom();
648  uint32 layout_index = InteractiveRandomRange((uint32)indsp->layouts.size());
649 
650  if (_game_mode == GM_EDITOR) {
651  /* Show error if no town exists at all */
652  if (Town::GetNumItems() == 0) {
653  SetDParam(0, indsp->name);
654  ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO, pt.x, pt.y);
655  return;
656  }
657 
658  Backup<CompanyID> cur_company(_current_company, OWNER_NONE, FILE_LINE);
659  _generating_world = true;
660  _ignore_restrictions = true;
661 
662  DoCommandP(tile, (layout_index << 8) | this->selected_type, seed,
663  CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY), &CcBuildIndustry);
664 
665  cur_company.Restore();
666  _ignore_restrictions = false;
667  _generating_world = false;
668  } else {
669  success = DoCommandP(tile, (layout_index << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY));
670  }
671 
672  /* If an industry has been built, just reset the cursor and the system */
674  }
675 
676  void OnGameTick() override
677  {
678  if (!this->timer_enabled) return;
679  if (--this->callback_timer == 0) {
680  /* We have just passed another day.
681  * See if we need to update availability of currently selected industry */
682  this->callback_timer = DAY_TICKS; // restart counter
683 
684  const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
685 
686  if (indsp->enabled) {
687  bool call_back_result = GetIndustryProbabilityCallback(this->selected_type, IACT_USERCREATION, 1) > 0;
688 
689  /* Only if result does match the previous state would it require a redraw. */
690  if (call_back_result != this->enabled[this->selected_index]) {
691  this->enabled[this->selected_index] = call_back_result;
692  this->SetButtons();
693  this->SetDirty();
694  }
695  }
696  }
697  }
698 
699  void OnTimeout() override
700  {
701  this->RaiseButtons();
702  }
703 
704  void OnPlaceObjectAbort() override
705  {
706  this->RaiseButtons();
707  }
708 
714  void OnInvalidateData(int data = 0, bool gui_scope = true) override
715  {
716  if (!gui_scope) return;
717  this->SetupArrays();
718 
719  const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? nullptr : GetIndustrySpec(this->selected_type);
720  if (indsp == nullptr) this->enabled[this->selected_index] = _settings_game.difficulty.industry_density != ID_FUND_ONLY;
721  this->SetButtons();
722  }
723 };
724 
725 void ShowBuildIndustryWindow()
726 {
727  if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
729  new BuildIndustryWindow();
730 }
731 
732 static void UpdateIndustryProduction(Industry *i);
733 
734 static inline bool IsProductionAlterable(const Industry *i)
735 {
736  const IndustrySpec *is = GetIndustrySpec(i->type);
737  bool has_prod = false;
738  for (size_t j = 0; j < lengthof(is->production_rate); j++) {
739  if (is->production_rate[j] != 0) {
740  has_prod = true;
741  break;
742  }
743  }
744  return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
745  (has_prod || is->IsRawIndustry()) &&
746  !_networking);
747 }
748 
750 {
752  enum Editability {
756  };
757 
759  enum InfoLine {
764  };
765 
772 
773 public:
774  IndustryViewWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
775  {
776  this->flags |= WF_DISABLE_VP_SCROLL;
777  this->editbox_line = IL_NONE;
778  this->clicked_line = IL_NONE;
779  this->clicked_button = 0;
780  this->info_height = WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM + 1; // Info panel has at least two lines text.
781 
782  this->InitNested(window_number);
783  NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
784  nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ZOOM_LVL_INDUSTRY);
785 
786  this->InvalidateData();
787  }
788 
789  void OnPaint() override
790  {
791  this->DrawWidgets();
792 
793  if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
794 
795  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_IV_INFO);
796  uint expected = this->DrawInfo(nwi->pos_x, nwi->pos_x + nwi->current_x - 1, nwi->pos_y) - nwi->pos_y;
797  if (expected > nwi->current_y - 1) {
798  this->info_height = expected + 1;
799  this->ReInit();
800  return;
801  }
802  }
803 
811  int DrawInfo(uint left, uint right, uint top)
812  {
813  Industry *i = Industry::Get(this->window_number);
814  const IndustrySpec *ind = GetIndustrySpec(i->type);
815  int y = top + WD_FRAMERECT_TOP;
816  bool first = true;
817  bool has_accept = false;
818 
819  if (i->prod_level == PRODLEVEL_CLOSURE) {
820  DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE);
821  y += 2 * FONT_HEIGHT_NORMAL;
822  }
823 
824  CargoSuffix cargo_suffix[lengthof(i->accepts_cargo)];
825  GetAllCargoSuffixes(CARGOSUFFIX_IN, CST_VIEW, i, i->type, ind, i->accepts_cargo, cargo_suffix);
827 
828  uint left_side = left + WD_FRAMERECT_LEFT * 4; // Indent accepted cargoes.
829  for (byte j = 0; j < lengthof(i->accepts_cargo); j++) {
830  if (i->accepts_cargo[j] == CT_INVALID) continue;
831  has_accept = true;
832  if (first) {
833  DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_REQUIRES);
834  y += FONT_HEIGHT_NORMAL;
835  first = false;
836  }
838  SetDParam(1, i->accepts_cargo[j]);
840  SetDParamStr(3, "");
841  StringID str = STR_NULL;
842  switch (cargo_suffix[j].display) {
844  SetDParamStr(3, cargo_suffix[j].text);
845  FALLTHROUGH;
846  case CSD_CARGO_AMOUNT:
847  str = stockpiling ? STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT : STR_INDUSTRY_VIEW_ACCEPT_CARGO;
848  break;
849 
850  case CSD_CARGO_TEXT:
851  SetDParamStr(3, cargo_suffix[j].text);
852  FALLTHROUGH;
853  case CSD_CARGO:
854  str = STR_INDUSTRY_VIEW_ACCEPT_CARGO;
855  break;
856 
857  default:
858  NOT_REACHED();
859  }
860  DrawString(left_side, right - WD_FRAMERECT_RIGHT, y, str);
861  y += FONT_HEIGHT_NORMAL;
862  }
863 
864  GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_VIEW, i, i->type, ind, i->produced_cargo, cargo_suffix);
865  first = true;
866  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
867  if (i->produced_cargo[j] == CT_INVALID) continue;
868  if (first) {
869  if (has_accept) y += WD_PAR_VSEP_WIDE;
870  DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
871  y += FONT_HEIGHT_NORMAL;
872  if (this->editable == EA_RATE) this->production_offset_y = y;
873  first = false;
874  }
875 
876  SetDParam(0, i->produced_cargo[j]);
878  SetDParamStr(2, cargo_suffix[j].text);
880  uint x = left + WD_FRAMETEXT_LEFT + (this->editable == EA_RATE ? SETTING_BUTTON_WIDTH + 10 : 0);
881  DrawString(x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_TRANSPORTED);
882  /* Let's put out those buttons.. */
883  if (this->editable == EA_RATE) {
884  DrawArrowButtons(left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_RATE1 + j) ? this->clicked_button : 0,
885  i->production_rate[j] > 0, i->production_rate[j] < 255);
886  }
887  y += FONT_HEIGHT_NORMAL;
888  }
889 
890  /* Display production multiplier if editable */
891  if (this->editable == EA_MULTIPLIER) {
892  y += WD_PAR_VSEP_WIDE;
893  this->production_offset_y = y;
895  uint x = left + WD_FRAMETEXT_LEFT + SETTING_BUTTON_WIDTH + 10;
896  DrawString(x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LEVEL);
897  DrawArrowButtons(left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_MULTIPLIER) ? this->clicked_button : 0,
899  y += FONT_HEIGHT_NORMAL;
900  }
901 
902  /* Get the extra message for the GUI */
904  uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->location.tile);
905  if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
906  if (callback_res > 0x400) {
908  } else {
909  StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res);
910  if (message != STR_NULL && message != STR_UNDEFINED) {
911  y += WD_PAR_VSEP_WIDE;
912 
914  /* Use all the available space left from where we stand up to the
915  * end of the window. We ALSO enlarge the window if needed, so we
916  * can 'go' wild with the bottom of the window. */
917  y = DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, UINT16_MAX, message, TC_BLACK);
919  }
920  }
921  }
922  }
923  return y + WD_FRAMERECT_BOTTOM;
924  }
925 
926  void SetStringParameters(int widget) const override
927  {
928  if (widget == WID_IV_CAPTION) SetDParam(0, this->window_number);
929  }
930 
931  void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
932  {
933  if (widget == WID_IV_INFO) size->height = this->info_height;
934  }
935 
936  void OnClick(Point pt, int widget, int click_count) override
937  {
938  switch (widget) {
939  case WID_IV_INFO: {
940  Industry *i = Industry::Get(this->window_number);
941  InfoLine line = IL_NONE;
942 
943  switch (this->editable) {
944  case EA_NONE: break;
945 
946  case EA_MULTIPLIER:
947  if (IsInsideBS(pt.y, this->production_offset_y, FONT_HEIGHT_NORMAL)) line = IL_MULTIPLIER;
948  break;
949 
950  case EA_RATE:
951  if (pt.y >= this->production_offset_y) {
952  int row = (pt.y - this->production_offset_y) / FONT_HEIGHT_NORMAL;
953  for (uint j = 0; j < lengthof(i->produced_cargo); j++) {
954  if (i->produced_cargo[j] == CT_INVALID) continue;
955  row--;
956  if (row < 0) {
957  line = (InfoLine)(IL_RATE1 + j);
958  break;
959  }
960  }
961  }
962  break;
963  }
964  if (line == IL_NONE) return;
965 
966  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget);
967  int left = nwi->pos_x + WD_FRAMETEXT_LEFT;
968  int right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT;
969  if (IsInsideMM(pt.x, left, left + SETTING_BUTTON_WIDTH)) {
970  /* Clicked buttons, decrease or increase production */
971  byte button = (pt.x < left + SETTING_BUTTON_WIDTH / 2) ? 1 : 2;
972  switch (this->editable) {
973  case EA_MULTIPLIER:
974  if (button == 1) {
975  if (i->prod_level <= PRODLEVEL_MINIMUM) return;
976  i->prod_level = max<uint>(i->prod_level / 2, PRODLEVEL_MINIMUM);
977  } else {
978  if (i->prod_level >= PRODLEVEL_MAXIMUM) return;
980  }
981  break;
982 
983  case EA_RATE:
984  if (button == 1) {
985  if (i->production_rate[line - IL_RATE1] <= 0) return;
986  i->production_rate[line - IL_RATE1] = max(i->production_rate[line - IL_RATE1] / 2, 0);
987  } else {
988  if (i->production_rate[line - IL_RATE1] >= 255) return;
989  /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
990  int new_prod = i->production_rate[line - IL_RATE1] == 0 ? 1 : i->production_rate[line - IL_RATE1] * 2;
991  i->production_rate[line - IL_RATE1] = minu(new_prod, 255);
992  }
993  break;
994 
995  default: NOT_REACHED();
996  }
997 
998  UpdateIndustryProduction(i);
999  this->SetDirty();
1000  this->SetTimeout();
1001  this->clicked_line = line;
1002  this->clicked_button = button;
1003  } else if (IsInsideMM(pt.x, left + SETTING_BUTTON_WIDTH + 10, right)) {
1004  /* clicked the text */
1005  this->editbox_line = line;
1006  switch (this->editable) {
1007  case EA_MULTIPLIER:
1009  ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION_LEVEL, 10, this, CS_ALPHANUMERAL, QSF_NONE);
1010  break;
1011 
1012  case EA_RATE:
1013  SetDParam(0, i->production_rate[line - IL_RATE1] * 8);
1014  ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION, 10, this, CS_ALPHANUMERAL, QSF_NONE);
1015  break;
1016 
1017  default: NOT_REACHED();
1018  }
1019  }
1020  break;
1021  }
1022 
1023  case WID_IV_GOTO: {
1024  Industry *i = Industry::Get(this->window_number);
1025  if (_ctrl_pressed) {
1027  } else {
1029  }
1030  break;
1031  }
1032 
1033  case WID_IV_DISPLAY: {
1034  Industry *i = Industry::Get(this->window_number);
1036  break;
1037  }
1038  }
1039  }
1040 
1041  void OnTimeout() override
1042  {
1043  this->clicked_line = IL_NONE;
1044  this->clicked_button = 0;
1045  this->SetDirty();
1046  }
1047 
1048  void OnResize() override
1049  {
1050  if (this->viewport != nullptr) {
1051  NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
1052  nvp->UpdateViewportCoordinates(this);
1053 
1054  ScrollWindowToTile(Industry::Get(this->window_number)->location.GetCenterTile(), this, true); // Re-center viewport.
1055  }
1056  }
1057 
1058  void OnQueryTextFinished(char *str) override
1059  {
1060  if (StrEmpty(str)) return;
1061 
1062  Industry *i = Industry::Get(this->window_number);
1063  uint value = atoi(str);
1064  switch (this->editbox_line) {
1065  case IL_NONE: NOT_REACHED();
1066 
1067  case IL_MULTIPLIER:
1069  break;
1070 
1071  default:
1072  i->production_rate[this->editbox_line - IL_RATE1] = ClampU(RoundDivSU(value, 8), 0, 255);
1073  break;
1074  }
1075  UpdateIndustryProduction(i);
1076  this->SetDirty();
1077  }
1078 
1084  void OnInvalidateData(int data = 0, bool gui_scope = true) override
1085  {
1086  if (!gui_scope) return;
1087  const Industry *i = Industry::Get(this->window_number);
1088  if (IsProductionAlterable(i)) {
1089  const IndustrySpec *ind = GetIndustrySpec(i->type);
1090  this->editable = ind->UsesSmoothEconomy() ? EA_RATE : EA_MULTIPLIER;
1091  } else {
1092  this->editable = EA_NONE;
1093  }
1094  }
1095 
1096  bool IsNewGRFInspectable() const override
1097  {
1098  return ::IsNewGRFInspectable(GSF_INDUSTRIES, this->window_number);
1099  }
1100 
1101  void ShowNewGRFInspectWindow() const override
1102  {
1103  ::ShowNewGRFInspectWindow(GSF_INDUSTRIES, this->window_number);
1104  }
1105 };
1106 
1107 static void UpdateIndustryProduction(Industry *i)
1108 {
1109  const IndustrySpec *indspec = GetIndustrySpec(i->type);
1110  if (!indspec->UsesSmoothEconomy()) i->RecomputeProductionMultipliers();
1111 
1112  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1113  if (i->produced_cargo[j] != CT_INVALID) {
1114  i->last_month_production[j] = 8 * i->production_rate[j];
1115  }
1116  }
1117 }
1118 
1122  NWidget(WWT_CLOSEBOX, COLOUR_CREAM),
1123  NWidget(WWT_CAPTION, COLOUR_CREAM, WID_IV_CAPTION), SetDataTip(STR_INDUSTRY_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1124  NWidget(WWT_DEBUGBOX, COLOUR_CREAM),
1125  NWidget(WWT_SHADEBOX, COLOUR_CREAM),
1126  NWidget(WWT_DEFSIZEBOX, COLOUR_CREAM),
1127  NWidget(WWT_STICKYBOX, COLOUR_CREAM),
1128  EndContainer(),
1129  NWidget(WWT_PANEL, COLOUR_CREAM),
1130  NWidget(WWT_INSET, COLOUR_CREAM), SetPadding(2, 2, 2, 2),
1131  NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_IV_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetPadding(1, 1, 1, 1), SetResize(1, 1),
1132  EndContainer(),
1133  EndContainer(),
1134  NWidget(WWT_PANEL, COLOUR_CREAM, WID_IV_INFO), SetMinimalSize(260, 2), SetResize(1, 0),
1135  EndContainer(),
1137  NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GOTO), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_BUTTON_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
1138  NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
1139  NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
1140  EndContainer(),
1141 };
1142 
1145  WDP_AUTO, "view_industry", 260, 120,
1147  0,
1148  _nested_industry_view_widgets, lengthof(_nested_industry_view_widgets)
1149 );
1150 
1151 void ShowIndustryViewWindow(int industry)
1152 {
1153  AllocateWindowDescFront<IndustryViewWindow>(&_industry_view_desc, industry);
1154 }
1155 
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(),
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_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_ACC_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetDataTip(STR_INDUSTRY_DIRECTORY_ACCEPTED_CARGO_FILTER, STR_TOOLTIP_FILTER_CRITERIA),
1171  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_FILTER_BY_PROD_CARGO), SetMinimalSize(225, 12), SetFill(0, 1), SetDataTip(STR_INDUSTRY_DIRECTORY_PRODUCED_CARGO_FILTER, STR_TOOLTIP_FILTER_CRITERIA),
1172  NWidget(WWT_PANEL, COLOUR_BROWN), SetResize(1, 0), EndContainer(),
1173  EndContainer(),
1174  NWidget(WWT_PANEL, COLOUR_BROWN, WID_ID_INDUSTRY_LIST), SetDataTip(0x0, STR_INDUSTRY_DIRECTORY_LIST_CAPTION), SetResize(1, 1), SetScrollbar(WID_ID_SCROLLBAR), EndContainer(),
1175  EndContainer(),
1177  NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_ID_SCROLLBAR),
1178  NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1179  EndContainer(),
1180  EndContainer(),
1181 };
1182 
1184 
1189 };
1190 
1198 static bool CDECL CargoFilter(const Industry * const *industry, const std::pair<CargoID, CargoID> &cargoes)
1199 {
1200  auto accepted_cargo = cargoes.first;
1201  auto produced_cargo = cargoes.second;
1202 
1203  bool accepted_cargo_matches;
1204 
1205  switch (accepted_cargo) {
1206  case CF_ANY:
1207  accepted_cargo_matches = true;
1208  break;
1209 
1210  case CF_NONE:
1211  accepted_cargo_matches = std::all_of(std::begin((*industry)->accepts_cargo), std::end((*industry)->accepts_cargo), [](CargoID cargo) {
1212  return cargo == CT_INVALID;
1213  });
1214  break;
1215 
1216  default:
1217  const auto &ac = (*industry)->accepts_cargo;
1218  accepted_cargo_matches = std::find(std::begin(ac), std::end(ac), accepted_cargo) != std::end(ac);
1219  break;
1220  }
1221 
1222  bool produced_cargo_matches;
1223 
1224  switch (produced_cargo) {
1225  case CF_ANY:
1226  produced_cargo_matches = true;
1227  break;
1228 
1229  case CF_NONE:
1230  produced_cargo_matches = std::all_of(std::begin((*industry)->produced_cargo), std::end((*industry)->produced_cargo), [](CargoID cargo) {
1231  return cargo == CT_INVALID;
1232  });
1233  break;
1234 
1235  default:
1236  const auto &pc = (*industry)->produced_cargo;
1237  produced_cargo_matches = std::find(std::begin(pc), std::end(pc), produced_cargo) != std::end(pc);
1238  break;
1239  }
1240 
1241  return accepted_cargo_matches && produced_cargo_matches;
1242 }
1243 
1244 static GUIIndustryList::FilterFunction * const _filter_funcs[] = { &CargoFilter };
1245 
1246 
1251 protected:
1252  /* Runtime saved values */
1253  static Listing last_sorting;
1254 
1255  /* Constants for sorting stations */
1256  static const StringID sorter_names[];
1257  static GUIIndustryList::SortFunction * const sorter_funcs[];
1258 
1259  GUIIndustryList industries;
1260  Scrollbar *vscroll;
1261 
1262  CargoID cargo_filter[NUM_CARGO + 2];
1263  StringID cargo_filter_texts[NUM_CARGO + 3];
1266 
1272  {
1273  if (this->produced_cargo_filter_criteria != index) {
1274  this->produced_cargo_filter_criteria = index;
1275  /* deactivate filter if criteria is 'Show All', activate it otherwise */
1276  bool is_filtering_necessary = this->cargo_filter[this->produced_cargo_filter_criteria] != CF_ANY || this->cargo_filter[this->accepted_cargo_filter_criteria] != CF_ANY;
1277 
1278  this->industries.SetFilterState(is_filtering_necessary);
1279  this->industries.SetFilterType(0);
1280  this->industries.ForceRebuild();
1281  }
1282  }
1283 
1289  {
1290  if (this->accepted_cargo_filter_criteria != index) {
1291  this->accepted_cargo_filter_criteria = index;
1292  /* deactivate filter if criteria is 'Show All', activate it otherwise */
1293  bool is_filtering_necessary = this->cargo_filter[this->produced_cargo_filter_criteria] != CF_ANY || this->cargo_filter[this->accepted_cargo_filter_criteria] != CF_ANY;
1294 
1295  this->industries.SetFilterState(is_filtering_necessary);
1296  this->industries.SetFilterType(0);
1297  this->industries.ForceRebuild();
1298  }
1299  }
1300 
1305  {
1306  uint filter_items = 0;
1307 
1308  /* Add item for disabling filtering. */
1309  this->cargo_filter[filter_items] = CF_ANY;
1310  this->cargo_filter_texts[filter_items] = STR_INDUSTRY_DIRECTORY_FILTER_ALL_TYPES;
1311  this->produced_cargo_filter_criteria = filter_items;
1312  this->accepted_cargo_filter_criteria = filter_items;
1313  filter_items++;
1314 
1315  /* Add item for industries not producing anything, e.g. power plants */
1316  this->cargo_filter[filter_items] = CF_NONE;
1317  this->cargo_filter_texts[filter_items] = STR_INDUSTRY_DIRECTORY_FILTER_NONE;
1318  filter_items++;
1319 
1320  /* Collect available cargo types for filtering. */
1321  const CargoSpec *cs;
1323  this->cargo_filter[filter_items] = cs->Index();
1324  this->cargo_filter_texts[filter_items] = cs->name;
1325  filter_items++;
1326  }
1327 
1328  /* Terminate the filter list. */
1329  this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
1330 
1331  this->industries.SetFilterFuncs(_filter_funcs);
1332 
1333  bool is_filtering_necessary = this->cargo_filter[this->produced_cargo_filter_criteria] != CF_ANY || this->cargo_filter[this->accepted_cargo_filter_criteria] != CF_ANY;
1334 
1335  this->industries.SetFilterState(is_filtering_necessary);
1336  }
1337 
1340  {
1341  if (this->industries.NeedRebuild()) {
1342  this->industries.clear();
1343 
1344  for (const Industry *i : Industry::Iterate()) {
1345  this->industries.push_back(i);
1346  }
1347 
1348  this->industries.shrink_to_fit();
1349  this->industries.RebuildDone();
1350  }
1351 
1352  auto filter = std::make_pair(this->cargo_filter[this->accepted_cargo_filter_criteria],
1353  this->cargo_filter[this->produced_cargo_filter_criteria]);
1354 
1355  this->industries.Filter(filter);
1356  this->industries.Sort();
1357 
1358  this->vscroll->SetCount((uint)this->industries.size()); // Update scrollbar as well.
1359 
1360  this->SetDirty();
1361  }
1362 
1370  static inline int GetCargoTransportedPercentsIfValid(const Industry *i, uint id)
1371  {
1372  assert(id < lengthof(i->produced_cargo));
1373 
1374  if (i->produced_cargo[id] == CT_INVALID) return 101;
1375  return ToPercent8(i->last_month_pct_transported[id]);
1376  }
1377 
1386  {
1387  int p1 = GetCargoTransportedPercentsIfValid(i, 0);
1388  int p2 = GetCargoTransportedPercentsIfValid(i, 1);
1389 
1390  if (p1 > p2) Swap(p1, p2); // lower value has higher priority
1391 
1392  return (p1 << 8) + p2;
1393  }
1394 
1396  static bool IndustryNameSorter(const Industry * const &a, const Industry * const &b)
1397  {
1398  int r = strnatcmp(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting).
1399  if (r == 0) return a->index < b->index;
1400  return r < 0;
1401  }
1402 
1404  static bool IndustryTypeSorter(const Industry * const &a, const Industry * const &b)
1405  {
1406  int it_a = 0;
1407  while (it_a != NUM_INDUSTRYTYPES && a->type != _sorted_industry_types[it_a]) it_a++;
1408  int it_b = 0;
1409  while (it_b != NUM_INDUSTRYTYPES && b->type != _sorted_industry_types[it_b]) it_b++;
1410  int r = it_a - it_b;
1411  return (r == 0) ? IndustryNameSorter(a, b) : r < 0;
1412  }
1413 
1415  static bool IndustryProductionSorter(const Industry * const &a, const Industry * const &b)
1416  {
1417  uint prod_a = 0, prod_b = 0;
1418  for (uint i = 0; i < lengthof(a->produced_cargo); i++) {
1419  if (a->produced_cargo[i] != CT_INVALID) prod_a += a->last_month_production[i];
1420  if (b->produced_cargo[i] != CT_INVALID) prod_b += b->last_month_production[i];
1421  }
1422  int r = prod_a - prod_b;
1423 
1424  return (r == 0) ? IndustryTypeSorter(a, b) : r < 0;
1425  }
1426 
1428  static bool IndustryTransportedCargoSorter(const Industry * const &a, const Industry * const &b)
1429  {
1430  int r = GetCargoTransportedSortValue(a) - GetCargoTransportedSortValue(b);
1431  return (r == 0) ? IndustryNameSorter(a, b) : r < 0;
1432  }
1433 
1440  {
1441  const IndustrySpec *indsp = GetIndustrySpec(i->type);
1442  byte p = 0;
1443 
1444  /* Industry name */
1445  SetDParam(p++, i->index);
1446 
1447  static CargoSuffix cargo_suffix[lengthof(i->produced_cargo)];
1448  GetAllCargoSuffixes(CARGOSUFFIX_OUT, CST_DIR, i, i->type, indsp, i->produced_cargo, cargo_suffix);
1449 
1450  /* Get industry productions (CargoID, production, suffix, transported) */
1451  typedef std::tuple<CargoID, uint16, const char*, uint> CargoInfo;
1452  std::vector<CargoInfo> cargos;
1453 
1454  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1455  if (i->produced_cargo[j] == CT_INVALID) continue;
1456  cargos.emplace_back(i->produced_cargo[j], i->last_month_production[j], cargo_suffix[j].text, ToPercent8(i->last_month_pct_transported[j]));
1457  }
1458 
1459  /* Sort by descending production, then descending transported */
1460  std::sort(cargos.begin(), cargos.end(), [](const CargoInfo a, const CargoInfo b) {
1461  if (std::get<1>(a) != std::get<1>(b)) return std::get<1>(a) > std::get<1>(b);
1462  return std::get<3>(a) > std::get<3>(b);
1463  });
1464 
1465  /* If the produced cargo filter is active then move the filtered cargo to the beginning of the list,
1466  * because this is the one the player interested in, and that way it is not hidden in the 'n' more cargos */
1467  const CargoID cid = this->cargo_filter[this->produced_cargo_filter_criteria];
1468  if (cid != CF_ANY && cid != CF_NONE) {
1469  auto filtered_ci = std::find_if(cargos.begin(), cargos.end(), [cid](const CargoInfo& ci) -> bool {
1470  return std::get<0>(ci) == cid;
1471  });
1472  if (filtered_ci != cargos.end()) {
1473  std::rotate(cargos.begin(), filtered_ci, filtered_ci + 1);
1474  }
1475  }
1476 
1477  /* Display first 3 cargos */
1478  for (size_t j = 0; j < min<size_t>(3, cargos.size()); j++) {
1479  CargoInfo ci = cargos[j];
1480  SetDParam(p++, STR_INDUSTRY_DIRECTORY_ITEM_INFO);
1481  SetDParam(p++, std::get<0>(ci));
1482  SetDParam(p++, std::get<1>(ci));
1483  SetDParamStr(p++, std::get<2>(ci));
1484  SetDParam(p++, std::get<3>(ci));
1485  }
1486 
1487  /* Undisplayed cargos if any */
1488  SetDParam(p++, cargos.size() - 3);
1489 
1490  /* Drawing the right string */
1491  switch (cargos.size()) {
1492  case 0: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1493  case 1: return STR_INDUSTRY_DIRECTORY_ITEM_PROD1;
1494  case 2: return STR_INDUSTRY_DIRECTORY_ITEM_PROD2;
1495  case 3: return STR_INDUSTRY_DIRECTORY_ITEM_PROD3;
1496  default: return STR_INDUSTRY_DIRECTORY_ITEM_PRODMORE;
1497  }
1498  }
1499 
1500 public:
1501  IndustryDirectoryWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
1502  {
1503  this->CreateNestedTree();
1504  this->vscroll = this->GetScrollbar(WID_ID_SCROLLBAR);
1505 
1506  this->industries.SetListing(this->last_sorting);
1507  this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1508  this->industries.ForceRebuild();
1509  this->BuildSortIndustriesList();
1510 
1511  this->FinishInitNested(0);
1512  }
1513 
1515  {
1516  this->last_sorting = this->industries.GetListing();
1517  }
1518 
1519  void OnInit() override
1520  {
1521  this->SetCargoFilterArray();
1522  }
1523 
1524  void SetStringParameters(int widget) const override
1525  {
1526  switch (widget) {
1528  SetDParam(0, IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1529  break;
1530 
1532  SetDParam(0, this->cargo_filter_texts[this->accepted_cargo_filter_criteria]);
1533  break;
1534 
1536  SetDParam(0, this->cargo_filter_texts[this->produced_cargo_filter_criteria]);
1537  break;
1538  }
1539  }
1540 
1541  void DrawWidget(const Rect &r, int widget) const override
1542  {
1543  switch (widget) {
1544  case WID_ID_DROPDOWN_ORDER:
1545  this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1546  break;
1547 
1548  case WID_ID_INDUSTRY_LIST: {
1549  int n = 0;
1550  int y = r.top + WD_FRAMERECT_TOP;
1551  if (this->industries.size() == 0) {
1552  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_DIRECTORY_NONE);
1553  break;
1554  }
1555  TextColour tc;
1556  const CargoID acf_cid = this->cargo_filter[this->accepted_cargo_filter_criteria];
1557  for (uint i = this->vscroll->GetPosition(); i < this->industries.size(); i++) {
1558  tc = TC_FROMSTRING;
1559  if (acf_cid != CF_ANY && acf_cid != CF_NONE) {
1560  Industry *ind = const_cast<Industry *>(this->industries[i]);
1561  if (IndustryTemporarilyRefusesCargo(ind, acf_cid)) {
1562  tc = TC_GREY | TC_FORCED;
1563  }
1564  }
1565  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, this->GetIndustryString(this->industries[i]), tc);
1566 
1567  y += this->resize.step_height;
1568  if (++n == this->vscroll->GetCapacity()) break; // max number of industries in 1 window
1569  }
1570  break;
1571  }
1572  }
1573  }
1574 
1575  void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1576  {
1577  switch (widget) {
1578  case WID_ID_DROPDOWN_ORDER: {
1579  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1580  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1581  d.height += padding.height;
1582  *size = maxdim(*size, d);
1583  break;
1584  }
1585 
1586  case WID_ID_DROPDOWN_CRITERIA: {
1587  Dimension d = {0, 0};
1588  for (uint i = 0; IndustryDirectoryWindow::sorter_names[i] != INVALID_STRING_ID; i++) {
1589  d = maxdim(d, GetStringBoundingBox(IndustryDirectoryWindow::sorter_names[i]));
1590  }
1591  d.width += padding.width;
1592  d.height += padding.height;
1593  *size = maxdim(*size, d);
1594  break;
1595  }
1596 
1597  case WID_ID_INDUSTRY_LIST: {
1598  Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1599  for (uint i = 0; i < this->industries.size(); i++) {
1600  d = maxdim(d, GetStringBoundingBox(this->GetIndustryString(this->industries[i])));
1601  }
1602  resize->height = d.height;
1603  d.height *= 5;
1604  d.width += padding.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1605  d.height += padding.height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
1606  *size = maxdim(*size, d);
1607  break;
1608  }
1609  }
1610  }
1611 
1612 
1613  void OnClick(Point pt, int widget, int click_count) override
1614  {
1615  switch (widget) {
1616  case WID_ID_DROPDOWN_ORDER:
1617  this->industries.ToggleSortOrder();
1618  this->SetDirty();
1619  break;
1620 
1622  ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1623  break;
1624 
1625  case WID_ID_FILTER_BY_ACC_CARGO: // Cargo filter dropdown
1626  ShowDropDownMenu(this, this->cargo_filter_texts, this->accepted_cargo_filter_criteria, WID_ID_FILTER_BY_ACC_CARGO, 0, 0);
1627  break;
1628 
1629  case WID_ID_FILTER_BY_PROD_CARGO: // Cargo filter dropdown
1630  ShowDropDownMenu(this, this->cargo_filter_texts, this->produced_cargo_filter_criteria, WID_ID_FILTER_BY_PROD_CARGO, 0, 0);
1631  break;
1632 
1633  case WID_ID_INDUSTRY_LIST: {
1634  uint p = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_ID_INDUSTRY_LIST, WD_FRAMERECT_TOP);
1635  if (p < this->industries.size()) {
1636  if (_ctrl_pressed) {
1637  ShowExtraViewPortWindow(this->industries[p]->location.tile);
1638  } else {
1639  ScrollMainWindowToTile(this->industries[p]->location.tile);
1640  }
1641  }
1642  break;
1643  }
1644  }
1645  }
1646 
1647  void OnDropdownSelect(int widget, int index) override
1648  {
1649  switch (widget) {
1650  case WID_ID_DROPDOWN_CRITERIA: {
1651  if (this->industries.SortType() != index) {
1652  this->industries.SetSortType(index);
1653  this->BuildSortIndustriesList();
1654  }
1655  break;
1656  }
1657 
1659  this->SetAcceptedCargoFilterIndex(index);
1660  this->BuildSortIndustriesList();
1661  break;
1662  }
1663 
1665  this->SetProducedCargoFilterIndex(index);
1666  this->BuildSortIndustriesList();
1667  break;
1668  }
1669  }
1670  }
1671 
1672  void OnResize() override
1673  {
1674  this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST);
1675  }
1676 
1677  void OnPaint() override
1678  {
1679  if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1680  this->DrawWidgets();
1681  }
1682 
1683  void OnHundredthTick() override
1684  {
1685  this->industries.ForceResort();
1686  this->BuildSortIndustriesList();
1687  }
1688 
1694  void OnInvalidateData(int data = 0, bool gui_scope = true) override
1695  {
1696  switch (data) {
1697  case IDIWD_FORCE_REBUILD:
1698  /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1699  this->industries.ForceRebuild();
1700  break;
1701 
1702  case IDIWD_PRODUCTION_CHANGE:
1703  if (this->industries.SortType() == 2) this->industries.ForceResort();
1704  break;
1705 
1706  default:
1707  this->industries.ForceResort();
1708  break;
1709  }
1710  }
1711 };
1712 
1713 Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1714 
1715 /* Available station sorting functions. */
1716 GUIIndustryList::SortFunction * const IndustryDirectoryWindow::sorter_funcs[] = {
1717  &IndustryNameSorter,
1718  &IndustryTypeSorter,
1719  &IndustryProductionSorter,
1720  &IndustryTransportedCargoSorter
1721 };
1722 
1723 /* Names of the sorting functions */
1724 const StringID IndustryDirectoryWindow::sorter_names[] = {
1725  STR_SORT_BY_NAME,
1726  STR_SORT_BY_TYPE,
1727  STR_SORT_BY_PRODUCTION,
1728  STR_SORT_BY_TRANSPORTED,
1730 };
1731 
1732 
1735  WDP_AUTO, "list_industries", 428, 190,
1737  0,
1738  _nested_industry_directory_widgets, lengthof(_nested_industry_directory_widgets)
1739 );
1740 
1741 void ShowIndustryDirectory()
1742 {
1743  AllocateWindowDescFront<IndustryDirectoryWindow>(&_industry_directory_desc, 0);
1744 }
1745 
1749  NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1750  NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION), SetDataTip(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1751  NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1752  NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1753  NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1754  EndContainer(),
1759  NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1760  SetDataTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1761  NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1762  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1763  SetDataTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1764  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1765  SetDataTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1766  EndContainer(),
1767  EndContainer(),
1769  NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_IC_SCROLLBAR),
1770  NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1771  EndContainer(),
1772  EndContainer(),
1773 };
1774 
1777  WDP_AUTO, "industry_cargoes", 300, 210,
1779  0,
1780  _nested_industry_cargoes_widgets, lengthof(_nested_industry_cargoes_widgets)
1781 );
1782 
1791 };
1792 
1793 static const uint MAX_CARGOES = 16;
1794 
1797  static const int VERT_INTER_INDUSTRY_SPACE;
1798  static const int HOR_CARGO_BORDER_SPACE;
1799  static const int CARGO_STUB_WIDTH;
1800  static const int HOR_CARGO_WIDTH, HOR_CARGO_SPACE;
1801  static const int VERT_CARGO_SPACE, VERT_CARGO_EDGE;
1802  static const int BLOB_DISTANCE, BLOB_WIDTH, BLOB_HEIGHT;
1803 
1804  static const int INDUSTRY_LINE_COLOUR;
1805  static const int CARGO_LINE_COLOUR;
1806 
1807  static int small_height, normal_height;
1808  static int cargo_field_width;
1809  static int industry_width;
1810  static uint max_cargoes;
1811 
1813  union {
1814  struct {
1815  IndustryType ind_type;
1816  CargoID other_produced[MAX_CARGOES];
1817  CargoID other_accepted[MAX_CARGOES];
1818  } industry;
1819  struct {
1820  CargoID vertical_cargoes[MAX_CARGOES];
1822  CargoID supp_cargoes[MAX_CARGOES];
1823  byte top_end;
1824  CargoID cust_cargoes[MAX_CARGOES];
1825  byte bottom_end;
1826  } cargo;
1827  struct {
1829  bool left_align;
1830  } cargo_label;
1832  } u; // Data for each type.
1833 
1839  {
1840  this->type = type;
1841  }
1842 
1848  void MakeIndustry(IndustryType ind_type)
1849  {
1850  this->type = CFT_INDUSTRY;
1851  this->u.industry.ind_type = ind_type;
1852  MemSetT(this->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
1853  MemSetT(this->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
1854  }
1855 
1862  int ConnectCargo(CargoID cargo, bool producer)
1863  {
1864  assert(this->type == CFT_CARGO);
1865  if (cargo == INVALID_CARGO) return -1;
1866 
1867  /* Find the vertical cargo column carrying the cargo. */
1868  int column = -1;
1869  for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
1870  if (cargo == this->u.cargo.vertical_cargoes[i]) {
1871  column = i;
1872  break;
1873  }
1874  }
1875  if (column < 0) return -1;
1876 
1877  if (producer) {
1878  assert(this->u.cargo.supp_cargoes[column] == INVALID_CARGO);
1879  this->u.cargo.supp_cargoes[column] = column;
1880  } else {
1881  assert(this->u.cargo.cust_cargoes[column] == INVALID_CARGO);
1882  this->u.cargo.cust_cargoes[column] = column;
1883  }
1884  return column;
1885  }
1886 
1892  {
1893  assert(this->type == CFT_CARGO);
1894 
1895  for (uint i = 0; i < MAX_CARGOES; i++) {
1896  if (this->u.cargo.supp_cargoes[i] != INVALID_CARGO) return true;
1897  if (this->u.cargo.cust_cargoes[i] != INVALID_CARGO) return true;
1898  }
1899  return false;
1900  }
1901 
1911  void MakeCargo(const CargoID *cargoes, uint length, int count = -1, bool top_end = false, bool bottom_end = false)
1912  {
1913  this->type = CFT_CARGO;
1914  uint i;
1915  uint num = 0;
1916  for (i = 0; i < MAX_CARGOES && i < length; i++) {
1917  if (cargoes[i] != INVALID_CARGO) {
1918  this->u.cargo.vertical_cargoes[num] = cargoes[i];
1919  num++;
1920  }
1921  }
1922  this->u.cargo.num_cargoes = (count < 0) ? num : count;
1923  for (; num < MAX_CARGOES; num++) this->u.cargo.vertical_cargoes[num] = INVALID_CARGO;
1924  this->u.cargo.top_end = top_end;
1925  this->u.cargo.bottom_end = bottom_end;
1926  MemSetT(this->u.cargo.supp_cargoes, INVALID_CARGO, MAX_CARGOES);
1927  MemSetT(this->u.cargo.cust_cargoes, INVALID_CARGO, MAX_CARGOES);
1928  }
1929 
1936  void MakeCargoLabel(const CargoID *cargoes, uint length, bool left_align)
1937  {
1938  this->type = CFT_CARGO_LABEL;
1939  uint i;
1940  for (i = 0; i < MAX_CARGOES && i < length; i++) this->u.cargo_label.cargoes[i] = cargoes[i];
1941  for (; i < MAX_CARGOES; i++) this->u.cargo_label.cargoes[i] = INVALID_CARGO;
1942  this->u.cargo_label.left_align = left_align;
1943  }
1944 
1949  void MakeHeader(StringID textid)
1950  {
1951  this->type = CFT_HEADER;
1952  this->u.header = textid;
1953  }
1954 
1960  int GetCargoBase(int xpos) const
1961  {
1962  assert(this->type == CFT_CARGO);
1963  int n = this->u.cargo.num_cargoes;
1964 
1965  if (n % 2 == 0) {
1966  return xpos + cargo_field_width / 2 - (HOR_CARGO_WIDTH + HOR_CARGO_SPACE / 2) * (n / 2);
1967  } else {
1968  return xpos + cargo_field_width / 2 - HOR_CARGO_WIDTH / 2 - (HOR_CARGO_WIDTH + HOR_CARGO_SPACE) * (n / 2);
1969  }
1970  }
1971 
1977  void Draw(int xpos, int ypos) const
1978  {
1979  switch (this->type) {
1980  case CFT_EMPTY:
1981  case CFT_SMALL_EMPTY:
1982  break;
1983 
1984  case CFT_HEADER:
1985  ypos += (small_height - FONT_HEIGHT_NORMAL) / 2;
1986  DrawString(xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
1987  break;
1988 
1989  case CFT_INDUSTRY: {
1990  int ypos1 = ypos + VERT_INTER_INDUSTRY_SPACE / 2;
1991  int ypos2 = ypos + normal_height - 1 - VERT_INTER_INDUSTRY_SPACE / 2;
1992  int xpos2 = xpos + industry_width - 1;
1993  GfxDrawLine(xpos, ypos1, xpos2, ypos1, INDUSTRY_LINE_COLOUR);
1994  GfxDrawLine(xpos, ypos1, xpos, ypos2, INDUSTRY_LINE_COLOUR);
1995  GfxDrawLine(xpos, ypos2, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1996  GfxDrawLine(xpos2, ypos1, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1997  ypos += (normal_height - FONT_HEIGHT_NORMAL) / 2;
1998  if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
1999  const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
2000  DrawString(xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
2001 
2002  /* Draw the industry legend. */
2003  int blob_left, blob_right;
2004  if (_current_text_dir == TD_RTL) {
2005  blob_right = xpos2 - BLOB_DISTANCE;
2006  blob_left = blob_right - BLOB_WIDTH;
2007  } else {
2008  blob_left = xpos + BLOB_DISTANCE;
2009  blob_right = blob_left + BLOB_WIDTH;
2010  }
2011  GfxFillRect(blob_left, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT, blob_right, ypos2 - BLOB_DISTANCE, PC_BLACK); // Border
2012  GfxFillRect(blob_left + 1, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT + 1, blob_right - 1, ypos2 - BLOB_DISTANCE - 1, indsp->map_colour);
2013  } else {
2014  DrawString(xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
2015  }
2016 
2017  /* Draw the other_produced/other_accepted cargoes. */
2018  const CargoID *other_right, *other_left;
2019  if (_current_text_dir == TD_RTL) {
2020  other_right = this->u.industry.other_accepted;
2021  other_left = this->u.industry.other_produced;
2022  } else {
2023  other_right = this->u.industry.other_produced;
2024  other_left = this->u.industry.other_accepted;
2025  }
2026  ypos1 += VERT_CARGO_EDGE;
2027  for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2028  if (other_right[i] != INVALID_CARGO) {
2029  const CargoSpec *csp = CargoSpec::Get(other_right[i]);
2030  int xp = xpos + industry_width + CARGO_STUB_WIDTH;
2031  DrawHorConnection(xpos + industry_width, xp - 1, ypos1, csp);
2032  GfxDrawLine(xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
2033  }
2034  if (other_left[i] != INVALID_CARGO) {
2035  const CargoSpec *csp = CargoSpec::Get(other_left[i]);
2036  int xp = xpos - CARGO_STUB_WIDTH;
2037  DrawHorConnection(xp + 1, xpos - 1, ypos1, csp);
2038  GfxDrawLine(xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
2039  }
2040  ypos1 += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
2041  }
2042  break;
2043  }
2044 
2045  case CFT_CARGO: {
2046  int cargo_base = this->GetCargoBase(xpos);
2047  int top = ypos + (this->u.cargo.top_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0);
2048  int bot = ypos - (this->u.cargo.bottom_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0) + normal_height - 1;
2049  int colpos = cargo_base;
2050  for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
2051  if (this->u.cargo.top_end) GfxDrawLine(colpos, top - 1, colpos + HOR_CARGO_WIDTH - 1, top - 1, CARGO_LINE_COLOUR);
2052  if (this->u.cargo.bottom_end) GfxDrawLine(colpos, bot + 1, colpos + HOR_CARGO_WIDTH - 1, bot + 1, CARGO_LINE_COLOUR);
2053  GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2054  colpos++;
2055  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
2056  GfxFillRect(colpos, top, colpos + HOR_CARGO_WIDTH - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
2057  colpos += HOR_CARGO_WIDTH - 2;
2058  GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
2059  colpos += 1 + HOR_CARGO_SPACE;
2060  }
2061 
2062  const CargoID *hor_left, *hor_right;
2063  if (_current_text_dir == TD_RTL) {
2064  hor_left = this->u.cargo.cust_cargoes;
2065  hor_right = this->u.cargo.supp_cargoes;
2066  } else {
2067  hor_left = this->u.cargo.supp_cargoes;
2068  hor_right = this->u.cargo.cust_cargoes;
2069  }
2070  ypos += VERT_CARGO_EDGE + VERT_INTER_INDUSTRY_SPACE / 2;
2071  for (uint i = 0; i < MAX_CARGOES; i++) {
2072  if (hor_left[i] != INVALID_CARGO) {
2073  int col = hor_left[i];
2074  int dx = 0;
2075  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2076  for (; col > 0; col--) {
2077  int lf = cargo_base + col * HOR_CARGO_WIDTH + (col - 1) * HOR_CARGO_SPACE;
2078  DrawHorConnection(lf, lf + HOR_CARGO_SPACE - dx, ypos, csp);
2079  dx = 1;
2080  }
2081  DrawHorConnection(xpos, cargo_base - dx, ypos, csp);
2082  }
2083  if (hor_right[i] != INVALID_CARGO) {
2084  int col = hor_right[i];
2085  int dx = 0;
2086  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
2087  for (; col < this->u.cargo.num_cargoes - 1; col++) {
2088  int lf = cargo_base + (col + 1) * HOR_CARGO_WIDTH + col * HOR_CARGO_SPACE;
2089  DrawHorConnection(lf + dx - 1, lf + HOR_CARGO_SPACE - 1, ypos, csp);
2090  dx = 1;
2091  }
2092  DrawHorConnection(cargo_base + col * HOR_CARGO_SPACE + (col + 1) * HOR_CARGO_WIDTH - 1 + dx, xpos + CargoesField::cargo_field_width - 1, ypos, csp);
2093  }
2094  ypos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
2095  }
2096  break;
2097  }
2098 
2099  case CFT_CARGO_LABEL:
2100  ypos += VERT_CARGO_EDGE + VERT_INTER_INDUSTRY_SPACE / 2;
2101  for (uint i = 0; i < MAX_CARGOES; i++) {
2102  if (this->u.cargo_label.cargoes[i] != INVALID_CARGO) {
2103  const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
2104  DrawString(xpos + WD_FRAMERECT_LEFT, xpos + industry_width - 1 - WD_FRAMERECT_RIGHT, ypos, csp->name, TC_WHITE,
2105  (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
2106  }
2107  ypos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
2108  }
2109  break;
2110 
2111  default:
2112  NOT_REACHED();
2113  }
2114  }
2115 
2123  CargoID CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
2124  {
2125  assert(this->type == CFT_CARGO);
2126 
2127  /* Vertical matching. */
2128  int cpos = this->GetCargoBase(0);
2129  uint col;
2130  for (col = 0; col < this->u.cargo.num_cargoes; col++) {
2131  if (pt.x < cpos) break;
2132  if (pt.x < cpos + CargoesField::HOR_CARGO_WIDTH) return this->u.cargo.vertical_cargoes[col];
2134  }
2135  /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
2136 
2137  int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
2138  uint row;
2139  for (row = 0; row < MAX_CARGOES; row++) {
2140  if (pt.y < vpos) return INVALID_CARGO;
2141  if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
2142  vpos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
2143  }
2144  if (row == MAX_CARGOES) return INVALID_CARGO;
2145 
2146  /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
2147  if (col == 0) {
2148  if (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]];
2149  if (left != nullptr) {
2150  if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
2151  if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
2152  }
2153  return INVALID_CARGO;
2154  }
2155  if (col == this->u.cargo.num_cargoes) {
2156  if (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]];
2157  if (right != nullptr) {
2158  if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
2159  if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
2160  }
2161  return INVALID_CARGO;
2162  }
2163  if (row >= col) {
2164  /* Clicked somewhere in-between vertical cargo connection.
2165  * Since the horizontal connection is made in the same order as the vertical list, the above condition
2166  * ensures we are left-below the main diagonal, thus at the supplying side.
2167  */
2168  return (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]] : INVALID_CARGO;
2169  } else {
2170  /* Clicked at a customer connection. */
2171  return (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]] : INVALID_CARGO;
2172  }
2173  }
2174 
2181  {
2182  assert(this->type == CFT_CARGO_LABEL);
2183 
2184  int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
2185  uint row;
2186  for (row = 0; row < MAX_CARGOES; row++) {
2187  if (pt.y < vpos) return INVALID_CARGO;
2188  if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
2189  vpos += FONT_HEIGHT_NORMAL + VERT_CARGO_SPACE;
2190  }
2191  if (row == MAX_CARGOES) return INVALID_CARGO;
2192  return this->u.cargo_label.cargoes[row];
2193  }
2194 
2195 private:
2203  static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
2204  {
2205  GfxDrawLine(left, top, right, top, CARGO_LINE_COLOUR);
2206  GfxFillRect(left, top + 1, right, top + FONT_HEIGHT_NORMAL - 2, csp->legend_colour, FILLRECT_OPAQUE);
2207  GfxDrawLine(left, top + FONT_HEIGHT_NORMAL - 1, right, top + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
2208  }
2209 };
2210 
2211 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, produced_cargo));
2212 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, accepts_cargo));
2213 
2220 
2221 const int CargoesField::HOR_CARGO_BORDER_SPACE = 15;
2222 const int CargoesField::CARGO_STUB_WIDTH = 10;
2223 const int CargoesField::HOR_CARGO_WIDTH = 15;
2224 const int CargoesField::HOR_CARGO_SPACE = 5;
2225 const int CargoesField::VERT_CARGO_EDGE = 4;
2226 const int CargoesField::VERT_CARGO_SPACE = 4;
2227 
2228 const int CargoesField::BLOB_DISTANCE = 5;
2229 const int CargoesField::BLOB_WIDTH = 12;
2230 const int CargoesField::BLOB_HEIGHT = 9;
2231 
2234 
2236 struct CargoesRow {
2237  CargoesField columns[5];
2238 
2243  void ConnectIndustryProduced(int column)
2244  {
2245  CargoesField *ind_fld = this->columns + column;
2246  CargoesField *cargo_fld = this->columns + column + 1;
2247  assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2248 
2249  MemSetT(ind_fld->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
2250 
2251  if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2252  CargoID others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
2253  int other_count = 0;
2254 
2255  const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2256  assert(CargoesField::max_cargoes <= lengthof(indsp->produced_cargo));
2257  for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2258  int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
2259  if (col < 0) others[other_count++] = indsp->produced_cargo[i];
2260  }
2261 
2262  /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2263  for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2264  if (cargo_fld->u.cargo.supp_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_produced[i] = others[--other_count];
2265  }
2266  } else {
2267  /* Houses only display what is demanded. */
2268  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2269  CargoID cid = cargo_fld->u.cargo.vertical_cargoes[i];
2270  if (cid == CT_PASSENGERS || cid == CT_MAIL) cargo_fld->ConnectCargo(cid, true);
2271  }
2272  }
2273  }
2274 
2280  void MakeCargoLabel(int column, bool accepting)
2281  {
2282  CargoID cargoes[MAX_CARGOES];
2283  MemSetT(cargoes, INVALID_CARGO, lengthof(cargoes));
2284 
2285  CargoesField *label_fld = this->columns + column;
2286  CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2287 
2288  assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2289  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2290  int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2291  if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2292  }
2293  label_fld->MakeCargoLabel(cargoes, lengthof(cargoes), accepting);
2294  }
2295 
2296 
2301  void ConnectIndustryAccepted(int column)
2302  {
2303  CargoesField *ind_fld = this->columns + column;
2304  CargoesField *cargo_fld = this->columns + column - 1;
2305  assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2306 
2307  MemSetT(ind_fld->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
2308 
2309  if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2310  CargoID others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2311  int other_count = 0;
2312 
2313  const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2315  for (uint i = 0; i < CargoesField::max_cargoes; i++) {
2316  int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2317  if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2318  }
2319 
2320  /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2321  for (uint i = 0; i < CargoesField::max_cargoes && other_count > 0; i++) {
2322  if (cargo_fld->u.cargo.cust_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2323  }
2324  } else {
2325  /* Houses only display what is demanded. */
2326  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2327  for (uint h = 0; h < NUM_HOUSES; h++) {
2328  HouseSpec *hs = HouseSpec::Get(h);
2329  if (!hs->enabled) continue;
2330 
2331  for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2332  if (hs->cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs->accepts_cargo[j]) {
2333  cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2334  goto next_cargo;
2335  }
2336  }
2337  }
2338 next_cargo: ;
2339  }
2340  }
2341  }
2342 };
2343 
2344 
2373  static const int HOR_TEXT_PADDING, VERT_TEXT_PADDING;
2374 
2375  typedef std::vector<CargoesRow> Fields;
2376 
2377  Fields fields;
2378  uint ind_cargo;
2381  Scrollbar *vscroll;
2382 
2383  IndustryCargoesWindow(int id) : Window(&_industry_cargoes_desc)
2384  {
2385  this->OnInit();
2386  this->CreateNestedTree();
2387  this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2388  this->FinishInitNested(0);
2389  this->OnInvalidateData(id);
2390  }
2391 
2392  void OnInit() override
2393  {
2394  /* Initialize static CargoesField size variables. */
2395  Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2396  d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2398  d.height += WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
2399  CargoesField::small_height = d.height;
2400 
2401  /* Decide about the size of the box holding the text of an industry type. */
2402  this->ind_textsize.width = 0;
2403  this->ind_textsize.height = 0;
2405  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2406  const IndustrySpec *indsp = GetIndustrySpec(it);
2407  if (!indsp->enabled) continue;
2408  this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(indsp->name));
2410  CargoesField::max_cargoes = max<uint>(CargoesField::max_cargoes, std::count_if(indsp->produced_cargo, endof(indsp->produced_cargo), IsCargoIDValid));
2411  }
2412  d.width = max(d.width, this->ind_textsize.width);
2413  d.height = this->ind_textsize.height;
2414  this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2415 
2416  /* Compute max size of the cargo texts. */
2417  this->cargo_textsize.width = 0;
2418  this->cargo_textsize.height = 0;
2419  for (uint i = 0; i < NUM_CARGO; i++) {
2420  const CargoSpec *csp = CargoSpec::Get(i);
2421  if (!csp->IsValid()) continue;
2422  this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(csp->name));
2423  }
2424  d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2425  this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2426 
2427  d.width += 2 * HOR_TEXT_PADDING;
2428  /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2430  d.height = max(d.height + 2 * VERT_TEXT_PADDING, min_ind_height);
2431 
2432  CargoesField::industry_width = d.width;
2434 
2435  /* Width of a #CFT_CARGO field. */
2437  }
2438 
2439  void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2440  {
2441  switch (widget) {
2442  case WID_IC_PANEL:
2444  break;
2445 
2446  case WID_IC_IND_DROPDOWN:
2447  size->width = max(size->width, this->ind_textsize.width + padding.width);
2448  break;
2449 
2450  case WID_IC_CARGO_DROPDOWN:
2451  size->width = max(size->width, this->cargo_textsize.width + padding.width);
2452  break;
2453  }
2454  }
2455 
2456 
2458  void SetStringParameters (int widget) const override
2459  {
2460  if (widget != WID_IC_CAPTION) return;
2461 
2462  if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2463  const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2464  SetDParam(0, indsp->name);
2465  } else {
2466  const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2467  SetDParam(0, csp->name);
2468  }
2469  }
2470 
2479  static bool HasCommonValidCargo(const CargoID *cargoes1, uint length1, const CargoID *cargoes2, uint length2)
2480  {
2481  while (length1 > 0) {
2482  if (*cargoes1 != INVALID_CARGO) {
2483  for (uint i = 0; i < length2; i++) if (*cargoes1 == cargoes2[i]) return true;
2484  }
2485  cargoes1++;
2486  length1--;
2487  }
2488  return false;
2489  }
2490 
2497  static bool HousesCanSupply(const CargoID *cargoes, uint length)
2498  {
2499  for (uint i = 0; i < length; i++) {
2500  if (cargoes[i] == INVALID_CARGO) continue;
2501  if (cargoes[i] == CT_PASSENGERS || cargoes[i] == CT_MAIL) return true;
2502  }
2503  return false;
2504  }
2505 
2512  static bool HousesCanAccept(const CargoID *cargoes, uint length)
2513  {
2514  HouseZones climate_mask;
2516  case LT_TEMPERATE: climate_mask = HZ_TEMP; break;
2517  case LT_ARCTIC: climate_mask = HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW; break;
2518  case LT_TROPIC: climate_mask = HZ_SUBTROPIC; break;
2519  case LT_TOYLAND: climate_mask = HZ_TOYLND; break;
2520  default: NOT_REACHED();
2521  }
2522  for (uint i = 0; i < length; i++) {
2523  if (cargoes[i] == INVALID_CARGO) continue;
2524 
2525  for (uint h = 0; h < NUM_HOUSES; h++) {
2526  HouseSpec *hs = HouseSpec::Get(h);
2527  if (!hs->enabled || !(hs->building_availability & climate_mask)) continue;
2528 
2529  for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2530  if (hs->cargo_acceptance[j] > 0 && cargoes[i] == hs->accepts_cargo[j]) return true;
2531  }
2532  }
2533  }
2534  return false;
2535  }
2536 
2543  static int CountMatchingAcceptingIndustries(const CargoID *cargoes, uint length)
2544  {
2545  int count = 0;
2546  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2547  const IndustrySpec *indsp = GetIndustrySpec(it);
2548  if (!indsp->enabled) continue;
2549 
2550  if (HasCommonValidCargo(cargoes, length, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) count++;
2551  }
2552  return count;
2553  }
2554 
2561  static int CountMatchingProducingIndustries(const CargoID *cargoes, uint length)
2562  {
2563  int count = 0;
2564  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2565  const IndustrySpec *indsp = GetIndustrySpec(it);
2566  if (!indsp->enabled) continue;
2567 
2568  if (HasCommonValidCargo(cargoes, length, indsp->produced_cargo, lengthof(indsp->produced_cargo))) count++;
2569  }
2570  return count;
2571  }
2572 
2579  void ShortenCargoColumn(int column, int top, int bottom)
2580  {
2581  while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2582  this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2583  top++;
2584  }
2585  this->fields[top].columns[column].u.cargo.top_end = true;
2586 
2587  while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2588  this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2589  bottom--;
2590  }
2591  this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2592  }
2593 
2600  void PlaceIndustry(int row, int col, IndustryType it)
2601  {
2602  assert(this->fields[row].columns[col].type == CFT_EMPTY);
2603  this->fields[row].columns[col].MakeIndustry(it);
2604  if (col == 0) {
2605  this->fields[row].ConnectIndustryProduced(col);
2606  } else {
2607  this->fields[row].ConnectIndustryAccepted(col);
2608  }
2609  }
2610 
2615  {
2616  if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2617 
2618  /* Only notify the smallmap window if it exists. In particular, do not
2619  * bring it to the front to prevent messing up any nice layout of the user. */
2621  }
2622 
2627  void ComputeIndustryDisplay(IndustryType it)
2628  {
2629  this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION;
2630  this->ind_cargo = it;
2631  _displayed_industries.reset();
2632  _displayed_industries.set(it);
2633 
2634  this->fields.clear();
2635  /*C++17: CargoesRow &row = */ this->fields.emplace_back();
2636  CargoesRow &row = this->fields.back();
2637  row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2641  row.columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2642 
2643  const IndustrySpec *central_sp = GetIndustrySpec(it);
2644  bool houses_supply = HousesCanSupply(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2645  bool houses_accept = HousesCanAccept(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2646  /* Make a field consisting of two cargo columns. */
2647  int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo)) + houses_supply;
2648  int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo, lengthof(central_sp->produced_cargo)) + houses_accept;
2649  int num_indrows = max(3, max(num_supp, num_cust)); // One is needed for the 'it' industry, and 2 for the cargo labels.
2650  for (int i = 0; i < num_indrows; i++) {
2651  /*C++17: CargoesRow &row = */ this->fields.emplace_back();
2652  CargoesRow &row = this->fields.back();
2653  row.columns[0].MakeEmpty(CFT_EMPTY);
2654  row.columns[1].MakeCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2655  row.columns[2].MakeEmpty(CFT_EMPTY);
2656  row.columns[3].MakeCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2657  row.columns[4].MakeEmpty(CFT_EMPTY);
2658  }
2659  /* Add central industry. */
2660  int central_row = 1 + num_indrows / 2;
2661  this->fields[central_row].columns[2].MakeIndustry(it);
2662  this->fields[central_row].ConnectIndustryProduced(2);
2663  this->fields[central_row].ConnectIndustryAccepted(2);
2664 
2665  /* Add cargo labels. */
2666  this->fields[central_row - 1].MakeCargoLabel(2, true);
2667  this->fields[central_row + 1].MakeCargoLabel(2, false);
2668 
2669  /* Add suppliers and customers of the 'it' industry. */
2670  int supp_count = 0;
2671  int cust_count = 0;
2672  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2673  const IndustrySpec *indsp = GetIndustrySpec(it);
2674  if (!indsp->enabled) continue;
2675 
2676  if (HasCommonValidCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo), indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2677  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2678  _displayed_industries.set(it);
2679  supp_count++;
2680  }
2681  if (HasCommonValidCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo), indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2682  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2683  _displayed_industries.set(it);
2684  cust_count++;
2685  }
2686  }
2687  if (houses_supply) {
2688  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2689  supp_count++;
2690  }
2691  if (houses_accept) {
2692  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2693  cust_count++;
2694  }
2695 
2696  this->ShortenCargoColumn(1, 1, num_indrows);
2697  this->ShortenCargoColumn(3, 1, num_indrows);
2698  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2700  this->SetDirty();
2701  this->NotifySmallmap();
2702  }
2703 
2709  {
2710  this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_CARGO_CAPTION;
2711  this->ind_cargo = cid + NUM_INDUSTRYTYPES;
2712  _displayed_industries.reset();
2713 
2714  this->fields.clear();
2715  /*C++17: CargoesRow &row = */ this->fields.emplace_back();
2716  CargoesRow &row = this->fields.back();
2717  row.columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2719  row.columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2722 
2723  bool houses_supply = HousesCanSupply(&cid, 1);
2724  bool houses_accept = HousesCanAccept(&cid, 1);
2725  int num_supp = CountMatchingProducingIndustries(&cid, 1) + houses_supply + 1; // Ensure room for the cargo label.
2726  int num_cust = CountMatchingAcceptingIndustries(&cid, 1) + houses_accept;
2727  int num_indrows = max(num_supp, num_cust);
2728  for (int i = 0; i < num_indrows; i++) {
2729  /*C++17: CargoesRow &row = */ this->fields.emplace_back();
2730  CargoesRow &row = this->fields.back();
2731  row.columns[0].MakeEmpty(CFT_EMPTY);
2732  row.columns[1].MakeCargo(&cid, 1);
2733  row.columns[2].MakeEmpty(CFT_EMPTY);
2734  row.columns[3].MakeEmpty(CFT_EMPTY);
2735  row.columns[4].MakeEmpty(CFT_EMPTY);
2736  }
2737 
2738  this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2739 
2740  /* Add suppliers and customers of the cargo. */
2741  int supp_count = 0;
2742  int cust_count = 0;
2743  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2744  const IndustrySpec *indsp = GetIndustrySpec(it);
2745  if (!indsp->enabled) continue;
2746 
2747  if (HasCommonValidCargo(&cid, 1, indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2748  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2749  _displayed_industries.set(it);
2750  supp_count++;
2751  }
2752  if (HasCommonValidCargo(&cid, 1, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2753  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2754  _displayed_industries.set(it);
2755  cust_count++;
2756  }
2757  }
2758  if (houses_supply) {
2759  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2760  supp_count++;
2761  }
2762  if (houses_accept) {
2763  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2764  cust_count++;
2765  }
2766 
2767  this->ShortenCargoColumn(1, 1, num_indrows);
2768  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2770  this->SetDirty();
2771  this->NotifySmallmap();
2772  }
2773 
2781  void OnInvalidateData(int data = 0, bool gui_scope = true) override
2782  {
2783  if (!gui_scope) return;
2784  if (data == NUM_INDUSTRYTYPES) {
2785  if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2786  this->RaiseWidget(WID_IC_NOTIFY);
2787  this->SetWidgetDirty(WID_IC_NOTIFY);
2788  }
2789  return;
2790  }
2791 
2792  assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2793  this->ComputeIndustryDisplay(data);
2794  }
2795 
2796  void DrawWidget(const Rect &r, int widget) const override
2797  {
2798  if (widget != WID_IC_PANEL) return;
2799 
2800  DrawPixelInfo tmp_dpi, *old_dpi;
2801  int width = r.right - r.left + 1;
2802  int height = r.bottom - r.top + 1 - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM;
2803  if (!FillDrawPixelInfo(&tmp_dpi, r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP, width, height)) return;
2804  old_dpi = _cur_dpi;
2805  _cur_dpi = &tmp_dpi;
2806 
2807  int left_pos = WD_FRAMERECT_LEFT;
2808  if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::cargo_field_width) / 2;
2809  int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2810 
2811  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2812  int vpos = -this->vscroll->GetPosition() * nwp->resize_y;
2813  for (uint i = 0; i < this->fields.size(); i++) {
2814  int row_height = (i == 0) ? CargoesField::small_height : CargoesField::normal_height;
2815  if (vpos + row_height >= 0) {
2816  int xpos = left_pos;
2817  int col, dir;
2818  if (_current_text_dir == TD_RTL) {
2819  col = last_column;
2820  dir = -1;
2821  } else {
2822  col = 0;
2823  dir = 1;
2824  }
2825  while (col >= 0 && col <= last_column) {
2826  this->fields[i].columns[col].Draw(xpos, vpos);
2828  col += dir;
2829  }
2830  }
2831  vpos += row_height;
2832  if (vpos >= height) break;
2833  }
2834 
2835  _cur_dpi = old_dpi;
2836  }
2837 
2845  bool CalculatePositionInWidget(Point pt, Point *fieldxy, Point *xy)
2846  {
2847  const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2848  pt.x -= nw->pos_x;
2849  pt.y -= nw->pos_y;
2850 
2851  int vpos = WD_FRAMERECT_TOP + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
2852  if (pt.y < vpos) return false;
2853 
2854  int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
2855  if (row + 1 >= (int)this->fields.size()) return false;
2856  vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
2857  row++; // rebase row to match index of this->fields.
2858 
2859  int xpos = 2 * WD_FRAMERECT_LEFT + ((this->ind_cargo < NUM_INDUSTRYTYPES) ? 0 : (CargoesField::industry_width + CargoesField::cargo_field_width) / 2);
2860  if (pt.x < xpos) return false;
2861  int column;
2862  for (column = 0; column <= 5; column++) {
2864  if (pt.x < xpos + width) break;
2865  xpos += width;
2866  }
2867  int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2868  if (column > num_columns) return false;
2869  xpos = pt.x - xpos;
2870 
2871  /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
2872  fieldxy->y = row;
2873  xy->y = vpos;
2874  if (_current_text_dir == TD_RTL) {
2875  fieldxy->x = num_columns - column;
2876  xy->x = ((column & 1) ? CargoesField::cargo_field_width : CargoesField::industry_width) - xpos;
2877  } else {
2878  fieldxy->x = column;
2879  xy->x = xpos;
2880  }
2881  return true;
2882  }
2883 
2884  void OnClick(Point pt, int widget, int click_count) override
2885  {
2886  switch (widget) {
2887  case WID_IC_PANEL: {
2888  Point fieldxy, xy;
2889  if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
2890 
2891  const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2892  switch (fld->type) {
2893  case CFT_INDUSTRY:
2894  if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES) this->ComputeIndustryDisplay(fld->u.industry.ind_type);
2895  break;
2896 
2897  case CFT_CARGO: {
2898  CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
2899  CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
2900  CargoID cid = fld->CargoClickedAt(lft, rgt, xy);
2901  if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2902  break;
2903  }
2904 
2905  case CFT_CARGO_LABEL: {
2906  CargoID cid = fld->CargoLabelClickedAt(xy);
2907  if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2908  break;
2909  }
2910 
2911  default:
2912  break;
2913  }
2914  break;
2915  }
2916 
2917  case WID_IC_NOTIFY:
2918  this->ToggleWidgetLoweredState(WID_IC_NOTIFY);
2919  this->SetWidgetDirty(WID_IC_NOTIFY);
2920  if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
2921 
2922  if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2923  if (FindWindowByClass(WC_SMALLMAP) == nullptr) ShowSmallMap();
2924  this->NotifySmallmap();
2925  }
2926  break;
2927 
2928  case WID_IC_CARGO_DROPDOWN: {
2929  DropDownList lst;
2930  const CargoSpec *cs;
2932  lst.emplace_back(new DropDownListStringItem(cs->name, cs->Index(), false));
2933  }
2934  if (!lst.empty()) {
2935  int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
2936  ShowDropDownList(this, std::move(lst), selected, WID_IC_CARGO_DROPDOWN, 0, true);
2937  }
2938  break;
2939  }
2940 
2941  case WID_IC_IND_DROPDOWN: {
2942  DropDownList lst;
2943  for (IndustryType ind : _sorted_industry_types) {
2944  const IndustrySpec *indsp = GetIndustrySpec(ind);
2945  if (!indsp->enabled) continue;
2946  lst.emplace_back(new DropDownListStringItem(indsp->name, ind, false));
2947  }
2948  if (!lst.empty()) {
2949  int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
2950  ShowDropDownList(this, std::move(lst), selected, WID_IC_IND_DROPDOWN, 0, true);
2951  }
2952  break;
2953  }
2954  }
2955  }
2956 
2957  void OnDropdownSelect(int widget, int index) override
2958  {
2959  if (index < 0) return;
2960 
2961  switch (widget) {
2962  case WID_IC_CARGO_DROPDOWN:
2963  this->ComputeCargoDisplay(index);
2964  break;
2965 
2966  case WID_IC_IND_DROPDOWN:
2967  this->ComputeIndustryDisplay(index);
2968  break;
2969  }
2970  }
2971 
2972  bool OnTooltip(Point pt, int widget, TooltipCloseCondition close_cond) override
2973  {
2974  if (widget != WID_IC_PANEL) return false;
2975 
2976  Point fieldxy, xy;
2977  if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return false;
2978 
2979  const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2980  CargoID cid = INVALID_CARGO;
2981  switch (fld->type) {
2982  case CFT_CARGO: {
2983  CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : nullptr;
2984  CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : nullptr;
2985  cid = fld->CargoClickedAt(lft, rgt, xy);
2986  break;
2987  }
2988 
2989  case CFT_CARGO_LABEL: {
2990  cid = fld->CargoLabelClickedAt(xy);
2991  break;
2992  }
2993 
2994  case CFT_INDUSTRY:
2995  if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
2996  GuiShowTooltips(this, STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP, 0, nullptr, close_cond);
2997  }
2998  return true;
2999 
3000  default:
3001  break;
3002  }
3003  if (cid != INVALID_CARGO && (this->ind_cargo < NUM_INDUSTRYTYPES || cid != this->ind_cargo - NUM_INDUSTRYTYPES)) {
3004  const CargoSpec *csp = CargoSpec::Get(cid);
3005  uint64 params[5];
3006  params[0] = csp->name;
3007  GuiShowTooltips(this, STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, 1, params, close_cond);
3008  return true;
3009  }
3010 
3011  return false;
3012  }
3013 
3014  void OnResize() override
3015  {
3016  this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL);
3017  }
3018 };
3019 
3022 
3027 static void ShowIndustryCargoesWindow(IndustryType id)
3028 {
3029  if (id >= NUM_INDUSTRYTYPES) {
3030  for (IndustryType ind : _sorted_industry_types) {
3031  const IndustrySpec *indsp = GetIndustrySpec(ind);
3032  if (indsp->enabled) {
3033  id = ind;
3034  break;
3035  }
3036  }
3037  if (id >= NUM_INDUSTRYTYPES) return;
3038  }
3039 
3041  if (w != nullptr) {
3042  w->InvalidateData(id);
3043  return;
3044  }
3045  new IndustryCargoesWindow(id);
3046 }
3047 
3050 {
3052 }
CargoID accepts_cargo[INDUSTRY_NUM_INPUTS]
16 accepted cargoes.
Definition: industrytype.h:120
Nested widget containing a viewport.
Definition: widget_type.h:79
Display chain button.
bool enabled
the house is available to build (true by default, but can be disabled by newgrf)
Definition: house.h:111
Functions related to OTTD&#39;s strings.
void NotifySmallmap()
Notify smallmap that new displayed industries have been selected (in _displayed_industries).
void GenerateIndustries()
This function will create random industries during game creation.
static const int HOR_CARGO_BORDER_SPACE
Amount of space between the left/right edge of a CFT_CARGO field, and the left/right most vertical ca...
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
Update size and resize step of a widget in the window.
Functions/types related to NewGRF debugging.
static void Swap(T &a, T &b)
Type safe swap operation.
Definition: math_func.hpp:275
Transfer storage of cargo suffix information.
Base types for having sorted lists in GUIs.
List template of &#39;things&#39; T to sort in a GUI.
Definition: sortlist_type.h:47
void RebuildDone()
Notify the sortlist that the rebuild is done.
Matrix of the industries.
static const uint MAX_CARGOES
Maximum number of cargoes carried in a CFT_CARGO field in CargoesField.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition: settings.cpp:80
Definition of stuff that is very close to a company, like the company struct itself.
bool _networking
are we in networking mode?
Definition: network.cpp:52
static int GetCargoTransportedPercentsIfValid(const Industry *i, uint id)
Returns percents of cargo transported if industry produces this cargo, else -1.
static uint minu(const uint a, const uint b)
Returns the minimum of two unsigned integers.
Definition: math_func.hpp:68
void DrawWidget(const Rect &r, int widget) const override
Draw the contents of a nested widget.
bool enabled
entity still available (by default true).newgrf can disable it, though
Definition: industrytype.h:139
Build (fund or prospect) a new industry,.
byte production_rate[INDUSTRY_NUM_OUTPUTS]
production rate for each cargo
Definition: industry.h:47
void SortIndustryTypes()
Initialize the list of sorted industry types.
static const uint CALLBACK_FAILED
Different values for Callback result evaluations.
Data about how and where to blit pixels.
Definition: gfx_type.h:154
static const uint8 PC_WHITE
White palette colour.
Definition: gfx_func.h:207
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
Dropdown for the criteria of the sort.
Horizontally center the text.
Definition: gfx_func.h:95
void OnInit() override
Notification that the nested widget tree gets initialized.
static int CountMatchingAcceptingIndustries(const CargoID *cargoes, uint length)
Count how many industries have accepted cargoes in common with one of the supplied set...
static NWidgetPart SetResize(int16 dx, int16 dy)
Widget part function for setting the resize step.
Definition: widget_type.h:928
uint8 raw_industry_construction
type of (raw) industry construction (none, "normal", prospecting)
static bool CDECL CargoFilter(const Industry *const *industry, const std::pair< CargoID, CargoID > &cargoes)
Cargo filter functions.
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen...
Definition: gfx.cpp:110
Offset at right of a matrix cell.
Definition: window_gui.h:77
byte landscape
the landscape we&#39;re currently in
Viewport of the industry.
High level window description.
Definition: window_gui.h:166
byte map_colour
colour used for the small map
Definition: industrytype.h:125
static int industry_width
Width of an industry field.
uint16 count
How many industries are loaded.
Goto button.
void MakeIndustry(IndustryType ind_type)
Make an industry type field.
below this level, the industry is set to be closing
Definition: industry.h:32
bool Filter(FilterFunction *decide, F filter_data)
Filter the list.
static Titem * Get(size_t index)
Returns Titem with given index.
Definition: pool_type.hpp:329
static const int VERT_CARGO_SPACE
Amount of vertical space between two connected cargoes at an industry.
int info_height
Height needed for the WID_IV_INFO panel.
Scrollbar data structure.
Definition: widget_type.h:587
Functions for NewGRF industries.
static const int INDUSTRY_LINE_COLOUR
Line colour of the industry type box.
void MakeEmpty(CargoesFieldType type)
Make one of the empty fields (CFT_EMPTY or CFT_SMALL_EMPTY).
void PlaceIndustry(int row, int col, IndustryType it)
Place an industry in the fields.
Offset at top to draw the frame rectangular area.
Definition: window_gui.h:62
void OnGameTick() override
Called once per (game) tick.
void UpdateViewportCoordinates(Window *w)
Update the position and size of the viewport (after eg a resize).
Definition: widget.cpp:1934
Horizontal container.
Definition: widget_type.h:73
void ShowSmallMap()
Show the smallmap window.
NewGRF debug box (at top-right of a window, between WWT_CAPTION and WWT_SHADEBOX) ...
Definition: widget_type.h:61
void SetSortFuncs(SortFunction *const *n_funcs)
Hand the array of sort function pointers to the sort list.
void DrawWidget(const Rect &r, int widget) const override
Draw the contents of a nested widget.
void ShowQueryString(StringID str, StringID caption, uint maxsize, Window *parent, CharSetFilter afilter, QueryStringFlags flags)
Show a query popup window with a textbox in it.
Definition: misc_gui.cpp:1119
uint32 GetIndustryProbabilityCallback(IndustryType type, IndustryAvailabilityCallType creation_type, uint32 default_prob)
Check with callback CBID_INDUSTRY_PROBABILITY whether the industry can be built.
Maximal number of cargo types in a game.
Definition: cargo_type.h:64
CargoSuffixDisplay
Ways of displaying the cargo.
byte cargo_acceptance[HOUSE_NUM_ACCEPTS]
acceptance level for the cargo slots
Definition: house.h:107
void OnClick(Point pt, int widget, int click_count) override
A click with the left mouse button has been made on the window.
void BuildSortIndustriesList()
(Re)Build industries list
Specification of a cargo type.
Definition: cargotype.h:55
void OnClick(Point pt, int widget, int click_count) override
A click with the left mouse button has been made on the window.
void GuiShowTooltips(Window *parent, StringID str, uint paramcount, const uint64 params[], TooltipCloseCondition close_tooltip)
Shows a tooltip.
Definition: misc_gui.cpp:764
Display the cargo without sub-type (cb37 result 401).
from the Fund/build window
default level set when the industry is created
Definition: industry.h:33
Row of buttons at the bottom.
static bool IndustryTypeSorter(const Industry *const &a, const Industry *const &b)
Sort industries by type and name.
static int small_height
Height of the header row.
Editability editable
Mode for changing production.
CargoID accepts_cargo[HOUSE_NUM_ACCEPTS]
input cargo slots
Definition: house.h:108
Resize box (normally at bottom-right of a window)
Definition: widget_type.h:66
Pressed (inset) panel, most commonly used as combo box text area.
Definition: widget_type.h:49
uint16 callback_mask
Bitmask of industry callbacks that have to be called.
Definition: industrytype.h:137
void SetFilterType(uint8 n_type)
Set the filtertype of the list.
struct CargoesField::@17::@19 cargo
Cargo data (for CFT_CARGO).
static uint TileX(TileIndex tile)
Get the X component of a tile.
Definition: map_func.h:205
static const int CARGO_LINE_COLOUR
Line colour around the cargo.
static bool HousesCanSupply(const CargoID *cargoes, uint length)
Can houses be used to supply one of the cargoes?
signal set to actually close the industry
Definition: industry.h:31
Defines the internal data of a functional industry.
Definition: industry.h:40
static const int BLOB_HEIGHT
Height of the industry legend colour, including border.
void CcBuildIndustry(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
Command callback.
DifficultySettings difficulty
settings related to the difficulty
Industry-directory window.
Tindex index
Index of this pool item.
Definition: pool_type.hpp:227
void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, WarningLevel wl, int x=0, int y=0, const GRFFile *textref_stack_grffile=nullptr, uint textref_stack_size=0, const uint32 *textref_stack=nullptr)
Display an error message in a window.
Definition: error_gui.cpp:380
static const int DAY_TICKS
1 day is 74 ticks; _date_fract used to be uint16 and incremented by 885.
Definition: date_type.h:28
Close box (at top-left of a window)
Definition: widget_type.h:67
Offset at top of a matrix cell.
Definition: window_gui.h:78
InfoLine
Specific lines in the info panel.
View-industry window.
Display cargo labels.
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:48
Produced cargo filter dropdown list.
StringID GetGRFStringID(uint32 grfid, StringID stringid)
Returns the index for this stringid associated with its grfID.
Functions related to world/map generation.
static void ShowIndustryCargoesWindow(IndustryType id)
Open the industry and cargoes window.
Stuff related to the text buffer GUI.
void SetFilterFuncs(FilterFunction *const *n_funcs)
Hand the array of filter function pointers to the sort list.
static const int MATRIX_TEXT_OFFSET
The offset for the text in the matrix.
void InitializeViewport(Window *w, uint32 follow_flags, ZoomLevel zoom)
Initialize the viewport of the window.
Definition: widget.cpp:1925
static int CountMatchingProducingIndustries(const CargoID *cargoes, uint length)
Count how many industries have produced cargoes in common with one of the supplied set...
void OnDropdownSelect(int widget, int index) override
A dropdown option associated to this window has been selected.
bool persistent_buildingtools
keep the building tools active after usage
void ComputeCargoDisplay(CargoID cid)
Compute what and where to display for cargo id cid.
Common return value for all commands.
Definition: command_type.h:23
Dimension ind_textsize
Size to hold any industry type text, as well as STR_INDUSTRY_CARGOES_SELECT_INDUSTRY.
bool IsCargoIDValid(CargoID t)
Test whether cargo type is not CT_INVALID.
Definition: cargo_type.h:74
void MakeHeader(StringID textid)
Make a header above an industry column.
static T max(const T a, const T b)
Returns the maximum of two values.
Definition: math_func.hpp:24
std::vector< IndustryTileLayout > layouts
List of possible tile layouts for the industry.
Definition: industrytype.h:107
the industry is running at full speed
Definition: industry.h:34
Nested widget to display a viewport in a window.
Definition: widget_type.h:573
Common string list item.
Definition: dropdown_type.h:39
void OnDropdownSelect(int widget, int index) override
A dropdown option associated to this window has been selected.
void SetListing(Listing l)
Import sort conditions.
Display industry.
Large amount of vertical space between two paragraphs of text.
Definition: window_gui.h:138
int DrawInfo(uint left, uint right, uint top)
Draw the text in the WID_IV_INFO panel.
Allow changing the production rates.
Window * GetCallbackWnd()
Get the window that started the current highlighting.
Definition: viewport.cpp:2469
static const uint TILE_SIZE
Tile size in world coordinates.
Definition: tile_type.h:13
StringID name
Name of this type of cargo.
Definition: cargotype.h:70
Industry directory; Window numbers:
Definition: window_type.h:259
static const HouseID NUM_HOUSES
Total number of houses.
Definition: house.h:29
void OnPaint() override
The window must be repainted.
StringID name
Displayed name of the industry.
Definition: industrytype.h:126
Do not refit cargo of a vehicle (used in vehicle orders and auto-replace/auto-new).
Definition: cargo_type.h:67
static int RoundDivSU(int a, uint b)
Computes round(a / b) for signed a and unsigned b.
Definition: math_func.hpp:336
HouseZones
Definition: house.h:71
bool NeedRebuild() const
Check if a rebuild is needed.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
static int cargo_field_width
Width of a cargo field.
bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
Set up a clipping area for only drawing into a certain area.
Definition: gfx.cpp:1619
Called to determine more text in the fund industry window.
bool IndustryTemporarilyRefusesCargo(Industry *ind, CargoID cargo_type)
Check whether an industry temporarily refuses to accept a certain cargo.
Display the cargo and amount (if useful), but no sub-type (cb37 result 400 or fail).
static bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
Definition: math_func.hpp:248
static uint ClampU(const uint a, const uint min, const uint max)
Clamp an unsigned integer between an interval.
Definition: math_func.hpp:182
StringID GetIndustryString(const Industry *i) const
Get the StringID to draw and set the appropriate DParams.
The list of industries.
HouseZones building_availability
where can it be built (climates, zones)
Definition: house.h:110
Class to backup a specific variable and restore it later.
Definition: backup_type.hpp:21
Info panel about the industry.
void SetCount(int num)
Sets the number of elements in the list.
Definition: widget_type.h:668
Partial widget specification to allow NWidgets to be written nested.
Definition: widget_type.h:908
Functions related to (drawing on) viewports.
Pseudo random number generator.
void ForceRebuild()
Force that a rebuild is needed.
static bool IndustryTransportedCargoSorter(const Industry *const &a, const Industry *const &b)
Sort industries by transported cargo and name.
Data structure for an opened window.
Definition: window_gui.h:276
Invalid cargo type.
Definition: cargo_type.h:68
bool _ctrl_pressed
Is Ctrl pressed?
Definition: gfx.cpp:35
static NWidgetPart SetMatrixDataTip(uint8 cols, uint8 rows, StringID tip)
Widget part function for setting the data and tooltip of WWT_MATRIX widgets.
Definition: widget_type.h:1030
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
static NWidgetPart SetPadding(uint8 top, uint8 right, uint8 bottom, uint8 left)
Widget part function for setting additional space around a widget.
Definition: widget_type.h:1044
static bool IsInsideMM(const T x, const size_t min, const size_t max)
Checks if a value is in an interval.
Definition: math_func.hpp:264
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
Definition: window.cpp:3337
Bottom offset of the text of the frame.
Definition: window_gui.h:73
Header of Action 04 "universal holder" structure and functions.
void SetDParamStr(uint n, const char *str)
This function is used to "bind" a C string to a OpenTTD dparam slot.
Definition: strings.cpp:279
Header text.
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
Update size and resize step of a widget in the window.
Functions related to low-level strings.
The tile has no ownership.
Definition: company_type.h:25
void OnResize() override
Called after the window got resized.
Types related to cheating.
StringID header
Header text (for CFT_HEADER).
void OnTimeout() override
Called when this window&#39;s timeout has been reached.
Functions related to errors.
void OnResize() override
Called after the window got resized.
Offset at bottom of a matrix cell.
Definition: window_gui.h:79
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX) ...
Definition: widget_type.h:63
void SetStringParameters(int widget) const override
Initialize string parameters for a widget.
bool IsRawIndustry() const
Is an industry with the spec a raw industry?
void ShowNewGRFInspectWindow() const override
Show the NewGRF inspection window.
int GetScrolledRowFromWidget(int clickpos, const Window *const w, int widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition: widget.cpp:1957
Caption of the window.
void OnQueryTextFinished(char *str) override
The query window opened from this window has closed.
This window is used for construction; close it whenever changing company.
Definition: window_gui.h:208
Fill rectangle with a single colour.
Definition: gfx_type.h:282
SoundSettings sound
sound effect settings
std::array< IndustryType, NUM_INDUSTRYTYPES > _sorted_industry_types
Industry types sorted by name.
void SetStringParameters(int widget) const override
Initialize string parameters for a widget.
Listing GetListing() const
Export current sort conditions.
static const NWidgetPart _nested_industry_cargoes_widgets[]
Widgets of the industry cargoes window.
uint current_y
Current vertical size (after resizing).
Definition: widget_type.h:173
Sort descending.
Definition: window_gui.h:225
void MakeCargoLabel(int column, bool accepting)
Construct a CFT_CARGO_LABEL field.
StringID GetErrorMessage() const
Returns the error message of a command.
Definition: command_type.h:140
Caption of the window.
Small map; Window numbers:
Definition: window_type.h:97
#define FONT_HEIGHT_NORMAL
Height of characters in the normal (FS_NORMAL) font.
Definition: gfx_func.h:177
static NWidgetPart SetDataTip(uint32 data, StringID tip)
Widget part function for setting the data and tooltip.
Definition: widget_type.h:1012
void OnResize() override
Called after the window got resized.
CargoSuffixDisplay display
How to display the cargo and text.
ClientSettings _settings_client
The current settings for this game.
Definition: settings.cpp:79
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
Update size and resize step of a widget in the window.
static NWidgetPart SetMinimalSize(int16 x, int16 y)
Widget part function for setting the minimal size.
Definition: widget_type.h:945
bool Succeeded() const
Did this command succeed?
Definition: command_type.h:150
const IndustrySpec * GetIndustrySpec(IndustryType thistype)
Accessor for array _industry_specs.
Data about a single field in the IndustryCargoesWindow panel.
void OnPlaceObject(Point pt, TileIndex tile) override
The user clicked some place on the map when a tile highlight mode has been set.
Definition of base types and functions in a cross-platform compatible way.
void SetProducedCargoFilterIndex(int index)
Set cargo filter list item index.
TileIndex GetCenterTile() const
Get the center tile.
Definition: tilearea_type.h:57
char text[512]
Cargo suffix text.
bool UsesSmoothEconomy() const
Determines whether this industrytype uses smooth economy or whether it uses standard/newgrf productio...
A number of safeguards to prevent using unsafe methods.
CargoesFieldType
Available types of field.
void OnInit() override
Notification that the nested widget tree gets initialized.
bool value
tells if the bool cheat is active or not
Definition: cheat_type.h:18
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition: gfx_type.h:245
static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
Draw a horizontal cargo connection.
IndustryType type
type of industry.
Definition: industry.h:57
Normal push-button (no toggle button) with text caption.
Definition: widget_type.h:102
Geometry functions.
rectangle (stations, depots, ...)
Simple depressed panel.
Definition: widget_type.h:48
static const int VERT_TEXT_PADDING
Vertical padding around the industry type text.
static uint CeilDiv(uint a, uint b)
Computes ceil(a / b) for non-negative a and b.
Definition: math_func.hpp:314
Fields fields
Fields to display in the WID_IC_PANEL.
Cheat setup_prod
setup raw-material production in game
Definition: cheat_type.h:35
void MakeCargoLabel(const CargoID *cargoes, uint length, bool left_align)
Make a field displaying cargo type names.
TileArea location
Location of the industry.
Definition: industry.h:41
additional text in industry window
static const int BLOB_DISTANCE
Distance of the industry legend colour from the edge of the industry box.
uint16 last_month_production[INDUSTRY_NUM_OUTPUTS]
total units produced per cargo in the last full month
Definition: industry.h:53
void Draw(int xpos, int ypos) const
Draw the field.
static const int HOR_CARGO_WIDTH
Width of a vertical cargo column (inclusive the border line).
static const int VERT_INTER_INDUSTRY_SPACE
Amount of space between two industries in a column.
CargoID produced_cargo[INDUSTRY_NUM_OUTPUTS]
16 production cargo slots
Definition: industry.h:44
int pos_x
Horizontal position of top-left corner of the widget in the window.
Definition: widget_type.h:175
Offset at left of a matrix cell.
Definition: window_gui.h:76
static const NWidgetPart _nested_industry_view_widgets[]
Widget definition of the view industry gui.
static WindowDesc _build_industry_desc(WDP_AUTO, "build_industry", 170, 212, WC_BUILD_INDUSTRY, WC_NONE, WDF_CONSTRUCTION, _nested_build_industry_widgets, lengthof(_nested_build_industry_widgets))
Window definition of the dynamic place industries gui.
byte bottom_end
Stop at the bottom of the vertical cargoes.
Default zoom level for the industry view.
Definition: zoom_type.h:35
Defines the data structure for constructing industry.
Definition: industrytype.h:106
InfoLine clicked_line
The line of the button that has been clicked.
static WindowDesc _industry_directory_desc(WDP_AUTO, "list_industries", 428, 190, WC_INDUSTRY_DIRECTORY, WC_NONE, 0, _nested_industry_directory_widgets, lengthof(_nested_industry_directory_widgets))
Window definition of the industry directory gui.
static NWidgetPart NWidget(WidgetType tp, Colours col, int16 idx=-1)
Widget part function for starting a new &#39;real&#39; widget.
Definition: widget_type.h:1112
CargoesFieldType type
Type of field.
Offset at bottom to draw the frame rectangular area.
Definition: window_gui.h:63
std::string MakeCargoListString(const CargoID *cargolist, const CargoSuffix *cargo_suffix, int cargolistlen, StringID prefixstr) const
Build a string of cargo names with suffixes attached.
Baseclass for nested widgets.
Definition: widget_type.h:124
Money GetConstructionCost() const
Get the cost for constructing this industry.
int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition: gfx.cpp:636
additional text in fund window
void SetCargoFilterArray()
Populate the filter list and set the cargo filter criteria.
Basic functions/variables used all over the place.
static bool HasCommonValidCargo(const CargoID *cargoes1, uint length1, const CargoID *cargoes2, uint length2)
Do the two sets of cargoes have a valid cargo in common?
CargoID accepts_cargo[INDUSTRY_NUM_INPUTS]
16 input cargo slots
Definition: industry.h:49
void OnClick(Point pt, int widget, int click_count) override
A click with the left mouse button has been made on the window.
Empty small field (for the header).
Right offset of the text of the frame.
Definition: window_gui.h:71
bool DoCommandP(const CommandContainer *container, bool my_cmd)
Shortcut for the long DoCommandP when having a container with the data.
Definition: command.cpp:536
Industry view; Window numbers:
Definition: window_type.h:356
uint8 cargo_map[NUM_CARGO]
Inverse cargo translation table (CargoID -> local ID)
Definition: newgrf.h:127
uint16 incoming_cargo_waiting[INDUSTRY_NUM_INPUTS]
incoming cargo waiting to be processed
Definition: industry.h:46
static const int CARGO_STUB_WIDTH
Width of a cargo not carried in the column (should be less than HOR_CARGO_BORDER_SPACE).
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
Update size and resize step of a widget in the window.
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:40
IndustryBehaviour behaviour
How this industry will behave, and how others entities can use it.
Definition: industrytype.h:124
byte prod_level
general production level
Definition: industry.h:48
Select cargo dropdown.
uint resize_y
Vertical resize step (0 means not resizable).
Definition: widget_type.h:165
GRFFileProps grf_prop
properties related to the grf file
Definition: industrytype.h:140
Grid of rows and columns.
Definition: widget_type.h:57
#define FOR_ALL_SORTED_STANDARD_CARGOSPECS(var)
Loop header for iterating over &#39;real&#39; cargoes, sorted by name.
Definition: cargotype.h:171
Dimension cargo_textsize
Size to hold any cargo text, as well as STR_INDUSTRY_CARGOES_SELECT_CARGO.
Top offset of the text of the frame.
Definition: window_gui.h:72
Left offset of the text of the frame.
Definition: window_gui.h:70
bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
Scrolls the viewport in a window to a given location.
Definition: viewport.cpp:2385
Functions related to sound.
void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
Draw [<][>] boxes.
void SetSortType(uint8 n_type)
Set the sorttype of the list.
bool Sort(SortFunction *compare)
Sort the list.
void StartTextRefStackUsage(const GRFFile *grffile, byte numEntries, const uint32 *values)
Start using the TTDP compatible string code parsing.
The game does not build industries.
Definition: settings_type.h:42
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition: widget.cpp:656
bool CalculatePositionInWidget(Point pt, Point *fieldxy, Point *xy)
Calculate in which field was clicked, and within the field, at what position.
uint32 StringID
Numeric value that represents a string, independent of the selected language.
Definition: strings_type.h:16
void OnTimeout() override
Called when this window&#39;s timeout has been reached.
IndustryType selected_type
industry corresponding to the above index
static const uint8 PC_BLACK
Black palette colour.
Definition: gfx_func.h:204
#define SETTING_BUTTON_WIDTH
Width of setting buttons.
Definition: settings_gui.h:17
12 1000 can appear in temperate climate
Definition: house.h:80
IndustryType ind_type
Industry type (NUM_INDUSTRYTYPES means &#39;houses&#39;).
Empty field.
static const int HOR_CARGO_SPACE
Amount of horizontal space between two vertical cargoes.
static bool IndustryNameSorter(const Industry *const &a, const Industry *const &b)
Sort industries by name.
Show all industries (i.e. no filtering)
Display then cargo, amount, and string (cb37 result 000-3FF).
int GetCargoBase(int xpos) const
For a CFT_CARGO, compute the left position of the left-most vertical cargo connection.
static int normal_height
Height of the non-header rows.
bool IsNewGRFInspectable() const override
Is the data related to this window NewGRF inspectable?
Build industry; Window numbers:
Definition: window_type.h:428
void ShowExtraViewPortWindow(TileIndex tile=INVALID_TILE)
Show a new Extra Viewport window.
Allow changing the production multiplier.
Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition: gfx.cpp:838
bool timer_enabled
timer can be used
TileIndex tile
The base tile of the area.
Definition: tilearea_type.h:17
14 4000 can appear in subtropical climate
Definition: house.h:82
void ForceResort()
Force a resort next Sort call Reset the resort timer if used too.
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
No window, redirects to WC_MAIN_WINDOW.
Definition: window_type.h:38
std::bitset< NUM_INDUSTRYTYPES > _displayed_industries
Communication from the industry chain window to the smallmap window about what industries to display...
13 2000 can appear in sub-arctic climate below the snow line
Definition: house.h:81
void ConnectIndustryProduced(int column)
Connect industry production cargoes to the cargo column after it.
static bool IndustryTypeNameSorter(const IndustryType &a, const IndustryType &b)
Sort industry types by their name.
static bool HousesCanAccept(const CargoID *cargoes, uint length)
Can houses be used as customers of the produced cargoes?
static const uint8 PC_YELLOW
Yellow palette colour.
Definition: gfx_func.h:217
Smallmap GUI functions.
Functions related to companies.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
static WindowDesc _industry_cargoes_desc(WDP_AUTO, "industry_cargoes", 300, 210, WC_INDUSTRY_CARGOES, WC_NONE, 0, _nested_industry_cargoes_widgets, lengthof(_nested_industry_cargoes_widgets))
Window description for the industry cargoes window.
bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
Can we inspect the data given a certain feature and index.
CargoID accepted_cargo_filter_criteria
Selected accepted cargo filter.
void ErrorUnknownCallbackResult(uint32 grfid, uint16 cbid, uint16 cb_res)
Record that a NewGRF returned an unknown/invalid callback result.
bool _generating_world
Whether we are generating the map or not.
Definition: genworld.cpp:60
15 8000 can appear in toyland climate
Definition: house.h:83
Both numeric and alphabetic and spaces and stuff.
Definition: string_type.h:27
static const IndustryType INVALID_INDUSTRYTYPE
one above amount is considered invalid
Definition: industry_type.h:27
void SetStringParameters(int widget) const override
Initialize string parameters for a widget.
GUISettings gui
settings related to the GUI
static Pool::IterateWrapper< Titem > Iterate(size_t from=0)
Returns an iterable ensemble of all valid Titem.
Definition: pool_type.hpp:378
void SetStringParameters(int widget) const override
Initialize string parameters for a widget.
Window caption (window title between closebox and stickybox)
Definition: widget_type.h:59
Scrollbar of the panel.
int strnatcmp(const char *s1, const char *s2, bool ignore_garbage_at_front)
Compares two strings using case insensitive natural sort.
Definition: string.cpp:578
Display cargo connections.
static bool StrEmpty(const char *s)
Check if a string buffer is empty.
Definition: string_func.h:57
void SetFilterState(bool state)
Enable or disable the filter.
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo ID.
Definition: cargotype.h:117
static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, CargoSuffix &suffix)
Gets the string to display after the cargo name (using callback 37)
byte clicked_button
The button that has been clicked (to raise)
Types related to the industry widgets.
Editability
Modes for changing production.
Show only industries which do not produce/accept cargo.
void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid=0)
Show the inspect window for a given feature and index.
uint32 TileIndex
The index/ID of a Tile.
Definition: tile_type.h:78
bool HandlePlacePushButton(Window *w, int widget, CursorID cursor, HighLightStyle mode)
This code is shared for the majority of the pushbuttons.
Definition: main_gui.cpp:98
#define cpp_lengthof(base, variable)
Gets the length of an array variable within a class.
Definition: stdafx.h:413
static int GetCargoTransportedSortValue(const Industry *i)
Returns value representing industry&#39;s transported cargo percentage for industry sorting.
static uint ToPercent8(uint i)
Converts a "fract" value 0..255 to "percent" value 0..100.
Definition: math_func.hpp:287
Production rate of cargo 1.
static size_t GetNumItems()
Returns number of valid items in the pool.
Definition: pool_type.hpp:359
uint ind_cargo
If less than NUM_INDUSTRYTYPES, an industry type, else a cargo id + NUM_INDUSTRYTYPES.
CargoesField columns[5]
One row of fields.
void RecomputeProductionMultipliers()
Recompute production_rate for current prod_level.
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition: strings.cpp:48
void ToggleSortOrder()
Toggle the sort order Since that is the worst condition for the sort function reverse the list here...
A single row of CargoesField.
void ConnectIndustryAccepted(int column)
Connect industry accepted cargoes to the cargo column before it.
byte num_cargoes
Number of cargoes.
Industry list.
bool has_newindustries
Set if there are any newindustries loaded.
Definition: newgrf.h:178
bool CDECL FilterFunction(const T *, F)
Signature of filter function.
Definition: sortlist_type.h:50
static const IndustryType NUM_INDUSTRYTYPES
total number of industry types, new and old; limited to 240 because we need some special ids like INV...
Definition: industry_type.h:26
Sort ascending.
Definition: window_gui.h:224
Ignore colour changes from strings.
Definition: gfx_type.h:270
static uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition: map_func.h:215
Display then cargo and supplied string (cb37 result 800-BFF).
Vertical container.
Definition: widget_type.h:75
Scrollbar of the matrix.
Allow produced/accepted cargoes callbacks to supply more than 2 and 3 types.
Definition: industrytype.h:82
Functions for setting GUIs.
static NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME, WWT_INSET, or WWT_PANEL).
Definition: widget_type.h:997
static uint GB(const T x, const uint8 s, const uint8 n)
Fetch n bits from x, started at bit s.
Select industry dropdown.
void SetAcceptedCargoFilterIndex(int index)
Set cargo filter list item index.
static const byte INVALID_CARGO
Constant representing invalid cargo.
Definition: cargotype.h:52
uint current_x
Current horizontal size (after resizing).
Definition: widget_type.h:172
CargoID produced_cargo_filter_criteria
Selected produced cargo filter.
static Industry * PlaceIndustry(IndustryType type, IndustryAvailabilityCallType creation_type, bool try_hard)
Try to place the industry in the game.
#define endof(x)
Get the end element of an fixed size array.
Definition: stdafx.h:384
Window displaying the cargo connections around an industry (or cargo).
call production callback when cargo arrives at the industry
void OnInit() override
Notification that the nested widget tree gets initialized.
Window * FindWindowByClass(WindowClass cls)
Find any window by its class.
Definition: window.cpp:1149
cargo sub-type display
Panel that shows the chain.
CargoSuffixType
Cargo suffix type (for which window is it requested)
bool ScrollMainWindowToTile(TileIndex tile, bool instant)
Scrolls the viewport of the main window to a given location.
Definition: viewport.cpp:2396
Scrollbar of the list.
Functions related to commands.
Fund-industry window.
Coordinates of a point in 2D.
byte top_end
Stop at the top of the vertical cargoes.
CompanyID _current_company
Company currently doing an action.
Definition: company_cmd.cpp:45
CargoID Index() const
Determines index of this cargospec.
Definition: cargotype.h:88
static bool IsValidID(size_t index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
Definition: pool_type.hpp:318
Data structure describing how to show the list (what sort direction and criteria).
Definition: sortlist_type.h:31
Drop down list.
Definition: widget_type.h:68
static const int BLOB_WIDTH
Width of the industry legend colour, including border.
uint16 GetCapacity() const
Gets the number of visible elements of the scrollbar.
Definition: widget_type.h:620
bool OnTooltip(Point pt, int widget, TooltipCloseCondition close_cond) override
Event to display a custom tooltip.
Window does not do autoscroll,.
Definition: window_gui.h:239
ConstructionSettings construction
construction of things in-game
void OnHundredthTick() override
Called once every 100 (game) ticks.
CargoFilterSpecialType
Special cargo filter criteria.
static const StringID INVALID_STRING_ID
Constant representing an invalid string (16bit in case it is used in savegames)
Definition: strings_type.h:17
Base of all industries.
Called to determine text to display after cargo name.
bool left_align
Align all cargo texts to the left (else align to the right).
bool HasConnection()
Does this CFT_CARGO field have a horizontal connection?
Offset at right to draw the frame rectangular area.
Definition: window_gui.h:61
void OnPaint() override
The window must be repainted.
const struct GRFFile * grffile
grf file that introduced this entity
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition: widget_type.h:64
Used for DoCommand-like (and some non-fatal AI GUI) errors/information.
Definition: error.h:21
static NWidgetPart SetFill(uint fill_x, uint fill_y)
Widget part function for setting filling.
Definition: widget_type.h:981
static bool HasBit(const T x, const uint8 y)
Checks if a bit in a value is set.
bool IsValid() const
Tests for validity of this cargospec.
Definition: cargotype.h:98
Dropdown for the order of the sort.
CargoesFieldType type
Type of field.
CargoID CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
Decide which cargo was clicked at in a CFT_CARGO field.
void Restore()
Restore the variable.
void OnClick(Point pt, int widget, int click_count) override
A click with the left mouse button has been made on the window.
static const TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition: tile_type.h:83
static void GetAllCargoSuffixes(CargoSuffixInOut use_input, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, const TC &cargoes, TS &suffixes)
Gets all strings to display after the cargoes of industries (using callback 37)
Base of the town class.
bool IsDescSortOrder() const
Check if the sort order is descending.
#define CMD_MSG(x)
Used to combine a StringID with the command.
Definition: command_type.h:370
void OnPlaceObjectAbort() override
The user cancelled a tile highlight mode that has been set.
int32 WindowNumber
Number to differentiate different windows of the same class.
Definition: window_type.h:711
GameCreationSettings game_creation
settings used during the creation of a game (map)
void SetCapacityFromWidget(Window *w, int widget, int padding=0)
Set capacity of visible elements from the size and resize properties of a widget. ...
Definition: widget.cpp:1971
void ResetObjectToPlace()
Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows)...
Definition: viewport.cpp:3353
Specification of a rectangle with absolute coordinates of all edges.
Vertical scrollbar.
Definition: widget_type.h:82
byte CargoID
Cargo slots to indicate a cargo type within a game.
Definition: cargo_type.h:20
InfoLine editbox_line
The line clicked to open the edit box.
Text is written right-to-left by default.
Definition: strings_type.h:24
Right align the text (must be a single bit).
Definition: gfx_func.h:96
Accepted cargo filter dropdown list.
Called to determine more text in the industry window.
Info of the industry.
Left align the text.
Definition: gfx_func.h:94
Functions related to tile highlights.
Window functions not directly related to making/drawing windows.
int ConnectCargo(CargoID cargo, bool producer)
Connect a cargo from an industry to the CFT_CARGO column.
GRFLoadedFeatures _loaded_newgrf_features
Indicates which are the newgrf features currently loaded ingame.
Definition: newgrf.cpp:81
Find a place automatically.
Definition: window_gui.h:154
byte industry_density
The industry density.
Definition: settings_type.h:56
void MakeCargo(const CargoID *cargoes, uint length, int count=-1, bool top_end=false, bool bottom_end=false)
Make a piece of cargo column.
byte last_month_pct_transported[INDUSTRY_NUM_OUTPUTS]
percentage transported per cargo in the last full month
Definition: industry.h:52
static bool IndustryProductionSorter(const Industry *const &a, const Industry *const &b)
Sort industries by production and name.
GUI functions that shouldn&#39;t be here.
void OnResize() override
Called after the window got resized.
CargoID CargoLabelClickedAt(Point pt) const
Decide what cargo the user clicked in the cargo label field.
bool SortFunction(const T &, const T &)
Signature of sort function.
Definition: sortlist_type.h:49
void SetButtons()
Update status of the fund and display-chain widgets.
call production callback every 256 ticks
struct CargoesField::@17::@18 industry
Industry data (for CFT_INDUSTRY).
int production_offset_y
The offset of the production texts/buttons.
Industry cargoes chain; Window numbers:
Definition: window_type.h:504
void ComputeIndustryDisplay(IndustryType it)
Compute what and where to display for industry type it.
static NWidgetPart SetScrollbar(int index)
Attach a scrollbar to a widget.
Definition: widget_type.h:1093
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
Definition: company_cmd.cpp:44
static WindowDesc _industry_view_desc(WDP_AUTO, "view_industry", 260, 120, WC_INDUSTRY_VIEW, WC_NONE, 0, _nested_industry_view_widgets, lengthof(_nested_industry_view_widgets))
Window definition of the view industry gui.
Dimensions (a width and height) of a rectangle in 2D.
bool click_beep
Beep on a random selection of buttons.
Offset at left to draw the frame rectangular area.
Definition: window_gui.h:60
Class for backupping variables and making sure they are restored later.
void ShortenCargoColumn(int column, int top, int bottom)
Shorten the cargo column to just the part between industries.
Window * BringWindowToFrontById(WindowClass cls, WindowNumber number)
Find a window and make it the relative top-window on the screen.
Definition: window.cpp:1262
static const int HOR_TEXT_PADDING
Horizontal padding around the industry type text.
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition: widget_type.h:62
Display chain button.
void DrawWidget(const Rect &r, int widget) const override
Draw the contents of a nested widget.
build a new industry
Definition: command_type.h:232
int pos_y
Vertical position of top-left corner of the widget in the window.
Definition: widget_type.h:176
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window&#39;s data as invalid (in need of re-computing)
Definition: window.cpp:3259
struct CargoesField::@17::@20 cargo_label
Label data (for CFT_CARGO_LABEL).
void StopTextRefStackUsage()
Stop using the TTDP compatible string code parsing.
int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly over multiple lines.
Definition: gfx.cpp:759
(Toggle) Button with text
Definition: widget_type.h:53
static const int VERT_CARGO_EDGE
Amount of vertical space between top/bottom and the top/bottom connected cargo at an industry...
static void MemSetT(T *ptr, byte value, size_t num=1)
Type-safe version of memset().
Definition: mem_func.hpp:49
Cheats _cheats
All the cheats.
Definition: cheat.cpp:16
uint16 GetIndustryCallback(CallbackID callback, uint32 param1, uint32 param2, Industry *industry, IndustryType type, TileIndex tile)
Perform an industry callback.
int selected_index
index of the element in the matrix
11 800 can appear in sub-arctic climate above the snow line
Definition: house.h:79
uint16 GetPosition() const
Gets the position of the first visible element in the list.
Definition: widget_type.h:629
static uint max_cargoes
Largest number of cargoes actually on any industry.
static const NWidgetPart _nested_industry_directory_widgets[]
Widget definition of the industry directory gui.
Production rate of cargo 2.
uint8 SortType() const
Get the sorttype of the list.
Definition: sortlist_type.h:94
static void SetDParam(uint n, uint64 v)
Set a string parameter v at index n in the global string parameter array.
Definition: strings_func.h:199
uint16 callback_timer
timer counter for callback eventual verification