mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-09 01:23:57 -07:00
Merge branch 'master' into tooomm-doxy_awesome_theme
This commit is contained in:
commit
7613db0348
211 changed files with 10701 additions and 4480 deletions
|
|
@ -156,6 +156,18 @@ function ccachestatsverbose() {
|
||||||
|
|
||||||
# Compile
|
# Compile
|
||||||
if [[ $RUNNER_OS == macOS ]]; then
|
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
|
if [[ $TARGET_MACOS_VERSION ]]; then
|
||||||
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
|
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
|
||||||
|
|
|
||||||
40
.ci/resolve_latest_aqt_qt_version.sh
Executable file
40
.ci/resolve_latest_aqt_qt_version.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/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"
|
||||||
25
.ci/thin_macos_qtlib.sh
Executable file
25
.ci/thin_macos_qtlib.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/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::"
|
||||||
97
.github/workflows/desktop-build.yml
vendored
97
.github/workflows/desktop-build.yml
vendored
|
|
@ -142,7 +142,6 @@ jobs:
|
||||||
- distro: Ubuntu
|
- distro: Ubuntu
|
||||||
version: 22.04
|
version: 22.04
|
||||||
package: DEB
|
package: DEB
|
||||||
test: skip # Running tests on all distros is superfluous
|
|
||||||
|
|
||||||
- distro: Ubuntu
|
- distro: Ubuntu
|
||||||
version: 24.04
|
version: 24.04
|
||||||
|
|
@ -213,7 +212,7 @@ jobs:
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
id: upload_artifact
|
id: upload_artifact
|
||||||
if: matrix.package != 'skip'
|
if: matrix.package != 'skip'
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{matrix.distro}}${{matrix.version}}-package
|
name: ${{matrix.distro}}${{matrix.version}}-package
|
||||||
path: ${{steps.build.outputs.path}}
|
path: ${{steps.build.outputs.path}}
|
||||||
|
|
@ -263,7 +262,6 @@ jobs:
|
||||||
qt_version: 6.6.*
|
qt_version: 6.6.*
|
||||||
qt_arch: clang_64
|
qt_arch: clang_64
|
||||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||||
cache_qt: false # qt caches take too much space for macOS (1.1Gi)
|
|
||||||
cmake_generator: Ninja
|
cmake_generator: Ninja
|
||||||
use_ccache: 1
|
use_ccache: 1
|
||||||
|
|
||||||
|
|
@ -279,7 +277,6 @@ jobs:
|
||||||
qt_version: 6.6.*
|
qt_version: 6.6.*
|
||||||
qt_arch: clang_64
|
qt_arch: clang_64
|
||||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||||
cache_qt: false
|
|
||||||
cmake_generator: Ninja
|
cmake_generator: Ninja
|
||||||
use_ccache: 1
|
use_ccache: 1
|
||||||
|
|
||||||
|
|
@ -295,7 +292,6 @@ jobs:
|
||||||
qt_version: 6.6.*
|
qt_version: 6.6.*
|
||||||
qt_arch: clang_64
|
qt_arch: clang_64
|
||||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||||
cache_qt: false
|
|
||||||
cmake_generator: Ninja
|
cmake_generator: Ninja
|
||||||
use_ccache: 1
|
use_ccache: 1
|
||||||
|
|
||||||
|
|
@ -308,7 +304,6 @@ jobs:
|
||||||
qt_version: 6.6.*
|
qt_version: 6.6.*
|
||||||
qt_arch: clang_64
|
qt_arch: clang_64
|
||||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||||
cache_qt: false
|
|
||||||
cmake_generator: Ninja
|
cmake_generator: Ninja
|
||||||
use_ccache: 1
|
use_ccache: 1
|
||||||
|
|
||||||
|
|
@ -321,7 +316,6 @@ jobs:
|
||||||
artifact_name: Windows7-installer
|
artifact_name: Windows7-installer
|
||||||
qt_version: 5.15.*
|
qt_version: 5.15.*
|
||||||
qt_arch: win64_msvc2019_64
|
qt_arch: win64_msvc2019_64
|
||||||
cache_qt: true
|
|
||||||
cmake_generator: "Visual Studio 17 2022"
|
cmake_generator: "Visual Studio 17 2022"
|
||||||
cmake_generator_platform: x64
|
cmake_generator_platform: x64
|
||||||
|
|
||||||
|
|
@ -335,13 +329,17 @@ jobs:
|
||||||
qt_version: 6.6.*
|
qt_version: 6.6.*
|
||||||
qt_arch: win64_msvc2019_64
|
qt_arch: win64_msvc2019_64
|
||||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||||
cache_qt: true
|
|
||||||
cmake_generator: "Visual Studio 17 2022"
|
cmake_generator: "Visual Studio 17 2022"
|
||||||
cmake_generator_platform: x64
|
cmake_generator_platform: x64
|
||||||
|
|
||||||
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
|
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
|
||||||
needs: configure
|
needs: configure
|
||||||
runs-on: ${{matrix.runner}}
|
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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -356,24 +354,72 @@ jobs:
|
||||||
with:
|
with:
|
||||||
msbuild-architecture: x64
|
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
|
- name: Setup ccache
|
||||||
if: matrix.use_ccache == 1
|
if: matrix.use_ccache == 1 && matrix.os == 'macOS'
|
||||||
uses: jianmingyong/ccache-action@v1
|
run: brew install ccache
|
||||||
with:
|
|
||||||
install-type: "binary"
|
|
||||||
ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}
|
|
||||||
max-size: 500M
|
|
||||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install Qt ${{matrix.qt_version}}
|
- 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 }}
|
||||||
|
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}}-
|
||||||
|
|
||||||
|
- 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'
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{matrix.qt_version}}
|
version: ${{matrix.qt_version}}
|
||||||
arch: ${{matrix.qt_arch}}
|
arch: ${{matrix.qt_arch}}
|
||||||
modules: ${{matrix.qt_modules}}
|
modules: ${{matrix.qt_modules}}
|
||||||
cache: ${{matrix.cache_qt}}
|
cache: true
|
||||||
|
|
||||||
- name: Setup vcpkg cache
|
- name: Setup vcpkg cache
|
||||||
id: vcpkg-cache
|
id: vcpkg-cache
|
||||||
|
|
@ -403,6 +449,15 @@ jobs:
|
||||||
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
|
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
|
||||||
run: .ci/compile.sh --server --test --vcpkg
|
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
|
- name: Sign app bundle
|
||||||
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
|
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
|
||||||
env:
|
env:
|
||||||
|
|
@ -450,7 +505,7 @@ jobs:
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
id: upload_artifact
|
id: upload_artifact
|
||||||
if: matrix.make_package
|
if: matrix.make_package
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{matrix.artifact_name}}
|
name: ${{matrix.artifact_name}}
|
||||||
path: ${{steps.build.outputs.path}}
|
path: ${{steps.build.outputs.path}}
|
||||||
|
|
@ -458,7 +513,7 @@ jobs:
|
||||||
|
|
||||||
- name: Upload pdb database
|
- name: Upload pdb database
|
||||||
if: matrix.os == 'Windows'
|
if: matrix.os == 'Windows'
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: Windows${{matrix.target}}-debug-pdbs
|
name: Windows${{matrix.target}}-debug-pdbs
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
46
README.md
46
README.md
|
|
@ -44,10 +44,10 @@ Latest <kbd>beta</kbd> version:
|
||||||
|
|
||||||
# Related Repositories
|
# Related Repositories
|
||||||
|
|
||||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice
|
- [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): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to 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
|
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
|
||||||
- [Cockatrice @Flathub](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration for our Linux `flatpak` package
|
- [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)
|
||||||
|
|
||||||
|
|
||||||
# Community Resources [](https://discord.gg/3Z9yzmA)
|
# Community Resources [](https://discord.gg/3Z9yzmA)
|
||||||
|
|
@ -55,7 +55,6 @@ Latest <kbd>beta</kbd> version:
|
||||||
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
|
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
|
||||||
- [Official Website](https://cockatrice.github.io)
|
- [Official Website](https://cockatrice.github.io)
|
||||||
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
|
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
|
||||||
- [Official Code Documentation](https://cockatrice.github.io/docs)
|
|
||||||
- [Official Discord](https://discord.gg/3Z9yzmA)
|
- [Official Discord](https://discord.gg/3Z9yzmA)
|
||||||
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
|
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
|
||||||
|
|
||||||
|
|
@ -64,6 +63,23 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p
|
||||||
|
|
||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
|
<p>
|
||||||
|
<a href="#code">Code</a> <b>|</b>
|
||||||
|
<a href="#documentation-">Documentation</a> <b>|</b>
|
||||||
|
<a href="#translation-">Translation</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
#### Repository Activity
|
||||||
|

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

|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
|
|
||||||
<br>
|
|
||||||
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
|
|
||||||
</a><br>
|
|
||||||
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### Translations [](https://explore.transifex.com/cockatrice/cockatrice/)
|
|
||||||
|
|
||||||
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
|
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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ if(Qt6_FOUND)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
find_package(
|
find_package(
|
||||||
Qt5 5.8.0
|
Qt5 5.15.2
|
||||||
COMPONENTS ${REQUIRED_QT_COMPONENTS}
|
COMPONENTS ${REQUIRED_QT_COMPONENTS}
|
||||||
QUIET HINTS ${Qt5_DIR}
|
QUIET HINTS ${Qt5_DIR}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -144,28 +144,54 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/cards/card_size_widget.cpp
|
src/interface/widgets/cards/card_size_widget.cpp
|
||||||
src/interface/widgets/cards/deck_card_zone_display_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/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_analytics_widget.cpp
|
||||||
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
|
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
|
||||||
src/interface/widgets/deck_analytics/mana_base_widget.cpp
|
src/interface/widgets/deck_analytics/resizable_panel.cpp
|
||||||
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
|
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
|
||||||
src/interface/widgets/deck_analytics/mana_devotion_widget.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_editor/deck_list_history_manager_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_card_info_dock_widget.cpp
|
||||||
src/interface/widgets/deck_editor/deck_editor_database_display_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_deck_dock_widget.cpp
|
||||||
src/interface/widgets/deck_editor/deck_editor_filter_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_editor_printing_selector_dock_widget.cpp
|
||||||
src/interface/widgets/deck_editor/deck_list_style_proxy.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/background_sources.cpp
|
||||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||||
src/interface/widgets/general/display/banner_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_label.cpp
|
||||||
src/interface/widgets/general/display/dynamic_font_size_push_button.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/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/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_styled_button.cpp
|
||||||
src/interface/widgets/general/home_widget.cpp
|
src/interface/widgets/general/home_widget.cpp
|
||||||
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
||||||
|
|
@ -287,6 +313,10 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
|
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
|
||||||
src/interface/key_signals.cpp
|
src/interface/key_signals.cpp
|
||||||
src/interface/logger.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)
|
add_subdirectory(sounds)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -29,6 +29,11 @@ searches are case insensitive.
|
||||||
<dt><u>F</u>ormat:</dt>
|
<dt><u>F</u>ormat:</dt>
|
||||||
<dd>[f:standard](#f:standard) <small>(Any deck with format set to standard)</small></dd>
|
<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>
|
<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="#[[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>
|
<dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>
|
||||||
|
|
|
||||||
|
|
@ -43,23 +43,23 @@ void DeckStatsInterface::queryFinished(QNetworkReply *reply)
|
||||||
deleteLater();
|
deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
|
void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
|
||||||
{
|
{
|
||||||
DeckList deckWithoutTokens;
|
DeckList deckWithoutTokens;
|
||||||
copyDeckWithoutTokens(*deck, deckWithoutTokens);
|
copyDeckWithoutTokens(deck, deckWithoutTokens);
|
||||||
|
|
||||||
QUrl params;
|
QUrl params;
|
||||||
QUrlQuery urlQuery;
|
QUrlQuery urlQuery;
|
||||||
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
|
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
|
||||||
urlQuery.addQueryItem("decktitle", deck->getName());
|
urlQuery.addQueryItem("decktitle", deck.getName());
|
||||||
params.setQuery(urlQuery);
|
params.setQuery(urlQuery);
|
||||||
data->append(params.query(QUrl::EncodeReserved).toUtf8());
|
data.append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckStatsInterface::analyzeDeck(DeckList *deck)
|
void DeckStatsInterface::analyzeDeck(const DeckList &deck)
|
||||||
{
|
{
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
getAnalyzeRequestData(deck, &data);
|
getAnalyzeRequestData(deck, data);
|
||||||
|
|
||||||
QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
|
QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
|
@ -68,7 +68,7 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck)
|
||||||
manager->post(request, data);
|
manager->post(request, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination)
|
void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination)
|
||||||
{
|
{
|
||||||
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
|
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
|
||||||
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,15 @@ private:
|
||||||
* closest non-token card instead. So we construct a new deck which has no
|
* closest non-token card instead. So we construct a new deck which has no
|
||||||
* tokens.
|
* tokens.
|
||||||
*/
|
*/
|
||||||
void copyDeckWithoutTokens(DeckList &source, DeckList &destination);
|
void copyDeckWithoutTokens(const DeckList &source, DeckList &destination);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void queryFinished(QNetworkReply *reply);
|
void queryFinished(QNetworkReply *reply);
|
||||||
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
|
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
||||||
void analyzeDeck(DeckList *deck);
|
void analyzeDeck(const DeckList &deck);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -67,24 +67,24 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
||||||
deleteLater();
|
deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
|
void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
|
||||||
{
|
{
|
||||||
DeckList mainboard, sideboard;
|
DeckList mainboard, sideboard;
|
||||||
copyDeckSplitMainAndSide(*deck, mainboard, sideboard);
|
copyDeckSplitMainAndSide(deck, mainboard, sideboard);
|
||||||
|
|
||||||
QUrl params;
|
QUrl params;
|
||||||
QUrlQuery urlQuery;
|
QUrlQuery urlQuery;
|
||||||
urlQuery.addQueryItem("name", deck->getName());
|
urlQuery.addQueryItem("name", deck.getName());
|
||||||
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
|
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
|
||||||
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
|
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
|
||||||
params.setQuery(urlQuery);
|
params.setQuery(urlQuery);
|
||||||
data->append(params.query(QUrl::EncodeReserved).toUtf8());
|
data.append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TappedOutInterface::analyzeDeck(DeckList *deck)
|
void TappedOutInterface::analyzeDeck(const DeckList &deck)
|
||||||
{
|
{
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
getAnalyzeRequestData(deck, &data);
|
getAnalyzeRequestData(deck, data);
|
||||||
|
|
||||||
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
|
@ -93,7 +93,7 @@ void TappedOutInterface::analyzeDeck(DeckList *deck)
|
||||||
manager->post(request, data);
|
manager->post(request, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard)
|
void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
|
||||||
{
|
{
|
||||||
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
|
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
|
||||||
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,14 @@ private:
|
||||||
QNetworkAccessManager *manager;
|
QNetworkAccessManager *manager;
|
||||||
|
|
||||||
CardDatabase &cardDatabase;
|
CardDatabase &cardDatabase;
|
||||||
void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard);
|
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
|
||||||
private slots:
|
private slots:
|
||||||
void queryFinished(QNetworkReply *reply);
|
void queryFinished(QNetworkReply *reply);
|
||||||
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
|
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
||||||
void analyzeDeck(DeckList *deck);
|
void analyzeDeck(const DeckList &deck);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "../network/update/client/release_channel.h"
|
#include "../network/update/client/release_channel.h"
|
||||||
#include "card_counter_settings.h"
|
#include "card_counter_settings.h"
|
||||||
|
#include "version_string.h"
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
@ -198,7 +199,13 @@ SettingsCache::SettingsCache()
|
||||||
|
|
||||||
mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
|
mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
|
||||||
|
|
||||||
checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).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
|
||||||
|
}
|
||||||
startupCardUpdateCheckPromptForUpdate =
|
startupCardUpdateCheckPromptForUpdate =
|
||||||
settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
|
settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
|
||||||
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
|
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
|
||||||
|
|
@ -206,7 +213,15 @@ SettingsCache::SettingsCache()
|
||||||
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
|
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
|
||||||
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
|
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
|
||||||
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
|
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
|
||||||
updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt();
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
lang = settings->value("personal/lang").toString();
|
lang = settings->value("personal/lang").toString();
|
||||||
keepalive = settings->value("personal/keepalive", 3).toInt();
|
keepalive = settings->value("personal/keepalive", 3).toInt();
|
||||||
|
|
@ -273,6 +288,7 @@ SettingsCache::SettingsCache()
|
||||||
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
|
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
|
||||||
|
|
||||||
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
|
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
|
||||||
|
showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
|
||||||
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
|
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
|
||||||
roundCardCorners = settings->value("cards/roundcardcorners", true).toBool();
|
roundCardCorners = settings->value("cards/roundcardcorners", true).toBool();
|
||||||
overrideAllCardArtWithPersonalPreference =
|
overrideAllCardArtWithPersonalPreference =
|
||||||
|
|
@ -700,6 +716,13 @@ void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts)
|
||||||
settings->setValue("menu/showshortcuts", 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)
|
void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames)
|
||||||
{
|
{
|
||||||
displayCardNames = static_cast<bool>(_displayCardNames);
|
displayCardNames = static_cast<bool>(_displayCardNames);
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ signals:
|
||||||
void homeTabBackgroundShuffleFrequencyChanged();
|
void homeTabBackgroundShuffleFrequencyChanged();
|
||||||
void picDownloadChanged();
|
void picDownloadChanged();
|
||||||
void showStatusBarChanged(bool state);
|
void showStatusBarChanged(bool state);
|
||||||
|
void showGameSelectorFilterToolbarChanged(bool state);
|
||||||
void displayCardNamesChanged();
|
void displayCardNamesChanged();
|
||||||
void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference);
|
void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference);
|
||||||
void bumpSetsWithCardsInDeckToTopChanged();
|
void bumpSetsWithCardsInDeckToTopChanged();
|
||||||
|
|
@ -236,6 +237,7 @@ private:
|
||||||
bool annotateTokens;
|
bool annotateTokens;
|
||||||
QByteArray tabGameSplitterSizes;
|
QByteArray tabGameSplitterSizes;
|
||||||
bool showShortcuts;
|
bool showShortcuts;
|
||||||
|
bool showGameSelectorFilterToolbar;
|
||||||
bool displayCardNames;
|
bool displayCardNames;
|
||||||
bool overrideAllCardArtWithPersonalPreference;
|
bool overrideAllCardArtWithPersonalPreference;
|
||||||
bool bumpSetsWithCardsInDeckToTop;
|
bool bumpSetsWithCardsInDeckToTop;
|
||||||
|
|
@ -553,6 +555,10 @@ public:
|
||||||
{
|
{
|
||||||
return showShortcuts;
|
return showShortcuts;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const
|
||||||
|
{
|
||||||
|
return showGameSelectorFilterToolbar;
|
||||||
|
}
|
||||||
[[nodiscard]] bool getDisplayCardNames() const
|
[[nodiscard]] bool getDisplayCardNames() const
|
||||||
{
|
{
|
||||||
return displayCardNames;
|
return displayCardNames;
|
||||||
|
|
@ -1017,6 +1023,7 @@ public slots:
|
||||||
void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens);
|
void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens);
|
||||||
void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes);
|
void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes);
|
||||||
void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts);
|
void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts);
|
||||||
|
void setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar);
|
||||||
void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames);
|
void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames);
|
||||||
void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt);
|
void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt);
|
||||||
void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop);
|
void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop);
|
||||||
|
|
|
||||||
|
|
@ -150,12 +150,7 @@ void ShortcutTreeView::currentChanged(const QModelIndex ¤t, const QModelIn
|
||||||
*/
|
*/
|
||||||
void ShortcutTreeView::updateSearchString(const QString &searchString)
|
void ShortcutTreeView::updateSearchString(const QString &searchString)
|
||||||
{
|
{
|
||||||
#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
|
QStringList searchWords = searchString.split(" ", Qt::SkipEmptyParts);
|
||||||
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); };
|
auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); };
|
||||||
std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex);
|
std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex);
|
||||||
|
|
|
||||||
|
|
@ -660,6 +660,12 @@ private:
|
||||||
{"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"),
|
{"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"),
|
||||||
parseSequenceString("Ctrl+M"),
|
parseSequenceString("Ctrl+M"),
|
||||||
ShortcutGroup::Drawing)},
|
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"),
|
{"Player/aDrawCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw a Card"),
|
||||||
parseSequenceString("Ctrl+D"),
|
parseSequenceString("Ctrl+D"),
|
||||||
ShortcutGroup::Drawing)},
|
ShortcutGroup::Drawing)},
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
|
||||||
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
|
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
|
||||||
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
|
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
|
||||||
|
|
||||||
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery
|
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / CommentQuery / GenericQuery
|
||||||
|
|
||||||
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
|
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
|
||||||
|
|
||||||
|
|
@ -25,6 +25,7 @@ DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String
|
||||||
FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String
|
FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String
|
||||||
PathQuery <- [Pp] 'ath'? [:] String
|
PathQuery <- [Pp] 'ath'? [:] String
|
||||||
FormatQuery <- [Ff] 'ormat'? [:] String
|
FormatQuery <- [Ff] 'ormat'? [:] String
|
||||||
|
CommentQuery <- [Cc] ('omment' 's'?)? [:] String
|
||||||
|
|
||||||
GenericQuery <- String
|
GenericQuery <- String
|
||||||
|
|
||||||
|
|
@ -166,6 +167,14 @@ static void setupParserRules()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 {
|
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||||
auto name = std::any_cast<QString>(sv[0]);
|
auto name = std::any_cast<QString>(sv[0]);
|
||||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
||||||
|
|
|
||||||
|
|
@ -117,11 +117,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
|
||||||
chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option
|
chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option
|
||||||
} else {
|
} else {
|
||||||
chooseTokenFromDeckRadioButton->setChecked(true);
|
chooseTokenFromDeckRadioButton->setChecked(true);
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
||||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
|
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
|
||||||
#else
|
|
||||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *tokenChooseLayout = new QVBoxLayout;
|
auto *tokenChooseLayout = new QVBoxLayout;
|
||||||
|
|
@ -223,11 +219,7 @@ void DlgCreateToken::actChooseTokenFromAll(bool checked)
|
||||||
void DlgCreateToken::actChooseTokenFromDeck(bool checked)
|
void DlgCreateToken::actChooseTokenFromDeck(bool checked)
|
||||||
{
|
{
|
||||||
if (checked) {
|
if (checked) {
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
||||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
|
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
|
||||||
#else
|
|
||||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,16 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T
|
||||||
connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan);
|
connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan);
|
||||||
addAction(aMulligan);
|
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();
|
addSeparator();
|
||||||
|
|
||||||
mMoveHandMenu = addTearOffMenu(QString());
|
mMoveHandMenu = addTearOffMenu(QString());
|
||||||
|
|
@ -104,7 +114,9 @@ void HandMenu::retranslateUi()
|
||||||
aSortHandByType->setText(tr("Type"));
|
aSortHandByType->setText(tr("Type"));
|
||||||
aSortHandByManaValue->setText(tr("Mana Value"));
|
aSortHandByManaValue->setText(tr("Mana Value"));
|
||||||
|
|
||||||
aMulligan->setText(tr("Take &mulligan"));
|
aMulligan->setText(tr("Take &mulligan (Choose hand size)"));
|
||||||
|
aMulliganSame->setText(tr("Take mulligan (Same hand size)"));
|
||||||
|
aMulliganMinusOne->setText(tr("Take mulligan (Hand size - 1)"));
|
||||||
|
|
||||||
mMoveHandMenu->setTitle(tr("&Move hand to..."));
|
mMoveHandMenu->setTitle(tr("&Move hand to..."));
|
||||||
aMoveHandToTopLibrary->setText(tr("&Top of library"));
|
aMoveHandToTopLibrary->setText(tr("&Top of library"));
|
||||||
|
|
@ -128,6 +140,8 @@ void HandMenu::setShortcutsActive()
|
||||||
aSortHandByType->setShortcuts(shortcuts.getShortcut("Player/aSortHandByType"));
|
aSortHandByType->setShortcuts(shortcuts.getShortcut("Player/aSortHandByType"));
|
||||||
aSortHandByManaValue->setShortcuts(shortcuts.getShortcut("Player/aSortHandByManaValue"));
|
aSortHandByManaValue->setShortcuts(shortcuts.getShortcut("Player/aSortHandByManaValue"));
|
||||||
aMulligan->setShortcuts(shortcuts.getShortcut("Player/aMulligan"));
|
aMulligan->setShortcuts(shortcuts.getShortcut("Player/aMulligan"));
|
||||||
|
aMulliganSame->setShortcuts(shortcuts.getShortcut("Player/aMulliganSame"));
|
||||||
|
aMulliganMinusOne->setShortcuts(shortcuts.getShortcut("Player/aMulliganMinusOne"));
|
||||||
aRevealHandToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealHandToAll"));
|
aRevealHandToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealHandToAll"));
|
||||||
aRevealRandomHandCardToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealRandomHandCardToAll"));
|
aRevealRandomHandCardToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealRandomHandCardToAll"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ private:
|
||||||
|
|
||||||
QAction *aViewHand = nullptr;
|
QAction *aViewHand = nullptr;
|
||||||
QAction *aMulligan = nullptr;
|
QAction *aMulligan = nullptr;
|
||||||
|
QAction *aMulliganSame = nullptr;
|
||||||
|
QAction *aMulliganMinusOne = nullptr;
|
||||||
|
|
||||||
QMenu *mSortHand = nullptr;
|
QMenu *mSortHand = nullptr;
|
||||||
QAction *aSortHandByName = nullptr;
|
QAction *aSortHandByName = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -310,28 +310,48 @@ void PlayerActions::actMulligan()
|
||||||
{
|
{
|
||||||
int startSize = SettingsCache::instance().getStartingHandSize();
|
int startSize = SettingsCache::instance().getStartingHandSize();
|
||||||
int handSize = player->getHandZone()->getCards().size();
|
int handSize = player->getHandZone()->getCards().size();
|
||||||
int deckSize = player->getDeckZone()->getCards().size() + handSize; // hand is shuffled back into the deck
|
int deckSize = player->getDeckZone()->getCards().size() + handSize;
|
||||||
|
|
||||||
bool ok;
|
bool ok;
|
||||||
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"),
|
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"),
|
||||||
tr("Number of cards: (max. %1)").arg(deckSize) + '\n' +
|
tr("Number of cards: (max. %1)").arg(deckSize) + '\n' +
|
||||||
tr("0 and lower are in comparison to current hand size"),
|
tr("0 and lower are in comparison to current hand size"),
|
||||||
startSize, -handSize, deckSize, 1, &ok);
|
startSize, -handSize, deckSize, 1, &ok);
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Command_Mulligan cmd;
|
|
||||||
if (number < 1) {
|
if (number < 1) {
|
||||||
if (handSize == 0) {
|
number = handSize + number;
|
||||||
return;
|
|
||||||
}
|
|
||||||
cmd.set_number(handSize + number);
|
|
||||||
} else {
|
|
||||||
cmd.set_number(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);
|
||||||
sendGameCommand(cmd);
|
sendGameCommand(cmd);
|
||||||
if (startSize != number) {
|
|
||||||
SettingsCache::instance().setStartingHandSize(number);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actDrawCards()
|
void PlayerActions::actDrawCards()
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,9 @@ public slots:
|
||||||
void actDrawCards();
|
void actDrawCards();
|
||||||
void actUndoDraw();
|
void actUndoDraw();
|
||||||
void actMulligan();
|
void actMulligan();
|
||||||
|
void actMulliganSameSize();
|
||||||
|
void actMulliganMinusOne();
|
||||||
|
void doMulligan(int number);
|
||||||
|
|
||||||
void actPlay();
|
void actPlay();
|
||||||
void actPlayFacedown();
|
void actPlayFacedown();
|
||||||
|
|
|
||||||
|
|
@ -78,14 +78,7 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
|
||||||
void PlayerEventHandler::eventRollDie(const Event_RollDie &event)
|
void PlayerEventHandler::eventRollDie(const Event_RollDie &event)
|
||||||
{
|
{
|
||||||
if (!event.values().empty()) {
|
if (!event.values().empty()) {
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
||||||
QList<uint> rolls(event.values().begin(), event.values().end());
|
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());
|
std::sort(rolls.begin(), rolls.end());
|
||||||
emit logRollDie(player, static_cast<int>(event.sides()), rolls);
|
emit logRollDie(player, static_cast<int>(event.sides()), rolls);
|
||||||
} else if (event.value()) {
|
} else if (event.value()) {
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,20 @@
|
||||||
#include <QGraphicsLinearLayout>
|
#include <QGraphicsLinearLayout>
|
||||||
#include <QGraphicsProxyWidget>
|
#include <QGraphicsProxyWidget>
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
|
#include <QGraphicsView>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
|
#include <QStyle>
|
||||||
#include <QStyleOption>
|
#include <QStyleOption>
|
||||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
#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 _player the player the cards were revealed to.
|
||||||
* @param _origZone the zone the cards were revealed from.
|
* @param _origZone the zone the cards were revealed from.
|
||||||
|
|
@ -241,33 +249,191 @@ void ZoneViewWidget::retranslateUi()
|
||||||
pileViewCheckBox.setText(tr("pile view"));
|
pileViewCheckBox.setText(tr("pile view"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */)
|
void ZoneViewWidget::stopWindowDrag()
|
||||||
{
|
{
|
||||||
if (!scene())
|
if (!draggingWindow)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int titleBarHeight = 24;
|
draggingWindow = false;
|
||||||
|
ungrabMouse();
|
||||||
|
}
|
||||||
|
|
||||||
QPointF scenePos = pos();
|
void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event)
|
||||||
|
{
|
||||||
|
draggingWindow = true;
|
||||||
|
dragStartItemPos = pos();
|
||||||
|
dragStartScreenPos = event->screenPos();
|
||||||
|
dragView = findDragView(event->widget());
|
||||||
|
|
||||||
if (scenePos.x() < 0) {
|
// need to grab mouse to receive events and not miss initial movement
|
||||||
scenePos.setX(0);
|
grabMouse();
|
||||||
} else {
|
}
|
||||||
qreal maxw = scene()->sceneRect().width() - 100;
|
|
||||||
if (scenePos.x() > maxw)
|
QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const
|
||||||
scenePos.setX(maxw);
|
{
|
||||||
|
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.y() < titleBarHeight) {
|
// Fallback: frame-relative top-right
|
||||||
scenePos.setY(titleBarHeight);
|
return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight);
|
||||||
} else {
|
}
|
||||||
qreal maxh = scene()->sceneRect().height() - titleBarHeight;
|
|
||||||
if (scenePos.y() > maxh)
|
QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const
|
||||||
scenePos.setY(maxh);
|
{
|
||||||
|
QWidget *current = eventWidget;
|
||||||
|
while (current) {
|
||||||
|
if (auto *view = qobject_cast<QGraphicsView *>(current))
|
||||||
|
return view;
|
||||||
|
current = current->parentWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scenePos != pos())
|
if (scene() && !scene()->views().isEmpty())
|
||||||
setPos(scenePos);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
|
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
|
||||||
|
|
@ -350,6 +516,7 @@ void ZoneViewWidget::handleScrollBarChange(int value)
|
||||||
|
|
||||||
void ZoneViewWidget::closeEvent(QCloseEvent *event)
|
void ZoneViewWidget::closeEvent(QCloseEvent *event)
|
||||||
{
|
{
|
||||||
|
stopWindowDrag();
|
||||||
disconnect(zone, &ZoneViewZone::closed, this, 0);
|
disconnect(zone, &ZoneViewZone::closed, this, 0);
|
||||||
// manually call zone->close in order to remove it from the origZones views
|
// manually call zone->close in order to remove it from the origZones views
|
||||||
zone->close();
|
zone->close();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
* @ingroup GameGraphicsZones
|
* @ingroup GameGraphicsZones
|
||||||
* @brief TODO: Document this.
|
* @brief TODO: Document this.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef ZONEVIEWWIDGET_H
|
#ifndef ZONEVIEWWIDGET_H
|
||||||
#define ZONEVIEWWIDGET_H
|
#define ZONEVIEWWIDGET_H
|
||||||
|
|
||||||
|
|
@ -14,6 +13,7 @@
|
||||||
#include <QGraphicsProxyWidget>
|
#include <QGraphicsProxyWidget>
|
||||||
#include <QGraphicsWidget>
|
#include <QGraphicsWidget>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <QPointer>
|
||||||
#include <libcockatrice/utility/macros.h>
|
#include <libcockatrice/utility/macros.h>
|
||||||
|
|
||||||
class QLabel;
|
class QLabel;
|
||||||
|
|
@ -28,6 +28,8 @@ class ServerInfo_Card;
|
||||||
class QGraphicsSceneMouseEvent;
|
class QGraphicsSceneMouseEvent;
|
||||||
class QGraphicsSceneWheelEvent;
|
class QGraphicsSceneWheelEvent;
|
||||||
class QStyleOption;
|
class QStyleOption;
|
||||||
|
class QGraphicsView;
|
||||||
|
class QWidget;
|
||||||
|
|
||||||
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
|
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
|
||||||
{
|
{
|
||||||
|
|
@ -52,7 +54,6 @@ private:
|
||||||
ZoneViewZone *zone;
|
ZoneViewZone *zone;
|
||||||
QGraphicsWidget *zoneContainer;
|
QGraphicsWidget *zoneContainer;
|
||||||
|
|
||||||
QPushButton *closeButton;
|
|
||||||
QScrollBar *scrollBar;
|
QScrollBar *scrollBar;
|
||||||
ScrollableGraphicsProxyWidget *scrollBarProxy;
|
ScrollableGraphicsProxyWidget *scrollBarProxy;
|
||||||
|
|
||||||
|
|
@ -66,6 +67,33 @@ private:
|
||||||
int extraHeight;
|
int extraHeight;
|
||||||
Player *player;
|
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);
|
void resizeScrollbar(qreal newZoneHeight);
|
||||||
signals:
|
signals:
|
||||||
void closePressed(ZoneViewWidget *zv);
|
void closePressed(ZoneViewWidget *zv);
|
||||||
|
|
@ -76,7 +104,6 @@ private slots:
|
||||||
void resizeToZoneContents(bool forceInitialHeight = false);
|
void resizeToZoneContents(bool forceInitialHeight = false);
|
||||||
void handleScrollBarChange(int value);
|
void handleScrollBarChange(int value);
|
||||||
void zoneDeleted();
|
void zoneDeleted();
|
||||||
void moveEvent(QGraphicsSceneMoveEvent * /* event */) override;
|
|
||||||
void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override;
|
void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override;
|
||||||
void expandWindow();
|
void expandWindow();
|
||||||
|
|
||||||
|
|
@ -101,6 +128,10 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
void initStyleOption(QStyleOption *option) const 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;
|
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number,
|
||||||
font.setWeight(QFont::Bold);
|
font.setWeight(QFont::Bold);
|
||||||
|
|
||||||
QFontMetrics fm(font);
|
QFontMetrics fm(font);
|
||||||
double w = 1.3 *
|
double w = 1.3 * fm.horizontalAdvance(numStr);
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
fm.horizontalAdvance(numStr);
|
|
||||||
#else
|
|
||||||
fm.width(numStr);
|
|
||||||
#endif
|
|
||||||
double h = fm.height() * 1.3;
|
double h = fm.height() * 1.3;
|
||||||
if (w < h)
|
if (w < h)
|
||||||
w = h;
|
w = h;
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,7 @@ CardPictureLoaderWorker::CardPictureLoaderWorker()
|
||||||
// We need a timeout to ensure requests don't hang indefinitely in case of
|
// 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
|
// cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397
|
||||||
// Use Qt's default timeout (30s, as of 2023-02-22)
|
// Use Qt's default timeout (30s, as of 2023-02-22)
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
|
||||||
networkManager->setTransferTimeout();
|
networkManager->setTransferTimeout();
|
||||||
#endif
|
|
||||||
cache = new QNetworkDiskCache(this);
|
cache = new QNetworkDiskCache(this);
|
||||||
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
|
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
|
||||||
cache->setMaximumCacheSize(1024L * 1024L *
|
cache->setMaximumCacheSize(1024L * 1024L *
|
||||||
|
|
|
||||||
|
|
@ -287,14 +287,14 @@ static QString toDecklistExportString(const QList<const DecklistCardNode *> &car
|
||||||
* @param deckList The decklist to export
|
* @param deckList The decklist to export
|
||||||
* @param website The website we're sending the deck to
|
* @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
|
// Add the base url
|
||||||
QString deckString = "https://" + getDomainForWebsite(website) + "/?";
|
QString deckString = "https://" + getDomainForWebsite(website) + "/?";
|
||||||
|
|
||||||
// export all cards in zone
|
// export all cards in zone
|
||||||
QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN}));
|
QString mainBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_MAIN}));
|
||||||
QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE}));
|
QString sideBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_SIDE}));
|
||||||
|
|
||||||
// Remove the extra return at the end of the last cards
|
// Remove the extra return at the end of the last cards
|
||||||
mainBoardCards.chop(3);
|
mainBoardCards.chop(3);
|
||||||
|
|
@ -310,7 +310,7 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi
|
||||||
return deckString;
|
return deckString;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber)
|
void DeckLoader::saveToClipboard(const DeckList &deckList, bool addComments, bool addSetNameAndNumber)
|
||||||
{
|
{
|
||||||
QString buffer;
|
QString buffer;
|
||||||
QTextStream stream(&buffer);
|
QTextStream stream(&buffer);
|
||||||
|
|
@ -320,7 +320,7 @@ void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, boo
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
||||||
const DeckList *deckList,
|
const DeckList &deckList,
|
||||||
bool addComments,
|
bool addComments,
|
||||||
bool addSetNameAndNumber)
|
bool addSetNameAndNumber)
|
||||||
{
|
{
|
||||||
|
|
@ -329,7 +329,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop zones
|
// loop zones
|
||||||
for (auto zoneNode : deckList->getZoneNodes()) {
|
for (auto zoneNode : deckList.getZoneNodes()) {
|
||||||
saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber);
|
saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber);
|
||||||
|
|
||||||
// end of zone
|
// end of zone
|
||||||
|
|
@ -339,14 +339,14 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList)
|
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList)
|
||||||
{
|
{
|
||||||
if (!deckList->getName().isEmpty()) {
|
if (!deckList.getName().isEmpty()) {
|
||||||
out << "// " << deckList->getName() << "\n\n";
|
out << "// " << deckList.getName() << "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deckList->getComments().isEmpty()) {
|
if (!deckList.getComments().isEmpty()) {
|
||||||
QStringList commentRows = deckList->getComments().split(QRegularExpression("\n|\r\n|\r"));
|
QStringList commentRows = deckList.getComments().split(QRegularExpression("\n|\r\n|\r"));
|
||||||
for (const QString &row : commentRows) {
|
for (const QString &row : commentRows) {
|
||||||
out << "// " << row << "\n";
|
out << "// " << row << "\n";
|
||||||
}
|
}
|
||||||
|
|
@ -434,7 +434,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeckLoader::convertToCockatriceFormat(QString fileName)
|
bool DeckLoader::convertToCockatriceFormat(const QString &fileName)
|
||||||
{
|
{
|
||||||
// Change the file extension to .cod
|
// Change the file extension to .cod
|
||||||
QFileInfo fileInfo(fileName);
|
QFileInfo fileInfo(fileName);
|
||||||
|
|
@ -543,7 +543,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
|
||||||
cursor->movePosition(QTextCursor::End);
|
cursor->movePosition(QTextCursor::End);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
|
void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
|
||||||
{
|
{
|
||||||
QTextDocument doc;
|
QTextDocument doc;
|
||||||
|
|
||||||
|
|
@ -559,14 +559,14 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
|
||||||
headerCharFormat.setFontWeight(QFont::Bold);
|
headerCharFormat.setFontWeight(QFont::Bold);
|
||||||
|
|
||||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||||
cursor.insertText(deckList->getName());
|
cursor.insertText(deckList.getName());
|
||||||
|
|
||||||
headerCharFormat.setFontPointSize(12);
|
headerCharFormat.setFontPointSize(12);
|
||||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||||
cursor.insertText(deckList->getComments());
|
cursor.insertText(deckList.getComments());
|
||||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||||
|
|
||||||
for (auto zoneNode : deckList->getZoneNodes()) {
|
for (auto zoneNode : deckList.getZoneNodes()) {
|
||||||
cursor.insertHtml("<br><img src=theme:hr.jpg>");
|
cursor.insertHtml("<br><img src=theme:hr.jpg>");
|
||||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,11 @@ public:
|
||||||
bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt);
|
bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt);
|
||||||
bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt);
|
bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format 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 saveToClipboard(const DeckList &deckList, bool addComments = true, bool addSetNameAndNumber = true);
|
||||||
static bool saveToStream_Plain(QTextStream &out,
|
static bool saveToStream_Plain(QTextStream &out,
|
||||||
const DeckList *deckList,
|
const DeckList &deckList,
|
||||||
bool addComments = true,
|
bool addComments = true,
|
||||||
bool addSetNameAndNumber = true);
|
bool addSetNameAndNumber = true);
|
||||||
|
|
||||||
|
|
@ -72,9 +72,9 @@ public:
|
||||||
* @param printer The printer to render the decklist to.
|
* @param printer The printer to render the decklist to.
|
||||||
* @param deckList
|
* @param deckList
|
||||||
*/
|
*/
|
||||||
static void printDeckList(QPrinter *printer, const DeckList *deckList);
|
static void printDeckList(QPrinter *printer, const DeckList &deckList);
|
||||||
|
|
||||||
bool convertToCockatriceFormat(QString fileName);
|
bool convertToCockatriceFormat(const QString &fileName);
|
||||||
|
|
||||||
LoadedDeck &getDeck()
|
LoadedDeck &getDeck()
|
||||||
{
|
{
|
||||||
|
|
@ -91,7 +91,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node);
|
static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node);
|
||||||
static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList);
|
static void saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList);
|
||||||
|
|
||||||
static void saveToStream_DeckZone(QTextStream &out,
|
static void saveToStream_DeckZone(QTextStream &out,
|
||||||
const InnerDecklistNode *zoneNode,
|
const InnerDecklistNode *zoneNode,
|
||||||
|
|
|
||||||
|
|
@ -57,17 +57,10 @@ void Logger::openLogfileSession()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileStream.setDevice(&fileHandle);
|
fileStream.setDevice(&fileHandle);
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
||||||
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl;
|
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl;
|
||||||
fileStream << getClientVersion() << Qt::endl;
|
fileStream << getClientVersion() << Qt::endl;
|
||||||
fileStream << getSystemArchitecture() << Qt::endl;
|
fileStream << getSystemArchitecture() << Qt::endl;
|
||||||
fileStream << getClientInstallInfo() << 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;
|
logToFileEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,11 +70,7 @@ void Logger::closeLogfileSession()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
logToFileEnabled = false;
|
logToFileEnabled = false;
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
||||||
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl;
|
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl;
|
||||||
#else
|
|
||||||
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << endl;
|
|
||||||
#endif
|
|
||||||
fileHandle.close();
|
fileHandle.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,11 +92,7 @@ void Logger::internalLog(const QString &message)
|
||||||
std::cerr << message.toStdString() << std::endl; // Print to stdout
|
std::cerr << message.toStdString() << std::endl; // Print to stdout
|
||||||
|
|
||||||
if (logToFileEnabled) {
|
if (logToFileEnabled) {
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
||||||
fileStream << message << Qt::endl; // Print to fileStream
|
fileStream << message << Qt::endl; // Print to fileStream
|
||||||
#else
|
|
||||||
fileStream << message << endl; // Print to fileStream
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,9 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i
|
||||||
if (indexToWidgetMap.contains(index)) {
|
if (indexToWidgetMap.contains(index)) {
|
||||||
return indexToWidgetMap[index];
|
return indexToWidgetMap[index];
|
||||||
}
|
}
|
||||||
auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
|
auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString();
|
auto cardProviderId =
|
||||||
|
index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString();
|
||||||
|
|
||||||
auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true);
|
auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true);
|
||||||
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
|
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
|
||||||
|
|
@ -114,7 +115,7 @@ void CardGroupDisplayWidget::updateCardDisplays()
|
||||||
|
|
||||||
// This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway.
|
// This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway.
|
||||||
proxy.setSortRole(Qt::EditRole);
|
proxy.setSortRole(Qt::EditRole);
|
||||||
proxy.sort(1, Qt::AscendingOrder);
|
proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
|
||||||
|
|
||||||
// 1. trackedIndex is a source index → map it to proxy space
|
// 1. trackedIndex is a source index → map it to proxy space
|
||||||
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa
|
||||||
layout->addWidget(text, 0, Qt::AlignCenter);
|
layout->addWidget(text, 0, Qt::AlignCenter);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
setFrameStyle(QFrame::Panel | QFrame::Raised);
|
setFrameStyle(static_cast<int>(QFrame::Panel) | QFrame::Raised);
|
||||||
|
|
||||||
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
|
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
|
||||||
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);
|
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT
|
||||||
originalPos = this->pos();
|
originalPos = this->pos();
|
||||||
|
|
||||||
// Create the animation
|
// Create the animation
|
||||||
animation = new QPropertyAnimation(this, "pos");
|
animation = new QPropertyAnimation(this, "pos", this);
|
||||||
animation->setDuration(200); // 200ms animation duration
|
animation->setDuration(200); // 200ms animation duration
|
||||||
animation->setEasingCurve(QEasingCurve::OutQuad);
|
animation->setEasingCurve(QEasingCurve::OutQuad);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "card_group_display_widgets/flat_card_group_display_widget.h"
|
#include "card_group_display_widgets/flat_card_group_display_widget.h"
|
||||||
#include "card_group_display_widgets/overlapped_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 <QResizeEvent>
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
|
|
@ -82,10 +83,11 @@ void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *
|
||||||
|
|
||||||
void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index)
|
void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index)
|
||||||
{
|
{
|
||||||
auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
|
|
||||||
if (indexToWidgetMap.contains(index)) {
|
if (indexToWidgetMap.contains(index)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
if (displayType == DisplayType::Overlap) {
|
if (displayType == DisplayType::Overlap) {
|
||||||
auto *displayWidget = new OverlappedCardGroupDisplayWidget(
|
auto *displayWidget = new OverlappedCardGroupDisplayWidget(
|
||||||
cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria,
|
cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria,
|
||||||
|
|
@ -120,7 +122,7 @@ void DeckCardZoneDisplayWidget::displayCards()
|
||||||
QSortFilterProxyModel proxy;
|
QSortFilterProxyModel proxy;
|
||||||
proxy.setSourceModel(deckListModel);
|
proxy.setSourceModel(deckListModel);
|
||||||
proxy.setSortRole(Qt::EditRole);
|
proxy.setSortRole(Qt::EditRole);
|
||||||
proxy.sort(1, Qt::AscendingOrder);
|
proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
|
||||||
|
|
||||||
// 1. trackedIndex is a source index → map it to proxy space
|
// 1. trackedIndex is a source index → map it to proxy space
|
||||||
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
||||||
|
|
@ -229,10 +231,13 @@ QList<QString> DeckCardZoneDisplayWidget::getGroupCriteriaValueList()
|
||||||
{
|
{
|
||||||
QList<QString> groupCriteriaValues;
|
QList<QString> groupCriteriaValues;
|
||||||
|
|
||||||
QList<ExactCard> cardsInZone = deckListModel->getCardsForZone(zoneName);
|
QList<const DecklistCardNode *> nodes = deckListModel->getCardNodesForZone(zoneName);
|
||||||
|
|
||||||
for (const ExactCard &cardInZone : cardsInZone) {
|
for (auto node : nodes) {
|
||||||
groupCriteriaValues.append(cardInZone.getInfo().getProperty(activeGroupCriteria));
|
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||||
|
if (info) {
|
||||||
|
groupCriteriaValues.append(info->getProperty(activeGroupCriteria));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
groupCriteriaValues.removeDuplicates();
|
groupCriteriaValues.removeDuplicates();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#include "analytics_panel_widget_registrar.h"
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,236 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* @file mana_devotion_widget.h
|
||||||
|
* @ingroup DeckEditorAnalyticsWidgets
|
||||||
|
* @brief TODO: Document this.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MANA_DEVOTION_WIDGET_H
|
||||||
|
#define MANA_DEVOTION_WIDGET_H
|
||||||
|
#include "../../../general/display/banner_widget.h"
|
||||||
|
#include "../../abstract_analytics_panel_widget.h"
|
||||||
|
#include "mana_devotion_config.h"
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
|
||||||
|
class ManaDevotionWidget : public AbstractAnalyticsPanelWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
QSize sizeHint() const override;
|
||||||
|
void updateDisplay() override;
|
||||||
|
QDialog *createConfigDialog(QWidget *parent) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaDevotionConfig cfg = {});
|
||||||
|
|
||||||
|
QJsonObject saveConfig() const override
|
||||||
|
{
|
||||||
|
return config.toJson();
|
||||||
|
}
|
||||||
|
void loadConfig(const QJsonObject &o) override
|
||||||
|
{
|
||||||
|
config = ManaDevotionConfig::fromJson(o);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ManaDevotionConfig config;
|
||||||
|
QWidget *barContainer;
|
||||||
|
QHBoxLayout *barLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MANA_DEVOTION_WIDGET_H
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
#include "mana_distribution_config.h"
|
||||||
|
|
||||||
|
QJsonObject ManaDistributionConfig::toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject o;
|
||||||
|
o["displayType"] = displayType;
|
||||||
|
|
||||||
|
QJsonArray jsonArray;
|
||||||
|
for (auto &s : filters) {
|
||||||
|
jsonArray.append(s);
|
||||||
|
}
|
||||||
|
o["filters"] = jsonArray;
|
||||||
|
|
||||||
|
o["showColorRows"] = showColorRows;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
ManaDistributionConfig ManaDistributionConfig::fromJson(const QJsonObject &o)
|
||||||
|
{
|
||||||
|
ManaDistributionConfig config;
|
||||||
|
if (o.contains("displayType")) {
|
||||||
|
config.displayType = o["displayType"].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o.contains("filters")) {
|
||||||
|
config.filters.clear();
|
||||||
|
for (auto v : o["filters"].toArray())
|
||||||
|
config.filters << v.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o.contains("showColorRows")) {
|
||||||
|
config.showColorRows = o["showColorRows"].toBool(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef COCKATRICE_MANA_DISTRIBUTION_CONFIG_H
|
||||||
|
#define COCKATRICE_MANA_DISTRIBUTION_CONFIG_H
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
struct ManaDistributionConfig
|
||||||
|
{
|
||||||
|
QString displayType = "pie"; // "pie" or "bar"
|
||||||
|
QStringList filters; // empty = all colors
|
||||||
|
bool showColorRows = true;
|
||||||
|
|
||||||
|
QJsonObject toJson() const;
|
||||||
|
|
||||||
|
static ManaDistributionConfig fromJson(const QJsonObject &o);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_MANA_DISTRIBUTION_CONFIG_H
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
#include "mana_distribution_config_dialog.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
static const QStringList kColors = {"W", "U", "B", "R", "G", "C"};
|
||||||
|
|
||||||
|
ManaDistributionConfigDialog::ManaDistributionConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent)
|
||||||
|
: QDialog(parent), analyzer(analyzer)
|
||||||
|
{
|
||||||
|
auto *lay = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
labelDisplayType = new QLabel(this);
|
||||||
|
lay->addWidget(labelDisplayType);
|
||||||
|
|
||||||
|
displayType = new QComboBox(this);
|
||||||
|
lay->addWidget(displayType);
|
||||||
|
|
||||||
|
labelFilters = new QLabel(this);
|
||||||
|
lay->addWidget(labelFilters);
|
||||||
|
|
||||||
|
filterList = new QListWidget(this);
|
||||||
|
filterList->setSelectionMode(QAbstractItemView::MultiSelection);
|
||||||
|
filterList->addItems(kColors); // dynamic/fixed, no translation needed
|
||||||
|
lay->addWidget(filterList);
|
||||||
|
|
||||||
|
showColorRows = new QCheckBox(this);
|
||||||
|
showColorRows->setChecked(true);
|
||||||
|
lay->addWidget(showColorRows);
|
||||||
|
|
||||||
|
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
lay->addWidget(buttons);
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &ManaDistributionConfigDialog::accept);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &ManaDistributionConfigDialog::reject);
|
||||||
|
|
||||||
|
displayType->addItems({"pie", "bar"}); // combo items
|
||||||
|
|
||||||
|
retranslateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ManaDistributionConfigDialog::retranslateUi()
|
||||||
|
{
|
||||||
|
labelDisplayType->setText(tr("Top display type:"));
|
||||||
|
displayType->setItemText(0, tr("pie"));
|
||||||
|
displayType->setItemText(1, tr("bar"));
|
||||||
|
|
||||||
|
labelFilters->setText(tr("Colors:"));
|
||||||
|
|
||||||
|
showColorRows->setText(tr("Show per-color rows"));
|
||||||
|
|
||||||
|
// QDialogButtonBox buttons are automatically translated
|
||||||
|
}
|
||||||
|
|
||||||
|
void ManaDistributionConfigDialog::setFromConfig(const ManaDistributionConfig &cfgIn)
|
||||||
|
{
|
||||||
|
cfg = cfgIn;
|
||||||
|
|
||||||
|
displayType->setCurrentText(cfg.displayType);
|
||||||
|
|
||||||
|
for (int i = 0; i < filterList->count(); ++i)
|
||||||
|
filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text()));
|
||||||
|
|
||||||
|
showColorRows->setChecked(cfg.showColorRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ManaDistributionConfigDialog::accept()
|
||||||
|
{
|
||||||
|
cfg.displayType = displayType->currentText();
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
cfg.filters.clear();
|
||||||
|
for (auto *item : filterList->selectedItems())
|
||||||
|
cfg.filters << item->text();
|
||||||
|
|
||||||
|
cfg.showColorRows = showColorRows->isChecked();
|
||||||
|
|
||||||
|
QDialog::accept();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
#ifndef COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H
|
||||||
|
#define COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H
|
||||||
|
|
||||||
|
#include "mana_distribution_config.h"
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QComboBox;
|
||||||
|
class QListWidget;
|
||||||
|
class QCheckBox;
|
||||||
|
class DeckListStatisticsAnalyzer;
|
||||||
|
|
||||||
|
class ManaDistributionConfigDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ManaDistributionConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent = nullptr);
|
||||||
|
void retranslateUi();
|
||||||
|
|
||||||
|
void setFromConfig(const ManaDistributionConfig &cfg);
|
||||||
|
const ManaDistributionConfig &config() const
|
||||||
|
{
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void accept() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DeckListStatisticsAnalyzer *analyzer;
|
||||||
|
|
||||||
|
QLabel *labelDisplayType;
|
||||||
|
QComboBox *displayType;
|
||||||
|
QLabel *labelFilters;
|
||||||
|
QListWidget *filterList;
|
||||||
|
QCheckBox *showColorRows;
|
||||||
|
QDialogButtonBox *buttons;
|
||||||
|
|
||||||
|
ManaDistributionConfig cfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
#include "mana_distribution_single_display_widget.h"
|
||||||
|
|
||||||
|
#include "../../../cards/additional_info/mana_symbol_widget.h"
|
||||||
|
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
ManaDistributionSingleDisplayWidget::ManaDistributionSingleDisplayWidget(const QString &colorSymbol, QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
auto layout = new QVBoxLayout(this);
|
||||||
|
layout->setAlignment(Qt::AlignHCenter);
|
||||||
|
|
||||||
|
symbolLabel = new ManaSymbolWidget(this, colorSymbol, true, false);
|
||||||
|
symbolLabel->setFixedSize(40, 40);
|
||||||
|
|
||||||
|
devotionBar = new QProgressBar(this);
|
||||||
|
devotionBar->setRange(0, 100);
|
||||||
|
devotionBar->setTextVisible(false);
|
||||||
|
|
||||||
|
devotionLabel = new QLabel(this);
|
||||||
|
devotionLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
|
productionBar = new QProgressBar(this);
|
||||||
|
productionBar->setRange(0, 100);
|
||||||
|
productionBar->setTextVisible(false);
|
||||||
|
|
||||||
|
productionLabel = new QLabel(this);
|
||||||
|
productionLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
|
layout->addWidget(symbolLabel);
|
||||||
|
layout->addWidget(devotionBar);
|
||||||
|
layout->addWidget(devotionLabel);
|
||||||
|
layout->addWidget(productionBar);
|
||||||
|
layout->addWidget(productionLabel);
|
||||||
|
|
||||||
|
setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ManaDistributionSingleDisplayWidget::setDevotion(int pips, int cards, int percent)
|
||||||
|
{
|
||||||
|
devotionBar->setValue(percent);
|
||||||
|
devotionLabel->setText(QString(tr("%1 pips (%2 cards)")).arg(pips).arg(cards));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ManaDistributionSingleDisplayWidget::setProduction(int pips, int cards, int percent)
|
||||||
|
{
|
||||||
|
productionBar->setValue(percent);
|
||||||
|
productionLabel->setText(QString(tr("%1 mana (%2 cards)")).arg(pips).arg(cards));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H
|
||||||
|
#define COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class ManaDistributionSingleDisplayWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ManaDistributionSingleDisplayWidget(const QString &colorSymbol, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setDevotion(int pips, int cards, int percent);
|
||||||
|
void setProduction(int pips, int cards, int percent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLabel *symbolLabel;
|
||||||
|
|
||||||
|
QProgressBar *devotionBar;
|
||||||
|
QLabel *devotionLabel;
|
||||||
|
|
||||||
|
QProgressBar *productionBar;
|
||||||
|
QLabel *productionLabel;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
#include "mana_distribution_widget.h"
|
||||||
|
|
||||||
|
#include "../../analytics_panel_widget_registrar.h"
|
||||||
|
#include "mana_distribution_config_dialog.h"
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
AnalyticsPanelWidgetRegistrar registerManaDistribution{
|
||||||
|
"manaProdDevotion", ManaDistributionWidget::tr("Mana Production + Devotion"),
|
||||||
|
[](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaDistributionWidget(parent, analyzer); }};
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
static const QStringList kColors = {"W", "U", "B", "R", "G", "C"};
|
||||||
|
|
||||||
|
ManaDistributionWidget::ManaDistributionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
||||||
|
: AbstractAnalyticsPanelWidget(parent, analyzer)
|
||||||
|
{
|
||||||
|
container = new QWidget(this);
|
||||||
|
containerLayout = new QVBoxLayout(container);
|
||||||
|
|
||||||
|
devotionBarTop = new ColorBar({}, this);
|
||||||
|
devotionPieTop = new ColorPie({}, this);
|
||||||
|
productionBarTop = new ColorBar({}, this);
|
||||||
|
productionPieTop = new ColorPie({}, this);
|
||||||
|
|
||||||
|
containerLayout->addWidget(devotionBarTop);
|
||||||
|
containerLayout->addWidget(devotionPieTop);
|
||||||
|
containerLayout->addWidget(productionBarTop);
|
||||||
|
containerLayout->addWidget(productionPieTop);
|
||||||
|
|
||||||
|
devotionPieTop->hide();
|
||||||
|
productionPieTop->hide();
|
||||||
|
|
||||||
|
row = new QHBoxLayout();
|
||||||
|
containerLayout->addLayout(row);
|
||||||
|
|
||||||
|
for (const QString &c : kColors) {
|
||||||
|
auto *w = new ManaDistributionSingleDisplayWidget(c, this);
|
||||||
|
row->addWidget(w);
|
||||||
|
rows[c] = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
layout->addWidget(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ManaDistributionWidget::updateDisplay()
|
||||||
|
{
|
||||||
|
const auto &devPips = analyzer->getDevotionPipCount();
|
||||||
|
const auto &devCards = analyzer->getDevotionCardCount();
|
||||||
|
const auto &prodPips = analyzer->getProductionPipCount();
|
||||||
|
const auto &prodCards = analyzer->getProductionCardCount();
|
||||||
|
|
||||||
|
QStringList filtered = config.filters.isEmpty() ? kColors : config.filters;
|
||||||
|
|
||||||
|
QMap<QString, int> devMap, prodMap;
|
||||||
|
for (const QString &c : filtered) {
|
||||||
|
devMap[c] = devPips.value(c, 0);
|
||||||
|
prodMap[c] = prodPips.value(c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool showPie = (config.displayType == "pie");
|
||||||
|
|
||||||
|
devotionBarTop->setVisible(!showPie);
|
||||||
|
productionBarTop->setVisible(!showPie);
|
||||||
|
|
||||||
|
devotionPieTop->setVisible(showPie);
|
||||||
|
productionPieTop->setVisible(showPie);
|
||||||
|
|
||||||
|
if (showPie) {
|
||||||
|
devotionPieTop->setColors(devMap);
|
||||||
|
productionPieTop->setColors(prodMap);
|
||||||
|
} else {
|
||||||
|
devotionBarTop->setColors(devMap);
|
||||||
|
productionBarTop->setColors(prodMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &c : kColors) {
|
||||||
|
auto *w = rows.value(c);
|
||||||
|
|
||||||
|
if (!w) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visible = config.showColorRows && filtered.contains(c);
|
||||||
|
w->setVisible(visible);
|
||||||
|
if (!visible) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dp = devPips.value(c, 0);
|
||||||
|
int dc = devCards.value(c, 0);
|
||||||
|
int pp = prodPips.value(c, 0);
|
||||||
|
int pc = prodCards.value(c, 0);
|
||||||
|
|
||||||
|
// Compute percentages
|
||||||
|
int totalDev = 0;
|
||||||
|
int totalProd = 0;
|
||||||
|
for (const QString &cc : filtered) {
|
||||||
|
totalDev += devPips.value(cc, 0);
|
||||||
|
totalProd += prodPips.value(cc, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int devPct = (totalDev > 0) ? int(100.0 * dp / totalDev) : 0;
|
||||||
|
int prodPct = (totalProd > 0) ? int(100.0 * pp / totalProd) : 0;
|
||||||
|
|
||||||
|
w->setDevotion(dp, dc, devPct);
|
||||||
|
w->setProduction(pp, pc, prodPct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog *ManaDistributionWidget::createConfigDialog(QWidget *parent)
|
||||||
|
{
|
||||||
|
auto *dlg = new ManaDistributionConfigDialog(analyzer, parent);
|
||||||
|
dlg->setWindowTitle(tr("Mana Distribution Settings"));
|
||||||
|
dlg->setFromConfig(config);
|
||||||
|
|
||||||
|
connect(dlg, &QDialog::accepted, [this, dlg]() {
|
||||||
|
config = dlg->config();
|
||||||
|
updateDisplay();
|
||||||
|
});
|
||||||
|
|
||||||
|
return dlg;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
#ifndef COCKATRICE_MANA_DISTRIBUTION_WIDGET_H
|
||||||
|
#define COCKATRICE_MANA_DISTRIBUTION_WIDGET_H
|
||||||
|
|
||||||
|
#include "../../../general/display/charts/bars/color_bar.h"
|
||||||
|
#include "../../../general/display/charts/pies/color_pie.h"
|
||||||
|
#include "../../abstract_analytics_panel_widget.h"
|
||||||
|
#include "../../deck_list_statistics_analyzer.h"
|
||||||
|
#include "mana_distribution_config.h"
|
||||||
|
#include "mana_distribution_single_display_widget.h"
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class ManaDistributionWidget : public AbstractAnalyticsPanelWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ManaDistributionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||||
|
|
||||||
|
void updateDisplay() override;
|
||||||
|
QDialog *createConfigDialog(QWidget *parent) override;
|
||||||
|
QJsonObject extractConfigFromDialog(QDialog *) const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ManaDistributionConfig config;
|
||||||
|
|
||||||
|
QWidget *container;
|
||||||
|
QVBoxLayout *containerLayout;
|
||||||
|
|
||||||
|
QVBoxLayout *topLayout;
|
||||||
|
ColorBar *devotionBarTop;
|
||||||
|
ColorPie *devotionPieTop;
|
||||||
|
ColorBar *productionBarTop;
|
||||||
|
ColorPie *productionPieTop;
|
||||||
|
|
||||||
|
QHBoxLayout *row;
|
||||||
|
QMap<QString, ManaDistributionSingleDisplayWidget *> rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_MANA_DISTRIBUTION_WIDGET_H
|
||||||
|
|
@ -1,35 +1,298 @@
|
||||||
#include "deck_analytics_widget.h"
|
#include "deck_analytics_widget.h"
|
||||||
|
|
||||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel)
|
#include "abstract_analytics_panel_widget.h"
|
||||||
: QWidget(parent), deckListModel(_deckListModel)
|
#include "add_analytics_panel_dialog.h"
|
||||||
{
|
#include "analytics_panel_widget_factory.h"
|
||||||
mainLayout = new QVBoxLayout();
|
#include "analyzer_modules/mana_base/mana_base_config.h"
|
||||||
setLayout(mainLayout);
|
#include "analyzer_modules/mana_curve/mana_curve_config.h"
|
||||||
|
#include "analyzer_modules/mana_devotion/mana_devotion_config.h"
|
||||||
|
#include "deck_list_statistics_analyzer.h"
|
||||||
|
#include "resizable_panel.h"
|
||||||
|
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *_statsAnalyzer)
|
||||||
|
: QWidget(parent), statsAnalyzer(_statsAnalyzer)
|
||||||
|
{
|
||||||
|
layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
controlContainer = new QWidget(this);
|
||||||
|
controlLayout = new QHBoxLayout(controlContainer);
|
||||||
|
addButton = new QPushButton(this);
|
||||||
|
removeButton = new QPushButton(this);
|
||||||
|
saveButton = new QPushButton(this);
|
||||||
|
loadButton = new QPushButton(this);
|
||||||
|
controlLayout->addWidget(addButton);
|
||||||
|
controlLayout->addWidget(removeButton);
|
||||||
|
controlLayout->addWidget(saveButton);
|
||||||
|
controlLayout->addWidget(loadButton);
|
||||||
|
|
||||||
|
layout->addWidget(controlContainer);
|
||||||
|
|
||||||
|
connect(addButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onAddPanel);
|
||||||
|
connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected);
|
||||||
|
connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout);
|
||||||
|
connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout);
|
||||||
|
|
||||||
|
// Scroll area and container
|
||||||
scrollArea = new QScrollArea(this);
|
scrollArea = new QScrollArea(this);
|
||||||
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
||||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
||||||
scrollArea->setWidgetResizable(true);
|
scrollArea->setWidgetResizable(true);
|
||||||
mainLayout->addWidget(scrollArea);
|
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||||
|
|
||||||
container = new QWidget(scrollArea);
|
panelContainer = new QWidget(scrollArea);
|
||||||
containerLayout = new QVBoxLayout(container);
|
panelLayout = new QVBoxLayout(panelContainer);
|
||||||
container->setLayout(containerLayout);
|
panelLayout->setSpacing(8);
|
||||||
scrollArea->setWidget(container);
|
panelLayout->setContentsMargins(4, 4, 4, 4);
|
||||||
|
panelLayout->addStretch(1); // push panels up
|
||||||
|
|
||||||
deckListStatisticsAnalyzer = new DeckListStatisticsAnalyzer(this, deckListModel);
|
scrollArea->setWidget(panelContainer);
|
||||||
|
layout->addWidget(scrollArea);
|
||||||
|
|
||||||
manaCurveWidget = new ManaCurveWidget(this, deckListStatisticsAnalyzer);
|
loadLayout();
|
||||||
containerLayout->addWidget(manaCurveWidget);
|
|
||||||
|
|
||||||
manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer);
|
retranslateUi();
|
||||||
containerLayout->addWidget(manaDevotionWidget);
|
|
||||||
|
|
||||||
manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer);
|
|
||||||
containerLayout->addWidget(manaBaseWidget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckAnalyticsWidget::refreshDisplays()
|
void DeckAnalyticsWidget::retranslateUi()
|
||||||
{
|
{
|
||||||
deckListStatisticsAnalyzer->update();
|
addButton->setText(tr("Add Panel"));
|
||||||
|
removeButton->setText(tr("Remove Panel"));
|
||||||
|
saveButton->setText(tr("Save Layout"));
|
||||||
|
loadButton->setText(tr("Load Layout"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::updateDisplays()
|
||||||
|
{
|
||||||
|
statsAnalyzer->analyze();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::onAddPanel()
|
||||||
|
{
|
||||||
|
AddAnalyticsPanelDialog dlg(this);
|
||||||
|
if (dlg.exec() != QDialog::Accepted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString selection = dlg.selectedType();
|
||||||
|
if (selection.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractAnalyticsPanelWidget *analyticsWidget =
|
||||||
|
AnalyticsPanelWidgetFactory::instance().create(selection, this, statsAnalyzer);
|
||||||
|
if (!analyticsWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!analyticsWidget->applyConfigFromDialog()) {
|
||||||
|
analyticsWidget->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPanelInstance(selection, analyticsWidget, analyticsWidget->saveConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::addPanelInstance(const QString &typeId,
|
||||||
|
AbstractAnalyticsPanelWidget *panel,
|
||||||
|
const QJsonObject &cfg)
|
||||||
|
{
|
||||||
|
panel->loadConfig(cfg);
|
||||||
|
panel->updateDisplay();
|
||||||
|
|
||||||
|
auto *resPanel = new ResizablePanel(typeId, panel, panelContainer);
|
||||||
|
panelWrappers.push_back(resPanel);
|
||||||
|
|
||||||
|
panelLayout->insertWidget(panelLayout->count() - 1, resPanel);
|
||||||
|
|
||||||
|
// Event filter for selection
|
||||||
|
resPanel->installEventFilter(this);
|
||||||
|
panel->installEventFilter(this);
|
||||||
|
|
||||||
|
// Connect drag-drop signals
|
||||||
|
connect(resPanel, &ResizablePanel::dropRequested, this, &DeckAnalyticsWidget::onPanelDropped);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::onRemoveSelected()
|
||||||
|
{
|
||||||
|
int idx = indexOfSelectedWrapper();
|
||||||
|
if (idx < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizablePanel *panel = panelWrappers.takeAt(idx);
|
||||||
|
selectWrapper(nullptr);
|
||||||
|
|
||||||
|
panel->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::saveLayout()
|
||||||
|
{
|
||||||
|
QJsonArray arr;
|
||||||
|
|
||||||
|
for (auto *wrapper : panelWrappers) {
|
||||||
|
QJsonObject entry;
|
||||||
|
entry["type"] = wrapper->getTypeId();
|
||||||
|
entry["config"] = wrapper->panel->saveConfig();
|
||||||
|
entry["height"] = wrapper->getCurrentHeight();
|
||||||
|
arr.append(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSettings s;
|
||||||
|
s.setValue("deckAnalytics/layout", QString::fromUtf8(QJsonDocument(arr).toJson(QJsonDocument::Compact)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::loadLayout()
|
||||||
|
{
|
||||||
|
if (!loadLayoutInternal()) {
|
||||||
|
addDefaultPanels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::addDefaultPanels()
|
||||||
|
{
|
||||||
|
struct DefaultPanel
|
||||||
|
{
|
||||||
|
QString type;
|
||||||
|
QJsonObject cfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare configs
|
||||||
|
QJsonObject manaCurveCfg = ManaCurveConfig{}.toJson();
|
||||||
|
QJsonObject manaBaseCfg = ManaBaseConfig{"combinedBar", {}}.toJson();
|
||||||
|
QJsonObject manaDevotionCfg = ManaDevotionConfig{"combinedBar", {}}.toJson();
|
||||||
|
QVector<DefaultPanel> defaults = {
|
||||||
|
{"manaCurve", manaCurveCfg}, {"manaBase", manaBaseCfg}, {"manaDevotion", manaDevotionCfg}};
|
||||||
|
|
||||||
|
for (auto &d : defaults) {
|
||||||
|
AbstractAnalyticsPanelWidget *w = AnalyticsPanelWidgetFactory::instance().create(d.type, this, statsAnalyzer);
|
||||||
|
if (!w) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->loadConfig(d.cfg);
|
||||||
|
addPanelInstance(d.type, w, d.cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckAnalyticsWidget::loadLayoutInternal()
|
||||||
|
{
|
||||||
|
QSettings s;
|
||||||
|
QString layoutData = s.value("deckAnalytics/layout").toString();
|
||||||
|
if (layoutData.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(layoutData.toUtf8());
|
||||||
|
if (!doc.isArray()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPanels();
|
||||||
|
|
||||||
|
for (auto v : doc.array()) {
|
||||||
|
if (!v.isObject()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QJsonObject o = v.toObject();
|
||||||
|
QString type = o["type"].toString();
|
||||||
|
QJsonObject cfg = o["config"].toObject();
|
||||||
|
|
||||||
|
AbstractAnalyticsPanelWidget *w = AnalyticsPanelWidgetFactory::instance().create(type, this, statsAnalyzer);
|
||||||
|
if (!w) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPanelInstance(type, w, cfg);
|
||||||
|
|
||||||
|
// Restore height AFTER adding the panel
|
||||||
|
if (o.contains("height")) {
|
||||||
|
panelWrappers.last()->setHeightFromSaved(o["height"].toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::clearPanels()
|
||||||
|
{
|
||||||
|
selectWrapper(nullptr);
|
||||||
|
while (!panelWrappers.isEmpty()) {
|
||||||
|
ResizablePanel *p = panelWrappers.takeLast();
|
||||||
|
p->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckAnalyticsWidget::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::MouseButtonPress) {
|
||||||
|
for (auto *p : panelWrappers) {
|
||||||
|
if (obj == p || obj == p->panel) {
|
||||||
|
selectWrapper(p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QWidget::eventFilter(obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::selectWrapper(ResizablePanel *w)
|
||||||
|
{
|
||||||
|
// Same wrapper
|
||||||
|
if (selectedWrapper == w) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Deselect the old one
|
||||||
|
if (selectedWrapper) {
|
||||||
|
selectedWrapper->setSelected(false);
|
||||||
|
}
|
||||||
|
// Set current
|
||||||
|
selectedWrapper = w;
|
||||||
|
// Finally, select new
|
||||||
|
if (selectedWrapper) {
|
||||||
|
selectedWrapper->setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DeckAnalyticsWidget::indexOfSelectedWrapper() const
|
||||||
|
{
|
||||||
|
if (!selectedWrapper) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return panelWrappers.indexOf(selectedWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckAnalyticsWidget::onPanelDropped(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore)
|
||||||
|
{
|
||||||
|
int draggedIdx = panelWrappers.indexOf(dragged);
|
||||||
|
int targetIdx = panelWrappers.indexOf(target);
|
||||||
|
|
||||||
|
if (draggedIdx == -1 || targetIdx == -1 || draggedIdx == targetIdx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dragged panel from list and layout
|
||||||
|
panelWrappers.removeAt(draggedIdx);
|
||||||
|
panelLayout->removeWidget(dragged);
|
||||||
|
|
||||||
|
// Adjust target index if needed
|
||||||
|
if (draggedIdx < targetIdx) {
|
||||||
|
targetIdx--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate insertion position
|
||||||
|
int insertIdx = insertBefore ? targetIdx : targetIdx + 1;
|
||||||
|
|
||||||
|
// Insert back into list and layout
|
||||||
|
panelWrappers.insert(insertIdx, dragged);
|
||||||
|
panelLayout->insertWidget(insertIdx, dragged);
|
||||||
|
|
||||||
|
// Clear selection
|
||||||
|
selectWrapper(nullptr);
|
||||||
|
}
|
||||||
|
|
@ -1,44 +1,71 @@
|
||||||
/**
|
/**
|
||||||
* @file deck_analytics_widget.h
|
* @file deck_analytics_widget.h
|
||||||
* @ingroup DeckEditorAnalyticsWidgets
|
* @ingroup DeckEditorAnalyticsWidgets
|
||||||
* @brief TODO: Document this.
|
* @brief Main analytics widget container with resizable panels for deck statistics.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef DECK_ANALYTICS_WIDGET_H
|
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||||
#define DECK_ANALYTICS_WIDGET_H
|
#define DECK_ANALYTICS_WIDGET_H
|
||||||
|
|
||||||
#include "mana_base_widget.h"
|
#include "abstract_analytics_panel_widget.h"
|
||||||
#include "mana_curve_widget.h"
|
#include "deck_list_statistics_analyzer.h"
|
||||||
#include "mana_devotion_widget.h"
|
#include "resizable_panel.h"
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QJsonObject>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QVector>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
|
||||||
|
class LayoutInspector;
|
||||||
|
|
||||||
class DeckAnalyticsWidget : public QWidget
|
class DeckAnalyticsWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateDisplays();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel);
|
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||||
void setDeckList(const DeckList &_deckListModel);
|
void retranslateUi();
|
||||||
std::map<int, int> analyzeManaCurve();
|
|
||||||
void refreshDisplays();
|
private slots:
|
||||||
|
void onAddPanel();
|
||||||
|
void onRemoveSelected();
|
||||||
|
void onPanelDropped(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore);
|
||||||
|
void saveLayout();
|
||||||
|
void loadLayout();
|
||||||
|
void addDefaultPanels();
|
||||||
|
bool loadLayoutInternal();
|
||||||
|
void clearPanels();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
void selectWrapper(ResizablePanel *panel);
|
||||||
|
int indexOfSelectedWrapper() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeckListModel *deckListModel;
|
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||||
DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer;
|
|
||||||
QVBoxLayout *mainLayout;
|
|
||||||
|
|
||||||
QWidget *container;
|
QVBoxLayout *layout;
|
||||||
QVBoxLayout *containerLayout;
|
QWidget *controlContainer;
|
||||||
|
QHBoxLayout *controlLayout;
|
||||||
|
|
||||||
|
QPushButton *addButton;
|
||||||
|
QPushButton *removeButton;
|
||||||
|
QPushButton *saveButton;
|
||||||
|
QPushButton *loadButton;
|
||||||
|
|
||||||
QScrollArea *scrollArea;
|
QScrollArea *scrollArea;
|
||||||
|
QWidget *panelContainer;
|
||||||
|
QVBoxLayout *panelLayout;
|
||||||
|
|
||||||
ManaCurveWidget *manaCurveWidget;
|
QVector<ResizablePanel *> panelWrappers;
|
||||||
ManaDevotionWidget *manaDevotionWidget;
|
ResizablePanel *selectedWrapper = nullptr;
|
||||||
ManaBaseWidget *manaBaseWidget;
|
|
||||||
|
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||||
|
LayoutInspector *insp = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DECK_ANALYTICS_WIDGET_H
|
#endif // DECK_ANALYTICS_WIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -9,38 +9,102 @@
|
||||||
|
|
||||||
DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
|
DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
|
||||||
DeckListModel *_model,
|
DeckListModel *_model,
|
||||||
DeckListStatisticsAnalyzerConfig cfg)
|
DeckListStatisticsAnalyzerConfig _config)
|
||||||
: QObject(parent), model(_model), config(cfg)
|
: QObject(parent), model(_model), config(_config)
|
||||||
{
|
{
|
||||||
connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::update);
|
connect(model, &DeckListModel::cardsChanged, this, &DeckListStatisticsAnalyzer::analyze);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckListStatisticsAnalyzer::update()
|
void DeckListStatisticsAnalyzer::analyze()
|
||||||
{
|
{
|
||||||
manaBaseMap.clear();
|
clearData();
|
||||||
manaCurveMap.clear();
|
|
||||||
manaDevotionMap.clear();
|
|
||||||
|
|
||||||
QList<ExactCard> cards = model->getCards();
|
QList<const DecklistCardNode *> nodes = model->getCardNodes();
|
||||||
|
|
||||||
for (const ExactCard &card : cards) {
|
for (auto node : nodes) {
|
||||||
// ---- Mana curve ----
|
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||||
|
if (!info) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int amount = node->getNumber();
|
||||||
|
QStringList copiesOfName;
|
||||||
|
for (int i = 0; i < amount; i++) {
|
||||||
|
copiesOfName.append(node->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert once
|
||||||
|
const int cmc = info->getCmc().toInt();
|
||||||
|
QStringList types = info->getMainCardType().split(' ');
|
||||||
|
QStringList subtypes = info->getCardType().split('-').last().split(" ");
|
||||||
|
QString colors = info->getColors();
|
||||||
|
int power = info->getPowTough().split("/").first().toInt();
|
||||||
|
int toughness = info->getPowTough().split("/").last().toInt();
|
||||||
|
|
||||||
|
// For each copy of card
|
||||||
|
// ---------------- Mana Curve ----------------
|
||||||
if (config.computeManaCurve) {
|
if (config.computeManaCurve) {
|
||||||
manaCurveMap[card.getInfo().getCmc().toInt()]++;
|
manaCurveMap[cmc] += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Mana base ----
|
// per-type curve
|
||||||
|
for (auto &t : types) {
|
||||||
|
manaCurveByType[t][cmc] += amount;
|
||||||
|
manaCurveCardsByType[t][cmc] << copiesOfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-subtype curve
|
||||||
|
for (auto &st : subtypes) {
|
||||||
|
manaCurveBySubtype[st][cmc] += amount;
|
||||||
|
manaCurveCardsBySubtype[st][cmc] << copiesOfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// per-color curve
|
||||||
|
for (auto &c : colors) {
|
||||||
|
manaCurveByColor[c][cmc] += amount;
|
||||||
|
manaCurveCardsByColor[c][cmc] << copiesOfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Power/toughness
|
||||||
|
manaCurveByPower[QString::number(power)][cmc] += amount;
|
||||||
|
manaCurveCardsByPower[QString::number(power)][cmc] << copiesOfName;
|
||||||
|
manaCurveByToughness[QString::number(toughness)][cmc] += amount;
|
||||||
|
manaCurveCardsByToughness[QString::number(toughness)][cmc] << copiesOfName;
|
||||||
|
|
||||||
|
// ========== Category Counts ===========
|
||||||
|
for (auto &t : types) {
|
||||||
|
typeCount[t] += amount;
|
||||||
|
}
|
||||||
|
for (auto &st : subtypes) {
|
||||||
|
subtypeCount[st] += amount;
|
||||||
|
}
|
||||||
|
for (auto &c : colors) {
|
||||||
|
colorCount[c] += amount;
|
||||||
|
}
|
||||||
|
manaValueCount[cmc] += amount;
|
||||||
|
|
||||||
|
// ---------------- Mana Base ----------------
|
||||||
if (config.computeManaBase) {
|
if (config.computeManaBase) {
|
||||||
auto mana = determineManaProduction(card.getInfo().getText());
|
auto prod = determineManaProduction(info->getText());
|
||||||
for (auto it = mana.begin(); it != mana.end(); ++it)
|
for (auto it = prod.begin(); it != prod.end(); ++it) {
|
||||||
manaBaseMap[it.key()] += it.value();
|
if (it.value() > 0) {
|
||||||
|
productionPipCount[it.key()] += it.value() * amount;
|
||||||
|
productionCardCount[it.key()] += amount;
|
||||||
|
}
|
||||||
|
manaBaseMap[it.key()] += it.value() * amount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Devotion ----
|
// ---------------- Devotion ----------------
|
||||||
if (config.computeDevotion) {
|
if (config.computeDevotion) {
|
||||||
auto devo = countManaSymbols(card.getInfo().getManaCost());
|
auto devo = countManaSymbols(info->getManaCost());
|
||||||
for (auto &d : devo)
|
for (auto &d : devo) {
|
||||||
manaDevotionMap[d.first] += d.second;
|
if (d.second > 0) {
|
||||||
|
devotionPipCount[QString(d.first)] += d.second * amount;
|
||||||
|
devotionCardCount[QString(d.first)] += amount;
|
||||||
|
}
|
||||||
|
manaDevotionMap[d.first] += d.second * amount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,3 +176,57 @@ std::unordered_map<char, int> DeckListStatisticsAnalyzer::countManaSymbols(const
|
||||||
|
|
||||||
return manaCounts;
|
return manaCounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hypergeometric probability: P(X=k)
|
||||||
|
double DeckListStatisticsAnalyzer::hypergeometric(int N, int K, int n, int k)
|
||||||
|
{
|
||||||
|
if (k < 0 || k > n || K > N) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto choose = [](int n, int r) -> double {
|
||||||
|
if (r > n)
|
||||||
|
return 0.0;
|
||||||
|
if (r == 0 || r == n)
|
||||||
|
return 1.0;
|
||||||
|
double res = 1.0;
|
||||||
|
for (int i = 1; i <= r; ++i) {
|
||||||
|
res *= (n - r + i);
|
||||||
|
res /= i;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
return choose(K, k) * choose(N - K, n - k) / choose(N, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckListStatisticsAnalyzer::clearData()
|
||||||
|
{
|
||||||
|
manaBaseMap.clear();
|
||||||
|
manaCurveMap.clear();
|
||||||
|
manaDevotionMap.clear();
|
||||||
|
|
||||||
|
devotionPipCount.clear();
|
||||||
|
devotionCardCount.clear();
|
||||||
|
|
||||||
|
productionPipCount.clear();
|
||||||
|
productionCardCount.clear();
|
||||||
|
|
||||||
|
manaCurveByType.clear();
|
||||||
|
manaCurveBySubtype.clear();
|
||||||
|
manaCurveByColor.clear();
|
||||||
|
manaCurveByPower.clear();
|
||||||
|
manaCurveByToughness.clear();
|
||||||
|
|
||||||
|
manaCurveCardsByType.clear();
|
||||||
|
manaCurveCardsBySubtype.clear();
|
||||||
|
manaCurveCardsByColor.clear();
|
||||||
|
manaCurveCardsByPower.clear();
|
||||||
|
manaCurveCardsByToughness.clear();
|
||||||
|
|
||||||
|
typeCount.clear();
|
||||||
|
subtypeCount.clear();
|
||||||
|
colorCount.clear();
|
||||||
|
rarityCount.clear();
|
||||||
|
manaValueCount.clear();
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,9 @@ struct DeckListStatisticsAnalyzerConfig
|
||||||
bool computeManaBase = true;
|
bool computeManaBase = true;
|
||||||
bool computeManaCurve = true;
|
bool computeManaCurve = true;
|
||||||
bool computeDevotion = true;
|
bool computeDevotion = true;
|
||||||
|
bool computeCategories = true;
|
||||||
|
bool computeCurveBreakdowns = true;
|
||||||
|
bool computeProbabilities = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DeckListStatisticsAnalyzer : public QObject
|
class DeckListStatisticsAnalyzer : public QObject
|
||||||
|
|
@ -23,9 +26,9 @@ class DeckListStatisticsAnalyzer : public QObject
|
||||||
public:
|
public:
|
||||||
explicit DeckListStatisticsAnalyzer(QObject *parent,
|
explicit DeckListStatisticsAnalyzer(QObject *parent,
|
||||||
DeckListModel *model,
|
DeckListModel *model,
|
||||||
DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig());
|
DeckListStatisticsAnalyzerConfig _config = DeckListStatisticsAnalyzerConfig());
|
||||||
|
|
||||||
void update();
|
void analyze();
|
||||||
|
|
||||||
[[nodiscard]] const QHash<QString, int> &getManaBase() const
|
[[nodiscard]] const QHash<QString, int> &getManaBase() const
|
||||||
{
|
{
|
||||||
|
|
@ -40,6 +43,96 @@ public:
|
||||||
return manaDevotionMap;
|
return manaDevotionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QHash<QString, int> &getDevotionPipCount() const
|
||||||
|
{
|
||||||
|
return devotionPipCount;
|
||||||
|
}
|
||||||
|
const QHash<QString, int> &getDevotionCardCount() const
|
||||||
|
{
|
||||||
|
return devotionCardCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, int> &getProductionPipCount() const
|
||||||
|
{
|
||||||
|
return productionPipCount;
|
||||||
|
}
|
||||||
|
const QHash<QString, int> &getProductionCardCount() const
|
||||||
|
{
|
||||||
|
return productionCardCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, int> &getTypeCount() const
|
||||||
|
{
|
||||||
|
return typeCount;
|
||||||
|
}
|
||||||
|
const QHash<QString, int> &getSubtypeCount() const
|
||||||
|
{
|
||||||
|
return subtypeCount;
|
||||||
|
}
|
||||||
|
const QHash<QString, int> &getColorCount() const
|
||||||
|
{
|
||||||
|
return colorCount;
|
||||||
|
}
|
||||||
|
const QHash<QString, int> &getRarityCount() const
|
||||||
|
{
|
||||||
|
return rarityCount;
|
||||||
|
}
|
||||||
|
const QHash<int, int> &getManaValueCount() const
|
||||||
|
{
|
||||||
|
return manaValueCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, QHash<int, int>> &getManaCurveByType() const
|
||||||
|
{
|
||||||
|
return manaCurveByType;
|
||||||
|
}
|
||||||
|
const QHash<QString, QHash<int, int>> &getManaCurveBySubtype() const
|
||||||
|
{
|
||||||
|
return manaCurveBySubtype;
|
||||||
|
}
|
||||||
|
const QHash<QString, QHash<int, int>> &getManaCurveByColor() const
|
||||||
|
{
|
||||||
|
return manaCurveByColor;
|
||||||
|
}
|
||||||
|
const QHash<QString, QHash<int, int>> &getManaCurveByPower() const
|
||||||
|
{
|
||||||
|
return manaCurveByPower;
|
||||||
|
}
|
||||||
|
const QHash<QString, QHash<int, int>> &getManaCurveByToughness() const
|
||||||
|
{
|
||||||
|
return manaCurveByToughness;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByType() const
|
||||||
|
{
|
||||||
|
return manaCurveCardsByType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsBySubtype() const
|
||||||
|
{
|
||||||
|
return manaCurveCardsBySubtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByColor() const
|
||||||
|
{
|
||||||
|
return manaCurveCardsByColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByPower() const
|
||||||
|
{
|
||||||
|
return manaCurveCardsByPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QHash<QString, QHash<int, QStringList>> &getManaCurveCardsByToughness() const
|
||||||
|
{
|
||||||
|
return manaCurveCardsByToughness;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeckListModel *getModel() const
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void statsUpdated();
|
void statsUpdated();
|
||||||
|
|
||||||
|
|
@ -47,14 +140,42 @@ private:
|
||||||
DeckListModel *model;
|
DeckListModel *model;
|
||||||
DeckListStatisticsAnalyzerConfig config;
|
DeckListStatisticsAnalyzerConfig config;
|
||||||
|
|
||||||
// Internal result containers
|
|
||||||
QHash<QString, int> manaBaseMap;
|
QHash<QString, int> manaBaseMap;
|
||||||
std::unordered_map<int, int> manaCurveMap;
|
std::unordered_map<int, int> manaCurveMap;
|
||||||
std::unordered_map<char, int> manaDevotionMap;
|
std::unordered_map<char, int> manaDevotionMap;
|
||||||
|
|
||||||
// Internal helper functions
|
QHash<QString, int> devotionPipCount; // W/U/B/R/G total symbols
|
||||||
|
QHash<QString, int> devotionCardCount; // how many cards provide devotion
|
||||||
|
|
||||||
|
QHash<QString, int> productionPipCount; // mana produced by cards
|
||||||
|
QHash<QString, int> productionCardCount; // number of producers
|
||||||
|
|
||||||
|
QHash<QString, int> typeCount;
|
||||||
|
QHash<QString, int> subtypeCount;
|
||||||
|
QHash<QString, int> colorCount;
|
||||||
|
QHash<QString, int> rarityCount;
|
||||||
|
QHash<int, int> manaValueCount;
|
||||||
|
|
||||||
|
QHash<QString, QHash<int, int>> manaCurveByType;
|
||||||
|
QHash<QString, QHash<int, int>> manaCurveBySubtype;
|
||||||
|
QHash<QString, QHash<int, int>> manaCurveByColor;
|
||||||
|
QHash<QString, QHash<int, int>> manaCurveByPower;
|
||||||
|
QHash<QString, QHash<int, int>> manaCurveByToughness;
|
||||||
|
|
||||||
|
QHash<QString, QHash<int, QStringList>> manaCurveCardsByType;
|
||||||
|
QHash<QString, QHash<int, QStringList>> manaCurveCardsBySubtype;
|
||||||
|
QHash<QString, QHash<int, QStringList>> manaCurveCardsByColor;
|
||||||
|
QHash<QString, QHash<int, QStringList>> manaCurveCardsByPower;
|
||||||
|
QHash<QString, QHash<int, QStringList>> manaCurveCardsByToughness;
|
||||||
|
|
||||||
|
// Not storing card info — only numeric results.
|
||||||
|
QHash<QString, QHash<int, QHash<int, double>>> probabilityExact;
|
||||||
|
QHash<QString, QHash<int, QHash<int, double>>> probabilityAtLeast;
|
||||||
|
|
||||||
QHash<QString, int> determineManaProduction(const QString &);
|
QHash<QString, int> determineManaProduction(const QString &);
|
||||||
std::unordered_map<char, int> countManaSymbols(const QString &);
|
std::unordered_map<char, int> countManaSymbols(const QString &);
|
||||||
|
double hypergeometric(int N, int K, int n, int k);
|
||||||
|
void clearData();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H
|
#endif // COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
#include "mana_base_widget.h"
|
|
||||||
|
|
||||||
#include "../../deck_loader/deck_loader.h"
|
|
||||||
#include "../general/display/banner_widget.h"
|
|
||||||
#include "../general/display/bar_widget.h"
|
|
||||||
|
|
||||||
#include <QHash>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <libcockatrice/card/database/card_database.h>
|
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
|
||||||
|
|
||||||
ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer)
|
|
||||||
: QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer)
|
|
||||||
{
|
|
||||||
layout = new QVBoxLayout(this);
|
|
||||||
setLayout(layout);
|
|
||||||
|
|
||||||
bannerWidget = new BannerWidget(this, tr("Mana Base"), Qt::Vertical, 100);
|
|
||||||
bannerWidget->setMaximumHeight(100);
|
|
||||||
layout->addWidget(bannerWidget);
|
|
||||||
|
|
||||||
barContainer = new QWidget(this);
|
|
||||||
barLayout = new QHBoxLayout(barContainer);
|
|
||||||
layout->addWidget(barContainer);
|
|
||||||
|
|
||||||
connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaBaseWidget::updateDisplay);
|
|
||||||
|
|
||||||
retranslateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ManaBaseWidget::retranslateUi()
|
|
||||||
{
|
|
||||||
bannerWidget->setText(tr("Mana Base"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ManaBaseWidget::updateDisplay()
|
|
||||||
{
|
|
||||||
// Clear the layout first
|
|
||||||
QLayoutItem *item;
|
|
||||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
|
||||||
item->widget()->deleteLater();
|
|
||||||
delete item;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto manaBaseMap = deckStatAnalyzer->getManaBase();
|
|
||||||
|
|
||||||
int highestEntry = 0;
|
|
||||||
for (auto entry : manaBaseMap) {
|
|
||||||
if (entry > highestEntry) {
|
|
||||||
highestEntry = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define color mapping for mana types
|
|
||||||
QHash<QString, QColor> manaColors;
|
|
||||||
manaColors.insert("W", QColor(248, 231, 185));
|
|
||||||
manaColors.insert("U", QColor(14, 104, 171));
|
|
||||||
manaColors.insert("B", QColor(21, 11, 0));
|
|
||||||
manaColors.insert("R", QColor(211, 32, 42));
|
|
||||||
manaColors.insert("G", QColor(0, 115, 62));
|
|
||||||
manaColors.insert("C", QColor(150, 150, 150));
|
|
||||||
|
|
||||||
for (auto manaColor : manaBaseMap.keys()) {
|
|
||||||
QColor barColor = manaColors.value(manaColor, Qt::gray);
|
|
||||||
BarWidget *barWidget = new BarWidget(QString(manaColor), manaBaseMap[manaColor], highestEntry, barColor, this);
|
|
||||||
barLayout->addWidget(barWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +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 "deck_list_statistics_analyzer.h"
|
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
class ManaBaseWidget : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer);
|
|
||||||
void updateDisplay();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void retranslateUi();
|
|
||||||
|
|
||||||
private:
|
|
||||||
DeckListStatisticsAnalyzer *deckStatAnalyzer;
|
|
||||||
BannerWidget *bannerWidget;
|
|
||||||
QVBoxLayout *layout;
|
|
||||||
QWidget *barContainer;
|
|
||||||
QHBoxLayout *barLayout;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MANA_BASE_WIDGET_H
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
#include "mana_curve_widget.h"
|
|
||||||
|
|
||||||
#include "../../../main.h"
|
|
||||||
#include "../../deck_loader/deck_loader.h"
|
|
||||||
#include "../general/display/banner_widget.h"
|
|
||||||
#include "../general/display/bar_widget.h"
|
|
||||||
|
|
||||||
#include <libcockatrice/card/database/card_database.h>
|
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer)
|
|
||||||
: QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer)
|
|
||||||
{
|
|
||||||
layout = new QVBoxLayout(this);
|
|
||||||
setLayout(layout);
|
|
||||||
|
|
||||||
bannerWidget = new BannerWidget(this, tr("Mana Curve"), Qt::Vertical, 100);
|
|
||||||
bannerWidget->setMaximumHeight(100);
|
|
||||||
layout->addWidget(bannerWidget);
|
|
||||||
|
|
||||||
barContainer = new QWidget(this);
|
|
||||||
barLayout = new QHBoxLayout(barContainer);
|
|
||||||
layout->addWidget(barContainer);
|
|
||||||
|
|
||||||
connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay);
|
|
||||||
|
|
||||||
retranslateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ManaCurveWidget::retranslateUi()
|
|
||||||
{
|
|
||||||
bannerWidget->setText(tr("Mana Curve"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ManaCurveWidget::updateDisplay()
|
|
||||||
{
|
|
||||||
// Clear the layout first
|
|
||||||
if (barLayout != nullptr) {
|
|
||||||
QLayoutItem *item;
|
|
||||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
|
||||||
item->widget()->deleteLater();
|
|
||||||
delete item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto manaCurveMap = deckStatAnalyzer->getManaCurve();
|
|
||||||
|
|
||||||
int highestEntry = 0;
|
|
||||||
for (const auto &entry : manaCurveMap) {
|
|
||||||
if (entry.second > highestEntry) {
|
|
||||||
highestEntry = entry.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert unordered_map to ordered map to ensure sorting by CMC
|
|
||||||
std::map<int, int> sortedManaCurve(manaCurveMap.begin(), manaCurveMap.end());
|
|
||||||
|
|
||||||
// Add new widgets to the layout in sorted order
|
|
||||||
for (const auto &entry : sortedManaCurve) {
|
|
||||||
BarWidget *barWidget =
|
|
||||||
new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(122, 122, 122), this);
|
|
||||||
barLayout->addWidget(barWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(); // Update the widget display
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* @file mana_curve_widget.h
|
|
||||||
* @ingroup DeckEditorAnalyticsWidgets
|
|
||||||
* @brief TODO: Document this.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MANA_CURVE_WIDGET_H
|
|
||||||
#define MANA_CURVE_WIDGET_H
|
|
||||||
|
|
||||||
#include "../general/display/banner_widget.h"
|
|
||||||
#include "deck_list_statistics_analyzer.h"
|
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
class ManaCurveWidget : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer);
|
|
||||||
void updateDisplay();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void retranslateUi();
|
|
||||||
|
|
||||||
private:
|
|
||||||
DeckListStatisticsAnalyzer *deckStatAnalyzer;
|
|
||||||
QVBoxLayout *layout;
|
|
||||||
BannerWidget *bannerWidget;
|
|
||||||
QWidget *barContainer;
|
|
||||||
QHBoxLayout *barLayout;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MANA_CURVE_WIDGET_H
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
#include "mana_devotion_widget.h"
|
|
||||||
|
|
||||||
#include "../../deck_loader/deck_loader.h"
|
|
||||||
#include "../general/display/banner_widget.h"
|
|
||||||
#include "../general/display/bar_widget.h"
|
|
||||||
|
|
||||||
#include <libcockatrice/card/database/card_database.h>
|
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
|
||||||
#include <regex>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer)
|
|
||||||
: QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer)
|
|
||||||
{
|
|
||||||
layout = new QVBoxLayout(this);
|
|
||||||
setLayout(layout);
|
|
||||||
|
|
||||||
bannerWidget = new BannerWidget(this, tr("Mana Devotion"), Qt::Vertical, 100);
|
|
||||||
bannerWidget->setMaximumHeight(100);
|
|
||||||
layout->addWidget(bannerWidget);
|
|
||||||
|
|
||||||
barLayout = new QHBoxLayout();
|
|
||||||
layout->addLayout(barLayout);
|
|
||||||
|
|
||||||
connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaDevotionWidget::updateDisplay);
|
|
||||||
|
|
||||||
retranslateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ManaDevotionWidget::retranslateUi()
|
|
||||||
{
|
|
||||||
bannerWidget->setText(tr("Mana Devotion"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ManaDevotionWidget::updateDisplay()
|
|
||||||
{
|
|
||||||
// Clear the layout first
|
|
||||||
QLayoutItem *item;
|
|
||||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
|
||||||
item->widget()->deleteLater();
|
|
||||||
delete item;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto manaDevotionMap = deckStatAnalyzer->getDevotion();
|
|
||||||
|
|
||||||
int highestEntry = 0;
|
|
||||||
for (auto entry : manaDevotionMap) {
|
|
||||||
if (highestEntry < entry.second) {
|
|
||||||
highestEntry = entry.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define color mapping for devotion bars
|
|
||||||
std::unordered_map<char, QColor> manaColors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)},
|
|
||||||
{'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)},
|
|
||||||
{'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}};
|
|
||||||
|
|
||||||
for (auto entry : manaDevotionMap) {
|
|
||||||
QColor barColor = manaColors.count(entry.first) ? manaColors[entry.first] : Qt::gray;
|
|
||||||
BarWidget *barWidget = new BarWidget(QString(entry.first), entry.second, highestEntry, barColor, this);
|
|
||||||
barLayout->addWidget(barWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(); // Update the widget display
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* @file mana_devotion_widget.h
|
|
||||||
* @ingroup DeckEditorAnalyticsWidgets
|
|
||||||
* @brief TODO: Document this.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MANA_DEVOTION_WIDGET_H
|
|
||||||
#define MANA_DEVOTION_WIDGET_H
|
|
||||||
|
|
||||||
#include "../general/display/banner_widget.h"
|
|
||||||
#include "deck_list_statistics_analyzer.h"
|
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
class ManaDevotionWidget : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer);
|
|
||||||
void updateDisplay();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void retranslateUi();
|
|
||||||
|
|
||||||
private:
|
|
||||||
DeckListStatisticsAnalyzer *deckStatAnalyzer;
|
|
||||||
BannerWidget *bannerWidget;
|
|
||||||
QVBoxLayout *layout;
|
|
||||||
QHBoxLayout *barLayout;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MANA_DEVOTION_WIDGET_H
|
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
#include "resizable_panel.h"
|
||||||
|
|
||||||
|
#include "libcockatrice/utility/qt_utils.h"
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWidget *analyticsPanel, QWidget *parent)
|
||||||
|
: QWidget(parent), panel(analyticsPanel), typeId(_typeId)
|
||||||
|
{
|
||||||
|
setAcceptDrops(true);
|
||||||
|
|
||||||
|
auto *mainLayout = new QVBoxLayout(this);
|
||||||
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
mainLayout->setSpacing(0);
|
||||||
|
|
||||||
|
// Frame for selection highlight
|
||||||
|
frame = new QFrame(this);
|
||||||
|
frame->setFrameShape(QFrame::Box);
|
||||||
|
frame->setLineWidth(2);
|
||||||
|
frame->setStyleSheet("border: none;");
|
||||||
|
|
||||||
|
auto *frameLayout = new QVBoxLayout(frame);
|
||||||
|
frameLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
frameLayout->setSpacing(0);
|
||||||
|
|
||||||
|
// Add the analytics panel
|
||||||
|
frameLayout->addWidget(analyticsPanel);
|
||||||
|
|
||||||
|
dropIndicator = new QFrame(frame);
|
||||||
|
dropIndicator->setStyleSheet("background-color: #3daee9;");
|
||||||
|
dropIndicator->setFixedHeight(3);
|
||||||
|
dropIndicator->hide(); // hidden by default
|
||||||
|
dropIndicator->raise(); // make sure it's above children
|
||||||
|
|
||||||
|
selectionOverlay = new QFrame(frame);
|
||||||
|
selectionOverlay->setStyleSheet("background-color: rgba(61,174,233,50);"); // semi-transparent blue
|
||||||
|
selectionOverlay->hide(); // hidden by default
|
||||||
|
selectionOverlay->raise(); // make sure it is above children
|
||||||
|
selectionOverlay->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
// Bottom bar with drag button and resize handle
|
||||||
|
auto *bottomBar = new QWidget(frame);
|
||||||
|
auto *bottomLayout = new QHBoxLayout(bottomBar);
|
||||||
|
bottomLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
bottomLayout->setSpacing(0);
|
||||||
|
|
||||||
|
// Drag button on the left
|
||||||
|
dragButton = new QPushButton("☰", bottomBar);
|
||||||
|
dragButton->setFixedSize(40, 8);
|
||||||
|
dragButton->setCursor(Qt::OpenHandCursor);
|
||||||
|
dragButton->setStyleSheet("QPushButton { "
|
||||||
|
"background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a4a4a, stop:1 #3a3a3a); "
|
||||||
|
"border: none; color: #888; font-size: 10px; }"
|
||||||
|
"QPushButton:hover { background: #5a5a5a; }");
|
||||||
|
bottomLayout->addWidget(dragButton);
|
||||||
|
|
||||||
|
// Resize handle fills the rest
|
||||||
|
resizeHandle = new QWidget(bottomBar);
|
||||||
|
resizeHandle->setFixedHeight(8);
|
||||||
|
resizeHandle->setCursor(Qt::SizeVerCursor);
|
||||||
|
resizeHandle->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
|
||||||
|
"stop:0 #3a3a3a, stop:1 #2a2a2a);");
|
||||||
|
bottomLayout->addWidget(resizeHandle, 1);
|
||||||
|
|
||||||
|
frameLayout->addWidget(bottomBar);
|
||||||
|
|
||||||
|
mainLayout->addWidget(frame);
|
||||||
|
|
||||||
|
// Set size policy
|
||||||
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
|
||||||
|
// Calculate initial height - use panel's size hint if available
|
||||||
|
int panelHint = analyticsPanel->sizeHint().height();
|
||||||
|
int panelMin = analyticsPanel->minimumSizeHint().height();
|
||||||
|
|
||||||
|
// Start with the larger of panel's hint and panel's minimum hint
|
||||||
|
currentHeight = qMax(panelHint + 8, panelMin + 8);
|
||||||
|
updateSizeConstraints();
|
||||||
|
|
||||||
|
// Install event filters
|
||||||
|
dragButton->installEventFilter(this);
|
||||||
|
resizeHandle->installEventFilter(this);
|
||||||
|
|
||||||
|
// Timer for auto-scroll during drag
|
||||||
|
autoScrollTimer = new QTimer(this);
|
||||||
|
autoScrollTimer->setInterval(50);
|
||||||
|
connect(autoScrollTimer, &QTimer::timeout, this, &ResizablePanel::performAutoScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::setSelected(bool selected)
|
||||||
|
{
|
||||||
|
if (selected) {
|
||||||
|
selectionOverlay->setGeometry(0, 0, width(), height());
|
||||||
|
selectionOverlay->show();
|
||||||
|
} else {
|
||||||
|
selectionOverlay->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::setHeightFromSaved(int h)
|
||||||
|
{
|
||||||
|
if (h > 0) {
|
||||||
|
currentHeight = qMax(h, getMinimumAllowedHeight());
|
||||||
|
updateSizeConstraints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ResizablePanel::getCurrentHeight() const
|
||||||
|
{
|
||||||
|
return currentHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize ResizablePanel::sizeHint() const
|
||||||
|
{
|
||||||
|
return QSize(width(), currentHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize ResizablePanel::minimumSizeHint() const
|
||||||
|
{
|
||||||
|
return QSize(0, getMinimumAllowedHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
// Event Handling
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
bool ResizablePanel::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
if (obj == dragButton) {
|
||||||
|
if (event->type() == QEvent::MouseButtonPress) {
|
||||||
|
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||||
|
if (mouseEvent->button() == Qt::LeftButton) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
dragStartPos = mouseEvent->globalPosition().toPoint();
|
||||||
|
#else
|
||||||
|
dragStartPos = mouseEvent->globalPos();
|
||||||
|
#endif
|
||||||
|
isDraggingPanel = false;
|
||||||
|
dragButton->setCursor(Qt::ClosedHandCursor);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (event->type() == QEvent::MouseMove) {
|
||||||
|
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||||
|
if (mouseEvent->buttons() & Qt::LeftButton) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
QPoint currentPos = mouseEvent->globalPosition().toPoint();
|
||||||
|
#else
|
||||||
|
QPoint currentPos = mouseEvent->globalPos();
|
||||||
|
#endif
|
||||||
|
int distance = (currentPos - dragStartPos).manhattanLength();
|
||||||
|
if (distance >= 5 && !isDraggingPanel) {
|
||||||
|
isDraggingPanel = true;
|
||||||
|
startDrag();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||||
|
dragButton->setCursor(Qt::OpenHandCursor);
|
||||||
|
isDraggingPanel = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == resizeHandle) {
|
||||||
|
if (event->type() == QEvent::MouseButtonPress) {
|
||||||
|
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
resizeStartY = mouseEvent->globalPosition().y();
|
||||||
|
#else
|
||||||
|
resizeStartY = mouseEvent->globalPos().y();
|
||||||
|
#endif
|
||||||
|
isResizing = true;
|
||||||
|
resizeStartHeight = currentHeight;
|
||||||
|
resizeHandle->grabMouse();
|
||||||
|
return true;
|
||||||
|
} else if (event->type() == QEvent::MouseMove && isResizing) {
|
||||||
|
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
int deltaY = mouseEvent->globalPosition().y() - resizeStartY;
|
||||||
|
#else
|
||||||
|
int deltaY = mouseEvent->globalPos().y() - resizeStartY;
|
||||||
|
#endif
|
||||||
|
int newHeight = resizeStartHeight + deltaY;
|
||||||
|
|
||||||
|
int minAllowed = getMinimumAllowedHeight();
|
||||||
|
newHeight = qMax(newHeight, minAllowed);
|
||||||
|
|
||||||
|
currentHeight = newHeight;
|
||||||
|
updateSizeConstraints();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||||
|
isResizing = false;
|
||||||
|
resizeHandle->releaseMouse();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QWidget::eventFilter(obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::dragEnterEvent(QDragEnterEvent *event)
|
||||||
|
{
|
||||||
|
if (event->mimeData()->hasFormat("application/x-resizablepanel")) {
|
||||||
|
event->acceptProposedAction();
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
showDropIndicator(event->position().y());
|
||||||
|
#else
|
||||||
|
showDropIndicator(event->pos().y());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::dragMoveEvent(QDragMoveEvent *event)
|
||||||
|
{
|
||||||
|
if (event->mimeData()->hasFormat("application/x-resizablepanel")) {
|
||||||
|
event->acceptProposedAction();
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
showDropIndicator(event->position().y());
|
||||||
|
lastDragPos = mapToGlobal(event->position().toPoint());
|
||||||
|
#else
|
||||||
|
showDropIndicator(event->pos().y());
|
||||||
|
lastDragPos = mapToGlobal(event->pos());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!autoScrollTimer->isActive()) {
|
||||||
|
autoScrollTimer->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::dragLeaveEvent(QDragLeaveEvent *event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(event);
|
||||||
|
hideDropIndicator();
|
||||||
|
autoScrollTimer->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::dropEvent(QDropEvent *event)
|
||||||
|
{
|
||||||
|
hideDropIndicator();
|
||||||
|
autoScrollTimer->stop();
|
||||||
|
|
||||||
|
if (event->mimeData()->hasFormat("application/x-resizablepanel")) {
|
||||||
|
QByteArray data = event->mimeData()->data("application/x-resizablepanel");
|
||||||
|
quintptr ptr = *reinterpret_cast<const quintptr *>(data.constData());
|
||||||
|
ResizablePanel *draggedPanel = reinterpret_cast<ResizablePanel *>(ptr);
|
||||||
|
|
||||||
|
if (draggedPanel && draggedPanel != this) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
bool insertBefore = (event->position().y() < height() / 2);
|
||||||
|
#else
|
||||||
|
bool insertBefore = (event->pos().y() < height() / 2);
|
||||||
|
#endif
|
||||||
|
emit dropRequested(draggedPanel, this, insertBefore);
|
||||||
|
event->acceptProposedAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
|
||||||
|
if (selectionOverlay->isVisible()) {
|
||||||
|
selectionOverlay->setGeometry(0, 0, width(), height());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropIndicator->isVisible()) {
|
||||||
|
dropIndicator->setGeometry(0, dropIndicator->y(), width(), dropIndicator->height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
// Private Helpers
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
int ResizablePanel::getMinimumAllowedHeight() const
|
||||||
|
{
|
||||||
|
QSize panelMin = panel->minimumSizeHint();
|
||||||
|
int panelMinHeight = (panelMin.isValid() && panelMin.height() > 0) ? panelMin.height() : 100;
|
||||||
|
return panelMinHeight + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::updateSizeConstraints()
|
||||||
|
{
|
||||||
|
setMinimumHeight(currentHeight);
|
||||||
|
setMaximumHeight(currentHeight);
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::startDrag()
|
||||||
|
{
|
||||||
|
QDrag *drag = new QDrag(this);
|
||||||
|
QMimeData *mimeData = new QMimeData;
|
||||||
|
|
||||||
|
quintptr ptr = reinterpret_cast<quintptr>(this);
|
||||||
|
QByteArray data(reinterpret_cast<const char *>(&ptr), sizeof(ptr));
|
||||||
|
mimeData->setData("application/x-resizablepanel", data);
|
||||||
|
|
||||||
|
drag->setMimeData(mimeData);
|
||||||
|
|
||||||
|
QPixmap pixmap(width(), 40);
|
||||||
|
pixmap.fill(QColor(58, 58, 58, 200));
|
||||||
|
drag->setPixmap(pixmap);
|
||||||
|
drag->setHotSpot(QPoint(width() / 2, 20));
|
||||||
|
|
||||||
|
emit dragStarted(this);
|
||||||
|
|
||||||
|
autoScrollTimer->start();
|
||||||
|
|
||||||
|
Qt::DropAction result = drag->exec(Qt::MoveAction);
|
||||||
|
Q_UNUSED(result);
|
||||||
|
|
||||||
|
autoScrollTimer->stop();
|
||||||
|
dragButton->setCursor(Qt::OpenHandCursor);
|
||||||
|
isDraggingPanel = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::performAutoScroll()
|
||||||
|
{
|
||||||
|
QScrollArea *scrollArea = QtUtils::findParentOfType<QScrollArea>(this);
|
||||||
|
|
||||||
|
if (!scrollArea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar *scrollBar = scrollArea->verticalScrollBar();
|
||||||
|
if (!scrollBar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect scrollRect = scrollArea->viewport()->rect();
|
||||||
|
QPoint scrollTopLeft = scrollArea->viewport()->mapToGlobal(scrollRect.topLeft());
|
||||||
|
QRect globalScrollRect(scrollTopLeft, scrollRect.size());
|
||||||
|
|
||||||
|
const int scrollMargin = 50;
|
||||||
|
int scrollSpeed = 0;
|
||||||
|
|
||||||
|
if (lastDragPos.y() < globalScrollRect.top() + scrollMargin) {
|
||||||
|
scrollSpeed = -15;
|
||||||
|
} else if (lastDragPos.y() > globalScrollRect.bottom() - scrollMargin) {
|
||||||
|
scrollSpeed = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scrollSpeed != 0) {
|
||||||
|
int newValue = scrollBar->value() + scrollSpeed;
|
||||||
|
newValue = qBound(scrollBar->minimum(), newValue, scrollBar->maximum());
|
||||||
|
scrollBar->setValue(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::showDropIndicator(double y)
|
||||||
|
{
|
||||||
|
bool before = (y < height() / 2);
|
||||||
|
dropIndicator->setGeometry(0, before ? 0 : height() - 3, width(), 3);
|
||||||
|
dropIndicator->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizablePanel::hideDropIndicator()
|
||||||
|
{
|
||||||
|
dropIndicator->hide();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
#ifndef COCKATRICE_RESIZABLE_PANEL_H
|
||||||
|
#define COCKATRICE_RESIZABLE_PANEL_H
|
||||||
|
|
||||||
|
#include "abstract_analytics_panel_widget.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDrag>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class ResizablePanel : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ResizablePanel(const QString &typeId,
|
||||||
|
AbstractAnalyticsPanelWidget *analyticsPanel,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setSelected(bool selected);
|
||||||
|
void setHeightFromSaved(int h);
|
||||||
|
int getCurrentHeight() const;
|
||||||
|
|
||||||
|
QSize sizeHint() const override;
|
||||||
|
QSize minimumSizeHint() const override;
|
||||||
|
|
||||||
|
QString getTypeId() const
|
||||||
|
{
|
||||||
|
return typeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractAnalyticsPanelWidget *panel;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void dragStarted(ResizablePanel *panel);
|
||||||
|
void dropRequested(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||||
|
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||||
|
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||||
|
void dropEvent(QDropEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int getMinimumAllowedHeight() const;
|
||||||
|
void updateSizeConstraints();
|
||||||
|
void startDrag();
|
||||||
|
void performAutoScroll();
|
||||||
|
void showDropIndicator(double y);
|
||||||
|
void hideDropIndicator();
|
||||||
|
|
||||||
|
QString typeId;
|
||||||
|
|
||||||
|
QFrame *frame;
|
||||||
|
QFrame *selectionOverlay;
|
||||||
|
QFrame *dropIndicator;
|
||||||
|
QPushButton *dragButton;
|
||||||
|
QWidget *resizeHandle;
|
||||||
|
|
||||||
|
int currentHeight;
|
||||||
|
bool isResizing = false;
|
||||||
|
bool isDraggingPanel = false;
|
||||||
|
double resizeStartY = 0;
|
||||||
|
int resizeStartHeight = 0;
|
||||||
|
|
||||||
|
QPoint dragStartPos;
|
||||||
|
QPoint lastDragPos;
|
||||||
|
QTimer *autoScrollTimer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_RESIZABLE_PANEL_H
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include "deck_editor_card_database_dock_widget.h"
|
||||||
|
|
||||||
|
DeckEditorCardDatabaseDockWidget::DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent)
|
||||||
|
{
|
||||||
|
setObjectName("databaseDisplayDock");
|
||||||
|
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||||
|
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||||
|
|
||||||
|
createDatabaseDisplayDock(parent);
|
||||||
|
|
||||||
|
retranslateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor)
|
||||||
|
{
|
||||||
|
databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor);
|
||||||
|
|
||||||
|
auto *frame = new QVBoxLayout;
|
||||||
|
frame->setObjectName("databaseDisplayFrame");
|
||||||
|
frame->addWidget(databaseDisplayWidget);
|
||||||
|
|
||||||
|
auto *dockContents = new QWidget();
|
||||||
|
dockContents->setObjectName("databaseDisplayDockContents");
|
||||||
|
dockContents->setLayout(frame);
|
||||||
|
setWidget(dockContents);
|
||||||
|
|
||||||
|
installEventFilter(deckEditor);
|
||||||
|
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||||
|
|
||||||
|
// connect signals
|
||||||
|
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, deckEditor,
|
||||||
|
&AbstractTabDeckEditor::updateCard);
|
||||||
|
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, deckEditor,
|
||||||
|
&AbstractTabDeckEditor::actAddCard);
|
||||||
|
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, deckEditor,
|
||||||
|
&AbstractTabDeckEditor::actAddCardToSideboard);
|
||||||
|
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, deckEditor,
|
||||||
|
&AbstractTabDeckEditor::actDecrementCard);
|
||||||
|
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, deckEditor,
|
||||||
|
&AbstractTabDeckEditor::actDecrementCardFromSideboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
CardDatabase *DeckEditorCardDatabaseDockWidget::getDatabase() const
|
||||||
|
{
|
||||||
|
return databaseDisplayWidget->databaseModel->getDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorCardDatabaseDockWidget::retranslateUi()
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Card Database"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorCardDatabaseDockWidget::setFilterTree(FilterTree *filterTree)
|
||||||
|
{
|
||||||
|
databaseDisplayWidget->setFilterTree(filterTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters()
|
||||||
|
{
|
||||||
|
databaseDisplayWidget->clearAllDatabaseFilters();
|
||||||
|
}
|
||||||
|
void DeckEditorCardDatabaseDockWidget::highlightAllSearchEdit()
|
||||||
|
{
|
||||||
|
databaseDisplayWidget->searchEdit->setSelection(0, databaseDisplayWidget->searchEdit->text().length());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H
|
||||||
|
#define COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H
|
||||||
|
|
||||||
|
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
|
||||||
|
|
||||||
|
#include <QDockWidget>
|
||||||
|
|
||||||
|
class AbstractTabDeckEditor;
|
||||||
|
class CardDatabase;
|
||||||
|
class DeckEditorDatabaseDisplayWidget;
|
||||||
|
class FilterTree;
|
||||||
|
|
||||||
|
class DeckEditorCardDatabaseDockWidget : public QDockWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent);
|
||||||
|
|
||||||
|
DeckEditorDatabaseDisplayWidget *databaseDisplayWidget;
|
||||||
|
|
||||||
|
CardDatabase *getDatabase() const;
|
||||||
|
void setFilterTree(FilterTree *filterTree);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void retranslateUi();
|
||||||
|
void clearAllDatabaseFilters();
|
||||||
|
void highlightAllSearchEdit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H
|
||||||
|
|
@ -21,10 +21,10 @@ static bool canBeCommander(const CardInfo &cardInfo)
|
||||||
cardInfo.getText().contains("can be your commander", Qt::CaseInsensitive);
|
cardInfo.getText().contains("can be your commander", Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent)
|
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor)
|
||||||
: QWidget(parent), deckEditor(parent)
|
: QWidget(parent), deckEditor(deckEditor)
|
||||||
{
|
{
|
||||||
setObjectName("centralWidget");
|
setObjectName("databaseDisplayWidget");
|
||||||
|
|
||||||
centralFrame = new QVBoxLayout(this);
|
centralFrame = new QVBoxLayout(this);
|
||||||
centralFrame->setObjectName("centralFrame");
|
centralFrame->setObjectName("centralFrame");
|
||||||
|
|
@ -134,13 +134,13 @@ void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters()
|
||||||
|
|
||||||
void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||||
{
|
{
|
||||||
const QString cardName = current.sibling(current.row(), 0).data().toString();
|
|
||||||
|
|
||||||
if (!current.isValid()) {
|
if (!current.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
const QString cardName = current.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString();
|
||||||
|
|
||||||
|
if (!current.model()->hasChildren(current.siblingAtColumn(CardDatabaseModel::NameColumn))) {
|
||||||
emit cardChanged(CardDatabaseManager::query()->getPreferredCard(cardName));
|
emit cardChanged(CardDatabaseManager::query()->getPreferredCard(cardName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +172,7 @@ ExactCard DeckEditorDatabaseDisplayWidget::currentCard() const
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
|
const QString cardName = currentIndex.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString();
|
||||||
|
|
||||||
return CardDatabaseManager::query()->getPreferredCard(cardName);
|
return CardDatabaseManager::query()->getPreferredCard(cardName);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class DeckEditorDatabaseDisplayWidget : public QWidget
|
||||||
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent);
|
explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor);
|
||||||
AbstractTabDeckEditor *deckEditor;
|
AbstractTabDeckEditor *deckEditor;
|
||||||
SearchLineEdit *searchEdit;
|
SearchLineEdit *searchEdit;
|
||||||
CardDatabaseModel *databaseModel;
|
CardDatabaseModel *databaseModel;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#include "deck_editor_deck_dock_widget.h"
|
#include "deck_editor_deck_dock_widget.h"
|
||||||
|
|
||||||
#include "../../../client/settings/cache_settings.h"
|
#include "../../../client/settings/cache_settings.h"
|
||||||
#include "../../deck_loader/deck_loader.h"
|
|
||||||
#include "deck_list_style_proxy.h"
|
#include "deck_list_style_proxy.h"
|
||||||
|
#include "deck_state_manager.h"
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
|
|
@ -38,7 +38,7 @@ static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
|
||||||
}
|
}
|
||||||
|
|
||||||
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
|
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
|
||||||
: QDockWidget(parent), deckEditor(parent)
|
: QDockWidget(parent), deckEditor(parent), deckStateManager(parent->deckStateManager)
|
||||||
{
|
{
|
||||||
setObjectName("deckDock");
|
setObjectName("deckDock");
|
||||||
|
|
||||||
|
|
@ -52,19 +52,21 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::createDeckDock()
|
void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
{
|
{
|
||||||
deckModel = new DeckListModel(this);
|
connect(getModel(), &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
||||||
deckModel->setObjectName("deckModel");
|
|
||||||
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
|
||||||
|
|
||||||
deckLoader = new DeckLoader(this);
|
|
||||||
|
|
||||||
proxy = new DeckListStyleProxy(this);
|
proxy = new DeckListStyleProxy(this);
|
||||||
proxy->setSourceModel(deckModel);
|
proxy->setSourceModel(getModel());
|
||||||
|
|
||||||
historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, deckEditor->getHistoryManager(), this);
|
historyManagerWidget = new DeckListHistoryManagerWidget(deckStateManager, proxy, this);
|
||||||
connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this,
|
connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this,
|
||||||
&DeckEditorDeckDockWidget::syncDisplayWidgetsToModel);
|
&DeckEditorDeckDockWidget::syncDisplayWidgetsToModel);
|
||||||
|
|
||||||
|
connect(deckStateManager, &DeckStateManager::focusIndexChanged, this, &DeckEditorDeckDockWidget::setSelectedIndex);
|
||||||
|
connect(deckStateManager, &DeckStateManager::deckReplaced, this,
|
||||||
|
&DeckEditorDeckDockWidget::syncDisplayWidgetsToModel);
|
||||||
|
connect(deckStateManager, &DeckStateManager::deckReplaced, this,
|
||||||
|
&DeckEditorDeckDockWidget::applyActiveGroupCriteria);
|
||||||
|
|
||||||
deckView = new QTreeView();
|
deckView = new QTreeView();
|
||||||
deckView->setObjectName("deckView");
|
deckView->setObjectName("deckView");
|
||||||
deckView->setModel(proxy);
|
deckView->setModel(proxy);
|
||||||
|
|
@ -76,14 +78,14 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||||
&DeckEditorDeckDockWidget::updateCard);
|
&DeckEditorDeckDockWidget::updateCard);
|
||||||
connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard);
|
connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapSelection);
|
||||||
deckView->setContextMenuPolicy(Qt::CustomContextMenu);
|
deckView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu);
|
connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu);
|
||||||
connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapCard);
|
connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapSelection);
|
||||||
connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrement);
|
connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrementSelection);
|
||||||
connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrement);
|
connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrementSelection);
|
||||||
connect(&deckViewKeySignals, &KeySignals::onCtrlAltMinus, this, &DeckEditorDeckDockWidget::actDecrementSelection);
|
connect(&deckViewKeySignals, &KeySignals::onCtrlAltMinus, this, &DeckEditorDeckDockWidget::actDecrementSelection);
|
||||||
connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrement);
|
connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrementSelection);
|
||||||
connect(&deckViewKeySignals, &KeySignals::onShiftLeft, this, &DeckEditorDeckDockWidget::actDecrementSelection);
|
connect(&deckViewKeySignals, &KeySignals::onShiftLeft, this, &DeckEditorDeckDockWidget::actDecrementSelection);
|
||||||
connect(&deckViewKeySignals, &KeySignals::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard);
|
connect(&deckViewKeySignals, &KeySignals::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard);
|
||||||
|
|
||||||
|
|
@ -97,7 +99,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
nameDebounceTimer = new QTimer(this);
|
nameDebounceTimer = new QTimer(this);
|
||||||
nameDebounceTimer->setSingleShot(true);
|
nameDebounceTimer->setSingleShot(true);
|
||||||
nameDebounceTimer->setInterval(300); // debounce duration in ms
|
nameDebounceTimer->setInterval(300); // debounce duration in ms
|
||||||
connect(nameDebounceTimer, &QTimer::timeout, this, [this]() { updateName(nameEdit->text()); });
|
connect(nameDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeName);
|
||||||
|
|
||||||
connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() {
|
connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() {
|
||||||
nameDebounceTimer->start(); // restart debounce timer
|
nameDebounceTimer->start(); // restart debounce timer
|
||||||
|
|
@ -141,7 +143,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
commentsDebounceTimer = new QTimer(this);
|
commentsDebounceTimer = new QTimer(this);
|
||||||
commentsDebounceTimer->setSingleShot(true);
|
commentsDebounceTimer->setSingleShot(true);
|
||||||
commentsDebounceTimer->setInterval(400); // longer debounce for multi-line
|
commentsDebounceTimer->setInterval(400); // longer debounce for multi-line
|
||||||
connect(commentsDebounceTimer, &QTimer::timeout, this, [this]() { updateComments(); });
|
connect(commentsDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeComments);
|
||||||
|
|
||||||
connect(commentsEdit, &QTextEdit::textChanged, this, [this]() {
|
connect(commentsEdit, &QTextEdit::textChanged, this, [this]() {
|
||||||
commentsDebounceTimer->start(); // restart debounce timer
|
commentsDebounceTimer->start(); // restart debounce timer
|
||||||
|
|
@ -152,18 +154,21 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
bannerCardLabel->setText(tr("Banner Card"));
|
bannerCardLabel->setText(tr("Banner Card"));
|
||||||
bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||||
bannerCardComboBox = new QComboBox(this);
|
bannerCardComboBox = new QComboBox(this);
|
||||||
connect(deckModel, &DeckListModel::dataChanged, this, [this]() {
|
connect(getModel(), &DeckListModel::cardNodesChanged, this, [this]() {
|
||||||
// Delay the update to avoid race conditions
|
// Delay the update to avoid race conditions
|
||||||
QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox);
|
QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox);
|
||||||
});
|
});
|
||||||
|
connect(getModel(), &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand);
|
||||||
|
connect(getModel(), &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll);
|
||||||
|
|
||||||
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||||
&DeckEditorDeckDockWidget::setBannerCard);
|
&DeckEditorDeckDockWidget::writeBannerCard);
|
||||||
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||||
|
|
||||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags());
|
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, {});
|
||||||
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
|
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
|
||||||
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this,
|
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, deckStateManager,
|
||||||
&DeckEditorDeckDockWidget::setTags);
|
&DeckStateManager::setTags);
|
||||||
|
|
||||||
activeGroupCriteriaLabel = new QLabel(this);
|
activeGroupCriteriaLabel = new QLabel(this);
|
||||||
|
|
||||||
|
|
@ -171,17 +176,12 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
activeGroupCriteriaComboBox->addItem(tr("Main Type"), DeckListModelGroupCriteria::MAIN_TYPE);
|
activeGroupCriteriaComboBox->addItem(tr("Main Type"), DeckListModelGroupCriteria::MAIN_TYPE);
|
||||||
activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST);
|
activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST);
|
||||||
activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR);
|
activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR);
|
||||||
connect(activeGroupCriteriaComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() {
|
connect(activeGroupCriteriaComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
deckModel->setActiveGroupCriteria(static_cast<DeckListModelGroupCriteria::Type>(
|
&DeckEditorDeckDockWidget::applyActiveGroupCriteria);
|
||||||
activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
|
|
||||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
|
||||||
deckView->expandAll();
|
|
||||||
deckView->expandAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
aIncrement = new QAction(QString(), this);
|
aIncrement = new QAction(QString(), this);
|
||||||
aIncrement->setIcon(QPixmap("theme:icons/increment"));
|
aIncrement->setIcon(QPixmap("theme:icons/increment"));
|
||||||
connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrement);
|
connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrementSelection);
|
||||||
auto *tbIncrement = new QToolButton(this);
|
auto *tbIncrement = new QToolButton(this);
|
||||||
tbIncrement->setDefaultAction(aIncrement);
|
tbIncrement->setDefaultAction(aIncrement);
|
||||||
|
|
||||||
|
|
@ -199,7 +199,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
|
|
||||||
aSwapCard = new QAction(QString(), this);
|
aSwapCard = new QAction(QString(), this);
|
||||||
aSwapCard->setIcon(QPixmap("theme:icons/swap"));
|
aSwapCard->setIcon(QPixmap("theme:icons/swap"));
|
||||||
connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapCard);
|
connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapSelection);
|
||||||
auto *tbSwapCard = new QToolButton(this);
|
auto *tbSwapCard = new QToolButton(this);
|
||||||
tbSwapCard->setDefaultAction(aSwapCard);
|
tbSwapCard->setDefaultAction(aSwapCard);
|
||||||
|
|
||||||
|
|
@ -283,21 +283,20 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::initializeFormats()
|
void DeckEditorDeckDockWidget::initializeFormats()
|
||||||
{
|
{
|
||||||
QMap<QString, int> allFormats = CardDatabaseManager::query()->getAllFormatsWithCount();
|
QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys();
|
||||||
|
|
||||||
formatComboBox->clear(); // Remove "Loading Database..."
|
formatComboBox->clear(); // Remove "Loading Database..."
|
||||||
formatComboBox->setEnabled(true);
|
formatComboBox->setEnabled(true);
|
||||||
|
|
||||||
// Populate with formats
|
// Populate with formats
|
||||||
formatComboBox->addItem("", "");
|
formatComboBox->addItem("", "");
|
||||||
for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) {
|
for (auto formatName : allFormats) {
|
||||||
QString displayText = QString("%1").arg(it.key());
|
formatComboBox->addItem(formatName, formatName); // store the raw key in itemData
|
||||||
formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deckModel->getDeckList()->getGameFormat().isEmpty()) {
|
QString format = deckStateManager->getMetadata().gameFormat;
|
||||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
if (!format.isEmpty()) {
|
||||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
formatComboBox->setCurrentIndex(formatComboBox->findData(format));
|
||||||
} else {
|
} else {
|
||||||
// Ensure no selection is visible initially
|
// Ensure no selection is visible initially
|
||||||
formatComboBox->setCurrentIndex(-1);
|
formatComboBox->setCurrentIndex(-1);
|
||||||
|
|
@ -306,11 +305,10 @@ void DeckEditorDeckDockWidget::initializeFormats()
|
||||||
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
QString formatKey = formatComboBox->itemData(index).toString();
|
QString formatKey = formatComboBox->itemData(index).toString();
|
||||||
deckModel->setActiveFormat(formatKey);
|
deckStateManager->setFormat(formatKey);
|
||||||
} else {
|
} else {
|
||||||
deckModel->setActiveFormat(QString()); // clear format if deselected
|
deckStateManager->setFormat(""); // clear format if deselected
|
||||||
}
|
}
|
||||||
emit deckModified();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,17 +317,17 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||||
QModelIndex current = deckView->selectionModel()->currentIndex();
|
QModelIndex current = deckView->selectionModel()->currentIndex();
|
||||||
if (!current.isValid())
|
if (!current.isValid())
|
||||||
return {};
|
return {};
|
||||||
const QString cardName = current.sibling(current.row(), 1).data().toString();
|
const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||||
const QString cardProviderID = current.sibling(current.row(), 4).data().toString();
|
const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||||
const QModelIndex gparent = current.parent().parent();
|
const QModelIndex gparent = current.parent().parent();
|
||||||
|
|
||||||
if (!gparent.isValid()) {
|
if (!gparent.isValid()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
|
const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
|
||||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
if (!current.model()->hasChildren(current.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT))) {
|
||||||
if (ExactCard selectedCard = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) {
|
if (ExactCard selectedCard = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) {
|
||||||
return selectedCard;
|
return selectedCard;
|
||||||
}
|
}
|
||||||
|
|
@ -340,43 +338,37 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||||
void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/)
|
void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/)
|
||||||
{
|
{
|
||||||
if (ExactCard card = getCurrentCard()) {
|
if (ExactCard card = getCurrentCard()) {
|
||||||
emit cardChanged(card);
|
emit selectedCardChanged(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::updateName(const QString &name)
|
/**
|
||||||
|
* @brief Writes the contents of the name textBox to the DeckStateManager
|
||||||
|
*/
|
||||||
|
void DeckEditorDeckDockWidget::writeName()
|
||||||
{
|
{
|
||||||
emit requestDeckHistorySave(
|
QString name = nameEdit->text();
|
||||||
QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName()));
|
deckStateManager->setName(name);
|
||||||
deckModel->getDeckList()->setName(name);
|
|
||||||
deckEditor->setModified(name.isEmpty());
|
|
||||||
emit nameChanged();
|
|
||||||
emit deckModified();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::updateComments()
|
/**
|
||||||
|
* @brief Writes the contents of the comments textBox to the DeckStateManager
|
||||||
|
*/
|
||||||
|
void DeckEditorDeckDockWidget::writeComments()
|
||||||
{
|
{
|
||||||
emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)")
|
QString comments = commentsEdit->toPlainText();
|
||||||
.arg(deckLoader->getDeck().deckList.getComments().size())
|
deckStateManager->setComments(comments);
|
||||||
.arg(commentsEdit->toPlainText().size()));
|
|
||||||
|
|
||||||
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
|
|
||||||
deckEditor->setModified(commentsEdit->toPlainText().isEmpty());
|
|
||||||
emit commentsChanged();
|
|
||||||
emit deckModified();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::updateHash()
|
void DeckEditorDeckDockWidget::updateHash()
|
||||||
{
|
{
|
||||||
hashLabel->setText(deckModel->getDeckList()->getDeckHash());
|
hashLabel->setText(deckStateManager->getDeckHash());
|
||||||
emit hashChanged();
|
|
||||||
emit deckModified();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||||
{
|
{
|
||||||
// Store current banner card identity
|
// Store current banner card identity
|
||||||
CardRef wanted = deckModel->getDeckList()->getBannerCard();
|
CardRef wanted = deckStateManager->getMetadata().bannerCard;
|
||||||
|
|
||||||
// Block signals temporarily
|
// Block signals temporarily
|
||||||
bool wasBlocked = bannerCardComboBox->blockSignals(true);
|
bool wasBlocked = bannerCardComboBox->blockSignals(true);
|
||||||
|
|
@ -386,15 +378,15 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||||
|
|
||||||
// Collect unique (name, providerId) pairs
|
// Collect unique (name, providerId) pairs
|
||||||
QSet<QPair<QString, QString>> bannerCardSet;
|
QSet<QPair<QString, QString>> bannerCardSet;
|
||||||
QList<const DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes();
|
QList<CardRef> cardsInDeck = getModel()->getCardRefs();
|
||||||
|
|
||||||
for (auto currentCard : cardsInDeck) {
|
for (auto cardRef : cardsInDeck) {
|
||||||
if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
|
if (!CardDatabaseManager::query()->getCard(cardRef)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert one entry per distinct card, ignore copies
|
// Insert one entry per distinct card, ignore copies
|
||||||
bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()});
|
bannerCardSet.insert({cardRef.name, cardRef.providerId});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to sorted list
|
// Convert to sorted list
|
||||||
|
|
@ -415,7 +407,6 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||||
// Handle results
|
// Handle results
|
||||||
if (restoreIndex != -1) {
|
if (restoreIndex != -1) {
|
||||||
bannerCardComboBox->setCurrentIndex(restoreIndex);
|
bannerCardComboBox->setCurrentIndex(restoreIndex);
|
||||||
syncDeckListBannerCardWithComboBox();
|
|
||||||
} else {
|
} else {
|
||||||
// Add a placeholder "-" and set it as the current selection
|
// Add a placeholder "-" and set it as the current selection
|
||||||
bannerCardComboBox->insertItem(0, "-");
|
bannerCardComboBox->insertItem(0, "-");
|
||||||
|
|
@ -426,25 +417,21 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||||
bannerCardComboBox->blockSignals(wasBlocked);
|
bannerCardComboBox->blockSignals(wasBlocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */)
|
/**
|
||||||
|
* @brief Writes the selected bannerCard to the DeckStateManager
|
||||||
|
*/
|
||||||
|
void DeckEditorDeckDockWidget::writeBannerCard(int index)
|
||||||
{
|
{
|
||||||
emit requestDeckHistorySave(tr("Banner card changed"));
|
auto [name, id] = bannerCardComboBox->itemData(index).value<QPair<QString, QString>>();
|
||||||
syncDeckListBannerCardWithComboBox();
|
CardRef bannerCard = {name, id};
|
||||||
deckEditor->setModified(true);
|
deckStateManager->setBannerCard(bannerCard);
|
||||||
emit deckModified();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::setTags(const QStringList &tags)
|
void DeckEditorDeckDockWidget::applyActiveGroupCriteria()
|
||||||
{
|
{
|
||||||
deckModel->getDeckList()->setTags(tags);
|
getModel()->setActiveGroupCriteria(
|
||||||
deckEditor->setModified(true);
|
static_cast<DeckListModelGroupCriteria::Type>(activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
|
||||||
emit deckModified();
|
getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||||
}
|
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox()
|
|
||||||
{
|
|
||||||
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
|
|
||||||
deckModel->getDeckList()->setBannerCard({name, id});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible)
|
void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible)
|
||||||
|
|
@ -460,7 +447,7 @@ void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible)
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
||||||
{
|
{
|
||||||
if (deckModel->getDeckList()->getBannerCard().name == "") {
|
if (deckStateManager->getMetadata().bannerCard.name == "") {
|
||||||
if (bannerCardComboBox->findText("-") != -1) {
|
if (bannerCardComboBox->findText("-") != -1) {
|
||||||
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-"));
|
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-"));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -468,36 +455,29 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
||||||
bannerCardComboBox->setCurrentIndex(0);
|
bannerCardComboBox->setCurrentIndex(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name);
|
bannerCardComboBox->setCurrentText(deckStateManager->getMetadata().bannerCard.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus)
|
||||||
* Sets the currently active deck for this tab
|
|
||||||
* @param _deck The deck.
|
|
||||||
*/
|
|
||||||
void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck)
|
|
||||||
{
|
{
|
||||||
deckLoader->setDeck(_deck);
|
deckView->clearSelection();
|
||||||
deckModel->setDeckList(&deckLoader->getDeck().deckList);
|
deckView->setCurrentIndex(newCardIndex);
|
||||||
connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree);
|
recursiveExpand(newCardIndex);
|
||||||
|
|
||||||
emit requestDeckHistoryClear();
|
if (!preserveWidgetFocus) {
|
||||||
historyManagerWidget->setDeckListModel(deckModel);
|
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||||
|
}
|
||||||
syncDisplayWidgetsToModel();
|
|
||||||
|
|
||||||
emit deckChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||||
{
|
{
|
||||||
nameEdit->blockSignals(true);
|
nameEdit->blockSignals(true);
|
||||||
nameEdit->setText(deckModel->getDeckList()->getName());
|
nameEdit->setText(deckStateManager->getMetadata().name);
|
||||||
nameEdit->blockSignals(false);
|
nameEdit->blockSignals(false);
|
||||||
|
|
||||||
commentsEdit->blockSignals(true);
|
commentsEdit->blockSignals(true);
|
||||||
commentsEdit->setText(deckModel->getDeckList()->getComments());
|
commentsEdit->setText(deckStateManager->getMetadata().comments);
|
||||||
commentsEdit->blockSignals(false);
|
commentsEdit->blockSignals(false);
|
||||||
|
|
||||||
bannerCardComboBox->blockSignals(true);
|
bannerCardComboBox->blockSignals(true);
|
||||||
|
|
@ -505,95 +485,155 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||||
updateBannerCardComboBox();
|
updateBannerCardComboBox();
|
||||||
bannerCardComboBox->blockSignals(false);
|
bannerCardComboBox->blockSignals(false);
|
||||||
updateHash();
|
updateHash();
|
||||||
sortDeckModelToDeckView();
|
|
||||||
expandAll();
|
|
||||||
|
|
||||||
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
|
formatComboBox->blockSignals(true);
|
||||||
}
|
formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat));
|
||||||
|
formatComboBox->blockSignals(false);
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
|
deckTagsDisplayWidget->blockSignals(true);
|
||||||
{
|
deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags);
|
||||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
deckTagsDisplayWidget->blockSignals(false);
|
||||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
|
||||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
|
||||||
deckView->expandAll();
|
|
||||||
deckView->expandAll();
|
|
||||||
|
|
||||||
emit deckChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
DeckLoader *DeckEditorDeckDockWidget::getDeckLoader()
|
|
||||||
{
|
|
||||||
return deckLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeckList *DeckEditorDeckDockWidget::getDeckList()
|
|
||||||
{
|
|
||||||
return deckModel->getDeckList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the tab to the state for a blank new tab.
|
* @brief Convenience method to get the underlying model instance from the DeckStateManager
|
||||||
*/
|
*/
|
||||||
void DeckEditorDeckDockWidget::cleanDeck()
|
DeckListModel *DeckEditorDeckDockWidget::getModel() const
|
||||||
{
|
{
|
||||||
deckModel->cleanList();
|
return deckStateManager->getModel();
|
||||||
nameEdit->setText(QString());
|
|
||||||
emit nameChanged();
|
|
||||||
commentsEdit->setText(QString());
|
|
||||||
emit commentsChanged();
|
|
||||||
hashLabel->setText(QString());
|
|
||||||
emit hashChanged();
|
|
||||||
emit deckModified();
|
|
||||||
emit deckChanged();
|
|
||||||
updateBannerCardComboBox();
|
|
||||||
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
|
void DeckEditorDeckDockWidget::selectPrevCard()
|
||||||
{
|
{
|
||||||
if (index.parent().isValid())
|
changeSelectedCard(-1);
|
||||||
recursiveExpand(index.parent());
|
|
||||||
deckView->expand(index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::selectNextCard()
|
||||||
|
{
|
||||||
|
changeSelectedCard(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selects a card based on the change direction.
|
||||||
|
*
|
||||||
|
* @param changeBy The direction to change, -1 for previous, 1 for next.
|
||||||
|
*/
|
||||||
|
void DeckEditorDeckDockWidget::changeSelectedCard(int changeBy)
|
||||||
|
{
|
||||||
|
if (changeBy == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current index of the selected item
|
||||||
|
auto deckViewCurrentIndex = deckView->currentIndex();
|
||||||
|
|
||||||
|
// For some reason, if the deckModel is modified but the view is not manually reselected,
|
||||||
|
// currentIndex will return an index for the underlying deckModel instead of the proxy.
|
||||||
|
// That index will return an invalid index when indexBelow/indexAbove crosses a header node,
|
||||||
|
// causing the selection to fail to move down.
|
||||||
|
/// \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround
|
||||||
|
if (deckViewCurrentIndex.model() == proxy->sourceModel()) {
|
||||||
|
deckViewCurrentIndex = proxy->mapFromSource(deckViewCurrentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy);
|
||||||
|
if (!nextIndex.isValid()) {
|
||||||
|
nextIndex = deckViewCurrentIndex;
|
||||||
|
|
||||||
|
// Increment to the next valid index, skipping header rows
|
||||||
|
AbstractDecklistNode *node;
|
||||||
|
do {
|
||||||
|
if (changeBy > 0) {
|
||||||
|
nextIndex = deckView->indexBelow(nextIndex);
|
||||||
|
} else {
|
||||||
|
nextIndex = deckView->indexAbove(nextIndex);
|
||||||
|
}
|
||||||
|
node = static_cast<AbstractDecklistNode *>(nextIndex.internalPointer());
|
||||||
|
} while (node && node->isDeckHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextIndex.isValid()) {
|
||||||
|
deckView->setCurrentIndex(nextIndex);
|
||||||
|
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Expands all parents of the given index.
|
||||||
|
* @param sourceIndex The index to expand (model source index)
|
||||||
|
*/
|
||||||
|
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &sourceIndex)
|
||||||
|
{
|
||||||
|
auto index = proxy->mapFromSource(sourceIndex);
|
||||||
|
|
||||||
|
while (index.parent().isValid()) {
|
||||||
|
index = index.parent();
|
||||||
|
deckView->expand(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fully expands all levels of the deck view
|
||||||
|
*/
|
||||||
void DeckEditorDeckDockWidget::expandAll()
|
void DeckEditorDeckDockWidget::expandAll()
|
||||||
{
|
{
|
||||||
deckView->expandAll();
|
deckView->expandRecursively(deckView->rootIndex());
|
||||||
deckView->expandAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the index of all the currently selected card nodes in the decklist table.
|
* Gets the source index of all the currently selected card nodes in the decklist table.
|
||||||
* The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them.
|
* The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them.
|
||||||
*
|
*
|
||||||
* @return A model index list containing all selected card nodes
|
* @return A list containing the source indices of all selected card nodes
|
||||||
*/
|
*/
|
||||||
QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
|
QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodeSourceIndices() const
|
||||||
{
|
{
|
||||||
auto selectedRows = deckView->selectionModel()->selectedRows();
|
auto selectedRows = deckView->selectionModel()->selectedRows();
|
||||||
|
|
||||||
const auto notLeafNode = [this](const QModelIndex &index) {
|
const auto mapToSource = [this](const QModelIndex &index) { return proxy->mapToSource(index); };
|
||||||
return deckModel->hasChildren(proxy->mapToSource(index));
|
std::transform(selectedRows.begin(), selectedRows.end(), selectedRows.begin(), mapToSource);
|
||||||
};
|
|
||||||
|
const auto notLeafNode = [this](const QModelIndex &sourceIndex) { return getModel()->hasChildren(sourceIndex); };
|
||||||
selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end());
|
selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end());
|
||||||
|
|
||||||
std::reverse(selectedRows.begin(), selectedRows.end());
|
std::reverse(selectedRows.begin(), selectedRows.end());
|
||||||
return selectedRows;
|
return selectedRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::actIncrement()
|
void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString &zoneName)
|
||||||
{
|
{
|
||||||
auto selectedRows = getSelectedCardNodes();
|
if (!card) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &index : selectedRows) {
|
deckStateManager->addCard(card, zoneName);
|
||||||
offsetCountAtIndex(index, 1);
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::actIncrementSelection()
|
||||||
|
{
|
||||||
|
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||||
|
|
||||||
|
for (const auto &sourceIndex : selectedRows) {
|
||||||
|
offsetCountAtIndex(sourceIndex, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::actSwapCard()
|
void DeckEditorDeckDockWidget::actSwapCard(const ExactCard &card, const QString &zoneName)
|
||||||
{
|
{
|
||||||
auto selectedRows = getSelectedCardNodes();
|
QString providerId = card.getPrinting().getUuid();
|
||||||
|
QString collectorNumber = card.getPrinting().getProperty("num");
|
||||||
|
|
||||||
|
QModelIndex foundCard = getModel()->findCard(card.getName(), zoneName, providerId, collectorNumber);
|
||||||
|
if (!foundCard.isValid()) {
|
||||||
|
foundCard = getModel()->findCard(card.getName(), zoneName);
|
||||||
|
}
|
||||||
|
|
||||||
|
deckStateManager->swapCardAtIndex(foundCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::actSwapSelection()
|
||||||
|
{
|
||||||
|
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||||
|
|
||||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||||
|
|
@ -601,52 +641,15 @@ void DeckEditorDeckDockWidget::actSwapCard()
|
||||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isModified = false;
|
for (const auto &sourceIndex : selectedRows) {
|
||||||
for (const auto ¤tIndex : selectedRows) {
|
deckStateManager->swapCardAtIndex(sourceIndex);
|
||||||
if (swapCard(currentIndex)) {
|
|
||||||
isModified = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
|
||||||
if (isModified) {
|
|
||||||
emit deckModified();
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Swaps the card at the index between the maindeck and sideboard
|
|
||||||
*
|
|
||||||
* @param currentIndex The index to swap.
|
|
||||||
* @return True if the swap was successful
|
|
||||||
*/
|
|
||||||
bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex)
|
|
||||||
{
|
|
||||||
if (!currentIndex.isValid())
|
|
||||||
return false;
|
|
||||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString();
|
|
||||||
const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 4).data().toString();
|
|
||||||
const QModelIndex gparent = currentIndex.parent().parent();
|
|
||||||
|
|
||||||
if (!gparent.isValid())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
|
|
||||||
offsetCountAtIndex(currentIndex, -1);
|
|
||||||
const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
|
||||||
|
|
||||||
ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID});
|
|
||||||
QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName)
|
|
||||||
// Third argument (true) says create the card no matter what, even if not in DB
|
|
||||||
: deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
|
|
||||||
recursiveExpand(proxy->mapToSource(newCardIndex));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName)
|
void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName)
|
||||||
{
|
{
|
||||||
if (!card)
|
if (!card)
|
||||||
|
|
@ -654,22 +657,12 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
|
||||||
if (card.getInfo().getIsToken())
|
if (card.getInfo().getIsToken())
|
||||||
zoneName = DECK_ZONE_TOKENS;
|
zoneName = DECK_ZONE_TOKENS;
|
||||||
|
|
||||||
QString providerId = card.getPrinting().getUuid();
|
deckStateManager->decrementCard(card, zoneName);
|
||||||
QString collectorNumber = card.getPrinting().getProperty("num");
|
|
||||||
|
|
||||||
QModelIndex idx = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
|
|
||||||
if (!idx.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deckView->clearSelection();
|
|
||||||
deckView->setCurrentIndex(proxy->mapToSource(idx));
|
|
||||||
offsetCountAtIndex(idx, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::actDecrementSelection()
|
void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||||
{
|
{
|
||||||
auto selectedRows = getSelectedCardNodes();
|
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||||
|
|
||||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||||
|
|
@ -677,8 +670,8 @@ void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &index : selectedRows) {
|
for (const auto &sourceIndex : selectedRows) {
|
||||||
offsetCountAtIndex(index, -1);
|
offsetCountAtIndex(sourceIndex, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
|
@ -686,7 +679,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::actRemoveCard()
|
void DeckEditorDeckDockWidget::actRemoveCard()
|
||||||
{
|
{
|
||||||
auto selectedRows = getSelectedCardNodes();
|
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||||
|
|
||||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||||
|
|
@ -694,58 +687,29 @@ void DeckEditorDeckDockWidget::actRemoveCard()
|
||||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isModified = false;
|
for (const auto &sourceIndex : selectedRows) {
|
||||||
for (const auto &index : selectedRows) {
|
deckStateManager->removeCardAtIndex(sourceIndex);
|
||||||
if (!index.isValid() || deckModel->hasChildren(index)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
QModelIndex sourceIndex = proxy->mapToSource(index);
|
|
||||||
QString cardName = sourceIndex.sibling(sourceIndex.row(), 1).data().toString();
|
|
||||||
|
|
||||||
emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName));
|
|
||||||
|
|
||||||
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
|
|
||||||
isModified = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
|
||||||
if (isModified) {
|
|
||||||
emit deckModified();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
/**
|
||||||
|
* @brief Increments or decrements the amount of the card node at the index by 1.
|
||||||
|
* @param idx The source index
|
||||||
|
* @param isIncrement If true, increments the count. If false, decrements the count
|
||||||
|
*/
|
||||||
|
void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool isIncrement)
|
||||||
{
|
{
|
||||||
if (!idx.isValid() || deckModel->hasChildren(idx)) {
|
if (!idx.isValid() || getModel()->hasChildren(idx)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex sourceIndex = proxy->mapToSource(idx);
|
if (isIncrement) {
|
||||||
|
deckStateManager->incrementCountAtIndex(idx);
|
||||||
const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0);
|
|
||||||
const QModelIndex nameIndex = sourceIndex.sibling(sourceIndex.row(), 1);
|
|
||||||
|
|
||||||
const QString cardName = deckModel->data(nameIndex, Qt::EditRole).toString();
|
|
||||||
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
|
|
||||||
const int new_count = count + offset;
|
|
||||||
|
|
||||||
const auto reason =
|
|
||||||
QString(tr("%1 %2 × \"%3\" (%4)"))
|
|
||||||
.arg(offset > 0 ? tr("Added") : tr("Removed"))
|
|
||||||
.arg(qAbs(offset))
|
|
||||||
.arg(cardName)
|
|
||||||
.arg(deckModel->data(sourceIndex.sibling(sourceIndex.row(), 4), Qt::DisplayRole).toString());
|
|
||||||
|
|
||||||
emit requestDeckHistorySave(reason);
|
|
||||||
|
|
||||||
if (new_count <= 0) {
|
|
||||||
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
|
|
||||||
} else {
|
} else {
|
||||||
deckModel->setData(numberIndex, new_count, Qt::EditRole);
|
deckStateManager->decrementCountAtIndex(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit deckModified();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
||||||
|
|
|
||||||
|
|
@ -28,22 +28,14 @@ class DeckEditorDeckDockWidget : public QDockWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
|
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
|
||||||
DeckLoader *deckLoader;
|
|
||||||
DeckListStyleProxy *proxy;
|
DeckListStyleProxy *proxy;
|
||||||
DeckListModel *deckModel;
|
|
||||||
QTreeView *deckView;
|
QTreeView *deckView;
|
||||||
QComboBox *bannerCardComboBox;
|
QComboBox *bannerCardComboBox;
|
||||||
void createDeckDock();
|
void createDeckDock();
|
||||||
ExactCard getCurrentCard();
|
ExactCard getCurrentCard();
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
QString getDeckName()
|
|
||||||
{
|
|
||||||
return nameEdit->text();
|
|
||||||
}
|
|
||||||
QString getSimpleDeckName()
|
|
||||||
{
|
|
||||||
return nameEdit->text().simplified();
|
|
||||||
}
|
|
||||||
QComboBox *getGroupByComboBox()
|
QComboBox *getGroupByComboBox()
|
||||||
{
|
{
|
||||||
return activeGroupCriteriaComboBox;
|
return activeGroupCriteriaComboBox;
|
||||||
|
|
@ -55,35 +47,26 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void cleanDeck();
|
void selectPrevCard();
|
||||||
|
void selectNextCard();
|
||||||
void updateBannerCardComboBox();
|
void updateBannerCardComboBox();
|
||||||
void setDeck(const LoadedDeck &_deck);
|
|
||||||
void syncDisplayWidgetsToModel();
|
void syncDisplayWidgetsToModel();
|
||||||
void sortDeckModelToDeckView();
|
void actAddCard(const ExactCard &card, const QString &zoneName);
|
||||||
DeckLoader *getDeckLoader();
|
void actIncrementSelection();
|
||||||
DeckList *getDeckList();
|
|
||||||
void actIncrement();
|
|
||||||
bool swapCard(const QModelIndex &idx);
|
|
||||||
void actDecrementCard(const ExactCard &card, QString zoneName);
|
void actDecrementCard(const ExactCard &card, QString zoneName);
|
||||||
void actDecrementSelection();
|
void actDecrementSelection();
|
||||||
void actSwapCard();
|
void actSwapCard(const ExactCard &card, const QString &zoneName);
|
||||||
|
void actSwapSelection();
|
||||||
void actRemoveCard();
|
void actRemoveCard();
|
||||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
|
||||||
void initializeFormats();
|
void initializeFormats();
|
||||||
void expandAll();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged();
|
void selectedCardChanged(const ExactCard &card);
|
||||||
void commentsChanged();
|
|
||||||
void hashChanged();
|
|
||||||
void deckChanged();
|
|
||||||
void deckModified();
|
|
||||||
void requestDeckHistorySave(const QString &modificationReason);
|
|
||||||
void requestDeckHistoryClear();
|
|
||||||
void cardChanged(const ExactCard &_card);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AbstractTabDeckEditor *deckEditor;
|
AbstractTabDeckEditor *deckEditor;
|
||||||
|
DeckStateManager *deckStateManager;
|
||||||
|
|
||||||
DeckListHistoryManagerWidget *historyManagerWidget;
|
DeckListHistoryManagerWidget *historyManagerWidget;
|
||||||
KeySignals deckViewKeySignals;
|
KeySignals deckViewKeySignals;
|
||||||
QLabel *nameLabel;
|
QLabel *nameLabel;
|
||||||
|
|
@ -106,22 +89,26 @@ private:
|
||||||
|
|
||||||
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||||
|
|
||||||
void recursiveExpand(const QModelIndex &index);
|
DeckListModel *getModel() const;
|
||||||
[[nodiscard]] QModelIndexList getSelectedCardNodes() const;
|
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
|
||||||
|
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void decklistCustomMenu(QPoint point);
|
void decklistCustomMenu(QPoint point);
|
||||||
void updateCard(QModelIndex, const QModelIndex ¤t);
|
void updateCard(QModelIndex, const QModelIndex ¤t);
|
||||||
void updateName(const QString &name);
|
void writeName();
|
||||||
void updateComments();
|
void writeComments();
|
||||||
void setBannerCard(int);
|
void writeBannerCard(int);
|
||||||
void setTags(const QStringList &tags);
|
void applyActiveGroupCriteria();
|
||||||
void syncDeckListBannerCardWithComboBox();
|
void setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus);
|
||||||
void updateHash();
|
void updateHash();
|
||||||
void refreshShortcuts();
|
void refreshShortcuts();
|
||||||
void updateShowBannerCardComboBox(bool visible);
|
void updateShowBannerCardComboBox(bool visible);
|
||||||
void updateShowTagsWidget(bool visible);
|
void updateShowTagsWidget(bool visible);
|
||||||
void syncBannerCardComboBoxSelectionWithDeck();
|
void syncBannerCardComboBoxSelectionWithDeck();
|
||||||
|
void changeSelectedCard(int changeBy);
|
||||||
|
void recursiveExpand(const QModelIndex &parent);
|
||||||
|
void expandAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DECK_EDITOR_DECK_DOCK_WIDGET_H
|
#endif // DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock()
|
||||||
|
|
||||||
installEventFilter(deckEditor);
|
installEventFilter(deckEditor);
|
||||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||||
|
connect(printingSelector, &PrintingSelector::prevCardRequested, deckEditor->getDeckDockWidget(),
|
||||||
|
&DeckEditorDeckDockWidget::selectPrevCard);
|
||||||
|
connect(printingSelector, &PrintingSelector::nextCardRequested, deckEditor->getDeckDockWidget(),
|
||||||
|
&DeckEditorDeckDockWidget::selectNextCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckEditorPrintingSelectorDockWidget::retranslateUi()
|
void DeckEditorPrintingSelectorDockWidget::retranslateUi()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
#include "deck_list_history_manager_widget.h"
|
#include "deck_list_history_manager_widget.h"
|
||||||
|
|
||||||
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel,
|
#include "deck_state_manager.h"
|
||||||
|
|
||||||
|
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager,
|
||||||
DeckListStyleProxy *_styleProxy,
|
DeckListStyleProxy *_styleProxy,
|
||||||
DeckListHistoryManager *manager,
|
|
||||||
QWidget *parent)
|
QWidget *parent)
|
||||||
: QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager)
|
: QWidget(parent), deckStateManager(_deckStateManager), styleProxy(_styleProxy)
|
||||||
{
|
{
|
||||||
layout = new QHBoxLayout(this);
|
layout = new QHBoxLayout(this);
|
||||||
|
|
||||||
|
|
@ -43,8 +44,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckL
|
||||||
|
|
||||||
connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked);
|
connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked);
|
||||||
|
|
||||||
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this,
|
connect(deckStateManager, &DeckStateManager::historyChanged, this, &DeckListHistoryManagerWidget::refreshList);
|
||||||
&DeckListHistoryManagerWidget::refreshList);
|
|
||||||
|
|
||||||
refreshList();
|
refreshList();
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
|
|
@ -58,15 +58,12 @@ void DeckListHistoryManagerWidget::retranslateUi()
|
||||||
historyLabel->setText(tr("Click on an entry to revert to that point in the history."));
|
historyLabel->setText(tr("Click on an entry to revert to that point in the history."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckListHistoryManagerWidget::setDeckListModel(DeckListModel *_deckListModel)
|
|
||||||
{
|
|
||||||
deckListModel = _deckListModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeckListHistoryManagerWidget::refreshList()
|
void DeckListHistoryManagerWidget::refreshList()
|
||||||
{
|
{
|
||||||
historyList->clear();
|
historyList->clear();
|
||||||
|
|
||||||
|
DeckListHistoryManager *historyManager = deckStateManager->getHistoryManager();
|
||||||
|
|
||||||
// Fill redo section first (oldest redo at top, newest redo closest to divider)
|
// Fill redo section first (oldest redo at top, newest redo closest to divider)
|
||||||
const auto redoStack = historyManager->getRedoStack();
|
const auto redoStack = historyManager->getRedoStack();
|
||||||
for (int i = 0; i < redoStack.size(); ++i) { // iterate forward
|
for (int i = 0; i < redoStack.size(); ++i) { // iterate forward
|
||||||
|
|
@ -98,36 +95,7 @@ void DeckListHistoryManagerWidget::refreshList()
|
||||||
redoButton->setEnabled(historyManager->canRedo());
|
redoButton->setEnabled(historyManager->canRedo());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckListHistoryManagerWidget::doUndo()
|
void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item)
|
||||||
{
|
|
||||||
if (!historyManager->canUndo()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
historyManager->undo(deckListModel->getDeckList());
|
|
||||||
deckListModel->rebuildTree();
|
|
||||||
emit deckListModel->layoutChanged();
|
|
||||||
emit requestDisplayWidgetSync();
|
|
||||||
|
|
||||||
refreshList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeckListHistoryManagerWidget::doRedo()
|
|
||||||
{
|
|
||||||
if (!historyManager->canRedo()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
historyManager->redo(deckListModel->getDeckList());
|
|
||||||
deckListModel->rebuildTree();
|
|
||||||
|
|
||||||
emit deckListModel->layoutChanged();
|
|
||||||
emit requestDisplayWidgetSync();
|
|
||||||
|
|
||||||
refreshList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item)
|
|
||||||
{
|
{
|
||||||
// Ignore non-selectable items (like divider)
|
// Ignore non-selectable items (like divider)
|
||||||
if (!(item->flags() & Qt::ItemIsSelectable)) {
|
if (!(item->flags() & Qt::ItemIsSelectable)) {
|
||||||
|
|
@ -138,23 +106,24 @@ void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item)
|
||||||
int index = item->data(Qt::UserRole + 1).toInt();
|
int index = item->data(Qt::UserRole + 1).toInt();
|
||||||
|
|
||||||
if (mode == "redo") {
|
if (mode == "redo") {
|
||||||
const auto redoStack = historyManager->getRedoStack();
|
const auto redoStack = deckStateManager->getHistoryManager()->getRedoStack();
|
||||||
int steps = redoStack.size() - index;
|
int steps = redoStack.size() - index;
|
||||||
for (int i = 0; i < steps; ++i) {
|
deckStateManager->redo(steps);
|
||||||
historyManager->redo(deckListModel->getDeckList());
|
|
||||||
}
|
|
||||||
} else if (mode == "undo") {
|
} else if (mode == "undo") {
|
||||||
const auto undoStack = historyManager->getUndoStack();
|
const auto undoStack = deckStateManager->getHistoryManager()->getUndoStack();
|
||||||
int steps = undoStack.size() - 1 - index;
|
int steps = undoStack.size() - index;
|
||||||
for (int i = 0; i < steps + 1; ++i) {
|
deckStateManager->undo(steps);
|
||||||
historyManager->undo(deckListModel->getDeckList());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deckListModel->rebuildTree();
|
|
||||||
|
|
||||||
emit deckListModel->layoutChanged();
|
|
||||||
emit requestDisplayWidgetSync();
|
|
||||||
|
|
||||||
refreshList();
|
refreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckListHistoryManagerWidget::doUndo()
|
||||||
|
{
|
||||||
|
deckStateManager->undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckListHistoryManagerWidget::doRedo()
|
||||||
|
{
|
||||||
|
deckStateManager->redo();
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
|
|
||||||
|
class DeckStateManager;
|
||||||
|
|
||||||
class DeckListHistoryManagerWidget : public QWidget
|
class DeckListHistoryManagerWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
@ -25,22 +27,19 @@ public slots:
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel,
|
explicit DeckListHistoryManagerWidget(DeckStateManager *deckStateManager,
|
||||||
DeckListStyleProxy *styleProxy,
|
DeckListStyleProxy *styleProxy,
|
||||||
DeckListHistoryManager *manager,
|
|
||||||
QWidget *parent = nullptr);
|
QWidget *parent = nullptr);
|
||||||
void setDeckListModel(DeckListModel *_deckListModel);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void refreshList();
|
void refreshList();
|
||||||
void onListClicked(QListWidgetItem *item);
|
void onListClicked(const QListWidgetItem *item);
|
||||||
void doUndo();
|
void doUndo();
|
||||||
void doRedo();
|
void doRedo();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeckListModel *deckListModel;
|
DeckStateManager *deckStateManager;
|
||||||
DeckListStyleProxy *styleProxy;
|
DeckListStyleProxy *styleProxy;
|
||||||
DeckListHistoryManager *historyManager;
|
|
||||||
|
|
||||||
QHBoxLayout *layout;
|
QHBoxLayout *layout;
|
||||||
QAction *aUndo;
|
QAction *aUndo;
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue