Translations update
[openttd/fttd.git] / src / group_gui.cpp
blobb96786068c4c6a4b71d06b89f0c1aa8d23b4a1b6
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file group_gui.cpp GUI for the group window. */
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.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"
29 #include "widgets/group_widget.h"
31 #include "table/sprites.h"
33 static const int LEVEL_WIDTH = 10; ///< Indenting width of a sub-group in pixels
35 typedef GUIList<const Group*> GUIGroupList;
37 static const NWidgetPart _nested_group_widgets[] = {
38 NWidget(NWID_HORIZONTAL), // Window header
39 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
40 NWidget(WWT_CAPTION, COLOUR_GREY, WID_GL_CAPTION),
41 NWidget(WWT_SHADEBOX, COLOUR_GREY),
42 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
43 NWidget(WWT_STICKYBOX, COLOUR_GREY),
44 EndContainer(),
45 NWidget(NWID_HORIZONTAL),
46 /* left part */
47 NWidget(NWID_VERTICAL),
48 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetFill(1, 0), EndContainer(),
49 NWidget(WWT_PANEL, COLOUR_GREY, WID_GL_ALL_VEHICLES), SetFill(1, 0), EndContainer(),
50 NWidget(WWT_PANEL, COLOUR_GREY, WID_GL_DEFAULT_VEHICLES), SetFill(1, 0), EndContainer(),
51 NWidget(NWID_HORIZONTAL),
52 NWidget(WWT_MATRIX, COLOUR_GREY, WID_GL_LIST_GROUP), SetMatrixDataTip(1, 0, STR_GROUPS_CLICK_ON_GROUP_FOR_TOOLTIP),
53 SetFill(1, 0), SetResize(0, 1), SetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR),
54 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_GL_LIST_GROUP_SCROLLBAR),
55 EndContainer(),
56 NWidget(NWID_HORIZONTAL),
57 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_CREATE_GROUP), SetFill(0, 1),
58 SetDataTip(SPR_GROUP_CREATE_TRAIN, STR_GROUP_CREATE_TOOLTIP),
59 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_DELETE_GROUP), SetFill(0, 1),
60 SetDataTip(SPR_GROUP_DELETE_TRAIN, STR_GROUP_DELETE_TOOLTIP),
61 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_RENAME_GROUP), SetFill(0, 1),
62 SetDataTip(SPR_GROUP_RENAME_TRAIN, STR_GROUP_RENAME_TOOLTIP),
63 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(),
64 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_REPLACE_PROTECTION), SetFill(0, 1),
65 SetDataTip(SPR_GROUP_REPLACE_OFF_TRAIN, STR_GROUP_REPLACE_PROTECTION_TOOLTIP),
66 EndContainer(),
67 EndContainer(),
68 /* right part */
69 NWidget(NWID_VERTICAL),
70 NWidget(NWID_HORIZONTAL),
71 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_SORT_BY_ORDER), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
72 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_SORT_BY_DROPDOWN), SetMinimalSize(167, 12), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA),
73 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetResize(1, 0), EndContainer(),
74 EndContainer(),
75 NWidget(NWID_HORIZONTAL),
76 NWidget(WWT_MATRIX, COLOUR_GREY, WID_GL_LIST_VEHICLE), SetMinimalSize(248, 0), SetMatrixDataTip(1, 0, STR_NULL), SetResize(1, 1), SetFill(1, 0), SetScrollbar(WID_GL_LIST_VEHICLE_SCROLLBAR),
77 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_GL_LIST_VEHICLE_SCROLLBAR),
78 EndContainer(),
79 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(1, 0), SetFill(1, 1), SetResize(1, 0), EndContainer(),
80 NWidget(NWID_HORIZONTAL),
81 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
82 SetDataTip(STR_BLACK_STRING, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
83 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 1), SetResize(1, 0), EndContainer(),
84 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(0, 1),
85 SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
86 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
87 SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
88 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
89 SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
90 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
91 EndContainer(),
92 EndContainer(),
93 EndContainer(),
96 class VehicleGroupWindow : public BaseVehicleListWindow {
97 private:
98 /* Columns in the group list */
99 enum ListColumns {
100 VGC_NAME, ///< Group name.
101 VGC_PROTECT, ///< Autoreplace protect icon.
102 VGC_AUTOREPLACE, ///< Autoreplace active icon.
103 VGC_PROFIT, ///< Profit icon.
104 VGC_NUMBER, ///< Number of vehicles in the group.
106 VGC_END
109 VehicleID vehicle_sel; ///< Selected vehicle
110 GroupID group_sel; ///< Selected group (for drag/drop)
111 GroupID group_rename; ///< Group being renamed, INVALID_GROUP if none
112 GroupID group_over; ///< Group over which a vehicle is dragged, INVALID_GROUP if none
113 GroupID group_confirm; ///< Group awaiting delete confirmation
114 GUIGroupList groups; ///< List of groups
115 uint tiny_step_height; ///< Step height for the group list
116 Scrollbar *group_sb;
118 SmallVector<int, 16> indents; ///< Indentation levels
120 Dimension column_size[VGC_END]; ///< Size of the columns in the group list.
122 void AddParents(GUIGroupList *source, GroupID parent, int indent)
124 for (const Group **g = source->Begin(); g != source->End(); g++) {
125 if ((*g)->parent == parent) {
126 *this->groups.Append() = *g;
127 *this->indents.Append() = indent;
128 AddParents(source, (*g)->index, indent + 1);
133 /** Sort the groups by their name */
134 static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b)
136 static const Group *last_group[2] = { NULL, NULL };
137 static char last_name[2][64] = { "", "" };
139 if (*a != last_group[0]) {
140 last_group[0] = *a;
141 SetDParam(0, (*a)->index);
142 GetString (last_name[0], STR_GROUP_NAME);
145 if (*b != last_group[1]) {
146 last_group[1] = *b;
147 SetDParam(0, (*b)->index);
148 GetString (last_name[1], STR_GROUP_NAME);
151 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
152 if (r == 0) return (*a)->index - (*b)->index;
153 return r;
157 * (Re)Build the group list.
159 * @param owner The owner of the window
161 void BuildGroupList(Owner owner)
163 if (!this->groups.NeedRebuild()) return;
165 this->groups.Clear();
166 this->indents.Clear();
168 GUIGroupList list;
170 const Group *g;
171 FOR_ALL_GROUPS(g) {
172 if (g->owner == owner && g->vehicle_type == this->vli.vtype) {
173 *list.Append() = g;
177 list.ForceResort();
178 list.Sort(&GroupNameSorter);
180 AddParents(&list, INVALID_GROUP, 0);
182 this->groups.Compact();
183 this->groups.RebuildDone();
187 * Compute tiny_step_height and column_size
188 * @return Total width required for the group list.
190 uint ComputeGroupInfoSize()
192 this->column_size[VGC_NAME] = maxdim(GetStringBoundingBox(STR_GROUP_DEFAULT_TRAINS + this->vli.vtype), GetStringBoundingBox(STR_GROUP_ALL_TRAINS + this->vli.vtype));
193 this->column_size[VGC_NAME].width = max(170u, this->column_size[VGC_NAME].width);
194 this->tiny_step_height = this->column_size[VGC_NAME].height;
196 this->column_size[VGC_PROTECT] = GetSpriteSize(SPR_GROUP_REPLACE_PROTECT);
197 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROTECT].height);
199 this->column_size[VGC_AUTOREPLACE] = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
200 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_AUTOREPLACE].height);
202 this->column_size[VGC_PROFIT].width = 0;
203 this->column_size[VGC_PROFIT].height = 0;
204 static const SpriteID profit_sprites[] = {SPR_PROFIT_NA, SPR_PROFIT_NEGATIVE, SPR_PROFIT_SOME, SPR_PROFIT_LOT};
205 for (uint i = 0; i < lengthof(profit_sprites); i++) {
206 Dimension d = GetSpriteSize(profit_sprites[i]);
207 this->column_size[VGC_PROFIT] = maxdim(this->column_size[VGC_PROFIT], d);
209 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROFIT].height);
211 SetDParamMaxValue(0, GroupStatistics::Get(this->vli.company, ALL_GROUP, this->vli.vtype).num_vehicle, 3, FS_SMALL);
212 this->column_size[VGC_NUMBER] = GetStringBoundingBox(STR_TINY_COMMA);
213 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_NUMBER].height);
215 this->tiny_step_height += WD_MATRIX_TOP;
217 return WD_FRAMERECT_LEFT + 8 +
218 this->column_size[VGC_NAME].width + 8 +
219 this->column_size[VGC_PROTECT].width + 2 +
220 this->column_size[VGC_AUTOREPLACE].width + 2 +
221 this->column_size[VGC_PROFIT].width + 2 +
222 this->column_size[VGC_NUMBER].width + 2 +
223 WD_FRAMERECT_RIGHT;
227 * Draw a row in the group list.
228 * @param dpi Area to draw on.
229 * @param y Top of the row.
230 * @param left Left of the row.
231 * @param right Right of the row.
232 * @param g_id Group to list.
233 * @param indent Indentation level.
234 * @param protection Whether autoreplace protection is set.
236 void DrawGroupInfo (BlitArea *dpi, int y, int left, int right,
237 GroupID g_id, int indent = 0, bool protection = false) const
239 /* Highlight the group if a vehicle is dragged over it */
240 if (g_id == this->group_over) {
241 GfxFillRect (dpi, left + WD_FRAMERECT_LEFT, y + WD_FRAMERECT_TOP, right - WD_FRAMERECT_RIGHT, y + this->tiny_step_height - WD_FRAMERECT_BOTTOM - WD_MATRIX_TOP, _colour_gradient[COLOUR_GREY][7]);
244 if (g_id == NEW_GROUP) return;
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;
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;
261 int x = rtl ? right - WD_FRAMERECT_RIGHT - 8 - this->column_size[VGC_NAME].width + 1 : left + WD_FRAMERECT_LEFT + 8;
262 DrawString (dpi, 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);
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 (dpi, SPR_GROUP_REPLACE_PROTECT, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROTECT].height) / 2);
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 (dpi, SPR_GROUP_REPLACE_ACTIVE, stats.autoreplace_finished ? PALETTE_CRASH : PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_AUTOREPLACE].height) / 2);
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;
284 DrawSprite (dpi, spr, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROFIT].height) / 2);
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 (dpi, 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);
293 * Mark the widget containing the currently highlighted group as dirty.
295 void DirtyHighlightedGroupWidget()
297 if (this->group_over == INVALID_GROUP) return;
299 if (IsAllGroupID(this->group_over)) {
300 this->SetWidgetDirty(WID_GL_ALL_VEHICLES);
301 } else if (IsDefaultGroupID(this->group_over)) {
302 this->SetWidgetDirty(WID_GL_DEFAULT_VEHICLES);
303 } else {
304 this->SetWidgetDirty(WID_GL_LIST_GROUP);
308 public:
309 VehicleGroupWindow (const WindowDesc *desc, WindowNumber window_number)
310 : BaseVehicleListWindow (desc, window_number),
311 vehicle_sel (0), group_sel (0), group_rename (0),
312 group_over (0), group_confirm (0), groups(),
313 tiny_step_height (0), group_sb (NULL), indents()
315 memset (this->column_size, 0, sizeof(this->column_size));
317 this->CreateNestedTree();
319 this->vscroll = this->GetScrollbar(WID_GL_LIST_VEHICLE_SCROLLBAR);
320 this->group_sb = this->GetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR);
322 switch (this->vli.vtype) {
323 default: NOT_REACHED();
324 case VEH_TRAIN: this->sorting = &_sorting.train; break;
325 case VEH_ROAD: this->sorting = &_sorting.roadveh; break;
326 case VEH_SHIP: this->sorting = &_sorting.ship; break;
327 case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
330 this->vli.index = ALL_GROUP;
331 this->vehicle_sel = INVALID_VEHICLE;
332 this->group_sel = INVALID_GROUP;
333 this->group_rename = INVALID_GROUP;
334 this->group_over = INVALID_GROUP;
336 this->vehicles.SetListing(*this->sorting);
337 this->vehicles.ForceRebuild();
338 this->vehicles.NeedResort();
340 this->BuildVehicleList();
341 this->SortVehicleList();
343 this->groups.ForceRebuild();
344 this->groups.NeedResort();
345 this->BuildGroupList(vli.company);
347 this->GetWidget<NWidgetCore>(WID_GL_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vli.vtype;
348 this->GetWidget<NWidgetCore>(WID_GL_LIST_VEHICLE)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vli.vtype;
350 this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data += this->vli.vtype;
351 this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data += this->vli.vtype;
352 this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data += this->vli.vtype;
353 this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data += this->vli.vtype;
355 this->InitNested(window_number);
356 this->owner = vli.company;
359 void OnDelete (void) FINAL_OVERRIDE
361 *this->sorting = this->vehicles.GetListing();
364 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
366 switch (widget) {
367 case WID_GL_LIST_GROUP: {
368 size->width = this->ComputeGroupInfoSize();
369 resize->height = this->tiny_step_height;
371 /* Minimum height is the height of the list widget minus all and default vehicles... */
372 size->height = 4 * GetVehicleListHeight(this->vli.vtype, this->tiny_step_height) - 2 * this->tiny_step_height;
374 /* ... minus the buttons at the bottom ... */
375 uint max_icon_height = GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data).height;
376 max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data).height);
377 max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data).height);
378 max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data).height);
380 /* Get a multiple of tiny_step_height of that amount */
381 size->height = Ceil(size->height - max_icon_height, tiny_step_height);
382 break;
385 case WID_GL_ALL_VEHICLES:
386 case WID_GL_DEFAULT_VEHICLES:
387 size->width = this->ComputeGroupInfoSize();
388 size->height = this->tiny_step_height;
389 break;
391 case WID_GL_SORT_BY_ORDER: {
392 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
393 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
394 d.height += padding.height;
395 *size = maxdim(*size, d);
396 break;
399 case WID_GL_LIST_VEHICLE:
400 this->ComputeGroupInfoSize();
401 resize->height = GetVehicleListHeight(this->vli.vtype, this->tiny_step_height);
402 size->height = 4 * resize->height;
403 break;
405 case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
406 Dimension d = this->GetActionDropdownSize(true, true);
407 d.height += padding.height;
408 d.width += padding.width;
409 *size = maxdim(*size, d);
410 break;
416 * Some data on this window has become invalid.
417 * @param data Information about the changed data.
418 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
420 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
422 if (data == 0) {
423 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
424 this->vehicles.ForceRebuild();
425 this->groups.ForceRebuild();
426 } else {
427 this->vehicles.ForceResort();
428 this->groups.ForceResort();
431 /* Process ID-invalidation in command-scope as well */
432 if (this->group_rename != INVALID_GROUP && !Group::IsValidID(this->group_rename)) {
433 DeleteWindowByClass(WC_QUERY_STRING);
434 this->group_rename = INVALID_GROUP;
437 if (!(IsAllGroupID(this->vli.index) || IsDefaultGroupID(this->vli.index) || Group::IsValidID(this->vli.index))) {
438 this->vli.index = ALL_GROUP;
439 HideDropDownMenu(this);
441 this->SetDirty();
444 virtual void SetStringParameters(int widget) const
446 switch (widget) {
447 case WID_GL_AVAILABLE_VEHICLES:
448 SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype);
449 break;
451 case WID_GL_CAPTION:
452 /* If selected_group == DEFAULT_GROUP || ALL_GROUP, draw the standard caption
453 * We list all vehicles or ungrouped vehicles */
454 if (IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index)) {
455 SetDParam(0, STR_COMPANY_NAME);
456 SetDParam(1, this->vli.company);
457 SetDParam(2, this->vehicles.Length());
458 SetDParam(3, this->vehicles.Length());
459 } else {
460 const Group *g = Group::Get(this->vli.index);
462 SetDParam(0, STR_GROUP_NAME);
463 SetDParam(1, g->index);
464 SetDParam(2, g->statistics.num_vehicle);
465 SetDParam(3, g->statistics.num_vehicle);
467 break;
471 void OnPaint (BlitArea *dpi) OVERRIDE
473 /* If we select the all vehicles, this->list will contain all vehicles of the owner
474 * else this->list will contain all vehicles which belong to the selected group */
475 this->BuildVehicleList();
476 this->SortVehicleList();
478 this->BuildGroupList(this->owner);
480 this->group_sb->SetCount(this->groups.Length());
481 this->vscroll->SetCount(this->vehicles.Length());
483 /* The drop down menu is out, *but* it may not be used, retract it. */
484 if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_GL_MANAGE_VEHICLES_DROPDOWN)) {
485 this->RaiseWidget(WID_GL_MANAGE_VEHICLES_DROPDOWN);
486 HideDropDownMenu(this);
489 /* Disable all lists management button when the list is empty */
490 this->SetWidgetsDisabledState(this->vehicles.Length() == 0 || _local_company != this->vli.company,
491 WID_GL_STOP_ALL,
492 WID_GL_START_ALL,
493 WID_GL_MANAGE_VEHICLES_DROPDOWN,
494 WIDGET_LIST_END);
496 /* Disable the group specific function when we select the default group or all vehicles */
497 this->SetWidgetsDisabledState(IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index) || _local_company != this->vli.company,
498 WID_GL_DELETE_GROUP,
499 WID_GL_RENAME_GROUP,
500 WID_GL_REPLACE_PROTECTION,
501 WIDGET_LIST_END);
503 /* Disable remaining buttons for non-local companies
504 * Needed while changing _local_company, eg. by cheats
505 * All procedures (eg. move vehicle to another group)
506 * verify, whether you are the owner of the vehicle,
507 * so it doesn't have to be disabled
509 this->SetWidgetsDisabledState(_local_company != this->vli.company,
510 WID_GL_CREATE_GROUP,
511 WID_GL_AVAILABLE_VEHICLES,
512 WIDGET_LIST_END);
514 /* If not a default group and the group has replace protection, show an enabled replace sprite. */
515 uint16 protect_sprite = SPR_GROUP_REPLACE_OFF_TRAIN;
516 if (!IsDefaultGroupID(this->vli.index) && !IsAllGroupID(this->vli.index) && Group::Get(this->vli.index)->replace_protection) protect_sprite = SPR_GROUP_REPLACE_ON_TRAIN;
517 this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data = protect_sprite + this->vli.vtype;
519 /* Set text of sort by dropdown */
520 this->GetWidget<NWidgetCore>(WID_GL_SORT_BY_DROPDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
522 this->DrawWidgets (dpi);
525 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
527 switch (widget) {
528 case WID_GL_ALL_VEHICLES:
529 DrawGroupInfo (dpi, r.top + WD_FRAMERECT_TOP, r.left, r.right, ALL_GROUP);
530 break;
532 case WID_GL_DEFAULT_VEHICLES:
533 DrawGroupInfo (dpi, r.top + WD_FRAMERECT_TOP, r.left, r.right, DEFAULT_GROUP);
534 break;
536 case WID_GL_LIST_GROUP: {
537 int y1 = r.top + WD_FRAMERECT_TOP;
538 int max = min(this->group_sb->GetPosition() + this->group_sb->GetCapacity(), this->groups.Length());
539 for (int i = this->group_sb->GetPosition(); i < max; ++i) {
540 const Group *g = this->groups[i];
542 assert(g->owner == this->owner);
544 DrawGroupInfo (dpi, y1, r.left, r.right, g->index, this->indents[i], g->replace_protection);
546 y1 += this->tiny_step_height;
548 if ((uint)this->group_sb->GetPosition() + this->group_sb->GetCapacity() > this->groups.Length()) {
549 DrawGroupInfo (dpi, y1, r.left, r.right, NEW_GROUP);
551 break;
554 case WID_GL_SORT_BY_ORDER:
555 this->DrawSortButtonState (dpi, WID_GL_SORT_BY_ORDER, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
556 break;
558 case WID_GL_LIST_VEHICLE:
559 if (this->vli.index != ALL_GROUP) {
560 /* Mark vehicles which are in sub-groups */
561 int y = r.top;
562 uint max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehicles.Length());
563 for (uint i = this->vscroll->GetPosition(); i < max; ++i) {
564 const Vehicle *v = this->vehicles[i];
565 if (v->group_id != this->vli.index) {
566 GfxFillRect (dpi, r.left + 1, y + 1, r.right - 1, y + this->resize.step_height - 2, _colour_gradient[COLOUR_GREY][3], FILLRECT_CHECKER);
568 y += this->resize.step_height;
572 this->DrawVehicleListItems (dpi, this->vehicle_sel, this->resize.step_height, r);
573 break;
577 static void DeleteGroupCallback(Window *win, bool confirmed)
579 if (confirmed) {
580 VehicleGroupWindow *w = (VehicleGroupWindow*)win;
581 w->vli.index = ALL_GROUP;
582 DoCommandP(0, w->group_confirm, 0, CMD_DELETE_GROUP);
586 virtual void OnClick(Point pt, int widget, int click_count)
588 switch (widget) {
589 case WID_GL_SORT_BY_ORDER: // Flip sorting method ascending/descending
590 this->vehicles.ToggleSortOrder();
591 this->SetDirty();
592 break;
594 case WID_GL_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu
595 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));
596 return;
598 case WID_GL_ALL_VEHICLES: // All vehicles button
599 if (!IsAllGroupID(this->vli.index)) {
600 this->vli.index = ALL_GROUP;
601 this->vehicles.ForceRebuild();
602 this->SetDirty();
604 break;
606 case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles button
607 if (!IsDefaultGroupID(this->vli.index)) {
608 this->vli.index = DEFAULT_GROUP;
609 this->vehicles.ForceRebuild();
610 this->SetDirty();
612 break;
614 case WID_GL_LIST_GROUP: { // Matrix Group
615 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
616 if (id_g >= this->groups.Length()) return;
618 this->group_sel = this->vli.index = this->groups[id_g]->index;
620 SetPointerMode (POINTER_DRAG, this, SPR_CURSOR_MOUSE);
622 this->vehicles.ForceRebuild();
623 this->SetDirty();
624 break;
627 case WID_GL_LIST_VEHICLE: { // Matrix Vehicle
628 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
629 if (id_v >= this->vehicles.Length()) return; // click out of list bound
631 const Vehicle *v = this->vehicles[id_v];
632 if (VehicleClicked(v)) break;
634 this->vehicle_sel = v->index;
636 SetPointerMode (POINTER_DRAG, this, SPR_CURSOR_MOUSE);
637 SetMouseCursorVehicle(v, EIT_IN_LIST);
639 this->SetDirty();
640 break;
643 case WID_GL_CREATE_GROUP: { // Create a new group
644 DoCommandP(0, this->vli.vtype, 0, CMD_CREATE_GROUP);
645 break;
648 case WID_GL_DELETE_GROUP: { // Delete the selected group
649 this->group_confirm = this->vli.index;
650 ShowQuery(STR_QUERY_GROUP_DELETE_CAPTION, STR_GROUP_DELETE_QUERY_TEXT, this, DeleteGroupCallback);
651 break;
654 case WID_GL_RENAME_GROUP: // Rename the selected roup
655 this->ShowRenameGroupWindow(this->vli.index, false);
656 break;
658 case WID_GL_AVAILABLE_VEHICLES:
659 ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
660 break;
662 case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
663 DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index));
664 ShowDropDownList(this, list, 0, WID_GL_MANAGE_VEHICLES_DROPDOWN);
665 break;
668 case WID_GL_START_ALL:
669 case WID_GL_STOP_ALL: { // Start/stop all vehicles of the list
670 DoCommandP(0, (1 << 1) | (widget == WID_GL_START_ALL ? (1 << 0) : 0), this->vli.Pack(), CMD_MASS_START_STOP);
671 break;
674 case WID_GL_REPLACE_PROTECTION: {
675 const Group *g = Group::GetIfValid(this->vli.index);
676 if (g != NULL) {
677 DoCommandP(0, this->vli.index, (g->replace_protection ? 0 : 1) | (_ctrl_pressed << 1), CMD_SET_GROUP_REPLACE_PROTECTION);
679 break;
684 void OnDragDrop_Group(Point pt, int widget)
686 const Group *g = Group::Get(this->group_sel);
688 switch (widget) {
689 case WID_GL_ALL_VEHICLES: // All vehicles
690 case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles
691 if (g->parent != INVALID_GROUP) {
692 DoCommandP(0, this->group_sel | (1 << 16), INVALID_GROUP, CMD_ALTER_GROUP);
695 this->group_sel = INVALID_GROUP;
696 this->group_over = INVALID_GROUP;
697 this->SetDirty();
698 break;
700 case WID_GL_LIST_GROUP: { // Matrix group
701 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
702 GroupID new_g = id_g >= this->groups.Length() ? INVALID_GROUP : this->groups[id_g]->index;
704 if (this->group_sel != new_g && g->parent != new_g) {
705 DoCommandP(0, this->group_sel | (1 << 16), new_g, CMD_ALTER_GROUP);
708 this->group_sel = INVALID_GROUP;
709 this->group_over = INVALID_GROUP;
710 this->SetDirty();
711 break;
716 void OnDragDrop_Vehicle(Point pt, int widget)
718 switch (widget) {
719 case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles
720 DoCommandP(0, DEFAULT_GROUP, this->vehicle_sel | (_ctrl_pressed ? 1 << 31 : 0), CMD_ADD_VEHICLE_GROUP);
722 this->vehicle_sel = INVALID_VEHICLE;
723 this->group_over = INVALID_GROUP;
725 this->SetDirty();
726 break;
728 case WID_GL_LIST_GROUP: { // Matrix group
729 const VehicleID vindex = this->vehicle_sel;
730 this->vehicle_sel = INVALID_VEHICLE;
731 this->group_over = INVALID_GROUP;
732 this->SetDirty();
734 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
735 GroupID new_g = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
737 DoCommandP(0, new_g, vindex | (_ctrl_pressed ? 1 << 31 : 0), CMD_ADD_VEHICLE_GROUP);
738 break;
741 case WID_GL_LIST_VEHICLE: { // Matrix vehicle
742 const VehicleID vindex = this->vehicle_sel;
743 this->vehicle_sel = INVALID_VEHICLE;
744 this->group_over = INVALID_GROUP;
745 this->SetDirty();
747 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
748 if (id_v >= this->vehicles.Length()) return; // click out of list bound
750 const Vehicle *v = this->vehicles[id_v];
751 if (!VehicleClicked(v) && vindex == v->index) {
752 ShowVehicleViewWindow(v);
754 break;
759 virtual void OnDragDrop(Point pt, int widget)
761 if (this->vehicle_sel != INVALID_VEHICLE) OnDragDrop_Vehicle(pt, widget);
762 if (this->group_sel != INVALID_GROUP) OnDragDrop_Group(pt, widget);
765 virtual void OnQueryTextFinished(char *str)
767 if (str != NULL) DoCommandP(0, this->group_rename, 0, CMD_ALTER_GROUP, str);
768 this->group_rename = INVALID_GROUP;
771 virtual void OnResize()
773 this->group_sb->SetCapacityFromWidget(this, WID_GL_LIST_GROUP);
774 this->vscroll->SetCapacityFromWidget(this, WID_GL_LIST_VEHICLE);
777 virtual void OnDropdownSelect(int widget, int index)
779 switch (widget) {
780 case WID_GL_SORT_BY_DROPDOWN:
781 this->vehicles.SetSortType(index);
782 break;
784 case WID_GL_MANAGE_VEHICLES_DROPDOWN:
785 assert(this->vehicles.Length() != 0);
787 switch (index) {
788 case ADI_REPLACE: // Replace window
789 ShowReplaceGroupVehicleWindow(this->vli.index, this->vli.vtype);
790 break;
791 case ADI_SERVICE: // Send for servicing
792 case ADI_DEPOT: { // Send to Depots
793 DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : 0U), this->vli.Pack(), CMD_SEND_VEHICLE_TO_DEPOT);
794 break;
797 case ADI_ADD_SHARED: // Add shared Vehicles
798 assert(Group::IsValidID(this->vli.index));
800 DoCommandP(0, this->vli.index, this->vli.vtype, CMD_ADD_SHARED_VEHICLE_GROUP);
801 break;
802 case ADI_REMOVE_ALL: // Remove all Vehicles from the selected group
803 assert(Group::IsValidID(this->vli.index));
805 DoCommandP(0, this->vli.index, 0, CMD_REMOVE_ALL_VEHICLES_GROUP);
806 break;
807 default: NOT_REACHED();
809 break;
811 default: NOT_REACHED();
814 this->SetDirty();
817 virtual void OnTick()
819 if (_pause_mode != PM_UNPAUSED) return;
820 if (this->groups.NeedResort() || this->vehicles.NeedResort()) {
821 this->SetDirty();
825 virtual void OnPlaceObjectAbort()
827 /* abort drag & drop */
828 this->vehicle_sel = INVALID_VEHICLE;
829 this->DirtyHighlightedGroupWidget();
830 this->group_over = INVALID_GROUP;
831 this->SetWidgetDirty(WID_GL_LIST_VEHICLE);
834 virtual void OnMouseDrag(Point pt, int widget)
836 if (this->vehicle_sel == INVALID_VEHICLE && this->group_sel == INVALID_GROUP) return;
838 /* A vehicle is dragged over... */
839 GroupID new_group_over = INVALID_GROUP;
840 switch (widget) {
841 case WID_GL_DEFAULT_VEHICLES: // ... the 'default' group.
842 new_group_over = DEFAULT_GROUP;
843 break;
845 case WID_GL_LIST_GROUP: { // ... the list of custom groups.
846 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
847 new_group_over = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
848 break;
852 /* Do not highlight when dragging over the current group */
853 if (this->vehicle_sel != INVALID_VEHICLE) {
854 if (Vehicle::Get(vehicle_sel)->group_id == new_group_over) new_group_over = INVALID_GROUP;
855 } else if (this->group_sel != INVALID_GROUP) {
856 if (this->group_sel == new_group_over || Group::Get(this->group_sel)->parent == new_group_over) new_group_over = INVALID_GROUP;
859 /* Mark widgets as dirty if the group changed. */
860 if (new_group_over != this->group_over) {
861 this->DirtyHighlightedGroupWidget();
862 this->group_over = new_group_over;
863 this->DirtyHighlightedGroupWidget();
867 void ShowRenameGroupWindow(GroupID group, bool empty)
869 assert(Group::IsValidID(group));
870 this->group_rename = group;
871 /* Show empty query for new groups */
872 StringID str = STR_EMPTY;
873 if (!empty) {
874 SetDParam(0, group);
875 str = STR_GROUP_NAME;
877 ShowQueryString(str, STR_GROUP_RENAME_CAPTION, MAX_LENGTH_GROUP_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
881 * Tests whether a given vehicle is selected in the window, and unselects it if necessary.
882 * Called when the vehicle is deleted.
883 * @param vehicle Vehicle that is going to be deleted
885 void UnselectVehicle(VehicleID vehicle)
887 if (this->vehicle_sel == vehicle) ResetPointerMode();
892 static WindowDesc::Prefs _other_group_prefs ("list_groups");
894 static WindowDesc::Prefs _train_group_prefs ("list_groups_train");
896 static const WindowDesc _train_group_desc(
897 WDP_AUTO, 525, 246,
898 WC_TRAINS_LIST, WC_NONE,
900 _nested_group_widgets, lengthof(_nested_group_widgets),
901 &_train_group_prefs
904 static const WindowDesc _roadveh_group_desc(
905 WDP_AUTO, 460, 246,
906 WC_ROADVEH_LIST, WC_NONE,
908 _nested_group_widgets, lengthof(_nested_group_widgets),
909 &_other_group_prefs
912 static const WindowDesc _ship_group_desc(
913 WDP_AUTO, 460, 246,
914 WC_SHIPS_LIST, WC_NONE,
916 _nested_group_widgets, lengthof(_nested_group_widgets),
917 &_other_group_prefs
920 static const WindowDesc _aircraft_group_desc(
921 WDP_AUTO, 460, 246,
922 WC_AIRCRAFT_LIST, WC_NONE,
924 _nested_group_widgets, lengthof(_nested_group_widgets),
925 &_other_group_prefs
929 * Show the group window for the given company and vehicle type.
930 * @param company The company to show the window for.
931 * @param vehicle_type The type of vehicle to show it for.
933 void ShowCompanyGroup(CompanyID company, VehicleType vehicle_type)
935 static const WindowDesc *const descs[VEH_COMPANY_END] = {
936 &_train_group_desc, // VEH_TRAIN
937 &_roadveh_group_desc, // VEH_ROAD
938 &_ship_group_desc, // VEH_SHIP
939 &_aircraft_group_desc, // VEH_AIRCRAFT
942 if (!Company::IsValidID(company)) return;
944 WindowNumber num = VehicleListIdentifier(VL_GROUP_LIST, vehicle_type, company).Pack();
945 AllocateWindowDescFront<VehicleGroupWindow> (descs[vehicle_type], num);
949 * Finds a group list window determined by vehicle type and owner
950 * @param vt vehicle type
951 * @param owner owner of groups
952 * @return pointer to VehicleGroupWindow, NULL if not found
954 static inline VehicleGroupWindow *FindVehicleGroupWindow(VehicleType vt, Owner owner)
956 return (VehicleGroupWindow *)FindWindowById(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, owner).Pack());
960 * Opens a 'Rename group' window for a newly created group
961 * @param type the vehicle type to open the window for
963 static void ShowRenameNewGroupWindow(VehicleType type)
965 VehicleGroupWindow *w = FindVehicleGroupWindow(type, _current_company);
966 if (w != NULL) w->ShowRenameGroupWindow(_new_group_id, true);
970 * Opens a 'Rename group' window for newly created group
971 * @param success did command succeed?
972 * @param tile unused
973 * @param p1 vehicle type
974 * @param p2 unused
975 * @see CmdCreateGroup
977 void CcCreateGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
979 if (result.Failed()) return;
980 assert(p1 <= VEH_AIRCRAFT);
982 ShowRenameNewGroupWindow((VehicleType)p1);
986 * Open rename window after adding a vehicle to a new group via drag and drop.
987 * @param success Did command succeed?
988 * @param tile Unused.
989 * @param p1 Group the vehicle was added to.
990 * @param p2 Bit 0-19: Vehicle ID.
992 void CcAddVehicleGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
994 if (result.Failed()) return;
995 if (p1 != NEW_GROUP) return;
996 assert(Vehicle::IsValidID(GB(p2, 0, 20)));
998 ShowRenameNewGroupWindow(Vehicle::Get(GB(p2, 0, 20))->type);
1002 * Get the error string when altering a group.
1003 * @param tile unused
1004 * @param flags type of operation
1005 * @param p1 index of array group
1006 * - p1 bit 0-15 : GroupID
1007 * - p1 bit 16: 0 - Rename group
1008 * 1 - Set group parent
1009 * @param p2 parent group index
1010 * @param text the new name or an empty string when resetting to the default
1012 StringID GetErrAlterGroup (TileIndex tile, uint32 p1, uint32 p2, const char *text)
1014 return HasBit(p1, 16) ? STR_ERROR_GROUP_CAN_T_SET_PARENT : STR_ERROR_GROUP_CAN_T_RENAME;
1018 * Removes the highlight of a vehicle in a group window
1019 * @param *v Vehicle to remove all highlights from
1021 void DeleteGroupHighlightOfVehicle(const Vehicle *v)
1023 /* If we haven't got any vehicles on the mouse pointer, we haven't got any highlighted in any group windows either
1024 * If that is the case, we can skip looping though the windows and save time
1026 if (_pointer_mode != POINTER_DRAG) return;
1028 VehicleGroupWindow *w = FindVehicleGroupWindow(v->type, v->owner);
1029 if (w != NULL) w->UnselectVehicle(v->index);