Skip to content

Commit

Permalink
Add options to sort item list by item name (#79393)
Browse files Browse the repository at this point in the history
* Added option to sort listed items by name

* Use unprefixed tname
Changed to case-insensitive sorting

* Only sort the list when necessary

* Removed unnecessary variable assignments
Moved variables closer to relevant code

* AStyle reformatting

* clang-tidy fixes
Extracted item name comparison to function

* Changes recommended by reviewers

* More clang-tidy fixes
  • Loading branch information
LegionnaireLenny authored Jan 30, 2025
1 parent 117147f commit f510ba6
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 61 deletions.
13 changes: 13 additions & 0 deletions src/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ struct enum_traits<bionic_ui_sort_mode> {
static constexpr bionic_ui_sort_mode last = bionic_ui_sort_mode::nsort;
};

enum class list_item_sort_mode : int {
DISTANCE,
NAME,
CATEGORY_DISTANCE,
CATEGORY_NAME,
count
};

template<>
struct enum_traits<list_item_sort_mode> {
static constexpr list_item_sort_mode last = list_item_sort_mode::count;
};

// When bool is not enough. NONE, SOME or ALL
enum class trinary : int {
NONE = 0,
Expand Down
160 changes: 104 additions & 56 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8391,7 +8391,8 @@ bool game::take_screenshot() const
#endif

//helper method so we can keep list_items shorter
void game::reset_item_list_state( const catacurses::window &window, int height, bool bRadiusSort )
void game::reset_item_list_state( const catacurses::window &window, int height,
list_item_sort_mode sortMode )
{
const int width = getmaxx( window );
wattron( window, c_light_gray );
Expand All @@ -8414,12 +8415,24 @@ void game::reset_item_list_state( const catacurses::window &window, int height,
wprintz( window, c_white, _( "Items" ) );

std::string sSort;
if( bRadiusSort ) {
//~ Sort type: distance.
sSort = _( "<s>ort: dist" );
} else {
//~ Sort type: category.
sSort = _( "<s>ort: cat" );
switch( sortMode ) {
// Sort by distance only
case list_item_sort_mode::count:
case list_item_sort_mode::DISTANCE:
sSort = _( "<s>ort: dist" );
break;
// Sort by name only
case list_item_sort_mode::NAME:
sSort = _( "<s>ort: name" );
break;
// Group by category, sort by distance
case list_item_sort_mode::CATEGORY_DISTANCE:
sSort = _( "<s>ort: cat-dist" );
break;
// Group by category, sort by item name
case list_item_sort_mode::CATEGORY_NAME:
sSort = _( "<s>ort: cat-name" );
break;
}

int letters = utf8_width( sSort );
Expand Down Expand Up @@ -8456,6 +8469,23 @@ void game::reset_item_list_state( const catacurses::window &window, int height,
}
}

template<>
std::string io::enum_to_string<list_item_sort_mode>( list_item_sort_mode data )
{
switch( data ) {
case list_item_sort_mode::count:
case list_item_sort_mode::DISTANCE:
return "DISTANCE";
case list_item_sort_mode::NAME:
return "NAME";
case list_item_sort_mode::CATEGORY_DISTANCE:
return "CATEGORY_DISTANCE";
case list_item_sort_mode::CATEGORY_NAME:
return "CATEGORY_NAME";
}
cata_fatal( "Invalid list item sort mode" );
}

void game::list_items_monsters()
{
// Search whole reality bubble because each function internally verifies
Expand Down Expand Up @@ -8604,10 +8634,6 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
} );
ui.mark_resize();

// use previously selected sorting method
bool sort_radius = uistate.list_item_sort != 2;
bool addcategory = !sort_radius;

// reload filter/priority settings on the first invocation, if they were active
if( !uistate.list_item_init ) {
if( uistate.list_item_filter_active ) {
Expand All @@ -8634,39 +8660,13 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
u.view_offset = tripoint_rel_ms::zero;

int iActive = 0; // Item index that we're looking at
bool refilter = true;
int page_num = 0;
int iCatSortNum = 0;
int iScrollPos = 0;
std::map<int, std::string> mSortCategory;

std::string action;
input_context ctxt( "LIST_ITEMS" );
ctxt.register_action( "UP", to_translation( "Move cursor up" ) );
ctxt.register_action( "DOWN", to_translation( "Move cursor down" ) );
ctxt.register_action( "LEFT", to_translation( "Previous item" ) );
ctxt.register_action( "RIGHT", to_translation( "Next item" ) );
ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) );
ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) );
ctxt.register_action( "SCROLL_ITEM_INFO_DOWN" );
ctxt.register_action( "SCROLL_ITEM_INFO_UP" );
ctxt.register_action( "zoom_in" );
ctxt.register_action( "zoom_out" );
ctxt.register_action( "NEXT_TAB" );
ctxt.register_action( "PREV_TAB" );
ctxt.register_action( "HELP_KEYBINDINGS" );
ctxt.register_action( "QUIT" );
ctxt.register_action( "FILTER" );
ctxt.register_action( "RESET_FILTER" );
ctxt.register_action( "EXAMINE" );
ctxt.register_action( "COMPARE" );
ctxt.register_action( "PRIORITY_INCREASE" );
ctxt.register_action( "PRIORITY_DECREASE" );
ctxt.register_action( "SORT" );
ctxt.register_action( "TRAVEL_TO" );

ui.on_redraw( [&]( ui_adaptor & ui ) {
reset_item_list_state( w_items_border, iInfoHeight, sort_radius );
reset_item_list_state( w_items_border, iInfoHeight, uistate.list_item_sort );

int iStartPos = 0;
if( ground_items.empty() ) {
Expand Down Expand Up @@ -8811,6 +8811,35 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
trail_end_x );
add_draw_callback( trail_cb );

bool addCategory = uistate.list_item_sort == list_item_sort_mode::CATEGORY_DISTANCE ||
uistate.list_item_sort == list_item_sort_mode::CATEGORY_NAME;
bool refilter = true;

std::string action;
input_context ctxt( "LIST_ITEMS" );
ctxt.register_action( "UP", to_translation( "Move cursor up" ) );
ctxt.register_action( "DOWN", to_translation( "Move cursor down" ) );
ctxt.register_action( "LEFT", to_translation( "Previous item" ) );
ctxt.register_action( "RIGHT", to_translation( "Next item" ) );
ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) );
ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) );
ctxt.register_action( "SCROLL_ITEM_INFO_DOWN" );
ctxt.register_action( "SCROLL_ITEM_INFO_UP" );
ctxt.register_action( "zoom_in" );
ctxt.register_action( "zoom_out" );
ctxt.register_action( "NEXT_TAB" );
ctxt.register_action( "PREV_TAB" );
ctxt.register_action( "HELP_KEYBINDINGS" );
ctxt.register_action( "QUIT" );
ctxt.register_action( "FILTER" );
ctxt.register_action( "RESET_FILTER" );
ctxt.register_action( "EXAMINE" );
ctxt.register_action( "COMPARE" );
ctxt.register_action( "PRIORITY_INCREASE" );
ctxt.register_action( "PRIORITY_DECREASE" );
ctxt.register_action( "SORT" );
ctxt.register_action( "TRAVEL_TO" );

