1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2026-01-26 21:54:22 +01:00

Feature: User-defined collections of saved items in the picker window (#14813)

This commit is contained in:
mmtunligit
2026-01-25 18:38:01 +01:00
committed by GitHub
parent e661db0396
commit 2bcbea2694
8 changed files with 365 additions and 70 deletions

View File

@@ -2858,18 +2858,38 @@ STR_PICKER_PREVIEW_SHRINK_TOOLTIP :Reduce the heig
STR_PICKER_PREVIEW_EXPAND :+
STR_PICKER_PREVIEW_EXPAND_TOOLTIP :Increase the height of preview images. Ctrl+Click to increase to maximum
STR_PICKER_DEFAULT_COLLECTION :Default collection
STR_PICKER_SELECT_COLLECTION_TOOLTIP :Select a collection
STR_PICKER_COLLECTION_ADD :Add
STR_PICKER_COLLECTION_ADD_TOOLTIP :Create a new collection
STR_PICKER_COLLECTION_RENAME :Rename
STR_PICKER_COLLECTION_RENAME_TOOLTIP :Rename a collection
STR_PICKER_COLLECTION_DELETE :Delete
STR_PICKER_COLLECTION_DELETE_TOOLTIP :Delete a collection
STR_PICKER_COLLECTION_RENAME_QUERY :Rename this collection
STR_PICKER_COLLECTION_DELETE_QUERY :Delete collection
STR_PICKER_COLLECTION_DELETE_QUERY_TEXT :{YELLOW}Are you sure you want to delete this collection?
STR_PICKER_COLLECTION_DELETE_QUERY_DISABLED_TEXT :{YELLOW}Are you sure you want to delete this collection? There are items from disabled NewGRFs in it!
STR_PICKER_STATION_CLASS_TOOLTIP :Select a station class to display
STR_PICKER_STATION_TYPE_TOOLTIP :Select a station type to build. Ctrl+Click to add or remove in saved items
STR_PICKER_STATION_COLLECTION_TOOLTIP :Select a collection of stations to display
STR_PICKER_WAYPOINT_CLASS_TOOLTIP :Select a waypoint class to display
STR_PICKER_WAYPOINT_TYPE_TOOLTIP :Select a waypoint to build. Ctrl+Click to add or remove in saved items
STR_PICKER_WAYPOINT_COLLECTION_TOOLTIP :Select a collection of waypoints to display
STR_PICKER_ROADSTOP_BUS_CLASS_TOOLTIP :Select a bus station class to display
STR_PICKER_ROADSTOP_BUS_TYPE_TOOLTIP :Select a bus station type to build. Ctrl+Click to add or remove in saved items
STR_PICKER_ROADSTOP_BUS_COLLECTION_TOOLTIP :Select a collection of bus stations to display
STR_PICKER_ROADSTOP_TRUCK_CLASS_TOOLTIP :Select a lorry station class to display
STR_PICKER_ROADSTOP_TRUCK_TYPE_TOOLTIP :Select a lorry station type to build. Ctrl+Click to add or remove in saved items
STR_PICKER_ROADSTOP_TRUCK_COLLECTION_TOOLTIP :Select a collection of lorry stations to display
STR_PICKER_OBJECT_CLASS_TOOLTIP :Select an object class to display
STR_PICKER_OBJECT_TYPE_TOOLTIP :Select an object type to build. Ctrl+Click to add or remove in saved items. Ctrl+Click+Drag to select the area diagonally. Also press Shift to show cost estimate only
STR_PICKER_OBJECT_COLLECTION_TOOLTIP :Select a collection of objects to display
STR_PICKER_HOUSE_CLASS_TOOLTIP :Select a town zone to display
STR_PICKER_HOUSE_TYPE_TOOLTIP :Select a house type to build. Ctrl+Click to add or remove in saved items
STR_PICKER_HOUSE_COLLECTION_TOOLTIP :Select a collection of houses to display
STR_HOUSE_PICKER_CAPTION :House Selection
STR_HOUSE_PICKER_NAME :{BLACK}Name: {GOLD}{STRING}

View File

@@ -50,6 +50,7 @@ public:
StringID GetClassTooltip() const override { return STR_PICKER_OBJECT_CLASS_TOOLTIP; }
StringID GetTypeTooltip() const override { return STR_PICKER_OBJECT_TYPE_TOOLTIP; }
StringID GetCollectionTooltip() const override { return STR_PICKER_OBJECT_COLLECTION_TOOLTIP; }
bool IsActive() const override
{

View File

@@ -10,6 +10,7 @@
#include "stdafx.h"
#include "core/backup_type.hpp"
#include "company_func.h"
#include "dropdown_func.h"
#include "gui.h"
#include "hotkeys.h"
#include "ini_type.h"
@@ -64,29 +65,39 @@ PickerCallbacks::~PickerCallbacks()
*/
static void PickerLoadConfig(const IniFile &ini, PickerCallbacks &callbacks)
{
const IniGroup *group = ini.GetGroup(callbacks.ini_group);
if (group == nullptr) return;
callbacks.saved.clear();
for (const IniItem &item : group->items) {
std::array<uint8_t, 4> grfid_buf;
for (const IniGroup &group : ini.groups) {
/* Read the collection name */
if (!group.name.starts_with(callbacks.ini_group)) continue;
auto pos = group.name.find('-');
if (pos == std::string_view::npos && group.name != callbacks.ini_group) continue;
std::string collection = (pos == std::string_view::npos) ? "" : group.name.substr(pos + 1);
std::string_view str = item.name;
if (group.items.empty() && pos != std::string_view::npos) {
callbacks.saved[collection];
continue;
}
/* Try reading "<grfid>|<localid>" */
auto grfid_pos = str.find('|');
if (grfid_pos == std::string_view::npos) continue;
for (const IniItem &item : group.items) {
std::array<uint8_t, 4> grfid_buf;
std::string_view grfid_str = str.substr(0, grfid_pos);
if (!ConvertHexToBytes(grfid_str, grfid_buf)) continue;
std::string_view str = item.name;
str = str.substr(grfid_pos + 1);
uint32_t grfid = grfid_buf[0] | (grfid_buf[1] << 8) | (grfid_buf[2] << 16) | (grfid_buf[3] << 24);
uint16_t localid;
auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), localid);
/* Try reading "<grfid>|<localid>" */
auto grfid_pos = str.find('|');
if (grfid_pos == std::string_view::npos) continue;
if (err == std::errc{} && ptr == str.data() + str.size()) {
callbacks.saved.insert({grfid, localid, 0, 0});
std::string_view grfid_str = str.substr(0, grfid_pos);
if (!ConvertHexToBytes(grfid_str, grfid_buf)) continue;
str = str.substr(grfid_pos + 1);
uint32_t grfid = grfid_buf[0] | (grfid_buf[1] << 8) | (grfid_buf[2] << 16) | (grfid_buf[3] << 24);
uint16_t localid;
auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), localid);
if (err == std::errc{} && ptr == str.data() + str.size()) {
callbacks.saved[collection].emplace(grfid, localid, 0, 0);
}
}
}
}
@@ -98,12 +109,18 @@ static void PickerLoadConfig(const IniFile &ini, PickerCallbacks &callbacks)
*/
static void PickerSaveConfig(IniFile &ini, const PickerCallbacks &callbacks)
{
IniGroup &group = ini.GetOrCreateGroup(callbacks.ini_group);
group.Clear();
/* Clean the ini file of any obsolete collections to prevent them coming back after a restart */
for (const std::string &rm_collection : callbacks.rm_collections) {
ini.RemoveGroup(callbacks.ini_group + "-" + rm_collection);
}
for (const PickerItem &item : callbacks.saved) {
std::string key = fmt::format("{:08X}|{}", std::byteswap(item.grfid), item.local_id);
group.CreateItem(key);
for (const auto &collection : callbacks.saved) {
IniGroup &group = ini.GetOrCreateGroup(collection.first == "" ? callbacks.ini_group : callbacks.ini_group + "-" + collection.first);
group.Clear();
for (const PickerItem &item : collection.second) {
std::string key = fmt::format("{:08X}|{}", std::byteswap(item.grfid), item.local_id);
group.CreateItem(key);
}
}
}
@@ -159,10 +176,28 @@ static bool TypeTagNameFilter(PickerItem const *item, PickerFilterData &filter)
return filter.GetState();
}
/** Allow the collection sorter to test if the collection has inactive items */
PickerWindow *picker_window;
/**
* Sort collections by id.
* @param a First string for sorting.
* @param b Second string for sorting.
* @return Sort order.
*/
static bool CollectionIDSorter(std::string const &a, std::string const &b)
{
if (a == GetString(STR_PICKER_DEFAULT_COLLECTION) || b == GetString(STR_PICKER_DEFAULT_COLLECTION)) return a == GetString(STR_PICKER_DEFAULT_COLLECTION);
if (picker_window->inactive.contains(a) == picker_window->inactive.contains(b)) return StrNaturalCompare(a, b) < 0;
return picker_window->inactive.contains(a) < picker_window->inactive.contains(b);
}
static const std::initializer_list<PickerClassList::SortFunction * const> _class_sorter_funcs = { &ClassIDSorter }; ///< Sort functions of the #PickerClassList
static const std::initializer_list<PickerClassList::FilterFunction * const> _class_filter_funcs = { &ClassTagNameFilter }; ///< Filter functions of the #PickerClassList.
static const std::initializer_list<PickerTypeList::SortFunction * const> _type_sorter_funcs = { TypeIDSorter }; ///< Sort functions of the #PickerTypeList.
static const std::initializer_list<PickerTypeList::FilterFunction * const> _type_filter_funcs = { TypeTagNameFilter }; ///< Filter functions of the #PickerTypeList.
static const std::initializer_list<PickerCollectionList::SortFunction * const> _collection_sorter_funcs = { &CollectionIDSorter }; ///< Sort functions of the #PickerCollectionList.
PickerWindow::PickerWindow(WindowDesc &desc, Window *parent, int window_number, PickerCallbacks &callbacks) : PickerWindowBase(desc, parent), callbacks(callbacks),
class_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE),
@@ -182,10 +217,12 @@ void PickerWindow::ConstructWindow()
bool is_active = this->callbacks.IsActive();
this->preview_height = std::max(this->callbacks.preview_height, PREVIEW_HEIGHT);
picker_window = this;
/* Functionality depends on widgets being present, not window class. */
this->has_class_picker = is_active && this->GetWidget<NWidgetBase>(WID_PW_CLASS_LIST) != nullptr && this->callbacks.HasClassChoice();
this->has_type_picker = is_active && this->GetWidget<NWidgetBase>(WID_PW_TYPE_MATRIX) != nullptr;
this->has_collection_picker = is_active && this->GetWidget<NWidgetBase>(WID_PW_COLEC_LIST) != nullptr;
if (this->has_class_picker) {
this->GetWidget<NWidgetCore>(WID_PW_CLASS_LIST)->SetToolTip(this->callbacks.GetClassTooltip());
@@ -209,7 +246,10 @@ void PickerWindow::ConstructWindow()
this->classes.SetFilterFuncs(_class_filter_funcs);
/* Update saved type information. */
if (this->callbacks.sel_collection == "") SetWidgetsDisabledState(true, WID_PW_COLEC_RENAME, WID_PW_COLEC_DELETE);
this->callbacks.saved = this->callbacks.UpdateSavedItems(this->callbacks.saved);
this->inactive = this->callbacks.InitializeInactiveCollections(this->callbacks.saved);
this->collections.ForceRebuild();
/* Clear used type information. */
this->callbacks.used.clear();
@@ -243,6 +283,13 @@ void PickerWindow::ConstructWindow()
this->types.SetSortFuncs(_type_sorter_funcs);
this->types.SetFilterFuncs(_type_filter_funcs);
if (this->has_collection_picker) {
this->GetWidget<NWidgetCore>(WID_PW_COLEC_LIST)->SetToolTip(this->callbacks.GetCollectionTooltip());
}
this->collections.SetListing(this->callbacks.collection_last_sorting);
this->collections.SetSortFuncs(_collection_sorter_funcs);
this->FinishInitNested(this->window_number);
this->InvalidateData(PICKER_INVALIDATION_ALL);
@@ -298,13 +345,30 @@ void PickerWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, const Dime
std::string PickerWindow::GetWidgetString(WidgetID widget, StringID stringid) const
{
if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) {
return this->GetWidget<NWidgetBadgeFilter>(widget)->GetStringParameter(this->badge_filter_choices);
}
switch (widget) {
case WID_PW_COLEC_LIST:
return this->callbacks.sel_collection == "" ? GetString(STR_PICKER_DEFAULT_COLLECTION) : this->callbacks.sel_collection;
default:
if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) {
return this->GetWidget<NWidgetBadgeFilter>(widget)->GetStringParameter(this->badge_filter_choices);
}
break;
}
return this->Window::GetWidgetString(widget, stringid);
}
DropDownList PickerWindow::BuildCollectionDropDownList()
{
DropDownList list;
int i = 0;
for (const auto &collection : collections) {
list.push_back(MakeDropDownListStringItem(GetString(collection == "" ? STR_PICKER_DEFAULT_COLLECTION : STR_JUST_RAW_STRING, collection), i, false, this->inactive.contains(collection)));
i++;
}
return list;
}
void PickerWindow::DrawWidget(const Rect &r, WidgetID widget) const
{
switch (widget) {
@@ -343,8 +407,10 @@ void PickerWindow::DrawWidget(const Rect &r, WidgetID widget) const
PaletteID palette = _game_mode != GM_NORMAL || feature == GSF_HOUSES ? PAL_NONE : GetCompanyPalette(_local_company);
DrawBadgeColumn({0, by, ir.Width() - 1, ir.Height() - 1}, 0, this->badge_classes, this->callbacks.GetTypeBadges(item.class_index, item.index), feature, std::nullopt, palette);
if (this->callbacks.saved.contains(item)) {
DrawSprite(SPR_BLOT, PALETTE_TO_YELLOW, 0, 0);
if (this->callbacks.saved.contains(this->callbacks.sel_collection)) {
if (this->callbacks.saved.at(this->callbacks.sel_collection).contains(item)) {
DrawSprite(SPR_BLOT, PALETTE_TO_YELLOW, 0, 0);
}
}
if (this->callbacks.used.contains(item)) {
DrawSprite(SPR_BLOT, PALETTE_TO_GREEN, ir.Width() - GetSpriteSize(SPR_BLOT).width, 0);
@@ -372,6 +438,21 @@ void PickerWindow::OnResize()
}
}
void PickerWindow::DeletePickerCollectionCallback(Window *win, bool confirmed)
{
if (confirmed) {
PickerWindow *w = (PickerWindow*)win;
w->callbacks.saved.erase(w->callbacks.saved.find(w->callbacks.edit_collection));
w->inactive.erase(w->callbacks.edit_collection);
w->callbacks.rm_collections.emplace(w->callbacks.edit_collection);
w->callbacks.sel_collection = "";
w->callbacks.edit_collection.clear();
picker_window = w;
w->SetWidgetsDisabledState(true, WID_PW_COLEC_RENAME, WID_PW_COLEC_DELETE);
w->InvalidateData({PickerInvalidation::Collection, PickerInvalidation::Position});
}
}
void PickerWindow::OnClick(Point pt, WidgetID widget, int)
{
switch (widget) {
@@ -422,13 +503,20 @@ void PickerWindow::OnClick(Point pt, WidgetID widget, int)
const auto &item = this->types[sel];
if (_ctrl_pressed) {
auto it = this->callbacks.saved.find(item);
if (it == std::end(this->callbacks.saved)) {
this->callbacks.saved.insert(item);
} else {
this->callbacks.saved.erase(it);
if (this->callbacks.saved.find(this->callbacks.sel_collection) == this->callbacks.saved.end()) {
this->callbacks.saved[""].emplace(item);
this->InvalidateData({PickerInvalidation::Collection, PickerInvalidation::Class});
this->SetDirty();
break;
}
this->InvalidateData(PickerInvalidation::Type);
auto it = this->callbacks.saved.at(this->callbacks.sel_collection).find(item);
if (it == std::end(this->callbacks.saved.at(this->callbacks.sel_collection))) {
this->callbacks.saved.at(this->callbacks.sel_collection).emplace(item);
} else {
this->callbacks.saved.at(this->callbacks.sel_collection).erase(it);
}
this->InvalidateData({PickerInvalidation::Type, PickerInvalidation::Class});
break;
}
@@ -442,6 +530,37 @@ void PickerWindow::OnClick(Point pt, WidgetID widget, int)
break;
}
case WID_PW_COLEC_LIST: {
ShowDropDownList(this, this->BuildCollectionDropDownList(), -1, widget, 0);
CloseWindowById(WC_SELECT_STATION, 0);
break;
}
case WID_PW_COLEC_ADD:
this->callbacks.rename_collection = false;
ShowQueryString({}, STR_PICKER_COLLECTION_ADD_TOOLTIP, MAX_LENGTH_GROUP_NAME_CHARS, this, CS_ALPHANUMERAL, QueryStringFlag::LengthIsInChars);
break;
case WID_PW_COLEC_RENAME:
if (this->callbacks.saved.contains(this->callbacks.sel_collection)) {
CloseChildWindows(WC_CONFIRM_POPUP_QUERY);
this->callbacks.edit_collection = this->callbacks.sel_collection;
this->callbacks.rename_collection = true;
ShowQueryString(this->callbacks.sel_collection, STR_PICKER_COLLECTION_RENAME_QUERY, MAX_LENGTH_GROUP_NAME_CHARS, this, CS_ALPHANUMERAL, QueryStringFlag::LengthIsInChars);
}
break;
case WID_PW_COLEC_DELETE:
if (this->callbacks.saved.contains(this->callbacks.sel_collection)) {
CloseChildWindows(WC_QUERY_STRING);
this->callbacks.edit_collection = this->callbacks.sel_collection;
this->inactive.contains(this->callbacks.sel_collection) ?
ShowQuery(GetEncodedString(STR_PICKER_COLLECTION_DELETE_QUERY), GetEncodedString(STR_PICKER_COLLECTION_DELETE_QUERY_DISABLED_TEXT), this, DeletePickerCollectionCallback) :
ShowQuery(GetEncodedString(STR_PICKER_COLLECTION_DELETE_QUERY), GetEncodedString(STR_PICKER_COLLECTION_DELETE_QUERY_TEXT), this, DeletePickerCollectionCallback);
}
break;
case WID_PW_CONFIGURE_BADGES:
if (this->badge_classes.GetClasses().empty()) break;
ShowDropDownList(this, BuildBadgeClassConfigurationList(this->badge_classes, 1, {}, COLOUR_DARK_GREEN), -1, widget, 0, DropDownOption::Persist);
@@ -457,9 +576,54 @@ void PickerWindow::OnClick(Point pt, WidgetID widget, int)
}
}
void PickerWindow::OnQueryTextFinished(std::optional<std::string> str)
{
if (!str.has_value()) return;
if (!this->callbacks.saved.contains(*str)) {
if (this->callbacks.saved.contains(this->callbacks.edit_collection) && this->callbacks.rename_collection) {
auto rename_collection = this->callbacks.saved.extract(this->callbacks.edit_collection);
rename_collection.key() = *str;
this->callbacks.saved.insert(std::move(rename_collection));
if (this->inactive.contains(this->callbacks.edit_collection)) {
this->inactive.erase(this->callbacks.edit_collection);
this->inactive.emplace(*str);
}
this->callbacks.rm_collections.emplace(this->callbacks.edit_collection);
this->callbacks.edit_collection.clear();
} else {
this->callbacks.saved.insert({*str, {}});
}
}
this->callbacks.sel_collection = *str;
picker_window = this;
SetWidgetsDisabledState(this->callbacks.sel_collection == "" ? true : false, WID_PW_COLEC_RENAME, WID_PW_COLEC_DELETE);
if (!IsWidgetLowered(WID_PW_MODE_SAVED)) {
this->InvalidateData({PickerInvalidation::Type, PickerInvalidation::Class});
}
this->InvalidateData({PickerInvalidation::Collection, PickerInvalidation::Position});
}
void PickerWindow::OnDropdownSelect(WidgetID widget, int index, int click_result)
{
switch (widget) {
case WID_PW_COLEC_LIST: {
auto it = this->collections.begin() + index;
if (this->callbacks.sel_collection != *it) {
this->callbacks.sel_collection = *it;
if (this->IsWidgetLowered(WID_PW_MODE_SAVED)) this->InvalidateData({PickerInvalidation::Class, PickerInvalidation::Type, PickerInvalidation::Validate});
this->InvalidateData(PickerInvalidation::Position);
}
SetWidgetsDisabledState(this->callbacks.sel_collection == "" ? true : false, WID_PW_COLEC_RENAME, WID_PW_COLEC_DELETE);
SndClickBeep();
break;
}
case WID_PW_CONFIGURE_BADGES: {
bool reopen = HandleBadgeConfigurationDropDownClick(this->callbacks.GetFeature(), 1, index, click_result, this->badge_filter_choices);
@@ -506,6 +670,7 @@ void PickerWindow::OnInvalidateData(int data, bool gui_scope)
if (pi.Test(PickerInvalidation::Class)) this->classes.ForceRebuild();
if (pi.Test(PickerInvalidation::Type)) this->types.ForceRebuild();
if (pi.Test(PickerInvalidation::Collection)) this->collections.ForceRebuild();
this->BuildPickerClassList();
if (pi.Test(PickerInvalidation::Validate)) this->EnsureSelectedClassIsValid();
@@ -515,6 +680,8 @@ void PickerWindow::OnInvalidateData(int data, bool gui_scope)
if (pi.Test(PickerInvalidation::Validate)) this->EnsureSelectedTypeIsValid();
if (pi.Test(PickerInvalidation::Position)) this->EnsureSelectedTypeIsVisible();
this->BuildPickerCollectionList();
if (this->has_type_picker) {
SetWidgetLoweredState(WID_PW_MODE_ALL, HasBit(this->callbacks.mode, PFM_ALL));
SetWidgetLoweredState(WID_PW_MODE_USED, HasBit(this->callbacks.mode, PFM_USED));
@@ -582,7 +749,8 @@ void PickerWindow::BuildPickerClassList()
for (int i = 0; i < count; i++) {
if (this->callbacks.GetClassName(i) == INVALID_STRING_ID) continue;
if (filter_used && std::none_of(std::begin(this->callbacks.used), std::end(this->callbacks.used), [i](const PickerItem &item) { return item.class_index == i; })) continue;
if (filter_saved && std::none_of(std::begin(this->callbacks.saved), std::end(this->callbacks.saved), [i](const PickerItem &item) { return item.class_index == i; })) continue;
if (filter_saved && this->callbacks.saved.find(this->callbacks.sel_collection) == this->callbacks.saved.end()) continue;
if (filter_saved && std::none_of(std::begin(this->callbacks.saved.at(this->callbacks.sel_collection)), std::end(this->callbacks.saved.at(this->callbacks.sel_collection)), [i](const PickerItem &item) { return item.class_index == i; })) continue;
this->classes.emplace_back(i);
}
@@ -656,10 +824,10 @@ void PickerWindow::BuildPickerTypeList()
if (this->callbacks.GetTypeName(item.class_index, item.index) == INVALID_STRING_ID) continue;
this->types.emplace_back(item);
}
} else if (filter_saved) {
} else if (filter_saved && this->callbacks.saved.contains(this->callbacks.sel_collection)) {
/* Showing only saved items. */
this->types.reserve(this->callbacks.saved.size());
for (const PickerItem &item : this->callbacks.saved) {
this->types.reserve(std::size(this->callbacks.saved.at(this->callbacks.sel_collection)));
for (const PickerItem &item : this->callbacks.saved.at(this->callbacks.sel_collection)) {
/* The used list may contain items that aren't currently loaded, skip these. */
if (item.class_index == -1) continue;
if (!show_all && item.class_index != cls_id) continue;
@@ -741,6 +909,30 @@ void PickerWindow::EnsureSelectedTypeIsVisible()
this->GetWidget<NWidgetMatrix>(WID_PW_TYPE_MATRIX)->SetClicked(pos);
}
/** Builds the filter list of collections. */
void PickerWindow::BuildPickerCollectionList()
{
if (!this->collections.NeedRebuild()) return;
int count = std::max(static_cast<int>(this->callbacks.saved.size()), 1);
this->collections.clear();
this->collections.reserve(count);
if (this->callbacks.saved.find("") == this->callbacks.saved.end()) {
this->collections.emplace_back("");
}
for (auto it = this->callbacks.saved.begin(); it != this->callbacks.saved.end(); it++) {
this->collections.emplace_back(it->first);
}
this->collections.RebuildDone();
this->collections.Sort();
if (!this->has_class_picker) return;
}
/** Create nested widgets for the class picker widgets. */
std::unique_ptr<NWidgetBase> MakePickerClassWidgets()
{
@@ -750,12 +942,24 @@ std::unique_ptr<NWidgetBase> MakePickerClassWidgets()
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(WWT_EDITBOX, COLOUR_DARK_GREEN, WID_PW_CLASS_FILTER), SetMinimalSize(144, 0), SetPadding(2), SetFill(1, 0), SetStringTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_PW_CLASS_LIST), SetFill(1, 1), SetResize(1, 1), SetPadding(WidgetDimensions::unscaled.picker),
SetMatrixDataTip(1, 0), SetScrollbar(WID_PW_CLASS_SCROLL),
/* Collection view */
NWidget(NWID_VERTICAL),
NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize),
NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_GREEN, WID_PW_COLEC_ADD), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_PICKER_COLLECTION_ADD, STR_PICKER_COLLECTION_ADD_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_GREEN, WID_PW_COLEC_RENAME), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_PICKER_COLLECTION_RENAME, STR_PICKER_COLLECTION_RENAME_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_GREEN, WID_PW_COLEC_DELETE), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_PICKER_COLLECTION_DELETE, STR_PICKER_COLLECTION_DELETE_TOOLTIP),
EndContainer(),
NWidget(WWT_DROPDOWN, COLOUR_DARK_GREEN, WID_PW_COLEC_LIST), SetMinimalSize(144, 12), SetFill(0, 1), SetResize(1, 0), SetToolTip(STR_PICKER_SELECT_COLLECTION_TOOLTIP),
EndContainer(),
/* Class view */
NWidget(NWID_VERTICAL),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_PW_CLASS_LIST), SetFill(1, 1), SetResize(1, 1), SetPadding(WidgetDimensions::unscaled.picker),
SetMatrixDataTip(1, 0), SetScrollbar(WID_PW_CLASS_SCROLL),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_CLASS_SCROLL),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_CLASS_SCROLL),
EndContainer(),
EndContainer(),
EndContainer(),

