From 0d303d6c3fd2b514c6f941af2fc481f0db0283d9 Mon Sep 17 00:00:00 2001 From: stormcone <48624099+stormcone@users.noreply.github.com> Date: Tue, 8 Nov 2022 21:11:16 +0100 Subject: [PATCH] Feature: Add cargo filter support to vehicle list. (#8308) --- src/group_gui.cpp | 18 ++++- src/lang/english.txt | 5 ++ src/vehicle_gui.cpp | 135 +++++++++++++++++++++++++++++++++++ src/vehicle_gui_base.h | 33 ++++++--- src/widgets/group_widget.h | 1 + src/widgets/vehicle_widget.h | 2 + 6 files changed, 183 insertions(+), 11 deletions(-) diff --git a/src/group_gui.cpp b/src/group_gui.cpp index 197be9e2e7..b463a236b8 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -81,6 +81,7 @@ static const NWidgetPart _nested_group_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_SORT_BY_ORDER), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_SORT_BY_DROPDOWN), SetMinimalSize(167, 12), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_FILTER_BY_CARGO), SetMinimalSize(167, 12), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA), NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetResize(1, 0), EndContainer(), EndContainer(), NWidget(NWID_HORIZONTAL), @@ -118,7 +119,6 @@ private: VGC_END }; - VehicleID vehicle_sel; ///< Selected vehicle GroupID group_sel; ///< Selected group (for drag/drop) GroupID group_rename; ///< Group being renamed, INVALID_GROUP if none GroupID group_over; ///< Group over which a vehicle is dragged, INVALID_GROUP if none @@ -349,7 +349,6 @@ public: this->group_sb = this->GetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR); this->vli.index = ALL_GROUP; - this->vehicle_sel = INVALID_VEHICLE; this->group_sel = INVALID_GROUP; this->group_rename = INVALID_GROUP; this->group_over = INVALID_GROUP; @@ -451,6 +450,10 @@ public: void SetStringParameters(int widget) const override { switch (widget) { + case WID_GL_FILTER_BY_CARGO: + SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]); + break; + case WID_GL_AVAILABLE_VEHICLES: SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype); break; @@ -530,6 +533,9 @@ public: /* Set text of "sort by" dropdown widget. */ this->GetWidget(WID_GL_SORT_BY_DROPDOWN)->widget_data = this->GetVehicleSorterNames()[this->vehgroups.SortType()]; + /* Set text of filter by cargo dropdown */ + this->GetWidget(WID_GL_FILTER_BY_CARGO)->widget_data = this->cargo_filter_texts[this->cargo_filter_criteria]; + this->DrawWidgets(); } @@ -647,6 +653,10 @@ public: ShowDropDownMenu(this, this->GetVehicleSorterNames(), this->vehgroups.SortType(), WID_GL_SORT_BY_DROPDOWN, 0, (this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : (1 << 10)); return; + case WID_GL_FILTER_BY_CARGO: // Select filtering criteria dropdown menu + ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_GL_FILTER_BY_CARGO, 0, 0); + break; + case WID_GL_ALL_VEHICLES: // All vehicles button if (!IsAllGroupID(this->vli.index)) { this->vli.index = ALL_GROUP; @@ -930,6 +940,10 @@ public: this->vehgroups.SetSortType(index); break; + case WID_GL_FILTER_BY_CARGO: // Select a cargo filter criteria + this->SetCargoFilterIndex(index); + break; + case WID_GL_MANAGE_VEHICLES_DROPDOWN: assert(this->vehicles.size() != 0); diff --git a/src/lang/english.txt b/src/lang/english.txt index f8ef8cb4fe..8983b60402 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3884,6 +3884,11 @@ STR_PURCHASE_INFO_MAX_TE :{BLACK}Max. Tra STR_PURCHASE_INFO_AIRCRAFT_RANGE :{BLACK}Range: {GOLD}{COMMA} tiles STR_PURCHASE_INFO_AIRCRAFT_TYPE :{BLACK}Aircraft type: {GOLD}{STRING} +###length 3 +STR_CARGO_TYPE_FILTER_ALL :All cargo types +STR_CARGO_TYPE_FILTER_FREIGHT :Freight +STR_CARGO_TYPE_FILTER_NONE :None + ###length VEHICLE_TYPES STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP :{BLACK}Train vehicle selection list. Click on vehicle for information. Ctrl+Click for toggling hiding of the vehicle type STR_BUY_VEHICLE_ROAD_VEHICLE_LIST_TOOLTIP :{BLACK}Road vehicle selection list. Click on vehicle for information. Ctrl+Click for toggling hiding of the vehicle type diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index c840a09d71..789dd37929 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -139,6 +139,7 @@ const StringID BaseVehicleListWindow::vehicle_depot_name[] = { BaseVehicleListWindow::BaseVehicleListWindow(WindowDesc *desc, WindowNumber wno) : Window(desc), vli(VehicleListIdentifier::UnPack(wno)) { + this->vehicle_sel = INVALID_VEHICLE; this->grouping = _grouping[vli.type][vli.vtype]; this->UpdateSortingFromGrouping(); } @@ -195,6 +196,8 @@ void BaseVehicleListWindow::BuildVehicleList() max_unitnumber = std::max(max_unitnumber, (*it)->unitnumber); } this->unitnumber_digits = CountDigitsForAllocatingSpace(max_unitnumber); + + this->FilterVehicleList(); } else { /* Sort by the primary vehicle; we just want all vehicles that share the same orders to form a contiguous range. */ std::stable_sort(this->vehicles.begin(), this->vehicles.end(), [](const Vehicle * const &u, const Vehicle * const &v) { @@ -223,6 +226,118 @@ void BaseVehicleListWindow::BuildVehicleList() this->vscroll->SetCount(static_cast(this->vehgroups.size())); } + +/** Cargo filter functions */ +/** + * Check whether a vehicle can carry a specific cargo. + * @param vehgroup The vehicle group which contains the vehicle to be checked + * @param cid The cargo what we are looking for + * @return Whether the vehicle can carry the specified cargo or not + */ +static bool CDECL CargoFilter(const GUIVehicleGroup *vehgroup, const CargoID cid) +{ + const Vehicle *v = (*vehgroup).GetSingleVehicle(); + if (cid == BaseVehicleListWindow::CF_ANY) { + return true; + } else if (cid == BaseVehicleListWindow::CF_NONE) { + for (const Vehicle *w = v; w != nullptr; w = w->Next()) { + if (w->cargo_cap > 0) { + return false; + } + } + return true; + } else if (cid == BaseVehicleListWindow::CF_FREIGHT) { + bool have_capacity = false; + for (const Vehicle *w = v; w != nullptr; w = w->Next()) { + if (w->cargo_cap > 0) { + if (IsCargoInClass(w->cargo_type, CC_PASSENGERS)) { + return false; + } else { + have_capacity = true; + } + } + } + return have_capacity; + } else { + for (const Vehicle *w = v; w != nullptr; w = w->Next()) { + if (w->cargo_cap > 0 && w->cargo_type == cid) { + return true; + } + } + return false; + } +} + +static GUIVehicleGroupList::FilterFunction * const _filter_funcs[] = { + &CargoFilter, +}; + +/** + * Set cargo filter list item index. + * @param index The index to be set + */ +void BaseVehicleListWindow::SetCargoFilterIndex(byte index) +{ + if (this->cargo_filter_criteria != index) { + this->cargo_filter_criteria = index; + /* Deactivate filter if criteria is 'Show All', activate it otherwise. */ + this->vehgroups.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY); + this->vehgroups.SetFilterType(0); + this->vehgroups.ForceRebuild(); + } +} + +/** + *Populate the filter list and set the cargo filter criteria. + */ +void BaseVehicleListWindow::SetCargoFilterArray() +{ + byte filter_items = 0; + + /* Add item for disabling filtering. */ + this->cargo_filter[filter_items] = CF_ANY; + this->cargo_filter_texts[filter_items] = STR_CARGO_TYPE_FILTER_ALL; + this->cargo_filter_criteria = filter_items; + filter_items++; + + /* Add item for freight (i.e. vehicles with cargo capacity and with no passenger capacity). */ + this->cargo_filter[filter_items] = CF_FREIGHT; + this->cargo_filter_texts[filter_items] = STR_CARGO_TYPE_FILTER_FREIGHT; + filter_items++; + + /* Add item for vehicles not carrying anything, e.g. train engines. */ + this->cargo_filter[filter_items] = CF_NONE; + this->cargo_filter_texts[filter_items] = STR_CARGO_TYPE_FILTER_NONE; + filter_items++; + + /* Collect available cargo types for filtering. */ + for (const auto &cs : _sorted_cargo_specs) { + this->cargo_filter[filter_items] = cs->Index(); + this->cargo_filter_texts[filter_items] = cs->name; + filter_items++; + } + + /* Terminate the filter list. */ + this->cargo_filter_texts[filter_items] = INVALID_STRING_ID; + + this->vehgroups.SetFilterFuncs(_filter_funcs); + this->vehgroups.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY); +} + +/** + *Filter the engine list against the currently selected cargo filter. + */ +void BaseVehicleListWindow::FilterVehicleList() +{ + this->vehgroups.Filter(this->cargo_filter[this->cargo_filter_criteria]); + if (this->vehicles.size() == 0) { + /* No vehicle passed through the filter, invalidate the previously selected vehicle */ + this->vehicle_sel = INVALID_VEHICLE; + } else if (this->vehicle_sel != INVALID_VEHICLE && std::find(this->vehicles.begin(), this->vehicles.end(), Vehicle::Get(this->vehicle_sel)) == this->vehicles.end()) { // previously selected engine didn't pass the filter, remove selection + this->vehicle_sel = INVALID_VEHICLE; + } +} + /** * Compute the size for the Action dropdown. * @param show_autoreplace If true include the autoreplace item. @@ -248,6 +363,7 @@ Dimension BaseVehicleListWindow::GetActionDropdownSize(bool show_autoreplace, bo void BaseVehicleListWindow::OnInit() { this->order_arrow_width = GetStringBoundingBox(STR_TINY_RIGHT_ARROW).width; + this->SetCargoFilterArray(); } /** @@ -1391,6 +1507,9 @@ static const NWidgetPart _nested_vehicle_list[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VL_SORT_ORDER), SetMinimalSize(81, 12), SetFill(0, 1), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VL_SORT_BY_PULLDOWN), SetMinimalSize(167, 12), SetFill(0, 1), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VL_FILTER_BY_CARGO_SEL), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VL_FILTER_BY_CARGO), SetMinimalSize(167, 12), SetFill(0, 1), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA), + EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetFill(1, 1), SetResize(1, 0), EndContainer(), EndContainer(), @@ -1662,6 +1781,8 @@ public: { this->CreateNestedTree(); + this->GetWidget(WID_VL_FILTER_BY_CARGO_SEL)->SetDisplayedPlane((this->vli.type == VL_SHARED_ORDERS) ? SZSP_NONE : 0); + this->vscroll = this->GetScrollbar(WID_VL_SCROLLBAR); this->BuildVehicleList(); @@ -1736,6 +1857,10 @@ public: SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype); break; + case WID_VL_FILTER_BY_CARGO: + SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]); + break; + case WID_VL_CAPTION: case WID_VL_CAPTION_SHARED_ORDERS: { switch (this->vli.type) { @@ -1819,6 +1944,8 @@ public: /* Set text of sort by dropdown widget. */ this->GetWidget(WID_VL_SORT_BY_PULLDOWN)->widget_data = this->GetVehicleSorterNames()[this->vehgroups.SortType()]; + this->GetWidget(WID_VL_FILTER_BY_CARGO)->widget_data = this->cargo_filter_texts[this->cargo_filter_criteria]; + this->DrawWidgets(); } @@ -1845,6 +1972,10 @@ public: (this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : (1 << 10)); return; + case WID_VL_FILTER_BY_CARGO: // Cargo filter dropdown + ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_VL_FILTER_BY_CARGO, 0, 0); + break; + case WID_VL_LIST: { // Matrix to show vehicles uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VL_LIST); if (id_v >= this->vehgroups.size()) return; // click out of list bound @@ -1913,6 +2044,10 @@ public: this->vehgroups.SetSortType(index); break; + case WID_VL_FILTER_BY_CARGO: + this->SetCargoFilterIndex(index); + break; + case WID_VL_MANAGE_VEHICLES_DROPDOWN: assert(this->vehicles.size() != 0); diff --git a/src/vehicle_gui_base.h b/src/vehicle_gui_base.h index d724bc3433..73f2159d39 100644 --- a/src/vehicle_gui_base.h +++ b/src/vehicle_gui_base.h @@ -11,6 +11,7 @@ #define VEHICLE_GUI_BASE_H #include "core/smallvec_type.hpp" +#include "cargo_type.h" #include "date_type.h" #include "economy_type.h" #include "sortlist_type.h" @@ -22,7 +23,7 @@ #include #include -typedef GUIList GUIVehicleList; +typedef GUIList GUIVehicleList; struct GUIVehicleGroup { VehicleList::const_iterator vehicles_begin; ///< Pointer to beginning element of this vehicle group. @@ -65,7 +66,7 @@ struct GUIVehicleGroup { } }; -typedef GUIList GUIVehicleGroupList; +typedef GUIList GUIVehicleGroupList; struct BaseVehicleListWindow : public Window { @@ -76,14 +77,25 @@ struct BaseVehicleListWindow : public Window { GB_END, }; - GroupBy grouping; ///< How we want to group the list. - VehicleList vehicles; ///< List of vehicles. This is the buffer for `vehgroups` to point into; if this is structurally modified, `vehgroups` must be rebuilt. - GUIVehicleGroupList vehgroups; ///< List of (groups of) vehicles. This stores iterators of `vehicles`, and should be rebuilt if `vehicles` is structurally changed. - Listing *sorting; ///< Pointer to the vehicle type related sorting. - byte unitnumber_digits; ///< The number of digits of the highest unit number. + /** Special cargo filter criteria */ + enum CargoFilterSpecialType { + CF_NONE = CT_INVALID, ///< Show only vehicles which do not carry cargo (e.g. train engines) + CF_ANY = CT_NO_REFIT, ///< Show all vehicles independent of carried cargo (i.e. no filtering) + CF_FREIGHT = CT_AUTO_REFIT, ///< Show only vehicles which carry any freight (non-passenger) cargo + }; + + GroupBy grouping; ///< How we want to group the list. + VehicleList vehicles; ///< List of vehicles. This is the buffer for `vehgroups` to point into; if this is structurally modified, `vehgroups` must be rebuilt. + GUIVehicleGroupList vehgroups; ///< List of (groups of) vehicles. This stores iterators of `vehicles`, and should be rebuilt if `vehicles` is structurally changed. + Listing *sorting; ///< Pointer to the vehicle type related sorting. + byte unitnumber_digits; ///< The number of digits of the highest unit number. Scrollbar *vscroll; - VehicleListIdentifier vli; ///< Identifier of the vehicle list we want to currently show. - uint order_arrow_width; ///< Width of the arrow in the small order list. + VehicleListIdentifier vli; ///< Identifier of the vehicle list we want to currently show. + VehicleID vehicle_sel; ///< Selected vehicle + CargoID cargo_filter[NUM_CARGO + 3]; ///< Available cargo filters; CargoID or CF_ANY or CF_FREIGHT or CF_NONE + StringID cargo_filter_texts[NUM_CARGO + 4]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID + byte cargo_filter_criteria; ///< Selected cargo filter index + uint order_arrow_width; ///< Width of the arrow in the small order list. typedef GUIVehicleGroupList::SortFunction VehicleGroupSortFunction; typedef GUIVehicleList::SortFunction VehicleIndividualSortFunction; @@ -113,6 +125,9 @@ struct BaseVehicleListWindow : public Window { void UpdateVehicleGroupBy(GroupBy group_by); void SortVehicleList(); void BuildVehicleList(); + void SetCargoFilterIndex(byte index); + void SetCargoFilterArray(); + void FilterVehicleList(); Dimension GetActionDropdownSize(bool show_autoreplace, bool show_group); DropDownList BuildActionDropdownList(bool show_autoreplace, bool show_group); diff --git a/src/widgets/group_widget.h b/src/widgets/group_widget.h index 03ac61a48e..e097b5314e 100644 --- a/src/widgets/group_widget.h +++ b/src/widgets/group_widget.h @@ -17,6 +17,7 @@ enum GroupListWidgets { WID_GL_GROUP_BY_DROPDOWN, ///< Group by dropdown list. WID_GL_SORT_BY_ORDER, ///< Sort order. WID_GL_SORT_BY_DROPDOWN, ///< Sort by dropdown list. + WID_GL_FILTER_BY_CARGO, ///< Filter vehicles by cargo type. WID_GL_LIST_VEHICLE, ///< List of the vehicles. WID_GL_LIST_VEHICLE_SCROLLBAR, ///< Scrollbar for the list. WID_GL_AVAILABLE_VEHICLES, ///< Available vehicles. diff --git a/src/widgets/vehicle_widget.h b/src/widgets/vehicle_widget.h index 954e72f70e..375f914e10 100644 --- a/src/widgets/vehicle_widget.h +++ b/src/widgets/vehicle_widget.h @@ -69,6 +69,8 @@ enum VehicleListWidgets { WID_VL_GROUP_BY_PULLDOWN, ///< Group by dropdown list. WID_VL_SORT_ORDER, ///< Sort order. WID_VL_SORT_BY_PULLDOWN, ///< Sort by dropdown list. + WID_VL_FILTER_BY_CARGO, ///< Cargo filter dropdown list. + WID_VL_FILTER_BY_CARGO_SEL, ///< Cargo filter dropdown list panel selector. WID_VL_LIST, ///< List of the vehicles. WID_VL_SCROLLBAR, ///< Scrollbar for the list. WID_VL_HIDE_BUTTONS, ///< Selection to hide the buttons.