OpenTTD
industry_gui.cpp
Go to the documentation of this file.
1 /* $Id: industry_gui.cpp 27952 2017-12-27 21:54:52Z frosch $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
9 
12 #include "stdafx.h"
13 #include "error.h"
14 #include "gui.h"
15 #include "settings_gui.h"
16 #include "sound_func.h"
17 #include "window_func.h"
18 #include "textbuf_gui.h"
19 #include "command_func.h"
20 #include "viewport_func.h"
21 #include "industry.h"
22 #include "town.h"
23 #include "cheat_type.h"
24 #include "newgrf_industries.h"
25 #include "newgrf_text.h"
26 #include "newgrf_debug.h"
27 #include "network/network.h"
28 #include "strings_func.h"
29 #include "company_func.h"
30 #include "tilehighlight_func.h"
31 #include "string_func.h"
32 #include "sortlist_type.h"
33 #include "widgets/dropdown_func.h"
34 #include "company_base.h"
35 #include "core/geometry_func.hpp"
36 #include "core/random_func.hpp"
37 #include "core/backup_type.hpp"
38 #include "genworld.h"
39 #include "smallmap_gui.h"
40 #include "widgets/dropdown_type.h"
42 
43 #include "table/strings.h"
44 
45 #include <bitset>
46 
47 #include "safeguards.h"
48 
49 bool _ignore_restrictions;
50 std::bitset<NUM_INDUSTRYTYPES> _displayed_industries;
51 
57 };
58 
65 };
66 
68 struct CargoSuffix {
70  char text[512];
71 };
72 
73 static void ShowIndustryCargoesWindow(IndustryType id);
74 
89 static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, CargoSuffix &suffix)
90 {
91  suffix.text[0] = '\0';
92  suffix.display = CSD_CARGO_AMOUNT;
93 
94  if (HasBit(indspec->callback_mask, CBM_IND_CARGO_SUFFIX)) {
95  TileIndex t = (cst != CST_FUND) ? ind->location.tile : INVALID_TILE;
96  uint16 callback = GetIndustryCallback(CBID_INDUSTRY_CARGO_SUFFIX, 0, (cst << 8) | cargo, const_cast<Industry *>(ind), ind_type, t);
97  if (callback == CALLBACK_FAILED) return;
98 
99  if (indspec->grf_prop.grffile->grf_version < 8) {
100  if (GB(callback, 0, 8) == 0xFF) return;
101  if (callback < 0x400) {
103  GetString(suffix.text, GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 + callback), lastof(suffix.text));
106  return;
107  }
109  return;
110 
111  } else { // GRF version 8 or higher.
112  if (callback == 0x400) return;
113  if (callback == 0x401) {
114  suffix.display = CSD_CARGO;
115  return;
116  }
117  if (callback < 0x400) {
119  GetString(suffix.text, GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 + callback), lastof(suffix.text));
122  return;
123  }
124  if (callback >= 0x800 && callback < 0xC00) {
126  GetString(suffix.text, GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 - 0x800 + callback), lastof(suffix.text));
128  suffix.display = CSD_CARGO_TEXT;
129  return;
130  }
132  return;
133  }
134  }
135 }
136 
147 template <typename TC, typename TS>
148 static inline void GetAllCargoSuffixes(uint cb_offset, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, const TC &cargoes, TS &suffixes)
149 {
150  assert_compile(lengthof(cargoes) <= lengthof(suffixes));
151  for (uint j = 0; j < lengthof(cargoes); j++) {
152  if (cargoes[j] != CT_INVALID) {
153  GetCargoSuffix(cb_offset + j, cst, ind, ind_type, indspec, suffixes[j]);
154  } else {
155  suffixes[j].text[0] = '\0';
156  }
157  }
158 }
159 
161 
163 static int CDECL IndustryTypeNameSorter(const IndustryType *a, const IndustryType *b)
164 {
165  static char industry_name[2][64];
166 
167  const IndustrySpec *indsp1 = GetIndustrySpec(*a);
168  GetString(industry_name[0], indsp1->name, lastof(industry_name[0]));
169 
170  const IndustrySpec *indsp2 = GetIndustrySpec(*b);
171  GetString(industry_name[1], indsp2->name, lastof(industry_name[1]));
172 
173  int r = strnatcmp(industry_name[0], industry_name[1]); // Sort by name (natural sorting).
174 
175  /* If the names are equal, sort by industry type. */
176  return (r != 0) ? r : (*a - *b);
177 }
178 
183 {
184  /* Add each industry type to the list. */
185  for (IndustryType i = 0; i < NUM_INDUSTRYTYPES; i++) {
186  _sorted_industry_types[i] = i;
187  }
188 
189  /* Sort industry types by name. */
191 }
192 
200 void CcBuildIndustry(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
201 {
202  if (result.Succeeded()) return;
203 
204  uint8 indtype = GB(p1, 0, 8);
205  if (indtype < NUM_INDUSTRYTYPES) {
206  const IndustrySpec *indsp = GetIndustrySpec(indtype);
207  if (indsp->enabled) {
208  SetDParam(0, indsp->name);
209  ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, result.GetErrorMessage(), WL_INFO, TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE);
210  }
211  }
212 }
213 
214 static const NWidgetPart _nested_build_industry_widgets[] = {
216  NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
217  NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_FUND_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
218  NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
219  NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
220  NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
221  EndContainer(),
223  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),
224  NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_DPI_SCROLLBAR),
225  EndContainer(),
226  NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_DPI_INFOPANEL), SetResize(1, 0),
227  EndContainer(),
229  NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_DISPLAY_WIDGET), SetFill(1, 0), SetResize(1, 0),
230  SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
231  NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_FUND_WIDGET), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
232  NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
233  EndContainer(),
234 };
235 
238  WDP_AUTO, "build_industry", 170, 212,
241  _nested_build_industry_widgets, lengthof(_nested_build_industry_widgets)
242 );
243 
245 class BuildIndustryWindow : public Window {
247  IndustryType selected_type;
248  uint16 callback_timer;
250  uint16 count;
251  IndustryType index[NUM_INDUSTRYTYPES + 1];
253  Scrollbar *vscroll;
254 
256  static const int MATRIX_TEXT_OFFSET = 17;
257 
258  void SetupArrays()
259  {
260  this->count = 0;
261 
262  for (uint i = 0; i < lengthof(this->index); i++) {
263  this->index[i] = INVALID_INDUSTRYTYPE;
264  this->enabled[i] = false;
265  }
266 
267  if (_game_mode == GM_EDITOR) { // give room for the Many Random "button"
268  this->index[this->count] = INVALID_INDUSTRYTYPE;
269  this->enabled[this->count] = true;
270  this->count++;
271  this->timer_enabled = false;
272  }
273  /* Fill the arrays with industries.
274  * The tests performed after the enabled allow to load the industries
275  * In the same way they are inserted by grf (if any)
276  */
277  for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
278  IndustryType ind = _sorted_industry_types[i];
279  const IndustrySpec *indsp = GetIndustrySpec(ind);
280  if (indsp->enabled) {
281  /* Rule is that editor mode loads all industries.
282  * In game mode, all non raw industries are loaded too
283  * and raw ones are loaded only when setting allows it */
284  if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
285  /* Unselect if the industry is no longer in the list */
286  if (this->selected_type == ind) this->selected_index = -1;
287  continue;
288  }
289  this->index[this->count] = ind;
290  this->enabled[this->count] = (_game_mode == GM_EDITOR) || GetIndustryProbabilityCallback(ind, IACT_USERCREATION, 1) > 0;
291  /* Keep the selection to the correct line */
292  if (this->selected_type == ind) this->selected_index = this->count;
293  this->count++;
294  }
295  }
296 
297  /* first industry type is selected if the current selection is invalid.
298  * I'll be damned if there are none available ;) */
299  if (this->selected_index == -1) {
300  this->selected_index = 0;
301  this->selected_type = this->index[0];
302  }
303 
304  this->vscroll->SetCount(this->count);
305  }
306 
308  void SetButtons()
309  {
311  this->SetWidgetDisabledState(WID_DPI_DISPLAY_WIDGET, this->selected_type == INVALID_INDUSTRYTYPE && this->enabled[this->selected_index]);
312  }
313 
314 public:
316  {
318 
319  this->selected_index = -1;
321 
322  this->callback_timer = DAY_TICKS;
323 
324  this->CreateNestedTree();
325  this->vscroll = this->GetScrollbar(WID_DPI_SCROLLBAR);
326  this->FinishInitNested(0);
327 
328  this->SetButtons();
329  }
330 
331  virtual void OnInit()
332  {
333  this->SetupArrays();
334  }
335 
336  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
337  {
338  switch (widget) {
339  case WID_DPI_MATRIX_WIDGET: {
340  Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES);
341  for (byte i = 0; i < this->count; i++) {
342  if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
343  d = maxdim(d, GetStringBoundingBox(GetIndustrySpec(this->index[i])->name));
344  }
346  d.width += MATRIX_TEXT_OFFSET + padding.width;
347  d.height = 5 * resize->height;
348  *size = maxdim(*size, d);
349  break;
350  }
351 
352  case WID_DPI_INFOPANEL: {
353  /* Extra line for cost outside of editor + extra lines for 'extra' information for NewGRFs. */
354  int height = 2 + (_game_mode == GM_EDITOR ? 0 : 1) + (_loaded_newgrf_features.has_newindustries ? 4 : 0);
355  Dimension d = {0, 0};
356  for (byte i = 0; i < this->count; i++) {
357  if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
358 
359  const IndustrySpec *indsp = GetIndustrySpec(this->index[i]);
360 
361  CargoSuffix cargo_suffix[3];
362  GetAllCargoSuffixes(0, CST_FUND, NULL, this->index[i], indsp, indsp->accepts_cargo, cargo_suffix);
363  StringID str = STR_INDUSTRY_VIEW_REQUIRES_CARGO;
364  byte p = 0;
365  SetDParam(0, STR_JUST_NOTHING);
366  SetDParamStr(1, "");
367  for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) {
368  if (indsp->accepts_cargo[j] == CT_INVALID) continue;
369  if (p > 0) str++;
370  SetDParam(p++, CargoSpec::Get(indsp->accepts_cargo[j])->name);
371  SetDParamStr(p++, cargo_suffix[j].text);
372  }
373  d = maxdim(d, GetStringBoundingBox(str));
374 
375  /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
376  GetAllCargoSuffixes(3, CST_FUND, NULL, this->index[i], indsp, indsp->produced_cargo, cargo_suffix);
377  str = STR_INDUSTRY_VIEW_PRODUCES_CARGO;
378  p = 0;
379  SetDParam(0, STR_JUST_NOTHING);
380  SetDParamStr(1, "");
381  for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) {
382  if (indsp->produced_cargo[j] == CT_INVALID) continue;
383  if (p > 0) str++;
384  SetDParam(p++, CargoSpec::Get(indsp->produced_cargo[j])->name);
385  SetDParamStr(p++, cargo_suffix[j].text);
386  }
387  d = maxdim(d, GetStringBoundingBox(str));
388  }
389 
390  /* Set it to something more sane :) */
391  size->height = height * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
392  size->width = d.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
393  break;
394  }
395 
396  case WID_DPI_FUND_WIDGET: {
397  Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
398  d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY));
399  d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY));
400  d.width += padding.width;
401  d.height += padding.height;
402  *size = maxdim(*size, d);
403  break;
404  }
405  }
406  }
407 
408  virtual void SetStringParameters(int widget) const
409  {
410  switch (widget) {
411  case WID_DPI_FUND_WIDGET:
412  /* Raw industries might be prospected. Show this fact by changing the string
413  * In Editor, you just build, while ingame, or you fund or you prospect */
414  if (_game_mode == GM_EDITOR) {
415  /* We've chosen many random industries but no industries have been specified */
416  SetDParam(0, STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
417  } else {
418  const IndustrySpec *indsp = GetIndustrySpec(this->index[this->selected_index]);
419  SetDParam(0, (_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
420  }
421  break;
422  }
423  }
424 
425  virtual void DrawWidget(const Rect &r, int widget) const
426  {
427  switch (widget) {
428  case WID_DPI_MATRIX_WIDGET: {
429  uint text_left, text_right, icon_left, icon_right;
430  if (_current_text_dir == TD_RTL) {
431  icon_right = r.right - WD_MATRIX_RIGHT;
432  icon_left = icon_right - 10;
433  text_right = icon_right - BuildIndustryWindow::MATRIX_TEXT_OFFSET;
434  text_left = r.left + WD_MATRIX_LEFT;
435  } else {
436  icon_left = r.left + WD_MATRIX_LEFT;
437  icon_right = icon_left + 10;
438  text_left = icon_left + BuildIndustryWindow::MATRIX_TEXT_OFFSET;
439  text_right = r.right - WD_MATRIX_RIGHT;
440  }
441 
442  for (byte i = 0; i < this->vscroll->GetCapacity() && i + this->vscroll->GetPosition() < this->count; i++) {
443  int y = r.top + WD_MATRIX_TOP + i * this->resize.step_height;
444  bool selected = this->selected_index == i + this->vscroll->GetPosition();
445 
446  if (this->index[i + this->vscroll->GetPosition()] == INVALID_INDUSTRYTYPE) {
447  DrawString(text_left, text_right, y, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, selected ? TC_WHITE : TC_ORANGE);
448  continue;
449  }
450  const IndustrySpec *indsp = GetIndustrySpec(this->index[i + this->vscroll->GetPosition()]);
451 
452  /* Draw the name of the industry in white is selected, otherwise, in orange */
453  DrawString(text_left, text_right, y, indsp->name, selected ? TC_WHITE : TC_ORANGE);
454  GfxFillRect(icon_left, y + 1, icon_right, y + 7, selected ? PC_WHITE : PC_BLACK);
455  GfxFillRect(icon_left + 1, y + 2, icon_right - 1, y + 6, indsp->map_colour);
456  }
457  break;
458  }
459 
460  case WID_DPI_INFOPANEL: {
461  int y = r.top + WD_FRAMERECT_TOP;
462  int bottom = r.bottom - WD_FRAMERECT_BOTTOM;
463  int left = r.left + WD_FRAMERECT_LEFT;
464  int right = r.right - WD_FRAMERECT_RIGHT;
465 
466  if (this->selected_type == INVALID_INDUSTRYTYPE) {
467  DrawStringMultiLine(left, right, y, bottom, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
468  break;
469  }
470 
471  const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
472 
473  if (_game_mode != GM_EDITOR) {
474  SetDParam(0, indsp->GetConstructionCost());
475  DrawString(left, right, y, STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST);
476  y += FONT_HEIGHT_NORMAL;
477  }
478 
479  /* Draw the accepted cargoes, if any. Otherwise, will print "Nothing". */
480  CargoSuffix cargo_suffix[3];
481  GetAllCargoSuffixes(0, CST_FUND, NULL, this->selected_type, indsp, indsp->accepts_cargo, cargo_suffix);
482  StringID str = STR_INDUSTRY_VIEW_REQUIRES_CARGO;
483  byte p = 0;
484  SetDParam(0, STR_JUST_NOTHING);
485  SetDParamStr(1, "");
486  for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) {
487  if (indsp->accepts_cargo[j] == CT_INVALID) continue;
488  if (p > 0) str++;
489  SetDParam(p++, CargoSpec::Get(indsp->accepts_cargo[j])->name);
490  SetDParamStr(p++, cargo_suffix[j].text);
491  }
492  DrawString(left, right, y, str);
493  y += FONT_HEIGHT_NORMAL;
494 
495  /* Draw the produced cargoes, if any. Otherwise, will print "Nothing". */
496  GetAllCargoSuffixes(3, CST_FUND, NULL, this->selected_type, indsp, indsp->produced_cargo, cargo_suffix);
497  str = STR_INDUSTRY_VIEW_PRODUCES_CARGO;
498  p = 0;
499  SetDParam(0, STR_JUST_NOTHING);
500  SetDParamStr(1, "");
501  for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) {
502  if (indsp->produced_cargo[j] == CT_INVALID) continue;
503  if (p > 0) str++;
504  SetDParam(p++, CargoSpec::Get(indsp->produced_cargo[j])->name);
505  SetDParamStr(p++, cargo_suffix[j].text);
506  }
507  DrawString(left, right, y, str);
508  y += FONT_HEIGHT_NORMAL;
509 
510  /* Get the additional purchase info text, if it has not already been queried. */
511  str = STR_NULL;
513  uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, NULL, this->selected_type, INVALID_TILE);
514  if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
515  if (callback_res > 0x400) {
517  } else {
518  str = GetGRFStringID(indsp->grf_prop.grffile->grfid, 0xD000 + callback_res); // No. here's the new string
519  if (str != STR_UNDEFINED) {
521  DrawStringMultiLine(left, right, y, bottom, str, TC_YELLOW);
523  }
524  }
525  }
526  }
527  break;
528  }
529  }
530  }
531 
532  virtual void OnClick(Point pt, int widget, int click_count)
533  {
534  switch (widget) {
535  case WID_DPI_MATRIX_WIDGET: {
536  int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_DPI_MATRIX_WIDGET);
537  if (y < this->count) { // Is it within the boundaries of available data?
538  this->selected_index = y;
539  this->selected_type = this->index[y];
540  const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
541 
542  this->SetDirty();
543 
544  if (_thd.GetCallbackWnd() == this &&
545  ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != NULL && indsp->IsRawIndustry()) ||
547  !this->enabled[this->selected_index])) {
548  /* Reset the button state if going to prospecting or "build many industries" */
549  this->RaiseButtons();
551  }
552 
553  this->SetButtons();
554  if (this->enabled[this->selected_index] && click_count > 1) this->OnClick(pt, WID_DPI_FUND_WIDGET, 1);
555  }
556  break;
557  }
558 
561  break;
562 
563  case WID_DPI_FUND_WIDGET: {
564  if (this->selected_type == INVALID_INDUSTRYTYPE) {
566 
567  if (Town::GetNumItems() == 0) {
568  ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO);
569  } else {
570  extern void GenerateIndustries();
571  _generating_world = true;
573  _generating_world = false;
574  }
575  } else if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
576  DoCommandP(0, this->selected_type, InteractiveRandom(), CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY));
578  } else {
579  HandlePlacePushButton(this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT);
580  }
581  break;
582  }
583  }
584  }
585 
586  virtual void OnResize()
587  {
588  /* Adjust the number of items in the matrix depending of the resize */
589  this->vscroll->SetCapacityFromWidget(this, WID_DPI_MATRIX_WIDGET);
590  }
591 
592  virtual void OnPlaceObject(Point pt, TileIndex tile)
593  {
594  bool success = true;
595  /* We do not need to protect ourselves against "Random Many Industries" in this mode */
596  const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
597  uint32 seed = InteractiveRandom();
598 
599  if (_game_mode == GM_EDITOR) {
600  /* Show error if no town exists at all */
601  if (Town::GetNumItems() == 0) {
602  SetDParam(0, indsp->name);
603  ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO, pt.x, pt.y);
604  return;
605  }
606 
607  Backup<CompanyByte> cur_company(_current_company, OWNER_NONE, FILE_LINE);
608  _generating_world = true;
609  _ignore_restrictions = true;
610 
611  DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 8) | this->selected_type, seed,
612  CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY), &CcBuildIndustry);
613 
614  cur_company.Restore();
615  _ignore_restrictions = false;
616  _generating_world = false;
617  } else {
618  success = DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY));
619  }
620 
621  /* If an industry has been built, just reset the cursor and the system */
623  }
624 
625  virtual void OnTick()
626  {
627  if (_pause_mode != PM_UNPAUSED) return;
628  if (!this->timer_enabled) return;
629  if (--this->callback_timer == 0) {
630  /* We have just passed another day.
631  * See if we need to update availability of currently selected industry */
632  this->callback_timer = DAY_TICKS; // restart counter
633 
634  const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
635 
636  if (indsp->enabled) {
637  bool call_back_result = GetIndustryProbabilityCallback(this->selected_type, IACT_USERCREATION, 1) > 0;
638 
639  /* Only if result does match the previous state would it require a redraw. */
640  if (call_back_result != this->enabled[this->selected_index]) {
641  this->enabled[this->selected_index] = call_back_result;
642  this->SetButtons();
643  this->SetDirty();
644  }
645  }
646  }
647  }
648 
649  virtual void OnTimeout()
650  {
651  this->RaiseButtons();
652  }
653 
654  virtual void OnPlaceObjectAbort()
655  {
656  this->RaiseButtons();
657  }
658 
664  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
665  {
666  if (!gui_scope) return;
667  this->SetupArrays();
668 
669  const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
670  if (indsp == NULL) this->enabled[this->selected_index] = _settings_game.difficulty.industry_density != ID_FUND_ONLY;
671  this->SetButtons();
672  }
673 };
674 
675 void ShowBuildIndustryWindow()
676 {
677  if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
679  new BuildIndustryWindow();
680 }
681 
682 static void UpdateIndustryProduction(Industry *i);
683 
684 static inline bool IsProductionAlterable(const Industry *i)
685 {
686  const IndustrySpec *is = GetIndustrySpec(i->type);
687  return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
688  (is->production_rate[0] != 0 || is->production_rate[1] != 0 || is->IsRawIndustry()) &&
689  !_networking);
690 }
691 
693 {
695  enum Editability {
699  };
700 
702  enum InfoLine {
707  };
708 
715 
716 public:
718  {
719  this->flags |= WF_DISABLE_VP_SCROLL;
720  this->editbox_line = IL_NONE;
721  this->clicked_line = IL_NONE;
722  this->clicked_button = 0;
723  this->info_height = WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM + 1; // Info panel has at least two lines text.
724 
725  this->InitNested(window_number);
726  NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
727  nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ZOOM_LVL_INDUSTRY);
728 
729  this->InvalidateData();
730  }
731 
732  virtual void OnPaint()
733  {
734  this->DrawWidgets();
735 
736  if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
737 
738  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_IV_INFO);
739  uint expected = this->DrawInfo(nwi->pos_x, nwi->pos_x + nwi->current_x - 1, nwi->pos_y) - nwi->pos_y;
740  if (expected > nwi->current_y - 1) {
741  this->info_height = expected + 1;
742  this->ReInit();
743  return;
744  }
745  }
746 
754  int DrawInfo(uint left, uint right, uint top)
755  {
757  const IndustrySpec *ind = GetIndustrySpec(i->type);
758  int y = top + WD_FRAMERECT_TOP;
759  bool first = true;
760  bool has_accept = false;
761 
762  if (i->prod_level == PRODLEVEL_CLOSURE) {
763  DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE);
764  y += 2 * FONT_HEIGHT_NORMAL;
765  }
766 
767  CargoSuffix cargo_suffix[3];
768  GetAllCargoSuffixes(0, CST_VIEW, i, i->type, ind, i->accepts_cargo, cargo_suffix);
770 
771  uint left_side = left + WD_FRAMERECT_LEFT * 4; // Indent accepted cargoes.
772  for (byte j = 0; j < lengthof(i->accepts_cargo); j++) {
773  if (i->accepts_cargo[j] == CT_INVALID) continue;
774  has_accept = true;
775  if (first) {
776  DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_REQUIRES);
777  y += FONT_HEIGHT_NORMAL;
778  first = false;
779  }
781  SetDParam(1, i->accepts_cargo[j]);
783  SetDParamStr(3, "");
784  StringID str = STR_NULL;
785  switch (cargo_suffix[j].display) {
787  SetDParamStr(3, cargo_suffix[j].text);
788  FALLTHROUGH;
789  case CSD_CARGO_AMOUNT:
790  str = stockpiling ? STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT : STR_INDUSTRY_VIEW_ACCEPT_CARGO;
791  break;
792 
793  case CSD_CARGO_TEXT:
794  SetDParamStr(3, cargo_suffix[j].text);
795  FALLTHROUGH;
796  case CSD_CARGO:
797  str = STR_INDUSTRY_VIEW_ACCEPT_CARGO;
798  break;
799 
800  default:
801  NOT_REACHED();
802  }
803  DrawString(left_side, right - WD_FRAMERECT_RIGHT, y, str);
804  y += FONT_HEIGHT_NORMAL;
805  }
806 
807  GetAllCargoSuffixes(3, CST_VIEW, i, i->type, ind, i->produced_cargo, cargo_suffix);
808  first = true;
809  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
810  if (i->produced_cargo[j] == CT_INVALID) continue;
811  if (first) {
812  if (has_accept) y += WD_PAR_VSEP_WIDE;
813  DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
814  y += FONT_HEIGHT_NORMAL;
815  if (this->editable == EA_RATE) this->production_offset_y = y;
816  first = false;
817  }
818 
819  SetDParam(0, i->produced_cargo[j]);
821  SetDParamStr(2, cargo_suffix[j].text);
823  uint x = left + WD_FRAMETEXT_LEFT + (this->editable == EA_RATE ? SETTING_BUTTON_WIDTH + 10 : 0);
824  DrawString(x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_TRANSPORTED);
825  /* Let's put out those buttons.. */
826  if (this->editable == EA_RATE) {
827  DrawArrowButtons(left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_RATE1 + j) ? this->clicked_button : 0,
828  i->production_rate[j] > 0, i->production_rate[j] < 255);
829  }
830  y += FONT_HEIGHT_NORMAL;
831  }
832 
833  /* Display production multiplier if editable */
834  if (this->editable == EA_MULTIPLIER) {
835  y += WD_PAR_VSEP_WIDE;
836  this->production_offset_y = y;
838  uint x = left + WD_FRAMETEXT_LEFT + SETTING_BUTTON_WIDTH + 10;
839  DrawString(x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LEVEL);
840  DrawArrowButtons(left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_MULTIPLIER) ? this->clicked_button : 0,
842  y += FONT_HEIGHT_NORMAL;
843  }
844 
845  /* Get the extra message for the GUI */
847  uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->location.tile);
848  if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
849  if (callback_res > 0x400) {
851  } else {
852  StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res);
853  if (message != STR_NULL && message != STR_UNDEFINED) {
854  y += WD_PAR_VSEP_WIDE;
855 
857  /* Use all the available space left from where we stand up to the
858  * end of the window. We ALSO enlarge the window if needed, so we
859  * can 'go' wild with the bottom of the window. */
860  y = DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, UINT16_MAX, message, TC_BLACK);
862  }
863  }
864  }
865  }
866  return y + WD_FRAMERECT_BOTTOM;
867  }
868 
869  virtual void SetStringParameters(int widget) const
870  {
871  if (widget == WID_IV_CAPTION) SetDParam(0, this->window_number);
872  }
873 
874  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
875  {
876  if (widget == WID_IV_INFO) size->height = this->info_height;
877  }
878 
879  virtual void OnClick(Point pt, int widget, int click_count)
880  {
881  switch (widget) {
882  case WID_IV_INFO: {
884  InfoLine line = IL_NONE;
885 
886  switch (this->editable) {
887  case EA_NONE: break;
888 
889  case EA_MULTIPLIER:
890  if (IsInsideBS(pt.y, this->production_offset_y, FONT_HEIGHT_NORMAL)) line = IL_MULTIPLIER;
891  break;
892 
893  case EA_RATE:
894  if (pt.y >= this->production_offset_y) {
895  int row = (pt.y - this->production_offset_y) / FONT_HEIGHT_NORMAL;
896  for (uint j = 0; j < lengthof(i->produced_cargo); j++) {
897  if (i->produced_cargo[j] == CT_INVALID) continue;
898  row--;
899  if (row < 0) {
900  line = (InfoLine)(IL_RATE1 + j);
901  break;
902  }
903  }
904  }
905  break;
906  }
907  if (line == IL_NONE) return;
908 
909  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget);
910  int left = nwi->pos_x + WD_FRAMETEXT_LEFT;
911  int right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT;
912  if (IsInsideMM(pt.x, left, left + SETTING_BUTTON_WIDTH)) {
913  /* Clicked buttons, decrease or increase production */
914  byte button = (pt.x < left + SETTING_BUTTON_WIDTH / 2) ? 1 : 2;
915  switch (this->editable) {
916  case EA_MULTIPLIER:
917  if (button == 1) {
918  if (i->prod_level <= PRODLEVEL_MINIMUM) return;
919  i->prod_level = max<uint>(i->prod_level / 2, PRODLEVEL_MINIMUM);
920  } else {
921  if (i->prod_level >= PRODLEVEL_MAXIMUM) return;
923  }
924  break;
925 
926  case EA_RATE:
927  if (button == 1) {
928  if (i->production_rate[line - IL_RATE1] <= 0) return;
929  i->production_rate[line - IL_RATE1] = max(i->production_rate[line - IL_RATE1] / 2, 0);
930  } else {
931  if (i->production_rate[line - IL_RATE1] >= 255) return;
932  /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
933  int new_prod = i->production_rate[line - IL_RATE1] == 0 ? 1 : i->production_rate[line - IL_RATE1] * 2;
934  i->production_rate[line - IL_RATE1] = minu(new_prod, 255);
935  }
936  break;
937 
938  default: NOT_REACHED();
939  }
940 
941  UpdateIndustryProduction(i);
942  this->SetDirty();
943  this->SetTimeout();
944  this->clicked_line = line;
945  this->clicked_button = button;
946  } else if (IsInsideMM(pt.x, left + SETTING_BUTTON_WIDTH + 10, right)) {
947  /* clicked the text */
948  this->editbox_line = line;
949  switch (this->editable) {
950  case EA_MULTIPLIER:
952  ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION_LEVEL, 10, this, CS_ALPHANUMERAL, QSF_NONE);
953  break;
954 
955  case EA_RATE:
956  SetDParam(0, i->production_rate[line - IL_RATE1] * 8);
957  ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION, 10, this, CS_ALPHANUMERAL, QSF_NONE);
958  break;
959 
960  default: NOT_REACHED();
961  }
962  }
963  break;
964  }
965 
966  case WID_IV_GOTO: {
968  if (_ctrl_pressed) {
970  } else {
972  }
973  break;
974  }
975 
976  case WID_IV_DISPLAY: {
979  break;
980  }
981  }
982  }
983 
984  virtual void OnTimeout()
985  {
986  this->clicked_line = IL_NONE;
987  this->clicked_button = 0;
988  this->SetDirty();
989  }
990 
991  virtual void OnResize()
992  {
993  if (this->viewport != NULL) {
994  NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
995  nvp->UpdateViewportCoordinates(this);
996 
997  ScrollWindowToTile(Industry::Get(this->window_number)->location.GetCenterTile(), this, true); // Re-center viewport.
998  }
999  }
1000 
1001  virtual void OnQueryTextFinished(char *str)
1002  {
1003  if (StrEmpty(str)) return;
1004 
1005  Industry *i = Industry::Get(this->window_number);
1006  uint value = atoi(str);
1007  switch (this->editbox_line) {
1008  case IL_NONE: NOT_REACHED();
1009 
1010  case IL_MULTIPLIER:
1012  break;
1013 
1014  default:
1015  i->production_rate[this->editbox_line - IL_RATE1] = ClampU(RoundDivSU(value, 8), 0, 255);
1016  break;
1017  }
1018  UpdateIndustryProduction(i);
1019  this->SetDirty();
1020  }
1021 
1027  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1028  {
1029  if (!gui_scope) return;
1030  const Industry *i = Industry::Get(this->window_number);
1031  if (IsProductionAlterable(i)) {
1032  const IndustrySpec *ind = GetIndustrySpec(i->type);
1033  this->editable = ind->UsesSmoothEconomy() ? EA_RATE : EA_MULTIPLIER;
1034  } else {
1035  this->editable = EA_NONE;
1036  }
1037  }
1038 
1039  virtual bool IsNewGRFInspectable() const
1040  {
1041  return ::IsNewGRFInspectable(GSF_INDUSTRIES, this->window_number);
1042  }
1043 
1044  virtual void ShowNewGRFInspectWindow() const
1045  {
1046  ::ShowNewGRFInspectWindow(GSF_INDUSTRIES, this->window_number);
1047  }
1048 };
1049 
1050 static void UpdateIndustryProduction(Industry *i)
1051 {
1052  const IndustrySpec *indspec = GetIndustrySpec(i->type);
1053  if (!indspec->UsesSmoothEconomy()) i->RecomputeProductionMultipliers();
1054 
1055  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1056  if (i->produced_cargo[j] != CT_INVALID) {
1057  i->last_month_production[j] = 8 * i->production_rate[j];
1058  }
1059  }
1060 }
1061 
1065  NWidget(WWT_CLOSEBOX, COLOUR_CREAM),
1066  NWidget(WWT_CAPTION, COLOUR_CREAM, WID_IV_CAPTION), SetDataTip(STR_INDUSTRY_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1067  NWidget(WWT_DEBUGBOX, COLOUR_CREAM),
1068  NWidget(WWT_SHADEBOX, COLOUR_CREAM),
1069  NWidget(WWT_DEFSIZEBOX, COLOUR_CREAM),
1070  NWidget(WWT_STICKYBOX, COLOUR_CREAM),
1071  EndContainer(),
1072  NWidget(WWT_PANEL, COLOUR_CREAM),
1073  NWidget(WWT_INSET, COLOUR_CREAM), SetPadding(2, 2, 2, 2),
1074  NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_IV_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetPadding(1, 1, 1, 1), SetResize(1, 1),
1075  EndContainer(),
1076  EndContainer(),
1077  NWidget(WWT_PANEL, COLOUR_CREAM, WID_IV_INFO), SetMinimalSize(260, 2), SetResize(1, 0),
1078  EndContainer(),
1080  NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GOTO), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_BUTTON_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
1081  NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
1082  NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
1083  EndContainer(),
1084 };
1085 
1088  WDP_AUTO, "view_industry", 260, 120,
1090  0,
1091  _nested_industry_view_widgets, lengthof(_nested_industry_view_widgets)
1092 );
1093 
1094 void ShowIndustryViewWindow(int industry)
1095 {
1096  AllocateWindowDescFront<IndustryViewWindow>(&_industry_view_desc, industry);
1097 }
1098 
1102  NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1103  NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_INDUSTRY_DIRECTORY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1104  NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1105  NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1106  NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1107  EndContainer(),
1111  NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_ID_DROPDOWN_ORDER), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1112  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_DROPDOWN_CRITERIA), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
1113  NWidget(WWT_PANEL, COLOUR_BROWN), SetResize(1, 0), EndContainer(),
1114  EndContainer(),
1115  NWidget(WWT_PANEL, COLOUR_BROWN, WID_ID_INDUSTRY_LIST), SetDataTip(0x0, STR_INDUSTRY_DIRECTORY_LIST_CAPTION), SetResize(1, 1), SetScrollbar(WID_ID_SCROLLBAR), EndContainer(),
1116  EndContainer(),
1118  NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_ID_SCROLLBAR),
1119  NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1120  EndContainer(),
1121  EndContainer(),
1122 };
1123 
1125 
1126 
1131 protected:
1132  /* Runtime saved values */
1133  static Listing last_sorting;
1134  static const Industry *last_industry;
1135 
1136  /* Constants for sorting stations */
1137  static const StringID sorter_names[];
1138  static GUIIndustryList::SortFunction * const sorter_funcs[];
1139 
1140  GUIIndustryList industries;
1141  Scrollbar *vscroll;
1142 
1145  {
1146  if (this->industries.NeedRebuild()) {
1147  this->industries.Clear();
1148 
1149  const Industry *i;
1150  FOR_ALL_INDUSTRIES(i) {
1151  *this->industries.Append() = i;
1152  }
1153 
1154  this->industries.Compact();
1155  this->industries.RebuildDone();
1156  this->vscroll->SetCount(this->industries.Length()); // Update scrollbar as well.
1157  }
1158 
1159  if (!this->industries.Sort()) return;
1160  IndustryDirectoryWindow::last_industry = NULL; // Reset name sorter sort cache
1161  this->SetWidgetDirty(WID_ID_INDUSTRY_LIST); // Set the modified widget dirty
1162  }
1163 
1171  static inline int GetCargoTransportedPercentsIfValid(const Industry *i, uint id)
1172  {
1173  assert(id < lengthof(i->produced_cargo));
1174 
1175  if (i->produced_cargo[id] == CT_INVALID) return 101;
1176  return ToPercent8(i->last_month_pct_transported[id]);
1177  }
1178 
1187  {
1188  int p1 = GetCargoTransportedPercentsIfValid(i, 0);
1189  int p2 = GetCargoTransportedPercentsIfValid(i, 1);
1190 
1191  if (p1 > p2) Swap(p1, p2); // lower value has higher priority
1192 
1193  return (p1 << 8) + p2;
1194  }
1195 
1197  static int CDECL IndustryNameSorter(const Industry * const *a, const Industry * const *b)
1198  {
1199  static char buf_cache[96];
1200  static char buf[96];
1201 
1202  SetDParam(0, (*a)->index);
1203  GetString(buf, STR_INDUSTRY_NAME, lastof(buf));
1204 
1205  if (*b != last_industry) {
1206  last_industry = *b;
1207  SetDParam(0, (*b)->index);
1208  GetString(buf_cache, STR_INDUSTRY_NAME, lastof(buf_cache));
1209  }
1210 
1211  return strnatcmp(buf, buf_cache); // Sort by name (natural sorting).
1212  }
1213 
1215  static int CDECL IndustryTypeSorter(const Industry * const *a, const Industry * const *b)
1216  {
1217  int it_a = 0;
1218  while (it_a != NUM_INDUSTRYTYPES && (*a)->type != _sorted_industry_types[it_a]) it_a++;
1219  int it_b = 0;
1220  while (it_b != NUM_INDUSTRYTYPES && (*b)->type != _sorted_industry_types[it_b]) it_b++;
1221  int r = it_a - it_b;
1222  return (r == 0) ? IndustryNameSorter(a, b) : r;
1223  }
1224 
1226  static int CDECL IndustryProductionSorter(const Industry * const *a, const Industry * const *b)
1227  {
1228  uint prod_a = 0, prod_b = 0;
1229  for (uint i = 0; i < lengthof((*a)->produced_cargo); i++) {
1230  if ((*a)->produced_cargo[i] != CT_INVALID) prod_a += (*a)->last_month_production[i];
1231  if ((*b)->produced_cargo[i] != CT_INVALID) prod_b += (*b)->last_month_production[i];
1232  }
1233  int r = prod_a - prod_b;
1234 
1235  return (r == 0) ? IndustryTypeSorter(a, b) : r;
1236  }
1237 
1239  static int CDECL IndustryTransportedCargoSorter(const Industry * const *a, const Industry * const *b)
1240  {
1242  return (r == 0) ? IndustryNameSorter(a, b) : r;
1243  }
1244 
1251  {
1252  const IndustrySpec *indsp = GetIndustrySpec(i->type);
1253  byte p = 0;
1254 
1255  /* Industry name */
1256  SetDParam(p++, i->index);
1257 
1258  static CargoSuffix cargo_suffix[lengthof(i->produced_cargo)];
1259  GetAllCargoSuffixes(3, CST_DIR, i, i->type, indsp, i->produced_cargo, cargo_suffix);
1260 
1261  /* Industry productions */
1262  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1263  if (i->produced_cargo[j] == CT_INVALID) continue;
1264  SetDParam(p++, i->produced_cargo[j]);
1265  SetDParam(p++, i->last_month_production[j]);
1266  SetDParamStr(p++, cargo_suffix[j].text);
1267  }
1268 
1269  /* Transported productions */
1270  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1271  if (i->produced_cargo[j] == CT_INVALID) continue;
1273  }
1274 
1275  /* Drawing the right string */
1276  switch (p) {
1277  case 1: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1278  case 5: return STR_INDUSTRY_DIRECTORY_ITEM;
1279  default: return STR_INDUSTRY_DIRECTORY_ITEM_TWO;
1280  }
1281  }
1282 
1283 public:
1284  IndustryDirectoryWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
1285  {
1286  this->CreateNestedTree();
1287  this->vscroll = this->GetScrollbar(WID_ID_SCROLLBAR);
1288 
1289  this->industries.SetListing(this->last_sorting);
1290  this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1291  this->industries.ForceRebuild();
1292  this->BuildSortIndustriesList();
1293 
1294  this->FinishInitNested(0);
1295  }
1296 
1298  {
1299  this->last_sorting = this->industries.GetListing();
1300  }
1301 
1302  virtual void SetStringParameters(int widget) const
1303  {
1304  if (widget == WID_ID_DROPDOWN_CRITERIA) SetDParam(0, IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1305  }
1306 
1307  virtual void DrawWidget(const Rect &r, int widget) const
1308  {
1309  switch (widget) {
1310  case WID_ID_DROPDOWN_ORDER:
1311  this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1312  break;
1313 
1314  case WID_ID_INDUSTRY_LIST: {
1315  int n = 0;
1316  int y = r.top + WD_FRAMERECT_TOP;
1317  if (this->industries.Length() == 0) {
1318  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_DIRECTORY_NONE);
1319  break;
1320  }
1321  for (uint i = this->vscroll->GetPosition(); i < this->industries.Length(); i++) {
1322  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, this->GetIndustryString(this->industries[i]));
1323 
1324  y += this->resize.step_height;
1325  if (++n == this->vscroll->GetCapacity()) break; // max number of industries in 1 window
1326  }
1327  break;
1328  }
1329  }
1330  }
1331 
1332  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1333  {
1334  switch (widget) {
1335  case WID_ID_DROPDOWN_ORDER: {
1336  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1337  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1338  d.height += padding.height;
1339  *size = maxdim(*size, d);
1340  break;
1341  }
1342 
1343  case WID_ID_DROPDOWN_CRITERIA: {
1344  Dimension d = {0, 0};
1345  for (uint i = 0; IndustryDirectoryWindow::sorter_names[i] != INVALID_STRING_ID; i++) {
1346  d = maxdim(d, GetStringBoundingBox(IndustryDirectoryWindow::sorter_names[i]));
1347  }
1348  d.width += padding.width;
1349  d.height += padding.height;
1350  *size = maxdim(*size, d);
1351  break;
1352  }
1353 
1354  case WID_ID_INDUSTRY_LIST: {
1355  Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1356  for (uint i = 0; i < this->industries.Length(); i++) {
1357  d = maxdim(d, GetStringBoundingBox(this->GetIndustryString(this->industries[i])));
1358  }
1359  resize->height = d.height;
1360  d.height *= 5;
1361  d.width += padding.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1362  d.height += padding.height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
1363  *size = maxdim(*size, d);
1364  break;
1365  }
1366  }
1367  }
1368 
1369 
1370  virtual void OnClick(Point pt, int widget, int click_count)
1371  {
1372  switch (widget) {
1373  case WID_ID_DROPDOWN_ORDER:
1374  this->industries.ToggleSortOrder();
1375  this->SetDirty();
1376  break;
1377 
1379  ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1380  break;
1381 
1382  case WID_ID_INDUSTRY_LIST: {
1383  uint p = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_ID_INDUSTRY_LIST, WD_FRAMERECT_TOP);
1384  if (p < this->industries.Length()) {
1385  if (_ctrl_pressed) {
1386  ShowExtraViewPortWindow(this->industries[p]->location.tile);
1387  } else {
1388  ScrollMainWindowToTile(this->industries[p]->location.tile);
1389  }
1390  }
1391  break;
1392  }
1393  }
1394  }
1395 
1396  virtual void OnDropdownSelect(int widget, int index)
1397  {
1398  if (this->industries.SortType() != index) {
1399  this->industries.SetSortType(index);
1400  this->BuildSortIndustriesList();
1401  }
1402  }
1403 
1404  virtual void OnResize()
1405  {
1406  this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST);
1407  }
1408 
1409  virtual void OnPaint()
1410  {
1411  if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1412  this->DrawWidgets();
1413  }
1414 
1415  virtual void OnHundredthTick()
1416  {
1417  this->industries.ForceResort();
1418  this->BuildSortIndustriesList();
1419  }
1420 
1426  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1427  {
1428  if (data == 0) {
1429  /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1430  this->industries.ForceRebuild();
1431  } else {
1432  this->industries.ForceResort();
1433  }
1434  }
1435 };
1436 
1437 Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1438 const Industry *IndustryDirectoryWindow::last_industry = NULL;
1439 
1440 /* Available station sorting functions. */
1441 GUIIndustryList::SortFunction * const IndustryDirectoryWindow::sorter_funcs[] = {
1442  &IndustryNameSorter,
1443  &IndustryTypeSorter,
1444  &IndustryProductionSorter,
1445  &IndustryTransportedCargoSorter
1446 };
1447 
1448 /* Names of the sorting functions */
1449 const StringID IndustryDirectoryWindow::sorter_names[] = {
1450  STR_SORT_BY_NAME,
1451  STR_SORT_BY_TYPE,
1452  STR_SORT_BY_PRODUCTION,
1453  STR_SORT_BY_TRANSPORTED,
1455 };
1456 
1457 
1460  WDP_AUTO, "list_industries", 428, 190,
1462  0,
1463  _nested_industry_directory_widgets, lengthof(_nested_industry_directory_widgets)
1464 );
1465 
1466 void ShowIndustryDirectory()
1467 {
1468  AllocateWindowDescFront<IndustryDirectoryWindow>(&_industry_directory_desc, 0);
1469 }
1470 
1474  NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1475  NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION), SetDataTip(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1476  NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1477  NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1478  NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1479  EndContainer(),
1484  NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1485  SetDataTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1486  NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1487  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1488  SetDataTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1489  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1490  SetDataTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1491  EndContainer(),
1492  EndContainer(),
1494  NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_IC_SCROLLBAR),
1495  NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1496  EndContainer(),
1497  EndContainer(),
1498 };
1499 
1502  WDP_AUTO, "industry_cargoes", 300, 210,
1504  0,
1505  _nested_industry_cargoes_widgets, lengthof(_nested_industry_cargoes_widgets)
1506 );
1507 
1516 };
1517 
1518 static const uint MAX_CARGOES = 3;
1519 
1522  static const int VERT_INTER_INDUSTRY_SPACE;
1523  static const int HOR_CARGO_BORDER_SPACE;
1524  static const int CARGO_STUB_WIDTH;
1526  static const int CARGO_FIELD_WIDTH;
1529 
1530  static const int INDUSTRY_LINE_COLOUR;
1531  static const int CARGO_LINE_COLOUR;
1532 
1534  static int industry_width;
1535 
1537  union {
1538  struct {
1539  IndustryType ind_type;
1542  } industry;
1543  struct {
1547  byte top_end;
1549  byte bottom_end;
1550  } cargo;
1551  struct {
1553  bool left_align;
1554  } cargo_label;
1556  } u; // Data for each type.
1557 
1563  {
1564  this->type = type;
1565  }
1566 
1572  void MakeIndustry(IndustryType ind_type)
1573  {
1574  this->type = CFT_INDUSTRY;
1575  this->u.industry.ind_type = ind_type;
1576  MemSetT(this->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
1577  MemSetT(this->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
1578  }
1579 
1586  int ConnectCargo(CargoID cargo, bool producer)
1587  {
1588  assert(this->type == CFT_CARGO);
1589  if (cargo == INVALID_CARGO) return -1;
1590 
1591  /* Find the vertical cargo column carrying the cargo. */
1592  int column = -1;
1593  for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
1594  if (cargo == this->u.cargo.vertical_cargoes[i]) {
1595  column = i;
1596  break;
1597  }
1598  }
1599  if (column < 0) return -1;
1600 
1601  if (producer) {
1602  assert(this->u.cargo.supp_cargoes[column] == INVALID_CARGO);
1603  this->u.cargo.supp_cargoes[column] = column;
1604  } else {
1605  assert(this->u.cargo.cust_cargoes[column] == INVALID_CARGO);
1606  this->u.cargo.cust_cargoes[column] = column;
1607  }
1608  return column;
1609  }
1610 
1616  {
1617  assert(this->type == CFT_CARGO);
1618 
1619  for (uint i = 0; i < MAX_CARGOES; i++) {
1620  if (this->u.cargo.supp_cargoes[i] != INVALID_CARGO) return true;
1621  if (this->u.cargo.cust_cargoes[i] != INVALID_CARGO) return true;
1622  }
1623  return false;
1624  }
1625 
1635  void MakeCargo(const CargoID *cargoes, uint length, int count = -1, bool top_end = false, bool bottom_end = false)
1636  {
1637  this->type = CFT_CARGO;
1638  uint i;
1639  uint num = 0;
1640  for (i = 0; i < MAX_CARGOES && i < length; i++) {
1641  if (cargoes[i] != INVALID_CARGO) {
1642  this->u.cargo.vertical_cargoes[num] = cargoes[i];
1643  num++;
1644  }
1645  }
1646  this->u.cargo.num_cargoes = (count < 0) ? num : count;
1647  for (; num < MAX_CARGOES; num++) this->u.cargo.vertical_cargoes[num] = INVALID_CARGO;
1648  this->u.cargo.top_end = top_end;
1649  this->u.cargo.bottom_end = bottom_end;
1650  MemSetT(this->u.cargo.supp_cargoes, INVALID_CARGO, MAX_CARGOES);
1651  MemSetT(this->u.cargo.cust_cargoes, INVALID_CARGO, MAX_CARGOES);
1652  }
1653 
1660  void MakeCargoLabel(const CargoID *cargoes, uint length, bool left_align)
1661  {
1662  this->type = CFT_CARGO_LABEL;
1663  uint i;
1664  for (i = 0; i < MAX_CARGOES && i < length; i++) this->u.cargo_label.cargoes[i] = cargoes[i];
1665  for (; i < MAX_CARGOES; i++) this->u.cargo_label.cargoes[i] = INVALID_CARGO;
1666  this->u.cargo_label.left_align = left_align;
1667  }
1668 
1673  void MakeHeader(StringID textid)
1674  {
1675  this->type = CFT_HEADER;
1676  this->u.header = textid;
1677  }
1678 
1684  int GetCargoBase(int xpos) const
1685  {
1686  assert(this->type == CFT_CARGO);
1687 
1688  switch (this->u.cargo.num_cargoes) {
1689  case 0: return xpos + CARGO_FIELD_WIDTH / 2;
1690  case 1: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH / 2;
1691  case 2: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH - HOR_CARGO_SPACE / 2;
1692  case 3: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH - HOR_CARGO_SPACE - HOR_CARGO_WIDTH / 2;
1693  default: NOT_REACHED();
1694  }
1695  }
1696 
1702  void Draw(int xpos, int ypos) const
1703  {
1704  switch (this->type) {
1705  case CFT_EMPTY:
1706  case CFT_SMALL_EMPTY:
1707  break;
1708 
1709  case CFT_HEADER:
1710  ypos += (small_height - FONT_HEIGHT_NORMAL) / 2;
1711  DrawString(xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
1712  break;
1713 
1714  case CFT_INDUSTRY: {
1715  int ypos1 = ypos + VERT_INTER_INDUSTRY_SPACE / 2;
1716  int ypos2 = ypos + normal_height - 1 - VERT_INTER_INDUSTRY_SPACE / 2;
1717  int xpos2 = xpos + industry_width - 1;
1718  GfxDrawLine(xpos, ypos1, xpos2, ypos1, INDUSTRY_LINE_COLOUR);
1719  GfxDrawLine(xpos, ypos1, xpos, ypos2, INDUSTRY_LINE_COLOUR);
1720  GfxDrawLine(xpos, ypos2, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1721  GfxDrawLine(xpos2, ypos1, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1722  ypos += (normal_height - FONT_HEIGHT_NORMAL) / 2;
1723  if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
1724  const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
1725  DrawString(xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
1726 
1727  /* Draw the industry legend. */
1728  int blob_left, blob_right;
1729  if (_current_text_dir == TD_RTL) {
1730  blob_right = xpos2 - BLOB_DISTANCE;
1731  blob_left = blob_right - BLOB_WIDTH;
1732  } else {
1733  blob_left = xpos + BLOB_DISTANCE;
1734  blob_right = blob_left + BLOB_WIDTH;
1735  }
1736  GfxFillRect(blob_left, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT, blob_right, ypos2 - BLOB_DISTANCE, PC_BLACK); // Border
1737  GfxFillRect(blob_left + 1, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT + 1, blob_right - 1, ypos2 - BLOB_DISTANCE - 1, indsp->map_colour);
1738  } else {
1739  DrawString(xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
1740  }
1741 
1742  /* Draw the other_produced/other_accepted cargoes. */
1743  const CargoID *other_right, *other_left;
1744  if (_current_text_dir == TD_RTL) {
1745  other_right = this->u.industry.other_accepted;
1746  other_left = this->u.industry.other_produced;
1747  } else {
1748  other_right = this->u.industry.other_produced;
1749  other_left = this->u.industry.other_accepted;
1750  }
1751  ypos1 += VERT_CARGO_EDGE;
1752  for (uint i = 0; i < MAX_CARGOES; i++) {
1753  if (other_right[i] != INVALID_CARGO) {
1754  const CargoSpec *csp = CargoSpec::Get(other_right[i]);
1755  int xp = xpos + industry_width + CARGO_STUB_WIDTH;
1756  DrawHorConnection(xpos + industry_width, xp - 1, ypos1, csp);
1757  GfxDrawLine(xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1758  }
1759  if (other_left[i] != INVALID_CARGO) {
1760  const CargoSpec *csp = CargoSpec::Get(other_left[i]);
1761  int xp = xpos - CARGO_STUB_WIDTH;
1762  DrawHorConnection(xp + 1, xpos - 1, ypos1, csp);
1763  GfxDrawLine(xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1764  }
1766  }
1767  break;
1768  }
1769 
1770  case CFT_CARGO: {
1771  int cargo_base = this->GetCargoBase(xpos);
1772  int top = ypos + (this->u.cargo.top_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0);
1773  int bot = ypos - (this->u.cargo.bottom_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0) + normal_height - 1;
1774  int colpos = cargo_base;
1775  for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
1776  if (this->u.cargo.top_end) GfxDrawLine(colpos, top - 1, colpos + HOR_CARGO_WIDTH - 1, top - 1, CARGO_LINE_COLOUR);
1777  if (this->u.cargo.bottom_end) GfxDrawLine(colpos, bot + 1, colpos + HOR_CARGO_WIDTH - 1, bot + 1, CARGO_LINE_COLOUR);
1778  GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
1779  colpos++;
1780  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
1781  GfxFillRect(colpos, top, colpos + HOR_CARGO_WIDTH - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
1782  colpos += HOR_CARGO_WIDTH - 2;
1783  GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
1784  colpos += 1 + HOR_CARGO_SPACE;
1785  }
1786 
1787  const CargoID *hor_left, *hor_right;
1788  if (_current_text_dir == TD_RTL) {
1789  hor_left = this->u.cargo.cust_cargoes;
1790  hor_right = this->u.cargo.supp_cargoes;
1791  } else {
1792  hor_left = this->u.cargo.supp_cargoes;
1793  hor_right = this->u.cargo.cust_cargoes;
1794  }
1796  for (uint i = 0; i < MAX_CARGOES; i++) {
1797  if (hor_left[i] != INVALID_CARGO) {
1798  int col = hor_left[i];
1799  int dx = 0;
1800  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
1801  for (; col > 0; col--) {
1802  int lf = cargo_base + col * HOR_CARGO_WIDTH + (col - 1) * HOR_CARGO_SPACE;
1803  DrawHorConnection(lf, lf + HOR_CARGO_SPACE - dx, ypos, csp);
1804  dx = 1;
1805  }
1806  DrawHorConnection(xpos, cargo_base - dx, ypos, csp);
1807  }
1808  if (hor_right[i] != INVALID_CARGO) {
1809  int col = hor_right[i];
1810  int dx = 0;
1811  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
1812  for (; col < this->u.cargo.num_cargoes - 1; col++) {
1813  int lf = cargo_base + (col + 1) * HOR_CARGO_WIDTH + col * HOR_CARGO_SPACE;
1814  DrawHorConnection(lf + dx - 1, lf + HOR_CARGO_SPACE - 1, ypos, csp);
1815  dx = 1;
1816  }
1817  DrawHorConnection(cargo_base + col * HOR_CARGO_SPACE + (col + 1) * HOR_CARGO_WIDTH - 1 + dx, xpos + CARGO_FIELD_WIDTH - 1, ypos, csp);
1818  }
1820  }
1821  break;
1822  }
1823 
1824  case CFT_CARGO_LABEL:
1826  for (uint i = 0; i < MAX_CARGOES; i++) {
1827  if (this->u.cargo_label.cargoes[i] != INVALID_CARGO) {
1828  const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
1829  DrawString(xpos + WD_FRAMERECT_LEFT, xpos + industry_width - 1 - WD_FRAMERECT_RIGHT, ypos, csp->name, TC_WHITE,
1830  (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
1831  }
1833  }
1834  break;
1835 
1836  default:
1837  NOT_REACHED();
1838  }
1839  }
1840 
1848  CargoID CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
1849  {
1850  assert(this->type == CFT_CARGO);
1851 
1852  /* Vertical matching. */
1853  int cpos = this->GetCargoBase(0);
1854  uint col;
1855  for (col = 0; col < this->u.cargo.num_cargoes; col++) {
1856  if (pt.x < cpos) break;
1857  if (pt.x < cpos + CargoesField::HOR_CARGO_WIDTH) return this->u.cargo.vertical_cargoes[col];
1859  }
1860  /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
1861 
1862  int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
1863  uint row;
1864  for (row = 0; row < MAX_CARGOES; row++) {
1865  if (pt.y < vpos) return INVALID_CARGO;
1866  if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
1868  }
1869  if (row == MAX_CARGOES) return INVALID_CARGO;
1870 
1871  /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
1872  if (col == 0) {
1873  if (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]];
1874  if (left != NULL) {
1875  if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
1876  if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
1877  }
1878  return INVALID_CARGO;
1879  }
1880  if (col == this->u.cargo.num_cargoes) {
1881  if (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]];
1882  if (right != NULL) {
1883  if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
1884  if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
1885  }
1886  return INVALID_CARGO;
1887  }
1888  if (row >= col) {
1889  /* Clicked somewhere in-between vertical cargo connection.
1890  * Since the horizontal connection is made in the same order as the vertical list, the above condition
1891  * ensures we are left-below the main diagonal, thus at the supplying side.
1892  */
1893  return (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]] : INVALID_CARGO;
1894  } else {
1895  /* Clicked at a customer connection. */
1896  return (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]] : INVALID_CARGO;
1897  }
1898  }
1899 
1906  {
1907  assert(this->type == CFT_CARGO_LABEL);
1908 
1909  int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
1910  uint row;
1911  for (row = 0; row < MAX_CARGOES; row++) {
1912  if (pt.y < vpos) return INVALID_CARGO;
1913  if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
1915  }
1916  if (row == MAX_CARGOES) return INVALID_CARGO;
1917  return this->u.cargo_label.cargoes[row];
1918  }
1919 
1920 private:
1928  static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
1929  {
1930  GfxDrawLine(left, top, right, top, CARGO_LINE_COLOUR);
1931  GfxFillRect(left, top + 1, right, top + FONT_HEIGHT_NORMAL - 2, csp->legend_colour, FILLRECT_OPAQUE);
1932  GfxDrawLine(left, top + FONT_HEIGHT_NORMAL - 1, right, top + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1933  }
1934 };
1935 
1936 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, produced_cargo));
1937 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, accepts_cargo));
1938 
1943 
1944 const int CargoesField::HOR_CARGO_BORDER_SPACE = 15;
1945 const int CargoesField::CARGO_STUB_WIDTH = 10;
1946 const int CargoesField::HOR_CARGO_WIDTH = 15;
1947 const int CargoesField::HOR_CARGO_SPACE = 5;
1948 const int CargoesField::VERT_CARGO_EDGE = 4;
1949 const int CargoesField::VERT_CARGO_SPACE = 4;
1950 
1951 const int CargoesField::BLOB_DISTANCE = 5;
1952 const int CargoesField::BLOB_WIDTH = 12;
1953 const int CargoesField::BLOB_HEIGHT = 9;
1954 
1956 const int CargoesField::CARGO_FIELD_WIDTH = HOR_CARGO_BORDER_SPACE * 2 + HOR_CARGO_WIDTH * MAX_CARGOES + HOR_CARGO_SPACE * (MAX_CARGOES - 1);
1957 
1960 
1962 struct CargoesRow {
1964 
1969  void ConnectIndustryProduced(int column)
1970  {
1971  CargoesField *ind_fld = this->columns + column;
1972  CargoesField *cargo_fld = this->columns + column + 1;
1973  assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
1974 
1975  MemSetT(ind_fld->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
1976 
1977  if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
1978  CargoID others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
1979  int other_count = 0;
1980 
1981  const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
1982  for (uint i = 0; i < lengthof(indsp->produced_cargo); i++) {
1983  int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
1984  if (col < 0) others[other_count++] = indsp->produced_cargo[i];
1985  }
1986 
1987  /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
1988  for (uint i = 0; i < MAX_CARGOES && other_count > 0; i++) {
1989  if (cargo_fld->u.cargo.supp_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_produced[i] = others[--other_count];
1990  }
1991  } else {
1992  /* Houses only display what is demanded. */
1993  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
1994  CargoID cid = cargo_fld->u.cargo.vertical_cargoes[i];
1995  if (cid == CT_PASSENGERS || cid == CT_MAIL) cargo_fld->ConnectCargo(cid, true);
1996  }
1997  }
1998  }
1999 
2005  void MakeCargoLabel(int column, bool accepting)
2006  {
2007  CargoID cargoes[MAX_CARGOES];
2008  MemSetT(cargoes, INVALID_CARGO, lengthof(cargoes));
2009 
2010  CargoesField *label_fld = this->columns + column;
2011  CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2012 
2013  assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2014  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2015  int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2016  if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2017  }
2018  label_fld->MakeCargoLabel(cargoes, lengthof(cargoes), accepting);
2019  }
2020 
2021 
2026  void ConnectIndustryAccepted(int column)
2027  {
2028  CargoesField *ind_fld = this->columns + column;
2029  CargoesField *cargo_fld = this->columns + column - 1;
2030  assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2031 
2032  MemSetT(ind_fld->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
2033 
2034  if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2035  CargoID others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2036  int other_count = 0;
2037 
2038  const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2039  for (uint i = 0; i < lengthof(indsp->accepts_cargo); i++) {
2040  int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2041  if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2042  }
2043 
2044  /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2045  for (uint i = 0; i < MAX_CARGOES && other_count > 0; i++) {
2046  if (cargo_fld->u.cargo.cust_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2047  }
2048  } else {
2049  /* Houses only display what is demanded. */
2050  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2051  for (uint h = 0; h < NUM_HOUSES; h++) {
2052  HouseSpec *hs = HouseSpec::Get(h);
2053  if (!hs->enabled) continue;
2054 
2055  for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2056  if (hs->cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs->accepts_cargo[j]) {
2057  cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2058  goto next_cargo;
2059  }
2060  }
2061  }
2062 next_cargo: ;
2063  }
2064  }
2065  }
2066 };
2067 
2068 
2098 
2100 
2102  uint ind_cargo;
2105  Scrollbar *vscroll;
2106 
2108  {
2109  this->OnInit();
2110  this->CreateNestedTree();
2111  this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2112  this->FinishInitNested(0);
2113  this->OnInvalidateData(id);
2114  }
2115 
2116  virtual void OnInit()
2117  {
2118  /* Initialize static CargoesField size variables. */
2119  Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2120  d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2122  d.height += WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
2123  CargoesField::small_height = d.height;
2124 
2125  /* Decide about the size of the box holding the text of an industry type. */
2126  this->ind_textsize.width = 0;
2127  this->ind_textsize.height = 0;
2128  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2129  const IndustrySpec *indsp = GetIndustrySpec(it);
2130  if (!indsp->enabled) continue;
2131  this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(indsp->name));
2132  }
2133  d.width = max(d.width, this->ind_textsize.width);
2134  d.height = this->ind_textsize.height;
2135  this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2136 
2137  /* Compute max size of the cargo texts. */
2138  this->cargo_textsize.width = 0;
2139  this->cargo_textsize.height = 0;
2140  for (uint i = 0; i < NUM_CARGO; i++) {
2141  const CargoSpec *csp = CargoSpec::Get(i);
2142  if (!csp->IsValid()) continue;
2144  }
2145  d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2146  this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2147 
2148  d.width += 2 * HOR_TEXT_PADDING;
2149  /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2150  uint min_ind_height = CargoesField::VERT_CARGO_EDGE * 2 + MAX_CARGOES * FONT_HEIGHT_NORMAL + (MAX_CARGOES - 1) * CargoesField::VERT_CARGO_SPACE;
2151  d.height = max(d.height + 2 * VERT_TEXT_PADDING, min_ind_height);
2152 
2153  CargoesField::industry_width = d.width;
2154  CargoesField::normal_height = d.height + CargoesField::VERT_INTER_INDUSTRY_SPACE;
2155  }
2156 
2157  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2158  {
2159  switch (widget) {
2160  case WID_IC_PANEL:
2161  size->width = WD_FRAMETEXT_LEFT + CargoesField::industry_width * 3 + CargoesField::CARGO_FIELD_WIDTH * 2 + WD_FRAMETEXT_RIGHT;
2162  break;
2163 
2164  case WID_IC_IND_DROPDOWN:
2165  size->width = max(size->width, this->ind_textsize.width + padding.width);
2166  break;
2167 
2168  case WID_IC_CARGO_DROPDOWN:
2169  size->width = max(size->width, this->cargo_textsize.width + padding.width);
2170  break;
2171  }
2172  }
2173 
2174 
2176  virtual void SetStringParameters (int widget) const
2177  {
2178  if (widget != WID_IC_CAPTION) return;
2179 
2180  if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2181  const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2182  SetDParam(0, indsp->name);
2183  } else {
2184  const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2185  SetDParam(0, csp->name);
2186  }
2187  }
2188 
2197  static bool HasCommonValidCargo(const CargoID *cargoes1, uint length1, const CargoID *cargoes2, uint length2)
2198  {
2199  while (length1 > 0) {
2200  if (*cargoes1 != INVALID_CARGO) {
2201  for (uint i = 0; i < length2; i++) if (*cargoes1 == cargoes2[i]) return true;
2202  }
2203  cargoes1++;
2204  length1--;
2205  }
2206  return false;
2207  }
2208 
2215  static bool HousesCanSupply(const CargoID *cargoes, uint length)
2216  {
2217  for (uint i = 0; i < length; i++) {
2218  if (cargoes[i] == INVALID_CARGO) continue;
2219  if (cargoes[i] == CT_PASSENGERS || cargoes[i] == CT_MAIL) return true;
2220  }
2221  return false;
2222  }
2223 
2230  static bool HousesCanAccept(const CargoID *cargoes, uint length)
2231  {
2232  HouseZones climate_mask;
2234  case LT_TEMPERATE: climate_mask = HZ_TEMP; break;
2235  case LT_ARCTIC: climate_mask = HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW; break;
2236  case LT_TROPIC: climate_mask = HZ_SUBTROPIC; break;
2237  case LT_TOYLAND: climate_mask = HZ_TOYLND; break;
2238  default: NOT_REACHED();
2239  }
2240  for (uint i = 0; i < length; i++) {
2241  if (cargoes[i] == INVALID_CARGO) continue;
2242 
2243  for (uint h = 0; h < NUM_HOUSES; h++) {
2244  HouseSpec *hs = HouseSpec::Get(h);
2245  if (!hs->enabled || !(hs->building_availability & climate_mask)) continue;
2246 
2247  for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2248  if (hs->cargo_acceptance[j] > 0 && cargoes[i] == hs->accepts_cargo[j]) return true;
2249  }
2250  }
2251  }
2252  return false;
2253  }
2254 
2261  static int CountMatchingAcceptingIndustries(const CargoID *cargoes, uint length)
2262  {
2263  int count = 0;
2264  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2265  const IndustrySpec *indsp = GetIndustrySpec(it);
2266  if (!indsp->enabled) continue;
2267 
2268  if (HasCommonValidCargo(cargoes, length, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) count++;
2269  }
2270  return count;
2271  }
2272 
2279  static int CountMatchingProducingIndustries(const CargoID *cargoes, uint length)
2280  {
2281  int count = 0;
2282  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2283  const IndustrySpec *indsp = GetIndustrySpec(it);
2284  if (!indsp->enabled) continue;
2285 
2286  if (HasCommonValidCargo(cargoes, length, indsp->produced_cargo, lengthof(indsp->produced_cargo))) count++;
2287  }
2288  return count;
2289  }
2290 
2297  void ShortenCargoColumn(int column, int top, int bottom)
2298  {
2299  while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2300  this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2301  top++;
2302  }
2303  this->fields[top].columns[column].u.cargo.top_end = true;
2304 
2305  while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2306  this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2307  bottom--;
2308  }
2309  this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2310  }
2311 
2318  void PlaceIndustry(int row, int col, IndustryType it)
2319  {
2320  assert(this->fields[row].columns[col].type == CFT_EMPTY);
2321  this->fields[row].columns[col].MakeIndustry(it);
2322  if (col == 0) {
2323  this->fields[row].ConnectIndustryProduced(col);
2324  } else {
2325  this->fields[row].ConnectIndustryAccepted(col);
2326  }
2327  }
2328 
2333  {
2334  if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2335 
2336  /* Only notify the smallmap window if it exists. In particular, do not
2337  * bring it to the front to prevent messing up any nice layout of the user. */
2339  }
2340 
2345  void ComputeIndustryDisplay(IndustryType it)
2346  {
2347  this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION;
2348  this->ind_cargo = it;
2349  _displayed_industries.reset();
2350  _displayed_industries.set(it);
2351 
2352  this->fields.Clear();
2353  CargoesRow *row = this->fields.Append();
2354  row->columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2358  row->columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2359 
2360  const IndustrySpec *central_sp = GetIndustrySpec(it);
2361  bool houses_supply = HousesCanSupply(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2362  bool houses_accept = HousesCanAccept(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2363  /* Make a field consisting of two cargo columns. */
2364  int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo)) + houses_supply;
2365  int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo, lengthof(central_sp->produced_cargo)) + houses_accept;
2366  int num_indrows = max(3, max(num_supp, num_cust)); // One is needed for the 'it' industry, and 2 for the cargo labels.
2367  for (int i = 0; i < num_indrows; i++) {
2368  CargoesRow *row = this->fields.Append();
2369  row->columns[0].MakeEmpty(CFT_EMPTY);
2370  row->columns[1].MakeCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2371  row->columns[2].MakeEmpty(CFT_EMPTY);
2372  row->columns[3].MakeCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2373  row->columns[4].MakeEmpty(CFT_EMPTY);
2374  }
2375  /* Add central industry. */
2376  int central_row = 1 + num_indrows / 2;
2377  this->fields[central_row].columns[2].MakeIndustry(it);
2378  this->fields[central_row].ConnectIndustryProduced(2);
2379  this->fields[central_row].ConnectIndustryAccepted(2);
2380 
2381  /* Add cargo labels. */
2382  this->fields[central_row - 1].MakeCargoLabel(2, true);
2383  this->fields[central_row + 1].MakeCargoLabel(2, false);
2384 
2385  /* Add suppliers and customers of the 'it' industry. */
2386  int supp_count = 0;
2387  int cust_count = 0;
2388  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2389  const IndustrySpec *indsp = GetIndustrySpec(it);
2390  if (!indsp->enabled) continue;
2391 
2392  if (HasCommonValidCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo), indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2393  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2394  _displayed_industries.set(it);
2395  supp_count++;
2396  }
2397  if (HasCommonValidCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo), indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2398  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2399  _displayed_industries.set(it);
2400  cust_count++;
2401  }
2402  }
2403  if (houses_supply) {
2404  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2405  supp_count++;
2406  }
2407  if (houses_accept) {
2408  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2409  cust_count++;
2410  }
2411 
2412  this->ShortenCargoColumn(1, 1, num_indrows);
2413  this->ShortenCargoColumn(3, 1, num_indrows);
2414  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2415  this->vscroll->SetCount(CeilDiv(WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + CargoesField::small_height + num_indrows * CargoesField::normal_height, nwp->resize_y));
2416  this->SetDirty();
2417  this->NotifySmallmap();
2418  }
2419 
2425  {
2426  this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_CARGO_CAPTION;
2427  this->ind_cargo = cid + NUM_INDUSTRYTYPES;
2428  _displayed_industries.reset();
2429 
2430  this->fields.Clear();
2431  CargoesRow *row = this->fields.Append();
2432  row->columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2434  row->columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2437 
2438  bool houses_supply = HousesCanSupply(&cid, 1);
2439  bool houses_accept = HousesCanAccept(&cid, 1);
2440  int num_supp = CountMatchingProducingIndustries(&cid, 1) + houses_supply + 1; // Ensure room for the cargo label.
2441  int num_cust = CountMatchingAcceptingIndustries(&cid, 1) + houses_accept;
2442  int num_indrows = max(num_supp, num_cust);
2443  for (int i = 0; i < num_indrows; i++) {
2444  CargoesRow *row = this->fields.Append();
2445  row->columns[0].MakeEmpty(CFT_EMPTY);
2446  row->columns[1].MakeCargo(&cid, 1);
2447  row->columns[2].MakeEmpty(CFT_EMPTY);
2448  row->columns[3].MakeEmpty(CFT_EMPTY);
2449  row->columns[4].MakeEmpty(CFT_EMPTY);
2450  }
2451 
2452  this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2453 
2454  /* Add suppliers and customers of the cargo. */
2455  int supp_count = 0;
2456  int cust_count = 0;
2457  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2458  const IndustrySpec *indsp = GetIndustrySpec(it);
2459  if (!indsp->enabled) continue;
2460 
2461  if (HasCommonValidCargo(&cid, 1, indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2462  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2463  _displayed_industries.set(it);
2464  supp_count++;
2465  }
2466  if (HasCommonValidCargo(&cid, 1, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2467  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2468  _displayed_industries.set(it);
2469  cust_count++;
2470  }
2471  }
2472  if (houses_supply) {
2473  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2474  supp_count++;
2475  }
2476  if (houses_accept) {
2477  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2478  cust_count++;
2479  }
2480 
2481  this->ShortenCargoColumn(1, 1, num_indrows);
2482  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2483  this->vscroll->SetCount(CeilDiv(WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + CargoesField::small_height + num_indrows * CargoesField::normal_height, nwp->resize_y));
2484  this->SetDirty();
2485  this->NotifySmallmap();
2486  }
2487 
2495  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2496  {
2497  if (!gui_scope) return;
2498  if (data == NUM_INDUSTRYTYPES) {
2499  if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2500  this->RaiseWidget(WID_IC_NOTIFY);
2502  }
2503  return;
2504  }
2505 
2506  assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2507  this->ComputeIndustryDisplay(data);
2508  }
2509 
2510  virtual void DrawWidget(const Rect &r, int widget) const
2511  {
2512  if (widget != WID_IC_PANEL) return;
2513 
2514  DrawPixelInfo tmp_dpi, *old_dpi;
2515  int width = r.right - r.left + 1;
2516  int height = r.bottom - r.top + 1 - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM;
2517  if (!FillDrawPixelInfo(&tmp_dpi, r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP, width, height)) return;
2518  old_dpi = _cur_dpi;
2519  _cur_dpi = &tmp_dpi;
2520 
2521  int left_pos = WD_FRAMERECT_LEFT;
2522  if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::CARGO_FIELD_WIDTH) / 2;
2523  int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2524 
2525  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2526  int vpos = -this->vscroll->GetPosition() * nwp->resize_y;
2527  for (uint i = 0; i < this->fields.Length(); i++) {
2528  int row_height = (i == 0) ? CargoesField::small_height : CargoesField::normal_height;
2529  if (vpos + row_height >= 0) {
2530  int xpos = left_pos;
2531  int col, dir;
2532  if (_current_text_dir == TD_RTL) {
2533  col = last_column;
2534  dir = -1;
2535  } else {
2536  col = 0;
2537  dir = 1;
2538  }
2539  while (col >= 0 && col <= last_column) {
2540  this->fields[i].columns[col].Draw(xpos, vpos);
2541  xpos += (col & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width;
2542  col += dir;
2543  }
2544  }
2545  vpos += row_height;
2546  if (vpos >= height) break;
2547  }
2548 
2549  _cur_dpi = old_dpi;
2550  }
2551 
2559  bool CalculatePositionInWidget(Point pt, Point *fieldxy, Point *xy)
2560  {
2561  const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2562  pt.x -= nw->pos_x;
2563  pt.y -= nw->pos_y;
2564 
2565  int vpos = WD_FRAMERECT_TOP + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
2566  if (pt.y < vpos) return false;
2567 
2568  int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
2569  if (row + 1 >= (int)this->fields.Length()) return false;
2570  vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
2571  row++; // rebase row to match index of this->fields.
2572 
2573  int xpos = 2 * WD_FRAMERECT_LEFT + ((this->ind_cargo < NUM_INDUSTRYTYPES) ? 0 : (CargoesField::industry_width + CargoesField::CARGO_FIELD_WIDTH) / 2);
2574  if (pt.x < xpos) return false;
2575  int column;
2576  for (column = 0; column <= 5; column++) {
2577  int width = (column & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width;
2578  if (pt.x < xpos + width) break;
2579  xpos += width;
2580  }
2581  int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2582  if (column > num_columns) return false;
2583  xpos = pt.x - xpos;
2584 
2585  /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
2586  fieldxy->y = row;
2587  xy->y = vpos;
2588  if (_current_text_dir == TD_RTL) {
2589  fieldxy->x = num_columns - column;
2590  xy->x = ((column & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width) - xpos;
2591  } else {
2592  fieldxy->x = column;
2593  xy->x = xpos;
2594  }
2595  return true;
2596  }
2597 
2598  virtual void OnClick(Point pt, int widget, int click_count)
2599  {
2600  switch (widget) {
2601  case WID_IC_PANEL: {
2602  Point fieldxy, xy;
2603  if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
2604 
2605  const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2606  switch (fld->type) {
2607  case CFT_INDUSTRY:
2608  if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES) this->ComputeIndustryDisplay(fld->u.industry.ind_type);
2609  break;
2610 
2611  case CFT_CARGO: {
2612  CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : NULL;
2613  CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : NULL;
2614  CargoID cid = fld->CargoClickedAt(lft, rgt, xy);
2615  if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2616  break;
2617  }
2618 
2619  case CFT_CARGO_LABEL: {
2620  CargoID cid = fld->CargoLabelClickedAt(xy);
2621  if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2622  break;
2623  }
2624 
2625  default:
2626  break;
2627  }
2628  break;
2629  }
2630 
2631  case WID_IC_NOTIFY:
2634  if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
2635 
2636  if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2637  if (FindWindowByClass(WC_SMALLMAP) == NULL) ShowSmallMap();
2638  this->NotifySmallmap();
2639  }
2640  break;
2641 
2642  case WID_IC_CARGO_DROPDOWN: {
2643  DropDownList *lst = new DropDownList;
2644  const CargoSpec *cs;
2646  *lst->Append() = new DropDownListStringItem(cs->name, cs->Index(), false);
2647  }
2648  if (lst->Length() == 0) {
2649  delete lst;
2650  break;
2651  }
2652  int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
2653  ShowDropDownList(this, lst, selected, WID_IC_CARGO_DROPDOWN, 0, true);
2654  break;
2655  }
2656 
2657  case WID_IC_IND_DROPDOWN: {
2658  DropDownList *lst = new DropDownList;
2659  for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
2660  IndustryType ind = _sorted_industry_types[i];
2661  const IndustrySpec *indsp = GetIndustrySpec(ind);
2662  if (!indsp->enabled) continue;
2663  *lst->Append() = new DropDownListStringItem(indsp->name, ind, false);
2664  }
2665  if (lst->Length() == 0) {
2666  delete lst;
2667  break;
2668  }
2669  int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
2670  ShowDropDownList(this, lst, selected, WID_IC_IND_DROPDOWN, 0, true);
2671  break;
2672  }
2673  }
2674  }
2675 
2676  virtual void OnDropdownSelect(int widget, int index)
2677  {
2678  if (index < 0) return;
2679 
2680  switch (widget) {
2681  case WID_IC_CARGO_DROPDOWN:
2682  this->ComputeCargoDisplay(index);
2683  break;
2684 
2685  case WID_IC_IND_DROPDOWN:
2686  this->ComputeIndustryDisplay(index);
2687  break;
2688  }
2689  }
2690 
2691  virtual void OnHover(Point pt, int widget)
2692  {
2693  if (widget != WID_IC_PANEL) return;
2694 
2695  Point fieldxy, xy;
2696  if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
2697 
2698  const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2699  CargoID cid = INVALID_CARGO;
2700  switch (fld->type) {
2701  case CFT_CARGO: {
2702  CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : NULL;
2703  CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : NULL;
2704  cid = fld->CargoClickedAt(lft, rgt, xy);
2705  break;
2706  }
2707 
2708  case CFT_CARGO_LABEL: {
2709  cid = fld->CargoLabelClickedAt(xy);
2710  break;
2711  }
2712 
2713  case CFT_INDUSTRY:
2714  if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
2715  GuiShowTooltips(this, STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP, 0, NULL, TCC_HOVER);
2716  }
2717  return;
2718 
2719  default:
2720  break;
2721  }
2722  if (cid != INVALID_CARGO && (this->ind_cargo < NUM_INDUSTRYTYPES || cid != this->ind_cargo - NUM_INDUSTRYTYPES)) {
2723  const CargoSpec *csp = CargoSpec::Get(cid);
2724  uint64 params[5];
2725  params[0] = csp->name;
2726  GuiShowTooltips(this, STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, 1, params, TCC_HOVER);
2727  }
2728  }
2729 
2730  virtual void OnResize()
2731  {
2732  this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL);
2733  }
2734 };
2735 
2738 
2743 static void ShowIndustryCargoesWindow(IndustryType id)
2744 {
2745  if (id >= NUM_INDUSTRYTYPES) {
2746  for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
2748  if (indsp->enabled) {
2749  id = _sorted_industry_types[i];
2750  break;
2751  }
2752  }
2753  if (id >= NUM_INDUSTRYTYPES) return;
2754  }
2755 
2757  if (w != NULL) {
2758  w->InvalidateData(id);
2759  return;
2760  }
2761  new IndustryCargoesWindow(id);
2762 }
2763 
2766 {
2768 }