mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2025-12-21 06:42:41 -08:00
Compare commits
5 Commits
tooomm-rea
...
first-run-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54ee406a58 | ||
|
|
f6a1f34864 | ||
|
|
06a162c1f3 | ||
|
|
d074fd5491 | ||
|
|
319e8fe7c9 |
@@ -122,7 +122,7 @@ if [[ $MAKE_SERVER ]]; then
|
||||
flags+=("-DWITH_SERVER=1")
|
||||
fi
|
||||
if [[ $MAKE_NO_CLIENT ]]; then
|
||||
flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0")
|
||||
flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0" "-DWITH_DBCONVERTER=0")
|
||||
fi
|
||||
if [[ $MAKE_TEST ]]; then
|
||||
flags+=("-DTEST=1")
|
||||
@@ -246,7 +246,7 @@ fi
|
||||
|
||||
if [[ $RUNNER_OS == macOS ]]; then
|
||||
echo "::group::Inspect Mach-O binaries"
|
||||
for app in cockatrice oracle servatrice; do
|
||||
for app in cockatrice oracle servatrice dbconverter; do
|
||||
binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app"
|
||||
echo "Inspecting $app..."
|
||||
vtool -show-build "$binary"
|
||||
|
||||
4
.github/workflows/desktop-build.yml
vendored
4
.github/workflows/desktop-build.yml
vendored
@@ -166,7 +166,7 @@ jobs:
|
||||
|
||||
- name: Restore compiler cache (ccache)
|
||||
id: ccache_restore
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v4
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
with:
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
|
||||
- name: Save compiler cache (ccache)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{env.CACHE}}
|
||||
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
||||
|
||||
2
.github/workflows/translations-pull.yml
vendored
2
.github/workflows/translations-pull.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Create pull request
|
||||
if: github.event_name != 'pull_request'
|
||||
id: create_pr
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
add-paths: |
|
||||
cockatrice/translations/*.ts
|
||||
|
||||
2
.github/workflows/translations-push.yml
vendored
2
.github/workflows/translations-push.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
- name: Create pull request
|
||||
if: github.event_name != 'pull_request'
|
||||
id: create_pr
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
add-paths: |
|
||||
cockatrice/cockatrice_en@source.ts
|
||||
|
||||
@@ -20,6 +20,8 @@ option(WITH_SERVER "build servatrice" OFF)
|
||||
option(WITH_CLIENT "build cockatrice" ON)
|
||||
# Compile oracle
|
||||
option(WITH_ORACLE "build oracle" ON)
|
||||
# Compile dbconverter
|
||||
option(WITH_DBCONVERTER "build dbconverter" ON)
|
||||
# Compile tests
|
||||
option(TEST "build tests" OFF)
|
||||
# Use vcpkg regardless of OS
|
||||
@@ -354,6 +356,11 @@ if(WITH_ORACLE)
|
||||
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
|
||||
endif()
|
||||
|
||||
if(WITH_DBCONVERTER)
|
||||
add_subdirectory(dbconverter)
|
||||
set(CPACK_INSTALL_CMAKE_PROJECTS "Dbconverter;Dbconverter;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
|
||||
endif()
|
||||
|
||||
if(TEST)
|
||||
include(CTest)
|
||||
add_subdirectory(tests)
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN mkdir build && cd build && \
|
||||
cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 && \
|
||||
cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=0 && \
|
||||
make -j$(nproc) && \
|
||||
make install
|
||||
|
||||
|
||||
1
Doxyfile
1
Doxyfile
@@ -1068,6 +1068,7 @@ RECURSIVE = YES
|
||||
|
||||
EXCLUDE = build/ \
|
||||
cmake/ \
|
||||
dbconverter/ \
|
||||
vcpkg/ \
|
||||
webclient/
|
||||
|
||||
|
||||
53
README.md
53
README.md
@@ -44,10 +44,10 @@ Latest <kbd>beta</kbd> version:
|
||||
|
||||
# Related Repositories
|
||||
|
||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for use in Cockatrice
|
||||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
|
||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice
|
||||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice
|
||||
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
|
||||
- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
|
||||
- [Cockatrice @Flathub](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration for our Linux `flatpak` package
|
||||
|
||||
|
||||
# Community Resources [](https://discord.gg/3Z9yzmA)
|
||||
@@ -55,31 +55,15 @@ Latest <kbd>beta</kbd> version:
|
||||
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
|
||||
- [Official Website](https://cockatrice.github.io)
|
||||
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
|
||||
- [Official Code Documentation](https://cockatrice.github.io/docs)
|
||||
- [Official Discord](https://discord.gg/3Z9yzmA)
|
||||
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
|
||||
>[!IMPORTANT]
|
||||
>For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
|
||||
|
||||
|
||||
# Contribute
|
||||
<p>
|
||||
<a href="#code">Code</a> <b>|</b>
|
||||
<a href="#documentation-">Documentation</a> <b>|</b>
|
||||
<a href="#translation-">Translation</a>
|
||||
</p>
|
||||
|
||||
#### Repository Activity
|
||||

|
||||
|
||||
<details>
|
||||
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
|
||||
<br>
|
||||
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
|
||||
</a><br>
|
||||
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
|
||||
</details>
|
||||
|
||||
### Code
|
||||
|
||||
@@ -93,19 +77,18 @@ This tag is used for issues that we are looking for somebody to pick up. Often t
|
||||
For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.<br>
|
||||
We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on!
|
||||
|
||||
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
|
||||
|
||||
### Documentation [](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush)
|
||||
|
||||
There are various places where useful information for different needs are maintained:
|
||||
- [Official Code Documentation](https://cockatrice.github.io/docs/)
|
||||
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) `Community supported`
|
||||
- [Official Webpage](https://cockatrice.github.io/)
|
||||
- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file`
|
||||
|
||||
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
|
||||
|
||||
### Translation [](https://explore.transifex.com/cockatrice/cockatrice/)
|
||||
<details>
|
||||
<summary><b>Kudos to our amazing contributors ❤️</b></summary>
|
||||
<br>
|
||||
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
|
||||
</a><br>
|
||||
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
|
||||
</details>
|
||||
|
||||
### Translations [](https://explore.transifex.com/cockatrice/cockatrice/)
|
||||
|
||||
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
|
||||
|
||||
@@ -143,8 +126,8 @@ You can then
|
||||
make package
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
|
||||
>[!NOTE]
|
||||
>Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ tell disk image_name
|
||||
set position of item "Cockatrice.app" to { 139, 214 }
|
||||
set position of item "Oracle.app" to { 139, 414 }
|
||||
set position of item "Servatrice.app" to { 139, 614 }
|
||||
set position of item "dbconverter.app" to { 1400, 1400 }
|
||||
set position of item "Applications" to { 861, 414 }
|
||||
end tell
|
||||
update without registering applications
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# Find a compatible Qt version
|
||||
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, FORCE_USE_QT5
|
||||
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, WITH_DBCONVERTER, FORCE_USE_QT5
|
||||
# Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system
|
||||
# Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system
|
||||
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6
|
||||
# Output: SERVATRICE_QT_MODULES
|
||||
# Output: COCKATRICE_QT_MODULES
|
||||
# Output: ORACLE_QT_MODULES
|
||||
# Output: DBCONVERTER_QT_MODULES
|
||||
# Output: TEST_QT_MODULES
|
||||
|
||||
set(REQUIRED_QT_COMPONENTS Core)
|
||||
@@ -28,12 +29,15 @@ endif()
|
||||
if(WITH_ORACLE)
|
||||
set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
|
||||
endif()
|
||||
if(WITH_DBCONVERTER)
|
||||
set(_DBCONVERTER_NEEDED Network Widgets)
|
||||
endif()
|
||||
if(TEST)
|
||||
set(_TEST_NEEDED Widgets)
|
||||
endif()
|
||||
|
||||
set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED}
|
||||
${_TEST_NEEDED}
|
||||
${_DBCONVERTER_NEEDED} ${_TEST_NEEDED}
|
||||
)
|
||||
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
|
||||
|
||||
@@ -108,6 +112,7 @@ message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}")
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" SERVATRICE_QT_MODULES "${_SERVATRICE_NEEDED}")
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_QT_MODULES "${_COCKATRICE_NEEDED}")
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}")
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}")
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}")
|
||||
|
||||
# Core-only export (useful for headless libs)
|
||||
|
||||
@@ -213,6 +213,7 @@ ${AndIf} ${FileExists} "$INSTDIR\portable.dat"
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
Delete "$INSTDIR\cockatrice.exe"
|
||||
Delete "$INSTDIR\oracle.exe"
|
||||
Delete "$INSTDIR\dbconverter.exe"
|
||||
Delete "$INSTDIR\servatrice.exe"
|
||||
Delete "$INSTDIR\Qt*.dll"
|
||||
Delete "$INSTDIR\libmysql.dll"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN)
|
||||
|
||||
if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0)
|
||||
set(APPLICATIONS "cockatrice" "servatrice" "oracle")
|
||||
set(APPLICATIONS "cockatrice" "servatrice" "oracle" "dbconverter")
|
||||
foreach(app_name IN LISTS APPLICATIONS)
|
||||
set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app")
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@ set(cockatrice_SOURCES
|
||||
src/client/settings/card_counter_settings.cpp
|
||||
src/client/settings/shortcut_treeview.cpp
|
||||
src/client/settings/shortcuts_settings.cpp
|
||||
src/interface/deck_loader/card_node_function.cpp
|
||||
src/interface/deck_loader/deck_file_format.cpp
|
||||
src/interface/deck_loader/deck_loader.cpp
|
||||
src/interface/deck_loader/loaded_deck.cpp
|
||||
src/interface/widgets/dialogs/dlg_connect.cpp
|
||||
src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp
|
||||
src/interface/widgets/dialogs/dlg_create_game.cpp
|
||||
@@ -171,6 +168,9 @@ set(cockatrice_SOURCES
|
||||
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/overlap_widget.cpp
|
||||
src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp
|
||||
src/interface/widgets/general/tutorial/tutorial_controller.cpp
|
||||
src/interface/widgets/general/tutorial/tutorial_overlay.cpp
|
||||
src/interface/widgets/menus/deck_editor_menu.cpp
|
||||
src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp
|
||||
src/interface/widgets/printing_selector/card_amount_widget.cpp
|
||||
@@ -202,7 +202,6 @@ set(cockatrice_SOURCES
|
||||
src/interface/widgets/utility/sequence_edit.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp
|
||||
@@ -230,7 +229,6 @@ set(cockatrice_SOURCES
|
||||
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp
|
||||
|
||||
@@ -15,18 +15,15 @@ searches are case insensitive.
|
||||
<dd>[n:red n:deck n:wins](#n:red n:deck n:wins) <small>(Any deck with a name containing the words red, deck, and wins)</small></dd>
|
||||
<dd>[n:"red deck wins"](#n:%22red deck wins%22) <small>(Any deck with a name containing the exact phrase "red deck wins")</small></dd>
|
||||
|
||||
<dt><u>F</u>ile <u>N</u>ame:</dt>
|
||||
<dd>[fn:aggro](#fn:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd>
|
||||
<dd>[fn:red fn:deck fn:wins](#fn:red fn:deck fn:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd>
|
||||
<dd>[fn:"red deck wins"](#fn:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd>
|
||||
<dt><u>F</u>ile Name:</dt>
|
||||
<dd>[f:aggro](#f:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd>
|
||||
<dd>[f:red f:deck f:wins](#f:red f:deck f:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd>
|
||||
<dd>[f:"red deck wins"](#f:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd>
|
||||
|
||||
<dt>Relative <u>P</u>ath (starting from the deck folder):</dt>
|
||||
<dd>[p:aggro](#p:aggro) <small>(Any deck that has "aggro" somewhere in its relative path)</small></dd>
|
||||
<dd>[p:edh/](#p:edh/) <small>(Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)</small></dd>
|
||||
|
||||
<dt><u>F</u>ormat:</dt>
|
||||
<dd>[f:standard](#f:standard) <small>(Any deck with format set to standard)</small></dd>
|
||||
|
||||
<dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt>
|
||||
<dd><a href="#[[plains]]">[[plains]]</a> <small>(Any deck that contains at least one card with "plains" in its name)</small></dd>
|
||||
<dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <QUrlQuery>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
#include <version_string.h>
|
||||
|
||||
DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent)
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
@@ -63,7 +62,6 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck)
|
||||
|
||||
QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <QUrlQuery>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
#include <version_string.h>
|
||||
|
||||
TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
@@ -88,7 +87,6 @@ void TappedOutInterface::analyzeDeck(DeckList *deck)
|
||||
|
||||
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#ifndef INTERFACE_JSON_DECK_PARSER_H
|
||||
#define INTERFACE_JSON_DECK_PARSER_H
|
||||
|
||||
#include "../../../interface/deck_loader/card_node_function.h"
|
||||
#include "../../../interface/deck_loader/deck_loader.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
@@ -18,21 +16,21 @@ class IJsonDeckParser
|
||||
public:
|
||||
virtual ~IJsonDeckParser() = default;
|
||||
|
||||
virtual DeckList parse(const QJsonObject &obj) = 0;
|
||||
virtual DeckLoader *parse(const QJsonObject &obj) = 0;
|
||||
};
|
||||
|
||||
class ArchidektJsonParser : public IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
DeckList parse(const QJsonObject &obj) override
|
||||
DeckLoader *parse(const QJsonObject &obj) override
|
||||
{
|
||||
DeckList deckList;
|
||||
DeckLoader *loader = new DeckLoader(nullptr);
|
||||
|
||||
QString deckName = obj.value("name").toString();
|
||||
QString deckDescription = obj.value("description").toString();
|
||||
|
||||
deckList.setName(deckName);
|
||||
deckList.setComments(deckDescription);
|
||||
loader->getDeckList()->setName(deckName);
|
||||
loader->getDeckList()->setComments(deckDescription);
|
||||
|
||||
QString outputText;
|
||||
QTextStream outStream(&outputText);
|
||||
@@ -49,25 +47,25 @@ public:
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
deckList.loadFromStream_Plain(outStream, false);
|
||||
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
||||
loader->getDeckList()->loadFromStream_Plain(outStream, false);
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
|
||||
|
||||
return deckList;
|
||||
return loader;
|
||||
}
|
||||
};
|
||||
|
||||
class MoxfieldJsonParser : public IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
DeckList parse(const QJsonObject &obj) override
|
||||
DeckLoader *parse(const QJsonObject &obj) override
|
||||
{
|
||||
DeckList deckList;
|
||||
DeckLoader *loader = new DeckLoader(nullptr);
|
||||
|
||||
QString deckName = obj.value("name").toString();
|
||||
QString deckDescription = obj.value("description").toString();
|
||||
|
||||
deckList.setName(deckName);
|
||||
deckList.setComments(deckDescription);
|
||||
loader->getDeckList()->setName(deckName);
|
||||
loader->getDeckList()->setComments(deckDescription);
|
||||
|
||||
QString outputText;
|
||||
QTextStream outStream(&outputText);
|
||||
@@ -96,8 +94,8 @@ public:
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
deckList.loadFromStream_Plain(outStream, false);
|
||||
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
||||
loader->getDeckList()->loadFromStream_Plain(outStream, false);
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
|
||||
|
||||
QJsonObject commandersObj = obj.value("commanders").toObject();
|
||||
if (!commandersObj.isEmpty()) {
|
||||
@@ -108,12 +106,12 @@ public:
|
||||
QString collectorNumber = cardData.value("cn").toString();
|
||||
QString providerId = cardData.value("scryfall_id").toString();
|
||||
|
||||
deckList.setBannerCard({commanderName, providerId});
|
||||
deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
|
||||
loader->getDeckList()->setBannerCard({commanderName, providerId});
|
||||
loader->getDeckList()->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
|
||||
}
|
||||
}
|
||||
|
||||
return deckList;
|
||||
return loader;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <QtConcurrent>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <version_string.h>
|
||||
|
||||
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
|
||||
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
|
||||
@@ -40,9 +39,7 @@ void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool sav
|
||||
void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
|
||||
{
|
||||
auto *nam = new QNetworkAccessManager(this);
|
||||
auto request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
QNetworkReply *reply = nam->get(request);
|
||||
QNetworkReply *reply = nam->get(QNetworkRequest(url));
|
||||
|
||||
if (saveResults) {
|
||||
// This will write out to the file (used for spoiler.xml)
|
||||
|
||||
@@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
|
||||
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
|
||||
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
|
||||
|
||||
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery
|
||||
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery
|
||||
|
||||
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
|
||||
|
||||
@@ -22,9 +22,8 @@ CardSearch <- '[[' CardFilterString ']]'
|
||||
CardFilterString <- (!']]'.)*
|
||||
|
||||
DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String
|
||||
FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String
|
||||
FileNameQuery <- [Ff] ('ile' 'name'?)? [:] String
|
||||
PathQuery <- [Pp] 'ath'? [:] String
|
||||
FormatQuery <- [Ff] 'ormat'? [:] String
|
||||
|
||||
GenericQuery <- String
|
||||
|
||||
@@ -119,13 +118,12 @@ static void setupParserRules()
|
||||
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool {
|
||||
int count = 0;
|
||||
auto cardNodes = deck->deckLoader->getDeck().deckList.getCardNodes();
|
||||
for (auto node : cardNodes) {
|
||||
deck->deckLoader->getDeckList()->forEachCard([&](InnerDecklistNode *, const DecklistCardNode *node) {
|
||||
auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||
if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) {
|
||||
count += node->getNumber();
|
||||
}
|
||||
}
|
||||
});
|
||||
return numberMatcher(count);
|
||||
};
|
||||
};
|
||||
@@ -139,7 +137,7 @@ static void setupParserRules()
|
||||
search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto name = std::any_cast<QString>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
||||
return deck->deckLoader->getDeck().deckList.getName().contains(name, Qt::CaseInsensitive);
|
||||
return deck->deckLoader->getDeckList()->getName().contains(name, Qt::CaseInsensitive);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -158,14 +156,6 @@ static void setupParserRules()
|
||||
};
|
||||
};
|
||||
|
||||
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto format = std::any_cast<QString>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
||||
auto gameFormat = deck->deckLoader->getDeck().deckList.getGameFormat();
|
||||
return QString::compare(format, gameFormat, Qt::CaseInsensitive) == 0;
|
||||
};
|
||||
};
|
||||
|
||||
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto name = std::any_cast<QString>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
||||
|
||||
@@ -343,7 +343,10 @@ void DeckViewScene::rebuildTree()
|
||||
if (!deck)
|
||||
return;
|
||||
|
||||
for (auto *currentZone : deck->getZoneNodes()) {
|
||||
InnerDecklistNode *listRoot = deck->getRoot();
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
auto *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
|
||||
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
|
||||
if (!container) {
|
||||
container = new DeckViewCardContainer(currentZone->getName());
|
||||
|
||||
@@ -259,7 +259,7 @@ void DeckViewContainer::loadLocalDeck()
|
||||
|
||||
void DeckViewContainer::loadDeckFromFile(const QString &filePath)
|
||||
{
|
||||
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(filePath);
|
||||
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(filePath);
|
||||
DeckLoader deck(this);
|
||||
|
||||
bool success = deck.loadFromFile(filePath, fmt, true);
|
||||
@@ -269,12 +269,12 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath)
|
||||
return;
|
||||
}
|
||||
|
||||
loadDeckFromDeckList(deck.getDeck().deckList);
|
||||
loadDeckFromDeckLoader(&deck);
|
||||
}
|
||||
|
||||
void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
|
||||
void DeckViewContainer::loadDeckFromDeckLoader(DeckLoader *deck)
|
||||
{
|
||||
QString deckString = deck.writeToString_Native();
|
||||
QString deckString = deck->getDeckList()->writeToString_Native();
|
||||
|
||||
if (deckString.length() > MAX_FILE_LENGTH) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Deck is greater than maximum file size."));
|
||||
@@ -308,8 +308,8 @@ void DeckViewContainer::loadFromClipboard()
|
||||
return;
|
||||
}
|
||||
|
||||
DeckList deck = dlg.getDeckList();
|
||||
loadDeckFromDeckList(deck);
|
||||
DeckLoader *deck = dlg.getDeckList();
|
||||
loadDeckFromDeckLoader(deck);
|
||||
}
|
||||
|
||||
void DeckViewContainer::loadFromWebsite()
|
||||
@@ -320,15 +320,16 @@ void DeckViewContainer::loadFromWebsite()
|
||||
return;
|
||||
}
|
||||
|
||||
DeckList deck = dlg.getDeck();
|
||||
loadDeckFromDeckList(deck);
|
||||
DeckLoader *deck = dlg.getDeck();
|
||||
loadDeckFromDeckLoader(deck);
|
||||
}
|
||||
|
||||
void DeckViewContainer::deckSelectFinished(const Response &r)
|
||||
{
|
||||
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
|
||||
DeckList newDeck = DeckList(QString::fromStdString(resp.deck()));
|
||||
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(newDeck.getCardRefList()));
|
||||
DeckLoader newDeck(this, new DeckList(QString::fromStdString(resp.deck())));
|
||||
CardPictureLoader::cacheCardPixmaps(
|
||||
CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList()));
|
||||
setDeck(newDeck);
|
||||
switchToDeckLoadedView();
|
||||
}
|
||||
@@ -409,8 +410,8 @@ void DeckViewContainer::setSideboardLocked(bool locked)
|
||||
deckView->resetSideboardPlan();
|
||||
}
|
||||
|
||||
void DeckViewContainer::setDeck(const DeckList &deck)
|
||||
void DeckViewContainer::setDeck(DeckLoader &deck)
|
||||
{
|
||||
deckView->setDeck(deck);
|
||||
deckView->setDeck(*deck.getDeckList());
|
||||
switchToDeckLoadedView();
|
||||
}
|
||||
@@ -85,12 +85,12 @@ public:
|
||||
void setReadyStart(bool ready);
|
||||
void readyAndUpdate();
|
||||
void setSideboardLocked(bool locked);
|
||||
void setDeck(const DeckList &deck);
|
||||
void setDeck(DeckLoader &deck);
|
||||
void setVisualDeckStorageExists(bool exists);
|
||||
|
||||
public slots:
|
||||
void loadDeckFromFile(const QString &filePath);
|
||||
void loadDeckFromDeckList(const DeckList &deck);
|
||||
void loadDeckFromDeckLoader(DeckLoader *deck);
|
||||
};
|
||||
|
||||
#endif // DECK_VIEW_CONTAINER_H
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "../player_actions.h"
|
||||
#include "player_menu.h"
|
||||
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
|
||||
|
||||
UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player)
|
||||
@@ -60,19 +59,21 @@ void UtilityMenu::populatePredefinedTokensMenu()
|
||||
clear();
|
||||
setEnabled(false);
|
||||
predefinedTokens.clear();
|
||||
const DeckList &deckList = player->getDeck();
|
||||
DeckLoader *_deck = player->getDeck();
|
||||
|
||||
if (deckList.isEmpty()) {
|
||||
if (!_deck) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto tokenCardNodes = deckList.getCardNodes({DECK_ZONE_TOKENS});
|
||||
InnerDecklistNode *tokenZone =
|
||||
dynamic_cast<InnerDecklistNode *>(_deck->getDeckList()->getRoot()->findChild(DECK_ZONE_TOKENS));
|
||||
|
||||
if (!tokenCardNodes.isEmpty()) {
|
||||
setEnabled(true);
|
||||
if (tokenZone) {
|
||||
if (!tokenZone->empty())
|
||||
setEnabled(true);
|
||||
|
||||
for (int i = 0; i < tokenCardNodes.size(); ++i) {
|
||||
const QString tokenName = tokenCardNodes[i]->getName();
|
||||
for (int i = 0; i < tokenZone->size(); ++i) {
|
||||
const QString tokenName = tokenZone->at(i)->getName();
|
||||
predefinedTokens.append(tokenName);
|
||||
QAction *a = addAction(tokenName);
|
||||
if (i < 10) {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent)
|
||||
: QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)),
|
||||
playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false),
|
||||
conceded(false), zoneId(0), dialogSemaphore(false)
|
||||
conceded(false), deck(nullptr), zoneId(0), dialogSemaphore(false)
|
||||
{
|
||||
initializeZones();
|
||||
|
||||
@@ -263,9 +263,10 @@ void Player::deleteCard(CardItem *card)
|
||||
}
|
||||
}
|
||||
|
||||
void Player::setDeck(const DeckList &_deck)
|
||||
// TODO: Does a player need a DeckLoader?
|
||||
void Player::setDeck(DeckLoader &_deck)
|
||||
{
|
||||
deck = _deck;
|
||||
deck = new DeckLoader(this, _deck.getDeckList());
|
||||
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "../../game_graphics/board/abstract_graphics_item.h"
|
||||
#include "../../interface/widgets/menus/tearoff_menu.h"
|
||||
#include "../interface/deck_loader/loaded_deck.h"
|
||||
#include "../zones/logic/hand_zone_logic.h"
|
||||
#include "../zones/logic/pile_zone_logic.h"
|
||||
#include "../zones/logic/stack_zone_logic.h"
|
||||
@@ -45,6 +44,7 @@ class ArrowTarget;
|
||||
class CardDatabase;
|
||||
class CardZone;
|
||||
class CommandContainer;
|
||||
class DeckLoader;
|
||||
class GameCommand;
|
||||
class GameEvent;
|
||||
class PlayerInfo;
|
||||
@@ -66,7 +66,7 @@ class Player : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void openDeckEditor(const LoadedDeck &deck);
|
||||
void openDeckEditor(DeckLoader *deck);
|
||||
void deckChanged();
|
||||
void newCardAdded(AbstractCardItem *card);
|
||||
void rearrangeCounters();
|
||||
@@ -130,9 +130,9 @@ public:
|
||||
return playerMenu;
|
||||
}
|
||||
|
||||
void setDeck(const DeckList &_deck);
|
||||
void setDeck(DeckLoader &_deck);
|
||||
|
||||
[[nodiscard]] const DeckList &getDeck() const
|
||||
[[nodiscard]] DeckLoader *getDeck() const
|
||||
{
|
||||
return deck;
|
||||
}
|
||||
@@ -241,7 +241,7 @@ private:
|
||||
bool active;
|
||||
bool conceded;
|
||||
|
||||
DeckList deck;
|
||||
DeckLoader *deck;
|
||||
|
||||
int zoneId;
|
||||
QMap<QString, CardZoneLogic *> zones;
|
||||
|
||||
@@ -218,7 +218,7 @@ void PlayerActions::actAlwaysLookAtTopCard()
|
||||
|
||||
void PlayerActions::actOpenDeckInDeckEditor()
|
||||
{
|
||||
emit player->openDeckEditor({.deckList = player->getDeck()});
|
||||
emit player->openDeckEditor(player->getDeck());
|
||||
}
|
||||
|
||||
void PlayerActions::actViewGraveyard()
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QThread>
|
||||
#include <utility>
|
||||
#include <version_string.h>
|
||||
|
||||
static constexpr int MAX_REQUESTS_PER_SEC = 10;
|
||||
|
||||
@@ -87,7 +86,6 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture
|
||||
}
|
||||
|
||||
QNetworkRequest req(url);
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
if (!picDownload) {
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "card_node_function.h"
|
||||
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
|
||||
void CardNodeFunction::SetProviderIdToPreferred::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
|
||||
{
|
||||
Q_UNUSED(node);
|
||||
PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName());
|
||||
QString providerId = preferredPrinting.getUuid();
|
||||
QString setShortName = preferredPrinting.getSet()->getShortName();
|
||||
QString collectorNumber = preferredPrinting.getProperty("num");
|
||||
|
||||
card->setCardProviderId(providerId);
|
||||
card->setCardCollectorNumber(collectorNumber);
|
||||
card->setCardSetShortName(setShortName);
|
||||
}
|
||||
|
||||
void CardNodeFunction::ClearPrintingData::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
|
||||
{
|
||||
Q_UNUSED(node);
|
||||
card->setCardSetShortName(nullptr);
|
||||
card->setCardCollectorNumber(nullptr);
|
||||
card->setCardProviderId(nullptr);
|
||||
}
|
||||
|
||||
void CardNodeFunction::ResolveProviderId::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
|
||||
{
|
||||
Q_UNUSED(node);
|
||||
// Retrieve the providerId based on setName and collectorNumber
|
||||
QString providerId =
|
||||
CardDatabaseManager::getInstance()
|
||||
->query()
|
||||
->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber())
|
||||
.getUuid();
|
||||
|
||||
// Set the providerId on the card
|
||||
card->setCardProviderId(providerId);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef COCKATRICE_DECK_FUNCTION_H
|
||||
#define COCKATRICE_DECK_FUNCTION_H
|
||||
|
||||
class DecklistCardNode;
|
||||
class InnerDecklistNode;
|
||||
|
||||
/**
|
||||
* Functions to be used with DeckList::forEachCard
|
||||
*/
|
||||
namespace CardNodeFunction
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Sets the providerId of the card to the preferred printing.
|
||||
*/
|
||||
struct SetProviderIdToPreferred
|
||||
{
|
||||
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Clears all fields on the card related to the printing
|
||||
*/
|
||||
struct ClearPrintingData
|
||||
{
|
||||
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sets the providerId of the card based on its set name and collector number.
|
||||
*/
|
||||
struct ResolveProviderId
|
||||
{
|
||||
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const;
|
||||
};
|
||||
|
||||
} // namespace CardNodeFunction
|
||||
|
||||
#endif // COCKATRICE_DECK_FUNCTION_H
|
||||
@@ -1,9 +0,0 @@
|
||||
#include "deck_file_format.h"
|
||||
|
||||
DeckFileFormat::Format DeckFileFormat::getFormatFromName(const QString &fileName)
|
||||
{
|
||||
if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
|
||||
return Cockatrice;
|
||||
}
|
||||
return PlainText;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef COCKATRICE_DECK_FILE_FORMAT_H
|
||||
#define COCKATRICE_DECK_FILE_FORMAT_H
|
||||
#include <QString>
|
||||
|
||||
namespace DeckFileFormat
|
||||
{
|
||||
|
||||
/**
|
||||
* The deck file formats that Cockatrice supports.
|
||||
*/
|
||||
enum Format
|
||||
{
|
||||
/**
|
||||
* Plaintext deck files, a format that is intended to be widely supported among different programs.
|
||||
* This format does not support Cockatrice specific features such as banner cards or tags.
|
||||
*/
|
||||
PlainText,
|
||||
|
||||
/**
|
||||
* This is cockatrice's native deck file format, and supports deck metadata such as banner cards and tags.
|
||||
* Stored as .cod files.
|
||||
*/
|
||||
Cockatrice
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines what deck file format the given filename corresponds to.
|
||||
*
|
||||
* @param fileName The filename
|
||||
* @return The deck format
|
||||
*/
|
||||
Format getFormatFromName(const QString &fileName);
|
||||
|
||||
} // namespace DeckFileFormat
|
||||
|
||||
#endif // COCKATRICE_DECK_FILE_FORMAT_H
|
||||
@@ -25,11 +25,15 @@ const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.d
|
||||
const QStringList DeckLoader::FILE_NAME_FILTERS = {
|
||||
tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")};
|
||||
|
||||
DeckLoader::DeckLoader(QObject *parent) : QObject(parent)
|
||||
DeckLoader::DeckLoader(QObject *parent) : QObject(parent), deckList(new DeckList())
|
||||
{
|
||||
}
|
||||
|
||||
bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
|
||||
DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) : QObject(parent), deckList(_deckList)
|
||||
{
|
||||
}
|
||||
|
||||
bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
@@ -37,19 +41,18 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
DeckList deckList = DeckList();
|
||||
switch (fmt) {
|
||||
case DeckFileFormat::PlainText:
|
||||
result = deckList.loadFromFile_Plain(&file);
|
||||
case PlainTextFormat:
|
||||
result = deckList->loadFromFile_Plain(&file);
|
||||
break;
|
||||
case DeckFileFormat::Cockatrice: {
|
||||
result = deckList.loadFromFile_Native(&file);
|
||||
case CockatriceFormat: {
|
||||
result = deckList->loadFromFile_Native(&file);
|
||||
qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result;
|
||||
if (!result) {
|
||||
qCInfo(DeckLoaderLog) << "Retrying as plain format";
|
||||
file.seek(0);
|
||||
result = deckList.loadFromFile_Plain(&file);
|
||||
fmt = DeckFileFormat::PlainText;
|
||||
result = deckList->loadFromFile_Plain(&file);
|
||||
fmt = PlainTextFormat;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -59,8 +62,7 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm
|
||||
}
|
||||
|
||||
if (result) {
|
||||
loadedDeck.deckList = deckList;
|
||||
loadedDeck.lastLoadInfo = {
|
||||
lastLoadInfo = {
|
||||
.fileName = fileName,
|
||||
.fileFormat = fmt,
|
||||
};
|
||||
@@ -75,7 +77,7 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
|
||||
bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest)
|
||||
{
|
||||
auto *watcher = new QFutureWatcher<bool>(this);
|
||||
|
||||
@@ -84,7 +86,7 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form
|
||||
watcher->deleteLater();
|
||||
|
||||
if (result) {
|
||||
loadedDeck.lastLoadInfo = {
|
||||
lastLoadInfo = {
|
||||
.fileName = fileName,
|
||||
.fileFormat = fmt,
|
||||
};
|
||||
@@ -104,14 +106,14 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form
|
||||
}
|
||||
|
||||
switch (fmt) {
|
||||
case DeckFileFormat::PlainText:
|
||||
return loadedDeck.deckList.loadFromFile_Plain(&file);
|
||||
case DeckFileFormat::Cockatrice: {
|
||||
case PlainTextFormat:
|
||||
return deckList->loadFromFile_Plain(&file);
|
||||
case CockatriceFormat: {
|
||||
bool result = false;
|
||||
result = loadedDeck.deckList.loadFromFile_Native(&file);
|
||||
result = deckList->loadFromFile_Native(&file);
|
||||
if (!result) {
|
||||
file.seek(0);
|
||||
return loadedDeck.deckList.loadFromFile_Plain(&file);
|
||||
return deckList->loadFromFile_Plain(&file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -127,9 +129,9 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form
|
||||
|
||||
bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
|
||||
{
|
||||
bool result = loadedDeck.deckList.loadFromString_Native(nativeString);
|
||||
bool result = deckList->loadFromString_Native(nativeString);
|
||||
if (result) {
|
||||
loadedDeck.lastLoadInfo = {
|
||||
lastLoadInfo = {
|
||||
.remoteDeckId = remoteDeckId,
|
||||
};
|
||||
|
||||
@@ -138,7 +140,7 @@ bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt)
|
||||
bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
@@ -147,17 +149,17 @@ bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt)
|
||||
|
||||
bool result = false;
|
||||
switch (fmt) {
|
||||
case DeckFileFormat::PlainText:
|
||||
result = loadedDeck.deckList.saveToFile_Plain(&file);
|
||||
case PlainTextFormat:
|
||||
result = deckList->saveToFile_Plain(&file);
|
||||
break;
|
||||
case DeckFileFormat::Cockatrice:
|
||||
result = loadedDeck.deckList.saveToFile_Native(&file);
|
||||
case CockatriceFormat:
|
||||
result = deckList->saveToFile_Native(&file);
|
||||
qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result;
|
||||
break;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
loadedDeck.lastLoadInfo = {
|
||||
lastLoadInfo = {
|
||||
.fileName = fileName,
|
||||
.fileFormat = fmt,
|
||||
};
|
||||
@@ -170,7 +172,7 @@ bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt)
|
||||
bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt)
|
||||
{
|
||||
QFileInfo fileInfo(fileName);
|
||||
if (!fileInfo.exists()) {
|
||||
@@ -191,19 +193,19 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileForm
|
||||
|
||||
// Perform file modifications
|
||||
switch (fmt) {
|
||||
case DeckFileFormat::PlainText:
|
||||
result = loadedDeck.deckList.saveToFile_Plain(&file);
|
||||
case PlainTextFormat:
|
||||
result = deckList->saveToFile_Plain(&file);
|
||||
break;
|
||||
case DeckFileFormat::Cockatrice:
|
||||
loadedDeck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
|
||||
result = loadedDeck.deckList.saveToFile_Native(&file);
|
||||
case CockatriceFormat:
|
||||
deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
|
||||
result = deckList->saveToFile_Native(&file);
|
||||
break;
|
||||
}
|
||||
|
||||
file.close(); // Close the file to ensure changes are flushed
|
||||
|
||||
if (result) {
|
||||
loadedDeck.lastLoadInfo = {
|
||||
lastLoadInfo = {
|
||||
.fileName = fileName,
|
||||
.fileFormat = fmt,
|
||||
};
|
||||
@@ -267,20 +269,6 @@ static QString toDecklistExportString(const DecklistCardNode *card)
|
||||
return cardString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all cards in the list to their decklist export string and joins them into one string
|
||||
*/
|
||||
static QString toDecklistExportString(const QList<const DecklistCardNode *> &cardNodes)
|
||||
{
|
||||
QString result;
|
||||
|
||||
for (auto cardNode : cardNodes) {
|
||||
result += toDecklistExportString(cardNode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export deck to decklist function, called to format the deck in a way to be sent to a server
|
||||
*
|
||||
@@ -291,11 +279,29 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi
|
||||
{
|
||||
// Add the base url
|
||||
QString deckString = "https://" + getDomainForWebsite(website) + "/?";
|
||||
// Create two strings to pass to function
|
||||
QString mainBoardCards, sideBoardCards;
|
||||
|
||||
// export all cards in zone
|
||||
QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN}));
|
||||
QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE}));
|
||||
// Set up the function to call
|
||||
auto formatDeckListForExport = [&mainBoardCards, &sideBoardCards](const auto *node, const auto *card) {
|
||||
// Get the card name
|
||||
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
|
||||
if (!dbCard || dbCard->getIsToken()) {
|
||||
// If it's a token, we don't care about the card.
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a sideboard card.
|
||||
if (node->getName() == DECK_ZONE_SIDE) {
|
||||
sideBoardCards += toDecklistExportString(card);
|
||||
} else {
|
||||
// If it's a mainboard card, do the same thing, but for the mainboard card string
|
||||
mainBoardCards += toDecklistExportString(card);
|
||||
}
|
||||
};
|
||||
|
||||
// call our struct function for each card in the deck
|
||||
deckList->forEachCard(formatDeckListForExport);
|
||||
// Remove the extra return at the end of the last cards
|
||||
mainBoardCards.chop(3);
|
||||
sideBoardCards.chop(3);
|
||||
@@ -310,6 +316,112 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi
|
||||
return deckString;
|
||||
}
|
||||
|
||||
// This struct is here to support the forEachCard function call, defined in decklist.
|
||||
// It requires a function to be called for each card, and it will set the providerId to the preferred printing.
|
||||
struct SetProviderIdToPreferred
|
||||
{
|
||||
// Main operator for struct, allowing the foreachcard to work.
|
||||
SetProviderIdToPreferred()
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
|
||||
{
|
||||
Q_UNUSED(node);
|
||||
PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName());
|
||||
QString providerId = preferredPrinting.getUuid();
|
||||
QString setShortName = preferredPrinting.getSet()->getShortName();
|
||||
QString collectorNumber = preferredPrinting.getProperty("num");
|
||||
|
||||
card->setCardProviderId(providerId);
|
||||
card->setCardCollectorNumber(collectorNumber);
|
||||
card->setCardSetShortName(setShortName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This function iterates through each card in the decklist and sets the providerId
|
||||
* on each card based on its set name and collector number.
|
||||
*
|
||||
* @param deckList The decklist to modify
|
||||
*/
|
||||
void DeckLoader::setProviderIdToPreferredPrinting(const DeckList *deckList)
|
||||
{
|
||||
// Set up the struct to call.
|
||||
SetProviderIdToPreferred setProviderIdToPreferred;
|
||||
|
||||
// Call the forEachCard method for each card in the deck
|
||||
deckList->forEachCard(setProviderIdToPreferred);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the providerId on each card in the decklist based on its set name and collector number.
|
||||
*
|
||||
* @param deckList The decklist to modify
|
||||
*/
|
||||
void DeckLoader::resolveSetNameAndNumberToProviderID(const DeckList *deckList)
|
||||
{
|
||||
auto setProviderId = [](const auto node, const auto card) {
|
||||
Q_UNUSED(node);
|
||||
// Retrieve the providerId based on setName and collectorNumber
|
||||
QString providerId =
|
||||
CardDatabaseManager::getInstance()
|
||||
->query()
|
||||
->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber())
|
||||
.getUuid();
|
||||
|
||||
// Set the providerId on the card
|
||||
card->setCardProviderId(providerId);
|
||||
};
|
||||
|
||||
deckList->forEachCard(setProviderId);
|
||||
}
|
||||
|
||||
// This struct is here to support the forEachCard function call, defined in decklist.
|
||||
// It requires a function to be called for each card, and it will set the providerId.
|
||||
struct ClearSetNameNumberAndProviderId
|
||||
{
|
||||
// Main operator for struct, allowing the foreachcard to work.
|
||||
ClearSetNameNumberAndProviderId()
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
|
||||
{
|
||||
Q_UNUSED(node);
|
||||
// Set the providerId on the card
|
||||
card->setCardSetShortName(nullptr);
|
||||
card->setCardCollectorNumber(nullptr);
|
||||
card->setCardProviderId(nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the set name and numbers on each card in the decklist.
|
||||
*
|
||||
* @param deckList The decklist to modify
|
||||
*/
|
||||
void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList)
|
||||
{
|
||||
auto clearSetNameAndNumber = [](const auto node, auto card) {
|
||||
Q_UNUSED(node)
|
||||
// Set the providerId on the card
|
||||
card->setCardSetShortName(nullptr);
|
||||
card->setCardCollectorNumber(nullptr);
|
||||
card->setCardProviderId(nullptr);
|
||||
};
|
||||
|
||||
deckList->forEachCard(clearSetNameAndNumber);
|
||||
}
|
||||
|
||||
DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName)
|
||||
{
|
||||
if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
|
||||
return CockatriceFormat;
|
||||
}
|
||||
return PlainTextFormat;
|
||||
}
|
||||
|
||||
void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber)
|
||||
{
|
||||
QString buffer;
|
||||
@@ -329,7 +441,9 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
||||
}
|
||||
|
||||
// loop zones
|
||||
for (auto zoneNode : deckList->getZoneNodes()) {
|
||||
for (int i = 0; i < deckList->getRoot()->size(); i++) {
|
||||
const auto *zoneNode = dynamic_cast<InnerDecklistNode *>(deckList->getRoot()->at(i));
|
||||
|
||||
saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber);
|
||||
|
||||
// end of zone
|
||||
@@ -450,12 +564,12 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
|
||||
bool result = false;
|
||||
|
||||
// Perform file modifications based on the detected format
|
||||
switch (DeckFileFormat::getFormatFromName(fileName)) {
|
||||
case DeckFileFormat::PlainText:
|
||||
switch (getFormatFromName(fileName)) {
|
||||
case PlainTextFormat:
|
||||
// Save in Cockatrice's native format
|
||||
result = loadedDeck.deckList.saveToFile_Native(&file);
|
||||
result = deckList->saveToFile_Native(&file);
|
||||
break;
|
||||
case DeckFileFormat::Cockatrice:
|
||||
case CockatriceFormat:
|
||||
qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed.";
|
||||
result = true;
|
||||
break;
|
||||
@@ -474,16 +588,39 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
|
||||
} else {
|
||||
qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName;
|
||||
}
|
||||
loadedDeck.lastLoadInfo = {
|
||||
lastLoadInfo = {
|
||||
.fileName = newFileName,
|
||||
.fileFormat = DeckFileFormat::Cockatrice,
|
||||
.fileFormat = CockatriceFormat,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node)
|
||||
QString DeckLoader::getCardZoneFromName(const QString &cardName, QString currentZoneName)
|
||||
{
|
||||
CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName);
|
||||
|
||||
if (card && card->getIsToken()) {
|
||||
return DECK_ZONE_TOKENS;
|
||||
}
|
||||
|
||||
return currentZoneName;
|
||||
}
|
||||
|
||||
QString DeckLoader::getCompleteCardName(const QString &cardName)
|
||||
{
|
||||
if (CardDatabaseManager::getInstance()) {
|
||||
ExactCard temp = CardDatabaseManager::query()->guessCard({cardName});
|
||||
if (temp) {
|
||||
return temp.getName();
|
||||
}
|
||||
}
|
||||
|
||||
return cardName;
|
||||
}
|
||||
|
||||
void DeckLoader::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node)
|
||||
{
|
||||
const int totalColumns = 2;
|
||||
|
||||
@@ -566,11 +703,12 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
|
||||
cursor.insertText(deckList->getComments());
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
|
||||
for (auto zoneNode : deckList->getZoneNodes()) {
|
||||
for (int i = 0; i < deckList->getRoot()->size(); i++) {
|
||||
cursor.insertHtml("<br><img src=theme:hr.jpg>");
|
||||
// cursor.insertHtml("<hr>");
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
|
||||
printDeckListNode(&cursor, zoneNode);
|
||||
printDeckListNode(&cursor, dynamic_cast<InnerDecklistNode *>(deckList->getRoot()->at(i)));
|
||||
}
|
||||
|
||||
doc.print(printer);
|
||||
|
||||
@@ -7,16 +7,14 @@
|
||||
#ifndef DECK_LOADER_H
|
||||
#define DECK_LOADER_H
|
||||
|
||||
#include "loaded_deck.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QPrinter>
|
||||
#include <QTextCursor>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader");
|
||||
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader")
|
||||
|
||||
class DeckLoader : public QObject
|
||||
class DeckLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
@@ -24,6 +22,27 @@ signals:
|
||||
void loadFinished(bool success);
|
||||
|
||||
public:
|
||||
enum FileFormat
|
||||
{
|
||||
PlainTextFormat,
|
||||
CockatriceFormat
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Information about where the deck was loaded from.
|
||||
*
|
||||
* For local decks, the remoteDeckId field will always be -1.
|
||||
* For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat
|
||||
*/
|
||||
struct LoadInfo
|
||||
{
|
||||
static constexpr int NON_REMOTE_ID = -1;
|
||||
|
||||
QString fileName = "";
|
||||
FileFormat fileFormat = CockatriceFormat;
|
||||
int remoteDeckId = NON_REMOTE_ID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Supported file extensions for decklist files
|
||||
*/
|
||||
@@ -41,26 +60,44 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
LoadedDeck loadedDeck;
|
||||
DeckList *deckList;
|
||||
LoadInfo lastLoadInfo;
|
||||
|
||||
public:
|
||||
DeckLoader(QObject *parent);
|
||||
DeckLoader(QObject *parent, DeckList *_deckList);
|
||||
DeckLoader(const DeckLoader &) = delete;
|
||||
DeckLoader &operator=(const DeckLoader &) = delete;
|
||||
|
||||
[[nodiscard]] bool hasNotBeenLoaded() const
|
||||
const LoadInfo &getLastLoadInfo() const
|
||||
{
|
||||
return loadedDeck.lastLoadInfo.isEmpty();
|
||||
return lastLoadInfo;
|
||||
}
|
||||
|
||||
bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false);
|
||||
bool loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest);
|
||||
void setLastLoadInfo(const LoadInfo &info)
|
||||
{
|
||||
lastLoadInfo = info;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasNotBeenLoaded() const
|
||||
{
|
||||
return lastLoadInfo.fileName.isEmpty() && lastLoadInfo.remoteDeckId == LoadInfo::NON_REMOTE_ID;
|
||||
}
|
||||
|
||||
static void clearSetNamesAndNumbers(const DeckList *deckList);
|
||||
static FileFormat getFormatFromName(const QString &fileName);
|
||||
|
||||
bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false);
|
||||
bool loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest);
|
||||
bool loadFromRemote(const QString &nativeString, int remoteDeckId);
|
||||
bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt);
|
||||
bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt);
|
||||
bool saveToFile(const QString &fileName, FileFormat fmt);
|
||||
bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt);
|
||||
|
||||
static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website);
|
||||
|
||||
static void setProviderIdToPreferredPrinting(const DeckList *deckList);
|
||||
static void resolveSetNameAndNumberToProviderID(const DeckList *deckList);
|
||||
|
||||
static void saveToClipboard(const DeckList *deckList, bool addComments = true, bool addSetNameAndNumber = true);
|
||||
static bool saveToStream_Plain(QTextStream &out,
|
||||
const DeckList *deckList,
|
||||
@@ -76,21 +113,13 @@ public:
|
||||
|
||||
bool convertToCockatriceFormat(QString fileName);
|
||||
|
||||
LoadedDeck &getDeck()
|
||||
DeckList *getDeckList()
|
||||
{
|
||||
return loadedDeck;
|
||||
}
|
||||
const LoadedDeck &getDeck() const
|
||||
{
|
||||
return loadedDeck;
|
||||
}
|
||||
void setDeck(const LoadedDeck &deck)
|
||||
{
|
||||
loadedDeck = deck;
|
||||
return deckList;
|
||||
}
|
||||
|
||||
private:
|
||||
static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node);
|
||||
static void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node);
|
||||
static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList);
|
||||
|
||||
static void saveToStream_DeckZone(QTextStream &out,
|
||||
@@ -102,6 +131,9 @@ private:
|
||||
QList<DecklistCardNode *> cards,
|
||||
bool addComments = true,
|
||||
bool addSetNameAndNumber = true);
|
||||
|
||||
[[nodiscard]] static QString getCardZoneFromName(const QString &cardName, QString currentZoneName);
|
||||
[[nodiscard]] static QString getCompleteCardName(const QString &cardName);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "loaded_deck.h"
|
||||
|
||||
bool LoadedDeck::LoadInfo::isEmpty() const
|
||||
{
|
||||
return fileName.isEmpty() && remoteDeckId == NON_REMOTE_ID;
|
||||
}
|
||||
|
||||
bool LoadedDeck::isEmpty() const
|
||||
{
|
||||
return deckList.isEmpty() && lastLoadInfo.isEmpty();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef COCKATRICE_LOADED_DECK_H
|
||||
#define COCKATRICE_LOADED_DECK_H
|
||||
|
||||
#include "deck_file_format.h"
|
||||
#include "libcockatrice/deck_list/deck_list.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* @brief Represents a deck that was loaded from somewhere.
|
||||
* Contains the DeckList itself, as well as info about where it was loaded from.
|
||||
*/
|
||||
struct LoadedDeck
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Information about where the deck was loaded from.
|
||||
*
|
||||
* For local decks, the remoteDeckId field will always be -1.
|
||||
* For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat
|
||||
*/
|
||||
struct LoadInfo
|
||||
{
|
||||
static constexpr int NON_REMOTE_ID = -1;
|
||||
|
||||
QString fileName = "";
|
||||
DeckFileFormat::Format fileFormat = DeckFileFormat::Cockatrice;
|
||||
int remoteDeckId = NON_REMOTE_ID;
|
||||
|
||||
bool isEmpty() const;
|
||||
};
|
||||
|
||||
DeckList deckList; ///< The decklist itself
|
||||
LoadInfo lastLoadInfo = {}; ///< info about where the deck was loaded from
|
||||
|
||||
bool isEmpty() const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_LOADED_DECK_H
|
||||
@@ -39,6 +39,10 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this->window());
|
||||
enlargedPixmapWidget->hide();
|
||||
connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater);
|
||||
|
||||
hoverTimer = new QTimer(this);
|
||||
hoverTimer->setSingleShot(true);
|
||||
connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap);
|
||||
@@ -273,7 +277,7 @@ void CardInfoPictureWidget::leaveEvent(QEvent *event)
|
||||
|
||||
if (hoverToZoomEnabled) {
|
||||
hoverTimer->stop();
|
||||
destroyEnlargedPixmapWidget();
|
||||
enlargedPixmapWidget->hide();
|
||||
}
|
||||
|
||||
if (raiseOnEnter) {
|
||||
@@ -290,7 +294,7 @@ void CardInfoPictureWidget::moveEvent(QMoveEvent *event)
|
||||
QWidget::moveEvent(event);
|
||||
|
||||
hoverTimer->stop();
|
||||
destroyEnlargedPixmapWidget();
|
||||
enlargedPixmapWidget->hide();
|
||||
|
||||
if (animation->state() == QAbstractAnimation::Running) {
|
||||
return;
|
||||
@@ -306,7 +310,7 @@ void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mouseMoveEvent(event);
|
||||
|
||||
if (hoverToZoomEnabled && enlargedPixmapWidget && enlargedPixmapWidget->isVisible()) {
|
||||
if (hoverToZoomEnabled && enlargedPixmapWidget->isVisible()) {
|
||||
const QPoint cursorPos = QCursor::pos();
|
||||
const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry();
|
||||
const QSize widgetSize = enlargedPixmapWidget->size();
|
||||
@@ -340,7 +344,7 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event)
|
||||
|
||||
void CardInfoPictureWidget::hideEvent(QHideEvent *event)
|
||||
{
|
||||
destroyEnlargedPixmapWidget();
|
||||
enlargedPixmapWidget->hide();
|
||||
QWidget::hideEvent(event);
|
||||
}
|
||||
|
||||
@@ -440,19 +444,12 @@ QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu()
|
||||
* If card information is available, the enlarged pixmap is loaded, positioned near the cursor,
|
||||
* and displayed.
|
||||
*/
|
||||
void CardInfoPictureWidget::showEnlargedPixmap()
|
||||
void CardInfoPictureWidget::showEnlargedPixmap() const
|
||||
{
|
||||
if (!exactCard) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lazy creation of the enlarged widget
|
||||
if (!enlargedPixmapWidget) {
|
||||
enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(const_cast<CardInfoPictureWidget *>(this)->window());
|
||||
enlargedPixmapWidget->hide();
|
||||
connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater);
|
||||
}
|
||||
|
||||
const QSize enlargedSize(static_cast<int>(size().width() * 2), static_cast<int>(size().width() * aspectRatio * 2));
|
||||
enlargedPixmapWidget->setCardPixmap(exactCard, enlargedSize);
|
||||
|
||||
@@ -463,6 +460,7 @@ void CardInfoPictureWidget::showEnlargedPixmap()
|
||||
int newX = cursorPos.x() + enlargedPixmapOffset;
|
||||
int newY = cursorPos.y() + enlargedPixmapOffset;
|
||||
|
||||
// Adjust if out of bounds
|
||||
if (newX + widgetSize.width() > screenGeometry.right()) {
|
||||
newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset;
|
||||
}
|
||||
@@ -474,11 +472,3 @@ void CardInfoPictureWidget::showEnlargedPixmap()
|
||||
|
||||
enlargedPixmapWidget->show();
|
||||
}
|
||||
|
||||
void CardInfoPictureWidget::destroyEnlargedPixmapWidget()
|
||||
{
|
||||
if (enlargedPixmapWidget) {
|
||||
enlargedPixmapWidget->deleteLater();
|
||||
enlargedPixmapWidget = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,7 @@ protected:
|
||||
{
|
||||
return resizedPixmap;
|
||||
}
|
||||
void showEnlargedPixmap();
|
||||
void destroyEnlargedPixmapWidget();
|
||||
void showEnlargedPixmap() const;
|
||||
|
||||
private:
|
||||
ExactCard exactCard;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* @param outlineColor The color of the outline around the text.
|
||||
* @param fontSize The font size of the overlay text.
|
||||
* @param alignment The alignment of the text within the overlay.
|
||||
* @param _deckLoader The Deck Loader holding the Deck associated with this preview.
|
||||
*
|
||||
* Sets the widget's size policy and default border style.
|
||||
*/
|
||||
|
||||
@@ -21,26 +21,32 @@ void DeckListStatisticsAnalyzer::update()
|
||||
manaCurveMap.clear();
|
||||
manaDevotionMap.clear();
|
||||
|
||||
QList<ExactCard> cards = model->getCards();
|
||||
auto nodes = model->getDeckList()->getCardNodes();
|
||||
|
||||
for (const ExactCard &card : cards) {
|
||||
// ---- Mana curve ----
|
||||
if (config.computeManaCurve) {
|
||||
manaCurveMap[card.getInfo().getCmc().toInt()]++;
|
||||
}
|
||||
for (auto *node : nodes) {
|
||||
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||
if (!info)
|
||||
continue;
|
||||
|
||||
// ---- Mana base ----
|
||||
if (config.computeManaBase) {
|
||||
auto mana = determineManaProduction(card.getInfo().getText());
|
||||
for (auto it = mana.begin(); it != mana.end(); ++it)
|
||||
manaBaseMap[it.key()] += it.value();
|
||||
}
|
||||
for (int i = 0; i < node->getNumber(); ++i) {
|
||||
// ---- Mana curve ----
|
||||
if (config.computeManaCurve) {
|
||||
manaCurveMap[info->getCmc().toInt()]++;
|
||||
}
|
||||
|
||||
// ---- Devotion ----
|
||||
if (config.computeDevotion) {
|
||||
auto devo = countManaSymbols(card.getInfo().getManaCost());
|
||||
for (auto &d : devo)
|
||||
manaDevotionMap[d.first] += d.second;
|
||||
// ---- Mana base ----
|
||||
if (config.computeManaBase) {
|
||||
auto mana = determineManaProduction(info->getText());
|
||||
for (auto it = mana.begin(); it != mana.end(); ++it)
|
||||
manaBaseMap[it.key()] += it.value();
|
||||
}
|
||||
|
||||
// ---- Devotion ----
|
||||
if (config.computeDevotion) {
|
||||
auto devo = countManaSymbols(info->getManaCost());
|
||||
for (auto &d : devo)
|
||||
manaDevotionMap[d.first] += d.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
deckModel->setObjectName("deckModel");
|
||||
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
||||
|
||||
deckLoader = new DeckLoader(this);
|
||||
deckLoader = new DeckLoader(this, deckModel->getDeckList());
|
||||
|
||||
proxy = new DeckListStyleProxy(this);
|
||||
proxy->setSourceModel(deckModel);
|
||||
@@ -124,12 +124,6 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
quickSettingsWidget->addSettingsWidget(showBannerCardCheckBox);
|
||||
quickSettingsWidget->addSettingsWidget(showTagsWidgetCheckBox);
|
||||
|
||||
formatLabel = new QLabel(this);
|
||||
|
||||
formatComboBox = new QComboBox(this);
|
||||
formatComboBox->addItem(tr("Loading Database..."));
|
||||
formatComboBox->setEnabled(false); // Disable until loaded
|
||||
|
||||
commentsLabel = new QLabel();
|
||||
commentsLabel->setObjectName("commentsLabel");
|
||||
commentsEdit = new QTextEdit;
|
||||
@@ -214,16 +208,13 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
upperLayout->addWidget(commentsLabel, 1, 0);
|
||||
upperLayout->addWidget(commentsEdit, 1, 1);
|
||||
|
||||
upperLayout->addWidget(formatLabel, 2, 0);
|
||||
upperLayout->addWidget(formatComboBox, 2, 1);
|
||||
upperLayout->addWidget(bannerCardLabel, 2, 0);
|
||||
upperLayout->addWidget(bannerCardComboBox, 2, 1);
|
||||
|
||||
upperLayout->addWidget(bannerCardLabel, 3, 0);
|
||||
upperLayout->addWidget(bannerCardComboBox, 3, 1);
|
||||
upperLayout->addWidget(deckTagsDisplayWidget, 3, 1);
|
||||
|
||||
upperLayout->addWidget(deckTagsDisplayWidget, 4, 1);
|
||||
|
||||
upperLayout->addWidget(activeGroupCriteriaLabel, 5, 0);
|
||||
upperLayout->addWidget(activeGroupCriteriaComboBox, 5, 1);
|
||||
upperLayout->addWidget(activeGroupCriteriaLabel, 4, 0);
|
||||
upperLayout->addWidget(activeGroupCriteriaComboBox, 4, 1);
|
||||
|
||||
hashLabel1 = new QLabel();
|
||||
hashLabel1->setObjectName("hashLabel1");
|
||||
@@ -272,46 +263,6 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||
|
||||
refreshShortcuts();
|
||||
retranslateUi();
|
||||
|
||||
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
|
||||
&DeckEditorDeckDockWidget::initializeFormats);
|
||||
|
||||
if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) {
|
||||
initializeFormats();
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::initializeFormats()
|
||||
{
|
||||
QMap<QString, int> allFormats = CardDatabaseManager::query()->getAllFormatsWithCount();
|
||||
|
||||
formatComboBox->clear(); // Remove "Loading Database..."
|
||||
formatComboBox->setEnabled(true);
|
||||
|
||||
// Populate with formats
|
||||
formatComboBox->addItem("", "");
|
||||
for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) {
|
||||
QString displayText = QString("%1").arg(it.key());
|
||||
formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData
|
||||
}
|
||||
|
||||
if (!deckModel->getDeckList()->getGameFormat().isEmpty()) {
|
||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
||||
} else {
|
||||
// Ensure no selection is visible initially
|
||||
formatComboBox->setCurrentIndex(-1);
|
||||
}
|
||||
|
||||
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
if (index >= 0) {
|
||||
QString formatKey = formatComboBox->itemData(index).toString();
|
||||
deckModel->setActiveFormat(formatKey);
|
||||
} else {
|
||||
deckModel->setActiveFormat(QString()); // clear format if deselected
|
||||
}
|
||||
emit deckModified();
|
||||
});
|
||||
}
|
||||
|
||||
ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||
@@ -347,7 +298,7 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const
|
||||
void DeckEditorDeckDockWidget::updateName(const QString &name)
|
||||
{
|
||||
emit requestDeckHistorySave(
|
||||
QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName()));
|
||||
QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName()));
|
||||
deckModel->getDeckList()->setName(name);
|
||||
deckEditor->setModified(name.isEmpty());
|
||||
emit nameChanged();
|
||||
@@ -357,7 +308,7 @@ void DeckEditorDeckDockWidget::updateName(const QString &name)
|
||||
void DeckEditorDeckDockWidget::updateComments()
|
||||
{
|
||||
emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)")
|
||||
.arg(deckLoader->getDeck().deckList.getComments().size())
|
||||
.arg(deckLoader->getDeckList()->getComments().size())
|
||||
.arg(commentsEdit->toPlainText().size()));
|
||||
|
||||
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
|
||||
@@ -386,7 +337,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
|
||||
// Collect unique (name, providerId) pairs
|
||||
QSet<QPair<QString, QString>> bannerCardSet;
|
||||
QList<const DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes();
|
||||
QList<DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes();
|
||||
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
|
||||
@@ -474,12 +425,13 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
||||
|
||||
/**
|
||||
* Sets the currently active deck for this tab
|
||||
* @param _deck The deck.
|
||||
* @param _deck The deck. Takes ownership of the object
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck)
|
||||
void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
|
||||
{
|
||||
deckLoader->setDeck(_deck);
|
||||
deckModel->setDeckList(&deckLoader->getDeck().deckList);
|
||||
deckLoader = _deck;
|
||||
deckLoader->setParent(this);
|
||||
deckModel->setDeckList(deckLoader->getDeckList());
|
||||
connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree);
|
||||
|
||||
emit requestDeckHistoryClear();
|
||||
@@ -514,12 +466,6 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
|
||||
{
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
||||
deckView->expandAll();
|
||||
deckView->expandAll();
|
||||
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
DeckLoader *DeckEditorDeckDockWidget::getDeckLoader()
|
||||
@@ -778,8 +724,6 @@ void DeckEditorDeckDockWidget::retranslateUi()
|
||||
showTagsWidgetCheckBox->setText(tr("Show tags selection menu"));
|
||||
commentsLabel->setText(tr("&Comments:"));
|
||||
activeGroupCriteriaLabel->setText(tr("Group by:"));
|
||||
formatLabel->setText(tr("Format:"));
|
||||
|
||||
hashLabel1->setText(tr("Hash:"));
|
||||
|
||||
aIncrement->setText(tr("&Increment number"));
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
public slots:
|
||||
void cleanDeck();
|
||||
void updateBannerCardComboBox();
|
||||
void setDeck(const LoadedDeck &_deck);
|
||||
void setDeck(DeckLoader *_deck);
|
||||
void syncDisplayWidgetsToModel();
|
||||
void sortDeckModelToDeckView();
|
||||
DeckLoader *getDeckLoader();
|
||||
@@ -69,7 +69,6 @@ public slots:
|
||||
void actSwapCard();
|
||||
void actRemoveCard();
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void initializeFormats();
|
||||
void expandAll();
|
||||
|
||||
signals:
|
||||
@@ -101,8 +100,6 @@ private:
|
||||
LineEditUnfocusable *hashLabel;
|
||||
QLabel *activeGroupCriteriaLabel;
|
||||
QComboBox *activeGroupCriteriaComboBox;
|
||||
QLabel *formatLabel;
|
||||
QComboBox *formatComboBox;
|
||||
|
||||
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (role == Qt::BackgroundRole) {
|
||||
if (isCard) {
|
||||
const bool legal = QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool();
|
||||
const bool legal =
|
||||
true; // TODO: Not implemented yet. QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool();
|
||||
int base = 255 - (index.row() % 2) * 30;
|
||||
return legal ? QBrush(QColor(base, base, base)) : QBrush(QColor(255, base / 3, base / 3));
|
||||
} else {
|
||||
|
||||
@@ -59,7 +59,7 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent)
|
||||
|
||||
void DlgEditPassword::actOk()
|
||||
{
|
||||
//! \todo this stuff should be using qvalidators
|
||||
// TODO this stuff should be using qvalidators
|
||||
if (newPasswordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
|
||||
return;
|
||||
|
||||
@@ -121,7 +121,7 @@ void DlgForgotPasswordReset::actOk()
|
||||
return;
|
||||
}
|
||||
|
||||
//! \todo this stuff should be using qvalidators
|
||||
// TODO this stuff should be using qvalidators
|
||||
if (newpasswordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "dlg_load_deck_from_clipboard.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../deck_loader/card_node_function.h"
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "dlg_settings.h"
|
||||
|
||||
@@ -66,26 +65,26 @@ void AbstractDlgDeckTextEdit::setText(const QString &text)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the current contents of the contentsEdit into the deckList
|
||||
* Tries to load the current contents of the contentsEdit into the DeckLoader
|
||||
*
|
||||
* @param deckList The deckList to load the deck into
|
||||
* @param deckLoader The DeckLoader to load the deck into
|
||||
* @return Whether the loading was successful
|
||||
*/
|
||||
bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckList &deckList) const
|
||||
bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const
|
||||
{
|
||||
QString buffer = contentsEdit->toPlainText();
|
||||
|
||||
if (buffer.contains("<cockatrice_deck version=\"1\">")) {
|
||||
return deckList.loadFromString_Native(buffer);
|
||||
return deckLoader->getDeckList()->loadFromString_Native(buffer);
|
||||
}
|
||||
|
||||
QTextStream stream(&buffer);
|
||||
|
||||
if (deckList.loadFromStream_Plain(stream, true)) {
|
||||
if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) {
|
||||
if (loadSetNameAndNumberCheckBox->isChecked()) {
|
||||
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(deckLoader->getDeckList());
|
||||
} else {
|
||||
deckList.forEachCard(CardNodeFunction::ClearPrintingData());
|
||||
DeckLoader::clearSetNamesAndNumbers(deckLoader->getDeckList());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -108,7 +107,7 @@ void AbstractDlgDeckTextEdit::keyPressEvent(QKeyEvent *event)
|
||||
*
|
||||
* @param parent The parent widget
|
||||
*/
|
||||
DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : AbstractDlgDeckTextEdit(parent)
|
||||
DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : AbstractDlgDeckTextEdit(parent), deckList(nullptr)
|
||||
{
|
||||
setWindowTitle(tr("Load deck from clipboard"));
|
||||
|
||||
@@ -122,6 +121,8 @@ void DlgLoadDeckFromClipboard::actRefresh()
|
||||
|
||||
void DlgLoadDeckFromClipboard::actOK()
|
||||
{
|
||||
deckList = new DeckLoader(this);
|
||||
|
||||
if (loadIntoDeck(deckList)) {
|
||||
accept();
|
||||
} else {
|
||||
@@ -132,15 +133,18 @@ void DlgLoadDeckFromClipboard::actOK()
|
||||
/**
|
||||
* Creates the dialog window for the "Edit deck in clipboard" action
|
||||
*
|
||||
* @param _deckList The existing deck in the deck editor.
|
||||
* @param _deckLoader The existing deck in the deck editor. Copies the instance
|
||||
* @param _annotated Whether to add annotations to the text that is loaded from the deck
|
||||
* @param parent The parent widget
|
||||
*/
|
||||
DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckList &_deckList, bool _annotated, QWidget *parent)
|
||||
: AbstractDlgDeckTextEdit(parent), deckList(_deckList), annotated(_annotated)
|
||||
DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent)
|
||||
: AbstractDlgDeckTextEdit(parent), annotated(_annotated)
|
||||
{
|
||||
setWindowTitle(tr("Edit deck in clipboard"));
|
||||
|
||||
deckLoader = new DeckLoader(this, _deckLoader->getDeckList());
|
||||
deckLoader->setParent(this);
|
||||
|
||||
DlgEditDeckInClipboard::actRefresh();
|
||||
}
|
||||
|
||||
@@ -160,12 +164,12 @@ static QString deckListToString(const DeckList *deckList, bool addComments)
|
||||
|
||||
void DlgEditDeckInClipboard::actRefresh()
|
||||
{
|
||||
setText(deckListToString(&deckList, annotated));
|
||||
setText(deckListToString(deckLoader->getDeckList(), annotated));
|
||||
}
|
||||
|
||||
void DlgEditDeckInClipboard::actOK()
|
||||
{
|
||||
if (loadIntoDeck(deckList)) {
|
||||
if (loadIntoDeck(deckLoader)) {
|
||||
accept();
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid deck list."));
|
||||
|
||||
@@ -8,11 +8,10 @@
|
||||
#ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H
|
||||
#define DLG_LOAD_DECK_FROM_CLIPBOARD_H
|
||||
|
||||
#include "../../deck_loader/loaded_deck.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
|
||||
class DeckLoader;
|
||||
class QPlainTextEdit;
|
||||
class QPushButton;
|
||||
|
||||
@@ -36,13 +35,15 @@ public:
|
||||
/**
|
||||
* Gets the loaded deck. Only call this method after this dialog window has been successfully exec'd.
|
||||
*
|
||||
* @return The loaded decklist
|
||||
* The returned DeckLoader is parented to this object; make sure to take ownership of the DeckLoader if you intend
|
||||
* to use it, since otherwise it will get destroyed once this dlg is destroyed
|
||||
* @return The DeckLoader
|
||||
*/
|
||||
[[nodiscard]] virtual const DeckList &getDeckList() = 0;
|
||||
[[nodiscard]] virtual DeckLoader *getDeckList() const = 0;
|
||||
|
||||
protected:
|
||||
void setText(const QString &text);
|
||||
bool loadIntoDeck(DeckList &deckList) const;
|
||||
bool loadIntoDeck(DeckLoader *deckLoader) const;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
protected slots:
|
||||
@@ -61,12 +62,12 @@ protected slots:
|
||||
void actRefresh() override;
|
||||
|
||||
private:
|
||||
DeckList deckList;
|
||||
DeckLoader *deckList;
|
||||
|
||||
public:
|
||||
explicit DlgLoadDeckFromClipboard(QWidget *parent = nullptr);
|
||||
|
||||
[[nodiscard]] const DeckList &getDeckList() override
|
||||
[[nodiscard]] DeckLoader *getDeckList() const override
|
||||
{
|
||||
return deckList;
|
||||
}
|
||||
@@ -83,15 +84,15 @@ protected slots:
|
||||
void actRefresh() override;
|
||||
|
||||
private:
|
||||
DeckList deckList;
|
||||
DeckLoader *deckLoader;
|
||||
bool annotated;
|
||||
|
||||
public:
|
||||
explicit DlgEditDeckInClipboard(const DeckList &_deckList, bool _annotated, QWidget *parent = nullptr);
|
||||
explicit DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent = nullptr);
|
||||
|
||||
[[nodiscard]] const DeckList &getDeckList() override
|
||||
[[nodiscard]] DeckLoader *getDeckList() const override
|
||||
{
|
||||
return deckList;
|
||||
return deckLoader;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <version_string.h>
|
||||
|
||||
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
@@ -68,7 +67,6 @@ void DlgLoadDeckFromWebsite::accept()
|
||||
}
|
||||
|
||||
QNetworkRequest request(QUrl(info.fullUrl));
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
QNetworkReply *reply = nam->get(request);
|
||||
|
||||
QEventLoop loop;
|
||||
@@ -97,11 +95,11 @@ void DlgLoadDeckFromWebsite::accept()
|
||||
}
|
||||
|
||||
// Parse the plain text deck here
|
||||
DeckList deckList;
|
||||
DeckLoader *loader = new DeckLoader(this);
|
||||
QTextStream stream(&deckText);
|
||||
deckList.loadFromStream_Plain(stream, false);
|
||||
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
||||
deck = deckList;
|
||||
loader->getDeckList()->loadFromStream_Plain(stream, false);
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
|
||||
deck = loader;
|
||||
|
||||
QDialog::accept();
|
||||
return;
|
||||
|
||||
@@ -26,9 +26,9 @@ public:
|
||||
explicit DlgLoadDeckFromWebsite(QWidget *parent);
|
||||
void retranslateUi();
|
||||
bool testValidUrl();
|
||||
DeckList deck;
|
||||
DeckLoader *deck;
|
||||
|
||||
const DeckList &getDeck() const
|
||||
DeckLoader *getDeck()
|
||||
{
|
||||
return deck;
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
|
||||
|
||||
void DlgRegister::actOk()
|
||||
{
|
||||
//! \todo this stuff should be using qvalidators
|
||||
// TODO this stuff should be using qvalidators
|
||||
if (passwordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short."));
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "dlg_select_set_for_cards.h"
|
||||
|
||||
#include "../../deck_loader/card_node_function.h"
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "../interface/widgets/cards/card_info_picture_widget.h"
|
||||
#include "../interface/widgets/general/layout_containers/flow_widget.h"
|
||||
@@ -178,7 +177,7 @@ void DlgSelectSetForCards::actOK()
|
||||
void DlgSelectSetForCards::actClear()
|
||||
{
|
||||
emit deckAboutToBeModified(tr("Cleared all printing information."));
|
||||
model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData());
|
||||
DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
|
||||
emit deckModified();
|
||||
accept();
|
||||
}
|
||||
@@ -186,8 +185,8 @@ void DlgSelectSetForCards::actClear()
|
||||
void DlgSelectSetForCards::actSetAllToPreferred()
|
||||
{
|
||||
emit deckAboutToBeModified(tr("Set all printings to preferred."));
|
||||
model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData());
|
||||
model->getDeckList()->forEachCard(CardNodeFunction::SetProviderIdToPreferred());
|
||||
DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
|
||||
DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList());
|
||||
emit deckModified();
|
||||
accept();
|
||||
}
|
||||
@@ -224,10 +223,14 @@ QMap<QString, int> DlgSelectSetForCards::getSetsForCards()
|
||||
if (!model)
|
||||
return setCounts;
|
||||
|
||||
QList<QString> cardNames = model->getCardNames();
|
||||
DeckList *decklist = model->getDeckList();
|
||||
if (!decklist)
|
||||
return setCounts;
|
||||
|
||||
for (auto cardName : cardNames) {
|
||||
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
|
||||
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
|
||||
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
|
||||
if (!infoPtr)
|
||||
continue;
|
||||
|
||||
@@ -263,15 +266,19 @@ void DlgSelectSetForCards::updateCardLists()
|
||||
}
|
||||
}
|
||||
|
||||
QList<QString> cardNames = model->getCardNames();
|
||||
DeckList *decklist = model->getDeckList();
|
||||
if (!decklist)
|
||||
return;
|
||||
|
||||
for (auto cardName : cardNames) {
|
||||
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
|
||||
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
bool found = false;
|
||||
QString foundSetName;
|
||||
|
||||
// Check across all sets if the card is present
|
||||
for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) {
|
||||
if (it.value().contains(cardName)) {
|
||||
if (it.value().contains(currentCard->getName())) {
|
||||
found = true;
|
||||
foundSetName = it.key(); // Store the set name where it was found
|
||||
break; // Stop at the first match
|
||||
@@ -280,16 +287,16 @@ void DlgSelectSetForCards::updateCardLists()
|
||||
|
||||
if (!found) {
|
||||
// The card was not in any selected set
|
||||
ExactCard card = CardDatabaseManager::query()->getCard({cardName});
|
||||
ExactCard card = CardDatabaseManager::query()->getCard({currentCard->getName()});
|
||||
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget);
|
||||
picture_widget->setCard(card);
|
||||
uneditedCardsFlowWidget->addWidget(picture_widget);
|
||||
} else {
|
||||
ExactCard card =
|
||||
CardDatabaseManager::query()->getCard({cardName, CardDatabaseManager::getInstance()
|
||||
->query()
|
||||
->getSpecificPrinting(cardName, foundSetName, "")
|
||||
.getUuid()});
|
||||
ExactCard card = CardDatabaseManager::query()->getCard(
|
||||
{currentCard->getName(), CardDatabaseManager::getInstance()
|
||||
->query()
|
||||
->getSpecificPrinting(currentCard->getName(), foundSetName, "")
|
||||
.getUuid()});
|
||||
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget);
|
||||
picture_widget->setCard(card);
|
||||
modifiedCardsFlowWidget->addWidget(picture_widget);
|
||||
@@ -348,16 +355,20 @@ QMap<QString, QStringList> DlgSelectSetForCards::getCardsForSets()
|
||||
if (!model)
|
||||
return setCards;
|
||||
|
||||
QList<QString> cardNames = model->getCardNames();
|
||||
DeckList *decklist = model->getDeckList();
|
||||
if (!decklist)
|
||||
return setCards;
|
||||
|
||||
for (auto cardName : cardNames) {
|
||||
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
|
||||
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
|
||||
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
|
||||
if (!infoPtr)
|
||||
continue;
|
||||
|
||||
SetToPrintingsMap setMap = infoPtr->getSets();
|
||||
for (auto it = setMap.begin(); it != setMap.end(); ++it) {
|
||||
setCards[it.key()].append(cardName);
|
||||
setCards[it.key()].append(currentCard->getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1913,7 +1913,7 @@ void DlgSettings::closeEvent(QCloseEvent *event)
|
||||
}
|
||||
|
||||
if (!QDir(SettingsCache::instance().getDeckPath()).exists() || SettingsCache::instance().getDeckPath().isEmpty()) {
|
||||
//! \todo Prompt to create it
|
||||
// TODO: Prompt to create it
|
||||
if (QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"),
|
||||
@@ -1924,7 +1924,7 @@ void DlgSettings::closeEvent(QCloseEvent *event)
|
||||
}
|
||||
|
||||
if (!QDir(SettingsCache::instance().getPicsPath()).exists() || SettingsCache::instance().getPicsPath().isEmpty()) {
|
||||
//! \todo Prompt to create it
|
||||
// TODO: Prompt to create it
|
||||
if (QMessageBox::critical(this, tr("Error"),
|
||||
tr("The path to your card pictures directory is invalid. Would you like to go back "
|
||||
"and set the correct path?"),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../../window_main.h"
|
||||
#include "background_sources.h"
|
||||
#include "home_styled_button.h"
|
||||
#include "tutorial/tutorial_controller.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QPainter>
|
||||
@@ -20,6 +21,10 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||
layout = new QGridLayout(this);
|
||||
|
||||
backgroundSourceCard = new CardInfoPictureArtCropWidget(this);
|
||||
backgroundSourceDeck = new DeckLoader(this);
|
||||
|
||||
backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod",
|
||||
DeckLoader::CockatriceFormat, false);
|
||||
|
||||
gradientColors = extractDominantColors(background);
|
||||
|
||||
@@ -44,6 +49,30 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||
&HomeWidget::initializeBackgroundFromSource);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::homeTabBackgroundShuffleFrequencyChanged, this,
|
||||
&HomeWidget::onBackgroundShuffleFrequencyChanged);
|
||||
|
||||
tutorialController = new TutorialController(this);
|
||||
auto sequence = TutorialSequence();
|
||||
sequence.addStep({connectButton, "Connect to a server to play here!"});
|
||||
sequence.addStep({visualDeckEditorButton, "Create a new deck from cards in the database here!"});
|
||||
sequence.addStep({visualDeckStorageButton, "Browse the decks in your local collection."});
|
||||
sequence.addStep({visualDatabaseDisplayButton, "View the card database here."});
|
||||
sequence.addStep(
|
||||
{edhrecButton, "Browse EDHRec, an external service designed to provide card recommendations for decks."});
|
||||
sequence.addStep({archidektButton, "Browse Archidekt, an external service that allows users to store "
|
||||
"decklists and import them to your local collection."});
|
||||
sequence.addStep({replaybutton, "View replays of your past games here."});
|
||||
sequence.addStep({exitButton, "Exit the application."});
|
||||
tutorialController->addSequence(sequence);
|
||||
}
|
||||
|
||||
void HomeWidget::showEvent(QShowEvent *event)
|
||||
{
|
||||
QWidget::showEvent(event);
|
||||
if (!tutorialStarted) {
|
||||
tutorialStarted = true;
|
||||
// Start on next event loop iteration so everything is fully painted
|
||||
QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); });
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWidget::initializeBackgroundFromSource()
|
||||
@@ -68,20 +97,13 @@ void HomeWidget::initializeBackgroundFromSource()
|
||||
cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
|
||||
break;
|
||||
case BackgroundSources::DeckFileArt:
|
||||
loadBackgroundSourceDeck();
|
||||
backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod",
|
||||
DeckLoader::CockatriceFormat, false);
|
||||
cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWidget::loadBackgroundSourceDeck()
|
||||
{
|
||||
DeckLoader deckLoader = DeckLoader(this);
|
||||
deckLoader.loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", DeckFileFormat::Cockatrice,
|
||||
false);
|
||||
backgroundSourceDeck = deckLoader.getDeck().deckList;
|
||||
}
|
||||
|
||||
void HomeWidget::updateRandomCard()
|
||||
{
|
||||
auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource());
|
||||
@@ -98,7 +120,7 @@ void HomeWidget::updateRandomCard()
|
||||
newCard.getCardPtr()->getProperty("layout") != "normal");
|
||||
break;
|
||||
case BackgroundSources::DeckFileArt:
|
||||
QList<CardRef> cardRefs = backgroundSourceDeck.getCardRefList();
|
||||
QList<CardRef> cardRefs = backgroundSourceDeck->getDeckList()->getCardRefList();
|
||||
ExactCard oldCard = backgroundSourceCard->getCard();
|
||||
|
||||
if (!cardRefs.empty()) {
|
||||
@@ -184,29 +206,29 @@ QGroupBox *HomeWidget::createButtons()
|
||||
connectButton = new HomeStyledButton("Connect/Play", gradientColors);
|
||||
boxLayout->addWidget(connectButton);
|
||||
|
||||
auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors);
|
||||
visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors);
|
||||
connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor,
|
||||
[this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); });
|
||||
[this] { tabSupervisor->openDeckInNewTab(nullptr); });
|
||||
boxLayout->addWidget(visualDeckEditorButton);
|
||||
auto visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors);
|
||||
visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors);
|
||||
connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor,
|
||||
[this] { tabSupervisor->actTabVisualDeckStorage(true); });
|
||||
boxLayout->addWidget(visualDeckStorageButton);
|
||||
auto visualDatabaseDisplayButton = new HomeStyledButton(tr("Browse Card Database"), gradientColors);
|
||||
visualDatabaseDisplayButton = new HomeStyledButton(tr("Browse Card Database"), gradientColors);
|
||||
connect(visualDatabaseDisplayButton, &QPushButton::clicked, tabSupervisor,
|
||||
&TabSupervisor::addVisualDatabaseDisplayTab);
|
||||
boxLayout->addWidget(visualDatabaseDisplayButton);
|
||||
auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
||||
edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
||||
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
||||
boxLayout->addWidget(edhrecButton);
|
||||
auto archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors);
|
||||
archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors);
|
||||
connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab);
|
||||
boxLayout->addWidget(archidektButton);
|
||||
auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
|
||||
replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
|
||||
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
||||
boxLayout->addWidget(replaybutton);
|
||||
if (qobject_cast<MainWindow *>(tabSupervisor->parentWidget())) {
|
||||
auto exitButton = new HomeStyledButton(tr("Quit"), gradientColors);
|
||||
exitButton = new HomeStyledButton(tr("Quit"), gradientColors);
|
||||
connect(exitButton, &QPushButton::clicked, qobject_cast<MainWindow *>(tabSupervisor->parentWidget()),
|
||||
&MainWindow::actExit);
|
||||
boxLayout->addWidget(exitButton);
|
||||
|
||||
@@ -24,9 +24,18 @@ public:
|
||||
HomeWidget(QWidget *parent, TabSupervisor *tabSupervisor);
|
||||
void updateRandomCard();
|
||||
QPair<QColor, QColor> extractDominantColors(const QPixmap &pixmap);
|
||||
HomeStyledButton *connectButton;
|
||||
HomeStyledButton *visualDeckEditorButton;
|
||||
HomeStyledButton *visualDeckStorageButton;
|
||||
HomeStyledButton *visualDatabaseDisplayButton;
|
||||
HomeStyledButton *edhrecButton;
|
||||
HomeStyledButton *archidektButton;
|
||||
HomeStyledButton *replaybutton;
|
||||
HomeStyledButton *exitButton;
|
||||
|
||||
public slots:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void initializeBackgroundFromSource();
|
||||
void onBackgroundShuffleFrequencyChanged();
|
||||
void updateBackgroundProperties();
|
||||
@@ -39,13 +48,12 @@ private:
|
||||
QTimer *cardChangeTimer;
|
||||
TabSupervisor *tabSupervisor;
|
||||
QPixmap background;
|
||||
TutorialController *tutorialController;
|
||||
bool tutorialStarted = false;
|
||||
CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr;
|
||||
DeckList backgroundSourceDeck;
|
||||
DeckLoader *backgroundSourceDeck;
|
||||
QPixmap overlay;
|
||||
QPair<QColor, QColor> gradientColors;
|
||||
HomeStyledButton *connectButton;
|
||||
|
||||
void loadBackgroundSourceDeck();
|
||||
};
|
||||
|
||||
#endif // HOME_WIDGET_H
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "tutorial_bubble_widget.h"
|
||||
|
||||
BubbleWidget::BubbleWidget(QWidget *parent) : QFrame(parent)
|
||||
{
|
||||
setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
|
||||
setStyleSheet("background:white; border-radius:8px;");
|
||||
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(12, 10, 12, 10);
|
||||
layout->setHorizontalSpacing(8);
|
||||
layout->setVerticalSpacing(8);
|
||||
|
||||
counterLabel = new QLabel(this);
|
||||
counterLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
|
||||
closeButton = new QPushButton("✕", this);
|
||||
closeButton->setFixedSize(20, 20);
|
||||
|
||||
textLabel = new QLabel(this);
|
||||
textLabel->setWordWrap(true);
|
||||
textLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
textLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
textLabel->setStyleSheet("color:black;"); // guard against global styles
|
||||
|
||||
// nav buttons
|
||||
previousSequenceButton = new QPushButton("<<", this);
|
||||
previousStepButton = new QPushButton("<", this);
|
||||
nextStepButton = new QPushButton(">", this);
|
||||
nextSequenceButton = new QPushButton(">>", this);
|
||||
|
||||
QHBoxLayout *navLayout = new QHBoxLayout;
|
||||
navLayout->addStretch();
|
||||
navLayout->addWidget(previousSequenceButton);
|
||||
navLayout->addWidget(previousStepButton);
|
||||
navLayout->addWidget(nextStepButton);
|
||||
navLayout->addWidget(nextSequenceButton);
|
||||
|
||||
// Layout
|
||||
layout->addWidget(counterLabel, 0, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
layout->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 1);
|
||||
layout->addWidget(closeButton, 0, 2, Qt::AlignRight);
|
||||
layout->addWidget(textLabel, 1, 0, 1, 3);
|
||||
layout->addLayout(navLayout, 2, 0, 1, 3);
|
||||
|
||||
// Make column 1 take extra space so text gets room to expand/wrap
|
||||
layout->setColumnStretch(1, 1);
|
||||
|
||||
// sensible default maximum width for bubble so text will wrap
|
||||
setMaximumWidth(420);
|
||||
}
|
||||
|
||||
void BubbleWidget::setText(const QString &text)
|
||||
{
|
||||
textLabel->setText(text);
|
||||
update();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
|
||||
#define COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
|
||||
#include <QFrame>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
class BubbleWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QLabel *textLabel;
|
||||
QLabel *counterLabel;
|
||||
QPushButton *closeButton;
|
||||
QPushButton *previousSequenceButton;
|
||||
QPushButton *previousStepButton;
|
||||
QPushButton *nextStepButton;
|
||||
QPushButton *nextSequenceButton;
|
||||
|
||||
BubbleWidget(QWidget *parent);
|
||||
void setText(const QString &text);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
|
||||
@@ -0,0 +1,181 @@
|
||||
#include "tutorial_controller.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
TutorialController::TutorialController(QWidget *_tutorializedWidget)
|
||||
: QObject(_tutorializedWidget), tutorializedWidget(_tutorializedWidget)
|
||||
{
|
||||
tutorialOverlay = new TutorialOverlay(tutorializedWidget->window());
|
||||
|
||||
// Make it frameless + translucent
|
||||
tutorialOverlay->setWindowFlags(tutorialOverlay->windowFlags() | Qt::FramelessWindowHint);
|
||||
tutorialOverlay->setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
// hide until start
|
||||
tutorialOverlay->hide();
|
||||
|
||||
connect(tutorialOverlay, &TutorialOverlay::nextStep, this, &TutorialController::nextStep);
|
||||
connect(tutorialOverlay, &TutorialOverlay::prevStep, this, &TutorialController::prevStep);
|
||||
connect(tutorialOverlay, &TutorialOverlay::nextSequence, this, &TutorialController::nextSequence);
|
||||
connect(tutorialOverlay, &TutorialOverlay::prevSequence, this, &TutorialController::prevSequence);
|
||||
|
||||
connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, &TutorialController::exitTutorial);
|
||||
}
|
||||
|
||||
void TutorialController::addSequence(const TutorialSequence &seq)
|
||||
{
|
||||
sequences.append(seq);
|
||||
}
|
||||
|
||||
void TutorialController::start()
|
||||
{
|
||||
if (sequences.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
QWidget *win = tutorializedWidget->window();
|
||||
tutorialOverlay->parentResized();
|
||||
tutorialOverlay->setGeometry(QRect(QPoint(0, 0), win->size()));
|
||||
tutorialOverlay->show();
|
||||
tutorialOverlay->raise();
|
||||
tutorialOverlay->parentResized();
|
||||
|
||||
currentSequence = 0;
|
||||
currentStep = 0;
|
||||
showStep();
|
||||
});
|
||||
}
|
||||
|
||||
void TutorialController::nextStep()
|
||||
{
|
||||
// advance within sequence
|
||||
currentStep++;
|
||||
|
||||
if (currentSequence < 0) {
|
||||
return; // defensive in case we haven't started yet
|
||||
}
|
||||
|
||||
if (currentStep >= sequences[currentSequence].steps.size()) {
|
||||
// advance to next sequence
|
||||
nextSequence();
|
||||
return;
|
||||
}
|
||||
|
||||
showStep();
|
||||
}
|
||||
|
||||
void TutorialController::prevStep()
|
||||
{
|
||||
if (currentSequence < 0) {
|
||||
return; // defensive in case we haven't started yet
|
||||
}
|
||||
|
||||
if (currentStep == 0) {
|
||||
prevSequence();
|
||||
return;
|
||||
}
|
||||
|
||||
currentStep--;
|
||||
showStep();
|
||||
}
|
||||
|
||||
void TutorialController::nextSequence()
|
||||
{
|
||||
if (currentSequence < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// run exit for the last step of the current sequence (showStep handles previous onExit,
|
||||
// but ensure we run it here because we're jumping sequence)
|
||||
// We'll increment sequence and then call showStep which will call the onEnter for the new step.
|
||||
currentSequence++;
|
||||
currentStep = 0;
|
||||
|
||||
if (currentSequence >= sequences.size()) {
|
||||
exitTutorial();
|
||||
return;
|
||||
}
|
||||
|
||||
showStep();
|
||||
}
|
||||
|
||||
void TutorialController::prevSequence()
|
||||
{
|
||||
if (currentSequence <= 0) {
|
||||
// already at first sequence -> stay
|
||||
currentStep = 0;
|
||||
showStep();
|
||||
return;
|
||||
}
|
||||
|
||||
currentSequence--;
|
||||
currentStep = 0;
|
||||
showStep();
|
||||
}
|
||||
|
||||
void TutorialController::exitTutorial()
|
||||
{
|
||||
// Run onExit for the current step if present
|
||||
if (currentSequence >= 0 && currentStep >= 0 && currentSequence < sequences.size() &&
|
||||
currentStep < sequences[currentSequence].steps.size()) {
|
||||
const auto &curStep = sequences[currentSequence].steps[currentStep];
|
||||
if (curStep.onExit) {
|
||||
curStep.onExit();
|
||||
}
|
||||
}
|
||||
|
||||
tutorialOverlay->hide();
|
||||
|
||||
// reset indices so start() can be called again cleanly
|
||||
currentSequence = -1;
|
||||
currentStep = -1;
|
||||
}
|
||||
|
||||
void TutorialController::showStep()
|
||||
{
|
||||
// bounds checks
|
||||
if (currentSequence < 0 || currentSequence >= sequences.size()) {
|
||||
return;
|
||||
}
|
||||
const auto &seq = sequences[currentSequence];
|
||||
if (currentStep < 0 || currentStep >= seq.steps.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// run onExit for the previous step (including if previous step was in previous sequence)
|
||||
if (!(currentSequence == 0 && currentStep == 0)) {
|
||||
int prevSeq = currentSequence;
|
||||
int prevStepIndex = currentStep - 1;
|
||||
if (prevStepIndex < 0) {
|
||||
// previous is last step of previous sequence
|
||||
prevSeq = currentSequence - 1;
|
||||
if (prevSeq >= 0) {
|
||||
prevStepIndex = sequences[prevSeq].steps.size() - 1;
|
||||
} else {
|
||||
prevStepIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevSeq >= 0 && prevStepIndex >= 0) {
|
||||
const auto &previousStep = sequences[prevSeq].steps[prevStepIndex];
|
||||
if (previousStep.onExit) {
|
||||
previousStep.onExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// current step
|
||||
const auto &step = seq.steps[currentStep];
|
||||
|
||||
// Run any action associated with this step
|
||||
if (step.onEnter) {
|
||||
step.onEnter();
|
||||
}
|
||||
|
||||
tutorialOverlay->setTargetWidget(step.targetWidget);
|
||||
tutorialOverlay->setText(step.text);
|
||||
tutorialOverlay->parentResized();
|
||||
tutorialOverlay->raise();
|
||||
tutorialOverlay->update();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef COCKATRICE_TUTORIAL_CONTROLLER_H
|
||||
#define COCKATRICE_TUTORIAL_CONTROLLER_H
|
||||
|
||||
#include "tutorial_overlay.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <functional>
|
||||
|
||||
struct TutorialStep
|
||||
{
|
||||
QWidget *targetWidget;
|
||||
QString text;
|
||||
std::function<void()> onEnter = nullptr; // Optional function to run when this step starts
|
||||
std::function<void()> onExit = nullptr; // Optional function to run when step ends
|
||||
};
|
||||
|
||||
struct TutorialSequence
|
||||
{
|
||||
QString name;
|
||||
QVector<TutorialStep> steps;
|
||||
|
||||
void addStep(const TutorialStep &step)
|
||||
{
|
||||
steps.append(step);
|
||||
}
|
||||
};
|
||||
|
||||
class TutorialController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
void nextStep();
|
||||
void prevStep();
|
||||
void nextSequence();
|
||||
void prevSequence();
|
||||
void exitTutorial();
|
||||
|
||||
public:
|
||||
explicit TutorialController(QWidget *_tutorializedWidget);
|
||||
|
||||
void addSequence(const TutorialSequence &step);
|
||||
|
||||
private:
|
||||
QWidget *tutorializedWidget;
|
||||
QVector<TutorialSequence> sequences;
|
||||
int currentSequence = -1;
|
||||
int currentStep = -1;
|
||||
|
||||
TutorialOverlay *tutorialOverlay;
|
||||
|
||||
void showStep();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TUTORIAL_CONTROLLER_H
|
||||
@@ -0,0 +1,178 @@
|
||||
#include "tutorial_overlay.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent, Qt::Window)
|
||||
{
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
|
||||
|
||||
// This ensures the overlay stays exactly over the parent
|
||||
if (parent) {
|
||||
QRect r = parent->rect();
|
||||
|
||||
// convert the parent’s rect to screen coordinates
|
||||
QPoint globalTopLeft = parent->mapToGlobal(QPoint(0, 0));
|
||||
r.moveTopLeft(globalTopLeft);
|
||||
|
||||
setGeometry(r);
|
||||
}
|
||||
|
||||
bubble = new BubbleWidget(this);
|
||||
bubble->hide();
|
||||
|
||||
connect(bubble->nextStepButton, &QPushButton::clicked, this, &TutorialOverlay::nextStep);
|
||||
connect(bubble->previousStepButton, &QPushButton::clicked, this, &TutorialOverlay::prevStep);
|
||||
connect(bubble->previousSequenceButton, &QPushButton::clicked, this, &TutorialOverlay::prevSequence);
|
||||
connect(bubble->nextSequenceButton, &QPushButton::clicked, this, &TutorialOverlay::nextSequence);
|
||||
connect(bubble->closeButton, &QPushButton::clicked, this, &TutorialOverlay::skipTutorial);
|
||||
}
|
||||
|
||||
void TutorialOverlay::setTargetWidget(QWidget *w)
|
||||
{
|
||||
targetWidget = w;
|
||||
update();
|
||||
}
|
||||
|
||||
void TutorialOverlay::setText(const QString &t)
|
||||
{
|
||||
tutorialText = t;
|
||||
bubble->setText(tutorialText);
|
||||
bubble->adjustSize(); // let layout recalc sizes
|
||||
QSize bsize = bubble->sizeHint();
|
||||
|
||||
const QSize minSize(160, 60);
|
||||
if (bsize.width() < minSize.width()) {
|
||||
bsize.setWidth(minSize.width());
|
||||
}
|
||||
if (bsize.height() < minSize.height()) {
|
||||
bsize.setHeight(minSize.height());
|
||||
}
|
||||
|
||||
// Compute the bubble rect from the current target hole
|
||||
QRect hole = targetRectOnOverlay().adjusted(-6, -6, 6, 6);
|
||||
highlightBubbleRect = computeBubbleRect(hole, bsize);
|
||||
|
||||
bubble->setGeometry(highlightBubbleRect);
|
||||
bubble->raise();
|
||||
bubble->show();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void TutorialOverlay::showEvent(QShowEvent *)
|
||||
{
|
||||
raise();
|
||||
}
|
||||
|
||||
void TutorialOverlay::resizeEvent(QResizeEvent *)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QRect TutorialOverlay::targetRectOnOverlay() const
|
||||
{
|
||||
if (!targetWidget) {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
// Widget -> global screen coordinates
|
||||
QPoint globalTopLeft = targetWidget->mapToGlobal(QPoint(0, 0));
|
||||
|
||||
// Global -> overlay-local coordinates
|
||||
QPoint localTopLeft = mapFromGlobal(globalTopLeft);
|
||||
|
||||
return QRect(localTopLeft, targetWidget->size());
|
||||
}
|
||||
|
||||
QRect TutorialOverlay::computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const
|
||||
{
|
||||
const int margin = 16;
|
||||
QRect r = rect(); // overlay bounds
|
||||
QRect bubble;
|
||||
|
||||
// Try right
|
||||
bubble = QRect(hole.right() + margin, hole.top(), bubbleSize.width(), bubbleSize.height());
|
||||
if (r.contains(bubble)) {
|
||||
return bubble;
|
||||
}
|
||||
|
||||
// Try left
|
||||
bubble = QRect(hole.left() - margin - bubbleSize.width(), hole.top(), bubbleSize.width(), bubbleSize.height());
|
||||
if (r.contains(bubble)) {
|
||||
return bubble;
|
||||
}
|
||||
|
||||
// Try above, centered
|
||||
bubble = QRect(hole.center().x() - bubbleSize.width() / 2, hole.top() - margin - bubbleSize.height(),
|
||||
bubbleSize.width(), bubbleSize.height());
|
||||
if (r.contains(bubble)) {
|
||||
return bubble;
|
||||
}
|
||||
|
||||
// Try below, centered
|
||||
bubble = QRect(hole.center().x() - bubbleSize.width() / 2, hole.bottom() + margin, bubbleSize.width(),
|
||||
bubbleSize.height());
|
||||
if (r.contains(bubble)) {
|
||||
return bubble;
|
||||
}
|
||||
|
||||
// Last-resort: clamp inside overlay
|
||||
bubble.moveLeft(std::max(r.left(), std::min(bubble.left(), r.right() - bubbleSize.width())));
|
||||
bubble.moveTop(std::max(r.top(), std::min(bubble.top(), r.bottom() - bubbleSize.height())));
|
||||
bubble.setSize(bubbleSize);
|
||||
return bubble;
|
||||
}
|
||||
|
||||
void TutorialOverlay::parentResized()
|
||||
{
|
||||
if (parentWidget()) {
|
||||
QRect r = parentWidget()->rect();
|
||||
QPoint globalTopLeft = parentWidget()->mapToGlobal(QPoint(0, 0));
|
||||
r.moveTopLeft(globalTopLeft);
|
||||
setGeometry(r);
|
||||
}
|
||||
}
|
||||
|
||||
void TutorialOverlay::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
QColor overlay(0, 0, 0, 160);
|
||||
p.fillRect(rect(), overlay);
|
||||
|
||||
QRect hole = targetRectOnOverlay().adjusted(-6, -6, 6, 6);
|
||||
if (!hole.isEmpty()) {
|
||||
QPainterPath path;
|
||||
path.addRect(rect());
|
||||
QPainterPath holePath;
|
||||
holePath.addRoundedRect(hole, 8, 8);
|
||||
path = path.subtracted(holePath);
|
||||
|
||||
p.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
p.fillPath(holePath, Qt::transparent);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
}
|
||||
|
||||
// recompute bubble size/position in case available geometry changed:
|
||||
bubble->adjustSize();
|
||||
QSize bsize = bubble->sizeHint();
|
||||
const QSize minSize(160, 60);
|
||||
if (bsize.width() < minSize.width())
|
||||
bsize.setWidth(minSize.width());
|
||||
if (bsize.height() < minSize.height())
|
||||
bsize.setHeight(minSize.height());
|
||||
|
||||
highlightBubbleRect = computeBubbleRect(hole, bsize);
|
||||
bubble->setGeometry(highlightBubbleRect);
|
||||
bubble->raise();
|
||||
bubble->show();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef COCKATRICE_TUTORIAL_OVERLAY_H
|
||||
#define COCKATRICE_TUTORIAL_OVERLAY_H
|
||||
|
||||
#include "tutorial_bubble_widget.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
|
||||
class TutorialOverlay : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TutorialOverlay(QWidget *parent = nullptr);
|
||||
|
||||
void setTargetWidget(QWidget *w);
|
||||
void setText(const QString &t);
|
||||
void parentResized();
|
||||
|
||||
signals:
|
||||
void nextStep();
|
||||
void prevStep();
|
||||
void nextSequence();
|
||||
void prevSequence();
|
||||
void skipTutorial();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void resizeEvent(QResizeEvent *) override;
|
||||
void showEvent(QShowEvent *) override;
|
||||
|
||||
private:
|
||||
QRect targetRectOnOverlay() const;
|
||||
QRect computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const;
|
||||
|
||||
QPointer<QWidget> targetWidget;
|
||||
QString tutorialText;
|
||||
|
||||
QRect highlightBubbleRect;
|
||||
BubbleWidget *bubble;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TUTORIAL_OVERLAY_H
|
||||
@@ -306,12 +306,19 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone)
|
||||
return -1;
|
||||
}
|
||||
|
||||
QList<ExactCard> cards = deckModel->getCardsForZone(deckZone);
|
||||
DeckList *decklist = deckModel->getDeckList();
|
||||
if (!decklist) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes({deckZone});
|
||||
|
||||
int count = 0;
|
||||
for (auto currentCard : cards) {
|
||||
if (currentCard.getPrinting().getUuid() == rootCard.getPrinting().getProperty("uuid")) {
|
||||
count++;
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
if (currentCard->getCardProviderId() == rootCard.getPrinting().getProperty("uuid")) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ void ChatView::appendMessage(QString message,
|
||||
defaultFormat = QTextCharFormat();
|
||||
if (!isUserMessage) {
|
||||
if (messageType == Event_RoomSay::ChatHistory) {
|
||||
defaultFormat.setForeground(Qt::gray); //! \todo hardcoded color
|
||||
defaultFormat.setForeground(Qt::gray); // FIXME : hardcoded color
|
||||
defaultFormat.setFontWeight(QFont::Light);
|
||||
defaultFormat.setFontItalic(true);
|
||||
static const QRegularExpression userNameRegex("^(\\[[^\\]]*\\]\\s)(\\S+):\\s");
|
||||
@@ -229,7 +229,7 @@ void ChatView::appendMessage(QString message,
|
||||
message.remove(0, pos.relativePosition - 2); // do not remove semicolon
|
||||
}
|
||||
} else {
|
||||
defaultFormat.setForeground(Qt::darkGreen); //! \todo hardcoded color
|
||||
defaultFormat.setForeground(Qt::darkGreen); // FIXME : hardcoded color
|
||||
defaultFormat.setFontWeight(QFont::Bold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,22 +213,22 @@ void AbstractTabDeckEditor::actSwapCard(const ExactCard &card, const QString &zo
|
||||
|
||||
/**
|
||||
* @brief Opens a deck in this tab.
|
||||
* @param deck The deck
|
||||
* @param deck DeckLoader object (takes ownership).
|
||||
*/
|
||||
void AbstractTabDeckEditor::openDeck(const LoadedDeck &deck)
|
||||
void AbstractTabDeckEditor::openDeck(DeckLoader *deck)
|
||||
{
|
||||
setDeck(deck);
|
||||
|
||||
if (!deck.lastLoadInfo.fileName.isEmpty()) {
|
||||
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck.lastLoadInfo.fileName);
|
||||
if (!deck->getLastLoadInfo().fileName.isEmpty()) {
|
||||
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastLoadInfo().fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the currently active deck.
|
||||
* @param _deck The deck
|
||||
* @param _deck DeckLoader object.
|
||||
*/
|
||||
void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck)
|
||||
void AbstractTabDeckEditor::setDeck(DeckLoader *_deck)
|
||||
{
|
||||
deckDockWidget->setDeck(_deck);
|
||||
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList()->getCardRefList()));
|
||||
@@ -265,8 +265,8 @@ void AbstractTabDeckEditor::setModified(bool _modified)
|
||||
*/
|
||||
bool AbstractTabDeckEditor::isBlankNewDeck() const
|
||||
{
|
||||
const LoadedDeck &loadedDeck = deckDockWidget->getDeckLoader()->getDeck();
|
||||
return !modified && loadedDeck.isEmpty();
|
||||
DeckLoader *deck = deckDockWidget->getDeckLoader();
|
||||
return !modified && deck->getDeckList()->isBlankDeck() && deck->hasNotBeenLoaded();
|
||||
}
|
||||
|
||||
/** @brief Creates a new deck. Handles opening in new tab if needed. */
|
||||
@@ -277,7 +277,7 @@ void AbstractTabDeckEditor::actNewDeck()
|
||||
return;
|
||||
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(LoadedDeck());
|
||||
emit openDeckEditor(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -380,17 +380,19 @@ void AbstractTabDeckEditor::actOpenRecent(const QString &fileName)
|
||||
*/
|
||||
void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation)
|
||||
{
|
||||
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
|
||||
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
|
||||
|
||||
auto l = DeckLoader(this);
|
||||
if (l.loadFromFile(fileName, fmt, true)) {
|
||||
auto *l = new DeckLoader(this);
|
||||
if (l->loadFromFile(fileName, fmt, true)) {
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(l.getDeck());
|
||||
emit openDeckEditor(l);
|
||||
l->deleteLater();
|
||||
} else {
|
||||
deckMenu->setSaveStatus(false);
|
||||
openDeck(l.getDeck());
|
||||
openDeck(l);
|
||||
}
|
||||
} else {
|
||||
l->deleteLater();
|
||||
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
|
||||
}
|
||||
deckMenu->setSaveStatus(true);
|
||||
@@ -403,16 +405,16 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo
|
||||
*/
|
||||
bool AbstractTabDeckEditor::actSaveDeck()
|
||||
{
|
||||
const LoadedDeck &loadedDeck = getDeckLoader()->getDeck();
|
||||
if (loadedDeck.lastLoadInfo.remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) {
|
||||
QString deckString = loadedDeck.deckList.writeToString_Native();
|
||||
DeckLoader *const deck = getDeckLoader();
|
||||
if (deck->getLastLoadInfo().remoteDeckId != DeckLoader::LoadInfo::NON_REMOTE_ID) {
|
||||
QString deckString = deck->getDeckList()->writeToString_Native();
|
||||
if (deckString.length() > MAX_FILE_LENGTH) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck"));
|
||||
return false;
|
||||
}
|
||||
|
||||
Command_DeckUpload cmd;
|
||||
cmd.set_deck_id(static_cast<google::protobuf::uint32>(loadedDeck.lastLoadInfo.remoteDeckId));
|
||||
cmd.set_deck_id(static_cast<google::protobuf::uint32>(deck->getLastLoadInfo().remoteDeckId));
|
||||
cmd.set_deck_list(deckString.toStdString());
|
||||
|
||||
PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
|
||||
@@ -420,11 +422,9 @@ bool AbstractTabDeckEditor::actSaveDeck()
|
||||
tabSupervisor->getClient()->sendCommand(pend);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (loadedDeck.lastLoadInfo.fileName.isEmpty())
|
||||
} else if (deck->getLastLoadInfo().fileName.isEmpty())
|
||||
return actSaveDeckAs();
|
||||
|
||||
if (getDeckLoader()->saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) {
|
||||
else if (deck->saveToFile(deck->getLastLoadInfo().fileName, deck->getLastLoadInfo().fileFormat)) {
|
||||
setModified(false);
|
||||
return true;
|
||||
}
|
||||
@@ -452,7 +452,7 @@ bool AbstractTabDeckEditor::actSaveDeckAs()
|
||||
return false;
|
||||
|
||||
QString fileName = dialog.selectedFiles().at(0);
|
||||
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
|
||||
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
|
||||
|
||||
if (!getDeckLoader()->saveToFile(fileName, fmt)) {
|
||||
QMessageBox::critical(
|
||||
@@ -493,9 +493,9 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard()
|
||||
return;
|
||||
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor({.deckList = dlg.getDeckList()});
|
||||
emit openDeckEditor(dlg.getDeckList());
|
||||
} else {
|
||||
setDeck({.deckList = dlg.getDeckList()});
|
||||
setDeck(dlg.getDeckList());
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@@ -508,11 +508,11 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard()
|
||||
*/
|
||||
void AbstractTabDeckEditor::editDeckInClipboard(bool annotated)
|
||||
{
|
||||
DlgEditDeckInClipboard dlg(getDeckLoader()->getDeck().deckList, annotated, this);
|
||||
DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
setDeck({dlg.getDeckList(), getDeckLoader()->getDeck().lastLoadInfo});
|
||||
setDeck(dlg.getDeckList());
|
||||
setModified(true);
|
||||
deckMenu->setSaveStatus(true);
|
||||
}
|
||||
@@ -576,9 +576,9 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite()
|
||||
return;
|
||||
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor({.deckList = dlg.getDeck()});
|
||||
emit openDeckEditor(dlg.getDeck());
|
||||
} else {
|
||||
setDeck({.deckList = dlg.getDeck()});
|
||||
setDeck(dlg.getDeck());
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@@ -591,8 +591,8 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite()
|
||||
*/
|
||||
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
|
||||
{
|
||||
if (DeckList *deckList = getDeckList()) {
|
||||
QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckList, website);
|
||||
if (DeckLoader *const deck = getDeckLoader()) {
|
||||
QString decklistUrlString = deck->exportDeckToDecklist(getDeckList(), website);
|
||||
// Check to make sure the string isn't empty.
|
||||
if (decklistUrlString.isEmpty()) {
|
||||
// Show an error if the deck is empty, and return.
|
||||
|
||||
@@ -114,9 +114,9 @@ public:
|
||||
virtual void retranslateUi() override = 0;
|
||||
|
||||
/** @brief Opens a deck in this tab.
|
||||
* @param deck The deck to open
|
||||
* @param deck Pointer to a DeckLoader object.
|
||||
*/
|
||||
void openDeck(const LoadedDeck &deck);
|
||||
void openDeck(DeckLoader *deck);
|
||||
|
||||
/** @brief Returns the currently active deck loader. */
|
||||
DeckLoader *getDeckLoader() const;
|
||||
@@ -198,7 +198,7 @@ public slots:
|
||||
|
||||
signals:
|
||||
/** @brief Emitted when a deck should be opened in a new editor tab. */
|
||||
void openDeckEditor(const LoadedDeck &deck);
|
||||
void openDeckEditor(DeckLoader *deckLoader);
|
||||
|
||||
/** @brief Emitted before the tab is closed. */
|
||||
void deckEditorClosing(AbstractTabDeckEditor *tab);
|
||||
@@ -286,7 +286,7 @@ private:
|
||||
/** @brief Sets the deck for this tab.
|
||||
* @param _deck The deck object.
|
||||
*/
|
||||
virtual void setDeck(const LoadedDeck &_deck);
|
||||
virtual void setDeck(DeckLoader *_deck);
|
||||
|
||||
/** @brief Helper for editing decks from the clipboard. */
|
||||
void editDeckInClipboard(bool annotated);
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_FORMATS_H
|
||||
#define COCKATRICE_ARCHIDEKT_FORMATS_H
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
namespace ArchidektFormats
|
||||
{
|
||||
enum class DeckFormat
|
||||
{
|
||||
Standard = 0,
|
||||
Modern = 1,
|
||||
Commander = 2,
|
||||
Legacy = 3,
|
||||
Vintage = 4,
|
||||
Pauper = 5,
|
||||
Custom = 6,
|
||||
Frontier = 7,
|
||||
FutureStandard = 8,
|
||||
PennyDreadful = 9,
|
||||
Commander1v1 = 10,
|
||||
DualCommander = 11,
|
||||
Brawl = 12,
|
||||
|
||||
// Values outside Archidekt range
|
||||
Alchemy = 1000,
|
||||
Historic = 1001,
|
||||
Gladiator = 1002,
|
||||
Oathbreaker = 1003,
|
||||
OldSchool = 1004,
|
||||
PauperCommander = 1005,
|
||||
Pioneer = 1006,
|
||||
PreDH = 1007,
|
||||
Premodern = 1008,
|
||||
StandardBrawl = 1009,
|
||||
Timeless = 1010,
|
||||
Unknown = 1011
|
||||
};
|
||||
|
||||
inline static QString formatToApiName(DeckFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case DeckFormat::Standard:
|
||||
return "Standard";
|
||||
case DeckFormat::Modern:
|
||||
return "Modern";
|
||||
case DeckFormat::Commander:
|
||||
return "Commander";
|
||||
case DeckFormat::Legacy:
|
||||
return "Legacy";
|
||||
case DeckFormat::Vintage:
|
||||
return "Vintage";
|
||||
case DeckFormat::Pauper:
|
||||
return "Pauper";
|
||||
case DeckFormat::Custom:
|
||||
return "Custom";
|
||||
case DeckFormat::Frontier:
|
||||
return "Frontier";
|
||||
case DeckFormat::FutureStandard:
|
||||
return "Future Std";
|
||||
case DeckFormat::PennyDreadful:
|
||||
return "Penny Dreadful";
|
||||
case DeckFormat::Commander1v1:
|
||||
return "1v1 Commander";
|
||||
case DeckFormat::DualCommander:
|
||||
return "Dual Commander";
|
||||
case DeckFormat::Brawl:
|
||||
return "Brawl";
|
||||
|
||||
case DeckFormat::Alchemy:
|
||||
return "Alchemy";
|
||||
case DeckFormat::Historic:
|
||||
return "Historic";
|
||||
case DeckFormat::Gladiator:
|
||||
return "Gladiator";
|
||||
case DeckFormat::Oathbreaker:
|
||||
return "Oathbreaker";
|
||||
case DeckFormat::OldSchool:
|
||||
return "Old School";
|
||||
case DeckFormat::PauperCommander:
|
||||
return "Pauper Commander";
|
||||
case DeckFormat::Pioneer:
|
||||
return "Pioneer";
|
||||
case DeckFormat::PreDH:
|
||||
return "PreDH";
|
||||
case DeckFormat::Premodern:
|
||||
return "Premodern";
|
||||
case DeckFormat::StandardBrawl:
|
||||
return "Standard Brawl";
|
||||
case DeckFormat::Timeless:
|
||||
return "Timeless";
|
||||
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
inline static DeckFormat apiNameToFormat(const QString &name)
|
||||
{
|
||||
const QString n = name.trimmed();
|
||||
|
||||
if (n.compare("Standard", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Standard;
|
||||
if (n.compare("Modern", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Modern;
|
||||
if (n.compare("Commander", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Commander;
|
||||
if (n.compare("Legacy", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Legacy;
|
||||
if (n.compare("Vintage", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Vintage;
|
||||
if (n.compare("Pauper", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Pauper;
|
||||
if (n.compare("Custom", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Custom;
|
||||
if (n.compare("Frontier", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Frontier;
|
||||
if (n.compare("Future Std", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::FutureStandard;
|
||||
if (n.compare("Penny Dreadful", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::PennyDreadful;
|
||||
if (n.compare("1v1 Commander", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Commander1v1;
|
||||
if (n.compare("Dual Commander", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::DualCommander;
|
||||
if (n.compare("Brawl", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Brawl;
|
||||
|
||||
if (n.compare("Alchemy", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Alchemy;
|
||||
if (n.compare("Historic", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Historic;
|
||||
if (n.compare("Gladiator", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Gladiator;
|
||||
if (n.compare("Oathbreaker", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Oathbreaker;
|
||||
if (n.compare("Old School", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::OldSchool;
|
||||
if (n.compare("Pauper Commander", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::PauperCommander;
|
||||
if (n.compare("Pioneer", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Pioneer;
|
||||
if (n.compare("PreDH", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::PreDH;
|
||||
if (n.compare("Premodern", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Premodern;
|
||||
if (n.compare("Standard Brawl", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::StandardBrawl;
|
||||
if (n.compare("Timeless", Qt::CaseInsensitive) == 0)
|
||||
return DeckFormat::Timeless;
|
||||
|
||||
return DeckFormat::Unknown;
|
||||
}
|
||||
|
||||
inline static QString formatToCockatriceName(DeckFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case DeckFormat::Standard:
|
||||
return "standard";
|
||||
case DeckFormat::Modern:
|
||||
return "modern";
|
||||
case DeckFormat::Commander:
|
||||
return "commander";
|
||||
case DeckFormat::Legacy:
|
||||
return "legacy";
|
||||
case DeckFormat::Vintage:
|
||||
return "vintage";
|
||||
case DeckFormat::Pauper:
|
||||
return "pauper";
|
||||
case DeckFormat::Brawl:
|
||||
return "brawl";
|
||||
case DeckFormat::PennyDreadful:
|
||||
return "penny";
|
||||
case DeckFormat::FutureStandard:
|
||||
return "future";
|
||||
case DeckFormat::Commander1v1:
|
||||
return "duel";
|
||||
case DeckFormat::DualCommander:
|
||||
return "duel";
|
||||
case DeckFormat::Alchemy:
|
||||
return "alchemy";
|
||||
case DeckFormat::Historic:
|
||||
return "historic";
|
||||
case DeckFormat::Gladiator:
|
||||
return "gladiator";
|
||||
case DeckFormat::Oathbreaker:
|
||||
return "oathbreaker";
|
||||
case DeckFormat::OldSchool:
|
||||
return "oldschool";
|
||||
case DeckFormat::PauperCommander:
|
||||
return "paupercommander";
|
||||
case DeckFormat::Pioneer:
|
||||
return "pioneer";
|
||||
case DeckFormat::PreDH:
|
||||
return "predh";
|
||||
case DeckFormat::Premodern:
|
||||
return "premodern";
|
||||
case DeckFormat::StandardBrawl:
|
||||
return "standardbrawl";
|
||||
case DeckFormat::Timeless:
|
||||
return "timeless";
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
inline static DeckFormat cockatriceNameToFormat(const QString &apiName)
|
||||
{
|
||||
static const QHash<QString, DeckFormat> map = {
|
||||
{"standard", DeckFormat::Standard}, {"modern", DeckFormat::Modern},
|
||||
{"commander", DeckFormat::Commander}, {"legacy", DeckFormat::Legacy},
|
||||
{"vintage", DeckFormat::Vintage}, {"pauper", DeckFormat::Pauper},
|
||||
{"brawl", DeckFormat::Brawl}, {"penny", DeckFormat::PennyDreadful},
|
||||
{"future", DeckFormat::FutureStandard}, {"duel", DeckFormat::Commander1v1},
|
||||
{"alchemy", DeckFormat::Alchemy}, {"historic", DeckFormat::Historic},
|
||||
{"gladiator", DeckFormat::Gladiator}, {"oathbreaker", DeckFormat::Oathbreaker},
|
||||
{"oldschool", DeckFormat::OldSchool}, {"paupercommander", DeckFormat::PauperCommander},
|
||||
{"pioneer", DeckFormat::Pioneer}, {"predh", DeckFormat::PreDH},
|
||||
{"premodern", DeckFormat::Premodern}, {"standardbrawl", DeckFormat::StandardBrawl},
|
||||
{"timeless", DeckFormat::Timeless}};
|
||||
|
||||
return map.value(apiName.toLower(), DeckFormat::Unknown);
|
||||
}
|
||||
|
||||
} // namespace ArchidektFormats
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_FORMATS_H
|
||||
@@ -21,16 +21,16 @@ void ArchidektApiResponseCard::fromJson(const QJsonObject &json)
|
||||
edition.fromJson(json.value("edition").toObject());
|
||||
|
||||
flavor = json.value("flavor").toString();
|
||||
//! \todo but not really important
|
||||
//! \todo games = {""};
|
||||
//! \todo options = {""};
|
||||
// TODO but not really important
|
||||
// games = {""};
|
||||
// options = {""};
|
||||
scryfallImageHash = json.value("scryfallImageHash").toString();
|
||||
oracleCard = json.value("oracleCard").toObject();
|
||||
owned = json.value("owned").toInt();
|
||||
pinnedStatus = json.value("pinnedStatus").toInt();
|
||||
rarity = json.value("rarity").toString();
|
||||
//! \todo but not really important
|
||||
//! \todo globalCategories = {""};
|
||||
// TODO but not really important
|
||||
// globalCategories = {""};
|
||||
}
|
||||
|
||||
void ArchidektApiResponseCard::debugPrint() const
|
||||
|
||||
@@ -39,11 +39,6 @@ public:
|
||||
return name;
|
||||
};
|
||||
|
||||
int getDeckFormat() const
|
||||
{
|
||||
return deckFormat;
|
||||
}
|
||||
|
||||
private:
|
||||
int id;
|
||||
QString name;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#include "archidekt_api_response_deck_display_widget.h"
|
||||
|
||||
#include "../../../../../deck_loader/card_node_function.h"
|
||||
#include "../../../../../deck_loader/deck_loader.h"
|
||||
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../../../../cards/card_size_widget.h"
|
||||
#include "../../../../cards/deck_card_zone_display_widget.h"
|
||||
#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h"
|
||||
#include "../api_response/archidekt_formats.h"
|
||||
#include "../api_response/deck/archidekt_api_response_deck.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
@@ -70,7 +68,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
|
||||
connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset);
|
||||
model->getDeckList()->loadFromStream_Plain(deckStream, false);
|
||||
|
||||
model->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId());
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(model->getDeckList());
|
||||
|
||||
model->rebuildTree();
|
||||
|
||||
@@ -90,14 +88,12 @@ void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor()
|
||||
{
|
||||
DeckList deckList(*model->getDeckList());
|
||||
deckList.setName(response.getDeckName());
|
||||
deckList.setGameFormat(
|
||||
ArchidektFormats::formatToCockatriceName(ArchidektFormats::DeckFormat(response.getDeckFormat() - 1)));
|
||||
auto loader = new DeckLoader(this);
|
||||
loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native());
|
||||
|
||||
LoadedDeck loadedDeck = {deckList, {}};
|
||||
loader->getDeckList()->setName(response.getDeckName());
|
||||
|
||||
emit openInDeckEditor(loadedDeck);
|
||||
emit openInDeckEditor(loader);
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets()
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
*
|
||||
* ### Signals
|
||||
* - `requestNavigation(QString url)` — triggered when navigation to a deck URL is requested.
|
||||
* - `openInDeckEditor(const LoadedDeck &deck)` — emitted when the user chooses to open the deck
|
||||
* - `openInDeckEditor(DeckLoader *loader)` — emitted when the user chooses to open the deck
|
||||
* in the deck editor.
|
||||
*
|
||||
* ### Features
|
||||
@@ -52,9 +52,9 @@ signals:
|
||||
|
||||
/**
|
||||
* @brief Emitted when the deck should be opened in the deck editor.
|
||||
* @param deck LoadedDeck containing the deck data.
|
||||
* @param loader Initialized DeckLoader containing the deck data.
|
||||
*/
|
||||
void openInDeckEditor(const LoadedDeck &deck);
|
||||
void openInDeckEditor(DeckLoader *loader);
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
void retranslateUi();
|
||||
|
||||
/**
|
||||
* @brief Opens the deck in the deck editor.
|
||||
* @brief Opens the deck in the deck editor via DeckLoader.
|
||||
*/
|
||||
void actOpenInDeckEditor();
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
#include <version_string.h>
|
||||
|
||||
#define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg"
|
||||
|
||||
static QString timeAgo(const QString ×tamp)
|
||||
QString timeAgo(const QString ×tamp)
|
||||
{
|
||||
QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate);
|
||||
|
||||
@@ -83,7 +82,6 @@ ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplay
|
||||
imageUrl = response.getFeatured().isEmpty() ? QUrl(ARCHIDEKT_DEFAULT_IMAGE) : QUrl(response.getFeatured());
|
||||
|
||||
QNetworkRequest req(imageUrl);
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
QNetworkReply *reply = imageNetworkManager->get(req);
|
||||
|
||||
// tag the reply with "this" so we know it belongs to us later
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
|
||||
#include <libcockatrice/models/database/card/card_search_model.h>
|
||||
#include <version_string.h>
|
||||
|
||||
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
|
||||
{
|
||||
@@ -214,7 +213,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
|
||||
minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); // Exact = unset, ≥ = GTE, ≤ = LTE
|
||||
minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE
|
||||
|
||||
connect(minDeckSizeSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
|
||||
connect(minDeckSizeSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
|
||||
connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
|
||||
|
||||
// Page number
|
||||
@@ -224,7 +223,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
|
||||
pageSpin->setRange(1, 9999);
|
||||
pageSpin->setValue(1);
|
||||
|
||||
connect(pageSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
|
||||
connect(pageSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
|
||||
|
||||
// Page display
|
||||
currentPageDisplay = new QWidget(container);
|
||||
@@ -427,21 +426,18 @@ void TabArchidekt::doSearchImmediate()
|
||||
{
|
||||
QString url = buildSearchUrl();
|
||||
QNetworkRequest req{QUrl(url)};
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
networkManager->get(req);
|
||||
}
|
||||
|
||||
void TabArchidekt::actNavigatePage(QString url)
|
||||
{
|
||||
QNetworkRequest request{QUrl(url)};
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
networkManager->get(request);
|
||||
}
|
||||
|
||||
void TabArchidekt::getTopDecks()
|
||||
{
|
||||
QNetworkRequest request{QUrl(buildSearchUrl())};
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
networkManager->get(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@ void EdhrecDeckApiResponse::fromJson(const QJsonArray &json)
|
||||
deckList += cardlistValue.toString() + "\n";
|
||||
}
|
||||
|
||||
deckLoader = new DeckLoader(nullptr);
|
||||
|
||||
QTextStream stream(&deckList);
|
||||
deck.loadFromStream_Plain(stream, true);
|
||||
deckLoader->getDeckList()->loadFromStream_Plain(stream, true);
|
||||
}
|
||||
|
||||
void EdhrecDeckApiResponse::debugPrint() const
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
DeckList deck;
|
||||
DeckLoader *deckLoader;
|
||||
};
|
||||
|
||||
#endif // EDHREC_DECK_API_RESPONSE_H
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
|
||||
#include <libcockatrice/models/database/card/card_search_model.h>
|
||||
#include <version_string.h>
|
||||
|
||||
static bool canBeCommander(const CardInfoPtr &cardInfo)
|
||||
{
|
||||
@@ -167,7 +166,6 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander)
|
||||
}
|
||||
|
||||
QNetworkRequest request{QUrl(url)};
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
networkManager->get(request);
|
||||
}
|
||||
@@ -175,7 +173,6 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander)
|
||||
void TabEdhRecMain::actNavigatePage(QString url)
|
||||
{
|
||||
QNetworkRequest request{QUrl("https://json.edhrec.com/pages" + url + ".json")};
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
networkManager->get(request);
|
||||
}
|
||||
@@ -183,7 +180,6 @@ void TabEdhRecMain::actNavigatePage(QString url)
|
||||
void TabEdhRecMain::getTopCards()
|
||||
{
|
||||
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/top/year.json")};
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
networkManager->get(request);
|
||||
}
|
||||
@@ -191,7 +187,6 @@ void TabEdhRecMain::getTopCards()
|
||||
void TabEdhRecMain::getTopCommanders()
|
||||
{
|
||||
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/commanders/year.json")};
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
networkManager->get(request);
|
||||
}
|
||||
@@ -199,7 +194,7 @@ void TabEdhRecMain::getTopCommanders()
|
||||
void TabEdhRecMain::getTopTags()
|
||||
{
|
||||
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/tags.json")};
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
networkManager->get(request);
|
||||
}
|
||||
|
||||
@@ -363,7 +358,7 @@ void TabEdhRecMain::processAverageDeckResponse(QJsonObject reply)
|
||||
{
|
||||
EdhrecAverageDeckApiResponse deckData;
|
||||
deckData.fromJson(reply);
|
||||
tabSupervisor->openDeckInNewTab({deckData.deck.deck, {}});
|
||||
tabSupervisor->openDeckInNewTab(deckData.deck.deckLoader);
|
||||
}
|
||||
|
||||
void TabEdhRecMain::prettyPrintJson(const QJsonValue &value, int indentLevel)
|
||||
|
||||
@@ -12,6 +12,7 @@ class CardDatabaseDisplayModel;
|
||||
class DeckListModel;
|
||||
|
||||
class QLabel;
|
||||
class DeckLoader;
|
||||
|
||||
/**
|
||||
* @class TabDeckEditor
|
||||
|
||||
@@ -242,10 +242,10 @@ void TabDeckStorage::actOpenLocalDeck()
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
|
||||
auto deckLoader = new DeckLoader(this);
|
||||
if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true))
|
||||
if (!deckLoader->loadFromFile(filePath, DeckLoader::CockatriceFormat, true))
|
||||
continue;
|
||||
|
||||
emit openDeckEditor(deckLoader->getDeck());
|
||||
emit openDeckEditor(deckLoader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,15 +307,13 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa
|
||||
QFile deckFile(filePath);
|
||||
QFileInfo deckFileInfo(deckFile);
|
||||
|
||||
DeckLoader deckLoader(this);
|
||||
if (!deckLoader.loadFromFile(filePath, DeckFileFormat::Cockatrice)) {
|
||||
DeckLoader deck(this);
|
||||
if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
|
||||
return;
|
||||
}
|
||||
|
||||
DeckList deck = deckLoader.getDeck().deckList;
|
||||
|
||||
if (deck.getName().isEmpty()) {
|
||||
if (deck.getDeckList()->getName().isEmpty()) {
|
||||
bool ok;
|
||||
QString deckName =
|
||||
getTextWithMax(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"),
|
||||
@@ -324,12 +322,12 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa
|
||||
return;
|
||||
if (deckName.isEmpty())
|
||||
deckName = tr("Unnamed deck");
|
||||
deck.setName(deckName);
|
||||
deck.getDeckList()->setName(deckName);
|
||||
} else {
|
||||
deck.setName(deck.getName().left(MAX_NAME_LENGTH));
|
||||
deck.getDeckList()->setName(deck.getDeckList()->getName().left(MAX_NAME_LENGTH));
|
||||
}
|
||||
|
||||
QString deckString = deck.writeToString_Native();
|
||||
QString deckString = deck.getDeckList()->writeToString_Native();
|
||||
if (deckString.length() > MAX_FILE_LENGTH) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
|
||||
return;
|
||||
@@ -438,7 +436,7 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont
|
||||
if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id()))
|
||||
return;
|
||||
|
||||
emit openDeckEditor(loader.getDeck());
|
||||
emit openDeckEditor(&loader);
|
||||
}
|
||||
|
||||
void TabDeckStorage::actDownload()
|
||||
@@ -494,12 +492,8 @@ void TabDeckStorage::downloadFinished(const Response &r,
|
||||
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
|
||||
QString filePath = extraData.toString();
|
||||
|
||||
DeckList deckList = DeckList(QString::fromStdString(resp.deck()));
|
||||
|
||||
DeckLoader deckLoader(this);
|
||||
deckLoader.setDeck({deckList, {}});
|
||||
|
||||
deckLoader.saveToFile(filePath, DeckFileFormat::Cockatrice);
|
||||
DeckLoader deck(this, new DeckList(QString::fromStdString(resp.deck())));
|
||||
deck.saveToFile(filePath, DeckLoader::CockatriceFormat);
|
||||
}
|
||||
|
||||
void TabDeckStorage::actNewFolder()
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
|
||||
struct LoadedDeck;
|
||||
class ServerInfo_User;
|
||||
class AbstractClient;
|
||||
class QTreeView;
|
||||
@@ -24,6 +23,7 @@ class QTreeWidgetItem;
|
||||
class QGroupBox;
|
||||
class CommandContainer;
|
||||
class Response;
|
||||
class DeckLoader;
|
||||
|
||||
class TabDeckStorage : public Tab
|
||||
{
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
return tr("Deck Storage");
|
||||
}
|
||||
signals:
|
||||
void openDeckEditor(const LoadedDeck &deck);
|
||||
void openDeckEditor(DeckLoader *deckLoader);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -749,10 +749,11 @@ void TabGame::loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerIn
|
||||
{
|
||||
TabbedDeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
|
||||
if (playerInfo.has_deck_list()) {
|
||||
DeckList deckList = DeckList(QString::fromStdString(playerInfo.deck_list()));
|
||||
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(deckList.getCardRefList()));
|
||||
deckViewContainer->playerDeckView->setDeck(deckList);
|
||||
localPlayer->setDeck(deckList);
|
||||
DeckLoader newDeck(this, new DeckList(QString::fromStdString(playerInfo.deck_list())));
|
||||
CardPictureLoader::cacheCardPixmaps(
|
||||
CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList()));
|
||||
deckViewContainer->playerDeckView->setDeck(newDeck);
|
||||
localPlayer->setDeck(newDeck);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class ReplayTimelineWidget;
|
||||
class CardZone;
|
||||
class AbstractCardItem;
|
||||
class CardItem;
|
||||
class DeckLoader;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
class GameReplay;
|
||||
@@ -118,7 +119,7 @@ signals:
|
||||
void containerProcessingStarted(const GameEventContext &context);
|
||||
void containerProcessingDone();
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void openDeckEditor(const LoadedDeck &deck);
|
||||
void openDeckEditor(DeckLoader *deck);
|
||||
void notIdle();
|
||||
|
||||
void phaseChanged(int phase);
|
||||
|
||||
@@ -133,10 +133,10 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
|
||||
|
||||
// create tabs menu actions
|
||||
aTabDeckEditor = new QAction(this);
|
||||
connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(LoadedDeck()); });
|
||||
connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(nullptr); });
|
||||
|
||||
aTabVisualDeckEditor = new QAction(this);
|
||||
connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(LoadedDeck()); });
|
||||
connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(nullptr); });
|
||||
|
||||
aTabEdhRec = new QAction(this);
|
||||
connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); });
|
||||
@@ -846,9 +846,9 @@ void TabSupervisor::talkLeft(TabMessage *tab)
|
||||
/**
|
||||
* Creates a new deck editor tab and loads the deck into it.
|
||||
* Creates either a classic or visual deck editor tab depending on settings
|
||||
* @param deckToOpen The deck to open in the tab.
|
||||
* @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance.
|
||||
*/
|
||||
void TabSupervisor::openDeckInNewTab(const LoadedDeck &deckToOpen)
|
||||
void TabSupervisor::openDeckInNewTab(DeckLoader *deckToOpen)
|
||||
{
|
||||
int type = SettingsCache::instance().getDefaultDeckEditorType();
|
||||
switch (type) {
|
||||
@@ -868,12 +868,13 @@ void TabSupervisor::openDeckInNewTab(const LoadedDeck &deckToOpen)
|
||||
|
||||
/**
|
||||
* Creates a new deck editor tab
|
||||
* @param deckToOpen The deck to open in the tab.
|
||||
* @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance.
|
||||
*/
|
||||
TabDeckEditor *TabSupervisor::addDeckEditorTab(const LoadedDeck &deckToOpen)
|
||||
TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen)
|
||||
{
|
||||
auto *tab = new TabDeckEditor(this);
|
||||
tab->openDeck(deckToOpen);
|
||||
if (deckToOpen)
|
||||
tab->openDeck(deckToOpen);
|
||||
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
|
||||
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
|
||||
myAddTab(tab);
|
||||
@@ -882,10 +883,11 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const LoadedDeck &deckToOpen)
|
||||
return tab;
|
||||
}
|
||||
|
||||
TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(const LoadedDeck &deckToOpen)
|
||||
TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(DeckLoader *deckToOpen)
|
||||
{
|
||||
auto *tab = new TabDeckEditorVisual(this);
|
||||
tab->openDeck(deckToOpen);
|
||||
if (deckToOpen)
|
||||
tab->openDeck(deckToOpen);
|
||||
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
|
||||
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab);
|
||||
myAddTab(tab);
|
||||
|
||||
@@ -168,9 +168,9 @@ signals:
|
||||
void showWindowIfHidden();
|
||||
|
||||
public slots:
|
||||
void openDeckInNewTab(const LoadedDeck &deckToOpen);
|
||||
TabDeckEditor *addDeckEditorTab(const LoadedDeck &deckToOpen);
|
||||
TabDeckEditorVisual *addVisualDeckEditorTab(const LoadedDeck &deckToOpen);
|
||||
void openDeckInNewTab(DeckLoader *deckToOpen);
|
||||
TabDeckEditor *addDeckEditorTab(DeckLoader *deckToOpen);
|
||||
TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen);
|
||||
TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab();
|
||||
TabEdhRecMain *addEdhrecMainTab();
|
||||
TabArchidekt *addArchidektTab();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../../interface/pixel_map_generator.h"
|
||||
#include "../../interface/widgets/cards/card_info_frame_widget.h"
|
||||
#include "../../interface/widgets/deck_analytics/deck_analytics_widget.h"
|
||||
#include "../../interface/widgets/general/tutorial/tutorial_controller.h"
|
||||
#include "../../interface/widgets/visual_deck_editor/visual_deck_editor_widget.h"
|
||||
#include "../tab_deck_editor.h"
|
||||
#include "../tab_supervisor.h"
|
||||
@@ -50,6 +51,31 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra
|
||||
|
||||
loadLayout();
|
||||
databaseDisplayDockWidget->setHidden(true);
|
||||
tutorialController = new TutorialController(this);
|
||||
|
||||
auto sequence = TutorialSequence();
|
||||
|
||||
sequence.addStep({tabContainer->visualDeckView, "View your deck here.",
|
||||
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDeckView); }});
|
||||
sequence.addStep({printingSelectorDockWidget, "Change the printings in your deck here."});
|
||||
|
||||
tutorialController->addSequence(sequence);
|
||||
|
||||
auto vddSequence = tabContainer->visualDatabaseDisplay->addTutorialSteps();
|
||||
vddSequence.steps.prepend({tabContainer->visualDatabaseDisplay, "View the database here",
|
||||
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDatabaseDisplay); }});
|
||||
|
||||
tutorialController->addSequence(vddSequence);
|
||||
}
|
||||
|
||||
void TabDeckEditorVisual::showEvent(QShowEvent *ev)
|
||||
{
|
||||
QWidget::showEvent(ev);
|
||||
if (!tutorialStarted) {
|
||||
tutorialStarted = true;
|
||||
// Start on next event loop iteration so everything is fully painted
|
||||
QTimer::singleShot(0, tutorialController, [this] { tutorialController->start(); });
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Creates the central frame containing the tab container. */
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "../tab.h"
|
||||
#include "tab_deck_editor_visual_tab_widget.h"
|
||||
|
||||
class TutorialController;
|
||||
/**
|
||||
* @class TabDeckEditorVisual
|
||||
* @ingroup DeckEditorTabs
|
||||
@@ -55,7 +56,12 @@ class TabDeckEditorVisual : public AbstractTabDeckEditor
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
TutorialController *tutorialController = nullptr;
|
||||
bool tutorialStarted = false;
|
||||
|
||||
protected slots:
|
||||
void showEvent(QShowEvent *ev) override;
|
||||
/**
|
||||
* @brief Load the editor layout from settings.
|
||||
*/
|
||||
|
||||
@@ -24,11 +24,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
|
||||
|
||||
void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath)
|
||||
{
|
||||
auto deckLoader = DeckLoader(this);
|
||||
if (!deckLoader.loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) {
|
||||
auto deckLoader = new DeckLoader(this);
|
||||
if (!deckLoader->loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
emit openDeckEditor(deckLoader.getDeck());
|
||||
emit openDeckEditor(deckLoader);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
#include "../tab.h"
|
||||
|
||||
struct LoadedDeck;
|
||||
class AbstractClient;
|
||||
class CommandContainer;
|
||||
class DeckLoader;
|
||||
class DeckPreviewWidget;
|
||||
class QFileSystemModel;
|
||||
class QGroupBox;
|
||||
@@ -39,7 +39,7 @@ public slots:
|
||||
void actOpenLocalDeck(const QString &filePath);
|
||||
|
||||
signals:
|
||||
void openDeckEditor(const LoadedDeck &deck);
|
||||
void openDeckEditor(DeckLoader *deckLoader);
|
||||
|
||||
private:
|
||||
VisualDeckStorageWidget *visualDeckStorageWidget;
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
#include "visual_database_display_format_legality_filter_widget.h"
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QTimer>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/filters/filter_tree.h>
|
||||
|
||||
VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLegalityFilterWidget(
|
||||
QWidget *parent,
|
||||
FilterTreeModel *_filterModel)
|
||||
: QWidget(parent), filterModel(_filterModel)
|
||||
{
|
||||
allFormatsWithCount = CardDatabaseManager::query()->getAllFormatsWithCount();
|
||||
|
||||
setMaximumHeight(75);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
|
||||
|
||||
layout = new QHBoxLayout(this);
|
||||
setLayout(layout);
|
||||
layout->setContentsMargins(0, 1, 0, 1);
|
||||
layout->setSpacing(1);
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||
layout->addWidget(flowWidget);
|
||||
|
||||
// Create the spinbox
|
||||
spinBox = new QSpinBox(this);
|
||||
spinBox->setMinimum(1);
|
||||
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
|
||||
spinBox->setValue(150);
|
||||
layout->addWidget(spinBox);
|
||||
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility);
|
||||
|
||||
// Create the toggle button for Exact Match/Includes mode
|
||||
toggleButton = new QPushButton(this);
|
||||
toggleButton->setCheckable(true);
|
||||
layout->addWidget(toggleButton);
|
||||
connect(toggleButton, &QPushButton::toggled, this,
|
||||
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode);
|
||||
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
|
||||
QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel);
|
||||
});
|
||||
|
||||
createFormatButtons(); // Populate buttons initially
|
||||
updateFilterMode(false); // Initialize toggle button text
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFormatLegalityFilterWidget::retranslateUi()
|
||||
{
|
||||
spinBox->setToolTip(tr("Do not display formats with less than this amount of cards in the database"));
|
||||
toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)"));
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons()
|
||||
{
|
||||
// Iterate through main types and create buttons
|
||||
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
|
||||
auto *button = new QPushButton(it.key(), flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
formatButtons[it.key()] = button;
|
||||
|
||||
// Connect toggle signal
|
||||
connect(button, &QPushButton::toggled, this,
|
||||
[this, mainType = it.key()](bool checked) { handleFormatToggled(mainType, checked); });
|
||||
}
|
||||
updateFormatButtonsVisibility(); // Ensure visibility is updated initially
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility()
|
||||
{
|
||||
int threshold = spinBox->value(); // Get the current spinbox value
|
||||
|
||||
// Iterate through buttons and hide/disable those below the threshold
|
||||
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
|
||||
bool visible = allFormatsWithCount[it.key()] >= threshold;
|
||||
it.value()->setVisible(visible);
|
||||
it.value()->setEnabled(visible);
|
||||
}
|
||||
}
|
||||
|
||||
int VisualDatabaseDisplayFormatLegalityFilterWidget::getMaxMainTypeCount() const
|
||||
{
|
||||
int maxCount = 1;
|
||||
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
|
||||
maxCount = qMax(maxCount, it.value());
|
||||
}
|
||||
return maxCount;
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFormatLegalityFilterWidget::handleFormatToggled(const QString &format, bool active)
|
||||
{
|
||||
activeFormats[format] = active;
|
||||
|
||||
if (formatButtons.contains(format)) {
|
||||
formatButtons[format]->setChecked(active);
|
||||
}
|
||||
|
||||
updateFormatFilter();
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatFilter()
|
||||
{
|
||||
// Clear existing filters related to main type
|
||||
filterModel->blockSignals(true);
|
||||
filterModel->filterTree()->blockSignals(true);
|
||||
filterModel->clearFiltersOfType(CardFilter::Attr::AttrFormat);
|
||||
|
||||
if (exactMatchMode) {
|
||||
// Exact Match: Only selected main types are allowed
|
||||
QSet<QString> selectedTypes;
|
||||
for (const auto &type : activeFormats.keys()) {
|
||||
if (activeFormats[type]) {
|
||||
selectedTypes.insert(type);
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedTypes.isEmpty()) {
|
||||
// Require all selected types (TypeAnd)
|
||||
for (const auto &type : selectedTypes) {
|
||||
QString typeString = type;
|
||||
filterModel->addFilter(
|
||||
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat));
|
||||
}
|
||||
|
||||
// Exclude any other types (TypeAndNot)
|
||||
for (const auto &type : formatButtons.keys()) {
|
||||
if (!selectedTypes.contains(type)) {
|
||||
QString typeString = type;
|
||||
filterModel->addFilter(
|
||||
new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Default Includes Mode (TypeOr) - match any selected main types
|
||||
for (const auto &type : activeFormats.keys()) {
|
||||
if (activeFormats[type]) {
|
||||
QString typeString = type;
|
||||
filterModel->addFilter(
|
||||
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterModel->blockSignals(false);
|
||||
filterModel->filterTree()->blockSignals(false);
|
||||
|
||||
emit filterModel->filterTree()->changed();
|
||||
emit filterModel->layoutChanged();
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode(bool checked)
|
||||
{
|
||||
exactMatchMode = checked;
|
||||
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
|
||||
updateFormatFilter();
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel()
|
||||
{
|
||||
// Temporarily block signals for each button to prevent toggling while updating button states
|
||||
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
|
||||
it.value()->blockSignals(true);
|
||||
}
|
||||
|
||||
// Uncheck all buttons
|
||||
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
|
||||
it.value()->setChecked(false);
|
||||
}
|
||||
|
||||
// Get active filters for main types
|
||||
QSet<QString> activeTypes;
|
||||
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrFormat)) {
|
||||
if (filter->type() == CardFilter::Type::TypeAnd) {
|
||||
activeTypes.insert(filter->term());
|
||||
}
|
||||
}
|
||||
|
||||
// Check the buttons for active types
|
||||
for (const auto &type : activeTypes) {
|
||||
activeFormats[type] = true;
|
||||
if (formatButtons.contains(type)) {
|
||||
formatButtons[type]->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable signal emissions for each button
|
||||
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
|
||||
it.value()->blockSignals(false);
|
||||
}
|
||||
|
||||
// Update the visibility of buttons
|
||||
updateFormatButtonsVisibility();
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H
|
||||
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class VisualDatabaseDisplayFormatLegalityFilterWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VisualDatabaseDisplayFormatLegalityFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
|
||||
void retranslateUi();
|
||||
void createFormatButtons();
|
||||
void updateFormatButtonsVisibility();
|
||||
int getMaxMainTypeCount() const;
|
||||
|
||||
void handleFormatToggled(const QString &format, bool active);
|
||||
void updateFormatFilter();
|
||||
void updateFilterMode(bool checked);
|
||||
void syncWithFilterModel();
|
||||
|
||||
private:
|
||||
FilterTreeModel *filterModel;
|
||||
QMap<QString, int> allFormatsWithCount;
|
||||
QSpinBox *spinBox;
|
||||
QHBoxLayout *layout;
|
||||
FlowWidget *flowWidget;
|
||||
QPushButton *toggleButton; // Mode switch button
|
||||
|
||||
QMap<QString, bool> activeFormats; // Track active filters
|
||||
QMap<QString, QPushButton *> formatButtons; // Store toggle buttons
|
||||
|
||||
bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes"
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H
|
||||
@@ -33,7 +33,7 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi
|
||||
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
|
||||
spinBox->setValue(150);
|
||||
layout->addWidget(spinBox);
|
||||
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
|
||||
&VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility);
|
||||
|
||||
// Create the toggle button for Exact Match/Includes mode
|
||||
|
||||
@@ -51,7 +51,7 @@ VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWi
|
||||
|
||||
void VisualDatabaseDisplayNameFilterWidget::retranslateUi()
|
||||
{
|
||||
searchBox->setPlaceholderText(tr("Filter by name... (Exact match)"));
|
||||
searchBox->setPlaceholderText(tr("Filter by name..."));
|
||||
loadFromDeckButton->setText(tr("Load from Deck"));
|
||||
loadFromDeckButton->setToolTip(tr("Apply all card names in currently loaded deck as exact match name filters"));
|
||||
loadFromClipboardButton->setText(tr("Load from Clipboard"));
|
||||
@@ -64,10 +64,14 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck()
|
||||
|
||||
if (!deckListModel)
|
||||
return;
|
||||
DeckList *decklist = deckListModel->getDeckList();
|
||||
if (!decklist)
|
||||
return;
|
||||
|
||||
QList<QString> cardNames = deckListModel->getCardNames();
|
||||
for (auto cardName : cardNames) {
|
||||
createNameFilter(cardName);
|
||||
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
|
||||
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
createNameFilter(currentCard->getName());
|
||||
}
|
||||
|
||||
updateFilterModel();
|
||||
@@ -79,7 +83,7 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard()
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
QStringList cardsInClipboard = dlg.getDeckList().getCardList();
|
||||
QStringList cardsInClipboard = dlg.getDeckList()->getDeckList()->getCardList();
|
||||
for (QString cardName : cardsInClipboard) {
|
||||
createNameFilter(cardName);
|
||||
}
|
||||
@@ -119,14 +123,14 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel()
|
||||
{
|
||||
// Clear existing name filters
|
||||
emit filterModel->layoutAboutToBeChanged();
|
||||
filterModel->clearFiltersOfType(CardFilter::Attr::AttrNameExact);
|
||||
filterModel->clearFiltersOfType(CardFilter::Attr::AttrName);
|
||||
|
||||
filterModel->blockSignals(true);
|
||||
filterModel->filterTree()->blockSignals(true);
|
||||
|
||||
for (const auto &name : activeFilters.keys()) {
|
||||
QString nameString = name;
|
||||
filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrNameExact));
|
||||
filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrName));
|
||||
}
|
||||
|
||||
filterModel->blockSignals(false);
|
||||
@@ -142,7 +146,7 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel()
|
||||
void VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel()
|
||||
{
|
||||
QStringList currentFilters;
|
||||
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrNameExact)) {
|
||||
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrName)) {
|
||||
if (filter->type() == CardFilter::Type::TypeOr) {
|
||||
currentFilters.append(filter->term());
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentS
|
||||
filterToMostRecentSetsAmount->setMaximum(100);
|
||||
filterToMostRecentSetsAmount->setValue(
|
||||
SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsAmount());
|
||||
connect(filterToMostRecentSetsAmount, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
|
||||
connect(filterToMostRecentSetsAmount, QOverload<int>::of(&QSpinBox::valueChanged), &SettingsCache::instance(),
|
||||
&SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount);
|
||||
|
||||
layout->addWidget(filterToMostRecentSetsCheckBox);
|
||||
|
||||
@@ -26,7 +26,7 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg
|
||||
spinBox->setMaximum(getMaxSubTypeCount());
|
||||
spinBox->setValue(150);
|
||||
layout->addWidget(spinBox);
|
||||
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
|
||||
&VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility);
|
||||
|
||||
// Create search box
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../../../filters/syntax_help.h"
|
||||
#include "../../pixel_map_generator.h"
|
||||
#include "../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../general/tutorial/tutorial_controller.h"
|
||||
#include "../quick_settings/settings_button_widget.h"
|
||||
#include "../utility/custom_line_edit.h"
|
||||
#include "visual_database_display_color_filter_widget.h"
|
||||
@@ -192,6 +193,28 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps()
|
||||
{
|
||||
auto sequence = TutorialSequence();
|
||||
sequence.addStep({colorFilterWidget, "Filter the database by colors with these controls"});
|
||||
sequence.addStep({displayModeButton, "You can change back to the old table display-style with this button."});
|
||||
sequence.addStep({filterContainer, "Use these controls for quick access to common filters."});
|
||||
sequence.addStep(
|
||||
{quickFilterSaveLoadWidget, "This button will let you save and load all currently applied filters to files."});
|
||||
sequence.addStep({quickFilterNameWidget,
|
||||
"This button will let you apply name filters. Optionally, you can import every card in "
|
||||
"your deck as a name filter and then save this as a filter using the save/load button "
|
||||
"to make your own quick access collections!"});
|
||||
sequence.addStep({mainTypeFilterWidget, "Use these buttons to quickly filter by card types."});
|
||||
sequence.addStep({quickFilterSubTypeWidget, "This button will let you apply filters for card sub-types."});
|
||||
sequence.addStep(
|
||||
{quickFilterSetWidget,
|
||||
"This button will let you apply filters for card sets. You can also filter to the X most recent sets. "
|
||||
"Filtering to a set will display all printings of a card within that set."});
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayWidget::initialize()
|
||||
{
|
||||
databaseLoadIndicator->setVisible(false);
|
||||
@@ -206,7 +229,6 @@ void VisualDatabaseDisplayWidget::initialize()
|
||||
saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel);
|
||||
nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel);
|
||||
mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel);
|
||||
formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel);
|
||||
subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel);
|
||||
setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel);
|
||||
|
||||
@@ -224,7 +246,6 @@ void VisualDatabaseDisplayWidget::initialize()
|
||||
filterContainerLayout->addWidget(quickFilterSubTypeWidget);
|
||||
filterContainerLayout->addWidget(quickFilterSetWidget);
|
||||
filterContainerLayout->addWidget(mainTypeFilterWidget);
|
||||
filterContainerLayout->addWidget(formatLegalityWidget);
|
||||
|
||||
searchLayout->addWidget(colorFilterWidget);
|
||||
searchLayout->addWidget(clearFilterWidget);
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
#include "../cards/card_size_widget.h"
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "../general/layout_containers/overlap_control_widget.h"
|
||||
#include "../general/tutorial/tutorial_controller.h"
|
||||
#include "../utility/custom_line_edit.h"
|
||||
#include "visual_database_display_color_filter_widget.h"
|
||||
#include "visual_database_display_filter_save_load_widget.h"
|
||||
#include "visual_database_display_format_legality_filter_widget.h"
|
||||
#include "visual_database_display_main_type_filter_widget.h"
|
||||
#include "visual_database_display_name_filter_widget.h"
|
||||
#include "visual_database_display_set_filter_widget.h"
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
#include <qscrollarea.h>
|
||||
|
||||
class TutorialController;
|
||||
inline Q_LOGGING_CATEGORY(VisualDatabaseDisplayLog, "visual_database_display");
|
||||
|
||||
class VisualDatabaseDisplayWidget : public QWidget
|
||||
@@ -60,6 +61,7 @@ public:
|
||||
VisualDatabaseDisplayColorFilterWidget *colorFilterWidget;
|
||||
|
||||
public slots:
|
||||
TutorialSequence addTutorialSteps();
|
||||
void searchModelChanged();
|
||||
|
||||
signals:
|
||||
@@ -92,7 +94,6 @@ private:
|
||||
SettingsButtonWidget *quickFilterNameWidget;
|
||||
VisualDatabaseDisplayNameFilterWidget *nameFilterWidget;
|
||||
VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget;
|
||||
VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget;
|
||||
SettingsButtonWidget *quickFilterSubTypeWidget;
|
||||
VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget;
|
||||
SettingsButtonWidget *quickFilterSetWidget;
|
||||
|
||||
@@ -24,9 +24,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare
|
||||
handSizeSpinBox = new QSpinBox(this);
|
||||
handSizeSpinBox->setValue(SettingsCache::instance().getVisualDeckEditorSampleHandSize());
|
||||
handSizeSpinBox->setMinimum(1);
|
||||
connect(handSizeSpinBox, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
|
||||
connect(handSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), &SettingsCache::instance(),
|
||||
&SettingsCache::setVisualDeckEditorSampleHandSize);
|
||||
connect(handSizeSpinBox, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
connect(handSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
|
||||
&VisualDeckEditorSampleHandWidget::updateDisplay);
|
||||
resetAndHandSizeLayout->addWidget(handSizeSpinBox);
|
||||
|
||||
@@ -76,11 +76,25 @@ void VisualDeckEditorSampleHandWidget::updateDisplay()
|
||||
|
||||
QList<ExactCard> VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet)
|
||||
{
|
||||
QList<ExactCard> mainDeckCards;
|
||||
QList<ExactCard> randomCards;
|
||||
if (!deckListModel)
|
||||
return randomCards;
|
||||
DeckList *decklist = deckListModel->getDeckList();
|
||||
if (!decklist)
|
||||
return randomCards;
|
||||
|
||||
QList<ExactCard> mainDeckCards = deckListModel->getCardsForZone(DECK_ZONE_MAIN);
|
||||
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes({DECK_ZONE_MAIN});
|
||||
|
||||
// Collect all cards in the main deck, allowing duplicates based on their count
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef());
|
||||
if (card) {
|
||||
mainDeckCards.append(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mainDeckCards.isEmpty())
|
||||
return randomCards;
|
||||
|
||||
@@ -79,8 +79,8 @@ static QStringList findAllKnownTags()
|
||||
QStringList knownTags;
|
||||
auto loader = DeckLoader(nullptr);
|
||||
for (const QString &file : allFiles) {
|
||||
loader.loadFromFile(file, DeckFileFormat::getFormatFromName(file), false);
|
||||
QStringList tags = loader.getDeck().deckList.getTags();
|
||||
loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false);
|
||||
QStringList tags = loader.getDeckList()->getTags();
|
||||
knownTags.append(tags);
|
||||
knownTags.removeDuplicates();
|
||||
}
|
||||
@@ -125,7 +125,7 @@ static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
|
||||
static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget)
|
||||
{
|
||||
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
|
||||
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getDeck().lastLoadInfo.fileName;
|
||||
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
|
||||
deckPreviewWidget->refreshBannerCardText();
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget)
|
||||
*/
|
||||
bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget)
|
||||
{
|
||||
if (DeckFileFormat::getFormatFromName(deckPreviewWidget->filePath) == DeckFileFormat::Cockatrice) {
|
||||
if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) == DeckLoader::CockatriceFormat) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent,
|
||||
many deck loads have finished already and if we've loaded all decks and THEN load all the tags at once. */
|
||||
connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget,
|
||||
&VisualDeckStorageTagFilterWidget::refreshTags);
|
||||
deckLoader->loadFromFileAsync(filePath, DeckFileFormat::getFormatFromName(filePath), false);
|
||||
deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false);
|
||||
|
||||
bannerCardDisplayWidget =
|
||||
new DeckPreviewCardPictureWidget(this, false, visualDeckStorageWidget->deckPreviewSelectionAnimationEnabled);
|
||||
@@ -74,16 +74,16 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
|
||||
if (!deckLoadSuccess) {
|
||||
return;
|
||||
}
|
||||
auto bannerCard = deckLoader->getDeck().deckList.getBannerCard().name.isEmpty()
|
||||
auto bannerCard = deckLoader->getDeckList()->getBannerCard().name.isEmpty()
|
||||
? ExactCard()
|
||||
: CardDatabaseManager::query()->getCard(deckLoader->getDeck().deckList.getBannerCard());
|
||||
: CardDatabaseManager::query()->getCard(deckLoader->getDeckList()->getBannerCard());
|
||||
|
||||
bannerCardDisplayWidget->setCard(bannerCard);
|
||||
bannerCardDisplayWidget->setFontSize(24);
|
||||
setFilePath(deckLoader->getDeck().lastLoadInfo.fileName);
|
||||
setFilePath(deckLoader->getLastLoadInfo().fileName);
|
||||
|
||||
colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity());
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeck().deckList.getTags());
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()->getTags());
|
||||
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags);
|
||||
|
||||
bannerCardLabel = new QLabel(this);
|
||||
@@ -91,7 +91,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
|
||||
bannerCardComboBox = new QComboBox(this);
|
||||
bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
bannerCardComboBox->setObjectName("bannerCardComboBox");
|
||||
bannerCardComboBox->setCurrentText(deckLoader->getDeck().deckList.getBannerCard().name);
|
||||
bannerCardComboBox->setCurrentText(deckLoader->getDeckList()->getBannerCard().name);
|
||||
bannerCardComboBox->installEventFilter(new NoScrollFilter());
|
||||
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DeckPreviewWidget::setBannerCard);
|
||||
@@ -153,7 +153,7 @@ void DeckPreviewWidget::updateTagsVisibility(bool visible)
|
||||
|
||||
QString DeckPreviewWidget::getColorIdentity()
|
||||
{
|
||||
QStringList cardList = deckLoader->getDeck().deckList.getCardList();
|
||||
QStringList cardList = deckLoader->getDeckList()->getCardList();
|
||||
if (cardList.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
@@ -187,8 +187,8 @@ QString DeckPreviewWidget::getColorIdentity()
|
||||
*/
|
||||
QString DeckPreviewWidget::getDisplayName() const
|
||||
{
|
||||
QString deckName = deckLoader->getDeck().deckList.getName();
|
||||
return !deckName.isEmpty() ? deckName : QFileInfo(deckLoader->getDeck().lastLoadInfo.fileName).fileName();
|
||||
return deckLoader->getDeckList()->getName().isEmpty() ? QFileInfo(deckLoader->getLastLoadInfo().fileName).fileName()
|
||||
: deckLoader->getDeckList()->getName();
|
||||
}
|
||||
|
||||
void DeckPreviewWidget::setFilePath(const QString &_filePath)
|
||||
@@ -235,7 +235,7 @@ void DeckPreviewWidget::updateBannerCardComboBox()
|
||||
// Prepare the new items with deduplication
|
||||
QSet<QPair<QString, QString>> bannerCardSet;
|
||||
|
||||
QList<const DecklistCardNode *> cardsInDeck = deckLoader->getDeck().deckList.getCardNodes();
|
||||
QList<DecklistCardNode *> cardsInDeck = deckLoader->getDeckList()->getCardNodes();
|
||||
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
@@ -269,7 +269,7 @@ void DeckPreviewWidget::updateBannerCardComboBox()
|
||||
bannerCardComboBox->setCurrentIndex(restoredIndex);
|
||||
} else {
|
||||
// Add a placeholder "-" and set it as the current selection
|
||||
int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeck().deckList.getBannerCard().name);
|
||||
int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeckList()->getBannerCard().name);
|
||||
if (bannerIndex != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(bannerIndex);
|
||||
} else {
|
||||
@@ -287,8 +287,8 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */)
|
||||
{
|
||||
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
|
||||
CardRef cardRef = {name, id};
|
||||
deckLoader->getDeck().deckList.setBannerCard(cardRef);
|
||||
deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath));
|
||||
deckLoader->getDeckList()->setBannerCard(cardRef);
|
||||
deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath));
|
||||
bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef));
|
||||
}
|
||||
|
||||
@@ -310,8 +310,8 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC
|
||||
|
||||
void DeckPreviewWidget::setTags(const QStringList &tags)
|
||||
{
|
||||
deckLoader->getDeck().deckList.setTags(tags);
|
||||
deckLoader->saveToFile(filePath, DeckFileFormat::Cockatrice);
|
||||
deckLoader->getDeckList()->setTags(tags);
|
||||
deckLoader->saveToFile(filePath, DeckLoader::CockatriceFormat);
|
||||
}
|
||||
|
||||
QMenu *DeckPreviewWidget::createRightClickMenu()
|
||||
@@ -320,7 +320,7 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this,
|
||||
[this] { emit openDeckEditor(deckLoader->getDeck()); });
|
||||
[this] { emit openDeckEditor(deckLoader); });
|
||||
|
||||
connect(menu->addAction(tr("Edit Tags")), &QAction::triggered, deckTagsDisplayWidget,
|
||||
&DeckPreviewDeckTagsDisplayWidget::openTagEditDlg);
|
||||
@@ -334,13 +334,13 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
|
||||
auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard"));
|
||||
|
||||
connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this,
|
||||
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, true); });
|
||||
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, true); });
|
||||
connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this,
|
||||
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, false); });
|
||||
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, false); });
|
||||
connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this,
|
||||
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, true); });
|
||||
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, true); });
|
||||
connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this,
|
||||
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, false); });
|
||||
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, false); });
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
@@ -376,7 +376,7 @@ void DeckPreviewWidget::addSetBannerCardMenu(QMenu *menu)
|
||||
void DeckPreviewWidget::actRenameDeck()
|
||||
{
|
||||
// read input
|
||||
const QString oldName = deckLoader->getDeck().deckList.getName();
|
||||
const QString oldName = deckLoader->getDeckList()->getName();
|
||||
|
||||
bool ok;
|
||||
QString newName = QInputDialog::getText(this, "Rename deck", tr("New name:"), QLineEdit::Normal, oldName, &ok);
|
||||
@@ -385,8 +385,8 @@ void DeckPreviewWidget::actRenameDeck()
|
||||
}
|
||||
|
||||
// write change
|
||||
deckLoader->getDeck().deckList.setName(newName);
|
||||
deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath));
|
||||
deckLoader->getDeckList()->setName(newName);
|
||||
deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath));
|
||||
|
||||
// update VDS
|
||||
refreshBannerCardText();
|
||||
@@ -416,7 +416,9 @@ void DeckPreviewWidget::actRenameFile()
|
||||
return;
|
||||
}
|
||||
|
||||
deckLoader->getDeck().lastLoadInfo.fileName = newFilePath;
|
||||
DeckLoader::LoadInfo lastLoadInfo = deckLoader->getLastLoadInfo();
|
||||
lastLoadInfo.fileName = newFilePath;
|
||||
deckLoader->setLastLoadInfo(lastLoadInfo);
|
||||
|
||||
// update VDS
|
||||
setFilePath(newFilePath);
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
|
||||
signals:
|
||||
void deckLoadRequested(const QString &filePath);
|
||||
void openDeckEditor(const LoadedDeck &deck);
|
||||
void openDeckEditor(DeckLoader *deck);
|
||||
|
||||
public slots:
|
||||
void setFilePath(const QString &filePath);
|
||||
|
||||
@@ -211,7 +211,7 @@ QStringList VisualDeckStorageFolderDisplayWidget::gatherAllTagsFromFlowWidget()
|
||||
// Iterate through all DeckPreviewWidgets
|
||||
for (DeckPreviewWidget *display : flowWidget->findChildren<DeckPreviewWidget *>()) {
|
||||
// Get tags from each DeckPreviewWidget
|
||||
QStringList tags = display->deckLoader->getDeck().deckList.getTags();
|
||||
QStringList tags = display->deckLoader->getDeckList()->getTags();
|
||||
|
||||
// Add tags to the list while avoiding duplicates
|
||||
allTags.append(tags);
|
||||
|
||||
@@ -61,10 +61,10 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg
|
||||
unusedColorIdentitiesOpacitySpinBox->setMaximum(100);
|
||||
unusedColorIdentitiesOpacitySpinBox->setValue(
|
||||
SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity());
|
||||
connect(unusedColorIdentitiesOpacitySpinBox, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
|
||||
&VisualDeckStorageQuickSettingsWidget::unusedColorIdentitiesOpacityChanged);
|
||||
connect(unusedColorIdentitiesOpacitySpinBox, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
|
||||
&SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
|
||||
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||
&SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
|
||||
|
||||
unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox);
|
||||
|
||||
|
||||
@@ -95,17 +95,14 @@ QList<DeckPreviewWidget *> VisualDeckStorageSortWidget::filterFiles(QList<DeckPr
|
||||
|
||||
switch (sortOrder) {
|
||||
case ByName:
|
||||
return widget1->deckLoader->getDeck().deckList.getName() <
|
||||
widget2->deckLoader->getDeck().deckList.getName();
|
||||
return widget1->deckLoader->getDeckList()->getName() < widget2->deckLoader->getDeckList()->getName();
|
||||
case Alphabetical:
|
||||
return QString::localeAwareCompare(info1.fileName(), info2.fileName()) <= 0;
|
||||
case ByLastModified:
|
||||
return info1.lastModified() > info2.lastModified();
|
||||
case ByLastLoaded: {
|
||||
QDateTime time1 =
|
||||
QDateTime::fromString(widget1->deckLoader->getDeck().deckList.getLastLoadedTimestamp());
|
||||
QDateTime time2 =
|
||||
QDateTime::fromString(widget2->deckLoader->getDeck().deckList.getLastLoadedTimestamp());
|
||||
QDateTime time1 = QDateTime::fromString(widget1->deckLoader->getDeckList()->getLastLoadedTimestamp());
|
||||
QDateTime time2 = QDateTime::fromString(widget2->deckLoader->getDeckList()->getLastLoadedTimestamp());
|
||||
return time1 > time2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList<Dec
|
||||
}
|
||||
|
||||
for (DeckPreviewWidget *deckPreview : deckPreviews) {
|
||||
QStringList deckTags = deckPreview->deckLoader->getDeck().deckList.getTags();
|
||||
QStringList deckTags = deckPreview->deckLoader->getDeckList()->getTags();
|
||||
|
||||
bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(),
|
||||
[&deckTags](const QString &tag) { return deckTags.contains(tag); });
|
||||
@@ -153,7 +153,7 @@ QSet<QString> VisualDeckStorageTagFilterWidget::gatherAllTags() const
|
||||
|
||||
for (DeckPreviewWidget *widget : deckWidgets) {
|
||||
if (widget->checkVisibility()) {
|
||||
for (const QString &tag : widget->deckLoader->getDeck().deckList.getTags()) {
|
||||
for (const QString &tag : widget->deckLoader->getDeckList()->getTags()) {
|
||||
allTags.insert(tag);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user