# group_gui.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "textbuf_gui.h"
    00014 #include "command_func.h"
    00015 #include "vehicle_gui.h"
    00016 #include "vehicle_base.h"
    00017 #include "string_func.h"
    00018 #include "strings_func.h"
    00019 #include "window_func.h"
    00020 #include "vehicle_func.h"
    00021 #include "autoreplace_gui.h"
    00022 #include "company_func.h"
    00023 #include "widgets/dropdown_func.h"
    00024 #include "tilehighlight_func.h"
    00025 #include "vehicle_gui_base.h"
    00026 #include "core/geometry_func.hpp"
    00027 #include "company_base.h"
    00028
    00029 #include "widgets/group_widget.h"
    00030
    00031 #include "table/sprites.h"
    00032
    00033 typedef GUIList<const Group*> GUIGroupList;
    00034
    00035 static const NWidgetPart _nested_group_widgets[] = {
    00036   NWidget(NWID_HORIZONTAL), // Window header
    00037     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
    00038     NWidget(WWT_CAPTION, COLOUR_GREY, WID_GL_CAPTION),
    00039     NWidget(WWT_SHADEBOX, COLOUR_GREY),
    00040     NWidget(WWT_STICKYBOX, COLOUR_GREY),
    00041   EndContainer(),
    00042   NWidget(NWID_HORIZONTAL),
    00043     /* left part */
    00044     NWidget(NWID_VERTICAL),
    00045       NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetFill(1, 0), EndContainer(),
    00046       NWidget(WWT_PANEL, COLOUR_GREY, WID_GL_ALL_VEHICLES), SetFill(1, 0), EndContainer(),
    00047       NWidget(WWT_PANEL, COLOUR_GREY, WID_GL_DEFAULT_VEHICLES), SetFill(1, 0), EndContainer(),
    00048       NWidget(NWID_HORIZONTAL),
    00049         NWidget(WWT_MATRIX, COLOUR_GREY, WID_GL_LIST_GROUP), SetDataTip(0x701, STR_GROUPS_CLICK_ON_GROUP_FOR_TOOLTIP),
    00050             SetFill(1, 0), SetResize(0, 1), SetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR),
    00051         NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_GL_LIST_GROUP_SCROLLBAR),
    00052       EndContainer(),
    00053       NWidget(NWID_HORIZONTAL),
    00054         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_CREATE_GROUP), SetFill(0, 1),
    00055             SetDataTip(SPR_GROUP_CREATE_TRAIN, STR_GROUP_CREATE_TOOLTIP),
    00056         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_DELETE_GROUP), SetFill(0, 1),
    00057             SetDataTip(SPR_GROUP_DELETE_TRAIN, STR_GROUP_DELETE_TOOLTIP),
    00058         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_RENAME_GROUP), SetFill(0, 1),
    00059             SetDataTip(SPR_GROUP_RENAME_TRAIN, STR_GROUP_RENAME_TOOLTIP),
    00060         NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(),
    00061         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_REPLACE_PROTECTION), SetFill(0, 1),
    00062             SetDataTip(SPR_GROUP_REPLACE_OFF_TRAIN, STR_GROUP_REPLACE_PROTECTION_TOOLTIP),
    00063       EndContainer(),
    00064     EndContainer(),
    00065     /* right part */
    00066     NWidget(NWID_VERTICAL),
    00067       NWidget(NWID_HORIZONTAL),
    00068         NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_SORT_BY_ORDER), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
    00069         NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_SORT_BY_DROPDOWN), SetMinimalSize(167, 12), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA),
    00070         NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetResize(1, 0), EndContainer(),
    00071       EndContainer(),
    00072       NWidget(NWID_HORIZONTAL),
    00073         NWidget(WWT_MATRIX, COLOUR_GREY, WID_GL_LIST_VEHICLE), SetMinimalSize(248, 0), SetDataTip(0x701, STR_NULL), SetResize(1, 1), SetFill(1, 0), SetScrollbar(WID_GL_LIST_VEHICLE_SCROLLBAR),
    00074         NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_GL_LIST_VEHICLE_SCROLLBAR),
    00075       EndContainer(),
    00076       NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(1, 0), SetFill(1, 1), SetResize(1, 0), EndContainer(),
    00077       NWidget(NWID_HORIZONTAL),
    00078         NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
    00079             SetDataTip(STR_BLACK_STRING, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
    00080         NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 1), SetResize(1, 0), EndContainer(),
    00081         NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(0, 1),
    00082             SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
    00083         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
    00084             SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
    00085         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
    00086             SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
    00087         NWidget(WWT_RESIZEBOX, COLOUR_GREY),
    00088       EndContainer(),
    00089     EndContainer(),
    00090   EndContainer(),
    00091 };
    00092
    00093 class VehicleGroupWindow : public BaseVehicleListWindow {
    00094 private:
    00095   /* Columns in the group list */
    00096   enum ListColumns {
    00097     VGC_NAME,
    00098     VGC_PROTECT,
    00099     VGC_AUTOREPLACE,
    00100     VGC_PROFIT,
    00101     VGC_NUMBER,
    00102
    00103     VGC_END
    00104   };
    00105
    00106   VehicleID vehicle_sel;
    00107   GroupID group_rename;
    00108   GroupID group_over;
    00109   GUIGroupList groups;
    00110   uint tiny_step_height;
    00111   Scrollbar *group_sb;
    00112
    00113   Dimension column_size[VGC_END];
    00114
    00120   void BuildGroupList(Owner owner)
    00121   {
    00122     if (!this->groups.NeedRebuild()) return;
    00123
    00124     this->groups.Clear();
    00125
    00126     const Group *g;
    00127     FOR_ALL_GROUPS(g) {
    00128       if (g->owner == owner && g->vehicle_type == this->vli.vtype) {
    00129         *this->groups.Append() = g;
    00130       }
    00131     }
    00132
    00133     this->groups.Compact();
    00134     this->groups.RebuildDone();
    00135   }
    00136
    00138   static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b)
    00139   {
    00140     static const Group *last_group[2] = { NULL, NULL };
    00141     static char         last_name[2][64] = { "", "" };
    00142
    00143     if (*a != last_group[0]) {
    00144       last_group[0] = *a;
    00145       SetDParam(0, (*a)->index);
    00146       GetString(last_name[0], STR_GROUP_NAME, lastof(last_name[0]));
    00147     }
    00148
    00149     if (*b != last_group[1]) {
    00150       last_group[1] = *b;
    00151       SetDParam(0, (*b)->index);
    00152       GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1]));
    00153     }
    00154
    00155     int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
    00156     if (r == 0) return (*a)->index - (*b)->index;
    00157     return r;
    00158   }
    00159
    00164   uint ComputeGroupInfoSize()
    00165   {
    00166     this->column_size[VGC_NAME] = maxdim(GetStringBoundingBox(STR_GROUP_DEFAULT_TRAINS + this->vli.vtype), GetStringBoundingBox(STR_GROUP_ALL_TRAINS + this->vli.vtype));
    00167     this->column_size[VGC_NAME].width = max(170u, this->column_size[VGC_NAME].width);
    00168     this->tiny_step_height = this->column_size[VGC_NAME].height;
    00169
    00170     this->column_size[VGC_PROTECT] = GetSpriteSize(SPR_GROUP_REPLACE_PROTECT);
    00171     this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROTECT].height);
    00172
    00173     this->column_size[VGC_AUTOREPLACE] = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
    00174     this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_AUTOREPLACE].height);
    00175
    00176     this->column_size[VGC_PROFIT].width = 0;
    00177     this->column_size[VGC_PROFIT].height = 0;
    00178     static const SpriteID profit_sprites[] = {SPR_PROFIT_NA, SPR_PROFIT_NEGATIVE, SPR_PROFIT_SOME, SPR_PROFIT_LOT};
    00179     for (uint i = 0; i < lengthof(profit_sprites); i++) {
    00180       Dimension d = GetSpriteSize(profit_sprites[i]);
    00181       this->column_size[VGC_PROFIT] = maxdim(this->column_size[VGC_PROFIT], d);
    00182     }
    00183     this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROFIT].height);
    00184
    00185     SetDParam(0, GroupStatistics::Get(this->vli.company, ALL_GROUP, this->vli.vtype).num_vehicle > 900 ? 9999 : 999);
    00186     this->column_size[VGC_NUMBER] = GetStringBoundingBox(STR_TINY_COMMA);
    00187     this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_NUMBER].height);
    00188
    00189     this->tiny_step_height += WD_MATRIX_TOP;
    00190
    00191     return WD_FRAMERECT_LEFT + 8 +
    00192       this->column_size[VGC_NAME].width + 8 +
    00193       this->column_size[VGC_PROTECT].width + 2 +
    00194       this->column_size[VGC_AUTOREPLACE].width + 2 +
    00195       this->column_size[VGC_PROFIT].width + 2 +
    00196       this->column_size[VGC_NUMBER].width + 2 +
    00197       WD_FRAMERECT_RIGHT;
    00198   }
    00199
    00208   void DrawGroupInfo(int y, int left, int right, GroupID g_id, bool protection = false) const
    00209   {
    00210     /* Highlight the group if a vehicle is dragged over it */
    00211     if (g_id == this->group_over) {
    00212       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]);
    00213     }
    00214
    00215     if (g_id == NEW_GROUP) return;
    00216
    00217     /* draw the selected group in white, else we draw it in black */
    00218     TextColour colour = g_id == this->vli.index ? TC_WHITE : TC_BLACK;
    00219     const GroupStatistics &stats = GroupStatistics::Get(this->vli.company, g_id, this->vli.vtype);
    00220     bool rtl = _current_text_dir == TD_RTL;
    00221
    00222     /* draw group name */
    00223     StringID str;
    00224     if (IsAllGroupID(g_id)) {
    00225       str = STR_GROUP_ALL_TRAINS + this->vli.vtype;
    00226     } else if (IsDefaultGroupID(g_id)) {
    00227       str = STR_GROUP_DEFAULT_TRAINS + this->vli.vtype;
    00228     } else {
    00229       SetDParam(0, g_id);
    00230       str = STR_GROUP_NAME;
    00231     }
    00232     int x = rtl ? right - WD_FRAMERECT_RIGHT - 8 - this->column_size[VGC_NAME].width + 1 : left + WD_FRAMERECT_LEFT + 8;
    00233     DrawString(x, x + this->column_size[VGC_NAME].width - 1, y + (this->tiny_step_height - this->column_size[VGC_NAME].height) / 2, str, colour);
    00234
    00235     /* draw autoreplace protection */
    00236     x = rtl ? x - 8 - this->column_size[VGC_PROTECT].width : x + 8 + this->column_size[VGC_NAME].width;
    00237     if (protection) DrawSprite(SPR_GROUP_REPLACE_PROTECT, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROTECT].height) / 2);
    00238
    00239     /* draw autoreplace status */
    00240     x = rtl ? x - 2 - this->column_size[VGC_AUTOREPLACE].width : x + 2 + this->column_size[VGC_PROTECT].width;
    00241     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);
    00242
    00243     /* draw the profit icon */
    00244     x = rtl ? x - 2 - this->column_size[VGC_PROFIT].width : x + 2 + this->column_size[VGC_AUTOREPLACE].width;
    00245     SpriteID spr;
    00246     if (stats.num_profit_vehicle == 0) {
    00247       spr = SPR_PROFIT_NA;
    00248     } else if (stats.profit_last_year < 0) {
    00249       spr = SPR_PROFIT_NEGATIVE;
    00250     } else if (stats.profit_last_year < 10000 * stats.num_profit_vehicle) { // TODO magic number
    00251       spr = SPR_PROFIT_SOME;
    00252     } else {
    00253       spr = SPR_PROFIT_LOT;
    00254     }
    00255     DrawSprite(spr, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROFIT].height) / 2);
    00256
    00257     /* draw the number of vehicles of the group */
    00258     x = rtl ? x - 2 - this->column_size[VGC_NUMBER].width : x + 2 + this->column_size[VGC_PROFIT].width;
    00259     SetDParam(0, stats.num_vehicle);
    00260     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);
    00261   }
    00262
    00266   void DirtyHighlightedGroupWidget()
    00267   {
    00268     if (this->group_over == INVALID_GROUP) return;
    00269
    00270     if (IsAllGroupID(this->group_over)) {
    00271       this->SetWidgetDirty(WID_GL_ALL_VEHICLES);
    00272     } else if (IsDefaultGroupID(this->group_over)) {
    00273       this->SetWidgetDirty(WID_GL_DEFAULT_VEHICLES);
    00274     } else {
    00275       this->SetWidgetDirty(WID_GL_LIST_GROUP);
    00276     }
    00277   }
    00278
    00279 public:
    00280   VehicleGroupWindow(const WindowDesc *desc, WindowNumber window_number) : BaseVehicleListWindow(window_number)
    00281   {
    00282     this->CreateNestedTree(desc);
    00283
    00284     this->vscroll = this->GetScrollbar(WID_GL_LIST_VEHICLE_SCROLLBAR);
    00285     this->group_sb = this->GetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR);
    00286
    00287     switch (this->vli.vtype) {
    00288       default: NOT_REACHED();
    00289       case VEH_TRAIN:    this->sorting = &_sorting.train;    break;
    00290       case VEH_ROAD:     this->sorting = &_sorting.roadveh;  break;
    00291       case VEH_SHIP:     this->sorting = &_sorting.ship;     break;
    00292       case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
    00293     }
    00294
    00295     this->vli.index = ALL_GROUP;
    00296     this->vehicle_sel = INVALID_VEHICLE;
    00297     this->group_rename = INVALID_GROUP;
    00298     this->group_over = INVALID_GROUP;
    00299
    00300     this->vehicles.SetListing(*this->sorting);
    00301     this->vehicles.ForceRebuild();
    00302     this->vehicles.NeedResort();
    00303
    00304     this->BuildVehicleList();
    00305     this->SortVehicleList();
    00306
    00307     this->groups.ForceRebuild();
    00308     this->groups.NeedResort();
    00309     this->BuildGroupList(vli.company);
    00310     this->groups.Sort(&GroupNameSorter);
    00311
    00312     this->GetWidget<NWidgetCore>(WID_GL_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vli.vtype;
    00313     this->GetWidget<NWidgetCore>(WID_GL_LIST_VEHICLE)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vli.vtype;
    00314
    00315     this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data += this->vli.vtype;
    00316     this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data += this->vli.vtype;
    00317     this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data += this->vli.vtype;
    00318     this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data += this->vli.vtype;
    00319
    00320     this->FinishInitNested(desc, window_number);
    00321     this->owner = vli.company;
    00322   }
    00323
    00324   ~VehicleGroupWindow()
    00325   {
    00326     *this->sorting = this->vehicles.GetListing();
    00327   }
    00328
    00329   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
    00330   {
    00331     switch (widget) {
    00332       case WID_GL_LIST_GROUP: {
    00333         size->width = this->ComputeGroupInfoSize();
    00334         resize->height = this->tiny_step_height;
    00335
    00336         /* Minimum height is the height of the list widget minus all and default vehicles... */
    00337         size->height =  4 * GetVehicleListHeight(this->vli.vtype, this->tiny_step_height) - 2 * this->tiny_step_height;
    00338
    00339         /* ... minus the buttons at the bottom ... */
    00340         uint max_icon_height = GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_CREATE_GROUP)->widget_data).height;
    00341         max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_RENAME_GROUP)->widget_data).height);
    00342         max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_DELETE_GROUP)->widget_data).height);
    00343         max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data).height);
    00344
    00345         /* Get a multiple of tiny_step_height of that amount */
    00346         size->height = Ceil(size->height - max_icon_height, tiny_step_height);
    00347         break;
    00348       }
    00349
    00350       case WID_GL_ALL_VEHICLES:
    00351       case WID_GL_DEFAULT_VEHICLES:
    00352         size->width = this->ComputeGroupInfoSize();
    00353         size->height = this->tiny_step_height;
    00354         break;
    00355
    00356       case WID_GL_SORT_BY_ORDER: {
    00357         Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
    00358         d.width += padding.width + WD_SORTBUTTON_ARROW_WIDTH * 2; // Doubled since the string is centred and it also looks better.
    00359         d.height += padding.height;
    00360         *size = maxdim(*size, d);
    00361         break;
    00362       }
    00363
    00364       case WID_GL_LIST_VEHICLE:
    00365         this->ComputeGroupInfoSize();
    00366         resize->height = GetVehicleListHeight(this->vli.vtype, this->tiny_step_height);
    00367         size->height = 4 * resize->height;
    00368         break;
    00369
    00370       case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
    00371         Dimension d = this->GetActionDropdownSize(true, true);
    00372         d.height += padding.height;
    00373         d.width  += padding.width;
    00374         *size = maxdim(*size, d);
    00375         break;
    00376       }
    00377     }
    00378   }
    00379
    00385   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
    00386   {
    00387     if (data == 0) {
    00388       /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
    00389       this->vehicles.ForceRebuild();
    00390       this->groups.ForceRebuild();
    00391     } else {
    00392       this->vehicles.ForceResort();
    00393       this->groups.ForceResort();
    00394     }
    00395
    00396     /* Process ID-invalidation in command-scope as well */
    00397     if (this->group_rename != INVALID_GROUP && !Group::IsValidID(this->group_rename)) {
    00398       DeleteWindowByClass(WC_QUERY_STRING);
    00399       this->group_rename = INVALID_GROUP;
    00400     }
    00401
    00402     if (!(IsAllGroupID(this->vli.index) || IsDefaultGroupID(this->vli.index) || Group::IsValidID(this->vli.index))) {
    00403       this->vli.index = ALL_GROUP;
    00404       HideDropDownMenu(this);
    00405     }
    00406     this->SetDirty();
    00407   }
    00408
    00409   virtual void SetStringParameters(int widget) const
    00410   {
    00411     switch (widget) {
    00412       case WID_GL_AVAILABLE_VEHICLES:
    00413         SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype);
    00414         break;
    00415
    00416       case WID_GL_CAPTION:
    00417         /* If selected_group == DEFAULT_GROUP || ALL_GROUP, draw the standard caption
    00418          * We list all vehicles or ungrouped vehicles */
    00419         if (IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index)) {
    00420           SetDParam(0, STR_COMPANY_NAME);
    00421           SetDParam(1, this->vli.company);
    00422           SetDParam(2, this->vehicles.Length());
    00423           SetDParam(3, this->vehicles.Length());
    00424         } else {
    00425           const Group *g = Group::Get(this->vli.index);
    00426
    00427           SetDParam(0, STR_GROUP_NAME);
    00428           SetDParam(1, g->index);
    00429           SetDParam(2, g->statistics.num_vehicle);
    00430           SetDParam(3, g->statistics.num_vehicle);
    00431         }
    00432         break;
    00433     }
    00434   }
    00435
    00436   virtual void OnPaint()
    00437   {
    00438     /* If we select the all vehicles, this->list will contain all vehicles of the owner
    00439      * else this->list will contain all vehicles which belong to the selected group */
    00440     this->BuildVehicleList();
    00441     this->SortVehicleList();
    00442
    00443     this->BuildGroupList(this->owner);
    00444     this->groups.Sort(&GroupNameSorter);
    00445
    00446     this->group_sb->SetCount(this->groups.Length());
    00447     this->vscroll->SetCount(this->vehicles.Length());
    00448
    00449     /* The drop down menu is out, *but* it may not be used, retract it. */
    00450     if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_GL_MANAGE_VEHICLES_DROPDOWN)) {
    00451       this->RaiseWidget(WID_GL_MANAGE_VEHICLES_DROPDOWN);
    00452       HideDropDownMenu(this);
    00453     }
    00454
    00455     /* Disable all lists management button when the list is empty */
    00456     this->SetWidgetsDisabledState(this->vehicles.Length() == 0 || _local_company != this->vli.company,
    00457         WID_GL_STOP_ALL,
    00458         WID_GL_START_ALL,
    00459         WID_GL_MANAGE_VEHICLES_DROPDOWN,
    00460         WIDGET_LIST_END);
    00461
    00462     /* Disable the group specific function when we select the default group or all vehicles */
    00463     this->SetWidgetsDisabledState(IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index) || _local_company != this->vli.company,
    00464         WID_GL_DELETE_GROUP,
    00465         WID_GL_RENAME_GROUP,
    00466         WID_GL_REPLACE_PROTECTION,
    00467         WIDGET_LIST_END);
    00468
    00469     /* Disable remaining buttons for non-local companies
    00470      * Needed while changing _local_company, eg. by cheats
    00471      * All procedures (eg. move vehicle to another group)
    00472      *  verify, whether you are the owner of the vehicle,
    00473      *  so it doesn't have to be disabled
    00474      */
    00475     this->SetWidgetsDisabledState(_local_company != this->vli.company,
    00476         WID_GL_CREATE_GROUP,
    00477         WID_GL_AVAILABLE_VEHICLES,
    00478         WIDGET_LIST_END);
    00479
    00480     /* If not a default group and the group has replace protection, show an enabled replace sprite. */
    00481     uint16 protect_sprite = SPR_GROUP_REPLACE_OFF_TRAIN;
    00482     if (!IsDefaultGroupID(this->vli.index) && !IsAllGroupID(this->vli.index) && Group::Get(this->vli.index)->replace_protection) protect_sprite = SPR_GROUP_REPLACE_ON_TRAIN;
    00483     this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data = protect_sprite + this->vli.vtype;
    00484
    00485     /* Set text of sort by dropdown */
    00486     this->GetWidget<NWidgetCore>(WID_GL_SORT_BY_DROPDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
    00487
    00488     this->DrawWidgets();
    00489   }
    00490
    00491   virtual void DrawWidget(const Rect &r, int widget) const
    00492   {
    00493     switch (widget) {
    00494       case WID_GL_ALL_VEHICLES:
    00495         DrawGroupInfo(r.top + WD_FRAMERECT_TOP, r.left, r.right, ALL_GROUP);
    00496         break;
    00497
    00498       case WID_GL_DEFAULT_VEHICLES:
    00499         DrawGroupInfo(r.top + WD_FRAMERECT_TOP, r.left, r.right, DEFAULT_GROUP);
    00500         break;
    00501
    00502       case WID_GL_LIST_GROUP: {
    00503         int y1 = r.top + WD_FRAMERECT_TOP;
    00504         int max = min(this->group_sb->GetPosition() + this->group_sb->GetCapacity(), this->groups.Length());
    00505         for (int i = this->group_sb->GetPosition(); i < max; ++i) {
    00506           const Group *g = this->groups[i];
    00507
    00508           assert(g->owner == this->owner);
    00509
    00510           DrawGroupInfo(y1, r.left, r.right, g->index, g->replace_protection);
    00511
    00512           y1 += this->tiny_step_height;
    00513         }
    00514         if ((uint)this->group_sb->GetPosition() + this->group_sb->GetCapacity() > this->groups.Length()) {
    00515           DrawGroupInfo(y1, r.left, r.right, NEW_GROUP);
    00516         }
    00517         break;
    00518       }
    00519
    00520       case WID_GL_SORT_BY_ORDER:
    00521         this->DrawSortButtonState(WID_GL_SORT_BY_ORDER, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
    00522         break;
    00523
    00524       case WID_GL_LIST_VEHICLE:
    00525         this->DrawVehicleListItems(this->vehicle_sel, this->resize.step_height, r);
    00526         break;
    00527     }
    00528   }
    00529
    00530   virtual void OnClick(Point pt, int widget, int click_count)
    00531   {
    00532     switch (widget) {
    00533       case WID_GL_SORT_BY_ORDER: // Flip sorting method ascending/descending
    00534         this->vehicles.ToggleSortOrder();
    00535         this->SetDirty();
    00536         break;
    00537
    00538       case WID_GL_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu
    00539         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));
    00540         return;
    00541
    00542       case WID_GL_ALL_VEHICLES: // All vehicles button
    00543         if (!IsAllGroupID(this->vli.index)) {
    00544           this->vli.index = ALL_GROUP;
    00545           this->vehicles.ForceRebuild();
    00546           this->SetDirty();
    00547         }
    00548         break;
    00549
    00550       case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles button
    00551         if (!IsDefaultGroupID(this->vli.index)) {
    00552           this->vli.index = DEFAULT_GROUP;
    00553           this->vehicles.ForceRebuild();
    00554           this->SetDirty();
    00555         }
    00556         break;
    00557
    00558       case WID_GL_LIST_GROUP: { // Matrix Group
    00559         uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
    00560         if (id_g >= this->groups.Length()) return;
    00561
    00562         this->vli.index = this->groups[id_g]->index;
    00563
    00564         this->vehicles.ForceRebuild();
    00565         this->SetDirty();
    00566         break;
    00567       }
    00568
    00569       case WID_GL_LIST_VEHICLE: { // Matrix Vehicle
    00570         uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
    00571         if (id_v >= this->vehicles.Length()) return; // click out of list bound
    00572
    00573         const Vehicle *v = this->vehicles[id_v];
    00574         if (VehicleClicked(v)) break;
    00575
    00576         this->vehicle_sel = v->index;
    00577
    00578         int image = v->GetImage(_current_text_dir == TD_RTL ? DIR_E : DIR_W, EIT_IN_LIST);
    00579         SetObjectToPlaceWnd(image, GetVehiclePalette(v), HT_DRAG, this);
    00580         _cursor.vehchain = true;
    00581
    00582         this->SetDirty();
    00583         break;
    00584       }
    00585
    00586       case WID_GL_CREATE_GROUP: { // Create a new group
    00587         DoCommandP(0, this->vli.vtype, 0, CMD_CREATE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_CREATE), CcCreateGroup);
    00588         break;
    00589       }
    00590
    00591       case WID_GL_DELETE_GROUP: { // Delete the selected group
    00592         GroupID group = this->vli.index;
    00593         this->vli.index = ALL_GROUP;
    00594
    00595         DoCommandP(0, group, 0, CMD_DELETE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_DELETE));
    00596         break;
    00597       }
    00598
    00599       case WID_GL_RENAME_GROUP: // Rename the selected roup
    00600         this->ShowRenameGroupWindow(this->vli.index, false);
    00601         break;
    00602
    00603       case WID_GL_AVAILABLE_VEHICLES:
    00604         ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
    00605         break;
    00606
    00607       case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
    00608         DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index));
    00609         ShowDropDownList(this, list, 0, WID_GL_MANAGE_VEHICLES_DROPDOWN);
    00610         break;
    00611       }
    00612
    00613       case WID_GL_START_ALL:
    00614       case WID_GL_STOP_ALL: { // Start/stop all vehicles of the list
    00615         DoCommandP(0, (1 << 1) | (widget == WID_GL_START_ALL ? (1 << 0) : 0), this->vli.Pack(), CMD_MASS_START_STOP);
    00616         break;
    00617       }
    00618
    00619       case WID_GL_REPLACE_PROTECTION: {
    00620         const Group *g = Group::GetIfValid(this->vli.index);
    00621         if (g != NULL) {
    00622           DoCommandP(0, this->vli.index, !g->replace_protection, CMD_SET_GROUP_REPLACE_PROTECTION);
    00623         }
    00624         break;
    00625       }
    00626     }
    00627   }
    00628
    00629   virtual void OnDragDrop(Point pt, int widget)
    00630   {
    00631     switch (widget) {
    00632       case WID_GL_DEFAULT_VEHICLES: // Ungrouped vehicles
    00633         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));
    00634
    00635         this->vehicle_sel = INVALID_VEHICLE;
    00636         this->group_over = INVALID_GROUP;
    00637
    00638         this->SetDirty();
    00639         break;
    00640
    00641       case WID_GL_LIST_GROUP: { // Matrix group
    00642         const VehicleID vindex = this->vehicle_sel;
    00643         this->vehicle_sel = INVALID_VEHICLE;
    00644         this->group_over = INVALID_GROUP;
    00645         this->SetDirty();
    00646
    00647         uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
    00648         GroupID new_g = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
    00649
    00650         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);
    00651         break;
    00652       }
    00653
    00654       case WID_GL_LIST_VEHICLE: { // Matrix vehicle
    00655         const VehicleID vindex = this->vehicle_sel;
    00656         this->vehicle_sel = INVALID_VEHICLE;
    00657         this->group_over = INVALID_GROUP;
    00658         this->SetDirty();
    00659
    00660         uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE);
    00661         if (id_v >= this->vehicles.Length()) return; // click out of list bound
    00662
    00663         const Vehicle *v = this->vehicles[id_v];
    00664         if (!VehicleClicked(v) && vindex == v->index) {
    00665           ShowVehicleViewWindow(v);
    00666         }
    00667         break;
    00668       }
    00669     }
    00670     _cursor.vehchain = false;
    00671   }
    00672
    00673   virtual void OnQueryTextFinished(char *str)
    00674   {
    00675     if (str != NULL) DoCommandP(0, this->group_rename, 0, CMD_RENAME_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_RENAME), NULL, str);
    00676     this->group_rename = INVALID_GROUP;
    00677   }
    00678
    00679   virtual void OnResize()
    00680   {
    00681     NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_GL_LIST_GROUP);
    00682     this->group_sb->SetCapacity(nwi->current_y / this->tiny_step_height);
    00683     nwi->widget_data = (this->group_sb->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
    00684
    00685     nwi = this->GetWidget<NWidgetCore>(WID_GL_LIST_VEHICLE);
    00686     this->vscroll->SetCapacityFromWidget(this, WID_GL_LIST_VEHICLE);
    00687     nwi->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
    00688   }
    00689
    00690   virtual void OnDropdownSelect(int widget, int index)
    00691   {
    00692     switch (widget) {
    00693       case WID_GL_SORT_BY_DROPDOWN:
    00694         this->vehicles.SetSortType(index);
    00695         break;
    00696
    00697       case WID_GL_MANAGE_VEHICLES_DROPDOWN:
    00698         assert(this->vehicles.Length() != 0);
    00699
    00700         switch (index) {
    00701           case ADI_REPLACE: // Replace window
    00702             ShowReplaceGroupVehicleWindow(this->vli.index, this->vli.vtype);
    00703             break;
    00704           case ADI_SERVICE: // Send for servicing
    00705           case ADI_DEPOT: { // Send to Depots
    00706             DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : 0U), this->vli.Pack(), GetCmdSendToDepot(this->vli.vtype));
    00707             break;
    00708           }
    00709
    00710           case ADI_ADD_SHARED: // Add shared Vehicles
    00711             assert(Group::IsValidID(this->vli.index));
    00712
    00713             DoCommandP(0, this->vli.index, this->vli.vtype, CMD_ADD_SHARED_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_SHARED_VEHICLE));
    00714             break;
    00715           case ADI_REMOVE_ALL: // Remove all Vehicles from the selected group
    00716             assert(Group::IsValidID(this->vli.index));
    00717
    00718             DoCommandP(0, this->vli.index, 0, CMD_REMOVE_ALL_VEHICLES_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_REMOVE_ALL_VEHICLES));
    00719             break;
    00720           default: NOT_REACHED();
    00721         }
    00722         break;
    00723
    00724       default: NOT_REACHED();
    00725     }
    00726
    00727     this->SetDirty();
    00728   }
    00729
    00730   virtual void OnTick()
    00731   {
    00732     if (_pause_mode != PM_UNPAUSED) return;
    00733     if (this->groups.NeedResort() || this->vehicles.NeedResort()) {
    00734       this->SetDirty();
    00735     }
    00736   }
    00737
    00738   virtual void OnPlaceObjectAbort()
    00739   {
    00740     /* abort drag & drop */
    00741     this->vehicle_sel = INVALID_VEHICLE;
    00742     this->DirtyHighlightedGroupWidget();
    00743     this->group_over = INVALID_GROUP;
    00744     this->SetWidgetDirty(WID_GL_LIST_VEHICLE);
    00745   }
    00746
    00747   virtual void OnMouseDrag(Point pt, int widget)
    00748   {
    00749     if (this->vehicle_sel == INVALID_VEHICLE) return;
    00750
    00751     /* A vehicle is dragged over... */
    00752     GroupID new_group_over = INVALID_GROUP;
    00753     switch (widget) {
    00754       case WID_GL_DEFAULT_VEHICLES: // ... the 'default' group.
    00755         new_group_over = DEFAULT_GROUP;
    00756         break;
    00757
    00758       case WID_GL_LIST_GROUP: { // ... the list of custom groups.
    00759         uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
    00760         new_group_over = id_g >= this->groups.Length() ? NEW_GROUP : this->groups[id_g]->index;
    00761         break;
    00762       }
    00763     }
    00764
    00765     /* Do not highlight when dragging over the current group */
    00766     if (Vehicle::Get(vehicle_sel)->group_id == new_group_over) new_group_over = INVALID_GROUP;
    00767
    00768     /* Mark widgets as dirty if the group changed. */
    00769     if (new_group_over != this->group_over) {
    00770       this->DirtyHighlightedGroupWidget();
    00771       this->group_over = new_group_over;
    00772       this->DirtyHighlightedGroupWidget();
    00773     }
    00774   }
    00775
    00776   void ShowRenameGroupWindow(GroupID group, bool empty)
    00777   {
    00778     assert(Group::IsValidID(group));
    00779     this->group_rename = group;
    00780     /* Show empty query for new groups */
    00781     StringID str = STR_EMPTY;
    00782     if (!empty) {
    00783       SetDParam(0, group);
    00784       str = STR_GROUP_NAME;
    00785     }
    00786     ShowQueryString(str, STR_GROUP_RENAME_CAPTION, MAX_LENGTH_GROUP_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
    00787   }
    00788
    00794   void UnselectVehicle(VehicleID vehicle)
    00795   {
    00796     if (this->vehicle_sel == vehicle) ResetObjectToPlace();
    00797   }
    00798 };
    00799
    00800
    00801 static WindowDesc _other_group_desc(
    00802   WDP_AUTO, 460, 246,
    00803   WC_INVALID, WC_NONE,
    00804   0,
    00805   _nested_group_widgets, lengthof(_nested_group_widgets)
    00806 );
    00807
    00808 static const WindowDesc _train_group_desc(
    00809   WDP_AUTO, 525, 246,
    00810   WC_TRAINS_LIST, WC_NONE,
    00811   0,
    00812   _nested_group_widgets, lengthof(_nested_group_widgets)
    00813 );
    00814
    00820 void ShowCompanyGroup(CompanyID company, VehicleType vehicle_type)
    00821 {
    00822   if (!Company::IsValidID(company)) return;
    00823
    00824   WindowNumber num = VehicleListIdentifier(VL_GROUP_LIST, vehicle_type, company).Pack();
    00825   if (vehicle_type == VEH_TRAIN) {
    00826     AllocateWindowDescFront<VehicleGroupWindow>(&_train_group_desc, num);
    00827   } else {
    00828     _other_group_desc.cls = GetWindowClassForVehicleType(vehicle_type);
    00829     AllocateWindowDescFront<VehicleGroupWindow>(&_other_group_desc, num);
    00830   }
    00831 }
    00832
    00839 static inline VehicleGroupWindow *FindVehicleGroupWindow(VehicleType vt, Owner owner)
    00840 {
    00841   return (VehicleGroupWindow *)FindWindowById(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, owner).Pack());
    00842 }
    00843
    00852 void CcCreateGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
    00853 {
    00854   if (result.Failed()) return;
    00855   assert(p1 <= VEH_AIRCRAFT);
    00856
    00857   VehicleGroupWindow *w = FindVehicleGroupWindow((VehicleType)p1, _current_company);
    00858   if (w != NULL) w->ShowRenameGroupWindow(_new_group_id, true);
    00859 }
    00860
    00868 void CcAddVehicleNewGroup(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
    00869 {
    00870   if (result.Failed()) return;
    00871   assert(Vehicle::IsValidID(GB(p2, 0, 20)));
    00872
    00873   CcCreateGroup(result, 0, Vehicle::Get(GB(p2, 0, 20))->type, 0);
    00874 }
    00875
    00880 void DeleteGroupHighlightOfVehicle(const Vehicle *v)
    00881 {
    00882   /* If we haven't got any vehicles on the mouse pointer, we haven't got any highlighted in any group windows either
    00883    * If that is the case, we can skip looping though the windows and save time
    00884    */
    00885   if (_special_mouse_mode != WSM_DRAGDROP) return;
    00886
    00887   VehicleGroupWindow *w = FindVehicleGroupWindow(v->type, v->owner);
    00888   if (w != NULL) w->UnselectVehicle(v->index);
    00889 }