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 @@ + inkscape:version="1.4 (86a8ad7, 2024-10-11)" + sodipodi:docname="pawn_double.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> - - - - - - - - + inkscape:window-width="1147" + inkscape:window-height="1211" + inkscape:window-x="2842" + inkscape:window-y="58" + inkscape:window-maximized="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -291,10 +244,25 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,-952.36218)"> - + + + + + 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 @@ + inkscape:version="1.4 (86a8ad7, 2024-10-11)" + sodipodi:docname="pawn_single.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> - - - - - - - - - - - + inkscape:window-width="1147" + inkscape:window-height="1211" + inkscape:window-x="3185" + inkscape:window-y="44" + inkscape:window-maximized="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -283,9 +245,9 @@ id="layer1" transform="translate(0,-952.36218)"> 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - 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 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - 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 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - 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 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - 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 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 DlgFilterGames::getGameTypeFilter() const { QSet result; @@ -257,15 +230,6 @@ QSet DlgFilterGames::getGameTypeFilter() const return result; } -void DlgFilterGames::setGameTypeFilter(const QSet &_gameTypeFilter) -{ - QMapIterator i(gameTypeFilterCheckBoxes); - while (i.hasNext()) { - i.next(); - i.value()->setChecked(_gameTypeFilter.contains(i.key())); - } -} - int DlgFilterGames::getMaxPlayersFilterMin() const { return maxPlayersFilterMinSpinBox->value(); @@ -285,13 +249,6 @@ QTime DlgFilterGames::getMaxGameAge() const return gameAgeMap.keys().at(index); } -void DlgFilterGames::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax) -{ - maxPlayersFilterMinSpinBox->setValue(_maxPlayersFilterMin); - maxPlayersFilterMaxSpinBox->setValue(_maxPlayersFilterMax == -1 ? maxPlayersFilterMaxSpinBox->maximum() - : _maxPlayersFilterMax); -} - bool DlgFilterGames::getShowOnlyIfSpectatorsCanWatch() const { return showOnlyIfSpectatorsCanWatch->isChecked(); diff --git a/cockatrice/src/dialogs/dlg_filter_games.h b/cockatrice/src/dialogs/dlg_filter_games.h index 77e5cbb2e..3b3c01a41 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.h +++ b/cockatrice/src/dialogs/dlg_filter_games.h @@ -21,10 +21,10 @@ class DlgFilterGames : public QDialog Q_OBJECT private: QGroupBox *generalGroupBox; - QCheckBox *showBuddiesOnlyGames; - QCheckBox *showFullGames; - QCheckBox *showGamesThatStarted; - QCheckBox *showPasswordProtectedGames; + QCheckBox *hideBuddiesOnlyGames; + QCheckBox *hideFullGames; + QCheckBox *hideGamesThatStarted; + QCheckBox *hidePasswordProtectedGames; QCheckBox *hideIgnoredUserGames; QLineEdit *gameNameFilterEdit; QLineEdit *creatorNameFilterEdit; @@ -50,12 +50,12 @@ public: const GamesProxyModel *_gamesProxyModel, QWidget *parent = nullptr); - bool getShowFullGames() const; - bool getShowGamesThatStarted() const; - bool getShowPasswordProtectedGames() const; + bool getHideFullGames() const; + bool getHideGamesThatStarted() const; + bool getHidePasswordProtectedGames() const; void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden); - bool getShowBuddiesOnlyGames() const; - void setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames); + bool getHideBuddiesOnlyGames() const; + void setHideBuddiesOnlyGames(bool _hideBuddiesOnlyGames); bool getHideIgnoredUserGames() const; void setHideIgnoredUserGames(bool _hideIgnoredUserGames); QString getGameNameFilter() const; diff --git a/cockatrice/src/dialogs/dlg_forgot_password_challenge.h b/cockatrice/src/dialogs/dlg_forgot_password_challenge.h index 34aa237bd..73fb44c33 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_challenge.h +++ b/cockatrice/src/dialogs/dlg_forgot_password_challenge.h @@ -13,7 +13,7 @@ class DlgForgotPasswordChallenge : public QDialog { Q_OBJECT public: - DlgForgotPasswordChallenge(QWidget *parent = nullptr); + explicit DlgForgotPasswordChallenge(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_forgot_password_request.h b/cockatrice/src/dialogs/dlg_forgot_password_request.h index c423444c0..8d267042a 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_request.h +++ b/cockatrice/src/dialogs/dlg_forgot_password_request.h @@ -13,7 +13,7 @@ class DlgForgotPasswordRequest : public QDialog { Q_OBJECT public: - DlgForgotPasswordRequest(QWidget *parent = nullptr); + explicit DlgForgotPasswordRequest(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_forgot_password_reset.h b/cockatrice/src/dialogs/dlg_forgot_password_reset.h index ed018ee7f..e523e19c3 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_reset.h +++ b/cockatrice/src/dialogs/dlg_forgot_password_reset.h @@ -13,7 +13,7 @@ class DlgForgotPasswordReset : public QDialog { Q_OBJECT public: - DlgForgotPasswordReset(QWidget *parent = nullptr); + explicit DlgForgotPasswordReset(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_load_remote_deck.h b/cockatrice/src/dialogs/dlg_load_remote_deck.h index 61dcd7a61..54be44c77 100644 --- a/cockatrice/src/dialogs/dlg_load_remote_deck.h +++ b/cockatrice/src/dialogs/dlg_load_remote_deck.h @@ -20,7 +20,7 @@ private slots: void currentItemChanged(const QModelIndex ¤t, const QModelIndex &previous); public: - DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent = nullptr); + explicit DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent = nullptr); int getDeckId() const; }; diff --git a/cockatrice/src/dialogs/dlg_manage_sets.h b/cockatrice/src/dialogs/dlg_manage_sets.h index 079057bfb..e974f67f9 100644 --- a/cockatrice/src/dialogs/dlg_manage_sets.h +++ b/cockatrice/src/dialogs/dlg_manage_sets.h @@ -51,8 +51,8 @@ private: }; public: - WndSets(QWidget *parent = nullptr); - ~WndSets(); + explicit WndSets(QWidget *parent = nullptr); + ~WndSets() override; protected: void selectRows(QSet rows); diff --git a/cockatrice/src/dialogs/dlg_register.h b/cockatrice/src/dialogs/dlg_register.h index 51dd593a5..1d87212bf 100644 --- a/cockatrice/src/dialogs/dlg_register.h +++ b/cockatrice/src/dialogs/dlg_register.h @@ -13,7 +13,7 @@ class DlgRegister : public QDialog { Q_OBJECT public: - DlgRegister(QWidget *parent = nullptr); + explicit DlgRegister(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 861090e5c..27986c601 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include #include @@ -181,6 +183,9 @@ GeneralSettingsPage::GeneralSettingsPage() updateReleaseChannelBox.setCurrentIndex(settings.getUpdateReleaseChannelIndex()); setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &GeneralSettingsPage::retranslateUi); + retranslateUi(); } QStringList GeneralSettingsPage::findQmFiles() @@ -464,6 +469,9 @@ AppearanceSettingsPage::AppearanceSettingsPage() mainLayout->addStretch(); setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &AppearanceSettingsPage::retranslateUi); + retranslateUi(); } void AppearanceSettingsPage::themeBoxChanged(int index) @@ -561,6 +569,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setPlayToStack); + closeEmptyCardViewCheckBox.setChecked(SettingsCache::instance().getCloseEmptyCardView()); + connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setCloseEmptyCardView); + annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens()); connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setAnnotateTokens); @@ -573,8 +585,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); generalGrid->addWidget(&playToStackCheckBox, 2, 0); - generalGrid->addWidget(&annotateTokensCheckBox, 3, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 4, 0); + generalGrid->addWidget(&closeEmptyCardViewCheckBox, 3, 0); + generalGrid->addWidget(&annotateTokensCheckBox, 4, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 5, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -603,8 +616,19 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&openDeckInNewTabCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setOpenDeckInNewTab); + visualDeckStoragePromptForConversionCheckBox.setChecked( + SettingsCache::instance().getVisualDeckStoragePromptForConversion()); + connect(&visualDeckStoragePromptForConversionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStoragePromptForConversion); + + visualDeckStorageAlwaysConvertCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageAlwaysConvert()); + connect(&visualDeckStorageAlwaysConvertCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageAlwaysConvert); + auto *deckEditorGrid = new QGridLayout; deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionCheckBox, 1, 0); + deckEditorGrid->addWidget(&visualDeckStorageAlwaysConvertCheckBox, 2, 0); deckEditorGroupBox = new QGroupBox; deckEditorGroupBox->setLayout(deckEditorGrid); @@ -632,6 +656,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() mainLayout->addStretch(); setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &UserInterfaceSettingsPage::retranslateUi); + retranslateUi(); } void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i) @@ -650,6 +677,7 @@ void UserInterfaceSettingsPage::retranslateUi() doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)")); clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); + closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); notificationsGroupBox->setTitle(tr("Notifications settings")); @@ -658,8 +686,10 @@ void UserInterfaceSettingsPage::retranslateUi() buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect")); animationGroupBox->setTitle(tr("Animation settings")); tapAnimationCheckBox.setText(tr("&Tap/untap animation")); - deckEditorGroupBox->setTitle(tr("Deck editor settings")); + deckEditorGroupBox->setTitle(tr("Deck editor/storage settings")); openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default")); + visualDeckStoragePromptForConversionCheckBox.setText(tr("Prompt before converting .txt decks to .cod format")); + visualDeckStorageAlwaysConvertCheckBox.setText(tr("Always convert if not prompted")); replayGroupBox->setTitle(tr("Replay settings")); rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:")); rewindBufferingMsBox.setSuffix(" ms"); @@ -801,6 +831,9 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() lpMainLayout->addWidget(mpSpoilerGroupBox); setLayout(lpMainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &DeckEditorSettingsPage::retranslateUi); + retranslateUi(); } void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() @@ -1103,6 +1136,7 @@ MessagesSettingsPage::MessagesSettingsPage() setLayout(mainLayout); + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &MessagesSettingsPage::retranslateUi); retranslateUi(); } @@ -1277,6 +1311,9 @@ SoundSettingsPage::SoundSettingsPage() mainLayout->addStretch(); setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &SoundSettingsPage::retranslateUi); + retranslateUi(); } void SoundSettingsPage::themeBoxChanged(int index) @@ -1369,6 +1406,9 @@ ShortcutSettingsPage::ShortcutSettingsPage() connect(btnClearAll, SIGNAL(clicked()), this, SLOT(clearShortcuts())); connect(shortcutsTable, &ShortcutTreeView::currentItemChanged, this, &ShortcutSettingsPage::currentItemChanged); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &ShortcutSettingsPage::retranslateUi); + retranslateUi(); } void ShortcutSettingsPage::currentItemChanged(const QString &key) @@ -1416,10 +1456,23 @@ void ShortcutSettingsPage::retranslateUi() searchEdit->setPlaceholderText(tr("Search by shortcut name")); } +static QScrollArea *makeScrollable(QWidget *widget) +{ + widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum); + + auto *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setContentsMargins(0, 0, 0, 0); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->horizontalScrollBar()->setEnabled(false); + scrollArea->setWidget(widget); + return scrollArea; +} + DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) { auto rec = QGuiApplication::primaryScreen()->availableGeometry(); - this->setBaseSize(qMin(700, rec.width()), qMin(700, rec.height())); + this->setMinimumSize(qMin(700, rec.width()), qMin(700, rec.height())); connect(&SettingsCache::instance(), SIGNAL(langChanged()), this, SLOT(updateLanguage())); @@ -1433,8 +1486,8 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) pagesWidget = new QStackedWidget; pagesWidget->addWidget(new GeneralSettingsPage); - pagesWidget->addWidget(new AppearanceSettingsPage); - pagesWidget->addWidget(new UserInterfaceSettingsPage); + pagesWidget->addWidget(makeScrollable(new AppearanceSettingsPage)); + pagesWidget->addWidget(makeScrollable(new UserInterfaceSettingsPage)); pagesWidget->addWidget(new DeckEditorSettingsPage); pagesWidget->addWidget(new MessagesSettingsPage); pagesWidget->addWidget(new SoundSettingsPage); @@ -1456,6 +1509,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) mainLayout->addWidget(buttonBox); setLayout(mainLayout); + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &DlgSettings::retranslateUi); retranslateUi(); adjustSize(); @@ -1524,13 +1578,6 @@ void DlgSettings::updateLanguage() installNewTranslator(); } -void DlgSettings::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::LanguageChange) - retranslateUi(); - QDialog::changeEvent(event); -} - void DlgSettings::closeEvent(QCloseEvent *event) { bool showLoadError = true; @@ -1619,8 +1666,5 @@ void DlgSettings::retranslateUi() soundButton->setText(tr("Sound")); shortcutsButton->setText(tr("Shortcuts")); - for (int i = 0; i < pagesWidget->count(); i++) - dynamic_cast(pagesWidget->widget(i))->retranslateUi(); - contentsWidget->reset(); } diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index cc6120ffc..c608969d7 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -139,10 +139,13 @@ private: QCheckBox doubleClickToPlayCheckBox; QCheckBox clickPlaysAllSelectedCheckBox; QCheckBox playToStackCheckBox; + QCheckBox closeEmptyCardViewCheckBox; QCheckBox annotateTokensCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox tapAnimationCheckBox; QCheckBox openDeckInNewTabCheckBox; + QCheckBox visualDeckStoragePromptForConversionCheckBox; + QCheckBox visualDeckStorageAlwaysConvertCheckBox; QLabel rewindBufferingMsLabel; QSpinBox rewindBufferingMsBox; QGroupBox *generalGroupBox; @@ -320,7 +323,6 @@ private: void retranslateUi(); protected: - void changeEvent(QEvent *event) override; void closeEvent(QCloseEvent *event) override; }; diff --git a/cockatrice/src/dialogs/dlg_update.h b/cockatrice/src/dialogs/dlg_update.h index e4415283e..0fb254cb7 100644 --- a/cockatrice/src/dialogs/dlg_update.h +++ b/cockatrice/src/dialogs/dlg_update.h @@ -16,7 +16,7 @@ class DlgUpdate : public QDialog { Q_OBJECT public: - DlgUpdate(QWidget *parent); + explicit DlgUpdate(QWidget *parent); private slots: void finishedUpdateCheck(bool needToUpdate, bool isCompatible, Release *release); diff --git a/cockatrice/src/game/board/abstract_counter.h b/cockatrice/src/game/board/abstract_counter.h index 24d2f1243..8f2ee2691 100644 --- a/cockatrice/src/game/board/abstract_counter.h +++ b/cockatrice/src/game/board/abstract_counter.h @@ -88,7 +88,7 @@ public: AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent = nullptr); protected: - bool eventFilter(QObject *obj, QEvent *event); + bool eventFilter(QObject *obj, QEvent *event) override; void changeValue(int diff); }; diff --git a/cockatrice/src/game/board/abstract_graphics_item.h b/cockatrice/src/game/board/abstract_graphics_item.h index 3461c35b3..738be17d8 100644 --- a/cockatrice/src/game/board/abstract_graphics_item.h +++ b/cockatrice/src/game/board/abstract_graphics_item.h @@ -24,7 +24,7 @@ protected: void paintNumberEllipse(int number, int radius, const QColor &color, int position, int count, QPainter *painter); public: - AbstractGraphicsItem(QGraphicsItem *parent = nullptr) : QObject(), QGraphicsItem(parent) + explicit AbstractGraphicsItem(QGraphicsItem *parent = nullptr) : QGraphicsItem(parent) { } }; diff --git a/cockatrice/src/game/board/arrow_item.h b/cockatrice/src/game/board/arrow_item.h index 31c2e47de..fe5669eb3 100644 --- a/cockatrice/src/game/board/arrow_item.h +++ b/cockatrice/src/game/board/arrow_item.h @@ -24,17 +24,17 @@ protected: bool targetLocked; QColor color; bool fullColor; - void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; public: ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color); - ~ArrowItem(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - QRectF boundingRect() const + ~ArrowItem() override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + QRectF boundingRect() const override { return path.boundingRect(); } - QPainterPath shape() const + QPainterPath shape() const override { return path; } @@ -83,8 +83,8 @@ public: void addChildArrow(ArrowDragItem *childArrow); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; class ArrowAttachItem : public ArrowItem @@ -96,12 +96,12 @@ private: void attachCards(CardItem *startCard, const CardItem *targetCard); public: - ArrowAttachItem(ArrowTarget *_startItem); + explicit ArrowAttachItem(ArrowTarget *_startItem); void addChildArrow(ArrowAttachItem *childArrow); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; #endif // ARROWITEM_H diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h index 3f8f2135f..98a80091f 100644 --- a/cockatrice/src/game/board/arrow_target.h +++ b/cockatrice/src/game/board/arrow_target.h @@ -19,8 +19,8 @@ private: QList arrowsFrom, arrowsTo; public: - ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr); - ~ArrowTarget(); + explicit ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr); + ~ArrowTarget() override; Player *getOwner() const { diff --git a/cockatrice/src/game/board/counter_general.h b/cockatrice/src/game/board/counter_general.h index 150ce2659..0bee9c824 100644 --- a/cockatrice/src/game/board/counter_general.h +++ b/cockatrice/src/game/board/counter_general.h @@ -20,8 +20,8 @@ public: bool useNameForShortcut = false, QGraphicsItem *parent = nullptr, QWidget *game = nullptr); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; }; #endif diff --git a/cockatrice/src/game/cards/abstract_card_drag_item.h b/cockatrice/src/game/cards/abstract_card_drag_item.h index 491a7043c..85ce38b15 100644 --- a/cockatrice/src/game/cards/abstract_card_drag_item.h +++ b/cockatrice/src/game/cards/abstract_card_drag_item.h @@ -26,7 +26,7 @@ public: return Type; } AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); - ~AbstractCardDragItem(); + ~AbstractCardDragItem() override; QRectF boundingRect() const override { return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); diff --git a/cockatrice/src/game/cards/abstract_card_item.h b/cockatrice/src/game/cards/abstract_card_item.h index 75bb794f1..51b345267 100644 --- a/cockatrice/src/game/cards/abstract_card_item.h +++ b/cockatrice/src/game/cards/abstract_card_item.h @@ -48,12 +48,12 @@ public: { return Type; } - AbstractCardItem(QGraphicsItem *parent = nullptr, - const QString &_name = QString(), - const QString &_providerId = QString(), - Player *_owner = nullptr, - int _id = -1); - ~AbstractCardItem(); + explicit AbstractCardItem(QGraphicsItem *parent = nullptr, + const QString &_name = QString(), + const QString &_providerId = QString(), + Player *_owner = nullptr, + int _id = -1); + ~AbstractCardItem() override; QRectF boundingRect() const override; QPainterPath shape() const override; QSizeF getTranslatedSize(QPainter *painter) const; diff --git a/cockatrice/src/game/cards/card_database_model.cpp b/cockatrice/src/game/cards/card_database_model.cpp index fb105d424..31ff98263 100644 --- a/cockatrice/src/game/cards/card_database_model.cpp +++ b/cockatrice/src/game/cards/card_database_model.cpp @@ -179,7 +179,8 @@ void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index) return; } - beginInsertRows(QModelIndex(), loadedRowCount, loadedRowCount + itemsToFetch - 1); + const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount); + beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1); loadedRowCount += itemsToFetch; endInsertRows(); diff --git a/cockatrice/src/game/cards/card_drag_item.h b/cockatrice/src/game/cards/card_drag_item.h index 96c8eca50..794fd6ed3 100644 --- a/cockatrice/src/game/cards/card_drag_item.h +++ b/cockatrice/src/game/cards/card_drag_item.h @@ -28,11 +28,11 @@ public: { return faceDown; } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void updatePosition(const QPointF &cursorScenePos); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void updatePosition(const QPointF &cursorScenePos) override; protected: - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; #endif diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index 31c3b7c0f..19cff1df1 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -38,16 +38,9 @@ CardItem::CardItem(Player *_owner, CardItem::~CardItem() { - prepareDelete(); - - if (scene()) - static_cast(scene())->unregisterAnimationItem(this); - delete cardMenu; delete ptMenu; delete moveMenu; - - deleteDragItem(); } void CardItem::prepareDelete() @@ -74,6 +67,8 @@ void CardItem::prepareDelete() void CardItem::deleteLater() { prepareDelete(); + if (scene()) + static_cast(scene())->unregisterAnimationItem(this); AbstractCardItem::deleteLater(); } diff --git a/cockatrice/src/game/cards/card_item.h b/cockatrice/src/game/cards/card_item.h index d71f0b231..2c77af20e 100644 --- a/cockatrice/src/game/cards/card_item.h +++ b/cockatrice/src/game/cards/card_item.h @@ -46,25 +46,25 @@ public: { Type = typeCard }; - int type() const + int type() const override { return Type; } - CardItem(Player *_owner, - QGraphicsItem *parent = nullptr, - const QString &_name = QString(), - const QString &_providerId = QString(), - int _cardid = -1, - bool revealedCard = false, - CardZone *_zone = nullptr); - ~CardItem(); + explicit CardItem(Player *_owner, + QGraphicsItem *parent = nullptr, + const QString &_name = QString(), + const QString &_providerId = QString(), + int _cardid = -1, + bool revealedCard = false, + CardZone *_zone = nullptr); + ~CardItem() override; void retranslateUi(); CardZone *getZone() const { return zone; } void setZone(CardZone *_zone); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; QPoint getGridPoint() const { return gridPoint; @@ -163,10 +163,10 @@ public: void playCard(bool faceDown); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); - QVariant itemChange(GraphicsItemChange change, const QVariant &value); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; }; #endif diff --git a/cockatrice/src/game/cards/card_list.h b/cockatrice/src/game/cards/card_list.h index 1bbf08639..d719b9a7e 100644 --- a/cockatrice/src/game/cards/card_list.h +++ b/cockatrice/src/game/cards/card_list.h @@ -34,7 +34,7 @@ public: SortBySet, SortByPrinting }; - CardList(bool _contentsKnown); + explicit CardList(bool _contentsKnown); CardItem *findCard(const int cardId) const; bool getContentsKnown() const { diff --git a/cockatrice/src/game/deckview/deck_view.h b/cockatrice/src/game/deckview/deck_view.h index 9026c64cf..a61c8fd8c 100644 --- a/cockatrice/src/game/deckview/deck_view.h +++ b/cockatrice/src/game/deckview/deck_view.h @@ -24,20 +24,20 @@ private: DeckViewCardDragItem *dragItem; public: - DeckViewCard(QGraphicsItem *parent = nullptr, - const QString &_name = QString(), - const QString &_providerId = QString(), - const QString &_originZone = QString()); - ~DeckViewCard(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit DeckViewCard(QGraphicsItem *parent = nullptr, + const QString &_name = QString(), + const QString &_providerId = QString(), + const QString &_originZone = QString()); + ~DeckViewCard() override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; const QString &getOriginZone() const { return originZone; } protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; }; class DeckViewCardDragItem : public AbstractCardDragItem @@ -48,10 +48,10 @@ private: public: DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); - void updatePosition(const QPointF &cursorScenePos); + void updatePosition(const QPointF &cursorScenePos) override; protected: - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; class DeckViewCardContainer : public QGraphicsItem @@ -73,13 +73,13 @@ public: { Type = typeDeckViewCardContainer }; - int type() const + int type() const override { return Type; } - DeckViewCardContainer(const QString &_name); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit DeckViewCardContainer(const QString &_name); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void addCard(DeckViewCard *card); void removeCard(DeckViewCard *card); const QList &getCards() const @@ -113,8 +113,8 @@ private: void rebuildTree(); public: - DeckViewScene(QObject *parent = nullptr); - ~DeckViewScene(); + explicit DeckViewScene(QObject *parent = nullptr); + ~DeckViewScene() override; void setLocked(bool _locked) { locked = _locked; @@ -142,7 +142,7 @@ private: DeckViewScene *deckViewScene; protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event) override; public slots: void updateSceneRect(const QRectF &rect); signals: @@ -150,7 +150,7 @@ signals: void sideboardPlanChanged(); public: - DeckView(QWidget *parent = nullptr); + explicit DeckView(QWidget *parent = nullptr); void setDeck(const DeckList &_deck); void setLocked(bool _locked) { @@ -160,7 +160,7 @@ public: { return deckViewScene->getSideboardPlan(); } - void mouseDoubleClickEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event) override; void resetSideboardPlan(); }; diff --git a/cockatrice/src/game/filters/filter_builder.h b/cockatrice/src/game/filters/filter_builder.h index be048836d..45ae24839 100644 --- a/cockatrice/src/game/filters/filter_builder.h +++ b/cockatrice/src/game/filters/filter_builder.h @@ -21,8 +21,8 @@ private: void destroyFilter(); public: - FilterBuilder(QWidget *parent = nullptr); - ~FilterBuilder(); + explicit FilterBuilder(QWidget *parent = nullptr); + ~FilterBuilder() override; signals: void add(const CardFilter *f); diff --git a/cockatrice/src/game/filters/filter_string.cpp b/cockatrice/src/game/filters/filter_string.cpp index d1defdefa..039dcdb9f 100644 --- a/cockatrice/src/game/filters/filter_string.cpp +++ b/cockatrice/src/game/filters/filter_string.cpp @@ -281,7 +281,7 @@ static void setupParserRules() for (const auto &i : sv) { parts += std::any_cast(i); } - const bool identity = sv.tokens[0][0] != 'i'; + const bool identity = sv.tokens[0].empty() || sv.tokens[0][0] != 'i'; if (sv.tokens[1][0] == ':') { return [=](const CardData &x) { QString match = identity ? x->getColors() : x->getProperty("coloridentity"); diff --git a/cockatrice/src/game/filters/filter_tree_model.h b/cockatrice/src/game/filters/filter_tree_model.h index 83271f49b..3ea2c4013 100644 --- a/cockatrice/src/game/filters/filter_tree_model.h +++ b/cockatrice/src/game/filters/filter_tree_model.h @@ -28,19 +28,19 @@ private: public: FilterTreeModel(QObject *parent = nullptr); - ~FilterTreeModel(); + ~FilterTreeModel() override; FilterTree *filterTree() const { return fTree; } - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - Qt::ItemFlags flags(const QModelIndex &index) const; - QModelIndex parent(const QModelIndex &ind) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const; - bool removeRows(int row, int count, const QModelIndex &parent); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QModelIndex parent(const QModelIndex &ind) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + bool removeRows(int row, int count, const QModelIndex &parent) override; }; #endif diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index af1e40f96..e649bbb2a 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -29,6 +29,13 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) GameScene::~GameScene() { delete animationTimer; + + // DO NOT call clearViews() here + // clearViews calls close() on the zoneViews, which sends signals; sending signals in destructors leads to segfaults + // deleteLater() deletes the zoneView without allowing it to send signals + for (const auto &zoneView : zoneViews) { + zoneView->deleteLater(); + } } void GameScene::retranslateUi() @@ -76,7 +83,7 @@ void GameScene::rearrange() QListIterator playersIter(players); while (playersIter.hasNext()) { Player *p = playersIter.next(); - if (!p->getConceded()) { + if (p && !p->getConceded()) { playersPlaying.append(p); if (!firstPlayerFound && (p->getLocal())) { firstPlayerIndex = playersPlaying.size() - 1; diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index 7654f0b6e..801acacee 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -37,8 +37,8 @@ private: void updateHover(const QPointF &scenePos); public: - GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); - ~GameScene(); + explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); + ~GameScene() override; void retranslateUi(); void processViewSizeChange(const QSize &newSize); QTransform getViewTransform() const; @@ -65,8 +65,8 @@ public slots: void rearrange(); protected: - bool event(QEvent *event); - void timerEvent(QTimerEvent *event); + bool event(QEvent *event) override; + void timerEvent(QTimerEvent *event) override; signals: void sigStartRubberBand(const QPointF &selectionOrigin); void sigResizeRubberBand(const QPointF &cursorPoint); diff --git a/cockatrice/src/game/game_selector.cpp b/cockatrice/src/game/game_selector.cpp index 67d938554..694e79ec9 100644 --- a/cockatrice/src/game/game_selector.cpp +++ b/cockatrice/src/game/game_selector.cpp @@ -38,6 +38,9 @@ GameSelector::GameSelector(AbstractClient *_client, : QGroupBox(parent), client(_client), tabSupervisor(_tabSupervisor), room(_room), showFilters(_showfilters) { gameListView = new QTreeView; + gameListView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(gameListView, &QTreeView::customContextMenuRequested, this, &GameSelector::customContextMenu); + gameListModel = new GamesModel(_rooms, _gameTypes, this); if (showFilters) { gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserListManager()); @@ -47,6 +50,7 @@ GameSelector::GameSelector(AbstractClient *_client, } else { gameListView->setModel(gameListModel); } + gameListView->setIconSize(QSize(13, 13)); gameListView->setSortingEnabled(true); gameListView->sortByColumn(gameListModel->startTimeColIndex(), Qt::AscendingOrder); gameListView->setAlternatingRowColors(true); @@ -113,7 +117,7 @@ GameSelector::GameSelector(AbstractClient *_client, setMinimumHeight(200); connect(joinButton, &QPushButton::clicked, this, &GameSelector::actJoin); - connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actJoin); + connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actSpectate); connect(gameListView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &GameSelector::actSelectedGameChanged); connect(gameListView, &QTreeView::activated, this, &GameSelector::actJoin); @@ -151,10 +155,10 @@ void GameSelector::actSetFilter() if (!dlg.exec()) return; - gameListProxyModel->setShowBuddiesOnlyGames(dlg.getShowBuddiesOnlyGames()); - gameListProxyModel->setShowFullGames(dlg.getShowFullGames()); - gameListProxyModel->setShowGamesThatStarted(dlg.getShowGamesThatStarted()); - gameListProxyModel->setShowPasswordProtectedGames(dlg.getShowPasswordProtectedGames()); + gameListProxyModel->setHideBuddiesOnlyGames(dlg.getHideBuddiesOnlyGames()); + gameListProxyModel->setHideFullGames(dlg.getHideFullGames()); + gameListProxyModel->setHideGamesThatStarted(dlg.getHideGamesThatStarted()); + gameListProxyModel->setHidePasswordProtectedGames(dlg.getHidePasswordProtectedGames()); gameListProxyModel->setHideIgnoredUserGames(dlg.getHideIgnoredUserGames()); gameListProxyModel->setGameNameFilter(dlg.getGameNameFilter()); gameListProxyModel->setCreatorNameFilter(dlg.getCreatorNameFilter()); @@ -241,21 +245,65 @@ void GameSelector::checkResponse(const Response &response) void GameSelector::actJoin() { - QModelIndex ind = gameListView->currentIndex(); - if (!ind.isValid()) + return joinGame(false); +} + +void GameSelector::actSpectate() +{ + return joinGame(true); +} + +void GameSelector::customContextMenu(const QPoint &point) +{ + const auto &index = gameListView->indexAt(point); + if (!index.isValid()) { return; + } + + QAction joinGame(tr("Join Game")); + connect(&joinGame, &QAction::triggered, this, &GameSelector::actJoin); + + QAction spectateGame(tr("Spectate Game")); + connect(&spectateGame, &QAction::triggered, this, &GameSelector::actSpectate); + + QAction getGameInfo(tr("Game Information")); + connect(&getGameInfo, &QAction::triggered, this, [=, this]() { + const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt()); + const QMap &gameTypes = gameListModel->getGameTypes().value(gameInfo.room_id()); + + DlgCreateGame dlg(gameInfo, gameTypes, this); + dlg.exec(); + }); + + QMenu menu; + menu.addAction(&joinGame); + menu.addAction(&spectateGame); + menu.addAction(&getGameInfo); + menu.exec(gameListView->mapToGlobal(point)); +} + +void GameSelector::joinGame(const bool isSpectator) +{ + QModelIndex ind = gameListView->currentIndex(); + if (!ind.isValid()) { + return; + } + const ServerInfo_Game &game = gameListModel->getGame(ind.data(Qt::UserRole).toInt()); if (tabSupervisor->switchToGameTabIfAlreadyExists(game.game_id())) { return; } - bool spectator = sender() == spectateButton || game.player_count() == game.max_players(); + + bool spectator = isSpectator || game.player_count() == game.max_players(); + bool overrideRestrictions = !tabSupervisor->getAdminLocked(); QString password; if (game.with_password() && !(spectator && !game.spectators_need_password()) && !overrideRestrictions) { bool ok; password = getTextWithMax(this, tr("Join game"), tr("Password:"), QLineEdit::Password, QString(), &ok); - if (!ok) + if (!ok) { return; + } } Command_JoinGame cmd; diff --git a/cockatrice/src/game/game_selector.h b/cockatrice/src/game/game_selector.h index b65ceb3f3..a4b8a68e2 100644 --- a/cockatrice/src/game/game_selector.h +++ b/cockatrice/src/game/game_selector.h @@ -27,7 +27,11 @@ private slots: void actSetFilter(); void actClearFilter(); void actCreate(); + void actJoin(); + void actSpectate(); + void customContextMenu(const QPoint &point); + void actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex &previous); void checkResponse(const Response &response); @@ -53,6 +57,7 @@ private: void disableButtons(); void enableButtons(); void enableButtonsForIndex(const QModelIndex ¤t); + void joinGame(const bool isSpectator); public: GameSelector(AbstractClient *_client, diff --git a/cockatrice/src/game/game_view.h b/cockatrice/src/game/game_view.h index 89a669a9c..19771298d 100644 --- a/cockatrice/src/game/game_view.h +++ b/cockatrice/src/game/game_view.h @@ -15,7 +15,7 @@ private: QPointF selectionOrigin; protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event) override; private slots: void startRubberBand(const QPointF &selectionOrigin); void resizeRubberBand(const QPointF &cursorPoint); @@ -25,7 +25,7 @@ public slots: void updateSceneRect(const QRectF &rect); public: - GameView(GameScene *scene, QWidget *parent = nullptr); + explicit GameView(GameScene *scene, QWidget *parent = nullptr); }; #endif diff --git a/cockatrice/src/game/games_model.cpp b/cockatrice/src/game/games_model.cpp index 0ec6df027..93dbb7f20 100644 --- a/cockatrice/src/game/games_model.cpp +++ b/cockatrice/src/game/games_model.cpp @@ -26,15 +26,6 @@ enum GameListColumn SPECTATORS }; -const bool DEFAULT_SHOW_FULL_GAMES = false; -const bool DEFAULT_SHOW_GAMES_THAT_STARTED = false; -const bool DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES = true; -const bool DEFAULT_SHOW_BUDDIES_ONLY_GAMES = true; -const bool DEFAULT_HIDE_IGNORED_USER_GAMES = false; -const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH = false; -const bool DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED = true; -const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT = false; -const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS = false; const int DEFAULT_MAX_PLAYERS_MIN = 1; const int DEFAULT_MAX_PLAYERS_MAX = 99; constexpr QTime DEFAULT_MAX_GAME_AGE = QTime(); @@ -127,10 +118,10 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: return QString::fromStdString(gameentry.creator_info().name()); case Qt::DecorationRole: { - QPixmap avatarPixmap = UserLevelPixmapGenerator::generatePixmap( - 13, (UserLevelFlags)gameentry.creator_info().user_level(), false, + return UserLevelPixmapGenerator::generateIcon( + 13, UserLevelFlags(gameentry.creator_info().user_level()), + gameentry.creator_info().pawn_colors(), false, QString::fromStdString(gameentry.creator_info().privlevel())); - return QIcon(avatarPixmap); } default: return QVariant(); @@ -293,9 +284,9 @@ GamesProxyModel::GamesProxyModel(QObject *parent, const UserListProxy *_userList setDynamicSortFilter(true); } -void GamesProxyModel::setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames) +void GamesProxyModel::setHideBuddiesOnlyGames(bool _showBuddiesOnlyGames) { - showBuddiesOnlyGames = _showBuddiesOnlyGames; + hideBuddiesOnlyGames = _showBuddiesOnlyGames; invalidateFilter(); } @@ -305,21 +296,21 @@ void GamesProxyModel::setHideIgnoredUserGames(bool _hideIgnoredUserGames) invalidateFilter(); } -void GamesProxyModel::setShowFullGames(bool _showFullGames) +void GamesProxyModel::setHideFullGames(bool _showFullGames) { - showFullGames = _showFullGames; + hideFullGames = _showFullGames; invalidateFilter(); } -void GamesProxyModel::setShowGamesThatStarted(bool _showGamesThatStarted) +void GamesProxyModel::setHideGamesThatStarted(bool _showGamesThatStarted) { - showGamesThatStarted = _showGamesThatStarted; + hideGamesThatStarted = _showGamesThatStarted; invalidateFilter(); } -void GamesProxyModel::setShowPasswordProtectedGames(bool _showPasswordProtectedGames) +void GamesProxyModel::setHidePasswordProtectedGames(bool _showPasswordProtectedGames) { - showPasswordProtectedGames = _showPasswordProtectedGames; + hidePasswordProtectedGames = _showPasswordProtectedGames; invalidateFilter(); } @@ -395,47 +386,43 @@ int GamesProxyModel::getNumFilteredGames() const void GamesProxyModel::resetFilterParameters() { - showFullGames = DEFAULT_SHOW_FULL_GAMES; - showGamesThatStarted = DEFAULT_SHOW_GAMES_THAT_STARTED; - showPasswordProtectedGames = DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES; - showBuddiesOnlyGames = DEFAULT_SHOW_BUDDIES_ONLY_GAMES; - hideIgnoredUserGames = DEFAULT_HIDE_IGNORED_USER_GAMES; + hideFullGames = false; + hideGamesThatStarted = false; + hidePasswordProtectedGames = false; + hideBuddiesOnlyGames = false; + hideIgnoredUserGames = false; gameNameFilter = QString(); creatorNameFilter = QString(); gameTypeFilter.clear(); maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN; maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX; maxGameAge = DEFAULT_MAX_GAME_AGE; - showOnlyIfSpectatorsCanWatch = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH; - showSpectatorPasswordProtected = DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED; - showOnlyIfSpectatorsCanChat = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT; - showOnlyIfSpectatorsCanSeeHands = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS; + showOnlyIfSpectatorsCanWatch = false; + showSpectatorPasswordProtected = false; + showOnlyIfSpectatorsCanChat = false; + showOnlyIfSpectatorsCanSeeHands = false; invalidateFilter(); } bool GamesProxyModel::areFilterParametersSetToDefaults() const { - return showFullGames == DEFAULT_SHOW_FULL_GAMES && showGamesThatStarted == DEFAULT_SHOW_GAMES_THAT_STARTED && - showPasswordProtectedGames == DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES && - showBuddiesOnlyGames == DEFAULT_SHOW_BUDDIES_ONLY_GAMES && - hideIgnoredUserGames == DEFAULT_HIDE_IGNORED_USER_GAMES && gameNameFilter.isEmpty() && - creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && + return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames && + !hideIgnoredUserGames && gameNameFilter.isEmpty() && creatorNameFilter.isEmpty() && + gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && - showOnlyIfSpectatorsCanWatch == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH && - showSpectatorPasswordProtected == DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED && - showOnlyIfSpectatorsCanChat == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT && - showOnlyIfSpectatorsCanSeeHands == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS; + !showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat && + !showOnlyIfSpectatorsCanSeeHands; } void GamesProxyModel::loadFilterParameters(const QMap &allGameTypes) { GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters(); - showFullGames = gameFilters.isShowFullGames(); - showGamesThatStarted = gameFilters.isShowGamesThatStarted(); - showPasswordProtectedGames = gameFilters.isShowPasswordProtectedGames(); + hideFullGames = gameFilters.isHideFullGames(); + hideGamesThatStarted = gameFilters.isHideGamesThatStarted(); + hidePasswordProtectedGames = gameFilters.isHidePasswordProtectedGames(); hideIgnoredUserGames = gameFilters.isHideIgnoredUserGames(); - showBuddiesOnlyGames = gameFilters.isShowBuddiesOnlyGames(); + hideBuddiesOnlyGames = gameFilters.isHideBuddiesOnlyGames(); gameNameFilter = gameFilters.getGameNameFilter(); creatorNameFilter = gameFilters.getCreatorNameFilter(); maxPlayersFilterMin = gameFilters.getMinPlayers(); @@ -460,10 +447,10 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType void GamesProxyModel::saveFilterParameters(const QMap &allGameTypes) { GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters(); - gameFilters.setShowBuddiesOnlyGames(showBuddiesOnlyGames); - gameFilters.setShowFullGames(showFullGames); - gameFilters.setShowGamesThatStarted(showGamesThatStarted); - gameFilters.setShowPasswordProtectedGames(showPasswordProtectedGames); + gameFilters.setHideBuddiesOnlyGames(hideBuddiesOnlyGames); + gameFilters.setHideFullGames(hideFullGames); + gameFilters.setHideGamesThatStarted(hideGamesThatStarted); + gameFilters.setHidePasswordProtectedGames(hidePasswordProtectedGames); gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames); gameFilters.setGameNameFilter(gameNameFilter); gameFilters.setCreatorNameFilter(creatorNameFilter); @@ -505,20 +492,20 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const const ServerInfo_Game &game = model->getGame(sourceRow); - if (!showBuddiesOnlyGames && game.only_buddies()) { + if (hideBuddiesOnlyGames && game.only_buddies()) { return false; } if (hideIgnoredUserGames && userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) { return false; } - if (!showFullGames && game.player_count() == game.max_players()) + if (hideFullGames && game.player_count() == game.max_players()) return false; - if (!showGamesThatStarted && game.started()) + if (hideGamesThatStarted && game.started()) return false; if (!userListProxy->isOwnUserRegistered()) if (game.only_registered()) return false; - if (!showPasswordProtectedGames && game.with_password()) + if (hidePasswordProtectedGames && game.with_password()) return false; if (!gameNameFilter.isEmpty()) if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive)) diff --git a/cockatrice/src/game/games_model.h b/cockatrice/src/game/games_model.h index e70e95f6b..6f590ee47 100644 --- a/cockatrice/src/game/games_model.h +++ b/cockatrice/src/game/games_model.h @@ -27,16 +27,16 @@ public: static const int SORT_ROLE = Qt::UserRole + 1; GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent.isValid() ? 0 : gameList.size(); } - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return NUM_COLS; } - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; static const QString getGameCreatedString(const int secs); const ServerInfo_Game &getGame(int row); @@ -75,11 +75,11 @@ private: // - loadFilterParameters() // - saveFilterParameters() // - filterAcceptsRow() - bool showBuddiesOnlyGames; + bool hideBuddiesOnlyGames; bool hideIgnoredUserGames; - bool showFullGames; - bool showGamesThatStarted; - bool showPasswordProtectedGames; + bool hideFullGames; + bool hideGamesThatStarted; + bool hidePasswordProtectedGames; QString gameNameFilter, creatorNameFilter; QSet gameTypeFilter; quint32 maxPlayersFilterMin, maxPlayersFilterMax; @@ -88,33 +88,33 @@ private: showOnlyIfSpectatorsCanSeeHands; public: - GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); + explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); - bool getShowBuddiesOnlyGames() const + bool getHideBuddiesOnlyGames() const { - return showBuddiesOnlyGames; + return hideBuddiesOnlyGames; } - void setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames); + void setHideBuddiesOnlyGames(bool _showBuddiesOnlyGames); bool getHideIgnoredUserGames() const { return hideIgnoredUserGames; } void setHideIgnoredUserGames(bool _hideIgnoredUserGames); - bool getShowFullGames() const + bool getHideFullGames() const { - return showFullGames; + return hideFullGames; } - void setShowFullGames(bool _showFullGames); - bool getShowGamesThatStarted() const + void setHideFullGames(bool _showFullGames); + bool getHideGamesThatStarted() const { - return showGamesThatStarted; + return hideGamesThatStarted; } - void setShowGamesThatStarted(bool _showGamesThatStarted); - bool getShowPasswordProtectedGames() const + void setHideGamesThatStarted(bool _showGamesThatStarted); + bool getHidePasswordProtectedGames() const { - return showPasswordProtectedGames; + return hidePasswordProtectedGames; } - void setShowPasswordProtectedGames(bool _showPasswordProtectedGames); + void setHidePasswordProtectedGames(bool _showPasswordProtectedGames); QString getGameNameFilter() const { return gameNameFilter; @@ -173,7 +173,7 @@ public: void refresh(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow) const; }; diff --git a/cockatrice/src/game/hand_counter.h b/cockatrice/src/game/hand_counter.h index 71b1a95a4..2a6633b63 100644 --- a/cockatrice/src/game/hand_counter.h +++ b/cockatrice/src/game/hand_counter.h @@ -15,7 +15,7 @@ private: int number; protected: - void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; public slots: void updateNumber(); signals: @@ -26,14 +26,14 @@ public: { Type = typeOther }; - int type() const + int type() const override { return Type; } - HandCounter(QGraphicsItem *parent = nullptr); - ~HandCounter(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit HandCounter(QGraphicsItem *parent = nullptr); + ~HandCounter() override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; }; #endif diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index d25f3ef63..71a9a0a3f 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -564,9 +564,6 @@ Player::~Player() { qCDebug(PlayerLog) << "Player destructor:" << getName(); - static_cast(scene())->removePlayer(this); - - clear(); QMapIterator i(zones); while (i.hasNext()) delete i.next().value(); @@ -2086,7 +2083,7 @@ void Player::eventShuffle(const Event_Shuffle &event) int length = view->getCards().length(); // we want to close empty views as well if (length == 0 || length > absStart) { // note this assumes views always start at the top of the library - view->deleteLater(); + view->close(); break; } } else { diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index b5596928d..5404cdd96 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -522,7 +522,7 @@ class AnnotationDialog : public QInputDialog void keyPressEvent(QKeyEvent *e) override; public: - AnnotationDialog(QWidget *parent) : QInputDialog(parent) + explicit AnnotationDialog(QWidget *parent) : QInputDialog(parent) { } }; diff --git a/cockatrice/src/game/player/player_list_widget.cpp b/cockatrice/src/game/player/player_list_widget.cpp index 930b36d69..a5901c392 100644 --- a/cockatrice/src/game/player/player_list_widget.cpp +++ b/cockatrice/src/game/player/player_list_widget.cpp @@ -140,9 +140,9 @@ void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties } if (prop.has_user_info()) { player->setData(3, Qt::UserRole, prop.user_info().user_level()); - player->setIcon( - 3, QIcon(UserLevelPixmapGenerator::generatePixmap(12, UserLevelFlags(prop.user_info().user_level()), false, - QString::fromStdString(prop.user_info().privlevel())))); + player->setIcon(3, UserLevelPixmapGenerator::generateIcon( + 12, UserLevelFlags(prop.user_info().user_level()), prop.user_info().pawn_colors(), false, + QString::fromStdString(prop.user_info().privlevel()))); player->setText(4, QString::fromStdString(prop.user_info().name())); const QString country = QString::fromStdString(prop.user_info().country()); if (!country.isEmpty()) diff --git a/cockatrice/src/game/player/player_list_widget.h b/cockatrice/src/game/player/player_list_widget.h index 6cc633856..a0714bc4d 100644 --- a/cockatrice/src/game/player/player_list_widget.h +++ b/cockatrice/src/game/player/player_list_widget.h @@ -15,16 +15,18 @@ class UserContextMenu; class PlayerListItemDelegate : public QStyledItemDelegate { public: - PlayerListItemDelegate(QObject *const parent); - bool - editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); + explicit PlayerListItemDelegate(QObject *parent); + bool editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) override; }; class PlayerListTWI : public QTreeWidgetItem { public: PlayerListTWI(); - bool operator<(const QTreeWidgetItem &other) const; + bool operator<(const QTreeWidgetItem &other) const override; }; class PlayerListWidget : public QTreeWidget diff --git a/cockatrice/src/game/player/player_target.cpp b/cockatrice/src/game/player/player_target.cpp index a4542225b..ba1c3d52d 100644 --- a/cockatrice/src/game/player/player_target.cpp +++ b/cockatrice/src/game/player/player_target.cpp @@ -103,9 +103,9 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o QPixmap tempPixmap; if (fullPixmap.isNull()) - tempPixmap = - UserLevelPixmapGenerator::generatePixmap(translatedSize.height(), UserLevelFlags(info->user_level()), - false, QString::fromStdString(info->privlevel())); + tempPixmap = UserLevelPixmapGenerator::generatePixmap( + translatedSize.height(), UserLevelFlags(info->user_level()), info->pawn_colors(), false, + QString::fromStdString(info->privlevel())); else tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); diff --git a/cockatrice/src/game/player/player_target.h b/cockatrice/src/game/player/player_target.h index 7adcf5d80..f02f3bcea 100644 --- a/cockatrice/src/game/player/player_target.h +++ b/cockatrice/src/game/player/player_target.h @@ -19,8 +19,8 @@ public: int _value, QGraphicsItem *parent = nullptr, QWidget *game = nullptr); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; }; class PlayerTarget : public ArrowTarget @@ -38,15 +38,15 @@ public: { Type = typePlayerTarget }; - int type() const + int type() const override { return Type; } - PlayerTarget(Player *_player = nullptr, QGraphicsItem *parentItem = nullptr, QWidget *_game = nullptr); - ~PlayerTarget(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit PlayerTarget(Player *_player = nullptr, QGraphicsItem *parentItem = nullptr, QWidget *_game = nullptr); + ~PlayerTarget() override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; AbstractCounter *addCounter(int _counterId, const QString &_name, int _value); }; diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index 893a9b181..f3ebbd214 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -40,17 +40,6 @@ CardZone::CardZone(Player *_p, &CardZone::refreshCardInfos); } -CardZone::~CardZone() -{ - qCDebug(CardZoneLog) << "CardZone destructor: " << name; - for (auto *view : views) { - if (view != nullptr) { - view->deleteLater(); - } - } - clearContents(); -} - void CardZone::retranslateUi() { for (int i = 0; i < cards.size(); ++i) @@ -152,7 +141,7 @@ void CardZone::addCard(CardItem *card, const bool reorganize, const int x, const } for (auto *view : views) { - if (view->getIsReversed() || (x <= view->getCards().size()) || (view->getNumberCards() == -1)) { + if (view->prepareAddCard(x)) { view->addCard(new CardItem(player, nullptr, card->getName(), card->getProviderId(), card->getId()), reorganize, x, y); } @@ -184,7 +173,7 @@ CardItem *CardZone::getCard(int cardId, const QString &cardName) return c; } -CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) +CardItem *CardZone::takeCard(int position, int cardId, bool toNewZone) { if (position == -1) { // position == -1 means either that the zone is indexed by card id @@ -201,7 +190,7 @@ CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) return nullptr; for (auto *view : views) { - view->removeCard(position); + view->removeCard(position, toNewZone); } CardItem *c = cards.takeAt(position); diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h index 20f58d4ba..91cad179c 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game/zones/card_zone.h @@ -37,8 +37,8 @@ protected: bool isShufflable; bool isView; bool alwaysRevealTopCard; - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; virtual void addCardImpl(CardItem *card, int x, int y) = 0; signals: void cardCountChanged(); @@ -54,7 +54,7 @@ public: { Type = typeZone }; - int type() const + int type() const override { return Type; } @@ -67,7 +67,6 @@ public: bool _contentsKnown, QGraphicsItem *parent = nullptr, bool _isView = false); - ~CardZone(); void retranslateUi(); void clearContents(); bool getHasCardAttr() const diff --git a/cockatrice/src/game/zones/hand_zone.h b/cockatrice/src/game/zones/hand_zone.h index 004847f7f..8189c7087 100644 --- a/cockatrice/src/game/zones/hand_zone.h +++ b/cockatrice/src/game/zones/hand_zone.h @@ -15,14 +15,14 @@ public slots: public: HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent = nullptr); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void reorganizeCards() override; void setWidth(qreal _width); protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y) override; }; #endif diff --git a/cockatrice/src/game/zones/select_zone.h b/cockatrice/src/game/zones/select_zone.h index 07a8d7a0d..c8c88b450 100644 --- a/cockatrice/src/game/zones/select_zone.h +++ b/cockatrice/src/game/zones/select_zone.h @@ -16,9 +16,9 @@ private: QSet cardsInSelectionRect; protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; public: SelectZone(Player *_player, diff --git a/cockatrice/src/game/zones/stack_zone.h b/cockatrice/src/game/zones/stack_zone.h index f45ec0e9d..38dee0aca 100644 --- a/cockatrice/src/game/zones/stack_zone.h +++ b/cockatrice/src/game/zones/stack_zone.h @@ -13,13 +13,13 @@ private slots: public: StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent = nullptr); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void reorganizeCards() override; protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y) override; }; #endif diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index ee1d0219d..686cb3588 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -244,10 +244,10 @@ void TableZone::toggleTapped() player->sendGameCommand(player->prepareGameCommand(cmdList)); } -CardItem *TableZone::takeCard(int position, int cardId, bool canResize) +CardItem *TableZone::takeCard(int position, int cardId, bool toNewZone) { CardItem *result = CardZone::takeCard(position, cardId); - if (canResize) + if (toNewZone) resizeToContents(); return result; } diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 3e93ef781..271276967 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -89,7 +89,7 @@ public slots: /** Reorganizes CardItems in the TableZone */ - void reorganizeCards(); + void reorganizeCards() override; public: /** @@ -98,12 +98,12 @@ public: @param _p the Player @param parent defaults to null */ - TableZone(Player *_p, QGraphicsItem *parent = nullptr); + explicit TableZone(Player *_p, QGraphicsItem *parent = nullptr); /** @return a QRectF of the TableZone bounding box. */ - QRectF boundingRect() const; + QRectF boundingRect() const override; /** Render the TableZone @@ -111,7 +111,7 @@ public: @param painter @param option */ - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; /** Toggles the selected items as tapped. @@ -121,7 +121,7 @@ public: /** See HandleDropEventByGrid */ - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; /** Handles the placement of cards @@ -138,7 +138,7 @@ public: */ CardItem *getCardFromCoords(const QPointF &point) const; - QPointF closestGridPoint(const QPointF &point); + QPointF closestGridPoint(const QPointF &point) override; static int clampValidTableRow(const int row); @@ -147,10 +147,10 @@ public: @param position card position @param cardId id of card to take - @param canResize defaults to true + @param toNewZone Whether the destination of the card is not the same as the starting zone. Defaults to true @return CardItem that has been removed */ - CardItem *takeCard(int position, int cardId, bool canResize = true); + CardItem *takeCard(int position, int cardId, bool toNewZone = true) override; /** Resizes the TableZone in case CardItems are within or @@ -178,7 +178,7 @@ public: } protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y) override; private: void paintZoneOutline(QPainter *painter); diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index 9da4ecc39..d86e71f90 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -40,13 +40,17 @@ ZoneViewZone::ZoneViewZone(Player *_p, } } -ZoneViewZone::~ZoneViewZone() +/** + * Deletes this ZoneView and removes it from the origZone's views. + * You should normally call this method instead of deleteLater() + */ +void ZoneViewZone::close() { - emit beingDeleted(); - qDebug("ZoneViewZone destructor"); + emit closed(); if (!(revealZone && !writeableRevealZone)) { origZone->getViews().removeOne(this); } + deleteLater(); } QRectF ZoneViewZone::boundingRect() const @@ -278,11 +282,57 @@ void ZoneViewZone::setPileView(int _pileView) reorganizeCards(); } +/** + * Checks if inserting a card at the given position requires an actual new card to be created and added to the view. + * Also does any cardId updates that would be required if a card is inserted in that position. + * + * Note that this method can end up modifying the cardIds despite returning false. + * (for example, if the card is inserted into a hidden portion of the deck while the view is reversed) + * + * Make sure to call this method once before calling addCard(), so that you skip creating a new CardItem and calling + * addCard() if it's not required. + * + * @param x The position to insert the card at. + * @return Whether to proceed with calling addCard. + */ +bool ZoneViewZone::prepareAddCard(int x) +{ + bool doInsert = false; + if (!isReversed) { + if (x <= cards.size() || cards.size() == -1) { + doInsert = true; + } + } else { + // map x (which is in origZone indexes) to this viewZone's cardList index + int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId(); + int insertionIndex = x - firstId; + if (insertionIndex >= 0) { + // card was put into a portion of the deck that's in the view + doInsert = true; + } else { + // card was put into a portion of the deck that's not in the view; update ids but don't insert card + updateCardIds(ADD_CARD); + } + } + + // autoclose check is done both here and in removeCard + if (cards.isEmpty() && !doInsert && SettingsCache::instance().getCloseEmptyCardView()) { + close(); + } + + return doInsert; +} + +/** + * Make sure prepareAddCard() was called before calling addCard(). + * This method assumes we already checked that the card is being inserted into the visible portion + */ void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/) { if (!isReversed) { // if x is negative set it to add at end - if (x < 0) { + // if x is out-of-bounds then also set it to add at the end + if (x < 0 || x >= cards.size()) { x = cards.size(); } cards.insert(x, card); @@ -290,14 +340,8 @@ void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/) // map x (which is in origZone indexes) to this viewZone's cardList index int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId(); int insertionIndex = x - firstId; - if (insertionIndex >= 0) { - // card was put into a portion of the deck that's in the view - cards.insert(insertionIndex, card); - } else { - // card was put into a portion of the deck that's not in the view - updateCardIds(ADD_CARD); - return; - } + // qMin to prevent out-of-bounds error when bottoming a card that is already in the view + cards.insert(qMin(insertionIndex, cards.size()), card); } card->setParentItem(this); @@ -326,7 +370,7 @@ void ZoneViewZone::handleDropEvent(const QList &dragItems, player->sendGameCommand(cmd); } -void ZoneViewZone::removeCard(int position) +void ZoneViewZone::removeCard(int position, bool toNewZone) { if (isReversed) { position -= cards.first()->getId(); @@ -342,6 +386,16 @@ void ZoneViewZone::removeCard(int position) CardItem *card = cards.takeAt(position); card->deleteLater(); + + // The toNewZone check is to prevent the view from auto-closing if the view contains only a single card and that + // card gets dragged within the view. + // Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an + // unrevealed portion of the same zone. + if (cards.isEmpty() && SettingsCache::instance().getCloseEmptyCardView() && toNewZone) { + close(); + return; + } + updateCardIds(REMOVE_CARD); reorganizeCards(); } diff --git a/cockatrice/src/game/zones/view_zone.h b/cockatrice/src/game/zones/view_zone.h index 6eda0fd23..3c69317aa 100644 --- a/cockatrice/src/game/zones/view_zone.h +++ b/cockatrice/src/game/zones/view_zone.h @@ -33,7 +33,6 @@ private: QRectF bRect, optimumRect; int minRows, numberCards; - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); CardZone *origZone; bool revealZone, writeableRevealZone; CardList::SortOption groupBy, sortBy; @@ -57,6 +56,8 @@ private: GridSize positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption = CardList::NoSort); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; + public: ZoneViewZone(Player *_p, CardZone *_origZone, @@ -65,17 +66,17 @@ public: bool _writeableRevealZone = false, QGraphicsItem *parent = nullptr, bool _isReversed = false); - ~ZoneViewZone(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void reorganizeCards() override; void initializeCards(const QList &cardList = QList()); - void removeCard(int position); + bool prepareAddCard(int x); + void removeCard(int position, bool toNewZone); int getNumberCards() const { return numberCards; } - void setGeometry(const QRectF &rect); + void setGeometry(const QRectF &rect) override; QRectF getOptimumRect() const { return optimumRect; @@ -94,20 +95,21 @@ public: return isReversed; } public slots: + void close(); void setGroupBy(CardList::SortOption _groupBy); void setSortBy(CardList::SortOption _sortBy); void setPileView(int _pileView); private slots: void zoneDumpReceived(const Response &r); signals: - void beingDeleted(); + void closed(); void optimumRectChanged(); void wheelEventReceived(QGraphicsSceneWheelEvent *event); protected: - void addCardImpl(CardItem *card, int x, int y); - QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; - void wheelEvent(QGraphicsSceneWheelEvent *event); + void addCardImpl(CardItem *card, int x, int y) override; + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override; + void wheelEvent(QGraphicsSceneWheelEvent *event) override; }; #endif diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 4bc596531..531ca06df 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -133,7 +133,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, setLayout(vbox); connect(zone, &ZoneViewZone::optimumRectChanged, this, &ZoneViewWidget::resizeToZoneContents); - connect(zone, &ZoneViewZone::beingDeleted, this, &ZoneViewWidget::zoneDeleted); + connect(zone, &ZoneViewZone::closed, this, &ZoneViewWidget::zoneDeleted); zone->initializeCards(cardList); // QLabel sizes aren't taken into account until the widget is rendered. @@ -314,11 +314,12 @@ void ZoneViewWidget::handleScrollBarChange(int value) void ZoneViewWidget::closeEvent(QCloseEvent *event) { - disconnect(zone, &ZoneViewZone::beingDeleted, this, 0); + disconnect(zone, &ZoneViewZone::closed, this, 0); + // manually call zone->close in order to remove it from the origZones views + zone->close(); if (shuffleCheckBox.isChecked()) player->sendGameCommand(Command_Shuffle()); - emit closePressed(this); - deleteLater(); + zoneDeleted(); event->accept(); } diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game/zones/view_zone_widget.h index d656c4cb2..af86fe624 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game/zones/view_zone_widget.h @@ -66,8 +66,8 @@ private slots: void resizeToZoneContents(); void handleScrollBarChange(int value); void zoneDeleted(); - void moveEvent(QGraphicsSceneMoveEvent * /* event */); - void resizeEvent(QGraphicsSceneResizeEvent * /* event */); + void moveEvent(QGraphicsSceneMoveEvent * /* event */) override; + void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override; public: ZoneViewWidget(Player *_player, @@ -88,8 +88,8 @@ public: void retranslateUi(); protected: - void closeEvent(QCloseEvent *event); - void initStyleOption(QStyleOption *option) const; + void closeEvent(QCloseEvent *event) override; + void initStyleOption(QStyleOption *option) const override; }; #endif diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index bdfd263e0..0023a5991 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -177,8 +177,21 @@ int main(int argc, char *argv[]) SetUnhandledExceptionFilter(CockatriceUnhandledExceptionFilter); #endif +#ifdef Q_OS_APPLE + // /cockatrice/cockatrice.app/Contents/MacOS/cockatrice + const QByteArray configPath = "../../../qtlogging.ini"; +#elif defined(Q_OS_UNIX) + // /cockatrice/cockatrice + const QByteArray configPath = "./qtlogging.ini"; +#elif defined(Q_OS_WIN) + // /cockatrice/Debug/cockatrice.exe + const QByteArray configPath = "../qtlogging.ini"; +#else + const QByteArray configPath = ""; +#endif + // Set the QT_LOGGING_CONF environment variable - qputenv("QT_LOGGING_CONF", "./qtlogging.ini"); + qputenv("QT_LOGGING_CONF", configPath); qSetMessagePattern( "\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} " "%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{" diff --git a/cockatrice/src/server/chat_view/chat_view.cpp b/cockatrice/src/server/chat_view/chat_view.cpp index 3ae6ab885..33249829d 100644 --- a/cockatrice/src/server/chat_view/chat_view.cpp +++ b/cockatrice/src/server/chat_view/chat_view.cpp @@ -149,11 +149,12 @@ void ChatView::appendUrlTag(QTextCursor &cursor, QString url) void ChatView::appendMessage(QString message, RoomMessageTypeFlags messageType, - const QString &userName, - UserLevelFlags userLevel, - QString UserPrivLevel, + const ServerInfo_User &userInfo, bool playerBold) { + const QString userName = QString::fromStdString(userInfo.name()); + const UserLevelFlags userLevel = UserLevelFlags(userInfo.user_level()); + bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); // messageType should be Event_RoomSay::UserMessage though we don't actually check bool isUserMessage = !(userName.toLower() == "servatrice" || userName.isEmpty()); @@ -189,8 +190,10 @@ void ChatView::appendMessage(QString message, } else { const int pixelSize = QFontInfo(cursor.charFormat().font()).pixelSize(); bool isBuddy = userListProxy->isUserBuddy(userName); - cursor.insertImage( - UserLevelPixmapGenerator::generatePixmap(pixelSize, userLevel, isBuddy, UserPrivLevel).toImage()); + const QString privLevel = userInfo.has_privlevel() ? QString::fromStdString(userInfo.privlevel()) : "NONE"; + cursor.insertImage(UserLevelPixmapGenerator::generatePixmap(pixelSize, userLevel, userInfo.pawn_colors(), + isBuddy, privLevel) + .toImage()); cursor.insertText(" "); cursor.setCharFormat(senderFormat); cursor.insertText(userName); diff --git a/cockatrice/src/server/chat_view/chat_view.h b/cockatrice/src/server/chat_view/chat_view.h index 160f63c72..586ba90b3 100644 --- a/cockatrice/src/server/chat_view/chat_view.h +++ b/cockatrice/src/server/chat_view/chat_view.h @@ -91,23 +91,21 @@ public: QString optionalFontColor = QString()); void appendMessage(QString message, RoomMessageTypeFlags messageType = {}, - const QString &userName = QString(), - UserLevelFlags userLevel = UserLevelFlags(), - QString UserPrivLevel = "NONE", + const ServerInfo_User &userInfo = {}, bool playerBold = false); void clearChat(); void redactMessages(const QString &userName, int amount); protected: #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - void enterEvent(QEnterEvent *event); + void enterEvent(QEnterEvent *event) override; #else - void enterEvent(QEvent *event); + void enterEvent(QEvent *event) override; #endif - void leaveEvent(QEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mousePressEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); + void leaveEvent(QEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; signals: void openMessageDialog(const QString &userName, bool focus); void cardNameHovered(QString cardName); diff --git a/cockatrice/src/server/local_client.h b/cockatrice/src/server/local_client.h index ce33611ab..06084b4d4 100644 --- a/cockatrice/src/server/local_client.h +++ b/cockatrice/src/server/local_client.h @@ -16,9 +16,9 @@ public: const QString &_playerName, const QString &_clientId, QObject *parent = nullptr); - ~LocalClient(); + ~LocalClient() override; - void sendCommandContainer(const CommandContainer &cont); + void sendCommandContainer(const CommandContainer &cont) override; private slots: void itemFromServer(const ServerMessage &item); }; diff --git a/cockatrice/src/server/local_server.h b/cockatrice/src/server/local_server.h index d3242548e..5f7d480dc 100644 --- a/cockatrice/src/server/local_server.h +++ b/cockatrice/src/server/local_server.h @@ -10,8 +10,8 @@ class LocalServer : public Server { Q_OBJECT public: - LocalServer(QObject *parent = nullptr); - ~LocalServer(); + explicit LocalServer(QObject *parent = nullptr); + ~LocalServer() override; LocalServerInterface *newConnection(); }; @@ -23,27 +23,27 @@ private: LocalServer *localServer; protected: - ServerInfo_User getUserData(const QString &name, bool withId = false); + ServerInfo_User getUserData(const QString &name, bool withId = false) override; public: - LocalServer_DatabaseInterface(LocalServer *_localServer); - ~LocalServer_DatabaseInterface() = default; + explicit LocalServer_DatabaseInterface(LocalServer *_localServer); + ~LocalServer_DatabaseInterface() override = default; AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft, - bool passwordNeedsHash); - int getNextGameId() + bool passwordNeedsHash) override; + int getNextGameId() override { return localServer->getNextLocalGameId(); } - int getNextReplayId() + int getNextReplayId() override { return -1; } - int getActiveUserCount(QString /* connectionType */) + int getActiveUserCount(QString /* connectionType */) override { return 0; } diff --git a/cockatrice/src/server/local_server_interface.h b/cockatrice/src/server/local_server_interface.h index 46c82d68c..8e997ae30 100644 --- a/cockatrice/src/server/local_server_interface.h +++ b/cockatrice/src/server/local_server_interface.h @@ -10,17 +10,17 @@ class LocalServerInterface : public Server_ProtocolHandler Q_OBJECT public: LocalServerInterface(LocalServer *_server, Server_DatabaseInterface *_databaseInterface); - ~LocalServerInterface(); + ~LocalServerInterface() override; - QString getAddress() const + QString getAddress() const override { return QString(); } - QString getConnectionType() const + QString getConnectionType() const override { return "local"; }; - void transmitProtocolItem(const ServerMessage &item); + void transmitProtocolItem(const ServerMessage &item) override; signals: void itemToClient(const ServerMessage &item); public slots: diff --git a/cockatrice/src/server/message_log_widget.cpp b/cockatrice/src/server/message_log_widget.cpp index 1342fca0a..568253e31 100644 --- a/cockatrice/src/server/message_log_widget.cpp +++ b/cockatrice/src/server/message_log_widget.cpp @@ -606,8 +606,7 @@ void MessageLogWidget::logRollDie(Player *player, int sides, const QList & void MessageLogWidget::logSay(Player *player, QString message) { - appendMessage(std::move(message), {}, player->getName(), UserLevelFlags(player->getUserInfo()->user_level()), - QString::fromStdString(player->getUserInfo()->privlevel()), true); + appendMessage(std::move(message), {}, *player->getUserInfo(), true); } void MessageLogWidget::logSetActivePhase(int phaseNumber) @@ -783,12 +782,9 @@ void MessageLogWidget::logShuffle(Player *player, CardZone *zone, int start, int } } -void MessageLogWidget::logSpectatorSay(QString spectatorName, - UserLevelFlags spectatorUserLevel, - QString userPrivLevel, - QString message) +void MessageLogWidget::logSpectatorSay(const ServerInfo_User &spectator, QString message) { - appendMessage(std::move(message), {}, spectatorName, spectatorUserLevel, userPrivLevel, false); + appendMessage(std::move(message), {}, spectator, false); } void MessageLogWidget::logUnattachCard(Player *player, QString cardName) diff --git a/cockatrice/src/server/message_log_widget.h b/cockatrice/src/server/message_log_widget.h index 55c99e9ec..ef66f21e7 100644 --- a/cockatrice/src/server/message_log_widget.h +++ b/cockatrice/src/server/message_log_widget.h @@ -92,8 +92,7 @@ public slots: void logSetSideboardLock(Player *player, bool locked); void logSetTapped(Player *player, CardItem *card, bool tapped); void logShuffle(Player *player, CardZone *zone, int start, int end); - void - logSpectatorSay(QString spectatorName, UserLevelFlags spectatorUserLevel, QString userPrivLevel, QString message); + void logSpectatorSay(const ServerInfo_User &spectator, QString message); void logUnattachCard(Player *player, QString cardName); void logUndoDraw(Player *player, QString cardName); void setContextJudgeName(QString player); diff --git a/cockatrice/src/server/pending_command.h b/cockatrice/src/server/pending_command.h index 9914741f1..d845167ac 100644 --- a/cockatrice/src/server/pending_command.h +++ b/cockatrice/src/server/pending_command.h @@ -18,7 +18,7 @@ private: int ticks; public: - PendingCommand(const CommandContainer &_commandContainer, QVariant _extraData = QVariant()); + explicit PendingCommand(const CommandContainer &_commandContainer, QVariant _extraData = QVariant()); CommandContainer &getCommandContainer(); void setExtraData(const QVariant &_extraData); QVariant getExtraData() const; diff --git a/cockatrice/src/server/remote/remote_client.cpp b/cockatrice/src/server/remote/remote_client.cpp index 282f45e68..5812483ad 100644 --- a/cockatrice/src/server/remote/remote_client.cpp +++ b/cockatrice/src/server/remote/remote_client.cpp @@ -140,7 +140,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica hashedPassword = PasswordHasher::computeHash(password, passwordSalt); cmdForgotPasswordReset.set_hashed_new_password(hashedPassword.toStdString()); } else if (!password.isEmpty()) { - qWarning() << "using plain text password to reset password"; + qCWarning(RemoteClientLog) << "using plain text password to reset password"; cmdForgotPasswordReset.set_new_password(password.toStdString()); } PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordReset); @@ -170,7 +170,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica hashedPassword = PasswordHasher::computeHash(password, passwordSalt); cmdRegister.set_hashed_password(hashedPassword.toStdString()); } else if (!password.isEmpty()) { - qWarning() << "using plain text password to register"; + qCWarning(RemoteClientLog) << "using plain text password to register"; cmdRegister.set_password(password.toStdString()); } cmdRegister.set_email(email.toStdString()); @@ -241,7 +241,7 @@ void RemoteClient::doLogin() setStatus(StatusLoggingIn); Command_Login cmdLogin = generateCommandLogin(); if (!password.isEmpty()) { - qWarning() << "using plain text password to log in"; + qCWarning(RemoteClientLog) << "using plain text password to log in"; cmdLogin.set_password(password.toStdString()); } @@ -393,9 +393,9 @@ void RemoteClient::readData() ServerMessage newServerMessage; newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); -#ifdef QT_DEBUG - qDebug().noquote() << "IN" << getSafeDebugString(newServerMessage); -#endif + + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + inputBuffer.remove(0, messageLength); messageInProgress = false; @@ -411,9 +411,9 @@ void RemoteClient::websocketMessageReceived(const QByteArray &message) lastDataReceived = timeRunning; ServerMessage newServerMessage; newServerMessage.ParseFromArray(message.data(), message.length()); -#ifdef QT_DEBUG - qDebug().noquote() << "IN" << getSafeDebugString(newServerMessage); -#endif + + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + processProtocolItem(newServerMessage); } @@ -424,9 +424,8 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont) #else auto size = static_cast(cont.ByteSize()); #endif -#ifdef QT_DEBUG - qDebug().noquote() << "OUT" << getSafeDebugString(cont); -#endif + + qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont); QByteArray buf; if (usingWebSocket) { @@ -590,7 +589,7 @@ QString RemoteClient::getSrvClientID(const QString &_hostname) QHostAddress hostAddress = hostInfo.addresses().first(); srvClientID += hostAddress.toString(); } else { - qWarning() << "ClientID generation host lookup failure [" << hostInfo.errorString() << "]"; + qCWarning(RemoteClientLog) << "ClientID generation host lookup failure [" << hostInfo.errorString() << "]"; srvClientID += _hostname; } QString uniqueServerClientID = diff --git a/cockatrice/src/server/remote/remote_client.h b/cockatrice/src/server/remote/remote_client.h index 5d9bca63c..f34fdb71b 100644 --- a/cockatrice/src/server/remote/remote_client.h +++ b/cockatrice/src/server/remote/remote_client.h @@ -4,9 +4,12 @@ #include "../../client/game_logic/abstract_client.h" #include "pb/commands.pb.h" +#include #include #include +inline Q_LOGGING_CATEGORY(RemoteClientLog, "remote_client"); + class QTimer; class RemoteClient : public AbstractClient diff --git a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp index 314d7687b..40ea30da6 100644 --- a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp +++ b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp @@ -86,7 +86,6 @@ RemoteDeckList_TreeModel::RemoteDeckList_TreeModel(AbstractClient *_client, QObj fileIcon = fip.icon(QFileIconProvider::File); root = new DirectoryNode; - refreshTree(); } RemoteDeckList_TreeModel::~RemoteDeckList_TreeModel() @@ -270,15 +269,18 @@ void RemoteDeckList_TreeModel::refreshTree() client->sendCommand(pend); } +void RemoteDeckList_TreeModel::clearTree() +{ + beginResetModel(); + root->clearTree(); + endResetModel(); +} + void RemoteDeckList_TreeModel::deckListFinished(const Response &r) { const Response_DeckList &resp = r.GetExtension(Response_DeckList::ext); - beginResetModel(); - - root->clearTree(); - - endResetModel(); + clearTree(); ServerInfo_DeckStorage_TreeItem tempRoot; tempRoot.set_id(0); @@ -361,3 +363,8 @@ void RemoteDeckList_TreeWidget::refreshTree() { treeModel->refreshTree(); } + +void RemoteDeckList_TreeWidget::clearTree() +{ + treeModel->clearTree(); +} diff --git a/cockatrice/src/server/remote/remote_decklist_tree_widget.h b/cockatrice/src/server/remote/remote_decklist_tree_widget.h index 59a3e4950..f82378731 100644 --- a/cockatrice/src/server/remote/remote_decklist_tree_widget.h +++ b/cockatrice/src/server/remote/remote_decklist_tree_widget.h @@ -23,10 +23,10 @@ public: QString name; public: - Node(const QString &_name, DirectoryNode *_parent = nullptr) : parent(_parent), name(_name) + explicit Node(const QString &_name, DirectoryNode *_parent = nullptr) : parent(_parent), name(_name) { } - virtual ~Node(){}; + virtual ~Node() = default; DirectoryNode *getParent() const { return parent; @@ -39,8 +39,8 @@ public: class DirectoryNode : public Node, public QList { public: - DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr); - ~DirectoryNode(); + explicit DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr); + ~DirectoryNode() override; void clearTree(); QString getPath() const; DirectoryNode *getNodeByPath(QStringList path); @@ -87,15 +87,15 @@ private slots: void deckListFinished(const Response &r); public: - RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); - ~RemoteDeckList_TreeModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex &index) const; - Qt::ItemFlags flags(const QModelIndex &index) const; + explicit RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); + ~RemoteDeckList_TreeModel() override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; DirectoryNode *getRoot() const { @@ -106,6 +106,7 @@ public: DirectoryNode *addNamedFolderToTree(const QString &name, DirectoryNode *parent); void removeNode(Node *node); void refreshTree(); + void clearTree(); }; class RemoteDeckList_TreeWidget : public QTreeView @@ -115,7 +116,7 @@ private: QSortFilterProxyModel *proxyModel; public: - RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); + explicit RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; RemoteDeckList_TreeModel::Node *getCurrentItem() const; QList getCurrentSelection() const; @@ -127,6 +128,7 @@ public: void addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent); void removeNode(RemoteDeckList_TreeModel::Node *node); void refreshTree(); + void clearTree(); }; #endif diff --git a/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp b/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp index 3f58d898a..1f76a1f8b 100644 --- a/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp +++ b/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp @@ -37,13 +37,11 @@ RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, dirIcon = fip.icon(QFileIconProvider::Folder); fileIcon = fip.icon(QFileIconProvider::File); lockIcon = QPixmap("theme:icons/lock"); - - refreshTree(); } RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel() { - clearTree(); + clearAll(); } int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const @@ -242,7 +240,10 @@ ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getEnclosingReplayMatc return &node->getMatchInfo(); } -void RemoteReplayList_TreeModel::clearTree() +/** + * Deletes all items in the model + */ +void RemoteReplayList_TreeModel::clearAll() { for (int i = 0; i < replayMatches.size(); ++i) delete replayMatches[i]; @@ -258,6 +259,13 @@ void RemoteReplayList_TreeModel::refreshTree() client->sendCommand(pend); } +void RemoteReplayList_TreeModel::clearTree() +{ + beginResetModel(); + clearAll(); + endResetModel(); +} + void RemoteReplayList_TreeModel::addMatchInfo(const ServerInfo_ReplayMatch &matchInfo) { beginInsertRows(QModelIndex(), replayMatches.size(), replayMatches.size()); @@ -294,7 +302,7 @@ void RemoteReplayList_TreeModel::replayListFinished(const Response &r) const Response_ReplayList &resp = r.GetExtension(Response_ReplayList::ext); beginResetModel(); - clearTree(); + clearAll(); for (int i = 0; i < resp.match_list_size(); ++i) replayMatches.append(new MatchNode(resp.match_list(i))); diff --git a/cockatrice/src/server/remote/remote_replay_list_tree_widget.h b/cockatrice/src/server/remote/remote_replay_list_tree_widget.h index f64d87857..02082046a 100644 --- a/cockatrice/src/server/remote/remote_replay_list_tree_widget.h +++ b/cockatrice/src/server/remote/remote_replay_list_tree_widget.h @@ -24,10 +24,10 @@ private: QString name; public: - Node(const QString &_name) : name(_name) + explicit Node(const QString &_name) : name(_name) { } - virtual ~Node(){}; + virtual ~Node() = default; QString getName() const { return name; @@ -39,8 +39,8 @@ private: ServerInfo_ReplayMatch matchInfo; public: - MatchNode(const ServerInfo_ReplayMatch &_matchInfo); - ~MatchNode(); + explicit MatchNode(const ServerInfo_ReplayMatch &_matchInfo); + ~MatchNode() override; void clearTree(); const ServerInfo_ReplayMatch &getMatchInfo() { @@ -73,7 +73,7 @@ private: QList replayMatches; QIcon dirIcon, fileIcon, lockIcon; - void clearTree(); + void clearAll(); static const int numberOfColumns; signals: @@ -82,18 +82,19 @@ private slots: void replayListFinished(const Response &r); public: - RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); - ~RemoteReplayList_TreeModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const + explicit RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); + ~RemoteReplayList_TreeModel() override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return numberOfColumns; } - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex &index) const; - Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + void clearTree(); void refreshTree(); ServerInfo_Replay const *getReplay(const QModelIndex &index) const; ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const; @@ -110,11 +111,15 @@ private: QSortFilterProxyModel *proxyModel; public: - RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); + explicit RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); ServerInfo_Replay const *getReplay(const QModelIndex &ind) const; ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const; QList getSelectedReplays() const; QSet getSelectedReplayMatches() const; + void clearTree() + { + treeModel->clearTree(); + } void refreshTree() { treeModel->refreshTree(); diff --git a/cockatrice/src/server/user/user_info_box.cpp b/cockatrice/src/server/user/user_info_box.cpp index 53c1881ad..3f2381011 100644 --- a/cockatrice/src/server/user/user_info_box.cpp +++ b/cockatrice/src/server/user/user_info_box.cpp @@ -9,6 +9,7 @@ #include "../pending_command.h" #include "passwordhasher.h" #include "pb/response_get_user_info.pb.h" +#include "pb/serverinfo_user.pb.h" #include "pb/session_commands.pb.h" #include @@ -84,14 +85,27 @@ void UserInfoBox::retranslateUi() avatarButton.setText(tr("Change avatar")); } +/** + * Creates the default profile pic that is used when the user doesn't have a custom pic + */ +static QPixmap createDefaultAvatar(int height, const ServerInfo_User &user) +{ + return UserLevelPixmapGenerator::generatePixmap(height, UserLevelFlags(user.user_level()), user.pawn_colors(), + false, QString::fromStdString(user.privlevel())); +} + void UserInfoBox::updateInfo(const ServerInfo_User &user) { + currentUserInfo = &user; + const UserLevelFlags userLevel(user.user_level()); const std::string &bmp = user.avatar_bmp(); if (!avatarPixmap.loadFromData((const uchar *)bmp.data(), static_cast(bmp.size()))) { - avatarPixmap = - UserLevelPixmapGenerator::generatePixmap(64, userLevel, false, QString::fromStdString(user.privlevel())); + avatarPixmap = createDefaultAvatar(64, user); + hasAvatar = false; + } else { + hasAvatar = true; } nameLabel.setText(QString::fromStdString(user.name())); @@ -106,8 +120,8 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user) countryLabel3.setText(""); } - userLevelIcon.setPixmap( - UserLevelPixmapGenerator::generatePixmap(15, userLevel, false, QString::fromStdString(user.privlevel()))); + userLevelIcon.setPixmap(UserLevelPixmapGenerator::generatePixmap(15, userLevel, user.pawn_colors(), false, + QString::fromStdString(user.privlevel()))); QString userLevelText; if (userLevel.testFlag(ServerInfo_User::IsAdmin)) userLevelText = tr("Administrator"); @@ -363,7 +377,14 @@ void UserInfoBox::processAvatarResponse(const Response &r) void UserInfoBox::resizeEvent(QResizeEvent *event) { - QPixmap resizedPixmap = avatarPixmap.scaled(avatarPic.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap resizedPixmap; + if (hasAvatar) { + resizedPixmap = avatarPixmap.scaled(avatarPic.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } else { + int height = qMin(avatarPic.size().width(), avatarPic.size().height()); + resizedPixmap = createDefaultAvatar(height, *currentUserInfo); + } avatarPic.setPixmap(resizedPixmap); + QWidget::resizeEvent(event); } diff --git a/cockatrice/src/server/user/user_info_box.h b/cockatrice/src/server/user/user_info_box.h index 1fa5bb308..170c27bb2 100644 --- a/cockatrice/src/server/user/user_info_box.h +++ b/cockatrice/src/server/user/user_info_box.h @@ -20,6 +20,8 @@ private: countryLabel3, userLevelLabel1, userLevelLabel2, accountAgeLabel1, accountAgeLabel2; QPushButton editButton, passwordButton, avatarButton; QPixmap avatarPixmap; + bool hasAvatar; + const ServerInfo_User *currentUserInfo; static QString getAgeString(int ageSeconds); diff --git a/cockatrice/src/server/user/user_list_widget.cpp b/cockatrice/src/server/user/user_list_widget.cpp index 8077f4cab..71b5888a7 100644 --- a/cockatrice/src/server/user/user_list_widget.cpp +++ b/cockatrice/src/server/user/user_list_widget.cpp @@ -344,8 +344,8 @@ void UserListTWI::setUserInfo(const ServerInfo_User &_userInfo) userInfo = _userInfo; setData(0, Qt::UserRole, userInfo.user_level()); - setIcon(0, QIcon(UserLevelPixmapGenerator::generatePixmap(12, UserLevelFlags(userInfo.user_level()), false, - QString::fromStdString(userInfo.privlevel())))); + setIcon(0, UserLevelPixmapGenerator::generateIcon(12, UserLevelFlags(userInfo.user_level()), userInfo.pawn_colors(), + false, QString::fromStdString(userInfo.privlevel()))); setIcon(1, QIcon(CountryPixmapGenerator::generatePixmap(12, QString::fromStdString(userInfo.country())))); setData(2, Qt::UserRole, QString::fromStdString(userInfo.name())); setData(2, Qt::DisplayRole, QString::fromStdString(userInfo.name())); diff --git a/cockatrice/src/server/user/user_list_widget.h b/cockatrice/src/server/user/user_list_widget.h index af9286d3d..561d91e77 100644 --- a/cockatrice/src/server/user/user_list_widget.h +++ b/cockatrice/src/server/user/user_list_widget.h @@ -39,7 +39,7 @@ private slots: void enableTemporaryEdits(bool enabled); public: - BanDialog(const ServerInfo_User &info, QWidget *parent = nullptr); + explicit BanDialog(const ServerInfo_User &info, QWidget *parent = nullptr); QString getBanName() const; QString getBanIP() const; QString getBanId() const; @@ -90,9 +90,11 @@ public: class UserListItemDelegate : public QStyledItemDelegate { public: - UserListItemDelegate(QObject *const parent); - bool - editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); + explicit UserListItemDelegate(QObject *const parent); + bool editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) override; }; class UserListTWI : public QTreeWidgetItem @@ -101,14 +103,14 @@ private: ServerInfo_User userInfo; public: - UserListTWI(const ServerInfo_User &_userInfo); + explicit UserListTWI(const ServerInfo_User &_userInfo); const ServerInfo_User &getUserInfo() const { return userInfo; } void setUserInfo(const ServerInfo_User &_userInfo); void setOnline(bool online); - bool operator<(const QTreeWidgetItem &other) const; + bool operator<(const QTreeWidgetItem &other) const override; }; class UserListWidget : public QGroupBox diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 0b0adb18e..816e2b1da 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -104,11 +104,7 @@ void SettingsCache::translateLegacySettings() // Game filters legacySetting.beginGroup("filter_games"); - gameFilters().setShowFullGames(legacySetting.value("unavailable_games_visible").toBool()); - gameFilters().setShowGamesThatStarted(legacySetting.value("unavailable_games_visible").toBool()); - gameFilters().setShowPasswordProtectedGames(legacySetting.value("show_password_protected_games").toBool()); gameFilters().setGameNameFilter(legacySetting.value("game_name_filter").toString()); - gameFilters().setShowBuddiesOnlyGames(legacySetting.value("show_buddies_only_games").toBool()); gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool()); gameFilters().setMinPlayers(legacySetting.value("min_players").toInt()); @@ -253,6 +249,7 @@ SettingsCache::SettingsCache() knownMissingFeatures = settings->value("interface/knownmissingfeatures", "").toString(); useTearOffMenus = settings->value("interface/usetearoffmenus", true).toBool(); cardViewInitialRowsMax = settings->value("interface/cardViewInitialRowsMax", 14).toInt(); + closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool(); @@ -273,6 +270,9 @@ SettingsCache::SettingsCache() settings->value("interface/visualdeckstoragedrawunusedcoloridentities", true).toBool(); visualDeckStorageUnusedColorIdentitiesOpacity = settings->value("interface/visualdeckstorageunusedcoloridentitiesopacity", 15).toInt(); + visualDeckStoragePromptForConversion = + settings->value("interface/visualdeckstoragepromptforconversion", true).toBool(); + visualDeckStorageAlwaysConvert = settings->value("interface/visualdeckstoragealwaysconvert", false).toBool(); horizontalHand = settings->value("hand/horizontal", true).toBool(); invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt(); @@ -341,6 +341,12 @@ void SettingsCache::setCardViewInitialRowsMax(int _cardViewInitialRowsMax) settings->setValue("interface/cardViewInitialRowsMax", cardViewInitialRowsMax); } +void SettingsCache::setCloseEmptyCardView(QT_STATE_CHANGED_T value) +{ + closeEmptyCardView = value; + settings->setValue("interface/closeEmptyCardView", closeEmptyCardView); +} + void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures) { knownMissingFeatures = _knownMissingFeatures; @@ -692,6 +698,18 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visual visualDeckStorageUnusedColorIdentitiesOpacity); } +void SettingsCache::setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion) +{ + visualDeckStoragePromptForConversion = _visualDeckStoragePromptForConversion; + settings->setValue("interface/visualdeckstoragepromptforconversion", visualDeckStoragePromptForConversion); +} + +void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert) +{ + visualDeckStorageAlwaysConvert = _visualDeckStorageAlwaysConvert; + settings->setValue("interface/visualdeckstoragealwaysconvert", visualDeckStorageAlwaysConvert); +} + void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand) { horizontalHand = static_cast(_horizontalHand); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 77833bfbe..50c970a6b 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -133,6 +133,8 @@ private: int visualDeckStorageCardSize; bool visualDeckStorageDrawUnusedColorIdentities; int visualDeckStorageUnusedColorIdentitiesOpacity; + bool visualDeckStoragePromptForConversion; + bool visualDeckStorageAlwaysConvert; bool horizontalHand; bool invertVerticalCoordinate; int minPlayersForMultiColumnLayout; @@ -159,6 +161,7 @@ private: QString knownMissingFeatures; bool useTearOffMenus; int cardViewInitialRowsMax; + bool closeEmptyCardView; int pixmapCacheSize; int networkCacheSize; int redirectCacheTtl; @@ -423,6 +426,14 @@ public: { return visualDeckStorageUnusedColorIdentitiesOpacity; } + bool getVisualDeckStoragePromptForConversion() const + { + return visualDeckStoragePromptForConversion; + } + bool getVisualDeckStorageAlwaysConvert() const + { + return visualDeckStorageAlwaysConvert; + } bool getHorizontalHand() const { return horizontalHand; @@ -619,6 +630,7 @@ public: void setKnownMissingFeatures(const QString &_knownMissingFeatures); void setUseTearOffMenus(bool _useTearOffMenus); void setCardViewInitialRowsMax(int _cardViewInitialRowsMax); + void setCloseEmptyCardView(QT_STATE_CHANGED_T value); QString getClientID() { return clientID; @@ -639,6 +651,10 @@ public: { return cardViewInitialRowsMax; } + bool getCloseEmptyCardView() const + { + return closeEmptyCardView; + } ShortcutsSettings &shortcuts() const { return *shortcutsSettings; @@ -742,6 +758,8 @@ public slots: void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); void setVisualDeckStorageDrawUnusedColorIdentities(QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities); void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity); + void setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion); + void setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert); void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand); void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate); void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); diff --git a/cockatrice/src/settings/game_filters_settings.cpp b/cockatrice/src/settings/game_filters_settings.cpp index ebcbb04aa..776cd9edc 100644 --- a/cockatrice/src/settings/game_filters_settings.cpp +++ b/cockatrice/src/settings/game_filters_settings.cpp @@ -17,48 +17,48 @@ QString GameFiltersSettings::hashGameType(const QString &gameType) const return QCryptographicHash::hash(gameType.toUtf8(), QCryptographicHash::Md5).toHex(); } -void GameFiltersSettings::setShowBuddiesOnlyGames(bool show) +void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide) { - setValue(show, "show_buddies_only_games", "filter_games"); + setValue(hide, "hide_buddies_only_games", "filter_games"); } -bool GameFiltersSettings::isShowBuddiesOnlyGames() +bool GameFiltersSettings::isHideBuddiesOnlyGames() { - QVariant previous = getValue("show_buddies_only_games", "filter_games"); - return previous == QVariant() ? true : previous.toBool(); + QVariant previous = getValue("hide_buddies_only_games", "filter_games"); + return previous == QVariant() || previous.toBool(); } -void GameFiltersSettings::setShowFullGames(bool show) +void GameFiltersSettings::setHideFullGames(bool hide) { - setValue(show, "show_full_games", "filter_games"); + setValue(hide, "hide_full_games", "filter_games"); } -bool GameFiltersSettings::isShowFullGames() +bool GameFiltersSettings::isHideFullGames() { - QVariant previous = getValue("show_full_games", "filter_games"); - return previous == QVariant() ? false : previous.toBool(); + QVariant previous = getValue("hide_full_games", "filter_games"); + return !(previous == QVariant()) && previous.toBool(); } -void GameFiltersSettings::setShowGamesThatStarted(bool show) +void GameFiltersSettings::setHideGamesThatStarted(bool hide) { - setValue(show, "show_games_that_started", "filter_games"); + setValue(hide, "hide_games_that_started", "filter_games"); } -bool GameFiltersSettings::isShowGamesThatStarted() +bool GameFiltersSettings::isHideGamesThatStarted() { - QVariant previous = getValue("show_games_that_started", "filter_games"); - return previous == QVariant() ? false : previous.toBool(); + QVariant previous = getValue("hide_games_that_started", "filter_games"); + return !(previous == QVariant()) && previous.toBool(); } -void GameFiltersSettings::setShowPasswordProtectedGames(bool show) +void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) { - setValue(show, "show_password_protected_games", "filter_games"); + setValue(hide, "hide_password_protected_games", "filter_games"); } -bool GameFiltersSettings::isShowPasswordProtectedGames() +bool GameFiltersSettings::isHidePasswordProtectedGames() { - QVariant previous = getValue("show_password_protected_games", "filter_games"); - return previous == QVariant() ? true : previous.toBool(); + QVariant previous = getValue("hide_password_protected_games", "filter_games"); + return previous == QVariant() || previous.toBool(); } void GameFiltersSettings::setHideIgnoredUserGames(bool hide) @@ -69,7 +69,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide) bool GameFiltersSettings::isHideIgnoredUserGames() { QVariant previous = getValue("hide_ignored_user_games", "filter_games"); - return previous == QVariant() ? false : previous.toBool(); + return !(previous == QVariant()) && previous.toBool(); } void GameFiltersSettings::setGameNameFilter(QString gameName) diff --git a/cockatrice/src/settings/game_filters_settings.h b/cockatrice/src/settings/game_filters_settings.h index 4e5047974..7f6b75077 100644 --- a/cockatrice/src/settings/game_filters_settings.h +++ b/cockatrice/src/settings/game_filters_settings.h @@ -9,10 +9,10 @@ class GameFiltersSettings : public SettingsManager friend class SettingsCache; public: - bool isShowBuddiesOnlyGames(); - bool isShowFullGames(); - bool isShowGamesThatStarted(); - bool isShowPasswordProtectedGames(); + bool isHideBuddiesOnlyGames(); + bool isHideFullGames(); + bool isHideGamesThatStarted(); + bool isHidePasswordProtectedGames(); bool isHideIgnoredUserGames(); QString getGameNameFilter(); QString getCreatorNameFilter(); @@ -25,11 +25,11 @@ public: bool isShowOnlyIfSpectatorsCanChat(); bool isShowOnlyIfSpectatorsCanSeeHands(); - void setShowBuddiesOnlyGames(bool show); + void setHideBuddiesOnlyGames(bool hide); void setHideIgnoredUserGames(bool hide); - void setShowFullGames(bool show); - void setShowGamesThatStarted(bool show); - void setShowPasswordProtectedGames(bool show); + void setHideFullGames(bool hide); + void setHideGamesThatStarted(bool hide); + void setHidePasswordProtectedGames(bool hide); void setGameNameFilter(QString gameName); void setCreatorNameFilter(QString creatorName); void setMinPlayers(int min); diff --git a/cockatrice/src/utility/sequence_edit.h b/cockatrice/src/utility/sequence_edit.h index ddb7847d6..e2ce6845e 100644 --- a/cockatrice/src/utility/sequence_edit.h +++ b/cockatrice/src/utility/sequence_edit.h @@ -11,7 +11,7 @@ class SequenceEdit : public QWidget { Q_OBJECT public: - SequenceEdit(const QString &_shortcutName, QWidget *parent = nullptr); + explicit SequenceEdit(const QString &_shortcutName, QWidget *parent = nullptr); QString getSequence(); void setShortcutName(const QString &_shortcutName); void refreshShortcut(); @@ -23,7 +23,7 @@ private slots: void restoreDefault(); protected: - bool eventFilter(QObject *, QEvent *event); + bool eventFilter(QObject *, QEvent *event) override; private: QString shortcutName; diff --git a/common/pb/serverinfo_user.proto b/common/pb/serverinfo_user.proto index c74ed8ee6..10add611f 100644 --- a/common/pb/serverinfo_user.proto +++ b/common/pb/serverinfo_user.proto @@ -8,6 +8,11 @@ message ServerInfo_User { IsAdmin = 8; IsJudge = 16; }; + message PawnColorsOverride { + optional string left_side = 1; + optional string right_side = 2; + }; + optional string name = 1; optional uint32 user_level = 2; optional string address = 3; @@ -22,4 +27,5 @@ message ServerInfo_User { optional string email = 12; optional string clientid = 13; optional string privlevel = 14; + optional PawnColorsOverride pawn_colors = 15; } diff --git a/common/rng_abstract.h b/common/rng_abstract.h index 84c7066c0..ffd45b9d7 100644 --- a/common/rng_abstract.h +++ b/common/rng_abstract.h @@ -8,7 +8,7 @@ class RNG_Abstract : public QObject { Q_OBJECT public: - RNG_Abstract(QObject *parent = 0) : QObject(parent) + explicit RNG_Abstract(QObject *parent = nullptr) : QObject(parent) { } virtual unsigned int rand(int min, int max) = 0; diff --git a/common/rng_sfmt.h b/common/rng_sfmt.h index b4e87ae1c..7e9f53df3 100644 --- a/common/rng_sfmt.h +++ b/common/rng_sfmt.h @@ -36,8 +36,8 @@ private: unsigned int cdf(unsigned int min, unsigned int max); public: - RNG_SFMT(QObject *parent = 0); - unsigned int rand(int min, int max); + explicit RNG_SFMT(QObject *parent = nullptr); + unsigned int rand(int min, int max) override; }; #endif diff --git a/common/server.h b/common/server.h index 16cbf4086..293aeb5b2 100644 --- a/common/server.h +++ b/common/server.h @@ -58,7 +58,7 @@ private slots: public: mutable QReadWriteLock clientsLock, roomsLock; // locking order: roomsLock before clientsLock explicit Server(QObject *parent = nullptr); - virtual ~Server() = default; + ~Server() override = default; AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, diff --git a/common/server_abstractuserinterface.h b/common/server_abstractuserinterface.h index cf65480d6..763bcc567 100644 --- a/common/server_abstractuserinterface.h +++ b/common/server_abstractuserinterface.h @@ -26,14 +26,14 @@ protected: Server *server; public: - Server_AbstractUserInterface(Server *_server) : server(_server) + explicit Server_AbstractUserInterface(Server *_server) : server(_server) { } Server_AbstractUserInterface(Server *_server, const ServerInfo_User_Container &other) : ServerInfo_User_Container(other), server(_server) { } - virtual ~Server_AbstractUserInterface() + ~Server_AbstractUserInterface() override { } diff --git a/common/server_database_interface.h b/common/server_database_interface.h index 80578cc60..fdbded4ba 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -9,7 +9,7 @@ class Server_DatabaseInterface : public QObject { Q_OBJECT public: - Server_DatabaseInterface(QObject *parent = 0) : QObject(parent) + explicit Server_DatabaseInterface(QObject *parent = nullptr) : QObject(parent) { } diff --git a/common/server_game.h b/common/server_game.h index d1a8dda96..2f8c005b6 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -108,7 +108,7 @@ public: bool _spectatorsSeeEverything, int startingLifeTotal, Server_Room *parent); - ~Server_Game(); + ~Server_Game() override; Server_Room *getRoom() const { return room; diff --git a/common/server_room.h b/common/server_room.h index 76988f012..7c804a461 100644 --- a/common/server_room.h +++ b/common/server_room.h @@ -64,7 +64,7 @@ public: const QString &_joinMessage, const QStringList &_gameTypes, Server *parent); - ~Server_Room(); + ~Server_Room() override; int getId() const { return id; diff --git a/common/serverinfo_user_container.h b/common/serverinfo_user_container.h index 3a6484661..4a29bce2d 100644 --- a/common/serverinfo_user_container.h +++ b/common/serverinfo_user_container.h @@ -9,8 +9,8 @@ protected: ServerInfo_User *userInfo; public: - ServerInfo_User_Container(ServerInfo_User *_userInfo = nullptr); - ServerInfo_User_Container(const ServerInfo_User &_userInfo); + explicit ServerInfo_User_Container(ServerInfo_User *_userInfo = nullptr); + explicit ServerInfo_User_Container(const ServerInfo_User &_userInfo); ServerInfo_User_Container(const ServerInfo_User_Container &other); ServerInfo_User_Container &operator=(const ServerInfo_User_Container &other) = default; virtual ~ServerInfo_User_Container(); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 303e0a46c..a34dcc191 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -58,6 +58,9 @@ void SettingsCache::setUseTearOffMenus(bool /* _useTearOffMenus */) void SettingsCache::setCardViewInitialRowsMax(int /* _cardViewInitialRowsMax */) { } +void SettingsCache::setCloseEmptyCardView(const QT_STATE_CHANGED_T /* value */) +{ +} void SettingsCache::setKnownMissingFeatures(const QString & /* _knownMissingFeatures */) { } @@ -219,6 +222,13 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } +void SettingsCache::setVisualDeckStoragePromptForConversion( + QT_STATE_CHANGED_T /* _visualDeckStoragePromptForConversion */) +{ +} +void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T /* _visualDeckStorageAlwaysConvert */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { } diff --git a/servatrice/migrations/servatrice_0032_to_0033.sql b/servatrice/migrations/servatrice_0032_to_0033.sql new file mode 100644 index 000000000..5706c01f8 --- /dev/null +++ b/servatrice/migrations/servatrice_0032_to_0033.sql @@ -0,0 +1,5 @@ +-- Servatrice db migration from version 32 to version 33 + +ALTER TABLE cockatrice_user_analytics ADD INDEX `idx_last_login` (`last_login`); + +UPDATE cockatrice_schema_version SET version=33 WHERE version=32; diff --git a/servatrice/migrations/servatrice_0033_to_0034.sql b/servatrice/migrations/servatrice_0033_to_0034.sql new file mode 100644 index 000000000..19c33c2dc --- /dev/null +++ b/servatrice/migrations/servatrice_0033_to_0034.sql @@ -0,0 +1,8 @@ +-- Servatrice db migration from version 33 to version 34 + +ALTER TABLE cockatrice_users ADD COLUMN leftPawnColorOverride varchar(255); +ALTER TABLE cockatrice_users ADD COLUMN rightPawnColorOverride varchar(255); + +ALTER TABLE cockatrice_users ADD INDEX `idx_pawnColorOverrides` (`leftPawnColorOverride`, `rightPawnColorOverride`); + +UPDATE cockatrice_schema_version SET version=34 WHERE version=33; diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 9e9f304d3..fa644dbc0 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` ( PRIMARY KEY (`version`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; -INSERT INTO cockatrice_schema_version VALUES(32); +INSERT INTO cockatrice_schema_version VALUES(34); -- users and user data tables CREATE TABLE IF NOT EXISTS `cockatrice_users` ( @@ -41,6 +41,8 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` ( `privlevelStartDate` datetime NOT NULL, `privlevelEndDate` datetime NOT NULL, `passwordLastChangedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `leftPawnColorOverride` varchar(255), + `rightPawnColorOverride` varchar(255), PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), KEY `token` (`token`), @@ -48,7 +50,8 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` ( INDEX `idx_admin` (`admin`), INDEX `idx_active` (`active`), INDEX `idx_privlevel` (`privlevel`), - INDEX `idx_clientid` (`clientid`) + INDEX `idx_clientid` (`clientid`), + INDEX `idx_pawnColorOverrides` (`leftPawnColorOverride`, `rightPawnColorOverride`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( @@ -258,6 +261,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_user_analytics` ( `last_login` datetime NOT NULL, `notes` varchar(255) NOT NULL, PRIMARY KEY (`id`), + INDEX `idx_last_login` (`last_login`), FOREIGN KEY(`id`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 4ad61c6e4..feeb5c792 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -146,6 +146,8 @@ bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery *query) return true; const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); qCritical() << QString("[%1] Error executing query: %2").arg(poolStr).arg(query->lastError().text()); + sqlDatabase.close(); + openDatabase(); return false; } @@ -615,27 +617,36 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer if (!privlevel.isEmpty()) result.set_privlevel(privlevel.toStdString()); + const auto &pawn_left_override = query->value(5).toString(); + const auto &pawn_right_override = query->value(6).toString(); + if (!pawn_left_override.isEmpty()) { + result.mutable_pawn_colors()->set_left_side(pawn_left_override.toStdString()); + } + if (!pawn_right_override.isEmpty()) { + result.mutable_pawn_colors()->set_right_side(pawn_right_override.toStdString()); + } + if (complete) { - const QString realName = query->value(5).toString(); + const QString realName = query->value(7).toString(); if (!realName.isEmpty()) result.set_real_name(realName.toStdString()); - const QByteArray avatarBmp = query->value(6).toByteArray(); + const QByteArray avatarBmp = query->value(8).toByteArray(); if (avatarBmp.size()) result.set_avatar_bmp(avatarBmp.data(), avatarBmp.size()); - const QDateTime regDate = query->value(7).toDateTime(); + const QDateTime regDate = query->value(9).toDateTime(); if (!regDate.toString(Qt::ISODate).isEmpty()) { // the registration date is in utc qint64 accountAgeInSeconds = regDate.secsTo(QDateTime::currentDateTimeUtc()); result.set_accountage_secs(accountAgeInSeconds); } - const QString email = query->value(8).toString(); + const QString email = query->value(10).toString(); if (!email.isEmpty()) result.set_email(email.toStdString()); - const QString clientid = query->value(9).toString(); + const QString clientid = query->value(11).toString(); if (!clientid.isEmpty()) result.set_clientid(clientid.toStdString()); } @@ -652,9 +663,10 @@ ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, b if (!checkSql()) return result; - QSqlQuery *query = - prepareQuery("select id, name, admin, country, privlevel, realname, avatar_bmp, registrationDate, " - "email, clientid from {prefix}_users where name = :name and active = 1"); + QSqlQuery *query = prepareQuery("select id, name, admin, country, privlevel, leftPawnColorOverride, " + "rightPawnColorOverride, realname, avatar_bmp, registrationDate, " + "email, clientid from {prefix}_users where " + "name = :name and active = 1"); query->bindValue(":name", name); if (!execSqlQuery(query)) return result; @@ -751,7 +763,8 @@ QMap Servatrice_DatabaseInterface::getBuddyList(const if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); - QSqlQuery *query = prepareQuery("select a.id, a.name, a.admin, a.country, a.privlevel from {prefix}_users a " + QSqlQuery *query = prepareQuery("select a.id, a.name, a.admin, a.country, a.privlevel, " + "a.leftPawnColorOverride, a.rightPawnColorOverride from {prefix}_users a " "left join {prefix}_buddylist b on a.id = b.id_user2 left join {prefix}_users " "c on b.id_user1 = c.id where c.name = :name"); query->bindValue(":name", name); @@ -773,7 +786,8 @@ QMap Servatrice_DatabaseInterface::getIgnoreList(const if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); - QSqlQuery *query = prepareQuery("select a.id, a.name, a.admin, a.country, a.privlevel from {prefix}_users a " + QSqlQuery *query = prepareQuery("select a.id, a.name, a.admin, a.country, a.privlevel, " + "a.leftPawnColorOverride, a.rightPawnColorOverride from {prefix}_users a " "left join {prefix}_ignorelist b on a.id = b.id_user2 left join {prefix}_users " "c on b.id_user1 = c.id where c.name = :name"); query->bindValue(":name", name); diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 1e2ed492f..e3846cdd4 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -9,7 +9,7 @@ #include #include -#define DATABASE_SCHEMA_VERSION 32 +#define DATABASE_SCHEMA_VERSION 34 class Servatrice; diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index f409c1078..40e8fc5a9 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -62,6 +62,9 @@ void SettingsCache::setUseTearOffMenus(bool /* _useTearOffMenus */) void SettingsCache::setCardViewInitialRowsMax(int /* _cardViewInitialRowsMax */) { } +void SettingsCache::setCloseEmptyCardView(QT_STATE_CHANGED_T /* value */) +{ +} void SettingsCache::setKnownMissingFeatures(const QString & /* _knownMissingFeatures */) { } @@ -223,6 +226,13 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } +void SettingsCache::setVisualDeckStoragePromptForConversion( + QT_STATE_CHANGED_T /* _visualDeckStoragePromptForConversion */) +{ +} +void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T /* _visualDeckStorageAlwaysConvert */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { }