Compare commits

..

1 Commits

Author SHA1 Message Date
Brübach, Lukas
a2af648e00 [VDE] Be saner about proxy indices 2025-12-05 04:14:53 +01:00
327 changed files with 6032 additions and 13070 deletions

View File

@@ -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")
@@ -156,18 +156,6 @@ function ccachestatsverbose() {
# Compile
if [[ $RUNNER_OS == macOS ]]; then
# QTDIR is needed for macOS since we actually only use the cached thin Qt binaries instead of the install-qt-action,
# which sets a few environment variables
if QTDIR=$(find "$GITHUB_WORKSPACE/Qt" -depth -maxdepth 2 -name macos -type d -print -quit); then
echo "found QTDIR at $QTDIR"
else
echo "could not find QTDIR!"
exit 2
fi
# the qtdir is located at Qt/[qtversion]/macos
# we use find to get the first subfolder with the name "macos"
# this works independent of the qt version as there should be only one version installed on the runner at a time
export QTDIR
if [[ $TARGET_MACOS_VERSION ]]; then
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
@@ -258,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"

View File

@@ -1,40 +0,0 @@
#!/bin/bash
# This script is used to resolve the latest patch version of Qt using aqtinstall.
# It interprets wildcards to get the latest patch version. E.g. "6.6.*" -> "6.6.3".
# This script is meant to be used by the ci enironment.
# It uses the runner's GITHUB_OUTPUT env variable.
# Usage example: .ci/resolve_latest_aqt_qt_version.sh "6.6.*"
qt_spec=$1
if [[ ! $qt_spec ]]; then
echo "usage: $0 [version]"
exit 2
fi
# If version is already specific (no wildcard), use it as-is
if [[ $qt_spec != *"*" ]]; then
echo "version $qt_spec is already resolved"
echo "version=$qt_spec" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! hash aqt; then
echo "aqt could not be found, has aqtinstall been installed?"
exit 2
fi
# Resolve latest patch
if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
echo "resolved $qt_spec to $qt_resolved"
if [[ ! $qt_resolved ]]; then
echo "Error: Could not resolve Qt version for $qt_spec"
exit 1
fi
echo "version=$qt_resolved" >> "$GITHUB_OUTPUT"

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# The macos binaries from aqt are fat (universal), so we thin them to the target architecture to reduce the size of
# the packages and caches using lipo.
# This script is meant to be used by the ci enironment on macos runners only.
# It uses the runner's GITHUB_WORKSPACE env variable.
arch=$(uname -m)
nproc=$(sysctl -n hw.ncpu)
function thin() {
local libfile=$1
if [[ $(file -b --mime-type "$libfile") == application/x-mach-binary* ]]; then
echo "Processing $libfile"
lipo "$libfile" -thin "$arch" -output "$libfile"
fi
return 0
}
export -f thin # export to allow use in xargs
export arch
set -eo pipefail
echo "::group::Thinning Qt libraries to $arch using $nproc cores"
find "$GITHUB_WORKSPACE/Qt" -type f -print0 | xargs -0 -n1 -P"$nproc" -I{} bash -c "thin '{}'"
echo "::endgroup::"

View File

@@ -142,6 +142,7 @@ jobs:
- distro: Ubuntu
version: 22.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 24.04
@@ -165,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:
@@ -204,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 }}
@@ -212,7 +213,7 @@ jobs:
- name: Upload artifact
id: upload_artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}}
@@ -262,6 +263,7 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false # qt caches take too much space for macOS (1.1Gi)
cmake_generator: Ninja
use_ccache: 1
@@ -277,6 +279,7 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
use_ccache: 1
@@ -292,6 +295,7 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
use_ccache: 1
@@ -304,6 +308,7 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
use_ccache: 1
@@ -316,6 +321,7 @@ jobs:
artifact_name: Windows7-installer
qt_version: 5.15.*
qt_arch: win64_msvc2019_64
cache_qt: true
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
@@ -329,17 +335,13 @@ jobs:
qt_version: 6.6.*
qt_arch: win64_msvc2019_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: true
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
runs-on: ${{matrix.runner}}
env:
CCACHE_DIR: ${{github.workspace}}/.cache/
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 500M
steps:
- name: Checkout
@@ -354,72 +356,24 @@ jobs:
with:
msbuild-architecture: x64
# Using jianmingyong/ccache-action to setup ccache without using brew
# It tries to download a binary of ccache from GitHub Release and falls back to building from source if it fails
- name: Setup ccache
if: matrix.use_ccache == 1 && matrix.os == 'macOS'
run: brew install ccache
- name: Restore compiler cache (ccache)
if: matrix.use_ccache == 1
id: ccache_restore
uses: actions/cache/restore@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
uses: jianmingyong/ccache-action@v1
with:
path: ${{env.CCACHE_DIR}}
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
install-type: "binary"
ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}
max-size: 500M
gh-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install aqtinstall
if: matrix.os == 'macOS'
run: pipx install aqtinstall
# Checking if there's a newer, uncached version of Qt available to install via aqtinstall
- name: Resolve latest Qt patch version
if: matrix.os == 'macOS'
id: resolve_qt_version
shell: bash
# Ouputs the version of Qt to install via aqtinstall
run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}"
- name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v5
with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
# Using jurplel/install-qt-action to install Qt without using brew
# qt build using vcpkg either just fails or takes too long to build
- name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
cache: false
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
dir: ${{github.workspace}}
- name: Thin Qt libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- name: Cache thin Qt libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
- name: Install Qt ${{matrix.qt_version}} (Windows)
if: matrix.os == 'Windows'
- name: Install Qt ${{matrix.qt_version}}
uses: jurplel/install-qt-action@v4
with:
version: ${{matrix.qt_version}}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: true
cache: ${{matrix.cache_qt}}
- name: Setup vcpkg cache
id: vcpkg-cache
@@ -449,15 +403,6 @@ jobs:
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
run: .ci/compile.sh --server --test --vcpkg
- name: Save compiler cache (ccache)
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1
uses: actions/cache/save@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
path: ${{env.CCACHE_DIR}}
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
- name: Sign app bundle
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
env:
@@ -505,7 +450,7 @@ jobs:
- name: Upload artifact
id: upload_artifact
if: matrix.make_package
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: ${{matrix.artifact_name}}
path: ${{steps.build.outputs.path}}
@@ -513,7 +458,7 @@ jobs:
- name: Upload pdb database
if: matrix.os == 'Windows'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: Windows${{matrix.target}}-debug-pdbs
path: |

View File

@@ -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

View File

@@ -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

3
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
[submodule "doxygen-awesome-css"]
path = doc/doxygen/theme
url = https://github.com/jothepro/doxygen-awesome-css.git

View File

@@ -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)

View File

@@ -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

View File

@@ -54,7 +54,7 @@ PROJECT_NUMBER = $(COCKATRICE_REF)
# for a project that appears at the top of each page and should give viewers a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "A virtual tabletop for multiplayer card games"
PROJECT_BRIEF = "A cross-platform virtual tabletop for multiplayer card games"
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
@@ -1068,8 +1068,7 @@ RECURSIVE = YES
EXCLUDE = build/ \
cmake/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
dbconverter/ \
vcpkg/ \
webclient/
@@ -1432,9 +1431,7 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = doc/doxygen/theme/doxygen-awesome.css \
doc/doxygen/css/hide_nav_sync.css \
doc/doxygen/css/cockatrice_docs_style.css
HTML_EXTRA_STYLESHEET = doc/doxygen/css/doxygen_style.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@@ -1457,7 +1454,7 @@ HTML_EXTRA_FILES = doc/doxygen/js/graph_toggle.js
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = LIGHT # required with doxygen-awesome-css theme, Auto Dark Mode will still work
HTML_COLORSTYLE = AUTO_DARK
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
@@ -1768,7 +1765,7 @@ ECLIPSE_DOC_ID = org.doxygen.Project
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
DISABLE_INDEX = NO # YES is bugged in the theme, see jothepro/doxygen-awesome-css/issues/201
DISABLE_INDEX = YES
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag
@@ -1806,7 +1803,7 @@ PAGE_OUTLINE_PANEL = YES
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
FULL_SIDEBAR = NO # required for doxygen-awesome-css theme
FULL_SIDEBAR = NO
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# Doxygen will group on one line in the generated HTML documentation.

View File

