OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

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