Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-filter for recipes in crafting menu #27500

Merged
merged 10 commits into from Jan 12, 2019
120 changes: 66 additions & 54 deletions src/crafting_gui.cpp
Expand Up @@ -258,64 +258,75 @@ const recipe *select_crafting_recipe( int &batch_size )
std::vector<const recipe *> picking;
if( !filterstring.empty() ) {
auto qry = trim( filterstring );
if( qry.size() > 2 && qry[1] == ':' ) {
switch( qry[0] ) {
case 't':
picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::tool );
break;

case 'c':
picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::component );
break;

case 's':
picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::skill );
break;

case 'p':
picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::primary_skill );
break;

case 'Q':
picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::quality );
break;

case 'q':
picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::quality_result );
break;

case 'd':
picking = available_recipes.search( qry.substr( 2 ),
recipe_subset::search_type::description_result );
break;

case 'm': {
auto &learned = g->u.get_learned_recipes();
if( query_is_yes( qry ) ) {
std::set_intersection( available_recipes.begin(), available_recipes.end(), learned.begin(),
learned.end(), std::back_inserter( picking ) );
} else {
std::set_difference( available_recipes.begin(), available_recipes.end(), learned.begin(),
learned.end(),
std::back_inserter( picking ) );
size_t qry_begin = 0;
size_t qry_end = 0;
recipe_subset filtered_recipes = available_recipes;
do {
// Find next ','
qry_end = qry.find_first_of( ',', qry_begin );

auto qry_filter_str = trim( qry.substr( qry_begin, qry_end - qry_begin ) );
// Process filter
if( qry_filter_str.size() > 2 && qry_filter_str[1] == ':' ) {
switch( qry_filter_str[0] ) {
case 't':
filtered_recipes = filtered_recipes.reduce( qry_filter_str.substr( 2 ), recipe_subset::search_type::tool );
break;

case 'c':
filtered_recipes = filtered_recipes.reduce( qry_filter_str.substr( 2 ), recipe_subset::search_type::component );
break;

case 's':
filtered_recipes = filtered_recipes.reduce( qry_filter_str.substr( 2 ), recipe_subset::search_type::skill );
break;

case 'p':
filtered_recipes = filtered_recipes.reduce( qry_filter_str.substr( 2 ), recipe_subset::search_type::primary_skill );
break;

case 'Q':
filtered_recipes = filtered_recipes.reduce( qry_filter_str.substr( 2 ), recipe_subset::search_type::quality );
break;

case 'q':
filtered_recipes = filtered_recipes.reduce( qry_filter_str.substr( 2 ), recipe_subset::search_type::quality_result );
break;

case 'd':
filtered_recipes = filtered_recipes.reduce( qry_filter_str.substr( 2 ), recipe_subset::search_type::description_result );
break;

case 'm': {
auto &learned = g->u.get_learned_recipes();
recipe_subset temp_subset;
if( query_is_yes( qry_filter_str ) ) {
temp_subset = available_recipes.intersection( learned );
} else {
temp_subset = available_recipes.difference( learned );
}
filtered_recipes = filtered_recipes.intersection( temp_subset );
break;
}
break;
}

case 'h': {
std::copy( available_recipes.begin(), available_recipes.end(), std::back_inserter( picking ) );
if( query_is_yes( qry ) ) {
show_hidden = true;
case 'h': {
filtered_recipes = filtered_recipes.intersection( available_recipes );
if( query_is_yes( qry_filter_str ) ) {
show_hidden = true;
}
break;
}
break;
}

default:
current.clear();
default:
current.clear();
}
} else {
filtered_recipes = filtered_recipes.reduce( qry_filter_str );
}
} else {
picking = available_recipes.search( qry );
}

qry_begin = qry_end + 1;
} while( qry_end != std::string::npos );
picking.insert( picking.end(), filtered_recipes.begin(), filtered_recipes.end() );
} else if( subtab.cur() == "CSC_*_FAVORITE" ) {
picking = available_recipes.favorite();
} else if( subtab.cur() == "CSC_*_RECENT" ) {
Expand Down Expand Up @@ -703,7 +714,8 @@ const recipe *select_crafting_recipe( int &batch_size )

std::string description =
_( "The default is to search result names. Some single-character prefixes "
"can be used with a colon (:) to search in other ways.\n"
"can be used with a colon (:) to search in other ways. Additional filters "
"are separated by commas (,).\n"
"\n"
"<color_white>Examples:</color>\n" );

Expand Down
32 changes: 31 additions & 1 deletion src/recipe_dictionary.cpp
Expand Up @@ -120,7 +120,6 @@ std::vector<const recipe *> recipe_subset::recent() const

return res;
}