@@ -44,10 +44,9 @@ 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
- [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)
- [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 webpage of the Cockatrice project
# Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
@@ -58,28 +57,11 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p
- [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
![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats")
<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 +75,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 [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](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 [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](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 [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](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 +124,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>

View File

@@ -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

View File

@@ -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)
@@ -59,7 +63,7 @@ if(Qt6_FOUND)
endif()
else()
find_package(
Qt5 5.15.2
Qt5 5.8.0
COMPONENTS ${REQUIRED_QT_COMPONENTS}
QUIET HINTS ${Qt5_DIR}
)
@@ -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)

View File

@@ -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"

View File

@@ -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")

View File

@@ -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
@@ -144,54 +141,28 @@ set(cockatrice_SOURCES
src/interface/widgets/cards/card_size_widget.cpp
src/interface/widgets/cards/deck_card_zone_display_widget.cpp
src/interface/widgets/cards/deck_preview_card_picture_widget.cpp
src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp
src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp
src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp
src/interface/widgets/deck_analytics/deck_analytics_widget.cpp
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
src/interface/widgets/deck_analytics/resizable_panel.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
src/interface/widgets/deck_analytics/mana_base_widget.cpp
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
src/interface/widgets/deck_analytics/mana_devotion_widget.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
src/interface/widgets/deck_editor/deck_state_manager.cpp
src/interface/widgets/general/background_sources.cpp
src/interface/widgets/general/display/background_plate_widget.cpp
src/interface/widgets/general/display/banner_widget.cpp
src/interface/widgets/general/display/bar_widget.cpp
src/interface/widgets/general/display/color_bar.cpp
src/interface/widgets/general/display/dynamic_font_size_label.cpp
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
src/interface/widgets/general/display/labeled_input.cpp
src/interface/widgets/general/display/percent_bar_widget.cpp
src/interface/widgets/general/display/shadow_background_label.cpp
src/interface/widgets/general/display/charts/bars/bar_widget.cpp
src/interface/widgets/general/display/charts/bars/color_bar.cpp
src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp
src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp
src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
src/interface/widgets/general/display/charts/pies/color_pie.cpp
src/interface/widgets/general/home_styled_button.cpp
src/interface/widgets/general/home_widget.cpp
src/interface/widgets/general/layout_containers/flow_widget.cpp
@@ -228,7 +199,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
@@ -256,7 +226,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
@@ -313,10 +282,6 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/interface/key_signals.cpp
src/interface/logger.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
)
add_subdirectory(sounds)

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,6 @@
[Rules]
# The default log level is info
*.debug = false
#*.info = true
#*.warning = true
#*.critical = true
#*.fatal = true
# Uncomment a rule to see debug level logs for that category,
# or set <category> = false to disable logging

View File

@@ -1,13 +1,11 @@
@page deck_search_syntax_help Deck Search Syntax Help
## Deck Search Syntax Help
-----
The search bar recognizes a set of special commands.<br>
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all
searches are case insensitive.
<dl>
<dt>Display Name (The deck name, or the filename if the deck name isn't set):</dt>
<dd>[red deck wins](#red deck wins) <small>(Any deck with a display name containing the words red, deck, and wins)</small></dd>
<dd>["red deck wins"](#%22red deck wins%22) <small>(Any deck with a display name containing the exact phrase "red deck wins")</small></dd>
@@ -17,23 +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><u>C</u>omments:</dt>
<dd>[c:good](#c:good) <small>(Any deck with comments containing the word good)</small></dd>
<dd>[c:good c:deck](#c:good c:deck) <small>(Any deck with comments containing the words good and deck)</small></dd>
<dd>[c:"good deck"](#c:%22good deck%22) <small>(Any deck with comments containing the exact phrase "good deck")</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>
@@ -49,4 +39,4 @@ searches are case insensitive.
<dt>Grouping:</dt>
<dd><a href="#red -([[]]:100 or aggro)">red -([[]]:100 or aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
</dl>
</dl>

View File

@@ -1,12 +1,10 @@
@page search_syntax_help Search Syntax Help
## Search Syntax Help
-----
The search bar recognizes a set of special commands similar to some other card databases.<br>
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
<dl>
<dt>Name:</dt>
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
<dd>["birds of paradise"](#%22birds of paradise%22) <small>(Any card name containing the exact phrase "birds of paradise")</small></dd>
@@ -67,4 +65,4 @@ In this list of examples below, each entry has an explanation and can be clicked
<dd>[o:/counter target .* spell/](#o:/counter target .* spell/) <small>(Any card text with "counter target *something* spell")</small></dd>
<dd>[o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) <small>(/'s can be escaped with a \)</small></dd>
</dl>
</dl>

View File

@@ -350,11 +350,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="left" />
id="right" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="right"
id="left"
inkscape:connector-curvature="0" />
<path
style="display:inline;fill:url(#linearGradient3);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.77952756;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -321,11 +321,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="left" />
id="right" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="right"
id="left"
inkscape:connector-curvature="0" />
<path
d="m 46.656521,12.167234 18.055171,18.054184 a 6.6081919,6.6078288 0 0 1 -0.126303,9.352065 6.6804126,6.6800456 0 0 1 -8.233169,1.011048 l -7.944268,7.943843 6.463762,6.445343 a 6.9331851,6.9328042 0 0 1 5.741536,2.022073 l 28.057729,28.092294 a 6.9962797,6.9958953 0 0 1 -9.894222,9.893685 L 50.719018,66.907526 A 7.0595711,7.0591833 0 0 1 49.18433,59.270613 l -5.741527,-5.741238 -7.944298,7.943843 A 6.716523,6.7161541 0 0 1 25.134866,69.832263 L 7.079684,51.778091 a 6.716523,6.7161541 0 0 1 8.39566,-10.345064 L 36.31101,20.59853 a 6.716523,6.7161541 0 0 1 10.345612,-8.431329 z"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -340,11 +340,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="left" />
id="right" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="right"
id="left"
inkscape:connector-curvature="0" />
<path
sodipodi:type="star"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -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)
@@ -43,32 +42,31 @@ void DeckStatsInterface::queryFinished(QNetworkReply *reply)
deleteLater();
}
void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
{
DeckList deckWithoutTokens;
copyDeckWithoutTokens(deck, deckWithoutTokens);
copyDeckWithoutTokens(*deck, deckWithoutTokens);
QUrl params;
QUrlQuery urlQuery;
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
urlQuery.addQueryItem("decktitle", deck.getName());
urlQuery.addQueryItem("decktitle", deck->getName());
params.setQuery(urlQuery);
data.append(params.query(QUrl::EncodeReserved).toUtf8());
data->append(params.query(QUrl::EncodeReserved).toUtf8());
}
void DeckStatsInterface::analyzeDeck(const DeckList &deck)
void DeckStatsInterface::analyzeDeck(DeckList *deck)
{
QByteArray data;
getAnalyzeRequestData(deck, data);
getAnalyzeRequestData(deck, &data);
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);
}
void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination)
void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination)
{
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());

View File

@@ -28,15 +28,15 @@ private:
* closest non-token card instead. So we construct a new deck which has no
* tokens.
*/
void copyDeckWithoutTokens(const DeckList &source, DeckList &destination);
void copyDeckWithoutTokens(DeckList &source, DeckList &destination);
private slots:
void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
public:
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
void analyzeDeck(DeckList *deck);
};
#endif

View File

@@ -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)
@@ -67,33 +66,32 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
deleteLater();
}
void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
{
DeckList mainboard, sideboard;
copyDeckSplitMainAndSide(deck, mainboard, sideboard);
copyDeckSplitMainAndSide(*deck, mainboard, sideboard);
QUrl params;
QUrlQuery urlQuery;
urlQuery.addQueryItem("name", deck.getName());
urlQuery.addQueryItem("name", deck->getName());
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
params.setQuery(urlQuery);
data.append(params.query(QUrl::EncodeReserved).toUtf8());
data->append(params.query(QUrl::EncodeReserved).toUtf8());
}
void TappedOutInterface::analyzeDeck(const DeckList &deck)
void TappedOutInterface::analyzeDeck(DeckList *deck)
{
QByteArray data;
getAnalyzeRequestData(deck, data);
getAnalyzeRequestData(deck, &data);
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);
}
void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard)
{
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());

View File

@@ -30,14 +30,14 @@ private:
QNetworkAccessManager *manager;
CardDatabase &cardDatabase;
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard);
private slots:
void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
public:
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
void analyzeDeck(DeckList *deck);
};
#endif

View File

@@ -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;
}
};

View File

@@ -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)

View File

@@ -2,7 +2,6 @@
#include "../network/update/client/release_channel.h"
#include "card_counter_settings.h"
#include "version_string.h"
#include <QAbstractListModel>
#include <QApplication>
@@ -199,13 +198,7 @@ SettingsCache::SettingsCache()
mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
if (settings->contains("personal/startupUpdateCheck")) {
checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool();
} else if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) {
checkUpdatesOnStartup = false; // do not run auto updater on custom version
} else {
checkUpdatesOnStartup = true; // default to run auto updater
}
checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool();
startupCardUpdateCheckPromptForUpdate =
settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
@@ -213,15 +206,7 @@ SettingsCache::SettingsCache()
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
if (settings->contains("personal/updatereleasechannel")) {
updateReleaseChannel = settings->value("personal/updatereleasechannel").toInt();
} else if (QString(VERSION_STRING).contains("beta", Qt::CaseInsensitive)) {
// default to beta if this is a beta release
updateReleaseChannel = 1;
} else {
updateReleaseChannel = 0; // stable
}
updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt();
lang = settings->value("personal/lang").toString();
keepalive = settings->value("personal/keepalive", 3).toInt();
@@ -288,7 +273,6 @@ SettingsCache::SettingsCache()
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
roundCardCorners = settings->value("cards/roundcardcorners", true).toBool();
overrideAllCardArtWithPersonalPreference =
@@ -716,13 +700,6 @@ void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts)
settings->setValue("menu/showshortcuts", showShortcuts);
}
void SettingsCache::setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar)
{
showGameSelectorFilterToolbar = static_cast<bool>(_showGameSelectorFilterToolbar);
settings->setValue("menu/showgameselectorfiltertoolbar", showGameSelectorFilterToolbar);
emit showGameSelectorFilterToolbarChanged(showGameSelectorFilterToolbar);
}
void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames)
{
displayCardNames = static_cast<bool>(_displayCardNames);

View File

@@ -145,7 +145,6 @@ signals:
void homeTabBackgroundShuffleFrequencyChanged();
void picDownloadChanged();
void showStatusBarChanged(bool state);
void showGameSelectorFilterToolbarChanged(bool state);
void displayCardNamesChanged();
void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference);
void bumpSetsWithCardsInDeckToTopChanged();
@@ -237,7 +236,6 @@ private:
bool annotateTokens;
QByteArray tabGameSplitterSizes;
bool showShortcuts;
bool showGameSelectorFilterToolbar;
bool displayCardNames;
bool overrideAllCardArtWithPersonalPreference;
bool bumpSetsWithCardsInDeckToTop;
@@ -555,10 +553,6 @@ public:
{
return showShortcuts;
}
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const
{
return showGameSelectorFilterToolbar;
}
[[nodiscard]] bool getDisplayCardNames() const
{
return displayCardNames;
@@ -1023,7 +1017,6 @@ public slots:
void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens);
void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes);
void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts);
void setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar);
void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames);
void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt);
void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop);

View File

@@ -150,7 +150,12 @@ void ShortcutTreeView::currentChanged(const QModelIndex &current, const QModelIn
*/
void ShortcutTreeView::updateSearchString(const QString &searchString)
{
QStringList searchWords = searchString.split(" ", Qt::SkipEmptyParts);
#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
const auto skipEmptyParts = Qt::SkipEmptyParts;
#else
const auto skipEmptyParts = QString::SkipEmptyParts;
#endif
QStringList searchWords = searchString.split(" ", skipEmptyParts);
auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); };
std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex);

View File

@@ -660,12 +660,6 @@ private:
{"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"),
parseSequenceString("Ctrl+M"),
ShortcutGroup::Drawing)},
{"Player/aMulliganSame", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Same hand size)"),
parseSequenceString("Ctrl+Shift+M"),
ShortcutGroup::Drawing)},
{"Player/aMulliganMinusOne", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Hand size - 1)"),
parseSequenceString("Ctrl+Shift+Alt+M"),
ShortcutGroup::Drawing)},
{"Player/aDrawCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw a Card"),
parseSequenceString("Ctrl+D"),
ShortcutGroup::Drawing)},

View File

@@ -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 / CommentQuery / GenericQuery
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
@@ -22,10 +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
CommentQuery <- [Cc] ('omment' 's'?)? [:] String
GenericQuery <- String
@@ -120,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);
};
};
@@ -140,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);
};
};
@@ -159,22 +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["CommentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto value = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
auto comments = deck->deckLoader->getDeck().deckList.getComments();
return comments.contains(value, Qt::CaseInsensitive);
};
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {

View File

@@ -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());

View File

@@ -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();
}

View File

@@ -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

View File

@@ -117,7 +117,11 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option
} else {
chooseTokenFromDeckRadioButton->setChecked(true);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
#else
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
#endif
}
auto *tokenChooseLayout = new QVBoxLayout;
@@ -219,7 +223,11 @@ void DlgCreateToken::actChooseTokenFromAll(bool checked)
void DlgCreateToken::actChooseTokenFromDeck(bool checked)
{
if (checked) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
#else
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
#endif
}
}

View File

@@ -60,16 +60,6 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T
connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan);
addAction(aMulligan);
// Mulligan same size
aMulliganSame = new QAction(this);
connect(aMulliganSame, &QAction::triggered, actions, &PlayerActions::actMulliganSameSize);
addAction(aMulliganSame);
// Mulligan -1
aMulliganMinusOne = new QAction(this);
connect(aMulliganMinusOne, &QAction::triggered, actions, &PlayerActions::actMulliganMinusOne);
addAction(aMulliganMinusOne);
addSeparator();
mMoveHandMenu = addTearOffMenu(QString());
@@ -114,9 +104,7 @@ void HandMenu::retranslateUi()
aSortHandByType->setText(tr("Type"));
aSortHandByManaValue->setText(tr("Mana Value"));
aMulligan->setText(tr("Take &mulligan (Choose hand size)"));
aMulliganSame->setText(tr("Take mulligan (Same hand size)"));
aMulliganMinusOne->setText(tr("Take mulligan (Hand size - 1)"));
aMulligan->setText(tr("Take &mulligan"));
mMoveHandMenu->setTitle(tr("&Move hand to..."));
aMoveHandToTopLibrary->setText(tr("&Top of library"));
@@ -140,8 +128,6 @@ void HandMenu::setShortcutsActive()
aSortHandByType->setShortcuts(shortcuts.getShortcut("Player/aSortHandByType"));
aSortHandByManaValue->setShortcuts(shortcuts.getShortcut("Player/aSortHandByManaValue"));
aMulligan->setShortcuts(shortcuts.getShortcut("Player/aMulligan"));
aMulliganSame->setShortcuts(shortcuts.getShortcut("Player/aMulliganSame"));
aMulliganMinusOne->setShortcuts(shortcuts.getShortcut("Player/aMulliganMinusOne"));
aRevealHandToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealHandToAll"));
aRevealRandomHandCardToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealRandomHandCardToAll"));
}

View File

