mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-08 17:13:57 -07:00
Merge branch 'master' into vde-cleanup
This commit is contained in:
commit
863a6384c1
216 changed files with 10758 additions and 4497 deletions
|
|
@ -156,6 +156,18 @@ function ccachestatsverbose() {
|
|||
|
||||
# Compile
|
||||
if [[ $RUNNER_OS == macOS ]]; then
|
||||
# QTDIR is needed for macOS since we actually only use the cached thin Qt binaries instead of the install-qt-action,
|
||||
# which sets a few environment variables
|
||||
if QTDIR=$(find "$GITHUB_WORKSPACE/Qt" -depth -maxdepth 2 -name macos -type d -print -quit); then
|
||||
echo "found QTDIR at $QTDIR"
|
||||
else
|
||||
echo "could not find QTDIR!"
|
||||
exit 2
|
||||
fi
|
||||
# the qtdir is located at Qt/[qtversion]/macos
|
||||
# we use find to get the first subfolder with the name "macos"
|
||||
# this works independent of the qt version as there should be only one version installed on the runner at a time
|
||||
export QTDIR
|
||||
|
||||
if [[ $TARGET_MACOS_VERSION ]]; then
|
||||
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
|
||||
|
|
|
|||
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
|
||||
version: 22.04
|
||||
package: DEB
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Ubuntu
|
||||
version: 24.04
|
||||
|
|
@ -213,7 +212,7 @@ jobs:
|
|||
- name: Upload artifact
|
||||
id: upload_artifact
|
||||
if: matrix.package != 'skip'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{matrix.distro}}${{matrix.version}}-package
|
||||
path: ${{steps.build.outputs.path}}
|
||||
|
|
@ -263,7 +262,6 @@ jobs:
|
|||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false # qt caches take too much space for macOS (1.1Gi)
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
|
|
@ -279,7 +277,6 @@ jobs:
|
|||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
|
|
@ -295,7 +292,6 @@ jobs:
|
|||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
|
|
@ -308,7 +304,6 @@ jobs:
|
|||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
|
|
@ -321,7 +316,6 @@ jobs:
|
|||
artifact_name: Windows7-installer
|
||||
qt_version: 5.15.*
|
||||
qt_arch: win64_msvc2019_64
|
||||
cache_qt: true
|
||||
cmake_generator: "Visual Studio 17 2022"
|
||||
cmake_generator_platform: x64
|
||||
|
||||
|
|
@ -335,13 +329,17 @@ jobs:
|
|||
qt_version: 6.6.*
|
||||
qt_arch: win64_msvc2019_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: true
|
||||
cmake_generator: "Visual Studio 17 2022"
|
||||
cmake_generator_platform: x64
|
||||
|
||||
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
|
||||
needs: configure
|
||||
runs-on: ${{matrix.runner}}
|
||||
env:
|
||||
CCACHE_DIR: ${{github.workspace}}/.cache/
|
||||
# Cache size over the entire repo is 10Gi:
|
||||
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
||||
CCACHE_SIZE: 500M
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
@ -356,24 +354,72 @@ jobs:
|
|||
with:
|
||||
msbuild-architecture: x64
|
||||
|
||||
# Using jianmingyong/ccache-action to setup ccache without using brew
|
||||
# It tries to download a binary of ccache from GitHub Release and falls back to building from source if it fails
|
||||
- name: Setup ccache
|
||||
if: matrix.use_ccache == 1
|
||||
uses: jianmingyong/ccache-action@v1
|
||||
with:
|
||||
install-type: "binary"
|
||||
ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}
|
||||
max-size: 500M
|
||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: matrix.use_ccache == 1 && matrix.os == 'macOS'
|
||||
run: brew install ccache
|
||||
|
||||
- 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
|
||||
with:
|
||||
version: ${{matrix.qt_version}}
|
||||
arch: ${{matrix.qt_arch}}
|
||||
modules: ${{matrix.qt_modules}}
|
||||
cache: ${{matrix.cache_qt}}
|
||||
cache: true
|
||||
|
||||
- name: Setup vcpkg cache
|
||||
id: vcpkg-cache
|
||||
|
|
@ -403,6 +449,15 @@ jobs:
|
|||
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
|
||||
run: .ci/compile.sh --server --test --vcpkg
|
||||
|
||||
- name: Save compiler cache (ccache)
|
||||
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1
|
||||
uses: actions/cache/save@v5
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
with:
|
||||
path: ${{env.CCACHE_DIR}}
|
||||
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
|
||||
|
||||
- name: Sign app bundle
|
||||
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
|
||||
env:
|
||||
|
|
@ -450,7 +505,7 @@ jobs:
|
|||
- name: Upload artifact
|
||||
id: upload_artifact
|
||||
if: matrix.make_package
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{matrix.artifact_name}}
|
||||
path: ${{steps.build.outputs.path}}
|
||||
|
|
@ -458,7 +513,7 @@ jobs:
|
|||
|
||||
- name: Upload pdb database
|
||||
if: matrix.os == 'Windows'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Windows${{matrix.target}}-debug-pdbs
|
||||
path: |
|
||||
|
|
|
|||
54
README.md
54
README.md
|
|
@ -44,10 +44,10 @@ Latest <kbd>beta</kbd> version:
|
|||
|
||||
# Related Repositories
|
||||
|
||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice
|
||||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice
|
||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for use in Cockatrice
|
||||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
|
||||
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
|
||||
- [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)
|
||||
|
|
@ -55,15 +55,31 @@ Latest <kbd>beta</kbd> version:
|
|||
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
|
||||
- [Official Website](https://cockatrice.github.io)
|
||||
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
|
||||
- [Official Code Documentation](https://cockatrice.github.io/docs)
|
||||
- [Official Discord](https://discord.gg/3Z9yzmA)
|
||||
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
|
||||
|
||||
>[!IMPORTANT]
|
||||
>For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
|
||||
> [!IMPORTANT]
|
||||
> For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
|
||||
|
||||
|
||||
# Contribute
|
||||
<p>
|
||||
<a href="#code">Code</a> <b>|</b>
|
||||
<a href="#documentation-">Documentation</a> <b>|</b>
|
||||
<a href="#translation-">Translation</a>
|
||||
</p>
|
||||
|
||||
#### Repository Activity
|
||||

|
||||
|
||||
<details>
|
||||
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
|
||||
<br>
|
||||
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
|
||||
</a><br>
|
||||
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
|
||||
</details>
|
||||
|
||||
### Code
|
||||
|
||||
|
|
@ -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).
|
||||
|
||||
### 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.
|
||||
|
||||
#### 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>
|
||||
|
||||
### Translations [](https://explore.transifex.com/cockatrice/cockatrice/)
|
||||
### Translation [](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>
|
||||
|
||||
|
|
@ -131,8 +143,8 @@ You can then
|
|||
make package
|
||||
```
|
||||
|
||||
>[!NOTE]
|
||||
>Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
|
||||
> [!NOTE]
|
||||
> Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
|
||||
|
||||
<br>
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ if(Qt6_FOUND)
|
|||
endif()
|
||||
else()
|
||||
find_package(
|
||||
Qt5 5.8.0
|
||||
Qt5 5.15.2
|
||||
COMPONENTS ${REQUIRED_QT_COMPONENTS}
|
||||
QUIET HINTS ${Qt5_DIR}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -144,28 +144,54 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/cards/card_size_widget.cpp
|
||||
src/interface/widgets/cards/deck_card_zone_display_widget.cpp
|
||||
src/interface/widgets/cards/deck_preview_card_picture_widget.cpp
|
||||
src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
|
||||
src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp
|
||||
src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp
|
||||
src/interface/widgets/deck_analytics/deck_analytics_widget.cpp
|
||||
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
|
||||
src/interface/widgets/deck_analytics/mana_base_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_devotion_widget.cpp
|
||||
src/interface/widgets/deck_analytics/resizable_panel.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
|
||||
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
||||
src/interface/widgets/deck_editor/deck_state_manager.cpp
|
||||
src/interface/widgets/general/background_sources.cpp
|
||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||
src/interface/widgets/general/display/banner_widget.cpp
|
||||
src/interface/widgets/general/display/bar_widget.cpp
|
||||
src/interface/widgets/general/display/color_bar.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_label.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||
src/interface/widgets/general/display/labeled_input.cpp
|
||||
src/interface/widgets/general/display/percent_bar_widget.cpp
|
||||
src/interface/widgets/general/display/shadow_background_label.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/color_bar.cpp
|
||||
src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp
|
||||
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
|
||||
src/interface/widgets/general/display/charts/pies/color_pie.cpp
|
||||
src/interface/widgets/general/home_styled_button.cpp
|
||||
src/interface/widgets/general/home_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
||||
|
|
@ -287,6 +313,10 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
|
||||
src/interface/key_signals.cpp
|
||||
src/interface/logger.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
|
||||
)
|
||||
|
||||
add_subdirectory(sounds)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,13 @@
|
|||
@page deck_search_syntax_help Deck Search Syntax Help
|
||||
|
||||
## Deck Search Syntax Help
|
||||
-----
|
||||
|
||||
The search bar recognizes a set of special commands.<br>
|
||||
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all
|
||||
searches are case insensitive.
|
||||
|
||||
<dl>
|
||||
|
||||
<dt>Display Name (The deck name, or the filename if the deck name isn't set):</dt>
|
||||
<dd>[red deck wins](#red deck wins) <small>(Any deck with a display name containing the words red, deck, and wins)</small></dd>
|
||||
<dd>["red deck wins"](#%22red deck wins%22) <small>(Any deck with a display name containing the exact phrase "red deck wins")</small></dd>
|
||||
|
|
@ -27,6 +29,11 @@ searches are case insensitive.
|
|||
<dt><u>F</u>ormat:</dt>
|
||||
<dd>[f:standard](#f:standard) <small>(Any deck with format set to standard)</small></dd>
|
||||
|
||||
<dt><u>C</u>omments:</dt>
|
||||
<dd>[c:good](#c:good) <small>(Any deck with comments containing the word good)</small></dd>
|
||||
<dd>[c:good c:deck](#c:good c:deck) <small>(Any deck with comments containing the words good and deck)</small></dd>
|
||||
<dd>[c:"good deck"](#c:%22good deck%22) <small>(Any deck with comments containing the exact phrase "good deck")</small></dd>
|
||||
|
||||
<dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt>
|
||||
<dd><a href="#[[plains]]">[[plains]]</a> <small>(Any deck that contains at least one card with "plains" in its name)</small></dd>
|
||||
<dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
@page search_syntax_help Search Syntax Help
|
||||
|
||||
## Search Syntax Help
|
||||
-----
|
||||
|
||||
The search bar recognizes a set of special commands similar to some other card databases.<br>
|
||||
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
|
||||
|
||||
<dl>
|
||||
|
||||
<dt>Name:</dt>
|
||||
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
|
||||
<dd>["birds of paradise"](#%22birds of paradise%22) <small>(Any card name containing the exact phrase "birds of paradise")</small></dd>
|
||||
|
|
|
|||
|
|
@ -43,23 +43,23 @@ void DeckStatsInterface::queryFinished(QNetworkReply *reply)
|
|||
deleteLater();
|
||||
}
|
||||
|
||||
void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
|
||||
void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
|
||||
{
|
||||
DeckList deckWithoutTokens;
|
||||
copyDeckWithoutTokens(*deck, deckWithoutTokens);
|
||||
copyDeckWithoutTokens(deck, deckWithoutTokens);
|
||||
|
||||
QUrl params;
|
||||
QUrlQuery urlQuery;
|
||||
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
|
||||
urlQuery.addQueryItem("decktitle", deck->getName());
|
||||
urlQuery.addQueryItem("decktitle", deck.getName());
|
||||
params.setQuery(urlQuery);
|
||||
data->append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
data.append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
}
|
||||
|
||||
void DeckStatsInterface::analyzeDeck(DeckList *deck)
|
||||
void DeckStatsInterface::analyzeDeck(const DeckList &deck)
|
||||
{
|
||||
QByteArray data;
|
||||
getAnalyzeRequestData(deck, &data);
|
||||
getAnalyzeRequestData(deck, data);
|
||||
|
||||
QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
|
@ -68,7 +68,7 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck)
|
|||
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) {
|
||||
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
|
||||
* tokens.
|
||||
*/
|
||||
void copyDeckWithoutTokens(DeckList &source, DeckList &destination);
|
||||
void copyDeckWithoutTokens(const DeckList &source, DeckList &destination);
|
||||
|
||||
private slots:
|
||||
void queryFinished(QNetworkReply *reply);
|
||||
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
|
||||
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
|
||||
|
||||
public:
|
||||
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
||||
void analyzeDeck(DeckList *deck);
|
||||
void analyzeDeck(const DeckList &deck);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -67,24 +67,24 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
|||
deleteLater();
|
||||
}
|
||||
|
||||
void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
|
||||
void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
|
||||
{
|
||||
DeckList mainboard, sideboard;
|
||||
copyDeckSplitMainAndSide(*deck, mainboard, sideboard);
|
||||
copyDeckSplitMainAndSide(deck, mainboard, sideboard);
|
||||
|
||||
QUrl params;
|
||||
QUrlQuery urlQuery;
|
||||
urlQuery.addQueryItem("name", deck->getName());
|
||||
urlQuery.addQueryItem("name", deck.getName());
|
||||
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
|
||||
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
|
||||
params.setQuery(urlQuery);
|
||||
data->append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
data.append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
}
|
||||
|
||||
void TappedOutInterface::analyzeDeck(DeckList *deck)
|
||||
void TappedOutInterface::analyzeDeck(const DeckList &deck)
|
||||
{
|
||||
QByteArray data;
|
||||
getAnalyzeRequestData(deck, &data);
|
||||
getAnalyzeRequestData(deck, data);
|
||||
|
||||
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
|
@ -93,7 +93,7 @@ void TappedOutInterface::analyzeDeck(DeckList *deck)
|
|||
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) {
|
||||
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ private:
|
|||
QNetworkAccessManager *manager;
|
||||
|
||||
CardDatabase &cardDatabase;
|
||||
void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard);
|
||||
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
|
||||
private slots:
|
||||
void queryFinished(QNetworkReply *reply);
|
||||
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
|
||||
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
|
||||
|
||||
public:
|
||||
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
||||
void analyzeDeck(DeckList *deck);
|
||||
void analyzeDeck(const DeckList &deck);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "../network/update/client/release_channel.h"
|
||||
#include "card_counter_settings.h"
|
||||
#include "version_string.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QApplication>
|
||||
|
|
@ -198,7 +199,13 @@ SettingsCache::SettingsCache()
|
|||
|
||||
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 =
|
||||
settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
|
||||
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
|
||||
|
|
@ -206,7 +213,15 @@ SettingsCache::SettingsCache()
|
|||
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
|
||||
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
|
||||
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
|
||||
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();
|
||||
keepalive = settings->value("personal/keepalive", 3).toInt();
|
||||
|
|
@ -273,6 +288,7 @@ SettingsCache::SettingsCache()
|
|||
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
|
||||
|
||||
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
|
||||
showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
|
||||
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
|
||||
roundCardCorners = settings->value("cards/roundcardcorners", true).toBool();
|
||||
overrideAllCardArtWithPersonalPreference =
|
||||
|
|
@ -700,6 +716,13 @@ void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts)
|
|||
settings->setValue("menu/showshortcuts", showShortcuts);
|
||||
}
|
||||
|
||||
void SettingsCache::setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar)
|
||||
{
|
||||
showGameSelectorFilterToolbar = static_cast<bool>(_showGameSelectorFilterToolbar);
|
||||
settings->setValue("menu/showgameselectorfiltertoolbar", showGameSelectorFilterToolbar);
|
||||
emit showGameSelectorFilterToolbarChanged(showGameSelectorFilterToolbar);
|
||||
}
|
||||
|
||||
void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames)
|
||||
{
|
||||
displayCardNames = static_cast<bool>(_displayCardNames);
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ signals:
|
|||
void homeTabBackgroundShuffleFrequencyChanged();
|
||||
void picDownloadChanged();
|
||||
void showStatusBarChanged(bool state);
|
||||
void showGameSelectorFilterToolbarChanged(bool state);
|
||||
void displayCardNamesChanged();
|
||||
void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference);
|
||||
void bumpSetsWithCardsInDeckToTopChanged();
|
||||
|
|
@ -236,6 +237,7 @@ private:
|
|||
bool annotateTokens;
|
||||
QByteArray tabGameSplitterSizes;
|
||||
bool showShortcuts;
|
||||
bool showGameSelectorFilterToolbar;
|
||||
bool displayCardNames;
|
||||
bool overrideAllCardArtWithPersonalPreference;
|
||||
bool bumpSetsWithCardsInDeckToTop;
|
||||
|
|
@ -553,6 +555,10 @@ public:
|
|||
{
|
||||
return showShortcuts;
|
||||
}
|
||||
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const
|
||||
{
|
||||
return showGameSelectorFilterToolbar;
|
||||
}
|
||||
[[nodiscard]] bool getDisplayCardNames() const
|
||||
{
|
||||
return displayCardNames;
|
||||
|
|
@ -1017,6 +1023,7 @@ public slots:
|
|||
void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens);
|
||||
void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes);
|
||||
void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts);
|
||||
void setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar);
|
||||
void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames);
|
||||
void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt);
|
||||
void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop);
|
||||
|
|
|
|||
|
|
@ -150,12 +150,7 @@ void ShortcutTreeView::currentChanged(const QModelIndex ¤t, const QModelIn
|
|||
*/
|
||||
void ShortcutTreeView::updateSearchString(const QString &searchString)
|
||||
{
|
||||
#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
|
||||
const auto skipEmptyParts = Qt::SkipEmptyParts;
|
||||
#else
|
||||
const auto skipEmptyParts = QString::SkipEmptyParts;
|
||||
#endif
|
||||
QStringList searchWords = searchString.split(" ", skipEmptyParts);
|
||||
QStringList searchWords = searchString.split(" ", Qt::SkipEmptyParts);
|
||||
|
||||
auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); };
|
||||
std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex);
|
||||
|
|
|
|||
|
|
@ -660,6 +660,12 @@ private:
|
|||
{"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"),
|
||||
parseSequenceString("Ctrl+M"),
|
||||
ShortcutGroup::Drawing)},
|
||||
{"Player/aMulliganSame", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Same hand size)"),
|
||||
parseSequenceString("Ctrl+Shift+M"),
|
||||
ShortcutGroup::Drawing)},
|
||||
{"Player/aMulliganMinusOne", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Hand size - 1)"),
|
||||
parseSequenceString("Ctrl+Shift+Alt+M"),
|
||||
ShortcutGroup::Drawing)},
|
||||
{"Player/aDrawCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw a Card"),
|
||||
parseSequenceString("Ctrl+D"),
|
||||
ShortcutGroup::Drawing)},
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
|
|||
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
|
||||
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
|
||||
|
||||
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery
|
||||
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / CommentQuery / GenericQuery
|
||||
|
||||
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
|
||||
|
||||
|
|
@ -25,6 +25,7 @@ DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String
|
|||
FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String
|
||||
PathQuery <- [Pp] 'ath'? [:] String
|
||||
FormatQuery <- [Ff] 'ormat'? [:] String
|
||||
CommentQuery <- [Cc] ('omment' 's'?)? [:] 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 {
|
||||
auto name = std::any_cast<QString>(sv[0]);
|
||||
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
|
||||
} else {
|
||||
chooseTokenFromDeckRadioButton->setChecked(true);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
|
||||
#else
|
||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
|
||||
#endif
|
||||
}
|
||||
|
||||
auto *tokenChooseLayout = new QVBoxLayout;
|
||||
|
|
@ -223,11 +219,7 @@ void DlgCreateToken::actChooseTokenFromAll(bool checked)
|
|||
void DlgCreateToken::actChooseTokenFromDeck(bool checked)
|
||||
{
|
||||
if (checked) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
|
||||
#else
|
||||
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,16 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T
|
|||
connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan);
|
||||
addAction(aMulligan);
|
||||
|
||||
// Mulligan same size
|
||||
aMulliganSame = new QAction(this);
|
||||
connect(aMulliganSame, &QAction::triggered, actions, &PlayerActions::actMulliganSameSize);
|
||||
addAction(aMulliganSame);
|
||||
|
||||
// Mulligan -1
|
||||
aMulliganMinusOne = new QAction(this);
|
||||
connect(aMulliganMinusOne, &QAction::triggered, actions, &PlayerActions::actMulliganMinusOne);
|
||||
addAction(aMulliganMinusOne);
|
||||
|
||||
addSeparator();
|
||||
|
||||
mMoveHandMenu = addTearOffMenu(QString());
|
||||
|
|
@ -104,7 +114,9 @@ void HandMenu::retranslateUi()
|
|||
aSortHandByType->setText(tr("Type"));
|
||||
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..."));
|
||||
aMoveHandToTopLibrary->setText(tr("&Top of library"));
|
||||
|
|
@ -128,6 +140,8 @@ void HandMenu::setShortcutsActive()
|
|||
aSortHandByType->setShortcuts(shortcuts.getShortcut("Player/aSortHandByType"));
|
||||
aSortHandByManaValue->setShortcuts(shortcuts.getShortcut("Player/aSortHandByManaValue"));
|
||||
aMulligan->setShortcuts(shortcuts.getShortcut("Player/aMulligan"));
|
||||
aMulliganSame->setShortcuts(shortcuts.getShortcut("Player/aMulliganSame"));
|
||||
aMulliganMinusOne->setShortcuts(shortcuts.getShortcut("Player/aMulliganMinusOne"));
|
||||
aRevealHandToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealHandToAll"));
|
||||
aRevealRandomHandCardToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealRandomHandCardToAll"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ private:
|
|||
|
||||
QAction *aViewHand = nullptr;
|
||||
QAction *aMulligan = nullptr;
|
||||
QAction *aMulliganSame = nullptr;
|
||||
QAction *aMulliganMinusOne = nullptr;
|
||||
|
||||
QMenu *mSortHand = nullptr;
|
||||
QAction *aSortHandByName = nullptr;
|
||||
|
|
|
|||
|
|
@ -310,28 +310,48 @@ void PlayerActions::actMulligan()
|
|||
{
|
||||
int startSize = SettingsCache::instance().getStartingHandSize();
|
||||
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;
|
||||
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"),
|
||||
tr("Number of cards: (max. %1)").arg(deckSize) + '\n' +
|
||||
tr("0 and lower are in comparison to current hand size"),
|
||||
startSize, -handSize, deckSize, 1, &ok);
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
Command_Mulligan cmd;
|
||||
|
||||
if (number < 1) {
|
||||
if (handSize == 0) {
|
||||
return;
|
||||
}
|
||||
cmd.set_number(handSize + number);
|
||||
} else {
|
||||
cmd.set_number(number);
|
||||
number = handSize + number;
|
||||
}
|
||||
|
||||
doMulligan(number);
|
||||
SettingsCache::instance().setStartingHandSize(number);
|
||||
}
|
||||
|
||||
void PlayerActions::actMulliganSameSize()
|
||||
{
|
||||
int handSize = player->getHandZone()->getCards().size();
|
||||
doMulligan(handSize);
|
||||
}
|
||||
|
||||
void PlayerActions::actMulliganMinusOne()
|
||||
{
|
||||
int handSize = player->getHandZone()->getCards().size();
|
||||
int targetSize = qMax(1, handSize - 1);
|
||||
doMulligan(targetSize);
|
||||
}
|
||||
|
||||
void PlayerActions::doMulligan(int number)
|
||||
{
|
||||
if (number < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Command_Mulligan cmd;
|
||||
cmd.set_number(number);
|
||||
sendGameCommand(cmd);
|
||||
if (startSize != number) {
|
||||
SettingsCache::instance().setStartingHandSize(number);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerActions::actDrawCards()
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ public slots:
|
|||
void actDrawCards();
|
||||
void actUndoDraw();
|
||||
void actMulligan();
|
||||
void actMulliganSameSize();
|
||||
void actMulliganMinusOne();
|
||||
void doMulligan(int number);
|
||||
|
||||
void actPlay();
|
||||
void actPlayFacedown();
|
||||
|
|
|
|||
|
|
@ -78,14 +78,7 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
|
|||
void PlayerEventHandler::eventRollDie(const Event_RollDie &event)
|
||||
{
|
||||
if (!event.values().empty()) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
QList<uint> rolls(event.values().begin(), event.values().end());
|
||||
#else
|
||||
QList<uint> rolls;
|
||||
for (const auto &value : event.values()) {
|
||||
rolls.append(value);
|
||||
}
|
||||
#endif
|
||||
std::sort(rolls.begin(), rolls.end());
|
||||
emit logRollDie(player, static_cast<int>(event.sides()), rolls);
|
||||
} else if (event.value()) {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,20 @@
|
|||
#include <QGraphicsLinearLayout>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsView>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr qreal kTitleBarHeight = 24.0;
|
||||
constexpr qreal kMinVisibleWidth = 100.0;
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @param _player the player the cards were revealed to.
|
||||
* @param _origZone the zone the cards were revealed from.
|
||||
|
|
@ -241,33 +249,191 @@ void ZoneViewWidget::retranslateUi()
|
|||
pileViewCheckBox.setText(tr("pile view"));
|
||||
}
|
||||
|
||||
void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */)
|
||||
void ZoneViewWidget::stopWindowDrag()
|
||||
{
|
||||
if (!scene())
|
||||
if (!draggingWindow)
|
||||
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) {
|
||||
scenePos.setX(0);
|
||||
} else {
|
||||
qreal maxw = scene()->sceneRect().width() - 100;
|
||||
if (scenePos.x() > maxw)
|
||||
scenePos.setX(maxw);
|
||||
// need to grab mouse to receive events and not miss initial movement
|
||||
grabMouse();
|
||||
}
|
||||
|
||||
QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const
|
||||
{
|
||||
const QRectF frameRectF = windowFrameRect();
|
||||
|
||||
// query the style for the close button position (handles macOS top-left placement)
|
||||
// Title bar rect MUST be local (0,0-based) for QStyle
|
||||
const QRect titleBarRect(0, 0, static_cast<int>(frameRectF.width()), static_cast<int>(kTitleBarHeight));
|
||||
|
||||
if (styleWidget) {
|
||||
QStyleOptionTitleBar opt;
|
||||
opt.initFrom(styleWidget);
|
||||
opt.rect = titleBarRect;
|
||||
opt.text = windowTitle();
|
||||
opt.icon = styleWidget->windowIcon();
|
||||
opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
|
||||
|
||||
opt.subControls = QStyle::SC_TitleBarCloseButton;
|
||||
opt.activeSubControls = QStyle::SC_TitleBarCloseButton;
|
||||
opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState;
|
||||
|
||||
if (styleWidget->isActiveWindow()) {
|
||||
opt.state |= QStyle::State_Active;
|
||||
}
|
||||
|
||||
QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton,
|
||||
styleWidget);
|
||||
|
||||
if (r.isValid() && !r.isEmpty()) {
|
||||
// Translate from local-titlebar coords → frame coords
|
||||
r.translate(frameRectF.topLeft().toPoint());
|
||||
return QRectF(r);
|
||||
}
|
||||
}
|
||||
|
||||
if (scenePos.y() < titleBarHeight) {
|
||||
scenePos.setY(titleBarHeight);
|
||||
} else {
|
||||
qreal maxh = scene()->sceneRect().height() - titleBarHeight;
|
||||
if (scenePos.y() > maxh)
|
||||
scenePos.setY(maxh);
|
||||
// Fallback: frame-relative top-right
|
||||
return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight);
|
||||
}
|
||||
|
||||
QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const
|
||||
{
|
||||
QWidget *current = eventWidget;
|
||||
while (current) {
|
||||
if (auto *view = qobject_cast<QGraphicsView *>(current))
|
||||
return view;
|
||||
current = current->parentWidget();
|
||||
}
|
||||
|
||||
if (scenePos != pos())
|
||||
setPos(scenePos);
|
||||
if (scene() && !scene()->views().isEmpty())
|
||||
return scene()->views().constFirst();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QPointF ZoneViewWidget::calcDraggedWindowPos(const QPoint &screenPos,
|
||||
const QPointF &scenePos,
|
||||
const QPointF &buttonDownScenePos) const
|
||||
{
|
||||
if (dragView && dragView->viewport()) {
|
||||
const QPoint vpStart = dragView->viewport()->mapFromGlobal(dragStartScreenPos);
|
||||
const QPoint vpNow = dragView->viewport()->mapFromGlobal(screenPos);
|
||||
const QPointF sceneStart = dragView->mapToScene(vpStart);
|
||||
const QPointF sceneNow = dragView->mapToScene(vpNow);
|
||||
return dragStartItemPos + (sceneNow - sceneStart);
|
||||
}
|
||||
|
||||
return dragStartItemPos + (scenePos - buttonDownScenePos);
|
||||
}
|
||||
|
||||
bool ZoneViewWidget::windowFrameEvent(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::UngrabMouse) {
|
||||
stopWindowDrag();
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
}
|
||||
|
||||
auto *me = dynamic_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
if (!me)
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::GraphicsSceneMousePress:
|
||||
if (me->button() == Qt::LeftButton && windowFrameSectionAt(me->pos()) == Qt::TitleBarArea) {
|
||||
// avoid drag on close button
|
||||
if (closeButtonRect(me->widget()).contains(me->pos())) {
|
||||
me->accept();
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
startWindowDrag(me);
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::GraphicsSceneMouseMove:
|
||||
if (draggingWindow) {
|
||||
if (!(me->buttons() & Qt::LeftButton)) {
|
||||
stopWindowDrag();
|
||||
} else {
|
||||
setPos(
|
||||
calcDraggedWindowPos(me->screenPos(), me->scenePos(), me->buttonDownScenePos(Qt::LeftButton)));
|
||||
}
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::GraphicsSceneMouseRelease:
|
||||
if (draggingWindow && me->button() == Qt::LeftButton) {
|
||||
stopWindowDrag();
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
// move if the scene routes moves while dragging
|
||||
if (draggingWindow && (event->buttons() & Qt::LeftButton)) {
|
||||
setPos(calcDraggedWindowPos(event->screenPos(), event->scenePos(), event->buttonDownScenePos(Qt::LeftButton)));
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (draggingWindow && event->button() == Qt::LeftButton) {
|
||||
stopWindowDrag();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
QVariant ZoneViewWidget::itemChange(GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if (change == QGraphicsItem::ItemPositionChange && scene()) {
|
||||
// Keep grab area in main view
|
||||
const QRectF sceneRect = scene()->sceneRect();
|
||||
const QPointF requestedPos = value.toPointF();
|
||||
QPointF desiredPos = requestedPos;
|
||||
|
||||
const qreal minX = sceneRect.left();
|
||||
const qreal maxX = qMax(minX, sceneRect.right() - kMinVisibleWidth);
|
||||
const qreal minY = sceneRect.top() + kTitleBarHeight;
|
||||
const qreal maxY = qMax(minY, sceneRect.bottom() - kTitleBarHeight);
|
||||
|
||||
desiredPos.setX(qBound(minX, desiredPos.x(), maxX));
|
||||
desiredPos.setY(qBound(minY, desiredPos.y(), maxY));
|
||||
return desiredPos;
|
||||
}
|
||||
|
||||
return QGraphicsWidget::itemChange(change, value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
|
||||
|
|
@ -350,6 +516,7 @@ void ZoneViewWidget::handleScrollBarChange(int value)
|
|||
|
||||
void ZoneViewWidget::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
stopWindowDrag();
|
||||
disconnect(zone, &ZoneViewZone::closed, this, 0);
|
||||
// manually call zone->close in order to remove it from the origZones views
|
||||
zone->close();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef ZONEVIEWWIDGET_H
|
||||
#define ZONEVIEWWIDGET_H
|
||||
|
||||
|
|
@ -14,6 +13,7 @@
|
|||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QPointer>
|
||||
#include <libcockatrice/utility/macros.h>
|
||||
|
||||
class QLabel;
|
||||
|
|
@ -28,6 +28,8 @@ class ServerInfo_Card;
|
|||
class QGraphicsSceneMouseEvent;
|
||||
class QGraphicsSceneWheelEvent;
|
||||
class QStyleOption;
|
||||
class QGraphicsView;
|
||||
class QWidget;
|
||||
|
||||
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
|
||||
{
|
||||
|
|
@ -52,7 +54,6 @@ private:
|
|||
ZoneViewZone *zone;
|
||||
QGraphicsWidget *zoneContainer;
|
||||
|
||||
QPushButton *closeButton;
|
||||
QScrollBar *scrollBar;
|
||||
ScrollableGraphicsProxyWidget *scrollBarProxy;
|
||||
|
||||
|
|
@ -66,6 +67,33 @@ private:
|
|||
int extraHeight;
|
||||
Player *player;
|
||||
|
||||
bool draggingWindow = false;
|
||||
QPoint dragStartScreenPos;
|
||||
QPointF dragStartItemPos;
|
||||
QPointer<QGraphicsView> dragView;
|
||||
|
||||
void stopWindowDrag();
|
||||
void startWindowDrag(QGraphicsSceneMouseEvent *event);
|
||||
QRectF closeButtonRect(QWidget *styleWidget) const;
|
||||
/**
|
||||
* @brief Resolves the QGraphicsView to use for drag coordinate mapping
|
||||
*
|
||||
* @param eventWidget QWidget that originated the mouse event
|
||||
* @return The resolved QGraphicsView
|
||||
*/
|
||||
QGraphicsView *findDragView(QWidget *eventWidget) const;
|
||||
/**
|
||||
* @brief Calculates the desired widget position while dragging
|
||||
*
|
||||
* @param screenPos Global screen coordinates of the current mouse position
|
||||
* @param scenePos Scene coordinates of the current mouse position
|
||||
* @param buttonDownScenePos Scene coordinates of the initial mouse press position
|
||||
*
|
||||
* @return The new widget position in scene coordinates
|
||||
*/
|
||||
QPointF
|
||||
calcDraggedWindowPos(const QPoint &screenPos, const QPointF &scenePos, const QPointF &buttonDownScenePos) const;
|
||||
|
||||
void resizeScrollbar(qreal newZoneHeight);
|
||||
signals:
|
||||
void closePressed(ZoneViewWidget *zv);
|
||||
|
|
@ -76,7 +104,6 @@ private slots:
|
|||
void resizeToZoneContents(bool forceInitialHeight = false);
|
||||
void handleScrollBarChange(int value);
|
||||
void zoneDeleted();
|
||||
void moveEvent(QGraphicsSceneMoveEvent * /* event */) override;
|
||||
void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override;
|
||||
void expandWindow();
|
||||
|
||||
|
|
@ -101,6 +128,10 @@ public:
|
|||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void initStyleOption(QStyleOption *option) const override;
|
||||
bool windowFrameEvent(QEvent *event) override;
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number,
|
|||
font.setWeight(QFont::Bold);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
double w = 1.3 *
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
fm.horizontalAdvance(numStr);
|
||||
#else
|
||||
fm.width(numStr);
|
||||
#endif
|
||||
double w = 1.3 * fm.horizontalAdvance(numStr);
|
||||
double h = fm.height() * 1.3;
|
||||
if (w < h)
|
||||
w = h;
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ CardPictureLoaderWorker::CardPictureLoaderWorker()
|
|||
// We need a timeout to ensure requests don't hang indefinitely in case of
|
||||
// cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397
|
||||
// Use Qt's default timeout (30s, as of 2023-02-22)
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
networkManager->setTransferTimeout();
|
||||
#endif
|
||||
cache = new QNetworkDiskCache(this);
|
||||
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
|
||||
cache->setMaximumCacheSize(1024L * 1024L *
|
||||
|
|
|
|||
|
|
@ -287,14 +287,14 @@ static QString toDecklistExportString(const QList<const DecklistCardNode *> &car
|
|||
* @param deckList The decklist to export
|
||||
* @param website The website we're sending the deck to
|
||||
*/
|
||||
QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website)
|
||||
QString DeckLoader::exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website)
|
||||
{
|
||||
// Add the base url
|
||||
QString deckString = "https://" + getDomainForWebsite(website) + "/?";
|
||||
|
||||
// export all cards in zone
|
||||
QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN}));
|
||||
QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE}));
|
||||
QString mainBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_MAIN}));
|
||||
QString sideBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_SIDE}));
|
||||
|
||||
// Remove the extra return at the end of the last cards
|
||||
mainBoardCards.chop(3);
|
||||
|
|
@ -310,7 +310,7 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi
|
|||
return deckString;
|
||||
}
|
||||
|
||||
void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber)
|
||||
void DeckLoader::saveToClipboard(const DeckList &deckList, bool addComments, bool addSetNameAndNumber)
|
||||
{
|
||||
QString buffer;
|
||||
QTextStream stream(&buffer);
|
||||
|
|
@ -320,7 +320,7 @@ void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, boo
|
|||
}
|
||||
|
||||
bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
||||
const DeckList *deckList,
|
||||
const DeckList &deckList,
|
||||
bool addComments,
|
||||
bool addSetNameAndNumber)
|
||||
{
|
||||
|
|
@ -329,7 +329,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
|||
}
|
||||
|
||||
// loop zones
|
||||
for (auto zoneNode : deckList->getZoneNodes()) {
|
||||
for (auto zoneNode : deckList.getZoneNodes()) {
|
||||
saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber);
|
||||
|
||||
// end of zone
|
||||
|
|
@ -339,14 +339,14 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
|||
return true;
|
||||
}
|
||||
|
||||
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList)
|
||||
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList)
|
||||
{
|
||||
if (!deckList->getName().isEmpty()) {
|
||||
out << "// " << deckList->getName() << "\n\n";
|
||||
if (!deckList.getName().isEmpty()) {
|
||||
out << "// " << deckList.getName() << "\n\n";
|
||||
}
|
||||
|
||||
if (!deckList->getComments().isEmpty()) {
|
||||
QStringList commentRows = deckList->getComments().split(QRegularExpression("\n|\r\n|\r"));
|
||||
if (!deckList.getComments().isEmpty()) {
|
||||
QStringList commentRows = deckList.getComments().split(QRegularExpression("\n|\r\n|\r"));
|
||||
for (const QString &row : commentRows) {
|
||||
out << "// " << row << "\n";
|
||||
}
|
||||
|
|
@ -434,7 +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
|
||||
QFileInfo fileInfo(fileName);
|
||||
|
|
@ -543,7 +543,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
|
|||
cursor->movePosition(QTextCursor::End);
|
||||
}
|
||||
|
||||
void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
|
||||
void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
|
||||
{
|
||||
QTextDocument doc;
|
||||
|
||||
|
|
@ -559,14 +559,14 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
|
|||
headerCharFormat.setFontWeight(QFont::Bold);
|
||||
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
cursor.insertText(deckList->getName());
|
||||
cursor.insertText(deckList.getName());
|
||||
|
||||
headerCharFormat.setFontPointSize(12);
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
cursor.insertText(deckList->getComments());
|
||||
cursor.insertText(deckList.getComments());
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
|
||||
for (auto zoneNode : deckList->getZoneNodes()) {
|
||||
for (auto zoneNode : deckList.getZoneNodes()) {
|
||||
cursor.insertHtml("<br><img src=theme:hr.jpg>");
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ public:
|
|||
bool saveToFile(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,
|
||||
const DeckList *deckList,
|
||||
const DeckList &deckList,
|
||||
bool addComments = true,
|
||||
bool addSetNameAndNumber = true);
|
||||
|
||||
|
|
@ -72,9 +72,9 @@ public:
|
|||
* @param printer The printer to render the decklist to.
|
||||
* @param deckList
|
||||
*/
|
||||
static void printDeckList(QPrinter *printer, const DeckList *deckList);
|
||||
static void printDeckList(QPrinter *printer, const DeckList &deckList);
|
||||
|
||||
bool convertToCockatriceFormat(QString fileName);
|
||||
bool convertToCockatriceFormat(const QString &fileName);
|
||||
|
||||
LoadedDeck &getDeck()
|
||||
{
|
||||
|
|
@ -91,7 +91,7 @@ public:
|
|||
|
||||
private:
|
||||
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,
|
||||
const InnerDecklistNode *zoneNode,
|
||||
|
|
|
|||
|
|
@ -57,17 +57,10 @@ void Logger::openLogfileSession()
|
|||
return;
|
||||
}
|
||||
fileStream.setDevice(&fileHandle);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl;
|
||||
fileStream << getClientVersion() << Qt::endl;
|
||||
fileStream << getSystemArchitecture() << Qt::endl;
|
||||
fileStream << getClientInstallInfo() << Qt::endl;
|
||||
#else
|
||||
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << endl;
|
||||
fileStream << getClientVersion() << endl;
|
||||
fileStream << getSystemArchitecture() << endl;
|
||||
fileStream << getClientInstallInfo() << endl;
|
||||
#endif
|
||||
logToFileEnabled = true;
|
||||
}
|
||||
|
||||
|
|
@ -77,11 +70,7 @@ void Logger::closeLogfileSession()
|
|||
return;
|
||||
|
||||
logToFileEnabled = false;
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl;
|
||||
#else
|
||||
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << endl;
|
||||
#endif
|
||||
fileHandle.close();
|
||||
}
|
||||
|
||||
|
|
@ -103,11 +92,7 @@ void Logger::internalLog(const QString &message)
|
|||
std::cerr << message.toStdString() << std::endl; // Print to stdout
|
||||
|
||||
if (logToFileEnabled) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
fileStream << message << Qt::endl; // Print to fileStream
|
||||
#else
|
||||
fileStream << message << endl; // Print to fileStream
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,8 +113,9 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i
|
|||
if (indexToWidgetMap.contains(index)) {
|
||||
return indexToWidgetMap[index];
|
||||
}
|
||||
auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
|
||||
auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString();
|
||||
auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
auto cardProviderId =
|
||||
index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString();
|
||||
|
||||
auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true);
|
||||
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
|
||||
|
|
@ -136,7 +137,7 @@ void CardGroupDisplayWidget::updateCardDisplays()
|
|||
|
||||
// This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway.
|
||||
proxy.setSortRole(Qt::EditRole);
|
||||
proxy.sort(1, Qt::AscendingOrder);
|
||||
proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
|
||||
|
||||
// 1. trackedIndex is a source index → map it to proxy space
|
||||
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa
|
|||
layout->addWidget(text, 0, Qt::AlignCenter);
|
||||
setLayout(layout);
|
||||
|
||||
setFrameStyle(QFrame::Panel | QFrame::Raised);
|
||||
setFrameStyle(static_cast<int>(QFrame::Panel) | QFrame::Raised);
|
||||
|
||||
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
|
||||
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT
|
|||
originalPos = this->pos();
|
||||
|
||||
// Create the animation
|
||||
animation = new QPropertyAnimation(this, "pos");
|
||||
animation = new QPropertyAnimation(this, "pos", this);
|
||||
animation->setDuration(200); // 200ms animation duration
|
||||
animation->setEasingCurve(QEasingCurve::OutQuad);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "card_group_display_widgets/flat_card_group_display_widget.h"
|
||||
#include "card_group_display_widgets/overlapped_card_group_display_widget.h"
|
||||
#include "libcockatrice/card/database/card_database_manager.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
|
|
@ -89,10 +90,11 @@ void DeckCardZoneDisplayWidget::onSelectionChanged(const QItemSelection &selecte
|
|||
|
||||
void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index)
|
||||
{
|
||||
auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
|
||||
if (indexToWidgetMap.contains(index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
if (displayType == DisplayType::Overlap) {
|
||||
auto *displayWidget = new OverlappedCardGroupDisplayWidget(
|
||||
cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria,
|
||||
|
|
@ -127,7 +129,7 @@ void DeckCardZoneDisplayWidget::displayCards()
|
|||
QSortFilterProxyModel proxy;
|
||||
proxy.setSourceModel(deckListModel);
|
||||
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
|
||||
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
||||
|
|
@ -227,4 +229,23 @@ void DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeS
|
|||
{
|
||||
activeSortCriteria = _activeSortCriteria;
|
||||
emit activeSortCriteriaChanged(activeSortCriteria);
|
||||
}
|
||||
}
|
||||
|
||||
QList<QString> DeckCardZoneDisplayWidget::getGroupCriteriaValueList()
|
||||
{
|
||||
QList<QString> groupCriteriaValues;
|
||||
|
||||
QList<const DecklistCardNode *> nodes = deckListModel->getCardNodesForZone(zoneName);
|
||||
|
||||
for (auto node : nodes) {
|
||||
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||
if (info) {
|
||||
groupCriteriaValues.append(info->getProperty(activeGroupCriteria));
|
||||
}
|
||||
}
|
||||
|
||||
groupCriteriaValues.removeDuplicates();
|
||||
groupCriteriaValues.sort();
|
||||
|
||||
return groupCriteriaValues;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
{
|
||||
mainLayout = new QVBoxLayout();
|
||||
setLayout(mainLayout);
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "add_analytics_panel_dialog.h"
|
||||
#include "analytics_panel_widget_factory.h"
|
||||
#include "analyzer_modules/mana_base/mana_base_config.h"
|
||||
#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->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
mainLayout->addWidget(scrollArea);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
|
||||
container = new QWidget(scrollArea);
|
||||
containerLayout = new QVBoxLayout(container);
|
||||
container->setLayout(containerLayout);
|
||||
scrollArea->setWidget(container);
|
||||
panelContainer = new QWidget(scrollArea);
|
||||
panelLayout = new QVBoxLayout(panelContainer);
|
||||
panelLayout->setSpacing(8);
|
||||
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);
|
||||
containerLayout->addWidget(manaCurveWidget);
|
||||
loadLayout();
|
||||
|
||||
manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaDevotionWidget);
|
||||
|
||||
manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer);
|
||||
containerLayout->addWidget(manaBaseWidget);
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
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
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
* @brief Main analytics widget container with resizable panels for deck statistics.
|
||||
*/
|
||||
|
||||
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||
#define DECK_ANALYTICS_WIDGET_H
|
||||
|
||||
#include "mana_base_widget.h"
|
||||
#include "mana_curve_widget.h"
|
||||
#include "mana_devotion_widget.h"
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QJsonObject>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||
|
||||
class LayoutInspector;
|
||||
|
||||
class DeckAnalyticsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
void updateDisplays();
|
||||
|
||||
public:
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
void setDeckList(const DeckList &_deckListModel);
|
||||
std::map<int, int> analyzeManaCurve();
|
||||
void refreshDisplays();
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
void retranslateUi();
|
||||
|
||||
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:
|
||||
DeckListModel *deckListModel;
|
||||
DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer;
|
||||
QVBoxLayout *mainLayout;
|
||||
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||
|
||||
QWidget *container;
|
||||
QVBoxLayout *containerLayout;
|
||||
QVBoxLayout *layout;
|
||||
QWidget *controlContainer;
|
||||
QHBoxLayout *controlLayout;
|
||||
|
||||
QPushButton *addButton;
|
||||
QPushButton *removeButton;
|
||||
QPushButton *saveButton;
|
||||
QPushButton *loadButton;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
QWidget *panelContainer;
|
||||
QVBoxLayout *panelLayout;
|
||||
|
||||
ManaCurveWidget *manaCurveWidget;
|
||||
ManaDevotionWidget *manaDevotionWidget;
|
||||
ManaBaseWidget *manaBaseWidget;
|
||||
QVector<ResizablePanel *> panelWrappers;
|
||||
ResizablePanel *selectedWrapper = nullptr;
|
||||
|
||||
DeckListStatisticsAnalyzer *statsAnalyzer;
|
||||
LayoutInspector *insp = nullptr;
|
||||
};
|
||||
|
||||
#endif // DECK_ANALYTICS_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -9,38 +9,102 @@
|
|||
|
||||
DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
|
||||
DeckListModel *_model,
|
||||
DeckListStatisticsAnalyzerConfig cfg)
|
||||
: QObject(parent), model(_model), config(cfg)
|
||||
DeckListStatisticsAnalyzerConfig _config)
|
||||
: 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();
|
||||
manaCurveMap.clear();
|
||||
manaDevotionMap.clear();
|
||||
clearData();
|
||||
|
||||
QList<ExactCard> cards = model->getCards();
|
||||
QList<const DecklistCardNode *> nodes = model->getCardNodes();
|
||||
|
||||
for (const ExactCard &card : cards) {
|
||||
// ---- Mana curve ----
|
||||
for (auto node : nodes) {
|
||||
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) {
|
||||
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) {
|
||||
auto mana = determineManaProduction(card.getInfo().getText());
|
||||
for (auto it = mana.begin(); it != mana.end(); ++it)
|
||||
manaBaseMap[it.key()] += it.value();
|
||||
auto prod = determineManaProduction(info->getText());
|
||||
for (auto it = prod.begin(); it != prod.end(); ++it) {
|
||||
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) {
|
||||
auto devo = countManaSymbols(card.getInfo().getManaCost());
|
||||
for (auto &d : devo)
|
||||
manaDevotionMap[d.first] += d.second;
|
||||
auto devo = countManaSymbols(info->getManaCost());
|
||||
for (auto &d : devo) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 computeManaCurve = true;
|
||||
bool computeDevotion = true;
|
||||
bool computeCategories = true;
|
||||
bool computeCurveBreakdowns = true;
|
||||
bool computeProbabilities = true;
|
||||
};
|
||||
|
||||
class DeckListStatisticsAnalyzer : public QObject
|
||||
|
|
@ -23,9 +26,9 @@ class DeckListStatisticsAnalyzer : public QObject
|
|||
public:
|
||||
explicit DeckListStatisticsAnalyzer(QObject *parent,
|
||||
DeckListModel *model,
|
||||
DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig());
|
||||
DeckListStatisticsAnalyzerConfig _config = DeckListStatisticsAnalyzerConfig());
|
||||
|
||||
void update();
|
||||
void analyze();
|
||||
|
||||
[[nodiscard]] const QHash<QString, int> &getManaBase() const
|
||||
{
|
||||
|
|
@ -40,6 +43,96 @@ public:
|
|||
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:
|
||||
void statsUpdated();
|
||||
|
||||
|
|
@ -47,14 +140,42 @@ private:
|
|||
DeckListModel *model;
|
||||
DeckListStatisticsAnalyzerConfig config;
|
||||
|
||||
// Internal result containers
|
||||
QHash<QString, int> manaBaseMap;
|
||||
std::unordered_map<int, int> manaCurveMap;
|
||||
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 &);
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent)
|
||||
: QWidget(parent), deckEditor(parent)
|
||||
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor)
|
||||
: QWidget(parent), deckEditor(deckEditor)
|
||||
{
|
||||
setObjectName("centralWidget");
|
||||
setObjectName("databaseDisplayWidget");
|
||||
|
||||
centralFrame = new QVBoxLayout(this);
|
||||
centralFrame->setObjectName("centralFrame");
|
||||
|
|
@ -134,13 +134,13 @@ void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters()
|
|||
|
||||
void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||
{
|
||||
const QString cardName = current.sibling(current.row(), 0).data().toString();
|
||||
|
||||
if (!current.isValid()) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -172,7 +172,7 @@ ExactCard DeckEditorDatabaseDisplayWidget::currentCard() const
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class DeckEditorDatabaseDisplayWidget : public QWidget
|
|||
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent);
|
||||
explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor);
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
SearchLineEdit *searchEdit;
|
||||
CardDatabaseModel *databaseModel;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#include "deck_editor_deck_dock_widget.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "deck_list_style_proxy.h"
|
||||
#include "deck_state_manager.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
|
|
@ -38,7 +38,7 @@ static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
|
|||
}
|
||||
|
||||
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
: QDockWidget(parent), deckEditor(parent), deckStateManager(parent->deckStateManager)
|
||||
{
|
||||
setObjectName("deckDock");
|
||||
|
||||
|
|
@ -52,19 +52,21 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent
|
|||
|
||||
void DeckEditorDeckDockWidget::createDeckDock()
|
||||
{
|
||||
deckModel = new DeckListModel(this);
|
||||
deckModel->setObjectName("deckModel");
|
||||
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
||||
|
||||
deckLoader = new DeckLoader(this);
|
||||
connect(getModel(), &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
||||
|
||||
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,
|
||||
&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->setObjectName("deckView");
|
||||
deckView->setModel(proxy);
|
||||
|
|
@ -76,14 +78,14 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||
&DeckEditorDeckDockWidget::updateCard);
|
||||
connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard);
|
||||
connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapSelection);
|
||||
deckView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu);
|
||||
connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapCard);
|
||||
connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrement);
|
||||
connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrement);
|
||||
connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapSelection);
|
||||
connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrementSelection);
|
||||
connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrementSelection);
|
||||
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::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard);
|
||||
|
||||
|
|
@ -97,7 +99,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
nameDebounceTimer = new QTimer(this);
|
||||
nameDebounceTimer->setSingleShot(true);
|
||||
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]() {
|
||||
nameDebounceTimer->start(); // restart debounce timer
|
||||
|
|
@ -141,7 +143,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
commentsDebounceTimer = new QTimer(this);
|
||||
commentsDebounceTimer->setSingleShot(true);
|
||||
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]() {
|
||||
commentsDebounceTimer->start(); // restart debounce timer
|
||||
|
|
@ -152,18 +154,21 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
bannerCardLabel->setText(tr("Banner Card"));
|
||||
bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||
bannerCardComboBox = new QComboBox(this);
|
||||
connect(deckModel, &DeckListModel::dataChanged, this, [this]() {
|
||||
connect(getModel(), &DeckListModel::cardNodesChanged, this, [this]() {
|
||||
// Delay the update to avoid race conditions
|
||||
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,
|
||||
&DeckEditorDeckDockWidget::setBannerCard);
|
||||
&DeckEditorDeckDockWidget::writeBannerCard);
|
||||
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags());
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, {});
|
||||
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
|
||||
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this,
|
||||
&DeckEditorDeckDockWidget::setTags);
|
||||
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, deckStateManager,
|
||||
&DeckStateManager::setTags);
|
||||
|
||||
activeGroupCriteriaLabel = new QLabel(this);
|
||||
|
||||
|
|
@ -171,17 +176,12 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
activeGroupCriteriaComboBox->addItem(tr("Main Type"), DeckListModelGroupCriteria::MAIN_TYPE);
|
||||
activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST);
|
||||
activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR);
|
||||
connect(activeGroupCriteriaComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() {
|
||||
deckModel->setActiveGroupCriteria(static_cast<DeckListModelGroupCriteria::Type>(
|
||||
activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckView->expandAll();
|
||||
deckView->expandAll();
|
||||
});
|
||||
connect(activeGroupCriteriaComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&DeckEditorDeckDockWidget::applyActiveGroupCriteria);
|
||||
|
||||
aIncrement = new QAction(QString(), this);
|
||||
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);
|
||||
tbIncrement->setDefaultAction(aIncrement);
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
|
||||
aSwapCard = new QAction(QString(), this);
|
||||
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);
|
||||
tbSwapCard->setDefaultAction(aSwapCard);
|
||||
|
||||
|
|
@ -283,21 +283,20 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
|
||||
void DeckEditorDeckDockWidget::initializeFormats()
|
||||
{
|
||||
QMap<QString, int> allFormats = CardDatabaseManager::query()->getAllFormatsWithCount();
|
||||
QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys();
|
||||
|
||||
formatComboBox->clear(); // Remove "Loading Database..."
|
||||
formatComboBox->setEnabled(true);
|
||||
|
||||
// Populate with formats
|
||||
formatComboBox->addItem("", "");
|
||||
for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) {
|
||||
QString displayText = QString("%1").arg(it.key());
|
||||
formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData
|
||||
for (auto formatName : allFormats) {
|
||||
formatComboBox->addItem(formatName, formatName); // store the raw key in itemData
|
||||
}
|
||||
|
||||
if (!deckModel->getDeckList()->getGameFormat().isEmpty()) {
|
||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
||||
QString format = deckStateManager->getMetadata().gameFormat;
|
||||
if (!format.isEmpty()) {
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(format));
|
||||
} else {
|
||||
// Ensure no selection is visible initially
|
||||
formatComboBox->setCurrentIndex(-1);
|
||||
|
|
@ -306,11 +305,10 @@ void DeckEditorDeckDockWidget::initializeFormats()
|
|||
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
if (index >= 0) {
|
||||
QString formatKey = formatComboBox->itemData(index).toString();
|
||||
deckModel->setActiveFormat(formatKey);
|
||||
deckStateManager->setFormat(formatKey);
|
||||
} 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();
|
||||
if (!current.isValid())
|
||||
return {};
|
||||
const QString cardName = current.sibling(current.row(), 1).data().toString();
|
||||
const QString cardProviderID = current.sibling(current.row(), 4).data().toString();
|
||||
const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||
const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||
const QModelIndex gparent = current.parent().parent();
|
||||
|
||||
if (!gparent.isValid()) {
|
||||
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})) {
|
||||
return selectedCard;
|
||||
}
|
||||
|
|
@ -340,43 +338,37 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
|||
void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/)
|
||||
{
|
||||
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(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName()));
|
||||
deckModel->getDeckList()->setName(name);
|
||||
deckEditor->setModified(name.isEmpty());
|
||||
emit nameChanged();
|
||||
emit deckModified();
|
||||
QString name = nameEdit->text();
|
||||
deckStateManager->setName(name);
|
||||
}
|
||||
|
||||
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)")
|
||||
.arg(deckLoader->getDeck().deckList.getComments().size())
|
||||
.arg(commentsEdit->toPlainText().size()));
|
||||
|
||||
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
|
||||
deckEditor->setModified(commentsEdit->toPlainText().isEmpty());
|
||||
emit commentsChanged();
|
||||
emit deckModified();
|
||||
QString comments = commentsEdit->toPlainText();
|
||||
deckStateManager->setComments(comments);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateHash()
|
||||
{
|
||||
hashLabel->setText(deckModel->getDeckList()->getDeckHash());
|
||||
emit hashChanged();
|
||||
emit deckModified();
|
||||
hashLabel->setText(deckStateManager->getDeckHash());
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
{
|
||||
// Store current banner card identity
|
||||
CardRef wanted = deckModel->getDeckList()->getBannerCard();
|
||||
CardRef wanted = deckStateManager->getMetadata().bannerCard;
|
||||
|
||||
// Block signals temporarily
|
||||
bool wasBlocked = bannerCardComboBox->blockSignals(true);
|
||||
|
|
@ -386,15 +378,15 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
|||
|
||||
// Collect unique (name, providerId) pairs
|
||||
QSet<QPair<QString, QString>> bannerCardSet;
|
||||
QList<const DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes();
|
||||
QList<CardRef> cardsInDeck = getModel()->getCardRefs();
|
||||
|
||||
for (auto currentCard : cardsInDeck) {
|
||||
if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
|
||||
for (auto cardRef : cardsInDeck) {
|
||||
if (!CardDatabaseManager::query()->getCard(cardRef)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Insert one entry per distinct card, ignore copies
|
||||
bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()});
|
||||
bannerCardSet.insert({cardRef.name, cardRef.providerId});
|
||||
}
|
||||
|
||||
// Convert to sorted list
|
||||
|
|
@ -415,7 +407,6 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
|||
// Handle results
|
||||
if (restoreIndex != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(restoreIndex);
|
||||
syncDeckListBannerCardWithComboBox();
|
||||
} else {
|
||||
// Add a placeholder "-" and set it as the current selection
|
||||
bannerCardComboBox->insertItem(0, "-");
|
||||
|
|
@ -426,25 +417,21 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
|||
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"));
|
||||
syncDeckListBannerCardWithComboBox();
|
||||
deckEditor->setModified(true);
|
||||
emit deckModified();
|
||||
auto [name, id] = bannerCardComboBox->itemData(index).value<QPair<QString, QString>>();
|
||||
CardRef bannerCard = {name, id};
|
||||
deckStateManager->setBannerCard(bannerCard);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::setTags(const QStringList &tags)
|
||||
void DeckEditorDeckDockWidget::applyActiveGroupCriteria()
|
||||
{
|
||||
deckModel->getDeckList()->setTags(tags);
|
||||
deckEditor->setModified(true);
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox()
|
||||
{
|
||||
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
|
||||
deckModel->getDeckList()->setBannerCard({name, id});
|
||||
getModel()->setActiveGroupCriteria(
|
||||
static_cast<DeckListModelGroupCriteria::Type>(activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
|
||||
getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible)
|
||||
|
|
@ -460,7 +447,7 @@ void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible)
|
|||
|
||||
void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
||||
{
|
||||
if (deckModel->getDeckList()->getBannerCard().name == "") {
|
||||
if (deckStateManager->getMetadata().bannerCard.name == "") {
|
||||
if (bannerCardComboBox->findText("-") != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-"));
|
||||
} else {
|
||||
|
|
@ -468,36 +455,29 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
|||
bannerCardComboBox->setCurrentIndex(0);
|
||||
}
|
||||
} else {
|
||||
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name);
|
||||
bannerCardComboBox->setCurrentText(deckStateManager->getMetadata().bannerCard.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently active deck for this tab
|
||||
* @param _deck The deck.
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck)
|
||||
void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus)
|
||||
{
|
||||
deckLoader->setDeck(_deck);
|
||||
deckModel->setDeckList(&deckLoader->getDeck().deckList);
|
||||
connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree);
|
||||
deckView->clearSelection();
|
||||
deckView->setCurrentIndex(newCardIndex);
|
||||
recursiveExpand(newCardIndex);
|
||||
|
||||
emit requestDeckHistoryClear();
|
||||
historyManagerWidget->setDeckListModel(deckModel);
|
||||
|
||||
syncDisplayWidgetsToModel();
|
||||
|
||||
emit deckChanged();
|
||||
if (!preserveWidgetFocus) {
|
||||
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||
{
|
||||
nameEdit->blockSignals(true);
|
||||
nameEdit->setText(deckModel->getDeckList()->getName());
|
||||
nameEdit->setText(deckStateManager->getMetadata().name);
|
||||
nameEdit->blockSignals(false);
|
||||
|
||||
commentsEdit->blockSignals(true);
|
||||
commentsEdit->setText(deckModel->getDeckList()->getComments());
|
||||
commentsEdit->setText(deckStateManager->getMetadata().comments);
|
||||
commentsEdit->blockSignals(false);
|
||||
|
||||
bannerCardComboBox->blockSignals(true);
|
||||
|
|
@ -505,95 +485,155 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
|||
updateBannerCardComboBox();
|
||||
bannerCardComboBox->blockSignals(false);
|
||||
updateHash();
|
||||
sortDeckModelToDeckView();
|
||||
expandAll();
|
||||
|
||||
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
|
||||
}
|
||||
formatComboBox->blockSignals(true);
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat));
|
||||
formatComboBox->blockSignals(false);
|
||||
|
||||
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
|
||||
{
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
|
||||
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
|
||||
deckView->expandAll();
|
||||
deckView->expandAll();
|
||||
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
DeckLoader *DeckEditorDeckDockWidget::getDeckLoader()
|
||||
{
|
||||
return deckLoader;
|
||||
}
|
||||
|
||||
DeckList *DeckEditorDeckDockWidget::getDeckList()
|
||||
{
|
||||
return deckModel->getDeckList();
|
||||
deckTagsDisplayWidget->blockSignals(true);
|
||||
deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags);
|
||||
deckTagsDisplayWidget->blockSignals(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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());
|
||||
return deckStateManager->getModel();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
|
||||
void DeckEditorDeckDockWidget::selectPrevCard()
|
||||
{
|
||||
if (index.parent().isValid())
|
||||
recursiveExpand(index.parent());
|
||||
deckView->expand(index);
|
||||
changeSelectedCard(-1);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
deckView->expandAll();
|
||||
deckView->expandAll();
|
||||
deckView->expandRecursively(deckView->rootIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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();
|
||||
|
||||
const auto notLeafNode = [this](const QModelIndex &index) {
|
||||
return deckModel->hasChildren(proxy->mapToSource(index));
|
||||
};
|
||||
const auto mapToSource = [this](const QModelIndex &index) { return 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());
|
||||
|
||||
std::reverse(selectedRows.begin(), selectedRows.end());
|
||||
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) {
|
||||
offsetCountAtIndex(index, 1);
|
||||
deckStateManager->addCard(card, zoneName);
|
||||
}
|
||||
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
bool isModified = false;
|
||||
for (const auto ¤tIndex : selectedRows) {
|
||||
if (swapCard(currentIndex)) {
|
||||
isModified = true;
|
||||
}
|
||||
for (const auto &sourceIndex : selectedRows) {
|
||||
deckStateManager->swapCardAtIndex(sourceIndex);
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
if (isModified) {
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!card)
|
||||
|
|
@ -654,22 +657,12 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
|
|||
if (card.getInfo().getIsToken())
|
||||
zoneName = DECK_ZONE_TOKENS;
|
||||
|
||||
QString providerId = card.getPrinting().getUuid();
|
||||
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);
|
||||
deckStateManager->decrementCard(card, zoneName);
|
||||
}
|
||||
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
for (const auto &index : selectedRows) {
|
||||
offsetCountAtIndex(index, -1);
|
||||
for (const auto &sourceIndex : selectedRows) {
|
||||
offsetCountAtIndex(sourceIndex, false);
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
|
@ -686,7 +679,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection()
|
|||
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
bool isModified = false;
|
||||
for (const auto &index : selectedRows) {
|
||||
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;
|
||||
for (const auto &sourceIndex : selectedRows) {
|
||||
deckStateManager->removeCardAtIndex(sourceIndex);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QModelIndex sourceIndex = proxy->mapToSource(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());
|
||||
if (isIncrement) {
|
||||
deckStateManager->incrementCountAtIndex(idx);
|
||||
} else {
|
||||
deckModel->setData(numberIndex, new_count, Qt::EditRole);
|
||||
deckStateManager->decrementCountAtIndex(idx);
|
||||
}
|
||||
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
||||
|
|
|
|||
|
|
@ -28,22 +28,14 @@ class DeckEditorDeckDockWidget : public QDockWidget
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
|
||||
DeckLoader *deckLoader;
|
||||
|
||||
DeckListStyleProxy *proxy;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QComboBox *bannerCardComboBox;
|
||||
void createDeckDock();
|
||||
ExactCard getCurrentCard();
|
||||
void retranslateUi();
|
||||
QString getDeckName()
|
||||
{
|
||||
return nameEdit->text();
|
||||
}
|
||||
QString getSimpleDeckName()
|
||||
{
|
||||
return nameEdit->text().simplified();
|
||||
}
|
||||
|
||||
QComboBox *getGroupByComboBox()
|
||||
{
|
||||
return activeGroupCriteriaComboBox;
|
||||
|
|
@ -55,35 +47,26 @@ public:
|
|||
}
|
||||
|
||||
public slots:
|
||||
void cleanDeck();
|
||||
void selectPrevCard();
|
||||
void selectNextCard();
|
||||
void updateBannerCardComboBox();
|
||||
void setDeck(const LoadedDeck &_deck);
|
||||
void syncDisplayWidgetsToModel();
|
||||
void sortDeckModelToDeckView();
|
||||
DeckLoader *getDeckLoader();
|
||||
DeckList *getDeckList();
|
||||
void actIncrement();
|
||||
bool swapCard(const QModelIndex &idx);
|
||||
void actAddCard(const ExactCard &card, const QString &zoneName);
|
||||
void actIncrementSelection();
|
||||
void actDecrementCard(const ExactCard &card, QString zoneName);
|
||||
void actDecrementSelection();
|
||||
void actSwapCard();
|
||||
void actSwapCard(const ExactCard &card, const QString &zoneName);
|
||||
void actSwapSelection();
|
||||
void actRemoveCard();
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void initializeFormats();
|
||||
void expandAll();
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
void commentsChanged();
|
||||
void hashChanged();
|
||||
void deckChanged();
|
||||
void deckModified();
|
||||
void requestDeckHistorySave(const QString &modificationReason);
|
||||
void requestDeckHistoryClear();
|
||||
void cardChanged(const ExactCard &_card);
|
||||
void selectedCardChanged(const ExactCard &card);
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckStateManager *deckStateManager;
|
||||
|
||||
DeckListHistoryManagerWidget *historyManagerWidget;
|
||||
KeySignals deckViewKeySignals;
|
||||
QLabel *nameLabel;
|
||||
|
|
@ -106,22 +89,26 @@ private:
|
|||
|
||||
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||
|
||||
void recursiveExpand(const QModelIndex &index);
|
||||
[[nodiscard]] QModelIndexList getSelectedCardNodes() const;
|
||||
DeckListModel *getModel() const;
|
||||
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
|
||||
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
|
||||
|
||||
private slots:
|
||||
void decklistCustomMenu(QPoint point);
|
||||
void updateCard(QModelIndex, const QModelIndex ¤t);
|
||||
void updateName(const QString &name);
|
||||
void updateComments();
|
||||
void setBannerCard(int);
|
||||
void setTags(const QStringList &tags);
|
||||
void syncDeckListBannerCardWithComboBox();
|
||||
void writeName();
|
||||
void writeComments();
|
||||
void writeBannerCard(int);
|
||||
void applyActiveGroupCriteria();
|
||||
void setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus);
|
||||
void updateHash();
|
||||
void refreshShortcuts();
|
||||
void updateShowBannerCardComboBox(bool visible);
|
||||
void updateShowTagsWidget(bool visible);
|
||||
void syncBannerCardComboBoxSelectionWithDeck();
|
||||
void changeSelectedCard(int changeBy);
|
||||
void recursiveExpand(const QModelIndex &parent);
|
||||
void expandAll();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock()
|
|||
|
||||
installEventFilter(deckEditor);
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#include "deck_list_history_manager_widget.h"
|
||||
|
||||
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel,
|
||||
#include "deck_state_manager.h"
|
||||
|
||||
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager,
|
||||
DeckListStyleProxy *_styleProxy,
|
||||
DeckListHistoryManager *manager,
|
||||
QWidget *parent)
|
||||
: QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager)
|
||||
: QWidget(parent), deckStateManager(_deckStateManager), styleProxy(_styleProxy)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
|
||||
|
|
@ -43,8 +44,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckL
|
|||
|
||||
connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked);
|
||||
|
||||
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this,
|
||||
&DeckListHistoryManagerWidget::refreshList);
|
||||
connect(deckStateManager, &DeckStateManager::historyChanged, this, &DeckListHistoryManagerWidget::refreshList);
|
||||
|
||||
refreshList();
|
||||
retranslateUi();
|
||||
|
|
@ -58,15 +58,12 @@ void DeckListHistoryManagerWidget::retranslateUi()
|
|||
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()
|
||||
{
|
||||
historyList->clear();
|
||||
|
||||
DeckListHistoryManager *historyManager = deckStateManager->getHistoryManager();
|
||||
|
||||
// Fill redo section first (oldest redo at top, newest redo closest to divider)
|
||||
const auto redoStack = historyManager->getRedoStack();
|
||||
for (int i = 0; i < redoStack.size(); ++i) { // iterate forward
|
||||
|
|
@ -98,36 +95,7 @@ void DeckListHistoryManagerWidget::refreshList()
|
|||
redoButton->setEnabled(historyManager->canRedo());
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::doUndo()
|
||||
{
|
||||
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)
|
||||
void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item)
|
||||
{
|
||||
// Ignore non-selectable items (like divider)
|
||||
if (!(item->flags() & Qt::ItemIsSelectable)) {
|
||||
|
|
@ -138,23 +106,24 @@ void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item)
|
|||
int index = item->data(Qt::UserRole + 1).toInt();
|
||||
|
||||
if (mode == "redo") {
|
||||
const auto redoStack = historyManager->getRedoStack();
|
||||
const auto redoStack = deckStateManager->getHistoryManager()->getRedoStack();
|
||||
int steps = redoStack.size() - index;
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
historyManager->redo(deckListModel->getDeckList());
|
||||
}
|
||||
deckStateManager->redo(steps);
|
||||
} else if (mode == "undo") {
|
||||
const auto undoStack = historyManager->getUndoStack();
|
||||
int steps = undoStack.size() - 1 - index;
|
||||
for (int i = 0; i < steps + 1; ++i) {
|
||||
historyManager->undo(deckListModel->getDeckList());
|
||||
}
|
||||
const auto undoStack = deckStateManager->getHistoryManager()->getUndoStack();
|
||||
int steps = undoStack.size() - index;
|
||||
deckStateManager->undo(steps);
|
||||
}
|
||||
|
||||
deckListModel->rebuildTree();
|
||||
|
||||
emit deckListModel->layoutChanged();
|
||||
emit requestDisplayWidgetSync();
|
||||
|
||||
refreshList();
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::doUndo()
|
||||
{
|
||||
deckStateManager->undo();
|
||||
}
|
||||
|
||||
void DeckListHistoryManagerWidget::doRedo()
|
||||
{
|
||||
deckStateManager->redo();
|
||||
}
|
||||
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