Rework ChoosePylonPosition to simplify the loop
[openttd/fttd.git] / src / group_gui.cpp
blob85cca7db49f2f786af2fcf2247e85c3527362fde
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(WWT_PANEL, COLOUR_GREY, WID_GL_INFO), SetFill(1, 0), EndContainer(),
57 NWidget(NWID_HORIZONTAL),
58 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_CREATE_GROUP), SetFill(0, 1),
59 SetDataTip(SPR_GROUP_CREATE_TRAIN, STR_GROUP_CREATE_TOOLTIP),
60 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_DELETE_GROUP), SetFill(0, 1),
61 SetDataTip(SPR_GROUP_DELETE_TRAIN, STR_GROUP_DELETE_TOOLTIP),
62 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_RENAME_GROUP), SetFill(0, 1),
63 SetDataTip(SPR_GROUP_RENAME_TRAIN, STR_GROUP_RENAME_TOOLTIP),
64 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(),
65 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_REPLACE_PROTECTION), SetFill(0, 1),
66 SetDataTip(SPR_GROUP_REPLACE_OFF_TRAIN, STR_GROUP_REPLACE_PROTECTION_TOOLTIP),
67 EndContainer(),
68 EndContainer(),
69 /* right part */
70 NWidget(NWID_VERTICAL),
71 NWidget(NWID_HORIZONTAL),
72 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_SORT_BY_ORDER), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
73 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_SORT_BY_DROPDOWN), SetMinimalSize(167, 12), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA),
74 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetResize(1, 0), EndContainer(),
75 EndContainer(),
76 NWidget(NWID_HORIZONTAL),
77 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),
78 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_GL_LIST_VEHICLE_SCROLLBAR),
79 EndContainer(),
80 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(1, 0), SetFill(1, 1), SetResize(1, 0), EndContainer(),
81 NWidget(NWID_HORIZONTAL),
82 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
83 SetDataTip(STR_BLACK_STRING, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
84 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 1), SetResize(1, 0), EndContainer(),
85 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(0, 1),
86 SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
87 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
88 SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
89 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
90 SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
91 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
92 EndContainer(),
93 EndContainer(),
94 EndContainer(),
97 class VehicleGroupWindow : public BaseVehicleListWindow {
98 private:
99 /* Columns in the group list */
100 enum ListColumns {
101 VGC_NAME, ///< Group name.
102 VGC_PROTECT, ///< Autoreplace protect icon.
103 VGC_AUTOREPLACE, ///< Autoreplace active icon.
104 VGC_PROFIT, ///< Profit icon.
105 VGC_NUMBER, ///< Number of vehicles in the group.
107 VGC_END
110 VehicleID vehicle_sel; ///< Selected vehicle
111 GroupID group_sel; ///< Selected group (for drag/drop)
112 GroupID group_rename; ///< Group being renamed, INVALID_GROUP if none
113 GroupID group_over; ///< Group over which a vehicle is dragged, INVALID_GROUP if none
114 GroupID group_confirm; ///< Group awaiting delete confirmation
115 GUIGroupList groups; ///< List of groups
116 uint tiny_step_height; ///< Step height for the group list
117 Scrollbar *group_sb;
119 SmallVector<int, 16> indents; ///< Indentation levels
121 Dimension column_size[VGC_END]; ///< Size of the columns in the group list.
123 void AddParents(GUIGroupList *source, GroupID parent, int indent)
125 for (const Group **g = source->Begin(); g != source->End(); g++) {
126 if ((*g)->parent == parent) {
127 *this->groups.Append() = *g;
128 *this->indents.Append() = indent;
129 AddParents(source, (*g)->index, indent + 1);
134 /** Sort the groups by their name */
135 static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b)
137 static const Group *last_group[2] = { NULL, NULL };
138 static char last_name[2][64] = { "", "" };
140 if (*a != last_group[0]) {
141 last_group[0] = *a;
142 SetDParam(0, (*a)->index);
143 GetString (last_name[0], STR_GROUP_NAME);
146 if (*b != last_group[1]) {
147 last_group[1] = *b;
148 SetDParam(0, (*b)->index);
149 GetString (last_name[1], STR_GROUP_NAME);
152 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
153 if (r == 0) return (*a)->index - (*b)->index;
154 return r;
158 * (Re)Build the group list.
160 * @param owner The owner of the window
162 void BuildGroupList(Owner owner)
164 if (!this->groups.NeedRebuild()) return;
166 this->groups.Clear();
167 this->indents.Clear();
169 GUIGroupList list;
171 const Group *g;
172 FOR_ALL_GROUPS(g) {
173 if (g->owner == owner && g->vehicle_type == this->vli.vtype) {
174 *list.Append() = g;
178 list.ForceResort();
179 list.Sort(&GroupNameSorter);
181 AddParents(&list, INVALID_GROUP, 0);
183 this->groups.Compact();
184 this->groups.RebuildDone();
188 * Compute tiny_step_height and column_size
189 * @return Total width required for the group list.
191 uint ComputeGroupInfoSize()
193 this->column_size[VGC_NAME] = maxdim(GetStringBoundingBox(STR_GROUP_DEFAULT_TRAINS + this->vli.vtype), GetStringBoundingBox(STR_GROUP_ALL_TRAINS + this->vli.vtype));
194 this->column_size[VGC_NAME].width = max(170u, this->column_size[VGC_NAME].width);
195 this->tiny_step_height = this->column_size[VGC_NAME].height;
197 this->column_size[VGC_PROTECT] = GetSpriteSize(SPR_GROUP_REPLACE_PROTECT);
198 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROTECT].height);
200 this->column_size[VGC_AUTOREPLACE] = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
201 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_AUTOREPLACE].height);
203 this->column_size[VGC_PROFIT].width = 0;
204 this->column_size[VGC_PROFIT].height = 0;
205 static const SpriteID profit_sprites[] = {SPR_PROFIT_NA, SPR_PROFIT_NEGATIVE, SPR_PROFIT_SOME, SPR_PROFIT_LOT};
206 for (uint i = 0; i < lengthof(profit_sprites); i++) {
207 Dimension d = GetSpriteSize(profit_sprites[i]);
208 this->column_size[VGC_PROFIT] = maxdim(this->column_size[VGC_PROFIT], d);
210 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROFIT].height);
212 SetDParamMaxValue(0, GroupStatistics::Get(this->vli.company, ALL_GROUP, this->vli.vtype).num_vehicle, 3, FS_SMALL);
213 this->column_size[VGC_NUMBER] = GetStringBoundingBox(STR_TINY_COMMA);
214 this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_NUMBER].height);
216 this->tiny_step_height += WD_MATRIX_TOP;
218 return WD_FRAMERECT_LEFT + 8 +
219 this->column_size[VGC_NAME].width + 8 +
220 this->column_size[VGC_PROTECT].width + 2 +
221 this->column_size[VGC_AUTOREPLACE].width + 2 +
222 this->column_size[VGC_PROFIT].width + 2 +
223 this->column_size[VGC_NUMBER].width + 2 +
224 WD_FRAMERECT_RIGHT;
228 * Draw a row in the group list.
229 * @param dpi Area to draw on.
230 * @param y Top of the row.
231 * @param left Left of the row.
232 * @param right Right of the row.
233 * @param g_id Group to list.
234 * @param indent Indentation level.
235 * @param protection Whether autoreplace protection is set.
237 void DrawGroupInfo (BlitArea *dpi, int y, int left, int right,
238 GroupID g_id, int indent = 0, bool protection = false) const
240 /* Highlight the group if a vehicle is dragged over it */
241 if (g_id == this->group_over) {
242 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]);
245 if (g_id == NEW_GROUP) return;
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;
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;
262 int x = rtl ? right - WD_FRAMERECT_RIGHT - 8 - this->column_size[VGC_NAME].width + 1 : left + WD_FRAMERECT_LEFT + 8;
263 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);
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 (dpi, SPR_GROUP_REPLACE_PROTECT, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROTECT].height) / 2);
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 (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);
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;
285 DrawSprite (dpi, spr, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROFIT].height) / 2);
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 (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);
294 * Mark the widget containing the currently highlighted group as dirty.
296 void DirtyHighlightedGroupWidget()
298 if (this->group_over == INVALID_GROUP) return;
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);
309 public:
310 VehicleGroupWindow (const WindowDesc *desc, WindowNumber window_number)
311 : BaseVehicleListWindow (desc, window_number),
312 vehicle_sel (0), group_sel (0), group_rename (0),
313 group_over (0), group_confirm (0), groups(),
314 tiny_step_height (0), group_sb (NULL), indents()
316 memset (this->column_size, 0, sizeof(this->column_size));
318 this->CreateNestedTree();
320 this->vscroll = this->GetScrollbar(WID_GL_LIST_VEHICLE_SCROLLBAR);
321 this->group_sb = this->GetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR);
323 switch (this->vli.vtype) {
324 default: NOT_REACHED();
325 case VEH_TRAIN: this->sorting = &_sorting.train; break;
326 case VEH_ROAD: this->sorting = &_sorting.roadveh; break;
327 case VEH_SHIP: this->sorting = &_sorting.ship; break;
328 case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
331 this->vli.index = ALL_GROUP;
332 this->vehicle_sel = INVALID_VEHICLE;
333 this->group_sel = INVALID_GROUP;
334 this->group_rename = INVALID_GROUP;
335 this->group_over = INVALID_GROUP;
337 this->vehicles.SetListing(*this->sorting);
338 this->vehicles.ForceRebuild();
339 this->vehicles.NeedResort();
341 this->BuildVehicleList();
342 this->SortVehicleList();
344 this->groups.ForceRebuild();
345 this->groups.NeedResort();
346 this->BuildGroupList(vli.company);
348 this->GetWidget<NWidgetCore>(WID_GL_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vli.vtype;
349 this->GetWidget<NWidgetCore>(WID_GL_LIST_VEHICLE)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vli.vtype;
351 this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data += this->vli.vtype;
352 this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data += this->vli.vtype;
353 this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data += this->vli.vtype;
354 this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data += this->vli.vtype;
356 this->InitNested(window_number);
357 this->owner = vli.company;
360 void OnDelete (void) FINAL_OVERRIDE
362 *this->sorting = this->vehicles.GetListing();
365 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
367 switch (widget) {
368 case WID_GL_LIST_GROUP: {
369 size->width = this->ComputeGroupInfoSize();
370 resize->height = this->tiny_step_height;
372 /* Minimum height is the height of the list widget minus all and default vehicles... */
373 size->height = 4 * GetVehicleListHeight(this->vli.vtype, this->tiny_step_height) - 2 * this->tiny_step_height;
375 /* ... minus the buttons at the bottom ... */
376 uint max_icon_height = GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data).height;
377 max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data).height);
378 max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data).height);
379 max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data).height);
381 /* ... minus the height of the group info ... */
382 max_icon_height += (FONT_HEIGHT_NORMAL * 3) + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
384 /* Get a multiple of tiny_step_height of that amount */
385 size->height = Ceil(size->height - max_icon_height, tiny_step_height);
386 break;
389 case WID_GL_ALL_VEHICLES:
390 case WID_GL_DEFAULT_VEHICLES:
391 size->width = this->ComputeGroupInfoSize();
392 size->height = this->tiny_step_height;
393 break;
395 case WID_GL_SORT_BY_ORDER: {
396 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
397 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
398 d.height += padding.height;
399 *size = maxdim(*size, d);
400 break;
403 case WID_GL_LIST_VEHICLE:
404 this->ComputeGroupInfoSize();
405 resize->height = GetVehicleListHeight(this->vli.vtype, this->tiny_step_height);
406 size->height = 4 * resize->height;
407 break;
409 case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
410 Dimension d = this->GetActionDropdownSize(true, true);
411 d.height += padding.height;
412 d.width += padding.width;
413 *size = maxdim(*size, d);
414 break;
417 case WID_GL_INFO: {
418 size->height = (FONT_HEIGHT_NORMAL * 3) + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
419 break;
425 * Some data on this window has become invalid.
426 * @param data Information about the changed data.
427 * @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.
429 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
431 if (data == 0) {
432 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
433 this->vehicles.ForceRebuild();
434 this->groups.ForceRebuild();
435 } else {
436 this->vehicles.ForceResort();
437 this->groups.ForceResort();
440 /* Process ID-invalidation in command-scope as well */
441 if (this->group_rename != INVALID_GROUP && !Group::IsValidID(this->group_rename)) {
442 DeleteWindowByClass(WC_QUERY_STRING);
443 this->group_rename = INVALID_GROUP;
446 if (!(IsAllGroupID(this->vli.index) || IsDefaultGroupID(this->vli.index) || Group::IsValidID(this->vli.index))) {
447 this->vli.index = ALL_GROUP;
448 HideDropDownMenu(this);
450 this->SetDirty();
453 virtual void SetStringParameters(int widget) const
455 switch (widget) {
456 case WID_GL_AVAILABLE_VEHICLES:
457 SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype);
458 break;
460 case WID_GL_CAPTION:
461 /* If selected_group == DEFAULT_GROUP || ALL_GROUP, draw the standard caption
462 * We list all vehicles or ungrouped vehicles */
463 if (IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index)) {
464 SetDParam(0, STR_COMPANY_NAME);
465 SetDParam(1, this->vli.company);
466 SetDParam(2, this->vehicles.Length());
467 SetDParam(3, this->vehicles.Length());
468 } else {
469 const Group *g = Group::Get(this->vli.index);
471 SetDParam(0, STR_GROUP_NAME);
472 SetDParam(1, g->index);
473 SetDParam(2, g->statistics.num_vehicle);
474 SetDParam(3, g->statistics.num_vehicle);
476 break;
480 void OnPaint (BlitArea *dpi) OVERRIDE
482 /* If we select the all vehicles, this->list will contain all vehicles of the owner
483 * else this->list will contain all vehicles which belong to the selected group */
484 this->BuildVehicleList();
485 this->SortVehicleList();
487 this->BuildGroupList(this->owner);
489 this->group_sb->SetCount(this->groups.Length());
490 this->vscroll->SetCount(this->vehicles.Length());
492 /* The drop down menu is out, *but* it may not be used, retract it. */
493 if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_GL_MANAGE_VEHICLES_DROPDOWN)) {
494 this->RaiseWidget(WID_GL_MANAGE_VEHICLES_DROPDOWN);
495 HideDropDownMenu(this);
498 /* Disable all lists management button when the list is empty */
499 this->SetWidgetsDisabledState(this->vehicles.Length() == 0 || _local_company != this->vli.company,
500 WID_GL_STOP_ALL,
501 WID_GL_START_ALL,
502 WID_GL_MANAGE_VEHICLES_DROPDOWN,
503 WIDGET_LIST_END);
505 /* Disable the group specific function when we select the default group or all vehicles */
506 this->SetWidgetsDisabledState(IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index) || _local_company != this->vli.company,
507 WID_GL_DELETE_GROUP,
508 WID_GL_RENAME_GROUP,
509 WID_GL_REPLACE_PROTECTION,
510 WIDGET_LIST_END);
512 /* Disable remaining buttons for non-local companies
513 * Needed while changing _local_company, eg. by cheats
514 * All procedures (eg. move vehicle to another group)
515 * verify, whether you are the owner of the vehicle,
516 * so it doesn't have to be disabled
518 this->SetWidgetsDisabledState(_local_company != this->vli.company,
519 WID_GL_CREATE_GROUP,
520 WID_GL_AVAILABLE_VEHICLES,
521 WIDGET_LIST_END);
523 /* If not a default group and the group has replace protection, show an enabled replace sprite. */
524 uint16 protect_sprite = SPR_GROUP_REPLACE_OFF_TRAIN;
525 if (!IsDefaultGroupID(this->vli.index) && !IsAllGroupID(this->vli.index) && Group::Get(this->vli.index)->replace_protection) protect_sprite = SPR_GROUP_REPLACE_ON_TRAIN;
526 this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data = protect_sprite + this->vli.vtype;
528 /* Set text of sort by dropdown */
529 this->GetWidget<NWidgetCore>(WID_GL_SORT_BY_DROPDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
531 this->DrawWidgets (dpi);
534 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
536 switch (widget) {
537 case WID_GL_ALL_VEHICLES:
538 DrawGroupInfo (dpi, r.top + WD_FRAMERECT_TOP, r.left, r.right, ALL_GROUP);
539 break;
541 case WID_GL_DEFAULT_VEHICLES:
542 DrawGroupInfo (dpi, r.top + WD_FRAMERECT_TOP, r.left, r.right, DEFAULT_GROUP);
543 break;
545 case WID_GL_INFO: {
546 Money this_year = 0;
547 Money last_year = 0;
548 uint32 occupancy = 0;
549 uint32 vehicle_count = this->vehicles.Length();
551 for (uint i = 0; i < vehicle_count; i++) {
552 const Vehicle *v = this->vehicles[i];
553 assert(v->owner == this->owner);
555 this_year += v->GetDisplayProfitThisYear();
556 last_year += v->GetDisplayProfitLastYear();
557 occupancy += v->trip_occupancy;
560 const int left = r.left + WD_FRAMERECT_LEFT + 8;
561 const int right = r.right - WD_FRAMERECT_RIGHT - 8;
563 int y = r.top + WD_FRAMERECT_TOP;
564 DrawString (dpi, left, right, y, STR_GROUP_PROFIT_THIS_YEAR, TC_BLACK);
565 SetDParam(0, this_year);
566 DrawString (dpi, left, right, y, STR_JUST_CURRENCY_LONG, TC_BLACK, SA_RIGHT);
568 y += FONT_HEIGHT_NORMAL;
569 DrawString (dpi, left, right, y, STR_GROUP_PROFIT_LAST_YEAR, TC_BLACK);
570 SetDParam(0, last_year);
571 DrawString (dpi, left, right, y, STR_JUST_CURRENCY_LONG, TC_BLACK, SA_RIGHT);
573 y += FONT_HEIGHT_NORMAL;
574 DrawString (dpi, left, right, y, STR_GROUP_OCCUPANCY, TC_BLACK);
575 if (vehicle_count > 0) {
576 SetDParam(0, occupancy / vehicle_count);
577 DrawString (dpi, left, right, y, STR_GROUP_OCCUPANCY_VALUE, TC_BLACK, SA_RIGHT);
580 break;
583 case WID_GL_LIST_GROUP: {
584 int y1 = r.top + WD_FRAMERECT_TOP;
585 int max = min(this->group_sb->GetPosition() + this->group_sb->GetCapacity(), this->groups.Length());
586 for (int i = this->group_sb->GetPosition(); i < max; ++i) {
587 const Group *g = this->groups[i];
589 assert(g->owner == this->owner);
591 DrawGroupInfo (dpi, y1, r.left, r.right, g->index, this->indents[i], g->replace_protection);
593 y1 += this->tiny_step_height;
595 if ((uint)this->group_sb->GetPosition() + this->group_sb->GetCapacity() > this->groups.Length()) {
596 DrawGroupInfo (dpi, y1, r.left, r.right, NEW_GROUP);
598 break;
601 case WID_GL_SORT_BY_ORDER:
602 this->DrawSortButtonState (dpi, WID_GL_SORT_BY_ORDER, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
603 break;
605 case WID_GL_LIST_VEHICLE:
606 if (this->vli.index != ALL_GROUP) {
607 /* Mark vehicles which are in sub-groups */
608 int y = r.top;
609 uint max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehicles.Length());
610 for (uint i = this->vscroll->GetPosition(); i < max; ++i) {
611 const Vehicle *v = this->vehicles[i];
612 if (v->group_id != this->vli.index) {
613 GfxFillRect (dpi, r.left + 1, y + 1, r.right - 1, y + this->resize.step_height - 2, _colour_gradient[COLOUR_GREY][3], FILLRECT_CHECKER);
615 y += this->resize.step_height;
619 this->DrawVehicleListItems (dpi, this->vehicle_sel, this->resize.step_height, r);
620 break;
624 static void DeleteGroupCallback(Window *win, bool confirmed)
626 if (confirmed) {
627 VehicleGroupWindow *w = (VehicleGroupWindow*)win;
628 w->vli.index = ALL_GROUP;
629 DoCommandP(0, w->group_confirm, 0, CMD_DELETE_GROUP);
633 virtual void OnClick(Point pt, int widget, int click_count)
635 switch (widget) {
636 case WID_GL_SORT_BY_ORDER: // Flip sorting method ascending/descending
637 this->vehicles.ToggleSortOrder();
638 this->SetDirty();
639 break;
641 case WID_GL_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu
642 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));
643 return;
645 case WID_GL_ALL_VEHICLES: // All vehicles button
646 if (!IsAllGroupID(this->vli.index)) {
647 this->vli.index = ALL_GROUP;
648 this->vehicles.ForceRebuild();
649 this->SetDirty();
651 break;
653 case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles button
654 if (!IsDefaultGroupID(this->vli.index)) {
655 this->vli.index = DEFAULT_GROUP;
656 this->vehicles.ForceRebuild();
657 this->SetDirty();
659 break;
661 case WID_GL_LIST_GROUP: { // Matrix Group
662 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
663 if (id_g >= this->groups.Length()) return;
665 this->group_sel = this->vli.index = this->groups[id_g]->index;
667 SetPointerMode (POINTER_DRAG, this, SPR_CURSOR_MOUSE);
669 this->vehicles.ForceRebuild();
670 this->SetDirty();
671 break;
674 case WID_GL_LIST_VEHICLE: { // Matrix Vehicle
675 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
676 if (id_v >= this->vehicles.Length()) return; // click out of list bound
678 const Vehicle *v = this->vehicles[id_v];
679 if (VehicleClicked(v)) break;
681 this->vehicle_sel = v->index;
683 SetPointerMode (POINTER_DRAG, this, SPR_CURSOR_MOUSE);
684 SetMouseCursorVehicle(v, EIT_IN_LIST);
686 this->SetDirty();
687 break;
690 case WID_GL_CREATE_GROUP: { // Create a new group
691 DoCommandP(0, this->vli.vtype, 0, CMD_CREATE_GROUP);
692 break;
695 case WID_GL_DELETE_GROUP: { // Delete the selected group
696 this->group_confirm = this->vli.index;
697 ShowQuery(STR_QUERY_GROUP_DELETE_CAPTION, STR_GROUP_DELETE_QUERY_TEXT, this, DeleteGroupCallback);
698 break;
701 case WID_GL_RENAME_GROUP: // Rename the selected roup
702 this->ShowRenameGroupWindow(this->vli.index, false);
703 break;
705 case WID_GL_AVAILABLE_VEHICLES:
706 ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
707 break;
709 case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
710 DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index));
711 ShowDropDownList(this, list, 0, WID_GL_MANAGE_VEHICLES_DROPDOWN);
712 break;
715 case WID_GL_START_ALL:
716 case WID_GL_STOP_ALL: { // Start/stop all vehicles of the list
717 DoCommandP(0, (1 << 1) | (widget == WID_GL_START_ALL ? (1 << 0) : 0), this->vli.Pack(), CMD_MASS_START_STOP);
718 break;
721 case WID_GL_REPLACE_PROTECTION: {
722 const Group *g = Group::GetIfValid(this->vli.index);
723 if (g != NULL) {
724 DoCommandP(0, this->vli.index, (g->replace_protection ? 0 : 1) | (_ctrl_pressed << 1), CMD_SET_GROUP_REPLACE_PROTECTION);
726 break;
731 void OnDragDrop_Group(Point pt, int widget)
733 const Group *g = Group::Get(this->group_sel);
735 switch (widget) {
736 case WID_GL_ALL_VEHICLES: // All vehicles
737 case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles
738 if (g->parent != INVALID_GROUP) {
739 DoCommandP(0, this->group_sel | (1 << 16), INVALID_GROUP, CMD_ALTER_GROUP);
742 this->group_sel = INVALID_GROUP;
743 this->group_over = INVALID_GROUP;
744 this->SetDirty();
745 break;
747 case WID_GL_LIST_GROUP: { // Matrix group
748 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
749 GroupID new_g = id_g >= this->groups.Length() ? INVALID_GROUP : this->groups[id_g]->index;
751 if (this->group_sel != new_g && g->parent != new_g) {
752 DoCommandP(0, this->group_sel | (1 << 16), new_g, CMD_ALTER_GROUP);
755 this->group_sel = INVALID_GROUP;
756 this->group_over = INVALID_GROUP;
757 this->SetDirty();
758 break;
763 void OnDragDrop_Vehicle(Point pt, int widget)
765 switch (widget) {
766 case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles
767 DoCommandP(0, DEFAULT_GROUP, this->vehicle_sel | (_ctrl_pressed ? 1 << 31 : 0), CMD_ADD_VEHICLE_GROUP);
769 this->vehicle_sel = INVALID_VEHICLE;
770 this->group_over = INVALID_GROUP;
772 this->SetDirty();
773 break;
775 case WID_GL_LIST_GROUP: { // Matrix group
776 const VehicleID vindex = this->vehicle_sel;
777 this->vehicle_sel = INVALID_VEHICLE;
778 this->group_over = INVALID_GROUP;
779 this->SetDirty();
781 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
782 GroupID new_g = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
784 DoCommandP(0, new_g, vindex | (_ctrl_pressed ? 1 << 31 : 0), CMD_ADD_VEHICLE_GROUP);
785 break;
788 case WID_GL_LIST_VEHICLE: { // Matrix vehicle
789 const VehicleID vindex = this->vehicle_sel;
790 this->vehicle_sel = INVALID_VEHICLE;
791 this->group_over = INVALID_GROUP;
792 this->SetDirty();
794 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
795 if (id_v >= this->vehicles.Length()) return; // click out of list bound
797 const Vehicle *v = this->vehicles[id_v];
798 if (!VehicleClicked(v) && vindex == v->index) {
799 ShowVehicleViewWindow(v);
801 break;
806 virtual void OnDragDrop(Point pt, int widget)
808 if (this->vehicle_sel != INVALID_VEHICLE) OnDragDrop_Vehicle(pt, widget);
809 if (this->group_sel != INVALID_GROUP) OnDragDrop_Group(pt, widget);
812 virtual void OnQueryTextFinished(char *str)
814 if (str != NULL) DoCommandP(0, this->group_rename, 0, CMD_ALTER_GROUP, str);
815 this->group_rename = INVALID_GROUP;
818 virtual void OnResize()
820 this->group_sb->SetCapacityFromWidget(this, WID_GL_LIST_GROUP);
821 this->vscroll->SetCapacityFromWidget(this, WID_GL_LIST_VEHICLE);
824 virtual void OnDropdownSelect(int widget, int index)
826 switch (widget) {
827 case WID_GL_SORT_BY_DROPDOWN:
828 this->vehicles.SetSortType(index);
829 break;
831 case WID_GL_MANAGE_VEHICLES_DROPDOWN:
832 assert(this->vehicles.Length() != 0);
834 switch (index) {
835 case ADI_REPLACE: // Replace window
836 ShowReplaceGroupVehicleWindow(this->vli.index, this->vli.vtype);
837 break;
838 case ADI_SERVICE: // Send for servicing
839 case ADI_DEPOT: { // Send to Depots
840 DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : 0U), this->vli.Pack(), CMD_SEND_VEHICLE_TO_DEPOT);
841 break;
844 case ADI_ADD_SHARED: // Add shared Vehicles
845 assert(Group::IsValidID(this->vli.index));
847 DoCommandP(0, this->vli.index, this->vli.vtype, CMD_ADD_SHARED_VEHICLE_GROUP);
848 break;
849 case ADI_REMOVE_ALL: // Remove all Vehicles from the selected group
850 assert(Group::IsValidID(this->vli.index));
852 DoCommandP(0, this->vli.index, 0, CMD_REMOVE_ALL_VEHICLES_GROUP);
853 break;
854 default: NOT_REACHED();
856 break;
858 default: NOT_REACHED();
861 this->SetDirty();
864 virtual void OnTick()
866 if (_pause_mode != PM_UNPAUSED) return;
867 if (this->groups.NeedResort() || this->vehicles.NeedResort()) {
868 this->SetDirty();
872 virtual void OnPlaceObjectAbort()
874 /* abort drag & drop */
875 this->vehicle_sel = INVALID_VEHICLE;
876 this->DirtyHighlightedGroupWidget();
877 this->group_over = INVALID_GROUP;
878 this->SetWidgetDirty(WID_GL_LIST_VEHICLE);
881 virtual void OnMouseDrag(Point pt, int widget)
883 if (this->vehicle_sel == INVALID_VEHICLE && this->group_sel == INVALID_GROUP) return;
885 /* A vehicle is dragged over... */
886 GroupID new_group_over = INVALID_GROUP;
887 switch (widget) {
888 case WID_GL_DEFAULT_VEHICLES: // ... the 'default' group.
889 new_group_over = DEFAULT_GROUP;
890 break;
892 case WID_GL_LIST_GROUP: { // ... the list of custom groups.
893 uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
894 new_group_over = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
895 break;
899 /* Do not highlight when dragging over the current group */
900 if (this->vehicle_sel != INVALID_VEHICLE) {
901 if (Vehicle::Get(vehicle_sel)->group_id == new_group_over) new_group_over = INVALID_GROUP;
902 } else if (this->group_sel != INVALID_GROUP) {
903 if (this->group_sel == new_group_over || Group::Get(this->group_sel)->parent == new_group_over) new_group_over = INVALID_GROUP;
906 /* Mark widgets as dirty if the group changed. */
907 if (new_group_over != this->group_over) {
908 this->DirtyHighlightedGroupWidget();
909 this->group_over = new_group_over;
910 this->DirtyHighlightedGroupWidget();
914 void ShowRenameGroupWindow(GroupID group, bool empty)
916 assert(Group::IsValidID(group));
917 this->group_rename = group;
918 /* Show empty query for new groups */
919 StringID str = STR_EMPTY;
920 if (!empty) {
921 SetDParam(0, group);
922 str = STR_GROUP_NAME;
924 ShowQueryString(str, STR_GROUP_RENAME_CAPTION, MAX_LENGTH_GROUP_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
928 * Tests whether a given vehicle is selected in the window, and unselects it if necessary.
929 * Called when the vehicle is deleted.
930 * @param vehicle Vehicle that is going to be deleted
932 void UnselectVehicle(VehicleID vehicle)
934 if (this->vehicle_sel == vehicle) ResetPointerMode();
939 static WindowDesc::Prefs _other_group_prefs ("list_groups");
941 static WindowDesc::Prefs _train_group_prefs ("list_groups_train");
943 static const WindowDesc _train_group_desc(
944 WDP_AUTO, 525, 246,
945 WC_TRAINS_LIST, WC_NONE,
947 _nested_group_widgets, lengthof(_nested_group_widgets),
948 &_train_group_prefs
951 static const WindowDesc _roadveh_group_desc(
952 WDP_AUTO, 460, 246,
953 WC_ROADVEH_LIST, WC_NONE,
955 _nested_group_widgets, lengthof(_nested_group_widgets),
956 &_other_group_prefs
959 static const WindowDesc _ship_group_desc(
960 WDP_AUTO, 460, 246,
961 WC_SHIPS_LIST, WC_NONE,
963 _nested_group_widgets, lengthof(_nested_group_widgets),
964 &_other_group_prefs
967 static const WindowDesc _aircraft_group_desc(
968 WDP_AUTO, 460, 246,
969 WC_AIRCRAFT_LIST, WC_NONE,
971 _nested_group_widgets, lengthof(_nested_group_widgets),
972 &_other_group_prefs
976 * Show the group window for the given company and vehicle type.
977 * @param company The company to show the window for.
978 * @param vehicle_type The type of vehicle to show it for.
980 void ShowCompanyGroup(CompanyID company, VehicleType vehicle_type)
982 static const WindowDesc *const descs[VEH_COMPANY_END] = {
983 &_train_group_desc, // VEH_TRAIN
984 &_roadveh_group_desc, // VEH_ROAD
985 &_ship_group_desc, // VEH_SHIP
986 &_aircraft_group_desc, // VEH_AIRCRAFT
989 if (!Company::IsValidID(company)) return;
991 WindowNumber num = VehicleListIdentifier(VL_GROUP_LIST, vehicle_type, company).Pack();
992 AllocateWindowDescFront<VehicleGroupWindow> (descs[vehicle_type], num);
996 * Finds a group list window determined by vehicle type and owner
997 * @param vt vehicle type
998 * @param owner owner of groups
999 * @return pointer to VehicleGroupWindow, NULL if not found
1001 static inline VehicleGroupWindow *FindVehicleGroupWindow(VehicleType vt, Owner owner)
1003 return (VehicleGroupWindow *)FindWindowById(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, owner).Pack());
1007 * Opens a 'Rename group' window for a newly created group
1008 * @param type the vehicle type to open the window for
1010 static void ShowRenameNewGroupWindow(VehicleType type)
1012 VehicleGroupWindow *w = FindVehicleGroupWindow(type, _current_company);
1013 if (w != NULL) w->ShowRenameGroupWindow(_new_group_id, true);
1017 * Opens a 'Rename group' window for newly created group
1018 * @param success did command succeed?
1019 * @param tile unused
1020 * @param p1 vehicle type
1021 * @param p2 unused
1022 * @see CmdCreateGroup
1024 void CcCreateGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
1026 if (result.Failed()) return;
1027 assert(p1 <= VEH_AIRCRAFT);
1029 ShowRenameNewGroupWindow((VehicleType)p1);
1033 * Open rename window after adding a vehicle to a new group via drag and drop.
1034 * @param success Did command succeed?
1035 * @param tile Unused.
1036 * @param p1 Group the vehicle was added to.
1037 * @param p2 Bit 0-19: Vehicle ID.
1039 void CcAddVehicleGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
1041 if (result.Failed()) return;
1042 if (p1 != NEW_GROUP) return;
1043 assert(Vehicle::IsValidID(GB(p2, 0, 20)));
1045 ShowRenameNewGroupWindow(Vehicle::Get(GB(p2, 0, 20))->type);
1049 * Get the error string when altering a group.
1050 * @param tile unused
1051 * @param flags type of operation
1052 * @param p1 index of array group
1053 * - p1 bit 0-15 : GroupID
1054 * - p1 bit 16: 0 - Rename group
1055 * 1 - Set group parent
1056 * @param p2 parent group index
1057 * @param text the new name or an empty string when resetting to the default
1059 StringID GetErrAlterGroup (TileIndex tile, uint32 p1, uint32 p2, const char *text)
1061 return HasBit(p1, 16) ? STR_ERROR_GROUP_CAN_T_SET_PARENT : STR_ERROR_GROUP_CAN_T_RENAME;
1065 * Removes the highlight of a vehicle in a group window
1066 * @param *v Vehicle to remove all highlights from
1068 void DeleteGroupHighlightOfVehicle(const Vehicle *v)
1070 /* If we haven't got any vehicles on the mouse pointer, we haven't got any highlighted in any group windows either
1071 * If that is the case, we can skip looping though the windows and save time
1073 if (_pointer_mode != POINTER_DRAG) return;
1075 VehicleGroupWindow *w = FindVehicleGroupWindow(v->type, v->owner);
1076 if (w != NULL) w->UnselectVehicle(v->index);