@@ -46,8 +46,6 @@ private:
QAction *aViewHand = nullptr;
QAction *aMulligan = nullptr;
QAction *aMulliganSame = nullptr;
QAction *aMulliganMinusOne = nullptr;
QMenu *mSortHand = nullptr;
QAction *aSortHandByName = nullptr;

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -218,7 +218,7 @@ void PlayerActions::actAlwaysLookAtTopCard()
void PlayerActions::actOpenDeckInDeckEditor()
{
emit player->openDeckEditor({.deckList = player->getDeck()});
emit player->openDeckEditor(player->getDeck());
}
void PlayerActions::actViewGraveyard()
@@ -310,48 +310,28 @@ void PlayerActions::actMulligan()
{
int startSize = SettingsCache::instance().getStartingHandSize();
int handSize = player->getHandZone()->getCards().size();
int deckSize = player->getDeckZone()->getCards().size() + handSize;
int deckSize = player->getDeckZone()->getCards().size() + handSize; // hand is shuffled back into the deck
bool ok;
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"),
tr("Number of cards: (max. %1)").arg(deckSize) + '\n' +
tr("0 and lower are in comparison to current hand size"),
startSize, -handSize, deckSize, 1, &ok);
if (!ok) {
return;
}
if (number < 1) {
number = handSize + number;
}
doMulligan(number);
SettingsCache::instance().setStartingHandSize(number);
}
void PlayerActions::actMulliganSameSize()
{
int handSize = player->getHandZone()->getCards().size();
doMulligan(handSize);
}
void PlayerActions::actMulliganMinusOne()
{
int handSize = player->getHandZone()->getCards().size();
int targetSize = qMax(1, handSize - 1);
doMulligan(targetSize);
}
void PlayerActions::doMulligan(int number)
{
if (number < 1) {
return;
}
Command_Mulligan cmd;
cmd.set_number(number);
if (number < 1) {
if (handSize == 0) {
return;
}
cmd.set_number(handSize + number);
} else {
cmd.set_number(number);
}
sendGameCommand(cmd);
if (startSize != number) {
SettingsCache::instance().setStartingHandSize(number);
}
}
void PlayerActions::actDrawCards()

View File

@@ -85,9 +85,6 @@ public slots:
void actDrawCards();
void actUndoDraw();
void actMulligan();
void actMulliganSameSize();
void actMulliganMinusOne();
void doMulligan(int number);
void actPlay();
void actPlayFacedown();

View File

@@ -78,7 +78,14 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
void PlayerEventHandler::eventRollDie(const Event_RollDie &event)
{
if (!event.values().empty()) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QList<uint> rolls(event.values().begin(), event.values().end());
#else
QList<uint> rolls;
for (const auto &value : event.values()) {
rolls.append(value);
}
#endif
std::sort(rolls.begin(), rolls.end());
emit logRollDie(player, static_cast<int>(event.sides()), rolls);
} else if (event.value()) {

View File

@@ -13,20 +13,12 @@
#include <QGraphicsLinearLayout>
#include <QGraphicsProxyWidget>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QLabel>
#include <QPainter>
#include <QScrollBar>
#include <QStyle>
#include <QStyleOption>
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
namespace
{
constexpr qreal kTitleBarHeight = 24.0;
constexpr qreal kMinVisibleWidth = 100.0;
} // namespace
/**
* @param _player the player the cards were revealed to.
* @param _origZone the zone the cards were revealed from.
@@ -249,191 +241,33 @@ void ZoneViewWidget::retranslateUi()
pileViewCheckBox.setText(tr("pile view"));
}
void ZoneViewWidget::stopWindowDrag()
void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */)
{
if (!draggingWindow)
if (!scene())
return;
draggingWindow = false;
ungrabMouse();
}
int titleBarHeight = 24;
void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event)
{
draggingWindow = true;
dragStartItemPos = pos();
dragStartScreenPos = event->screenPos();
dragView = findDragView(event->widget());
QPointF scenePos = pos();
// need to grab mouse to receive events and not miss initial movement
grabMouse();
}
QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const
{
const QRectF frameRectF = windowFrameRect();
// query the style for the close button position (handles macOS top-left placement)
// Title bar rect MUST be local (0,0-based) for QStyle
const QRect titleBarRect(0, 0, static_cast<int>(frameRectF.width()), static_cast<int>(kTitleBarHeight));
if (styleWidget) {
QStyleOptionTitleBar opt;
opt.initFrom(styleWidget);
opt.rect = titleBarRect;
opt.text = windowTitle();
opt.icon = styleWidget->windowIcon();
opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
opt.subControls = QStyle::SC_TitleBarCloseButton;
opt.activeSubControls = QStyle::SC_TitleBarCloseButton;
opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState;
if (styleWidget->isActiveWindow()) {
opt.state |= QStyle::State_Active;
}
QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton,
styleWidget);
if (r.isValid() && !r.isEmpty()) {
// Translate from local-titlebar coords → frame coords
r.translate(frameRectF.topLeft().toPoint());
return QRectF(r);
}
if (scenePos.x() < 0) {
scenePos.setX(0);
} else {
qreal maxw = scene()->sceneRect().width() - 100;
if (scenePos.x() > maxw)
scenePos.setX(maxw);
}
// Fallback: frame-relative top-right
return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight);
}
QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const
{
QWidget *current = eventWidget;
while (current) {
if (auto *view = qobject_cast<QGraphicsView *>(current))
return view;
current = current->parentWidget();
if (scenePos.y() < titleBarHeight) {
scenePos.setY(titleBarHeight);
} else {
qreal maxh = scene()->sceneRect().height() - titleBarHeight;
if (scenePos.y() > maxh)
scenePos.setY(maxh);
}
if (scene() && !scene()->views().isEmpty())
return scene()->views().constFirst();
return nullptr;
}
QPointF ZoneViewWidget::calcDraggedWindowPos(const QPoint &screenPos,
const QPointF &scenePos,
const QPointF &buttonDownScenePos) const
{
if (dragView && dragView->viewport()) {
const QPoint vpStart = dragView->viewport()->mapFromGlobal(dragStartScreenPos);
const QPoint vpNow = dragView->viewport()->mapFromGlobal(screenPos);
const QPointF sceneStart = dragView->mapToScene(vpStart);
const QPointF sceneNow = dragView->mapToScene(vpNow);
return dragStartItemPos + (sceneNow - sceneStart);
}
return dragStartItemPos + (scenePos - buttonDownScenePos);
}
bool ZoneViewWidget::windowFrameEvent(QEvent *event)
{
if (event->type() == QEvent::UngrabMouse) {
stopWindowDrag();
return QGraphicsWidget::windowFrameEvent(event);
}
auto *me = dynamic_cast<QGraphicsSceneMouseEvent *>(event);
if (!me)
return QGraphicsWidget::windowFrameEvent(event);
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:
if (me->button() == Qt::LeftButton && windowFrameSectionAt(me->pos()) == Qt::TitleBarArea) {
// avoid drag on close button
if (closeButtonRect(me->widget()).contains(me->pos())) {
me->accept();
close();
return true;
}
startWindowDrag(me);
me->accept();
return true;
}
break;
case QEvent::GraphicsSceneMouseMove:
if (draggingWindow) {
if (!(me->buttons() & Qt::LeftButton)) {
stopWindowDrag();
} else {
setPos(
calcDraggedWindowPos(me->screenPos(), me->scenePos(), me->buttonDownScenePos(Qt::LeftButton)));
}
me->accept();
return true;
}
break;
case QEvent::GraphicsSceneMouseRelease:
if (draggingWindow && me->button() == Qt::LeftButton) {
stopWindowDrag();
me->accept();
return true;
}
break;
default:
break;
}
return QGraphicsWidget::windowFrameEvent(event);
}
void ZoneViewWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// move if the scene routes moves while dragging
if (draggingWindow && (event->buttons() & Qt::LeftButton)) {
setPos(calcDraggedWindowPos(event->screenPos(), event->scenePos(), event->buttonDownScenePos(Qt::LeftButton)));
event->accept();
return;
}
QGraphicsWidget::mouseMoveEvent(event);
}
void ZoneViewWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (draggingWindow && event->button() == Qt::LeftButton) {
stopWindowDrag();
event->accept();
return;
}
QGraphicsWidget::mouseReleaseEvent(event);
}
QVariant ZoneViewWidget::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange && scene()) {
// Keep grab area in main view
const QRectF sceneRect = scene()->sceneRect();
const QPointF requestedPos = value.toPointF();
QPointF desiredPos = requestedPos;
const qreal minX = sceneRect.left();
const qreal maxX = qMax(minX, sceneRect.right() - kMinVisibleWidth);
const qreal minY = sceneRect.top() + kTitleBarHeight;
const qreal maxY = qMax(minY, sceneRect.bottom() - kTitleBarHeight);
desiredPos.setX(qBound(minX, desiredPos.x(), maxX));
desiredPos.setY(qBound(minY, desiredPos.y(), maxY));
return desiredPos;
}
return QGraphicsWidget::itemChange(change, value);
if (scenePos != pos())
setPos(scenePos);
}
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
@@ -516,7 +350,6 @@ void ZoneViewWidget::handleScrollBarChange(int value)
void ZoneViewWidget::closeEvent(QCloseEvent *event)
{
stopWindowDrag();
disconnect(zone, &ZoneViewZone::closed, this, 0);
// manually call zone->close in order to remove it from the origZones views
zone->close();

View File

@@ -3,6 +3,7 @@
* @ingroup GameGraphicsZones
* @brief TODO: Document this.
*/
#ifndef ZONEVIEWWIDGET_H
#define ZONEVIEWWIDGET_H
@@ -13,7 +14,6 @@
#include <QGraphicsProxyWidget>
#include <QGraphicsWidget>
#include <QLineEdit>
#include <QPointer>
#include <libcockatrice/utility/macros.h>
class QLabel;
@@ -28,8 +28,6 @@ class ServerInfo_Card;
class QGraphicsSceneMouseEvent;
class QGraphicsSceneWheelEvent;
class QStyleOption;
class QGraphicsView;
class QWidget;
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
{
@@ -54,6 +52,7 @@ private:
ZoneViewZone *zone;
QGraphicsWidget *zoneContainer;
QPushButton *closeButton;
QScrollBar *scrollBar;
ScrollableGraphicsProxyWidget *scrollBarProxy;
@@ -67,33 +66,6 @@ private:
int extraHeight;
Player *player;
bool draggingWindow = false;
QPoint dragStartScreenPos;
QPointF dragStartItemPos;
QPointer<QGraphicsView> dragView;
void stopWindowDrag();
void startWindowDrag(QGraphicsSceneMouseEvent *event);
QRectF closeButtonRect(QWidget *styleWidget) const;
/**
* @brief Resolves the QGraphicsView to use for drag coordinate mapping
*
* @param eventWidget QWidget that originated the mouse event
* @return The resolved QGraphicsView
*/
QGraphicsView *findDragView(QWidget *eventWidget) const;
/**
* @brief Calculates the desired widget position while dragging
*
* @param screenPos Global screen coordinates of the current mouse position
* @param scenePos Scene coordinates of the current mouse position
* @param buttonDownScenePos Scene coordinates of the initial mouse press position
*
* @return The new widget position in scene coordinates
*/
QPointF
calcDraggedWindowPos(const QPoint &screenPos, const QPointF &scenePos, const QPointF &buttonDownScenePos) const;
void resizeScrollbar(qreal newZoneHeight);
signals:
void closePressed(ZoneViewWidget *zv);
@@ -104,6 +76,7 @@ private slots:
void resizeToZoneContents(bool forceInitialHeight = false);
void handleScrollBarChange(int value);
void zoneDeleted();
void moveEvent(QGraphicsSceneMoveEvent * /* event */) override;
void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override;
void expandWindow();
@@ -128,10 +101,6 @@ public:
protected:
void closeEvent(QCloseEvent *event) override;
void initStyleOption(QStyleOption *option) const override;
bool windowFrameEvent(QEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
};

View File

@@ -17,7 +17,12 @@ void AbstractGraphicsItem::paintNumberEllipse(int number,
font.setWeight(QFont::Bold);
QFontMetrics fm(font);
double w = 1.3 * fm.horizontalAdvance(numStr);
double w = 1.3 *
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
fm.horizontalAdvance(numStr);
#else
fm.width(numStr);
#endif
double h = fm.height() * 1.3;
if (w < h)
w = h;

View File

@@ -10,7 +10,6 @@
#include <QNetworkReply>
#include <QThread>
#include <utility>
#include <version_string.h>
static constexpr int MAX_REQUESTS_PER_SEC = 10;
@@ -21,7 +20,9 @@ CardPictureLoaderWorker::CardPictureLoaderWorker()
// We need a timeout to ensure requests don't hang indefinitely in case of
// cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397
// Use Qt's default timeout (30s, as of 2023-02-22)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
networkManager->setTransferTimeout();
#endif
cache = new QNetworkDiskCache(this);
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
cache->setMaximumCacheSize(1024L * 1024L *
@@ -85,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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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,35 +269,39 @@ 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
*
* @param deckList The decklist to export
* @param website The website we're sending the deck to
*/
QString DeckLoader::exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website)
QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website)
{
// 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,7 +316,113 @@ QString DeckLoader::exportDeckToDecklist(const DeckList &deckList, DecklistWebsi
return deckString;
}
void DeckLoader::saveToClipboard(const DeckList &deckList, bool addComments, bool addSetNameAndNumber)
// 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;
QTextStream stream(&buffer);
@@ -320,7 +432,7 @@ void DeckLoader::saveToClipboard(const DeckList &deckList, bool addComments, boo
}
bool DeckLoader::saveToStream_Plain(QTextStream &out,
const DeckList &deckList,
const DeckList *deckList,
bool addComments,
bool addSetNameAndNumber)
{
@@ -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
@@ -339,14 +453,14 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
return true;
}
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList)
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList)
{
if (!deckList.getName().isEmpty()) {
out << "// " << deckList.getName() << "\n\n";
if (!deckList->getName().isEmpty()) {
out << "// " << deckList->getName() << "\n\n";
}
if (!deckList.getComments().isEmpty()) {
QStringList commentRows = deckList.getComments().split(QRegularExpression("\n|\r\n|\r"));
if (!deckList->getComments().isEmpty()) {
QStringList commentRows = deckList->getComments().split(QRegularExpression("\n|\r\n|\r"));
for (const QString &row : commentRows) {
out << "// " << row << "\n";
}
@@ -434,7 +548,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
}
}
bool DeckLoader::convertToCockatriceFormat(const QString &fileName)
bool DeckLoader::convertToCockatriceFormat(QString fileName)
{
// Change the file extension to .cod
QFileInfo fileInfo(fileName);
@@ -450,12 +564,12 @@ bool DeckLoader::convertToCockatriceFormat(const 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(const 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;
@@ -543,7 +680,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
cursor->movePosition(QTextCursor::End);
}
void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
{
QTextDocument doc;
@@ -559,18 +696,19 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
headerCharFormat.setFontWeight(QFont::Bold);
cursor.insertBlock(headerBlockFormat, headerCharFormat);
cursor.insertText(deckList.getName());
cursor.insertText(deckList->getName());
headerCharFormat.setFontPointSize(12);
cursor.insertBlock(headerBlockFormat, headerCharFormat);
cursor.insertText(deckList.getComments());
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);

View File

@@ -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,29 +60,47 @@ 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 QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website);
static void saveToClipboard(const DeckList &deckList, bool addComments = true, bool addSetNameAndNumber = true);
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,
const DeckList *deckList,
bool addComments = true,
bool addSetNameAndNumber = true);
@@ -72,26 +109,18 @@ public:
* @param printer The printer to render the decklist to.
* @param deckList
*/
static void printDeckList(QPrinter *printer, const DeckList &deckList);
static void printDeckList(QPrinter *printer, const DeckList *deckList);
bool convertToCockatriceFormat(const QString &fileName);
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 saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList);
static void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node);
static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList);
static void saveToStream_DeckZone(QTextStream &out,
const InnerDecklistNode *zoneNode,
@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -57,10 +57,17 @@ void Logger::openLogfileSession()
return;
}
fileStream.setDevice(&fileHandle);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl;
fileStream << getClientVersion() << Qt::endl;
fileStream << getSystemArchitecture() << Qt::endl;
fileStream << getClientInstallInfo() << Qt::endl;
#else
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << endl;
fileStream << getClientVersion() << endl;
fileStream << getSystemArchitecture() << endl;
fileStream << getClientInstallInfo() << endl;
#endif
logToFileEnabled = true;
}
@@ -70,7 +77,11 @@ void Logger::closeLogfileSession()
return;
logToFileEnabled = false;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl;
#else
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << endl;
#endif
fileHandle.close();
}
@@ -92,7 +103,11 @@ void Logger::internalLog(const QString &message)
std::cerr << message.toStdString() << std::endl; // Print to stdout
if (logToFileEnabled) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << message << Qt::endl; // Print to fileStream
#else
fileStream << message << endl; // Print to fileStream
#endif
}
}