do {
bool recalc_unread = false;
if( action == "COMPARE" && activeItem ) {
Expand All @@ -8825,14 +8854,12 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
imgui_popup.set_identifier( "item_filter" );
sFilter = imgui_popup.query();
refilter = true;
addcategory = !sort_radius;
uistate.list_item_filter_active = !sFilter.empty();
} else if( action == "RESET_FILTER" ) {
sFilter.clear();
filtered_items = ground_items;
refilter = true;
uistate.list_item_filter_active = false;
addcategory = !sort_radius;
} else if( action == "EXAMINE" && !filtered_items.empty() && activeItem ) {
std::vector<iteminfo> vThisItem;
std::vector<iteminfo> vDummy;
Expand Down Expand Up @@ -8860,7 +8887,6 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
.max_length( 256 )
.query_string();
refilter = true;
addcategory = !sort_radius;
uistate.list_item_priority_active = !list_item_upvote.empty();
} else if( action == "PRIORITY_DECREASE" ) {
ui.invalidate_ui();
Expand All @@ -8875,17 +8901,24 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
.max_length( 256 )
.query_string();
refilter = true;
addcategory = !sort_radius;
uistate.list_item_downvote_active = !list_item_downvote.empty();
} else if( action == "SORT" ) {
if( sort_radius ) {
sort_radius = false;
addcategory = true;
uistate.list_item_sort = 2; // list is sorted by category
} else {
sort_radius = true;
uistate.list_item_sort = 1; // list is sorted by distance
switch( uistate.list_item_sort ) {
case list_item_sort_mode::count:
case list_item_sort_mode::DISTANCE:
uistate.list_item_sort = list_item_sort_mode::NAME;
break;
case list_item_sort_mode::NAME:
uistate.list_item_sort = list_item_sort_mode::CATEGORY_DISTANCE;
break;
case list_item_sort_mode::CATEGORY_DISTANCE:
uistate.list_item_sort = list_item_sort_mode::CATEGORY_NAME;
break;
case list_item_sort_mode::CATEGORY_NAME:
uistate.list_item_sort = list_item_sort_mode::DISTANCE;
break;
}

highPEnd = -1;
lowPStart = -1;
iCatSortNum = 0;
Expand All @@ -8904,14 +8937,29 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
break;
}
}
if( uistate.list_item_sort == 1 ) {
ground_items = item_list;
} else if( uistate.list_item_sort == 2 ) {
std::sort( ground_items.begin(), ground_items.end(), map_item_stack::map_item_stack_sort );
}

if( refilter ) {
switch( uistate.list_item_sort ) {
case list_item_sort_mode::count:
case list_item_sort_mode::DISTANCE:
ground_items = item_list;
break;
case list_item_sort_mode::NAME:
std::sort( ground_items.begin(), ground_items.end(), map_item_stack::map_item_stack_sort_name );
break;
case list_item_sort_mode::CATEGORY_DISTANCE:
std::sort( ground_items.begin(), ground_items.end(),
map_item_stack::map_item_stack_sort_category_distance );
break;
case list_item_sort_mode::CATEGORY_NAME:
std::sort( ground_items.begin(), ground_items.end(),
map_item_stack::map_item_stack_sort_category_name );
break;
}

refilter = false;
addCategory = uistate.list_item_sort == list_item_sort_mode::CATEGORY_DISTANCE ||
uistate.list_item_sort == list_item_sort_mode::CATEGORY_NAME;
filtered_items = filter_item_stacks( ground_items, sFilter );
highPEnd = list_filter_high_priority( filtered_items, list_item_upvote );
lowPStart = list_filter_low_priority( filtered_items, highPEnd, list_item_downvote );
Expand All @@ -8920,8 +8968,8 @@ game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
iItemNum = filtered_items.size();
}

if( addcategory ) {
addcategory = false;
if( addCategory ) {
addCategory = false;
iCatSortNum = 0;
mSortCategory.clear();
if( highPEnd > 0 ) {
Expand Down
3 changes: 2 additions & 1 deletion src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,8 @@ class game

game::vmenu_ret list_items( const std::vector<map_item_stack> &item_list );
std::vector<map_item_stack> find_nearby_items( int iRadius );
void reset_item_list_state( const catacurses::window &window, int height, bool bRadiusSort );
void reset_item_list_state( const catacurses::window &window, int height,
list_item_sort_mode sortMode );

game::vmenu_ret list_monsters( const std::vector<Creature *> &monster_list );

Expand Down
33 changes: 32 additions & 1 deletion src/map_item_stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "item_category.h"
#include "item_search.h"
#include "line.h"
#include "localized_comparator.h"

map_item_stack::item_group::item_group() : count( 0 ), it( nullptr )
{
Expand Down Expand Up @@ -43,7 +44,18 @@ void map_item_stack::add_at_pos( const item *const it, const tripoint_rel_ms &po
totalcount += amount;
}

bool map_item_stack::map_item_stack_sort( const map_item_stack &lhs, const map_item_stack &rhs )
bool map_item_stack::compare_item_names( const map_item_stack &lhs, const map_item_stack &rhs )
{
std::string left = lhs.example->tname( 1, tname::unprefixed_tname );
std::string right = rhs.example->tname( 1, tname::unprefixed_tname );
transform( left.begin(), left.end(), left.begin(), ::tolower );
transform( right.begin(), right.end(), right.begin(), ::tolower );

return localized_compare( left, right );
}

bool map_item_stack::map_item_stack_sort_category_distance( const map_item_stack &lhs,
const map_item_stack &rhs )
{
const item_category &lhs_cat = lhs.example->get_category_of_contents();
const item_category &rhs_cat = rhs.example->get_category_of_contents();
Expand All @@ -56,6 +68,25 @@ bool map_item_stack::map_item_stack_sort( const map_item_stack &lhs, const map_i
return lhs_cat < rhs_cat;
}

bool map_item_stack::map_item_stack_sort_category_name( const map_item_stack &lhs,
const map_item_stack &rhs )
{
const item_category &lhs_cat = lhs.example->get_category_of_contents();
const item_category &rhs_cat = rhs.example->get_category_of_contents();

if( lhs_cat == rhs_cat ) {
return compare_item_names( lhs, rhs );
}

return lhs_cat < rhs_cat;
}

bool map_item_stack::map_item_stack_sort_name( const map_item_stack &lhs,
const map_item_stack &rhs )
{
return compare_item_names( lhs, rhs );
}

std::vector<map_item_stack> filter_item_stacks( const std::vector<map_item_stack> &stack,
const std::string &filter )
{
Expand Down
8 changes: 7 additions & 1 deletion src/map_item_stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ class map_item_stack
// through all older item groups for a match.
void add_at_pos( const item *it, const tripoint_rel_ms &pos );

static bool map_item_stack_sort( const map_item_stack &lhs, const map_item_stack &rhs );
static bool compare_item_names( const map_item_stack &lhs,
const map_item_stack &rhs );
static bool map_item_stack_sort_category_distance( const map_item_stack &lhs,
const map_item_stack &rhs );
static bool map_item_stack_sort_category_name( const map_item_stack &lhs,
const map_item_stack &rhs );
static bool map_item_stack_sort_name( const map_item_stack &lhs, const map_item_stack &rhs );
};

std::vector<map_item_stack> filter_item_stacks( const std::vector<map_item_stack> &stack,
Expand Down
4 changes: 2 additions & 2 deletions src/uistate.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ class uistatedata
bool numpad_navigation = false;

// V Menu Stuff
int list_item_sort = 0;
list_item_sort_mode list_item_sort = list_item_sort_mode::DISTANCE;
std::set<itype_id> read_items;

// These three aren't serialized because deserialize can extraect them
// These three aren't serialized because deserialize can extract them
// from the history
std::string list_item_filter; // NOLINT(cata-serialize)
std::string list_item_downvote; // NOLINT(cata-serialize)
Expand Down

0 comments on commit f510ba6

Please sign in to comment.