Skip to content

Commit

Permalink
Feature: rail station class name filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
perezdidac committed Feb 24, 2021
1 parent 5fc983a commit 116c18e
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 47 deletions.
253 changes: 206 additions & 47 deletions src/rail_gui.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -891,6 +895,30 @@ struct BuildRailStationWindow : public PickerWindowBase {
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.

/** Scroll #WID_BRAS_NEWST_SCROLL so that the selected station class is visible. */
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 ).
Expand Down Expand Up @@ -925,7 +953,7 @@ struct BuildRailStationWindow : public PickerWindowBase {
}

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;
Expand All @@ -940,10 +968,24 @@ struct BuildRailStationWindow : public PickerWindowBase {
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);
Expand All @@ -956,37 +998,144 @@ struct BuildRailStationWindow : public PickerWindowBase {
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()
{
DeleteWindowById(WC_SELECT_STATION, 0);
}

/** Sort station classes by class name. */
static bool NameSorter(StationClassID const &a, StationClassID const &b)
{
/* Keep default station at the top. */
if (a == STAT_CLASS_DFLT) {
return true;
} else if (b == STAT_CLASS_DFLT) {
return false;
}

char buffer_a[DRAW_STRING_BUFFER];
GetString(buffer_a, StationClass::Get(a)->name, lastof(buffer_a));

char buffer_b[DRAW_STRING_BUFFER];
GetString(buffer_b, StationClass::Get(b)->name, lastof(buffer_b));

return strnatcmp(buffer_a, buffer_b, true) < 0; // Sort by name (natural sorting).
}

/** 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; StationClass::GetClassCount(); ++i) {
if ((StationClassID)i == _railstation.station_class) {
available = true;
break;
}
}

if (available) {
this->SelectOtherClass(_railstation.station_class);
} else {
this->SelectOtherClass(this->station_classes[0]);
}
}

assert(StationClass::Get(_railstation.station_class)->GetUISpecCount() > 0); // station GUI should be disabled elsewise
}

/**
* 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;
Expand Down Expand Up @@ -1043,9 +1192,8 @@ struct BuildRailStationWindow : public PickerWindowBase {
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;
Expand All @@ -1064,9 +1212,8 @@ struct BuildRailStationWindow : public PickerWindowBase {
/* 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);
Expand Down Expand Up @@ -1131,12 +1278,11 @@ struct BuildRailStationWindow : public PickerWindowBase {
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++;
Expand Down Expand Up @@ -1314,29 +1460,23 @@ struct BuildRailStationWindow : public PickerWindowBase {

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;
}

Expand Down Expand Up @@ -1367,6 +1507,17 @@ struct BuildRailStationWindow : public PickerWindowBase {
}
};

Listing BuildRailStationWindow::last_sorting = { false, 0 };
Filtering BuildRailStationWindow::last_filtering = { false, 0 };

BuildRailStationWindow::GUIStationClassList::SortFunction * const BuildRailStationWindow::sorter_funcs[] = {
&NameSorter,
};

BuildRailStationWindow::GUIStationClassList::FilterFunction * const BuildRailStationWindow::filter_funcs[] = {
&TagNameFilter,
};

static const NWidgetPart _nested_station_builder_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
Expand All @@ -1379,6 +1530,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),
Expand Down Expand Up @@ -1876,6 +2034,7 @@ static void ShowBuildWaypointPicker(Window *parent)
void InitializeRailGui()
{
_build_depot_direction = DIAGDIR_NW;
_railstation.station_class = StationClassID::STAT_CLASS_DFLT;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/widgets/rail_widget.h
Expand Up @@ -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.
Expand Down

0 comments on commit 116c18e

Please sign in to comment.