View File

@@ -91,9 +91,8 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i
if (indexToWidgetMap.contains(index)) {
return indexToWidgetMap[index];
}
auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
auto cardProviderId =
index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString();
auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString();
auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true);
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
@@ -115,7 +114,7 @@ void CardGroupDisplayWidget::updateCardDisplays()
// This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway.
proxy.setSortRole(Qt::EditRole);
proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
proxy.sort(1, Qt::AscendingOrder);
// 1. trackedIndex is a source index → map it to proxy space
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);

View File

@@ -27,7 +27,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa
layout->addWidget(text, 0, Qt::AlignCenter);
setLayout(layout);
setFrameStyle(static_cast<int>(QFrame::Panel) | QFrame::Raised);
setFrameStyle(QFrame::Panel | QFrame::Raised);
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);

View File

@@ -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);
@@ -47,7 +51,7 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT
originalPos = this->pos();
// Create the animation
animation = new QPropertyAnimation(this, "pos", this);
animation = new QPropertyAnimation(this, "pos");
animation->setDuration(200); // 200ms animation duration
animation->setEasingCurve(QEasingCurve::OutQuad);
@@ -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;
}
}

View File

@@ -63,8 +63,7 @@ protected:
{
return resizedPixmap;
}
void showEnlargedPixmap();
void destroyEnlargedPixmapWidget();
void showEnlargedPixmap() const;
private:
ExactCard exactCard;

View File

