summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDidac Perez Parera <perez.didac@gmail.com>2021-03-13 01:00:36 -0800
committerGitHub <noreply@github.com>2021-03-13 10:00:36 +0100
commite708fb38da7a3635c7cc15dd1cc516ea1d2d5871 (patch)
treeb0efc2abaf2eef20543e5b761ab547c04d1149e2
parent9c5a7d3a572d02813e6bab83004334b9a154a62b (diff)
downloadopenttd-e708fb38da7a3635c7cc15dd1cc516ea1d2d5871.tar.xz
Feature: allow filtering on name in rail station window (#8706)
-rw-r--r--src/rail_gui.cpp240
-rw-r--r--src/widgets/rail_widget.h2
2 files changed, 195 insertions, 47 deletions
diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp
index 2211a48a6..d14a292c9 100644
--- a/src/rail_gui.cpp
+++ b/src/rail_gui.cpp
@@ -32,6 +32,10 @@
#include "vehicle_func.h"
#include "zoom_func.h"
#include "rail_gui.h"
+#include "querystring_gui.h"
+#include "sortlist_type.h"
+#include "stringfilter_type.h"
+#include "string_func.h"
#include "station_map.h"
#include "tunnelbridge_map.h"
@@ -891,6 +895,36 @@ private:
Scrollbar *vscroll; ///< Vertical scrollbar of the new station list.
Scrollbar *vscroll2; ///< Vertical scrollbar of the matrix with new stations.
+ typedef GUIList<StationClassID, StringFilter &> GUIStationClassList; ///< Type definition for the list to hold available station classes.
+
+ static const uint EDITBOX_MAX_SIZE = 16; ///< The maximum number of characters for the filter edit box.
+
+ static Listing last_sorting; ///< Default sorting of #GUIStationClassList.
+ static Filtering last_filtering; ///< Default filtering of #GUIStationClassList.
+ static GUIStationClassList::SortFunction * const sorter_funcs[]; ///< Sort functions of the #GUIStationClassList.
+ static GUIStationClassList::FilterFunction * const filter_funcs[]; ///< Filter functions of the #GUIStationClassList.
+ GUIStationClassList station_classes; ///< Available station classes.
+ StringFilter string_filter; ///< Filter for available station classes.
+ QueryString filter_editbox; ///< Filter editbox.
+
+ /**
+ * Scrolls #WID_BRAS_NEWST_SCROLL so that the selected station class is visible.
+ *
+ * Note that this method should be called shortly after SelectClassAndStation() which will ensure
+ * an actual existing station class is selected, or the one at position 0 which will always be
+ * the default TTD rail station.
+ */
+ void EnsureSelectedStationClassIsVisible()
+ {
+ uint pos = 0;
+ for (auto station_class : this->station_classes) {
+ if (station_class == _railstation.station_class) break;
+ pos++;
+ }
+ this->vscroll->SetCount((int)this->station_classes.size());
+ this->vscroll->ScrollTowards(pos);
+ }
+
/**
* Verify whether the currently selected station size is allowed after selecting a new station class/type.
* If not, change the station size variables ( _settings_client.gui.station_numtracks and _settings_client.gui.station_platlength ).
@@ -925,7 +959,7 @@ private:
}
public:
- BuildRailStationWindow(WindowDesc *desc, Window *parent, bool newstation) : PickerWindowBase(desc, parent)
+ BuildRailStationWindow(WindowDesc *desc, Window *parent, bool newstation) : PickerWindowBase(desc, parent), filter_editbox(EDITBOX_MAX_SIZE)
{
this->coverage_height = 2 * FONT_HEIGHT_NORMAL + 3 * WD_PAR_VSEP_NORMAL;
this->vscroll = nullptr;
@@ -940,10 +974,24 @@ public:
newst_additions->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
newst_additions = this->GetWidget<NWidgetStacked>(WID_BRAS_SHOW_NEWST_RESIZE);
newst_additions->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
+ /* Hide the station class filter if no stations other than the default one are available. */
+ this->GetWidget<NWidgetStacked>(WID_BRAS_FILTER_CONTAINER)->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
if (newstation) {
this->vscroll = this->GetScrollbar(WID_BRAS_NEWST_SCROLL);
this->vscroll2 = this->GetScrollbar(WID_BRAS_MATRIX_SCROLL);
+
+ this->querystrings[WID_BRAS_FILTER_EDITBOX] = &this->filter_editbox;
+ this->station_classes.SetListing(this->last_sorting);
+ this->station_classes.SetFiltering(this->last_filtering);
+ this->station_classes.SetSortFuncs(this->sorter_funcs);
+ this->station_classes.SetFilterFuncs(this->filter_funcs);
}
+
+ this->station_classes.ForceRebuild();
+
+ BuildStationClassesAvailable();
+ SelectClassAndStation();
+
this->FinishInitNested(TRANSPORT_RAIL);
this->LowerWidget(_railstation.orientation + WID_BRAS_PLATFORM_DIR_X);
@@ -956,30 +1004,25 @@ public:
this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_OFF, !_settings_client.gui.station_show_coverage);
this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_ON, _settings_client.gui.station_show_coverage);
- if (!newstation || _railstation.station_class >= (int)StationClass::GetClassCount()) {
- /* New stations are not available or changed, so ensure the default station
- * type is 'selected'. */
- _railstation.station_class = STAT_CLASS_DFLT;
+ if (!newstation) {
+ _railstation.station_class = StationClassID::STAT_CLASS_DFLT;
_railstation.station_type = 0;
this->vscroll2 = nullptr;
- }
- if (newstation) {
+ } else {
_railstation.station_count = StationClass::Get(_railstation.station_class)->GetSpecCount();
_railstation.station_type = std::min<int>(_railstation.station_type, _railstation.station_count - 1);
- int count = 0;
- for (uint i = 0; i < StationClass::GetClassCount(); i++) {
- if (i == STAT_CLASS_WAYP) continue;
- count++;
- }
- this->vscroll->SetCount(count);
- this->vscroll->SetPosition(Clamp(_railstation.station_class - 2, 0, std::max(this->vscroll->GetCount() - this->vscroll->GetCapacity(), 0)));
-
NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BRAS_MATRIX);
matrix->SetScrollbar(this->vscroll2);
matrix->SetCount(_railstation.station_count);
matrix->SetClicked(_railstation.station_type);
+
+ EnsureSelectedStationClassIsVisible();
+
+ this->SetFocusedWidget(WID_BRAS_FILTER_EDITBOX);
}
+
+ this->InvalidateData();
}
virtual ~BuildRailStationWindow()
@@ -987,6 +1030,99 @@ public:
DeleteWindowById(WC_SELECT_STATION, 0);
}
+ /** Sort station classes by StationClassID. */
+ static bool StationClassIDSorter(StationClassID const &a, StationClassID const &b)
+ {
+ return a < b;
+ }
+
+ /** Filter station classes by class name. */
+ static bool CDECL TagNameFilter(StationClassID const * sc, StringFilter &filter)
+ {
+ char buffer[DRAW_STRING_BUFFER];
+ GetString(buffer, StationClass::Get(*sc)->name, lastof(buffer));
+
+ filter.ResetState();
+ filter.AddLine(buffer);
+ return filter.GetState();
+ }
+
+ /** Builds the filter list of available station classes. */
+ void BuildStationClassesAvailable()
+ {
+ if (!this->station_classes.NeedRebuild()) return;
+
+ this->station_classes.clear();
+
+ for (uint i = 0; i < StationClass::GetClassCount(); i++) {
+ StationClassID station_class_id = (StationClassID)i;
+ if (station_class_id == StationClassID::STAT_CLASS_WAYP) {
+ // Skip waypoints.
+ continue;
+ }
+ StationClass *station_class = StationClass::Get(station_class_id);
+ if (station_class->GetUISpecCount() == 0) continue;
+ station_classes.push_back(station_class_id);
+ }
+
+ if (_railstation.newstations) {
+ this->station_classes.Filter(this->string_filter);
+ this->station_classes.shrink_to_fit();
+ this->station_classes.RebuildDone();
+ this->station_classes.Sort();
+
+ this->vscroll->SetCount((uint)this->station_classes.size());
+ }
+ }
+
+ /**
+ * Checks if the previously selected current station class and station
+ * can be shown as selected to the user when the dialog is opened.
+ */
+ void SelectClassAndStation()
+ {
+ if (_railstation.station_class == StationClassID::STAT_CLASS_DFLT) {
+ /* This happens during the first time the window is open during the game life cycle. */
+ this->SelectOtherClass(StationClassID::STAT_CLASS_DFLT);
+ } else {
+ /* Check if the previously selected station class is not available anymore as a
+ * result of starting a new game without the corresponding NewGRF. */
+ bool available = false;
+ for (uint i = 0; i < StationClass::GetClassCount(); ++i) {
+ if ((StationClassID)i == _railstation.station_class) {
+ available = true;
+ break;
+ }
+ }
+
+ this->SelectOtherClass(available ? _railstation.station_class : StationClassID::STAT_CLASS_DFLT);
+ }
+ }
+
+ /**
+ * Select the specified station class.
+ * @param station_class Station class select.
+ */
+ void SelectOtherClass(StationClassID station_class)
+ {
+ _railstation.station_class = station_class;
+ }
+
+ void OnInvalidateData(int data = 0, bool gui_scope = true) override
+ {
+ if (!gui_scope) return;
+
+ this->BuildStationClassesAvailable();
+ }
+
+ void OnEditboxChanged(int wid) override
+ {
+ string_filter.SetFilterTerm(this->filter_editbox.text.buf);
+ this->station_classes.SetFilterState(!string_filter.IsEmpty());
+ this->station_classes.ForceRebuild();
+ this->InvalidateData();
+ }
+
void OnPaint() override
{
bool newstations = _railstation.newstations;
@@ -1043,9 +1179,8 @@ public:
switch (widget) {
case WID_BRAS_NEWST_LIST: {
Dimension d = {0, 0};
- for (uint i = 0; i < StationClass::GetClassCount(); i++) {
- if (i == STAT_CLASS_WAYP) continue;
- d = maxdim(d, GetStringBoundingBox(StationClass::Get((StationClassID)i)->name));
+ for (auto station_class : this->station_classes) {
+ d = maxdim(d, GetStringBoundingBox(StationClass::Get(station_class)->name));
}
size->width = std::max(size->width, d.width + padding.width);
this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
@@ -1064,9 +1199,8 @@ public:
/* If newstations exist, compute the non-zero minimal size. */
Dimension d = {0, 0};
StringID str = this->GetWidget<NWidgetCore>(widget)->widget_data;
- for (StationClassID statclass = STAT_CLASS_BEGIN; statclass < (StationClassID)StationClass::GetClassCount(); statclass++) {
- if (statclass == STAT_CLASS_WAYP) continue;
- StationClass *stclass = StationClass::Get(statclass);
+ for (auto station_class : this->station_classes) {
+ StationClass *stclass = StationClass::Get(station_class);
for (uint16 j = 0; j < stclass->GetSpecCount(); j++) {
const StationSpec *statspec = stclass->GetSpec(j);
SetDParam(0, (statspec != nullptr && statspec->name != 0) ? statspec->name : STR_STATION_CLASS_DFLT);
@@ -1131,12 +1265,11 @@ public:
case WID_BRAS_NEWST_LIST: {
uint statclass = 0;
uint row = 0;
- for (uint i = 0; i < StationClass::GetClassCount(); i++) {
- if (i == STAT_CLASS_WAYP) continue;
+ for (auto station_class : this->station_classes) {
if (this->vscroll->IsVisible(statclass)) {
DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, row * this->line_height + r.top + WD_MATRIX_TOP,
- StationClass::Get((StationClassID)i)->name,
- (StationClassID)i == _railstation.station_class ? TC_WHITE : TC_BLACK);
+ StationClass::Get(station_class)->name,
+ station_class == _railstation.station_class ? TC_WHITE : TC_BLACK);
row++;
}
statclass++;
@@ -1314,29 +1447,23 @@ public:
case WID_BRAS_NEWST_LIST: {
int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BRAS_NEWST_LIST, 0, this->line_height);
- if (y >= (int)StationClass::GetClassCount()) return;
- for (uint i = 0; i < StationClass::GetClassCount(); i++) {
- if (i == STAT_CLASS_WAYP) continue;
- if (y == 0) {
- if (_railstation.station_class != (StationClassID)i) {
- _railstation.station_class = (StationClassID)i;
- StationClass *stclass = StationClass::Get(_railstation.station_class);
- _railstation.station_count = stclass->GetSpecCount();
- _railstation.station_type = std::min((int)_railstation.station_type, std::max(0, (int)_railstation.station_count - 1));
-
- this->CheckSelectedSize(stclass->GetSpec(_railstation.station_type));
-
- NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BRAS_MATRIX);
- matrix->SetCount(_railstation.station_count);
- matrix->SetClicked(_railstation.station_type);
- }
- if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
- this->SetDirty();
- DeleteWindowById(WC_SELECT_STATION, 0);
- break;
- }
- y--;
+ if (y >= (int)this->station_classes.size()) return;
+ StationClassID station_class_id = this->station_classes[y];
+ if (_railstation.station_class != station_class_id) {
+ StationClass *station_class = StationClass::Get(station_class_id);
+ _railstation.station_class = station_class_id;
+ _railstation.station_count = station_class->GetSpecCount();
+ _railstation.station_type = 0;
+
+ this->CheckSelectedSize(station_class->GetSpec(_railstation.station_type));
+
+ NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BRAS_MATRIX);
+ matrix->SetCount(_railstation.station_count);
+ matrix->SetClicked(_railstation.station_type);
}
+ if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
+ this->SetDirty();
+ DeleteWindowById(WC_SELECT_STATION, 0);
break;
}
@@ -1367,6 +1494,17 @@ public:
}
};
+Listing BuildRailStationWindow::last_sorting = { false, 0 };
+Filtering BuildRailStationWindow::last_filtering = { false, 0 };
+
+BuildRailStationWindow::GUIStationClassList::SortFunction * const BuildRailStationWindow::sorter_funcs[] = {
+ &StationClassIDSorter,
+};
+
+BuildRailStationWindow::GUIStationClassList::FilterFunction * const BuildRailStationWindow::filter_funcs[] = {
+ &TagNameFilter,
+};
+
static const NWidgetPart _nested_station_builder_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
@@ -1379,6 +1517,13 @@ static const NWidgetPart _nested_station_builder_widgets[] = {
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_VERTICAL),
+ NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BRAS_FILTER_CONTAINER),
+ NWidget(NWID_HORIZONTAL), SetPadding(2, 2, 0, 5),
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
+ NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BRAS_FILTER_EDITBOX), SetFill(1, 0), SetResize(1, 0),
+ SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
+ EndContainer(),
+ EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BRAS_SHOW_NEWST_ADDITIONS),
NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7), SetPadding(2, 0, 1, 0),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_BRAS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0),
@@ -1876,6 +2021,7 @@ static void ShowBuildWaypointPicker(Window *parent)
void InitializeRailGui()
{
_build_depot_direction = DIAGDIR_NW;
+ _railstation.station_class = StationClassID::STAT_CLASS_DFLT;
}
/**
diff --git a/src/widgets/rail_widget.h b/src/widgets/rail_widget.h
index 65af03d4a..f10f9bee4 100644
--- a/src/widgets/rail_widget.h
+++ b/src/widgets/rail_widget.h
@@ -62,6 +62,8 @@ enum BuildRailStationWidgets {
WID_BRAS_IMAGE, ///< Panel used at each cell of the matrix.
WID_BRAS_MATRIX_SCROLL, ///< Scrollbar of the matrix widget.
+ WID_BRAS_FILTER_CONTAINER, ///< Container for the filter text box for the station class list.
+ WID_BRAS_FILTER_EDITBOX, ///< Filter text box for the station class list.
WID_BRAS_SHOW_NEWST_DEFSIZE, ///< Selection for default-size button for newstation.
WID_BRAS_SHOW_NEWST_ADDITIONS, ///< Selection for newstation class selection list.
WID_BRAS_SHOW_NEWST_MATRIX, ///< Selection for newstation image matrix.