mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-01-25 10:54:42 -08:00
Compare commits
1 Commits
tooomm-tra
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e675162797 |
1
.github/workflows/desktop-build.yml
vendored
1
.github/workflows/desktop-build.yml
vendored
@@ -142,6 +142,7 @@ jobs:
|
||||
- distro: Ubuntu
|
||||
version: 22.04
|
||||
package: DEB
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Ubuntu
|
||||
version: 24.04
|
||||
|
||||
46
README.md
46
README.md
@@ -44,10 +44,10 @@ Latest <kbd>beta</kbd> version:
|
||||
|
||||
# Related Repositories
|
||||
|
||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for use in Cockatrice
|
||||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
|
||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice
|
||||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice
|
||||
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
|
||||
- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
|
||||
- [Cockatrice @Flathub](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration for our Linux `flatpak` package
|
||||
|
||||
|
||||
# Community Resources [](https://discord.gg/3Z9yzmA)
|
||||
@@ -55,6 +55,7 @@ Latest <kbd>beta</kbd> version:
|
||||
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
|
||||
- [Official Website](https://cockatrice.github.io)
|
||||
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
|
||||
- [Official Code Documentation](https://cockatrice.github.io/docs)
|
||||
- [Official Discord](https://discord.gg/3Z9yzmA)
|
||||
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
|
||||
|
||||
@@ -63,23 +64,6 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p
|
||||
|
||||
|
||||
# Contribute
|
||||
<p>
|
||||
<a href="#code">Code</a> <b>|</b>
|
||||
<a href="#documentation-">Documentation</a> <b>|</b>
|
||||
<a href="#translation-">Translation</a>
|
||||
</p>
|
||||
|
||||
#### Repository Activity
|
||||

|
||||
|
||||
<details>
|
||||
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
|
||||
<br>
|
||||
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
|
||||
</a><br>
|
||||
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
|
||||
</details>
|
||||
|
||||
### Code
|
||||
|
||||
@@ -95,17 +79,21 @@ We'll happily advice on how best to implement a feature, or we can show you wher
|
||||
|
||||
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
|
||||
|
||||
### Documentation [](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush)
|
||||
|
||||
There are various places where useful information for different needs are maintained:
|
||||
- [Official Code Documentation](https://cockatrice.github.io/docs/)
|
||||
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) `Community supported`
|
||||
- [Official Webpage](https://cockatrice.github.io/)
|
||||
- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file`
|
||||
|
||||
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
|
||||
|
||||
### Translation [](https://explore.transifex.com/cockatrice/cockatrice/)
|
||||
#### Repository Activity
|
||||

|
||||
|
||||
<details>
|
||||
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
|
||||
<br>
|
||||
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
|
||||
</a><br>
|
||||
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
|
||||
</details>
|
||||
|
||||
### Translations [](https://explore.transifex.com/cockatrice/cockatrice/)
|
||||
|
||||
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
|
||||
|
||||
|
||||
@@ -144,31 +144,11 @@ set(cockatrice_SOURCES
|
||||
src/interface/widgets/cards/card_size_widget.cpp
|
||||
src/interface/widgets/cards/deck_card_zone_display_widget.cpp
|
||||
src/interface/widgets/cards/deck_preview_card_picture_widget.cpp
|
||||
src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
|
||||
src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp
|
||||
src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp
|
||||
src/interface/widgets/deck_analytics/deck_analytics_widget.cpp
|
||||
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
|
||||
src/interface/widgets/deck_analytics/resizable_panel.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_base_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_devotion_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
|
||||
@@ -176,21 +156,16 @@ set(cockatrice_SOURCES
|
||||
src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
||||
src/interface/widgets/deck_editor/deck_state_manager.cpp
|
||||
src/interface/widgets/general/background_sources.cpp
|
||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||
src/interface/widgets/general/display/banner_widget.cpp
|
||||
src/interface/widgets/general/display/bar_widget.cpp
|
||||
src/interface/widgets/general/display/color_bar.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_label.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||
src/interface/widgets/general/display/labeled_input.cpp
|
||||
src/interface/widgets/general/display/percent_bar_widget.cpp
|
||||
src/interface/widgets/general/display/shadow_background_label.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/color_bar.cpp
|
||||
src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/pies/color_pie.cpp
|
||||
src/interface/widgets/general/home_styled_button.cpp
|
||||
src/interface/widgets/general/home_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
@page deck_search_syntax_help Deck Search Syntax Help
|
||||
|
||||
## Deck Search Syntax Help
|
||||
|
||||
-----
|
||||
The search bar recognizes a set of special commands.<br>
|
||||
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all
|
||||
searches are case insensitive.
|
||||
|
||||
<dl>
|
||||
|
||||
<dt>Display Name (The deck name, or the filename if the deck name isn't set):</dt>
|
||||
<dd>[red deck wins](#red deck wins) <small>(Any deck with a display name containing the words red, deck, and wins)</small></dd>
|
||||
<dd>["red deck wins"](#%22red deck wins%22) <small>(Any deck with a display name containing the exact phrase "red deck wins")</small></dd>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
@page search_syntax_help Search Syntax Help
|
||||
|
||||
## Search Syntax Help
|
||||
|
||||
-----
|
||||
The search bar recognizes a set of special commands similar to some other card databases.<br>
|
||||
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
|
||||
|
||||
<dl>
|
||||
|
||||
<dt>Name:</dt>
|
||||
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
|
||||
<dd>["birds of paradise"](#%22birds of paradise%22) <small>(Any card name containing the exact phrase "birds of paradise")</small></dd>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "../network/update/client/release_channel.h"
|
||||
#include "card_counter_settings.h"
|
||||
#include "version_string.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QApplication>
|
||||
@@ -199,13 +198,7 @@ SettingsCache::SettingsCache()
|
||||
|
||||
mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
|
||||
|
||||
if (settings->contains("personal/startupUpdateCheck")) {
|
||||
checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool();
|
||||
} else if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) {
|
||||
checkUpdatesOnStartup = false; // do not run auto updater on custom version
|
||||
} else {
|
||||
checkUpdatesOnStartup = true; // default to run auto updater
|
||||
}
|
||||
checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool();
|
||||
startupCardUpdateCheckPromptForUpdate =
|
||||
settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
|
||||
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
|
||||
@@ -213,15 +206,7 @@ SettingsCache::SettingsCache()
|
||||
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
|
||||
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
|
||||
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
|
||||
|
||||
if (settings->contains("personal/updatereleasechannel")) {
|
||||
updateReleaseChannel = settings->value("personal/updatereleasechannel").toInt();
|
||||
} else if (QString(VERSION_STRING).contains("beta", Qt::CaseInsensitive)) {
|
||||
// default to beta if this is a beta release
|
||||
updateReleaseChannel = 1;
|
||||
} else {
|
||||
updateReleaseChannel = 0; // stable
|
||||
}
|
||||
updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt();
|
||||
|
||||
lang = settings->value("personal/lang").toString();
|
||||
keepalive = settings->value("personal/keepalive", 3).toInt();
|
||||
@@ -288,7 +273,6 @@ SettingsCache::SettingsCache()
|
||||
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
|
||||
|
||||
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
|
||||
showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
|
||||
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
|
||||
roundCardCorners = settings->value("cards/roundcardcorners", true).toBool();
|
||||
overrideAllCardArtWithPersonalPreference =
|
||||
@@ -716,13 +700,6 @@ void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts)
|
||||
settings->setValue("menu/showshortcuts", showShortcuts);
|
||||
}
|
||||
|
||||
void SettingsCache::setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar)
|
||||
{
|
||||
showGameSelectorFilterToolbar = static_cast<bool>(_showGameSelectorFilterToolbar);
|
||||
settings->setValue("menu/showgameselectorfiltertoolbar", showGameSelectorFilterToolbar);
|
||||
emit showGameSelectorFilterToolbarChanged(showGameSelectorFilterToolbar);
|
||||
}
|
||||
|
||||
void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames)
|
||||
{
|
||||
displayCardNames = static_cast<bool>(_displayCardNames);
|
||||
|
||||
@@ -145,7 +145,6 @@ signals:
|
||||
void homeTabBackgroundShuffleFrequencyChanged();
|
||||
void picDownloadChanged();
|
||||
void showStatusBarChanged(bool state);
|
||||
void showGameSelectorFilterToolbarChanged(bool state);
|
||||
void displayCardNamesChanged();
|
||||
void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference);
|
||||
void bumpSetsWithCardsInDeckToTopChanged();
|
||||
@@ -237,7 +236,6 @@ private:
|
||||
bool annotateTokens;
|
||||
QByteArray tabGameSplitterSizes;
|
||||
bool showShortcuts;
|
||||
bool showGameSelectorFilterToolbar;
|
||||
bool displayCardNames;
|
||||
bool overrideAllCardArtWithPersonalPreference;
|
||||
bool bumpSetsWithCardsInDeckToTop;
|
||||
@@ -555,10 +553,6 @@ public:
|
||||
{
|
||||
return showShortcuts;
|
||||
}
|
||||
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const
|
||||
{
|
||||
return showGameSelectorFilterToolbar;
|
||||
}
|
||||
[[nodiscard]] bool getDisplayCardNames() const
|
||||
{
|
||||
return displayCardNames;
|
||||
@@ -1023,7 +1017,6 @@ public slots:
|
||||
void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens);
|
||||
void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes);
|
||||
void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts);
|
||||
void setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar);
|
||||
void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames);
|
||||
void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt);
|
||||
void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop);
|
||||
|
||||
@@ -660,12 +660,6 @@ private:
|
||||
{"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"),
|
||||
parseSequenceString("Ctrl+M"),
|
||||
ShortcutGroup::Drawing)},
|
||||
{"Player/aMulliganSame", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Same hand size)"),
|
||||
parseSequenceString("Ctrl+Shift+M"),
|
||||
ShortcutGroup::Drawing)},
|
||||
{"Player/aMulliganMinusOne", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Hand size - 1)"),
|
||||
parseSequenceString("Ctrl+Shift+Alt+M"),
|
||||
ShortcutGroup::Drawing)},
|
||||
{"Player/aDrawCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw a Card"),
|
||||
parseSequenceString("Ctrl+D"),
|
||||
ShortcutGroup::Drawing)},
|
||||
|
||||
@@ -396,7 +396,7 @@ void CardMenu::addRelatedCardActions()
|
||||
relatedCardName = relatedCard.getName(); // "name"
|
||||
}
|
||||
|
||||
QString text = tr("Token") + ": ";
|
||||
QString text = tr("Token: ");
|
||||
if (cardRelation->getDoesAttach()) {
|
||||
text +=
|
||||
tr(cardRelation->getDoesTransform() ? "Transform into " : "Attach to ") + "\"" + relatedCardName + "\"";
|
||||
@@ -502,4 +502,4 @@ void CardMenu::setShortcutsActive()
|
||||
aRemoveCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aRC" + colorWords[i]));
|
||||
aSetCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aSC" + colorWords[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,16 +60,6 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T
|
||||
connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan);
|
||||
addAction(aMulligan);
|
||||
|
||||
// Mulligan same size
|
||||
aMulliganSame = new QAction(this);
|
||||
connect(aMulliganSame, &QAction::triggered, actions, &PlayerActions::actMulliganSameSize);
|
||||
addAction(aMulliganSame);
|
||||
|
||||
// Mulligan -1
|
||||
aMulliganMinusOne = new QAction(this);
|
||||
connect(aMulliganMinusOne, &QAction::triggered, actions, &PlayerActions::actMulliganMinusOne);
|
||||
addAction(aMulliganMinusOne);
|
||||
|
||||
addSeparator();
|
||||
|
||||
mMoveHandMenu = addTearOffMenu(QString());
|
||||
@@ -114,9 +104,7 @@ void HandMenu::retranslateUi()
|
||||
aSortHandByType->setText(tr("Type"));
|
||||
aSortHandByManaValue->setText(tr("Mana Value"));
|
||||
|
||||
aMulligan->setText(tr("Take &mulligan (Choose hand size)"));
|
||||
aMulliganSame->setText(tr("Take mulligan (Same hand size)"));
|
||||
aMulliganMinusOne->setText(tr("Take mulligan (Hand size - 1)"));
|
||||
aMulligan->setText(tr("Take &mulligan"));
|
||||
|
||||
mMoveHandMenu->setTitle(tr("&Move hand to..."));
|
||||
aMoveHandToTopLibrary->setText(tr("&Top of library"));
|
||||
@@ -140,8 +128,6 @@ void HandMenu::setShortcutsActive()
|
||||
aSortHandByType->setShortcuts(shortcuts.getShortcut("Player/aSortHandByType"));
|
||||
aSortHandByManaValue->setShortcuts(shortcuts.getShortcut("Player/aSortHandByManaValue"));
|
||||
aMulligan->setShortcuts(shortcuts.getShortcut("Player/aMulligan"));
|
||||
aMulliganSame->setShortcuts(shortcuts.getShortcut("Player/aMulliganSame"));
|
||||
aMulliganMinusOne->setShortcuts(shortcuts.getShortcut("Player/aMulliganMinusOne"));
|
||||
aRevealHandToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealHandToAll"));
|
||||
aRevealRandomHandCardToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealRandomHandCardToAll"));
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ private:
|
||||
|
||||
QAction *aViewHand = nullptr;
|
||||
QAction *aMulligan = nullptr;
|
||||
QAction *aMulliganSame = nullptr;
|
||||
QAction *aMulliganMinusOne = nullptr;
|
||||
|
||||
QMenu *mSortHand = nullptr;
|
||||
QAction *aSortHandByName = nullptr;
|
||||
|
||||
@@ -310,48 +310,28 @@ void PlayerActions::actMulligan()
|
||||
{
|
||||
int startSize = SettingsCache::instance().getStartingHandSize();
|
||||
int handSize = player->getHandZone()->getCards().size();
|
||||
int deckSize = player->getDeckZone()->getCards().size() + handSize;
|
||||
|
||||
int deckSize = player->getDeckZone()->getCards().size() + handSize; // hand is shuffled back into the deck
|
||||
bool ok;
|
||||
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"),
|
||||
tr("Number of cards: (max. %1)").arg(deckSize) + '\n' +
|
||||
tr("0 and lower are in comparison to current hand size"),
|
||||
startSize, -handSize, deckSize, 1, &ok);
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (number < 1) {
|
||||
number = handSize + number;
|
||||
}
|
||||
|
||||
doMulligan(number);
|
||||
SettingsCache::instance().setStartingHandSize(number);
|
||||
}
|
||||
|
||||
void PlayerActions::actMulliganSameSize()
|
||||
{
|
||||
int handSize = player->getHandZone()->getCards().size();
|
||||
doMulligan(handSize);
|
||||
}
|
||||
|
||||
void PlayerActions::actMulliganMinusOne()
|
||||
{
|
||||
int handSize = player->getHandZone()->getCards().size();
|
||||
int targetSize = qMax(1, handSize - 1);
|
||||
doMulligan(targetSize);
|
||||
}
|
||||
|
||||
void PlayerActions::doMulligan(int number)
|
||||
{
|
||||
if (number < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Command_Mulligan cmd;
|
||||
cmd.set_number(number);
|
||||
if (number < 1) {
|
||||
if (handSize == 0) {
|
||||
return;
|
||||
}
|
||||
cmd.set_number(handSize + number);
|
||||
} else {
|
||||
cmd.set_number(number);
|
||||
}
|
||||
sendGameCommand(cmd);
|
||||
if (startSize != number) {
|
||||
SettingsCache::instance().setStartingHandSize(number);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerActions::actDrawCards()
|
||||
|
||||
@@ -85,9 +85,6 @@ public slots:
|
||||
void actDrawCards();
|
||||
void actUndoDraw();
|
||||
void actMulligan();
|
||||
void actMulliganSameSize();
|
||||
void actMulliganMinusOne();
|
||||
void doMulligan(int number);
|
||||
|
||||
void actPlay();
|
||||
void actPlayFacedown();
|
||||
|
||||
@@ -13,20 +13,12 @@
|
||||
#include <QGraphicsLinearLayout>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsView>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr qreal kTitleBarHeight = 24.0;
|
||||
constexpr qreal kMinVisibleWidth = 100.0;
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @param _player the player the cards were revealed to.
|
||||
* @param _origZone the zone the cards were revealed from.
|
||||
@@ -249,191 +241,33 @@ void ZoneViewWidget::retranslateUi()
|
||||
pileViewCheckBox.setText(tr("pile view"));
|
||||
}
|
||||
|
||||
void ZoneViewWidget::stopWindowDrag()
|
||||
void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */)
|
||||
{
|
||||
if (!draggingWindow)
|
||||
if (!scene())
|
||||
return;
|
||||
|
||||
draggingWindow = false;
|
||||
ungrabMouse();
|
||||
}
|
||||
int titleBarHeight = 24;
|
||||
|
||||
void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
draggingWindow = true;
|
||||
dragStartItemPos = pos();
|
||||
dragStartScreenPos = event->screenPos();
|
||||
dragView = findDragView(event->widget());
|
||||
QPointF scenePos = pos();
|
||||
|
||||
// need to grab mouse to receive events and not miss initial movement
|
||||
grabMouse();
|
||||
}
|
||||
|
||||
QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const
|
||||
{
|
||||
const QRectF frameRectF = windowFrameRect();
|
||||
|
||||
// query the style for the close button position (handles macOS top-left placement)
|
||||
// Title bar rect MUST be local (0,0-based) for QStyle
|
||||
const QRect titleBarRect(0, 0, static_cast<int>(frameRectF.width()), static_cast<int>(kTitleBarHeight));
|
||||
|
||||
if (styleWidget) {
|
||||
QStyleOptionTitleBar opt;
|
||||
opt.initFrom(styleWidget);
|
||||
opt.rect = titleBarRect;
|
||||
opt.text = windowTitle();
|
||||
opt.icon = styleWidget->windowIcon();
|
||||
opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
|
||||
|
||||
opt.subControls = QStyle::SC_TitleBarCloseButton;
|
||||
opt.activeSubControls = QStyle::SC_TitleBarCloseButton;
|
||||
opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState;
|
||||
|
||||
if (styleWidget->isActiveWindow()) {
|
||||
opt.state |= QStyle::State_Active;
|
||||
}
|
||||
|
||||
QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton,
|
||||
styleWidget);
|
||||
|
||||
if (r.isValid() && !r.isEmpty()) {
|
||||
// Translate from local-titlebar coords → frame coords
|
||||
r.translate(frameRectF.topLeft().toPoint());
|
||||
return QRectF(r);
|
||||
}
|
||||
if (scenePos.x() < 0) {
|
||||
scenePos.setX(0);
|
||||
} else {
|
||||
qreal maxw = scene()->sceneRect().width() - 100;
|
||||
if (scenePos.x() > maxw)
|
||||
scenePos.setX(maxw);
|
||||
}
|
||||
|
||||
// Fallback: frame-relative top-right
|
||||
return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight);
|
||||
}
|
||||
|
||||
QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const
|
||||
{
|
||||
QWidget *current = eventWidget;
|
||||
while (current) {
|
||||
if (auto *view = qobject_cast<QGraphicsView *>(current))
|
||||
return view;
|
||||
current = current->parentWidget();
|
||||
if (scenePos.y() < titleBarHeight) {
|
||||
scenePos.setY(titleBarHeight);
|
||||
} else {
|
||||
qreal maxh = scene()->sceneRect().height() - titleBarHeight;
|
||||
if (scenePos.y() > maxh)
|
||||
scenePos.setY(maxh);
|
||||
}
|
||||
|
||||
if (scene() && !scene()->views().isEmpty())
|
||||
return scene()->views().constFirst();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QPointF ZoneViewWidget::calcDraggedWindowPos(const QPoint &screenPos,
|
||||
const QPointF &scenePos,
|
||||
const QPointF &buttonDownScenePos) const
|
||||
{
|
||||
if (dragView && dragView->viewport()) {
|
||||
const QPoint vpStart = dragView->viewport()->mapFromGlobal(dragStartScreenPos);
|
||||
const QPoint vpNow = dragView->viewport()->mapFromGlobal(screenPos);
|
||||
const QPointF sceneStart = dragView->mapToScene(vpStart);
|
||||
const QPointF sceneNow = dragView->mapToScene(vpNow);
|
||||
return dragStartItemPos + (sceneNow - sceneStart);
|
||||
}
|
||||
|
||||
return dragStartItemPos + (scenePos - buttonDownScenePos);
|
||||
}
|
||||
|
||||
bool ZoneViewWidget::windowFrameEvent(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::UngrabMouse) {
|
||||
stopWindowDrag();
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
}
|
||||
|
||||
auto *me = dynamic_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
if (!me)
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::GraphicsSceneMousePress:
|
||||
if (me->button() == Qt::LeftButton && windowFrameSectionAt(me->pos()) == Qt::TitleBarArea) {
|
||||
// avoid drag on close button
|
||||
if (closeButtonRect(me->widget()).contains(me->pos())) {
|
||||
me->accept();
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
startWindowDrag(me);
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::GraphicsSceneMouseMove:
|
||||
if (draggingWindow) {
|
||||
if (!(me->buttons() & Qt::LeftButton)) {
|
||||
stopWindowDrag();
|
||||
} else {
|
||||
setPos(
|
||||
calcDraggedWindowPos(me->screenPos(), me->scenePos(), me->buttonDownScenePos(Qt::LeftButton)));
|
||||
}
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::GraphicsSceneMouseRelease:
|
||||
if (draggingWindow && me->button() == Qt::LeftButton) {
|
||||
stopWindowDrag();
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
// move if the scene routes moves while dragging
|
||||
if (draggingWindow && (event->buttons() & Qt::LeftButton)) {
|
||||
setPos(calcDraggedWindowPos(event->screenPos(), event->scenePos(), event->buttonDownScenePos(Qt::LeftButton)));
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (draggingWindow && event->button() == Qt::LeftButton) {
|
||||
stopWindowDrag();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
QVariant ZoneViewWidget::itemChange(GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if (change == QGraphicsItem::ItemPositionChange && scene()) {
|
||||
// Keep grab area in main view
|
||||
const QRectF sceneRect = scene()->sceneRect();
|
||||
const QPointF requestedPos = value.toPointF();
|
||||
QPointF desiredPos = requestedPos;
|
||||
|
||||
const qreal minX = sceneRect.left();
|
||||
const qreal maxX = qMax(minX, sceneRect.right() - kMinVisibleWidth);
|
||||
const qreal minY = sceneRect.top() + kTitleBarHeight;
|
||||
const qreal maxY = qMax(minY, sceneRect.bottom() - kTitleBarHeight);
|
||||
|
||||
desiredPos.setX(qBound(minX, desiredPos.x(), maxX));
|
||||
desiredPos.setY(qBound(minY, desiredPos.y(), maxY));
|
||||
return desiredPos;
|
||||
}
|
||||
|
||||
return QGraphicsWidget::itemChange(change, value);
|
||||
if (scenePos != pos())
|
||||
setPos(scenePos);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
|
||||
@@ -516,7 +350,6 @@ void ZoneViewWidget::handleScrollBarChange(int value)
|
||||
|
||||
void ZoneViewWidget::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
stopWindowDrag();
|
||||
disconnect(zone, &ZoneViewZone::closed, this, 0);
|
||||
// manually call zone->close in order to remove it from the origZones views
|
||||
zone->close();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef ZONEVIEWWIDGET_H
|
||||
#define ZONEVIEWWIDGET_H
|
||||
|
||||
@@ -13,7 +14,6 @@
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QPointer>
|
||||
#include <libcockatrice/utility/macros.h>
|
||||
|
||||
class QLabel;
|
||||
@@ -28,8 +28,6 @@ class ServerInfo_Card;
|
||||
class QGraphicsSceneMouseEvent;
|
||||
class QGraphicsSceneWheelEvent;
|
||||
class QStyleOption;
|
||||
class QGraphicsView;
|
||||
class QWidget;
|
||||
|
||||
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
|
||||
{
|
||||
@@ -54,6 +52,7 @@ private:
|
||||
ZoneViewZone *zone;
|
||||
QGraphicsWidget *zoneContainer;
|
||||
|
||||
QPushButton *closeButton;
|
||||
QScrollBar *scrollBar;
|
||||
ScrollableGraphicsProxyWidget *scrollBarProxy;
|
||||
|
||||
@@ -67,33 +66,6 @@ private:
|
||||
int extraHeight;
|
||||
Player *player;
|
||||
|
||||
bool draggingWindow = false;
|
||||
QPoint dragStartScreenPos;
|
||||
QPointF dragStartItemPos;
|
||||
QPointer<QGraphicsView> dragView;
|
||||
|
||||
void stopWindowDrag();
|
||||
void startWindowDrag(QGraphicsSceneMouseEvent *event);
|
||||
QRectF closeButtonRect(QWidget *styleWidget) const;
|
||||
/**
|
||||
* @brief Resolves the QGraphicsView to use for drag coordinate mapping
|
||||
*
|
||||
* @param eventWidget QWidget that originated the mouse event
|
||||
* @return The resolved QGraphicsView
|
||||
*/
|
||||
QGraphicsView *findDragView(QWidget *eventWidget) const;
|
||||
/**
|
||||
* @brief Calculates the desired widget position while dragging
|
||||
*
|
||||
* @param screenPos Global screen coordinates of the current mouse position
|
||||
* @param scenePos Scene coordinates of the current mouse position
|
||||
* @param buttonDownScenePos Scene coordinates of the initial mouse press position
|
||||
*
|
||||
* @return The new widget position in scene coordinates
|
||||
*/
|
||||
QPointF
|
||||
calcDraggedWindowPos(const QPoint &screenPos, const QPointF &scenePos, const QPointF &buttonDownScenePos) const;
|
||||
|
||||
void resizeScrollbar(qreal newZoneHeight);
|
||||
signals:
|
||||
void closePressed(ZoneViewWidget *zv);
|
||||
@@ -104,6 +76,7 @@ private slots:
|
||||
void resizeToZoneContents(bool forceInitialHeight = false);
|
||||
void handleScrollBarChange(int value);
|
||||
void zoneDeleted();
|
||||
void moveEvent(QGraphicsSceneMoveEvent * /* event */) override;
|
||||
void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override;
|
||||
void expandWindow();
|
||||
|
||||
@@ -128,10 +101,6 @@ public:
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void initStyleOption(QStyleOption *option) const override;
|
||||
bool windowFrameEvent(QEvent *event) override;
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa
|
||||
layout->addWidget(text, 0, Qt::AlignCenter);
|
||||
setLayout(layout);
|
||||
|
||||
setFrameStyle(static_cast<int>(QFrame::Panel) | QFrame::Raised);
|
||||
setFrameStyle(QFrame::Panel | QFrame::Raised);
|
||||
|
||||
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
|
||||
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#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
|
||||
@@ -1,32 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
#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
|
||||
@@ -1,33 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#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
|
||||
@@ -1 +0,0 @@
|
||||
#include "analytics_panel_widget_registrar.h"
|
||||
@@ -1,17 +0,0 @@
|
||||
#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
|
||||
@@ -1,28 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#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
|
||||
@@ -1,92 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#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;
|
||||
};
|
||||
@@ -1,236 +0,0 @@
|
||||
#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()->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);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#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
|
||||
@@ -1,32 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
|
||||
#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
|
||||
@@ -1,67 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
#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
|
||||
@@ -1,115 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,51 +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 "../../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
|
||||
@@ -1,121 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#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
|
||||
@@ -1,41 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
#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
|
||||
@@ -1,91 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#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
|
||||
@@ -1,78 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#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
|
||||
@@ -1,148 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @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
|
||||
@@ -1,31 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#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
|
||||
@@ -1,62 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
#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
|
||||
@@ -1,123 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,45 +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 "../../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
|
||||
@@ -1,36 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#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
|
||||
@@ -1,83 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#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
|
||||
@@ -1,49 +0,0 @@
|
||||
#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));
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#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
|
||||
@@ -1,129 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#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,298 +1,35 @@
|
||||
#include "deck_analytics_widget.h"
|
||||
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "add_analytics_panel_dialog.h"
|
||||
#include "analytics_panel_widget_factory.h"
|
||||
#include "analyzer_modules/mana_base/mana_base_config.h"
|
||||
#include "analyzer_modules/mana_curve/mana_curve_config.h"
|
||||
#include "analyzer_modules/mana_devotion/mana_devotion_config.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *_statsAnalyzer)
|
||||
: QWidget(parent), statsAnalyzer(_statsAnalyzer)
|
||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
mainLayout = new QVBoxLayout();
|
||||
setLayout(mainLayout);
|
||||
|
||||
// Controls
|
||||
controlContainer = new QWidget(this);
|
||||
controlLayout = new QHBoxLayout(controlContainer);
|
||||
addButton = new QPushButton(this);
|
||||
removeButton = new QPushButton(this);
|
||||
saveButton = new QPushButton(this);
|
||||
loadButton = new QPushButton(this);
|
||||
controlLayout->addWidget(addButton);
|
||||
controlLayout->addWidget(removeButton);
|
||||
controlLayout->addWidget(saveButton);
|
||||
controlLayout->addWidget(loadButton);
|
||||
|
||||
layout->addWidget(controlContainer);
|
||||
|
||||
connect(addButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onAddPanel);
|
||||
connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected);
|
||||
connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout);
|
||||
connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout);
|
||||
|
||||
// Scroll area and container
|
||||
scrollArea = new QScrollArea(this);
|
||||
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
mainLayout->addWidget(scrollArea);
|
||||
|
||||
panelContainer = new QWidget(scrollArea);
|
||||
panelLayout = new QVBoxLayout(panelContainer);
|
||||
panelLayout->setSpacing(8);
|
||||
panelLayout->setContentsMargins(4, 4, 4, 4);
|
||||
panelLayout->addStretch(1); // push panels up
|
||||
container = new QWidget(scrollArea);
|
||||
containerLayout = new QVBoxLayout(container);
|
||||
container->setLayout(containerLayout);
|
||||
scrollArea->setWidget(container);
|
||||
|
||||
scrollArea->setWidget(panelContainer);
|
||||
layout->addWidget(scrollArea);
|
||||
deckListStatisticsAnalyzer = new DeckListStatisticsAnalyzer(this, deckListModel);
|
||||
|
||||
loadLayout();
|
||||
manaCurveWidget = new ManaCurveWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaCurveWidget);
|
||||
|
||||
retranslateUi();
|
||||
manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaDevotionWidget);
|
||||
|
||||
manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaBaseWidget);
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::retranslateUi()
|
||||
void DeckAnalyticsWidget::refreshDisplays()
|
||||
{
|
||||
addButton->setText(tr("Add Panel"));
|
||||
removeButton->setText(tr("Remove Panel"));
|
||||
saveButton->setText(tr("Save Layout"));
|
||||
loadButton->setText(tr("Load Layout"));
|
||||
deckListStatisticsAnalyzer->update();
|
||||
}
|
||||
|
||||
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,71 +1,44 @@
|
||||
/**
|
||||
* @file deck_analytics_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief Main analytics widget container with resizable panels for deck statistics.
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||
#define DECK_ANALYTICS_WIDGET_H
|
||||
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
#include "mana_base_widget.h"
|
||||
#include "mana_curve_widget.h"
|
||||
#include "mana_devotion_widget.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QHBoxLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
class LayoutInspector;
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
|
||||
class DeckAnalyticsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
void updateDisplays();
|
||||
|
||||
public:
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
void retranslateUi();
|
||||
|
||||
private slots:
|
||||
void onAddPanel();
|
||||
void onRemoveSelected();
|
||||
void onPanelDropped(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore);
|
||||
void saveLayout();
|
||||
void loadLayout();
|
||||
void addDefaultPanels();
|
||||
bool loadLayoutInternal();
|
||||
void clearPanels();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
void selectWrapper(ResizablePanel *panel);
|
||||
int indexOfSelectedWrapper() const;
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
void setDeckList(const DeckList &_deckListModel);
|
||||
std::map<int, int> analyzeManaCurve();
|
||||
void refreshDisplays();
|
||||
|
||||
private:
|
||||
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||
DeckListModel *deckListModel;
|
||||
DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer;
|
||||
QVBoxLayout *mainLayout;
|
||||
|
||||
QVBoxLayout *layout;
|
||||
QWidget *controlContainer;
|
||||
QHBoxLayout *controlLayout;
|
||||
|
||||
QPushButton *addButton;
|
||||
QPushButton *removeButton;
|
||||
QPushButton *saveButton;
|
||||
QPushButton *loadButton;
|
||||
QWidget *container;
|
||||
QVBoxLayout *containerLayout;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
QWidget *panelContainer;
|
||||
QVBoxLayout *panelLayout;
|
||||
|
||||
QVector<ResizablePanel *> panelWrappers;
|
||||
ResizablePanel *selectedWrapper = nullptr;
|
||||
|
||||
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||
LayoutInspector *insp = nullptr;
|
||||
ManaCurveWidget *manaCurveWidget;
|
||||
ManaDevotionWidget *manaDevotionWidget;
|
||||
ManaBaseWidget *manaBaseWidget;
|
||||
};
|
||||
|
||||
#endif // DECK_ANALYTICS_WIDGET_H
|
||||
|
||||
@@ -9,93 +9,38 @@
|
||||
|
||||
DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
|
||||
DeckListModel *_model,
|
||||
DeckListStatisticsAnalyzerConfig _config)
|
||||
: QObject(parent), model(_model), config(_config)
|
||||
DeckListStatisticsAnalyzerConfig cfg)
|
||||
: QObject(parent), model(_model), config(cfg)
|
||||
{
|
||||
connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::analyze);
|
||||
connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::update);
|
||||
}
|
||||
|
||||
void DeckListStatisticsAnalyzer::analyze()
|
||||
void DeckListStatisticsAnalyzer::update()
|
||||
{
|
||||
clearData();
|
||||
manaBaseMap.clear();
|
||||
manaCurveMap.clear();
|
||||
manaDevotionMap.clear();
|
||||
|
||||
QList<ExactCard> cards = model->getCards();
|
||||
|
||||
for (auto card : cards) {
|
||||
auto info = card.getInfo();
|
||||
const int cmc = info.getCmc().toInt();
|
||||
|
||||
// Convert once
|
||||
QStringList types = info.getMainCardType().split(' ');
|
||||
QStringList subtypes = info.getCardType().split('-').last().split(" ");
|
||||
QString colors = info.getColors();
|
||||
int power = info.getPowTough().split("/").first().toInt();
|
||||
int toughness = info.getPowTough().split("/").last().toInt();
|
||||
|
||||
// For each copy of card
|
||||
// ---------------- Mana Curve ----------------
|
||||
for (const ExactCard &card : cards) {
|
||||
// ---- Mana curve ----
|
||||
if (config.computeManaCurve) {
|
||||
manaCurveMap[cmc]++;
|
||||
manaCurveMap[card.getInfo().getCmc().toInt()]++;
|
||||
}
|
||||
|
||||
// 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 ----------------
|
||||
// ---- Mana base ----
|
||||
if (config.computeManaBase) {
|
||||
auto prod = determineManaProduction(info.getText());
|
||||
for (auto it = prod.begin(); it != prod.end(); ++it) {
|
||||
if (it.value() > 0) {
|
||||
productionPipCount[it.key()] += it.value();
|
||||
productionCardCount[it.key()]++;
|
||||
}
|
||||
auto mana = determineManaProduction(card.getInfo().getText());
|
||||
for (auto it = mana.begin(); it != mana.end(); ++it)
|
||||
manaBaseMap[it.key()] += it.value();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Devotion ----------------
|
||||
// ---- Devotion ----
|
||||
if (config.computeDevotion) {
|
||||
auto devo = countManaSymbols(info.getManaCost());
|
||||
for (auto &d : devo) {
|
||||
if (d.second > 0) {
|
||||
devotionPipCount[QString(d.first)] += d.second;
|
||||
devotionCardCount[QString(d.first)]++;
|
||||
}
|
||||
auto devo = countManaSymbols(card.getInfo().getManaCost());
|
||||
for (auto &d : devo)
|
||||
manaDevotionMap[d.first] += d.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,57 +112,3 @@ std::unordered_map<char, int> DeckListStatisticsAnalyzer::countManaSymbols(const
|
||||
|
||||
return manaCounts;
|
||||
}
|
||||
|
||||
// Hypergeometric probability: P(X=k)
|
||||
double DeckListStatisticsAnalyzer::hypergeometric(int N, int K, int n, int k)
|
||||
{
|
||||
if (k < 0 || k > n || K > N) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
auto choose = [](int n, int r) -> double {
|
||||
if (r > n)
|
||||
return 0.0;
|
||||
if (r == 0 || r == n)
|
||||
return 1.0;
|
||||
double res = 1.0;
|
||||
for (int i = 1; i <= r; ++i) {
|
||||
res *= (n - r + i);
|
||||
res /= i;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
return choose(K, k) * choose(N - K, n - k) / choose(N, n);
|
||||
}
|
||||
|
||||
void DeckListStatisticsAnalyzer::clearData()
|
||||
{
|
||||
manaBaseMap.clear();
|
||||
manaCurveMap.clear();
|
||||
manaDevotionMap.clear();
|
||||
|
||||
devotionPipCount.clear();
|
||||
devotionCardCount.clear();
|
||||
|
||||
productionPipCount.clear();
|
||||
productionCardCount.clear();
|
||||
|
||||
manaCurveByType.clear();
|
||||
manaCurveBySubtype.clear();
|
||||
manaCurveByColor.clear();
|
||||
manaCurveByPower.clear();
|
||||
manaCurveByToughness.clear();
|
||||
|
||||
manaCurveCardsByType.clear();
|
||||
manaCurveCardsBySubtype.clear();
|
||||
manaCurveCardsByColor.clear();
|
||||
manaCurveCardsByPower.clear();
|
||||
manaCurveCardsByToughness.clear();
|
||||
|
||||
typeCount.clear();
|
||||
subtypeCount.clear();
|
||||
colorCount.clear();
|
||||
rarityCount.clear();
|
||||
manaValueCount.clear();
|
||||
}
|
||||
@@ -14,9 +14,6 @@ struct DeckListStatisticsAnalyzerConfig
|
||||
bool computeManaBase = true;
|
||||
bool computeManaCurve = true;
|
||||
bool computeDevotion = true;
|
||||
bool computeCategories = true;
|
||||
bool computeCurveBreakdowns = true;
|
||||
bool computeProbabilities = true;
|
||||
};
|
||||
|
||||
class DeckListStatisticsAnalyzer : public QObject
|
||||
@@ -26,9 +23,9 @@ class DeckListStatisticsAnalyzer : public QObject
|
||||
public:
|
||||
explicit DeckListStatisticsAnalyzer(QObject *parent,
|
||||
DeckListModel *model,
|
||||
DeckListStatisticsAnalyzerConfig _config = DeckListStatisticsAnalyzerConfig());
|
||||
DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig());
|
||||
|
||||
void analyze();
|
||||
void update();
|
||||
|
||||
[[nodiscard]] const QHash<QString, int> &getManaBase() const
|
||||
{
|
||||
@@ -43,96 +40,6 @@ public:
|
||||
return manaDevotionMap;
|
||||
}
|
||||
|
||||
const QHash<QString, int> &getDevotionPipCount() const
|
||||
{
|
||||
return devotionPipCount;
|
||||
}
|
||||
const QHash<QString, int> &getDevotionCardCount() const
|
||||
{
|
||||
return devotionCardCount;
|
||||
}
|
||||
|
||||
const QHash<QString, int> &getProductionPipCount() const
|
||||
{
|
||||
return productionPipCount;
|
||||
}
|
||||
const QHash<QString, int> &getProductionCardCount() const
|
||||
{
|
||||
return productionCardCount;
|
||||
}
|
||||
|
||||
const QHash<QString, int> &getTypeCount() const
|
||||
{
|
||||
return typeCount;
|
||||
}
|
||||
const QHash<QString, int> &getSubtypeCount() const
|
||||
{
|
||||
return subtypeCount;
|
||||
}
|
||||
const QHash<QString, int> &getColorCount() const
|
||||
{
|
||||
return colorCount;
|
||||
}
|
||||
const QHash<QString, int> &getRarityCount() const
|
||||
{
|
||||
return rarityCount;
|
||||
}
|
||||
const QHash<int, int> &getManaValueCount() const
|
||||
{
|
||||
return manaValueCount;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByType() const
|
||||
{
|
||||
return manaCurveByType;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveBySubtype() const
|
||||
{
|
||||
return manaCurveBySubtype;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByColor() const
|
||||
{
|
||||
return manaCurveByColor;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByPower() const
|
||||
{
|
||||
return manaCurveByPower;
|
||||
}
|
||||
const QHash<QString, QHash<int, int>> &getManaCurveByToughness() const
|
||||
{
|
||||
return manaCurveByToughness;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByType() const
|
||||
{
|
||||
return manaCurveCardsByType;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsBySubtype() const
|
||||
{
|
||||
return manaCurveCardsBySubtype;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByColor() const
|
||||
{
|
||||
return manaCurveCardsByColor;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByPower() const
|
||||
{
|
||||
return manaCurveCardsByPower;
|
||||
}
|
||||
|
||||
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByToughness() const
|
||||
{
|
||||
return manaCurveCardsByToughness;
|
||||
}
|
||||
|
||||
DeckListModel *getModel() const
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
signals:
|
||||
void statsUpdated();
|
||||
|
||||
@@ -140,42 +47,14 @@ private:
|
||||
DeckListModel *model;
|
||||
DeckListStatisticsAnalyzerConfig config;
|
||||
|
||||
// Internal result containers
|
||||
QHash<QString, int> manaBaseMap;
|
||||
std::unordered_map<int, int> manaCurveMap;
|
||||
std::unordered_map<char, int> manaDevotionMap;
|
||||
|
||||
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;
|
||||
|
||||
// Internal helper functions
|
||||
QHash<QString, int> determineManaProduction(const QString &);
|
||||
std::unordered_map<char, int> countManaSymbols(const QString &);
|
||||
double hypergeometric(int N, int K, int n, int k);
|
||||
void clearData();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @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
|
||||
@@ -0,0 +1,68 @@
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @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
|
||||
@@ -0,0 +1,66 @@
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @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
|
||||
@@ -1,367 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
#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
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "deck_editor_deck_dock_widget.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "deck_list_style_proxy.h"
|
||||
#include "deck_state_manager.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
@@ -38,7 +38,7 @@ static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
|
||||
}
|
||||
|
||||
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent), deckStateManager(parent->deckStateManager)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("deckDock");
|
||||
|
||||
@@ -52,21 +52,19 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent
|
||||
|
||||
void DeckEditorDeckDockWidget::createDeckDock()
|
||||
{
|
||||
connect(getModel(), &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
||||
deckModel = new DeckListModel(this);
|
||||
deckModel->setObjectName("deckModel");
|
||||
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
||||
|
||||
deckLoader = new DeckLoader(this);
|
||||
|
||||
proxy = new DeckListStyleProxy(this);
|
||||
proxy->setSourceModel(getModel());
|
||||
proxy->setSourceModel(deckModel);
|
||||
|
||||
historyManagerWidget = new DeckListHistoryManagerWidget(deckStateManager, proxy, this);
|
||||
historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, deckEditor->getHistoryManager(), this);
|
||||
connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this,
|
||||
&DeckEditorDeckDockWidget::syncDisplayWidgetsToModel);
|
||||
|
||||
connect(deckStateManager, &DeckStateManager::focusIndexChanged, this, &DeckEditorDeckDockWidget::setSelectedIndex);
|
||||
connect(deckStateManager, &DeckStateManager::deckReplaced, this,
|
||||
&DeckEditorDeckDockWidget::syncDisplayWidgetsToModel);
|
||||
connect(deckStateManager, &DeckStateManager::deckReplaced, this,
|
||||
&DeckEditorDeckDockWidget::applyActiveGroupCriteria);
|
||||
|
||||
deckView = new QTreeView();
|
||||
deckView->setObjectName("deckView");
|
||||
deckView->setModel(proxy);
|
||||
@@ -99,7 +97,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
nameDebounceTimer = new QTimer(this);
|
||||
nameDebounceTimer->setSingleShot(true);
|
||||
nameDebounceTimer->setInterval(300); // debounce duration in ms
|
||||
connect(nameDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeName);
|
||||
connect(nameDebounceTimer, &QTimer::timeout, this, [this]() { updateName(nameEdit->text()); });
|
||||
|
||||
connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() {
|
||||
nameDebounceTimer->start(); // restart debounce timer
|
||||
@@ -143,7 +141,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
commentsDebounceTimer = new QTimer(this);
|
||||
commentsDebounceTimer->setSingleShot(true);
|
||||
commentsDebounceTimer->setInterval(400); // longer debounce for multi-line
|
||||
connect(commentsDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeComments);
|
||||
connect(commentsDebounceTimer, &QTimer::timeout, this, [this]() { updateComments(); });
|
||||
|
||||
connect(commentsEdit, &QTextEdit::textChanged, this, [this]() {
|
||||
commentsDebounceTimer->start(); // restart debounce timer
|
||||
@@ -154,21 +152,21 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
bannerCardLabel->setText(tr("Banner Card"));
|
||||
bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||
bannerCardComboBox = new QComboBox(this);
|
||||
connect(getModel(), &DeckListModel::dataChanged, this, [this]() {
|
||||
connect(deckModel, &DeckListModel::dataChanged, this, [this]() {
|
||||
// Delay the update to avoid race conditions
|
||||
QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox);
|
||||
});
|
||||
connect(getModel(), &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand);
|
||||
connect(getModel(), &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll);
|
||||
connect(deckModel, &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand);
|
||||
connect(deckModel, &DeckListModel::deckReplaced, this, &DeckEditorDeckDockWidget::expandAll);
|
||||
|
||||
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DeckEditorDeckDockWidget::writeBannerCard);
|
||||
&DeckEditorDeckDockWidget::setBannerCard);
|
||||
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, {});
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags());
|
||||
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
|
||||
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, deckStateManager,
|
||||
&DeckStateManager::setTags);
|
||||
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this,
|
||||
&DeckEditorDeckDockWidget::setTags);
|
||||
|
||||
activeGroupCriteriaLabel = new QLabel(this);
|
||||
|
||||
@@ -176,8 +174,11 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
activeGroupCriteriaComboBox->addItem(tr("Main Type"), DeckListModelGroupCriteria::MAIN_TYPE);
|
||||
activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST);
|
||||
activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR);
|
||||
connect(activeGroupCriteriaComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&DeckEditorDeckDockWidget::applyActiveGroupCriteria);
|
||||
connect(activeGroupCriteriaComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() {
|
||||
deckModel->setActiveGroupCriteria(static_cast<DeckListModelGroupCriteria::Type>(
|
||||
activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
});
|
||||
|
||||
aIncrement = new QAction(QString(), this);
|
||||
aIncrement->setIcon(QPixmap("theme:icons/increment"));
|
||||
@@ -294,9 +295,9 @@ void DeckEditorDeckDockWidget::initializeFormats()
|
||||
formatComboBox->addItem(formatName, formatName); // store the raw key in itemData
|
||||
}
|
||||
|
||||
QString format = deckStateManager->getMetadata().gameFormat;
|
||||
if (!format.isEmpty()) {
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(format));
|
||||
if (!deckModel->getDeckList()->getGameFormat().isEmpty()) {
|
||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
||||
} else {
|
||||
// Ensure no selection is visible initially
|
||||
formatComboBox->setCurrentIndex(-1);
|
||||
@@ -305,10 +306,11 @@ void DeckEditorDeckDockWidget::initializeFormats()
|
||||
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
if (index >= 0) {
|
||||
QString formatKey = formatComboBox->itemData(index).toString();
|
||||
deckStateManager->setFormat(formatKey);
|
||||
deckModel->setActiveFormat(formatKey);
|
||||
} else {
|
||||
deckStateManager->setFormat(""); // clear format if deselected
|
||||
deckModel->setActiveFormat(QString()); // clear format if deselected
|
||||
}
|
||||
emit deckModified();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -338,37 +340,43 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||
void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/)
|
||||
{
|
||||
if (ExactCard card = getCurrentCard()) {
|
||||
emit selectedCardChanged(card);
|
||||
emit cardChanged(card);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the contents of the name textBox to the DeckStateManager
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::writeName()
|
||||
void DeckEditorDeckDockWidget::updateName(const QString &name)
|
||||
{
|
||||
QString name = nameEdit->text();
|
||||
deckStateManager->setName(name);
|
||||
emit requestDeckHistorySave(
|
||||
QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName()));
|
||||
deckModel->getDeckList()->setName(name);
|
||||
deckEditor->setModified(name.isEmpty());
|
||||
emit nameChanged();
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the contents of the comments textBox to the DeckStateManager
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::writeComments()
|
||||
void DeckEditorDeckDockWidget::updateComments()
|
||||
{
|
||||
QString comments = commentsEdit->toPlainText();
|
||||
deckStateManager->setComments(comments);
|
||||
emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)")
|
||||
.arg(deckLoader->getDeck().deckList.getComments().size())
|
||||
.arg(commentsEdit->toPlainText().size()));
|
||||
|
||||
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
|
||||
deckEditor->setModified(commentsEdit->toPlainText().isEmpty());
|
||||
emit commentsChanged();
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateHash()
|
||||
{
|
||||
hashLabel->setText(deckStateManager->getDeckHash());
|
||||
hashLabel->setText(deckModel->getDeckList()->getDeckHash());
|
||||
emit hashChanged();
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
{
|
||||
// Store current banner card identity
|
||||
CardRef wanted = deckStateManager->getMetadata().bannerCard;
|
||||
CardRef wanted = deckModel->getDeckList()->getBannerCard();
|
||||
|
||||
// Block signals temporarily
|
||||
bool wasBlocked = bannerCardComboBox->blockSignals(true);
|
||||
@@ -378,7 +386,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
|
||||
// Collect unique (name, providerId) pairs
|
||||
QSet<QPair<QString, QString>> bannerCardSet;
|
||||
QList<CardRef> cardsInDeck = getModel()->getCardRefs();
|
||||
QList<CardRef> cardsInDeck = deckModel->getCardRefs();
|
||||
|
||||
for (auto cardRef : cardsInDeck) {
|
||||
if (!CardDatabaseManager::query()->getCard(cardRef)) {
|
||||
@@ -407,6 +415,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
// Handle results
|
||||
if (restoreIndex != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(restoreIndex);
|
||||
syncDeckListBannerCardWithComboBox();
|
||||
} else {
|
||||
// Add a placeholder "-" and set it as the current selection
|
||||
bannerCardComboBox->insertItem(0, "-");
|
||||
@@ -417,21 +426,25 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
bannerCardComboBox->blockSignals(wasBlocked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the selected bannerCard to the DeckStateManager
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::writeBannerCard(int index)
|
||||
void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */)
|
||||
{
|
||||
auto [name, id] = bannerCardComboBox->itemData(index).value<QPair<QString, QString>>();
|
||||
CardRef bannerCard = {name, id};
|
||||
deckStateManager->setBannerCard(bannerCard);
|
||||
emit requestDeckHistorySave(tr("Banner card changed"));
|
||||
syncDeckListBannerCardWithComboBox();
|
||||
deckEditor->setModified(true);
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::applyActiveGroupCriteria()
|
||||
void DeckEditorDeckDockWidget::setTags(const QStringList &tags)
|
||||
{
|
||||
getModel()->setActiveGroupCriteria(
|
||||
static_cast<DeckListModelGroupCriteria::Type>(activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
|
||||
getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckModel->getDeckList()->setTags(tags);
|
||||
deckEditor->setModified(true);
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox()
|
||||
{
|
||||
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
|
||||
deckModel->getDeckList()->setBannerCard({name, id});
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible)
|
||||
@@ -447,7 +460,7 @@ void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible)
|
||||
|
||||
void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
||||
{
|
||||
if (deckStateManager->getMetadata().bannerCard.name == "") {
|
||||
if (deckModel->getDeckList()->getBannerCard().name == "") {
|
||||
if (bannerCardComboBox->findText("-") != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-"));
|
||||
} else {
|
||||
@@ -455,26 +468,36 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
||||
bannerCardComboBox->setCurrentIndex(0);
|
||||
}
|
||||
} else {
|
||||
bannerCardComboBox->setCurrentText(deckStateManager->getMetadata().bannerCard.name);
|
||||
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex)
|
||||
/**
|
||||
* Sets the currently active deck for this tab
|
||||
* @param _deck The deck.
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck)
|
||||
{
|
||||
deckView->clearSelection();
|
||||
deckView->setCurrentIndex(newCardIndex);
|
||||
recursiveExpand(newCardIndex);
|
||||
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
deckLoader->setDeck(_deck);
|
||||
deckModel->setDeckList(&deckLoader->getDeck().deckList);
|
||||
connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree);
|
||||
|
||||
emit requestDeckHistoryClear();
|
||||
historyManagerWidget->setDeckListModel(deckModel);
|
||||
|
||||
syncDisplayWidgetsToModel();
|
||||
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||
{
|
||||
nameEdit->blockSignals(true);
|
||||
nameEdit->setText(deckStateManager->getMetadata().name);
|
||||
nameEdit->setText(deckModel->getDeckList()->getName());
|
||||
nameEdit->blockSignals(false);
|
||||
|
||||
commentsEdit->blockSignals(true);
|
||||
commentsEdit->setText(deckStateManager->getMetadata().comments);
|
||||
commentsEdit->setText(deckModel->getDeckList()->getComments());
|
||||
commentsEdit->blockSignals(false);
|
||||
|
||||
bannerCardComboBox->blockSignals(true);
|
||||
@@ -482,22 +505,46 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||
updateBannerCardComboBox();
|
||||
bannerCardComboBox->blockSignals(false);
|
||||
updateHash();
|
||||
sortDeckModelToDeckView();
|
||||
|
||||
formatComboBox->blockSignals(true);
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat));
|
||||
formatComboBox->blockSignals(false);
|
||||
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
|
||||
}
|
||||
|
||||
deckTagsDisplayWidget->blockSignals(true);
|
||||
deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags);
|
||||
deckTagsDisplayWidget->blockSignals(false);
|
||||
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
|
||||
{
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
||||
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
DeckLoader *DeckEditorDeckDockWidget::getDeckLoader()
|
||||
{
|
||||
return deckLoader;
|
||||
}
|
||||
|
||||
const DeckList &DeckEditorDeckDockWidget::getDeckList() const
|
||||
{
|
||||
return *deckModel->getDeckList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convenience method to get the underlying model instance from the DeckStateManager
|
||||
* Resets the tab to the state for a blank new tab.
|
||||
*/
|
||||
DeckListModel *DeckEditorDeckDockWidget::getModel() const
|
||||
void DeckEditorDeckDockWidget::cleanDeck()
|
||||
{
|
||||
return deckStateManager->getModel();
|
||||
deckModel->cleanList();
|
||||
nameEdit->setText(QString());
|
||||
emit nameChanged();
|
||||
commentsEdit->setText(QString());
|
||||
emit commentsChanged();
|
||||
hashLabel->setText(QString());
|
||||
emit hashChanged();
|
||||
emit deckModified();
|
||||
emit deckChanged();
|
||||
updateBannerCardComboBox();
|
||||
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::selectPrevCard()
|
||||
@@ -524,15 +571,6 @@ void DeckEditorDeckDockWidget::changeSelectedCard(int changeBy)
|
||||
// Get the current index of the selected item
|
||||
auto deckViewCurrentIndex = deckView->currentIndex();
|
||||
|
||||
// For some reason, if the deckModel is modified but the view is not manually reselected,
|
||||
// currentIndex will return an index for the underlying deckModel instead of the proxy.
|
||||
// That index will return an invalid index when indexBelow/indexAbove crosses a header node,
|
||||
// causing the selection to fail to move down.
|
||||
/// \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround
|
||||
if (deckViewCurrentIndex.model() == proxy->sourceModel()) {
|
||||
deckViewCurrentIndex = proxy->mapFromSource(deckViewCurrentIndex);
|
||||
}
|
||||
|
||||
auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy);
|
||||
if (!nextIndex.isValid()) {
|
||||
nextIndex = deckViewCurrentIndex;
|
||||
@@ -578,19 +616,18 @@ void DeckEditorDeckDockWidget::expandAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source index of all the currently selected card nodes in the decklist table.
|
||||
* Gets the index of all the currently selected card nodes in the decklist table.
|
||||
* The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them.
|
||||
*
|
||||
* @return A list containing the source indices of all selected card nodes
|
||||
* @return A model index list containing all selected card nodes
|
||||
*/
|
||||
QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodeSourceIndices() const
|
||||
QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
|
||||
{
|
||||
auto selectedRows = deckView->selectionModel()->selectedRows();
|
||||
|
||||
const auto mapToSource = [this](const QModelIndex &index) { return proxy->mapToSource(index); };
|
||||
std::transform(selectedRows.begin(), selectedRows.end(), selectedRows.begin(), mapToSource);
|
||||
|
||||
const auto notLeafNode = [this](const QModelIndex &sourceIndex) { return getModel()->hasChildren(sourceIndex); };
|
||||
const auto notLeafNode = [this](const QModelIndex &index) {
|
||||
return deckModel->hasChildren(proxy->mapToSource(index));
|
||||
};
|
||||
selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end());
|
||||
|
||||
std::reverse(selectedRows.begin(), selectedRows.end());
|
||||
@@ -604,15 +641,29 @@ void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString &
|
||||
}
|
||||
|
||||
QString zoneName = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : _zoneName;
|
||||
deckStateManager->addCard(card, zoneName);
|
||||
|
||||
emit requestDeckHistorySave(tr("Added (%1): %2 (%3) %4")
|
||||
.arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(),
|
||||
card.getPrinting().getProperty("num")));
|
||||
|
||||
QModelIndex newCardIndex = deckModel->addCard(card, zoneName);
|
||||
|
||||
if (!newCardIndex.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deckView->clearSelection();
|
||||
deckView->setCurrentIndex(newCardIndex);
|
||||
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actIncrementSelection()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
for (const auto &sourceIndex : selectedRows) {
|
||||
offsetCountAtIndex(sourceIndex, true);
|
||||
for (const auto &index : selectedRows) {
|
||||
offsetCountAtIndex(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,17 +672,17 @@ void DeckEditorDeckDockWidget::actSwapCard(const ExactCard &card, const QString
|
||||
QString providerId = card.getPrinting().getUuid();
|
||||
QString collectorNumber = card.getPrinting().getProperty("num");
|
||||
|
||||
QModelIndex foundCard = getModel()->findCard(card.getName(), zoneName, providerId, collectorNumber);
|
||||
QModelIndex foundCard = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
|
||||
if (!foundCard.isValid()) {
|
||||
foundCard = getModel()->findCard(card.getName(), zoneName);
|
||||
foundCard = deckModel->findCard(card.getName(), zoneName);
|
||||
}
|
||||
|
||||
deckStateManager->swapCardAtIndex(foundCard);
|
||||
swapCard(foundCard);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actSwapSelection()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
@@ -639,15 +690,54 @@ void DeckEditorDeckDockWidget::actSwapSelection()
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
for (const auto &sourceIndex : selectedRows) {
|
||||
deckStateManager->swapCardAtIndex(sourceIndex);
|
||||
bool isModified = false;
|
||||
for (const auto ¤tIndex : selectedRows) {
|
||||
if (swapCard(currentIndex)) {
|
||||
isModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
if (isModified) {
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the card at the index between the maindeck and sideboard
|
||||
*
|
||||
* @param currentIndex The index to swap.
|
||||
* @return True if the swap was successful
|
||||
*/
|
||||
bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex)
|
||||
{
|
||||
if (!currentIndex.isValid())
|
||||
return false;
|
||||
const QString cardName = currentIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||
const QString cardProviderID =
|
||||
currentIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||
const QModelIndex gparent = currentIndex.parent().parent();
|
||||
|
||||
if (!gparent.isValid())
|
||||
return false;
|
||||
|
||||
const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
offsetCountAtIndex(currentIndex, false);
|
||||
const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
||||
|
||||
if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) {
|
||||
deckModel->addCard(card, otherZoneName);
|
||||
} else {
|
||||
// Third argument (true) says create the card no matter what, even if not in DB
|
||||
deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName)
|
||||
{
|
||||
if (!card)
|
||||
@@ -655,12 +745,22 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
|
||||
if (card.getInfo().getIsToken())
|
||||
zoneName = DECK_ZONE_TOKENS;
|
||||
|
||||
deckStateManager->decrementCard(card, zoneName);
|
||||
QString providerId = card.getPrinting().getUuid();
|
||||
QString collectorNumber = card.getPrinting().getProperty("num");
|
||||
|
||||
QModelIndex idx = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
|
||||
if (!idx.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deckView->clearSelection();
|
||||
deckView->setCurrentIndex(proxy->mapToSource(idx));
|
||||
offsetCountAtIndex(idx, false);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
@@ -668,8 +768,8 @@ void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
for (const auto &sourceIndex : selectedRows) {
|
||||
offsetCountAtIndex(sourceIndex, false);
|
||||
for (const auto &index : selectedRows) {
|
||||
offsetCountAtIndex(index, false);
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
@@ -677,7 +777,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||
|
||||
void DeckEditorDeckDockWidget::actRemoveCard()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
@@ -685,29 +785,56 @@ void DeckEditorDeckDockWidget::actRemoveCard()
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
for (const auto &sourceIndex : selectedRows) {
|
||||
deckStateManager->removeCardAtIndex(sourceIndex);
|
||||
bool isModified = false;
|
||||
for (const auto &index : selectedRows) {
|
||||
if (!index.isValid() || deckModel->hasChildren(index)) {
|
||||
continue;
|
||||
}
|
||||
QModelIndex sourceIndex = proxy->mapToSource(index);
|
||||
QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||
|
||||
emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName));
|
||||
|
||||
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
|
||||
isModified = true;
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
if (isModified) {
|
||||
emit deckModified();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increments or decrements the amount of the card node at the index by 1.
|
||||
* @param idx The source index
|
||||
* @param idx The proxy index
|
||||
* @param isIncrement If true, increments the count. If false, decrements the count
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool isIncrement)
|
||||
{
|
||||
if (!idx.isValid() || getModel()->hasChildren(idx)) {
|
||||
if (!idx.isValid() || deckModel->hasChildren(idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIncrement) {
|
||||
deckStateManager->incrementCountAtIndex(idx);
|
||||
} else {
|
||||
deckStateManager->decrementCountAtIndex(idx);
|
||||
}
|
||||
QModelIndex sourceIndex = proxy->mapToSource(idx);
|
||||
|
||||
QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
QString providerId =
|
||||
sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString();
|
||||
|
||||
const auto reason = QString(tr("%1 %2 × \"%3\" (%4)"))
|
||||
.arg(isIncrement ? tr("Added") : tr("Removed"))
|
||||
.arg(1)
|
||||
.arg(cardName)
|
||||
.arg(providerId);
|
||||
|
||||
emit requestDeckHistorySave(reason);
|
||||
|
||||
int offset = isIncrement ? 1 : -1;
|
||||
deckModel->offsetCountAtIndex(sourceIndex, offset);
|
||||
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
||||
|
||||
@@ -28,14 +28,22 @@ class DeckEditorDeckDockWidget : public QDockWidget
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
|
||||
|
||||
DeckLoader *deckLoader;
|
||||
DeckListStyleProxy *proxy;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QComboBox *bannerCardComboBox;
|
||||
void createDeckDock();
|
||||
ExactCard getCurrentCard();
|
||||
void retranslateUi();
|
||||
|
||||
QString getDeckName()
|
||||
{
|
||||
return nameEdit->text();
|
||||
}
|
||||
QString getSimpleDeckName()
|
||||
{
|
||||
return nameEdit->text().simplified();
|
||||
}
|
||||
QComboBox *getGroupByComboBox()
|
||||
{
|
||||
return activeGroupCriteriaComboBox;
|
||||
@@ -47,10 +55,15 @@ public:
|
||||
}
|
||||
|
||||
public slots:
|
||||
void cleanDeck();
|
||||
void selectPrevCard();
|
||||
void selectNextCard();
|
||||
void updateBannerCardComboBox();
|
||||
void setDeck(const LoadedDeck &_deck);
|
||||
void syncDisplayWidgetsToModel();
|
||||
void sortDeckModelToDeckView();
|
||||
DeckLoader *getDeckLoader();
|
||||
const DeckList &getDeckList() const;
|
||||
void actAddCard(const ExactCard &card, const QString &zoneName);
|
||||
void actIncrementSelection();
|
||||
void actDecrementCard(const ExactCard &card, QString zoneName);
|
||||
@@ -61,12 +74,17 @@ public slots:
|
||||
void initializeFormats();
|
||||
|
||||
signals:
|
||||
void selectedCardChanged(const ExactCard &card);
|
||||
void nameChanged();
|
||||
void commentsChanged();
|
||||
void hashChanged();
|
||||
void deckChanged();
|
||||
void deckModified();
|
||||
void requestDeckHistorySave(const QString &modificationReason);
|
||||
void requestDeckHistoryClear();
|
||||
void cardChanged(const ExactCard &_card);
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckStateManager *deckStateManager;
|
||||
|
||||
DeckListHistoryManagerWidget *historyManagerWidget;
|
||||
KeySignals deckViewKeySignals;
|
||||
QLabel *nameLabel;
|
||||
@@ -89,18 +107,18 @@ private:
|
||||
|
||||
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||
|
||||
DeckListModel *getModel() const;
|
||||
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
|
||||
[[nodiscard]] QModelIndexList getSelectedCardNodes() const;
|
||||
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
|
||||
|
||||
private slots:
|
||||
void decklistCustomMenu(QPoint point);
|
||||
bool swapCard(const QModelIndex ¤tIndex);
|
||||
void updateCard(QModelIndex, const QModelIndex ¤t);
|
||||
void writeName();
|
||||
void writeComments();
|
||||
void writeBannerCard(int);
|
||||
void applyActiveGroupCriteria();
|
||||
void setSelectedIndex(const QModelIndex &newCardIndex);
|
||||
void updateName(const QString &name);
|
||||
void updateComments();
|
||||
void setBannerCard(int);
|
||||
void setTags(const QStringList &tags);
|
||||
void syncDeckListBannerCardWithComboBox();
|
||||
void updateHash();
|
||||
void refreshShortcuts();
|
||||
void updateShowBannerCardComboBox(bool visible);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#include "deck_list_history_manager_widget.h"
|
||||
|
||||
#include "deck_state_manager.h"
|
||||
|
||||
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager,
|
||||
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel,
|
||||
DeckListStyleProxy *_styleProxy,
|
||||
DeckListHistoryManager *manager,
|
||||
QWidget *parent)
|
||||
: QWidget(parent), deckStateManager(_deckStateManager), styleProxy(_styleProxy)
|
||||
: QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
|
||||
@@ -44,7 +43,8 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de
|
||||
|
||||
connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked);
|
||||
|
||||
connect(deckStateManager, &DeckStateManager::historyChanged, this, &DeckListHistoryManagerWidget::refreshList);
|
||||
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this,
|
||||
&DeckListHistoryManagerWidget::refreshList);
|
||||
|
||||
refreshList();
|
||||
retranslateUi();
|
||||
@@ -58,12 +58,15 @@ void DeckListHistoryManagerWidget::retranslateUi()
|
||||
historyLabel->setText(tr("Click on an entry to revert to that point in the history."));
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::setDeckListModel(DeckListModel *_deckListModel)
|
||||
{
|
||||
deckListModel = _deckListModel;
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::refreshList()
|
||||
{
|
||||
historyList->clear();
|
||||
|
||||
DeckListHistoryManager *historyManager = deckStateManager->getHistoryManager();
|
||||
|
||||
// Fill redo section first (oldest redo at top, newest redo closest to divider)
|
||||
const auto redoStack = historyManager->getRedoStack();
|
||||
for (int i = 0; i < redoStack.size(); ++i) { // iterate forward
|
||||
@@ -95,7 +98,36 @@ void DeckListHistoryManagerWidget::refreshList()
|
||||
redoButton->setEnabled(historyManager->canRedo());
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item)
|
||||
void DeckListHistoryManagerWidget::doUndo()
|
||||
{
|
||||
if (!historyManager->canUndo()) {
|
||||
return;
|
||||
}
|
||||
|
||||
historyManager->undo(deckListModel->getDeckList());
|
||||
deckListModel->rebuildTree();
|
||||
emit deckListModel->layoutChanged();
|
||||
emit requestDisplayWidgetSync();
|
||||
|
||||
refreshList();
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::doRedo()
|
||||
{
|
||||
if (!historyManager->canRedo()) {
|
||||
return;
|
||||
}
|
||||
|
||||
historyManager->redo(deckListModel->getDeckList());
|
||||
deckListModel->rebuildTree();
|
||||
|
||||
emit deckListModel->layoutChanged();
|
||||
emit requestDisplayWidgetSync();
|
||||
|
||||
refreshList();
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item)
|
||||
{
|
||||
// Ignore non-selectable items (like divider)
|
||||
if (!(item->flags() & Qt::ItemIsSelectable)) {
|
||||
@@ -106,24 +138,23 @@ void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item)
|
||||
int index = item->data(Qt::UserRole + 1).toInt();
|
||||
|
||||
if (mode == "redo") {
|
||||
const auto redoStack = deckStateManager->getHistoryManager()->getRedoStack();
|
||||
const auto redoStack = historyManager->getRedoStack();
|
||||
int steps = redoStack.size() - index;
|
||||
deckStateManager->redo(steps);
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
historyManager->redo(deckListModel->getDeckList());
|
||||
}
|
||||
} else if (mode == "undo") {
|
||||
const auto undoStack = deckStateManager->getHistoryManager()->getUndoStack();
|
||||
int steps = undoStack.size() - index;
|
||||
deckStateManager->undo(steps);
|
||||
const auto undoStack = historyManager->getUndoStack();
|
||||
int steps = undoStack.size() - 1 - index;
|
||||
for (int i = 0; i < steps + 1; ++i) {
|
||||
historyManager->undo(deckListModel->getDeckList());
|
||||
}
|
||||
}
|
||||
|
||||
deckListModel->rebuildTree();
|
||||
|
||||
emit deckListModel->layoutChanged();
|
||||
emit requestDisplayWidgetSync();
|
||||
|
||||
refreshList();
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::doUndo()
|
||||
{
|
||||
deckStateManager->undo();
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::doRedo()
|
||||
{
|
||||
deckStateManager->redo();
|
||||
}
|
||||
@@ -14,8 +14,6 @@
|
||||
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
|
||||
class DeckStateManager;
|
||||
|
||||
class DeckListHistoryManagerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -27,19 +25,22 @@ public slots:
|
||||
void retranslateUi();
|
||||
|
||||
public:
|
||||
explicit DeckListHistoryManagerWidget(DeckStateManager *deckStateManager,
|
||||
explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel,
|
||||
DeckListStyleProxy *styleProxy,
|
||||
DeckListHistoryManager *manager,
|
||||
QWidget *parent = nullptr);
|
||||
void setDeckListModel(DeckListModel *_deckListModel);
|
||||
|
||||
private slots:
|
||||
void refreshList();
|
||||
void onListClicked(const QListWidgetItem *item);
|
||||
void onListClicked(QListWidgetItem *item);
|
||||
void doUndo();
|
||||
void doRedo();
|
||||
|
||||
private:
|
||||
DeckStateManager *deckStateManager;
|
||||
DeckListModel *deckListModel;
|
||||
DeckListStyleProxy *styleProxy;
|
||||
DeckListHistoryManager *historyManager;
|
||||
|
||||
QHBoxLayout *layout;
|
||||
QAction *aUndo;
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
#include "deck_state_manager.h"
|
||||
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
||||
|
||||
DeckStateManager::DeckStateManager(QObject *parent)
|
||||
: QObject(parent), deckList(QSharedPointer<DeckList>(new DeckList)),
|
||||
deckListModel(new DeckListModel(this, deckList)), historyManager(new DeckListHistoryManager(this))
|
||||
{
|
||||
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, [this] {
|
||||
setModified(true);
|
||||
emit historyChanged();
|
||||
});
|
||||
connect(deckListModel, &DeckListModel::rowsInserted, this, &DeckStateManager::uniqueCardsChanged);
|
||||
connect(deckListModel, &DeckListModel::rowsRemoved, this, &DeckStateManager::uniqueCardsChanged);
|
||||
}
|
||||
|
||||
const DeckList &DeckStateManager::getDeckList() const
|
||||
{
|
||||
return *deckList.get();
|
||||
}
|
||||
|
||||
LoadedDeck DeckStateManager::toLoadedDeck() const
|
||||
{
|
||||
return {getDeckList(), lastLoadInfo};
|
||||
}
|
||||
|
||||
DeckList::Metadata const &DeckStateManager::getMetadata() const
|
||||
{
|
||||
return deckList->getMetadata();
|
||||
}
|
||||
|
||||
QString DeckStateManager::getSimpleDeckName() const
|
||||
{
|
||||
return deckList->getMetadata().name.simplified();
|
||||
}
|
||||
|
||||
QString DeckStateManager::getDeckHash() const
|
||||
{
|
||||
return deckList->getDeckHash();
|
||||
}
|
||||
|
||||
bool DeckStateManager::isModified() const
|
||||
{
|
||||
return modified;
|
||||
}
|
||||
|
||||
void DeckStateManager::setModified(bool state)
|
||||
{
|
||||
if (state == modified) {
|
||||
return;
|
||||
}
|
||||
|
||||
modified = state;
|
||||
emit isModifiedChanged(modified);
|
||||
}
|
||||
|
||||
bool DeckStateManager::isBlankNewDeck() const
|
||||
{
|
||||
return !isModified() && deckList->isBlankDeck();
|
||||
}
|
||||
|
||||
void DeckStateManager::replaceDeck(const LoadedDeck &deck)
|
||||
{
|
||||
lastLoadInfo = deck.lastLoadInfo;
|
||||
deckList = QSharedPointer<DeckList>(new DeckList(deck.deckList));
|
||||
deckListModel->setDeckList(deckList);
|
||||
|
||||
historyManager->clear();
|
||||
|
||||
setModified(false);
|
||||
emit deckReplaced();
|
||||
}
|
||||
|
||||
void DeckStateManager::clearDeck()
|
||||
{
|
||||
replaceDeck(LoadedDeck());
|
||||
}
|
||||
|
||||
bool DeckStateManager::modifyDeck(const QString &reason, const std::function<bool(DeckListModel *)> &operation)
|
||||
{
|
||||
DeckListMemento memento = deckList->createMemento(reason);
|
||||
bool success = operation(deckListModel);
|
||||
|
||||
if (success) {
|
||||
historyManager->save(memento);
|
||||
doCardModified();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QModelIndex DeckStateManager::modifyDeck(const QString &reason,
|
||||
const std::function<QModelIndex(DeckListModel *)> &operation)
|
||||
{
|
||||
DeckListMemento memento = deckList->createMemento(reason);
|
||||
QModelIndex idx = operation(deckListModel);
|
||||
|
||||
if (idx.isValid()) {
|
||||
historyManager->save(memento);
|
||||
doCardModified();
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
void DeckStateManager::setName(const QString &name)
|
||||
{
|
||||
QString previous = deckList->getName();
|
||||
if (previous == name) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestHistorySave(tr("Rename deck to \"%1\" from \"%2\"").arg(name).arg(previous));
|
||||
deckList->setName(name);
|
||||
|
||||
doMetadataModified();
|
||||
}
|
||||
|
||||
void DeckStateManager::setComments(const QString &comments)
|
||||
{
|
||||
QString previous = deckList->getComments();
|
||||
if (previous == comments) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestHistorySave(tr("Updated comments (was %1 chars, now %2 chars)").arg(previous.size()).arg(comments.size()));
|
||||
deckList->setComments(comments);
|
||||
|
||||
doMetadataModified();
|
||||
}
|
||||
|
||||
void DeckStateManager::setBannerCard(const CardRef &bannerCard)
|
||||
{
|
||||
CardRef previous = deckList->getBannerCard();
|
||||
if (previous == bannerCard) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestHistorySave(tr("Set banner card to %1 (%2)").arg(bannerCard.name).arg(bannerCard.providerId));
|
||||
deckList->setBannerCard(bannerCard);
|
||||
|
||||
doMetadataModified();
|
||||
}
|
||||
|
||||
void DeckStateManager::setTags(const QStringList &tags)
|
||||
{
|
||||
QStringList previous = deckList->getTags();
|
||||
if (previous == tags) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestHistorySave(tr("Tags changed"));
|
||||
deckList->setTags(tags);
|
||||
|
||||
doMetadataModified();
|
||||
}
|
||||
|
||||
void DeckStateManager::setFormat(const QString &format)
|
||||
{
|
||||
if (deckList->getMetadata().gameFormat == format) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestHistorySave(tr("Set format to %1").arg(format));
|
||||
deckListModel->setActiveFormat(format);
|
||||
|
||||
doMetadataModified();
|
||||
}
|
||||
|
||||
QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zoneName)
|
||||
{
|
||||
if (!card) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString reason = tr("Added (%1): %2 (%3) %4")
|
||||
.arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(),
|
||||
card.getPrinting().getProperty("num"));
|
||||
|
||||
QModelIndex idx = modifyDeck(reason, [&card, &zoneName](auto model) { return model->addCard(card, zoneName); });
|
||||
|
||||
if (idx.isValid()) {
|
||||
emit focusIndexChanged(idx);
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString &zoneName)
|
||||
{
|
||||
if (!card)
|
||||
return {};
|
||||
|
||||
QString providerId = card.getPrinting().getUuid();
|
||||
QString collectorNumber = card.getPrinting().getProperty("num");
|
||||
|
||||
QModelIndex idx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
|
||||
if (!idx.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool success = offsetCountAtIndex(idx, false);
|
||||
|
||||
if (!success) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (idx.isValid()) {
|
||||
emit focusIndexChanged(idx);
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
static bool doSwapCard(DeckListModel *model,
|
||||
const QModelIndex &idx,
|
||||
const QString &cardName,
|
||||
const QString &providerId,
|
||||
const QString &otherZone)
|
||||
{
|
||||
bool success = model->offsetCountAtIndex(idx, -1);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, providerId})) {
|
||||
model->addCard(card, otherZone);
|
||||
} else {
|
||||
// Third argument (true) says create the card no matter what, even if not in DB
|
||||
model->addPreferredPrintingCard(cardName, otherZone, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx)
|
||||
{
|
||||
if (!idx.isValid())
|
||||
return false;
|
||||
|
||||
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||
QModelIndex gparent = idx.parent().parent();
|
||||
|
||||
if (!gparent.isValid())
|
||||
return false;
|
||||
|
||||
QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
||||
|
||||
QString reason = tr("Moved to %1 1 × \"%2\" (%3)") //
|
||||
.arg(otherZoneName)
|
||||
.arg(cardName)
|
||||
.arg(providerId);
|
||||
|
||||
return modifyDeck(reason, [&idx, &cardName, &providerId, &otherZoneName](auto model) {
|
||||
return doSwapCard(model, idx, cardName, providerId, otherZoneName);
|
||||
});
|
||||
}
|
||||
|
||||
bool DeckStateManager::removeCardAtIndex(const QModelIndex &idx)
|
||||
{
|
||||
if (!idx.isValid() || deckListModel->hasChildren(idx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||
|
||||
QString reason = tr("Removed \"%1\" (all copies)").arg(cardName);
|
||||
|
||||
return modifyDeck(reason, [&idx](auto model) { return model->removeRow(idx.row(), idx.parent()); });
|
||||
}
|
||||
|
||||
bool DeckStateManager::incrementCountAtIndex(const QModelIndex &idx)
|
||||
{
|
||||
return offsetCountAtIndex(idx, 1);
|
||||
}
|
||||
|
||||
bool DeckStateManager::decrementCountAtIndex(const QModelIndex &idx)
|
||||
{
|
||||
return offsetCountAtIndex(idx, -1);
|
||||
}
|
||||
|
||||
bool DeckStateManager::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
||||
{
|
||||
if (!idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString();
|
||||
|
||||
QString reason = tr("%1 1 × \"%2\" (%3)") //
|
||||
.arg(offset > 0 ? tr("Added") : tr("Removed"))
|
||||
.arg(cardName)
|
||||
.arg(providerId);
|
||||
|
||||
return modifyDeck(reason, [&idx, &offset](auto model) { return model->offsetCountAtIndex(idx, offset); });
|
||||
}
|
||||
|
||||
void DeckStateManager::undo(int steps)
|
||||
{
|
||||
if (!historyManager->canUndo()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < steps; i++) {
|
||||
if (!historyManager->canUndo()) {
|
||||
continue;
|
||||
}
|
||||
historyManager->undo(deckList.get());
|
||||
}
|
||||
|
||||
deckListModel->rebuildTree();
|
||||
|
||||
emit deckListModel->layoutChanged();
|
||||
}
|
||||
|
||||
void DeckStateManager::redo(int steps)
|
||||
{
|
||||
if (!historyManager->canRedo()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < steps; i++) {
|
||||
if (!historyManager->canRedo()) {
|
||||
continue;
|
||||
}
|
||||
historyManager->redo(deckList.get());
|
||||
}
|
||||
|
||||
deckListModel->rebuildTree();
|
||||
|
||||
emit deckListModel->layoutChanged();
|
||||
}
|
||||
|
||||
void DeckStateManager::requestHistorySave(const QString &reason)
|
||||
{
|
||||
historyManager->save(deckList->createMemento(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles updating state and emitting signals whenever the cards are modified
|
||||
*/
|
||||
void DeckStateManager::doCardModified()
|
||||
{
|
||||
setModified(true);
|
||||
emit cardModified();
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles updating state and emitting signals whenever the metadata is modified
|
||||
*/
|
||||
void DeckStateManager::doMetadataModified()
|
||||
{
|
||||
setModified(true);
|
||||
emit metadataModified();
|
||||
emit deckModified();
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
#ifndef COCKATRICE_DECK_STATE_MANAGER_H
|
||||
#define COCKATRICE_DECK_STATE_MANAGER_H
|
||||
|
||||
#include "../../deck_loader/loaded_deck.h"
|
||||
#include "deck_list_model.h"
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class DeckListHistoryManager;
|
||||
|
||||
/**
|
||||
* @brief This class centralizes the management of the state of the deck in the deck editor tab.
|
||||
* It is responsible for owning and managing the DeckListModel, underlying DeckList, load info, and edit history.
|
||||
*
|
||||
* Although this class provides getters for the underlying DeckListModel, you should generally refrain from directly
|
||||
* modifying the returned model. Outside modifications to the deck state should be done through @link
|
||||
* DeckStateManager::modifyDeck and the metadata setters.
|
||||
* Those methods ensure that the history is recorded and correct signals are emitted.
|
||||
*/
|
||||
class DeckStateManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
LoadedDeck::LoadInfo lastLoadInfo;
|
||||
QSharedPointer<DeckList> deckList;
|
||||
DeckListModel *deckListModel;
|
||||
DeckListHistoryManager *historyManager;
|
||||
|
||||
bool modified = false;
|
||||
|
||||
public:
|
||||
explicit DeckStateManager(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Gets the underlying HistoryManager.
|
||||
* @return The DeckListHistoryManager instance
|
||||
*/
|
||||
DeckListHistoryManager *getHistoryManager() const
|
||||
{
|
||||
return historyManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the underlying DeckListModel.
|
||||
* You should generally refrain modifying the returned model directly.
|
||||
* However, it's fine (and intended) to perform queries on the returned model.
|
||||
* @return The DeckListModel instance
|
||||
*/
|
||||
DeckListModel *getModel() const
|
||||
{
|
||||
return deckListModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a view of the current deck.
|
||||
*/
|
||||
const DeckList &getDeckList() const;
|
||||
|
||||
/**
|
||||
* @brief Creates a LoadedDeck containing the contents of the current deck and the current LoadInfo.
|
||||
*
|
||||
* @return A new LoadedDeck instance.
|
||||
*/
|
||||
LoadedDeck toLoadedDeck() const;
|
||||
|
||||
/**
|
||||
* @brief Gets a view of the metadata in the DeckList
|
||||
*/
|
||||
DeckList::Metadata const &getMetadata() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the deck's simplified name.
|
||||
*/
|
||||
QString getSimpleDeckName() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the deck hash.
|
||||
*/
|
||||
QString getDeckHash() const;
|
||||
|
||||
/**
|
||||
* @brief Checks if the deck has been modified since it was last saved
|
||||
*/
|
||||
bool isModified() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the new isModified state, emitting a signal if the state changed.
|
||||
* This class will automatically update its isModified state, but you may need to set it manually to handle, for
|
||||
* example, saving.
|
||||
* @param state The state
|
||||
*/
|
||||
void setModified(bool state);
|
||||
|
||||
/**
|
||||
* @brief Checks if the deck state is as if it was a new deck
|
||||
*/
|
||||
bool isBlankNewDeck() const;
|
||||
|
||||
/**
|
||||
* @brief Overwrites the current deck with a new deck, resetting all history
|
||||
* @param deck The new deck.
|
||||
*/
|
||||
void replaceDeck(const LoadedDeck &deck);
|
||||
|
||||
/**
|
||||
* @brief Resets the deck to a blank new deck, resetting all history.
|
||||
*/
|
||||
void clearDeck();
|
||||
|
||||
/**
|
||||
* @brief Sets the lastLoadInfo.
|
||||
* @param loadInfo The lastLoadInfo
|
||||
*/
|
||||
void setLastLoadInfo(const LoadedDeck::LoadInfo &loadInfo)
|
||||
{
|
||||
lastLoadInfo = loadInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history.
|
||||
*
|
||||
* The operation is a function that accepts a DeckListModel that it operates upon, and returns a bool.
|
||||
*
|
||||
* This method will pass the underlying DeckListModel into the operation function. The function can call methods on
|
||||
* the model to modify the deck.
|
||||
* The function should return a bool to indicate success/failure.
|
||||
*
|
||||
* If the operation returns true, the state of the deck before the operation is ran is saved to the history, and the
|
||||
* isModified state is updated.
|
||||
* If the operation returns false, the history and isModified state is not updated.
|
||||
*
|
||||
* Note that even if the operation fails, any modifications to the model will already have been made.
|
||||
* It's recommended for the operation to always return true if any modification has already been made to the model,
|
||||
* as not doing that may cause the state to become desynced.
|
||||
*
|
||||
* @param reason The reason to display in the history
|
||||
* @param operation The modification operation.
|
||||
* @return The bool returned from the operation
|
||||
*/
|
||||
bool modifyDeck(const QString &reason, const std::function<bool(DeckListModel *)> &operation);
|
||||
|
||||
/**
|
||||
* @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history.
|
||||
*
|
||||
* The operation is a function that accepts a DeckListModel that it operates upon, and returns a QModelIndex.
|
||||
* If the index is invalid, then the operation is considered to be a failure.
|
||||
*
|
||||
* See the other @link DeckStateManager::modifyDeck for more info about the behavior of this method.
|
||||
*
|
||||
* @param reason The reason to display in the history
|
||||
* @param operation The modification operation.
|
||||
* @return The QModelIndex returned from the operation
|
||||
*/
|
||||
QModelIndex modifyDeck(const QString &reason, const std::function<QModelIndex(DeckListModel *)> &operation);
|
||||
|
||||
/// @name Metadata setters
|
||||
/// @brief These methods set the metadata. Will no-op if the new value is the same as the current value.
|
||||
/// Saves the operation to history if successful.
|
||||
///@{
|
||||
void setName(const QString &name);
|
||||
void setComments(const QString &comments);
|
||||
void setBannerCard(const CardRef &bannerCard);
|
||||
void setTags(const QStringList &tags);
|
||||
void setFormat(const QString &format);
|
||||
///@}
|
||||
|
||||
/**
|
||||
* @brief Adds the given card to the given zone.
|
||||
* Saves the operation to history if successful.
|
||||
*
|
||||
* @param card The card to add
|
||||
* @param zoneName The zone to add the card to
|
||||
* @return The index of the added card
|
||||
*/
|
||||
QModelIndex addCard(const ExactCard &card, const QString &zoneName);
|
||||
|
||||
/**
|
||||
* @brief Removes 1 copy of the given card from the given zone.
|
||||
* Saves the operation to history if successful.
|
||||
*
|
||||
* @param card The card to remove
|
||||
* @param zoneName The zone to remove the card from
|
||||
* @return The index of the removed card. Will be invalid if the last copy was removed.
|
||||
*/
|
||||
QModelIndex decrementCard(const ExactCard &card, const QString &zoneName);
|
||||
|
||||
/**
|
||||
* @brief Swaps one copy of the card at the given index between the maindeck and sideboard.
|
||||
* No-ops if index is invalid or not a card node.
|
||||
* Saves the operation to history if successful.
|
||||
*
|
||||
* @param idx The model index
|
||||
* @return Whether the operation was successfully performed
|
||||
*/
|
||||
bool swapCardAtIndex(const QModelIndex &idx);
|
||||
|
||||
/**
|
||||
* @brief Removes all copies of the card at the given index.
|
||||
* No-ops if index is invalid or not a card node.
|
||||
* Saves the operation to history if successful.
|
||||
*
|
||||
* @param idx The model index
|
||||
* @return Whether the operation was successfully performed
|
||||
*/
|
||||
bool removeCardAtIndex(const QModelIndex &idx);
|
||||
|
||||
/**
|
||||
* @brief Increments the number of copies of the card at the given index by 1.
|
||||
* No-ops if index is invalid or not a card node.
|
||||
* Saves the operation to history if successful.
|
||||
*
|
||||
* @param idx The model index
|
||||
* @return Whether the operation was successfully performed
|
||||
*/
|
||||
bool incrementCountAtIndex(const QModelIndex &idx);
|
||||
|
||||
/**
|
||||
* @brief Decrements the number of copies of the card at the given index by 1.
|
||||
* No-ops if index is invalid or not a card node.
|
||||
* Saves the operation to history if successful.
|
||||
*
|
||||
* @param idx The model index
|
||||
* @return Whether the operation was successfully performed
|
||||
*/
|
||||
bool decrementCountAtIndex(const QModelIndex &idx);
|
||||
|
||||
/**
|
||||
* Undoes n steps of the history, setting the decklist state and updating the current step in the historyManager.
|
||||
* @param steps Number of steps to undo.
|
||||
*/
|
||||
void undo(int steps = 1);
|
||||
|
||||
/**
|
||||
* Redoes n steps of the history, setting the decklist state and updating the current step in the historyManager.
|
||||
* @param steps Number of steps to redo.
|
||||
*/
|
||||
void redo(int steps = 1);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* Saves the current decklist state to history.
|
||||
* @param reason The reason that is shown in the history.
|
||||
*/
|
||||
void requestHistorySave(const QString &reason);
|
||||
|
||||
private:
|
||||
bool offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void doCardModified();
|
||||
void doMetadataModified();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* A modification has been made to the cards in the deck
|
||||
*/
|
||||
void cardModified();
|
||||
|
||||
/**
|
||||
* A card that wasn't previously in the deck was added to the deck, or the last copy of a card was removed from the
|
||||
* deck.
|
||||
*/
|
||||
void uniqueCardsChanged();
|
||||
|
||||
/**
|
||||
* A modification has been made to the metadata in the deck
|
||||
*/
|
||||
void metadataModified();
|
||||
|
||||
/**
|
||||
* A modification has been made to the cards or metadata in the deck
|
||||
*/
|
||||
void deckModified();
|
||||
|
||||
/**
|
||||
* The history has been greatly changed and needs to be reloaded.
|
||||
*/
|
||||
void historyChanged();
|
||||
|
||||
/**
|
||||
* The deck has been completely changed.
|
||||
*/
|
||||
void deckReplaced();
|
||||
|
||||
/**
|
||||
* The isModified state of the deck has changed
|
||||
* @param isModified the new state
|
||||
*/
|
||||
void isModifiedChanged(bool isModified);
|
||||
|
||||
/**
|
||||
* The selected card on any views connected to this deck should be changed to this index.
|
||||
* @param index The model index
|
||||
*/
|
||||
void focusIndexChanged(QModelIndex index);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_STATE_MANAGER_H
|
||||
@@ -49,7 +49,7 @@ DlgForgotPasswordReset::DlgForgotPasswordReset(QWidget *parent) : QDialog(parent
|
||||
playernameEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
playernameLabel->setBuddy(playernameEdit);
|
||||
|
||||
tokenLabel = new QLabel(tr("Token") + ":");
|
||||
tokenLabel = new QLabel(tr("Token:"));
|
||||
tokenEdit = new QLineEdit();
|
||||
tokenEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
tokenLabel->setBuddy(tokenLabel);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "../../deck_loader/card_node_function.h"
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "../deck_editor/deck_state_manager.h"
|
||||
#include "../interface/widgets/cards/card_info_picture_widget.h"
|
||||
#include "../interface/widgets/general/layout_containers/flow_widget.h"
|
||||
|
||||
@@ -22,8 +21,7 @@
|
||||
#include <qdrag.h>
|
||||
#include <qevent.h>
|
||||
|
||||
DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManger)
|
||||
: QDialog(parent), deckStateManager(deckStateManger)
|
||||
DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckListModel *_model) : QDialog(parent), model(_model)
|
||||
{
|
||||
setMinimumSize(500, 500);
|
||||
setAcceptDrops(true);
|
||||
@@ -167,39 +165,36 @@ void DlgSelectSetForCards::actOK()
|
||||
|
||||
if (modifiedSetsAndCardsMap.isEmpty()) {
|
||||
accept(); // Nothing to do
|
||||
return;
|
||||
} else {
|
||||
emit deckAboutToBeModified(tr("Bulk modified printings."));
|
||||
}
|
||||
|
||||
auto bulkModify = [&modifiedSetsAndCardsMap](DeckListModel *model) {
|
||||
for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) {
|
||||
for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) {
|
||||
swapPrinting(model, modifiedSet, card);
|
||||
}
|
||||
for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) {
|
||||
for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) {
|
||||
swapPrinting(model, modifiedSet, card);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
deckStateManager->modifyDeck(tr("Bulk modified printings."), bulkModify);
|
||||
}
|
||||
|
||||
if (!modifiedSetsAndCardsMap.isEmpty()) {
|
||||
emit deckModified();
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
void DlgSelectSetForCards::actClear()
|
||||
{
|
||||
deckStateManager->modifyDeck(tr("Cleared all printing information."), [](auto model) {
|
||||
model->forEachCard(CardNodeFunction::ClearPrintingData());
|
||||
return true;
|
||||
});
|
||||
emit deckAboutToBeModified(tr("Cleared all printing information."));
|
||||
model->forEachCard(CardNodeFunction::ClearPrintingData());
|
||||
emit deckModified();
|
||||
accept();
|
||||
}
|
||||
|
||||
void DlgSelectSetForCards::actSetAllToPreferred()
|
||||
{
|
||||
deckStateManager->modifyDeck(tr("Set all printings to preferred."), [](auto model) {
|
||||
model->forEachCard(CardNodeFunction::ClearPrintingData());
|
||||
model->forEachCard(CardNodeFunction::SetProviderIdToPreferred());
|
||||
return true;
|
||||
});
|
||||
emit deckAboutToBeModified(tr("Set all printings to preferred."));
|
||||
model->forEachCard(CardNodeFunction::ClearPrintingData());
|
||||
model->forEachCard(CardNodeFunction::SetProviderIdToPreferred());
|
||||
emit deckModified();
|
||||
accept();
|
||||
}
|
||||
|
||||
@@ -232,8 +227,10 @@ void DlgSelectSetForCards::sortSetsByCount()
|
||||
QMap<QString, int> DlgSelectSetForCards::getSetsForCards()
|
||||
{
|
||||
QMap<QString, int> setCounts;
|
||||
if (!model)
|
||||
return setCounts;
|
||||
|
||||
QList<QString> cardNames = deckStateManager->getModel()->getCardNames();
|
||||
QList<QString> cardNames = model->getCardNames();
|
||||
|
||||
for (auto cardName : cardNames) {
|
||||
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
|
||||
@@ -272,7 +269,7 @@ void DlgSelectSetForCards::updateCardLists()
|
||||
}
|
||||
}
|
||||
|
||||
QList<QString> cardNames = deckStateManager->getModel()->getCardNames();
|
||||
QList<QString> cardNames = model->getCardNames();
|
||||
|
||||
for (auto cardName : cardNames) {
|
||||
bool found = false;
|
||||
@@ -354,8 +351,10 @@ void DlgSelectSetForCards::dropEvent(QDropEvent *event)
|
||||
QMap<QString, QStringList> DlgSelectSetForCards::getCardsForSets()
|
||||
{
|
||||
QMap<QString, QStringList> setCards;
|
||||
if (!model)
|
||||
return setCards;
|
||||
|
||||
QList<QString> cardNames = deckStateManager->getModel()->getCardNames();
|
||||
QList<QString> cardNames = model->getCardNames();
|
||||
|
||||
for (auto cardName : cardNames) {
|
||||
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
|
||||
class DeckStateManager;
|
||||
class SetEntryWidget; // Forward declaration
|
||||
|
||||
class DlgSelectSetForCards : public QDialog
|
||||
@@ -26,7 +25,7 @@ class DlgSelectSetForCards : public QDialog
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManager);
|
||||
explicit DlgSelectSetForCards(QWidget *parent, DeckListModel *_model);
|
||||
void retranslateUi();
|
||||
void sortSetsByCount();
|
||||
QMap<QString, QStringList> getCardsForSets();
|
||||
@@ -38,6 +37,7 @@ public:
|
||||
signals:
|
||||
void widgetOrderChanged();
|
||||
void orderChanged();
|
||||
void deckAboutToBeModified(const QString &reason);
|
||||
void deckModified();
|
||||
|
||||
public slots:
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
QLabel *modifiedCardsLabel;
|
||||
QWidget *listContainer;
|
||||
QListWidget *listWidget;
|
||||
DeckStateManager *deckStateManager;
|
||||
DeckListModel *model;
|
||||
QMap<QString, SetEntryWidget *> setEntries;
|
||||
QPushButton *clearButton;
|
||||
QPushButton *setAllToPreferredButton;
|
||||
|
||||
@@ -441,7 +441,7 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||
});
|
||||
|
||||
homeTabBackgroundShuffleFrequencySpinBox.setRange(0, 3600);
|
||||
homeTabBackgroundShuffleFrequencySpinBox.setSuffix(QString(" ") + tr("seconds"));
|
||||
homeTabBackgroundShuffleFrequencySpinBox.setSuffix(tr(" seconds"));
|
||||
homeTabBackgroundShuffleFrequencySpinBox.setValue(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency());
|
||||
connect(&homeTabBackgroundShuffleFrequencySpinBox, qOverload<int>(&QSpinBox::valueChanged),
|
||||
&SettingsCache::instance(), &SettingsCache::setHomeTabBackgroundShuffleFrequency);
|
||||
@@ -462,13 +462,8 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||
showShortcutsCheckBox.setChecked(settings.getShowShortcuts());
|
||||
connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged);
|
||||
|
||||
showGameSelectorFilterToolbarCheckBox.setChecked(settings.getShowGameSelectorFilterToolbar());
|
||||
connect(&showGameSelectorFilterToolbarCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
|
||||
&SettingsCache::setShowGameSelectorFilterToolbar);
|
||||
|
||||
auto *menuGrid = new QGridLayout;
|
||||
menuGrid->addWidget(&showShortcutsCheckBox, 0, 0);
|
||||
menuGrid->addWidget(&showGameSelectorFilterToolbarCheckBox, 1, 0);
|
||||
|
||||
menuGroupBox = new QGroupBox;
|
||||
menuGroupBox->setLayout(menuGrid);
|
||||
@@ -732,7 +727,6 @@ void AppearanceSettingsPage::retranslateUi()
|
||||
|
||||
menuGroupBox->setTitle(tr("Menu settings"));
|
||||
showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus"));
|
||||
showGameSelectorFilterToolbarCheckBox.setText(tr("Show game filter toolbar above list in room tab"));
|
||||
|
||||
cardsGroupBox->setTitle(tr("Card rendering"));
|
||||
displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture"));
|
||||
|
||||
@@ -120,7 +120,6 @@ private:
|
||||
QLabel minPlayersForMultiColumnLayoutLabel;
|
||||
QLabel maxFontSizeForCardsLabel;
|
||||
QCheckBox showShortcutsCheckBox;
|
||||
QCheckBox showGameSelectorFilterToolbarCheckBox;
|
||||
QCheckBox displayCardNamesCheckBox;
|
||||
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
|
||||
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#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)));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#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
|
||||
@@ -1,215 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#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,140 +0,0 @@
|
||||
#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
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#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
|
||||
@@ -1,205 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#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
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
#include "color_bar.h"
|
||||
|
||||
#include <QLinearGradient>
|
||||
@@ -11,12 +11,16 @@
|
||||
* UI elements for managing card counts in both the mainboard and sideboard zones.
|
||||
*
|
||||
* @param parent The parent widget.
|
||||
* @param deckStateManager Pointer to the DeckStateManager
|
||||
* @param deckEditor Pointer to the TabDeckEditor.
|
||||
* @param deckModel Pointer to the DeckListModel.
|
||||
* @param deckView Pointer to the QTreeView for the deck display.
|
||||
* @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing.
|
||||
* @param rootCard The root card for the widget.
|
||||
*/
|
||||
AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent,
|
||||
DeckStateManager *deckStateManager,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
const ExactCard &rootCard)
|
||||
: QWidget(parent), cardSizeSlider(cardSizeSlider)
|
||||
@@ -28,9 +32,11 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent,
|
||||
setContentsMargins(5, 5, 5, 5); // Padding around the text
|
||||
|
||||
zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard"));
|
||||
buttonBoxMainboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_MAIN);
|
||||
buttonBoxMainboard =
|
||||
new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_MAIN);
|
||||
zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard"));
|
||||
buttonBoxSideboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_SIDE);
|
||||
buttonBoxSideboard =
|
||||
new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_SIDE);
|
||||
|
||||
layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop);
|
||||
@@ -72,12 +78,6 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage)
|
||||
repaint();
|
||||
}
|
||||
|
||||
void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount)
|
||||
{
|
||||
buttonBoxMainboard->setAmount(mainboardAmount);
|
||||
buttonBoxSideboard->setAmount(sideboardAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the card count in the mainboard zone.
|
||||
*
|
||||
@@ -85,7 +85,7 @@ void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmou
|
||||
*/
|
||||
int AllZonesCardAmountWidget::getMainboardAmount()
|
||||
{
|
||||
return buttonBoxMainboard->getAmount();
|
||||
return buttonBoxMainboard->countCardsInZone(DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,15 +95,7 @@ int AllZonesCardAmountWidget::getMainboardAmount()
|
||||
*/
|
||||
int AllZonesCardAmountWidget::getSideboardAmount()
|
||||
{
|
||||
return buttonBoxSideboard->getAmount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the amount is at least one in either the mainboard or sideboard.
|
||||
*/
|
||||
bool AllZonesCardAmountWidget::isNonZero()
|
||||
{
|
||||
return getMainboardAmount() > 0 || getSideboardAmount() > 0;
|
||||
return buttonBoxSideboard->countCardsInZone(DECK_ZONE_SIDE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,13 +18,13 @@ class AllZonesCardAmountWidget : public QWidget
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AllZonesCardAmountWidget(QWidget *parent,
|
||||
DeckStateManager *deckStateManager,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
const ExactCard &rootCard);
|
||||
int getMainboardAmount();
|
||||
int getSideboardAmount();
|
||||
bool isNonZero();
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
@@ -33,7 +33,6 @@ public:
|
||||
|
||||
public slots:
|
||||
void adjustFontSize(int scalePercentage);
|
||||
void setAmounts(int mainboardAmount, int sideboardAmount);
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "card_amount_widget.h"
|
||||
|
||||
#include "../deck_editor/deck_state_manager.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
|
||||
@@ -9,17 +7,22 @@
|
||||
* @brief Constructs a widget for displaying and controlling the card count in a specific zone.
|
||||
*
|
||||
* @param parent The parent widget.
|
||||
* @param deckEditor Pointer to the TabDeckEditor instance.
|
||||
* @param deckModel Pointer to the DeckListModel instance.
|
||||
* @param deckView Pointer to the QTreeView displaying the deck.
|
||||
* @param cardSizeSlider Pointer to the QSlider for adjusting font size.
|
||||
* @param rootCard The root card to manage within the widget.
|
||||
* @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE).
|
||||
*/
|
||||
CardAmountWidget::CardAmountWidget(QWidget *parent,
|
||||
DeckStateManager *deckStateManager,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
const ExactCard &rootCard,
|
||||
const QString &zoneName)
|
||||
: QWidget(parent), deckStateManager(deckStateManager), cardSizeSlider(cardSizeSlider), rootCard(rootCard),
|
||||
zoneName(zoneName), hovered(false)
|
||||
: QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider),
|
||||
rootCard(rootCard), zoneName(zoneName), hovered(false)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -45,26 +48,23 @@ CardAmountWidget::CardAmountWidget(QWidget *parent,
|
||||
connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingSideboard);
|
||||
}
|
||||
|
||||
cardCountInZone = new QLabel(QString::number(amount), this);
|
||||
cardCountInZone = new QLabel(QString::number(countCardsInZone(zoneName)), this);
|
||||
cardCountInZone->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(decrementButton);
|
||||
layout->addWidget(cardCountInZone);
|
||||
layout->addWidget(incrementButton);
|
||||
|
||||
// React to model changes
|
||||
connect(deckModel, &DeckListModel::dataChanged, this, &CardAmountWidget::updateCardCount);
|
||||
connect(deckModel, &QAbstractItemModel::rowsRemoved, this, &CardAmountWidget::updateCardCount);
|
||||
|
||||
// Connect slider for dynamic font size adjustment
|
||||
connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize);
|
||||
}
|
||||
|
||||
int CardAmountWidget::getAmount()
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
|
||||
void CardAmountWidget::setAmount(int _amount)
|
||||
{
|
||||
amount = _amount;
|
||||
updateCardCount();
|
||||
if (deckEditor) {
|
||||
connect(this, &CardAmountWidget::deckModified, deckEditor, &AbstractTabDeckEditor::onDeckHistorySaveRequested);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +132,7 @@ void CardAmountWidget::adjustFontSize(int scalePercentage)
|
||||
*/
|
||||
void CardAmountWidget::updateCardCount()
|
||||
{
|
||||
cardCountInZone->setText("<font color='white'>" + QString::number(amount) + "</font>");
|
||||
cardCountInZone->setText("<font color='white'>" + QString::number(countCardsInZone(zoneName)) + "</font>");
|
||||
layout->invalidate();
|
||||
layout->activate();
|
||||
}
|
||||
@@ -151,7 +151,9 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model,
|
||||
|
||||
// Check if a card without a providerId already exists in the deckModel and replace it, if so.
|
||||
if (existing.isValid() && existing != newCardIndex && replaceProviderless) {
|
||||
model->offsetCountAtIndex(newCardIndex, extraCopies);
|
||||
for (int i = 0; i < extraCopies; i++) {
|
||||
model->addCard(rootCard, zone);
|
||||
}
|
||||
model->removeRow(existing.row(), existing.parent());
|
||||
}
|
||||
|
||||
@@ -168,7 +170,7 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model,
|
||||
void CardAmountWidget::addPrinting(const QString &zone)
|
||||
{
|
||||
// Check if we will need to add extra copies due to replacing copies without providerIds
|
||||
QModelIndex existing = deckStateManager->getModel()->findCard(rootCard.getName(), zone);
|
||||
QModelIndex existing = deckModel->findCard(rootCard.getName(), zone);
|
||||
|
||||
int extraCopies = 0;
|
||||
bool replacingProviderless = false;
|
||||
@@ -177,8 +179,8 @@ void CardAmountWidget::addPrinting(const QString &zone)
|
||||
QString foundProviderId =
|
||||
existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString();
|
||||
if (foundProviderId.isEmpty()) {
|
||||
int existingAmount = existing.data(Qt::DisplayRole).toInt();
|
||||
extraCopies = existingAmount - 1; // One less because we *always* add one
|
||||
int amount = existing.data(Qt::DisplayRole).toInt();
|
||||
extraCopies = amount - 1; // One less because we *always* add one
|
||||
replacingProviderless = true;
|
||||
}
|
||||
}
|
||||
@@ -192,13 +194,15 @@ void CardAmountWidget::addPrinting(const QString &zone)
|
||||
.arg(rootCard.getPrinting().getUuid())
|
||||
.arg(replacingProviderless ? " (replaced providerless printings)" : "");
|
||||
|
||||
emit deckModified(reason);
|
||||
|
||||
// Add the card and expand the list UI
|
||||
QModelIndex newCardIndex = deckStateManager->modifyDeck(reason, [&](auto model) {
|
||||
return addAndReplacePrintings(model, existing, rootCard, zone, extraCopies, replacingProviderless);
|
||||
});
|
||||
auto newCardIndex = addAndReplacePrintings(deckModel, existing, rootCard, zone, extraCopies, replacingProviderless);
|
||||
|
||||
if (newCardIndex.isValid()) {
|
||||
emit deckStateManager->focusIndexChanged(newCardIndex);
|
||||
deckView->setCurrentIndex(newCardIndex);
|
||||
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
deckEditor->setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,9 +252,31 @@ void CardAmountWidget::decrementCardHelper(const QString &zone)
|
||||
.arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard")
|
||||
.arg(rootCard.getPrinting().getUuid());
|
||||
|
||||
deckStateManager->modifyDeck(reason, [this, &zone](auto model) {
|
||||
QModelIndex idx = model->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
|
||||
emit deckModified(reason);
|
||||
|
||||
QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
|
||||
rootCard.getPrinting().getProperty("num"));
|
||||
return model->offsetCountAtIndex(idx, -1);
|
||||
});
|
||||
|
||||
deckModel->offsetCountAtIndex(idx, -1);
|
||||
deckEditor->setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Counts the number of cards in a specific zone (mainboard or sideboard).
|
||||
*
|
||||
* @param deckZone The name of the zone (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE).
|
||||
* @return The number of cards in the zone.
|
||||
*/
|
||||
int CardAmountWidget::countCardsInZone(const QString &deckZone)
|
||||
{
|
||||
QString uuid = rootCard.getPrinting().getUuid();
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us.
|
||||
}
|
||||
|
||||
QList<ExactCard> cards = deckModel->getCardsForZone(deckZone);
|
||||
|
||||
return std::count_if(cards.cbegin(), cards.cend(),
|
||||
[&uuid](const ExactCard &card) { return card.getPrinting().getUuid() == uuid; });
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user