@@ -2,7 +2,6 @@
#include "card_group_display_widgets/flat_card_group_display_widget.h"
#include "card_group_display_widgets/overlapped_card_group_display_widget.h"
#include "libcockatrice/card/database/card_database_manager.h"
#include <QResizeEvent>
#include <libcockatrice/models/deck_list/deck_list_model.h>
@@ -83,11 +82,10 @@ void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *
void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index)
{
auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
if (indexToWidgetMap.contains(index)) {
return;
}
auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
if (displayType == DisplayType::Overlap) {
auto *displayWidget = new OverlappedCardGroupDisplayWidget(
cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria,
@@ -122,7 +120,7 @@ void DeckCardZoneDisplayWidget::displayCards()
QSortFilterProxyModel proxy;
proxy.setSourceModel(deckListModel);
proxy.setSortRole(Qt::EditRole);
proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
proxy.sort(1, Qt::AscendingOrder);
// 1. trackedIndex is a source index → map it to proxy space
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
@@ -231,13 +229,10 @@ QList<QString> DeckCardZoneDisplayWidget::getGroupCriteriaValueList()
{
QList<QString> groupCriteriaValues;
QList<const DecklistCardNode *> nodes = deckListModel->getCardNodesForZone(zoneName);
QList<ExactCard> cardsInZone = deckListModel->getCardsForZone(zoneName);
for (auto node : nodes) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
if (info) {
groupCriteriaValues.append(info->getProperty(activeGroupCriteria));
}
for (const ExactCard &cardInZone : cardsInZone) {
groupCriteriaValues.append(cardInZone.getInfo().getProperty(activeGroupCriteria));
}
groupCriteriaValues.removeDuplicates();

View File

@@ -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.
*/

View File

@@ -1,49 +0,0 @@
#include "abstract_analytics_panel_widget.h"
#include "deck_list_statistics_analyzer.h"
#include <QPushButton>
AbstractAnalyticsPanelWidget::AbstractAnalyticsPanelWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
: QWidget(parent), analyzer(analyzer)
{
layout = new QVBoxLayout(this);
bannerAndSettingsContainer = new QWidget(this);
bannerAndSettingsLayout = new QHBoxLayout(bannerAndSettingsContainer);
bannerAndSettingsContainer->setLayout(bannerAndSettingsLayout);
bannerWidget = new BannerWidget(this, "Analytics Widget", Qt::Vertical, 100);
bannerWidget->setMaximumHeight(100);
bannerAndSettingsLayout->addWidget(bannerWidget, 1);
// config button
configureButton = new QPushButton(this);
configureButton->setIcon(QPixmap("theme:icons/cogwheel"));
configureButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect(configureButton, &QPushButton::clicked, this, &AbstractAnalyticsPanelWidget::applyConfigFromDialog);
bannerAndSettingsLayout->addWidget(configureButton, 0);
layout->addWidget(bannerAndSettingsContainer);
connect(analyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &AbstractAnalyticsPanelWidget::updateDisplay);
}
bool AbstractAnalyticsPanelWidget::applyConfigFromDialog()
{
QDialog *dlg = createConfigDialog(this);
if (!dlg) {
return false;
}
bool ok = dlg->exec() == QDialog::Accepted;
if (ok) {
// dialog must expose its final config as JSON
auto newCfg = extractConfigFromDialog(dlg);
loadConfig(newCfg);
updateDisplay();
}
dlg->deleteLater();
return ok;
}

View File

@@ -1,61 +0,0 @@
#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H
#define COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H
#include "../general/display/banner_widget.h"
#include <QDialog>
#include <QJsonObject>
#include <QVBoxLayout>
#include <QWidget>
class DeckListStatisticsAnalyzer;
class AbstractAnalyticsPanelWidget : public QWidget
{
Q_OBJECT
public slots:
virtual void updateDisplay() = 0;
// Widgets must return a config dialog
virtual QDialog *createConfigDialog(QWidget *parent) = 0;
public:
explicit AbstractAnalyticsPanelWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
void setDisplayTitle(const QString &title)
{
displayTitle = title;
if (bannerWidget) {
bannerWidget->setText(displayTitle);
}
}
QString displayTitleText() const
{
return displayTitle;
}
virtual QJsonObject saveConfig() const
{
return {};
}
virtual void loadConfig(const QJsonObject &)
{
}
// Unified helper to run config dialog and update widget
bool applyConfigFromDialog();
// Dialog → JSON must be supplied by each subclass
virtual QJsonObject extractConfigFromDialog(QDialog *dlg) const = 0;
protected:
DeckListStatisticsAnalyzer *analyzer;
QVBoxLayout *layout;
QWidget *bannerAndSettingsContainer;
QHBoxLayout *bannerAndSettingsLayout;
QString displayTitle;
BannerWidget *bannerWidget;
QPushButton *configureButton;
};
#endif // COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H

View File

@@ -1,32 +0,0 @@
#include "add_analytics_panel_dialog.h"
#include "analytics_panel_widget_factory.h"
#include <QDialogButtonBox>
#include <QVBoxLayout>
AddAnalyticsPanelDialog::AddAnalyticsPanelDialog(QWidget *parent) : QDialog(parent)
{
setWindowTitle(tr("Add Analytics Panel"));
layout = new QVBoxLayout(this);
typeCombo = new QComboBox(this);
// Populate using descriptors
const auto widgets = AnalyticsPanelWidgetFactory::instance().availableWidgets();
for (const auto &desc : widgets) {
// Show translated title to user
typeCombo->addItem(desc.title, desc.type);
}
layout->addWidget(typeCombo);
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
layout->addWidget(buttons);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}

View File

@@ -1,29 +0,0 @@
#ifndef COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H
#define COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H
#include "analytics_panel_widget_factory.h"
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class AddAnalyticsPanelDialog : public QDialog
{
Q_OBJECT
public:
explicit AddAnalyticsPanelDialog(QWidget *parent);
QString selectedType() const
{
return typeCombo->currentData().toString();
}
private:
QVBoxLayout *layout;
QComboBox *typeCombo;
QDialogButtonBox *buttons;
};
#endif // COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H

View File

@@ -1,33 +0,0 @@
#include "analytics_panel_widget_factory.h"
#include "abstract_analytics_panel_widget.h"
AnalyticsPanelWidgetFactory &AnalyticsPanelWidgetFactory::instance()
{
static AnalyticsPanelWidgetFactory f;
return f;
}
void AnalyticsPanelWidgetFactory::registerWidget(const Descriptor &desc)
{
widgets.insert(desc.type, desc);
}
AbstractAnalyticsPanelWidget *
AnalyticsPanelWidgetFactory::create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const
{
auto it = widgets.find(type);
if (it == widgets.end())
return nullptr;
auto w = it->creator(parent, analyzer);
w->setDisplayTitle(it->title);
return w;
}
QList<AnalyticsPanelWidgetFactory::Descriptor> AnalyticsPanelWidgetFactory::availableWidgets() const
{
return widgets.values();
}

View File

@@ -1,44 +0,0 @@
#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_FACTORY_H
#define COCKATRICE_DECK_ANALYTICS_WIDGET_FACTORY_H
#include <QMap>
#include <QString>
#include <QStringList>
#include <QWidget>
#include <functional>
class AbstractAnalyticsPanelWidget;
class DeckListStatisticsAnalyzer;
class AnalyticsPanelWidgetFactory
{
public:
using Creator = std::function<AbstractAnalyticsPanelWidget *(QWidget *, DeckListStatisticsAnalyzer *)>;
struct Descriptor
{
QString type; // stable ID ("manaProdDevotion")
QString title; // translated, user-facing
Creator creator;
};
static AnalyticsPanelWidgetFactory &instance();
// NEW: richer registration
void registerWidget(const Descriptor &desc);
AbstractAnalyticsPanelWidget *
create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const;
// NEW: expose widgets to UI
QList<Descriptor> availableWidgets() const;
private:
AnalyticsPanelWidgetFactory() = default; // Ensure private constructor
AnalyticsPanelWidgetFactory(const AnalyticsPanelWidgetFactory &) = delete;
AnalyticsPanelWidgetFactory &operator=(const AnalyticsPanelWidgetFactory &) = delete;
QMap<QString, Descriptor> widgets;
};
#endif

View File

@@ -1 +0,0 @@
#include "analytics_panel_widget_registrar.h"

View File

@@ -1,17 +0,0 @@
#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H
#define COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H
#include "analytics_panel_widget_factory.h"
class AnalyticsPanelWidgetRegistrar
{
public:
AnalyticsPanelWidgetRegistrar(const QString &type,
const QString &title,
AnalyticsPanelWidgetFactory::Creator creator)
{
AnalyticsPanelWidgetFactory::instance().registerWidget({type, title, creator});
}
};
#endif // COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H

View File

@@ -1,28 +0,0 @@
#include "draw_probability_config.h"
QJsonObject DrawProbabilityConfig::toJson() const
{
QJsonObject o;
o["criteria"] = criteria;
o["atLeast"] = atLeast;
o["quantity"] = quantity;
o["drawn"] = drawn;
return o;
}
DrawProbabilityConfig DrawProbabilityConfig::fromJson(const QJsonObject &o)
{
DrawProbabilityConfig cfg;
if (o.contains("criteria")) {
cfg.criteria = o["criteria"].toString();
}
if (o.contains("atLeast")) {
cfg.atLeast = o["atLeast"].toBool(true);
}
if (o.contains("quantity")) {
cfg.quantity = o["quantity"].toInt(1);
}
if (o.contains("drawn")) {
cfg.drawn = o["drawn"].toInt(7);
}
return cfg;
}

View File

@@ -1,19 +0,0 @@
#ifndef COCKATRICE_DRAW_PROBABILITY_CONFIG_H
#define COCKATRICE_DRAW_PROBABILITY_CONFIG_H
#include <QJsonObject>
#include <QString>
struct DrawProbabilityConfig
{
QString criteria = "name"; // name, type, subtype, cmc
bool atLeast = true; // true = at least, false = exactly
int quantity = 1; // N
int drawn = 7; // M
QJsonObject toJson() const;
static DrawProbabilityConfig fromJson(const QJsonObject &o);
};
#endif

View File

@@ -1,92 +0,0 @@
#include "draw_probability_config_dialog.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLabel>
#include <QSpinBox>
DrawProbabilityConfigDialog::DrawProbabilityConfigDialog(QWidget *parent) : QDialog(parent)
{
form = new QFormLayout(this);
// Criteria
labelCriteria = new QLabel(this);
criteria = new QComboBox(this);
criteria->addItem(QString(), "name");
criteria->addItem(QString(), "type");
criteria->addItem(QString(), "subtype");
criteria->addItem(QString(), "cmc");
form->addRow(labelCriteria, criteria);
// Exactness
labelExactness = new QLabel(this);
exactness = new QComboBox(this);
exactness->addItem(QString(), true);
exactness->addItem(QString(), false);
form->addRow(labelExactness, exactness);
// Quantity
labelQuantity = new QLabel(this);
quantity = new QSpinBox(this);
quantity->setRange(1, 60);
form->addRow(labelQuantity, quantity);
// Drawn
labelDrawn = new QLabel(this);
drawn = new QSpinBox(this);
drawn->setRange(1, 60);
drawn->setValue(7);
form->addRow(labelDrawn, drawn);
// Button box
auto *bb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
form->addWidget(bb);
connect(bb, &QDialogButtonBox::accepted, this, &DrawProbabilityConfigDialog::accept);
connect(bb, &QDialogButtonBox::rejected, this, &QDialog::reject);
retranslateUi();
}
void DrawProbabilityConfigDialog::retranslateUi()
{
setWindowTitle(tr("Draw Probability Settings"));
labelCriteria->setText(tr("Criteria:"));
criteria->setItemText(0, tr("Card Name"));
criteria->setItemText(1, tr("Type"));
criteria->setItemText(2, tr("Subtype"));
criteria->setItemText(3, tr("Mana Value"));
labelExactness->setText(tr("Exactness:"));
exactness->setItemText(0, tr("At least"));
exactness->setItemText(1, tr("Exactly"));
labelQuantity->setText(tr("Quantity (N):"));
labelDrawn->setText(tr("Cards drawn (M):"));
// i18n-friendly suffixes
quantity->setSuffix(tr(" cards"));
drawn->setSuffix(tr(" cards"));
}
void DrawProbabilityConfigDialog::setFromConfig(const DrawProbabilityConfig &_config)
{
cfg = _config;
criteria->setCurrentIndex(criteria->findData(_config.criteria));
exactness->setCurrentIndex(exactness->findData(_config.atLeast));
quantity->setValue(_config.quantity);
drawn->setValue(_config.drawn);
}
void DrawProbabilityConfigDialog::accept()
{
cfg.criteria = criteria->currentData().toString();
cfg.atLeast = exactness->currentData().toBool();
cfg.quantity = quantity->value();
cfg.drawn = drawn->value();
QDialog::accept();
}

View File

@@ -1,44 +0,0 @@
#pragma once
#include "draw_probability_config.h"
#include <QDialog>
#include <QFormLayout>
class QComboBox;
class QSpinBox;
class QLabel;
class DrawProbabilityConfigDialog : public QDialog
{
Q_OBJECT
public:
explicit DrawProbabilityConfigDialog(QWidget *parent = nullptr);
void retranslateUi();
void setFromConfig(const DrawProbabilityConfig &_config);
DrawProbabilityConfig result() const
{
return cfg;
}
protected:
void accept() override;
private:
DrawProbabilityConfig cfg;
QFormLayout *form;
// Widgets
QComboBox *criteria;
QComboBox *exactness;
QSpinBox *quantity;
QSpinBox *drawn;
QLabel *labelCriteria;
QLabel *labelExactness;
QLabel *labelQuantity;
QLabel *labelDrawn;
};

View File

@@ -1,236 +0,0 @@
#include "draw_probability_widget.h"
#include "draw_probability_config_dialog.h"
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QMap>
#include <QSpinBox>
#include <QTableWidgetItem>
#include <QWidget>
#include <QtMath>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/card/database/card_database_manager.h>
DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
: AbstractAnalyticsPanelWidget(parent, analyzer)
{
controls = new QWidget(this);
controlLayout = new QHBoxLayout(controls);
labelPrefix = new QLabel(this);
controlLayout->addWidget(labelPrefix);
criteriaCombo = new QComboBox(this);
// Give these things item-data so we can translate the actual user-facing strings
criteriaCombo->addItem(QString(), "name");
criteriaCombo->addItem(QString(), "type");
criteriaCombo->addItem(QString(), "subtype");
criteriaCombo->addItem(QString(), "cmc");
controlLayout->addWidget(criteriaCombo);
exactnessCombo = new QComboBox(this);
exactnessCombo->addItem(QString(), true); // At least
exactnessCombo->addItem(QString(), false); // Exactly
controlLayout->addWidget(exactnessCombo);
quantitySpin = new QSpinBox(this);
quantitySpin->setRange(1, 60);
controlLayout->addWidget(quantitySpin);
labelMiddle = new QLabel(this);
controlLayout->addWidget(labelMiddle);
drawnSpin = new QSpinBox(this);
drawnSpin->setRange(1, 60);
drawnSpin->setValue(7);
controlLayout->addWidget(drawnSpin);
labelSuffix = new QLabel(this);
controlLayout->addWidget(labelSuffix);
labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
controlLayout->addStretch(1);
layout->addWidget(controls);
// Table
resultTable = new QTableWidget(this);
resultTable->setColumnCount(3);
resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
layout->addWidget(resultTable);
// Connections
connect(criteriaCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] {
config.criteria = criteriaCombo->currentData().toString();
updateDisplay();
});
connect(exactnessCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] {
config.atLeast = exactnessCombo->currentData().toBool();
updateDisplay();
});
connect(quantitySpin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int v) {
config.quantity = v;
updateDisplay();
});
connect(drawnSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int v) {
config.drawn = v;
updateDisplay();
});
retranslateUi();
applyConfigToToolbar();
updateFilterOptions();
}
void DrawProbabilityWidget::retranslateUi()
{
bannerWidget->setText(tr("Draw Probability"));
labelPrefix->setText(tr("Probability of drawing"));
criteriaCombo->setItemText(0, tr("Card Name"));
criteriaCombo->setItemText(1, tr("Type"));
criteriaCombo->setItemText(2, tr("Subtype"));
criteriaCombo->setItemText(3, tr("Mana Value"));
exactnessCombo->setItemText(0, tr("At least"));
exactnessCombo->setItemText(1, tr("Exactly"));
labelMiddle->setText(tr("card(s) having drawn at least"));
labelSuffix->setText(tr("cards"));
resultTable->setHorizontalHeaderLabels({tr("Category"), tr("Qty"), tr("Odds (%)")});
}
QDialog *DrawProbabilityWidget::createConfigDialog(QWidget *parent)
{
auto *dlg = new DrawProbabilityConfigDialog(parent);
dlg->setFromConfig(config);
return dlg;
}
QJsonObject DrawProbabilityWidget::extractConfigFromDialog(QDialog *dlg) const
{
auto *dp = qobject_cast<DrawProbabilityConfigDialog *>(dlg);
return dp ? dp->result().toJson() : QJsonObject{};
}
void DrawProbabilityWidget::applyConfigToToolbar()
{
auto setComboByData = [](QComboBox *combo, const QVariant &value) {
int idx = combo->findData(value);
if (idx >= 0) {
combo->setCurrentIndex(idx);
}
};
setComboByData(criteriaCombo, config.criteria);
setComboByData(exactnessCombo, config.atLeast);
quantitySpin->setValue(config.quantity);
drawnSpin->setValue(config.drawn);
}
void DrawProbabilityWidget::updateDisplay()
{
updateFilterOptions();
}
void DrawProbabilityWidget::loadConfig(const QJsonObject &cfg)
{
config = DrawProbabilityConfig::fromJson(cfg);
applyConfigToToolbar();
updateFilterOptions();
}
void DrawProbabilityWidget::updateFilterOptions()
{
if (!analyzer->getModel()->getDeckList()) {
return;
}
const QString criteria = config.criteria;
const bool atLeast = config.atLeast;
const int quantity = config.quantity;
const int drawn = config.drawn;
QMap<QString, int> categoryCounts;
int totalDeckCards = 0;
const auto nodes = analyzer->getModel()->getCardNodes();
for (auto *node : nodes) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
if (!info) {
continue;
}
totalDeckCards += node->getNumber();
QStringList categories;
if (criteria == "name") {
categories << info->getName();
} else if (criteria == "type") {
categories = info->getMainCardType().split(' ', Qt::SkipEmptyParts);
} else if (criteria == "subtype") {
categories = info->getCardType().split(' ', Qt::SkipEmptyParts);
} else if (criteria == "cmc") {
categories << QString::number(info->getCmc().toInt());
}
for (const QString &cat : categories) {
categoryCounts[cat] += node->getNumber();
}
}
resultTable->setRowCount(categoryCounts.size());
int row = 0;
for (auto it = categoryCounts.cbegin(); it != categoryCounts.cend(); ++it, ++row) {
const QString &cat = it.key();
const int copies = it.value();
double probability = 0.0;
if (atLeast) {
for (int k = quantity; k <= drawn && k <= copies; ++k) {
probability += hypergeometricProbability(totalDeckCards, copies, drawn, k);
}
} else {
probability = hypergeometricProbability(totalDeckCards, copies, drawn, quantity);
}
resultTable->setItem(row, 0, new QTableWidgetItem(cat));
resultTable->setItem(row, 1, new QTableWidgetItem(QString::number(copies)));
resultTable->setItem(row, 2, new QTableWidgetItem(QString::number(probability * 100.0, 'f', 2)));
}
}
double DrawProbabilityWidget::hypergeometricProbability(int N, int K, int n, int k)
{
if (k < 0 || k > n || K > N || n > N) {
return 0.0;
}
double logP = 0.0;
for (int i = 1; i <= k; ++i) {
logP += qLn(double(K - k + i) / i);
}
for (int i = 1; i <= n - k; ++i) {
logP += qLn(double(N - K - (n - k) + i) / i);
}
for (int i = 1; i <= n; ++i) {
logP -= qLn(double(N - n + i) / i);
}
return qExp(logP);
}

