mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 02:53:25 -08:00
[VDE] Deck Analytics Widgets overhaul (#6463)
* [VDE] Deck Analytics Widgets overhaul Took 2 minutes Took 3 minutes Took 3 minutes * Qt5 version guards. Took 33 minutes Took 3 seconds * Include QtMath Took 3 minutes Took 8 seconds * Use getCards() Took 4 minutes * Non pointer stuff Took 52 seconds * Add a newline to the tooltip Took 2 minutes Took 27 seconds * Fix build failure on macOS 15 * Rename some things. Took 17 minutes Took 11 seconds Took 18 seconds * Address overloads, fix default configuration. Took 1 hour 9 minutes Took 8 seconds * Fix mana curve default config. Took 4 minutes * Namespace to Qt libs Took 5 minutes * Selection overlay is transparent for mouse events. Took 2 minutes * Brace initialize. Took 8 minutes * Debian 11. Took 5 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: RickyRister <ricky.rister.wang@gmail.com>
This commit is contained in:
parent
36d8280765
commit
df9a8b2272
81 changed files with 4372 additions and 394 deletions
|
|
@ -0,0 +1,28 @@
|
|||
#include "draw_probability_config.h"
|
||||
|
||||
QJsonObject DrawProbabilityConfig::toJson() const
|
||||
{
|
||||
QJsonObject o;
|
||||
o["criteria"] = criteria;
|
||||
o["atLeast"] = atLeast;
|
||||
o["quantity"] = quantity;
|
||||
o["drawn"] = drawn;
|
||||
return o;
|
||||
}
|
||||
DrawProbabilityConfig DrawProbabilityConfig::fromJson(const QJsonObject &o)
|
||||
{
|
||||
DrawProbabilityConfig cfg;
|
||||
if (o.contains("criteria")) {
|
||||
cfg.criteria = o["criteria"].toString();
|
||||
}
|
||||
if (o.contains("atLeast")) {
|
||||
cfg.atLeast = o["atLeast"].toBool(true);
|
||||
}
|
||||
if (o.contains("quantity")) {
|
||||
cfg.quantity = o["quantity"].toInt(1);
|
||||
}
|
||||
if (o.contains("drawn")) {
|
||||
cfg.drawn = o["drawn"].toInt(7);
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef COCKATRICE_DRAW_PROBABILITY_CONFIG_H
|
||||
#define COCKATRICE_DRAW_PROBABILITY_CONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
struct DrawProbabilityConfig
|
||||
{
|
||||
QString criteria = "name"; // name, type, subtype, cmc
|
||||
bool atLeast = true; // true = at least, false = exactly
|
||||
int quantity = 1; // N
|
||||
int drawn = 7; // M
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static DrawProbabilityConfig fromJson(const QJsonObject &o);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#include "draw_probability_config_dialog.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
#include <QSpinBox>
|
||||
|
||||
DrawProbabilityConfigDialog::DrawProbabilityConfigDialog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
form = new QFormLayout(this);
|
||||
|
||||
// Criteria
|
||||
labelCriteria = new QLabel(this);
|
||||
criteria = new QComboBox(this);
|
||||
criteria->addItem(QString(), "name");
|
||||
criteria->addItem(QString(), "type");
|
||||
criteria->addItem(QString(), "subtype");
|
||||
criteria->addItem(QString(), "cmc");
|
||||
form->addRow(labelCriteria, criteria);
|
||||
|
||||
// Exactness
|
||||
labelExactness = new QLabel(this);
|
||||
exactness = new QComboBox(this);
|
||||
exactness->addItem(QString(), true);
|
||||
exactness->addItem(QString(), false);
|
||||
form->addRow(labelExactness, exactness);
|
||||
|
||||
// Quantity
|
||||
labelQuantity = new QLabel(this);
|
||||
quantity = new QSpinBox(this);
|
||||
quantity->setRange(1, 60);
|
||||
form->addRow(labelQuantity, quantity);
|
||||
|
||||
// Drawn
|
||||
labelDrawn = new QLabel(this);
|
||||
drawn = new QSpinBox(this);
|
||||
drawn->setRange(1, 60);
|
||||
drawn->setValue(7);
|
||||
form->addRow(labelDrawn, drawn);
|
||||
|
||||
// Button box
|
||||
auto *bb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
form->addWidget(bb);
|
||||
|
||||
connect(bb, &QDialogButtonBox::accepted, this, &DrawProbabilityConfigDialog::accept);
|
||||
connect(bb, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DrawProbabilityConfigDialog::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Draw Probability Settings"));
|
||||
|
||||
labelCriteria->setText(tr("Criteria:"));
|
||||
criteria->setItemText(0, tr("Card Name"));
|
||||
criteria->setItemText(1, tr("Type"));
|
||||
criteria->setItemText(2, tr("Subtype"));
|
||||
criteria->setItemText(3, tr("Mana Value"));
|
||||
|
||||
labelExactness->setText(tr("Exactness:"));
|
||||
exactness->setItemText(0, tr("At least"));
|
||||
exactness->setItemText(1, tr("Exactly"));
|
||||
|
||||
labelQuantity->setText(tr("Quantity (N):"));
|
||||
labelDrawn->setText(tr("Cards drawn (M):"));
|
||||
|
||||
// i18n-friendly suffixes
|
||||
quantity->setSuffix(tr(" cards"));
|
||||
drawn->setSuffix(tr(" cards"));
|
||||
}
|
||||
|
||||
void DrawProbabilityConfigDialog::setFromConfig(const DrawProbabilityConfig &_config)
|
||||
{
|
||||
cfg = _config;
|
||||
|
||||
criteria->setCurrentIndex(criteria->findData(_config.criteria));
|
||||
exactness->setCurrentIndex(exactness->findData(_config.atLeast));
|
||||
quantity->setValue(_config.quantity);
|
||||
drawn->setValue(_config.drawn);
|
||||
}
|
||||
|
||||
void DrawProbabilityConfigDialog::accept()
|
||||
{
|
||||
cfg.criteria = criteria->currentData().toString();
|
||||
cfg.atLeast = exactness->currentData().toBool();
|
||||
cfg.quantity = quantity->value();
|
||||
cfg.drawn = drawn->value();
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "draw_probability_config.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFormLayout>
|
||||
|
||||
class QComboBox;
|
||||
class QSpinBox;
|
||||
class QLabel;
|
||||
|
||||
class DrawProbabilityConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DrawProbabilityConfigDialog(QWidget *parent = nullptr);
|
||||
|
||||
void retranslateUi();
|
||||
|
||||
void setFromConfig(const DrawProbabilityConfig &_config);
|
||||
DrawProbabilityConfig result() const
|
||||
{
|
||||
return cfg;
|
||||
}
|
||||
|
||||
protected:
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
DrawProbabilityConfig cfg;
|
||||
|
||||
QFormLayout *form;
|
||||
|
||||
// Widgets
|
||||
QComboBox *criteria;
|
||||
QComboBox *exactness;
|
||||
QSpinBox *quantity;
|
||||
QSpinBox *drawn;
|
||||
|
||||
QLabel *labelCriteria;
|
||||
QLabel *labelExactness;
|
||||
QLabel *labelQuantity;
|
||||
QLabel *labelDrawn;
|
||||
};
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
#include "draw_probability_widget.h"
|
||||
|
||||
#include "draw_probability_config_dialog.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QMap>
|
||||
#include <QSpinBox>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QWidget>
|
||||
#include <QtMath>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
||||
DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
||||
: AbstractAnalyticsPanelWidget(parent, analyzer)
|
||||
{
|
||||
controls = new QWidget(this);
|
||||
controlLayout = new QHBoxLayout(controls);
|
||||
|
||||
labelPrefix = new QLabel(this);
|
||||
controlLayout->addWidget(labelPrefix);
|
||||
|
||||
criteriaCombo = new QComboBox(this);
|
||||
// Give these things item-data so we can translate the actual user-facing strings
|
||||
criteriaCombo->addItem(QString(), "name");
|
||||
criteriaCombo->addItem(QString(), "type");
|
||||
criteriaCombo->addItem(QString(), "subtype");
|
||||
criteriaCombo->addItem(QString(), "cmc");
|
||||
controlLayout->addWidget(criteriaCombo);
|
||||
|
||||
exactnessCombo = new QComboBox(this);
|
||||
exactnessCombo->addItem(QString(), true); // At least
|
||||
exactnessCombo->addItem(QString(), false); // Exactly
|
||||
controlLayout->addWidget(exactnessCombo);
|
||||
|
||||
quantitySpin = new QSpinBox(this);
|
||||
quantitySpin->setRange(1, 60);
|
||||
controlLayout->addWidget(quantitySpin);
|
||||
|
||||
labelMiddle = new QLabel(this);
|
||||
controlLayout->addWidget(labelMiddle);
|
||||
|
||||
drawnSpin = new QSpinBox(this);
|
||||
drawnSpin->setRange(1, 60);
|
||||
drawnSpin->setValue(7);
|
||||
controlLayout->addWidget(drawnSpin);
|
||||
|
||||
labelSuffix = new QLabel(this);
|
||||
controlLayout->addWidget(labelSuffix);
|
||||
|
||||
labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
|
||||
controlLayout->addStretch(1);
|
||||
layout->addWidget(controls);
|
||||
|
||||
// Table
|
||||
resultTable = new QTableWidget(this);
|
||||
resultTable->setColumnCount(3);
|
||||
resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
layout->addWidget(resultTable);
|
||||
|
||||
// Connections
|
||||
connect(criteriaCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] {
|
||||
config.criteria = criteriaCombo->currentData().toString();
|
||||
updateDisplay();
|
||||
});
|
||||
|
||||
connect(exactnessCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] {
|
||||
config.atLeast = exactnessCombo->currentData().toBool();
|
||||
updateDisplay();
|
||||
});
|
||||
|
||||
connect(quantitySpin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int v) {
|
||||
config.quantity = v;
|
||||
updateDisplay();
|
||||
});
|
||||
|
||||
connect(drawnSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int v) {
|
||||
config.drawn = v;
|
||||
updateDisplay();
|
||||
});
|
||||
|
||||
retranslateUi();
|
||||
applyConfigToToolbar();
|
||||
updateFilterOptions();
|
||||
}
|
||||
|
||||
void DrawProbabilityWidget::retranslateUi()
|
||||
{
|
||||
bannerWidget->setText(tr("Draw Probability"));
|
||||
|
||||
labelPrefix->setText(tr("Probability of drawing"));
|
||||
|
||||
criteriaCombo->setItemText(0, tr("Card Name"));
|
||||
criteriaCombo->setItemText(1, tr("Type"));
|
||||
criteriaCombo->setItemText(2, tr("Subtype"));
|
||||
criteriaCombo->setItemText(3, tr("Mana Value"));
|
||||
|
||||
exactnessCombo->setItemText(0, tr("At least"));
|
||||
exactnessCombo->setItemText(1, tr("Exactly"));
|
||||
|
||||
labelMiddle->setText(tr("card(s) having drawn at least"));
|
||||
labelSuffix->setText(tr("cards"));
|
||||
|
||||
resultTable->setHorizontalHeaderLabels({tr("Category"), tr("Qty"), tr("Odds (%)")});
|
||||
}
|
||||
|
||||
QDialog *DrawProbabilityWidget::createConfigDialog(QWidget *parent)
|
||||
{
|
||||
auto *dlg = new DrawProbabilityConfigDialog(parent);
|
||||
dlg->setFromConfig(config);
|
||||
return dlg;
|
||||
}
|
||||
|
||||
QJsonObject DrawProbabilityWidget::extractConfigFromDialog(QDialog *dlg) const
|
||||
{
|
||||
auto *dp = qobject_cast<DrawProbabilityConfigDialog *>(dlg);
|
||||
return dp ? dp->result().toJson() : QJsonObject{};
|
||||
}
|
||||
|
||||
void DrawProbabilityWidget::applyConfigToToolbar()
|
||||
{
|
||||
auto setComboByData = [](QComboBox *combo, const QVariant &value) {
|
||||
int idx = combo->findData(value);
|
||||
if (idx >= 0) {
|
||||
combo->setCurrentIndex(idx);
|
||||
}
|
||||
};
|
||||
|
||||
setComboByData(criteriaCombo, config.criteria);
|
||||
setComboByData(exactnessCombo, config.atLeast);
|
||||
|
||||
quantitySpin->setValue(config.quantity);
|
||||
drawnSpin->setValue(config.drawn);
|
||||
}
|
||||
|
||||
void DrawProbabilityWidget::updateDisplay()
|
||||
{
|
||||
updateFilterOptions();
|
||||
}
|
||||
|
||||
void DrawProbabilityWidget::loadConfig(const QJsonObject &cfg)
|
||||
{
|
||||
config = DrawProbabilityConfig::fromJson(cfg);
|
||||
applyConfigToToolbar();
|
||||
updateFilterOptions();
|
||||
}
|
||||
|
||||
void DrawProbabilityWidget::updateFilterOptions()
|
||||
{
|
||||
if (!analyzer->getModel()->getDeckList()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString criteria = config.criteria;
|
||||
const bool atLeast = config.atLeast;
|
||||
const int quantity = config.quantity;
|
||||
const int drawn = config.drawn;
|
||||
|
||||
QMap<QString, int> categoryCounts;
|
||||
int totalDeckCards = 0;
|
||||
|
||||
const auto nodes = analyzer->getModel()->getDeckList()->getCardNodes();
|
||||
for (auto *node : nodes) {
|
||||
CardInfoPtr info = CardDatabaseManager::query()->getCard({node->getName()}).getCardPtr();
|
||||
if (!info) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalDeckCards += node->getNumber();
|
||||
|
||||
QStringList categories;
|
||||
if (criteria == "name") {
|
||||
categories << info->getName();
|
||||
} else if (criteria == "type") {
|
||||
categories = info->getMainCardType().split(' ', Qt::SkipEmptyParts);
|
||||
} else if (criteria == "subtype") {
|
||||
categories = info->getCardType().split(' ', Qt::SkipEmptyParts);
|
||||
} else if (criteria == "cmc") {
|
||||
categories << QString::number(info->getCmc().toInt());
|
||||
}
|
||||
|
||||
for (const QString &cat : categories) {
|
||||
categoryCounts[cat] += node->getNumber();
|
||||
}
|
||||
}
|
||||
|
||||
resultTable->setRowCount(categoryCounts.size());
|
||||
|
||||
int row = 0;
|
||||
for (auto it = categoryCounts.cbegin(); it != categoryCounts.cend(); ++it, ++row) {
|
||||
const QString &cat = it.key();
|
||||
const int copies = it.value();
|
||||
|
||||
double probability = 0.0;
|
||||
if (atLeast) {
|
||||
for (int k = quantity; k <= drawn && k <= copies; ++k) {
|
||||
probability += hypergeometricProbability(totalDeckCards, copies, drawn, k);
|
||||
}
|
||||
} else {
|
||||
probability = hypergeometricProbability(totalDeckCards, copies, drawn, quantity);
|
||||
}
|
||||
|
||||
resultTable->setItem(row, 0, new QTableWidgetItem(cat));
|
||||
resultTable->setItem(row, 1, new QTableWidgetItem(QString::number(copies)));
|
||||
resultTable->setItem(row, 2, new QTableWidgetItem(QString::number(probability * 100.0, 'f', 2)));
|
||||
}
|
||||
}
|
||||
|
||||
double DrawProbabilityWidget::hypergeometricProbability(int N, int K, int n, int k)
|
||||
{
|
||||
if (k < 0 || k > n || K > N || n > N) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double logP = 0.0;
|
||||
for (int i = 1; i <= k; ++i) {
|
||||
logP += qLn(double(K - k + i) / i);
|
||||
}
|
||||
for (int i = 1; i <= n - k; ++i) {
|
||||
logP += qLn(double(N - K - (n - k) + i) / i);
|
||||
}
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
logP -= qLn(double(N - n + i) / i);
|
||||
}
|
||||
|
||||
return qExp(logP);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||
#define COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||
|
||||
#include "../../abstract_analytics_panel_widget.h"
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "draw_probability_config.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QSpinBox>
|
||||
#include <QTableWidget>
|
||||
|
||||
class DrawProbabilityWidget : public AbstractAnalyticsPanelWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
|
||||
QDialog *createConfigDialog(QWidget *parent) override;
|
||||
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
|
||||
void applyConfigToToolbar();
|
||||
|
||||
public slots:
|
||||
void updateDisplay() override;
|
||||
void loadConfig(const QJsonObject &cfg) override;
|
||||
void retranslateUi();
|
||||
|
||||
private slots:
|
||||
void updateFilterOptions();
|
||||
|
||||
private:
|
||||
DrawProbabilityConfig config;
|
||||
|
||||
QWidget *controls;
|
||||
QHBoxLayout *controlLayout;
|
||||
QLabel *labelPrefix;
|
||||
QLabel *labelMiddle;
|
||||
QLabel *labelSuffix;
|
||||
QLineEdit *cardNameEdit;
|
||||
QComboBox *criteriaCombo; // Card Name / Type / Subtype / Mana Value
|
||||
QComboBox *filterCombo; // The actual value
|
||||
QComboBox *exactnessCombo; // At least / Exactly
|
||||
QSpinBox *quantitySpin; // N
|
||||
QSpinBox *drawnSpin; // M
|
||||
|
||||
QSpinBox *manaValueSpin;
|
||||
|
||||
QTableWidget *resultTable;
|
||||
|
||||
double hypergeometricProbability(int N, int K, int n, int k);
|
||||
double calculateProbability(int totalCards, int copies, int drawn, bool atLeast);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#include "mana_base_config.h"
|
||||
|
||||
QJsonObject ManaBaseConfig::toJson() const
|
||||
{
|
||||
QJsonObject jsonObject;
|
||||
QJsonArray jsonArray;
|
||||
jsonObject["displayType"] = displayType;
|
||||
for (auto &filter : filters) {
|
||||
jsonArray.append(filter);
|
||||
}
|
||||
jsonObject["filters"] = jsonArray;
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
ManaBaseConfig ManaBaseConfig::fromJson(const QJsonObject &o)
|
||||
|
||||
{
|
||||
ManaBaseConfig config;
|
||||
|
||||
if (o.contains("displayType")) {
|
||||
config.displayType = o["displayType"].toString();
|
||||
}
|
||||
|
||||
if (o.contains("filters")) {
|
||||
config.filters.clear();
|
||||
for (auto v : o["filters"].toArray()) {
|
||||
config.filters << v.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
#ifndef COCKATRICE_MANA_BASE_CONFIG_H
|
||||
#define COCKATRICE_MANA_BASE_CONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QStringList>
|
||||
|
||||
struct ManaBaseConfig
|
||||
{
|
||||
QString displayType; // "pie" or "bar" or "combinedBar"
|
||||
QStringList filters; // which colors to show, empty = all
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static ManaBaseConfig fromJson(const QJsonObject &o);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_BASE_CONFIG_H
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#include "mana_base_config_dialog.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
ManaBaseConfigDialog::ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer,
|
||||
ManaBaseConfig initial,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), config(initial)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
|
||||
displayTypeLabel = new QLabel(this);
|
||||
layout->addWidget(displayTypeLabel);
|
||||
|
||||
displayType = new QComboBox(this);
|
||||
layout->addWidget(displayType);
|
||||
|
||||
filterLabel = new QLabel(this);
|
||||
layout->addWidget(filterLabel);
|
||||
|
||||
filterList = new QListWidget(this);
|
||||
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
|
||||
layout->addWidget(filterList);
|
||||
|
||||
QStringList colors = analyzer->getManaBase().keys();
|
||||
colors.sort();
|
||||
filterList->addItems(colors);
|
||||
|
||||
// select initial filters
|
||||
for (int i = 0; i < filterList->count(); ++i) {
|
||||
if (config.filters.contains(filterList->item(i)->text()))
|
||||
filterList->item(i)->setSelected(true);
|
||||
}
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ManaBaseConfigDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &ManaBaseConfigDialog::reject);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaBaseConfigDialog::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Mana Base Configuration"));
|
||||
|
||||
displayTypeLabel->setText(tr("Display type:"));
|
||||
|
||||
displayType->clear();
|
||||
displayType->addItems({tr("pie"), tr("bar"), tr("combinedBar")});
|
||||
|
||||
filterLabel->setText(tr("Filter Colors (optional):"));
|
||||
|
||||
buttons->button(QDialogButtonBox::Ok)->setText(tr("OK"));
|
||||
buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
|
||||
}
|
||||
|
||||
void ManaBaseConfigDialog::accept()
|
||||
{
|
||||
config.displayType = displayType->currentText();
|
||||
config.filters.clear();
|
||||
for (auto *item : filterList->selectedItems()) {
|
||||
config.filters << item->text();
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
#ifndef COCKATRICE_MANA_BASE_ADD_DIALOG_H
|
||||
#define COCKATRICE_MANA_BASE_ADD_DIALOG_H
|
||||
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "mana_base_config.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class ManaBaseConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig initial = {}, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
|
||||
void accept() override;
|
||||
|
||||
ManaBaseConfig result() const
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
private:
|
||||
ManaBaseConfig config;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *displayTypeLabel;
|
||||
QComboBox *displayType;
|
||||
QLabel *filterLabel;
|
||||
QListWidget *filterList;
|
||||
QDialogButtonBox *buttons;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_BASE_ADD_DIALOG_H
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
#include "mana_base_widget.h"
|
||||
|
||||
#include "../../../general/display/charts/bars/bar_widget.h"
|
||||
#include "../../../general/display/charts/bars/color_bar.h"
|
||||
#include "../../../general/display/charts/pies/color_pie.h"
|
||||
#include "../../analytics_panel_widget_registrar.h"
|
||||
#include "mana_base_config_dialog.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListWidget>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
AnalyticsPanelWidgetRegistrar registerManaBase{
|
||||
"manaBase", ManaBaseWidget::tr("Mana Base"),
|
||||
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaBaseWidget(parent, analyzer); }};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig cfg)
|
||||
: AbstractAnalyticsPanelWidget(parent, analyzer), config(std::move(cfg))
|
||||
{
|
||||
barContainer = new QWidget(this);
|
||||
barLayout = new QHBoxLayout(barContainer);
|
||||
layout->addWidget(barContainer);
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
void ManaBaseWidget::updateDisplay()
|
||||
{
|
||||
// Clear previous widgets
|
||||
while (QLayoutItem *item = barLayout->takeAt(0)) {
|
||||
if (item->widget()) {
|
||||
item->widget()->deleteLater();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
|
||||
auto &pipCount = analyzer->getProductionPipCount();
|
||||
auto &cardCount = analyzer->getProductionCardCount();
|
||||
|
||||
QHash<QString, int> manaMap;
|
||||
for (auto key : pipCount.keys()) {
|
||||
manaMap[key] = pipCount[key];
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
if (!config.filters.isEmpty()) {
|
||||
QHash<QString, int> filtered;
|
||||
for (auto f : config.filters) {
|
||||
if (manaMap.contains(f)) {
|
||||
filtered[f] = manaMap[f];
|
||||
}
|
||||
}
|
||||
manaMap = filtered;
|
||||
}
|
||||
|
||||
// Determine maximum for bar charts
|
||||
int highest = 1;
|
||||
for (auto val : manaMap) {
|
||||
highest = std::max(highest, val);
|
||||
}
|
||||
|
||||
// Convert to QMap for ColorBar / ColorPie (sorted)
|
||||
QMap<QString, int> mapSorted;
|
||||
for (auto it = manaMap.begin(); it != manaMap.end(); ++it) {
|
||||
mapSorted.insert(it.key(), it.value());
|
||||
}
|
||||
|
||||
// Choose display mode
|
||||
if (config.displayType == "bar") {
|
||||
QHash<QString, QColor> colors = {{"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)},
|
||||
{"B", QColor(21, 11, 0)}, {"R", QColor(211, 32, 42)},
|
||||
{"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)}};
|
||||
|
||||
for (auto color : manaMap.keys()) {
|
||||
QString label = QString("%1 %2 (%3)").arg(color).arg(manaMap[color]).arg(cardCount.value(color));
|
||||
|
||||
BarWidget *bar = new BarWidget(label, manaMap[color], highest, colors.value(color, Qt::gray), this);
|
||||
|
||||
barLayout->addWidget(bar);
|
||||
}
|
||||
} else if (config.displayType == "combinedBar") {
|
||||
ColorBar *cb = new ColorBar(mapSorted, this);
|
||||
cb->setMinimumHeight(30);
|
||||
barLayout->addWidget(cb);
|
||||
} else if (config.displayType == "pie") {
|
||||
ColorPie *pie = new ColorPie(mapSorted, this);
|
||||
pie->setMinimumSize(200, 200);
|
||||
barLayout->addWidget(pie);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
QSize ManaBaseWidget::sizeHint() const
|
||||
{
|
||||
return QSize(800, 150);
|
||||
}
|
||||
|
||||
QDialog *ManaBaseWidget::createConfigDialog(QWidget *parent)
|
||||
{
|
||||
ManaBaseConfigDialog *dlg = new ManaBaseConfigDialog(analyzer, config, parent);
|
||||
return dlg;
|
||||
}
|
||||
|
||||
QJsonObject ManaBaseWidget::extractConfigFromDialog(QDialog *dlg) const
|
||||
{
|
||||
auto *mc = qobject_cast<ManaBaseConfigDialog *>(dlg);
|
||||
if (!mc) {
|
||||
return {};
|
||||
}
|
||||
return mc->result().toJson();
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @file mana_base_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef MANA_BASE_WIDGET_H
|
||||
#define MANA_BASE_WIDGET_H
|
||||
|
||||
#include "../../../general/display/banner_widget.h"
|
||||
#include "../../abstract_analytics_panel_widget.h"
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "mana_base_config.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
#include <utility>
|
||||
|
||||
class ManaBaseWidget : public AbstractAnalyticsPanelWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
QSize sizeHint() const override;
|
||||
void updateDisplay() override;
|
||||
QDialog *createConfigDialog(QWidget *parent) override;
|
||||
|
||||
public:
|
||||
ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig cfg = {});
|
||||
|
||||
QJsonObject saveConfig() const override
|
||||
{
|
||||
return config.toJson();
|
||||
}
|
||||
void loadConfig(const QJsonObject &o) override
|
||||
{
|
||||
config = ManaBaseConfig::fromJson(o);
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
|
||||
|
||||
private:
|
||||
ManaBaseConfig config;
|
||||
QWidget *barContainer;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_BASE_WIDGET_H
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#include "mana_curve_category_widget.h"
|
||||
|
||||
#include "libcockatrice/utility/color.h"
|
||||
#include "libcockatrice/utility/qt_utils.h"
|
||||
#include "mana_curve_config.h"
|
||||
#include "mana_curve_total_widget.h"
|
||||
|
||||
constexpr int MIN_ROW_HEIGHT = 100; // Minimum readable height per row
|
||||
|
||||
ManaCurveCategoryWidget::ManaCurveCategoryWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(4);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
// Same as minimum for now
|
||||
QSize ManaCurveCategoryWidget::sizeHint() const
|
||||
{
|
||||
if (layout->isEmpty()) {
|
||||
return QSize(0, 0);
|
||||
}
|
||||
|
||||
// Calculate exact height needed for all rows
|
||||
int rowCount = layout->count();
|
||||
|
||||
int totalHeight = rowCount * MIN_ROW_HEIGHT;
|
||||
totalHeight += (rowCount - 1) * layout->spacing();
|
||||
|
||||
return QSize(0, totalHeight);
|
||||
}
|
||||
|
||||
QSize ManaCurveCategoryWidget::minimumSizeHint() const
|
||||
{
|
||||
if (layout->isEmpty()) {
|
||||
return QSize(0, 0);
|
||||
}
|
||||
|
||||
// Calculate actual minimum based on number of rows
|
||||
int rowCount = layout->count();
|
||||
|
||||
int totalHeight = rowCount * MIN_ROW_HEIGHT;
|
||||
totalHeight += (rowCount - 1) * layout->spacing();
|
||||
|
||||
return QSize(0, totalHeight);
|
||||
}
|
||||
|
||||
void ManaCurveCategoryWidget::updateDisplay(int minCmc,
|
||||
int maxCmc,
|
||||
int highest,
|
||||
QHash<QString, QHash<int, int>> qCategoryCounts,
|
||||
QHash<QString, QHash<int, QStringList>> qCategoryCards,
|
||||
const ManaCurveConfig &config)
|
||||
{
|
||||
// Clear previous content
|
||||
QtUtils::clearLayoutRec(layout);
|
||||
|
||||
if (!config.showCategoryRows) {
|
||||
return; // nothing to show
|
||||
}
|
||||
|
||||
// Collect categories
|
||||
QStringList categories = qCategoryCounts.keys();
|
||||
|
||||
// Apply filters
|
||||
if (!config.filters.isEmpty()) {
|
||||
QStringList filtered;
|
||||
for (const QString &cat : categories) {
|
||||
if (config.filters.contains(cat)) {
|
||||
filtered.append(cat);
|
||||
}
|
||||
}
|
||||
categories = filtered;
|
||||
}
|
||||
|
||||
std::sort(categories.begin(), categories.end());
|
||||
|
||||
for (const QString &cat : categories) {
|
||||
QWidget *row = new QWidget(this);
|
||||
row->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
row->setFixedHeight(MIN_ROW_HEIGHT);
|
||||
|
||||
QHBoxLayout *rowLayout = new QHBoxLayout(row);
|
||||
rowLayout->setContentsMargins(0, 0, 0, 0);
|
||||
rowLayout->setSpacing(4);
|
||||
|
||||
QLabel *categoryLabel = new QLabel(cat, row);
|
||||
categoryLabel->setFixedWidth(80);
|
||||
rowLayout->addWidget(categoryLabel);
|
||||
|
||||
QVector<BarData> catBars;
|
||||
const auto cmcCounts = qCategoryCounts.value(cat);
|
||||
const auto cmcCards = qCategoryCards.value(cat);
|
||||
|
||||
for (int cmc = minCmc; cmc <= maxCmc; ++cmc) {
|
||||
int val = cmcCounts.value(cmc, 0);
|
||||
QStringList cards = cmcCards.value(cmc);
|
||||
|
||||
QVector<BarSegment> segments;
|
||||
if (val > 0) {
|
||||
segments.push_back({cat, val, cards, GameSpecificColors::MTG::colorHelper(cat)});
|
||||
}
|
||||
|
||||
catBars.push_back({QString::number(cmc), segments});
|
||||
}
|
||||
|
||||
auto *catChart = new BarChartWidget(row);
|
||||
catChart->setHighest(highest);
|
||||
catChart->setBars(catBars);
|
||||
catChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
rowLayout->addWidget(catChart);
|
||||
layout->addWidget(row);
|
||||
}
|
||||
|
||||
// Update geometry after adding all widgets
|
||||
updateGeometry();
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H
|
||||
#define COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H
|
||||
|
||||
#include "../../../general/display/charts/bars/bar_chart_widget.h"
|
||||
#include "mana_curve_config.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
class ManaCurveCategoryWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaCurveCategoryWidget(QWidget *parent);
|
||||
void updateDisplay(int minCmc,
|
||||
int maxCmc,
|
||||
int highest,
|
||||
QHash<QString, QHash<int, int>> qCategoryCounts,
|
||||
QHash<QString, QHash<int, QStringList>> qCategoryCards,
|
||||
const ManaCurveConfig &config);
|
||||
|
||||
public slots:
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#include "mana_curve_config.h"
|
||||
|
||||
QJsonObject ManaCurveConfig::toJson() const
|
||||
{
|
||||
QJsonObject jsonObject;
|
||||
jsonObject["groupBy"] = groupBy;
|
||||
QJsonArray jsonArray;
|
||||
for (auto &filter : filters) {
|
||||
jsonArray.append(filter);
|
||||
}
|
||||
jsonObject["filters"] = jsonArray;
|
||||
jsonObject["showMain"] = showMain;
|
||||
jsonObject["showCategoryRows"] = showCategoryRows;
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
ManaCurveConfig ManaCurveConfig::fromJson(const QJsonObject &o)
|
||||
{
|
||||
ManaCurveConfig config;
|
||||
|
||||
if (o.contains("groupBy")) {
|
||||
config.groupBy = o["groupBy"].toString();
|
||||
}
|
||||
|
||||
if (o.contains("filters")) {
|
||||
config.filters.clear();
|
||||
for (auto v : o["filters"].toArray()) {
|
||||
config.filters << v.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (o.contains("showMain")) {
|
||||
config.showMain = o["showMain"].toBool(true);
|
||||
}
|
||||
|
||||
if (o.contains("showCategoryRows")) {
|
||||
config.showCategoryRows = o["showCategoryRows"].toBool(true);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
#ifndef COCKATRICE_MANA_CURVE_CONFIG_H
|
||||
#define COCKATRICE_MANA_CURVE_CONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QStringList>
|
||||
|
||||
struct ManaCurveConfig
|
||||
{
|
||||
QString groupBy = "type"; // "type", "color", "subtype", etc.
|
||||
QStringList filters; // empty = all
|
||||
bool showMain = true;
|
||||
bool showCategoryRows = true;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static ManaCurveConfig fromJson(const QJsonObject &o);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_CURVE_CONFIG_H
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
#include "mana_curve_config_dialog.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
ManaCurveConfigDialog::ManaCurveConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent)
|
||||
: QDialog(parent), analyzer(analyzer)
|
||||
{
|
||||
auto *lay = new QVBoxLayout(this);
|
||||
|
||||
labelGroupBy = new QLabel(this);
|
||||
lay->addWidget(labelGroupBy);
|
||||
|
||||
groupBy = new QComboBox(this);
|
||||
lay->addWidget(groupBy);
|
||||
|
||||
labelFilters = new QLabel(this);
|
||||
lay->addWidget(labelFilters);
|
||||
|
||||
filterList = new QListWidget(this);
|
||||
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
|
||||
lay->addWidget(filterList);
|
||||
|
||||
showMain = new QCheckBox(this);
|
||||
showMain->setChecked(true);
|
||||
lay->addWidget(showMain);
|
||||
|
||||
showCatRows = new QCheckBox(this);
|
||||
showCatRows->setChecked(true);
|
||||
lay->addWidget(showCatRows);
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
lay->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ManaCurveConfigDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &ManaCurveConfigDialog::reject);
|
||||
|
||||
// populate dynamic data
|
||||
QStringList cats = analyzer->getManaCurveByType().keys();
|
||||
cats.append(analyzer->getManaCurveByColor().keys());
|
||||
cats.removeDuplicates();
|
||||
cats.sort();
|
||||
filterList->addItems(cats);
|
||||
|
||||
groupBy->addItems({"type", "color", "subtype", "power", "toughness"});
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaCurveConfigDialog::retranslateUi()
|
||||
{
|
||||
labelGroupBy->setText(tr("Group By:"));
|
||||
groupBy->setItemText(0, tr("type"));
|
||||
groupBy->setItemText(1, tr("color"));
|
||||
groupBy->setItemText(2, tr("subtype"));
|
||||
groupBy->setItemText(3, tr("power"));
|
||||
groupBy->setItemText(4, tr("toughness"));
|
||||
|
||||
labelFilters->setText(tr("Filters (optional):"));
|
||||
|
||||
showMain->setText(tr("Show main bar row"));
|
||||
showCatRows->setText(tr("Show per-category rows"));
|
||||
}
|
||||
|
||||
void ManaCurveConfigDialog::setFromConfig(const ManaCurveConfig &cfg)
|
||||
{
|
||||
groupBy->setCurrentText(cfg.groupBy);
|
||||
// restore filters
|
||||
for (int i = 0; i < filterList->count(); ++i)
|
||||
filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text()));
|
||||
|
||||
showMain->setChecked(cfg.showMain);
|
||||
showCatRows->setChecked(cfg.showCategoryRows);
|
||||
}
|
||||
|
||||
void ManaCurveConfigDialog::accept()
|
||||
{
|
||||
cfg.groupBy = groupBy->currentText();
|
||||
|
||||
cfg.filters.clear();
|
||||
for (auto *item : filterList->selectedItems())
|
||||
cfg.filters << item->text();
|
||||
|
||||
cfg.showMain = showMain->isChecked();
|
||||
cfg.showCategoryRows = showCatRows->isChecked();
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef COCKATRICE_MANA_CURVE_ADD_DIALOG_H
|
||||
#define COCKATRICE_MANA_CURVE_ADD_DIALOG_H
|
||||
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "mana_curve_config.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
|
||||
class QListWidget;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
|
||||
class ManaCurveConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ManaCurveConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void setFromConfig(const ManaCurveConfig &cfg);
|
||||
|
||||
ManaCurveConfig result() const
|
||||
{
|
||||
return cfg;
|
||||
}
|
||||
|
||||
private:
|
||||
ManaCurveConfig cfg;
|
||||
DeckListStatisticsAnalyzer *analyzer;
|
||||
|
||||
QLabel *labelGroupBy;
|
||||
QComboBox *groupBy;
|
||||
QLabel *labelFilters;
|
||||
QListWidget *filterList;
|
||||
QDialogButtonBox *buttons;
|
||||
QCheckBox *showMain;
|
||||
QCheckBox *showCatRows;
|
||||
|
||||
private slots:
|
||||
void accept() override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_CURVE_ADD_DIALOG_H
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#include "mana_curve_total_widget.h"
|
||||
|
||||
#include "../../../general/display/charts/bars/bar_chart_widget.h"
|
||||
#include "libcockatrice/utility/color.h"
|
||||
#include "libcockatrice/utility/qt_utils.h"
|
||||
#include "mana_curve_config.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
ManaCurveTotalWidget::ManaCurveTotalWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
|
||||
label = new QLabel(this);
|
||||
label->setFixedWidth(80);
|
||||
layout->addWidget(label);
|
||||
|
||||
barChart = new BarChartWidget(this);
|
||||
layout->addWidget(barChart, 1);
|
||||
|
||||
setMinimumHeight(200);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
QSize ManaCurveTotalWidget::sizeHint() const
|
||||
{
|
||||
return {0, 280};
|
||||
}
|
||||
|
||||
QSize ManaCurveTotalWidget::minimumSizeHint() const
|
||||
{
|
||||
return {0, 200};
|
||||
}
|
||||
|
||||
void ManaCurveTotalWidget::updateDisplay(const QString &categoryName,
|
||||
int minCmc,
|
||||
int maxCmc,
|
||||
int highest,
|
||||
const QMap<int, QMap<QString, int>> &cmcMap,
|
||||
const QMap<QString, QMap<int, QStringList>> &cardsMap,
|
||||
const ManaCurveConfig &config)
|
||||
{
|
||||
QVector<BarData> mainBars;
|
||||
mainBars.reserve(maxCmc - minCmc + 1);
|
||||
|
||||
for (int cmc = minCmc; cmc <= maxCmc; ++cmc) {
|
||||
QVector<BarSegment> segments;
|
||||
|
||||
const auto cmcIt = cmcMap.constFind(cmc);
|
||||
if (cmcIt != cmcMap.cend()) {
|
||||
for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) {
|
||||
const QString &category = it.key();
|
||||
|
||||
if (!config.filters.isEmpty() && !config.filters.contains(category))
|
||||
continue;
|
||||
|
||||
const int value = it.value();
|
||||
|
||||
QStringList cards;
|
||||
const auto catIt = cardsMap.constFind(category);
|
||||
if (catIt != cardsMap.cend())
|
||||
cards = catIt->value(cmc);
|
||||
|
||||
segments.push_back({category, value, cards, GameSpecificColors::MTG::colorHelper(category)});
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(segments.begin(), segments.end(),
|
||||
[](const BarSegment &a, const BarSegment &b) { return a.category < b.category; });
|
||||
|
||||
mainBars.push_back({QString::number(cmc), segments});
|
||||
}
|
||||
|
||||
label->setText(categoryName);
|
||||
|
||||
barChart->setHighest(highest);
|
||||
barChart->setBars(mainBars);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H
|
||||
#define COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H
|
||||
#include "../../../general/display/charts/bars/bar_chart_widget.h"
|
||||
#include "mana_curve_config.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
class ManaCurveTotalWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaCurveTotalWidget(QWidget *parent);
|
||||
QSize sizeHint() const;
|
||||
QSize minimumSizeHint() const;
|
||||
void updateDisplay(const QString &categoryName,
|
||||
int minCmc,
|
||||
int maxCmc,
|
||||
int highest,
|
||||
const QMap<int, QMap<QString, int>> &cmcMap,
|
||||
const QMap<QString, QMap<int, QStringList>> &cardsMap,
|
||||
const ManaCurveConfig &config);
|
||||
|
||||
private:
|
||||
QHBoxLayout *layout;
|
||||
QLabel *label;
|
||||
BarChartWidget *barChart;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
#include "mana_curve_widget.h"
|
||||
|
||||
#include "../../../general/display/charts/bars/bar_chart_background_widget.h"
|
||||
#include "../../../general/display/charts/bars/bar_chart_widget.h"
|
||||
#include "../../../general/display/charts/bars/segmented_bar_widget.h"
|
||||
#include "../../analytics_panel_widget_registrar.h"
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "libcockatrice/utility/color.h"
|
||||
#include "libcockatrice/utility/qt_utils.h"
|
||||
#include "mana_curve_config_dialog.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QJsonArray>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
AnalyticsPanelWidgetRegistrar registerManaCurve{
|
||||
"manaCurve", ManaCurveWidget::tr("Mana Curve"),
|
||||
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaCurveWidget(parent, analyzer); }};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaCurveConfig cfg)
|
||||
: AbstractAnalyticsPanelWidget(parent, analyzer), config(cfg)
|
||||
{
|
||||
setLayout(layout);
|
||||
|
||||
totalWidget = new ManaCurveTotalWidget(this);
|
||||
totalWidget->setHidden(true);
|
||||
layout->addWidget(totalWidget);
|
||||
|
||||
categoryWidget = new ManaCurveCategoryWidget(this);
|
||||
categoryWidget->setHidden(true);
|
||||
layout->addWidget(categoryWidget);
|
||||
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
connect(analyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay);
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
QDialog *ManaCurveWidget::createConfigDialog(QWidget *parent)
|
||||
{
|
||||
auto *dlg = new ManaCurveConfigDialog(analyzer, parent);
|
||||
dlg->setFromConfig(config);
|
||||
return dlg;
|
||||
}
|
||||
|
||||
QJsonObject ManaCurveWidget::extractConfigFromDialog(QDialog *dlg) const
|
||||
{
|
||||
auto *mc = qobject_cast<ManaCurveConfigDialog *>(dlg);
|
||||
return mc ? mc->result().toJson() : QJsonObject{};
|
||||
}
|
||||
|
||||
static void buildMapsByCategory(const QHash<QString, QHash<int, int>> &categoryCounts,
|
||||
const QHash<QString, QHash<int, QStringList>> &categoryCards,
|
||||
QMap<int, QMap<QString, int>> &outCmcMap,
|
||||
QMap<QString, QMap<int, QStringList>> &outCardsMap)
|
||||
{
|
||||
outCmcMap.clear();
|
||||
outCardsMap.clear();
|
||||
|
||||
for (auto catIt = categoryCounts.cbegin(); catIt != categoryCounts.cend(); ++catIt) {
|
||||
const QString &category = catIt.key();
|
||||
const auto &countsByCmc = catIt.value();
|
||||
|
||||
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it)
|
||||
outCmcMap[it.key()][category] = it.value();
|
||||
}
|
||||
|
||||
for (auto catIt = categoryCards.cbegin(); catIt != categoryCards.cend(); ++catIt) {
|
||||
const QString &category = catIt.key();
|
||||
const auto &cardsByCmc = catIt.value();
|
||||
|
||||
for (auto it = cardsByCmc.cbegin(); it != cardsByCmc.cend(); ++it)
|
||||
outCardsMap[category][it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
static void findGlobalCmcRange(const QHash<QString, QHash<int, int>> &categoryCounts, int &minCmc, int &maxCmc)
|
||||
{
|
||||
minCmc = 0;
|
||||
maxCmc = 0;
|
||||
|
||||
for (const auto &countsByCmc : categoryCounts) {
|
||||
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it)
|
||||
maxCmc = qMax(maxCmc, it.key());
|
||||
}
|
||||
}
|
||||
|
||||
void ManaCurveWidget::updateDisplay()
|
||||
{
|
||||
QHash<QString, QHash<int, int>> categoryCounts;
|
||||
QHash<QString, QHash<int, QStringList>> categoryCards;
|
||||
|
||||
if (config.groupBy == "color") {
|
||||
categoryCounts = analyzer->getManaCurveByColor();
|
||||
categoryCards = analyzer->getManaCurveCardsByColor();
|
||||
} else if (config.groupBy == "subtype") {
|
||||
categoryCounts = analyzer->getManaCurveBySubtype();
|
||||
categoryCards = analyzer->getManaCurveCardsBySubtype();
|
||||
} else if (config.groupBy == "power") {
|
||||
categoryCounts = analyzer->getManaCurveByPower();
|
||||
categoryCards = analyzer->getManaCurveCardsByPower();
|
||||
} else {
|
||||
categoryCounts = analyzer->getManaCurveByType();
|
||||
categoryCards = analyzer->getManaCurveCardsByType();
|
||||
}
|
||||
|
||||
QMap<int, QMap<QString, int>> cmcMap;
|
||||
QMap<QString, QMap<int, QStringList>> cardsMap;
|
||||
buildMapsByCategory(categoryCounts, categoryCards, cmcMap, cardsMap);
|
||||
|
||||
int minCmc = 0;
|
||||
int maxCmc = 0;
|
||||
findGlobalCmcRange(categoryCounts, minCmc, maxCmc);
|
||||
|
||||
int highest = 1;
|
||||
for (int cmc = minCmc; cmc <= maxCmc; ++cmc) {
|
||||
int sum = 0;
|
||||
|
||||
const auto cmcIt = cmcMap.constFind(cmc);
|
||||
if (cmcIt != cmcMap.cend()) {
|
||||
for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) {
|
||||
if (!config.filters.isEmpty() && !config.filters.contains(it.key())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sum += it.value();
|
||||
}
|
||||
}
|
||||
|
||||
highest = qMax(highest, sum);
|
||||
}
|
||||
|
||||
totalWidget->updateDisplay(config.groupBy, minCmc, maxCmc, highest, cmcMap, cardsMap, config);
|
||||
|
||||
totalWidget->setVisible(config.showMain);
|
||||
|
||||
categoryWidget->updateDisplay(minCmc, maxCmc, highest, categoryCounts, categoryCards, config);
|
||||
|
||||
categoryWidget->setVisible(config.showCategoryRows);
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @file mana_curve_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef MANA_CURVE_WIDGET_H
|
||||
#define MANA_CURVE_WIDGET_H
|
||||
|
||||
#include "../../abstract_analytics_panel_widget.h"
|
||||
#include "mana_curve_category_widget.h"
|
||||
#include "mana_curve_config.h"
|
||||
#include "mana_curve_total_widget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class SegmentedBarWidget;
|
||||
class DeckListStatisticsAnalyzer;
|
||||
|
||||
class ManaCurveWidget : public AbstractAnalyticsPanelWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
// QSize sizeHint() const override;
|
||||
void updateDisplay() override;
|
||||
QDialog *createConfigDialog(QWidget *parent) override;
|
||||
|
||||
public:
|
||||
ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaCurveConfig cfg = {});
|
||||
|
||||
QJsonObject saveConfig() const override
|
||||
{
|
||||
return config.toJson();
|
||||
}
|
||||
void loadConfig(const QJsonObject &o) override
|
||||
{
|
||||
config = ManaCurveConfig::fromJson(o);
|
||||
updateDisplay();
|
||||
};
|
||||
|
||||
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
|
||||
|
||||
private:
|
||||
ManaCurveConfig config;
|
||||
ManaCurveTotalWidget *totalWidget;
|
||||
ManaCurveCategoryWidget *categoryWidget;
|
||||
};
|
||||
|
||||
#endif // MANA_CURVE_WIDGET_H
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "mana_devotion_config.h"
|
||||
|
||||
QJsonObject ManaDevotionConfig::toJson() const
|
||||
{
|
||||
QJsonObject jsonObject;
|
||||
QJsonArray jsonArray;
|
||||
jsonObject["displayType"] = displayType;
|
||||
for (auto &filter : filters) {
|
||||
jsonArray.append(filter);
|
||||
}
|
||||
jsonObject["filters"] = jsonArray;
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
ManaDevotionConfig ManaDevotionConfig::fromJson(const QJsonObject &o)
|
||||
{
|
||||
ManaDevotionConfig config;
|
||||
|
||||
if (o.contains("displayType")) {
|
||||
config.displayType = o["displayType"].toString();
|
||||
}
|
||||
|
||||
if (o.contains("filters")) {
|
||||
config.filters.clear();
|
||||
for (auto v : o["filters"].toArray()) {
|
||||
config.filters << v.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef COCKATRICE_MANA_DEVOTION_CONFIG_H
|
||||
#define COCKATRICE_MANA_DEVOTION_CONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QStringList>
|
||||
|
||||
struct ManaDevotionConfig
|
||||
{
|
||||
QString displayType; // "pie" or "bar" or "combinedBar"
|
||||
QStringList filters; // which colors to show, empty = all
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static ManaDevotionConfig fromJson(const QJsonObject &o);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_DEVOTION_CONFIG_H
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#include "mana_devotion_config_dialog.h"
|
||||
|
||||
ManaDevotionConfigDialog::ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *analyzer,
|
||||
ManaDevotionConfig initial,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), config(initial)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
|
||||
labelDisplayType = new QLabel(this);
|
||||
layout->addWidget(labelDisplayType);
|
||||
|
||||
displayType = new QComboBox(this);
|
||||
layout->addWidget(displayType);
|
||||
|
||||
labelFilters = new QLabel(this);
|
||||
layout->addWidget(labelFilters);
|
||||
|
||||
filterList = new QListWidget(this);
|
||||
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
|
||||
|
||||
QStringList colors = analyzer->getDevotionPipCount().keys();
|
||||
colors.sort();
|
||||
filterList->addItems(colors);
|
||||
layout->addWidget(filterList);
|
||||
|
||||
// select initial filters
|
||||
for (int i = 0; i < filterList->count(); ++i) {
|
||||
if (config.filters.contains(filterList->item(i)->text()))
|
||||
filterList->item(i)->setSelected(true);
|
||||
}
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ManaDevotionConfigDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &ManaDevotionConfigDialog::reject);
|
||||
|
||||
// populate combo box items
|
||||
displayType->addItems({"pie", "bar", "combinedBar"});
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaDevotionConfigDialog::retranslateUi()
|
||||
{
|
||||
labelDisplayType->setText(tr("Display type:"));
|
||||
displayType->setItemText(0, tr("pie"));
|
||||
displayType->setItemText(1, tr("bar"));
|
||||
displayType->setItemText(2, tr("combinedBar"));
|
||||
|
||||
labelFilters->setText(tr("Filter Colors (optional):"));
|
||||
}
|
||||
|
||||
void ManaDevotionConfigDialog::accept()
|
||||
{
|
||||
config.displayType = displayType->currentText();
|
||||
config.filters.clear();
|
||||
for (auto *item : filterList->selectedItems()) {
|
||||
config.filters << item->text();
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
#ifndef COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H
|
||||
#define COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H
|
||||
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "mana_devotion_config.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class ManaDevotionConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *analyzer,
|
||||
ManaDevotionConfig initial = {},
|
||||
QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
|
||||
void accept() override;
|
||||
|
||||
ManaDevotionConfig result() const
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
private:
|
||||
ManaDevotionConfig config;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *labelDisplayType;
|
||||
QComboBox *displayType;
|
||||
QLabel *labelFilters;
|
||||
QListWidget *filterList;
|
||||
QDialogButtonBox *buttons;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
#include "mana_devotion_widget.h"
|
||||
|
||||
#include "../../../general/display/charts/bars/bar_widget.h"
|
||||
#include "../../../general/display/charts/bars/color_bar.h"
|
||||
#include "../../../general/display/charts/pies/color_pie.h"
|
||||
#include "../../analytics_panel_widget_registrar.h"
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "mana_devotion_config_dialog.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QInputDialog>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
AnalyticsPanelWidgetRegistrar registerManaDevotion{
|
||||
"manaDevotion", ManaDevotionWidget::tr("Mana Devotion"),
|
||||
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaDevotionWidget(parent, analyzer); }};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaDevotionConfig cfg)
|
||||
: AbstractAnalyticsPanelWidget(parent, analyzer), config(std::move(cfg))
|
||||
{
|
||||
barContainer = new QWidget(this);
|
||||
barLayout = new QHBoxLayout(barContainer);
|
||||
barContainer->setLayout(barLayout);
|
||||
layout->addWidget(barContainer);
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
void ManaDevotionWidget::updateDisplay()
|
||||
{
|
||||
// Clear previous widgets
|
||||
while (QLayoutItem *item = barLayout->takeAt(0)) {
|
||||
if (item->widget()) {
|
||||
item->widget()->deleteLater();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
|
||||
auto &pipCount = analyzer->getDevotionPipCount();
|
||||
auto &cardCount = analyzer->getDevotionCardCount();
|
||||
|
||||
// Convert keys to single QChar form
|
||||
QHash<QChar, int> devoMap;
|
||||
for (auto key : pipCount.keys()) {
|
||||
devoMap[key[0]] = pipCount[key];
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
if (!config.filters.isEmpty()) {
|
||||
QHash<QChar, int> filtered;
|
||||
for (auto f : config.filters) {
|
||||
if (devoMap.contains(f[0])) {
|
||||
filtered[f[0]] = devoMap[f[0]];
|
||||
}
|
||||
}
|
||||
devoMap = filtered;
|
||||
}
|
||||
|
||||
// Determine maximum for bar charts
|
||||
int highest = 1;
|
||||
for (auto val : devoMap) {
|
||||
highest = std::max(highest, val);
|
||||
}
|
||||
|
||||
// Convert to QMap<QString,int> for ColorBar / ColorPie
|
||||
QMap<QString, int> mapSorted;
|
||||
for (auto it = devoMap.begin(); it != devoMap.end(); ++it) {
|
||||
mapSorted.insert(QString(it.key()), it.value());
|
||||
}
|
||||
|
||||
// Color map
|
||||
QHash<QChar, QColor> colors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)},
|
||||
{'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)},
|
||||
{'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}};
|
||||
|
||||
// Choose display mode
|
||||
if (config.displayType == "bar") {
|
||||
// One BarWidget per devotion color
|
||||
for (auto c : devoMap.keys()) {
|
||||
QString label = QString("%1 %2 (%3)").arg(c).arg(devoMap[c]).arg(cardCount.value(QString(c)));
|
||||
|
||||
BarWidget *bar = new BarWidget(label, devoMap[c], highest, colors.value(c, Qt::gray), this);
|
||||
|
||||
barLayout->addWidget(bar);
|
||||
}
|
||||
} else if (config.displayType == "combinedBar") {
|
||||
// Stacked devotion bar
|
||||
ColorBar *cb = new ColorBar(mapSorted, this);
|
||||
cb->setMinimumHeight(30);
|
||||
barLayout->addWidget(cb);
|
||||
} else if (config.displayType == "pie") {
|
||||
// Devotion pie chart
|
||||
ColorPie *pie = new ColorPie(mapSorted, this);
|
||||
pie->setMinimumSize(200, 200);
|
||||
barLayout->addWidget(pie);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
QDialog *ManaDevotionWidget::createConfigDialog(QWidget *parent)
|
||||
{
|
||||
ManaDevotionConfigDialog *dlg = new ManaDevotionConfigDialog(analyzer, config, parent);
|
||||
return dlg;
|
||||
}
|
||||
|
||||
QJsonObject ManaDevotionWidget::extractConfigFromDialog(QDialog *dlg) const
|
||||
{
|
||||
auto *mc = qobject_cast<ManaDevotionConfigDialog *>(dlg);
|
||||
if (!mc) {
|
||||
return {};
|
||||
}
|
||||
return mc->result().toJson();
|
||||
}
|
||||
|
||||
QSize ManaDevotionWidget::sizeHint() const
|
||||
{
|
||||
return QSize(800, 150);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @file mana_devotion_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef MANA_DEVOTION_WIDGET_H
|
||||
#define MANA_DEVOTION_WIDGET_H
|
||||
#include "../../../general/display/banner_widget.h"
|
||||
#include "../../abstract_analytics_panel_widget.h"
|
||||
#include "mana_devotion_config.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
class ManaDevotionWidget : public AbstractAnalyticsPanelWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
QSize sizeHint() const override;
|
||||
void updateDisplay() override;
|
||||
QDialog *createConfigDialog(QWidget *parent) override;
|
||||
|
||||
public:
|
||||
ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaDevotionConfig cfg = {});
|
||||
|
||||
QJsonObject saveConfig() const override
|
||||
{
|
||||
return config.toJson();
|
||||
}
|
||||
void loadConfig(const QJsonObject &o) override
|
||||
{
|
||||
config = ManaDevotionConfig::fromJson(o);
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
|
||||
|
||||
private:
|
||||
ManaDevotionConfig config;
|
||||
QWidget *barContainer;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_DEVOTION_WIDGET_H
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#include "mana_distribution_config.h"
|
||||
|
||||
QJsonObject ManaDistributionConfig::toJson() const
|
||||
{
|
||||
QJsonObject o;
|
||||
o["displayType"] = displayType;
|
||||
|
||||
QJsonArray jsonArray;
|
||||
for (auto &s : filters) {
|
||||
jsonArray.append(s);
|
||||
}
|
||||
o["filters"] = jsonArray;
|
||||
|
||||
o["showColorRows"] = showColorRows;
|
||||
return o;
|
||||
}
|
||||
|
||||
ManaDistributionConfig ManaDistributionConfig::fromJson(const QJsonObject &o)
|
||||
{
|
||||
ManaDistributionConfig config;
|
||||
if (o.contains("displayType")) {
|
||||
config.displayType = o["displayType"].toString();
|
||||
}
|
||||
|
||||
if (o.contains("filters")) {
|
||||
config.filters.clear();
|
||||
for (auto v : o["filters"].toArray())
|
||||
config.filters << v.toString();
|
||||
}
|
||||
|
||||
if (o.contains("showColorRows")) {
|
||||
config.showColorRows = o["showColorRows"].toBool(true);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef COCKATRICE_MANA_DISTRIBUTION_CONFIG_H
|
||||
#define COCKATRICE_MANA_DISTRIBUTION_CONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
struct ManaDistributionConfig
|
||||
{
|
||||
QString displayType = "pie"; // "pie" or "bar"
|
||||
QStringList filters; // empty = all colors
|
||||
bool showColorRows = true;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static ManaDistributionConfig fromJson(const QJsonObject &o);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_DISTRIBUTION_CONFIG_H
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#include "mana_distribution_config_dialog.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
static const QStringList kColors = {"W", "U", "B", "R", "G", "C"};
|
||||
|
||||
ManaDistributionConfigDialog::ManaDistributionConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent)
|
||||
: QDialog(parent), analyzer(analyzer)
|
||||
{
|
||||
auto *lay = new QVBoxLayout(this);
|
||||
|
||||
// Labels
|
||||
labelDisplayType = new QLabel(this);
|
||||
lay->addWidget(labelDisplayType);
|
||||
|
||||
displayType = new QComboBox(this);
|
||||
lay->addWidget(displayType);
|
||||
|
||||
labelFilters = new QLabel(this);
|
||||
lay->addWidget(labelFilters);
|
||||
|
||||
filterList = new QListWidget(this);
|
||||
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
|
||||
filterList->addItems(kColors); // dynamic/fixed, no translation needed
|
||||
lay->addWidget(filterList);
|
||||
|
||||
showColorRows = new QCheckBox(this);
|
||||
showColorRows->setChecked(true);
|
||||
lay->addWidget(showColorRows);
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
lay->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ManaDistributionConfigDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &ManaDistributionConfigDialog::reject);
|
||||
|
||||
displayType->addItems({"pie", "bar"}); // combo items
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaDistributionConfigDialog::retranslateUi()
|
||||
{
|
||||
labelDisplayType->setText(tr("Top display type:"));
|
||||
displayType->setItemText(0, tr("pie"));
|
||||
displayType->setItemText(1, tr("bar"));
|
||||
|
||||
labelFilters->setText(tr("Colors:"));
|
||||
|
||||
showColorRows->setText(tr("Show per-color rows"));
|
||||
|
||||
// QDialogButtonBox buttons are automatically translated
|
||||
}
|
||||
|
||||
void ManaDistributionConfigDialog::setFromConfig(const ManaDistributionConfig &cfgIn)
|
||||
{
|
||||
cfg = cfgIn;
|
||||
|
||||
displayType->setCurrentText(cfg.displayType);
|
||||
|
||||
for (int i = 0; i < filterList->count(); ++i)
|
||||
filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text()));
|
||||
|
||||
showColorRows->setChecked(cfg.showColorRows);
|
||||
}
|
||||
|
||||
void ManaDistributionConfigDialog::accept()
|
||||
{
|
||||
cfg.displayType = displayType->currentText();
|
||||
|
||||
// Filters
|
||||
cfg.filters.clear();
|
||||
for (auto *item : filterList->selectedItems())
|
||||
cfg.filters << item->text();
|
||||
|
||||
cfg.showColorRows = showColorRows->isChecked();
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H
|
||||
#define COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H
|
||||
|
||||
#include "mana_distribution_config.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QStringList>
|
||||
|
||||
class QComboBox;
|
||||
class QListWidget;
|
||||
class QCheckBox;
|
||||
class DeckListStatisticsAnalyzer;
|
||||
|
||||
class ManaDistributionConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ManaDistributionConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
|
||||
void setFromConfig(const ManaDistributionConfig &cfg);
|
||||
const ManaDistributionConfig &config() const
|
||||
{
|
||||
return cfg;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
DeckListStatisticsAnalyzer *analyzer;
|
||||
|
||||
QLabel *labelDisplayType;
|
||||
QComboBox *displayType;
|
||||
QLabel *labelFilters;
|
||||
QListWidget *filterList;
|
||||
QCheckBox *showColorRows;
|
||||
QDialogButtonBox *buttons;
|
||||
|
||||
ManaDistributionConfig cfg;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#include "mana_distribution_single_display_widget.h"
|
||||
|
||||
#include "../../../cards/additional_info/mana_symbol_widget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
ManaDistributionSingleDisplayWidget::ManaDistributionSingleDisplayWidget(const QString &colorSymbol, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setAlignment(Qt::AlignHCenter);
|
||||
|
||||
symbolLabel = new ManaSymbolWidget(this, colorSymbol, true, false);
|
||||
symbolLabel->setFixedSize(40, 40);
|
||||
|
||||
devotionBar = new QProgressBar(this);
|
||||
devotionBar->setRange(0, 100);
|
||||
devotionBar->setTextVisible(false);
|
||||
|
||||
devotionLabel = new QLabel(this);
|
||||
devotionLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
productionBar = new QProgressBar(this);
|
||||
productionBar->setRange(0, 100);
|
||||
productionBar->setTextVisible(false);
|
||||
|
||||
productionLabel = new QLabel(this);
|
||||
productionLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(symbolLabel);
|
||||
layout->addWidget(devotionBar);
|
||||
layout->addWidget(devotionLabel);
|
||||
layout->addWidget(productionBar);
|
||||
layout->addWidget(productionLabel);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void ManaDistributionSingleDisplayWidget::setDevotion(int pips, int cards, int percent)
|
||||
{
|
||||
devotionBar->setValue(percent);
|
||||
devotionLabel->setText(QString(tr("%1 pips (%2 cards)")).arg(pips).arg(cards));
|
||||
}
|
||||
|
||||
void ManaDistributionSingleDisplayWidget::setProduction(int pips, int cards, int percent)
|
||||
{
|
||||
productionBar->setValue(percent);
|
||||
productionLabel->setText(QString(tr("%1 mana (%2 cards)")).arg(pips).arg(cards));
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H
|
||||
#define COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QWidget>
|
||||
|
||||
class ManaDistributionSingleDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ManaDistributionSingleDisplayWidget(const QString &colorSymbol, QWidget *parent = nullptr);
|
||||
|
||||
void setDevotion(int pips, int cards, int percent);
|
||||
void setProduction(int pips, int cards, int percent);
|
||||
|
||||
private:
|
||||
QLabel *symbolLabel;
|
||||
|
||||
QProgressBar *devotionBar;
|
||||
QLabel *devotionLabel;
|
||||
|
||||
QProgressBar *productionBar;
|
||||
QLabel *productionLabel;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
#include "mana_distribution_widget.h"
|
||||
|
||||
#include "../../analytics_panel_widget_registrar.h"
|
||||
#include "mana_distribution_config_dialog.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace
|
||||
{
|
||||
AnalyticsPanelWidgetRegistrar registerManaDistribution{
|
||||
"manaProdDevotion", ManaDistributionWidget::tr("Mana Production + Devotion"),
|
||||
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaDistributionWidget(parent, analyzer); }};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
static const QStringList kColors = {"W", "U", "B", "R", "G", "C"};
|
||||
|
||||
ManaDistributionWidget::ManaDistributionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
||||
: AbstractAnalyticsPanelWidget(parent, analyzer)
|
||||
{
|
||||
container = new QWidget(this);
|
||||
containerLayout = new QVBoxLayout(container);
|
||||
|
||||
devotionBarTop = new ColorBar({}, this);
|
||||
devotionPieTop = new ColorPie({}, this);
|
||||
productionBarTop = new ColorBar({}, this);
|
||||
productionPieTop = new ColorPie({}, this);
|
||||
|
||||
containerLayout->addWidget(devotionBarTop);
|
||||
containerLayout->addWidget(devotionPieTop);
|
||||
containerLayout->addWidget(productionBarTop);
|
||||
containerLayout->addWidget(productionPieTop);
|
||||
|
||||
devotionPieTop->hide();
|
||||
productionPieTop->hide();
|
||||
|
||||
row = new QHBoxLayout();
|
||||
containerLayout->addLayout(row);
|
||||
|
||||
for (const QString &c : kColors) {
|
||||
auto *w = new ManaDistributionSingleDisplayWidget(c, this);
|
||||
row->addWidget(w);
|
||||
rows[c] = w;
|
||||
}
|
||||
|
||||
layout->addWidget(container);
|
||||
}
|
||||
|
||||
void ManaDistributionWidget::updateDisplay()
|
||||
{
|
||||
const auto &devPips = analyzer->getDevotionPipCount();
|
||||
const auto &devCards = analyzer->getDevotionCardCount();
|
||||
const auto &prodPips = analyzer->getProductionPipCount();
|
||||
const auto &prodCards = analyzer->getProductionCardCount();
|
||||
|
||||
QStringList filtered = config.filters.isEmpty() ? kColors : config.filters;
|
||||
|
||||
QMap<QString, int> devMap, prodMap;
|
||||
for (const QString &c : filtered) {
|
||||
devMap[c] = devPips.value(c, 0);
|
||||
prodMap[c] = prodPips.value(c, 0);
|
||||
}
|
||||
|
||||
bool showPie = (config.displayType == "pie");
|
||||
|
||||
devotionBarTop->setVisible(!showPie);
|
||||
productionBarTop->setVisible(!showPie);
|
||||
|
||||
devotionPieTop->setVisible(showPie);
|
||||
productionPieTop->setVisible(showPie);
|
||||
|
||||
if (showPie) {
|
||||
devotionPieTop->setColors(devMap);
|
||||
productionPieTop->setColors(prodMap);
|
||||
} else {
|
||||
devotionBarTop->setColors(devMap);
|
||||
productionBarTop->setColors(prodMap);
|
||||
}
|
||||
|
||||
for (const QString &c : kColors) {
|
||||
auto *w = rows.value(c);
|
||||
|
||||
if (!w) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool visible = config.showColorRows && filtered.contains(c);
|
||||
w->setVisible(visible);
|
||||
if (!visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int dp = devPips.value(c, 0);
|
||||
int dc = devCards.value(c, 0);
|
||||
int pp = prodPips.value(c, 0);
|
||||
int pc = prodCards.value(c, 0);
|
||||
|
||||
// Compute percentages
|
||||
int totalDev = 0;
|
||||
int totalProd = 0;
|
||||
for (const QString &cc : filtered) {
|
||||
totalDev += devPips.value(cc, 0);
|
||||
totalProd += prodPips.value(cc, 0);
|
||||
}
|
||||
|
||||
int devPct = (totalDev > 0) ? int(100.0 * dp / totalDev) : 0;
|
||||
int prodPct = (totalProd > 0) ? int(100.0 * pp / totalProd) : 0;
|
||||
|
||||
w->setDevotion(dp, dc, devPct);
|
||||
w->setProduction(pp, pc, prodPct);
|
||||
}
|
||||
}
|
||||
|
||||
QDialog *ManaDistributionWidget::createConfigDialog(QWidget *parent)
|
||||
{
|
||||
auto *dlg = new ManaDistributionConfigDialog(analyzer, parent);
|
||||
dlg->setWindowTitle(tr("Mana Distribution Settings"));
|
||||
dlg->setFromConfig(config);
|
||||
|
||||
connect(dlg, &QDialog::accepted, [this, dlg]() {
|
||||
config = dlg->config();
|
||||
updateDisplay();
|
||||
});
|
||||
|
||||
return dlg;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef COCKATRICE_MANA_DISTRIBUTION_WIDGET_H
|
||||
#define COCKATRICE_MANA_DISTRIBUTION_WIDGET_H
|
||||
|
||||
#include "../../../general/display/charts/bars/color_bar.h"
|
||||
#include "../../../general/display/charts/pies/color_pie.h"
|
||||
#include "../../abstract_analytics_panel_widget.h"
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "mana_distribution_config.h"
|
||||
#include "mana_distribution_single_display_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QMap>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class ManaDistributionWidget : public AbstractAnalyticsPanelWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ManaDistributionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
|
||||
void updateDisplay() override;
|
||||
QDialog *createConfigDialog(QWidget *parent) override;
|
||||
QJsonObject extractConfigFromDialog(QDialog *) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
ManaDistributionConfig config;
|
||||
|
||||
QWidget *container;
|
||||
QVBoxLayout *containerLayout;
|
||||
|
||||
QVBoxLayout *topLayout;
|
||||
ColorBar *devotionBarTop;
|
||||
ColorPie *devotionPieTop;
|
||||
ColorBar *productionBarTop;
|
||||
ColorPie *productionPieTop;
|
||||
|
||||
QHBoxLayout *row;
|
||||
QMap<QString, ManaDistributionSingleDisplayWidget *> rows;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MANA_DISTRIBUTION_WIDGET_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue