Skip to content

Commit

Permalink
Add dynamic category (type) filter
Browse files Browse the repository at this point in the history
Item categories are generated dynamically based on the users item icon
paths.  Categories with no items available will not be listed and any
new item categories introduced in the future will automatically be
available for selection.
  • Loading branch information
ericsium committed Mar 22, 2017
1 parent a8b4888 commit 1c5f453
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 5 deletions.
67 changes: 65 additions & 2 deletions src/filters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@
#include <QCheckBox>
#include <QGroupBox>
#include <QLineEdit>
#include <QCompleter>
#include <QComboBox>
#include <boost/algorithm/string/case_conv.hpp>

#include "buyoutmanager.h"
#include "filters.h"
#include "util.h"
#include "porting.h"

const std::string CategorySearchFilter::k_Default = "<any>";

std::unique_ptr<FilterData> Filter::CreateData() {
return std::make_unique<FilterData>(this);
}
Expand Down Expand Up @@ -78,13 +83,71 @@ bool NameSearchFilter::Matches(const std::shared_ptr<Item> &item, FilterData *da
return name.find(query) != std::string::npos;
}

void NameSearchFilter::Initialize(QLayout *parent) {
void NameSearchFilter::Initialize(QLayout *parent) {
QWidget *group = new QWidget;
QHBoxLayout *layout = new QHBoxLayout;
layout->setMargin(0);
QLabel *label = new QLabel("Name");
label->setFixedWidth(Util::TextWidth(TextWidthId::WIDTH_LABEL));
label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
textbox_ = new QLineEdit;
parent->addWidget(textbox_);
layout->addWidget(label);
layout->addWidget(textbox_);
group->setLayout(layout);
parent->addWidget(group);
QObject::connect(textbox_, SIGNAL(textEdited(const QString&)),
parent->parentWidget()->window(), SLOT(OnDelayedSearchFormChange()));
}

CategorySearchFilter::CategorySearchFilter(QLayout *parent, QAbstractListModel *model):
model_(model)
{
Initialize(parent);
}

void CategorySearchFilter::FromForm(FilterData *data) {
std::string current_text = combobox_->currentText().toStdString();
boost::to_lower(current_text);
data->text_query = (current_text == k_Default) ? "":current_text;
}

void CategorySearchFilter::ToForm(FilterData *data) {
auto index = combobox_->findText(data->text_query.c_str(), Qt::MatchFixedString);
combobox_->setCurrentIndex(std::max(0, index));
}

void CategorySearchFilter::ResetForm() {
combobox_->setCurrentText(k_Default.c_str());
}

bool CategorySearchFilter::Matches(const std::shared_ptr<Item> &item, FilterData *data) {
return item->category().find(data->text_query) != std::string::npos;
}

void CategorySearchFilter::Initialize(QLayout *parent) {
QWidget *group = new QWidget;
QHBoxLayout *layout = new QHBoxLayout;
layout->setMargin(0);
QLabel *label = new QLabel("Type");
label->setFixedWidth(Util::TextWidth(TextWidthId::WIDTH_LABEL));
label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
combobox_ = new QComboBox;
combobox_->setModel(model_);
combobox_->setEditable(true);
combobox_->setInsertPolicy(QComboBox::NoInsert);
completer_ = new QCompleter(combobox_->model());
completer_->setCompletionMode(QCompleter::PopupCompletion);
completer_->setFilterMode(Qt::MatchContains);
completer_->setCaseSensitivity(Qt::CaseInsensitive);
combobox_->setCompleter(completer_);
layout->addWidget(label);
layout->addWidget(combobox_);
group->setLayout(layout);
parent->addWidget(group);
QObject::connect(combobox_, SIGNAL(currentIndexChanged(const QString&)),
parent->parentWidget()->window(), SLOT(OnDelayedSearchFormChange()));
}

MinMaxFilter::MinMaxFilter(QLayout *parent, std::string property):
property_(property),
caption_(property)
Expand Down
18 changes: 18 additions & 0 deletions src/filters.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
class QLineEdit;
class QCheckBox;
class FilterData;
class QComboBox;
class QCompleter;
class QAbstractListModel;

/*
* Objects of subclasses of this class do the following:
Expand Down Expand Up @@ -97,6 +100,21 @@ class NameSearchFilter : public Filter {
QLineEdit *textbox_;
};

class CategorySearchFilter : public Filter {
public:
explicit CategorySearchFilter(QLayout *parent, QAbstractListModel *model);
void FromForm(FilterData *data);
void ToForm(FilterData *data);
void ResetForm();
bool Matches(const std::shared_ptr<Item> &item, FilterData *data);
void Initialize(QLayout *parent);
static const std::string k_Default;
private:
QComboBox *combobox_;
QCompleter *completer_;
QAbstractListModel *model_;
};

class MinMaxFilter : public Filter {
public:
MinMaxFilter(QLayout *parent, std::string property);
Expand Down
41 changes: 39 additions & 2 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,34 @@

#include <utility>
#include <QString>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string.hpp>
#include <regex>
#include "rapidjson/document.h"

#include "modlist.h"
#include "util.h"
#include "porting.h"
#include "itemlocation.h"

const std::array<Item::CategoryReplaceMap, Item::k_CategoryLevels> Item::replace_map_ = {
// Category hierarchy 0 replacement map
Item::CategoryReplaceMap(),
// Category hierarchy 1 replacement map
Item::CategoryReplaceMap({{"BodyArmours", "Body"},
{"VaalGems", "Vaal"},
{"AtlasMaps", "Atlas"},
{"act4maps", "Act4"},
{"OneHandWeapons", "OneHand"},
{"TwoHandWeapons", "TwoHand"}}),
// Category hierarchy 2 replacement map
Item::CategoryReplaceMap({{"OneHandAxes", "Axes"},
{"OneHandMaces", "Maces"},
{"OneHandSwords", "Swords"},
{"TwoHandAxes", "Axes"},
{"TwoHandMaces", "Maces"},
{"TwoHandSwords", "Swords"}})
};

const std::vector<std::string> ITEM_MOD_TYPES = {
"implicitMods", "enchantMods", "explicitMods", "craftedMods", "cosmeticMods"
};
Expand Down Expand Up @@ -89,7 +109,24 @@ Item::Item(const rapidjson::Value &json) :

// Other code assumes icon is proper size so force quad=1 to quad=0 here as it's clunky
// to handle elsewhere
boost::algorithm::replace_last(icon_, "quad=1", "quad=0");
boost::replace_last(icon_, "quad=1", "quad=0");

// Derive item type 'category' hierarchy from icon path.
std::smatch sm;
if (std::regex_search(icon_, sm, std::regex("Art/.*?/(.*)/"))) {
std::string match = sm.str(1);
boost::split(category_vector_,match,boost::is_any_of("/"));
//Compress terms with redundant identifiers
//Weapons.OneHandWeapons.OneHandMaces -> Weapons.OneHand.Maces
size_t min = std::min(category_vector_.size(), replace_map_.size());
for (size_t i = 1; i < min; i++) {
auto it = replace_map_[i].find(category_vector_[i]);
if (it != replace_map_[i].end())
category_vector_[i] = it->second;
}
category_ = boost::join(category_vector_, ".");
boost::to_lower(category_);
}

if (json.HasMember("talismanTier")) {
talisman_tier_ = json["talismanTier"].GetUint();
Expand Down
9 changes: 9 additions & 0 deletions src/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <map>
#include <string>
#include <unordered_map>
#include <array>
#include <vector>
#include "rapidjson/document.h"

Expand Down Expand Up @@ -61,6 +62,8 @@ typedef std::unordered_map<std::string, double> ModTable;

class Item {
public:
typedef const std::unordered_map<std::string, std::string> CategoryReplaceMap;

explicit Item(const rapidjson::Value &json);
Item(const std::string &name, const ItemLocation &location); // used by tests
std::string name() const { return name_; }
Expand Down Expand Up @@ -91,12 +94,16 @@ class Item {
const ItemLocation &location() const { return location_; }
const std::string& json() { return json_; };
const std::string& note() const { return note_; };
const std::string& category() const { return category_; };
const std::vector<std::string>& category_vector() const { return category_vector_; };
uint talisman_tier() const { return talisman_tier_; };
int count() const { return count_; };
bool has_mtx() const { return has_mtx_; }
const ModTable &mod_table() const { return mod_table_; }
int ilvl() const { return ilvl_; }
bool operator<(const Item &other) const;
static const size_t k_CategoryLevels = 3;
static const std::array<CategoryReplaceMap, k_CategoryLevels> replace_map_;

private:
// The point of GenerateMods is to create combined (e.g. implicit+explicit) poe.trade-like mod map to be searched by mod filter.
Expand All @@ -107,6 +114,8 @@ class Item {
std::string name_;
ItemLocation location_;
std::string typeLine_;
std::string category_;
std::vector<std::string> category_vector_;
bool corrupted_;
bool identified_;
int w_, h_;
Expand Down
15 changes: 15 additions & 0 deletions src/itemsmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "shop.h"
#include "util.h"
#include "mainwindow.h"
#include "filters.h"

ItemsManager::ItemsManager(Application &app) :
auto_update_timer_(std::make_unique<QTimer>()),
Expand Down Expand Up @@ -146,10 +147,24 @@ void ItemsManager::OnItemsRefreshed(const Items &items, const std::vector<ItemLo
ApplyAutoTabBuyouts();
ApplyAutoItemBuyouts();
PropagateTabBuyouts();
UpdateCategories();

emit ItemsRefreshed(initial_refresh);
}

void ItemsManager::UpdateCategories() {
categories_.clear();
for (auto const &item: items_) {
QString tmp;
for (auto const &level: item->category_vector()) {
tmp = tmp.isEmpty() ? level.c_str(): tmp + "." + level.c_str();
categories_.insert(tmp);
}
}
// Need a 'default' string option for unconstrained search
categories_.insert(CategorySearchFilter::k_Default.c_str());
}

void ItemsManager::Update(TabSelection::Type type, const std::vector<ItemLocation> &locations) {
emit UpdateSignal(type, locations);
}
Expand Down
3 changes: 3 additions & 0 deletions src/itemsmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class ItemsManager : public QObject {
void ApplyAutoTabBuyouts();
void ApplyAutoItemBuyouts();
void PropagateTabBuyouts();
void UpdateCategories();
const QSet<QString>& categories() const { return categories_; };
public slots:
// called by auto_update_timer_
void OnAutoRefreshTimer();
Expand All @@ -79,4 +81,5 @@ public slots:
Shop &shop_;
Application &app_;
Items items_;
QSet<QString> categories_;
};
10 changes: 10 additions & 0 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <QScrollArea>
#include <QStringList>
#include <QTabBar>
#include <QStringListModel>
#include "QsLog.h"

#include "application.h"
Expand Down Expand Up @@ -544,7 +545,9 @@ void MainWindow::AddSearchGroup(QLayout *layout, const std::string &name="") {
}

void MainWindow::InitializeSearchForm() {
category_string_model_ = new QStringListModel;
auto name_search = std::make_unique<NameSearchFilter>(search_form_layout_);
auto category_search = std::make_unique<CategorySearchFilter>(search_form_layout_, category_string_model_);
auto offense_layout = new FlowLayout;
auto defense_layout = new FlowLayout;
auto sockets_layout = new FlowLayout;
Expand All @@ -564,6 +567,7 @@ void MainWindow::InitializeSearchForm() {
using move_only = std::unique_ptr<Filter>;
move_only init[] = {
std::move(name_search),
std::move(category_search),
// Offense
// new DamageFilter(offense_layout, "Damage"),
std::make_unique<SimplePropertyFilter>(offense_layout, "Critical Strike Chance", "Crit."),
Expand Down Expand Up @@ -691,6 +695,12 @@ void MainWindow::OnItemsRefreshed() {
}
tab++;
}
QList<QString> categories = app_->items_manager().categories().toList();
qSort(categories);
category_string_model_->setStringList(categories);
// Must re-populate category form after model re-init which clears selection
current_search_->ToForm();

ModelViewRefresh();
}

Expand Down
2 changes: 2 additions & 0 deletions src/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Filter;
class FlowLayout;
class ImageCache;
class Search;
class QStringListModel;

struct Buyout;

Expand Down Expand Up @@ -164,6 +165,7 @@ private slots:
QNetworkAccessManager *network_manager_;
QTimer delayed_update_current_item_;
QTimer delayed_search_form_change_;
QStringListModel *category_string_model_;
#ifdef Q_OS_WIN32
QWinTaskbarButton *taskbar_button_;
#endif
Expand Down
2 changes: 1 addition & 1 deletion src/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ std::string Util::StringReplace(const std::string &haystack, const std::string &
}

std::string Util::StringJoin(const std::vector<std::string> &arr, const std::string &separator) {
return boost::algorithm::join(arr, separator);
return boost::join(arr, separator);
}

std::vector<std::string> Util::StringSplit(const std::string &str, char delim) {
Expand Down

0 comments on commit 1c5f453

Please sign in to comment.