View File

@@ -1,54 +0,0 @@
#ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H
#define COCKATRICE_DRAW_PROBABILITY_WIDGET_H
#include "../../abstract_analytics_panel_widget.h"
#include "../../deck_list_statistics_analyzer.h"
#include "draw_probability_config.h"
#include <QComboBox>
#include <QLineEdit>
#include <QSpinBox>
#include <QTableWidget>
class DrawProbabilityWidget : public AbstractAnalyticsPanelWidget
{
Q_OBJECT
public:
DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
QDialog *createConfigDialog(QWidget *parent) override;
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
void applyConfigToToolbar();
public slots:
void updateDisplay() override;
void loadConfig(const QJsonObject &cfg) override;
void retranslateUi();
private slots:
void updateFilterOptions();
private:
DrawProbabilityConfig config;
QWidget *controls;
QHBoxLayout *controlLayout;
QLabel *labelPrefix;
QLabel *labelMiddle;
QLabel *labelSuffix;
QLineEdit *cardNameEdit;
QComboBox *criteriaCombo; // Card Name / Type / Subtype / Mana Value
QComboBox *filterCombo; // The actual value
QComboBox *exactnessCombo; // At least / Exactly
QSpinBox *quantitySpin; // N
QSpinBox *drawnSpin; // M
QSpinBox *manaValueSpin;
QTableWidget *resultTable;
double hypergeometricProbability(int N, int K, int n, int k);
double calculateProbability(int totalCards, int copies, int drawn, bool atLeast);
};
#endif // COCKATRICE_DRAW_PROBABILITY_WIDGET_H

View File

@@ -1,32 +0,0 @@
#include "mana_base_config.h"
QJsonObject ManaBaseConfig::toJson() const
{
QJsonObject jsonObject;
QJsonArray jsonArray;
jsonObject["displayType"] = displayType;
for (auto &filter : filters) {
jsonArray.append(filter);
}
jsonObject["filters"] = jsonArray;
return jsonObject;
}
ManaBaseConfig ManaBaseConfig::fromJson(const QJsonObject &o)
{
ManaBaseConfig config;
if (o.contains("displayType")) {
config.displayType = o["displayType"].toString();
}
if (o.contains("filters")) {
config.filters.clear();
for (auto v : o["filters"].toArray()) {
config.filters << v.toString();
}
}
return config;
}

View File

@@ -1,19 +0,0 @@
#ifndef COCKATRICE_MANA_BASE_CONFIG_H
#define COCKATRICE_MANA_BASE_CONFIG_H
#include <QJsonArray>
#include <QJsonObject>
#include <QStringList>
struct ManaBaseConfig
{
QString displayType; // "pie" or "bar" or "combinedBar"
QStringList filters; // which colors to show, empty = all
QJsonObject toJson() const;
static ManaBaseConfig fromJson(const QJsonObject &o);
};
#endif // COCKATRICE_MANA_BASE_CONFIG_H

View File

@@ -1,67 +0,0 @@
#include "mana_base_config_dialog.h"
#include <QPushButton>
ManaBaseConfigDialog::ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer,
ManaBaseConfig initial,
QWidget *parent)
: QDialog(parent), config(initial)
{
layout = new QVBoxLayout(this);
displayTypeLabel = new QLabel(this);
layout->addWidget(displayTypeLabel);
displayType = new QComboBox(this);
layout->addWidget(displayType);
filterLabel = new QLabel(this);
layout->addWidget(filterLabel);
filterList = new QListWidget(this);
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
layout->addWidget(filterList);
QStringList colors = analyzer->getManaBase().keys();
colors.sort();
filterList->addItems(colors);
// select initial filters
for (int i = 0; i < filterList->count(); ++i) {
if (config.filters.contains(filterList->item(i)->text()))
filterList->item(i)->setSelected(true);
}
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
layout->addWidget(buttons);
connect(buttons, &QDialogButtonBox::accepted, this, &ManaBaseConfigDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &ManaBaseConfigDialog::reject);
retranslateUi();
}
void ManaBaseConfigDialog::retranslateUi()
{
setWindowTitle(tr("Mana Base Configuration"));
displayTypeLabel->setText(tr("Display type:"));
displayType->clear();
displayType->addItems({tr("pie"), tr("bar"), tr("combinedBar")});
filterLabel->setText(tr("Filter Colors (optional):"));
buttons->button(QDialogButtonBox::Ok)->setText(tr("OK"));
buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
}
void ManaBaseConfigDialog::accept()
{
config.displayType = displayType->currentText();
config.filters.clear();
for (auto *item : filterList->selectedItems()) {
config.filters << item->text();
}
QDialog::accept();
}

View File

@@ -1,42 +0,0 @@
#ifndef COCKATRICE_MANA_BASE_ADD_DIALOG_H
#define COCKATRICE_MANA_BASE_ADD_DIALOG_H
#include "../../deck_list_statistics_analyzer.h"
#include "mana_base_config.h"
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QJsonArray>
#include <QJsonObject>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
class ManaBaseConfigDialog : public QDialog
{
Q_OBJECT
public:
ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig initial = {}, QWidget *parent = nullptr);
void retranslateUi();
void accept() override;
ManaBaseConfig result() const
{
return config;
}
private:
ManaBaseConfig config;
QVBoxLayout *layout;
QLabel *displayTypeLabel;
QComboBox *displayType;
QLabel *filterLabel;
QListWidget *filterList;
QDialogButtonBox *buttons;
};
#endif // COCKATRICE_MANA_BASE_ADD_DIALOG_H

View File

