mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-23 23:23:55 -07:00
[VDE] Deck Analytics Widgets overhaul (#6463)
* [VDE] Deck Analytics Widgets overhaul Took 2 minutes Took 3 minutes Took 3 minutes * Qt5 version guards. Took 33 minutes Took 3 seconds * Include QtMath Took 3 minutes Took 8 seconds * Use getCards() Took 4 minutes * Non pointer stuff Took 52 seconds * Add a newline to the tooltip Took 2 minutes Took 27 seconds * Fix build failure on macOS 15 * Rename some things. Took 17 minutes Took 11 seconds Took 18 seconds * Address overloads, fix default configuration. Took 1 hour 9 minutes Took 8 seconds * Fix mana curve default config. Took 4 minutes * Namespace to Qt libs Took 5 minutes * Selection overlay is transparent for mouse events. Took 2 minutes * Brace initialize. Took 8 minutes * Debian 11. Took 5 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: RickyRister <ricky.rister.wang@gmail.com>
This commit is contained in:
parent
36d8280765
commit
df9a8b2272
81 changed files with 4372 additions and 394 deletions
|
|
@ -144,11 +144,31 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/cards/card_size_widget.cpp
|
src/interface/widgets/cards/card_size_widget.cpp
|
||||||
src/interface/widgets/cards/deck_card_zone_display_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/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_analytics_widget.cpp
|
||||||
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.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/resizable_panel.cpp
|
||||||
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
|
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
|
||||||
src/interface/widgets/deck_analytics/mana_devotion_widget.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_list_history_manager_widget.cpp
|
||||||
src/interface/widgets/deck_editor/deck_editor_card_info_dock_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
|
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/background_sources.cpp
|
||||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||||
src/interface/widgets/general/display/banner_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_label.cpp
|
||||||
src/interface/widgets/general/display/dynamic_font_size_push_button.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/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/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_styled_button.cpp
|
||||||
src/interface/widgets/general/home_widget.cpp
|
src/interface/widgets/general/home_widget.cpp
|
||||||
src/interface/widgets/general/layout_containers/flow_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"
|
#include "deck_analytics_widget.h"
|
||||||
|
|
||||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel)
|
#include "abstract_analytics_panel_widget.h"
|
||||||
: QWidget(parent), deckListModel(_deckListModel)
|
#include "add_analytics_panel_dialog.h"
|
||||||
{
|
#include "analytics_panel_widget_factory.h"
|
||||||
mainLayout = new QVBoxLayout();
|
#include "analyzer_modules/mana_base/mana_base_config.h"
|
||||||
setLayout(mainLayout);
|
#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 = new QScrollArea(this);
|
||||||
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
||||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
||||||
scrollArea->setWidgetResizable(true);
|
scrollArea->setWidgetResizable(true);
|
||||||
mainLayout->addWidget(scrollArea);
|
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||||
|
|
||||||
container = new QWidget(scrollArea);
|
panelContainer = new QWidget(scrollArea);
|
||||||
containerLayout = new QVBoxLayout(container);
|
panelLayout = new QVBoxLayout(panelContainer);
|
||||||
container->setLayout(containerLayout);
|
panelLayout->setSpacing(8);
|
||||||
scrollArea->setWidget(container);
|
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);
|
loadLayout();
|
||||||
containerLayout->addWidget(manaCurveWidget);
|
|
||||||
|
|
||||||
manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer);
|
retranslateUi();
|
||||||
containerLayout->addWidget(manaDevotionWidget);
|
|
||||||
|
|
||||||
manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer);
|
|
||||||
containerLayout->addWidget(manaBaseWidget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* @file deck_analytics_widget.h
|
||||||
* @ingroup DeckEditorAnalyticsWidgets
|
* @ingroup DeckEditorAnalyticsWidgets
|
||||||
* @brief TODO: Document this.
|
* @brief Main analytics widget container with resizable panels for deck statistics.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef DECK_ANALYTICS_WIDGET_H
|
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||||
#define DECK_ANALYTICS_WIDGET_H
|
#define DECK_ANALYTICS_WIDGET_H
|
||||||
|
|
||||||
#include "mana_base_widget.h"
|
#include "abstract_analytics_panel_widget.h"
|
||||||
#include "mana_curve_widget.h"
|
#include "deck_list_statistics_analyzer.h"
|
||||||
#include "mana_devotion_widget.h"
|
#include "resizable_panel.h"
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QJsonObject>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QVector>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
|
||||||
|
class LayoutInspector;
|
||||||
|
|
||||||
class DeckAnalyticsWidget : public QWidget
|
class DeckAnalyticsWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateDisplays();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel);
|
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||||
void setDeckList(const DeckList &_deckListModel);
|
void retranslateUi();
|
||||||
std::map<int, int> analyzeManaCurve();
|
|
||||||
void refreshDisplays();
|
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:
|
private:
|
||||||
DeckListModel *deckListModel;
|
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||||
DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer;
|
|
||||||
QVBoxLayout *mainLayout;
|
|
||||||
|
|
||||||
QWidget *container;
|
QVBoxLayout *layout;
|
||||||
QVBoxLayout *containerLayout;
|
QWidget *controlContainer;
|
||||||
|
QHBoxLayout *controlLayout;
|
||||||
|
|
||||||
|
QPushButton *addButton;
|
||||||
|
QPushButton *removeButton;
|
||||||
|
QPushButton *saveButton;
|
||||||
|
QPushButton *loadButton;
|
||||||
|
|
||||||
QScrollArea *scrollArea;
|
QScrollArea *scrollArea;
|
||||||
|
QWidget *panelContainer;
|
||||||
|
QVBoxLayout *panelLayout;
|
||||||
|
|
||||||
ManaCurveWidget *manaCurveWidget;
|
QVector<ResizablePanel *> panelWrappers;
|
||||||
ManaDevotionWidget *manaDevotionWidget;
|
ResizablePanel *selectedWrapper = nullptr;
|
||||||
ManaBaseWidget *manaBaseWidget;
|
|
||||||
|
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||||
|
LayoutInspector *insp = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DECK_ANALYTICS_WIDGET_H
|
#endif // DECK_ANALYTICS_WIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -9,38 +9,93 @@
|
||||||
|
|
||||||
DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
|
DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
|
||||||
DeckListModel *_model,
|
DeckListModel *_model,
|
||||||
DeckListStatisticsAnalyzerConfig cfg)
|
DeckListStatisticsAnalyzerConfig _config)
|
||||||
: QObject(parent), model(_model), config(cfg)
|
: 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();
|
clearData();
|
||||||
manaCurveMap.clear();
|
|
||||||
manaDevotionMap.clear();
|
|
||||||
|
|
||||||
QList<ExactCard> cards = model->getCards();
|
QList<ExactCard> cards = model->getCards();
|
||||||
|
|
||||||
for (const ExactCard &card : cards) {
|
for (auto card : cards) {
|
||||||
// ---- Mana curve ----
|
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) {
|
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) {
|
if (config.computeManaBase) {
|
||||||
auto mana = determineManaProduction(card.getInfo().getText());
|
auto prod = determineManaProduction(info.getText());
|
||||||
for (auto it = mana.begin(); it != mana.end(); ++it)
|
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();
|
manaBaseMap[it.key()] += it.value();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Devotion ----
|
// ---------------- Devotion ----------------
|
||||||
if (config.computeDevotion) {
|
if (config.computeDevotion) {
|
||||||
auto devo = countManaSymbols(card.getInfo().getManaCost());
|
auto devo = countManaSymbols(info.getManaCost());
|
||||||
for (auto &d : devo)
|
for (auto &d : devo) {
|
||||||
|
if (d.second > 0) {
|
||||||
|
devotionPipCount[QString(d.first)] += d.second;
|
||||||
|
devotionCardCount[QString(d.first)]++;
|
||||||
|
}
|
||||||
manaDevotionMap[d.first] += d.second;
|
manaDevotionMap[d.first] += d.second;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,3 +167,57 @@ std::unordered_map<char, int> DeckListStatisticsAnalyzer::countManaSymbols(const
|
||||||
|
|
||||||
return manaCounts;
|
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 computeManaBase = true;
|
||||||
bool computeManaCurve = true;
|
bool computeManaCurve = true;
|
||||||
bool computeDevotion = true;
|
bool computeDevotion = true;
|
||||||
|
bool computeCategories = true;
|
||||||
|
bool computeCurveBreakdowns = true;
|
||||||
|
bool computeProbabilities = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DeckListStatisticsAnalyzer : public QObject
|
class DeckListStatisticsAnalyzer : public QObject
|
||||||
|
|
@ -23,9 +26,9 @@ class DeckListStatisticsAnalyzer : public QObject
|
||||||
public:
|
public:
|
||||||
explicit DeckListStatisticsAnalyzer(QObject *parent,
|
explicit DeckListStatisticsAnalyzer(QObject *parent,
|
||||||
DeckListModel *model,
|
DeckListModel *model,
|
||||||
DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig());
|
DeckListStatisticsAnalyzerConfig _config = DeckListStatisticsAnalyzerConfig());
|
||||||
|
|
||||||
void update();
|
void analyze();
|
||||||
|
|
||||||
[[nodiscard]] const QHash<QString, int> &getManaBase() const
|
[[nodiscard]] const QHash<QString, int> &getManaBase() const
|
||||||
{
|
{
|
||||||
|
|
@ -40,6 +43,96 @@ public:
|
||||||
return manaDevotionMap;
|
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:
|
signals:
|
||||||
void statsUpdated();
|
void statsUpdated();
|
||||||
|
|
||||||
|
|
@ -47,14 +140,42 @@ private:
|
||||||
DeckListModel *model;
|
DeckListModel *model;
|
||||||
DeckListStatisticsAnalyzerConfig config;
|
DeckListStatisticsAnalyzerConfig config;
|
||||||
|
|
||||||
// Internal result containers
|
|
||||||
QHash<QString, int> manaBaseMap;
|
QHash<QString, int> manaBaseMap;
|
||||||
std::unordered_map<int, int> manaCurveMap;
|
std::unordered_map<int, int> manaCurveMap;
|
||||||
std::unordered_map<char, int> manaDevotionMap;
|
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 &);
|
QHash<QString, int> determineManaProduction(const QString &);
|
||||||
std::unordered_map<char, int> countManaSymbols(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
|
#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 "color_bar.h"
|
||||||
|
|
||||||
#include <QLinearGradient>
|
#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 "../../../../../card_picture_loader/card_picture_loader.h"
|
||||||
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
||||||
#include "../../../../general/display/background_plate_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 "archidekt_deck_preview_image_display_widget.h"
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#ifndef EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H
|
#ifndef EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H
|
||||||
#define 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 "../../api_response/cards/edhrec_api_response_card_details.h"
|
||||||
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#ifndef EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H
|
#ifndef EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H
|
||||||
#define 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 "../../api_response/cards/edhrec_api_response_card_details.h"
|
||||||
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ void TabDeckEditorVisual::onDeckChanged()
|
||||||
{
|
{
|
||||||
AbstractTabDeckEditor::onDeckModified();
|
AbstractTabDeckEditor::onDeckModified();
|
||||||
tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel();
|
tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel();
|
||||||
tabContainer->deckAnalytics->refreshDisplays();
|
tabContainer->deckAnalytics->updateDisplays();
|
||||||
tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel());
|
tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,13 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent,
|
||||||
connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this,
|
connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this,
|
||||||
&TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay);
|
&TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay);
|
||||||
|
|
||||||
deckAnalytics = new DeckAnalyticsWidget(this, deckModel);
|
statsAnalyzer = new DeckListStatisticsAnalyzer(this, deckModel);
|
||||||
|
statsAnalyzer->analyze();
|
||||||
|
|
||||||
|
deckAnalytics = new DeckAnalyticsWidget(this, statsAnalyzer);
|
||||||
deckAnalytics->setObjectName("deckAnalytics");
|
deckAnalytics->setObjectName("deckAnalytics");
|
||||||
|
|
||||||
sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel);
|
sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel, statsAnalyzer);
|
||||||
|
|
||||||
this->addNewTab(visualDeckView, tr("Visual Deck View"));
|
this->addNewTab(visualDeckView, tr("Visual Deck View"));
|
||||||
this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display"));
|
this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display"));
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,8 @@ public:
|
||||||
/// Get the total number of tabs.
|
/// Get the total number of tabs.
|
||||||
[[nodiscard]] int getTabCount() const;
|
[[nodiscard]] int getTabCount() const;
|
||||||
|
|
||||||
VisualDeckEditorWidget *visualDeckView; ///< Visual deck editor widget.
|
VisualDeckEditorWidget *visualDeckView; ///< Visual deck editor widget.
|
||||||
|
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||||
DeckAnalyticsWidget *deckAnalytics; ///< Deck analytics widget.
|
DeckAnalyticsWidget *deckAnalytics; ///< Deck analytics widget.
|
||||||
VisualDatabaseDisplayWidget *visualDatabaseDisplay; ///< Database display widget.
|
VisualDatabaseDisplayWidget *visualDatabaseDisplay; ///< Database display widget.
|
||||||
PrintingSelector *printingSelector; ///< Printing selector widget.
|
PrintingSelector *printingSelector; ///< Printing selector widget.
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@
|
||||||
#include "../../../client/settings/cache_settings.h"
|
#include "../../../client/settings/cache_settings.h"
|
||||||
#include "../../deck_loader/deck_loader.h"
|
#include "../../deck_loader/deck_loader.h"
|
||||||
#include "../cards/card_info_picture_widget.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 <libcockatrice/card/database/card_database_manager.h>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *_deckListModel)
|
VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent,
|
||||||
: QWidget(parent), deckListModel(_deckListModel)
|
DeckListModel *_deckListModel,
|
||||||
|
DeckListStatisticsAnalyzer *_statsAnalyzer)
|
||||||
|
: QWidget(parent), deckListModel(_deckListModel), statsAnalyzer(_statsAnalyzer)
|
||||||
{
|
{
|
||||||
layout = new QVBoxLayout(this);
|
layout = new QVBoxLayout(this);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
@ -35,6 +39,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare
|
||||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||||
layout->addWidget(flowWidget);
|
layout->addWidget(flowWidget);
|
||||||
|
|
||||||
|
drawProbabilityWidget = new DrawProbabilityWidget(this, statsAnalyzer);
|
||||||
|
layout->addWidget(drawProbabilityWidget);
|
||||||
|
|
||||||
cardSizeWidget = new CardSizeWidget(this, flowWidget);
|
cardSizeWidget = new CardSizeWidget(this, flowWidget);
|
||||||
layout->addWidget(cardSizeWidget);
|
layout->addWidget(cardSizeWidget);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H
|
#define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H
|
||||||
|
|
||||||
#include "../cards/card_size_widget.h"
|
#include "../cards/card_size_widget.h"
|
||||||
|
#include "../deck_analytics/deck_list_statistics_analyzer.h"
|
||||||
#include "../general/layout_containers/flow_widget.h"
|
#include "../general/layout_containers/flow_widget.h"
|
||||||
|
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
@ -15,11 +16,14 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
|
|
||||||
|
class DrawProbabilityWidget;
|
||||||
class VisualDeckEditorSampleHandWidget : public QWidget
|
class VisualDeckEditorSampleHandWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *deckListModel);
|
VisualDeckEditorSampleHandWidget(QWidget *parent,
|
||||||
|
DeckListModel *deckListModel,
|
||||||
|
DeckListStatisticsAnalyzer *statsAnalyzer);
|
||||||
QList<ExactCard> getRandomCards(int amountToGet);
|
QList<ExactCard> getRandomCards(int amountToGet);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
@ -29,12 +33,14 @@ public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeckListModel *deckListModel;
|
DeckListModel *deckListModel;
|
||||||
|
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||||
QVBoxLayout *layout;
|
QVBoxLayout *layout;
|
||||||
QWidget *resetAndHandSizeContainerWidget;
|
QWidget *resetAndHandSizeContainerWidget;
|
||||||
QHBoxLayout *resetAndHandSizeLayout;
|
QHBoxLayout *resetAndHandSizeLayout;
|
||||||
QPushButton *resetButton;
|
QPushButton *resetButton;
|
||||||
QSpinBox *handSizeSpinBox;
|
QSpinBox *handSizeSpinBox;
|
||||||
FlowWidget *flowWidget;
|
FlowWidget *flowWidget;
|
||||||
|
DrawProbabilityWidget *drawProbabilityWidget;
|
||||||
CardSizeWidget *cardSizeWidget;
|
CardSizeWidget *cardSizeWidget;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,43 @@ inline color convertQColorToColor(const QColor &c)
|
||||||
result.set_b(c.blue());
|
result.set_b(c.blue());
|
||||||
return result;
|
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
|
#endif
|
||||||
|
|
||||||
inline color makeColor(int r, int g, int b)
|
inline color makeColor(int r, int g, int b)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,20 @@ template <typename T> T *findParentOfType(const QObject *obj)
|
||||||
}
|
}
|
||||||
return nullptr;
|
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
|
} // namespace QtUtils
|
||||||
|
|
||||||
#endif // COCKATRICE_QT_UTILS_H
|
#endif // COCKATRICE_QT_UTILS_H
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue