OpenTTD
industry_gui.cpp
Go to the documentation of this file.
1 /* $Id: industry_gui.cpp 27751 2017-02-26 15:34:15Z alberth $ */
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  }
780  switch (cargo_suffix[j].display) {
781  case CSD_CARGO_AMOUNT:
782  if (stockpiling) {
783  SetDParam(0, i->accepts_cargo[j]);
785  DrawString(left_side, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT);
786  break;
787  }
788  /* FALL THROUGH */
789 
790  case CSD_CARGO:
792  DrawString(left_side, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_ACCEPT_CARGO);
793  break;
794 
795  case CSD_CARGO_TEXT:
797  SetDParamStr(1, cargo_suffix[j].text);
798  DrawString(left_side, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_ACCEPT_CARGO_TEXT);
799  break;
800 
802  SetDParam(0, i->accepts_cargo[j]);
804  SetDParamStr(2, cargo_suffix[j].text);
805  DrawString(left_side, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_TEXT);
806  break;
807 
808  default:
809  NOT_REACHED();
810  }
811  y += FONT_HEIGHT_NORMAL;
812  }
813 
814  GetAllCargoSuffixes(3, CST_VIEW, i, i->type, ind, i->produced_cargo, cargo_suffix);
815  first = true;
816  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
817  if (i->produced_cargo[j] == CT_INVALID) continue;
818  if (first) {
819  if (has_accept) y += WD_PAR_VSEP_WIDE;
820  DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
821  y += FONT_HEIGHT_NORMAL;
822  if (this->editable == EA_RATE) this->production_offset_y = y;
823  first = false;
824  }
825 
826  SetDParam(0, i->produced_cargo[j]);
828  SetDParamStr(2, cargo_suffix[j].text);
830  uint x = left + WD_FRAMETEXT_LEFT + (this->editable == EA_RATE ? SETTING_BUTTON_WIDTH + 10 : 0);
831  DrawString(x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_TRANSPORTED);
832  /* Let's put out those buttons.. */
833  if (this->editable == EA_RATE) {
834  DrawArrowButtons(left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_RATE1 + j) ? this->clicked_button : 0,
835  i->production_rate[j] > 0, i->production_rate[j] < 255);
836  }
837  y += FONT_HEIGHT_NORMAL;
838  }
839 
840  /* Display production multiplier if editable */
841  if (this->editable == EA_MULTIPLIER) {
842  y += WD_PAR_VSEP_WIDE;
843  this->production_offset_y = y;
845  uint x = left + WD_FRAMETEXT_LEFT + SETTING_BUTTON_WIDTH + 10;
846  DrawString(x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LEVEL);
847  DrawArrowButtons(left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == IL_MULTIPLIER) ? this->clicked_button : 0,
849  y += FONT_HEIGHT_NORMAL;
850  }
851 
852  /* Get the extra message for the GUI */
854  uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->location.tile);
855  if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
856  if (callback_res > 0x400) {
858  } else {
859  StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res);
860  if (message != STR_NULL && message != STR_UNDEFINED) {
861  y += WD_PAR_VSEP_WIDE;
862 
864  /* Use all the available space left from where we stand up to the
865  * end of the window. We ALSO enlarge the window if needed, so we
866  * can 'go' wild with the bottom of the window. */
867  y = DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, UINT16_MAX, message, TC_BLACK);
869  }
870  }
871  }
872  }
873  return y + WD_FRAMERECT_BOTTOM;
874  }
875 
876  virtual void SetStringParameters(int widget) const
877  {
878  if (widget == WID_IV_CAPTION) SetDParam(0, this->window_number);
879  }
880 
881  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
882  {
883  if (widget == WID_IV_INFO) size->height = this->info_height;
884  }
885 
886  virtual void OnClick(Point pt, int widget, int click_count)
887  {
888  switch (widget) {
889  case WID_IV_INFO: {
891  InfoLine line = IL_NONE;
892 
893  switch (this->editable) {
894  case EA_NONE: break;
895 
896  case EA_MULTIPLIER:
897  if (IsInsideBS(pt.y, this->production_offset_y, FONT_HEIGHT_NORMAL)) line = IL_MULTIPLIER;
898  break;
899 
900  case EA_RATE:
901  if (pt.y >= this->production_offset_y) {
902  int row = (pt.y - this->production_offset_y) / FONT_HEIGHT_NORMAL;
903  for (uint j = 0; j < lengthof(i->produced_cargo); j++) {
904  if (i->produced_cargo[j] == CT_INVALID) continue;
905  row--;
906  if (row < 0) {
907  line = (InfoLine)(IL_RATE1 + j);
908  break;
909  }
910  }
911  }
912  break;
913  }
914  if (line == IL_NONE) return;
915 
916  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget);
917  int left = nwi->pos_x + WD_FRAMETEXT_LEFT;
918  int right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT;
919  if (IsInsideMM(pt.x, left, left + SETTING_BUTTON_WIDTH)) {
920  /* Clicked buttons, decrease or increase production */
921  byte button = (pt.x < left + SETTING_BUTTON_WIDTH / 2) ? 1 : 2;
922  switch (this->editable) {
923  case EA_MULTIPLIER:
924  if (button == 1) {
925  if (i->prod_level <= PRODLEVEL_MINIMUM) return;
926  i->prod_level = max<uint>(i->prod_level / 2, PRODLEVEL_MINIMUM);
927  } else {
928  if (i->prod_level >= PRODLEVEL_MAXIMUM) return;
930  }
931  break;
932 
933  case EA_RATE:
934  if (button == 1) {
935  if (i->production_rate[line - IL_RATE1] <= 0) return;
936  i->production_rate[line - IL_RATE1] = max(i->production_rate[line - IL_RATE1] / 2, 0);
937  } else {
938  if (i->production_rate[line - IL_RATE1] >= 255) return;
939  /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
940  int new_prod = i->production_rate[line - IL_RATE1] == 0 ? 1 : i->production_rate[line - IL_RATE1] * 2;
941  i->production_rate[line - IL_RATE1] = minu(new_prod, 255);
942  }
943  break;
944 
945  default: NOT_REACHED();
946  }
947 
948  UpdateIndustryProduction(i);
949  this->SetDirty();
950  this->SetTimeout();
951  this->clicked_line = line;
952  this->clicked_button = button;
953  } else if (IsInsideMM(pt.x, left + SETTING_BUTTON_WIDTH + 10, right)) {
954  /* clicked the text */
955  this->editbox_line = line;
956  switch (this->editable) {
957  case EA_MULTIPLIER:
959  ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION_LEVEL, 10, this, CS_ALPHANUMERAL, QSF_NONE);
960  break;
961 
962  case EA_RATE:
963  SetDParam(0, i->production_rate[line - IL_RATE1] * 8);
964  ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION, 10, this, CS_ALPHANUMERAL, QSF_NONE);
965  break;
966 
967  default: NOT_REACHED();
968  }
969  }
970  break;
971  }
972 
973  case WID_IV_GOTO: {
975  if (_ctrl_pressed) {
977  } else {
979  }
980  break;
981  }
982 
983  case WID_IV_DISPLAY: {
986  break;
987  }
988  }
989  }
990 
991  virtual void OnTimeout()
992  {
993  this->clicked_line = IL_NONE;
994  this->clicked_button = 0;
995  this->SetDirty();
996  }
997 
998  virtual void OnResize()
999  {
1000  if (this->viewport != NULL) {
1001  NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_IV_VIEWPORT);
1002  nvp->UpdateViewportCoordinates(this);
1003 
1004  ScrollWindowToTile(Industry::Get(this->window_number)->location.GetCenterTile(), this, true); // Re-center viewport.
1005  }
1006  }
1007 
1008  virtual void OnQueryTextFinished(char *str)
1009  {
1010  if (StrEmpty(str)) return;
1011 
1012  Industry *i = Industry::Get(this->window_number);
1013  uint value = atoi(str);
1014  switch (this->editbox_line) {
1015  case IL_NONE: NOT_REACHED();
1016 
1017  case IL_MULTIPLIER:
1019  break;
1020 
1021  default:
1022  i->production_rate[this->editbox_line - IL_RATE1] = ClampU(RoundDivSU(value, 8), 0, 255);
1023  break;
1024  }
1025  UpdateIndustryProduction(i);
1026  this->SetDirty();
1027  }
1028 
1034  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1035  {
1036  if (!gui_scope) return;
1037  const Industry *i = Industry::Get(this->window_number);
1038  if (IsProductionAlterable(i)) {
1039  const IndustrySpec *ind = GetIndustrySpec(i->type);
1040  this->editable = ind->UsesSmoothEconomy() ? EA_RATE : EA_MULTIPLIER;
1041  } else {
1042  this->editable = EA_NONE;
1043  }
1044  }
1045 
1046  virtual bool IsNewGRFInspectable() const
1047  {
1048  return ::IsNewGRFInspectable(GSF_INDUSTRIES, this->window_number);
1049  }
1050 
1051  virtual void ShowNewGRFInspectWindow() const
1052  {
1053  ::ShowNewGRFInspectWindow(GSF_INDUSTRIES, this->window_number);
1054  }
1055 };
1056 
1057 static void UpdateIndustryProduction(Industry *i)
1058 {
1059  const IndustrySpec *indspec = GetIndustrySpec(i->type);
1060  if (!indspec->UsesSmoothEconomy()) i->RecomputeProductionMultipliers();
1061 
1062  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1063  if (i->produced_cargo[j] != CT_INVALID) {
1064  i->last_month_production[j] = 8 * i->production_rate[j];
1065  }
1066  }
1067 }
1068 
1072  NWidget(WWT_CLOSEBOX, COLOUR_CREAM),
1073  NWidget(WWT_CAPTION, COLOUR_CREAM, WID_IV_CAPTION), SetDataTip(STR_INDUSTRY_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1074  NWidget(WWT_DEBUGBOX, COLOUR_CREAM),
1075  NWidget(WWT_SHADEBOX, COLOUR_CREAM),
1076  NWidget(WWT_DEFSIZEBOX, COLOUR_CREAM),
1077  NWidget(WWT_STICKYBOX, COLOUR_CREAM),
1078  EndContainer(),
1079  NWidget(WWT_PANEL, COLOUR_CREAM),
1080  NWidget(WWT_INSET, COLOUR_CREAM), SetPadding(2, 2, 2, 2),
1081  NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_IV_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetPadding(1, 1, 1, 1), SetResize(1, 1),
1082  EndContainer(),
1083  EndContainer(),
1084  NWidget(WWT_PANEL, COLOUR_CREAM, WID_IV_INFO), SetMinimalSize(260, 2), SetResize(1, 0),
1085  EndContainer(),
1087  NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GOTO), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_BUTTON_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
1088  NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
1089  NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
1090  EndContainer(),
1091 };
1092 
1095  WDP_AUTO, "view_industry", 260, 120,
1097  0,
1098  _nested_industry_view_widgets, lengthof(_nested_industry_view_widgets)
1099 );
1100 
1101 void ShowIndustryViewWindow(int industry)
1102 {
1103  AllocateWindowDescFront<IndustryViewWindow>(&_industry_view_desc, industry);
1104 }
1105 
1109  NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1110  NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_INDUSTRY_DIRECTORY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1111  NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1112  NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1113  NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1114  EndContainer(),
1118  NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_ID_DROPDOWN_ORDER), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1119  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_ID_DROPDOWN_CRITERIA), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
1120  NWidget(WWT_PANEL, COLOUR_BROWN), SetResize(1, 0), EndContainer(),
1121  EndContainer(),
1122  NWidget(WWT_PANEL, COLOUR_BROWN, WID_ID_INDUSTRY_LIST), SetDataTip(0x0, STR_INDUSTRY_DIRECTORY_LIST_CAPTION), SetResize(1, 1), SetScrollbar(WID_ID_SCROLLBAR), EndContainer(),
1123  EndContainer(),
1125  NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_ID_SCROLLBAR),
1126  NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1127  EndContainer(),
1128  EndContainer(),
1129 };
1130 
1132 
1133 
1138 protected:
1139  /* Runtime saved values */
1140  static Listing last_sorting;
1141  static const Industry *last_industry;
1142 
1143  /* Constants for sorting stations */
1144  static const StringID sorter_names[];
1145  static GUIIndustryList::SortFunction * const sorter_funcs[];
1146 
1147  GUIIndustryList industries;
1148  Scrollbar *vscroll;
1149 
1152  {
1153  if (this->industries.NeedRebuild()) {
1154  this->industries.Clear();
1155 
1156  const Industry *i;
1157  FOR_ALL_INDUSTRIES(i) {
1158  *this->industries.Append() = i;
1159  }
1160 
1161  this->industries.Compact();
1162  this->industries.RebuildDone();
1163  this->vscroll->SetCount(this->industries.Length()); // Update scrollbar as well.
1164  }
1165 
1166  if (!this->industries.Sort()) return;
1167  IndustryDirectoryWindow::last_industry = NULL; // Reset name sorter sort cache
1168  this->SetWidgetDirty(WID_ID_INDUSTRY_LIST); // Set the modified widget dirty
1169  }
1170 
1178  static inline int GetCargoTransportedPercentsIfValid(const Industry *i, uint id)
1179  {
1180  assert(id < lengthof(i->produced_cargo));
1181 
1182  if (i->produced_cargo[id] == CT_INVALID) return 101;
1183  return ToPercent8(i->last_month_pct_transported[id]);
1184  }
1185 
1194  {
1195  int p1 = GetCargoTransportedPercentsIfValid(i, 0);
1196  int p2 = GetCargoTransportedPercentsIfValid(i, 1);
1197 
1198  if (p1 > p2) Swap(p1, p2); // lower value has higher priority
1199 
1200  return (p1 << 8) + p2;
1201  }
1202 
1204  static int CDECL IndustryNameSorter(const Industry * const *a, const Industry * const *b)
1205  {
1206  static char buf_cache[96];
1207  static char buf[96];
1208 
1209  SetDParam(0, (*a)->index);
1210  GetString(buf, STR_INDUSTRY_NAME, lastof(buf));
1211 
1212  if (*b != last_industry) {
1213  last_industry = *b;
1214  SetDParam(0, (*b)->index);
1215  GetString(buf_cache, STR_INDUSTRY_NAME, lastof(buf_cache));
1216  }
1217 
1218  return strnatcmp(buf, buf_cache); // Sort by name (natural sorting).
1219  }
1220 
1222  static int CDECL IndustryTypeSorter(const Industry * const *a, const Industry * const *b)
1223  {
1224  int it_a = 0;
1225  while (it_a != NUM_INDUSTRYTYPES && (*a)->type != _sorted_industry_types[it_a]) it_a++;
1226  int it_b = 0;
1227  while (it_b != NUM_INDUSTRYTYPES && (*b)->type != _sorted_industry_types[it_b]) it_b++;
1228  int r = it_a - it_b;
1229  return (r == 0) ? IndustryNameSorter(a, b) : r;
1230  }
1231 
1233  static int CDECL IndustryProductionSorter(const Industry * const *a, const Industry * const *b)
1234  {
1235  uint prod_a = 0, prod_b = 0;
1236  for (uint i = 0; i < lengthof((*a)->produced_cargo); i++) {
1237  if ((*a)->produced_cargo[i] != CT_INVALID) prod_a += (*a)->last_month_production[i];
1238  if ((*b)->produced_cargo[i] != CT_INVALID) prod_b += (*b)->last_month_production[i];
1239  }
1240  int r = prod_a - prod_b;
1241 
1242  return (r == 0) ? IndustryTypeSorter(a, b) : r;
1243  }
1244 
1246  static int CDECL IndustryTransportedCargoSorter(const Industry * const *a, const Industry * const *b)
1247  {
1249  return (r == 0) ? IndustryNameSorter(a, b) : r;
1250  }
1251 
1258  {
1259  const IndustrySpec *indsp = GetIndustrySpec(i->type);
1260  byte p = 0;
1261 
1262  /* Industry name */
1263  SetDParam(p++, i->index);
1264 
1265  static CargoSuffix cargo_suffix[lengthof(i->produced_cargo)];
1266  GetAllCargoSuffixes(3, CST_DIR, i, i->type, indsp, i->produced_cargo, cargo_suffix);
1267 
1268  /* Industry productions */
1269  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1270  if (i->produced_cargo[j] == CT_INVALID) continue;
1271  SetDParam(p++, i->produced_cargo[j]);
1272  SetDParam(p++, i->last_month_production[j]);
1273  SetDParamStr(p++, cargo_suffix[j].text);
1274  }
1275 
1276  /* Transported productions */
1277  for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
1278  if (i->produced_cargo[j] == CT_INVALID) continue;
1280  }
1281 
1282  /* Drawing the right string */
1283  switch (p) {
1284  case 1: return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
1285  case 5: return STR_INDUSTRY_DIRECTORY_ITEM;
1286  default: return STR_INDUSTRY_DIRECTORY_ITEM_TWO;
1287  }
1288  }
1289 
1290 public:
1291  IndustryDirectoryWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
1292  {
1293  this->CreateNestedTree();
1294  this->vscroll = this->GetScrollbar(WID_ID_SCROLLBAR);
1295 
1296  this->industries.SetListing(this->last_sorting);
1297  this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
1298  this->industries.ForceRebuild();
1299  this->BuildSortIndustriesList();
1300 
1301  this->FinishInitNested(0);
1302  }
1303 
1305  {
1306  this->last_sorting = this->industries.GetListing();
1307  }
1308 
1309  virtual void SetStringParameters(int widget) const
1310  {
1311  if (widget == WID_ID_DROPDOWN_CRITERIA) SetDParam(0, IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
1312  }
1313 
1314  virtual void DrawWidget(const Rect &r, int widget) const
1315  {
1316  switch (widget) {
1317  case WID_ID_DROPDOWN_ORDER:
1318  this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1319  break;
1320 
1321  case WID_ID_INDUSTRY_LIST: {
1322  int n = 0;
1323  int y = r.top + WD_FRAMERECT_TOP;
1324  if (this->industries.Length() == 0) {
1325  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_DIRECTORY_NONE);
1326  break;
1327  }
1328  for (uint i = this->vscroll->GetPosition(); i < this->industries.Length(); i++) {
1329  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, this->GetIndustryString(this->industries[i]));
1330 
1331  y += this->resize.step_height;
1332  if (++n == this->vscroll->GetCapacity()) break; // max number of industries in 1 window
1333  }
1334  break;
1335  }
1336  }
1337  }
1338 
1339  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1340  {
1341  switch (widget) {
1342  case WID_ID_DROPDOWN_ORDER: {
1343  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1344  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1345  d.height += padding.height;
1346  *size = maxdim(*size, d);
1347  break;
1348  }
1349 
1350  case WID_ID_DROPDOWN_CRITERIA: {
1351  Dimension d = {0, 0};
1352  for (uint i = 0; IndustryDirectoryWindow::sorter_names[i] != INVALID_STRING_ID; i++) {
1353  d = maxdim(d, GetStringBoundingBox(IndustryDirectoryWindow::sorter_names[i]));
1354  }
1355  d.width += padding.width;
1356  d.height += padding.height;
1357  *size = maxdim(*size, d);
1358  break;
1359  }
1360 
1361  case WID_ID_INDUSTRY_LIST: {
1362  Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
1363  for (uint i = 0; i < this->industries.Length(); i++) {
1364  d = maxdim(d, GetStringBoundingBox(this->GetIndustryString(this->industries[i])));
1365  }
1366  resize->height = d.height;
1367  d.height *= 5;
1368  d.width += padding.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1369  d.height += padding.height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
1370  *size = maxdim(*size, d);
1371  break;
1372  }
1373  }
1374  }
1375 
1376 
1377  virtual void OnClick(Point pt, int widget, int click_count)
1378  {
1379  switch (widget) {
1380  case WID_ID_DROPDOWN_ORDER:
1381  this->industries.ToggleSortOrder();
1382  this->SetDirty();
1383  break;
1384 
1386  ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), WID_ID_DROPDOWN_CRITERIA, 0, 0);
1387  break;
1388 
1389  case WID_ID_INDUSTRY_LIST: {
1390  uint p = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_ID_INDUSTRY_LIST, WD_FRAMERECT_TOP);
1391  if (p < this->industries.Length()) {
1392  if (_ctrl_pressed) {
1393  ShowExtraViewPortWindow(this->industries[p]->location.tile);
1394  } else {
1395  ScrollMainWindowToTile(this->industries[p]->location.tile);
1396  }
1397  }
1398  break;
1399  }
1400  }
1401  }
1402 
1403  virtual void OnDropdownSelect(int widget, int index)
1404  {
1405  if (this->industries.SortType() != index) {
1406  this->industries.SetSortType(index);
1407  this->BuildSortIndustriesList();
1408  }
1409  }
1410 
1411  virtual void OnResize()
1412  {
1413  this->vscroll->SetCapacityFromWidget(this, WID_ID_INDUSTRY_LIST);
1414  }
1415 
1416  virtual void OnPaint()
1417  {
1418  if (this->industries.NeedRebuild()) this->BuildSortIndustriesList();
1419  this->DrawWidgets();
1420  }
1421 
1422  virtual void OnHundredthTick()
1423  {
1424  this->industries.ForceResort();
1425  this->BuildSortIndustriesList();
1426  }
1427 
1433  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1434  {
1435  if (data == 0) {
1436  /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1437  this->industries.ForceRebuild();
1438  } else {
1439  this->industries.ForceResort();
1440  }
1441  }
1442 };
1443 
1444 Listing IndustryDirectoryWindow::last_sorting = {false, 0};
1445 const Industry *IndustryDirectoryWindow::last_industry = NULL;
1446 
1447 /* Available station sorting functions. */
1448 GUIIndustryList::SortFunction * const IndustryDirectoryWindow::sorter_funcs[] = {
1449  &IndustryNameSorter,
1450  &IndustryTypeSorter,
1451  &IndustryProductionSorter,
1452  &IndustryTransportedCargoSorter
1453 };
1454 
1455 /* Names of the sorting functions */
1456 const StringID IndustryDirectoryWindow::sorter_names[] = {
1457  STR_SORT_BY_NAME,
1458  STR_SORT_BY_TYPE,
1459  STR_SORT_BY_PRODUCTION,
1460  STR_SORT_BY_TRANSPORTED,
1462 };
1463 
1464 
1467  WDP_AUTO, "list_industries", 428, 190,
1469  0,
1470  _nested_industry_directory_widgets, lengthof(_nested_industry_directory_widgets)
1471 );
1472 
1473 void ShowIndustryDirectory()
1474 {
1475  AllocateWindowDescFront<IndustryDirectoryWindow>(&_industry_directory_desc, 0);
1476 }
1477 
1481  NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1482  NWidget(WWT_CAPTION, COLOUR_BROWN, WID_IC_CAPTION), SetDataTip(STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1483  NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1484  NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1485  NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1486  EndContainer(),
1491  NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_IC_NOTIFY),
1492  SetDataTip(STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP, STR_INDUSTRY_CARGOES_NOTIFY_SMALLMAP_TOOLTIP),
1493  NWidget(WWT_PANEL, COLOUR_BROWN), SetFill(1, 0), SetResize(0, 0), EndContainer(),
1494  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_IND_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1495  SetDataTip(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY, STR_INDUSTRY_CARGOES_SELECT_INDUSTRY_TOOLTIP),
1496  NWidget(WWT_DROPDOWN, COLOUR_BROWN, WID_IC_CARGO_DROPDOWN), SetFill(0, 0), SetResize(0, 0),
1497  SetDataTip(STR_INDUSTRY_CARGOES_SELECT_CARGO, STR_INDUSTRY_CARGOES_SELECT_CARGO_TOOLTIP),
1498  EndContainer(),
1499  EndContainer(),
1501  NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_IC_SCROLLBAR),
1502  NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1503  EndContainer(),
1504  EndContainer(),
1505 };
1506 
1509  WDP_AUTO, "industry_cargoes", 300, 210,
1511  0,
1512  _nested_industry_cargoes_widgets, lengthof(_nested_industry_cargoes_widgets)
1513 );
1514 
1523 };
1524 
1525 static const uint MAX_CARGOES = 3;
1526 
1529  static const int VERT_INTER_INDUSTRY_SPACE;
1530  static const int HOR_CARGO_BORDER_SPACE;
1531  static const int CARGO_STUB_WIDTH;
1533  static const int CARGO_FIELD_WIDTH;
1536 
1537  static const int INDUSTRY_LINE_COLOUR;
1538  static const int CARGO_LINE_COLOUR;
1539 
1541  static int industry_width;
1542 
1544  union {
1545  struct {
1546  IndustryType ind_type;
1549  } industry;
1550  struct {
1554  byte top_end;
1556  byte bottom_end;
1557  } cargo;
1558  struct {
1560  bool left_align;
1561  } cargo_label;
1563  } u; // Data for each type.
1564 
1570  {
1571  this->type = type;
1572  }
1573 
1579  void MakeIndustry(IndustryType ind_type)
1580  {
1581  this->type = CFT_INDUSTRY;
1582  this->u.industry.ind_type = ind_type;
1583  MemSetT(this->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
1584  MemSetT(this->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
1585  }
1586 
1593  int ConnectCargo(CargoID cargo, bool producer)
1594  {
1595  assert(this->type == CFT_CARGO);
1596  if (cargo == INVALID_CARGO) return -1;
1597 
1598  /* Find the vertical cargo column carrying the cargo. */
1599  int column = -1;
1600  for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
1601  if (cargo == this->u.cargo.vertical_cargoes[i]) {
1602  column = i;
1603  break;
1604  }
1605  }
1606  if (column < 0) return -1;
1607 
1608  if (producer) {
1609  assert(this->u.cargo.supp_cargoes[column] == INVALID_CARGO);
1610  this->u.cargo.supp_cargoes[column] = column;
1611  } else {
1612  assert(this->u.cargo.cust_cargoes[column] == INVALID_CARGO);
1613  this->u.cargo.cust_cargoes[column] = column;
1614  }
1615  return column;
1616  }
1617 
1623  {
1624  assert(this->type == CFT_CARGO);
1625 
1626  for (uint i = 0; i < MAX_CARGOES; i++) {
1627  if (this->u.cargo.supp_cargoes[i] != INVALID_CARGO) return true;
1628  if (this->u.cargo.cust_cargoes[i] != INVALID_CARGO) return true;
1629  }
1630  return false;
1631  }
1632 
1642  void MakeCargo(const CargoID *cargoes, uint length, int count = -1, bool top_end = false, bool bottom_end = false)
1643  {
1644  this->type = CFT_CARGO;
1645  uint i;
1646  uint num = 0;
1647  for (i = 0; i < MAX_CARGOES && i < length; i++) {
1648  if (cargoes[i] != INVALID_CARGO) {
1649  this->u.cargo.vertical_cargoes[num] = cargoes[i];
1650  num++;
1651  }
1652  }
1653  this->u.cargo.num_cargoes = (count < 0) ? num : count;
1654  for (; num < MAX_CARGOES; num++) this->u.cargo.vertical_cargoes[num] = INVALID_CARGO;
1655  this->u.cargo.top_end = top_end;
1656  this->u.cargo.bottom_end = bottom_end;
1657  MemSetT(this->u.cargo.supp_cargoes, INVALID_CARGO, MAX_CARGOES);
1658  MemSetT(this->u.cargo.cust_cargoes, INVALID_CARGO, MAX_CARGOES);
1659  }
1660 
1667  void MakeCargoLabel(const CargoID *cargoes, uint length, bool left_align)
1668  {
1669  this->type = CFT_CARGO_LABEL;
1670  uint i;
1671  for (i = 0; i < MAX_CARGOES && i < length; i++) this->u.cargo_label.cargoes[i] = cargoes[i];
1672  for (; i < MAX_CARGOES; i++) this->u.cargo_label.cargoes[i] = INVALID_CARGO;
1673  this->u.cargo_label.left_align = left_align;
1674  }
1675 
1680  void MakeHeader(StringID textid)
1681  {
1682  this->type = CFT_HEADER;
1683  this->u.header = textid;
1684  }
1685 
1691  int GetCargoBase(int xpos) const
1692  {
1693  assert(this->type == CFT_CARGO);
1694 
1695  switch (this->u.cargo.num_cargoes) {
1696  case 0: return xpos + CARGO_FIELD_WIDTH / 2;
1697  case 1: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH / 2;
1698  case 2: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH - HOR_CARGO_SPACE / 2;
1699  case 3: return xpos + CARGO_FIELD_WIDTH / 2 - HOR_CARGO_WIDTH - HOR_CARGO_SPACE - HOR_CARGO_WIDTH / 2;
1700  default: NOT_REACHED();
1701  }
1702  }
1703 
1709  void Draw(int xpos, int ypos) const
1710  {
1711  switch (this->type) {
1712  case CFT_EMPTY:
1713  case CFT_SMALL_EMPTY:
1714  break;
1715 
1716  case CFT_HEADER:
1717  ypos += (small_height - FONT_HEIGHT_NORMAL) / 2;
1718  DrawString(xpos, xpos + industry_width, ypos, this->u.header, TC_WHITE, SA_HOR_CENTER);
1719  break;
1720 
1721  case CFT_INDUSTRY: {
1722  int ypos1 = ypos + VERT_INTER_INDUSTRY_SPACE / 2;
1723  int ypos2 = ypos + normal_height - 1 - VERT_INTER_INDUSTRY_SPACE / 2;
1724  int xpos2 = xpos + industry_width - 1;
1725  GfxDrawLine(xpos, ypos1, xpos2, ypos1, INDUSTRY_LINE_COLOUR);
1726  GfxDrawLine(xpos, ypos1, xpos, ypos2, INDUSTRY_LINE_COLOUR);
1727  GfxDrawLine(xpos, ypos2, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1728  GfxDrawLine(xpos2, ypos1, xpos2, ypos2, INDUSTRY_LINE_COLOUR);
1729  ypos += (normal_height - FONT_HEIGHT_NORMAL) / 2;
1730  if (this->u.industry.ind_type < NUM_INDUSTRYTYPES) {
1731  const IndustrySpec *indsp = GetIndustrySpec(this->u.industry.ind_type);
1732  DrawString(xpos, xpos2, ypos, indsp->name, TC_WHITE, SA_HOR_CENTER);
1733 
1734  /* Draw the industry legend. */
1735  int blob_left, blob_right;
1736  if (_current_text_dir == TD_RTL) {
1737  blob_right = xpos2 - BLOB_DISTANCE;
1738  blob_left = blob_right - BLOB_WIDTH;
1739  } else {
1740  blob_left = xpos + BLOB_DISTANCE;
1741  blob_right = blob_left + BLOB_WIDTH;
1742  }
1743  GfxFillRect(blob_left, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT, blob_right, ypos2 - BLOB_DISTANCE, PC_BLACK); // Border
1744  GfxFillRect(blob_left + 1, ypos2 - BLOB_DISTANCE - BLOB_HEIGHT + 1, blob_right - 1, ypos2 - BLOB_DISTANCE - 1, indsp->map_colour);
1745  } else {
1746  DrawString(xpos, xpos2, ypos, STR_INDUSTRY_CARGOES_HOUSES, TC_FROMSTRING, SA_HOR_CENTER);
1747  }
1748 
1749  /* Draw the other_produced/other_accepted cargoes. */
1750  const CargoID *other_right, *other_left;
1751  if (_current_text_dir == TD_RTL) {
1752  other_right = this->u.industry.other_accepted;
1753  other_left = this->u.industry.other_produced;
1754  } else {
1755  other_right = this->u.industry.other_produced;
1756  other_left = this->u.industry.other_accepted;
1757  }
1758  ypos1 += VERT_CARGO_EDGE;
1759  for (uint i = 0; i < MAX_CARGOES; i++) {
1760  if (other_right[i] != INVALID_CARGO) {
1761  const CargoSpec *csp = CargoSpec::Get(other_right[i]);
1762  int xp = xpos + industry_width + CARGO_STUB_WIDTH;
1763  DrawHorConnection(xpos + industry_width, xp - 1, ypos1, csp);
1764  GfxDrawLine(xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1765  }
1766  if (other_left[i] != INVALID_CARGO) {
1767  const CargoSpec *csp = CargoSpec::Get(other_left[i]);
1768  int xp = xpos - CARGO_STUB_WIDTH;
1769  DrawHorConnection(xp + 1, xpos - 1, ypos1, csp);
1770  GfxDrawLine(xp, ypos1, xp, ypos1 + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1771  }
1773  }
1774  break;
1775  }
1776 
1777  case CFT_CARGO: {
1778  int cargo_base = this->GetCargoBase(xpos);
1779  int top = ypos + (this->u.cargo.top_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0);
1780  int bot = ypos - (this->u.cargo.bottom_end ? VERT_INTER_INDUSTRY_SPACE / 2 + 1 : 0) + normal_height - 1;
1781  int colpos = cargo_base;
1782  for (int i = 0; i < this->u.cargo.num_cargoes; i++) {
1783  if (this->u.cargo.top_end) GfxDrawLine(colpos, top - 1, colpos + HOR_CARGO_WIDTH - 1, top - 1, CARGO_LINE_COLOUR);
1784  if (this->u.cargo.bottom_end) GfxDrawLine(colpos, bot + 1, colpos + HOR_CARGO_WIDTH - 1, bot + 1, CARGO_LINE_COLOUR);
1785  GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
1786  colpos++;
1787  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[i]);
1788  GfxFillRect(colpos, top, colpos + HOR_CARGO_WIDTH - 2, bot, csp->legend_colour, FILLRECT_OPAQUE);
1789  colpos += HOR_CARGO_WIDTH - 2;
1790  GfxDrawLine(colpos, top, colpos, bot, CARGO_LINE_COLOUR);
1791  colpos += 1 + HOR_CARGO_SPACE;
1792  }
1793 
1794  const CargoID *hor_left, *hor_right;
1795  if (_current_text_dir == TD_RTL) {
1796  hor_left = this->u.cargo.cust_cargoes;
1797  hor_right = this->u.cargo.supp_cargoes;
1798  } else {
1799  hor_left = this->u.cargo.supp_cargoes;
1800  hor_right = this->u.cargo.cust_cargoes;
1801  }
1803  for (uint i = 0; i < MAX_CARGOES; i++) {
1804  if (hor_left[i] != INVALID_CARGO) {
1805  int col = hor_left[i];
1806  int dx = 0;
1807  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
1808  for (; col > 0; col--) {
1809  int lf = cargo_base + col * HOR_CARGO_WIDTH + (col - 1) * HOR_CARGO_SPACE;
1810  DrawHorConnection(lf, lf + HOR_CARGO_SPACE - dx, ypos, csp);
1811  dx = 1;
1812  }
1813  DrawHorConnection(xpos, cargo_base - dx, ypos, csp);
1814  }
1815  if (hor_right[i] != INVALID_CARGO) {
1816  int col = hor_right[i];
1817  int dx = 0;
1818  const CargoSpec *csp = CargoSpec::Get(this->u.cargo.vertical_cargoes[col]);
1819  for (; col < this->u.cargo.num_cargoes - 1; col++) {
1820  int lf = cargo_base + (col + 1) * HOR_CARGO_WIDTH + col * HOR_CARGO_SPACE;
1821  DrawHorConnection(lf + dx - 1, lf + HOR_CARGO_SPACE - 1, ypos, csp);
1822  dx = 1;
1823  }
1824  DrawHorConnection(cargo_base + col * HOR_CARGO_SPACE + (col + 1) * HOR_CARGO_WIDTH - 1 + dx, xpos + CARGO_FIELD_WIDTH - 1, ypos, csp);
1825  }
1827  }
1828  break;
1829  }
1830 
1831  case CFT_CARGO_LABEL:
1833  for (uint i = 0; i < MAX_CARGOES; i++) {
1834  if (this->u.cargo_label.cargoes[i] != INVALID_CARGO) {
1835  const CargoSpec *csp = CargoSpec::Get(this->u.cargo_label.cargoes[i]);
1836  DrawString(xpos + WD_FRAMERECT_LEFT, xpos + industry_width - 1 - WD_FRAMERECT_RIGHT, ypos, csp->name, TC_WHITE,
1837  (this->u.cargo_label.left_align) ? SA_LEFT : SA_RIGHT);
1838  }
1840  }
1841  break;
1842 
1843  default:
1844  NOT_REACHED();
1845  }
1846  }
1847 
1855  CargoID CargoClickedAt(const CargoesField *left, const CargoesField *right, Point pt) const
1856  {
1857  assert(this->type == CFT_CARGO);
1858 
1859  /* Vertical matching. */
1860  int cpos = this->GetCargoBase(0);
1861  uint col;
1862  for (col = 0; col < this->u.cargo.num_cargoes; col++) {
1863  if (pt.x < cpos) break;
1864  if (pt.x < cpos + CargoesField::HOR_CARGO_WIDTH) return this->u.cargo.vertical_cargoes[col];
1866  }
1867  /* col = 0 -> left of first col, 1 -> left of 2nd col, ... this->u.cargo.num_cargoes right of last-col. */
1868 
1869  int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
1870  uint row;
1871  for (row = 0; row < MAX_CARGOES; row++) {
1872  if (pt.y < vpos) return INVALID_CARGO;
1873  if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
1875  }
1876  if (row == MAX_CARGOES) return INVALID_CARGO;
1877 
1878  /* row = 0 -> at first horizontal row, row = 1 -> second horizontal row, 2 = 3rd horizontal row. */
1879  if (col == 0) {
1880  if (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]];
1881  if (left != NULL) {
1882  if (left->type == CFT_INDUSTRY) return left->u.industry.other_produced[row];
1883  if (left->type == CFT_CARGO_LABEL && !left->u.cargo_label.left_align) return left->u.cargo_label.cargoes[row];
1884  }
1885  return INVALID_CARGO;
1886  }
1887  if (col == this->u.cargo.num_cargoes) {
1888  if (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) return this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]];
1889  if (right != NULL) {
1890  if (right->type == CFT_INDUSTRY) return right->u.industry.other_accepted[row];
1891  if (right->type == CFT_CARGO_LABEL && right->u.cargo_label.left_align) return right->u.cargo_label.cargoes[row];
1892  }
1893  return INVALID_CARGO;
1894  }
1895  if (row >= col) {
1896  /* Clicked somewhere in-between vertical cargo connection.
1897  * Since the horizontal connection is made in the same order as the vertical list, the above condition
1898  * ensures we are left-below the main diagonal, thus at the supplying side.
1899  */
1900  return (this->u.cargo.supp_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.supp_cargoes[row]] : INVALID_CARGO;
1901  } else {
1902  /* Clicked at a customer connection. */
1903  return (this->u.cargo.cust_cargoes[row] != INVALID_CARGO) ? this->u.cargo.vertical_cargoes[this->u.cargo.cust_cargoes[row]] : INVALID_CARGO;
1904  }
1905  }
1906 
1913  {
1914  assert(this->type == CFT_CARGO_LABEL);
1915 
1916  int vpos = VERT_INTER_INDUSTRY_SPACE / 2 + VERT_CARGO_EDGE;
1917  uint row;
1918  for (row = 0; row < MAX_CARGOES; row++) {
1919  if (pt.y < vpos) return INVALID_CARGO;
1920  if (pt.y < vpos + FONT_HEIGHT_NORMAL) break;
1922  }
1923  if (row == MAX_CARGOES) return INVALID_CARGO;
1924  return this->u.cargo_label.cargoes[row];
1925  }
1926 
1927 private:
1935  static void DrawHorConnection(int left, int right, int top, const CargoSpec *csp)
1936  {
1937  GfxDrawLine(left, top, right, top, CARGO_LINE_COLOUR);
1938  GfxFillRect(left, top + 1, right, top + FONT_HEIGHT_NORMAL - 2, csp->legend_colour, FILLRECT_OPAQUE);
1939  GfxDrawLine(left, top + FONT_HEIGHT_NORMAL - 1, right, top + FONT_HEIGHT_NORMAL - 1, CARGO_LINE_COLOUR);
1940  }
1941 };
1942 
1943 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, produced_cargo));
1944 assert_compile(MAX_CARGOES >= cpp_lengthof(IndustrySpec, accepts_cargo));
1945 
1950 
1951 const int CargoesField::HOR_CARGO_BORDER_SPACE = 15;
1952 const int CargoesField::CARGO_STUB_WIDTH = 10;
1953 const int CargoesField::HOR_CARGO_WIDTH = 15;
1954 const int CargoesField::HOR_CARGO_SPACE = 5;
1955 const int CargoesField::VERT_CARGO_EDGE = 4;
1956 const int CargoesField::VERT_CARGO_SPACE = 4;
1957 
1958 const int CargoesField::BLOB_DISTANCE = 5;
1959 const int CargoesField::BLOB_WIDTH = 12;
1960 const int CargoesField::BLOB_HEIGHT = 9;
1961 
1963 const int CargoesField::CARGO_FIELD_WIDTH = HOR_CARGO_BORDER_SPACE * 2 + HOR_CARGO_WIDTH * MAX_CARGOES + HOR_CARGO_SPACE * (MAX_CARGOES - 1);
1964 
1967 
1969 struct CargoesRow {
1971 
1976  void ConnectIndustryProduced(int column)
1977  {
1978  CargoesField *ind_fld = this->columns + column;
1979  CargoesField *cargo_fld = this->columns + column + 1;
1980  assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
1981 
1982  MemSetT(ind_fld->u.industry.other_produced, INVALID_CARGO, MAX_CARGOES);
1983 
1984  if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
1985  CargoID others[MAX_CARGOES]; // Produced cargoes not carried in the cargo column.
1986  int other_count = 0;
1987 
1988  const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
1989  for (uint i = 0; i < lengthof(indsp->produced_cargo); i++) {
1990  int col = cargo_fld->ConnectCargo(indsp->produced_cargo[i], true);
1991  if (col < 0) others[other_count++] = indsp->produced_cargo[i];
1992  }
1993 
1994  /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
1995  for (uint i = 0; i < MAX_CARGOES && other_count > 0; i++) {
1996  if (cargo_fld->u.cargo.supp_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_produced[i] = others[--other_count];
1997  }
1998  } else {
1999  /* Houses only display what is demanded. */
2000  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2001  CargoID cid = cargo_fld->u.cargo.vertical_cargoes[i];
2002  if (cid == CT_PASSENGERS || cid == CT_MAIL) cargo_fld->ConnectCargo(cid, true);
2003  }
2004  }
2005  }
2006 
2012  void MakeCargoLabel(int column, bool accepting)
2013  {
2014  CargoID cargoes[MAX_CARGOES];
2015  MemSetT(cargoes, INVALID_CARGO, lengthof(cargoes));
2016 
2017  CargoesField *label_fld = this->columns + column;
2018  CargoesField *cargo_fld = this->columns + (accepting ? column - 1 : column + 1);
2019 
2020  assert(cargo_fld->type == CFT_CARGO && label_fld->type == CFT_EMPTY);
2021  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2022  int col = cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], !accepting);
2023  if (col >= 0) cargoes[col] = cargo_fld->u.cargo.vertical_cargoes[i];
2024  }
2025  label_fld->MakeCargoLabel(cargoes, lengthof(cargoes), accepting);
2026  }
2027 
2028 
2033  void ConnectIndustryAccepted(int column)
2034  {
2035  CargoesField *ind_fld = this->columns + column;
2036  CargoesField *cargo_fld = this->columns + column - 1;
2037  assert(ind_fld->type == CFT_INDUSTRY && cargo_fld->type == CFT_CARGO);
2038 
2039  MemSetT(ind_fld->u.industry.other_accepted, INVALID_CARGO, MAX_CARGOES);
2040 
2041  if (ind_fld->u.industry.ind_type < NUM_INDUSTRYTYPES) {
2042  CargoID others[MAX_CARGOES]; // Accepted cargoes not carried in the cargo column.
2043  int other_count = 0;
2044 
2045  const IndustrySpec *indsp = GetIndustrySpec(ind_fld->u.industry.ind_type);
2046  for (uint i = 0; i < lengthof(indsp->accepts_cargo); i++) {
2047  int col = cargo_fld->ConnectCargo(indsp->accepts_cargo[i], false);
2048  if (col < 0) others[other_count++] = indsp->accepts_cargo[i];
2049  }
2050 
2051  /* Allocate other cargoes in the empty holes of the horizontal cargo connections. */
2052  for (uint i = 0; i < MAX_CARGOES && other_count > 0; i++) {
2053  if (cargo_fld->u.cargo.cust_cargoes[i] == INVALID_CARGO) ind_fld->u.industry.other_accepted[i] = others[--other_count];
2054  }
2055  } else {
2056  /* Houses only display what is demanded. */
2057  for (uint i = 0; i < cargo_fld->u.cargo.num_cargoes; i++) {
2058  for (uint h = 0; h < NUM_HOUSES; h++) {
2059  HouseSpec *hs = HouseSpec::Get(h);
2060  if (!hs->enabled) continue;
2061 
2062  for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2063  if (hs->cargo_acceptance[j] > 0 && cargo_fld->u.cargo.vertical_cargoes[i] == hs->accepts_cargo[j]) {
2064  cargo_fld->ConnectCargo(cargo_fld->u.cargo.vertical_cargoes[i], false);
2065  goto next_cargo;
2066  }
2067  }
2068  }
2069 next_cargo: ;
2070  }
2071  }
2072  }
2073 };
2074 
2075 
2105 
2107 
2109  uint ind_cargo;
2112  Scrollbar *vscroll;
2113 
2115  {
2116  this->OnInit();
2117  this->CreateNestedTree();
2118  this->vscroll = this->GetScrollbar(WID_IC_SCROLLBAR);
2119  this->FinishInitNested(0);
2120  this->OnInvalidateData(id);
2121  }
2122 
2123  virtual void OnInit()
2124  {
2125  /* Initialize static CargoesField size variables. */
2126  Dimension d = GetStringBoundingBox(STR_INDUSTRY_CARGOES_PRODUCERS);
2127  d = maxdim(d, GetStringBoundingBox(STR_INDUSTRY_CARGOES_CUSTOMERS));
2129  d.height += WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
2130  CargoesField::small_height = d.height;
2131 
2132  /* Decide about the size of the box holding the text of an industry type. */
2133  this->ind_textsize.width = 0;
2134  this->ind_textsize.height = 0;
2135  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2136  const IndustrySpec *indsp = GetIndustrySpec(it);
2137  if (!indsp->enabled) continue;
2138  this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(indsp->name));
2139  }
2140  d.width = max(d.width, this->ind_textsize.width);
2141  d.height = this->ind_textsize.height;
2142  this->ind_textsize = maxdim(this->ind_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_INDUSTRY));
2143 
2144  /* Compute max size of the cargo texts. */
2145  this->cargo_textsize.width = 0;
2146  this->cargo_textsize.height = 0;
2147  for (uint i = 0; i < NUM_CARGO; i++) {
2148  const CargoSpec *csp = CargoSpec::Get(i);
2149  if (!csp->IsValid()) continue;
2151  }
2152  d = maxdim(d, this->cargo_textsize); // Box must also be wide enough to hold any cargo label.
2153  this->cargo_textsize = maxdim(this->cargo_textsize, GetStringBoundingBox(STR_INDUSTRY_CARGOES_SELECT_CARGO));
2154 
2155  d.width += 2 * HOR_TEXT_PADDING;
2156  /* Ensure the height is enough for the industry type text, for the horizontal connections, and for the cargo labels. */
2157  uint min_ind_height = CargoesField::VERT_CARGO_EDGE * 2 + MAX_CARGOES * FONT_HEIGHT_NORMAL + (MAX_CARGOES - 1) * CargoesField::VERT_CARGO_SPACE;
2158  d.height = max(d.height + 2 * VERT_TEXT_PADDING, min_ind_height);
2159 
2160  CargoesField::industry_width = d.width;
2161  CargoesField::normal_height = d.height + CargoesField::VERT_INTER_INDUSTRY_SPACE;
2162  }
2163 
2164  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2165  {
2166  switch (widget) {
2167  case WID_IC_PANEL:
2168  size->width = WD_FRAMETEXT_LEFT + CargoesField::industry_width * 3 + CargoesField::CARGO_FIELD_WIDTH * 2 + WD_FRAMETEXT_RIGHT;
2169  break;
2170 
2171  case WID_IC_IND_DROPDOWN:
2172  size->width = max(size->width, this->ind_textsize.width + padding.width);
2173  break;
2174 
2175  case WID_IC_CARGO_DROPDOWN:
2176  size->width = max(size->width, this->cargo_textsize.width + padding.width);
2177  break;
2178  }
2179  }
2180 
2181 
2183  virtual void SetStringParameters (int widget) const
2184  {
2185  if (widget != WID_IC_CAPTION) return;
2186 
2187  if (this->ind_cargo < NUM_INDUSTRYTYPES) {
2188  const IndustrySpec *indsp = GetIndustrySpec(this->ind_cargo);
2189  SetDParam(0, indsp->name);
2190  } else {
2191  const CargoSpec *csp = CargoSpec::Get(this->ind_cargo - NUM_INDUSTRYTYPES);
2192  SetDParam(0, csp->name);
2193  }
2194  }
2195 
2204  static bool HasCommonValidCargo(const CargoID *cargoes1, uint length1, const CargoID *cargoes2, uint length2)
2205  {
2206  while (length1 > 0) {
2207  if (*cargoes1 != INVALID_CARGO) {
2208  for (uint i = 0; i < length2; i++) if (*cargoes1 == cargoes2[i]) return true;
2209  }
2210  cargoes1++;
2211  length1--;
2212  }
2213  return false;
2214  }
2215 
2222  static bool HousesCanSupply(const CargoID *cargoes, uint length)
2223  {
2224  for (uint i = 0; i < length; i++) {
2225  if (cargoes[i] == INVALID_CARGO) continue;
2226  if (cargoes[i] == CT_PASSENGERS || cargoes[i] == CT_MAIL) return true;
2227  }
2228  return false;
2229  }
2230 
2237  static bool HousesCanAccept(const CargoID *cargoes, uint length)
2238  {
2239  HouseZones climate_mask;
2241  case LT_TEMPERATE: climate_mask = HZ_TEMP; break;
2242  case LT_ARCTIC: climate_mask = HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW; break;
2243  case LT_TROPIC: climate_mask = HZ_SUBTROPIC; break;
2244  case LT_TOYLAND: climate_mask = HZ_TOYLND; break;
2245  default: NOT_REACHED();
2246  }
2247  for (uint i = 0; i < length; i++) {
2248  if (cargoes[i] == INVALID_CARGO) continue;
2249 
2250  for (uint h = 0; h < NUM_HOUSES; h++) {
2251  HouseSpec *hs = HouseSpec::Get(h);
2252  if (!hs->enabled || !(hs->building_availability & climate_mask)) continue;
2253 
2254  for (uint j = 0; j < lengthof(hs->accepts_cargo); j++) {
2255  if (hs->cargo_acceptance[j] > 0 && cargoes[i] == hs->accepts_cargo[j]) return true;
2256  }
2257  }
2258  }
2259  return false;
2260  }
2261 
2268  static int CountMatchingAcceptingIndustries(const CargoID *cargoes, uint length)
2269  {
2270  int count = 0;
2271  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2272  const IndustrySpec *indsp = GetIndustrySpec(it);
2273  if (!indsp->enabled) continue;
2274 
2275  if (HasCommonValidCargo(cargoes, length, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) count++;
2276  }
2277  return count;
2278  }
2279 
2286  static int CountMatchingProducingIndustries(const CargoID *cargoes, uint length)
2287  {
2288  int count = 0;
2289  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2290  const IndustrySpec *indsp = GetIndustrySpec(it);
2291  if (!indsp->enabled) continue;
2292 
2293  if (HasCommonValidCargo(cargoes, length, indsp->produced_cargo, lengthof(indsp->produced_cargo))) count++;
2294  }
2295  return count;
2296  }
2297 
2304  void ShortenCargoColumn(int column, int top, int bottom)
2305  {
2306  while (top < bottom && !this->fields[top].columns[column].HasConnection()) {
2307  this->fields[top].columns[column].MakeEmpty(CFT_EMPTY);
2308  top++;
2309  }
2310  this->fields[top].columns[column].u.cargo.top_end = true;
2311 
2312  while (bottom > top && !this->fields[bottom].columns[column].HasConnection()) {
2313  this->fields[bottom].columns[column].MakeEmpty(CFT_EMPTY);
2314  bottom--;
2315  }
2316  this->fields[bottom].columns[column].u.cargo.bottom_end = true;
2317  }
2318 
2325  void PlaceIndustry(int row, int col, IndustryType it)
2326  {
2327  assert(this->fields[row].columns[col].type == CFT_EMPTY);
2328  this->fields[row].columns[col].MakeIndustry(it);
2329  if (col == 0) {
2330  this->fields[row].ConnectIndustryProduced(col);
2331  } else {
2332  this->fields[row].ConnectIndustryAccepted(col);
2333  }
2334  }
2335 
2340  {
2341  if (!this->IsWidgetLowered(WID_IC_NOTIFY)) return;
2342 
2343  /* Only notify the smallmap window if it exists. In particular, do not
2344  * bring it to the front to prevent messing up any nice layout of the user. */
2346  }
2347 
2352  void ComputeIndustryDisplay(IndustryType it)
2353  {
2354  this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION;
2355  this->ind_cargo = it;
2356  _displayed_industries.reset();
2357  _displayed_industries.set(it);
2358 
2359  this->fields.Clear();
2360  CargoesRow *row = this->fields.Append();
2361  row->columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2365  row->columns[4].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2366 
2367  const IndustrySpec *central_sp = GetIndustrySpec(it);
2368  bool houses_supply = HousesCanSupply(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2369  bool houses_accept = HousesCanAccept(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2370  /* Make a field consisting of two cargo columns. */
2371  int num_supp = CountMatchingProducingIndustries(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo)) + houses_supply;
2372  int num_cust = CountMatchingAcceptingIndustries(central_sp->produced_cargo, lengthof(central_sp->produced_cargo)) + houses_accept;
2373  int num_indrows = max(3, max(num_supp, num_cust)); // One is needed for the 'it' industry, and 2 for the cargo labels.
2374  for (int i = 0; i < num_indrows; i++) {
2375  CargoesRow *row = this->fields.Append();
2376  row->columns[0].MakeEmpty(CFT_EMPTY);
2377  row->columns[1].MakeCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo));
2378  row->columns[2].MakeEmpty(CFT_EMPTY);
2379  row->columns[3].MakeCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo));
2380  row->columns[4].MakeEmpty(CFT_EMPTY);
2381  }
2382  /* Add central industry. */
2383  int central_row = 1 + num_indrows / 2;
2384  this->fields[central_row].columns[2].MakeIndustry(it);
2385  this->fields[central_row].ConnectIndustryProduced(2);
2386  this->fields[central_row].ConnectIndustryAccepted(2);
2387 
2388  /* Add cargo labels. */
2389  this->fields[central_row - 1].MakeCargoLabel(2, true);
2390  this->fields[central_row + 1].MakeCargoLabel(2, false);
2391 
2392  /* Add suppliers and customers of the 'it' industry. */
2393  int supp_count = 0;
2394  int cust_count = 0;
2395  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2396  const IndustrySpec *indsp = GetIndustrySpec(it);
2397  if (!indsp->enabled) continue;
2398 
2399  if (HasCommonValidCargo(central_sp->accepts_cargo, lengthof(central_sp->accepts_cargo), indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2400  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2401  _displayed_industries.set(it);
2402  supp_count++;
2403  }
2404  if (HasCommonValidCargo(central_sp->produced_cargo, lengthof(central_sp->produced_cargo), indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2405  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, it);
2406  _displayed_industries.set(it);
2407  cust_count++;
2408  }
2409  }
2410  if (houses_supply) {
2411  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2412  supp_count++;
2413  }
2414  if (houses_accept) {
2415  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 4, NUM_INDUSTRYTYPES);
2416  cust_count++;
2417  }
2418 
2419  this->ShortenCargoColumn(1, 1, num_indrows);
2420  this->ShortenCargoColumn(3, 1, num_indrows);
2421  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2422  this->vscroll->SetCount(CeilDiv(WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + CargoesField::small_height + num_indrows * CargoesField::normal_height, nwp->resize_y));
2423  this->SetDirty();
2424  this->NotifySmallmap();
2425  }
2426 
2432  {
2433  this->GetWidget<NWidgetCore>(WID_IC_CAPTION)->widget_data = STR_INDUSTRY_CARGOES_CARGO_CAPTION;
2434  this->ind_cargo = cid + NUM_INDUSTRYTYPES;
2435  _displayed_industries.reset();
2436 
2437  this->fields.Clear();
2438  CargoesRow *row = this->fields.Append();
2439  row->columns[0].MakeHeader(STR_INDUSTRY_CARGOES_PRODUCERS);
2441  row->columns[2].MakeHeader(STR_INDUSTRY_CARGOES_CUSTOMERS);
2444 
2445  bool houses_supply = HousesCanSupply(&cid, 1);
2446  bool houses_accept = HousesCanAccept(&cid, 1);
2447  int num_supp = CountMatchingProducingIndustries(&cid, 1) + houses_supply + 1; // Ensure room for the cargo label.
2448  int num_cust = CountMatchingAcceptingIndustries(&cid, 1) + houses_accept;
2449  int num_indrows = max(num_supp, num_cust);
2450  for (int i = 0; i < num_indrows; i++) {
2451  CargoesRow *row = this->fields.Append();
2452  row->columns[0].MakeEmpty(CFT_EMPTY);
2453  row->columns[1].MakeCargo(&cid, 1);
2454  row->columns[2].MakeEmpty(CFT_EMPTY);
2455  row->columns[3].MakeEmpty(CFT_EMPTY);
2456  row->columns[4].MakeEmpty(CFT_EMPTY);
2457  }
2458 
2459  this->fields[num_indrows].MakeCargoLabel(0, false); // Add cargo labels at the left bottom.
2460 
2461  /* Add suppliers and customers of the cargo. */
2462  int supp_count = 0;
2463  int cust_count = 0;
2464  for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
2465  const IndustrySpec *indsp = GetIndustrySpec(it);
2466  if (!indsp->enabled) continue;
2467 
2468  if (HasCommonValidCargo(&cid, 1, indsp->produced_cargo, lengthof(indsp->produced_cargo))) {
2469  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, it);
2470  _displayed_industries.set(it);
2471  supp_count++;
2472  }
2473  if (HasCommonValidCargo(&cid, 1, indsp->accepts_cargo, lengthof(indsp->accepts_cargo))) {
2474  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, it);
2475  _displayed_industries.set(it);
2476  cust_count++;
2477  }
2478  }
2479  if (houses_supply) {
2480  this->PlaceIndustry(1 + supp_count * num_indrows / num_supp, 0, NUM_INDUSTRYTYPES);
2481  supp_count++;
2482  }
2483  if (houses_accept) {
2484  this->PlaceIndustry(1 + cust_count * num_indrows / num_cust, 2, NUM_INDUSTRYTYPES);
2485  cust_count++;
2486  }
2487 
2488  this->ShortenCargoColumn(1, 1, num_indrows);
2489  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2490  this->vscroll->SetCount(CeilDiv(WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + CargoesField::small_height + num_indrows * CargoesField::normal_height, nwp->resize_y));
2491  this->SetDirty();
2492  this->NotifySmallmap();
2493  }
2494 
2502  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2503  {
2504  if (!gui_scope) return;
2505  if (data == NUM_INDUSTRYTYPES) {
2506  if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2507  this->RaiseWidget(WID_IC_NOTIFY);
2509  }
2510  return;
2511  }
2512 
2513  assert(data >= 0 && data < NUM_INDUSTRYTYPES);
2514  this->ComputeIndustryDisplay(data);
2515  }
2516 
2517  virtual void DrawWidget(const Rect &r, int widget) const
2518  {
2519  if (widget != WID_IC_PANEL) return;
2520 
2521  DrawPixelInfo tmp_dpi, *old_dpi;
2522  int width = r.right - r.left + 1;
2523  int height = r.bottom - r.top + 1 - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM;
2524  if (!FillDrawPixelInfo(&tmp_dpi, r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP, width, height)) return;
2525  old_dpi = _cur_dpi;
2526  _cur_dpi = &tmp_dpi;
2527 
2528  int left_pos = WD_FRAMERECT_LEFT;
2529  if (this->ind_cargo >= NUM_INDUSTRYTYPES) left_pos += (CargoesField::industry_width + CargoesField::CARGO_FIELD_WIDTH) / 2;
2530  int last_column = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2531 
2532  const NWidgetBase *nwp = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2533  int vpos = -this->vscroll->GetPosition() * nwp->resize_y;
2534  for (uint i = 0; i < this->fields.Length(); i++) {
2535  int row_height = (i == 0) ? CargoesField::small_height : CargoesField::normal_height;
2536  if (vpos + row_height >= 0) {
2537  int xpos = left_pos;
2538  int col, dir;
2539  if (_current_text_dir == TD_RTL) {
2540  col = last_column;
2541  dir = -1;
2542  } else {
2543  col = 0;
2544  dir = 1;
2545  }
2546  while (col >= 0 && col <= last_column) {
2547  this->fields[i].columns[col].Draw(xpos, vpos);
2548  xpos += (col & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width;
2549  col += dir;
2550  }
2551  }
2552  vpos += row_height;
2553  if (vpos >= height) break;
2554  }
2555 
2556  _cur_dpi = old_dpi;
2557  }
2558 
2566  bool CalculatePositionInWidget(Point pt, Point *fieldxy, Point *xy)
2567  {
2568  const NWidgetBase *nw = this->GetWidget<NWidgetBase>(WID_IC_PANEL);
2569  pt.x -= nw->pos_x;
2570  pt.y -= nw->pos_y;
2571 
2572  int vpos = WD_FRAMERECT_TOP + CargoesField::small_height - this->vscroll->GetPosition() * nw->resize_y;
2573  if (pt.y < vpos) return false;
2574 
2575  int row = (pt.y - vpos) / CargoesField::normal_height; // row is relative to row 1.
2576  if (row + 1 >= (int)this->fields.Length()) return false;
2577  vpos = pt.y - vpos - row * CargoesField::normal_height; // Position in the row + 1 field
2578  row++; // rebase row to match index of this->fields.
2579 
2580  int xpos = 2 * WD_FRAMERECT_LEFT + ((this->ind_cargo < NUM_INDUSTRYTYPES) ? 0 : (CargoesField::industry_width + CargoesField::CARGO_FIELD_WIDTH) / 2);
2581  if (pt.x < xpos) return false;
2582  int column;
2583  for (column = 0; column <= 5; column++) {
2584  int width = (column & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width;
2585  if (pt.x < xpos + width) break;
2586  xpos += width;
2587  }
2588  int num_columns = (this->ind_cargo < NUM_INDUSTRYTYPES) ? 4 : 2;
2589  if (column > num_columns) return false;
2590  xpos = pt.x - xpos;
2591 
2592  /* Return both positions, compensating for RTL languages (which works due to the equal symmetry in both displays). */
2593  fieldxy->y = row;
2594  xy->y = vpos;
2595  if (_current_text_dir == TD_RTL) {
2596  fieldxy->x = num_columns - column;
2597  xy->x = ((column & 1) ? CargoesField::CARGO_FIELD_WIDTH : CargoesField::industry_width) - xpos;
2598  } else {
2599  fieldxy->x = column;
2600  xy->x = xpos;
2601  }
2602  return true;
2603  }
2604 
2605  virtual void OnClick(Point pt, int widget, int click_count)
2606  {
2607  switch (widget) {
2608  case WID_IC_PANEL: {
2609  Point fieldxy, xy;
2610  if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
2611 
2612  const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2613  switch (fld->type) {
2614  case CFT_INDUSTRY:
2615  if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES) this->ComputeIndustryDisplay(fld->u.industry.ind_type);
2616  break;
2617 
2618  case CFT_CARGO: {
2619  CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : NULL;
2620  CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : NULL;
2621  CargoID cid = fld->CargoClickedAt(lft, rgt, xy);
2622  if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2623  break;
2624  }
2625 
2626  case CFT_CARGO_LABEL: {
2627  CargoID cid = fld->CargoLabelClickedAt(xy);
2628  if (cid != INVALID_CARGO) this->ComputeCargoDisplay(cid);
2629  break;
2630  }
2631 
2632  default:
2633  break;
2634  }
2635  break;
2636  }
2637 
2638  case WID_IC_NOTIFY:
2641  if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
2642 
2643  if (this->IsWidgetLowered(WID_IC_NOTIFY)) {
2644  if (FindWindowByClass(WC_SMALLMAP) == NULL) ShowSmallMap();
2645  this->NotifySmallmap();
2646  }
2647  break;
2648 
2649  case WID_IC_CARGO_DROPDOWN: {
2650  DropDownList *lst = new DropDownList;
2651  const CargoSpec *cs;
2653  *lst->Append() = new DropDownListStringItem(cs->name, cs->Index(), false);
2654  }
2655  if (lst->Length() == 0) {
2656  delete lst;
2657  break;
2658  }
2659  int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
2660  ShowDropDownList(this, lst, selected, WID_IC_CARGO_DROPDOWN, 0, true);
2661  break;
2662  }
2663 
2664  case WID_IC_IND_DROPDOWN: {
2665  DropDownList *lst = new DropDownList;
2666  for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
2667  IndustryType ind = _sorted_industry_types[i];
2668  const IndustrySpec *indsp = GetIndustrySpec(ind);
2669  if (!indsp->enabled) continue;
2670  *lst->Append() = new DropDownListStringItem(indsp->name, ind, false);
2671  }
2672  if (lst->Length() == 0) {
2673  delete lst;
2674  break;
2675  }
2676  int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
2677  ShowDropDownList(this, lst, selected, WID_IC_IND_DROPDOWN, 0, true);
2678  break;
2679  }
2680  }
2681  }
2682 
2683  virtual void OnDropdownSelect(int widget, int index)
2684  {
2685  if (index < 0) return;
2686 
2687  switch (widget) {
2688  case WID_IC_CARGO_DROPDOWN:
2689  this->ComputeCargoDisplay(index);
2690  break;
2691 
2692  case WID_IC_IND_DROPDOWN:
2693  this->ComputeIndustryDisplay(index);
2694  break;
2695  }
2696  }
2697 
2698  virtual void OnHover(Point pt, int widget)
2699  {
2700  if (widget != WID_IC_PANEL) return;
2701 
2702  Point fieldxy, xy;
2703  if (!CalculatePositionInWidget(pt, &fieldxy, &xy)) return;
2704 
2705  const CargoesField *fld = this->fields[fieldxy.y].columns + fieldxy.x;
2706  CargoID cid = INVALID_CARGO;
2707  switch (fld->type) {
2708  case CFT_CARGO: {
2709  CargoesField *lft = (fieldxy.x > 0) ? this->fields[fieldxy.y].columns + fieldxy.x - 1 : NULL;
2710  CargoesField *rgt = (fieldxy.x < 4) ? this->fields[fieldxy.y].columns + fieldxy.x + 1 : NULL;
2711  cid = fld->CargoClickedAt(lft, rgt, xy);
2712  break;
2713  }
2714 
2715  case CFT_CARGO_LABEL: {
2716  cid = fld->CargoLabelClickedAt(xy);
2717  break;
2718  }
2719 
2720  case CFT_INDUSTRY:
2721  if (fld->u.industry.ind_type < NUM_INDUSTRYTYPES && (this->ind_cargo >= NUM_INDUSTRYTYPES || fieldxy.x != 2)) {
2722  GuiShowTooltips(this, STR_INDUSTRY_CARGOES_INDUSTRY_TOOLTIP, 0, NULL, TCC_HOVER);
2723  }
2724  return;
2725 
2726  default:
2727  break;
2728  }
2729  if (cid != INVALID_CARGO && (this->ind_cargo < NUM_INDUSTRYTYPES || cid != this->ind_cargo - NUM_INDUSTRYTYPES)) {
2730  const CargoSpec *csp = CargoSpec::Get(cid);
2731  uint64 params[5];
2732  params[0] = csp->name;
2733  GuiShowTooltips(this, STR_INDUSTRY_CARGOES_CARGO_TOOLTIP, 1, params, TCC_HOVER);
2734  }
2735  }
2736 
2737  virtual void OnResize()
2738  {
2739  this->vscroll->SetCapacityFromWidget(this, WID_IC_PANEL);
2740  }
2741 };
2742 
2745 
2750 static void ShowIndustryCargoesWindow(IndustryType id)
2751 {
2752  if (id >= NUM_INDUSTRYTYPES) {
2753  for (uint i = 0; i < NUM_INDUSTRYTYPES; i++) {
2755  if (indsp->enabled) {
2756  id = _sorted_industry_types[i];
2757  break;
2758  }
2759  }
2760  if (id >= NUM_INDUSTRYTYPES) return;
2761  }
2762 
2764  if (w != NULL) {
2765  w->InvalidateData(id);
2766  return;
2767  }
2768  new IndustryCargoesWindow(id);
2769 }
2770 
2773 {
2775 }