mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-25 08:03:58 -05: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
|
|
@ -144,11 +144,31 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/cards/card_size_widget.cpp
|
||||
src/interface/widgets/cards/deck_card_zone_display_widget.cpp
|
||||
src/interface/widgets/cards/deck_preview_card_picture_widget.cpp
|
||||
src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
|
||||
src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp
|
||||
src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp
|
||||
src/interface/widgets/deck_analytics/deck_analytics_widget.cpp
|
||||
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
|
||||
src/interface/widgets/deck_analytics/mana_base_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_devotion_widget.cpp
|
||||
src/interface/widgets/deck_analytics/resizable_panel.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
|
||||
|
|
@ -160,13 +180,17 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/general/background_sources.cpp
|
||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||
src/interface/widgets/general/display/banner_widget.cpp
|
||||
src/interface/widgets/general/display/bar_widget.cpp
|
||||
src/interface/widgets/general/display/color_bar.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_label.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||
src/interface/widgets/general/display/labeled_input.cpp
|
||||
src/interface/widgets/general/display/percent_bar_widget.cpp
|
||||
src/interface/widgets/general/display/shadow_background_label.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/color_bar.cpp
|
||||
src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/pies/color_pie.cpp
|
||||
src/interface/widgets/general/home_styled_button.cpp
|
||||
src/interface/widgets/general/home_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
#include "abstract_analytics_panel_widget.h"
|
||||
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
AbstractAnalyticsPanelWidget::AbstractAnalyticsPanelWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
||||
: QWidget(parent), analyzer(analyzer)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
|
||||
bannerAndSettingsContainer = new QWidget(this);
|
||||
|
||||
bannerAndSettingsLayout = new QHBoxLayout(bannerAndSettingsContainer);
|
||||
bannerAndSettingsContainer->setLayout(bannerAndSettingsLayout);
|
||||
bannerWidget = new BannerWidget(this, "Analytics Widget", Qt::Vertical, 100);
|
||||
bannerWidget->setMaximumHeight(100);
|
||||
|
||||
bannerAndSettingsLayout->addWidget(bannerWidget, 1);
|
||||
|
||||
// config button
|
||||
configureButton = new QPushButton(tr("Configure"), this);
|
||||
configureButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
connect(configureButton, &QPushButton::clicked, this, &AbstractAnalyticsPanelWidget::applyConfigFromDialog);
|
||||
bannerAndSettingsLayout->addWidget(configureButton, 0);
|
||||
|
||||
layout->addWidget(bannerAndSettingsContainer);
|
||||
|
||||
connect(analyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &AbstractAnalyticsPanelWidget::updateDisplay);
|
||||
}
|
||||
|
||||
bool AbstractAnalyticsPanelWidget::applyConfigFromDialog()
|
||||
{
|
||||
QDialog *dlg = createConfigDialog(this);
|
||||
if (!dlg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = dlg->exec() == QDialog::Accepted;
|
||||
if (ok) {
|
||||
// dialog must expose its final config as JSON
|
||||
auto newCfg = extractConfigFromDialog(dlg);
|
||||
loadConfig(newCfg);
|
||||
updateDisplay();
|
||||
}
|
||||
dlg->deleteLater();
|
||||
return ok;
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H
|
||||
#define COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H
|
||||
|
||||
#include "../general/display/banner_widget.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QJsonObject>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class DeckListStatisticsAnalyzer;
|
||||
|
||||
class AbstractAnalyticsPanelWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
virtual void updateDisplay() = 0;
|
||||
// Widgets must return a config dialog
|
||||
virtual QDialog *createConfigDialog(QWidget *parent) = 0;
|
||||
|
||||
public:
|
||||
explicit AbstractAnalyticsPanelWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
|
||||
void setDisplayTitle(const QString &title)
|
||||
{
|
||||
displayTitle = title;
|
||||
if (bannerWidget) {
|
||||
bannerWidget->setText(displayTitle);
|
||||
}
|
||||
}
|
||||
|
||||
QString displayTitleText() const
|
||||
{
|
||||
return displayTitle;
|
||||
}
|
||||
|
||||
virtual QJsonObject saveConfig() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual void loadConfig(const QJsonObject &)
|
||||
{
|
||||
}
|
||||
|
||||
// Unified helper to run config dialog and update widget
|
||||
bool applyConfigFromDialog();
|
||||
|
||||
// Dialog → JSON must be supplied by each subclass
|
||||
virtual QJsonObject extractConfigFromDialog(QDialog *dlg) const = 0;
|
||||
|
||||
protected:
|
||||
DeckListStatisticsAnalyzer *analyzer;
|
||||
QVBoxLayout *layout;
|
||||
QWidget *bannerAndSettingsContainer;
|
||||
QHBoxLayout *bannerAndSettingsLayout;
|
||||
QString displayTitle;
|
||||
BannerWidget *bannerWidget;
|
||||
QPushButton *configureButton;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#include "add_analytics_panel_dialog.h"
|
||||
|
||||
#include "analytics_panel_widget_factory.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
AddAnalyticsPanelDialog::AddAnalyticsPanelDialog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Add Analytics Panel"));
|
||||
|
||||
layout = new QVBoxLayout(this);
|
||||
|
||||
typeCombo = new QComboBox(this);
|
||||
|
||||
// Populate using descriptors
|
||||
const auto widgets = AnalyticsPanelWidgetFactory::instance().availableWidgets();
|
||||
|
||||
for (const auto &desc : widgets) {
|
||||
// Show translated title to user
|
||||
typeCombo->addItem(desc.title, desc.type);
|
||||
}
|
||||
|
||||
layout->addWidget(typeCombo);
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
|
||||
layout->addWidget(buttons);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
#ifndef COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H
|
||||
#define COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H
|
||||
|
||||
#include "analytics_panel_widget_factory.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class AddAnalyticsPanelDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AddAnalyticsPanelDialog(QWidget *parent);
|
||||
|
||||
QString selectedType() const
|
||||
{
|
||||
return typeCombo->currentData().toString();
|
||||
}
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
QComboBox *typeCombo;
|
||||
QDialogButtonBox *buttons;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#include "analytics_panel_widget_factory.h"
|
||||
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
|
||||
AnalyticsPanelWidgetFactory &AnalyticsPanelWidgetFactory::instance()
|
||||
{
|
||||
static AnalyticsPanelWidgetFactory f;
|
||||
return f;
|
||||
}
|
||||
|
||||
void AnalyticsPanelWidgetFactory::registerWidget(const Descriptor &desc)
|
||||
{
|
||||
widgets.insert(desc.type, desc);
|
||||
}
|
||||
|
||||
AbstractAnalyticsPanelWidget *
|
||||
AnalyticsPanelWidgetFactory::create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const
|
||||
{
|
||||
auto it = widgets.find(type);
|
||||
if (it == widgets.end())
|
||||
return nullptr;
|
||||
|
||||
auto w = it->creator(parent, analyzer);
|
||||
|
||||
w->setDisplayTitle(it->title);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
QList<AnalyticsPanelWidgetFactory::Descriptor> AnalyticsPanelWidgetFactory::availableWidgets() const
|
||||
{
|
||||
return widgets.values();
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_FACTORY_H
|
||||
#define COCKATRICE_DECK_ANALYTICS_WIDGET_FACTORY_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
#include <functional>
|
||||
|
||||
class AbstractAnalyticsPanelWidget;
|
||||
class DeckListStatisticsAnalyzer;
|
||||
|
||||
class AnalyticsPanelWidgetFactory
|
||||
{
|
||||
public:
|
||||
using Creator = std::function<AbstractAnalyticsPanelWidget *(QWidget *, DeckListStatisticsAnalyzer *)>;
|
||||
|
||||
struct Descriptor
|
||||
{
|
||||
QString type; // stable ID ("manaProdDevotion")
|
||||
QString title; // translated, user-facing
|
||||
Creator creator;
|
||||
};
|
||||
|
||||
static AnalyticsPanelWidgetFactory &instance();
|
||||
|
||||
// NEW: richer registration
|
||||
void registerWidget(const Descriptor &desc);
|
||||
|
||||
AbstractAnalyticsPanelWidget *
|
||||
create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const;
|
||||
|
||||
// NEW: expose widgets to UI
|
||||
QList<Descriptor> availableWidgets() const;
|
||||
|
||||
private:
|
||||
AnalyticsPanelWidgetFactory() = default; // Ensure private constructor
|
||||
AnalyticsPanelWidgetFactory(const AnalyticsPanelWidgetFactory &) = delete;
|
||||
AnalyticsPanelWidgetFactory &operator=(const AnalyticsPanelWidgetFactory &) = delete;
|
||||
|
||||
QMap<QString, Descriptor> widgets;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "analytics_panel_widget_registrar.h"
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H
|
||||
#define COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H
|
||||
|
||||
#include "analytics_panel_widget_factory.h"
|
||||
|
||||
class AnalyticsPanelWidgetRegistrar
|
||||
{
|
||||
public:
|
||||
AnalyticsPanelWidgetRegistrar(const QString &type,
|
||||
const QString &title,
|
||||
AnalyticsPanelWidgetFactory::Creator creator)
|
||||
{
|
||||
AnalyticsPanelWidgetFactory::instance().registerWidget({type, title, creator});
|
||||
}
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H
|
||||
|
|
@ -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
|
||||
|
|
@ -1,35 +1,298 @@
|
|||
#include "deck_analytics_widget.h"
|
||||
|
||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
{
|
||||
mainLayout = new QVBoxLayout();
|
||||
setLayout(mainLayout);
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "add_analytics_panel_dialog.h"
|
||||
#include "analytics_panel_widget_factory.h"
|
||||
#include "analyzer_modules/mana_base/mana_base_config.h"
|
||||
#include "analyzer_modules/mana_curve/mana_curve_config.h"
|
||||
#include "analyzer_modules/mana_devotion/mana_devotion_config.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *_statsAnalyzer)
|
||||
: QWidget(parent), statsAnalyzer(_statsAnalyzer)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
|
||||
// Controls
|
||||
controlContainer = new QWidget(this);
|
||||
controlLayout = new QHBoxLayout(controlContainer);
|
||||
addButton = new QPushButton(this);
|
||||
removeButton = new QPushButton(this);
|
||||
saveButton = new QPushButton(this);
|
||||
loadButton = new QPushButton(this);
|
||||
controlLayout->addWidget(addButton);
|
||||
controlLayout->addWidget(removeButton);
|
||||
controlLayout->addWidget(saveButton);
|
||||
controlLayout->addWidget(loadButton);
|
||||
|
||||
layout->addWidget(controlContainer);
|
||||
|
||||
connect(addButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onAddPanel);
|
||||
connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected);
|
||||
connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout);
|
||||
connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout);
|
||||
|
||||
// Scroll area and container
|
||||
scrollArea = new QScrollArea(this);
|
||||
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
mainLayout->addWidget(scrollArea);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
|
||||
container = new QWidget(scrollArea);
|
||||
containerLayout = new QVBoxLayout(container);
|
||||
container->setLayout(containerLayout);
|
||||
scrollArea->setWidget(container);
|
||||
panelContainer = new QWidget(scrollArea);
|
||||
panelLayout = new QVBoxLayout(panelContainer);
|
||||
panelLayout->setSpacing(8);
|
||||
panelLayout->setContentsMargins(4, 4, 4, 4);
|
||||
panelLayout->addStretch(1); // push panels up
|
||||
|
||||
deckListStatisticsAnalyzer = new DeckListStatisticsAnalyzer(this, deckListModel);
|
||||
scrollArea->setWidget(panelContainer);
|
||||
layout->addWidget(scrollArea);
|
||||
|
||||
manaCurveWidget = new ManaCurveWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaCurveWidget);
|
||||
loadLayout();
|
||||
|
||||
manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaDevotionWidget);
|
||||
|
||||
manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaBaseWidget);
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::refreshDisplays()
|
||||
void DeckAnalyticsWidget::retranslateUi()
|
||||
{
|
||||
deckListStatisticsAnalyzer->update();
|
||||
addButton->setText(tr("Add Panel"));
|
||||
removeButton->setText(tr("Remove Panel"));
|
||||
saveButton->setText(tr("Save Layout"));
|
||||
loadButton->setText(tr("Load Layout"));
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::updateDisplays()
|
||||
{
|
||||
statsAnalyzer->analyze();
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::onAddPanel()
|
||||
{
|
||||
AddAnalyticsPanelDialog dlg(this);
|
||||
if (dlg.exec() != QDialog::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString selection = dlg.selectedType();
|
||||
if (selection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractAnalyticsPanelWidget *analyticsWidget =
|
||||
AnalyticsPanelWidgetFactory::instance().create(selection, this, statsAnalyzer);
|
||||
if (!analyticsWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!analyticsWidget->applyConfigFromDialog()) {
|
||||
analyticsWidget->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
addPanelInstance(selection, analyticsWidget, analyticsWidget->saveConfig());
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::addPanelInstance(const QString &typeId,
|
||||
AbstractAnalyticsPanelWidget *panel,
|
||||
const QJsonObject &cfg)
|
||||
{
|
||||
panel->loadConfig(cfg);
|
||||
panel->updateDisplay();
|
||||
|
||||
auto *resPanel = new ResizablePanel(typeId, panel, panelContainer);
|
||||
panelWrappers.push_back(resPanel);
|
||||
|
||||
panelLayout->insertWidget(panelLayout->count() - 1, resPanel);
|
||||
|
||||
// Event filter for selection
|
||||
resPanel->installEventFilter(this);
|
||||
panel->installEventFilter(this);
|
||||
|
||||
// Connect drag-drop signals
|
||||
connect(resPanel, &ResizablePanel::dropRequested, this, &DeckAnalyticsWidget::onPanelDropped);
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::onRemoveSelected()
|
||||
{
|
||||
int idx = indexOfSelectedWrapper();
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResizablePanel *panel = panelWrappers.takeAt(idx);
|
||||
selectWrapper(nullptr);
|
||||
|
||||
panel->deleteLater();
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::saveLayout()
|
||||
{
|
||||
QJsonArray arr;
|
||||
|
||||
for (auto *wrapper : panelWrappers) {
|
||||
QJsonObject entry;
|
||||
entry["type"] = wrapper->getTypeId();
|
||||
entry["config"] = wrapper->panel->saveConfig();
|
||||
entry["height"] = wrapper->getCurrentHeight();
|
||||
arr.append(entry);
|
||||
}
|
||||
|
||||
QSettings s;
|
||||
s.setValue("deckAnalytics/layout", QString::fromUtf8(QJsonDocument(arr).toJson(QJsonDocument::Compact)));
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::loadLayout()
|
||||
{
|
||||
if (!loadLayoutInternal()) {
|
||||
addDefaultPanels();
|
||||
}
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::addDefaultPanels()
|
||||
{
|
||||
struct DefaultPanel
|
||||
{
|
||||
QString type;
|
||||
QJsonObject cfg;
|
||||
};
|
||||
|
||||
// Prepare configs
|
||||
QJsonObject manaCurveCfg = ManaCurveConfig{}.toJson();
|
||||
QJsonObject manaBaseCfg = ManaBaseConfig{"combinedBar", {}}.toJson();
|
||||
QJsonObject manaDevotionCfg = ManaDevotionConfig{"combinedBar", {}}.toJson();
|
||||
QVector<DefaultPanel> defaults = {
|
||||
{"manaCurve", manaCurveCfg}, {"manaBase", manaBaseCfg}, {"manaDevotion", manaDevotionCfg}};
|
||||
|
||||
for (auto &d : defaults) {
|
||||
AbstractAnalyticsPanelWidget *w = AnalyticsPanelWidgetFactory::instance().create(d.type, this, statsAnalyzer);
|
||||
if (!w) {
|
||||
continue;
|
||||
}
|
||||
|
||||
w->loadConfig(d.cfg);
|
||||
addPanelInstance(d.type, w, d.cfg);
|
||||
}
|
||||
}
|
||||
|
||||
bool DeckAnalyticsWidget::loadLayoutInternal()
|
||||
{
|
||||
QSettings s;
|
||||
QString layoutData = s.value("deckAnalytics/layout").toString();
|
||||
if (layoutData.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(layoutData.toUtf8());
|
||||
if (!doc.isArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clearPanels();
|
||||
|
||||
for (auto v : doc.array()) {
|
||||
if (!v.isObject()) {
|
||||
continue;
|
||||
}
|
||||
QJsonObject o = v.toObject();
|
||||
QString type = o["type"].toString();
|
||||
QJsonObject cfg = o["config"].toObject();
|
||||
|
||||
AbstractAnalyticsPanelWidget *w = AnalyticsPanelWidgetFactory::instance().create(type, this, statsAnalyzer);
|
||||
if (!w) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addPanelInstance(type, w, cfg);
|
||||
|
||||
// Restore height AFTER adding the panel
|
||||
if (o.contains("height")) {
|
||||
panelWrappers.last()->setHeightFromSaved(o["height"].toInt());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::clearPanels()
|
||||
{
|
||||
selectWrapper(nullptr);
|
||||
while (!panelWrappers.isEmpty()) {
|
||||
ResizablePanel *p = panelWrappers.takeLast();
|
||||
p->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeckAnalyticsWidget::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
for (auto *p : panelWrappers) {
|
||||
if (obj == p || obj == p->panel) {
|
||||
selectWrapper(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::selectWrapper(ResizablePanel *w)
|
||||
{
|
||||
// Same wrapper
|
||||
if (selectedWrapper == w) {
|
||||
return;
|
||||
}
|
||||
// Deselect the old one
|
||||
if (selectedWrapper) {
|
||||
selectedWrapper->setSelected(false);
|
||||
}
|
||||
// Set current
|
||||
selectedWrapper = w;
|
||||
// Finally, select new
|
||||
if (selectedWrapper) {
|
||||
selectedWrapper->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
int DeckAnalyticsWidget::indexOfSelectedWrapper() const
|
||||
{
|
||||
if (!selectedWrapper) {
|
||||
return -1;
|
||||
}
|
||||
return panelWrappers.indexOf(selectedWrapper);
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::onPanelDropped(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore)
|
||||
{
|
||||
int draggedIdx = panelWrappers.indexOf(dragged);
|
||||
int targetIdx = panelWrappers.indexOf(target);
|
||||
|
||||
if (draggedIdx == -1 || targetIdx == -1 || draggedIdx == targetIdx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove dragged panel from list and layout
|
||||
panelWrappers.removeAt(draggedIdx);
|
||||
panelLayout->removeWidget(dragged);
|
||||
|
||||
// Adjust target index if needed
|
||||
if (draggedIdx < targetIdx) {
|
||||
targetIdx--;
|
||||
}
|
||||
|
||||
// Calculate insertion position
|
||||
int insertIdx = insertBefore ? targetIdx : targetIdx + 1;
|
||||
|
||||
// Insert back into list and layout
|
||||
panelWrappers.insert(insertIdx, dragged);
|
||||
panelLayout->insertWidget(insertIdx, dragged);
|
||||
|
||||
// Clear selection
|
||||
selectWrapper(nullptr);
|
||||
}
|
||||
|
|
@ -1,44 +1,71 @@
|
|||
/**
|
||||
* @file deck_analytics_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
* @brief Main analytics widget container with resizable panels for deck statistics.
|
||||
*/
|
||||
|
||||
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||
#define DECK_ANALYTICS_WIDGET_H
|
||||
|
||||
#include "mana_base_widget.h"
|
||||
#include "mana_curve_widget.h"
|
||||
#include "mana_devotion_widget.h"
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QJsonObject>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
|
||||
class LayoutInspector;
|
||||
|
||||
class DeckAnalyticsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
void updateDisplays();
|
||||
|
||||
public:
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
void setDeckList(const DeckList &_deckListModel);
|
||||
std::map<int, int> analyzeManaCurve();
|
||||
void refreshDisplays();
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
void retranslateUi();
|
||||
|
||||
private slots:
|
||||
void onAddPanel();
|
||||
void onRemoveSelected();
|
||||
void onPanelDropped(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore);
|
||||
void saveLayout();
|
||||
void loadLayout();
|
||||
void addDefaultPanels();
|
||||
bool loadLayoutInternal();
|
||||
void clearPanels();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
void selectWrapper(ResizablePanel *panel);
|
||||
int indexOfSelectedWrapper() const;
|
||||
|
||||
private:
|
||||
DeckListModel *deckListModel;
|
||||
DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer;
|
||||
QVBoxLayout *mainLayout;
|
||||
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||
|
||||
QWidget *container;
|
||||
QVBoxLayout *containerLayout;
|
||||
QVBoxLayout *layout;
|
||||
QWidget *controlContainer;
|
||||
QHBoxLayout *controlLayout;
|
||||
|
||||
QPushButton *addButton;
|
||||
QPushButton *removeButton;
|
||||
QPushButton *saveButton;
|
||||
QPushButton *loadButton;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
QWidget *panelContainer;
|
||||
QVBoxLayout *panelLayout;
|
||||
|
||||
ManaCurveWidget *manaCurveWidget;
|
||||
ManaDevotionWidget *manaDevotionWidget;
|
||||
ManaBaseWidget *manaBaseWidget;
|
||||
QVector<ResizablePanel *> panelWrappers;
|
||||
ResizablePanel *selectedWrapper = nullptr;
|
||||
|
||||
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||
LayoutInspector *insp = nullptr;
|
||||
};
|
||||
|
||||
#endif // DECK_ANALYTICS_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -9,38 +9,93 @@
|
|||
|
||||
DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
|
||||
DeckListModel *_model,
|
||||
DeckListStatisticsAnalyzerConfig cfg)
|
||||
: QObject(parent), model(_model), config(cfg)
|
||||
DeckListStatisticsAnalyzerConfig _config)
|
||||
: QObject(parent), model(_model), config(_config)
|
||||
{
|
||||
connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::update);
|
||||
connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::analyze);
|
||||
}
|
||||
|
||||
void DeckListStatisticsAnalyzer::update()
|
||||
void DeckListStatisticsAnalyzer::analyze()
|
||||
{
|
||||
manaBaseMap.clear();
|
||||
manaCurveMap.clear();
|
||||
manaDevotionMap.clear();
|
||||
clearData();
|
||||
|
||||
QList<ExactCard> cards = model->getCards();
|
||||
|
||||
for (const ExactCard &card : cards) {
|
||||
// ---- Mana curve ----
|
||||
for (auto card : cards) {
|
||||
auto info = card.getInfo();
|
||||
const int cmc = info.getCmc().toInt();
|
||||
|
||||
// Convert once
|
||||
QStringList types = info.getMainCardType().split(' ');
|
||||
QStringList subtypes = info.getCardType().split('-').last().split(" ");
|
||||
QString colors = info.getColors();
|
||||
int power = info.getPowTough().split("/").first().toInt();
|
||||
int toughness = info.getPowTough().split("/").last().toInt();
|
||||
|
||||
// For each copy of card
|
||||
// ---------------- Mana Curve ----------------
|
||||
if (config.computeManaCurve) {
|
||||
manaCurveMap[card.getInfo().getCmc().toInt()]++;
|
||||
manaCurveMap[cmc]++;
|
||||
}
|
||||
|
||||
// ---- Mana base ----
|
||||
// per-type curve
|
||||
for (auto &t : types) {
|
||||
manaCurveByType[t][cmc]++;
|
||||
manaCurveCardsByType[t][cmc].append(info.getName());
|
||||
}
|
||||
|
||||
// Per-subtype curve
|
||||
for (auto &st : subtypes) {
|
||||
manaCurveBySubtype[st][cmc]++;
|
||||
manaCurveCardsBySubtype[st][cmc].append(info.getName());
|
||||
}
|
||||
|
||||
// per-color curve
|
||||
for (auto &c : colors) {
|
||||
manaCurveByColor[c][cmc]++;
|
||||
manaCurveCardsByColor[c][cmc].append(info.getName());
|
||||
}
|
||||
|
||||
// Power/toughness
|
||||
manaCurveByPower[QString::number(power)][cmc]++;
|
||||
manaCurveCardsByPower[QString::number(power)][cmc].append(info.getName());
|
||||
manaCurveByToughness[QString::number(toughness)][cmc]++;
|
||||
manaCurveCardsByToughness[QString::number(toughness)][cmc].append(info.getName());
|
||||
|
||||
// ========== Category Counts ===========
|
||||
for (auto &t : types) {
|
||||
typeCount[t]++;
|
||||
}
|
||||
for (auto &st : subtypes) {
|
||||
subtypeCount[st]++;
|
||||
}
|
||||
for (auto &c : colors) {
|
||||
colorCount[c]++;
|
||||
}
|
||||
manaValueCount[cmc]++;
|
||||
|
||||
// ---------------- Mana Base ----------------
|
||||
if (config.computeManaBase) {
|
||||
auto mana = determineManaProduction(card.getInfo().getText());
|
||||
for (auto it = mana.begin(); it != mana.end(); ++it)
|
||||
auto prod = determineManaProduction(info.getText());
|
||||
for (auto it = prod.begin(); it != prod.end(); ++it) {
|
||||
if (it.value() > 0) {
|
||||
productionPipCount[it.key()] += it.value();
|
||||
productionCardCount[it.key()]++;
|
||||
}
|
||||
manaBaseMap[it.key()] += it.value();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Devotion ----
|
||||
// ---------------- Devotion ----------------
|
||||
if (config.computeDevotion) {
|
||||
auto devo = countManaSymbols(card.getInfo().getManaCost());
|
||||
for (auto &d : devo)
|
||||
auto devo = countManaSymbols(info.getManaCost());
|
||||
for (auto &d : devo) {
|
||||
if (d.second > 0) {
|
||||
devotionPipCount[QString(d.first)] += d.second;
|
||||
devotionCardCount[QString(d.first)]++;
|
||||
}
|
||||
manaDevotionMap[d.first] += d.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,3 +167,57 @@ std::unordered_map<char, int> DeckListStatisticsAnalyzer::countManaSymbols(const
|
|||
|
||||
return manaCounts;
|
||||
}
|
||||
|
||||
// Hypergeometric probability: P(X=k)
|
||||
double DeckListStatisticsAnalyzer::hypergeometric(int N, int K, int n, int k)
|
||||
{
|
||||
if (k < 0 || k > n || K > N) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
auto choose = [](int n, int r) -> double {
|
||||
if (r > n)
|
||||
return 0.0;
|
||||
if (r == 0 || r == n)
|
||||
return 1.0;
|
||||
double res = 1.0;
|
||||
for (int i = 1; i <= r; ++i) {
|
||||
res *= (n - r + i);
|
||||
res /= i;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
return choose(K, k) * choose(N - K, n - k) / choose(N, n);
|
||||
}
|
||||
|
||||
void DeckListStatisticsAnalyzer::clearData()
|
||||
{
|
||||
manaBaseMap.clear();
|
||||
manaCurveMap.clear();
|
||||
manaDevotionMap.clear();
|
||||
|
||||
devotionPipCount.clear();
|
||||
devotionCardCount.clear();
|
||||
|
||||
productionPipCount.clear();
|
||||
productionCardCount.clear();
|
||||
|
||||
manaCurveByType.clear();
|
||||
manaCurveBySubtype.clear();
|
||||
manaCurveByColor.clear();
|
||||
manaCurveByPower.clear();
|
||||
manaCurveByToughness.clear();
|
||||
|
||||
manaCurveCardsByType.clear();
|
||||
manaCurveCardsBySubtype.clear();
|
||||
manaCurveCardsByColor.clear();
|
||||
manaCurveCardsByPower.clear();
|
||||
manaCurveCardsByToughness.clear();
|
||||
|
||||
typeCount.clear();
|
||||
subtypeCount.clear();
|
||||
colorCount.clear();
|
||||
rarityCount.clear();
|
||||
manaValueCount.clear();
|
||||
}
|
||||
|
|
@ -14,6 +14,9 @@ struct DeckListStatisticsAnalyzerConfig
|
|||
bool computeManaBase = true;
|
||||
bool computeManaCurve = true;
|
||||
bool computeDevotion = true;
|
||||
bool computeCategories = true;
|
||||
bool computeCurveBreakdowns = true;
|
||||
bool computeProbabilities = true;
|
||||
};
|
||||
|
||||
class DeckListStatisticsAnalyzer : public QObject
|
||||
|
|
@ -23,9 +26,9 @@ class DeckListStatisticsAnalyzer : public QObject
|
|||
public:
|
||||
explicit DeckListStatisticsAnalyzer(QObject *parent,
|
||||
DeckListModel *model,
|
||||
DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig());
|
||||
DeckListStatisticsAnalyzerConfig _config = DeckListStatisticsAnalyzerConfig());
|
||||
|
||||
void update();
|
||||
void analyze();
|
||||
|
||||
[[nodiscard]] const QHash<QString, int> &getManaBase() const
|
||||
{
|
||||
|
|
@ -40,6 +43,96 @@ public:
|
|||
return manaDevotionMap;
|
||||
}
|
||||
|
||||
const QHash<QString, int> &getDevotionPipCount() const
|
||||
{
|
||||
return devotionPipCount;
|
||||
}
|
||||
const QHash<QString, int> &getDevotionCardCount() const
|
||||
{
|
||||
return devotionCardCount;
|
||||
}
|
||||
|
||||
const QHash<QString, int> &getProductionPipCount() const
|
||||
{
|
||||
return productionPipCount;
|
||||
}
|
||||
const QHash<QString, int> &getProductionCardCount() const
|
||||
{
|
||||
return productionCardCount;
|
||||
}
|
||||
|
||||
const QHash<QString, int> &getTypeCount() const
|
||||
{
|
||||
return typeCount;
|
||||
}
|
||||
const QHash<QString, int> &getSubtypeCount() const
|
||||
{
|
||||
return subtypeCount;
|
||||
}
|
||||
const QHash<QString, int> &getColorCount() const
|
||||
{
|
||||
return colorCount;
|
||||
}
|
||||
const QHash<QString, int> &getRarityCount() const
|
||||
{
|
||||
return rarityCount;
|
||||
}
|
||||
const QHash<int, int> &getManaValueCount() const
|
||||
{
|
||||
return manaValueCount;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByType() const
|
||||
{
|
||||
return manaCurveByType;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveBySubtype() const
|
||||
{
|
||||
return manaCurveBySubtype;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByColor() const
|
||||
{
|
||||
return manaCurveByColor;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByPower() const
|
||||
{
|
||||
return manaCurveByPower;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByToughness() const
|
||||
{
|
||||
return manaCurveByToughness;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByType() const
|
||||
{
|
||||
return manaCurveCardsByType;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsBySubtype() const
|
||||
{
|
||||
return manaCurveCardsBySubtype;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByColor() const
|
||||
{
|
||||
return manaCurveCardsByColor;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByPower() const
|
||||
{
|
||||
return manaCurveCardsByPower;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByToughness() const
|
||||
{
|
||||
return manaCurveCardsByToughness;
|
||||
}
|
||||
|
||||
DeckListModel *getModel() const
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
signals:
|
||||
void statsUpdated();
|
||||
|
||||
|
|
@ -47,14 +140,42 @@ private:
|
|||
DeckListModel *model;
|
||||
DeckListStatisticsAnalyzerConfig config;
|
||||
|
||||
// Internal result containers
|
||||
QHash<QString, int> manaBaseMap;
|
||||
std::unordered_map<int, int> manaCurveMap;
|
||||
std::unordered_map<char, int> manaDevotionMap;
|
||||
|
||||
// Internal helper functions
|
||||
QHash<QString, int> devotionPipCount; // W/U/B/R/G total symbols
|
||||
QHash<QString, int> devotionCardCount; // how many cards provide devotion
|
||||
|
||||
QHash<QString, int> productionPipCount; // mana produced by cards
|
||||
QHash<QString, int> productionCardCount; // number of producers
|
||||
|
||||
QHash<QString, int> typeCount;
|
||||
QHash<QString, int> subtypeCount;
|
||||
QHash<QString, int> colorCount;
|
||||
QHash<QString, int> rarityCount;
|
||||
QHash<int, int> manaValueCount;
|
||||
|
||||
QHash<QString, QHash<int, int>> manaCurveByType;
|
||||
QHash<QString, QHash<int, int>> manaCurveBySubtype;
|
||||
QHash<QString, QHash<int, int>> manaCurveByColor;
|
||||
QHash<QString, QHash<int, int>> manaCurveByPower;
|
||||
QHash<QString, QHash<int, int>> manaCurveByToughness;
|
||||
|
||||
QHash<QString, QHash<int, QStringList>> manaCurveCardsByType;
|
||||
QHash<QString, QHash<int, QStringList>> manaCurveCardsBySubtype;
|
||||
QHash<QString, QHash<int, QStringList>> manaCurveCardsByColor;
|
||||
QHash<QString, QHash<int, QStringList>> manaCurveCardsByPower;
|
||||
QHash<QString, QHash<int, QStringList>> manaCurveCardsByToughness;
|
||||
|
||||
// Not storing card info — only numeric results.
|
||||
QHash<QString, QHash<int, QHash<int, double>>> probabilityExact;
|
||||
QHash<QString, QHash<int, QHash<int, double>>> probabilityAtLeast;
|
||||
|
||||
QHash<QString, int> determineManaProduction(const QString &);
|
||||
std::unordered_map<char, int> countManaSymbols(const QString &);
|
||||
double hypergeometric(int N, int K, int n, int k);
|
||||
void clearData();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
#include "mana_base_widget.h"
|
||||
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "../general/display/bar_widget.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QRegularExpression>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer)
|
||||
: QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
bannerWidget = new BannerWidget(this, tr("Mana Base"), Qt::Vertical, 100);
|
||||
bannerWidget->setMaximumHeight(100);
|
||||
layout->addWidget(bannerWidget);
|
||||
|
||||
barContainer = new QWidget(this);
|
||||
barLayout = new QHBoxLayout(barContainer);
|
||||
layout->addWidget(barContainer);
|
||||
|
||||
connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaBaseWidget::updateDisplay);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaBaseWidget::retranslateUi()
|
||||
{
|
||||
bannerWidget->setText(tr("Mana Base"));
|
||||
}
|
||||
|
||||
void ManaBaseWidget::updateDisplay()
|
||||
{
|
||||
// Clear the layout first
|
||||
QLayoutItem *item;
|
||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
|
||||
auto manaBaseMap = deckStatAnalyzer->getManaBase();
|
||||
|
||||
int highestEntry = 0;
|
||||
for (auto entry : manaBaseMap) {
|
||||
if (entry > highestEntry) {
|
||||
highestEntry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Define color mapping for mana types
|
||||
QHash<QString, QColor> manaColors;
|
||||
manaColors.insert("W", QColor(248, 231, 185));
|
||||
manaColors.insert("U", QColor(14, 104, 171));
|
||||
manaColors.insert("B", QColor(21, 11, 0));
|
||||
manaColors.insert("R", QColor(211, 32, 42));
|
||||
manaColors.insert("G", QColor(0, 115, 62));
|
||||
manaColors.insert("C", QColor(150, 150, 150));
|
||||
|
||||
for (auto manaColor : manaBaseMap.keys()) {
|
||||
QColor barColor = manaColors.value(manaColor, Qt::gray);
|
||||
BarWidget *barWidget = new BarWidget(QString(manaColor), manaBaseMap[manaColor], highestEntry, barColor, this);
|
||||
barLayout->addWidget(barWidget);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* @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 "deck_list_statistics_analyzer.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 QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer);
|
||||
void updateDisplay();
|
||||
|
||||
public slots:
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
DeckListStatisticsAnalyzer *deckStatAnalyzer;
|
||||
BannerWidget *bannerWidget;
|
||||
QVBoxLayout *layout;
|
||||
QWidget *barContainer;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_BASE_WIDGET_H
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
#include "mana_curve_widget.h"
|
||||
|
||||
#include "../../../main.h"
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "../general/display/bar_widget.h"
|
||||
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <unordered_map>
|
||||
|
||||
ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer)
|
||||
: QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
bannerWidget = new BannerWidget(this, tr("Mana Curve"), Qt::Vertical, 100);
|
||||
bannerWidget->setMaximumHeight(100);
|
||||
layout->addWidget(bannerWidget);
|
||||
|
||||
barContainer = new QWidget(this);
|
||||
barLayout = new QHBoxLayout(barContainer);
|
||||
layout->addWidget(barContainer);
|
||||
|
||||
connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaCurveWidget::retranslateUi()
|
||||
{
|
||||
bannerWidget->setText(tr("Mana Curve"));
|
||||
}
|
||||
|
||||
void ManaCurveWidget::updateDisplay()
|
||||
{
|
||||
// Clear the layout first
|
||||
if (barLayout != nullptr) {
|
||||
QLayoutItem *item;
|
||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
auto manaCurveMap = deckStatAnalyzer->getManaCurve();
|
||||
|
||||
int highestEntry = 0;
|
||||
for (const auto &entry : manaCurveMap) {
|
||||
if (entry.second > highestEntry) {
|
||||
highestEntry = entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert unordered_map to ordered map to ensure sorting by CMC
|
||||
std::map<int, int> sortedManaCurve(manaCurveMap.begin(), manaCurveMap.end());
|
||||
|
||||
// Add new widgets to the layout in sorted order
|
||||
for (const auto &entry : sortedManaCurve) {
|
||||
BarWidget *barWidget =
|
||||
new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(122, 122, 122), this);
|
||||
barLayout->addWidget(barWidget);
|
||||
}
|
||||
|
||||
update(); // Update the widget display
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/**
|
||||
* @file mana_curve_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef MANA_CURVE_WIDGET_H
|
||||
#define MANA_CURVE_WIDGET_H
|
||||
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
#include <unordered_map>
|
||||
|
||||
class ManaCurveWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer);
|
||||
void updateDisplay();
|
||||
|
||||
public slots:
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
DeckListStatisticsAnalyzer *deckStatAnalyzer;
|
||||
QVBoxLayout *layout;
|
||||
BannerWidget *bannerWidget;
|
||||
QWidget *barContainer;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_CURVE_WIDGET_H
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
#include "mana_devotion_widget.h"
|
||||
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "../general/display/bar_widget.h"
|
||||
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <regex>
|
||||
#include <unordered_map>
|
||||
|
||||
ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer)
|
||||
: QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
bannerWidget = new BannerWidget(this, tr("Mana Devotion"), Qt::Vertical, 100);
|
||||
bannerWidget->setMaximumHeight(100);
|
||||
layout->addWidget(bannerWidget);
|
||||
|
||||
barLayout = new QHBoxLayout();
|
||||
layout->addLayout(barLayout);
|
||||
|
||||
connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaDevotionWidget::updateDisplay);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaDevotionWidget::retranslateUi()
|
||||
{
|
||||
bannerWidget->setText(tr("Mana Devotion"));
|
||||
}
|
||||
|
||||
void ManaDevotionWidget::updateDisplay()
|
||||
{
|
||||
// Clear the layout first
|
||||
QLayoutItem *item;
|
||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
|
||||
auto manaDevotionMap = deckStatAnalyzer->getDevotion();
|
||||
|
||||
int highestEntry = 0;
|
||||
for (auto entry : manaDevotionMap) {
|
||||
if (highestEntry < entry.second) {
|
||||
highestEntry = entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Define color mapping for devotion bars
|
||||
std::unordered_map<char, QColor> manaColors = {{'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 entry : manaDevotionMap) {
|
||||
QColor barColor = manaColors.count(entry.first) ? manaColors[entry.first] : Qt::gray;
|
||||
BarWidget *barWidget = new BarWidget(QString(entry.first), entry.second, highestEntry, barColor, this);
|
||||
barLayout->addWidget(barWidget);
|
||||
}
|
||||
|
||||
update(); // Update the widget display
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/**
|
||||
* @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 "deck_list_statistics_analyzer.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
#include <utility>
|
||||
|
||||
class ManaDevotionWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer);
|
||||
void updateDisplay();
|
||||
|
||||
public slots:
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
DeckListStatisticsAnalyzer *deckStatAnalyzer;
|
||||
BannerWidget *bannerWidget;
|
||||
QVBoxLayout *layout;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_DEVOTION_WIDGET_H
|
||||
|
|
@ -0,0 +1,367 @@
|
|||
#include "resizable_panel.h"
|
||||
|
||||
#include "libcockatrice/utility/qt_utils.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPixmap>
|
||||
#include <QtGlobal>
|
||||
|
||||
ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWidget *analyticsPanel, QWidget *parent)
|
||||
: QWidget(parent), panel(analyticsPanel), typeId(_typeId)
|
||||
{
|
||||
setAcceptDrops(true);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
|
||||
// Frame for selection highlight
|
||||
frame = new QFrame(this);
|
||||
frame->setFrameShape(QFrame::Box);
|
||||
frame->setLineWidth(2);
|
||||
frame->setStyleSheet("border: none;");
|
||||
|
||||
auto *frameLayout = new QVBoxLayout(frame);
|
||||
frameLayout->setContentsMargins(0, 0, 0, 0);
|
||||
frameLayout->setSpacing(0);
|
||||
|
||||
// Add the analytics panel
|
||||
frameLayout->addWidget(analyticsPanel);
|
||||
|
||||
dropIndicator = new QFrame(frame);
|
||||
dropIndicator->setStyleSheet("background-color: #3daee9;");
|
||||
dropIndicator->setFixedHeight(3);
|
||||
dropIndicator->hide(); // hidden by default
|
||||
dropIndicator->raise(); // make sure it's above children
|
||||
|
||||
selectionOverlay = new QFrame(frame);
|
||||
selectionOverlay->setStyleSheet("background-color: rgba(61,174,233,50);"); // semi-transparent blue
|
||||
selectionOverlay->hide(); // hidden by default
|
||||
selectionOverlay->raise(); // make sure it is above children
|
||||
selectionOverlay->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
// Bottom bar with drag button and resize handle
|
||||
auto *bottomBar = new QWidget(frame);
|
||||
auto *bottomLayout = new QHBoxLayout(bottomBar);
|
||||
bottomLayout->setContentsMargins(0, 0, 0, 0);
|
||||
bottomLayout->setSpacing(0);
|
||||
|
||||
// Drag button on the left
|
||||
dragButton = new QPushButton("☰", bottomBar);
|
||||
dragButton->setFixedSize(40, 8);
|
||||
dragButton->setCursor(Qt::OpenHandCursor);
|
||||
dragButton->setStyleSheet("QPushButton { "
|
||||
"background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a4a4a, stop:1 #3a3a3a); "
|
||||
"border: none; color: #888; font-size: 10px; }"
|
||||
"QPushButton:hover { background: #5a5a5a; }");
|
||||
bottomLayout->addWidget(dragButton);
|
||||
|
||||
// Resize handle fills the rest
|
||||
resizeHandle = new QWidget(bottomBar);
|
||||
resizeHandle->setFixedHeight(8);
|
||||
resizeHandle->setCursor(Qt::SizeVerCursor);
|
||||
resizeHandle->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
|
||||
"stop:0 #3a3a3a, stop:1 #2a2a2a);");
|
||||
bottomLayout->addWidget(resizeHandle, 1);
|
||||
|
||||
frameLayout->addWidget(bottomBar);
|
||||
|
||||
mainLayout->addWidget(frame);
|
||||
|
||||
// Set size policy
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
|
||||
// Calculate initial height - use panel's size hint if available
|
||||
int panelHint = analyticsPanel->sizeHint().height();
|
||||
int panelMin = analyticsPanel->minimumSizeHint().height();
|
||||
|
||||
// Start with the larger of panel's hint and panel's minimum hint
|
||||
currentHeight = qMax(panelHint + 8, panelMin + 8);
|
||||
updateSizeConstraints();
|
||||
|
||||
// Install event filters
|
||||
dragButton->installEventFilter(this);
|
||||
resizeHandle->installEventFilter(this);
|
||||
|
||||
// Timer for auto-scroll during drag
|
||||
autoScrollTimer = new QTimer(this);
|
||||
autoScrollTimer->setInterval(50);
|
||||
connect(autoScrollTimer, &QTimer::timeout, this, &ResizablePanel::performAutoScroll);
|
||||
}
|
||||
|
||||
void ResizablePanel::setSelected(bool selected)
|
||||
{
|
||||
if (selected) {
|
||||
selectionOverlay->setGeometry(0, 0, width(), height());
|
||||
selectionOverlay->show();
|
||||
} else {
|
||||
selectionOverlay->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void ResizablePanel::setHeightFromSaved(int h)
|
||||
{
|
||||
if (h > 0) {
|
||||
currentHeight = qMax(h, getMinimumAllowedHeight());
|
||||
updateSizeConstraints();
|
||||
}
|
||||
}
|
||||
|
||||
int ResizablePanel::getCurrentHeight() const
|
||||
{
|
||||
return currentHeight;
|
||||
}
|
||||
|
||||
QSize ResizablePanel::sizeHint() const
|
||||
{
|
||||
return QSize(width(), currentHeight);
|
||||
}
|
||||
|
||||
QSize ResizablePanel::minimumSizeHint() const
|
||||
{
|
||||
return QSize(0, getMinimumAllowedHeight());
|
||||
}
|
||||
|
||||
// =====================================================================================================================
|
||||
// Event Handling
|
||||
// =====================================================================================================================
|
||||
|
||||
bool ResizablePanel::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (obj == dragButton) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
dragStartPos = mouseEvent->globalPosition().toPoint();
|
||||
#else
|
||||
dragStartPos = mouseEvent->globalPos();
|
||||
#endif
|
||||
isDraggingPanel = false;
|
||||
dragButton->setCursor(Qt::ClosedHandCursor);
|
||||
}
|
||||
return false;
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
if (mouseEvent->buttons() & Qt::LeftButton) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QPoint currentPos = mouseEvent->globalPosition().toPoint();
|
||||
#else
|
||||
QPoint currentPos = mouseEvent->globalPos();
|
||||
#endif
|
||||
int distance = (currentPos - dragStartPos).manhattanLength();
|
||||
if (distance >= 5 && !isDraggingPanel) {
|
||||
isDraggingPanel = true;
|
||||
startDrag();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||
dragButton->setCursor(Qt::OpenHandCursor);
|
||||
isDraggingPanel = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (obj == resizeHandle) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
resizeStartY = mouseEvent->globalPosition().y();
|
||||
#else
|
||||
resizeStartY = mouseEvent->globalPos().y();
|
||||
#endif
|
||||
isResizing = true;
|
||||
resizeStartHeight = currentHeight;
|
||||
resizeHandle->grabMouse();
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseMove && isResizing) {
|
||||
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
int deltaY = mouseEvent->globalPosition().y() - resizeStartY;
|
||||
#else
|
||||
int deltaY = mouseEvent->globalPos().y() - resizeStartY;
|
||||
#endif
|
||||
int newHeight = resizeStartHeight + deltaY;
|
||||
|
||||
int minAllowed = getMinimumAllowedHeight();
|
||||
newHeight = qMax(newHeight, minAllowed);
|
||||
|
||||
currentHeight = newHeight;
|
||||
updateSizeConstraints();
|
||||
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||
isResizing = false;
|
||||
resizeHandle->releaseMouse();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void ResizablePanel::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData()->hasFormat("application/x-resizablepanel")) {
|
||||
event->acceptProposedAction();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
showDropIndicator(event->position().y());
|
||||
#else
|
||||
showDropIndicator(event->pos().y());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ResizablePanel::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
if (event->mimeData()->hasFormat("application/x-resizablepanel")) {
|
||||
event->acceptProposedAction();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
showDropIndicator(event->position().y());
|
||||
lastDragPos = mapToGlobal(event->position().toPoint());
|
||||
#else
|
||||
showDropIndicator(event->pos().y());
|
||||
lastDragPos = mapToGlobal(event->pos());
|
||||
#endif
|
||||
|
||||
if (!autoScrollTimer->isActive()) {
|
||||
autoScrollTimer->start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResizablePanel::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
hideDropIndicator();
|
||||
autoScrollTimer->stop();
|
||||
}
|
||||
|
||||
void ResizablePanel::dropEvent(QDropEvent *event)
|
||||
{
|
||||
hideDropIndicator();
|
||||
autoScrollTimer->stop();
|
||||
|
||||
if (event->mimeData()->hasFormat("application/x-resizablepanel")) {
|
||||
QByteArray data = event->mimeData()->data("application/x-resizablepanel");
|
||||
quintptr ptr = *reinterpret_cast<const quintptr *>(data.constData());
|
||||
ResizablePanel *draggedPanel = reinterpret_cast<ResizablePanel *>(ptr);
|
||||
|
||||
if (draggedPanel && draggedPanel != this) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
bool insertBefore = (event->position().y() < height() / 2);
|
||||
#else
|
||||
bool insertBefore = (event->pos().y() < height() / 2);
|
||||
#endif
|
||||
emit dropRequested(draggedPanel, this, insertBefore);
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResizablePanel::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
if (selectionOverlay->isVisible()) {
|
||||
selectionOverlay->setGeometry(0, 0, width(), height());
|
||||
}
|
||||
|
||||
if (dropIndicator->isVisible()) {
|
||||
dropIndicator->setGeometry(0, dropIndicator->y(), width(), dropIndicator->height());
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================================================================
|
||||
// Private Helpers
|
||||
// =====================================================================================================================
|
||||
|
||||
int ResizablePanel::getMinimumAllowedHeight() const
|
||||
{
|
||||
QSize panelMin = panel->minimumSizeHint();
|
||||
int panelMinHeight = (panelMin.isValid() && panelMin.height() > 0) ? panelMin.height() : 100;
|
||||
return panelMinHeight + 8;
|
||||
}
|
||||
|
||||
void ResizablePanel::updateSizeConstraints()
|
||||
{
|
||||
setMinimumHeight(currentHeight);
|
||||
setMaximumHeight(currentHeight);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void ResizablePanel::startDrag()
|
||||
{
|
||||
QDrag *drag = new QDrag(this);
|
||||
QMimeData *mimeData = new QMimeData;
|
||||
|
||||
quintptr ptr = reinterpret_cast<quintptr>(this);
|
||||
QByteArray data(reinterpret_cast<const char *>(&ptr), sizeof(ptr));
|
||||
mimeData->setData("application/x-resizablepanel", data);
|
||||
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
QPixmap pixmap(width(), 40);
|
||||
pixmap.fill(QColor(58, 58, 58, 200));
|
||||
drag->setPixmap(pixmap);
|
||||
drag->setHotSpot(QPoint(width() / 2, 20));
|
||||
|
||||
emit dragStarted(this);
|
||||
|
||||
autoScrollTimer->start();
|
||||
|
||||
Qt::DropAction result = drag->exec(Qt::MoveAction);
|
||||
Q_UNUSED(result);
|
||||
|
||||
autoScrollTimer->stop();
|
||||
dragButton->setCursor(Qt::OpenHandCursor);
|
||||
isDraggingPanel = false;
|
||||
}
|
||||
|
||||
void ResizablePanel::performAutoScroll()
|
||||
{
|
||||
QScrollArea *scrollArea = QtUtils::findParentOfType<QScrollArea>(this);
|
||||
|
||||
if (!scrollArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
QScrollBar *scrollBar = scrollArea->verticalScrollBar();
|
||||
if (!scrollBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
QRect scrollRect = scrollArea->viewport()->rect();
|
||||
QPoint scrollTopLeft = scrollArea->viewport()->mapToGlobal(scrollRect.topLeft());
|
||||
QRect globalScrollRect(scrollTopLeft, scrollRect.size());
|
||||
|
||||
const int scrollMargin = 50;
|
||||
int scrollSpeed = 0;
|
||||
|
||||
if (lastDragPos.y() < globalScrollRect.top() + scrollMargin) {
|
||||
scrollSpeed = -15;
|
||||
} else if (lastDragPos.y() > globalScrollRect.bottom() - scrollMargin) {
|
||||
scrollSpeed = 15;
|
||||
}
|
||||
|
||||
if (scrollSpeed != 0) {
|
||||
int newValue = scrollBar->value() + scrollSpeed;
|
||||
newValue = qBound(scrollBar->minimum(), newValue, scrollBar->maximum());
|
||||
scrollBar->setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
void ResizablePanel::showDropIndicator(double y)
|
||||
{
|
||||
bool before = (y < height() / 2);
|
||||
dropIndicator->setGeometry(0, before ? 0 : height() - 3, width(), 3);
|
||||
dropIndicator->show();
|
||||
}
|
||||
|
||||
void ResizablePanel::hideDropIndicator()
|
||||
{
|
||||
dropIndicator->hide();
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef COCKATRICE_RESIZABLE_PANEL_H
|
||||
#define COCKATRICE_RESIZABLE_PANEL_H
|
||||
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDrag>
|
||||
#include <QFrame>
|
||||
#include <QMimeData>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QScrollBar>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class ResizablePanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ResizablePanel(const QString &typeId,
|
||||
AbstractAnalyticsPanelWidget *analyticsPanel,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
void setSelected(bool selected);
|
||||
void setHeightFromSaved(int h);
|
||||
int getCurrentHeight() const;
|
||||
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
QString getTypeId() const
|
||||
{
|
||||
return typeId;
|
||||
}
|
||||
|
||||
AbstractAnalyticsPanelWidget *panel;
|
||||
|
||||
signals:
|
||||
void dragStarted(ResizablePanel *panel);
|
||||
void dropRequested(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
int getMinimumAllowedHeight() const;
|
||||
void updateSizeConstraints();
|
||||
void startDrag();
|
||||
void performAutoScroll();
|
||||
void showDropIndicator(double y);
|
||||
void hideDropIndicator();
|
||||
|
||||
QString typeId;
|
||||
|
||||
QFrame *frame;
|
||||
QFrame *selectionOverlay;
|
||||
QFrame *dropIndicator;
|
||||
QPushButton *dragButton;
|
||||
QWidget *resizeHandle;
|
||||
|
||||
int currentHeight;
|
||||
bool isResizing = false;
|
||||
bool isDraggingPanel = false;
|
||||
double resizeStartY = 0;
|
||||
int resizeStartHeight = 0;
|
||||
|
||||
QPoint dragStartPos;
|
||||
QPoint lastDragPos;
|
||||
QTimer *autoScrollTimer;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_RESIZABLE_PANEL_H
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#include "bar_chart_background_widget.h"
|
||||
|
||||
BarChartBackgroundWidget::BarChartBackgroundWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
QSize BarChartBackgroundWidget::sizeHint() const
|
||||
{
|
||||
return QSize(100, 150);
|
||||
}
|
||||
|
||||
void BarChartBackgroundWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
constexpr int PAD = 4;
|
||||
constexpr int LABEL_H = 20;
|
||||
|
||||
int left = 46; // axis space + internal padding
|
||||
int right = width() - PAD;
|
||||
int top = PAD;
|
||||
int bottom = height() - PAD - LABEL_H;
|
||||
|
||||
int barAreaHeight = bottom - top;
|
||||
int barAreaWidth = right - left;
|
||||
|
||||
p.fillRect(QRect(left, top, barAreaWidth, barAreaHeight), QColor(250, 250, 250));
|
||||
|
||||
int ticks = 5;
|
||||
for (int i = 0; i <= ticks; i++) {
|
||||
float r = float(i) / ticks;
|
||||
int y = bottom - r * barAreaHeight;
|
||||
|
||||
p.setPen(QPen(QColor(180, 180, 180, 120), 1));
|
||||
p.drawLine(left, y, right, y);
|
||||
|
||||
p.setPen(Qt::black);
|
||||
p.drawText(left - 35, y - 6, 32, 12, Qt::AlignRight | Qt::AlignVCenter, QString::number(int(r * highest)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef COCKATRICE_BAR_CHART_BACKGROUND_WIDGET_H
|
||||
#define COCKATRICE_BAR_CHART_BACKGROUND_WIDGET_H
|
||||
|
||||
#include <QPainter>
|
||||
#include <QWidget>
|
||||
|
||||
class BarChartBackgroundWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
int highest = 0; // global maximum (shared across bars)
|
||||
int barCount = 0; // number of CMC columns
|
||||
int labelHeight = 20; // reserved for CMC numbers
|
||||
|
||||
explicit BarChartBackgroundWidget(QWidget *parent);
|
||||
public slots:
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_BAR_CHART_BACKGROUND_WIDGET_H
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
#include "bar_chart_widget.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QToolTip>
|
||||
|
||||
BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void BarChartWidget::setBars(const QVector<BarData> &newBars)
|
||||
{
|
||||
bars = newBars;
|
||||
update();
|
||||
}
|
||||
|
||||
void BarChartWidget::setHighest(int h)
|
||||
{
|
||||
highest = qMax(1, h);
|
||||
update();
|
||||
}
|
||||
|
||||
QSize BarChartWidget::sizeHint() const
|
||||
{
|
||||
return QSize(300, 200);
|
||||
}
|
||||
|
||||
QSize BarChartWidget::minimumSizeHint() const
|
||||
{
|
||||
return QSize(300, 50);
|
||||
}
|
||||
|
||||
void BarChartWidget::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
constexpr int PAD = 4;
|
||||
constexpr int LABEL_H = 20;
|
||||
|
||||
int w = width();
|
||||
int h = height();
|
||||
|
||||
int left = 46;
|
||||
int right = w - PAD;
|
||||
int top = PAD;
|
||||
int bottom = h - PAD - LABEL_H;
|
||||
|
||||
int barAreaHeight = bottom - top;
|
||||
int barAreaWidth = right - left;
|
||||
|
||||
int barCount = bars.size();
|
||||
if (barCount == 0)
|
||||
return;
|
||||
|
||||
int spacing = 6;
|
||||
int barWidth = (barAreaWidth - (barCount - 1) * spacing) / barCount;
|
||||
|
||||
// background
|
||||
p.fillRect(QRect(left, top, barAreaWidth, barAreaHeight), QColor(250, 250, 250));
|
||||
|
||||
// y-axis ticks
|
||||
int ticks = 5;
|
||||
// qInfo() << "Tick Positions ";
|
||||
for (int i = 0; i <= ticks; i++) {
|
||||
float r = float(i) / ticks;
|
||||
int tickVal = i * highest / ticks; // integer value of tick
|
||||
int y = bottom - (tickVal * barAreaHeight / highest);
|
||||
|
||||
// qInfo() << "Tick" << i << "value" << int(r * highest) << "y" << y;
|
||||
|
||||
p.setPen(QPen(QColor(180, 180, 180, 120), 1));
|
||||
p.drawLine(left, y, right, y);
|
||||
|
||||
p.setPen(Qt::black);
|
||||
p.drawText(left - 35, y - 6, 32, 12, Qt::AlignRight | Qt::AlignVCenter, QString::number(int(r * highest)));
|
||||
}
|
||||
|
||||
// draw bars
|
||||
// qInfo() << "Bar Segments";
|
||||
int drawWidth = barWidth / 4; // 1/4 of allocated width
|
||||
int xOffset = (barWidth - drawWidth) / 2; // center the narrow bar
|
||||
|
||||
for (int i = 0; i < barCount; i++) {
|
||||
const BarData &bar = bars[i];
|
||||
int x = left + i * (barWidth + spacing) + xOffset; // shift to center
|
||||
int yCurrent = bottom;
|
||||
|
||||
for (int j = 0; j < bar.segments.size(); j++) {
|
||||
const auto &seg = bar.segments[j];
|
||||
int segHeight = (seg.value * barAreaHeight / highest);
|
||||
if (segHeight < 2 && seg.value > 0)
|
||||
segHeight = 2;
|
||||
|
||||
int topY = yCurrent - segHeight;
|
||||
|
||||
QRect r(x, topY, drawWidth, segHeight); // use drawWidth instead of barWidth
|
||||
bool isTop = (j == bar.segments.size() - 1);
|
||||
|
||||
QLinearGradient g(r.topLeft(), r.bottomLeft());
|
||||
g.setColorAt(0, seg.color.lighter(120));
|
||||
g.setColorAt(1, seg.color.darker(110));
|
||||
p.setBrush(g);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
if (isTop) {
|
||||
QPainterPath path;
|
||||
int radius = 6;
|
||||
|
||||
int bx = r.x();
|
||||
int by = r.y();
|
||||
int bw = r.width();
|
||||
int bh = r.height();
|
||||
|
||||
path.moveTo(bx, by + bh);
|
||||
path.lineTo(bx, by + radius);
|
||||
path.quadTo(bx, by, bx + radius, by);
|
||||
path.lineTo(bx + bw - radius, by);
|
||||
path.quadTo(bx + bw, by, bx + bw, by + radius);
|
||||
path.lineTo(bx + bw, by + bh);
|
||||
path.lineTo(bx, by + bh);
|
||||
path.closeSubpath();
|
||||
|
||||
p.drawPath(path);
|
||||
} else {
|
||||
p.drawRect(r);
|
||||
}
|
||||
|
||||
yCurrent -= segHeight;
|
||||
}
|
||||
|
||||
// draw label below bar
|
||||
QRect labelRect(left + i * (barWidth + spacing), bottom, barWidth, LABEL_H);
|
||||
QFont f = p.font();
|
||||
f.setBold(true);
|
||||
p.setFont(f);
|
||||
p.setPen(Qt::black);
|
||||
p.drawText(labelRect, Qt::AlignCenter, bar.label);
|
||||
}
|
||||
}
|
||||
|
||||
void BarChartWidget::leaveEvent(QEvent *)
|
||||
{
|
||||
hoveredBar = -1;
|
||||
hoveredSegment = -1;
|
||||
QToolTip::hideText();
|
||||
}
|
||||
|
||||
void BarChartWidget::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
if (bars.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int PAD = 4;
|
||||
constexpr int LABEL_H = 20;
|
||||
int w = width();
|
||||
int h = height();
|
||||
int left = 46;
|
||||
int right = w - PAD;
|
||||
int top = PAD;
|
||||
int bottom = h - PAD - LABEL_H;
|
||||
int barAreaHeight = bottom - top;
|
||||
|
||||
int barCount = bars.size();
|
||||
int spacing = 6;
|
||||
int barWidth = (right - left - (barCount - 1) * spacing) / barCount;
|
||||
|
||||
// find hovered bar
|
||||
int mx = e->pos().x();
|
||||
hoveredBar = -1;
|
||||
for (int i = 0; i < barCount; i++) {
|
||||
int x0 = left + i * (barWidth + spacing);
|
||||
if (mx >= x0 && mx <= x0 + barWidth) {
|
||||
hoveredBar = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hoveredBar < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find hovered segment
|
||||
int yCurrent = bottom;
|
||||
const auto &segments = bars[hoveredBar].segments;
|
||||
hoveredSegment = -1;
|
||||
for (int i = 0; i < segments.size(); i++) {
|
||||
const auto &seg = segments[i];
|
||||
int segHeight = (seg.value * barAreaHeight / highest);
|
||||
if (segHeight < 2 && seg.value > 0)
|
||||
segHeight = 2;
|
||||
|
||||
int topY = yCurrent - segHeight;
|
||||
int bottomY = yCurrent;
|
||||
if (e->pos().y() >= topY && e->pos().y() <= bottomY) {
|
||||
hoveredSegment = i;
|
||||
break;
|
||||
}
|
||||
yCurrent -= segHeight;
|
||||
}
|
||||
|
||||
if (hoveredSegment >= 0) {
|
||||
const auto &s = segments[hoveredSegment];
|
||||
QString text = QString("%1: %2 cards\n\n%3").arg(s.category).arg(s.value).arg(s.cards.join("\n"));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QToolTip::showText(e->globalPosition().toPoint(), text, this);
|
||||
#else
|
||||
QToolTip::showText(e->globalPos(), text, this);
|
||||
#endif
|
||||
} else {
|
||||
QToolTip::hideText();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef COCKATRICE_BAR_CHART_WIDGET_H
|
||||
#define COCKATRICE_BAR_CHART_WIDGET_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
struct BarSegment
|
||||
{
|
||||
QString category;
|
||||
int value;
|
||||
QStringList cards;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
struct BarData
|
||||
{
|
||||
QString label;
|
||||
QVector<BarSegment> segments;
|
||||
};
|
||||
|
||||
class BarChartWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BarChartWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setBars(const QVector<BarData> &bars);
|
||||
void setHighest(int h); // global max for scaling
|
||||
int barCount() const
|
||||
{
|
||||
return bars.size();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
QVector<BarData> bars;
|
||||
int highest = 1; // global maximum value
|
||||
|
||||
int hoveredBar = -1;
|
||||
int hoveredSegment = -1;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_BAR_CHART_WIDGET_H
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
#include "color_bar.h"
|
||||
|
||||
#include <QLinearGradient>
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
#include "segmented_bar_widget.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QToolTip>
|
||||
|
||||
SegmentedBarWidget::SegmentedBarWidget(QString label, QVector<Segment> segments, int total, QWidget *parent)
|
||||
: QWidget(parent), label(std::move(label)), segments(std::move(segments)), total(total)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setMinimumWidth(36);
|
||||
setMaximumWidth(50);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
QSize SegmentedBarWidget::sizeHint() const
|
||||
{
|
||||
return QSize(50, 150);
|
||||
}
|
||||
|
||||
void SegmentedBarWidget::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
constexpr int PAD = 4;
|
||||
constexpr int LABEL_H = 20;
|
||||
|
||||
int w = width();
|
||||
int h = height();
|
||||
|
||||
int barX = PAD;
|
||||
int barWidth = w - PAD * 2;
|
||||
|
||||
int barTop = PAD;
|
||||
int barBottom = h - PAD - LABEL_H;
|
||||
int barHeight = barBottom - barTop;
|
||||
|
||||
int yCurrent = barBottom;
|
||||
|
||||
// draw stacked segments
|
||||
for (int i = 0; i < segments.size(); i++) {
|
||||
const auto &seg = segments[i];
|
||||
|
||||
int segHeight = total > 0 ? (seg.value * barHeight / total) : 0;
|
||||
if (segHeight < 2)
|
||||
segHeight = 2;
|
||||
|
||||
QRect r(barX, yCurrent - segHeight, barWidth, segHeight);
|
||||
bool isTop = (i == segments.size() - 1);
|
||||
|
||||
QLinearGradient g(r.topLeft(), r.bottomLeft());
|
||||
g.setColorAt(0, seg.color.lighter(120));
|
||||
g.setColorAt(1, seg.color.darker(110));
|
||||
p.setBrush(g);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
if (isTop) {
|
||||
QPainterPath path;
|
||||
int radius = 6;
|
||||
|
||||
int x = r.x();
|
||||
int y = r.y();
|
||||
int w = r.width();
|
||||
int h = r.height();
|
||||
|
||||
path.moveTo(x, y + h);
|
||||
path.lineTo(x, y + radius);
|
||||
path.quadTo(x, y, x + radius, y);
|
||||
path.lineTo(x + w - radius, y);
|
||||
path.quadTo(x + w, y, x + w, y + radius);
|
||||
path.lineTo(x + w, y + h);
|
||||
path.lineTo(x, y + h);
|
||||
path.closeSubpath();
|
||||
|
||||
p.drawPath(path);
|
||||
} else {
|
||||
p.drawRect(r);
|
||||
}
|
||||
|
||||
yCurrent -= segHeight;
|
||||
}
|
||||
|
||||
// draw label
|
||||
QRect labelRect(0, h - LABEL_H, w, LABEL_H);
|
||||
QFont f = p.font();
|
||||
f.setBold(true);
|
||||
p.setFont(f);
|
||||
p.setPen(Qt::black);
|
||||
p.drawText(labelRect, Qt::AlignCenter, label);
|
||||
}
|
||||
|
||||
int SegmentedBarWidget::segmentAt(int y) const
|
||||
{
|
||||
int padding = 4;
|
||||
int labelHeight = 20;
|
||||
int barHeight = height() - padding * 2 - labelHeight;
|
||||
int barTop = padding;
|
||||
int barBottom = barTop + barHeight;
|
||||
|
||||
int currentTop = barBottom;
|
||||
|
||||
for (int i = 0; i < segments.size(); i++) {
|
||||
int segHeight = total > 0 ? (segments[i].value * barHeight / total) : 0;
|
||||
if (segHeight < 1) {
|
||||
segHeight = 1;
|
||||
}
|
||||
|
||||
int top = currentTop - segHeight;
|
||||
int bottom = currentTop;
|
||||
|
||||
if (y >= top && y <= bottom)
|
||||
return i;
|
||||
|
||||
currentTop -= segHeight;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void SegmentedBarWidget::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
if (!hovered) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = segmentAt(e->pos().y());
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Segment &s = segments[idx];
|
||||
QString text = QString("%1: %2 cards\n%3").arg(s.category).arg(s.value).arg(s.cards.join(", "));
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QToolTip::showText(e->globalPosition().toPoint(), text, this);
|
||||
#else
|
||||
QToolTip::showText(e->globalPos(), text, this);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef COCKATRICE_SEGMENTED_BAR_WIDGET_H
|
||||
#define COCKATRICE_SEGMENTED_BAR_WIDGET_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
class SegmentedBarWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Segment
|
||||
{
|
||||
QString category;
|
||||
int value = 0;
|
||||
QStringList cards;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
QString label;
|
||||
QVector<Segment> segments;
|
||||
float total = 1.0;
|
||||
|
||||
explicit SegmentedBarWidget(QString label, QVector<Segment> segments, int total, QWidget *parent = nullptr);
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
||||
int segmentAt(int y) const;
|
||||
|
||||
private:
|
||||
bool hovered = true;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_SEGMENTED_BAR_WIDGET_H
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
#include "color_pie.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QToolTip>
|
||||
#include <QtMath>
|
||||
|
||||
ColorPie::ColorPie(const QMap<QString, int> &_colors, QWidget *parent) : QWidget(parent), colors(_colors)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void ColorPie::setColors(const QMap<QString, int> &_colors)
|
||||
{
|
||||
colors = _colors;
|
||||
update();
|
||||
}
|
||||
|
||||
QSize ColorPie::minimumSizeHint() const
|
||||
{
|
||||
return QSize(200, 200);
|
||||
}
|
||||
|
||||
void ColorPie::paintEvent(QPaintEvent *)
|
||||
{
|
||||
if (colors.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
for (int v : colors.values()) {
|
||||
total += v;
|
||||
}
|
||||
|
||||
if (total == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
int w = width();
|
||||
int h = height();
|
||||
int size = qMin(w, h) - 40; // leave space for labels
|
||||
QRectF rect((w - size) / 2.0, (h - size) / 2.0, size, size);
|
||||
|
||||
// Draw border
|
||||
p.setPen(QPen(Qt::black, 1));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(rect);
|
||||
|
||||
// Sorted keys for predictable order
|
||||
QList<QString> sortedKeys = colors.keys();
|
||||
std::sort(sortedKeys.begin(), sortedKeys.end());
|
||||
|
||||
double startAngle = 0.0;
|
||||
|
||||
for (const QString &key : sortedKeys) {
|
||||
int value = colors[key];
|
||||
double ratio = double(value) / total;
|
||||
|
||||
if (ratio <= minRatioThreshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double spanAngle = ratio * 360.0;
|
||||
|
||||
QColor base = colorFromName(key);
|
||||
|
||||
// Gradient
|
||||
QRadialGradient grad(rect.center(), size / 2);
|
||||
grad.setColorAt(0, base.lighter(130));
|
||||
grad.setColorAt(1, base.darker(130));
|
||||
p.setBrush(grad);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
// Draw slice
|
||||
p.drawPie(rect, int(startAngle * 16), int(spanAngle * 16));
|
||||
|
||||
// Draw percent label
|
||||
double midAngle = startAngle + spanAngle / 2;
|
||||
double rad = qDegreesToRadians(midAngle);
|
||||
double labelRadius = size / 2 + 15; // slightly outside the pie
|
||||
QPointF center = rect.center();
|
||||
QPointF labelPos(center.x() + labelRadius * qCos(rad), center.y() - labelRadius * qSin(rad));
|
||||
|
||||
QString label = QString("%1%").arg(int(ratio * 100 + 0.5));
|
||||
|
||||
QFontMetrics fm(p.font());
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
||||
int labelWidth = fm.horizontalAdvance(label);
|
||||
#else
|
||||
int labelWidth = fm.width(label);
|
||||
#endif
|
||||
QRectF textRect(labelPos.x() - labelWidth / 2.0, labelPos.y() - fm.height() / 2.0, labelWidth, fm.height());
|
||||
|
||||
p.setPen(Qt::black);
|
||||
p.drawText(textRect, Qt::AlignCenter, label);
|
||||
|
||||
startAngle += spanAngle;
|
||||
}
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void ColorPie::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
isHovered = true;
|
||||
}
|
||||
#else
|
||||
void ColorPie::enterEvent(QEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
isHovered = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ColorPie::leaveEvent(QEvent *)
|
||||
{
|
||||
isHovered = false;
|
||||
}
|
||||
|
||||
void ColorPie::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!isHovered || colors.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QPoint p = event->position().toPoint();
|
||||
QPoint gp = event->globalPosition().toPoint();
|
||||
#else
|
||||
QPoint p = event->pos();
|
||||
QPoint gp = event->globalPos();
|
||||
#endif
|
||||
|
||||
QString text = tooltipForPoint(p);
|
||||
if (!text.isEmpty()) {
|
||||
QToolTip::showText(gp, text, this);
|
||||
}
|
||||
}
|
||||
|
||||
QString ColorPie::tooltipForPoint(const QPoint &pt) const
|
||||
{
|
||||
if (colors.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
for (int v : colors.values())
|
||||
total += v;
|
||||
if (total == 0)
|
||||
return {};
|
||||
|
||||
int w = width();
|
||||
int h = height();
|
||||
int size = qMin(w, h) - 40;
|
||||
QPointF center(w / 2.0, h / 2.0);
|
||||
|
||||
QPointF v = pt - center;
|
||||
double distance = std::sqrt(v.x() * v.x() + v.y() * v.y());
|
||||
if (distance > size / 2.0)
|
||||
return {}; // outside pie
|
||||
|
||||
double angle = std::atan2(-v.y(), v.x()) * 180.0 / M_PI;
|
||||
if (angle < 0) {
|
||||
angle += 360.0;
|
||||
}
|
||||
|
||||
double acc = 0.0;
|
||||
|
||||
QList<QString> keys = colors.keys();
|
||||
std::sort(keys.begin(), keys.end());
|
||||
|
||||
for (const QString &key : keys) {
|
||||
double span = (double(colors[key]) / total) * 360.0;
|
||||
|
||||
if (angle >= acc && angle < acc + span) {
|
||||
double percent = (100.0 * colors[key]) / total;
|
||||
return QString("%1: %2 cards (%3%)").arg(key).arg(colors[key]).arg(QString::number(percent, 'f', 1));
|
||||
}
|
||||
acc += span;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QColor ColorPie::colorFromName(const QString &name) const
|
||||
{
|
||||
static QMap<QString, QColor> map = {
|
||||
{"R", QColor(220, 30, 30)}, {"G", QColor(40, 170, 40)}, {"U", QColor(40, 90, 200)},
|
||||
{"W", QColor(235, 235, 230)}, {"B", QColor(30, 30, 30)},
|
||||
};
|
||||
|
||||
if (map.contains(name)) {
|
||||
return map[name];
|
||||
}
|
||||
|
||||
QColor c(name);
|
||||
if (!c.isValid()) {
|
||||
c = Qt::gray;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef COCKATRICE_COLOR_PIE_H
|
||||
#define COCKATRICE_COLOR_PIE_H
|
||||
|
||||
#ifndef COLOR_PIE_H
|
||||
#define COLOR_PIE_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
class ColorPie : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ColorPie(const QMap<QString, int> &_colors = {}, QWidget *parent = nullptr);
|
||||
|
||||
void setColors(const QMap<QString, int> &_colors);
|
||||
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
void leaveEvent(QEvent *) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
QMap<QString, int> colors;
|
||||
bool isHovered = false;
|
||||
const double minRatioThreshold = 0.01; // skip tiny slices
|
||||
|
||||
QColor colorFromName(const QString &name) const;
|
||||
QString tooltipForPoint(const QPoint &pt) const;
|
||||
};
|
||||
|
||||
#endif // COLOR_PIE_H
|
||||
|
||||
#endif // COCKATRICE_COLOR_PIE_H
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
#include "../../../../../card_picture_loader/card_picture_loader.h"
|
||||
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../../../../general/display/background_plate_widget.h"
|
||||
#include "../../../../general/display/color_bar.h"
|
||||
#include "../../../../general/display/charts/bars/color_bar.h"
|
||||
#include "archidekt_deck_preview_image_display_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#ifndef EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H
|
||||
#define EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../../general/display/percent_bar_widget.h"
|
||||
#include "../../../../../general/display/charts/bars/percent_bar_widget.h"
|
||||
#include "../../api_response/cards/edhrec_api_response_card_details.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#ifndef EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H
|
||||
#define EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../../general/display/percent_bar_widget.h"
|
||||
#include "../../../../../general/display/charts/bars/percent_bar_widget.h"
|
||||
#include "../../api_response/cards/edhrec_api_response_card_details.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ void TabDeckEditorVisual::onDeckChanged()
|
|||
{
|
||||
AbstractTabDeckEditor::onDeckModified();
|
||||
tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel();
|
||||
tabContainer->deckAnalytics->refreshDisplays();
|
||||
tabContainer->deckAnalytics->updateDisplays();
|
||||
tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,10 +45,13 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent,
|
|||
connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this,
|
||||
&TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay);
|
||||
|
||||
deckAnalytics = new DeckAnalyticsWidget(this, deckModel);
|
||||
statsAnalyzer = new DeckListStatisticsAnalyzer(this, deckModel);
|
||||
statsAnalyzer->analyze();
|
||||
|
||||
deckAnalytics = new DeckAnalyticsWidget(this, statsAnalyzer);
|
||||
deckAnalytics->setObjectName("deckAnalytics");
|
||||
|
||||
sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel);
|
||||
sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel, statsAnalyzer);
|
||||
|
||||
this->addNewTab(visualDeckView, tr("Visual Deck View"));
|
||||
this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display"));
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ public:
|
|||
/// Get the total number of tabs.
|
||||
[[nodiscard]] int getTabCount() const;
|
||||
|
||||
VisualDeckEditorWidget *visualDeckView; ///< Visual deck editor widget.
|
||||
VisualDeckEditorWidget *visualDeckView; ///< Visual deck editor widget.
|
||||
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||
DeckAnalyticsWidget *deckAnalytics; ///< Deck analytics widget.
|
||||
VisualDatabaseDisplayWidget *visualDatabaseDisplay; ///< Database display widget.
|
||||
PrintingSelector *printingSelector; ///< Printing selector widget.
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@
|
|||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "../cards/card_info_picture_widget.h"
|
||||
#include "../deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h"
|
||||
#include "../deck_analytics/deck_list_statistics_analyzer.h"
|
||||
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <random>
|
||||
|
||||
VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent,
|
||||
DeckListModel *_deckListModel,
|
||||
DeckListStatisticsAnalyzer *_statsAnalyzer)
|
||||
: QWidget(parent), deckListModel(_deckListModel), statsAnalyzer(_statsAnalyzer)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
|
@ -35,6 +39,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare
|
|||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||
layout->addWidget(flowWidget);
|
||||
|
||||
drawProbabilityWidget = new DrawProbabilityWidget(this, statsAnalyzer);
|
||||
layout->addWidget(drawProbabilityWidget);
|
||||
|
||||
cardSizeWidget = new CardSizeWidget(this, flowWidget);
|
||||
layout->addWidget(cardSizeWidget);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H
|
||||
|
||||
#include "../cards/card_size_widget.h"
|
||||
#include "../deck_analytics/deck_list_statistics_analyzer.h"
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
|
@ -15,11 +16,14 @@
|
|||
#include <QWidget>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
|
||||
class DrawProbabilityWidget;
|
||||
class VisualDeckEditorSampleHandWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
VisualDeckEditorSampleHandWidget(QWidget *parent,
|
||||
DeckListModel *deckListModel,
|
||||
DeckListStatisticsAnalyzer *statsAnalyzer);
|
||||
QList<ExactCard> getRandomCards(int amountToGet);
|
||||
|
||||
public slots:
|
||||
|
|
@ -29,12 +33,14 @@ public slots:
|
|||
|
||||
private:
|
||||
DeckListModel *deckListModel;
|
||||
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||
QVBoxLayout *layout;
|
||||
QWidget *resetAndHandSizeContainerWidget;
|
||||
QHBoxLayout *resetAndHandSizeLayout;
|
||||
QPushButton *resetButton;
|
||||
QSpinBox *handSizeSpinBox;
|
||||
FlowWidget *flowWidget;
|
||||
DrawProbabilityWidget *drawProbabilityWidget;
|
||||
CardSizeWidget *cardSizeWidget;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,43 @@ inline color convertQColorToColor(const QColor &c)
|
|||
result.set_b(c.blue());
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace GameSpecificColors
|
||||
{
|
||||
namespace MTG
|
||||
{
|
||||
inline QColor colorHelper(const QString &name)
|
||||
{
|
||||
static const QMap<QString, QColor> colorMap = {
|
||||
{"W", QColor(245, 245, 220)},
|
||||
{"U", QColor(80, 140, 255)},
|
||||
{"B", QColor(60, 60, 60)},
|
||||
{"R", QColor(220, 60, 50)},
|
||||
{"G", QColor(70, 160, 70)},
|
||||
{"Creature", QColor(70, 130, 180)},
|
||||
{"Instant", QColor(138, 43, 226)},
|
||||
{"Sorcery", QColor(199, 21, 133)},
|
||||
{"Enchantment", QColor(218, 165, 32)},
|
||||
{"Artifact", QColor(169, 169, 169)},
|
||||
{"Planeswalker", QColor(210, 105, 30)},
|
||||
{"Land", QColor(110, 80, 50)},
|
||||
};
|
||||
|
||||
if (colorMap.contains(name))
|
||||
return colorMap[name];
|
||||
|
||||
if (name.length() == 1 && colorMap.contains(name.toUpper()))
|
||||
return colorMap[name.toUpper()];
|
||||
|
||||
uint h = qHash(name);
|
||||
int r = 100 + (h % 120);
|
||||
int g = 100 + ((h >> 8) % 120);
|
||||
int b = 100 + ((h >> 16) % 120);
|
||||
|
||||
return QColor(r, g, b);
|
||||
}
|
||||
} // namespace MTG
|
||||
} // namespace GameSpecificColors
|
||||
#endif
|
||||
|
||||
inline color makeColor(int r, int g, int b)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,20 @@ template <typename T> T *findParentOfType(const QObject *obj)
|
|||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline void clearLayoutRec(QLayout *l)
|
||||
{
|
||||
if (!l)
|
||||
return;
|
||||
QLayoutItem *it;
|
||||
while ((it = l->takeAt(0)) != nullptr) {
|
||||
if (QWidget *w = it->widget())
|
||||
w->deleteLater();
|
||||
if (QLayout *sub = it->layout())
|
||||
clearLayoutRec(sub);
|
||||
delete it;
|
||||
}
|
||||
}
|
||||
} // namespace QtUtils
|
||||
|
||||
#endif // COCKATRICE_QT_UTILS_H
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user