@@ -1,115 +0,0 @@
#include "mana_base_widget.h"
#include "../../../general/display/charts/bars/bar_widget.h"
#include "../../../general/display/charts/bars/color_bar.h"
#include "../../../general/display/charts/pies/color_pie.h"
#include "../../analytics_panel_widget_registrar.h"
#include "mana_base_config_dialog.h"
#include <QDialog>
#include <QListWidget>
namespace
{
AnalyticsPanelWidgetRegistrar registerManaBase{
"manaBase", ManaBaseWidget::tr("Mana Base"),
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaBaseWidget(parent, analyzer); }};
} // anonymous namespace
ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig cfg)
: AbstractAnalyticsPanelWidget(parent, analyzer), config(std::move(cfg))
{
barContainer = new QWidget(this);
barLayout = new QHBoxLayout(barContainer);
layout->addWidget(barContainer);
updateDisplay();
}
void ManaBaseWidget::updateDisplay()
{
// Clear previous widgets
while (QLayoutItem *item = barLayout->takeAt(0)) {
if (item->widget()) {
item->widget()->deleteLater();
}
delete item;
}
auto &pipCount = analyzer->getProductionPipCount();
auto &cardCount = analyzer->getProductionCardCount();
QHash<QString, int> manaMap;
for (auto key : pipCount.keys()) {
manaMap[key] = pipCount[key];
}
// Apply filters
if (!config.filters.isEmpty()) {
QHash<QString, int> filtered;
for (auto f : config.filters) {
if (manaMap.contains(f)) {
filtered[f] = manaMap[f];
}
}
manaMap = filtered;
}
// Determine maximum for bar charts
int highest = 1;
for (auto val : manaMap) {
highest = std::max(highest, val);
}
// Convert to QMap for ColorBar / ColorPie (sorted)
QMap<QString, int> mapSorted;
for (auto it = manaMap.begin(); it != manaMap.end(); ++it) {
mapSorted.insert(it.key(), it.value());
}
// Choose display mode
if (config.displayType == "bar") {
QHash<QString, QColor> colors = {{"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)},
{"B", QColor(21, 11, 0)}, {"R", QColor(211, 32, 42)},
{"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)}};
for (auto color : manaMap.keys()) {
QString label = QString("%1 %2 (%3)").arg(color).arg(manaMap[color]).arg(cardCount.value(color));
BarWidget *bar = new BarWidget(label, manaMap[color], highest, colors.value(color, Qt::gray), this);
barLayout->addWidget(bar);
}
} else if (config.displayType == "combinedBar") {
ColorBar *cb = new ColorBar(mapSorted, this);
cb->setMinimumHeight(30);
barLayout->addWidget(cb);
} else if (config.displayType == "pie") {
ColorPie *pie = new ColorPie(mapSorted, this);
pie->setMinimumSize(200, 200);
barLayout->addWidget(pie);
}
update();
}
QSize ManaBaseWidget::sizeHint() const
{
return QSize(800, 150);
}
QDialog *ManaBaseWidget::createConfigDialog(QWidget *parent)
{
ManaBaseConfigDialog *dlg = new ManaBaseConfigDialog(analyzer, config, parent);
return dlg;
}
QJsonObject ManaBaseWidget::extractConfigFromDialog(QDialog *dlg) const
{
auto *mc = qobject_cast<ManaBaseConfigDialog *>(dlg);
if (!mc) {
return {};
}
return mc->result().toJson();
}

View File

@@ -1,51 +0,0 @@
/**
* @file mana_base_widget.h
* @ingroup DeckEditorAnalyticsWidgets
* @brief TODO: Document this.
*/
#ifndef MANA_BASE_WIDGET_H
#define MANA_BASE_WIDGET_H
#include "../../../general/display/banner_widget.h"
#include "../../abstract_analytics_panel_widget.h"
#include "../../deck_list_statistics_analyzer.h"
#include "mana_base_config.h"
#include <QHBoxLayout>
#include <QWidget>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/models/deck_list/deck_list_model.h>
#include <utility>
class ManaBaseWidget : public AbstractAnalyticsPanelWidget
{
Q_OBJECT
public slots:
QSize sizeHint() const override;
void updateDisplay() override;
QDialog *createConfigDialog(QWidget *parent) override;
public:
ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig cfg = {});
QJsonObject saveConfig() const override
{
return config.toJson();
}
void loadConfig(const QJsonObject &o) override
{
config = ManaBaseConfig::fromJson(o);
updateDisplay();
}
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
private:
ManaBaseConfig config;
QWidget *barContainer;
QHBoxLayout *barLayout;
};
#endif // MANA_BASE_WIDGET_H

View File

@@ -1,121 +0,0 @@
#include "mana_curve_category_widget.h"
#include "libcockatrice/utility/color.h"
#include "libcockatrice/utility/qt_utils.h"
#include "mana_curve_config.h"
#include "mana_curve_total_widget.h"
constexpr int MIN_ROW_HEIGHT = 100; // Minimum readable height per row
ManaCurveCategoryWidget::ManaCurveCategoryWidget(QWidget *parent) : QWidget(parent)
{
layout = new QVBoxLayout(this);
layout->setSpacing(4);
layout->setContentsMargins(0, 0, 0, 0);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
// Same as minimum for now
QSize ManaCurveCategoryWidget::sizeHint() const
{
if (layout->isEmpty()) {
return QSize(0, 0);
}
// Calculate exact height needed for all rows
int rowCount = layout->count();
int totalHeight = rowCount * MIN_ROW_HEIGHT;
totalHeight += (rowCount - 1) * layout->spacing();
return QSize(0, totalHeight);
}
QSize ManaCurveCategoryWidget::minimumSizeHint() const
{
if (layout->isEmpty()) {
return QSize(0, 0);
}
// Calculate actual minimum based on number of rows
int rowCount = layout->count();
int totalHeight = rowCount * MIN_ROW_HEIGHT;
totalHeight += (rowCount - 1) * layout->spacing();
return QSize(0, totalHeight);
}
void ManaCurveCategoryWidget::updateDisplay(int minCmc,
int maxCmc,
int highest,
QHash<QString, QHash<int, int>> qCategoryCounts,
QHash<QString, QHash<int, QStringList>> qCategoryCards,
const ManaCurveConfig &config)
{
// Clear previous content
QtUtils::clearLayoutRec(layout);
if (!config.showCategoryRows) {
return; // nothing to show
}
// Collect categories
QStringList categories = qCategoryCounts.keys();
// Apply filters
if (!config.filters.isEmpty()) {
QStringList filtered;
for (const QString &cat : categories) {
if (config.filters.contains(cat)) {
filtered.append(cat);
}
}
categories = filtered;
}
std::sort(categories.begin(), categories.end());
for (const QString &cat : categories) {
QWidget *row = new QWidget(this);
row->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
row->setFixedHeight(MIN_ROW_HEIGHT);
QHBoxLayout *rowLayout = new QHBoxLayout(row);
rowLayout->setContentsMargins(0, 0, 0, 0);
rowLayout->setSpacing(4);
QLabel *categoryLabel = new QLabel(cat, row);
categoryLabel->setFixedWidth(80);
rowLayout->addWidget(categoryLabel);
QVector<BarData> catBars;
const auto cmcCounts = qCategoryCounts.value(cat);
const auto cmcCards = qCategoryCards.value(cat);
for (int cmc = minCmc; cmc <= maxCmc; ++cmc) {
int val = cmcCounts.value(cmc, 0);
QStringList cards = cmcCards.value(cmc);
QVector<BarSegment> segments;
if (val > 0) {
segments.push_back({cat, val, cards, GameSpecificColors::MTG::colorHelper(cat)});
}
catBars.push_back({QString::number(cmc), segments});
}
auto *catChart = new BarChartWidget(row);
catChart->setHighest(highest);
catChart->setBars(catBars);
catChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
rowLayout->addWidget(catChart);
layout->addWidget(row);
}
// Update geometry after adding all widgets
updateGeometry();
}

View File

@@ -1,32 +0,0 @@
#ifndef COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H
#define COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H
#include "../../../general/display/charts/bars/bar_chart_widget.h"
#include "mana_curve_config.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QWidget>
class ManaCurveCategoryWidget : public QWidget
{
Q_OBJECT
public:
explicit ManaCurveCategoryWidget(QWidget *parent);
void updateDisplay(int minCmc,
int maxCmc,
int highest,
QHash<QString, QHash<int, int>> qCategoryCounts,
QHash<QString, QHash<int, QStringList>> qCategoryCards,
const ManaCurveConfig &config);
public slots:
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
private:
QVBoxLayout *layout;
};
#endif // COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H

View File

@@ -1,41 +0,0 @@
#include "mana_curve_config.h"
QJsonObject ManaCurveConfig::toJson() const
{
QJsonObject jsonObject;
jsonObject["groupBy"] = groupBy;
QJsonArray jsonArray;
for (auto &filter : filters) {
jsonArray.append(filter);
}
jsonObject["filters"] = jsonArray;
jsonObject["showMain"] = showMain;
jsonObject["showCategoryRows"] = showCategoryRows;
return jsonObject;
}
ManaCurveConfig ManaCurveConfig::fromJson(const QJsonObject &o)
{
ManaCurveConfig config;
if (o.contains("groupBy")) {
config.groupBy = o["groupBy"].toString();
}
if (o.contains("filters")) {
config.filters.clear();
for (auto v : o["filters"].toArray()) {
config.filters << v.toString();
}
}
if (o.contains("showMain")) {
config.showMain = o["showMain"].toBool(true);
}
if (o.contains("showCategoryRows")) {
config.showCategoryRows = o["showCategoryRows"].toBool(true);
}
return config;
}

View File

@@ -1,21 +0,0 @@
#ifndef COCKATRICE_MANA_CURVE_CONFIG_H
#define COCKATRICE_MANA_CURVE_CONFIG_H
#include <QJsonArray>
#include <QJsonObject>
#include <QStringList>
struct ManaCurveConfig
{
QString groupBy = "type"; // "type", "color", "subtype", etc.
QStringList filters; // empty = all
bool showMain = true;
bool showCategoryRows = true;
QJsonObject toJson() const;
static ManaCurveConfig fromJson(const QJsonObject &o);
};
#endif // COCKATRICE_MANA_CURVE_CONFIG_H

View File

@@ -1,91 +0,0 @@
#include "mana_curve_config_dialog.h"
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
ManaCurveConfigDialog::ManaCurveConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent)
: QDialog(parent), analyzer(analyzer)
{
auto *lay = new QVBoxLayout(this);
labelGroupBy = new QLabel(this);
lay->addWidget(labelGroupBy);
groupBy = new QComboBox(this);
lay->addWidget(groupBy);
labelFilters = new QLabel(this);
lay->addWidget(labelFilters);
filterList = new QListWidget(this);
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
lay->addWidget(filterList);
showMain = new QCheckBox(this);
showMain->setChecked(true);
lay->addWidget(showMain);
showCatRows = new QCheckBox(this);
showCatRows->setChecked(true);
lay->addWidget(showCatRows);
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
lay->addWidget(buttons);
connect(buttons, &QDialogButtonBox::accepted, this, &ManaCurveConfigDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &ManaCurveConfigDialog::reject);
// populate dynamic data
QStringList cats = analyzer->getManaCurveByType().keys();
cats.append(analyzer->getManaCurveByColor().keys());
cats.removeDuplicates();
cats.sort();
filterList->addItems(cats);
groupBy->addItems({"type", "color", "subtype", "power", "toughness"});
retranslateUi();
}
void ManaCurveConfigDialog::retranslateUi()
{
labelGroupBy->setText(tr("Group By:"));
groupBy->setItemText(0, tr("type"));
groupBy->setItemText(1, tr("color"));
groupBy->setItemText(2, tr("subtype"));
groupBy->setItemText(3, tr("power"));
groupBy->setItemText(4, tr("toughness"));
labelFilters->setText(tr("Filters (optional):"));
showMain->setText(tr("Show main bar row"));
showCatRows->setText(tr("Show per-category rows"));
}
void ManaCurveConfigDialog::setFromConfig(const ManaCurveConfig &cfg)
{
groupBy->setCurrentText(cfg.groupBy);
// restore filters
for (int i = 0; i < filterList->count(); ++i)
filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text()));
showMain->setChecked(cfg.showMain);
showCatRows->setChecked(cfg.showCategoryRows);
}
void ManaCurveConfigDialog::accept()
{
cfg.groupBy = groupBy->currentText();
cfg.filters.clear();
for (auto *item : filterList->selectedItems())
cfg.filters << item->text();
cfg.showMain = showMain->isChecked();
cfg.showCategoryRows = showCatRows->isChecked();
QDialog::accept();
}

View File

@@ -1,44 +0,0 @@
#ifndef COCKATRICE_MANA_CURVE_ADD_DIALOG_H
#define COCKATRICE_MANA_CURVE_ADD_DIALOG_H
#include "../../deck_list_statistics_analyzer.h"
#include "mana_curve_config.h"
#include <QDialog>
#include <QDialogButtonBox>
#include <QLabel>
class QListWidget;
class QCheckBox;
class QComboBox;
class ManaCurveConfigDialog : public QDialog
{
Q_OBJECT
public:
explicit ManaCurveConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent = nullptr);
void retranslateUi();
void setFromConfig(const ManaCurveConfig &cfg);
ManaCurveConfig result() const
{
return cfg;
}
private:
ManaCurveConfig cfg;
DeckListStatisticsAnalyzer *analyzer;
QLabel *labelGroupBy;
QComboBox *groupBy;
QLabel *labelFilters;
QListWidget *filterList;
QDialogButtonBox *buttons;
QCheckBox *showMain;
QCheckBox *showCatRows;
private slots:
void accept() override;
};
#endif // COCKATRICE_MANA_CURVE_ADD_DIALOG_H

View File

@@ -1,78 +0,0 @@
#include "mana_curve_total_widget.h"
#include "../../../general/display/charts/bars/bar_chart_widget.h"
#include "libcockatrice/utility/color.h"
#include "libcockatrice/utility/qt_utils.h"
#include "mana_curve_config.h"
#include <QHBoxLayout>
ManaCurveTotalWidget::ManaCurveTotalWidget(QWidget *parent) : QWidget(parent)
{
layout = new QHBoxLayout(this);
label = new QLabel(this);
label->setFixedWidth(80);
layout->addWidget(label);
barChart = new BarChartWidget(this);
layout->addWidget(barChart, 1);
setMinimumHeight(200);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
QSize ManaCurveTotalWidget::sizeHint() const
{
return {0, 280};
}
QSize ManaCurveTotalWidget::minimumSizeHint() const
{
return {0, 200};
}
void ManaCurveTotalWidget::updateDisplay(const QString &categoryName,
int minCmc,
int maxCmc,
int highest,
const QMap<int, QMap<QString, int>> &cmcMap,
const QMap<QString, QMap<int, QStringList>> &cardsMap,
const ManaCurveConfig &config)
{
QVector<BarData> mainBars;
mainBars.reserve(maxCmc - minCmc + 1);
for (int cmc = minCmc; cmc <= maxCmc; ++cmc) {
QVector<BarSegment> segments;
const auto cmcIt = cmcMap.constFind(cmc);
if (cmcIt != cmcMap.cend()) {
for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) {
const QString &category = it.key();
if (!config.filters.isEmpty() && !config.filters.contains(category))
continue;
const int value = it.value();
QStringList cards;
const auto catIt = cardsMap.constFind(category);
if (catIt != cardsMap.cend())
cards = catIt->value(cmc);
segments.push_back({category, value, cards, GameSpecificColors::MTG::colorHelper(category)});
}
}
std::sort(segments.begin(), segments.end(),
[](const BarSegment &a, const BarSegment &b) { return a.category < b.category; });
mainBars.push_back({QString::number(cmc), segments});
}
label->setText(categoryName);
barChart->setHighest(highest);
barChart->setBars(mainBars);
}

View File

@@ -1,32 +0,0 @@
#ifndef COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H
#define COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H
#include "../../../general/display/charts/bars/bar_chart_widget.h"
#include "mana_curve_config.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QWidget>
class ManaCurveTotalWidget : public QWidget
{
Q_OBJECT
public:
explicit ManaCurveTotalWidget(QWidget *parent);
QSize sizeHint() const;
QSize minimumSizeHint() const;
void updateDisplay(const QString &categoryName,
int minCmc,
int maxCmc,
int highest,
const QMap<int, QMap<QString, int>> &cmcMap,
const QMap<QString, QMap<int, QStringList>> &cardsMap,
const ManaCurveConfig &config);
private:
QHBoxLayout *layout;
QLabel *label;
BarChartWidget *barChart;
};
#endif // COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H

View File