std::vector<const recipe *> recipe_subset::search( const std::string &txt,
const search_type key ) const
{
Expand Down Expand Up @@ -169,6 +168,37 @@ std::vector<const recipe *> recipe_subset::search( const std::string &txt,

return res;
}
static void insert_vector( const recipe_subset &src, recipe_subset &dst, const std::vector<const recipe *> &recipes ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a recipe_subset constructor, rather than a static member function (Assuming I'm correct in thinking that you only ever use it to insert into an empty recipe_subset).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your assumption is correct, only ever adding to an empty recipe_subset. A constructor would take the const recipe_subset &src and const std::vector<const recipe *> &recipes arguments and transplant the functionality I guess.
recipe_subset doesn't currently have a constructor so I would need to define additional constructors (empty/copy at minimum). This is the only situation where we have a possible want for a new constructor, and at the moment would only be useful in the reduce/intersection/difference functions.
Unless there is a broader requirement for new constructors I am reluctant to add one.

Copy link
Contributor

@jbytheway jbytheway Jan 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recipe_subset doesn't currently have a constructor so I would need to define additional constructors (empty/copy at minimum). This is the only situation where we have a possible want for a new constructor, and at the moment would only be useful in the reduce/intersection/difference functions.

You don't need to add a copy constructor; the default continues to exist even if you define others. And the default constructor is just a one line recipe_subset() = default;.

Not vital; I think it would be neater, but that's subjective.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah okay, then my argument against is mostly moot. I'll try out the addition of recipe_subset() = default; and recipe_subset( const recipe_subset &src, const std::vector<const recipe *> ); constructors. Probably is neater, at the very least it should clean up the reduce/intersection/difference functions a little.

for( const auto elem : recipes ) {
dst.include( elem, src.get_custom_difficulty( elem ) );
}
}

recipe_subset recipe_subset::reduce( const std::string &txt, const search_type key ) const {
recipe_subset result;

insert_vector( *this, result, search( txt, key ) );

return result;
}
recipe_subset recipe_subset::intersection( const recipe_subset &subset ) const {
recipe_subset result;

std::vector<const recipe *> intersection_result;
std::set_intersection( this->begin(), this->end(), subset.begin(), subset.end(), std::back_inserter( intersection_result ) );
insert_vector( *this, result, intersection_result );

return result;
}
recipe_subset recipe_subset::difference( const recipe_subset &subset ) const {
recipe_subset result;

std::vector<const recipe *> difference_result;
std::set_difference( this->begin(), this->end(), subset.begin(), subset.end(), std::back_inserter( difference_result ) );
insert_vector( *this, result, difference_result );

return result;
}

std::vector<const recipe *> recipe_subset::search_result( const itype_id &item ) const
{
Expand Down
6 changes: 6 additions & 0 deletions src/recipe_dictionary.h
Expand Up @@ -133,6 +133,12 @@ class recipe_subset
/** Find recipes matching query (left anchored partial matches are supported) */
std::vector<const recipe *> search( const std::string &txt,
const search_type key = search_type::name ) const;
/** Find recipes matching query and return a new recipe_subset */
recipe_subset reduce( const std::string &txt, const search_type key = search_type::name ) const;
/** Set intersection between recipe_subsets */
recipe_subset intersection( const recipe_subset &subset ) const;
/** Set difference between recipe_subsets */
recipe_subset difference( const recipe_subset &subset ) const;
/** Find recipes producing the item */
std::vector<const recipe *> search_result( const itype_id &item ) const;

Expand Down