View File

@@ -66,7 +66,6 @@ public:
virtual StringID GetTypeTooltip() const = 0;
/** Get the number of types in a class. @note Used only to estimate space requirements. */
virtual int GetTypeCount(int cls_id) const = 0;
/** Get the selected type. */
virtual int GetSelectedType() const = 0;
/** Set the selected type. */
@@ -82,10 +81,38 @@ public:
/** Draw preview image of an item. */
virtual void DrawType(int x, int y, int cls_id, int id) const = 0;
/* Collection Callbacks */
/** Get the tooltip string for the collection list. */
virtual StringID GetCollectionTooltip() const = 0;
/** Fill a set with all items that are used by the current player. */
virtual void FillUsedItems(std::set<PickerItem> &items) = 0;
/** Update link between grfid/localidx and class_index/index in saved items. */
virtual std::set<PickerItem> UpdateSavedItems(const std::set<PickerItem> &src) = 0;
virtual std::map<std::string, std::set<PickerItem>> UpdateSavedItems(const std::map<std::string, std::set<PickerItem>> &src) = 0;
/**
* Initialize the list of active collections for sorting purposes.
* @param collections The map of collections to check.
* @return The set of collections with inactive items.
*/
inline std::set<std::string> InitializeInactiveCollections(const std::map<std::string, std::set<PickerItem>> collections)
{
std::set<std::string> inactive;
for (const auto &collection : collections) {
if ((collection.second.size() == 1 && collection.second.contains({})) || collection.first == "") continue;
for (const PickerItem &item : collection.second) {
if (item.class_index == -1 || item.index == -1) {
inactive.emplace(collection.first);
break;
}
if (GetTypeName(item.class_index, item.index) == INVALID_STRING_ID) {
inactive.emplace(collection.first);
break;
}
}
}
return inactive;
}
Listing class_last_sorting = { false, 0 }; ///< Default sorting of #PickerClassList.
Filtering class_last_filtering = { false, 0 }; ///< Default filtering of #PickerClassList.
@@ -93,13 +120,19 @@ public:
Listing type_last_sorting = { false, 0 }; ///< Default sorting of #PickerTypeList.
Filtering type_last_filtering = { false, 0 }; ///< Default filtering of #PickerTypeList.
Listing collection_last_sorting = { false, 0 }; ///< Default sorting of #PickerCollectionList.
const std::string ini_group; ///< Ini Group for saving favourites.
uint8_t mode = 0; ///< Bitmask of \c PickerFilterModes.
bool rename_collection = false; ///< Are we renaming a collection?
std::string sel_collection; ///< Currently selected collection of saved items.
std::string edit_collection; ///< Collection to rename or delete.
std::set<std::string> rm_collections; ///< Set of removed or renamed collections for updating ini file.
int preview_height = 0; ///< Previously adjusted height.
std::set<PickerItem> used; ///< Set of items used in the current game by the current company.
std::set<PickerItem> saved; ///< Set of saved favourite items.
std::map<std::string, std::set<PickerItem>> saved; ///< Set of saved collections of items.
};
/** Helper for PickerCallbacks when the class system is based on NewGRFClass. */
@@ -128,17 +161,24 @@ public:
return GetPickerItem(GetClass(cls_id)->GetSpec(id), cls_id, id);
}
std::set<PickerItem> UpdateSavedItems(const std::set<PickerItem> &src) override
std::map<std::string, std::set<PickerItem>> UpdateSavedItems(const std::map<std::string, std::set<PickerItem>> &src) override
{
if (src.empty()) return {};
std::set<PickerItem> dst;
for (const auto &item : src) {
const auto *spec = T::GetByGrf(item.grfid, item.local_id);
if (spec == nullptr) {
dst.emplace(item.grfid, item.local_id, -1, -1);
} else {
dst.emplace(GetPickerItem(spec));
std::map<std::string, std::set<PickerItem>> dst;
for (auto it = src.begin(); it != src.end(); it++) {
if (it->second.empty() || (it->second.size() == 1 && it->second.contains({}))) {
dst[it->first];
continue;
}
for (const auto &item : it->second) {
const auto *spec = T::GetByGrf(item.grfid, item.local_id);
if (spec == nullptr) {
dst[it->first].emplace(item.grfid, item.local_id, -1, -1);
} else {
dst[it->first].emplace(GetPickerItem(spec));
}
}
}
return dst;
@@ -153,6 +193,7 @@ struct PickerFilterData : StringFilter {
using PickerClassList = GUIList<int, std::nullptr_t, PickerFilterData &>; ///< GUIList holding classes to display.
using PickerTypeList = GUIList<PickerItem, std::nullptr_t, PickerFilterData &>; ///< GUIList holding classes/types to display.
using PickerCollectionList = GUIList<std::string, std::nullptr_t, PickerFilterData &>; ///< GUIList holding collections to display.
class PickerWindow : public PickerWindowBase {
public:
@@ -166,6 +207,7 @@ public:
enum class PickerInvalidation : uint8_t {
Class, ///< Refresh the class list.
Type, ///< Refresh the type list.
Collection, ///< Refresh the collection list.
Position, ///< Update scroll positions.
Validate, ///< Validate selected item.
Filter, ///< Update filter state.
@@ -186,17 +228,22 @@ public:
bool has_class_picker = false; ///< Set if this window has a class picker 'component'.
bool has_type_picker = false; ///< Set if this window has a type picker 'component'.
bool has_collection_picker = false; ///< Set if this window has a collection picker 'component'.
int preview_height = 0; ///< Height of preview images.
std::set<std::string> inactive; ///< Set of collections with inactive items.
PickerWindow(WindowDesc &desc, Window *parent, int window_number, PickerCallbacks &callbacks);
void OnInit() override;
void Close(int data = 0) override;
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override;
std::string GetWidgetString(WidgetID widget, StringID stringid) const override;
DropDownList BuildCollectionDropDownList();
void DrawWidget(const Rect &r, WidgetID widget) const override;
void OnDropdownSelect(WidgetID widget, int index, int click_result) override;
void OnResize() override;
void static DeletePickerCollectionCallback(Window *win, bool confirmed);
void OnClick(Point pt, WidgetID widget, int click_count) override;
void OnQueryTextFinished(std::optional<std::string> str) override;
void OnInvalidateData(int data = 0, bool gui_scope = true) override;
EventState OnHotkey(int hotkey) override;
void OnEditboxChanged(WidgetID wid) override;
@@ -231,6 +278,10 @@ private:
void EnsureSelectedTypeIsValid();
void EnsureSelectedTypeIsVisible();
PickerCollectionList collections; ///< List of collections.
void BuildPickerCollectionList();
GUIBadgeClasses badge_classes;
std::pair<WidgetID, WidgetID> badge_filters{};
BadgeFilterChoices badge_filter_choices{};

View File

@@ -978,6 +978,7 @@ public:
StringID GetClassTooltip() const override { return STR_PICKER_STATION_CLASS_TOOLTIP; }
StringID GetTypeTooltip() const override { return STR_PICKER_STATION_TYPE_TOOLTIP; }
StringID GetCollectionTooltip() const override { return STR_PICKER_STATION_COLLECTION_TOOLTIP; }
bool IsActive() const override
{
@@ -1796,6 +1797,7 @@ public:
StringID GetClassTooltip() const override { return STR_PICKER_WAYPOINT_CLASS_TOOLTIP; }
StringID GetTypeTooltip() const override { return STR_PICKER_WAYPOINT_TYPE_TOOLTIP; }
StringID GetCollectionTooltip() const override { return STR_PICKER_WAYPOINT_COLLECTION_TOOLTIP; }
bool IsActive() const override
{

View File

@@ -1200,6 +1200,7 @@ public:
StringID GetClassTooltip() const override;
StringID GetTypeTooltip() const override;
StringID GetCollectionTooltip() const override;
bool IsActive() const override
{
@@ -1290,9 +1291,11 @@ public:
template <> StringID RoadStopPickerCallbacks<RoadStopType::Bus>::GetClassTooltip() const { return STR_PICKER_ROADSTOP_BUS_CLASS_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<RoadStopType::Bus>::GetTypeTooltip() const { return STR_PICKER_ROADSTOP_BUS_TYPE_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<RoadStopType::Bus>::GetCollectionTooltip() const { return STR_PICKER_ROADSTOP_BUS_COLLECTION_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<RoadStopType::Truck>::GetClassTooltip() const { return STR_PICKER_ROADSTOP_TRUCK_CLASS_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<RoadStopType::Truck>::GetTypeTooltip() const { return STR_PICKER_ROADSTOP_TRUCK_TYPE_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<RoadStopType::Truck>::GetCollectionTooltip() const { return STR_PICKER_ROADSTOP_TRUCK_COLLECTION_TOOLTIP; }
static RoadStopPickerCallbacks<RoadStopType::Bus> _bus_callback_instance("fav_passenger_roadstops");
static RoadStopPickerCallbacks<RoadStopType::Truck> _truck_callback_instance("fav_freight_roadstops");
@@ -1632,6 +1635,7 @@ public:
StringID GetClassTooltip() const override { return STR_PICKER_WAYPOINT_CLASS_TOOLTIP; }
StringID GetTypeTooltip() const override { return STR_PICKER_WAYPOINT_TYPE_TOOLTIP; }
StringID GetCollectionTooltip() const override { return STR_PICKER_WAYPOINT_COLLECTION_TOOLTIP; }
bool IsActive() const override
{

View File

@@ -1491,6 +1491,7 @@ public:
StringID GetClassTooltip() const override { return STR_PICKER_HOUSE_CLASS_TOOLTIP; }
StringID GetTypeTooltip() const override { return STR_PICKER_HOUSE_TYPE_TOOLTIP; }
StringID GetCollectionTooltip() const override { return STR_PICKER_HOUSE_COLLECTION_TOOLTIP; }
bool IsActive() const override { return true; }
bool HasClassChoice() const override { return true; }
@@ -1580,27 +1581,34 @@ public:
}
}
std::set<PickerItem> UpdateSavedItems(const std::set<PickerItem> &src) override
std::map<std::string, std::set<PickerItem>> UpdateSavedItems(const std::map<std::string, std::set<PickerItem>> &src) override
{
if (src.empty()) return src;
const auto &specs = HouseSpec::Specs();
std::set<PickerItem> dst;
for (const auto &item : src) {
if (item.grfid == 0) {
const HouseSpec *hs = HouseSpec::Get(item.local_id);
if (hs == nullptr) continue;
int class_index = GetClassIdFromHouseZone(hs->building_availability);
dst.emplace(item.grfid, item.local_id, class_index, item.local_id);
} else {
/* Search for spec by grfid and local index. */
auto it = std::ranges::find_if(specs, [&item](const HouseSpec &spec) { return spec.grf_prop.grfid == item.grfid && spec.grf_prop.local_id == item.local_id; });
if (it == specs.end()) {
/* Not preset, hide from UI. */
dst.emplace(item.grfid, item.local_id, -1, -1);
std::map<std::string, std::set<PickerItem>> dst;
for (auto group_it = src.begin(); group_it != src.end(); group_it++) {
if (group_it->second.empty() || (group_it->second.size() == 1 && group_it->second.contains({}))) {
dst[group_it->first];
continue;
}
for (const auto &item : group_it->second) {
if (item.grfid == 0) {
const HouseSpec *hs = HouseSpec::Get(item.local_id);
if (hs == nullptr) continue;
int class_index = GetClassIdFromHouseZone(hs->building_availability);
dst[group_it->first].emplace(item.grfid, item.local_id, class_index, item.local_id);
} else {
int class_index = GetClassIdFromHouseZone(it->building_availability);
dst.emplace(item.grfid, item.local_id, class_index, it->Index());
/* Search for spec by grfid and local index. */
auto it = std::ranges::find_if(specs, [&item](const HouseSpec &spec) { return spec.grf_prop.grfid == item.grfid && spec.grf_prop.local_id == item.local_id; });
if (it == specs.end()) {
/* Not preset, hide from UI. */
dst[group_it->first].emplace(item.grfid, item.local_id, -1, -1);
} else {
int class_index = GetClassIdFromHouseZone(it->building_availability);
dst[group_it->first].emplace(item.grfid, item.local_id, class_index, it->Index());
}
}
}
}

View File

@@ -19,6 +19,11 @@ enum PickerClassWindowWidgets : WidgetID {
WID_PW_CLASS_LIST, ///< List of classes.
WID_PW_CLASS_SCROLL, ///< Scrollbar for list of classes.
WID_PW_COLEC_LIST, ///< List of collections.
WID_PW_COLEC_ADD, ///< Button to create a new collections.
WID_PW_COLEC_RENAME, ///< Button to rename a collections.
WID_PW_COLEC_DELETE, ///< Button to delete a collection.
WID_PW_TYPE_SEL, ///< Stack to hide the type picker.
WID_PW_TYPE_FILTER, ///< Text filter.
WID_PW_MODE_ALL, ///< Toggle "Show all" filter mode.
@@ -32,7 +37,7 @@ enum PickerClassWindowWidgets : WidgetID {
WID_PW_TYPE_NAME, ///< Name of selected item.
WID_PW_TYPE_RESIZE, ///< Type resize handle.
WID_PW_CONFIGURE_BADGES, ///< Button to configure badges.
WID_PW_BADGE_FILTER, ///< Container for dropdown badge filters.
WID_PW_BADGE_FILTER, ///< Container for dropdown badge filters. Must be last in this list.
};
#endif /* WIDGETS_PICKER_WIDGET_H */