@@ -1,148 +0,0 @@
#include "mana_curve_widget.h"
#include "../../../general/display/charts/bars/bar_chart_background_widget.h"
#include "../../../general/display/charts/bars/bar_chart_widget.h"
#include "../../../general/display/charts/bars/segmented_bar_widget.h"
#include "../../analytics_panel_widget_registrar.h"
#include "../../deck_list_statistics_analyzer.h"
#include "libcockatrice/utility/color.h"
#include "libcockatrice/utility/qt_utils.h"
#include "mana_curve_config_dialog.h"
#include <QInputDialog>
#include <QJsonArray>
#include <QLabel>
#include <QPushButton>
#include <QSettings>
namespace
{
AnalyticsPanelWidgetRegistrar registerManaCurve{
"manaCurve", ManaCurveWidget::tr("Mana Curve"),
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaCurveWidget(parent, analyzer); }};
} // anonymous namespace
ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaCurveConfig cfg)
: AbstractAnalyticsPanelWidget(parent, analyzer), config(cfg)
{
setLayout(layout);
totalWidget = new ManaCurveTotalWidget(this);
totalWidget->setHidden(true);
layout->addWidget(totalWidget);
categoryWidget = new ManaCurveCategoryWidget(this);
categoryWidget->setHidden(true);
layout->addWidget(categoryWidget);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(analyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay);
updateDisplay();
}
QDialog *ManaCurveWidget::createConfigDialog(QWidget *parent)
{
auto *dlg = new ManaCurveConfigDialog(analyzer, parent);
dlg->setFromConfig(config);
return dlg;
}
QJsonObject ManaCurveWidget::extractConfigFromDialog(QDialog *dlg) const
{
auto *mc = qobject_cast<ManaCurveConfigDialog *>(dlg);
return mc ? mc->result().toJson() : QJsonObject{};
}
static void buildMapsByCategory(const QHash<QString, QHash<int, int>> &categoryCounts,
const QHash<QString, QHash<int, QStringList>> &categoryCards,
QMap<int, QMap<QString, int>> &outCmcMap,
QMap<QString, QMap<int, QStringList>> &outCardsMap)
{
outCmcMap.clear();
outCardsMap.clear();
for (auto catIt = categoryCounts.cbegin(); catIt != categoryCounts.cend(); ++catIt) {
const QString &category = catIt.key();
const auto &countsByCmc = catIt.value();
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it)
outCmcMap[it.key()][category] = it.value();
}
for (auto catIt = categoryCards.cbegin(); catIt != categoryCards.cend(); ++catIt) {
const QString &category = catIt.key();
const auto &cardsByCmc = catIt.value();
for (auto it = cardsByCmc.cbegin(); it != cardsByCmc.cend(); ++it)
outCardsMap[category][it.key()] = it.value();
}
}
static void findGlobalCmcRange(const QHash<QString, QHash<int, int>> &categoryCounts, int &minCmc, int &maxCmc)
{
minCmc = 0;
maxCmc = 0;
for (const auto &countsByCmc : categoryCounts) {
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it)
maxCmc = qMax(maxCmc, it.key());
}
}
void ManaCurveWidget::updateDisplay()
{
QHash<QString, QHash<int, int>> categoryCounts;
QHash<QString, QHash<int, QStringList>> categoryCards;
if (config.groupBy == "color") {
categoryCounts = analyzer->getManaCurveByColor();
categoryCards = analyzer->getManaCurveCardsByColor();
} else if (config.groupBy == "subtype") {
categoryCounts = analyzer->getManaCurveBySubtype();
categoryCards = analyzer->getManaCurveCardsBySubtype();
} else if (config.groupBy == "power") {
categoryCounts = analyzer->getManaCurveByPower();
categoryCards = analyzer->getManaCurveCardsByPower();
} else {
categoryCounts = analyzer->getManaCurveByType();
categoryCards = analyzer->getManaCurveCardsByType();
}
QMap<int, QMap<QString, int>> cmcMap;
QMap<QString, QMap<int, QStringList>> cardsMap;
buildMapsByCategory(categoryCounts, categoryCards, cmcMap, cardsMap);
int minCmc = 0;
int maxCmc = 0;
findGlobalCmcRange(categoryCounts, minCmc, maxCmc);
int highest = 1;
for (int cmc = minCmc; cmc <= maxCmc; ++cmc) {
int sum = 0;
const auto cmcIt = cmcMap.constFind(cmc);
if (cmcIt != cmcMap.cend()) {
for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) {
if (!config.filters.isEmpty() && !config.filters.contains(it.key())) {
continue;
}
sum += it.value();
}
}
highest = qMax(highest, sum);
}
totalWidget->updateDisplay(config.groupBy, minCmc, maxCmc, highest, cmcMap, cardsMap, config);
totalWidget->setVisible(config.showMain);
categoryWidget->updateDisplay(minCmc, maxCmc, highest, categoryCounts, categoryCards, config);
categoryWidget->setVisible(config.showCategoryRows);
}

View File

@@ -1,50 +0,0 @@
/**
* @file mana_curve_widget.h
* @ingroup DeckEditorAnalyticsWidgets
* @brief TODO: Document this.
*/
#ifndef MANA_CURVE_WIDGET_H
#define MANA_CURVE_WIDGET_H
#include "../../abstract_analytics_panel_widget.h"
#include "mana_curve_category_widget.h"
#include "mana_curve_config.h"
#include "mana_curve_total_widget.h"
#include <QVBoxLayout>
class SegmentedBarWidget;
class DeckListStatisticsAnalyzer;
class ManaCurveWidget : public AbstractAnalyticsPanelWidget
{
Q_OBJECT
public slots:
// QSize sizeHint() const override;
void updateDisplay() override;
QDialog *createConfigDialog(QWidget *parent) override;
public:
ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaCurveConfig cfg = {});
QJsonObject saveConfig() const override
{
return config.toJson();
}
void loadConfig(const QJsonObject &o) override
{
config = ManaCurveConfig::fromJson(o);
updateDisplay();
};
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
private:
ManaCurveConfig config;
ManaCurveTotalWidget *totalWidget;
ManaCurveCategoryWidget *categoryWidget;
};
#endif // MANA_CURVE_WIDGET_H

View File

@@ -1,31 +0,0 @@
#include "mana_devotion_config.h"
QJsonObject ManaDevotionConfig::toJson() const
{
QJsonObject jsonObject;
QJsonArray jsonArray;
jsonObject["displayType"] = displayType;
for (auto &filter : filters) {
jsonArray.append(filter);
}
jsonObject["filters"] = jsonArray;
return jsonObject;
}
ManaDevotionConfig ManaDevotionConfig::fromJson(const QJsonObject &o)
{
ManaDevotionConfig config;
if (o.contains("displayType")) {
config.displayType = o["displayType"].toString();
}
if (o.contains("filters")) {
config.filters.clear();
for (auto v : o["filters"].toArray()) {
config.filters << v.toString();
}
}
return config;
}

View File

@@ -1,18 +0,0 @@
#ifndef COCKATRICE_MANA_DEVOTION_CONFIG_H
#define COCKATRICE_MANA_DEVOTION_CONFIG_H
#include <QJsonArray>
#include <QJsonObject>
#include <QStringList>
struct ManaDevotionConfig
{
QString displayType; // "pie" or "bar" or "combinedBar"
QStringList filters; // which colors to show, empty = all
QJsonObject toJson() const;
static ManaDevotionConfig fromJson(const QJsonObject &o);
};
#endif // COCKATRICE_MANA_DEVOTION_CONFIG_H

View File

@@ -1,62 +0,0 @@
#include "mana_devotion_config_dialog.h"
ManaDevotionConfigDialog::ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *analyzer,
ManaDevotionConfig initial,
QWidget *parent)
: QDialog(parent), config(initial)
{
layout = new QVBoxLayout(this);
labelDisplayType = new QLabel(this);
layout->addWidget(labelDisplayType);
displayType = new QComboBox(this);
layout->addWidget(displayType);
labelFilters = new QLabel(this);
layout->addWidget(labelFilters);
filterList = new QListWidget(this);
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
QStringList colors = analyzer->getDevotionPipCount().keys();
colors.sort();
filterList->addItems(colors);
layout->addWidget(filterList);
// select initial filters
for (int i = 0; i < filterList->count(); ++i) {
if (config.filters.contains(filterList->item(i)->text()))
filterList->item(i)->setSelected(true);
}
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
layout->addWidget(buttons);
connect(buttons, &QDialogButtonBox::accepted, this, &ManaDevotionConfigDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &ManaDevotionConfigDialog::reject);
// populate combo box items
displayType->addItems({"pie", "bar", "combinedBar"});
retranslateUi();
}
void ManaDevotionConfigDialog::retranslateUi()
{
labelDisplayType->setText(tr("Display type:"));
displayType->setItemText(0, tr("pie"));
displayType->setItemText(1, tr("bar"));
displayType->setItemText(2, tr("combinedBar"));
labelFilters->setText(tr("Filter Colors (optional):"));
}
void ManaDevotionConfigDialog::accept()
{
config.displayType = displayType->currentText();
config.filters.clear();
for (auto *item : filterList->selectedItems()) {
config.filters << item->text();
}
QDialog::accept();
}

View File

@@ -1,42 +0,0 @@
#ifndef COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H
#define COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H
#include "../../deck_list_statistics_analyzer.h"
#include "mana_devotion_config.h"
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
class ManaDevotionConfigDialog : public QDialog
{
Q_OBJECT
public:
ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *analyzer,
ManaDevotionConfig initial = {},
QWidget *parent = nullptr);
void retranslateUi();
void accept() override;
ManaDevotionConfig result() const
{
return config;
}
private:
ManaDevotionConfig config;
QVBoxLayout *layout;
QLabel *labelDisplayType;
QComboBox *displayType;
QLabel *labelFilters;
QListWidget *filterList;
QDialogButtonBox *buttons;
};
#endif // COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H

View File

@@ -1,123 +0,0 @@
#include "mana_devotion_widget.h"
#include "../../../general/display/charts/bars/bar_widget.h"
#include "../../../general/display/charts/bars/color_bar.h"
#include "../../../general/display/charts/pies/color_pie.h"
#include "../../analytics_panel_widget_registrar.h"
#include "../../deck_list_statistics_analyzer.h"
#include "mana_devotion_config_dialog.h"
#include <QHash>
#include <QInputDialog>
namespace
{
AnalyticsPanelWidgetRegistrar registerManaDevotion{
"manaDevotion", ManaDevotionWidget::tr("Mana Devotion"),
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaDevotionWidget(parent, analyzer); }};
} // anonymous namespace
ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaDevotionConfig cfg)
: AbstractAnalyticsPanelWidget(parent, analyzer), config(std::move(cfg))
{
barContainer = new QWidget(this);
barLayout = new QHBoxLayout(barContainer);
barContainer->setLayout(barLayout);
layout->addWidget(barContainer);
updateDisplay();
}
void ManaDevotionWidget::updateDisplay()
{
// Clear previous widgets
while (QLayoutItem *item = barLayout->takeAt(0)) {
if (item->widget()) {
item->widget()->deleteLater();
}
delete item;
}
auto &pipCount = analyzer->getDevotionPipCount();
auto &cardCount = analyzer->getDevotionCardCount();
// Convert keys to single QChar form
QHash<QChar, int> devoMap;
for (auto key : pipCount.keys()) {
devoMap[key[0]] = pipCount[key];
}
// Apply filters
if (!config.filters.isEmpty()) {
QHash<QChar, int> filtered;
for (auto f : config.filters) {
if (devoMap.contains(f[0])) {
filtered[f[0]] = devoMap[f[0]];
}
}
devoMap = filtered;
}
// Determine maximum for bar charts
int highest = 1;
for (auto val : devoMap) {
highest = std::max(highest, val);
}
// Convert to QMap<QString,int> for ColorBar / ColorPie
QMap<QString, int> mapSorted;
for (auto it = devoMap.begin(); it != devoMap.end(); ++it) {
mapSorted.insert(QString(it.key()), it.value());
}
// Color map
QHash<QChar, QColor> colors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)},
{'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)},
{'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}};
// Choose display mode
if (config.displayType == "bar") {
// One BarWidget per devotion color
for (auto c : devoMap.keys()) {
QString label = QString("%1 %2 (%3)").arg(c).arg(devoMap[c]).arg(cardCount.value(QString(c)));
BarWidget *bar = new BarWidget(label, devoMap[c], highest, colors.value(c, Qt::gray), this);
barLayout->addWidget(bar);
}
} else if (config.displayType == "combinedBar") {
// Stacked devotion bar
ColorBar *cb = new ColorBar(mapSorted, this);
cb->setMinimumHeight(30);
barLayout->addWidget(cb);
} else if (config.displayType == "pie") {
// Devotion pie chart
ColorPie *pie = new ColorPie(mapSorted, this);
pie->setMinimumSize(200, 200);
barLayout->addWidget(pie);
}
update();
}
QDialog *ManaDevotionWidget::createConfigDialog(QWidget *parent)
{
ManaDevotionConfigDialog *dlg = new ManaDevotionConfigDialog(analyzer, config, parent);
return dlg;
}
QJsonObject ManaDevotionWidget::extractConfigFromDialog(QDialog *dlg) const
{
auto *mc = qobject_cast<ManaDevotionConfigDialog *>(dlg);
if (!mc) {
return {};
}
return mc->result().toJson();
}
QSize ManaDevotionWidget::sizeHint() const
{
return QSize(800, 150);
}

Some files were not shown because too many files have changed in this diff Show More