mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-14 22:42:14 -07:00
Add a widget to quickly add cards to the deck.
This commit is contained in:
parent
a729b749ba
commit
a7ee47c098
9 changed files with 270 additions and 4 deletions
|
|
@ -24,6 +24,8 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent,
|
|||
&TabDeckEditorVisualTabWidget::onCardChanged);
|
||||
connect(visualDeckView, &VisualDeckEditorWidget::cardClicked, this,
|
||||
&TabDeckEditorVisualTabWidget::onCardClickedDeckEditor);
|
||||
connect(visualDeckView, &VisualDeckEditorWidget::cardAdditionRequested, deckEditor,
|
||||
&AbstractTabDeckEditor::actAddCard);
|
||||
|
||||
visualDatabaseDisplay =
|
||||
new VisualDatabaseDisplayWidget(this, deckEditor, cardDatabaseModel, cardDatabaseDisplayModel);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../deck/deck_loader.h"
|
||||
#include "../../../../game/cards/card_completer_proxy_model.h"
|
||||
#include "../../../../game/cards/card_database.h"
|
||||
#include "../../../../game/cards/card_database_manager.h"
|
||||
#include "../../../../game/cards/card_database_model.h"
|
||||
#include "../../../../game/cards/card_search_model.h"
|
||||
#include "../../../../main.h"
|
||||
#include "../../../../utility/card_info_comparator.h"
|
||||
#include "../../layouts/overlap_layout.h"
|
||||
|
|
@ -12,7 +15,10 @@
|
|||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "../general/layout_containers/overlap_control_widget.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <qscrollarea.h>
|
||||
|
||||
|
|
@ -22,10 +28,85 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_
|
|||
connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged);
|
||||
|
||||
// The Main Widget and Main Layout, which contain a single Widget: The Scroll Area
|
||||
this->setMinimumSize(0, 0);
|
||||
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
mainLayout = new QVBoxLayout();
|
||||
this->setLayout(mainLayout);
|
||||
setMinimumSize(0, 0);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
mainLayout = new QVBoxLayout(this);
|
||||
setLayout(mainLayout);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
searchContainer = new QWidget(this);
|
||||
searchLayout = new QHBoxLayout(searchContainer);
|
||||
searchContainer->setLayout(searchLayout);
|
||||
|
||||
searchBar = new QLineEdit(this);
|
||||
connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() {
|
||||
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text());
|
||||
if (card) {
|
||||
emit cardAdditionRequested(card);
|
||||
}
|
||||
});
|
||||
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
|
||||
cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this);
|
||||
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
|
||||
CardSearchModel *searchModel = new CardSearchModel(cardDatabaseDisplayModel, this);
|
||||
|
||||
proxyModel = new CardCompleterProxyModel(this);
|
||||
proxyModel->setSourceModel(searchModel);
|
||||
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
proxyModel->setFilterRole(Qt::DisplayRole);
|
||||
|
||||
completer = new QCompleter(proxyModel, this);
|
||||
completer->setCompletionRole(Qt::DisplayRole);
|
||||
completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
completer->setFilterMode(Qt::MatchContains);
|
||||
completer->setMaxVisibleItems(15);
|
||||
searchBar->setCompleter(completer);
|
||||
|
||||
// Update suggestions dynamically
|
||||
connect(searchBar, &QLineEdit::textEdited, searchModel, &CardSearchModel::updateSearchResults);
|
||||
connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) {
|
||||
// Ensure substring matching
|
||||
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
|
||||
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
|
||||
if (!text.isEmpty()) {
|
||||
completer->complete(); // Force the dropdown to appear
|
||||
}
|
||||
});
|
||||
|
||||
connect(completer, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this,
|
||||
[=, this](const QString &completion) {
|
||||
// Prevent the text from changing automatically when navigating with arrow keys
|
||||
if (searchBar->text() != completion) {
|
||||
searchBar->setText(completion); // Set the completion explicitly
|
||||
searchBar->setCursorPosition(searchBar->text().length()); // Move cursor to the end
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure that the text stays consistent during selection
|
||||
connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) {
|
||||
if (searchBar->hasFocus() && !searchBar->completer()->popup()->isVisible()) {
|
||||
// Allow text to change when typing, but not when navigating the completer
|
||||
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
|
||||
proxyModel->setFilterRegularExpression(
|
||||
QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
}
|
||||
});
|
||||
|
||||
// Search button functionality
|
||||
searchPushButton = new QPushButton(this);
|
||||
connect(searchPushButton, &QPushButton::clicked, this, [=, this]() {
|
||||
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text());
|
||||
if (card) {
|
||||
emit cardAdditionRequested(card);
|
||||
}
|
||||
});
|
||||
|
||||
searchLayout->addWidget(searchBar);
|
||||
searchLayout->addWidget(searchPushButton);
|
||||
|
||||
mainLayout->addWidget(searchContainer);
|
||||
|
||||
groupAndSortContainer = new QWidget(this);
|
||||
groupAndSortLayout = new QHBoxLayout(groupAndSortContainer);
|
||||
|
|
@ -100,6 +181,7 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_
|
|||
void VisualDeckEditorWidget::retranslateUi()
|
||||
{
|
||||
sortLabel->setText(tr("Click and drag to change\nthe sort order within the groups"));
|
||||
searchPushButton->setText(tr("Quick search and add card"));
|
||||
}
|
||||
|
||||
void VisualDeckEditorWidget::updateZoneWidgets()
|
||||
|
|
|
|||
|
|
@ -2,13 +2,16 @@
|
|||
#define VISUAL_DECK_EDITOR_H
|
||||
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../game/cards/card_completer_proxy_model.h"
|
||||
#include "../../../../game/cards/card_database.h"
|
||||
#include "../../../../game/cards/card_database_model.h"
|
||||
#include "../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "../general/layout_containers/overlap_control_widget.h"
|
||||
#include "../quick_settings/settings_button_widget.h"
|
||||
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QWidget>
|
||||
#include <qscrollarea.h>
|
||||
|
||||
|
|
@ -34,6 +37,7 @@ signals:
|
|||
void activeGroupCriteriaChanged(QString activeGroupCriteria);
|
||||
void activeSortCriteriaChanged(QStringList activeSortCriteria);
|
||||
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
|
||||
void cardAdditionRequested(CardInfoPtr card);
|
||||
|
||||
protected slots:
|
||||
void onHover(CardInfoPtr hoveredCard);
|
||||
|
|
@ -44,6 +48,14 @@ protected slots:
|
|||
private:
|
||||
DeckListModel *deckListModel;
|
||||
QVBoxLayout *mainLayout;
|
||||
QWidget *searchContainer;
|
||||
QHBoxLayout *searchLayout;
|
||||
QLineEdit *searchBar;
|
||||
CardDatabaseModel *cardDatabaseModel;
|
||||
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
|
||||
CardCompleterProxyModel *proxyModel;
|
||||
QCompleter *completer;
|
||||
QPushButton *searchPushButton;
|
||||
QWidget *groupAndSortContainer;
|
||||
QHBoxLayout *groupAndSortLayout;
|
||||
QComboBox *groupByComboBox;
|
||||
|
|
|
|||
18
cockatrice/src/game/cards/card_completer_proxy_model.cpp
Normal file
18
cockatrice/src/game/cards/card_completer_proxy_model.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include "card_completer_proxy_model.h"
|
||||
|
||||
CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (filterRegularExpression().pattern().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
QString data = index.data(Qt::DisplayRole).toString();
|
||||
|
||||
// Ensure substring matching
|
||||
return data.contains(filterRegularExpression());
|
||||
}
|
||||
16
cockatrice/src/game/cards/card_completer_proxy_model.h
Normal file
16
cockatrice/src/game/cards/card_completer_proxy_model.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef CARD_COMPLETER_PROXY_MODEL_H
|
||||
#define CARD_COMPLETER_PROXY_MODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class CardCompleterProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardCompleterProxyModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // CARD_COMPLETER_PROXY_MODEL_H
|
||||
73
cockatrice/src/game/cards/card_search_model.cpp
Normal file
73
cockatrice/src/game/cards/card_search_model.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#include "card_search_model.h"
|
||||
|
||||
#include "../../utility/levenshtein.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
|
||||
: QAbstractListModel(parent), sourceModel(sourceModel)
|
||||
{
|
||||
}
|
||||
|
||||
int CardSearchModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return searchResults.size();
|
||||
}
|
||||
|
||||
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= searchResults.size())
|
||||
return QVariant();
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
return searchResults.at(index.row()).card->getName();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void CardSearchModel::updateSearchResults(const QString &query)
|
||||
{
|
||||
beginResetModel();
|
||||
searchResults.clear();
|
||||
|
||||
if (query.isEmpty() || !sourceModel)
|
||||
return;
|
||||
|
||||
// Set the filter for the display model
|
||||
sourceModel->setCardName(query);
|
||||
|
||||
// Collect matching cards and compute Levenshtein distance
|
||||
for (int i = 0; i < sourceModel->rowCount(); ++i) {
|
||||
QModelIndex modelIndex = sourceModel->index(i, 0);
|
||||
QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex);
|
||||
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
|
||||
|
||||
if (!sourceDbModel || !sourceIndex.isValid())
|
||||
return;
|
||||
|
||||
CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row());
|
||||
|
||||
if (!card)
|
||||
continue;
|
||||
|
||||
int distance = levenshteinDistance(query.toLower(), card->getName().toLower());
|
||||
searchResults.append({card, distance});
|
||||
}
|
||||
|
||||
// Sort by Levenshtein distance (lower distance = better match)
|
||||
std::sort(searchResults.begin(), searchResults.end(),
|
||||
[](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; });
|
||||
|
||||
// Keep only the top 5 results
|
||||
if (searchResults.size() > 10)
|
||||
searchResults = searchResults.mid(0, 10);
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
|
||||
emit layoutChanged();
|
||||
for (SearchResult &result : searchResults) {
|
||||
qDebug() << result.card->getName();
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
30
cockatrice/src/game/cards/card_search_model.h
Normal file
30
cockatrice/src/game/cards/card_search_model.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef CARD_SEARCH_MODEL_H
|
||||
#define CARD_SEARCH_MODEL_H
|
||||
|
||||
#include "card_database_model.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class CardSearchModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void updateSearchResults(const QString &query); // Update results based on input
|
||||
|
||||
private:
|
||||
struct SearchResult
|
||||
{
|
||||
CardInfoPtr card;
|
||||
int distance;
|
||||
};
|
||||
|
||||
CardDatabaseDisplayModel *sourceModel;
|
||||
QList<SearchResult> searchResults;
|
||||
};
|
||||
|
||||
#endif // CARD_SEARCH_MODEL_H
|
||||
25
cockatrice/src/utility/levenshtein.cpp
Normal file
25
cockatrice/src/utility/levenshtein.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#include "levenshtein.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
int levenshteinDistance(const QString &s1, const QString &s2)
|
||||
{
|
||||
int len1 = s1.size();
|
||||
int len2 = s2.size();
|
||||
std::vector<std::vector<int>> dp(len1 + 1, std::vector<int>(len2 + 1));
|
||||
|
||||
for (int i = 0; i <= len1; i++)
|
||||
dp[i][0] = i;
|
||||
for (int j = 0; j <= len2; j++)
|
||||
dp[0][j] = j;
|
||||
|
||||
for (int i = 1; i <= len1; i++) {
|
||||
for (int j = 1; j <= len2; j++) {
|
||||
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
|
||||
dp[i][j] = std::min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost});
|
||||
}
|
||||
}
|
||||
|
||||
return dp[len1][len2];
|
||||
}
|
||||
8
cockatrice/src/utility/levenshtein.h
Normal file
8
cockatrice/src/utility/levenshtein.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef LEVENSHTEIN_H
|
||||
#define LEVENSHTEIN_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
int levenshteinDistance(const QString &s1, const QString &s2);
|
||||
|
||||
#endif // LEVENSHTEIN_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue