diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake
index 6db821dd7..3025934f4 100644
--- a/cmake/FindQtRuntime.cmake
+++ b/cmake/FindQtRuntime.cmake
@@ -23,6 +23,7 @@ if(WITH_CLIENT)
Svg
WebSockets
Widgets
+ Xml
)
endif()
if(WITH_ORACLE)
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index 27709eb72..4b4567e95 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -35,6 +35,7 @@ set(cockatrice_SOURCES
src/deck/deck_list_model.cpp
src/deck/deck_stats_interface.cpp
src/dialogs/dlg_connect.cpp
+ src/dialogs/dlg_convert_deck_to_cod_format.cpp
src/dialogs/dlg_create_token.cpp
src/dialogs/dlg_create_game.cpp
src/dialogs/dlg_edit_avatar.cpp
@@ -63,8 +64,6 @@ set(cockatrice_SOURCES
src/game/filters/filter_tree.cpp
src/game/filters/filter_tree_model.cpp
src/client/ui/layouts/flow_layout.cpp
- src/client/ui/layouts/horizontal_flow_layout.cpp
- src/client/ui/layouts/vertical_flow_layout.cpp
src/client/ui/widgets/general/layout_containers/flow_widget.cpp
src/game/game_scene.cpp
src/game/game_selector.cpp
@@ -82,6 +81,7 @@ set(cockatrice_SOURCES
src/utility/logger.cpp
src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
+ src/client/ui/widgets/general/display/banner_widget.cpp
src/client/ui/widgets/general/display/labeled_input.cpp
src/client/ui/widgets/general/display/dynamic_font_size_label.cpp
src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp
@@ -150,6 +150,19 @@ set(cockatrice_SOURCES
src/client/tabs/tab_room.cpp
src/client/tabs/tab_server.cpp
src/client/tabs/tab_supervisor.cpp
+ src/client/tabs/api/edhrec/tab_edhrec.cpp
+ src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp
+ src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp
+ src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp
+ src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp
+ src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp
src/game/zones/table_zone.cpp
src/client/tapped_out_interface.cpp
src/client/ui/theme_manager.cpp
@@ -175,6 +188,7 @@ set(cockatrice_SOURCES
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
+ src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index c9bd6d2bb..b59092ce2 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -329,23 +329,12 @@
resources/replay/fastforward.svg
resources/replay/pause.svg
- resources/userlevels/normal.svg
- resources/userlevels/registered.svg
- resources/userlevels/registered_buddy.svg
- resources/userlevels/registered_vip.svg
- resources/userlevels/registered_vip_buddy.svg
- resources/userlevels/registered_donator.svg
- resources/userlevels/registered_donator_buddy.svg
-
- resources/userlevels/moderator.svg
- resources/userlevels/moderator_buddy.svg
- resources/userlevels/moderator_vip.svg
- resources/userlevels/moderator_vip_buddy.svg
-
- resources/userlevels/admin.svg
- resources/userlevels/admin_buddy.svg
- resources/userlevels/admin_vip.svg
- resources/userlevels/admin_vip_buddy.svg
+ resources/usericons/pawn_single.svg
+ resources/usericons/pawn_double.svg
+ resources/usericons/pawn_vip_single.svg
+ resources/usericons/pawn_vip_double.svg
+ resources/usericons/star_single.svg
+ resources/usericons/star_double.svg
resources/tips/images/accounts_tab.png
diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini
index ddfbec51b..6e991124d 100644
--- a/cockatrice/resources/config/qtlogging.ini
+++ b/cockatrice/resources/config/qtlogging.ini
@@ -23,6 +23,8 @@
# servers_settings = false
# shortcuts_settings = false
+# remote_client = false
+
# player = false
# game_scene = false
# game_scene.player_addition_removal = false
@@ -44,4 +46,6 @@
# cockatrice_xml.xml_4_parser = false
# card_list = false
+# pixel_map_generator = false
+
# filter_string = false
\ No newline at end of file
diff --git a/cockatrice/resources/userlevels/normal.svg b/cockatrice/resources/usericons/pawn_double.svg
similarity index 72%
rename from cockatrice/resources/userlevels/normal.svg
rename to cockatrice/resources/usericons/pawn_double.svg
index 172d8620c..b9f72d385 100644
--- a/cockatrice/resources/userlevels/normal.svg
+++ b/cockatrice/resources/usericons/pawn_double.svg
@@ -2,20 +2,20 @@
diff --git a/cockatrice/resources/userlevels/registered_donator.svg b/cockatrice/resources/usericons/pawn_single.svg
similarity index 78%
rename from cockatrice/resources/userlevels/registered_donator.svg
rename to cockatrice/resources/usericons/pawn_single.svg
index 1ec430e68..2f0c94ae8 100644
--- a/cockatrice/resources/userlevels/registered_donator.svg
+++ b/cockatrice/resources/usericons/pawn_single.svg
@@ -2,20 +2,20 @@
diff --git a/cockatrice/resources/userlevels/moderator_vip.svg b/cockatrice/resources/usericons/pawn_vip_double.svg
similarity index 96%
rename from cockatrice/resources/userlevels/moderator_vip.svg
rename to cockatrice/resources/usericons/pawn_vip_double.svg
index 6d93983e3..0d002a791 100644
--- a/cockatrice/resources/userlevels/moderator_vip.svg
+++ b/cockatrice/resources/usericons/pawn_vip_double.svg
@@ -337,19 +337,19 @@
id="layer1"
transform="translate(0,-952.36218)">
+ id="right" />
+ id="left" />
diff --git a/cockatrice/resources/userlevels/registered_buddy.svg b/cockatrice/resources/usericons/star_single.svg
similarity index 97%
rename from cockatrice/resources/userlevels/registered_buddy.svg
rename to cockatrice/resources/usericons/star_single.svg
index 2d2ca600b..acc5e4da6 100644
--- a/cockatrice/resources/userlevels/registered_buddy.svg
+++ b/cockatrice/resources/usericons/star_single.svg
@@ -109,7 +109,7 @@
inkscape:connector-curvature="0" />
diff --git a/cockatrice/resources/userlevels/admin.svg b/cockatrice/resources/userlevels/admin.svg
deleted file mode 100644
index 5675cc0f8..000000000
--- a/cockatrice/resources/userlevels/admin.svg
+++ /dev/null
@@ -1,301 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/userlevels/admin_buddy.svg b/cockatrice/resources/userlevels/admin_buddy.svg
deleted file mode 100644
index 5ff5c87aa..000000000
--- a/cockatrice/resources/userlevels/admin_buddy.svg
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
diff --git a/cockatrice/resources/userlevels/admin_vip_buddy.svg b/cockatrice/resources/userlevels/admin_vip_buddy.svg
deleted file mode 100644
index 5ff5c87aa..000000000
--- a/cockatrice/resources/userlevels/admin_vip_buddy.svg
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
diff --git a/cockatrice/resources/userlevels/moderator.svg b/cockatrice/resources/userlevels/moderator.svg
deleted file mode 100644
index 37ac156b9..000000000
--- a/cockatrice/resources/userlevels/moderator.svg
+++ /dev/null
@@ -1,294 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/userlevels/moderator_vip_buddy.svg b/cockatrice/resources/userlevels/moderator_vip_buddy.svg
deleted file mode 100644
index 73a3efb04..000000000
--- a/cockatrice/resources/userlevels/moderator_vip_buddy.svg
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
diff --git a/cockatrice/resources/userlevels/registered.svg b/cockatrice/resources/userlevels/registered.svg
deleted file mode 100644
index a7f8a72cf..000000000
--- a/cockatrice/resources/userlevels/registered.svg
+++ /dev/null
@@ -1,333 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/userlevels/registered_donator_buddy.svg b/cockatrice/resources/userlevels/registered_donator_buddy.svg
deleted file mode 100644
index 476d55088..000000000
--- a/cockatrice/resources/userlevels/registered_donator_buddy.svg
+++ /dev/null
@@ -1,137 +0,0 @@
-
-
diff --git a/cockatrice/resources/userlevels/registered_vip.svg b/cockatrice/resources/userlevels/registered_vip.svg
deleted file mode 100644
index 48982fa2f..000000000
--- a/cockatrice/resources/userlevels/registered_vip.svg
+++ /dev/null
@@ -1,344 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/userlevels/registered_vip_buddy.svg b/cockatrice/resources/userlevels/registered_vip_buddy.svg
deleted file mode 100644
index 27757b934..000000000
--- a/cockatrice/resources/userlevels/registered_vip_buddy.svg
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
diff --git a/cockatrice/src/client/game_logic/abstract_client.h b/cockatrice/src/client/game_logic/abstract_client.h
index 1446cfc04..5e4948f5e 100644
--- a/cockatrice/src/client/game_logic/abstract_client.h
+++ b/cockatrice/src/client/game_logic/abstract_client.h
@@ -98,8 +98,8 @@ protected:
virtual void sendCommandContainer(const CommandContainer &cont) = 0;
public:
- AbstractClient(QObject *parent = nullptr);
- ~AbstractClient();
+ explicit AbstractClient(QObject *parent = nullptr);
+ ~AbstractClient() override;
ClientStatus getStatus() const
{
diff --git a/cockatrice/src/client/game_logic/key_signals.h b/cockatrice/src/client/game_logic/key_signals.h
index e0808fb0c..32e656c76 100644
--- a/cockatrice/src/client/game_logic/key_signals.h
+++ b/cockatrice/src/client/game_logic/key_signals.h
@@ -23,7 +23,7 @@ signals:
void onCtrlC();
protected:
- virtual bool eventFilter(QObject *, QEvent *event);
+ bool eventFilter(QObject *, QEvent *event) override;
};
#endif
diff --git a/cockatrice/src/client/network/sets_model.h b/cockatrice/src/client/network/sets_model.h
index cba1c735d..0bfbe57e0 100644
--- a/cockatrice/src/client/network/sets_model.h
+++ b/cockatrice/src/client/network/sets_model.h
@@ -57,28 +57,29 @@ public:
SortRole = Qt::UserRole
};
- SetsModel(CardDatabase *_db, QObject *parent = nullptr);
- ~SetsModel();
- int rowCount(const QModelIndex &parent = QModelIndex()) const;
- int columnCount(const QModelIndex &parent = QModelIndex()) const
+ explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr);
+ ~SetsModel() override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent);
return NUM_COLS;
}
- QVariant data(const QModelIndex &index, int role) const;
- bool setData(const QModelIndex &index, const QVariant &value, int role);
- QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
- Qt::ItemFlags flags(const QModelIndex &index) const;
- Qt::DropActions supportedDropActions() const;
+ QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ Qt::DropActions supportedDropActions() const override;
- QMimeData *mimeData(const QModelIndexList &indexes) const;
- bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
- QStringList mimeTypes() const;
+ QMimeData *mimeData(const QModelIndexList &indexes) const override;
+ bool
+ dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
+ QStringList mimeTypes() const override;
void swapRows(int oldRow, int newRow);
void toggleRow(int row, bool enable);
void toggleRow(int row);
void toggleAll(bool);
- void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
+ void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
void save(CardDatabase *db);
void restore(CardDatabase *db);
void restoreOriginalOrder();
@@ -88,7 +89,7 @@ class SetsDisplayModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
- SetsDisplayModel(QObject *parent = NULL);
+ explicit SetsDisplayModel(QObject *parent = nullptr);
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp
new file mode 100644
index 000000000..da2a9c052
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp
@@ -0,0 +1,45 @@
+#include "edhrec_commander_api_response.h"
+
+#include
+#include
+
+void EdhrecCommanderApiResponse::fromJson(const QJsonObject &json)
+{
+ // Parse the collapsed DeckStatistics
+ deckStats.fromJson(json);
+
+ // Parse Archidekt section
+ QJsonArray archidektJson = json.value("archidekt").toArray();
+ archidekt.fromJson(archidektJson);
+
+ // Parse other fields
+ similar = json.value("similar").toObject();
+ header = json.value("header").toString();
+ panels = json.value("panels").toObject();
+ description = json.value("description").toString();
+ QJsonObject containerJson = json.value("container").toObject();
+ container.fromJson(containerJson);
+}
+
+void EdhrecCommanderApiResponse::debugPrint() const
+{
+ qDebug() << "Deck Statistics:";
+ qDebug() << " Creature:" << deckStats.creature;
+ qDebug() << " Instant:" << deckStats.instant;
+ qDebug() << " Sorcery:" << deckStats.sorcery;
+ qDebug() << " Artifact:" << deckStats.artifact;
+ qDebug() << " Enchantment:" << deckStats.enchantment;
+ qDebug() << " Battle:" << deckStats.battle;
+ qDebug() << " Planeswalker:" << deckStats.planeswalker;
+ qDebug() << " Land:" << deckStats.land;
+ qDebug() << " Basic:" << deckStats.basic;
+ qDebug() << " Nonbasic:" << deckStats.nonbasic;
+
+ archidekt.debugPrint();
+
+ qDebug() << "Similar:" << similar;
+ qDebug() << "Header:" << header;
+ qDebug() << "Panels:" << panels;
+ qDebug() << "Description:" << description;
+ container.debugPrint();
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h
new file mode 100644
index 000000000..91cf3afbc
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h
@@ -0,0 +1,28 @@
+#ifndef DECKDATA_H
+#define DECKDATA_H
+
+#include "edhrec_commander_api_response_archidekt_links.h"
+#include "edhrec_commander_api_response_average_deck_statistics.h"
+#include "edhrec_commander_api_response_card_container.h"
+
+#include
+#include
+#include
+
+// Represents the main structure of the JSON
+class EdhrecCommanderApiResponse
+{
+public:
+ EdhrecCommanderApiResponseAverageDeckStatistics deckStats;
+ EdhrecCommanderApiResponseArchidektLinks archidekt;
+ QJsonObject similar;
+ QString header;
+ QJsonObject panels;
+ QString description;
+ EdhrecCommanderApiResponseCardContainer container;
+
+ void fromJson(const QJsonObject &json);
+ void debugPrint() const;
+};
+
+#endif // DECKDATA_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp
new file mode 100644
index 000000000..aac540f90
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp
@@ -0,0 +1,43 @@
+#include "edhrec_commander_api_response_archidekt_links.h"
+
+#include
+#include
+#include
+#include
+
+void EdhrecCommanderApiResponseArchidektLink::fromJson(const QJsonObject &json)
+{
+ c = json.value("c").toString();
+ f = json.value("f").toInt(0);
+ q = json.value("q").toInt(0);
+ u = json.value("u").toString();
+}
+
+void EdhrecCommanderApiResponseArchidektLink::debugPrint() const
+{
+ qDebug() << " C:" << c;
+ qDebug() << " F:" << f;
+ qDebug() << " Q:" << q;
+ qDebug() << " U:" << u;
+}
+
+void EdhrecCommanderApiResponseArchidektLinks::fromJson(const QJsonArray &json)
+{
+ entries.clear();
+ for (const QJsonValue &value : json) {
+ if (value.isObject()) {
+ QJsonObject entryJson = value.toObject();
+ EdhrecCommanderApiResponseArchidektLink entry;
+ entry.fromJson(entryJson);
+ entries.append(entry);
+ }
+ }
+}
+
+void EdhrecCommanderApiResponseArchidektLinks::debugPrint() const
+{
+ qDebug() << "Archidekt Entries:";
+ for (const auto &entry : entries) {
+ entry.debugPrint();
+ }
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h
new file mode 100644
index 000000000..537dbb1fc
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h
@@ -0,0 +1,32 @@
+#ifndef ARCHIDEKTENTRY_H
+#define ARCHIDEKTENTRY_H
+
+#include
+#include
+#include
+#include
+
+// Represents a single Archidekt entry
+class EdhrecCommanderApiResponseArchidektLink
+{
+public:
+ QString c;
+ int f = 0;
+ int q = 0;
+ QString u;
+
+ void fromJson(const QJsonObject &json);
+ void debugPrint() const;
+};
+
+// Represents the Archidekt section as a list of entries
+class EdhrecCommanderApiResponseArchidektLinks
+{
+public:
+ QVector entries;
+
+ void fromJson(const QJsonArray &json);
+ void debugPrint() const;
+};
+
+#endif // ARCHIDEKTENTRY_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp
new file mode 100644
index 000000000..74cce5322
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp
@@ -0,0 +1,15 @@
+#include "edhrec_commander_api_response_average_deck_statistics.h"
+
+void EdhrecCommanderApiResponseAverageDeckStatistics::fromJson(const QJsonObject &json)
+{
+ creature = json.value("creature").toInt(0);
+ instant = json.value("instant").toInt(0);
+ sorcery = json.value("sorcery").toInt(0);
+ artifact = json.value("artifact").toInt(0);
+ enchantment = json.value("enchantment").toInt(0);
+ battle = json.value("battle").toInt(0);
+ planeswalker = json.value("planeswalker").toInt(0);
+ land = json.value("land").toInt(0);
+ basic = json.value("basic").toInt(0);
+ nonbasic = json.value("nonbasic").toInt(0);
+}
\ No newline at end of file
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h
new file mode 100644
index 000000000..1dd01abe2
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h
@@ -0,0 +1,22 @@
+#ifndef AVERAGE_DECK_STATISTICS_H
+#define AVERAGE_DECK_STATISTICS_H
+
+#include
+
+// Represents the typical deck statistics (collapsed section)
+struct EdhrecCommanderApiResponseAverageDeckStatistics
+{
+ int creature = 0;
+ int instant = 0;
+ int sorcery = 0;
+ int artifact = 0;
+ int enchantment = 0;
+ int battle = 0;
+ int planeswalker = 0;
+ int land = 0;
+ int basic = 0;
+ int nonbasic = 0;
+
+ void fromJson(const QJsonObject &json);
+};
+#endif // AVERAGE_DECK_STATISTICS_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp
new file mode 100644
index 000000000..f8ffab787
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp
@@ -0,0 +1,49 @@
+#include "edhrec_commander_api_response_card_container.h"
+
+#include
+#include
+#include
+
+void EdhrecCommanderApiResponseCardContainer::fromJson(const QJsonObject &json)
+{
+ // Parse breadcrumb
+ QJsonArray breadcrumbArray = json.value("breadcrumb").toArray();
+ for (const QJsonValue &breadcrumbValue : breadcrumbArray) {
+ breadcrumb.push_back(breadcrumbValue.toObject());
+ }
+
+ description = json.value("description").toString();
+ QJsonObject jsonDict = json.value("json_dict").toObject();
+ card.fromJson(jsonDict.value("card").toObject());
+ QJsonArray cardlistsArray = jsonDict.value("cardlists").toArray();
+
+ for (const QJsonValue &cardlistValue : cardlistsArray) {
+ QJsonObject cardlistObj = cardlistValue.toObject();
+ QJsonArray cardviewsArray = cardlistObj.value("cardviews").toArray();
+ EdhrecCommanderApiResponseCardList cardView;
+ cardView.fromJson(cardlistValue.toObject());
+ cardlists.push_back(cardView);
+ }
+
+ keywords = json.value("keywords").toString();
+ title = json.value("title").toString();
+}
+
+void EdhrecCommanderApiResponseCardContainer::debugPrint() const
+{
+ qDebug() << "Breadcrumb:";
+ for (const auto &breadcrumbEntry : breadcrumb) {
+ qDebug() << breadcrumbEntry;
+ }
+
+ qDebug() << "Description:" << description;
+ card.debugPrint();
+
+ qDebug() << "Cardlists:";
+ for (const auto &cardlist : cardlists) {
+ cardlist.debugPrint();
+ }
+
+ qDebug() << "Keywords:" << keywords;
+ qDebug() << "Title:" << title;
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h
new file mode 100644
index 000000000..d2ea05874
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h
@@ -0,0 +1,60 @@
+#ifndef CONTAINER_ENTRY_H
+#define CONTAINER_ENTRY_H
+
+#include "edhrec_commander_api_response_card_list.h"
+#include "edhrec_commander_api_response_commander_details.h"
+
+#include
+#include
+#include
+#include
+#include
+
+class EdhrecCommanderApiResponseCardContainer
+{
+public:
+ // Constructor
+ EdhrecCommanderApiResponseCardContainer() = default;
+
+ // Parse deck-related data from JSON
+ void fromJson(const QJsonObject &json);
+
+ // Debug method for logging
+ void debugPrint() const;
+
+ // Getter methods for deck container
+ const QString &getDescription() const
+ {
+ return description;
+ }
+ const QVector &getBreadcrumb() const
+ {
+ return breadcrumb;
+ }
+ const EdhrecCommanderApiResponseCommanderDetails &getCommanderDetails() const
+ {
+ return card;
+ }
+ const QVector &getCardlists() const
+ {
+ return cardlists;
+ }
+ const QString &getKeywords() const
+ {
+ return keywords;
+ }
+ const QString &getTitle() const
+ {
+ return title;
+ }
+
+private:
+ QString description;
+ QVector breadcrumb;
+ EdhrecCommanderApiResponseCommanderDetails card;
+ QVector cardlists;
+ QString keywords;
+ QString title;
+};
+
+#endif // CONTAINER_ENTRY_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp
new file mode 100644
index 000000000..2f2791de1
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp
@@ -0,0 +1,36 @@
+#include "edhrec_commander_api_response_card_details.h"
+
+#include
+
+EdhrecCommanderApiResponseCardDetails::EdhrecCommanderApiResponseCardDetails()
+ : synergy(0.0), inclusion(0), numDecks(0), potentialDecks(0)
+{
+}
+
+void EdhrecCommanderApiResponseCardDetails::fromJson(const QJsonObject &json)
+{
+ // Parse the fields from the JSON object
+ name = json.value("name").toString();
+ sanitized = json.value("sanitized").toString();
+ sanitizedWo = json.value("sanitized_wo").toString();
+ url = json.value("url").toString();
+ synergy = json.value("synergy").toDouble(0.0);
+ inclusion = json.value("inclusion").toInt(0);
+ label = json.value("label").toString();
+ numDecks = json.value("num_decks").toInt(0);
+ potentialDecks = json.value("potential_decks").toInt(0);
+}
+
+void EdhrecCommanderApiResponseCardDetails::debugPrint() const
+{
+ // Print out all the fields for debugging
+ qDebug() << "Name:" << name;
+ qDebug() << "Sanitized:" << sanitized;
+ qDebug() << "Sanitized Wo:" << sanitizedWo;
+ qDebug() << "URL:" << url;
+ qDebug() << "Synergy:" << synergy;
+ qDebug() << "Inclusion:" << inclusion;
+ qDebug() << "Label:" << label;
+ qDebug() << "Num Decks:" << numDecks;
+ qDebug() << "Potential Decks:" << potentialDecks;
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h
new file mode 100644
index 000000000..82d85824b
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h
@@ -0,0 +1,29 @@
+#ifndef CARD_VIEW_H
+#define CARD_VIEW_H
+
+#include
+#include
+
+class EdhrecCommanderApiResponseCardDetails
+{
+public:
+ QString name;
+ QString sanitized;
+ QString sanitizedWo;
+ QString url;
+ double synergy;
+ int inclusion;
+ QString label;
+ int numDecks;
+ int potentialDecks;
+
+ EdhrecCommanderApiResponseCardDetails();
+
+ // Method to populate the object from a JSON object
+ void fromJson(const QJsonObject &json);
+
+ // Debug method to print out the data
+ void debugPrint() const;
+};
+
+#endif // CARD_VIEW_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp
new file mode 100644
index 000000000..5d3eebedd
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp
@@ -0,0 +1,33 @@
+#include "edhrec_commander_api_response_card_list.h"
+
+#include
+
+EdhrecCommanderApiResponseCardList::EdhrecCommanderApiResponseCardList()
+{
+}
+
+void EdhrecCommanderApiResponseCardList::fromJson(const QJsonObject &json)
+{
+ // Parse the header from the JSON object
+ header = json.value("header").toString();
+
+ // Parse the cardviews array and populate cardViews
+ QJsonArray cardviewsArray = json.value("cardviews").toArray();
+ for (const QJsonValue &value : cardviewsArray) {
+ QJsonObject cardviewObj = value.toObject();
+ EdhrecCommanderApiResponseCardDetails cardView;
+ cardView.fromJson(cardviewObj);
+ cardViews.append(cardView);
+ }
+}
+
+void EdhrecCommanderApiResponseCardList::debugPrint() const
+{
+ // Print out the header
+ qDebug() << "Header:" << header;
+
+ // Print out all the CardView objects
+ for (const EdhrecCommanderApiResponseCardDetails &cardView : cardViews) {
+ cardView.debugPrint();
+ }
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h
new file mode 100644
index 000000000..e0ddc3466
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h
@@ -0,0 +1,27 @@
+#ifndef CARD_LIST_H
+#define CARD_LIST_H
+
+#include "edhrec_commander_api_response_card_details.h"
+
+#include
+#include
+#include
+#include
+
+class EdhrecCommanderApiResponseCardList
+{
+public:
+ QString header;
+ QList cardViews;
+
+ // Default constructor
+ EdhrecCommanderApiResponseCardList();
+
+ // Method to populate the object from a JSON object
+ void fromJson(const QJsonObject &json);
+
+ // Debug method to print out the data
+ void debugPrint() const;
+};
+
+#endif // CARD_LIST_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp
new file mode 100644
index 000000000..6454e5500
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp
@@ -0,0 +1,31 @@
+#include "edhrec_commander_api_response_card_prices.h"
+
+#include
+
+void CardPrices::fromJson(const QJsonObject &json)
+{
+ // Parse prices from various sources
+ cardhoarder = json.value("cardhoarder").toObject();
+ cardkingdom = json.value("cardkingdom").toObject();
+ cardmarket = json.value("cardmarket").toObject();
+ face2face = json.value("face2face").toObject();
+ manapool = json.value("manapool").toObject();
+ mtgstocks = json.value("mtgstocks").toObject();
+ scg = json.value("scg").toObject();
+ tcgl = json.value("tcgl").toObject();
+ tcgplayer = json.value("tcgplayer").toObject();
+}
+
+void CardPrices::debugPrint() const
+{
+ qDebug() << "Card Prices:";
+ qDebug() << "Cardhoarder:" << cardhoarder;
+ qDebug() << "Cardkingdom:" << cardkingdom;
+ qDebug() << "Cardmarket:" << cardmarket;
+ qDebug() << "Face2Face:" << face2face;
+ qDebug() << "Manapool:" << manapool;
+ qDebug() << "Mtgstocks:" << mtgstocks;
+ qDebug() << "SCG:" << scg;
+ qDebug() << "TCGL:" << tcgl;
+ qDebug() << "Tcgplayer:" << tcgplayer;
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h
new file mode 100644
index 000000000..32e017a92
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h
@@ -0,0 +1,66 @@
+#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H
+#define EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H
+
+#include
+
+class CardPrices
+{
+public:
+ // Constructor
+ CardPrices() = default;
+
+ // Parse prices from JSON
+ void fromJson(const QJsonObject &json);
+ void debugPrint() const;
+
+ // Getter methods for card prices
+ const QJsonObject &getCardhoarder() const
+ {
+ return cardhoarder;
+ }
+ const QJsonObject &getCardkingdom() const
+ {
+ return cardkingdom;
+ }
+ const QJsonObject &getCardmarket() const
+ {
+ return cardmarket;
+ }
+ const QJsonObject &getFace2face() const
+ {
+ return face2face;
+ }
+ const QJsonObject &getManapool() const
+ {
+ return manapool;
+ }
+ const QJsonObject &getMtgstocks() const
+ {
+ return mtgstocks;
+ }
+ const QJsonObject &getScg() const
+ {
+ return scg;
+ }
+ const QJsonObject &getTcgl() const
+ {
+ return tcgl;
+ }
+ const QJsonObject &getTcgplayer() const
+ {
+ return tcgplayer;
+ }
+
+private:
+ QJsonObject cardhoarder;
+ QJsonObject cardkingdom;
+ QJsonObject cardmarket;
+ QJsonObject face2face;
+ QJsonObject manapool;
+ QJsonObject mtgstocks;
+ QJsonObject scg;
+ QJsonObject tcgl;
+ QJsonObject tcgplayer;
+};
+
+#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp
new file mode 100644
index 000000000..7b220603e
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp
@@ -0,0 +1,90 @@
+#include "edhrec_commander_api_response_commander_details.h"
+
+#include
+
+void EdhrecCommanderApiResponseCommanderDetails::fromJson(const QJsonObject &json)
+{
+ // Parse card-related data
+ aetherhubUri = json.value("aetherhub_uri").toString();
+ archidektUri = json.value("archidekt_uri").toString();
+ cmc = json.value("cmc").toInt(0);
+ colorIdentity = json.value("color_identity").toArray();
+ combos = json.value("combos").toBool(false);
+ deckstatsUri = json.value("deckstats_uri").toString();
+
+ // Parse image URIs
+ QJsonArray imageUrisArray = json.value("image_uris").toArray();
+ for (const QJsonValue &imageValue : imageUrisArray) {
+ QJsonObject imageObject = imageValue.toObject();
+ imageUris.push_back(imageObject.value("normal").toString());
+ imageUris.push_back(imageObject.value("art_crop").toString());
+ }
+
+ inclusion = json.value("inclusion").toInt(0);
+ isCommander = json.value("is_commander").toBool(false);
+ label = json.value("label").toString();
+ layout = json.value("layout").toString();
+ legalCommander = json.value("legal_commander").toBool(false);
+ moxfieldUri = json.value("moxfield_uri").toString();
+ mtggoldfishUri = json.value("mtggoldfish_uri").toString();
+ name = json.value("name").toString();
+ names = json.value("names").toArray();
+ numDecks = json.value("num_decks").toInt(0);
+ potentialDecks = json.value("potential_decks").toInt(0);
+ precon = json.value("precon").toString();
+
+ // Parse prices
+ prices.fromJson(json.value("prices").toObject());
+
+ primaryType = json.value("primary_type").toString();
+ rarity = json.value("rarity").toString();
+ salt = json.value("salt").toDouble(0.0);
+ sanitized = json.value("sanitized").toString();
+ sanitizedWo = json.value("sanitized_wo").toString();
+ scryfallUri = json.value("scryfall_uri").toString();
+ spellbookUri = json.value("spellbook_uri").toString();
+ type = json.value("type").toString();
+ url = json.value("url").toString();
+}
+
+void EdhrecCommanderApiResponseCommanderDetails::debugPrint() const
+{
+ qDebug() << "Card Data:";
+ qDebug() << "Aetherhub URI:" << aetherhubUri;
+ qDebug() << "Archidekt URI:" << archidektUri;
+ qDebug() << "CMC:" << cmc;
+ qDebug() << "Color Identity:" << colorIdentity;
+ qDebug() << "Combos:" << combos;
+ qDebug() << "Deckstats URI:" << deckstatsUri;
+
+ qDebug() << "Image URIs:";
+ for (const auto &uri : imageUris) {
+ qDebug() << uri;
+ }
+
+ qDebug() << "Inclusion:" << inclusion;
+ qDebug() << "Is Commander:" << isCommander;
+ qDebug() << "Label:" << label;
+ qDebug() << "Layout:" << layout;
+ qDebug() << "Legal Commander:" << legalCommander;
+ qDebug() << "Moxfield URI:" << moxfieldUri;
+ qDebug() << "MTGGoldfish URI:" << mtggoldfishUri;
+ qDebug() << "Name:" << name;
+ qDebug() << "Names:" << names;
+ qDebug() << "Number of Decks:" << numDecks;
+ qDebug() << "Potential Decks:" << potentialDecks;
+ qDebug() << "Precon:" << precon;
+
+ // Print the prices using the debugPrint method from CardPrices
+ prices.debugPrint();
+
+ qDebug() << "Primary Type:" << primaryType;
+ qDebug() << "Rarity:" << rarity;
+ qDebug() << "Salt:" << salt;
+ qDebug() << "Sanitized:" << sanitized;
+ qDebug() << "Sanitized WO:" << sanitizedWo;
+ qDebug() << "Scryfall URI:" << scryfallUri;
+ qDebug() << "Spellbook URI:" << spellbookUri;
+ qDebug() << "Type:" << type;
+ qDebug() << "URL:" << url;
+}
\ No newline at end of file
diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h
new file mode 100644
index 000000000..78ccfc23a
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h
@@ -0,0 +1,173 @@
+#ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H
+#define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H
+
+#include "edhrec_commander_api_response_card_prices.h"
+
+#include
+#include
+#include
+#include
+
+class EdhrecCommanderApiResponseCommanderDetails
+{
+public:
+ // Constructor
+ EdhrecCommanderApiResponseCommanderDetails() = default;
+
+ // Parse card-related data from JSON
+ void fromJson(const QJsonObject &json);
+
+ // Debug method for logging
+ void debugPrint() const;
+
+ // Getters for the card data
+ const QString &getAetherhubUri() const
+ {
+ return aetherhubUri;
+ }
+ const QString &getArchidektUri() const
+ {
+ return archidektUri;
+ }
+ int getCmc() const
+ {
+ return cmc;
+ }
+ const QJsonArray &getColorIdentity() const
+ {
+ return colorIdentity;
+ }
+ bool isCombos() const
+ {
+ return combos;
+ }
+ const QString &getDeckstatsUri() const
+ {
+ return deckstatsUri;
+ }
+ const QVector &getImageUris() const
+ {
+ return imageUris;
+ }
+ int getInclusion() const
+ {
+ return inclusion;
+ }
+ bool getIsCommander() const
+ {
+ return isCommander;
+ }
+ const QString &getLabel() const
+ {
+ return label;
+ }
+ const QString &getLayout() const
+ {
+ return layout;
+ }
+ bool getLegalCommander() const
+ {
+ return legalCommander;
+ }
+ const QString &getMoxfieldUri() const
+ {
+ return moxfieldUri;
+ }
+ const QString &getMtggoldfishUri() const
+ {
+ return mtggoldfishUri;
+ }
+ const QString &getName() const
+ {
+ return name;
+ }
+ const QJsonArray &getNames() const
+ {
+ return names;
+ }
+ int getNumDecks() const
+ {
+ return numDecks;
+ }
+ int getPotentialDecks() const
+ {
+ return potentialDecks;
+ }
+ const QString &getPrecon() const
+ {
+ return precon;
+ }
+ const CardPrices &getPrices() const
+ {
+ return prices;
+ }
+ const QString &getPrimaryType() const
+ {
+ return primaryType;
+ }
+ const QString &getRarity() const
+ {
+ return rarity;
+ }
+ double getSalt() const
+ {
+ return salt;
+ }
+ const QString &getSanitized() const
+ {
+ return sanitized;
+ }
+ const QString &getSanitizedWo() const
+ {
+ return sanitizedWo;
+ }
+ const QString &getScryfallUri() const
+ {
+ return scryfallUri;
+ }
+ const QString &getSpellbookUri() const
+ {
+ return spellbookUri;
+ }
+ const QString &getType() const
+ {
+ return type;
+ }
+ const QString &getUrl() const
+ {
+ return url;
+ }
+
+private:
+ QString aetherhubUri;
+ QString archidektUri;
+ int cmc = 0;
+ QJsonArray colorIdentity;
+ bool combos = false;
+ QString deckstatsUri;
+ QVector imageUris;
+ int inclusion = 0;
+ bool isCommander = false;
+ QString label;
+ QString layout;
+ bool legalCommander = false;
+ QString moxfieldUri;
+ QString mtggoldfishUri;
+ QString name;
+ QJsonArray names;
+ int numDecks = 0;
+ int potentialDecks = 0;
+ QString precon;
+ CardPrices prices;
+ QString primaryType;
+ QString rarity;
+ double salt = 0.0;
+ QString sanitized;
+ QString sanitizedWo;
+ QString scryfallUri;
+ QString spellbookUri;
+ QString type;
+ QString url;
+};
+
+#endif // EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp
new file mode 100644
index 000000000..87853b9aa
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp
@@ -0,0 +1,41 @@
+#include "edhrec_commander_api_response_card_details_display_widget.h"
+
+#include "../../../../game/cards/card_database_manager.h"
+
+EdhrecCommanderApiResponseCardDetailsDisplayWidget::EdhrecCommanderApiResponseCardDetailsDisplayWidget(
+ QWidget *parent,
+ const EdhrecCommanderApiResponseCardDetails &_toDisplay)
+ : QWidget(parent), toDisplay(_toDisplay)
+{
+ layout = new QVBoxLayout(this);
+ setLayout(layout);
+
+ cardPictureWidget = new CardInfoPictureWidget(this);
+ cardPictureWidget->setCard(CardDatabaseManager::getInstance()->getCard(toDisplay.name));
+
+ label = new QLabel(this);
+ label->setText(toDisplay.name + "\n" + toDisplay.label);
+ label->setAlignment(Qt::AlignHCenter);
+
+ // Set label color based on inclusion rate
+ int inclusionRate = (toDisplay.numDecks * 100) / toDisplay.potentialDecks;
+
+ QColor labelColor;
+ if (inclusionRate <= 30) {
+ labelColor = QColor(255, 0, 0); // Red
+ } else if (inclusionRate <= 60) {
+ int red = 255 - ((inclusionRate - 30) * 2);
+ int green = (inclusionRate - 30) * 4; // Adjust green to make the transition smoother
+ labelColor = QColor(red, green, 0); // purple-ish
+ } else if (inclusionRate <= 90) {
+ int green = (inclusionRate - 60) * 5; // Increase green
+ labelColor = QColor(100, green, 100); // Green shades
+ } else {
+ labelColor = QColor(100, 200, 100); // Dark Green
+ }
+
+ label->setStyleSheet(QString("color: %1").arg(labelColor.name()));
+
+ layout->addWidget(cardPictureWidget);
+ layout->addWidget(label);
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h
new file mode 100644
index 000000000..1cd76989c
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h
@@ -0,0 +1,26 @@
+#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
+#define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
+
+#include "../../../ui/widgets/cards/card_info_picture_widget.h"
+#include "api_response/edhrec_commander_api_response_card_details.h"
+
+#include
+#include
+#include
+
+class EdhrecCommanderApiResponseCardDetailsDisplayWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit EdhrecCommanderApiResponseCardDetailsDisplayWidget(
+ QWidget *parent,
+ const EdhrecCommanderApiResponseCardDetails &_toDisplay);
+
+private:
+ EdhrecCommanderApiResponseCardDetails toDisplay;
+ QVBoxLayout *layout;
+ CardInfoPictureWidget *cardPictureWidget;
+ QLabel *label;
+};
+
+#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp
new file mode 100644
index 000000000..0d1f1e8f3
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp
@@ -0,0 +1,34 @@
+#include "edhrec_commander_api_response_card_list_display_widget.h"
+
+#include "../../../ui/widgets/general/display/banner_widget.h"
+#include "edhrec_commander_api_response_card_details_display_widget.h"
+
+#include
+
+EdhrecCommanderApiResponseCardListDisplayWidget::EdhrecCommanderApiResponseCardListDisplayWidget(
+ QWidget *parent,
+ EdhrecCommanderApiResponseCardList toDisplay)
+ : QWidget(parent)
+{
+ layout = new QVBoxLayout(this);
+ setLayout(layout);
+
+ header = new BannerWidget(this, toDisplay.header);
+
+ flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
+ header->setBuddy(flowWidget);
+
+ foreach (EdhrecCommanderApiResponseCardDetails card_detail, toDisplay.cardViews) {
+ auto widget = new EdhrecCommanderApiResponseCardDetailsDisplayWidget(flowWidget, card_detail);
+ flowWidget->addWidget(widget);
+ }
+
+ layout->addWidget(header);
+ layout->addWidget(flowWidget);
+}
+
+void EdhrecCommanderApiResponseCardListDisplayWidget::resizeEvent(QResizeEvent *event)
+{
+ QWidget::resizeEvent(event);
+ qDebug() << event->size();
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h
new file mode 100644
index 000000000..9df667929
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h
@@ -0,0 +1,30 @@
+#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H
+#define EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H
+
+#include "../../../ui/widgets/general/display/banner_widget.h"
+#include "../../../ui/widgets/general/layout_containers/flow_widget.h"
+#include "api_response/edhrec_commander_api_response_card_list.h"
+
+#include
+#include
+#include
+
+class EdhrecCommanderApiResponseCardListDisplayWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit EdhrecCommanderApiResponseCardListDisplayWidget(QWidget *parent,
+ EdhrecCommanderApiResponseCardList toDisplay);
+ void resizeEvent(QResizeEvent *event) override;
+ [[nodiscard]] QString getBannerText() const
+ {
+ return header->getText();
+ };
+
+private:
+ QVBoxLayout *layout;
+ BannerWidget *header;
+ FlowWidget *flowWidget;
+};
+
+#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp
new file mode 100644
index 000000000..58b77df73
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp
@@ -0,0 +1,36 @@
+#include "edhrec_commander_api_response_commander_details_display_widget.h"
+
+#include "../../../../game/cards/card_database_manager.h"
+#include "../../../ui/widgets/cards/card_info_picture_widget.h"
+
+#include
+
+EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCommanderDetailsDisplayWidget(
+ QWidget *parent,
+ const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails)
+ : QWidget(parent), commanderDetails(_commanderDetails)
+{
+ layout = new QVBoxLayout(this);
+ setLayout(layout);
+
+ commanderPicture = new CardInfoPictureWidget(this);
+ commanderPicture->setCard(CardDatabaseManager::getInstance()->getCard(commanderDetails.getName()));
+
+ commanderDetails.debugPrint();
+
+ label = new QLabel(this);
+ label->setAlignment(Qt::AlignCenter);
+ salt = new QLabel(this);
+ salt->setAlignment(Qt::AlignCenter);
+
+ layout->addWidget(commanderPicture);
+ layout->addWidget(label);
+ layout->addWidget(salt);
+ retranslateUi();
+}
+
+void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi()
+{
+ label->setText(commanderDetails.getLabel());
+ salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt()));
+}
\ No newline at end of file
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h
new file mode 100644
index 000000000..85970d503
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h
@@ -0,0 +1,28 @@
+#ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H
+#define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H
+
+#include "../../../ui/widgets/cards/card_info_picture_widget.h"
+#include "api_response/edhrec_commander_api_response_commander_details.h"
+
+#include
+#include
+#include
+
+class EdhrecCommanderResponseCommanderDetailsDisplayWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit EdhrecCommanderResponseCommanderDetailsDisplayWidget(
+ QWidget *parent,
+ const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails);
+ void retranslateUi();
+
+private:
+ QLabel *label;
+ QLabel *salt;
+ QVBoxLayout *layout;
+ CardInfoPictureWidget *commanderPicture;
+ EdhrecCommanderApiResponseCommanderDetails commanderDetails;
+};
+
+#endif // EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp
new file mode 100644
index 000000000..3cab65bf8
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp
@@ -0,0 +1,102 @@
+#include "edhrec_commander_api_response_display_widget.h"
+
+#include "../../../ui/widgets/cards/card_info_picture_widget.h"
+#include "api_response/edhrec_commander_api_response.h"
+#include "edhrec_commander_api_response_card_list_display_widget.h"
+#include "edhrec_commander_api_response_commander_details_display_widget.h"
+
+#include
+#include
+#include
+#include
+#include
+
+EdhrecCommanderApiResponseDisplayWidget::EdhrecCommanderApiResponseDisplayWidget(QWidget *parent,
+ EdhrecCommanderApiResponse response)
+ : QWidget(parent)
+{
+ layout = new QHBoxLayout(this);
+ setLayout(layout);
+
+ cardDisplayLayout = new QVBoxLayout(this);
+
+ // Create a QSplitter to hold the ListView and ScrollArea holding CardListdisplayWidgets side by side
+ auto splitter = new QSplitter(this);
+ splitter->setOrientation(Qt::Horizontal);
+
+ auto listView = new QListView(splitter);
+ listView->setMinimumWidth(50);
+ listView->setMaximumWidth(150);
+ auto listModel = new QStringListModel(this);
+ QStringList widgetNames;
+
+ // Add commander details
+ auto commanderPicture =
+ new EdhrecCommanderResponseCommanderDetailsDisplayWidget(this, response.container.getCommanderDetails());
+ cardDisplayLayout->addWidget(commanderPicture);
+ widgetNames.append("Commander Details");
+
+ // Add card list widgets
+ auto edhrec_commander_api_response_card_lists = response.container.getCardlists();
+ for (const EdhrecCommanderApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) {
+ auto cardListDisplayWidget = new EdhrecCommanderApiResponseCardListDisplayWidget(this, card_list);
+ cardDisplayLayout->addWidget(cardListDisplayWidget);
+ widgetNames.append(cardListDisplayWidget->getBannerText());
+ }
+
+ // Create a QScrollArea to hold the card display widgets
+ scrollArea = new QScrollArea(splitter);
+ scrollArea->setWidgetResizable(true);
+ scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ // Set the cardDisplayLayout inside the scroll area
+ auto scrollWidget = new QWidget(scrollArea);
+ scrollWidget->setLayout(cardDisplayLayout);
+ connect(splitter, &QSplitter::splitterMoved, this, &EdhrecCommanderApiResponseDisplayWidget::onSplitterChange);
+ scrollArea->setWidget(scrollWidget);
+
+ // Configure the list view
+ listModel->setStringList(widgetNames);
+ listView->setModel(listModel);
+ listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+
+ // Connect the list view to ensure the corresponding widget is visible
+ connect(listView, &QListView::clicked, this, [this](const QModelIndex &index) {
+ int widgetIndex = index.row();
+ qDebug() << "clicked: " << widgetIndex;
+ auto targetWidget = cardDisplayLayout->itemAt(widgetIndex)->widget();
+ if (targetWidget) {
+ qDebug() << "Found targetWidget" << targetWidget;
+ // Attempt to cast the parent to QScrollArea
+ auto scrollArea = qobject_cast(this->scrollArea); // Use the scroll area instance
+ if (scrollArea) {
+ qDebug() << "ScrollArea" << scrollArea;
+ scrollArea->ensureWidgetVisible(targetWidget);
+ }
+ }
+ });
+
+ // Add splitter to the main layout
+ splitter->addWidget(listView);
+ splitter->addWidget(scrollArea);
+
+ layout->addWidget(splitter);
+}
+
+void EdhrecCommanderApiResponseDisplayWidget::onSplitterChange()
+{
+ scrollArea->widget()->resize(scrollArea->size());
+}
+
+void EdhrecCommanderApiResponseDisplayWidget::resizeEvent(QResizeEvent *event)
+{
+ QWidget::resizeEvent(event);
+ qDebug() << event->size();
+ layout->invalidate();
+ layout->activate();
+ layout->update();
+ if (scrollArea && scrollArea->widget()) {
+ scrollArea->widget()->resize(event->size());
+ }
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h
new file mode 100644
index 000000000..d1e95e3d6
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h
@@ -0,0 +1,27 @@
+#ifndef EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H
+#define EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H
+
+#include "api_response/edhrec_commander_api_response.h"
+
+#include
+#include
+#include
+
+class EdhrecCommanderApiResponseDisplayWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit EdhrecCommanderApiResponseDisplayWidget(QWidget *parent, EdhrecCommanderApiResponse response);
+ void resizeEvent(QResizeEvent *event) override;
+
+public slots:
+ void onSplitterChange();
+
+private:
+ QHBoxLayout *layout;
+ QVBoxLayout *cardDisplayLayout;
+ QScrollArea *scrollArea;
+};
+
+#endif // EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H
diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp
new file mode 100644
index 000000000..18662aaab
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp
@@ -0,0 +1,112 @@
+#include "tab_edhrec.h"
+
+#include "api_response/edhrec_commander_api_response.h"
+#include "edhrec_commander_api_response_display_widget.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+TabEdhRec::TabEdhRec(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
+{
+ networkManager = new QNetworkAccessManager(this);
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
+ networkManager->setTransferTimeout(); // Use Qt's default timeout
+#endif
+
+ networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
+ connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *)));
+}
+
+void TabEdhRec::retranslateUi()
+{
+}
+
+void TabEdhRec::setCard(CardInfoPtr _cardToQuery, bool isCommander)
+{
+ cardToQuery = _cardToQuery;
+
+ if (!cardToQuery) {
+ qDebug() << "Invalid card information provided.";
+ return;
+ }
+
+ QString cardName = cardToQuery->getName();
+ QString formattedName = cardName.toLower().replace(" ", "-").remove(QRegularExpression("[^a-z0-9\\-]"));
+
+ QString url;
+ if (isCommander) {
+ url = QString("https://json.edhrec.com/pages/commanders/%1.json").arg(formattedName);
+ } else {
+ url = QString("https://json.edhrec.com/pages/cards/%1.json").arg(formattedName);
+ }
+
+ QNetworkRequest request{QUrl(url)};
+
+ networkManager->get(request);
+}
+
+void TabEdhRec::processApiJson(QNetworkReply *reply)
+{
+ if (reply->error() != QNetworkReply::NoError) {
+ qDebug() << "Network error occurred:" << reply->errorString();
+ reply->deleteLater();
+ return;
+ }
+
+ QByteArray responseData = reply->readAll();
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
+
+ if (!jsonDoc.isObject()) {
+ qDebug() << "Invalid JSON response received.";
+ reply->deleteLater();
+ return;
+ }
+
+ QJsonObject jsonObj = jsonDoc.object();
+
+ // qDebug() << jsonObj;
+
+ EdhrecCommanderApiResponse deckData;
+ deckData.fromJson(jsonObj);
+
+ displayWidget = new EdhrecCommanderApiResponseDisplayWidget(this, deckData);
+ // flowWidget->addWidget(displayWidget);
+ setCentralWidget(displayWidget);
+
+ reply->deleteLater();
+ update();
+}
+
+void TabEdhRec::prettyPrintJson(const QJsonValue &value, int indentLevel)
+{
+ const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing
+
+ if (value.isObject()) {
+ QJsonObject obj = value.toObject();
+ for (auto it = obj.begin(); it != obj.end(); ++it) {
+ qDebug().noquote() << indent + it.key() + ":";
+ prettyPrintJson(it.value(), indentLevel + 1);
+ }
+ } else if (value.isArray()) {
+ QJsonArray array = value.toArray();
+ for (int i = 0; i < array.size(); ++i) {
+ qDebug().noquote() << indent + QString("[%1]:").arg(i);
+ prettyPrintJson(array[i], indentLevel + 1);
+ }
+ } else if (value.isString()) {
+ qDebug().noquote() << indent + "\"" + value.toString() + "\"";
+ } else if (value.isDouble()) {
+ qDebug().noquote() << indent + QString::number(value.toDouble());
+ } else if (value.isBool()) {
+ qDebug().noquote() << indent + (value.toBool() ? "true" : "false");
+ } else if (value.isNull()) {
+ qDebug().noquote() << indent + "null";
+ }
+}
diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h
new file mode 100644
index 000000000..c15f22b26
--- /dev/null
+++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h
@@ -0,0 +1,37 @@
+#ifndef TAB_EDHREC_H
+#define TAB_EDHREC_H
+
+#include "../../../../game/cards/card_database.h"
+#include "../../../ui/widgets/general/layout_containers/flow_widget.h"
+#include "../../tab.h"
+#include "edhrec_commander_api_response_display_widget.h"
+
+#include
+#include
+
+class TabEdhRec : public Tab
+{
+ Q_OBJECT
+public:
+ explicit TabEdhRec(TabSupervisor *_tabSupervisor);
+
+ void retranslateUi() override;
+ QString getTabText() const override
+ {
+ auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName();
+ return tr("EDHREC: ") + cardName;
+ }
+
+ QNetworkAccessManager *networkManager;
+
+public slots:
+ void processApiJson(QNetworkReply *reply);
+ void prettyPrintJson(const QJsonValue &value, int indentLevel);
+ void setCard(CardInfoPtr _cardToQuery, bool isCommander = false);
+
+private:
+ CardInfoPtr cardToQuery;
+ EdhrecCommanderApiResponseDisplayWidget *displayWidget;
+};
+
+#endif // TAB_EDHREC_H
diff --git a/cockatrice/src/client/tabs/tab_deck_editor.cpp b/cockatrice/src/client/tabs/tab_deck_editor.cpp
index 0152566ab..8eee22cf5 100644
--- a/cockatrice/src/client/tabs/tab_deck_editor.cpp
+++ b/cockatrice/src/client/tabs/tab_deck_editor.cpp
@@ -51,6 +51,13 @@
#include
#include
+static bool canBeCommander(const CardInfoPtr &cardInfo)
+{
+ return ((cardInfo->getCardType().contains("Legendary", Qt::CaseInsensitive) &&
+ cardInfo->getCardType().contains("Creature", Qt::CaseInsensitive))) ||
+ cardInfo->getText().contains("can be your commander", Qt::CaseInsensitive);
+}
+
void TabDeckEditor::createDeckDock()
{
deckModel = new DeckListModel(this);
@@ -540,31 +547,40 @@ void TabDeckEditor::databaseCustomMenu(QPoint point)
QMenu menu;
const CardInfoPtr info = currentCardInfo();
- // add to deck and sideboard options
- QAction *addToDeck, *addToSideboard, *selectPrinting;
- addToDeck = menu.addAction(tr("Add to Deck"));
- addToSideboard = menu.addAction(tr("Add to Sideboard"));
- selectPrinting = menu.addAction(tr("Select Printing"));
-
- connect(addToDeck, SIGNAL(triggered()), this, SLOT(actAddCard()));
- connect(addToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard()));
- connect(selectPrinting, &QAction::triggered, this, [this, info] { this->showPrintingSelector(); });
-
- // filling out the related cards submenu
- auto *relatedMenu = new QMenu(tr("Show Related cards"));
- menu.addMenu(relatedMenu);
- auto relatedCards = info->getAllRelatedCards();
- if (relatedCards.isEmpty()) {
- relatedMenu->setDisabled(true);
- } else {
- for (const CardRelation *rel : relatedCards) {
- const QString &relatedCardName = rel->getName();
- QAction *relatedCard = relatedMenu->addAction(relatedCardName);
- connect(relatedCard, &QAction::triggered, cardInfo,
- [this, relatedCardName] { cardInfo->setCard(relatedCardName); });
+ if (info) {
+ // add to deck and sideboard options
+ QAction *addToDeck, *addToSideboard, *selectPrinting, *edhRecCommander, *edhRecCard;
+ addToDeck = menu.addAction(tr("Add to Deck"));
+ addToSideboard = menu.addAction(tr("Add to Sideboard"));
+ selectPrinting = menu.addAction(tr("Select Printing"));
+ if (canBeCommander(info)) {
+ edhRecCommander = menu.addAction(tr("Show on EDHREC (Commander)"));
+ connect(edhRecCommander, &QAction::triggered, this,
+ [this, info] { this->tabSupervisor->addEdhrecTab(info, true); });
}
+ edhRecCard = menu.addAction(tr("Show on EDHREC (Card)"));
+
+ connect(addToDeck, SIGNAL(triggered()), this, SLOT(actAddCard()));
+ connect(addToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard()));
+ connect(selectPrinting, &QAction::triggered, this, [this, info] { this->showPrintingSelector(); });
+ connect(edhRecCard, &QAction::triggered, this, [this, info] { this->tabSupervisor->addEdhrecTab(info); });
+
+ // filling out the related cards submenu
+ auto *relatedMenu = new QMenu(tr("Show Related cards"));
+ menu.addMenu(relatedMenu);
+ auto relatedCards = info->getAllRelatedCards();
+ if (relatedCards.isEmpty()) {
+ relatedMenu->setDisabled(true);
+ } else {
+ for (const CardRelation *rel : relatedCards) {
+ const QString &relatedCardName = rel->getName();
+ QAction *relatedCard = relatedMenu->addAction(relatedCardName);
+ connect(relatedCard, &QAction::triggered, cardInfo,
+ [this, relatedCardName] { cardInfo->setCard(relatedCardName); });
+ }
+ }
+ menu.exec(databaseView->mapToGlobal(point));
}
- menu.exec(databaseView->mapToGlobal(point));
}
void TabDeckEditor::decklistCustomMenu(QPoint point)
@@ -1383,9 +1399,8 @@ QModelIndexList TabDeckEditor::getSelectedCardNodes() const
return selectedRows;
}
-void TabDeckEditor::addCardHelper(QString zoneName)
+void TabDeckEditor::addCardHelper(const CardInfoPtr info, QString zoneName)
{
- const CardInfoPtr info = currentCardInfo();
if (!info)
return;
if (info->getIsToken())
@@ -1461,13 +1476,13 @@ void TabDeckEditor::actAddCard()
if (QApplication::keyboardModifiers() & Qt::ControlModifier)
actAddCardToSideboard();
else
- addCardHelper(DECK_ZONE_MAIN);
+ addCardHelper(currentCardInfo(), DECK_ZONE_MAIN);
setSaveStatus(true);
}
void TabDeckEditor::actAddCardToSideboard()
{
- addCardHelper(DECK_ZONE_SIDE);
+ addCardHelper(currentCardInfo(), DECK_ZONE_SIDE);
setSaveStatus(true);
}
diff --git a/cockatrice/src/client/tabs/tab_deck_editor.h b/cockatrice/src/client/tabs/tab_deck_editor.h
index 6a5d320a4..ab0c0883e 100644
--- a/cockatrice/src/client/tabs/tab_deck_editor.h
+++ b/cockatrice/src/client/tabs/tab_deck_editor.h
@@ -110,7 +110,6 @@ private:
bool isBlankNewDeck() const;
CardInfoPtr currentCardInfo() const;
- void addCardHelper(QString zoneName);
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void decrementCardHelper(QString zoneName);
bool swapCard(const QModelIndex &idx);
@@ -179,6 +178,7 @@ public:
void createMenus();
void createCentralFrame();
void updateCardInfo(CardInfoPtr _card);
+ void addCardHelper(CardInfoPtr info, QString zoneName);
public slots:
void closeRequest(bool forced = false) override;
diff --git a/cockatrice/src/client/tabs/tab_deck_storage.cpp b/cockatrice/src/client/tabs/tab_deck_storage.cpp
index 4435e5a22..9d35819fc 100644
--- a/cockatrice/src/client/tabs/tab_deck_storage.cpp
+++ b/cockatrice/src/client/tabs/tab_deck_storage.cpp
@@ -4,7 +4,6 @@
#include "../../server/pending_command.h"
#include "../../server/remote/remote_decklist_tree_widget.h"
#include "../../settings/cache_settings.h"
-#include "../game_logic/abstract_client.h"
#include "../get_text_with_max.h"
#include "decklist.h"
#include "pb/command_deck_del.pb.h"
@@ -31,7 +30,9 @@
#include
#include
-TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client)
+TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor,
+ AbstractClient *_client,
+ const ServerInfo_User *currentUserInfo)
: Tab(_tabSupervisor), client(_client)
{
localDirModel = new QFileSystemModel(this);
@@ -151,6 +152,10 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
+
+ connect(client, &AbstractClient::userInfoChanged, this, &TabDeckStorage::handleConnected);
+ connect(client, &AbstractClient::statusChanged, this, &TabDeckStorage::handleConnectionChanged);
+ setRemoteEnabled(currentUserInfo && currentUserInfo->user_level() & ServerInfo_User::IsRegistered);
}
void TabDeckStorage::retranslateUi()
@@ -187,6 +192,36 @@ QString TabDeckStorage::getTargetPath() const
}
}
+void TabDeckStorage::handleConnected(const ServerInfo_User &userInfo)
+{
+ setRemoteEnabled(userInfo.user_level() & ServerInfo_User::IsRegistered);
+}
+
+/**
+ * This is only responsible for handling the disconnect. The connect is already handled elsewhere
+ */
+void TabDeckStorage::handleConnectionChanged(ClientStatus status)
+{
+ if (status == StatusDisconnected) {
+ setRemoteEnabled(false);
+ }
+}
+
+void TabDeckStorage::setRemoteEnabled(bool enabled)
+{
+ aUpload->setEnabled(enabled);
+ aOpenRemoteDeck->setEnabled(enabled);
+ aDownload->setEnabled(enabled);
+ aNewFolder->setEnabled(enabled);
+ aDeleteRemoteDeck->setEnabled(enabled);
+
+ if (enabled) {
+ serverDirView->refreshTree();
+ } else {
+ serverDirView->clearTree();
+ }
+}
+
void TabDeckStorage::actLocalDoubleClick(const QModelIndex &curLeft)
{
if (!localDirModel->isDir(curLeft)) {
diff --git a/cockatrice/src/client/tabs/tab_deck_storage.h b/cockatrice/src/client/tabs/tab_deck_storage.h
index 774b9f609..e9654609e 100644
--- a/cockatrice/src/client/tabs/tab_deck_storage.h
+++ b/cockatrice/src/client/tabs/tab_deck_storage.h
@@ -2,8 +2,10 @@
#define TAB_DECK_STORAGE_H
#include "../../server/remote/remote_decklist_tree_widget.h"
+#include "../game_logic/abstract_client.h"
#include "tab.h"
+class ServerInfo_User;
class AbstractClient;
class QTreeView;
class QFileSystemModel;
@@ -31,12 +33,17 @@ private:
QAction *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
QString getTargetPath() const;
+ void setRemoteEnabled(bool enabled);
+
void uploadDeck(const QString &filePath, const QString &targetPath);
void deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *node);
void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight);
private slots:
+ void handleConnected(const ServerInfo_User &userInfo);
+ void handleConnectionChanged(ClientStatus status);
+
void actLocalDoubleClick(const QModelIndex &curLeft);
void actOpenLocalDeck();
@@ -63,11 +70,11 @@ private slots:
void deleteDeckFinished(const Response &response, const CommandContainer &commandContainer);
public:
- TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client);
+ TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo);
void retranslateUi() override;
QString getTabText() const override
{
- return tr("Deck storage");
+ return tr("Deck Storage");
}
signals:
void openDeckEditor(const DeckLoader *deckLoader);
diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp
index b79968a5c..af1304fa2 100644
--- a/cockatrice/src/client/tabs/tab_game.cpp
+++ b/cockatrice/src/client/tabs/tab_game.cpp
@@ -189,14 +189,7 @@ void TabGame::emitUserEvent()
TabGame::~TabGame()
{
- scene->clearViews();
-
delete replay;
-
- QMapIterator i(players);
- while (i.hasNext()) {
- delete i.next().value();
- }
}
void TabGame::updatePlayerListDockTitle()
@@ -889,8 +882,7 @@ void TabGame::closeGame()
void TabGame::eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext & /*context*/)
{
const ServerInfo_User &userInfo = spectators.value(eventPlayerId);
- messageLog->logSpectatorSay(QString::fromStdString(userInfo.name()), UserLevelFlags(userInfo.user_level()),
- QString::fromStdString(userInfo.privlevel()), QString::fromStdString(event.message()));
+ messageLog->logSpectatorSay(userInfo, QString::fromStdString(event.message()));
}
void TabGame::eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/)
@@ -1097,6 +1089,7 @@ void TabGame::eventLeave(const Event_Leave &event, int eventPlayerId, const Game
players.remove(eventPlayerId);
emit playerRemoved(player);
player->clear();
+ scene->removePlayer(player);
player->deleteLater();
// Rearrange all remaining zones so that attachment relationship updates take place
diff --git a/cockatrice/src/client/tabs/tab_message.cpp b/cockatrice/src/client/tabs/tab_message.cpp
index 2901dc2e5..66e2e3582 100644
--- a/cockatrice/src/client/tabs/tab_message.cpp
+++ b/cockatrice/src/client/tabs/tab_message.cpp
@@ -118,11 +118,8 @@ void TabMessage::messageSent(const Response &response)
void TabMessage::processUserMessageEvent(const Event_UserMessage &event)
{
auto userInfo = event.sender_name() == otherUserInfo->name() ? otherUserInfo : ownUserInfo;
- const UserLevelFlags userLevel(userInfo->user_level());
- const QString userPriv = QString::fromStdString(userInfo->privlevel());
- chatView->appendMessage(QString::fromStdString(event.message()), {}, QString::fromStdString(event.sender_name()),
- userLevel, userPriv, true);
+ chatView->appendMessage(QString::fromStdString(event.message()), {}, *userInfo, true);
if (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this))
soundEngine->playSound("private_message");
if (SettingsCache::instance().getShowMessagePopup() && shouldShowSystemPopup(event))
diff --git a/cockatrice/src/client/tabs/tab_replays.cpp b/cockatrice/src/client/tabs/tab_replays.cpp
index 8382c1c39..8c023d671 100644
--- a/cockatrice/src/client/tabs/tab_replays.cpp
+++ b/cockatrice/src/client/tabs/tab_replays.cpp
@@ -27,7 +27,8 @@
#include
#include
-TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
+TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo)
+ : Tab(_tabSupervisor), client(_client)
{
localDirModel = new QFileSystemModel(this);
localDirModel->setRootPath(SettingsCache::instance().getReplaysPath());
@@ -147,6 +148,10 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) :
connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this,
SLOT(replayAddedEventReceived(const Event_ReplayAdded &)));
+
+ connect(client, &AbstractClient::userInfoChanged, this, &TabReplays::handleConnected);
+ connect(client, &AbstractClient::statusChanged, this, &TabReplays::handleConnectionChanged);
+ setRemoteEnabled(currentUserInfo && currentUserInfo->user_level() & ServerInfo_User::IsRegistered);
}
void TabReplays::retranslateUi()
@@ -165,6 +170,35 @@ void TabReplays::retranslateUi()
aDeleteRemoteReplay->setText(tr("Delete"));
}
+void TabReplays::handleConnected(const ServerInfo_User &userInfo)
+{
+ setRemoteEnabled(userInfo.user_level() & ServerInfo_User::IsRegistered);
+}
+
+/**
+ * This is only responsible for handling the disconnect. The connect is already handled elsewhere
+ */
+void TabReplays::handleConnectionChanged(ClientStatus status)
+{
+ if (status == StatusDisconnected) {
+ setRemoteEnabled(false);
+ }
+}
+
+void TabReplays::setRemoteEnabled(bool enabled)
+{
+ aOpenRemoteReplay->setEnabled(enabled);
+ aDownload->setEnabled(enabled);
+ aKeep->setEnabled(enabled);
+ aDeleteRemoteReplay->setEnabled(enabled);
+
+ if (enabled) {
+ serverDirView->refreshTree();
+ } else {
+ serverDirView->clearTree();
+ }
+}
+
void TabReplays::actLocalDoubleClick(const QModelIndex &curLeft)
{
if (!localDirModel->isDir(curLeft)) {
diff --git a/cockatrice/src/client/tabs/tab_replays.h b/cockatrice/src/client/tabs/tab_replays.h
index 187d2078b..7014a7f6e 100644
--- a/cockatrice/src/client/tabs/tab_replays.h
+++ b/cockatrice/src/client/tabs/tab_replays.h
@@ -1,8 +1,10 @@
#ifndef TAB_REPLAYS_H
#define TAB_REPLAYS_H
+#include "../game_logic/abstract_client.h"
#include "tab.h"
+class ServerInfo_User;
class Response;
class AbstractClient;
class QTreeView;
@@ -29,9 +31,14 @@ private:
QAction *aOpenReplaysFolder;
QAction *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay;
+ void setRemoteEnabled(bool enabled);
+
void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight);
private slots:
+ void handleConnected(const ServerInfo_User &userInfo);
+ void handleConnectionChanged(ClientStatus status);
+
void actLocalDoubleClick(const QModelIndex &curLeft);
void actRenameLocal();
void actOpenLocalReplay();
@@ -58,11 +65,11 @@ signals:
void openReplay(GameReplay *replay);
public:
- TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client);
+ TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo);
void retranslateUi() override;
QString getTabText() const override
{
- return tr("Game replays");
+ return tr("Game Replays");
}
};
diff --git a/cockatrice/src/client/tabs/tab_room.cpp b/cockatrice/src/client/tabs/tab_room.cpp
index b87b937b0..02243cd19 100644
--- a/cockatrice/src/client/tabs/tab_room.cpp
+++ b/cockatrice/src/client/tabs/tab_room.cpp
@@ -287,13 +287,11 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event)
return;
UserListTWI *twi = userList->getUsers().value(senderName);
- UserLevelFlags userLevel;
- QString userPrivLevel;
+ ServerInfo_User userInfo = {};
if (twi) {
- userLevel = UserLevelFlags(twi->getUserInfo().user_level());
- userPrivLevel = QString::fromStdString(twi->getUserInfo().privlevel());
+ userInfo = twi->getUserInfo();
if (SettingsCache::instance().getIgnoreUnregisteredUsers() &&
- !userLevel.testFlag(ServerInfo_User::IsRegistered))
+ !UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered))
return;
}
@@ -306,7 +304,7 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event)
QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) +
"] " + message;
- chatView->appendMessage(message, event.message_type(), senderName, userLevel, userPrivLevel, true);
+ chatView->appendMessage(message, event.message_type(), userInfo, true);
emit userEvent(false);
}
diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp
index c71d37a46..cfd50da02 100644
--- a/cockatrice/src/client/tabs/tab_supervisor.cpp
+++ b/cockatrice/src/client/tabs/tab_supervisor.cpp
@@ -176,11 +176,11 @@ void TabSupervisor::retranslateUi()
{
// tab menu actions
aTabDeckEditor->setText(tr("Deck Editor"));
- aTabVisualDeckStorage->setText(tr("&Visual Deck storage"));
+ aTabVisualDeckStorage->setText(tr("&Visual Deck Storage"));
aTabServer->setText(tr("Server"));
aTabAccount->setText(tr("Account"));
- aTabDeckStorage->setText(tr("Deck storage"));
- aTabReplays->setText(tr("Game replays"));
+ aTabDeckStorage->setText(tr("Deck Storage"));
+ aTabReplays->setText(tr("Game Replays"));
aTabAdmin->setText(tr("Administration"));
aTabLog->setText(tr("Logs"));
@@ -279,6 +279,8 @@ void TabSupervisor::initStartupTabs()
addDeckEditorTab(nullptr);
checkAndTrigger(aTabVisualDeckStorage, SettingsCache::instance().getTabVisualDeckStorageOpen());
+ checkAndTrigger(aTabDeckStorage, SettingsCache::instance().getTabDeckStorageOpen());
+ checkAndTrigger(aTabReplays, SettingsCache::instance().getTabReplaysOpen());
}
/**
@@ -334,6 +336,8 @@ void TabSupervisor::resetTabsMenu()
tabsMenu->addAction(aTabDeckEditor);
tabsMenu->addSeparator();
tabsMenu->addAction(aTabVisualDeckStorage);
+ tabsMenu->addAction(aTabDeckStorage);
+ tabsMenu->addAction(aTabReplays);
}
void TabSupervisor::start(const ServerInfo_User &_userInfo)
@@ -354,14 +358,6 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo)
updatePingTime(0, -1);
- if (userInfo->user_level() & ServerInfo_User::IsRegistered) {
- tabsMenu->addAction(aTabDeckStorage);
- tabsMenu->addAction(aTabReplays);
-
- checkAndTrigger(aTabDeckStorage, SettingsCache::instance().getTabDeckStorageOpen());
- checkAndTrigger(aTabReplays, SettingsCache::instance().getTabReplaysOpen());
- }
-
if (userInfo->user_level() & ServerInfo_User::IsModerator) {
tabsMenu->addSeparator();
tabsMenu->addAction(aTabAdmin);
@@ -379,8 +375,6 @@ void TabSupervisor::startLocal(const QList &_clients)
resetTabsMenu();
tabAccount = nullptr;
- tabDeckStorage = nullptr;
- tabReplays = nullptr;
tabAdmin = nullptr;
tabLog = nullptr;
isLocalGame = true;
@@ -416,12 +410,6 @@ void TabSupervisor::stop()
if (tabServer) {
tabServer->closeRequest(true);
}
- if (tabDeckStorage) {
- tabDeckStorage->closeRequest(true);
- }
- if (tabReplays) {
- tabReplays->closeRequest(true);
- }
if (tabAdmin) {
tabAdmin->closeRequest(true);
}
@@ -460,7 +448,6 @@ void TabSupervisor::actTabVisualDeckStorage(bool checked)
if (checked && !tabVisualDeckStorage) {
tabVisualDeckStorage = new TabDeckStorageVisual(this);
myAddTab(tabVisualDeckStorage, aTabVisualDeckStorage);
- setCurrentWidget(tabVisualDeckStorage);
connect(tabVisualDeckStorage, &Tab::closed, this, [this] {
tabVisualDeckStorage = nullptr;
aTabVisualDeckStorage->setChecked(false);
@@ -508,7 +495,7 @@ void TabSupervisor::actTabDeckStorage(bool checked)
{
SettingsCache::instance().setTabDeckStorageOpen(checked);
if (checked && !tabDeckStorage) {
- tabDeckStorage = new TabDeckStorage(this, client);
+ tabDeckStorage = new TabDeckStorage(this, client, userInfo);
connect(tabDeckStorage, &TabDeckStorage::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
myAddTab(tabDeckStorage, aTabDeckStorage);
connect(tabDeckStorage, &Tab::closed, this, [this] {
@@ -524,7 +511,7 @@ void TabSupervisor::actTabReplays(bool checked)
{
SettingsCache::instance().setTabReplaysOpen(checked);
if (checked && !tabReplays) {
- tabReplays = new TabReplays(this, client);
+ tabReplays = new TabReplays(this, client, userInfo);
connect(tabReplays, &TabReplays::openReplay, this, &TabSupervisor::openReplay);
myAddTab(tabReplays, aTabReplays);
connect(tabReplays, &Tab::closed, this, [this] {
@@ -720,6 +707,18 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
return tab;
}
+TabEdhRec *TabSupervisor::addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander)
+{
+ auto *tab = new TabEdhRec(this);
+ if (cardToQuery) {
+ tab->setCard(cardToQuery, isCommander);
+ }
+
+ myAddTab(tab);
+ setCurrentWidget(tab);
+ return tab;
+}
+
void TabSupervisor::deckEditorClosed(TabDeckEditor *tab)
{
if (tab == currentWidget())
@@ -810,10 +809,10 @@ void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined)
if (auto *tab = getTabAccount()) {
if (tab != currentWidget()) {
tab->setContentsChanged(true);
- QPixmap avatarPixmap =
- UserLevelPixmapGenerator::generatePixmap(13, (UserLevelFlags)userInfoJoined.user_level(), true,
- QString::fromStdString(userInfoJoined.privlevel()));
- setTabIcon(indexOf(tab), QPixmap(avatarPixmap));
+ QIcon avatarIcon = UserLevelPixmapGenerator::generateIcon(
+ 13, (UserLevelFlags)userInfoJoined.user_level(), userInfoJoined.pawn_colors(), true,
+ QString::fromStdString(userInfoJoined.privlevel()));
+ setTabIcon(indexOf(tab), avatarIcon);
}
}
diff --git a/cockatrice/src/client/tabs/tab_supervisor.h b/cockatrice/src/client/tabs/tab_supervisor.h
index 3925807fb..db941aacc 100644
--- a/cockatrice/src/client/tabs/tab_supervisor.h
+++ b/cockatrice/src/client/tabs/tab_supervisor.h
@@ -3,6 +3,7 @@
#include "../../deck/deck_loader.h"
#include "../../server/user/user_list_proxy.h"
+#include "api/edhrec/tab_edhrec.h"
#include "visual_deck_storage/tab_deck_storage_visual.h"
#include
@@ -131,6 +132,10 @@ public:
{
return roomTabs;
}
+ QList getDeckEditorTabs() const
+ {
+ return deckEditorTabs;
+ }
bool getAdminLocked() const;
bool closeRequest();
bool switchToGameTabIfAlreadyExists(const int gameId);
@@ -143,6 +148,7 @@ signals:
public slots:
TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen);
+ TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
void openReplay(GameReplay *replay);
void maximizeMainWindow();
private slots:
diff --git a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
index 728473d8a..e54811db2 100644
--- a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
+++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
@@ -25,7 +25,7 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
void TabDeckStorageVisual::actOpenLocalDeck(QMouseEvent * /*event*/, DeckPreviewWidget *instance)
{
DeckLoader deckLoader;
- if (!deckLoader.loadFromFile(instance->filePath, DeckLoader::CockatriceFormat, true)) {
+ if (!deckLoader.loadFromFile(instance->filePath, DeckLoader::getFormatFromName(instance->filePath), true)) {
return;
}
diff --git a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h
index b5554de42..c6138ef52 100644
--- a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h
+++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h
@@ -24,7 +24,7 @@ public:
void retranslateUi() override{};
[[nodiscard]] QString getTabText() const override
{
- return tr("Visual Deck storage");
+ return tr("Visual Deck Storage");
}
public slots:
diff --git a/cockatrice/src/client/tapped_out_interface.h b/cockatrice/src/client/tapped_out_interface.h
index 2135ff7d9..a6a763d45 100644
--- a/cockatrice/src/client/tapped_out_interface.h
+++ b/cockatrice/src/client/tapped_out_interface.h
@@ -33,7 +33,7 @@ private slots:
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
public:
- TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
+ explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(DeckList *deck);
};
diff --git a/cockatrice/src/client/ui/layouts/flow_layout.cpp b/cockatrice/src/client/ui/layouts/flow_layout.cpp
index f1a776c47..6e00fdb68 100644
--- a/cockatrice/src/client/ui/layouts/flow_layout.cpp
+++ b/cockatrice/src/client/ui/layouts/flow_layout.cpp
@@ -20,8 +20,12 @@
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
-FlowLayout::FlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
- : QLayout(parent), horizontalMargin(hSpacing), verticalMargin(vSpacing)
+FlowLayout::FlowLayout(QWidget *parent,
+ const Qt::Orientation _flowDirection,
+ const int margin,
+ const int hSpacing,
+ const int vSpacing)
+ : QLayout(parent), flowDirection(_flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
@@ -62,27 +66,51 @@ bool FlowLayout::hasHeightForWidth() const
*/
int FlowLayout::heightForWidth(const int width) const
{
- int height = 0;
- int rowWidth = 0;
- int rowHeight = 0;
+ if (flowDirection == Qt::Vertical) {
+ int height = 0;
+ int rowWidth = 0;
+ int rowHeight = 0;
- for (const QLayoutItem *item : items) {
- if (item == nullptr || item->isEmpty()) {
- continue;
- }
+ for (const QLayoutItem *item : items) {
+ if (item == nullptr || item->isEmpty()) {
+ continue;
+ }
- int itemWidth = item->sizeHint().width() + horizontalSpacing();
- if (rowWidth + itemWidth > width) { // Start a new row if the row width exceeds available width
- height += rowHeight + verticalSpacing();
- rowWidth = itemWidth;
- rowHeight = item->sizeHint().height() + verticalSpacing();
- } else {
- rowWidth += itemWidth;
- rowHeight = qMax(rowHeight, item->sizeHint().height());
+ int itemWidth = item->sizeHint().width() + horizontalSpacing();
+ if (rowWidth + itemWidth > width) {
+ height += rowHeight + verticalSpacing();
+ rowWidth = itemWidth;
+ rowHeight = item->sizeHint().height();
+ } else {
+ rowWidth += itemWidth;
+ rowHeight = qMax(rowHeight, item->sizeHint().height());
+ }
}
+ height += rowHeight; // Add height of the last row
+ return height;
+ } else {
+ int width = 0;
+ int colWidth = 0;
+ int colHeight = 0;
+
+ for (const QLayoutItem *item : items) {
+ if (item == nullptr || item->isEmpty()) {
+ continue;
+ }
+
+ int itemHeight = item->sizeHint().height();
+ if (colHeight + itemHeight > width) {
+ width += colWidth;
+ colHeight = itemHeight;
+ colWidth = item->sizeHint().width();
+ } else {
+ colHeight += itemHeight;
+ colWidth = qMax(colWidth, item->sizeHint().width());
+ }
+ }
+ width += colWidth; // Add width of the last column
+ return width;
}
- height += rowHeight; // Add the final row's height
- return height;
}
/**
@@ -93,132 +121,420 @@ void FlowLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
- int left, top, right, bottom;
- getContentsMargins(&left, &top, &right, &bottom); // Retrieves the layout's content margins.
+ if (flowDirection == Qt::Horizontal) {
+ // If we have a parent scroll area, we're clamped to that, else we use our own rectangle.
+ const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth();
- // Adjust the rectangle to exclude margins.
- const QRect adjustedRect = rect.adjusted(+left, +top, -right, -bottom);
+ const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
- // Calculate the available width for items, considering either the adjusted rectangle's width
- // or the parent scroll area width, if applicable.
- const int availableWidth = qMax(adjustedRect.width(), getParentScrollAreaWidth());
+ if (QWidget *parentWidgetPtr = parentWidget()) {
+ parentWidgetPtr->setFixedSize(availableWidth, totalHeight);
+ }
+ } else {
+ const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
- // Arrange all rows of items within the available width and get the total height used.
- const int totalHeight = layoutAllRows(adjustedRect.x(), adjustedRect.y(), availableWidth);
+ const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
- // If the layout's parent is a QWidget, update its minimum size to ensure it can accommodate
- // the arranged items' dimensions.
- if (QWidget *parentWidgetPtr = parentWidget()) {
- parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
+ if (QWidget *parentWidgetPtr = parentWidget()) {
+ parentWidgetPtr->setFixedSize(totalWidth, availableHeight);
+ }
}
}
/**
- * @brief Arranges items in rows based on the available width.
- * Items are added to a row until the row's width exceeds `availableWidth`.
- * Then, a new row is started.
- * @param originX The starting x-coordinate for the row layout.
- * @param originY The starting y-coordinate for the row layout.
- * @param availableWidth The available width to lay out items.
- * @return The y-coordinate of the final row's end position.
+ * @brief Lays out items into rows according to the available width, starting from a given origin.
+ * Each row is arranged within `availableWidth`, wrapping to a new row as necessary.
+ * @param originX The x-coordinate for the layout start position.
+ * @param originY The y-coordinate for the layout start position.
+ * @param availableWidth The width within which each row is constrained.
+ * @return The total height after arranging all rows.
*/
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
{
- QVector rowItems; // Temporary storage for items in the current row.
- int currentXPosition = originX; // Tracks the x-coordinate for placing items in the current row.
- int currentYPosition = originY; // Tracks the y-coordinate, updated after each row.
+ QVector rowItems; // Holds items for the current row
+ int currentXPosition = originX; // Tracks the x-coordinate while placing items
+ int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row
- int rowHeight = 0; // Tracks the maximum height of items in the current row.
+ int rowHeight = 0; // Tracks the maximum height of items in the current row
- // Iterate through all layout items to arrange them.
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
- QSize itemSize = item->sizeHint(); // The suggested size for the item.
- const int itemWidth = itemSize.width() + horizontalSpacing();
+ QSize itemSize = item->sizeHint(); // The suggested size for the current item
+ int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
- // Check if the item fits in the current row's remaining width.
+ // Check if the current item fits in the remaining width of the current row
if (currentXPosition + itemWidth > availableWidth) {
- // If not, layout the current row and start a new row.
+ // If not, layout the current row and start a new row
layoutSingleRow(rowItems, originX, currentYPosition);
- rowItems.clear(); // Clear the temporary storage for the new row.
- currentXPosition = originX; // Reset x-position to the start of the new row.
- currentYPosition += rowHeight + verticalSpacing(); // Move y-position down for the new row.
- rowHeight = 0; // Reset row height for the new row.
+ rowItems.clear(); // Reset the list for the new row
+ currentXPosition = originX; // Reset x-position to the row's start
+ currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
+ rowHeight = 0; // Reset row height for the new row
}
- // Add the item to the current row.
+ // Add the item to the current row
rowItems.append(item);
- rowHeight = qMax(rowHeight, itemSize.height()); // Update the row height to the tallest item.
- currentXPosition += itemSize.width() + horizontalSpacing(); // Move x-position for the next item.
+ rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
+ currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
}
- // Layout the final row if there are remaining items.
+ // Layout the final row if there are any remaining items
layoutSingleRow(rowItems, originX, currentYPosition);
- currentYPosition += rowHeight; // Add the final row's height
- return currentYPosition;
+ // Return the total height used, including the last row's height
+ return currentYPosition + rowHeight;
}
/**
- * @brief Helper function for arranging a single row of items within specified bounds.
- * @param rowItems Items to be arranged in the row.
- * @param x The x-coordinate for starting the row.
- * @param y The y-coordinate for starting the row.
+ * @brief Arranges a single row of items within specified x and y starting positions.
+ * @param rowItems A list of items to be arranged in the row.
+ * @param x The starting x-coordinate for the row.
+ * @param y The starting y-coordinate for the row.
*/
void FlowLayout::layoutSingleRow(const QVector &rowItems, int x, const int y)
{
- // Iterate through each item in the row and position it.
for (QLayoutItem *item : rowItems) {
if (item == nullptr || item->isEmpty()) {
continue;
}
- QSize itemMaxSize = item->widget()->maximumSize(); // Get the item's maximum allowable size.
- // Constrain the item's width and height to its size hint or maximum size.
- int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
- int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
- // Set the item's geometry based on the calculated size and position.
+ // Get the maximum allowed size for the item
+ QSize itemMaxSize = item->widget()->maximumSize();
+ // Constrain the item's width and height to its size hint or maximum size
+ const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
+ const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
+ // Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
- // Move the x-position for the next item, including horizontal spacing.
+ // Move the x-position to the right, leaving space for horizontal spacing
x += itemWidth + horizontalSpacing();
}
}
/**
- * @brief Returns the preferred size for this layout.
- * @return The maximum of all item size hints as a QSize.
+ * @brief Lays out items into columns according to the available height, starting from a given origin.
+ * Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
+ * @param originX The x-coordinate for the layout start position.
+ * @param originY The y-coordinate for the layout start position.
+ * @param availableHeight The height within which each column is constrained.
+ * @return The total width after arranging all columns.
*/
-QSize FlowLayout::sizeHint() const
+int FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
{
- QSize size;
- for (const QLayoutItem *item : items) {
- if (item != nullptr && !item->isEmpty()) {
- size = size.expandedTo(item->sizeHint());
+ QVector colItems; // Holds items for the current column
+ int currentXPosition = originX; // Tracks the x-coordinate while placing items
+ int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
+
+ int colWidth = 0; // Tracks the maximum width of items in the current column
+
+ for (QLayoutItem *item : items) {
+ if (item == nullptr || item->isEmpty()) {
+ continue;
}
+
+ QSize itemSize = item->sizeHint(); // The suggested size for the current item
+
+ // Check if the current item fits in the remaining height of the current column
+ if (currentYPosition + itemSize.height() > availableHeight) {
+ // If not, layout the current column and start a new column
+ layoutSingleColumn(colItems, currentXPosition, originY);
+ colItems.clear(); // Reset the list for the new column
+ currentYPosition = originY; // Reset y-position to the column's start
+ currentXPosition += colWidth; // Move x-position to the next column
+ colWidth = 0; // Reset column width for the new column
+ }
+
+ // Add the item to the current column
+ colItems.append(item);
+ colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
+ currentYPosition += itemSize.height(); // Move y-position for the next item
}
- return size.isValid() ? size : QSize(0, 0);
+
+ // Layout the final column if there are any remaining items
+ layoutSingleColumn(colItems, currentXPosition, originY);
+
+ // Return the total width used, including the last column's width
+ return currentXPosition + colWidth;
}
/**
- * @brief Returns the minimum size required to display all layout items.
- * @return The minimum QSize needed by the layout.
+ * @brief Arranges a single column of items within specified x and y starting positions.
+ * @param colItems A list of items to be arranged in the column.
+ * @param x The starting x-coordinate for the column.
+ * @param y The starting y-coordinate for the column.
+ */
+void FlowLayout::layoutSingleColumn(const QVector &colItems, const int x, int y)
+{
+ for (QLayoutItem *item : colItems) {
+ if (item == nullptr) {
+ qCDebug(FlowLayoutLog) << "Item is null.";
+ continue;
+ }
+
+ if (item->isEmpty()) {
+ qCDebug(FlowLayoutLog) << "Skipping empty item.";
+ continue;
+ }
+
+ // Debugging: Print the item's widget class name and size hint
+ QWidget *widget = item->widget();
+ if (widget) {
+ qCDebug(FlowLayoutLog) << "Widget class:" << widget->metaObject()->className();
+ qCDebug(FlowLayoutLog) << "Widget size hint:" << widget->sizeHint();
+ qCDebug(FlowLayoutLog) << "Widget maximum size:" << widget->maximumSize();
+ qCDebug(FlowLayoutLog) << "Widget minimum size:" << widget->minimumSize();
+
+ // Debugging: Print child widgets
+ const QObjectList &children = widget->children();
+ qCDebug(FlowLayoutLog) << "Child widgets:";
+ for (QObject *child : children) {
+ if (QWidget *childWidget = qobject_cast(child)) {
+ qCDebug(FlowLayoutLog) << " - Child widget class:" << childWidget->metaObject()->className();
+ qCDebug(FlowLayoutLog) << " Size hint:" << childWidget->sizeHint();
+ qCDebug(FlowLayoutLog) << " Maximum size:" << childWidget->maximumSize();
+ }
+ }
+ } else {
+ qCDebug(FlowLayoutLog) << "Item does not have a widget.";
+ }
+
+ // Get the maximum allowed size for the item
+ QSize itemMaxSize = widget->maximumSize();
+ // Constrain the item's width and height to its size hint or maximum size
+ const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
+ const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
+ // Debugging: Print the computed geometry
+ qCDebug(FlowLayoutLog) << "Computed geometry: x=" << x << ", y=" << y << ", width=" << itemWidth
+ << ", height=" << itemHeight;
+
+ // Set the item's geometry based on the computed size and position
+ item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
+
+ // Move the y-position down by the item's height to place the next item below
+ y += itemHeight;
+ }
+}
+
+/**
+ * @brief Calculates the preferred size of the layout based on the flow direction.
+ * @return A QSize representing the ideal dimensions of the layout.
+ */
+QSize FlowLayout::sizeHint() const
+{
+ if (flowDirection == Qt::Horizontal) {
+ return calculateSizeHintHorizontal();
+ } else {
+ return calculateSizeHintVertical();
+ }
+}
+
+/**
+ * @brief Calculates the minimum size required by the layout based on the flow direction.
+ * @return A QSize representing the minimum required dimensions.
*/
QSize FlowLayout::minimumSize() const
{
- QSize size;
+ if (flowDirection == Qt::Horizontal) {
+ return calculateMinimumSizeHorizontal();
+ } else {
+ return calculateMinimumSizeVertical();
+ }
+}
+
+/**
+ * @brief Calculates the size hint for horizontal flow direction.
+ * @return A QSize representing the preferred dimensions.
+ */
+QSize FlowLayout::calculateSizeHintHorizontal() const
+{
+ int maxWidth = 0; // Tracks the maximum width needed
+ int totalHeight = 0; // Tracks the total height across all rows
+ int rowHeight = 0; // Tracks the height of the current row
+ int currentWidth = 0; // Tracks the current row's width
+
+ const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
+
+ qCDebug(FlowLayoutLog) << "Calculating horizontal size hint. Available width:" << availableWidth;
+
for (const QLayoutItem *item : items) {
- if (item != nullptr && !item->isEmpty()) {
- size = size.expandedTo(item->minimumSize());
+ if (!item || item->isEmpty()) {
+ qCDebug(FlowLayoutLog) << "Skipping empty item.";
+ continue;
}
+
+ QSize itemSize = item->sizeHint();
+ int itemWidth = itemSize.width() + horizontalSpacing();
+ qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize << "Width with spacing:" << itemWidth;
+
+ if (currentWidth + itemWidth > availableWidth) {
+ qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
+ maxWidth = qMax(maxWidth, currentWidth);
+ totalHeight += rowHeight + verticalSpacing();
+ qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
+
+ currentWidth = 0;
+ rowHeight = 0;
+ }
+
+ currentWidth += itemWidth;
+ rowHeight = qMax(rowHeight, itemSize.height());
+ qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
}
- size.setWidth(qMin(size.width(), getParentScrollAreaWidth()));
- size.setHeight(qMin(size.height(), getParentScrollAreaHeight()));
+ // Account for the final row
+ maxWidth = qMax(maxWidth, currentWidth);
+ totalHeight += rowHeight;
+ qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
- return size.isValid() ? size : QSize(0, 0);
+ return QSize(maxWidth, totalHeight);
+}
+
+/**
+ * @brief Calculates the minimum size for horizontal flow direction.
+ * @return A QSize representing the minimum required dimensions.
+ */
+QSize FlowLayout::calculateMinimumSizeHorizontal() const
+{
+ int maxWidth = 0; // Tracks the maximum width of a row
+ int totalHeight = 0; // Tracks the total height across all rows
+ int rowHeight = 0; // Tracks the height of the current row
+ int currentWidth = 0; // Tracks the current row's width
+
+ const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
+
+ qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth;
+
+ for (const QLayoutItem *item : items) {
+ if (!item || item->isEmpty()) {
+ qCDebug(FlowLayoutLog) << "Skipping empty item.";
+ continue;
+ }
+
+ QSize itemMinSize = item->minimumSize();
+ int itemWidth = itemMinSize.width() + horizontalSpacing();
+ qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize << "Width with spacing:" << itemWidth;
+
+ if (currentWidth + itemWidth > availableWidth) {
+ qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
+ maxWidth = qMax(maxWidth, currentWidth);
+ totalHeight += rowHeight + verticalSpacing();
+ qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
+
+ currentWidth = 0;
+ rowHeight = 0;
+ }
+
+ currentWidth += itemWidth;
+ rowHeight = qMax(rowHeight, itemMinSize.height());
+ qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
+ }
+
+ // Account for the final row
+ maxWidth = qMax(maxWidth, currentWidth);
+ totalHeight += rowHeight;
+ qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
+
+ return QSize(maxWidth, totalHeight);
+}
+
+/**
+ * @brief Calculates the size hint for vertical flow direction.
+ * @return A QSize representing the preferred dimensions.
+ */
+QSize FlowLayout::calculateSizeHintVertical() const
+{
+ int totalWidth = 0;
+ int maxHeight = 0;
+ int colWidth = 0;
+ int currentHeight = 0;
+
+ const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
+
+ qCDebug(FlowLayoutLog) << "Calculating vertical size hint. Available height:" << availableHeight;
+
+ for (const QLayoutItem *item : items) {
+ if (!item || item->isEmpty()) {
+ qCDebug(FlowLayoutLog) << "Skipping empty item.";
+ continue;
+ }
+
+ QSize itemSize = item->sizeHint();
+ qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize;
+
+ if (currentHeight + itemSize.height() > availableHeight) {
+ qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
+ << "Column width:" << colWidth;
+ totalWidth += colWidth + horizontalSpacing();
+ maxHeight = qMax(maxHeight, currentHeight);
+ qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
+
+ currentHeight = 0;
+ colWidth = 0;
+ }
+
+ currentHeight += itemSize.height() + verticalSpacing();
+ colWidth = qMax(colWidth, itemSize.width());
+ qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
+ }
+
+ // Account for the final column
+ totalWidth += colWidth;
+ maxHeight = qMax(maxHeight, currentHeight);
+ qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
+
+ return QSize(totalWidth, maxHeight);
+}
+
+/**
+ * @brief Calculates the minimum size for vertical flow direction.
+ * @return A QSize representing the minimum required dimensions.
+ */
+QSize FlowLayout::calculateMinimumSizeVertical() const
+{
+ int totalWidth = 0; // Tracks the total width across all columns
+ int maxHeight = 0; // Tracks the maximum height of a column
+ int colWidth = 0; // Tracks the width of the current column
+ int currentHeight = 0; // Tracks the current column's height
+
+ const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
+
+ qCDebug(FlowLayoutLog) << "Calculating vertical minimum size. Available height:" << availableHeight;
+
+ for (const QLayoutItem *item : items) {
+ if (!item || item->isEmpty()) {
+ qCDebug(FlowLayoutLog) << "Skipping empty item.";
+ continue;
+ }
+
+ QSize itemMinSize = item->minimumSize();
+ int itemHeight = itemMinSize.height() + verticalSpacing();
+ qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize
+ << "Height with spacing:" << itemHeight;
+
+ if (currentHeight + itemHeight > availableHeight) {
+ qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
+ << "Column width:" << colWidth;
+ totalWidth += colWidth + horizontalSpacing();
+ maxHeight = qMax(maxHeight, currentHeight);
+ qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
+
+ currentHeight = 0;
+ colWidth = 0;
+ }
+
+ currentHeight += itemHeight;
+ colWidth = qMax(colWidth, itemMinSize.width());
+ qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
+ }
+
+ // Account for the final column
+ totalWidth += colWidth;
+ maxHeight = qMax(maxHeight, currentHeight);
+ qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
+
+ return QSize(totalWidth, maxHeight);
}
/**
diff --git a/cockatrice/src/client/ui/layouts/flow_layout.h b/cockatrice/src/client/ui/layouts/flow_layout.h
index 6fde7b802..f87386c76 100644
--- a/cockatrice/src/client/ui/layouts/flow_layout.h
+++ b/cockatrice/src/client/ui/layouts/flow_layout.h
@@ -3,16 +3,22 @@
#include
#include
+#include
#include
#include
+inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout");
+
class FlowLayout : public QLayout
{
public:
explicit FlowLayout(QWidget *parent = nullptr);
- FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing);
+ FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
~FlowLayout() override;
+ QSize calculateMinimumSizeHorizontal() const;
+ QSize calculateSizeHintVertical() const;
+ QSize calculateMinimumSizeVertical() const;
void addItem(QLayoutItem *item) override;
[[nodiscard]] int count() const override;
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
@@ -31,11 +37,15 @@ public:
void setGeometry(const QRect &rect) override;
virtual int layoutAllRows(int originX, int originY, int availableWidth);
virtual void layoutSingleRow(const QVector &rowItems, int x, int y);
+ int layoutAllColumns(int originX, int originY, int availableHeight);
+ void layoutSingleColumn(const QVector &colItems, int x, int y);
[[nodiscard]] QSize sizeHint() const override;
[[nodiscard]] QSize minimumSize() const override;
+ QSize calculateSizeHintHorizontal() const;
protected:
QList items; // List to store layout items
+ Qt::Orientation flowDirection;
int horizontalMargin;
int verticalMargin;
};
diff --git a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp b/cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp
deleted file mode 100644
index 16f409db7..000000000
--- a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-#include "horizontal_flow_layout.h"
-
-/**
- * @brief Constructs a HorizontalFlowLayout instance with the specified parent widget.
- * This layout arranges items in columns within the given height, automatically adjusting its width.
- * @param parent The parent widget to which this layout belongs.
- * @param margin The layout margin.
- * @param hSpacing The horizontal spacing between items.
- * @param vSpacing The vertical spacing between items.
- */
-HorizontalFlowLayout::HorizontalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
- : FlowLayout(parent, margin, hSpacing, vSpacing)
-{
-}
-
-/**
- * @brief Destructor for HorizontalFlowLayout, responsible for cleaning up layout items.
- */
-HorizontalFlowLayout::~HorizontalFlowLayout()
-{
- QLayoutItem *item;
- while ((item = FlowLayout::takeAt(0))) {
- delete item;
- }
-}
-
-/**
- * @brief Calculates the required width to display all items, given a specified height.
- * This method arranges items into columns and determines the total width needed.
- * @param height The available height for arranging layout items.
- * @return The total width required to fit all items, organized in columns constrained by the given height.
- */
-int HorizontalFlowLayout::heightForWidth(const int height) const
-{
- int width = 0;
- int colWidth = 0;
- int colHeight = 0;
-
- for (const QLayoutItem *item : items) {
- if (item == nullptr || item->isEmpty()) {
- continue;
- }
-
- int itemHeight = item->sizeHint().height();
- if (colHeight + itemHeight > height) {
- width += colWidth;
- colHeight = itemHeight;
- colWidth = item->sizeHint().width();
- } else {
- colHeight += itemHeight;
- colWidth = qMax(colWidth, item->sizeHint().width());
- }
- }
- width += colWidth; // Add width of the last column
- return width;
-}
-
-/**
- * @brief Sets the geometry of the layout items, arranging them in columns within the given height.
- * @param rect The rectangle area defining the layout space.
- */
-void HorizontalFlowLayout::setGeometry(const QRect &rect)
-{
- const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
-
- const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
-
- if (QWidget *parentWidgetPtr = parentWidget()) {
- parentWidgetPtr->setMinimumSize(totalWidth, availableHeight);
- }
-}
-
-/**
- * @brief Lays out items into columns according to the available height, starting from a given origin.
- * Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
- * @param originX The x-coordinate for the layout start position.
- * @param originY The y-coordinate for the layout start position.
- * @param availableHeight The height within which each column is constrained.
- * @return The total width after arranging all columns.
- */
-int HorizontalFlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
-{
- QVector colItems; // Holds items for the current column
- int currentXPosition = originX; // Tracks the x-coordinate while placing items
- int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
-
- int colWidth = 0; // Tracks the maximum width of items in the current column
-
- for (QLayoutItem *item : items) {
- if (item == nullptr || item->isEmpty()) {
- continue;
- }
-
- QSize itemSize = item->sizeHint(); // The suggested size for the current item
-
- // Check if the current item fits in the remaining height of the current column
- if (currentYPosition + itemSize.height() > availableHeight) {
- // If not, layout the current column and start a new column
- layoutSingleColumn(colItems, currentXPosition, originY);
- colItems.clear(); // Reset the list for the new column
- currentYPosition = originY; // Reset y-position to the column's start
- currentXPosition += colWidth; // Move x-position to the next column
- colWidth = 0; // Reset column width for the new column
- }
-
- // Add the item to the current column
- colItems.append(item);
- colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
- currentYPosition += itemSize.height(); // Move y-position for the next item
- }
-
- // Layout the final column if there are any remaining items
- layoutSingleColumn(colItems, currentXPosition, originY);
-
- // Return the total width used, including the last column's width
- return currentXPosition + colWidth;
-}
-
-/**
- * @brief Arranges a single column of items within specified x and y starting positions.
- * @param colItems A list of items to be arranged in the column.
- * @param x The starting x-coordinate for the column.
- * @param y The starting y-coordinate for the column.
- */
-void HorizontalFlowLayout::layoutSingleColumn(const QVector &colItems, const int x, int y)
-{
- for (QLayoutItem *item : colItems) {
- if (item != nullptr && item->isEmpty()) {
- continue;
- }
-
- // Get the maximum allowed size for the item
- QSize itemMaxSize = item->widget()->maximumSize();
- // Constrain the item's width and height to its size hint or maximum size
- const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
- const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
- // Set the item's geometry based on the computed size and position
- item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
- // Move the y-position down by the item's height to place the next item below
- y += itemHeight;
- }
-}
diff --git a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.h b/cockatrice/src/client/ui/layouts/horizontal_flow_layout.h
deleted file mode 100644
index f6aebb284..000000000
--- a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef HORIZONTAL_FLOW_LAYOUT_H
-#define HORIZONTAL_FLOW_LAYOUT_H
-
-#include "flow_layout.h"
-
-class HorizontalFlowLayout : public FlowLayout
-{
-public:
- explicit HorizontalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0);
- ~HorizontalFlowLayout() override;
-
- [[nodiscard]] int heightForWidth(int height) const override;
-
- void setGeometry(const QRect &rect) override;
- int layoutAllColumns(int originX, int originY, int availableHeight);
- static void layoutSingleColumn(const QVector &colItems, int x, int y);
-};
-
-#endif // HORIZONTAL_FLOW_LAYOUT_H
\ No newline at end of file
diff --git a/cockatrice/src/client/ui/layouts/vertical_flow_layout.cpp b/cockatrice/src/client/ui/layouts/vertical_flow_layout.cpp
deleted file mode 100644
index 90e154662..000000000
--- a/cockatrice/src/client/ui/layouts/vertical_flow_layout.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-#include "vertical_flow_layout.h"
-
-/**
- * @brief Constructs a VerticalFlowLayout instance with the specified parent widget.
- * This layout arranges items in rows within the given width, automatically adjusting its height.
- * @param parent The parent widget to which this layout belongs.
- * @param margin The layout margin.
- * @param hSpacing The horizontal spacing between items.
- * @param vSpacing The vertical spacing between items.
- */
-VerticalFlowLayout::VerticalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
- : FlowLayout(parent, margin, hSpacing, vSpacing)
-{
-}
-
-/**
- * @brief Destructor for VerticalFlowLayout, responsible for cleaning up layout items.
- */
-VerticalFlowLayout::~VerticalFlowLayout()
-{
- QLayoutItem *item;
- while ((item = FlowLayout::takeAt(0))) {
- delete item;
- }
-}
-
-/**
- * @brief Calculates the required height to display all items, given a specified width.
- * This method arranges items into rows and determines the total height needed.
- * @param width The available width for arranging layout items.
- * @return The total height required to fit all items, organized in rows constrained by the given width.
- */
-int VerticalFlowLayout::heightForWidth(const int width) const
-{
- int height = 0;
- int rowWidth = 0;
- int rowHeight = 0;
-
- for (const QLayoutItem *item : items) {
- if (item == nullptr || item->isEmpty()) {
- continue;
- }
-
- int itemWidth = item->sizeHint().width() + horizontalSpacing();
- if (rowWidth + itemWidth > width) {
- height += rowHeight + verticalSpacing();
- rowWidth = itemWidth;
- rowHeight = item->sizeHint().height();
- } else {
- rowWidth += itemWidth;
- rowHeight = qMax(rowHeight, item->sizeHint().height());
- }
- }
- height += rowHeight; // Add height of the last row
- return height;
-}
-
-/**
- * @brief Sets the geometry of the layout items, arranging them in rows within the given width.
- * @param rect The rectangle area defining the layout space.
- */
-void VerticalFlowLayout::setGeometry(const QRect &rect)
-{
- // If we have a parent scroll area, we're clamped to that, else we use our own rectangle.
- const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth();
-
- const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
-
- if (QWidget *parentWidgetPtr = parentWidget()) {
- parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
- }
-}
-
-/**
- * @brief Lays out items into rows according to the available width, starting from a given origin.
- * Each row is arranged within `availableWidth`, wrapping to a new row as necessary.
- * @param originX The x-coordinate for the layout start position.
- * @param originY The y-coordinate for the layout start position.
- * @param availableWidth The width within which each row is constrained.
- * @return The total height after arranging all rows.
- */
-int VerticalFlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
-{
- QVector rowItems; // Holds items for the current row
- int currentXPosition = originX; // Tracks the x-coordinate while placing items
- int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row
-
- int rowHeight = 0; // Tracks the maximum height of items in the current row
-
- for (QLayoutItem *item : items) {
- if (item == nullptr || item->isEmpty()) {
- continue;
- }
-
- QSize itemSize = item->sizeHint(); // The suggested size for the current item
- int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
-
- // Check if the current item fits in the remaining width of the current row
- if (currentXPosition + itemWidth > availableWidth) {
- // If not, layout the current row and start a new row
- layoutSingleRow(rowItems, originX, currentYPosition);
- rowItems.clear(); // Reset the list for the new row
- currentXPosition = originX; // Reset x-position to the row's start
- currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
- rowHeight = 0; // Reset row height for the new row
- }
-
- // Add the item to the current row
- rowItems.append(item);
- rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
- currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
- }
-
- // Layout the final row if there are any remaining items
- layoutSingleRow(rowItems, originX, currentYPosition);
-
- // Return the total height used, including the last row's height
- return currentYPosition + rowHeight;
-}
-
-/**
- * @brief Arranges a single row of items within specified x and y starting positions.
- * @param rowItems A list of items to be arranged in the row.
- * @param x The starting x-coordinate for the row.
- * @param y The starting y-coordinate for the row.
- */
-void VerticalFlowLayout::layoutSingleRow(const QVector &rowItems, int x, const int y)
-{
- for (QLayoutItem *item : rowItems) {
- if (item == nullptr || item->isEmpty()) {
- continue;
- }
-
- // Get the maximum allowed size for the item
- QSize itemMaxSize = item->widget()->maximumSize();
- // Constrain the item's width and height to its size hint or maximum size
- const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
- const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
- // Set the item's geometry based on the computed size and position
- item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
- // Move the x-position to the right, leaving space for horizontal spacing
- x += itemWidth + horizontalSpacing();
- }
-}
diff --git a/cockatrice/src/client/ui/layouts/vertical_flow_layout.h b/cockatrice/src/client/ui/layouts/vertical_flow_layout.h
deleted file mode 100644
index 5dcd4945c..000000000
--- a/cockatrice/src/client/ui/layouts/vertical_flow_layout.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef VERTICAL_FLOW_LAYOUT_H
-#define VERTICAL_FLOW_LAYOUT_H
-
-#include "flow_layout.h"
-
-class VerticalFlowLayout : public FlowLayout
-{
-public:
- explicit VerticalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0);
- ~VerticalFlowLayout() override;
-
- [[nodiscard]] int heightForWidth(int width) const override;
-
- void setGeometry(const QRect &rect) override;
- int layoutAllRows(int originX, int originY, int availableWidth) override;
- void layoutSingleRow(const QVector &rowItems, int x, int y) override;
-};
-
-#endif // VERTICAL_FLOW_LAYOUT_H
\ No newline at end of file
diff --git a/cockatrice/src/client/ui/pixel_map_generator.cpp b/cockatrice/src/client/ui/pixel_map_generator.cpp
index 09dc2d1ae..7e86d73ac 100644
--- a/cockatrice/src/client/ui/pixel_map_generator.cpp
+++ b/cockatrice/src/client/ui/pixel_map_generator.cpp
@@ -3,9 +3,52 @@
#include "pb/serverinfo_user.pb.h"
#include
-#include
+#include
+#include
#include
#include
+#include
+
+#define DEFAULT_COLOR_UNREGISTERED "#32c8ec";
+#define DEFAULT_COLOR_REGISTERED "#5ed900";
+#define DEFAULT_COLOR_DONATOR "#8c5fd3";
+#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff";
+#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
+#define DEFAULT_COLOR_ADMIN "#ff2701";
+
+/**
+ * Loads in an svg from file and scales it without affecting image quality.
+ *
+ * @param svgPath The path to the svg file. Automatically appends ".svg" to the end if not present
+ * @param size The desired size of the pixmap
+ *
+ * @return The svg loaded into a Pixmap with the given size, or an empty Pixmap if the loading failed.
+ */
+static QPixmap loadSvg(const QString &svgPath, const QSize &size)
+{
+ QString path = svgPath;
+ if (!path.endsWith(".svg")) {
+ path += ".svg";
+ }
+
+ QSvgRenderer svgRenderer(path);
+
+ if (!svgRenderer.isValid()) {
+ qCWarning(PixelMapGeneratorLog) << "Failed to load" << path;
+ return {};
+ }
+
+ // Makes sure the pixmap is at least as large as the svg, so that we don't lose any detail.
+ // QIcon.pixmap(size) will automatically scale down the image, but it won't scale it up.
+ QPixmap pix(svgRenderer.defaultSize().expandedTo(size));
+ pix.fill(Qt::transparent);
+
+ QPainter pixPainter(&pix);
+ svgRenderer.render(&pixPainter);
+
+ // Converting the pixmap to a QIcon and back is the easiest way to scale down a svg without affecting image quality
+ return QIcon(pix).pixmap(size);
+}
QMap PhasePixmapGenerator::pmCache;
@@ -15,8 +58,7 @@ QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
if (pmCache.contains(key))
return pmCache.value(key);
- QPixmap pixmap =
- QPixmap("theme:phases/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPixmap pixmap = loadSvg("theme:phases/" + name, QSize(height, height));
pmCache.insert(key, pixmap);
return pixmap;
@@ -26,20 +68,25 @@ QMap CounterPixmapGenerator::pmCache;
QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool highlight)
{
+ // The colorless counter is named "x" by the server but the file is named "general.svg"
+ if (name == "x") {
+ name = "general";
+ }
+
if (highlight)
name.append("_highlight");
QString key = name + QString::number(height);
if (pmCache.contains(key))
return pmCache.value(key);
- QPixmap pixmap =
- QPixmap("theme:counters/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPixmap pixmap = loadSvg("theme:counters/" + name, QSize(height, height));
+
+ // fall back to colorless counter if the name can't be found
if (pixmap.isNull()) {
name = "general";
if (highlight)
name.append("_highlight");
- pixmap =
- QPixmap("theme:counters/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ pixmap = loadSvg("theme:counters/" + name, QSize(height, height));
}
pmCache.insert(key, pixmap);
@@ -83,12 +130,17 @@ QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countr
return pmCache.value(key);
int width = height * 2;
- QPixmap pixmap = QPixmap("theme:countries/" + countryCode.toLower())
- .scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPixmap pixmap = loadSvg("theme:countries/" + countryCode.toLower(), QSize(width, height));
QPainter painter(&pixmap);
painter.setPen(Qt::black);
+
+ // width/height offset was determined through trial-and-error
+#ifdef Q_OS_WIN
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
+#else
+ painter.drawRect(0, 0, pixmap.width() / 2, pixmap.height() / 2);
+#endif
pmCache.insert(key, pixmap);
return pixmap;
@@ -96,40 +148,179 @@ QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countr
QMap CountryPixmapGenerator::pmCache;
-QPixmap UserLevelPixmapGenerator::generatePixmap(int height, UserLevelFlags userLevel, bool isBuddy, QString privLevel)
+/**
+ * Updates tags in the svg
+ *
+ * @param elem The svg
+ * @param tagName tag with attribute to update
+ * @param attrName attribute to be updated
+ * @param idName id that the tag has to match
+ * @param attrValue the value to update the attribute to
+ */
+void setAttrRecur(QDomElement &elem,
+ const QString &tagName,
+ const QString &attrName,
+ const QString &idName,
+ const QString &attrValue)
{
+ if (elem.tagName().compare(tagName) == 0) {
+ if (elem.attribute("id").compare(idName) == 0) {
+ elem.setAttribute(attrName, attrValue);
+ }
+ }
- QString key = QString::number(height * 10000) + ":" + (short)userLevel + ":" + (short)isBuddy + ":" + privLevel;
- if (pmCache.contains(key))
- return pmCache.value(key);
-
- QString levelString;
- if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
- levelString = "admin";
- if (privLevel.toLower() == "vip")
- levelString.append("_" + privLevel.toLower());
- } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
- levelString = "moderator";
- if (privLevel.toLower() == "vip")
- levelString.append("_" + privLevel.toLower());
- } else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) {
- levelString = "registered";
- if (privLevel.toLower() != "none")
- levelString.append("_" + privLevel.toLower());
- } else
- levelString = "normal";
-
- if (isBuddy)
- levelString.append("_buddy");
-
- QPixmap pixmap = QPixmap("theme:userlevels/" + levelString)
- .scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
-
- pmCache.insert(key, pixmap);
- return pixmap;
+ for (int i = 0; i < elem.childNodes().count(); i++) {
+ if (!elem.childNodes().at(i).isElement()) {
+ continue;
+ }
+ auto docElem = elem.childNodes().at(i).toElement();
+ setAttrRecur(docElem, tagName, attrName, idName, attrValue);
+ }
}
-QMap UserLevelPixmapGenerator::pmCache;
+/**
+ * Loads the usericon svg and fills in its colors.
+ * The image is kept as a QIcon to preserve the image quality.
+ *
+ * Call icon.pixmap(w, h) in order to convert this icon into a pixmap with the given dimensions.
+ * Avoid scaling the pixmap in other ways, as that destroys image quality.
+ *
+ * @param minSize If the dimensions of the source svg is smaller than this, then it will be scaled up to this size
+ */
+static QIcon loadAndColorSvg(const QString &iconPath,
+ const QString &colorLeft,
+ const std::optional &colorRight,
+ const int minSize)
+{
+ QFile file(iconPath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCWarning(PixelMapGeneratorLog) << "Unable to open" << iconPath;
+ return {};
+ }
+
+ const auto &baData = file.readAll();
+ QDomDocument doc;
+ doc.setContent(baData);
+
+ auto docElem = doc.documentElement();
+
+ setAttrRecur(docElem, "path", "fill", "left", colorLeft);
+ if (colorRight.has_value()) {
+ setAttrRecur(docElem, "path", "fill", "right", colorRight.value());
+ }
+
+ QSvgRenderer svgRenderer(doc.toByteArray());
+
+ QPixmap pix(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize)));
+ pix.fill(Qt::transparent);
+
+ QPainter pixPainter(&pix);
+ svgRenderer.render(&pixPainter);
+
+ return QIcon(pix);
+}
+
+QPixmap UserLevelPixmapGenerator::generatePixmap(int height,
+ UserLevelFlags userLevel,
+ ServerInfo_User::PawnColorsOverride pawnColorsOverride,
+ bool isBuddy,
+ const QString &privLevel)
+{
+ return generateIcon(height, userLevel, pawnColorsOverride, isBuddy, privLevel).pixmap(height, height);
+}
+
+QIcon UserLevelPixmapGenerator::generateIcon(int minHeight,
+ UserLevelFlags userLevel,
+ ServerInfo_User::PawnColorsOverride pawnColorsOverride,
+ bool isBuddy,
+ const QString &privLevel)
+{
+ std::optional colorLeft = std::nullopt;
+ if (pawnColorsOverride.has_left_side()) {
+ colorLeft = QString::fromStdString(pawnColorsOverride.left_side());
+ }
+
+ std::optional colorRight = std::nullopt;
+ if (pawnColorsOverride.has_right_side()) {
+ colorRight = QString::fromStdString(pawnColorsOverride.right_side());
+ }
+
+ QString key = QString::number(minHeight * 10000) + ":" + static_cast(userLevel) + ":" +
+ static_cast(isBuddy) + ":" + privLevel.toLower() + ":" + colorLeft.value_or("") + ":" +
+ colorRight.value_or("");
+
+ if (iconCache.contains(key)) {
+ return iconCache.value(key);
+ }
+
+ QIcon icon = colorLeft.has_value()
+ ? generateIconWithColorOverride(minHeight, isBuddy, privLevel, colorLeft, colorRight)
+ : generateIconDefault(minHeight, userLevel, isBuddy, privLevel);
+
+ iconCache.insert(key, icon);
+ return icon;
+}
+
+QIcon UserLevelPixmapGenerator::generateIconDefault(int height,
+ UserLevelFlags userLevel,
+ bool isBuddy,
+ const QString &privLevel)
+{
+ QString iconType;
+ if (isBuddy) {
+ iconType = "star";
+ } else if (privLevel.toLower() == "vip") {
+ iconType = "pawn_vip";
+ } else {
+ iconType = "pawn";
+ }
+
+ QString arity = "single";
+ QString colorLeft;
+ std::optional colorRight = std::nullopt;
+
+ if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
+ colorLeft = DEFAULT_COLOR_ADMIN;
+ } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
+ colorLeft = DEFAULT_COLOR_MODERATOR_LEFT;
+ colorRight = DEFAULT_COLOR_MODERATOR_RIGHT;
+ arity = "double";
+ } else if (privLevel.toLower() == "donator") {
+ colorLeft = DEFAULT_COLOR_DONATOR;
+ } else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) {
+ colorLeft = DEFAULT_COLOR_REGISTERED;
+ } else {
+ colorLeft = DEFAULT_COLOR_UNREGISTERED;
+ }
+
+ QString iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
+
+ return loadAndColorSvg(iconPath, colorLeft, colorRight, height);
+}
+
+QIcon UserLevelPixmapGenerator::generateIconWithColorOverride(int height,
+ bool isBuddy,
+ const QString &privLevel,
+ std::optional colorLeft,
+ std::optional colorRight)
+{
+ QString iconType;
+ if (isBuddy) {
+ iconType = "star";
+ } else if (privLevel.toLower() == "vip") {
+ iconType = "pawn_vip";
+ } else {
+ iconType = "pawn";
+ }
+
+ QString arity = colorRight.has_value() ? "double" : "single";
+
+ QString iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
+
+ return loadAndColorSvg(iconPath, colorLeft.value(), colorRight, height);
+}
+
+QMap UserLevelPixmapGenerator::iconCache;
QPixmap LockPixmapGenerator::generatePixmap(int height)
{
@@ -138,7 +329,7 @@ QPixmap LockPixmapGenerator::generatePixmap(int height)
if (pmCache.contains(key))
return pmCache.value(key);
- QPixmap pixmap = QPixmap("theme:icons/lock").scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPixmap pixmap = loadSvg("theme:icons/lock", QSize(height, height));
pmCache.insert(key, pixmap);
return pixmap;
}
diff --git a/cockatrice/src/client/ui/pixel_map_generator.h b/cockatrice/src/client/ui/pixel_map_generator.h
index 65f9a96fc..fc18f107d 100644
--- a/cockatrice/src/client/ui/pixel_map_generator.h
+++ b/cockatrice/src/client/ui/pixel_map_generator.h
@@ -3,9 +3,13 @@
#include "user_level.h"
+#include
+#include
#include
#include
+inline Q_LOGGING_CATEGORY(PixelMapGeneratorLog, "pixel_map_generator");
+
class PhasePixmapGenerator
{
private:
@@ -61,13 +65,30 @@ public:
class UserLevelPixmapGenerator
{
private:
- static QMap pmCache;
+ static QMap iconCache;
+
+ static QIcon generateIconDefault(int height, UserLevelFlags userLevel, bool isBuddy, const QString &privLevel);
+ static QIcon generateIconWithColorOverride(int height,
+ bool isBuddy,
+ const QString &privLevel,
+ std::optional colorLeft,
+ std::optional colorRight);
public:
- static QPixmap generatePixmap(int height, UserLevelFlags userLevel, bool isBuddy, QString privLevel = "NONE");
+ static QPixmap generatePixmap(int height,
+ UserLevelFlags userLevel,
+ ServerInfo_User::PawnColorsOverride pawnColors,
+ bool isBuddy,
+ const QString &privLevel);
+
+ static QIcon generateIcon(int minHeight,
+ UserLevelFlags userLevel,
+ ServerInfo_User::PawnColorsOverride pawnColors,
+ bool isBuddy,
+ const QString &privLevel);
static void clear()
{
- pmCache.clear();
+ iconCache.clear();
}
};
diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp
index 841b14e1b..714ad20ca 100644
--- a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp
@@ -12,7 +12,7 @@
#include
CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *parent)
- : QTabWidget(parent), info(nullptr), cardTextOnly(false)
+ : QTabWidget(parent), info(nullptr), viewTransformationButton(nullptr), cardTextOnly(false)
{
setContentsMargins(3, 3, 3, 3);
pic = new CardInfoPictureWidget();
@@ -23,11 +23,6 @@ CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *paren
text->setObjectName("text");
connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &)));
- viewTransformationButton = new QPushButton();
- viewTransformationButton->setObjectName("viewTransformationButton");
- connect(viewTransformationButton, &QPushButton::clicked, this, &CardInfoFrameWidget::viewTransformation);
- viewTransformationButton->setVisible(false);
-
tab1 = new QWidget(this);
tab2 = new QWidget(this);
tab3 = new QWidget(this);
@@ -74,36 +69,84 @@ void CardInfoFrameWidget::retranslateUi()
setTabText(ImageOnlyView, tr("Image"));
setTabText(TextOnlyView, tr("Description"));
setTabText(ImageAndTextView, tr("Both"));
- viewTransformationButton->setText(tr("View transformation"));
+
+ if (viewTransformationButton) {
+ viewTransformationButton->setText(tr("View transformation"));
+ }
}
-void CardInfoFrameWidget::setViewMode(int mode)
+void CardInfoFrameWidget::setViewTransformationButtonVisibility(bool visible)
{
- if (currentIndex() != mode)
- setCurrentIndex(mode);
+ if (!viewTransformationButton && visible) {
+ viewTransformationButton = new QPushButton();
+ viewTransformationButton->setObjectName("viewTransformationButton");
+ connect(viewTransformationButton, &QPushButton::clicked, this, &CardInfoFrameWidget::viewTransformation);
+ refreshLayout();
+ } else if (viewTransformationButton && !visible) {
+ // Deleting a widget automatically removes it from its parent
+ viewTransformationButton->deleteLater();
+ viewTransformationButton = nullptr;
+ }
+}
- switch (mode) {
+/**
+ * Adds the widgets to the layouts that are relevant to the currently active tab.
+ *
+ * QWidgets can only have one parent, so we need to re-parent the shared widgets whenever we switch tabs.
+ */
+void CardInfoFrameWidget::refreshLayout()
+{
+ switch (currentIndex()) {
case ImageOnlyView:
case TextOnlyView:
+ // We need to always parent all widgets, even the ones that aren't visible,
+ // since an unparented widget becomes free-floating.
tab1Layout->addWidget(pic);
- tab1Layout->addWidget(viewTransformationButton);
+ if (viewTransformationButton) {
+ tab1Layout->addWidget(viewTransformationButton);
+ }
tab2Layout->addWidget(text);
break;
case ImageAndTextView:
splitter->addWidget(pic);
- splitter->addWidget(viewTransformationButton);
+ if (viewTransformationButton) {
+ splitter->addWidget(viewTransformationButton);
+ }
splitter->addWidget(text);
break;
default:
break;
}
+ retranslateUi();
+}
+
+void CardInfoFrameWidget::setViewMode(int mode)
+{
+ if (currentIndex() != mode) {
+ setCurrentIndex(mode);
+ }
+
+ refreshLayout();
SettingsCache::instance().setCardInfoViewMode(mode);
}
+static bool hasTransformation(const CardInfoPtr &info)
+{
+ if (!info) {
+ return false;
+ }
+
+ for (const auto &cardRelation : info->getAllRelatedCards()) {
+ if (cardRelation->getDoesTransform()) {
+ return true;
+ }
+ }
+ return false;
+}
+
void CardInfoFrameWidget::setCard(CardInfoPtr card)
{
- viewTransformationButton->setVisible(false);
if (info) {
disconnect(info.data(), nullptr, this, nullptr);
}
@@ -114,15 +157,7 @@ void CardInfoFrameWidget::setCard(CardInfoPtr card)
connect(info.data(), SIGNAL(destroyed()), this, SLOT(clearCard()));
}
- if (info) {
- const auto &cardRelations = info->getAllRelatedCards();
- for (const auto &cardRelation : cardRelations) {
- if (cardRelation->getDoesTransform()) {
- viewTransformationButton->setVisible(true);
- break;
- }
- }
- }
+ setViewTransformationButtonVisibility(hasTransformation(info));
text->setCard(info);
pic->setCard(info);
diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h
index 6bb7fa0d8..2b6012f1f 100644
--- a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h
+++ b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h
@@ -25,6 +25,9 @@ private:
QVBoxLayout *tab1Layout, *tab2Layout, *tab3Layout;
QSplitter *splitter;
+ void setViewTransformationButtonVisibility(bool visible);
+ void refreshLayout();
+
public:
enum ViewMode
{
diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp
index d2806121a..f5a54e5c2 100644
--- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp
@@ -3,7 +3,10 @@
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/cards/card_item.h"
#include "../../../../settings/cache_settings.h"
+#include "../../../tabs/tab_deck_editor.h"
+#include "../../../tabs/tab_supervisor.h"
#include "../../picture_loader/picture_loader.h"
+#include "../../window_main.h"
#include
#include
@@ -253,22 +256,27 @@ QMenu *CardInfoPictureWidget::createRightClickMenu()
return cardMenu;
}
- auto viewRelatedCards = new QMenu(tr("View related cards"));
- cardMenu->addMenu(viewRelatedCards);
+ cardMenu->addMenu(createViewRelatedCardsMenu());
+ cardMenu->addMenu(createAddToOpenDeckMenu());
+
+ return cardMenu;
+}
+
+QMenu *CardInfoPictureWidget::createViewRelatedCardsMenu()
+{
+ auto viewRelatedCards = new QMenu(tr("View related cards"));
- bool atLeastOneGoodRelationFound = false;
QList relatedCards = info->getAllRelatedCards();
- for (const CardRelation *cardRelation : relatedCards) {
- CardInfoPtr relatedCard = CardDatabaseManager::getInstance()->getCard(cardRelation->getName());
- if (relatedCard != nullptr) {
- atLeastOneGoodRelationFound = true;
- break;
- }
- }
+
+ auto relatedCardExists = [](const CardRelation *cardRelation) {
+ return CardDatabaseManager::getInstance()->getCard(cardRelation->getName()) != nullptr;
+ };
+
+ bool atLeastOneGoodRelationFound = std::any_of(relatedCards.begin(), relatedCards.end(), relatedCardExists);
if (!atLeastOneGoodRelationFound) {
viewRelatedCards->setEnabled(false);
- return cardMenu;
+ return viewRelatedCards;
}
for (const auto &relatedCard : relatedCards) {
@@ -280,7 +288,38 @@ QMenu *CardInfoPictureWidget::createRightClickMenu()
viewRelatedCards->addAction(viewCard);
}
- return cardMenu;
+ return viewRelatedCards;
+}
+
+QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu()
+{
+ auto addToOpenDeckMenu = new QMenu(tr("Add card to deck"));
+
+ auto *mainWindow = qobject_cast(window());
+ QList deckEditorTabs = mainWindow->getTabSupervisor()->getDeckEditorTabs();
+
+ if (deckEditorTabs.isEmpty()) {
+ addToOpenDeckMenu->setEnabled(false);
+ return addToOpenDeckMenu;
+ }
+
+ for (auto &deckEditorTab : deckEditorTabs) {
+ auto *addCardMenu = addToOpenDeckMenu->addMenu(deckEditorTab->getTabText());
+
+ QAction *addCard = addCardMenu->addAction(tr("Mainboard"));
+ connect(addCard, &QAction::triggered, this, [this, deckEditorTab] {
+ deckEditorTab->updateCardInfo(info);
+ deckEditorTab->addCardHelper(info, DECK_ZONE_MAIN);
+ });
+
+ QAction *addCardSideboard = addCardMenu->addAction(tr("Sideboard"));
+ connect(addCardSideboard, &QAction::triggered, this, [this, deckEditorTab] {
+ deckEditorTab->updateCardInfo(info);
+ deckEditorTab->addCardHelper(info, DECK_ZONE_SIDE);
+ });
+ }
+
+ return addToOpenDeckMenu;
}
/**
diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h
index 9419de5b9..5f6500d92 100644
--- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h
+++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h
@@ -68,6 +68,8 @@ private:
QTimer *hoverTimer;
QMenu *createRightClickMenu();
+ QMenu *createViewRelatedCardsMenu();
+ QMenu *createAddToOpenDeckMenu();
};
#endif
diff --git a/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp
index 4efb1f44d..d6ce09247 100644
--- a/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp
@@ -32,6 +32,17 @@ CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *flowWidget, int defa
connect(cardSizeSlider, &QSlider::valueChanged, flowWidget, &FlowWidget::setMinimumSizeToMaxSizeHint);
}
+ // Debounce setup
+ debounceTimer.setSingleShot(true);
+ connect(&debounceTimer, &QTimer::timeout, this, [this]() {
+ // Check the type of the parent widget
+ if (qobject_cast(parentWidget())) {
+ SettingsCache::instance().setPrintingSelectorCardSize(pendingValue);
+ } else if (qobject_cast(parentWidget())) {
+ SettingsCache::instance().setVisualDeckStorageCardSize(pendingValue);
+ }
+ });
+
connect(cardSizeSlider, &QSlider::valueChanged, this, &CardSizeWidget::updateCardSizeSetting);
}
@@ -42,12 +53,8 @@ CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *flowWidget, int defa
*/
void CardSizeWidget::updateCardSizeSetting(int newValue)
{
- // Check the type of the parent widget
- if ((parent = qobject_cast(parentWidget()))) {
- SettingsCache::instance().setPrintingSelectorCardSize(newValue);
- } else if ((parent = qobject_cast(parentWidget()))) {
- SettingsCache::instance().setVisualDeckStorageCardSize(newValue);
- }
+ pendingValue = newValue;
+ debounceTimer.start(300); // 300ms debounce time
}
/**
diff --git a/cockatrice/src/client/ui/widgets/cards/card_size_widget.h b/cockatrice/src/client/ui/widgets/cards/card_size_widget.h
index 4bc2afc98..d79359f23 100644
--- a/cockatrice/src/client/ui/widgets/cards/card_size_widget.h
+++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.h
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
class CardSizeWidget : public QWidget
@@ -15,15 +16,18 @@ class CardSizeWidget : public QWidget
public:
explicit CardSizeWidget(QWidget *parent, FlowWidget *flowWidget = nullptr, int defaultValue = 100);
[[nodiscard]] QSlider *getSlider() const;
- QWidget *parent;
+
public slots:
void updateCardSizeSetting(int newValue);
private:
+ QWidget *parent;
FlowWidget *flowWidget;
QHBoxLayout *cardSizeLayout;
QLabel *cardSizeLabel;
QSlider *cardSizeSlider;
+ QTimer debounceTimer; // Debounce timer
+ int pendingValue; // Stores the latest slider value
};
#endif // CARD_SIZE_WIDGET_H
diff --git a/cockatrice/src/client/ui/widgets/general/display/banner_widget.cpp b/cockatrice/src/client/ui/widgets/general/display/banner_widget.cpp
new file mode 100644
index 000000000..64a64ba7c
--- /dev/null
+++ b/cockatrice/src/client/ui/widgets/general/display/banner_widget.cpp
@@ -0,0 +1,69 @@
+#include "banner_widget.h"
+
+#include
+#include
+#include
+#include
+
+BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency)
+ : QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency, 100))
+{
+ // Create the banner label and set properties
+ bannerLabel = new QLabel(text, this);
+ bannerLabel->setAlignment(Qt::AlignCenter);
+ bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold; color: white;");
+
+ // Layout to center the banner label
+ layout = new QVBoxLayout(this);
+ layout->addWidget(bannerLabel);
+ setLayout(layout);
+
+ // Set minimum height for the widget
+ setMinimumHeight(50);
+ connect(this, &BannerWidget::buddyVisibilityChanged, this, &BannerWidget::toggleBuddyVisibility);
+}
+
+void BannerWidget::mousePressEvent(QMouseEvent *event)
+{
+ QWidget::mousePressEvent(event);
+ if (clickable) {
+ emit buddyVisibilityChanged();
+ }
+}
+
+void BannerWidget::setText(const QString &text) const
+{
+ bannerLabel->setText(text);
+}
+
+void BannerWidget::toggleBuddyVisibility() const
+{
+ if (buddy) {
+ buddy->setVisible(!buddy->isVisible());
+ }
+}
+
+void BannerWidget::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+
+ QPainter painter(this);
+
+ // Calculate alpha based on transparency percentage
+ int alpha = (255 * transparency) / 100;
+
+ // Determine gradient direction
+ QLinearGradient gradient;
+ if (gradientOrientation == Qt::Vertical) {
+ gradient = QLinearGradient(rect().topLeft(), rect().bottomLeft());
+ } else {
+ gradient = QLinearGradient(rect().topLeft(), rect().topRight());
+ }
+
+ // Set neutral gradient colors with calculated transparency
+ gradient.setColorAt(0, QColor(200, 200, 200, alpha)); // Light grey with alpha
+ gradient.setColorAt(1, QColor(100, 100, 100, alpha / 1.5)); // Darker grey, slightly more transparent
+
+ // Fill the widget background with the gradient
+ painter.fillRect(rect(), gradient);
+}
diff --git a/cockatrice/src/client/ui/widgets/general/display/banner_widget.h b/cockatrice/src/client/ui/widgets/general/display/banner_widget.h
new file mode 100644
index 000000000..545cb5ed6
--- /dev/null
+++ b/cockatrice/src/client/ui/widgets/general/display/banner_widget.h
@@ -0,0 +1,48 @@
+#ifndef BANNER_WIDGET_H
+#define BANNER_WIDGET_H
+
+#include
+#include
+#include
+
+class BannerWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit BannerWidget(QWidget *parent,
+ const QString &text,
+ Qt::Orientation orientation = Qt::Vertical,
+ int transparency = 80);
+ void mousePressEvent(QMouseEvent *event) override;
+ void setText(const QString &text) const;
+ void setBuddy(QWidget *_buddy)
+ {
+ buddy = _buddy;
+ }
+ QString getText() const
+ {
+ return bannerLabel->text();
+ }
+ void setClickable(bool _clickable)
+ {
+ clickable = _clickable;
+ }
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ QVBoxLayout *layout;
+ QLabel *bannerLabel;
+ Qt::Orientation gradientOrientation;
+ int transparency; // Transparency percentage for the gradient
+ QWidget *buddy;
+ bool clickable = true;
+signals:
+ void buddyVisibilityChanged();
+private slots:
+ void toggleBuddyVisibility() const;
+};
+
+#endif // BANNER_WIDGET_H
diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp
index 958cc98be..13e1f15ea 100644
--- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp
@@ -5,10 +5,8 @@
#include "flow_widget.h"
-#include "../../../layouts/horizontal_flow_layout.h"
-#include "../../../layouts/vertical_flow_layout.h"
-
#include
+#include
#include
#include
#include
@@ -21,41 +19,53 @@
* @param verticalPolicy The vertical scroll bar policy for the scroll area.
*/
FlowWidget::FlowWidget(QWidget *parent,
+ const Qt::Orientation _flowDirection,
const Qt::ScrollBarPolicy horizontalPolicy,
const Qt::ScrollBarPolicy verticalPolicy)
- : QWidget(parent)
+ : QWidget(parent), flowDirection(_flowDirection)
{
// Main Widget and Layout
- this->setMinimumSize(0, 0);
- this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- mainLayout = new QHBoxLayout();
+ if (_flowDirection == Qt::Horizontal) {
+ this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
+ this->setMinimumWidth(0);
+ } else {
+ this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
+ this->setMinimumHeight(0);
+ }
+ mainLayout = new QHBoxLayout(this);
this->setLayout(mainLayout);
- // Flow Layout inside the scroll area
- container = new QWidget();
-
- if (horizontalPolicy != Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
- flowLayout = new HorizontalFlowLayout(container);
- } else if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy != Qt::ScrollBarAlwaysOff) {
- flowLayout = new VerticalFlowLayout(container);
+ if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) {
+ // Scroll Area, which should expand as much as possible, since it should be the only direct child widget.
+ scrollArea = new QScrollArea(this);
+ scrollArea->setWidgetResizable(true);
+ scrollArea->setMinimumSize(0, 0);
+ scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ // Set scrollbar policies
+ scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy);
+ scrollArea->setVerticalScrollBarPolicy(verticalPolicy);
} else {
- flowLayout = new FlowLayout(container, 0, 0, 0);
+ scrollArea = nullptr;
}
+ // Flow Layout inside the scroll area
+ if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
+ container = new QWidget(this);
+ } else {
+ container = new QWidget(scrollArea);
+ }
+
+ flowLayout = new FlowLayout(container, flowDirection);
+
container->setLayout(flowLayout);
// The container should expand as much as possible, trusting the scrollArea to constrain it.
- container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- container->setMinimumSize(0, 0);
-
- // Scroll Area, which should expand as much as possible, since it should be the only direct child widget.
- scrollArea = new QScrollArea();
- scrollArea->setWidgetResizable(true);
- scrollArea->setMinimumSize(0, 0);
- scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-
- // Set scrollbar policies
- scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy);
- scrollArea->setVerticalScrollBarPolicy(verticalPolicy);
+ if (_flowDirection == Qt::Horizontal) {
+ container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ container->setMinimumWidth(0);
+ } else {
+ container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ container->setMinimumHeight(0);
+ }
// Use the FlowLayout container directly if we disable the ScrollArea
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
@@ -75,15 +85,6 @@ FlowWidget::FlowWidget(QWidget *parent,
*/
void FlowWidget::addWidget(QWidget *widget_to_add) const
{
- // Adjust size policy if scrollbars are disabled
- if (scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
- widget_to_add->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
- }
- if (scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
- widget_to_add->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
- }
-
- // Add the widget to the flow layout
flowLayout->addWidget(widget_to_add);
}
@@ -106,15 +107,7 @@ void FlowWidget::clearLayout()
delete item; // Delete the layout item
}
} else {
- if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff &&
- scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
- flowLayout = new HorizontalFlowLayout(container);
- } else if (scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff &&
- scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) {
- flowLayout = new VerticalFlowLayout(container);
- } else {
- flowLayout = new FlowLayout(container, 0, 0, 0);
- }
+ flowLayout = new FlowLayout(container, flowDirection);
container->setLayout(flowLayout);
}
}
@@ -130,6 +123,8 @@ void FlowWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
+ qCDebug(FlowWidgetSizeLog) << event->size();
+
// Trigger the layout to recalculate
if (flowLayout != nullptr) {
flowLayout->invalidate(); // Marks the layout as dirty and requires recalculation
@@ -138,7 +133,10 @@ void FlowWidget::resizeEvent(QResizeEvent *event)
// Ensure the scroll area and its content adjust correctly
if (scrollArea != nullptr && scrollArea->widget() != nullptr) {
+ qCDebug(FlowWidgetSizeLog) << "Got a scrollarea: " << scrollArea->widget()->size();
scrollArea->widget()->adjustSize();
+ } else {
+ container->adjustSize();
}
}
diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h
index a514b56a7..baed97b8d 100644
--- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h
+++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h
@@ -3,15 +3,22 @@
#include "../../../layouts/flow_layout.h"
#include
+#include
#include
#include
+inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget");
+inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size");
+
class FlowWidget final : public QWidget
{
Q_OBJECT
public:
- FlowWidget(QWidget *parent, Qt::ScrollBarPolicy horizontalPolicy, Qt::ScrollBarPolicy verticalPolicy);
+ FlowWidget(QWidget *parent,
+ Qt::Orientation orientation,
+ Qt::ScrollBarPolicy horizontalPolicy,
+ Qt::ScrollBarPolicy verticalPolicy);
void addWidget(QWidget *widget_to_add) const;
void removeWidget(QWidget *widgetToRemove) const;
void clearLayout();
@@ -27,6 +34,7 @@ protected:
void resizeEvent(QResizeEvent *event) override;
private:
+ Qt::Orientation flowDirection;
QHBoxLayout *mainLayout;
FlowLayout *flowLayout;
QWidget *container;
diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp
index 9943b9403..4a3d9fe80 100644
--- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp
+++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp
@@ -45,7 +45,7 @@ PrintingSelector::PrintingSelector(QWidget *parent,
searchBar->setVisible(SettingsCache::instance().getPrintingSelectorSearchBarVisible());
layout->addWidget(searchBar);
- flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
+ flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getPrintingSelectorCardSize());
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
index 9b92f8994..d854e1588 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
@@ -112,9 +112,9 @@ DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(Visua
// Connect the button's toggled signal
connect(toggleButton, &QPushButton::toggled, this, &DeckPreviewColorIdentityFilterWidget::updateFilterMode);
connect(this, &DeckPreviewColorIdentityFilterWidget::activeColorsChanged, parent,
- &VisualDeckStorageWidget::refreshBannerCards);
+ &VisualDeckStorageWidget::updateColorFilter);
connect(this, &DeckPreviewColorIdentityFilterWidget::filterModeChanged, parent,
- &VisualDeckStorageWidget::refreshBannerCards);
+ &VisualDeckStorageWidget::updateColorFilter);
// Call retranslateUi to set the initial text
retranslateUi();
@@ -139,10 +139,8 @@ void DeckPreviewColorIdentityFilterWidget::updateFilterMode(bool checked)
emit filterModeChanged(exactMatchMode);
}
-QList DeckPreviewColorIdentityFilterWidget::filterWidgets(QList &widgets)
+void DeckPreviewColorIdentityFilterWidget::filterWidgets(QList widgets)
{
- QList filteredWidgets;
-
// Check if no colors are active
bool noColorsActive = true;
for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) {
@@ -154,7 +152,9 @@ QList DeckPreviewColorIdentityFilterWidget::filterWidgets(Q
// If no colors are active, return the unfiltered list of widgets
if (noColorsActive) {
- return widgets;
+ for (DeckPreviewWidget *previewWidget : widgets) {
+ previewWidget->filteredByColor = false;
+ }
}
for (const auto &widget : widgets) {
@@ -192,10 +192,6 @@ QList DeckPreviewColorIdentityFilterWidget::filterWidgets(Q
}
}
- if (matchesFilter) {
- filteredWidgets << widget;
- }
+ widget->filteredByColor = !matchesFilter;
}
-
- return filteredWidgets;
}
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h
index e18bf8860..3c9bdb70c 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h
@@ -42,7 +42,7 @@ class DeckPreviewColorIdentityFilterWidget : public QWidget
public:
explicit DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent);
void retranslateUi();
- QList filterWidgets(QList &widgets);
+ void filterWidgets(QList widgets);
signals:
void filterModeChanged(bool exactMatchMode);
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
index f4cab85fe..7f2dfaab0 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
@@ -23,7 +23,7 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(DeckPreviewWi
connect(deckLoader, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
- flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
+ flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
for (const QString &tag : this->deckLoader->getTags()) {
flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
}
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
index 3572daaee..16a87bc8d 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
@@ -1,5 +1,7 @@
#include "deck_preview_tag_addition_widget.h"
+#include "../../../../../dialogs/dlg_convert_deck_to_cod_format.h"
+#include "../../../../../settings/cache_settings.h"
#include "deck_preview_tag_dialog.h"
#include
@@ -37,14 +39,53 @@ void DeckPreviewTagAdditionWidget::mousePressEvent(QMouseEvent *event)
emit tagClicked();
}
QWidget::mousePressEvent(event);
- QStringList knownTags = parent->parent->parent->gatherAllTagsFromFlowWidget();
+ QStringList knownTags = parent->parent->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags();
QStringList activeTags = parent->deckLoader->getTags();
- DeckPreviewTagDialog dialog(knownTags, activeTags);
- if (dialog.exec() == QDialog::Accepted) {
- QStringList updatedTags = dialog.getActiveTags();
- parent->deckLoader->setTags(updatedTags);
- parent->deckLoader->saveToFile(parent->parent->filePath, DeckLoader::CockatriceFormat);
+ bool canAddTags = true;
+
+ if (DeckLoader::getFormatFromName(parent->parent->filePath) != DeckLoader::CockatriceFormat) {
+ canAddTags = false;
+ // Retrieve saved preference if the prompt is disabled
+ if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) {
+ if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) {
+ parent->deckLoader->convertToCockatriceFormat(parent->parent->filePath);
+ parent->parent->filePath = parent->deckLoader->getLastFileName();
+ parent->parent->refreshBannerCardText();
+ canAddTags = true;
+ }
+ } else {
+ // Show the dialog to the user
+ DialogConvertDeckToCodFormat conversionDialog(parent);
+ if (conversionDialog.exec() == QDialog::Accepted) {
+ parent->deckLoader->convertToCockatriceFormat(parent->parent->filePath);
+ parent->parent->filePath = parent->deckLoader->getLastFileName();
+ parent->parent->refreshBannerCardText();
+ canAddTags = true;
+
+ if (conversionDialog.dontAskAgain()) {
+ SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked);
+ SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Checked);
+ }
+ } else {
+ SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Unchecked);
+
+ if (conversionDialog.dontAskAgain()) {
+ SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked);
+ } else {
+ SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Checked);
+ }
+ }
+ }
+ }
+
+ if (canAddTags) {
+ DeckPreviewTagDialog dialog(knownTags, activeTags);
+ if (dialog.exec() == QDialog::Accepted) {
+ QStringList updatedTags = dialog.getActiveTags();
+ parent->deckLoader->setTags(updatedTags);
+ parent->deckLoader->saveToFile(parent->parent->filePath, DeckLoader::CockatriceFormat);
+ }
}
}
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
index 18bc771d1..ecd4f4a79 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
@@ -9,15 +9,19 @@
#include
#include
-DeckPreviewWidget::DeckPreviewWidget(VisualDeckStorageWidget *_parent, const QString &_filePath)
- : QWidget(_parent), parent(_parent), filePath(_filePath)
+DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent,
+ VisualDeckStorageWidget *_visualDeckStorageWidget,
+ const QString &_filePath)
+ : QWidget(_parent), visualDeckStorageWidget(_visualDeckStorageWidget), filePath(_filePath)
{
layout = new QVBoxLayout(this);
setLayout(layout);
deckLoader = new DeckLoader();
connect(deckLoader, &DeckLoader::loadFinished, this, &DeckPreviewWidget::initializeUi);
- deckLoader->loadFromFileAsync(filePath, DeckLoader::CockatriceFormat, false);
+ connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget,
+ &VisualDeckStorageTagFilterWidget::refreshTags);
+ deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false);
bannerCardDisplayWidget = new DeckPreviewCardPictureWidget(this);
@@ -52,6 +56,22 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
layout->addWidget(deckTagsDisplayWidget);
}
+void DeckPreviewWidget::updateVisibility()
+{
+ if (isVisible() != checkVisibility()) {
+ setHidden(!checkVisibility());
+ emit visibilityUpdated();
+ }
+}
+
+bool DeckPreviewWidget::checkVisibility() const
+{
+ if (filteredBySearch || filteredByColor || filteredByTags) {
+ return false;
+ }
+ return true;
+}
+
QString DeckPreviewWidget::getColorIdentity()
{
QStringList cardList = deckLoader->getCardList();
@@ -88,6 +108,12 @@ void DeckPreviewWidget::setFilePath(const QString &_filePath)
filePath = _filePath;
}
+void DeckPreviewWidget::refreshBannerCardText()
+{
+ bannerCardDisplayWidget->setOverlayText(
+ deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() : deckLoader->getName());
+}
+
void DeckPreviewWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
{
Q_UNUSED(instance);
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h
index 689c94b18..8be7d07ac 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h
@@ -16,26 +16,35 @@ class DeckPreviewWidget final : public QWidget
{
Q_OBJECT
public:
- explicit DeckPreviewWidget(VisualDeckStorageWidget *_parent, const QString &_filePath);
+ explicit DeckPreviewWidget(QWidget *_parent,
+ VisualDeckStorageWidget *_visualDeckStorageWidget,
+ const QString &_filePath);
QString getColorIdentity();
- VisualDeckStorageWidget *parent;
+ VisualDeckStorageWidget *visualDeckStorageWidget;
QVBoxLayout *layout;
QString filePath;
DeckLoader *deckLoader;
DeckPreviewCardPictureWidget *bannerCardDisplayWidget;
DeckPreviewColorIdentityWidget *colorIdentityWidget;
DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget;
+ bool filteredBySearch = false;
+ bool filteredByColor = false;
+ bool filteredByTags = false;
+ bool checkVisibility() const;
signals:
void deckPreviewClicked(QMouseEvent *event, DeckPreviewWidget *instance);
void deckPreviewDoubleClicked(QMouseEvent *event, DeckPreviewWidget *instance);
+ void visibilityUpdated();
public slots:
void setFilePath(const QString &filePath);
+ void refreshBannerCardText();
void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void initializeUi(bool deckLoadSuccess);
+ void updateVisibility();
};
#endif // DECK_PREVIEW_WIDGET_H
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
new file mode 100644
index 000000000..1ac62b784
--- /dev/null
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
@@ -0,0 +1,179 @@
+#include "visual_deck_storage_folder_display_widget.h"
+
+#include "../../../../settings/cache_settings.h"
+#include "deck_preview/deck_preview_widget.h"
+
+#include
+#include
+
+VisualDeckStorageFolderDisplayWidget::VisualDeckStorageFolderDisplayWidget(
+ QWidget *parent,
+ VisualDeckStorageWidget *_visualDeckStorageWidget,
+ QString _filePath,
+ bool canBeHidden)
+ : QWidget(parent), visualDeckStorageWidget(_visualDeckStorageWidget), filePath(_filePath)
+{
+ layout = new QVBoxLayout(this);
+ setLayout(layout);
+
+ header = new BannerWidget(this, "");
+ header->setClickable(canBeHidden);
+ layout->addWidget(header);
+
+ container = new QWidget(this);
+ containerLayout = new QVBoxLayout(container);
+ container->setLayout(containerLayout);
+
+ header->setBuddy(container);
+
+ layout->addWidget(container);
+
+ flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
+ containerLayout->addWidget(flowWidget);
+
+ connect(visualDeckStorageWidget, &VisualDeckStorageWidget::tagFilterUpdated, this,
+ &VisualDeckStorageFolderDisplayWidget::updateVisibility);
+ connect(visualDeckStorageWidget, &VisualDeckStorageWidget::colorFilterUpdated, this,
+ &VisualDeckStorageFolderDisplayWidget::updateVisibility);
+ connect(visualDeckStorageWidget, &VisualDeckStorageWidget::searchFilterUpdated, this,
+ &VisualDeckStorageFolderDisplayWidget::updateVisibility);
+
+ createWidgetsForFiles();
+ createWidgetsForFolders();
+
+ refreshUi();
+}
+
+void VisualDeckStorageFolderDisplayWidget::refreshUi()
+{
+ QString bannerText = tr("Deck Storage");
+ QString deckPath = SettingsCache::instance().getDeckPath();
+ if (filePath != deckPath) {
+ QString relativePath = filePath;
+
+ if (filePath.startsWith(deckPath)) {
+ relativePath = filePath.mid(deckPath.length()); // Remove the deckPath prefix
+ if (relativePath.startsWith('/')) {
+ relativePath.remove(0, 1); // Remove leading '/' if it exists
+ }
+ }
+
+ bannerText = relativePath;
+ }
+ header->setText(bannerText);
+}
+
+void VisualDeckStorageFolderDisplayWidget::createWidgetsForFiles()
+{
+ QList allDecks;
+ for (const QString &file : getAllFiles()) {
+ auto *display = new DeckPreviewWidget(flowWidget, visualDeckStorageWidget, file);
+
+ connect(display, &DeckPreviewWidget::deckPreviewClicked, visualDeckStorageWidget,
+ &VisualDeckStorageWidget::deckPreviewClickedEvent);
+ connect(display, &DeckPreviewWidget::deckPreviewDoubleClicked, visualDeckStorageWidget,
+ &VisualDeckStorageWidget::deckPreviewDoubleClickedEvent);
+ connect(visualDeckStorageWidget->cardSizeWidget->getSlider(), &QSlider::valueChanged,
+ display->bannerCardDisplayWidget, &CardInfoPictureWidget::setScaleFactor);
+ display->bannerCardDisplayWidget->setScaleFactor(visualDeckStorageWidget->cardSizeWidget->getSlider()->value());
+ allDecks.append(display);
+ }
+
+ flowWidget->clearLayout(); // Clear existing widgets in the flow layout
+
+ for (DeckPreviewWidget *deck : allDecks) {
+ flowWidget->addWidget(deck);
+ }
+}
+
+void VisualDeckStorageFolderDisplayWidget::updateVisibility()
+{
+ bool atLeastOneWidgetVisible = checkVisibility();
+ if (atLeastOneWidgetVisible) {
+ setVisible(true);
+ for (DeckPreviewWidget *display : flowWidget->findChildren()) {
+ display->updateVisibility();
+ }
+ for (VisualDeckStorageFolderDisplayWidget *subFolder : findChildren()) {
+ subFolder->updateVisibility();
+ }
+ } else {
+ setVisible(false);
+ }
+}
+
+bool VisualDeckStorageFolderDisplayWidget::checkVisibility()
+{
+ bool atLeastOneWidgetVisible = false;
+ if (flowWidget) {
+ // Iterate through all DeckPreviewWidgets
+ for (DeckPreviewWidget *display : flowWidget->findChildren()) {
+ if (display->checkVisibility()) {
+ atLeastOneWidgetVisible = true;
+ }
+ }
+ }
+ for (VisualDeckStorageFolderDisplayWidget *subFolder : findChildren()) {
+ if (subFolder->checkVisibility()) {
+ atLeastOneWidgetVisible = true;
+ }
+ }
+ return atLeastOneWidgetVisible;
+}
+
+void VisualDeckStorageFolderDisplayWidget::createWidgetsForFolders()
+{
+ for (const QString &dir : getAllSubFolders()) {
+ auto *display = new VisualDeckStorageFolderDisplayWidget(this, visualDeckStorageWidget, dir, true);
+ containerLayout->addWidget(display);
+ }
+}
+
+QStringList VisualDeckStorageFolderDisplayWidget::gatherAllTagsFromFlowWidget() const
+{
+ QStringList allTags;
+
+ if (flowWidget) {
+ // Iterate through all DeckPreviewWidgets
+ for (DeckPreviewWidget *display : flowWidget->findChildren()) {
+ // Get tags from each DeckPreviewWidget
+ QStringList tags = display->deckLoader->getTags();
+
+ // Add tags to the list while avoiding duplicates
+ allTags.append(tags);
+ }
+ }
+
+ // Remove duplicates by calling 'removeDuplicates'
+ allTags.removeDuplicates();
+
+ return allTags;
+}
+
+QStringList VisualDeckStorageFolderDisplayWidget::getAllFiles() const
+{
+ QStringList allFiles;
+
+ // QDirIterator with QDir::Files ensures only files are listed (no directories)
+ QDirIterator it(filePath, QDir::Files);
+
+ while (it.hasNext()) {
+ allFiles << it.next(); // Add each file path to the list
+ }
+
+ return allFiles;
+}
+
+QStringList VisualDeckStorageFolderDisplayWidget::getAllSubFolders() const
+{
+ QStringList allFolders;
+
+ // QDirIterator with QDir::Files ensures only files are listed (no directories)
+ QDirIterator it(filePath, QDir::Dirs | QDir::NoDotAndDotDot);
+
+ while (it.hasNext()) {
+ allFolders << it.next(); // Add each file path to the list
+ }
+
+ return allFolders;
+}
\ No newline at end of file
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h
new file mode 100644
index 000000000..7e1279e6b
--- /dev/null
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h
@@ -0,0 +1,44 @@
+#ifndef VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
+#define VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
+
+#include "../general/display/banner_widget.h"
+#include "../general/layout_containers/flow_widget.h"
+#include "visual_deck_storage_widget.h"
+
+#include
+#include
+
+class VisualDeckStorageFolderDisplayWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ VisualDeckStorageFolderDisplayWidget(QWidget *parent,
+ VisualDeckStorageWidget *_visualDeckStorageWidget,
+ QString _filePath,
+ bool canBeHidden);
+ void refreshUi();
+ void createWidgetsForFiles();
+ void createWidgetsForFolders();
+ QStringList gatherAllTagsFromFlowWidget() const;
+ [[nodiscard]] QStringList getAllFiles() const;
+ [[nodiscard]] QStringList getAllSubFolders() const;
+ FlowWidget *getFlowWidget() const
+ {
+ return flowWidget;
+ };
+
+public slots:
+ void updateVisibility();
+ bool checkVisibility();
+
+private:
+ QVBoxLayout *layout;
+ VisualDeckStorageWidget *visualDeckStorageWidget;
+ QString filePath;
+ BannerWidget *header;
+ QWidget *container;
+ QVBoxLayout *containerLayout;
+ FlowWidget *flowWidget;
+};
+
+#endif // VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
index 7828a76f4..8b6006a31 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
@@ -25,7 +25,7 @@ VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(VisualDeckStorageWi
searchDebounceTimer->start(300); // 300ms debounce
});
- connect(searchDebounceTimer, &QTimer::timeout, parent, &VisualDeckStorageWidget::refreshBannerCards);
+ connect(searchDebounceTimer, &QTimer::timeout, parent, &VisualDeckStorageWidget::updateSearchFilter);
}
/**
@@ -38,23 +38,18 @@ QString VisualDeckStorageSearchWidget::getSearchText()
return searchBar->text();
}
-QList VisualDeckStorageSearchWidget::filterFiles(QList &widgets,
- const QString &searchText)
+void VisualDeckStorageSearchWidget::filterWidgets(QList widgets, const QString &searchText)
{
if (searchText.isEmpty() || searchText.isNull()) {
- return widgets;
- }
-
- QList filteredWidgets;
-
- for (const auto &file : widgets) {
- QFileInfo fileInfo(file->filePath);
- QString fileName = fileInfo.fileName().toLower();
-
- if (fileName.contains(searchText.toLower())) {
- filteredWidgets << file;
+ for (auto widget : widgets) {
+ widget->filteredBySearch = false;
}
}
- return filteredWidgets;
+ for (auto file : widgets) {
+ QFileInfo fileInfo(file->filePath);
+ QString fileName = fileInfo.fileName().toLower();
+
+ file->filteredBySearch = !fileName.contains(searchText.toLower());
+ }
}
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h
index 880cc4a47..e8b91bc5b 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h
@@ -16,7 +16,7 @@ class VisualDeckStorageSearchWidget : public QWidget
public:
explicit VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent);
QString getSearchText();
- QList filterFiles(QList &widgets, const QString &searchText);
+ void filterWidgets(QList widgets, const QString &searchText);
private:
QHBoxLayout *layout;
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
index f19131407..a53e84a27 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
@@ -63,7 +63,24 @@ void VisualDeckStorageSortWidget::updateSortOrder()
emit sortOrderChanged();
}
-QList &VisualDeckStorageSortWidget::filterFiles(QList &widgets)
+void VisualDeckStorageSortWidget::sortFolder(VisualDeckStorageFolderDisplayWidget *folderWidget)
+{
+ auto children =
+ folderWidget->getFlowWidget()->findChildren(QString(), Qt::FindChildOption::FindDirectChildrenOnly);
+ for (auto widget : children) {
+ auto deckPreviewWidgets =
+ widget->findChildren(QString(), Qt::FindChildOption::FindDirectChildrenOnly);
+ auto newOrder = filterFiles(deckPreviewWidgets);
+ for (DeckPreviewWidget *previewWidget : newOrder) {
+ folderWidget->getFlowWidget()->removeWidget(previewWidget);
+ }
+ for (DeckPreviewWidget *previewWidget : newOrder) {
+ folderWidget->getFlowWidget()->addWidget(previewWidget);
+ }
+ }
+}
+
+QList VisualDeckStorageSortWidget::filterFiles(QList widgets)
{
// Sort the widgets list based on the current sort order
std::sort(widgets.begin(), widgets.end(), [this](DeckPreviewWidget *widget1, DeckPreviewWidget *widget2) {
@@ -81,8 +98,11 @@ QList &VisualDeckStorageSortWidget::filterFiles(QList info2.lastModified();
- case ByLastLoaded:
- return widget1->deckLoader->getLastLoadedTimestamp() > widget2->deckLoader->getLastLoadedTimestamp();
+ case ByLastLoaded: {
+ QDateTime time1 = QDateTime::fromString(widget1->deckLoader->getLastLoadedTimestamp());
+ QDateTime time2 = QDateTime::fromString(widget2->deckLoader->getLastLoadedTimestamp());
+ return time1 > time2;
+ }
}
return false; // Default case, no sorting applied
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h
index 58838f3ac..1aac55d2d 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h
@@ -8,6 +8,7 @@
#include
class VisualDeckStorageWidget;
+class VisualDeckStorageFolderDisplayWidget;
class VisualDeckStorageSortWidget : public QWidget
{
Q_OBJECT
@@ -16,8 +17,9 @@ public:
explicit VisualDeckStorageSortWidget(VisualDeckStorageWidget *parent);
void retranslateUi();
void updateSortOrder();
+ void sortFolder(VisualDeckStorageFolderDisplayWidget *folderWidget);
QString getSearchText();
- QList &filterFiles(QList &widgets);
+ QList filterFiles(QList widgets);
signals:
void sortOrderChanged();
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
index bf22fda3d..97b810d21 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
@@ -20,13 +20,18 @@ VisualDeckStorageTagFilterWidget::VisualDeckStorageTagFilterWidget(VisualDeckSto
setFixedHeight(100);
- auto *flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
+ auto *flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
}
-QList
-VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList &deckPreviews) const
+void VisualDeckStorageTagFilterWidget::showEvent(QShowEvent *event)
+{
+ QWidget::showEvent(event);
+ refreshTags();
+}
+
+void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList &deckPreviews) const
{
// Collect selected tags from DeckPreviewTagDisplayWidget
QStringList selectedTags;
@@ -36,9 +41,12 @@ VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QListfilteredByTags = false;
+ }
+ return;
}
// Filter DeckPreviewWidgets that contain all of the selected tags
@@ -50,12 +58,15 @@ VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QListfilteredByTags = !allTagsPresent;
}
+}
- return filteredDecks;
+void VisualDeckStorageTagFilterWidget::refreshTags()
+{
+ QStringList allTags = gatherAllTags();
+ removeTagsNotInList(gatherAllTags());
+ addTagsIfNotPresent(gatherAllTags());
}
void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QStringList &tags)
@@ -93,8 +104,37 @@ void VisualDeckStorageTagFilterWidget::addTagIfNotPresent(const QString &tag)
if (!tagExists) {
auto *newTagWidget = new DeckPreviewTagDisplayWidget(this, tag);
connect(newTagWidget, &DeckPreviewTagDisplayWidget::tagClicked, parent,
- &VisualDeckStorageWidget::refreshBannerCards);
+ &VisualDeckStorageWidget::updateTagFilter);
+ connect(newTagWidget, &DeckPreviewTagDisplayWidget::tagClicked, this,
+ &VisualDeckStorageTagFilterWidget::refreshTags);
auto *flowWidget = findChild();
flowWidget->addWidget(newTagWidget);
}
}
+
+QStringList VisualDeckStorageTagFilterWidget::gatherAllTags()
+{
+ QStringList allTags;
+ QList deckWidgets = parent->findChildren();
+
+ for (DeckPreviewWidget *widget : deckWidgets) {
+ if (widget->checkVisibility()) {
+ allTags << widget->deckLoader->getTags();
+ }
+ }
+ return allTags;
+}
+
+QStringList VisualDeckStorageTagFilterWidget::getAllKnownTags()
+{
+ QStringList allTags;
+
+ for (DeckPreviewTagDisplayWidget *tagWidget : findChildren()) {
+ allTags.append(tagWidget->getTagName());
+ }
+
+ // Remove duplicates by calling 'removeDuplicates'
+ allTags.removeDuplicates();
+
+ return allTags;
+}
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h
index 13fc7ef4d..ce8ca8d14 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h
@@ -12,12 +12,17 @@ class VisualDeckStorageTagFilterWidget : public QWidget
public:
explicit VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent);
- void refreshTags();
- QList filterDecksBySelectedTags(const QList &deckPreviews) const;
+ QStringList gatherAllTags();
+ void filterDecksBySelectedTags(const QList &deckPreviews) const;
void removeTagsNotInList(const QStringList &tags);
void addTagsIfNotPresent(const QStringList &tags);
void addTagIfNotPresent(const QString &tag);
+ QStringList getAllKnownTags();
VisualDeckStorageWidget *parent;
+
+public slots:
+ void refreshTags();
+ void showEvent(QShowEvent *event) override;
};
#endif // VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
index 124b35c34..cae8d65c8 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
@@ -3,6 +3,7 @@
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../settings/cache_settings.h"
#include "deck_preview/deck_preview_widget.h"
+#include "visual_deck_storage_folder_display_widget.h"
#include "visual_deck_storage_search_widget.h"
#include "visual_deck_storage_sort_widget.h"
#include "visual_deck_storage_tag_filter_widget.h"
@@ -17,12 +18,12 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
deckListModel = new DeckListModel(this);
deckListModel->setObjectName("visualDeckModel");
- layout = new QVBoxLayout();
+ layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(9, 0, 9, 5);
setLayout(layout);
- searchAndSortLayout = new QHBoxLayout();
+ searchAndSortLayout = new QHBoxLayout(this);
searchAndSortLayout->setSpacing(3);
searchAndSortLayout->setContentsMargins(9, 0, 9, 0);
@@ -36,15 +37,23 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
searchAndSortLayout->addWidget(searchWidget);
layout->addLayout(searchAndSortLayout);
layout->addWidget(tagFilterWidget);
+ cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize());
- flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
- layout->addWidget(flowWidget);
+ scrollArea = new QScrollArea(this);
+ scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ folderWidget = new VisualDeckStorageFolderDisplayWidget(this, this, SettingsCache::instance().getDeckPath(), false);
+
+ scrollArea->setWidget(folderWidget);
+ scrollArea->setWidgetResizable(true);
+
+ layout->addWidget(scrollArea);
- cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getVisualDeckStorageCardSize());
layout->addWidget(cardSizeWidget);
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
- &VisualDeckStorageWidget::refreshBannerCards);
+ &VisualDeckStorageWidget::createRootFolderWidget);
databaseLoadIndicator = new QLabel(this);
databaseLoadIndicator->setAlignment(Qt::AlignCenter);
@@ -53,10 +62,28 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
// Don't waste time processing the cards if they're going to get refreshed anyway once the db finishes loading
if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) {
- refreshBannerCards();
+ createRootFolderWidget();
databaseLoadIndicator->setVisible(false);
} else {
- flowWidget->addWidget(databaseLoadIndicator);
+ scrollArea->setWidget(databaseLoadIndicator);
+ }
+}
+
+void VisualDeckStorageWidget::showEvent(QShowEvent *event)
+{
+ QWidget::showEvent(event);
+ if (scrollArea->widget() == folderWidget) {
+ scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width());
+ scrollArea->widget()->adjustSize();
+ }
+}
+
+void VisualDeckStorageWidget::resizeEvent(QResizeEvent *event)
+{
+ QWidget::resizeEvent(event);
+ if (scrollArea->widget() == folderWidget) {
+ scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width());
+ scrollArea->widget()->adjustSize();
}
}
@@ -65,11 +92,6 @@ void VisualDeckStorageWidget::retranslateUi()
databaseLoadIndicator->setText(tr("Loading database ..."));
}
-void VisualDeckStorageWidget::updateSortOrder()
-{
- refreshBannerCards(); // Refresh the banner cards with the new sort order
-}
-
void VisualDeckStorageWidget::deckPreviewClickedEvent(QMouseEvent *event, DeckPreviewWidget *instance)
{
emit deckPreviewClicked(event, instance);
@@ -81,84 +103,46 @@ void VisualDeckStorageWidget::deckPreviewDoubleClickedEvent(QMouseEvent *event,
emit deckLoadRequested(instance->filePath);
}
-void VisualDeckStorageWidget::refreshBannerCards()
+void VisualDeckStorageWidget::createRootFolderWidget()
{
- QStringList allFiles;
- QList allDecks;
-
- // QDirIterator with QDir::Files and QDir::NoSymLinks ensures only files are listed (no directories or symlinks)
- QDirIterator it(SettingsCache::instance().getDeckPath(), QDir::Files | QDir::NoSymLinks,
- QDirIterator::Subdirectories);
-
- while (it.hasNext()) {
- allFiles << it.next(); // Add each file path to the list
- }
-
- for (const QString &file : allFiles) {
- auto *display = new DeckPreviewWidget(this, file);
-
- connect(display, &DeckPreviewWidget::deckPreviewClicked, this,
- &VisualDeckStorageWidget::deckPreviewClickedEvent);
- connect(display, &DeckPreviewWidget::deckPreviewDoubleClicked, this,
- &VisualDeckStorageWidget::deckPreviewDoubleClickedEvent);
- connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display->bannerCardDisplayWidget,
- &CardInfoPictureWidget::setScaleFactor);
- display->bannerCardDisplayWidget->setScaleFactor(cardSizeWidget->getSlider()->value());
- allDecks.append(display);
- }
-
- auto filteredByColorIdentity =
- deckPreviewColorIdentityFilterWidget->filterWidgets(sortWidget->filterFiles(allDecks));
- auto filteredByTags = tagFilterWidget->filterDecksBySelectedTags(filteredByColorIdentity);
- auto filteredFiles = searchWidget->filterFiles(filteredByTags, searchWidget->getSearchText());
-
- tagFilterWidget->removeTagsNotInList(gatherAllTags(filteredFiles));
- tagFilterWidget->addTagsIfNotPresent(gatherAllTags(filteredFiles));
-
- flowWidget->clearLayout(); // Clear existing widgets in the flow layout
-
- for (DeckPreviewWidget *deck : filteredFiles) {
- flowWidget->addWidget(deck);
- }
-
- emit bannerCardsRefreshed();
+ folderWidget = new VisualDeckStorageFolderDisplayWidget(this, this, SettingsCache::instance().getDeckPath(), false);
+ scrollArea->setWidget(folderWidget);
+ scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width());
+ scrollArea->widget()->adjustSize();
+ updateSortOrder();
}
-QStringList VisualDeckStorageWidget::gatherAllTagsFromFlowWidget() const
+void VisualDeckStorageWidget::updateSortOrder()
{
- QStringList allTags;
-
- if (flowWidget) {
- // Iterate through all DeckPreviewWidgets
- for (DeckPreviewWidget *display : flowWidget->findChildren()) {
- // Get tags from each DeckPreviewWidget
- QStringList tags = display->deckLoader->getTags();
-
- // Add tags to the list while avoiding duplicates
- allTags.append(tags);
+ if (folderWidget) {
+ sortWidget->sortFolder(folderWidget);
+ for (VisualDeckStorageFolderDisplayWidget *subFolderWidget :
+ folderWidget->findChildren()) {
+ sortWidget->sortFolder(subFolderWidget);
}
}
-
- // Remove duplicates by calling 'removeDuplicates'
- allTags.removeDuplicates();
-
- return allTags;
}
-QStringList VisualDeckStorageWidget::gatherAllTags(const QList &allDecks)
+void VisualDeckStorageWidget::updateTagFilter()
{
- QStringList allTags;
-
- // Iterate through all decks provided as input
- for (DeckPreviewWidget *deck : allDecks) {
- QStringList tags = deck->deckLoader->getTags();
-
- // Add tags to the list while avoiding duplicates
- allTags.append(tags);
+ if (folderWidget) {
+ tagFilterWidget->filterDecksBySelectedTags(folderWidget->findChildren());
}
-
- // Remove duplicates
- allTags.removeDuplicates();
-
- return allTags;
+ emit tagFilterUpdated();
+}
+
+void VisualDeckStorageWidget::updateColorFilter()
+{
+ if (folderWidget) {
+ deckPreviewColorIdentityFilterWidget->filterWidgets(folderWidget->findChildren());
+ }
+ emit colorFilterUpdated();
+}
+
+void VisualDeckStorageWidget::updateSearchFilter()
+{
+ if (folderWidget) {
+ searchWidget->filterWidgets(folderWidget->findChildren(), searchWidget->getSearchText());
+ }
+ emit searchFilterUpdated();
}
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h
index f294a1790..2d3a4b322 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h
@@ -6,6 +6,7 @@
#include "../cards/card_size_widget.h"
#include "deck_preview/deck_preview_color_identity_filter_widget.h"
#include "deck_preview/deck_preview_widget.h"
+#include "visual_deck_storage_folder_display_widget.h"
#include "visual_deck_storage_search_widget.h"
#include "visual_deck_storage_sort_widget.h"
#include "visual_deck_storage_tag_filter_widget.h"
@@ -15,40 +16,48 @@
class VisualDeckStorageSearchWidget;
class VisualDeckStorageSortWidget;
class VisualDeckStorageTagFilterWidget;
+class VisualDeckStorageFolderDisplayWidget;
class DeckPreviewColorIdentityFilterWidget;
class VisualDeckStorageWidget final : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckStorageWidget(QWidget *parent);
+
void retranslateUi();
+ CardSizeWidget *cardSizeWidget;
+ VisualDeckStorageTagFilterWidget *tagFilterWidget;
public slots:
void deckPreviewClickedEvent(QMouseEvent *event, DeckPreviewWidget *instance);
void deckPreviewDoubleClickedEvent(QMouseEvent *event, DeckPreviewWidget *instance);
- void refreshBannerCards(); // Refresh the display of cards based on the current sorting option
- QStringList gatherAllTagsFromFlowWidget() const;
- QStringList gatherAllTags(const QList &allDecks);
+ void createRootFolderWidget(); // Refresh the display of cards based on the current sorting option
+ void updateTagFilter();
+ void updateColorFilter();
+ void updateSearchFilter();
void updateSortOrder();
+ void resizeEvent(QResizeEvent *event) override;
+ void showEvent(QShowEvent *event) override;
signals:
void bannerCardsRefreshed();
void deckPreviewClicked(QMouseEvent *event, DeckPreviewWidget *instance);
void deckPreviewDoubleClicked(QMouseEvent *event, DeckPreviewWidget *instance);
void deckLoadRequested(QString &filePath);
+ void tagFilterUpdated();
+ void colorFilterUpdated();
+ void searchFilterUpdated();
private:
QVBoxLayout *layout;
QHBoxLayout *searchAndSortLayout;
- FlowWidget *flowWidget;
DeckListModel *deckListModel;
QLabel *databaseLoadIndicator;
-
VisualDeckStorageSortWidget *sortWidget;
VisualDeckStorageSearchWidget *searchWidget;
- VisualDeckStorageTagFilterWidget *tagFilterWidget;
DeckPreviewColorIdentityFilterWidget *deckPreviewColorIdentityFilterWidget;
- CardSizeWidget *cardSizeWidget;
+ QScrollArea *scrollArea;
+ VisualDeckStorageFolderDisplayWidget *folderWidget;
};
#endif // VISUAL_DECK_STORAGE_WIDGET_H
diff --git a/cockatrice/src/client/ui/window_main.h b/cockatrice/src/client/ui/window_main.h
index 8ba3ea440..9910cf7fb 100644
--- a/cockatrice/src/client/ui/window_main.h
+++ b/cockatrice/src/client/ui/window_main.h
@@ -163,6 +163,11 @@ public:
}
~MainWindow() override;
+ TabSupervisor *getTabSupervisor() const
+ {
+ return tabSupervisor;
+ }
+
protected:
void closeEvent(QCloseEvent *event) override;
void changeEvent(QEvent *event) override;
diff --git a/cockatrice/src/client/update_downloader.h b/cockatrice/src/client/update_downloader.h
index bc10fe198..cafed0377 100644
--- a/cockatrice/src/client/update_downloader.h
+++ b/cockatrice/src/client/update_downloader.h
@@ -14,7 +14,7 @@ class UpdateDownloader : public QObject
{
Q_OBJECT
public:
- UpdateDownloader(QObject *parent);
+ explicit UpdateDownloader(QObject *parent);
void beginDownload(QUrl url);
signals:
void downloadSuccessful(QUrl filepath);
diff --git a/cockatrice/src/deck/custom_line_edit.h b/cockatrice/src/deck/custom_line_edit.h
index e6d003906..075ec012e 100644
--- a/cockatrice/src/deck/custom_line_edit.h
+++ b/cockatrice/src/deck/custom_line_edit.h
@@ -35,7 +35,7 @@ protected:
void keyPressEvent(QKeyEvent *event) override;
public:
- SearchLineEdit() : LineEditUnfocusable(), treeView(nullptr)
+ SearchLineEdit() : treeView(nullptr)
{
}
void setTreeView(QTreeView *_treeView)
diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp
index bfb8711ea..ed1d8fa71 100644
--- a/cockatrice/src/deck/deck_loader.cpp
+++ b/cockatrice/src/deck/deck_loader.cpp
@@ -6,6 +6,7 @@
#include "decklist.h"
#include
+#include
#include
#include
#include
@@ -481,6 +482,53 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
}
}
+bool DeckLoader::convertToCockatriceFormat(QString fileName)
+{
+ // Change the file extension to .cod
+ QFileInfo fileInfo(fileName);
+ QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod");
+
+ // Open the new file for writing
+ QFile file(newFileName);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << newFileName;
+ return false;
+ }
+
+ bool result = false;
+
+ // Perform file modifications based on the detected format
+ switch (getFormatFromName(fileName)) {
+ case PlainTextFormat:
+ // Save in Cockatrice's native format
+ result = saveToFile_Native(&file);
+ break;
+ case CockatriceFormat:
+ qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed.";
+ result = true;
+ break;
+ default:
+ qCWarning(DeckLoaderLog) << "Unsupported file format for conversion:" << fileName;
+ result = false;
+ break;
+ }
+
+ file.close();
+
+ // Delete the old file if conversion was successful
+ if (result) {
+ if (!QFile::remove(fileName)) {
+ qCWarning(DeckLoaderLog) << "Failed to delete original file:" << fileName;
+ } else {
+ qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName;
+ }
+ lastFileName = newFileName;
+ lastFileFormat = CockatriceFormat;
+ }
+
+ return result;
+}
+
QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName)
{
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(cardName);
diff --git a/cockatrice/src/deck/deck_loader.h b/cockatrice/src/deck/deck_loader.h
index 0e9dfa374..ceb103009 100644
--- a/cockatrice/src/deck/deck_loader.h
+++ b/cockatrice/src/deck/deck_loader.h
@@ -29,8 +29,8 @@ private:
public:
DeckLoader();
- DeckLoader(const QString &nativeString);
- DeckLoader(const DeckList &other);
+ explicit DeckLoader(const QString &nativeString);
+ explicit DeckLoader(const DeckList &other);
DeckLoader(const DeckLoader &other);
const QString &getLastFileName() const
{
@@ -59,6 +59,7 @@ public:
// overload
bool saveToStream_Plain(QTextStream &out, bool addComments = true, bool addSetNameAndNumber = true);
+ bool convertToCockatriceFormat(QString fileName);
protected:
void saveToStream_DeckHeader(QTextStream &out);
diff --git a/cockatrice/src/deck/deck_stats_interface.h b/cockatrice/src/deck/deck_stats_interface.h
index a6d77d1dc..c3611df81 100644
--- a/cockatrice/src/deck/deck_stats_interface.h
+++ b/cockatrice/src/deck/deck_stats_interface.h
@@ -31,7 +31,7 @@ private slots:
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
public:
- DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
+ explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(DeckList *deck);
};
diff --git a/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp
new file mode 100644
index 000000000..198fa259b
--- /dev/null
+++ b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp
@@ -0,0 +1,40 @@
+#include "dlg_convert_deck_to_cod_format.h"
+
+#include
+#include
+#include
+#include
+
+DialogConvertDeckToCodFormat::DialogConvertDeckToCodFormat(QWidget *parent) : QDialog(parent)
+{
+ layout = new QVBoxLayout(this);
+ label = new QLabel();
+ layout->addWidget(label);
+
+ dontAskAgainCheckbox = new QCheckBox(this);
+ layout->addWidget(dontAskAgainCheckbox);
+
+ buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
+ layout->addWidget(buttonBox);
+
+ connect(buttonBox, &QDialogButtonBox::accepted, this, [this]() { accept(); });
+
+ connect(buttonBox, &QDialogButtonBox::rejected, this, [this]() { reject(); });
+
+ setLayout(layout);
+ retranslateUi();
+}
+
+void DialogConvertDeckToCodFormat::retranslateUi()
+{
+ setWindowTitle(tr("Deck Format Conversion"));
+ label->setText(
+ tr("You tried to add a tag to a .txt format deck.\n Tags can only be added to .cod format decks.\n Do "
+ "you want to convert the deck to the .cod format?"));
+ dontAskAgainCheckbox->setText(tr("Remember and automatically apply choice in the future"));
+}
+
+bool DialogConvertDeckToCodFormat::dontAskAgain() const
+{
+ return dontAskAgainCheckbox->isChecked();
+}
diff --git a/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h
new file mode 100644
index 000000000..88a19591e
--- /dev/null
+++ b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h
@@ -0,0 +1,29 @@
+#ifndef DIALOG_CONVERT_DECK_TO_COD_FORMAT_H
+#define DIALOG_CONVERT_DECK_TO_COD_FORMAT_H
+
+#include
+#include
+#include
+#include
+#include
+
+class DialogConvertDeckToCodFormat : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit DialogConvertDeckToCodFormat(QWidget *parent);
+ void retranslateUi();
+
+ bool dontAskAgain() const;
+
+private:
+ QVBoxLayout *layout;
+ QLabel *label;
+ QCheckBox *dontAskAgainCheckbox;
+ QDialogButtonBox *buttonBox;
+
+ Q_DISABLE_COPY(DialogConvertDeckToCodFormat)
+};
+
+#endif // DIALOG_CONVERT_DECK_TO_COD_FORMAT_H
diff --git a/cockatrice/src/dialogs/dlg_create_token.h b/cockatrice/src/dialogs/dlg_create_token.h
index dec17109e..a226faaca 100644
--- a/cockatrice/src/dialogs/dlg_create_token.h
+++ b/cockatrice/src/dialogs/dlg_create_token.h
@@ -21,7 +21,7 @@ class DlgCreateToken : public QDialog
{
Q_OBJECT
public:
- DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent = nullptr);
+ explicit DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent = nullptr);
QString getName() const;
QString getColor() const;
QString getPT() const;
diff --git a/cockatrice/src/dialogs/dlg_edit_avatar.h b/cockatrice/src/dialogs/dlg_edit_avatar.h
index 604650fa2..8a84349e2 100644
--- a/cockatrice/src/dialogs/dlg_edit_avatar.h
+++ b/cockatrice/src/dialogs/dlg_edit_avatar.h
@@ -16,7 +16,7 @@ class DlgEditAvatar : public QDialog
{
Q_OBJECT
public:
- DlgEditAvatar(QWidget *parent = nullptr);
+ explicit DlgEditAvatar(QWidget *parent = nullptr);
QByteArray getImage();
private slots:
void actOk();
diff --git a/cockatrice/src/dialogs/dlg_edit_password.h b/cockatrice/src/dialogs/dlg_edit_password.h
index 4fab9c8c7..e3fcce6ec 100644
--- a/cockatrice/src/dialogs/dlg_edit_password.h
+++ b/cockatrice/src/dialogs/dlg_edit_password.h
@@ -13,7 +13,7 @@ class DlgEditPassword : public QDialog
{
Q_OBJECT
public:
- DlgEditPassword(QWidget *parent = nullptr);
+ explicit DlgEditPassword(QWidget *parent = nullptr);
QString getOldPassword() const
{
return oldPasswordEdit->text();
diff --git a/cockatrice/src/dialogs/dlg_edit_user.h b/cockatrice/src/dialogs/dlg_edit_user.h
index 3dc6c7ebc..db2ca60ac 100644
--- a/cockatrice/src/dialogs/dlg_edit_user.h
+++ b/cockatrice/src/dialogs/dlg_edit_user.h
@@ -13,10 +13,10 @@ class DlgEditUser : public QDialog
{
Q_OBJECT
public:
- DlgEditUser(QWidget *parent = nullptr,
- QString email = QString(),
- QString country = QString(),
- QString realName = QString());
+ explicit DlgEditUser(QWidget *parent = nullptr,
+ QString email = QString(),
+ QString country = QString(),
+ QString realName = QString());
QString getEmail() const
{
return emailEdit->text();
diff --git a/cockatrice/src/dialogs/dlg_filter_games.cpp b/cockatrice/src/dialogs/dlg_filter_games.cpp
index f988c5a49..f8e9aa7b2 100644
--- a/cockatrice/src/dialogs/dlg_filter_games.cpp
+++ b/cockatrice/src/dialogs/dlg_filter_games.cpp
@@ -6,12 +6,10 @@
#include
#include
#include
-#include
#include
#include
#include
#include
-#include
DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes,
const GamesProxyModel *_gamesProxyModel,
@@ -24,19 +22,19 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes,
{QTime(1, 0), tr("1 hour")},
{QTime(2, 0), tr("2 hours")}})
{
- showBuddiesOnlyGames = new QCheckBox(tr("Show '&buddies only' games"));
- showBuddiesOnlyGames->setChecked(gamesProxyModel->getShowBuddiesOnlyGames());
+ hideBuddiesOnlyGames = new QCheckBox(tr("Hide 'buddies only' games"));
+ hideBuddiesOnlyGames->setChecked(gamesProxyModel->getHideBuddiesOnlyGames());
- showFullGames = new QCheckBox(tr("Show &full games"));
- showFullGames->setChecked(gamesProxyModel->getShowFullGames());
+ hideFullGames = new QCheckBox(tr("Hide full games"));
+ hideFullGames->setChecked(gamesProxyModel->getHideFullGames());
- showGamesThatStarted = new QCheckBox(tr("Show games &that have started"));
- showGamesThatStarted->setChecked(gamesProxyModel->getShowGamesThatStarted());
+ hideGamesThatStarted = new QCheckBox(tr("Hide games that have started"));
+ hideGamesThatStarted->setChecked(gamesProxyModel->getHideGamesThatStarted());
- showPasswordProtectedGames = new QCheckBox(tr("Show &password protected games"));
- showPasswordProtectedGames->setChecked(gamesProxyModel->getShowPasswordProtectedGames());
+ hidePasswordProtectedGames = new QCheckBox(tr("Hide password protected games"));
+ hidePasswordProtectedGames->setChecked(gamesProxyModel->getHidePasswordProtectedGames());
- hideIgnoredUserGames = new QCheckBox(tr("Hide '&ignored user' games"));
+ hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games"));
hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames());
maxGameAgeComboBox = new QComboBox();
@@ -108,10 +106,10 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes,
maxPlayersGroupBox->setLayout(maxPlayersFilterLayout);
auto *restrictionsLayout = new QGridLayout;
- restrictionsLayout->addWidget(showFullGames, 0, 0);
- restrictionsLayout->addWidget(showGamesThatStarted, 1, 0);
- restrictionsLayout->addWidget(showPasswordProtectedGames, 2, 0);
- restrictionsLayout->addWidget(showBuddiesOnlyGames, 3, 0);
+ restrictionsLayout->addWidget(hideFullGames, 0, 0);
+ restrictionsLayout->addWidget(hideGamesThatStarted, 1, 0);
+ restrictionsLayout->addWidget(hidePasswordProtectedGames, 2, 0);
+ restrictionsLayout->addWidget(hideBuddiesOnlyGames, 3, 0);
restrictionsLayout->addWidget(hideIgnoredUserGames, 4, 0);
auto *restrictionsGroupBox = new QGroupBox(tr("Restrictions"));
@@ -185,34 +183,24 @@ void DlgFilterGames::toggleSpectatorCheckboxEnabledness(bool spectatorsEnabled)
showOnlyIfSpectatorsCanSeeHands->setDisabled(!spectatorsEnabled);
}
-bool DlgFilterGames::getShowFullGames() const
+bool DlgFilterGames::getHideFullGames() const
{
- return showFullGames->isChecked();
+ return hideFullGames->isChecked();
}
-bool DlgFilterGames::getShowGamesThatStarted() const
+bool DlgFilterGames::getHideGamesThatStarted() const
{
- return showGamesThatStarted->isChecked();
+ return hideGamesThatStarted->isChecked();
}
-bool DlgFilterGames::getShowBuddiesOnlyGames() const
+bool DlgFilterGames::getHideBuddiesOnlyGames() const
{
- return showBuddiesOnlyGames->isChecked();
+ return hideBuddiesOnlyGames->isChecked();
}
-void DlgFilterGames::setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames)
+bool DlgFilterGames::getHidePasswordProtectedGames() const
{
- showBuddiesOnlyGames->setChecked(_showBuddiesOnlyGames);
-}
-
-bool DlgFilterGames::getShowPasswordProtectedGames() const
-{
- return showPasswordProtectedGames->isChecked();
-}
-
-void DlgFilterGames::setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden)
-{
- showPasswordProtectedGames->setChecked(_passwordProtectedGamesHidden);
+ return hidePasswordProtectedGames->isChecked();
}
bool DlgFilterGames::getHideIgnoredUserGames() const
@@ -220,31 +208,16 @@ bool DlgFilterGames::getHideIgnoredUserGames() const
return hideIgnoredUserGames->isChecked();
}
-void DlgFilterGames::setHideIgnoredUserGames(bool _hideIgnoredUserGames)
-{
- hideIgnoredUserGames->setChecked(_hideIgnoredUserGames);
-}
-
QString DlgFilterGames::getGameNameFilter() const
{
return gameNameFilterEdit->text();
}
-void DlgFilterGames::setGameNameFilter(const QString &_gameNameFilter)
-{
- gameNameFilterEdit->setText(_gameNameFilter);
-}
-
QString DlgFilterGames::getCreatorNameFilter() const
{
return creatorNameFilterEdit->text();
}
-void DlgFilterGames::setCreatorNameFilter(const QString &_creatorNameFilter)
-{
- creatorNameFilterEdit->setText(_creatorNameFilter);
-}
-
QSet