OpenTTD
group_gui.cpp
Go to the documentation of this file.
1 /* $Id: group_gui.cpp 27630 2016-08-15 18:33: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 "textbuf_gui.h"
14 #include "command_func.h"
15 #include "vehicle_gui.h"
16 #include "vehicle_base.h"
17 #include "string_func.h"
18 #include "strings_func.h"
19 #include "window_func.h"
20 #include "vehicle_func.h"
21 #include "autoreplace_gui.h"
22 #include "company_func.h"
23 #include "widgets/dropdown_func.h"
24 #include "tilehighlight_func.h"
25 #include "vehicle_gui_base.h"
26 #include "core/geometry_func.hpp"
27 #include "company_base.h"
28 
29 #include "widgets/group_widget.h"
30 
31 #include "table/sprites.h"
32 
33 #include "safeguards.h"
34 
35 static const int LEVEL_WIDTH = 10;
36 
38 
39 static const NWidgetPart _nested_group_widgets[] = {
40  NWidget(NWID_HORIZONTAL), // Window header
41  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
42  NWidget(WWT_CAPTION, COLOUR_GREY, WID_GL_CAPTION),
43  NWidget(WWT_SHADEBOX, COLOUR_GREY),
44  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
45  NWidget(WWT_STICKYBOX, COLOUR_GREY),
46  EndContainer(),
48  /* left part */
51  NWidget(WWT_PANEL, COLOUR_GREY, WID_GL_ALL_VEHICLES), SetFill(1, 0), EndContainer(),
54  NWidget(WWT_MATRIX, COLOUR_GREY, WID_GL_LIST_GROUP), SetMatrixDataTip(1, 0, STR_GROUPS_CLICK_ON_GROUP_FOR_TOOLTIP),
57  EndContainer(),
59  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_CREATE_GROUP), SetFill(0, 1),
60  SetDataTip(SPR_GROUP_CREATE_TRAIN, STR_GROUP_CREATE_TOOLTIP),
61  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_DELETE_GROUP), SetFill(0, 1),
62  SetDataTip(SPR_GROUP_DELETE_TRAIN, STR_GROUP_DELETE_TOOLTIP),
63  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_RENAME_GROUP), SetFill(0, 1),
64  SetDataTip(SPR_GROUP_RENAME_TRAIN, STR_GROUP_RENAME_TOOLTIP),
65  NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(),
67  SetDataTip(SPR_GROUP_REPLACE_OFF_TRAIN, STR_GROUP_REPLACE_PROTECTION_TOOLTIP),
68  EndContainer(),
69  EndContainer(),
70  /* right part */
73  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_SORT_BY_ORDER), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
74  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_SORT_BY_DROPDOWN), SetMinimalSize(167, 12), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA),
75  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetResize(1, 0), EndContainer(),
76  EndContainer(),
80  EndContainer(),
81  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(1, 0), SetFill(1, 1), SetResize(1, 0), EndContainer(),
84  SetDataTip(STR_BLACK_STRING, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
85  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 1), SetResize(1, 0), EndContainer(),
87  SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
88  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
89  SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
90  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
91  SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
92  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
93  EndContainer(),
94  EndContainer(),
95  EndContainer(),
96 };
97 
99 private:
100  /* Columns in the group list */
101  enum ListColumns {
107 
108  VGC_END
109  };
110 
118  Scrollbar *group_sb;
119 
121 
123 
124  void AddParents(GUIGroupList *source, GroupID parent, int indent)
125  {
126  for (const Group **g = source->Begin(); g != source->End(); g++) {
127  if ((*g)->parent == parent) {
128  *this->groups.Append() = *g;
129  *this->indents.Append() = indent;
130  AddParents(source, (*g)->index, indent + 1);
131  }
132  }
133  }
134 
136  static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b)
137  {
138  static const Group *last_group[2] = { NULL, NULL };
139  static char last_name[2][64] = { "", "" };
140 
141  if (*a != last_group[0]) {
142  last_group[0] = *a;
143  SetDParam(0, (*a)->index);
144  GetString(last_name[0], STR_GROUP_NAME, lastof(last_name[0]));
145  }
146 
147  if (*b != last_group[1]) {
148  last_group[1] = *b;
149  SetDParam(0, (*b)->index);
150  GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1]));
151  }
152 
153  int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
154  if (r == 0) return (*a)->index - (*b)->index;
155  return r;
156  }
157 
164  {
165  if (!this->groups.NeedRebuild()) return;
166 
167  this->groups.Clear();
168  this->indents.Clear();
169 
170  GUIGroupList list;
171 
172  const Group *g;
173  FOR_ALL_GROUPS(g) {
174  if (g->owner == owner && g->vehicle_type == this->vli.vtype) {
175  *list.Append() = g;
176  }
177  }
178 
179  list.ForceResort();
180  list.Sort(&GroupNameSorter);
181 
182  AddParents(&list, INVALID_GROUP, 0);
183 
184  this->groups.Compact();
185  this->groups.RebuildDone();
186  }
187 
193  {
194  this->column_size[VGC_NAME] = maxdim(GetStringBoundingBox(STR_GROUP_DEFAULT_TRAINS + this->vli.vtype), GetStringBoundingBox(STR_GROUP_ALL_TRAINS + this->vli.vtype));
195  this->column_size[VGC_NAME].width = max(170u, this->column_size[VGC_NAME].width);
196  this->tiny_step_height = this->column_size[VGC_NAME].height;
197 
198  this->column_size[VGC_PROTECT] = GetSpriteSize(SPR_GROUP_REPLACE_PROTECT);
200 
201  this->column_size[VGC_AUTOREPLACE] = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
202  this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_AUTOREPLACE].height);
203 
204  this->column_size[VGC_PROFIT].width = 0;
205  this->column_size[VGC_PROFIT].height = 0;
206  static const SpriteID profit_sprites[] = {SPR_PROFIT_NA, SPR_PROFIT_NEGATIVE, SPR_PROFIT_SOME, SPR_PROFIT_LOT};
207  for (uint i = 0; i < lengthof(profit_sprites); i++) {
208  Dimension d = GetSpriteSize(profit_sprites[i]);
209  this->column_size[VGC_PROFIT] = maxdim(this->column_size[VGC_PROFIT], d);
210  }
211  this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROFIT].height);
212 
214  this->column_size[VGC_NUMBER] = GetStringBoundingBox(STR_TINY_COMMA);
215  this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_NUMBER].height);
216 
218 
219  return WD_FRAMERECT_LEFT + 8 +
220  this->column_size[VGC_NAME].width + 8 +
221  this->column_size[VGC_PROTECT].width + 2 +
222  this->column_size[VGC_AUTOREPLACE].width + 2 +
223  this->column_size[VGC_PROFIT].width + 2 +
224  this->column_size[VGC_NUMBER].width + 2 +
226  }
227 
237  void DrawGroupInfo(int y, int left, int right, GroupID g_id, int indent = 0, bool protection = false) const
238  {
239  /* Highlight the group if a vehicle is dragged over it */
240  if (g_id == this->group_over) {
242  }
243 
244  if (g_id == NEW_GROUP) return;
245 
246  /* draw the selected group in white, else we draw it in black */
247  TextColour colour = g_id == this->vli.index ? TC_WHITE : TC_BLACK;
248  const GroupStatistics &stats = GroupStatistics::Get(this->vli.company, g_id, this->vli.vtype);
249  bool rtl = _current_text_dir == TD_RTL;
250 
251  /* draw group name */
252  StringID str;
253  if (IsAllGroupID(g_id)) {
254  str = STR_GROUP_ALL_TRAINS + this->vli.vtype;
255  } else if (IsDefaultGroupID(g_id)) {
256  str = STR_GROUP_DEFAULT_TRAINS + this->vli.vtype;
257  } else {
258  SetDParam(0, g_id);
259  str = STR_GROUP_NAME;
260  }
261  int x = rtl ? right - WD_FRAMERECT_RIGHT - 8 - this->column_size[VGC_NAME].width + 1 : left + WD_FRAMERECT_LEFT + 8;
262  DrawString(x + indent * LEVEL_WIDTH, x + this->column_size[VGC_NAME].width - 1, y + (this->tiny_step_height - this->column_size[VGC_NAME].height) / 2, str, colour);
263 
264  /* draw autoreplace protection */
265  x = rtl ? x - 8 - this->column_size[VGC_PROTECT].width : x + 8 + this->column_size[VGC_NAME].width;
266  if (protection) DrawSprite(SPR_GROUP_REPLACE_PROTECT, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROTECT].height) / 2);
267 
268  /* draw autoreplace status */
269  x = rtl ? x - 2 - this->column_size[VGC_AUTOREPLACE].width : x + 2 + this->column_size[VGC_PROTECT].width;
270  if (stats.autoreplace_defined) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, stats.autoreplace_finished ? PALETTE_CRASH : PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_AUTOREPLACE].height) / 2);
271 
272  /* draw the profit icon */
273  x = rtl ? x - 2 - this->column_size[VGC_PROFIT].width : x + 2 + this->column_size[VGC_AUTOREPLACE].width;
274  SpriteID spr;
275  if (stats.num_profit_vehicle == 0) {
276  spr = SPR_PROFIT_NA;
277  } else if (stats.profit_last_year < 0) {
278  spr = SPR_PROFIT_NEGATIVE;
279  } else if (stats.profit_last_year < 10000 * stats.num_profit_vehicle) { // TODO magic number
280  spr = SPR_PROFIT_SOME;
281  } else {
282  spr = SPR_PROFIT_LOT;
283  }
284  DrawSprite(spr, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROFIT].height) / 2);
285 
286  /* draw the number of vehicles of the group */
287  x = rtl ? x - 2 - this->column_size[VGC_NUMBER].width : x + 2 + this->column_size[VGC_PROFIT].width;
288  SetDParam(0, stats.num_vehicle);
289  DrawString(x, x + this->column_size[VGC_NUMBER].width - 1, y + (this->tiny_step_height - this->column_size[VGC_NUMBER].height) / 2, STR_TINY_COMMA, colour, SA_RIGHT | SA_FORCE);
290  }
291 
296  {
297  if (this->group_over == INVALID_GROUP) return;
298 
299  if (IsAllGroupID(this->group_over)) {
301  } else if (IsDefaultGroupID(this->group_over)) {
303  } else {
305  }
306  }
307 
308 public:
310  {
311  this->CreateNestedTree();
312 
313  this->vscroll = this->GetScrollbar(WID_GL_LIST_VEHICLE_SCROLLBAR);
314  this->group_sb = this->GetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR);
315 
316  switch (this->vli.vtype) {
317  default: NOT_REACHED();
318  case VEH_TRAIN: this->sorting = &_sorting.train; break;
319  case VEH_ROAD: this->sorting = &_sorting.roadveh; break;
320  case VEH_SHIP: this->sorting = &_sorting.ship; break;
321  case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
322  }
323 
324  this->vli.index = ALL_GROUP;
326  this->group_sel = INVALID_GROUP;
327  this->group_rename = INVALID_GROUP;
328  this->group_over = INVALID_GROUP;
329 
330  this->vehicles.SetListing(*this->sorting);
331  this->vehicles.ForceRebuild();
332  this->vehicles.NeedResort();
333 
334  this->BuildVehicleList();
335  this->SortVehicleList();
336 
337  this->groups.ForceRebuild();
338  this->groups.NeedResort();
339  this->BuildGroupList(vli.company);
340 
341  this->GetWidget<NWidgetCore>(WID_GL_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vli.vtype;
342  this->GetWidget<NWidgetCore>(WID_GL_LIST_VEHICLE)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vli.vtype;
343 
344  this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data += this->vli.vtype;
345  this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data += this->vli.vtype;
346  this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data += this->vli.vtype;
347  this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data += this->vli.vtype;
348 
349  this->FinishInitNested(window_number);
350  this->owner = vli.company;
351  }
352 
354  {
355  *this->sorting = this->vehicles.GetListing();
356  }
357 
358  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
359  {
360  switch (widget) {
361  case WID_GL_LIST_GROUP: {
362  size->width = this->ComputeGroupInfoSize();
363  resize->height = this->tiny_step_height;
364 
365  /* Minimum height is the height of the list widget minus all and default vehicles... */
366  size->height = 4 * GetVehicleListHeight(this->vli.vtype, this->tiny_step_height) - 2 * this->tiny_step_height;
367 
368  /* ... minus the buttons at the bottom ... */
369  uint max_icon_height = GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data).height;
370  max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data).height);
371  max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data).height);
372  max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data).height);
373 
374  /* Get a multiple of tiny_step_height of that amount */
375  size->height = Ceil(size->height - max_icon_height, tiny_step_height);
376  break;
377  }
378 
379  case WID_GL_ALL_VEHICLES:
381  size->width = this->ComputeGroupInfoSize();
382  size->height = this->tiny_step_height;
383  break;
384 
385  case WID_GL_SORT_BY_ORDER: {
386  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
387  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
388  d.height += padding.height;
389  *size = maxdim(*size, d);
390  break;
391  }
392 
393  case WID_GL_LIST_VEHICLE:
394  this->ComputeGroupInfoSize();
395  resize->height = GetVehicleListHeight(this->vli.vtype, this->tiny_step_height);
396  size->height = 4 * resize->height;
397  break;
398 
400  Dimension d = this->GetActionDropdownSize(true, true);
401  d.height += padding.height;
402  d.width += padding.width;
403  *size = maxdim(*size, d);
404  break;
405  }
406  }
407  }
408 
414  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
415  {
416  if (data == 0) {
417  /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
418  this->vehicles.ForceRebuild();
419  this->groups.ForceRebuild();
420  } else {
421  this->vehicles.ForceResort();
422  this->groups.ForceResort();
423  }
424 
425  /* Process ID-invalidation in command-scope as well */
426  if (this->group_rename != INVALID_GROUP && !Group::IsValidID(this->group_rename)) {
428  this->group_rename = INVALID_GROUP;
429  }
430 
431  if (!(IsAllGroupID(this->vli.index) || IsDefaultGroupID(this->vli.index) || Group::IsValidID(this->vli.index))) {
432  this->vli.index = ALL_GROUP;
433  HideDropDownMenu(this);
434  }
435  this->SetDirty();
436  }
437 
438  virtual void SetStringParameters(int widget) const
439  {
440  switch (widget) {
442  SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype);
443  break;
444 
445  case WID_GL_CAPTION:
446  /* If selected_group == DEFAULT_GROUP || ALL_GROUP, draw the standard caption
447  * We list all vehicles or ungrouped vehicles */
448  if (IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index)) {
449  SetDParam(0, STR_COMPANY_NAME);
450  SetDParam(1, this->vli.company);
451  SetDParam(2, this->vehicles.Length());
452  SetDParam(3, this->vehicles.Length());
453  } else {
454  const Group *g = Group::Get(this->vli.index);
455 
456  SetDParam(0, STR_GROUP_NAME);
457  SetDParam(1, g->index);
460  }
461  break;
462  }
463  }
464 
465  virtual void OnPaint()
466  {
467  /* If we select the all vehicles, this->list will contain all vehicles of the owner
468  * else this->list will contain all vehicles which belong to the selected group */
469  this->BuildVehicleList();
470  this->SortVehicleList();
471 
472  this->BuildGroupList(this->owner);
473 
474  this->group_sb->SetCount(this->groups.Length());
475  this->vscroll->SetCount(this->vehicles.Length());
476 
477  /* The drop down menu is out, *but* it may not be used, retract it. */
478  if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_GL_MANAGE_VEHICLES_DROPDOWN)) {
480  HideDropDownMenu(this);
481  }
482 
483  /* Disable all lists management button when the list is empty */
484  this->SetWidgetsDisabledState(this->vehicles.Length() == 0 || _local_company != this->vli.company,
489 
490  /* Disable the group specific function when we select the default group or all vehicles */
491  this->SetWidgetsDisabledState(IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index) || _local_company != this->vli.company,
496 
497  /* Disable remaining buttons for non-local companies
498  * Needed while changing _local_company, eg. by cheats
499  * All procedures (eg. move vehicle to another group)
500  * verify, whether you are the owner of the vehicle,
501  * so it doesn't have to be disabled
502  */
507 
508  /* If not a default group and the group has replace protection, show an enabled replace sprite. */
509  uint16 protect_sprite = SPR_GROUP_REPLACE_OFF_TRAIN;
510  if (!IsDefaultGroupID(this->vli.index) && !IsAllGroupID(this->vli.index) && Group::Get(this->vli.index)->replace_protection) protect_sprite = SPR_GROUP_REPLACE_ON_TRAIN;
511  this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data = protect_sprite + this->vli.vtype;
512 
513  /* Set text of sort by dropdown */
514  this->GetWidget<NWidgetCore>(WID_GL_SORT_BY_DROPDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
515 
516  this->DrawWidgets();
517  }
518 
519  virtual void DrawWidget(const Rect &r, int widget) const
520  {
521  switch (widget) {
522  case WID_GL_ALL_VEHICLES:
523  DrawGroupInfo(r.top + WD_FRAMERECT_TOP, r.left, r.right, ALL_GROUP);
524  break;
525 
527  DrawGroupInfo(r.top + WD_FRAMERECT_TOP, r.left, r.right, DEFAULT_GROUP);
528  break;
529 
530  case WID_GL_LIST_GROUP: {
531  int y1 = r.top + WD_FRAMERECT_TOP;
532  int max = min(this->group_sb->GetPosition() + this->group_sb->GetCapacity(), this->groups.Length());
533  for (int i = this->group_sb->GetPosition(); i < max; ++i) {
534  const Group *g = this->groups[i];
535 
536  assert(g->owner == this->owner);
537 
538  DrawGroupInfo(y1, r.left, r.right, g->index, this->indents[i], g->replace_protection);
539 
540  y1 += this->tiny_step_height;
541  }
542  if ((uint)this->group_sb->GetPosition() + this->group_sb->GetCapacity() > this->groups.Length()) {
543  DrawGroupInfo(y1, r.left, r.right, NEW_GROUP);
544  }
545  break;
546  }
547 
550  break;
551 
552  case WID_GL_LIST_VEHICLE:
553  if (this->vli.index != ALL_GROUP) {
554  /* Mark vehicles which are in sub-groups */
555  int y = r.top;
556  uint max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehicles.Length());
557  for (uint i = this->vscroll->GetPosition(); i < max; ++i) {
558  const Vehicle *v = this->vehicles[i];
559  if (v->group_id != this->vli.index) {
560  GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->resize.step_height - 2, _colour_gradient[COLOUR_GREY][3], FILLRECT_CHECKER);
561  }
562  y += this->resize.step_height;
563  }
564  }
565 
566  this->DrawVehicleListItems(this->vehicle_sel, this->resize.step_height, r);
567  break;
568  }
569  }
570 
571  static void DeleteGroupCallback(Window *win, bool confirmed)
572  {
573  if (confirmed) {
575  w->vli.index = ALL_GROUP;
576  DoCommandP(0, w->group_confirm, 0, CMD_DELETE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_DELETE));
577  }
578  }
579 
580  virtual void OnClick(Point pt, int widget, int click_count)
581  {
582  switch (widget) {
583  case WID_GL_SORT_BY_ORDER: // Flip sorting method ascending/descending
584  this->vehicles.ToggleSortOrder();
585  this->SetDirty();
586  break;
587 
588  case WID_GL_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu
589  ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), WID_GL_SORT_BY_DROPDOWN, 0, (this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : (1 << 10));
590  return;
591 
592  case WID_GL_ALL_VEHICLES: // All vehicles button
593  if (!IsAllGroupID(this->vli.index)) {
594  this->vli.index = ALL_GROUP;
595  this->vehicles.ForceRebuild();
596  this->SetDirty();
597  }
598  break;
599 
600  case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles button
601  if (!IsDefaultGroupID(this->vli.index)) {
602  this->vli.index = DEFAULT_GROUP;
603  this->vehicles.ForceRebuild();
604  this->SetDirty();
605  }
606  break;
607 
608  case WID_GL_LIST_GROUP: { // Matrix Group
609  uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
610  if (id_g >= this->groups.Length()) return;
611 
612  this->group_sel = this->vli.index = this->groups[id_g]->index;
613 
614  SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this);
615 
616  this->vehicles.ForceRebuild();
617  this->SetDirty();
618  break;
619  }
620 
621  case WID_GL_LIST_VEHICLE: { // Matrix Vehicle
622  uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
623  if (id_v >= this->vehicles.Length()) return; // click out of list bound
624 
625  const Vehicle *v = this->vehicles[id_v];
626  if (VehicleClicked(v)) break;
627 
628  this->vehicle_sel = v->index;
629 
630  SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this);
632  _cursor.vehchain = true;
633 
634  this->SetDirty();
635  break;
636  }
637 
638  case WID_GL_CREATE_GROUP: { // Create a new group
639  DoCommandP(0, this->vli.vtype, 0, CMD_CREATE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_CREATE), CcCreateGroup);
640  break;
641  }
642 
643  case WID_GL_DELETE_GROUP: { // Delete the selected group
644  this->group_confirm = this->vli.index;
645  ShowQuery(STR_QUERY_GROUP_DELETE_CAPTION, STR_GROUP_DELETE_QUERY_TEXT, this, DeleteGroupCallback);
646  break;
647  }
648 
649  case WID_GL_RENAME_GROUP: // Rename the selected roup
650  this->ShowRenameGroupWindow(this->vli.index, false);
651  break;
652 
654  ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
655  break;
656 
658  DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index));
660  break;
661  }
662 
663  case WID_GL_START_ALL:
664  case WID_GL_STOP_ALL: { // Start/stop all vehicles of the list
665  DoCommandP(0, (1 << 1) | (widget == WID_GL_START_ALL ? (1 << 0) : 0), this->vli.Pack(), CMD_MASS_START_STOP);
666  break;
667  }
668 
670  const Group *g = Group::GetIfValid(this->vli.index);
671  if (g != NULL) {
673  }
674  break;
675  }
676  }
677  }
678 
679  void OnDragDrop_Group(Point pt, int widget)
680  {
681  const Group *g = Group::Get(this->group_sel);
682 
683  switch (widget) {
684  case WID_GL_ALL_VEHICLES: // All vehicles
685  case WID_GL_DEFAULT_VEHICLES: // Ungroupd vehicles
686  if (g->parent != INVALID_GROUP) {
687  DoCommandP(0, this->group_sel | (1 << 16), INVALID_GROUP, CMD_ALTER_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_SET_PARENT));
688  }
689 
690  this->group_sel = INVALID_GROUP;
691  this->group_over = INVALID_GROUP;
692  this->SetDirty();
693  break;
694 
695  case WID_GL_LIST_GROUP: { // Matrix group
696  uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
697  GroupID new_g = id_g >= this->groups.Length() ? INVALID_GROUP : this->groups[id_g]->index;
698 
699  if (this->group_sel != new_g && g->parent != new_g) {
700  DoCommandP(0, this->group_sel | (1 << 16), new_g, CMD_ALTER_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_SET_PARENT));
701  }
702 
703  this->group_sel = INVALID_GROUP;
704  this->group_over = INVALID_GROUP;
705  this->SetDirty();
706  break;
707  }
708  }
709  }
710 
711  void OnDragDrop_Vehicle(Point pt, int widget)
712  {
713  switch (widget) {
714  case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles
715  DoCommandP(0, DEFAULT_GROUP, this->vehicle_sel | (_ctrl_pressed ? 1 << 31 : 0), CMD_ADD_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_VEHICLE));
716 
718  this->group_over = INVALID_GROUP;
719 
720  this->SetDirty();
721  break;
722 
723  case WID_GL_LIST_GROUP: { // Matrix group
724  const VehicleID vindex = this->vehicle_sel;
726  this->group_over = INVALID_GROUP;
727  this->SetDirty();
728 
729  uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
730  GroupID new_g = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
731 
732  DoCommandP(0, new_g, vindex | (_ctrl_pressed ? 1 << 31 : 0), CMD_ADD_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_VEHICLE), new_g == NEW_GROUP ? CcAddVehicleNewGroup : NULL);
733  break;
734  }
735 
736  case WID_GL_LIST_VEHICLE: { // Matrix vehicle
737  const VehicleID vindex = this->vehicle_sel;
739  this->group_over = INVALID_GROUP;
740  this->SetDirty();
741 
742  uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
743  if (id_v >= this->vehicles.Length()) return; // click out of list bound
744 
745  const Vehicle *v = this->vehicles[id_v];
746  if (!VehicleClicked(v) && vindex == v->index) {
748  }
749  break;
750  }
751  }
752  }
753 
754  virtual void OnDragDrop(Point pt, int widget)
755  {
756  if (this->vehicle_sel != INVALID_VEHICLE) OnDragDrop_Vehicle(pt, widget);
757  if (this->group_sel != INVALID_GROUP) OnDragDrop_Group(pt, widget);
758 
759  _cursor.vehchain = false;
760  }
761 
762  virtual void OnQueryTextFinished(char *str)
763  {
764  if (str != NULL) DoCommandP(0, this->group_rename, 0, CMD_ALTER_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_RENAME), NULL, str);
765  this->group_rename = INVALID_GROUP;
766  }
767 
768  virtual void OnResize()
769  {
770  this->group_sb->SetCapacityFromWidget(this, WID_GL_LIST_GROUP);
771  this->vscroll->SetCapacityFromWidget(this, WID_GL_LIST_VEHICLE);
772  }
773 
774  virtual void OnDropdownSelect(int widget, int index)
775  {
776  switch (widget) {
778  this->vehicles.SetSortType(index);
779  break;
780 
782  assert(this->vehicles.Length() != 0);
783 
784  switch (index) {
785  case ADI_REPLACE: // Replace window
786  ShowReplaceGroupVehicleWindow(this->vli.index, this->vli.vtype);
787  break;
788  case ADI_SERVICE: // Send for servicing
789  case ADI_DEPOT: { // Send to Depots
790  DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : 0U), this->vli.Pack(), GetCmdSendToDepot(this->vli.vtype));
791  break;
792  }
793 
794  case ADI_ADD_SHARED: // Add shared Vehicles
795  assert(Group::IsValidID(this->vli.index));
796 
797  DoCommandP(0, this->vli.index, this->vli.vtype, CMD_ADD_SHARED_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_SHARED_VEHICLE));
798  break;
799  case ADI_REMOVE_ALL: // Remove all Vehicles from the selected group
800  assert(Group::IsValidID(this->vli.index));
801 
802  DoCommandP(0, this->vli.index, 0, CMD_REMOVE_ALL_VEHICLES_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_REMOVE_ALL_VEHICLES));
803  break;
804  default: NOT_REACHED();
805  }
806  break;
807 
808  default: NOT_REACHED();
809  }
810 
811  this->SetDirty();
812  }
813 
814  virtual void OnTick()
815  {
816  if (_pause_mode != PM_UNPAUSED) return;
817  if (this->groups.NeedResort() || this->vehicles.NeedResort()) {
818  this->SetDirty();
819  }
820  }
821 
822  virtual void OnPlaceObjectAbort()
823  {
824  /* abort drag & drop */
827  this->group_over = INVALID_GROUP;
829  }
830 
831  virtual void OnMouseDrag(Point pt, int widget)
832  {
833  if (this->vehicle_sel == INVALID_VEHICLE && this->group_sel == INVALID_GROUP) return;
834 
835  /* A vehicle is dragged over... */
836  GroupID new_group_over = INVALID_GROUP;
837  switch (widget) {
838  case WID_GL_DEFAULT_VEHICLES: // ... the 'default' group.
839  new_group_over = DEFAULT_GROUP;
840  break;
841 
842  case WID_GL_LIST_GROUP: { // ... the list of custom groups.
843  uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
844  new_group_over = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
845  break;
846  }
847  }
848 
849  /* Do not highlight when dragging over the current group */
850  if (this->vehicle_sel != INVALID_VEHICLE) {
851  if (Vehicle::Get(vehicle_sel)->group_id == new_group_over) new_group_over = INVALID_GROUP;
852  } else if (this->group_sel != INVALID_GROUP) {
853  if (this->group_sel == new_group_over || Group::Get(this->group_sel)->parent == new_group_over) new_group_over = INVALID_GROUP;
854  }
855 
856  /* Mark widgets as dirty if the group changed. */
857  if (new_group_over != this->group_over) {
859  this->group_over = new_group_over;
861  }
862  }
863 
864  void ShowRenameGroupWindow(GroupID group, bool empty)
865  {
866  assert(Group::IsValidID(group));
867  this->group_rename = group;
868  /* Show empty query for new groups */
869  StringID str = STR_EMPTY;
870  if (!empty) {
871  SetDParam(0, group);
872  str = STR_GROUP_NAME;
873  }
875  }
876 
883  {
884  if (this->vehicle_sel == vehicle) ResetObjectToPlace();
885  }
886 };
887 
888 
889 static WindowDesc _other_group_desc(
890  WDP_AUTO, "list_groups", 460, 246,
892  0,
893  _nested_group_widgets, lengthof(_nested_group_widgets)
894 );
895 
896 static WindowDesc _train_group_desc(
897  WDP_AUTO, "list_groups_train", 525, 246,
899  0,
900  _nested_group_widgets, lengthof(_nested_group_widgets)
901 );
902 
908 void ShowCompanyGroup(CompanyID company, VehicleType vehicle_type)
909 {
910  if (!Company::IsValidID(company)) return;
911 
912  WindowNumber num = VehicleListIdentifier(VL_GROUP_LIST, vehicle_type, company).Pack();
913  if (vehicle_type == VEH_TRAIN) {
914  AllocateWindowDescFront<VehicleGroupWindow>(&_train_group_desc, num);
915  } else {
916  _other_group_desc.cls = GetWindowClassForVehicleType(vehicle_type);
917  AllocateWindowDescFront<VehicleGroupWindow>(&_other_group_desc, num);
918  }
919 }
920 
928 {
929  return (VehicleGroupWindow *)FindWindowById(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, owner).Pack());
930 }
931 
940 void CcCreateGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
941 {
942  if (result.Failed()) return;
943  assert(p1 <= VEH_AIRCRAFT);
944 
946  if (w != NULL) w->ShowRenameGroupWindow(_new_group_id, true);
947 }
948 
956 void CcAddVehicleNewGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
957 {
958  if (result.Failed()) return;
959  assert(Vehicle::IsValidID(GB(p2, 0, 20)));
960 
961  CcCreateGroup(result, 0, Vehicle::Get(GB(p2, 0, 20))->type, 0);
962 }
963 
969 {
970  /* If we haven't got any vehicles on the mouse pointer, we haven't got any highlighted in any group windows either
971  * If that is the case, we can skip looping though the windows and save time
972  */
973  if (_special_mouse_mode != WSM_DRAGDROP) return;
974 
976  if (w != NULL) w->UnselectVehicle(v->index);
977 }