Compare commits

..

1 Commits

Author SHA1 Message Date
ZeldaZach
eb712d25dc Addresses crash scenario where too many cards were being loaded in a game.
- Too many iterations that fill up the server's memory trying to (1) render the deck hash and (2) building up a string of the deck contents
2024-04-28 22:14:24 -04:00
586 changed files with 47069 additions and 77738 deletions

View File

@@ -1,4 +1,4 @@
FROM fedora:41
FROM fedora:39
RUN dnf install -y \
ccache \

View File

@@ -0,0 +1,25 @@
FROM ubuntu:bionic
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
protobuf-compiler \
qt5-default \
qt5-image-formats-plugins \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -1,4 +1,4 @@
FROM ubuntu:20.04
FROM ubuntu:focal
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM ubuntu:22.04
FROM ubuntu:jammy
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM ubuntu:24.04
FROM ubuntu:noble
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \

View File

@@ -163,7 +163,7 @@ echo "::group::Build project"
if [[ $RUNNER_OS == Windows ]]; then
# Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
# and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
cmake --build . "${buildflags[@]}" -- -p:UseMultiToolTask=true -p:EnableClServerMode=true
cmake --build . "${buildflags[@]}" -- -p:UseMultiToolTask=true
else
cmake --build . "${buildflags[@]}"
fi
@@ -189,12 +189,6 @@ fi
if [[ $MAKE_PACKAGE ]]; then
echo "::group::Create package"
if [[ $RUNNER_OS == macOS ]]; then
# Workaround https://github.com/actions/runner-images/issues/7522
echo "killing XProtectBehaviorService"; sudo pkill -9 XProtect >/dev/null || true;
echo "waiting for XProtectBehaviorService kill"; while pgrep "XProtect"; do sleep 3; done;
fi
cmake --build . --target package --config "$BUILDTYPE"
echo "::endgroup::"

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>

View File

@@ -4,24 +4,24 @@
git push -d origin --REPLACE-WITH-BETA-LIST--
-->
<!-- This list of binaries should be updated every time the CI is changed to
<!-- This list of binaries should be updated every time the ci is changed to
include different targets -->
<pre>
<b>Pre-compiled binaries we serve:</b>
- <kbd>Windows 10+</kbd>
- <kbd>Windows 7+</kbd>
- <kbd>macOS 14+</kbd> ("Sonoma") / Apple M
- <kbd>macOS 13+</kbd> ("Ventura") / Intel
- <kbd>Ubuntu 24.04 LTS</kbd> ("Noble Numbat")
- <kbd>Ubuntu 22.04 LTS</kbd> ("Jammy Jellyfish")
- <kbd>Windows 10+</kbd>
- <kbd>macOS 10.15+</kbd> ("Catalina")
- <kbd>macOS 13+</kbd> ("Ventura")
- <kbd>Ubuntu 18.04 LTS</kbd> ("Bionic Beaver")
- <kbd>Ubuntu 20.04 LTS</kbd> ("Focal Fossa")
- <kbd>Debian 12</kbd> ("Bookworm")
- <kbd>Ubuntu 22.04 LTS</kbd> ("Jammy Jellyfish")
- <kbd>Ubuntu 24.04 LTS</kbd> ("Noble Numbat")
- <kbd>Debian 11</kbd> ("Bullseye")
- <kbd>Fedora 41</kbd>
- <kbd>Debian 12</kbd> ("Bookworm")
- <kbd>Fedora 39</kbd>
- <kbd>Fedora 40</kbd>
<i>We are also packaged in <kbd>Arch Linux</kbd>'s official "extra" repository, courtesy of @FFY00</i>
<i>General Linux support is available via a <kbd>flatpak</kbd> package (Flathub)</i>
<kbd>We are also packaged in Arch Linux's official community repository, courtesy of @FFY00</kbd></i>
<kbd>General Linux support is available via a flatpak package (Flathub)</kbd></i>
</pre>
@@ -29,24 +29,22 @@ include different targets -->
We're pleased to announce the newest official release: <kbd>--REPLACE-WITH-RELEASE-TITLE--</kbd>
We hope you enjoy the changes made! All improvements with their corresponding tickets since the last version of Cockatrice are listed in the changelog below.
We hope you enjoy the changes made and we have listed all changes, with their corresponding tickets, since the last version of Cockatrice was released for your convenience.
If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a GitHub Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
For basic information related to the app and getting started, please take a look at our official site: **https://cockatrice.github.io**
For any information relating to Cockatrice, please take a look at our official site: **https://cockatrice.github.io**
If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-).
We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution.
If you'd like to help contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution.
## Upgrading Cockatrice
<!-- this optional section puts a warning banner for problems with updating
> [!IMPORTANT]
> **With this release, we no longer provide a ready-to-install binary for:**
> ⚠️ **With this release, we no longer provide a ready-to-install binary for:**
> --DEPRECATED-OS-HERE--
-->
Run the internal software updater: <kbd>Help → Check for Client Updates</kbd>
- Run the internal software updater: <kbd>Help → Check for Client Updates</kbd>
Don't forget to update your card database right after! (<kbd>Help → Check for Card Updates...</kbd>)
@@ -63,14 +61,14 @@ Remove empty headers when done.
-->
<!-- Highlights of the release -->
### 🔖 Highlights:
### ⚠️ Important:
### ✨ New Features:
### 🐛 Fixed Bugs / Resolved Issues:
### 🐛 Fixed Bugs / Resolved issues:
<!-- Complete list of changes (foldable) -->
<details>
<summary>
<b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits)
📘 <b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits)
</summary>
### User Interface
@@ -91,6 +89,5 @@ Remove empty headers when done.
## Special Thanks
<!-- Personalise this a bit! -->
It's amazing that so many people contribute their time, knowledge, code, testing and more to the project.
We'd like to thank the entire Cockatrice community for their efforts! 🙏
We continue to find it amazing that so many people contribute their time, knowledge, code, testing and more to the project. We'd like to thank the entire Cockatrice community for their efforts.
<!-- We'd like to especially recognize @ZeldaZach, --ADD-CONTRIBUTORS-HERE-- for their help in preparing so many amazing new features for the user base. -->

View File

@@ -37,13 +37,13 @@ jobs:
shell: bash
run: |
tag_regex='^refs/tags/'
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
sha="${{github.event.pull_request.head.sha}}"
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
sha="$GITHUB_SHA"
tag="${GITHUB_REF/refs\/tags\//}"
echo "tag=$tag" >>"$GITHUB_OUTPUT"
else # push to branch
else # push to branch
sha="$GITHUB_SHA"
fi
echo "sha=$sha" >>"$GITHUB_OUTPUT"
@@ -85,52 +85,46 @@ jobs:
strategy:
fail-fast: false
matrix:
# These names correspond to the files in ".ci/$distro$version"
# these names correspond to the files in .ci/$distro
include:
- distro: Arch
package: skip # We are packaged in Arch already
- distro: ArchLinux
package: skip # we are packaged in arch already
allow-failure: yes
- distro: Debian
version: 11
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Debian
version: 12
- distro: Debian11
package: DEB
- distro: Fedora
version: 40
package: RPM
test: skip # Running tests on all distros is superfluous
- distro: Debian12
package: DEB
- distro: Fedora
version: 41
- distro: Fedora39
package: RPM
- distro: Ubuntu
version: 20.04
package: DEB
test: skip # Ubuntu 20.04 has a broken Qt for debug builds
- distro: Fedora40
package: RPM
- distro: Ubuntu
version: 22.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 24.04
- distro: UbuntuBionic
package: DEB
name: ${{matrix.distro}} ${{matrix.version}}
- distro: UbuntuFocal
package: DEB
test: skip # UbuntuFocal has a broken qt for debug builds
- distro: UbuntuJammy
package: DEB
test: skip # running tests on all distros is superfluous
- distro: UbuntuNoble
package: DEB
name: ${{matrix.distro}}
needs: configure
runs-on: ubuntu-latest
continue-on-error: ${{matrix.allow-failure == 'yes'}}
env:
NAME: ${{matrix.distro}}${{matrix.version}}
CACHE: /tmp/${{matrix.distro}}${{matrix.version}}-cache # ${{runner.temp}} does not work?
# Cache size over the entire repo is 10Gi:
NAME: ${{matrix.distro}}
CACHE: /tmp/${{matrix.distro}}-cache # ${{runner.temp}} does not work?
# cache size over the entire repo is 10Gi link:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 200M
@@ -138,7 +132,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Generate cache timestamp
- name: Get cache timestamp
id: cache_timestamp
shell: bash
run: echo "timestamp=$(date -u '+%Y%m%d%H%M%S')" >>"$GITHUB_OUTPUT"
@@ -149,17 +143,19 @@ jobs:
timestamp: ${{steps.cache_timestamp.outputs.timestamp}}
with:
path: ${{env.CACHE}}
key: docker-${{matrix.distro}}${{matrix.version}}-cache-${{env.timestamp}}
key: docker-${{matrix.distro}}-cache-${{env.timestamp}}
restore-keys: |
docker-${{matrix.distro}}${{matrix.version}}-cache-
docker-${{matrix.distro}}-cache-
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
- name: Build ${{matrix.distro}} Docker image
shell: bash
run: source .ci/docker.sh --build
- name: Build debug and test
if: matrix.test != 'skip'
shell: bash
env:
distro: '${{matrix.distro}}'
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" --parallel 4
@@ -170,7 +166,8 @@ jobs:
shell: bash
env:
BUILD_DIR: build
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
SUFFIX: '-${{matrix.distro}}'
distro: '${{matrix.distro}}'
type: '${{matrix.package}}'
run: |
source .ci/docker.sh
@@ -182,7 +179,7 @@ jobs:
if: matrix.package != 'skip'
uses: actions/upload-artifact@v4
with:
name: ${{matrix.distro}}${{matrix.version}}-package
name: ${{matrix.distro}}-package
path: ${{steps.build.outputs.path}}
if-no-files-found: error
@@ -201,38 +198,33 @@ jobs:
fail-fast: false
matrix:
include:
- target: 13
soc: Intel
os: macos-13
xcode: "14.3.1"
type: Release
core_count: 4
make_package: 1
- target: 14
soc: Apple
os: macos-14
xcode: "15.4"
type: Release
core_count: 3
make_package: 1
- target: 15
soc: Apple
os: macos-15
xcode: "16.2"
type: Release
core_count: 3
make_package: 1
- target: 15
soc: Apple
os: macos-15
xcode: "16.2"
- target: Debug # tests only
os: macos-latest
xcode: 14.2
qt_version: homebrew
type: Debug
core_count: 3
do_tests: 1
name: macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
- target: 10.15_Catalina
os: macos-11
xcode: 11.7 # allows using macOS 10.15 SDK
qt_version: 6.2.* # 6.2 is last LTS compatible with 10.15, see https://doc.qt.io/qt-6.5/macos.html
qt_modules: "qtmultimedia qtwebsockets"
type: Release
do_tests: 1
make_package: 1
use_old_protobuf: 1
qt_py7zrversion: '==0.20.*'
- target: 13_Ventura
os: macos-13
xcode: 14.3.1
qt_version: homebrew
type: Release
do_tests: 1
make_package: 1
name: macOS ${{matrix.target}}
needs: configure
runs-on: ${{matrix.os}}
continue-on-error: ${{matrix.allow-failure == 'yes'}}
@@ -244,93 +236,56 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies using Homebrew
- name: Install dependencies using homebrew
shell: bash
# CMake cannot find the MySQL connector
# Neither of these works: mariadb-connector-c mysql-connector-c++
# cmake cannot find the mysql connector
# neither of these works: mariadb-connector-c mysql-connector-c++
env:
install_qt: ${{matrix.qt_version}}
use_old_protobuf: ${{matrix.use_old_protobuf}}
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
run: |
brew update
brew install protobuf qt --force-bottle
if [[ $use_old_protobuf == 1 ]]; then
brew install protobuf@21
brew link --force protobuf@21
else
brew install protobuf
brew link --force protobuf
fi
if [[ $install_qt == homebrew ]]; then
brew install qt --force-bottle
else # for some reason the tests fail with the action installed qt?
brew install googletest
fi
- name: Build & Sign on Xcode ${{matrix.xcode}}
- name: Install Qt ${{matrix.qt_version}} for ${{matrix.target}}
if: matrix.qt_version != 'homebrew'
uses: jurplel/install-qt-action@v3
with:
cache: true
setup-python: false
version: ${{matrix.qt_version}}
modules: ${{matrix.qt_modules}}
py7zrversion: ${{matrix.qt_py7zrversion}}
- name: Build on Xcode ${{matrix.xcode}}
shell: bash
id: build
env:
BUILDTYPE: '${{matrix.type}}'
MAKE_TEST: 1
MAKE_TEST: '${{matrix.do_tests}}'
MAKE_PACKAGE: '${{matrix.make_package}}'
PACKAGE_SUFFIX: '-macOS${{matrix.target}}_${{matrix.soc}}'
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
# macOS runner have 3 cores usually - only the macos-13 image has 4:
# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
run: |
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
then
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security default-keychain -s build.keychain
security set-keychain-settings -t 3600 -l build.keychain
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
fi
.ci/compile.sh --server --parallel ${{matrix.core_count}}
- name: Sign app bundle
if: matrix.make_package
env:
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
then
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
/usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose ${{steps.build.outputs.path}}
fi
- name: Notarize app bundle
if: matrix.make_package
env:
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
run: |
if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]]
then
# Store the notarization credentials so that we can prevent a UI password dialog from blocking the CI
echo "Create keychain profile"
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$MACOS_NOTARIZATION_APPLE_ID" --team-id "$MACOS_NOTARIZATION_TEAM_ID" --password "$MACOS_NOTARIZATION_PWD"
# We can't notarize an app bundle directly, but we need to compress it as an archive.
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
# notarization service
echo "Creating temp notarization archive"
ditto -c -k --keepParent ${{steps.build.outputs.path}} "notarization.zip"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
# you're curious
echo "Notarize app"
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available.
echo "Attach staple"
xcrun stapler staple ${{steps.build.outputs.path}}
fi
PACKAGE_SUFFIX: '-macOS-${{matrix.target}}'
# macOS runner actually have only 3 cores
# See https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
run: .ci/compile.sh --server --parallel 3
- name: Upload artifact
if: matrix.make_package
uses: actions/upload-artifact@v4
with:
name: macOS${{matrix.target}}${{ matrix.soc == 'Intel' && '_Intel' || '' }}${{ matrix.type == 'Debug' && '_Debug' || '' }}-dmg
name: macOS-${{matrix.target}}-dmg
path: ${{steps.build.outputs.path}}
if-no-files-found: error
@@ -352,24 +307,24 @@ jobs:
- target: 7
qt_version: 5.15.*
qt_arch: msvc2019_64
qt_tools: "tools_opensslv3_x64"
- target: 10
qt_version: 6.6.*
qt_version: 6.5.*
qt_arch: msvc2019_64
qt_modules: "qtimageformats qtmultimedia qtwebsockets"
qt_tools: "tools_opensslv3_x64"
qt_modules: "qtmultimedia qtwebsockets"
name: Windows ${{matrix.target}}
needs: configure
runs-on: windows-2022
runs-on: windows-2019
env:
CMAKE_GENERATOR: 'Visual Studio 17 2022'
CMAKE_GENERATOR: 'Visual Studio 16 2019'
steps:
- name: Add msbuild to PATH
id: add-msbuild
uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x64
- name: Checkout
uses: actions/checkout@v4
@@ -377,7 +332,7 @@ jobs:
submodules: recursive
- name: Install Qt ${{matrix.qt_version}}
uses: jurplel/install-qt-action@v4
uses: jurplel/install-qt-action@v3
with:
cache: true
setup-python: false

View File

@@ -32,7 +32,7 @@ jobs:
- name: Create pull request
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v6
with:
add-paths: |
cockatrice/translations/*.ts
@@ -64,7 +64,7 @@ jobs:
env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
run: |
if [[ "$STATUS" == "none" ]]; then
if [[ "$STATUS" == "" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY

View File

@@ -56,7 +56,7 @@ jobs:
- name: Create pull request
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v6
with:
add-paths: |
cockatrice/cockatrice_en@source.ts
@@ -79,7 +79,7 @@ jobs:
env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
run: |
if [[ "$STATUS" == "none" ]]; then
if [[ "$STATUS" == "" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY

3
.gitignore vendored
View File

@@ -6,11 +6,10 @@ mysql.cnf
.DS_Store
.idea/
*.aps
cmake-build*
cmake-build-debug*
preferences
compile_commands.json
.vs/
.vscode/
.cache
.gdb_history
cockatrice/resources/config/qtlogging.ini

View File

@@ -74,16 +74,16 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 2.10.0)
project("Cockatrice" VERSION 2.9.1)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Rings of the Wild")
endif()
# Use c++20 for all targets
# Use c++17 for all targets
set(CMAKE_CXX_STANDARD
20
17
CACHE STRING "C++ ISO Standard"
)
set(CMAKE_CXX_STANDARD_REQUIRED True)
@@ -140,14 +140,10 @@ endif()
# Define proper compilation flags
if(MSVC)
# Visual Studio: Disable Warning C4251, C++20 compatibility, Multi-threaded Builds, Warn Detection, Unwind Semantics
set(CMAKE_CXX_FLAGS "/wd4251 /Zc:__cplusplus /std:c++20 /permissive- /W4 /MP /EHsc")
# Visual Studio: Maximum Optimization, Multi-threaded DLL
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD")
# Visual Studio: No Optimization, Multi-threaded Debug DLL, Debug Symbols
set(CMAKE_CXX_FLAGS_DEBUG "/Od /MDd /Zi")
add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
# Visual Studio: Maximum optimization, disable warning C4251, establish C++17 compatibility
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251 /Zc:__cplusplus /std:c++17 /permissive- /W4")
# Generate complete debugging information
#set(CMAKE_CXX_FLAGS_DEBUG "/Zi")
elseif(CMAKE_COMPILER_IS_GNUCXX)
# linux/gcc, bsd/gcc, windows/mingw
include(CheckCXXCompilerFlag)
@@ -160,7 +156,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
endif()
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++20")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
endif()
set(ADDITIONAL_DEBUG_FLAGS
@@ -268,14 +264,13 @@ if(UNIX)
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice/resources/appicon.icns")
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeDMGSetup.script")
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dmgBackground.tif")
set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/SignMacApplications.cmake")
else()
# linux
if(CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
if(Qt6_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia")
elseif(Qt5_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt5-qttools, qt5-qtsvg, qt5-qtmultimedia")
endif()
@@ -297,7 +292,7 @@ if(UNIX)
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
if(Qt6_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins")
elseif(Qt5_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
endif()

View File

@@ -1,20 +1,19 @@
FROM ubuntu:24.04
ARG DEBIAN_FRONTEND=noninteractive
FROM ubuntu:bionic
MAINTAINER Zach Halpern <zahalpern+github@gmail.com>
RUN apt-get update && apt-get install -y\
build-essential \
cmake \
file \
g++ \
git \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6sql6-mysql \
qt6-websockets-dev \
protobuf-compiler \
qt6-tools-dev \
qt6-tools-dev-tools
build-essential\
cmake\
git\
libprotobuf-dev\
libqt5sql5-mysql\
libmysqlclient-dev\
libqt5websockets5-dev\
protobuf-compiler\
qt5-default\
qtbase5-dev\
qttools5-dev-tools\
qttools5-dev
COPY . /home/servatrice/code/
WORKDIR /home/servatrice/code
@@ -26,6 +25,7 @@ RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=
WORKDIR /home/servatrice
EXPOSE 4747 4748
EXPOSE 4747
ENTRYPOINT [ "servatrice", "--log-to-console" ]

View File

@@ -5,7 +5,7 @@
<p align='center'>
<a href="#cockatrice"><b>Cockatrice</b></a> <b>|</b>
<a href="#download-">Download</a> <b>|</b>
<a href="#get-involved-">Get Involved</a> <b>|</b>
<a href="#get-involved--">Get Involved</a> <b>|</b>
<a href="#community-resources">Community</a> <b>|</b>
<a href="#translations-">Translations</a> <b>|</b>
<a href="#build--">Build</a> <b>|</b>
@@ -18,7 +18,7 @@
<br><pre>
<b>To get started, &#8674; [view our webpage](https://cockatrice.github.io/)</b><br>
<b>To get support or suggest changes &#8674; [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))</b>
<b>To help with development, see how to [get involved](#get-involved-)</b>
<b>To help with development, see how to [get involved](#get-involved--)</b>
</pre><br>
@@ -42,7 +42,7 @@ Downloads are available for full releases and the current beta version in develo
# Get Involved [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project, contributors or fellow users of the app. Come here to talk about the application, features, or just to hang out.<br>
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project or fellow users of the app. The Cockatrice developers are also available on [Gitter](https://gitter.im/Cockatrice/Cockatrice). Come here to talk about the application, features, or just to hang out.<br>
For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.<br>
To contribute code to the project, please review [the guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md).

View File

@@ -24,7 +24,7 @@ if(WIN32)
get_filename_component(_path ${_path}/../../ ABSOLUTE)
foreach(redist_file ${REDIST_FILE_NAMES})
if(EXISTS "${_path}/${redist_file}")
if(EXISTS "${_path}/${REDIST_FILE}")
set(VCREDISTRUNTIME_FOUND "YES")
set(VCREDISTRUNTIME_FILE ${_path}/${redist_file})
break()

View File

@@ -5,7 +5,7 @@ OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@"
!define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@"
RequestExecutionlevel admin
RequestExecutionlevel highest
SetCompressor LZMA
Var NormalDestDir
@@ -235,13 +235,6 @@ ${If} $PortableMode = 0
WriteUninstaller "$INSTDIR\uninstall.exe"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
; Enable Windows User-Mode Dumps
; https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
WriteRegStr HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpFolder" "%LOCALAPPDATA%\CrashDumps\Cockatrice"
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpCount" "5"
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpType" "2"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayIcon" "$INSTDIR\cockatrice.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayName" "Cockatrice"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayVersion" "@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@"
@@ -255,20 +248,20 @@ ${If} $PortableMode = 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMajor" "@CPACK_PACKAGE_VERSION_MAJOR@"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMinor" "@CPACK_PACKAGE_VERSION_MINOR@"
IfFileExists "$INSTDIR\vc_redist.x86.exe" VcRedist86Exists PastVcRedist86Check
IfFileExists "$INSTDIR\vcredist_x86.exe" VcRedist86Exists PastVcRedist86Check
VcRedist86Exists:
ExecWait '"$INSTDIR\vc_redist.x86.exe" /passive /norestart'
ExecWait '"$INSTDIR\vcredist_x86.exe" /passive /norestart'
DetailPrint "Wait to ensure unlock of vc_redist file after installation..."
Sleep 3000
Delete "$INSTDIR\vc_redist.x86.exe"
Delete "$INSTDIR\vcredist_x86.exe"
PastVcRedist86Check:
IfFileExists "$INSTDIR\vc_redist.x64.exe" VcRedist64Exists PastVcRedist64Check
IfFileExists "$INSTDIR\vcredist_x64.exe" VcRedist64Exists PastVcRedist64Check
VcRedist64Exists:
ExecWait '"$INSTDIR\vc_redist.x64.exe" /passive /norestart'
ExecWait '"$INSTDIR\vcredist_x64.exe" /passive /norestart'
DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
Sleep 3000
Delete "$INSTDIR\vc_redist.x64.exe"
Delete "$INSTDIR\vcredist_x64.exe"
PastVcRedist64Check:
${Else}

View File

@@ -1,27 +0,0 @@
# This script re-signs all apps after CPack packages them. This is necessary because CPack modifies
# the library references used by Cockatrice to App relative paths, invalidating the code signature.
string(LENGTH $ENV{MACOS_CERTIFICATE_NAME} MACOS_CERTIFICATE_NAME_LEN)
if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0)
set(APPLICATIONS "cockatrice" "servatrice" "oracle" "dbconverter")
foreach(app_name IN LISTS APPLICATIONS)
set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app")
message(STATUS "Signing Interior Dynamically Loaded Libraries for ${app_name}.app")
execute_process(COMMAND "find" "${FULL_APP_PATH}" "-name" "*.dylib" OUTPUT_VARIABLE INTERIOR_DLLS)
string(REPLACE "\n" ";" INTERIOR_DLLS_LIST ${INTERIOR_DLLS})
foreach(INTERIOR_DLL IN LISTS INTERIOR_DLLS_LIST)
execute_process(
COMMAND "codesign" "--sign" "$ENV{MACOS_CERTIFICATE_NAME}" "--entitlements" "../.ci/macos.entitlements"
"--options" "runtime" "--force" "--deep" "--timestamp" "--verbose" "${INTERIOR_DLL}"
)
endforeach()
message(STATUS "Signing Exterior Applications ${app_name}.app")
execute_process(
COMMAND "codesign" "--sign" "$ENV{MACOS_CERTIFICATE_NAME}" "--entitlements" "../.ci/macos.entitlements"
"--options" "runtime" "--force" "--deep" "--timestamp" "--verbose" "${FULL_APP_PATH}"
)
endforeach()
endif()

View File

@@ -19,7 +19,7 @@ function(get_commit_id)
PARENT_SCOPE
)
set(PROJECT_VERSION_LABEL
"custom-${GIT_COM_ID}"
"custom(${GIT_COM_ID})"
PARENT_SCOPE
)
endfunction()

View File

@@ -5,173 +5,132 @@
project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(cockatrice_SOURCES
src/game/cards/abstract_card_drag_item.cpp
src/game/cards/abstract_card_item.cpp
src/client/game_logic/abstract_client.cpp
src/game/board/abstract_counter.cpp
src/game/board/abstract_graphics_item.cpp
src/game/board/arrow_item.cpp
src/game/board/arrow_target.cpp
src/game/cards/card_database.cpp
src/game/cards/card_database_manager.cpp
src/game/cards/card_database_model.cpp
src/game/cards/card_database_parser/card_database_parser.cpp
src/game/cards/card_database_parser/cockatrice_xml_3.cpp
src/game/cards/card_database_parser/cockatrice_xml_4.cpp
src/game/cards/card_drag_item.cpp
src/game/filters/filter_card.cpp
src/client/ui/widgets/cards/card_info_frame_widget.cpp
src/client/ui/widgets/cards/card_info_picture_widget.cpp
src/client/ui/widgets/cards/card_info_text_widget.cpp
src/client/ui/widgets/cards/card_info_display_widget.cpp
src/client/ui/widgets/cards/card_size_widget.cpp
src/game/cards/card_item.cpp
src/game/cards/card_list.cpp
src/game/zones/card_zone.cpp
src/server/chat_view/chat_view.cpp
src/game/board/counter_general.cpp
src/deck/custom_line_edit.cpp
src/deck/deck_loader.cpp
src/deck/deck_list_model.cpp
src/deck/deck_stats_interface.cpp
src/deck/deck_view.cpp
src/dialogs/dlg_connect.cpp
src/dialogs/dlg_create_token.cpp
src/dialogs/dlg_create_game.cpp
src/dialogs/dlg_edit_avatar.cpp
src/dialogs/dlg_edit_password.cpp
src/dialogs/dlg_edit_tokens.cpp
src/dialogs/dlg_edit_user.cpp
src/dialogs/dlg_filter_games.cpp
src/dialogs/dlg_forgot_password_challenge.cpp
src/dialogs/dlg_forgot_password_request.cpp
src/dialogs/dlg_forgot_password_reset.cpp
src/dialogs/dlg_load_deck_from_clipboard.cpp
src/dialogs/dlg_load_remote_deck.cpp
src/dialogs/dlg_manage_sets.cpp
src/dialogs/dlg_move_top_cards_until.cpp
src/dialogs/dlg_register.cpp
src/dialogs/dlg_roll_dice.cpp
src/dialogs/dlg_settings.cpp
src/dialogs/dlg_tip_of_the_day.cpp
src/dialogs/dlg_update.cpp
src/dialogs/dlg_view_log.cpp
src/dialogs/dlg_load_deck.cpp
src/game/filters/filter_string.cpp
src/game/filters/filter_builder.cpp
src/game/filters/filter_tree.cpp
src/game/filters/filter_tree_model.cpp
src/client/ui/layouts/flow_layout.cpp
src/client/ui/layouts/horizontal_flow_layout.cpp
src/client/ui/layouts/vertical_flow_layout.cpp
src/client/ui/widgets/general/layout_containers/flow_widget.cpp
src/game/game_scene.cpp
src/game/game_selector.cpp
src/game/games_model.cpp
src/game/game_view.cpp
src/client/get_text_with_max.cpp
src/game/hand_counter.cpp
src/server/handle_public_servers.cpp
src/game/zones/hand_zone.cpp
src/client/game_logic/key_signals.cpp
src/client/ui/line_edit_completer.cpp
src/server/local_client.cpp
src/server/local_server.cpp
src/server/local_server_interface.cpp
src/utility/logger.cpp
src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
src/client/ui/widgets/general/display/labeled_input.cpp
src/client/ui/widgets/general/display/dynamic_font_size_label.cpp
src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp
src/client/ui/widgets/general/display/shadow_background_label.cpp
src/abstractcarddragitem.cpp
src/abstractcarditem.cpp
src/abstractclient.cpp
src/abstractcounter.cpp
src/abstractgraphicsitem.cpp
src/arrowitem.cpp
src/arrowtarget.cpp
src/carddatabase.cpp
src/carddatabasemodel.cpp
src/carddbparser/carddatabaseparser.cpp
src/carddbparser/cockatricexml3.cpp
src/carddbparser/cockatricexml4.cpp
src/carddragitem.cpp
src/cardfilter.cpp
src/cardframe.cpp
src/cardinfopicture.cpp
src/cardinfotext.cpp
src/cardinfowidget.cpp
src/carditem.cpp
src/cardlist.cpp
src/cardzone.cpp
src/chatview/chatview.cpp
src/counter_general.cpp
src/customlineedit.cpp
src/deck_loader.cpp
src/decklistmodel.cpp
src/deckstats_interface.cpp
src/deckview.cpp
src/dlg_connect.cpp
src/dlg_create_token.cpp
src/dlg_creategame.cpp
src/dlg_edit_avatar.cpp
src/dlg_edit_password.cpp
src/dlg_edit_tokens.cpp
src/dlg_edit_user.cpp
src/dlg_filter_games.cpp
src/dlg_forgotpasswordchallenge.cpp
src/dlg_forgotpasswordrequest.cpp
src/dlg_forgotpasswordreset.cpp
src/dlg_load_deck_from_clipboard.cpp
src/dlg_load_remote_deck.cpp
src/dlg_manage_sets.cpp
src/dlg_register.cpp
src/dlg_settings.cpp
src/dlg_tip_of_the_day.cpp
src/dlg_update.cpp
src/dlg_viewlog.cpp
src/filter_string.cpp
src/filterbuilder.cpp
src/filtertree.cpp
src/filtertreemodel.cpp
src/gamescene.cpp
src/gameselector.cpp
src/gamesmodel.cpp
src/gameview.cpp
src/gettextwithmax.cpp
src/handcounter.cpp
src/handle_public_servers.cpp
src/handzone.cpp
src/keysignals.cpp
src/lineeditcompleter.cpp
src/localclient.cpp
src/localserver.cpp
src/localserverinterface.cpp
src/logger.cpp
src/main.cpp
src/server/message_log_widget.cpp
src/client/ui/layouts/overlap_layout.cpp
src/client/ui/widgets/general/layout_containers/overlap_widget.cpp
src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp
src/server/pending_command.cpp
src/game/phase.cpp
src/client/ui/phases_toolbar.cpp
src/client/ui/picture_loader.cpp
src/game/zones/pile_zone.cpp
src/client/ui/pixel_map_generator.cpp
src/game/player/player.cpp
src/game/player/player_list_widget.cpp
src/game/player/player_target.cpp
src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp
src/client/ui/widgets/printing_selector/card_amount_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_view_options_toolbar_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_view_options_widget.cpp
src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
src/client/network/release_channel.cpp
src/client/network/client_update_checker.cpp
src/server/remote/remote_client.cpp
src/server/remote/remote_decklist_tree_widget.cpp
src/server/remote/remote_replay_list_tree_widget.cpp
src/client/network/replay_timeline_widget.cpp
src/game/zones/select_zone.cpp
src/utility/sequence_edit.cpp
src/client/network/sets_model.cpp
src/settings/card_database_settings.cpp
src/settings/download_settings.cpp
src/settings/game_filters_settings.cpp
src/settings/layouts_settings.cpp
src/settings/message_settings.cpp
src/settings/recents_settings.cpp
src/settings/servers_settings.cpp
src/settings/settings_manager.cpp
src/settings/cache_settings.cpp
src/settings/shortcuts_settings.cpp
src/settings/shortcut_treeview.cpp
src/settings/card_override_settings.cpp
src/settings/debug_settings.cpp
src/client/sound_engine.cpp
src/client/network/spoiler_background_updater.cpp
src/game/zones/stack_zone.cpp
src/client/tabs/tab.cpp
src/client/tabs/tab_account.cpp
src/client/tabs/tab_admin.cpp
src/client/tabs/tab_deck_editor.cpp
src/client/tabs/tab_deck_storage.cpp
src/client/tabs/tab_game.cpp
src/client/tabs/tab_logs.cpp
src/client/tabs/tab_message.cpp
src/client/tabs/tab_replays.cpp
src/client/tabs/tab_room.cpp
src/client/tabs/tab_server.cpp
src/client/tabs/tab_supervisor.cpp
src/game/zones/table_zone.cpp
src/client/tapped_out_interface.cpp
src/client/ui/theme_manager.cpp
src/client/ui/tip_of_the_day.cpp
src/client/translate_counter_name.cpp
src/client/update_downloader.cpp
src/server/user/user_context_menu.cpp
src/server/user/user_info_connection.cpp
src/server/user/user_info_box.cpp
src/server/user/user_list.cpp
src/client/ui/window_main.cpp
src/game/zones/view_zone_widget.cpp
src/game/zones/view_zone.cpp
src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
src/messagelogwidget.cpp
src/pending_command.cpp
src/phase.cpp
src/phasestoolbar.cpp
src/pictureloader.cpp
src/pilezone.cpp
src/pixmapgenerator.cpp
src/player.cpp
src/playerlistwidget.cpp
src/playertarget.cpp
src/releasechannel.cpp
src/remoteclient.cpp
src/remotedecklist_treewidget.cpp
src/remotereplaylist_treewidget.cpp
src/replay_timeline_widget.cpp
src/selectzone.cpp
src/sequenceEdit/sequenceedit.cpp
src/setsmodel.cpp
src/settings/carddatabasesettings.cpp
src/settings/downloadsettings.cpp
src/settings/gamefilterssettings.cpp
src/settings/layoutssettings.cpp
src/settings/messagesettings.cpp
src/settings/serverssettings.cpp
src/settings/settingsmanager.cpp
src/settingscache.cpp
src/shortcutssettings.cpp
src/soundengine.cpp
src/spoilerbackgroundupdater.cpp
src/stackzone.cpp
src/tab.cpp
src/tab_account.cpp
src/tab_admin.cpp
src/tab_deck_editor.cpp
src/tab_deck_storage.cpp
src/tab_game.cpp
src/tab_logs.cpp
src/tab_message.cpp
src/tab_replays.cpp
src/tab_room.cpp
src/tab_server.cpp
src/tab_supervisor.cpp
src/tablezone.cpp
src/tappedout_interface.cpp
src/thememanager.cpp
src/tip_of_the_day.cpp
src/translatecountername.cpp
src/update_downloader.cpp
src/user_context_menu.cpp
src/userconnection_information.cpp
src/userinfobox.cpp
src/userlist.cpp
src/window_main.cpp
src/zoneviewwidget.cpp
src/zoneviewzone.cpp
${VERSION_STRING_CPP}
)
add_subdirectory(sounds)
add_subdirectory(themes)
configure_file(
${CMAKE_SOURCE_DIR}/cockatrice/resources/config/qtlogging.ini ${CMAKE_BINARY_DIR}/cockatrice/qtlogging.ini COPYONLY
)
set(cockatrice_RESOURCES cockatrice.qrc)
@@ -292,7 +251,7 @@ if(APPLE)
set(plugin_dest_dir cockatrice.app/Contents/Plugins)
set(qtconf_dest_dir cockatrice.app/Contents/Resources)
# Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6), platforms, printsupport (Qt5), styles, tls (Qt6)
# Qt plugins: audio (Qt5), iconengines, imageformats, platforms, printsupport (Qt5), styles, tls (Qt6)
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@@ -303,7 +262,6 @@ if(APPLE)
PATTERN "audio/*.dylib"
PATTERN "iconengines/*.dylib"
PATTERN "imageformats/*.dylib"
PATTERN "multimedia/*.dylib"
PATTERN "platforms/*.dylib"
PATTERN "printsupport/*.dylib"
PATTERN "styles/*.dylib"
@@ -344,7 +302,7 @@ if(WIN32)
PATTERN "*.dll"
)
# Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6) platforms, printsupport (Qt5), styles, tls (Qt6)
# Qt plugins: audio (Qt5), iconengines, imageformats, platforms, printsupport (Qt5), styles, tls (Qt6)
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@@ -356,7 +314,6 @@ if(WIN32)
PATTERN "imageformats/*.dll"
PATTERN "mediaservice/dsengine.dll"
PATTERN "mediaservice/wmfengine.dll"
PATTERN "multimedia/*.dll"
PATTERN "platforms/qdirect2d.dll"
PATTERN "platforms/qminimal.dll"
PATTERN "platforms/qoffscreen.dll"

View File

@@ -29,7 +29,6 @@
<file>resources/icons/search.svg</file>
<file>resources/icons/settings.svg</file>
<file>resources/icons/spectator.svg</file>
<file>resources/icons/swap.svg</file>
<file>resources/icons/sync.svg</file>
<file>resources/icons/tab_changed.svg</file>
<file>resources/icons/update.png</file>
@@ -42,8 +41,6 @@
<file>resources/config/deckeditor.svg</file>
<file>resources/config/shorcuts.svg</file>
<file>resources/config/sound.svg</file>
<file>resources/config/debug.ini</file>
<file>resources/config/qtlogging.ini</file>
<file>resources/counters/w.svg</file>
<file>resources/counters/w_highlight.svg</file>

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
[debug]
showCardId=false
[localgame]
onStartup=false
playerCount=1
;deck\Player 1=path/to/deck
;deck\Player 2=path/to/deck
; Fun Fact: You can assign a deck to your username and it will auto load and ready when you join a server game
;deck\Your Username Here=path/to/deck

View File

@@ -1,2 +0,0 @@
[Rules]
picture_loader.debug = true

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000" version="1.1" id="Capa_1"
xmlns="http://www.w3.org/2000/svg"
width="800px" height="800px" viewBox="0 0 71.753 71.753"
xml:space="preserve">
<g>
<path d="M39.798,20.736H28.172v20.738L11.625,41.47V20.736H0L19.899,0.839L39.798,20.736z M51.855,70.914l19.897-19.896H60.129
V30.282l-16.547-0.004v20.74H31.957L51.855,70.914z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 648 B

View File

@@ -1,6 +1,6 @@
#include "abstract_card_drag_item.h"
#include "abstractcarddragitem.h"
#include "card_database.h"
#include "carddatabase.h"
#include <QCursor>
#include <QDebug>

View File

@@ -1,7 +1,7 @@
#ifndef ABSTRACTCARDDRAGITEM_H
#define ABSTRACTCARDDRAGITEM_H
#include "abstract_card_item.h"
#include "abstractcarditem.h"
class QGraphicsScene;
class CardZone;

View File

@@ -1,10 +1,10 @@
#include "abstract_card_item.h"
#include "abstractcarditem.h"
#include "../../client/ui/picture_loader.h"
#include "../../settings/cache_settings.h"
#include "../game_scene.h"
#include "card_database.h"
#include "card_database_manager.h"
#include "carddatabase.h"
#include "gamescene.h"
#include "main.h"
#include "pictureloader.h"
#include "settingscache.h"
#include <QCursor>
#include <QGraphicsScene>
@@ -12,20 +12,16 @@
#include <QPainter>
#include <algorithm>
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent,
const QString &_name,
const QString &_providerId,
Player *_owner,
int _id)
: ArrowTarget(_owner, parent), id(_id), name(_name), providerId(_providerId), tapped(false), facedown(false),
tapAngle(0), bgColor(Qt::transparent), isHovered(false), realZValue(0)
AbstractCardItem::AbstractCardItem(const QString &_name, Player *_owner, int _id, QGraphicsItem *parent)
: ArrowTarget(_owner, parent), id(_id), name(_name), tapped(false), facedown(false), tapAngle(0),
bgColor(Qt::transparent), isHovered(false), realZValue(0)
{
setCursor(Qt::OpenHandCursor);
setFlag(ItemIsSelectable);
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
refreshCardInfo();
connect(&SettingsCache::instance(), SIGNAL(displayCardNamesChanged()), this, SLOT(callUpdate()));
cardInfoUpdated();
}
AbstractCardItem::~AbstractCardItem()
@@ -51,18 +47,18 @@ void AbstractCardItem::pixmapUpdated()
emit sigPixmapUpdated();
}
void AbstractCardItem::refreshCardInfo()
void AbstractCardItem::cardInfoUpdated()
{
info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId(name, providerId);
info = db->getCard(name);
if (!info && !name.isEmpty()) {
QVariantHash properties = QVariantHash();
info = CardInfo::newInstance(name, "", true, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
CardInfoPerSetMap(), false, false, -1, false);
CardInfoPerSetMap(), false, -1, false);
}
if (info.data()) {
connect(info.data(), &CardInfo::pixmapUpdated, this, &AbstractCardItem::pixmapUpdated);
connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated()));
}
cacheBgColor();
@@ -141,13 +137,8 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
QString nameStr;
if (facedown)
nameStr = "# " + QString::number(id);
else {
QString prefix = "";
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " ";
}
nameStr = prefix + name;
}
else
nameStr = name;
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
translatedSize.height() - 6 * scaleFactor),
Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere, nameStr);
@@ -190,22 +181,7 @@ void AbstractCardItem::setName(const QString &_name)
disconnect(info.data(), nullptr, this, nullptr);
name = _name;
refreshCardInfo();
}
void AbstractCardItem::setProviderId(const QString &_providerId)
{
if (providerId == _providerId) {
return;
}
emit deleteCardInfoPopup(name);
if (info) {
disconnect(info.data(), nullptr, this, nullptr);
}
providerId = _providerId;
refreshCardInfo();
cardInfoUpdated();
}
void AbstractCardItem::setHovered(bool _hovered)
@@ -301,7 +277,7 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
if (event->button() == Qt::LeftButton)
setCursor(Qt::ClosedHandCursor);
else if (event->button() == Qt::MiddleButton)
emit showCardInfoPopup(event->screenPos(), name, providerId);
emit showCardInfoPopup(event->screenPos(), name);
event->accept();
}

View File

@@ -1,8 +1,8 @@
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
#include "../board/arrow_target.h"
#include "card_database.h"
#include "arrowtarget.h"
#include "carddatabase.h"
class Player;
@@ -16,7 +16,6 @@ protected:
CardInfoPtr info;
int id;
QString name;
QString providerId;
bool tapped;
bool facedown;
int tapAngle;
@@ -28,13 +27,14 @@ private:
qreal realZValue;
private slots:
void pixmapUpdated();
public slots:
void refreshCardInfo();
void cardInfoUpdated();
void callUpdate()
{
update();
}
signals:
void hovered(AbstractCardItem *card);
void showCardInfoPopup(const QPoint &pos, const QString &cardName, const QString &providerId);
void showCardInfoPopup(QPoint pos, QString cardName);
void deleteCardInfoPopup(QString cardName);
void sigPixmapUpdated();
void cardShiftClicked(QString cardName);
@@ -48,11 +48,10 @@ public:
{
return Type;
}
AbstractCardItem(QGraphicsItem *parent = nullptr,
const QString &_name = QString(),
const QString &_providerId = QString(),
AbstractCardItem(const QString &_name = QString(),
Player *_owner = nullptr,
int _id = -1);
int _id = -1,
QGraphicsItem *parent = nullptr);
~AbstractCardItem();
QRectF boundingRect() const override;
QPainterPath shape() const override;
@@ -76,11 +75,6 @@ public:
return name;
}
void setName(const QString &_name = QString());
QString getProviderId() const
{
return providerId;
}
void setProviderId(const QString &_providerId = QString());
qreal getRealZValue() const
{
return realZValue;

View File

@@ -1,6 +1,5 @@
#include "abstract_client.h"
#include "abstractclient.h"
#include "../../server/pending_command.h"
#include "featureset.h"
#include "get_pb_extension.h"
#include "pb/commands.pb.h"
@@ -18,6 +17,7 @@
#include "pb/event_user_left.pb.h"
#include "pb/event_user_message.pb.h"
#include "pb/server_message.pb.h"
#include "pending_command.h"
#include <google/protobuf/descriptor.h>
@@ -52,7 +52,7 @@ AbstractClient::AbstractClient(QObject *parent)
FeatureSet features;
features.initalizeFeatureList(clientFeatures);
connect(this, &AbstractClient::sigQueuePendingCommand, this, &AbstractClient::queuePendingCommand);
connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *)));
}
AbstractClient::~AbstractClient()

View File

@@ -48,7 +48,6 @@ class AbstractClient : public QObject
Q_OBJECT
signals:
void statusChanged(ClientStatus _status);
void maxPingTime(int seconds, int maxSeconds);
// Room events
void roomEventReceived(const RoomEvent &event);

View File

@@ -1,11 +1,11 @@
#include "abstract_counter.h"
#include "abstractcounter.h"
#include "../../client/translate_counter_name.h"
#include "../../settings/cache_settings.h"
#include "../player/player.h"
#include "expression.h"
#include "pb/command_inc_counter.pb.h"
#include "pb/command_set_counter.pb.h"
#include "player.h"
#include "settingscache.h"
#include "translatecountername.h"
#include <QAction>
#include <QApplication>
@@ -36,7 +36,7 @@ AbstractCounter::AbstractCounter(Player *_player,
QString displayName = TranslateCounterName::getDisplayName(_name);
menu = new TearOffMenu(displayName);
aSet = new QAction(this);
connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter);
connect(aSet, SIGNAL(triggered()), this, SLOT(setCounter()));
menu->addAction(aSet);
menu->addSeparator();
for (int i = 10; i >= -10; --i) {
@@ -49,7 +49,7 @@ AbstractCounter::AbstractCounter(Player *_player,
else if (i == 1)
aInc = aIncrement;
aIncrement->setData(i);
connect(aIncrement, &QAction::triggered, this, &AbstractCounter::incrementCounter);
connect(aIncrement, SIGNAL(triggered()), this, SLOT(incrementCounter()));
menu->addAction(aIncrement);
}
}
@@ -57,8 +57,7 @@ AbstractCounter::AbstractCounter(Player *_player,
menu = nullptr;
}
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&AbstractCounter::refreshShortcuts);
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts();
retranslateUi();
}

View File

@@ -1,7 +1,7 @@
#ifndef COUNTER_H
#define COUNTER_H
#include "../../client/tearoff_menu.h"
#include "tearoffmenu.h"
#include <QGraphicsItem>
#include <QInputDialog>

View File

@@ -1,4 +1,4 @@
#include "abstract_graphics_item.h"
#include "abstractgraphicsitem.h"
#include <QPainter>

View File

@@ -13,9 +13,6 @@ enum GraphicsItemType
typeOther = QGraphicsItem::UserType + 6
};
/**
* Parent class of all objects that appear in a game.
*/
class AbstractGraphicsItem : public QObject, public QGraphicsItem
{
Q_OBJECT

View File

@@ -1,16 +1,16 @@
#define _USE_MATH_DEFINES
#include "arrow_item.h"
#include "arrowitem.h"
#include "../../settings/cache_settings.h"
#include "../cards/card_database.h"
#include "../cards/card_item.h"
#include "../player/player.h"
#include "../player/player_target.h"
#include "../zones/card_zone.h"
#include "carddatabase.h"
#include "carditem.h"
#include "cardzone.h"
#include "color.h"
#include "pb/command_attach_card.pb.h"
#include "pb/command_create_arrow.pb.h"
#include "pb/command_delete_arrow.pb.h"
#include "player.h"
#include "playertarget.h"
#include "settingscache.h"
#include <QDebug>
#include <QGraphicsScene>
@@ -19,8 +19,8 @@
#include <QtMath>
ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false),
color(_color), fullColor(true)
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color),
fullColor(true)
{
qDebug() << "ArrowItem constructor: startItem=" << static_cast<QGraphicsItem *>(startItem);
setZValue(2000000005);
@@ -167,7 +167,7 @@ void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// This ensures that if a mouse move event happens after a call to delArrow(),
// the event will be discarded as it would create some stray pointers.
if (targetLocked || !startItem)
if (!startItem)
return;
QPointF endPos = event->scenePos();
@@ -268,7 +268,7 @@ void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow)
void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (targetLocked || !startItem)
if (!startItem)
return;
QPointF endPos = event->scenePos();
@@ -307,48 +307,25 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
}
}
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
// do nothing if target is already attached to another card or is not in play
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != "table") {
return;
}
CardZone *startZone = startCard->getZone();
CardZone *targetZone = targetCard->getZone();
// move card onto table first if attaching from some other zone
if (startZone->getName() != "table") {
player->playCardToTable(startCard, false);
}
Command_AttachCard cmd;
cmd.set_start_zone("table");
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetZone->getPlayer()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
player->sendGameCommand(cmd);
}
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem)
return;
// Attaching could move startItem under the current cursor position, causing all children to retarget to it right
// before they are processed. Prevent that.
for (ArrowAttachItem *child : childArrows) {
child->setTargetLocked(true);
}
if (targetItem && (targetItem != startItem)) {
auto startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (startCard && targetCard) {
attachCards(startCard, targetCard);
}
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
CardZone *startZone = startCard->getZone();
CardItem *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
CardZone *targetZone = targetCard->getZone();
Command_AttachCard cmd;
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetZone->getPlayer()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
player->sendGameCommand(cmd);
}
delArrow();

View File

@@ -21,7 +21,6 @@ protected:
Player *player;
int id;
ArrowTarget *startItem, *targetItem;
bool targetLocked;
QColor color;
bool fullColor;
void mousePressEvent(QGraphicsSceneMouseEvent *event);
@@ -65,10 +64,6 @@ public:
{
return targetItem;
}
void setTargetLocked(bool _targetLocked)
{
targetLocked = _targetLocked;
}
void delArrow();
};
@@ -93,8 +88,6 @@ class ArrowAttachItem : public ArrowItem
private:
QList<ArrowAttachItem *> childArrows;
void attachCards(CardItem *startCard, const CardItem *targetCard);
public:
ArrowAttachItem(ArrowTarget *_startItem);
void addChildArrow(ArrowAttachItem *childArrow);

View File

@@ -1,7 +1,7 @@
#include "arrow_target.h"
#include "arrowtarget.h"
#include "../player/player.h"
#include "arrow_item.h"
#include "arrowitem.h"
#include "player.h"
ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent)
: AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false)

View File

@@ -1,7 +1,7 @@
#ifndef ARROWTARGET_H
#define ARROWTARGET_H
#include "abstract_graphics_item.h"
#include "abstractgraphicsitem.h"
#include <QList>

View File

@@ -1,12 +1,11 @@
#include "card_database.h"
#include "carddatabase.h"
#include "../../client/network/spoiler_background_updater.h"
#include "../../client/ui/picture_loader.h"
#include "../../settings/cache_settings.h"
#include "../../utility/card_set_comparator.h"
#include "../game_specific_terms.h"
#include "./card_database_parser/cockatrice_xml_3.h"
#include "./card_database_parser/cockatrice_xml_4.h"
#include "carddbparser/cockatricexml3.h"
#include "carddbparser/cockatricexml4.h"
#include "game_specific_terms.h"
#include "pictureloader.h"
#include "settingscache.h"
#include "spoilerbackgroundupdater.h"
#include <QCryptographicHash>
#include <QDebug>
@@ -23,9 +22,8 @@ const char *CardDatabase::TOKENS_SETNAME = "TK";
CardSet::CardSet(const QString &_shortName,
const QString &_longName,
const QString &_setType,
const QDate &_releaseDate,
const CardSet::Priority _priority)
: shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority)
const QDate &_releaseDate)
: shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType)
{
loadSetOptions();
}
@@ -33,10 +31,9 @@ CardSet::CardSet(const QString &_shortName,
CardSetPtr CardSet::newInstance(const QString &_shortName,
const QString &_longName,
const QString &_setType,
const QDate &_releaseDate,
const Priority _priority)
const QDate &_releaseDate)
{
CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority));
CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate));
// ptr->setSmartPointer(ptr);
return ptr;
}
@@ -197,29 +194,23 @@ void SetList::markAllAsKnown()
void SetList::guessSortKeys()
{
defaultSort();
// sort by release date DESC; invalid dates to the bottom.
QDate distantFuture(2050, 1, 1);
int aHundredYears = 36500;
for (int i = 0; i < size(); ++i) {
CardSetPtr set = at(i);
if (set.isNull()) {
qDebug() << "guessSortKeys set is null";
continue;
}
set->setSortKey(i);
}
}
void SetList::defaultSort()
{
std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) {
// Sort by priority, then by release date, then by short name
if (a->getPriority() != b->getPriority()) {
return a->getPriority() < b->getPriority(); // lowest first
} else if (a->getReleaseDate() != b->getReleaseDate()) {
return a->getReleaseDate() > b->getReleaseDate(); // most recent first
QDate date = set->getReleaseDate();
if (date.isNull()) {
set->setSortKey(static_cast<unsigned int>(aHundredYears));
} else {
return a->getShortName() < b->getShortName(); // alphabetically
set->setSortKey(static_cast<unsigned int>(date.daysTo(distantFuture)));
}
});
}
}
CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set)
@@ -234,12 +225,11 @@ CardInfo::CardInfo(const QString &_name,
const QList<CardRelation *> &_reverseRelatedCards,
CardInfoPerSetMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt)
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt),
landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt)
reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), tableRow(_tableRow),
upsideDownArt(_upsideDownArt)
{
pixmapCacheKey = QLatin1String("card_") + name;
simpleName = CardInfo::simplifyName(name);
@@ -252,12 +242,6 @@ CardInfo::~CardInfo()
PictureLoader::clearPixmapCache(smartThis);
}
CardInfoPtr CardInfo::newInstance(const QString &_name)
{
return newInstance(_name, QString(), false, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
CardInfoPerSetMap(), false, false, 0, false);
}
CardInfoPtr CardInfo::newInstance(const QString &_name,
const QString &_text,
bool _isToken,
@@ -266,19 +250,15 @@ CardInfoPtr CardInfo::newInstance(const QString &_name,
const QList<CardRelation *> &_reverseRelatedCards,
CardInfoPerSetMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt)
{
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
_sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt));
_sets, _cipt, _tableRow, _upsideDownArt));
ptr->setSmartPointer(ptr);
for (const auto &cardInfoPerSetList : _sets) {
for (const CardInfoPerSet &set : cardInfoPerSetList) {
set.getPtr()->append(ptr);
break;
}
for (const CardInfoPerSet &set : _sets) {
set.getPtr()->append(ptr);
}
return ptr;
@@ -299,32 +279,18 @@ QString CardInfo::getCorrectedName() const
void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info)
{
_set->append(smartThis);
sets[_set->getShortName()].append(_info);
sets.insert(_set->getShortName(), _info);
refreshCachedSetNames();
}
void CardInfo::combineLegalities(const QVariantHash &props)
{
QHashIterator<QString, QVariant> it(props);
while (it.hasNext()) {
it.next();
if (it.key().startsWith("format-")) {
smartThis->setProperty(it.key(), it.value().toString());
}
}
}
void CardInfo::refreshCachedSetNames()
{
QStringList setList;
// update the cached list of set names
for (const auto &cardInfoPerSetList : sets) {
for (const auto &set : cardInfoPerSetList) {
if (set.getPtr()->getEnabled()) {
setList << set.getPtr()->getShortName();
}
break;
for (const auto &set : sets) {
if (set.getPtr()->getEnabled()) {
setList << set.getPtr()->getShortName();
}
}
setsNames = setList.join(", ");
@@ -375,12 +341,11 @@ CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoa
availableParsers << new CockatriceXml3Parser;
for (auto &parser : availableParsers) {
connect(parser, &ICardDatabaseParser::addCard, this, &CardDatabase::addCard, Qt::DirectConnection);
connect(parser, &ICardDatabaseParser::addSet, this, &CardDatabase::addSet, Qt::DirectConnection);
connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection);
connect(parser, SIGNAL(addSet(CardSetPtr)), this, SLOT(addSet(CardSetPtr)), Qt::DirectConnection);
}
connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this,
&CardDatabase::loadCardDatabases);
connect(&SettingsCache::instance(), SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabases()));
}
CardDatabase::~CardDatabase()
@@ -422,10 +387,8 @@ void CardDatabase::addCard(CardInfoPtr card)
// if card already exists just add the new set property
if (cards.contains(card->getName())) {
CardInfoPtr sameCard = cards[card->getName()];
for (const auto &cardInfoPerSetList : card->getSets()) {
for (const CardInfoPerSet &set : cardInfoPerSetList) {
sameCard->addToSet(set.getPtr(), set);
}
for (const CardInfoPerSet &set : card->getSets()) {
sameCard->addToSet(set.getPtr(), set);
}
return;
}
@@ -477,39 +440,18 @@ QList<CardInfoPtr> CardDatabase::getCards(const QStringList &cardNames) const
return cardInfos;
}
CardInfoPtr CardDatabase::getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const
{
auto info = getCard(cardName);
if (providerId.isNull() || providerId.isEmpty() || info.isNull()) {
return info;
}
for (const auto &cardInfoPerSetList : info->getSets()) {
for (const auto &set : cardInfoPerSetList) {
if (set.getProperty("uuid") == providerId) {
CardInfoPtr cardFromSpecificSet = info->clone();
cardFromSpecificSet->setPixmapCacheKey(QLatin1String("card_") + QString(info->getName()) +
QString("_") + QString(set.getProperty("uuid")));
return cardFromSpecificSet;
}
}
}
return {};
}
CardInfoPtr CardDatabase::getCardBySimpleName(const QString &cardName) const
{
return getCardFromMap(simpleNameCards, CardInfo::simplifyName(cardName));
}
CardInfoPtr CardDatabase::guessCard(const QString &cardName, const QString &providerId) const
CardInfoPtr CardDatabase::guessCard(const QString &cardName) const
{
CardInfoPtr temp = providerId.isEmpty() ? getCard(cardName) : getCardByNameAndProviderId(cardName, providerId);
CardInfoPtr temp = getCard(cardName);
if (temp == nullptr) { // get card by simple name instead
temp = getCardBySimpleName(cardName);
if (temp == nullptr) { // still could not find the card, so simplify the cardName too
const auto &simpleCardName = CardInfo::simplifyName(cardName);
QString simpleCardName = CardInfo::simplifyName(cardName);
temp = getCardBySimpleName(simpleCardName);
}
}
@@ -619,15 +561,12 @@ LoadStatus CardDatabase::loadCardDatabases()
// AFTER all the cards have been loaded
// Refresh the pixmap cache keys for all cards by setting them to the UUID of the preferred printing
refreshPreferredPrintings();
// resolve the reverse-related tags
refreshCachedReverseRelatedCards();
if (loadStatus == Ok) {
checkUnknownSets(); // update deck editors, etc
qDebug() << "CardDatabase::loadCardDatabases success";
emit cardDatabaseLoadingFinished();
} else {
qDebug() << "CardDatabase::loadCardDatabases failed";
emit cardDatabaseLoadingFailed(); // bring up the settings dialog
@@ -637,142 +576,6 @@ LoadStatus CardDatabase::loadCardDatabases()
return loadStatus;
}
void CardDatabase::refreshPreferredPrintings()
{
for (const CardInfoPtr &card : cards) {
card->setPixmapCacheKey(QLatin1String("card_") + QString(card->getName()) + QString("_") +
QString(getPreferredPrintingProviderIdForCard(card->getName())));
}
}
CardInfoPerSet CardDatabase::getPreferredSetForCard(const QString &cardName) const
{
CardInfoPtr cardInfo = getCard(cardName);
if (!cardInfo) {
return CardInfoPerSet(nullptr);
}
CardInfoPerSetMap setMap = cardInfo->getSets();
if (setMap.empty()) {
return CardInfoPerSet(nullptr);
}
CardSetPtr preferredSet = nullptr;
CardInfoPerSet preferredCard;
SetPriorityComparator comparator;
for (const auto &cardInfoPerSetList : setMap) {
for (auto &cardInfoForSet : cardInfoPerSetList) {
CardSetPtr currentSet = cardInfoForSet.getPtr();
if (!preferredSet || comparator(currentSet, preferredSet)) {
preferredSet = currentSet;
preferredCard = cardInfoForSet;
}
}
}
if (preferredSet) {
return preferredCard;
}
return CardInfoPerSet(nullptr);
}
CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, const QString &providerId) const
{
CardInfoPtr cardInfo = getCard(cardName);
if (!cardInfo) {
return CardInfoPerSet(nullptr);
}
CardInfoPerSetMap setMap = cardInfo->getSets();
if (setMap.empty()) {
return CardInfoPerSet(nullptr);
}
for (const auto &cardInfoPerSetList : setMap) {
for (auto &cardInfoForSet : cardInfoPerSetList) {
if (cardInfoForSet.getProperty("uuid") == providerId) {
return cardInfoForSet;
}
}
}
if (providerId.isNull()) {
return getPreferredSetForCard(cardName);
}
return CardInfoPerSet(nullptr);
}
CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName,
const QString &setShortName,
const QString &collectorNumber) const
{
CardInfoPtr cardInfo = getCard(cardName);
if (!cardInfo) {
return CardInfoPerSet(nullptr);
}
CardInfoPerSetMap setMap = cardInfo->getSets();
if (setMap.empty()) {
return CardInfoPerSet(nullptr);
}
for (const auto &cardInfoPerSetList : setMap) {
for (auto &cardInfoForSet : cardInfoPerSetList) {
if (cardInfoForSet.getPtr()->getShortName() == setShortName &&
cardInfoForSet.getProperty("num") == collectorNumber) {
return cardInfoForSet;
}
}
}
return CardInfoPerSet(nullptr);
}
QString CardDatabase::getPreferredPrintingProviderIdForCard(const QString &cardName)
{
CardInfoPerSet preferredSetCardInfo = getPreferredSetForCard(cardName);
QString preferredPrintingProviderId = preferredSetCardInfo.getProperty(QString("uuid"));
if (preferredPrintingProviderId.isEmpty()) {
CardInfoPtr defaultCardInfo = getCard(cardName);
if (defaultCardInfo.isNull()) {
return cardName;
}
return defaultCardInfo->getName();
}
return preferredPrintingProviderId;
}
bool CardDatabase::isProviderIdForPreferredPrinting(const QString &cardName, const QString &providerId)
{
if (providerId.startsWith("card_")) {
return providerId ==
QLatin1String("card_") + cardName + QString("_") + getPreferredPrintingProviderIdForCard(cardName);
}
return providerId == getPreferredPrintingProviderIdForCard(cardName);
}
CardInfoPerSet CardDatabase::getSetInfoForCard(const CardInfoPtr &_card)
{
const CardInfoPerSetMap &setMap = _card->getSets();
if (setMap.empty()) {
return CardInfoPerSet(nullptr);
}
for (const auto &cardInfoPerSetList : setMap) {
for (const auto &cardInfoForSet : cardInfoPerSetList) {
if (QLatin1String("card_") + _card->getName() + QString("_") + cardInfoForSet.getProperty("uuid") ==
_card->getPixmapCacheKey()) {
return cardInfoForSet;
}
}
}
return CardInfoPerSet(nullptr);
}
void CardDatabase::refreshCachedReverseRelatedCards()
{
for (const CardInfoPtr &card : cards)

View File

@@ -23,42 +23,28 @@ class ICardDatabaseParser;
typedef QMap<QString, QString> QStringMap;
typedef QSharedPointer<CardInfo> CardInfoPtr;
typedef QSharedPointer<CardSet> CardSetPtr;
typedef QMap<QString, QList<CardInfoPerSet>> CardInfoPerSetMap;
typedef QMap<QString, CardInfoPerSet> CardInfoPerSetMap;
Q_DECLARE_METATYPE(CardInfoPtr)
class CardSet : public QList<CardInfoPtr>
{
public:
enum Priority
{
PriorityFallback = 0,
PriorityPrimary = 10,
PrioritySecondary = 20,
PriorityReprint = 30,
PriorityOther = 40,
PriorityLowest = 100,
};
private:
QString shortName, longName;
unsigned int sortKey;
QDate releaseDate;
QString setType;
Priority priority;
bool enabled, isknown;
public:
explicit CardSet(const QString &_shortName = QString(),
const QString &_longName = QString(),
const QString &_setType = QString(),
const QDate &_releaseDate = QDate(),
const Priority _priority = PriorityFallback);
const QDate &_releaseDate = QDate());
static CardSetPtr newInstance(const QString &_shortName = QString(),
const QString &_longName = QString(),
const QString &_setType = QString(),
const QDate &_releaseDate = QDate(),
const Priority _priority = PriorityFallback);
const QDate &_releaseDate = QDate());
QString getCorrectedShortName() const;
QString getShortName() const
{
@@ -76,10 +62,6 @@ public:
{
return releaseDate;
}
Priority getPriority() const
{
return priority;
}
void setLongName(const QString &_longName)
{
longName = _longName;
@@ -92,10 +74,6 @@ public:
{
releaseDate = _releaseDate;
}
void setPriority(const Priority _priority)
{
priority = _priority;
}
void loadSetOptions();
int getSortKey() const
@@ -135,7 +113,6 @@ public:
int getEnabledSetsNum();
int getUnknownSetsNum();
QStringList getUnknownSetsNames();
void defaultSort();
};
class CardInfoPerSet
@@ -197,53 +174,32 @@ private:
QString setsNames;
// positioning properties; used by UI
bool cipt;
bool landscapeOrientation;
int tableRow;
bool upsideDownArt;
public:
explicit CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
CardInfoPerSetMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
CardInfo(const CardInfo &other)
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey),
text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
sets(other.sets), setsNames(other.setsNames), cipt(other.cipt),
landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt)
{
}
explicit CardInfo(const QString &_name = QString(),
const QString &_text = QString(),
bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
bool _cipt = false,
int _tableRow = 0,
bool _upsideDownArt = false);
~CardInfo() override;
static CardInfoPtr newInstance(const QString &_name);
static CardInfoPtr newInstance(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
CardInfoPerSetMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
CardInfoPtr clone() const
{
// Use the copy constructor to create a new instance
CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this));
newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance
return newCardInfo;
}
static CardInfoPtr newInstance(const QString &_name = QString(),
const QString &_text = QString(),
bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
bool _cipt = false,
int _tableRow = 0,
bool _upsideDownArt = false);
void setSmartPointer(CardInfoPtr _ptr)
{
@@ -259,10 +215,6 @@ public:
{
return simpleName;
}
void setPixmapCacheKey(QString _pixmapCacheKey)
{
pixmapCacheKey = _pixmapCacheKey;
}
const QString &getPixmapCacheKey() const
{
return pixmapCacheKey;
@@ -311,15 +263,15 @@ public:
{
if (!sets.contains(setName))
return "";
return sets[setName].getProperty(propertyName);
}
void setSetProperty(const QString &setName, const QString &_name, const QString &_value)
{
if (!sets.contains(setName))
return;
for (const auto &set : sets[setName]) {
if (QLatin1String("card_") + this->getName() + QString("_") + QString(set.getProperty("uuid")) ==
this->getPixmapCacheKey()) {
return set.getProperty(propertyName);
}
}
return sets[setName][0].getProperty(propertyName);
sets[setName].setProperty(_name, _value);
emit cardInfoChanged(smartThis);
}
// related cards
@@ -353,10 +305,6 @@ public:
{
return cipt;
}
bool getLandscapeOrientation() const
{
return landscapeOrientation;
}
int getTableRow() const
{
return tableRow;
@@ -390,7 +338,6 @@ public:
}
QString getCorrectedName() const;
void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet());
void combineLegalities(const QVariantHash &props);
void emitPixmapUpdated()
{
emit pixmapUpdated();
@@ -460,28 +407,20 @@ public:
~CardDatabase() override;
void clear();
void removeCard(CardInfoPtr card);
[[nodiscard]] CardInfoPtr getCard(const QString &cardName) const;
[[nodiscard]] QList<CardInfoPtr> getCards(const QStringList &cardNames) const;
[[nodiscard]] CardInfoPtr getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const;
[[nodiscard]] CardInfoPerSet getPreferredSetForCard(const QString &cardName) const;
[[nodiscard]] CardInfoPerSet getSpecificSetForCard(const QString &cardName, const QString &providerId) const;
CardInfoPerSet
getSpecificSetForCard(const QString &cardName, const QString &setShortName, const QString &collectorNumber) const;
QString getPreferredPrintingProviderIdForCard(const QString &cardName);
[[nodiscard]] CardInfoPtr guessCard(const QString &cardName, const QString &providerId = QString()) const;
CardInfoPtr getCard(const QString &cardName) const;
QList<CardInfoPtr> getCards(const QStringList &cardNames) const;
CardInfoPtr guessCard(const QString &cardName) const;
/*
* Get a card by its simple name. The name will be simplified in this
* function, so you don't need to simplify it beforehand.
*/
[[nodiscard]] CardInfoPtr getCardBySimpleName(const QString &cardName) const;
CardInfoPtr getCardBySimpleName(const QString &cardName) const;
CardSetPtr getSet(const QString &setName);
bool isProviderIdForPreferredPrinting(const QString &cardName, const QString &providerId);
static CardInfoPerSet getSetInfoForCard(const CardInfoPtr &_card);
const CardNameMap &getCardList() const
QList<CardInfoPtr> getCardList() const
{
return cards;
return cards.values();
}
SetList getSetList() const;
LoadStatus loadFromFile(const QString &fileName);
@@ -497,13 +436,11 @@ public:
public slots:
LoadStatus loadCardDatabases();
void refreshPreferredPrintings();
void addCard(CardInfoPtr card);
void addSet(CardSetPtr set);
protected slots:
LoadStatus loadCardDatabase(const QString &path);
signals:
void cardDatabaseLoadingFinished();
void cardDatabaseLoadingFailed();
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
void cardDatabaseAllNewSetsEnabled();

View File

@@ -1,6 +1,6 @@
#include "card_database_model.h"
#include "carddatabasemodel.h"
#include "../filters/filter_tree.h"
#include "filtertree.h"
#include <QMap>
@@ -9,10 +9,9 @@
CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent)
: QAbstractListModel(parent), db(_db), showOnlyCardsFromEnabledSets(_showOnlyCardsFromEnabledSets)
{
connect(db, &CardDatabase::cardAdded, this, &CardDatabaseModel::cardAdded);
connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseModel::cardRemoved);
connect(db, &CardDatabase::cardDatabaseEnabledSetsChanged, this,
&CardDatabaseModel::cardDatabaseEnabledSetsChanged);
connect(db, SIGNAL(cardAdded(CardInfoPtr)), this, SLOT(cardAdded(CardInfoPtr)));
connect(db, SIGNAL(cardRemoved(CardInfoPtr)), this, SLOT(cardRemoved(CardInfoPtr)));
connect(db, SIGNAL(cardDatabaseEnabledSetsChanged()), this, SLOT(cardDatabaseEnabledSetsChanged()));
cardDatabaseEnabledSetsChanged();
}
@@ -98,11 +97,9 @@ bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card)
if (!showOnlyCardsFromEnabledSets)
return true;
for (const auto &cardInfoPerSetList : card->getSets()) {
for (const auto &set : cardInfoPerSetList) {
if (set.getPtr()->getEnabled())
return true;
}
for (const auto &set : card->getSets()) {
if (set.getPtr()->getEnabled())
return true;
}
return false;
@@ -111,17 +108,15 @@ bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card)
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
{
// remove all the cards no more present in at least one enabled set
for (const CardInfoPtr &card : cardList) {
if (!checkCardHasAtLeastOneEnabledSet(card)) {
foreach (CardInfoPtr card, cardList) {
if (!checkCardHasAtLeastOneEnabledSet(card))
cardRemoved(card);
}
}
// re-check all the card currently not shown, maybe their part of a newly-enabled set
for (const CardInfoPtr &card : db->getCardList()) {
if (!cardListSet.contains(card)) {
foreach (CardInfoPtr card, db->getCardList()) {
if (!cardList.contains(card))
cardAdded(card);
}
}
}
@@ -131,8 +126,7 @@ void CardDatabaseModel::cardAdded(CardInfoPtr card)
// add the card if it's present in at least one enabled set
beginInsertRows(QModelIndex(), cardList.size(), cardList.size());
cardList.append(card);
cardListSet.insert(card);
connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged);
connect(card.data(), SIGNAL(cardInfoChanged(CardInfoPtr)), this, SLOT(cardInfoChanged(CardInfoPtr)));
endInsertRows();
}
}
@@ -146,7 +140,6 @@ void CardDatabaseModel::cardRemoved(CardInfoPtr card)
beginRemoveRows(QModelIndex(), row, row);
disconnect(card.data(), nullptr, this, nullptr);
cardListSet.remove(card);
card.clear();
cardList.removeAt(row);
endRemoveRows();
@@ -175,10 +168,6 @@ void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index)
int remainder = sourceModel()->rowCount(index) - loadedRowCount;
int itemsToFetch = qMin(100, remainder);
if (itemsToFetch == 0) {
return;
}
beginInsertRows(QModelIndex(), loadedRowCount, loadedRowCount + itemsToFetch - 1);
loadedRowCount += itemsToFetch;
@@ -187,7 +176,7 @@ void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index)
int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
{
return QSortFilterProxyModel::rowCount(parent);
return qMin(QSortFilterProxyModel::rowCount(parent), loadedRowCount);
}
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
@@ -343,7 +332,7 @@ void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
disconnect(this->filterTree, nullptr, this, nullptr);
this->filterTree = _filterTree;
connect(this->filterTree, &FilterTree::changed, this, &CardDatabaseDisplayModel::filterTreeChanged);
connect(this->filterTree, SIGNAL(changed()), this, SLOT(filterTreeChanged()));
invalidate();
}

View File

@@ -1,8 +1,8 @@
#ifndef CARDDATABASEMODEL_H
#define CARDDATABASEMODEL_H
#include "../filters/filter_string.h"
#include "card_database.h"
#include "carddatabase.h"
#include "filter_string.h"
#include <QAbstractListModel>
#include <QList>
@@ -46,7 +46,6 @@ public:
private:
QList<CardInfoPtr> cardList;
QSet<CardInfoPtr> cardListSet; // Supports faster lookups in cardDatabaseEnabledSetsChanged()
CardDatabase *db;
bool showOnlyCardsFromEnabledSets;
@@ -117,15 +116,14 @@ public:
}
void clearFilterAll();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
static int lessThanNumerically(const QString &left, const QString &right);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool rowMatchesCardName(CardInfoPtr info) const;
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
private slots:
void filterTreeChanged();
/** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */

View File

@@ -1,4 +1,4 @@
#include "card_database_parser.h"
#include "carddatabaseparser.h"
SetNameMap ICardDatabaseParser::sets;
@@ -10,8 +10,7 @@ void ICardDatabaseParser::clearSetlist()
CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName,
const QString &longName,
const QString &setType,
const QDate &releaseDate,
const CardSet::Priority priority)
const QDate &releaseDate)
{
if (sets.contains(setName)) {
return sets.value(setName);
@@ -21,9 +20,8 @@ CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName,
newSet->setLongName(longName);
newSet->setSetType(setType);
newSet->setReleaseDate(releaseDate);
newSet->setPriority(priority);
sets.insert(setName, newSet);
emit addSet(newSet);
return newSet;
}
}

View File

@@ -1,7 +1,7 @@
#ifndef CARDDATABASE_PARSER_H
#define CARDDATABASE_PARSER_H
#include "../card_database.h"
#include "../carddatabase.h"
#include <QIODevice>
#include <QString>
@@ -10,7 +10,6 @@
class ICardDatabaseParser : public QObject
{
Q_OBJECT
public:
~ICardDatabaseParser() override = default;
@@ -33,13 +32,12 @@ protected:
CardSetPtr internalAddSet(const QString &setName,
const QString &longName = "",
const QString &setType = "",
const QDate &releaseDate = QDate(),
const CardSet::Priority priority = CardSet::PriorityFallback);
const QDate &releaseDate = QDate());
signals:
void addCard(CardInfoPtr card);
void addSet(CardSetPtr set);
virtual void addCard(CardInfoPtr card) = 0;
virtual void addSet(CardSetPtr set) = 0;
};
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")
#endif
#endif

View File

@@ -1,4 +1,4 @@
#include "cockatrice_xml_3.h"
#include "cockatricexml3.h"
#include <QCoreApplication>
#include <QDebug>
@@ -158,7 +158,6 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
auto _sets = CardInfoPerSetMap();
int tableRow = 0;
bool cipt = false;
bool landscapeOrientation = false;
bool isToken = false;
bool upsideDown = false;
@@ -195,8 +194,6 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
} else if (xmlName == "cipt") {
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "landscapeOrientation") {
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "upsidedown") {
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
// sets
@@ -224,7 +221,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
if (attrs.hasAttribute("rarity")) {
setInfo.setProperty("rarity", attrs.value("rarity").toString());
}
_sets[setName].append(setInfo);
_sets.insert(setName, setInfo);
// related cards
} else if (xmlName == "related" || xmlName == "reverse-related") {
CardRelation::AttachType attach = CardRelation::DoesNotAttach;
@@ -270,9 +267,8 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
}
properties.insert("colors", colors);
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
landscapeOrientation, tableRow, upsideDown);
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
reverseRelatedCards, _sets, cipt, tableRow, upsideDown);
emit addCard(newCard);
}
}
@@ -335,26 +331,24 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
// sets
const CardInfoPerSetMap sets = info->getSets();
for (const auto &cardInfoPerSetList : sets) {
for (const CardInfoPerSet &set : cardInfoPerSetList) {
xml.writeStartElement("set");
xml.writeAttribute("rarity", set.getProperty("rarity"));
xml.writeAttribute("muId", set.getProperty("muid"));
xml.writeAttribute("uuId", set.getProperty("uuid"));
for (CardInfoPerSet set : sets) {
xml.writeStartElement("set");
xml.writeAttribute("rarity", set.getProperty("rarity"));
xml.writeAttribute("muId", set.getProperty("muid"));
xml.writeAttribute("uuId", set.getProperty("uuid"));
tmpString = set.getProperty("num");
if (!tmpString.isEmpty()) {
xml.writeAttribute("num", tmpString);
}
tmpString = set.getProperty("picurl");
if (!tmpString.isEmpty()) {
xml.writeAttribute("picURL", tmpString);
}
xml.writeCharacters(set.getPtr()->getShortName());
xml.writeEndElement();
tmpString = set.getProperty("num");
if (!tmpString.isEmpty()) {
xml.writeAttribute("num", tmpString);
}
tmpString = set.getProperty("picurl");
if (!tmpString.isEmpty()) {
xml.writeAttribute("picURL", tmpString);
}
xml.writeCharacters(set.getPtr()->getShortName());
xml.writeEndElement();
}
// related cards
@@ -409,9 +403,6 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
if (info->getCipt()) {
xml.writeTextElement("cipt", "1");
}
if (info->getLandscapeOrientation()) {
xml.writeTextElement("landscapeOrientation", "1");
}
if (info->getUpsideDownArt()) {
xml.writeTextElement("upsidedown", "1");
}

View File

@@ -1,7 +1,7 @@
#ifndef COCKATRICE_XML3_H
#define COCKATRICE_XML3_H
#include "card_database_parser.h"
#include "carddatabaseparser.h"
#include <QXmlStreamReader>
@@ -24,6 +24,9 @@ private:
void loadCardsFromXml(QXmlStreamReader &xml);
void loadSetsFromXml(QXmlStreamReader &xml);
QString getMainCardType(QString &type);
signals:
void addCard(CardInfoPtr card) override;
void addSet(CardSetPtr set) override;
};
#endif

View File

@@ -1,4 +1,4 @@
#include "cockatrice_xml_4.h"
#include "cockatricexml4.h"
#include <QCoreApplication>
#include <QDebug>
@@ -77,7 +77,6 @@ void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml)
if (xmlName == "set") {
QString shortName, longName, setType;
QDate releaseDate;
short priority;
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
@@ -93,8 +92,6 @@ void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml)
} else if (xmlName == "releasedate") {
releaseDate =
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
} else if (xmlName == "priority") {
priority = xml.readElementText(QXmlStreamReader::IncludeChildElements).toShort();
} else if (!xmlName.isEmpty()) {
qDebug() << "[CockatriceXml4Parser] Unknown set property" << xmlName
<< ", trying to continue anyway";
@@ -102,7 +99,7 @@ void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml)
}
}
internalAddSet(shortName, longName, setType, releaseDate, static_cast<CardSet::Priority>(priority));
internalAddSet(shortName, longName, setType, releaseDate);
}
}
}
@@ -140,7 +137,6 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
auto _sets = CardInfoPerSetMap();
int tableRow = 0;
bool cipt = false;
bool landscapeOrientation = false;
bool isToken = false;
bool upsideDown = false;
@@ -166,8 +162,6 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
} else if (xmlName == "cipt") {
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "landscapeOrientation") {
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "upsidedown") {
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
// sets
@@ -184,7 +178,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
attrName = "picurl";
setInfo.setProperty(attrName, attr.value().toString());
}
_sets[setName].append(setInfo);
_sets.insert(setName, setInfo);
}
// related cards
} else if (xmlName == "related" || xmlName == "reverse-related") {
@@ -236,9 +230,8 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
}
}
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
landscapeOrientation, tableRow, upsideDown);
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
reverseRelatedCards, _sets, cipt, tableRow, upsideDown);
emit addCard(newCard);
}
}
@@ -256,7 +249,6 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set
xml.writeTextElement("longname", set->getLongName());
xml.writeTextElement("settype", set->getSetType());
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
xml.writeTextElement("priority", QString::number(set->getPriority()));
xml.writeEndElement();
return xml;
@@ -288,16 +280,14 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
xml.writeEndElement();
// sets
for (const auto &cardInfoPerSetList : info->getSets()) {
for (const CardInfoPerSet &set : cardInfoPerSetList) {
xml.writeStartElement("set");
for (const QString &propName : set.getProperties()) {
xml.writeAttribute(propName, set.getProperty(propName));
}
xml.writeCharacters(set.getPtr()->getShortName());
xml.writeEndElement();
for (CardInfoPerSet set : info->getSets()) {
xml.writeStartElement("set");
for (QString propName : set.getProperties()) {
xml.writeAttribute(propName, set.getProperty(propName));
}
xml.writeCharacters(set.getPtr()->getShortName());
xml.writeEndElement();
}
// related cards
@@ -357,9 +347,6 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
if (info->getCipt()) {
xml.writeTextElement("cipt", "1");
}
if (info->getLandscapeOrientation()) {
xml.writeTextElement("landscapeOrientation", "1");
}
if (info->getUpsideDownArt()) {
xml.writeTextElement("upsidedown", "1");
}

View File

@@ -1,7 +1,7 @@
#ifndef COCKATRICE_XML4_H
#define COCKATRICE_XML4_H
#include "card_database_parser.h"
#include "carddatabaseparser.h"
#include <QXmlStreamReader>
@@ -24,6 +24,9 @@ private:
QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml);
void loadCardsFromXml(QXmlStreamReader &xml);
void loadSetsFromXml(QXmlStreamReader &xml);
signals:
void addCard(CardInfoPtr card) override;
void addSet(CardSetPtr set) override;
};
#endif

View File

@@ -1,10 +1,10 @@
#include "card_drag_item.h"
#include "carddragitem.h"
#include "../game_scene.h"
#include "../zones/card_zone.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "card_item.h"
#include "carditem.h"
#include "cardzone.h"
#include "gamescene.h"
#include "tablezone.h"
#include "zoneviewzone.h"
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
@@ -95,24 +95,18 @@ void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
QList<CardDragItem *> dragItemList;
CardZone *startZone = static_cast<CardItem *>(item)->getZone();
if (currentZone && !(static_cast<CardItem *>(item)->getAttachedTo() && (startZone == currentZone))) {
if (!occupied) {
dragItemList.append(this);
}
if (currentZone && !(static_cast<CardItem *>(item)->getAttachedTo() && (startZone == currentZone)) && !occupied) {
dragItemList.append(this);
for (int i = 0; i < childDrags.size(); i++) {
CardDragItem *c = static_cast<CardDragItem *>(childDrags[i]);
if (!occupied && !(static_cast<CardItem *>(c->item)->getAttachedTo() && (startZone == currentZone)) &&
!c->occupied) {
if (!(static_cast<CardItem *>(c->item)->getAttachedTo() && (startZone == currentZone)) && !c->occupied)
dragItemList.append(c);
}
sc->removeItem(c);
}
}
if (currentZone) {
if (currentZone)
currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint());
}
event->accept();
}

View File

@@ -1,7 +1,7 @@
#ifndef CARDDRAGITEM_H
#define CARDDRAGITEM_H
#include "abstract_card_drag_item.h"
#include "abstractcarddragitem.h"
class CardItem;

View File

@@ -1,4 +1,4 @@
#include "filter_card.h"
#include "cardfilter.h"
const QString CardFilter::typeName(Type t)
{

View File

@@ -1,25 +1,21 @@
#include "card_info_frame_widget.h"
#include "cardframe.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/cards/card_item.h"
#include "../../../../settings/cache_settings.h"
#include "card_info_display_widget.h"
#include "card_info_picture_widget.h"
#include "card_info_text_widget.h"
#include "cardinfopicture.h"
#include "cardinfotext.h"
#include "carditem.h"
#include "main.h"
#include "settingscache.h"
#include <QSplitter>
#include <QVBoxLayout>
#include <utility>
CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *parent)
: QTabWidget(parent), info(nullptr), cardTextOnly(false)
CardFrame::CardFrame(const QString &cardName, QWidget *parent) : QTabWidget(parent), info(nullptr), cardTextOnly(false)
{
setContentsMargins(3, 3, 3, 3);
pic = new CardInfoPictureWidget();
pic = new CardInfoPicture();
pic->setObjectName("pic");
connect(pic, &CardInfoPictureWidget::cardChanged, this, qOverload<CardInfoPtr>(&CardInfoFrameWidget::setCard));
text = new CardInfoTextWidget();
text = new CardInfoText();
text->setObjectName("text");
connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &)));
@@ -61,17 +57,17 @@ CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *paren
setViewMode(SettingsCache::instance().getCardInfoViewMode());
setCard(CardDatabaseManager::getInstance()->getCard(cardName));
setCard(db->getCard(cardName));
}
void CardInfoFrameWidget::retranslateUi()
void CardFrame::retranslateUi()
{
setTabText(ImageOnlyView, tr("Image"));
setTabText(TextOnlyView, tr("Description"));
setTabText(ImageAndTextView, tr("Both"));
}
void CardInfoFrameWidget::setViewMode(int mode)
void CardFrame::setViewMode(int mode)
{
if (currentIndex() != mode)
setCurrentIndex(mode);
@@ -93,7 +89,7 @@ void CardInfoFrameWidget::setViewMode(int mode)
SettingsCache::instance().setCardInfoViewMode(mode);
}
void CardInfoFrameWidget::setCard(CardInfoPtr card)
void CardFrame::setCard(CardInfoPtr card)
{
if (info) {
disconnect(info.data(), nullptr, this, nullptr);
@@ -109,24 +105,19 @@ void CardInfoFrameWidget::setCard(CardInfoPtr card)
pic->setCard(info);
}
void CardInfoFrameWidget::setCard(const QString &cardName)
void CardFrame::setCard(const QString &cardName)
{
setCard(CardDatabaseManager::getInstance()->guessCard(cardName));
setCard(db->guessCard(cardName));
}
void CardInfoFrameWidget::setCard(const QString &cardName, const QString &providerId)
{
setCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, providerId));
}
void CardInfoFrameWidget::setCard(AbstractCardItem *card)
void CardFrame::setCard(AbstractCardItem *card)
{
if (card) {
setCard(card->getInfo());
}
}
void CardInfoFrameWidget::clearCard()
void CardFrame::clearCard()
{
setCard((CardInfoPtr) nullptr);
}

View File

@@ -1,23 +1,23 @@
#ifndef CARDFRAME_H
#define CARDFRAME_H
#include "../../../../game/cards/card_database.h"
#include "carddatabase.h"
#include <QTabWidget>
class AbstractCardItem;
class CardInfoPictureWidget;
class CardInfoTextWidget;
class CardInfoPicture;
class CardInfoText;
class QVBoxLayout;
class QSplitter;
class CardInfoFrameWidget : public QTabWidget
class CardFrame : public QTabWidget
{
Q_OBJECT
private:
CardInfoPtr info;
CardInfoPictureWidget *pic;
CardInfoTextWidget *text;
CardInfoPicture *pic;
CardInfoText *text;
bool cardTextOnly;
QWidget *tab1, *tab2, *tab3;
QVBoxLayout *tab1Layout, *tab2Layout, *tab3Layout;
@@ -30,18 +30,12 @@ public:
TextOnlyView,
ImageAndTextView
};
explicit CardInfoFrameWidget(const QString &cardName = QString(), QWidget *parent = nullptr);
CardInfoPtr getInfo()
{
return info;
}
explicit CardFrame(const QString &cardName = QString(), QWidget *parent = nullptr);
void retranslateUi();
public slots:
void setCard(CardInfoPtr card);
void setCard(const QString &cardName);
void setCard(const QString &cardName, const QString &providerId);
void setCard(AbstractCardItem *card);
void clearCard();
void setViewMode(int mode);

View File

@@ -0,0 +1,66 @@
#include "cardinfopicture.h"
#include "carditem.h"
#include "main.h"
#include "pictureloader.h"
#include <QStylePainter>
#include <QWidget>
CardInfoPicture::CardInfoPicture(QWidget *parent) : QWidget(parent), info(nullptr), pixmapDirty(true)
{
setMinimumHeight(100);
}
void CardInfoPicture::setCard(CardInfoPtr card)
{
if (info) {
disconnect(info.data(), nullptr, this, nullptr);
}
info = card;
if (info) {
connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap()));
}
updatePixmap();
}
void CardInfoPicture::resizeEvent(QResizeEvent *)
{
updatePixmap();
}
void CardInfoPicture::updatePixmap()
{
pixmapDirty = true;
update();
}
void CardInfoPicture::loadPixmap()
{
if (info)
PictureLoader::getPixmap(resizedPixmap, info, size());
else
PictureLoader::getCardBackPixmap(resizedPixmap, size());
}
void CardInfoPicture::paintEvent(QPaintEvent *)
{
if (width() == 0 || height() == 0)
return;
if (pixmapDirty)
loadPixmap();
QSize scaledSize = resizedPixmap.size().scaled(size(), Qt::KeepAspectRatio);
QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
qreal radius = 0.05 * scaledSize.width();
QStylePainter painter(this);
QPainterPath shape;
shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius);
painter.setClipPath(shape);
painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter, resizedPixmap);
}

View File

@@ -0,0 +1,31 @@
#ifndef CARDINFOPICTURE_H
#define CARDINFOPICTURE_H
#include "carddatabase.h"
#include <QWidget>
class AbstractCardItem;
class CardInfoPicture : public QWidget
{
Q_OBJECT
private:
CardInfoPtr info;
QPixmap resizedPixmap;
bool pixmapDirty;
public:
CardInfoPicture(QWidget *parent = nullptr);
protected:
void resizeEvent(QResizeEvent *event);
void paintEvent(QPaintEvent *);
void loadPixmap();
public slots:
void setCard(CardInfoPtr card);
void updatePixmap();
};
#endif

View File

@@ -1,13 +1,14 @@
#include "card_info_text_widget.h"
#include "cardinfotext.h"
#include "../../../../game/cards/card_item.h"
#include "../../../../game/game_specific_terms.h"
#include "carditem.h"
#include "game_specific_terms.h"
#include "main.h"
#include <QGridLayout>
#include <QLabel>
#include <QTextEdit>
CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent), info(nullptr)
CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr)
{
nameLabel = new QLabel;
nameLabel->setOpenExternalLinks(false);
@@ -26,7 +27,7 @@ CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent), info(n
retranslateUi();
}
void CardInfoTextWidget::setCard(CardInfoPtr card)
void CardInfoText::setCard(CardInfoPtr card)
{
if (card == nullptr) {
nameLabel->setText("");
@@ -64,13 +65,13 @@ void CardInfoTextWidget::setCard(CardInfoPtr card)
textLabel->setText(card->getText());
}
void CardInfoTextWidget::setInvalidCardName(const QString &cardName)
void CardInfoText::setInvalidCardName(const QString &cardName)
{
nameLabel->setText(tr("Unknown card:") + " " + cardName);
textLabel->setText("");
}
void CardInfoTextWidget::retranslateUi()
void CardInfoText::retranslateUi()
{
/*
* There's no way we can really translate the text currently being rendered.

View File

@@ -1,13 +1,13 @@
#ifndef CARDINFOTEXT_H
#define CARDINFOTEXT_H
#include "../../../../game/cards/card_database.h"
#include "carddatabase.h"
#include <QFrame>
class QLabel;
class QTextEdit;
class CardInfoTextWidget : public QFrame
class CardInfoText : public QFrame
{
Q_OBJECT
@@ -17,7 +17,7 @@ private:
CardInfoPtr info;
public:
explicit CardInfoTextWidget(QWidget *parent = nullptr);
explicit CardInfoText(QWidget *parent = nullptr);
void retranslateUi();
void setInvalidCardName(const QString &cardName);

View File

@@ -1,26 +1,22 @@
#include "card_info_display_widget.h"
#include "cardinfowidget.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/cards/card_item.h"
#include "../../../../main.h"
#include "card_info_picture_widget.h"
#include "card_info_text_widget.h"
#include "cardinfopicture.h"
#include "cardinfotext.h"
#include "carditem.h"
#include "main.h"
#include <QApplication>
#include <QScreen>
#include <QVBoxLayout>
#include <utility>
CardInfoDisplayWidget::CardInfoDisplayWidget(const QString &cardName,
const QString &providerId,
QWidget *parent,
Qt::WindowFlags flags)
CardInfoWidget::CardInfoWidget(const QString &cardName, QWidget *parent, Qt::WindowFlags flags)
: QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH), info(nullptr)
{
setContentsMargins(3, 3, 3, 3);
pic = new CardInfoPictureWidget();
pic = new CardInfoPicture();
pic->setObjectName("pic");
text = new CardInfoTextWidget();
text = new CardInfoText();
text->setObjectName("text");
connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &)));
@@ -40,13 +36,13 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const QString &cardName,
pic->setFixedHeight(pixmapHeight);
setFixedWidth(pixmapWidth + 150);
setCard(cardName, providerId);
setCard(cardName);
// ensure our parent gets a valid size to position us correctly
resize(width(), sizeHint().height());
}
void CardInfoDisplayWidget::setCard(CardInfoPtr card)
void CardInfoWidget::setCard(CardInfoPtr card)
{
if (info)
disconnect(info.data(), nullptr, this, nullptr);
@@ -58,20 +54,20 @@ void CardInfoDisplayWidget::setCard(CardInfoPtr card)
pic->setCard(info);
}
void CardInfoDisplayWidget::setCard(const QString &cardName, const QString &providerId)
void CardInfoWidget::setCard(const QString &cardName)
{
setCard(CardDatabaseManager::getInstance()->guessCard(cardName, providerId));
setCard(db->guessCard(cardName));
if (info == nullptr) {
text->setInvalidCardName(cardName);
}
}
void CardInfoDisplayWidget::setCard(AbstractCardItem *card)
void CardInfoWidget::setCard(AbstractCardItem *card)
{
setCard(card->getInfo());
}
void CardInfoDisplayWidget::clear()
void CardInfoWidget::clear()
{
setCard((CardInfoPtr) nullptr);
}

View File

@@ -0,0 +1,36 @@
#ifndef CARDINFOWIDGET_H
#define CARDINFOWIDGET_H
#include "carddatabase.h"
#include <QComboBox>
#include <QFrame>
#include <QStringList>
class CardInfoPicture;
class CardInfoText;
class AbstractCardItem;
class CardInfoWidget : public QFrame
{
Q_OBJECT
private:
qreal aspectRatio;
CardInfoPtr info;
CardInfoPicture *pic;
CardInfoText *text;
public:
explicit CardInfoWidget(const QString &cardName, QWidget *parent = nullptr, Qt::WindowFlags f = {});
public slots:
void setCard(CardInfoPtr card);
void setCard(const QString &cardName);
void setCard(AbstractCardItem *card);
private slots:
void clear();
};
#endif

View File

@@ -1,16 +1,17 @@
#include "card_item.h"
#include "carditem.h"
#include "../../client/tabs/tab_game.h"
#include "../../settings/cache_settings.h"
#include "../board/arrow_item.h"
#include "../game_scene.h"
#include "../player/player.h"
#include "../zones/card_zone.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "card_database.h"
#include "card_drag_item.h"
#include "arrowitem.h"
#include "carddatabase.h"
#include "carddragitem.h"
#include "cardzone.h"
#include "gamescene.h"
#include "main.h"
#include "pb/serverinfo_card.pb.h"
#include "player.h"
#include "settingscache.h"
#include "tab_game.h"
#include "tablezone.h"
#include "zoneviewzone.h"
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
@@ -18,14 +19,13 @@
#include <QPainter>
CardItem::CardItem(Player *_owner,
QGraphicsItem *parent,
const QString &_name,
const QString &_providerId,
int _cardid,
bool _revealedCard,
QGraphicsItem *parent,
CardZone *_zone)
: AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), revealedCard(_revealedCard),
attacking(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
: AbstractCardItem(_name, _owner, _cardid, parent), zone(_zone), revealedCard(_revealedCard), attacking(false),
destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
{
owner->addCard(this);
@@ -217,15 +217,13 @@ void CardItem::setAttachedTo(CardItem *_attachedTo)
}
}
void CardItem::resetState(bool keepAnnotations)
void CardItem::resetState()
{
attacking = false;
facedown = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
annotation.clear();
}
annotation.clear();
attachedTo = 0;
attachedCards.clear();
setTapped(false, false);
@@ -245,7 +243,6 @@ void CardItem::processCardInfo(const ServerInfo_Card &_info)
}
setId(_info.id());
setProviderId(QString::fromStdString(_info.provider_id()));
setName(QString::fromStdString(_info.name()));
setAttacking(_info.attacking());
setFaceDown(_info.face_down());
@@ -385,46 +382,8 @@ void CardItem::playCard(bool faceDown)
TableZone *tz = qobject_cast<TableZone *>(zone);
if (tz)
tz->toggleTapped();
else {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
faceDown ? zone->getPlayer()->actPlayFacedown() : zone->getPlayer()->actPlay();
} else {
zone->getPlayer()->playCard(this, faceDown);
}
}
}
/**
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
* is nullptr.
*/
static bool isUnwritableRevealZone(CardZone *zone)
{
if (zone && zone->getIsView()) {
if (auto *view = static_cast<ZoneViewZone *>(zone)) {
return view->getRevealZone() && !view->getWriteableRevealZone();
}
}
return false;
}
/**
* This method is called when a "click to play" is done on the card.
* This is either triggered by a single click or double click, depending on the settings.
*
* @param shiftHeld if the shift key was held during the click
*/
void CardItem::handleClickedToPlay(bool shiftHeld)
{
if (isUnwritableRevealZone(zone)) {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
zone->getPlayer()->actHide();
} else {
zone->removeCard(this);
}
} else {
playCard(shiftHeld);
}
else
zone->getPlayer()->playCard(this, faceDown, info ? info->getCipt() : false);
}
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
@@ -436,7 +395,17 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
}
} else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
bool hideCard = false;
if (zone && zone->getIsView()) {
auto *view = static_cast<ZoneViewZone *>(zone);
if (view->getRevealZone() && !view->getWriteableRevealZone())
hideCard = true;
}
if (zone && hideCard) {
zone->removeCard(this);
} else {
playCard(event->modifiers().testFlag(Qt::ShiftModifier));
}
}
if (owner != nullptr) { // cards without owner will be deleted
@@ -447,9 +416,12 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) &&
(SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
if ((event->modifiers() != Qt::AltModifier) && (SettingsCache::instance().getDoubleClickToPlay()) &&
(event->buttons() == Qt::LeftButton)) {
if (revealedCard)
zone->removeCard(this);
else
playCard(event->modifiers().testFlag(Qt::ShiftModifier));
}
event->accept();
}
@@ -488,9 +460,7 @@ QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
owner->setCardMenu(cardMenu);
owner->getGame()->setActiveCard(this);
} else if (owner->getCardMenu() == cardMenu) {
if (scene() && scene()->selectedItems().isEmpty()) {
owner->setCardMenu(nullptr);
}
owner->setCardMenu(nullptr);
owner->getGame()->setActiveCard(nullptr);
}
}

View File

@@ -1,7 +1,7 @@
#ifndef CARDITEM_H
#define CARDITEM_H
#include "abstract_card_item.h"
#include "abstractcarditem.h"
#include "server_card.h"
class CardDatabase;
@@ -37,7 +37,6 @@ private:
QMenu *cardMenu, *ptMenu, *moveMenu;
void prepareDelete();
void handleClickedToPlay(bool shiftHeld);
public slots:
void deleteLater();
@@ -51,11 +50,10 @@ public:
return Type;
}
CardItem(Player *_owner,
QGraphicsItem *parent = nullptr,
const QString &_name = QString(),
const QString &_providerId = QString(),
int _cardid = -1,
bool revealedCard = false,
QGraphicsItem *parent = nullptr,
CardZone *_zone = nullptr);
~CardItem();
void retranslateUi();
@@ -139,7 +137,7 @@ public:
{
return attachedCards;
}
void resetState(bool keepAnnotations = false);
void resetState();
void processCardInfo(const ServerInfo_Card &_info);
QMenu *getCardMenu() const

View File

@@ -0,0 +1,63 @@
#include "cardlist.h"
#include "carddatabase.h"
#include "carditem.h"
#include <algorithm>
CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), contentsKnown(_contentsKnown)
{
}
CardItem *CardList::findCard(const int id, const bool remove, int *position)
{
if (!contentsKnown) {
if (empty())
return 0;
CardItem *temp = at(0);
if (remove)
removeAt(0);
if (position)
*position = id;
return temp;
} else
for (int i = 0; i < size(); i++) {
CardItem *temp = at(i);
if (temp->getId() == id) {
if (remove)
removeAt(i);
if (position)
*position = i;
return temp;
}
}
return 0;
}
class CardList::compareFunctor
{
private:
int flags;
public:
explicit compareFunctor(int _flags) : flags(_flags)
{
}
inline bool operator()(CardItem *a, CardItem *b) const
{
if (flags & SortByType) {
QString t1 = a->getInfo() ? a->getInfo()->getMainCardType() : QString();
QString t2 = b->getInfo() ? b->getInfo()->getMainCardType() : QString();
if ((t1 == t2) && (flags & SortByName))
return a->getName() < b->getName();
return t1 < t2;
} else
return a->getName() < b->getName();
}
};
void CardList::sort(int flags)
{
compareFunctor cf(flags);
std::sort(begin(), end(), cf);
}

31
cockatrice/src/cardlist.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef CARDLIST_H
#define CARDLIST_H
#include <QList>
class CardItem;
class CardList : public QList<CardItem *>
{
private:
class compareFunctor;
protected:
bool contentsKnown;
public:
enum SortFlags
{
SortByName = 1,
SortByType = 2
};
CardList(bool _contentsKnown);
CardItem *findCard(const int id, const bool remove, int *position = NULL);
bool getContentsKnown() const
{
return contentsKnown;
}
void sort(int flags = SortByName);
};
#endif

View File

@@ -1,26 +1,16 @@
#include "card_zone.h"
#include "cardzone.h"
#include "../cards/card_database_manager.h"
#include "../cards/card_item.h"
#include "../player/player.h"
#include "carditem.h"
#include "pb/command_move_card.pb.h"
#include "pb/serverinfo_user.pb.h"
#include "pile_zone.h"
#include "view_zone.h"
#include "player.h"
#include "zoneviewzone.h"
#include <QAction>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
/**
* @param _p the player that the zone belongs to
* @param _name internal name of the zone
* @param _isShufflable whether it makes sense to shuffle this zone by default after viewing it
* @param _contentsKnown whether the cards in the zone are known to the client
* @param parent the parent graphics object.
* @param _isView whether this zone is a view of another zone. Modifications to a view should modify the original
*/
CardZone::CardZone(Player *_p,
const QString &_name,
bool _hasCardAttr,
@@ -33,11 +23,6 @@ CardZone::CardZone(Player *_p,
{
if (!isView)
player->addZone(this);
// If we join a game before the card db finishes loading, the cards might have the wrong printings.
// Force refresh all cards in the zone when db finishes loading to fix that.
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
&CardZone::refreshCardInfos);
}
CardZone::~CardZone()
@@ -126,13 +111,6 @@ bool CardZone::showContextMenu(const QPoint &screenPos)
return false;
}
void CardZone::refreshCardInfos()
{
for (const auto &cardItem : cards) {
cardItem->refreshCardInfo();
}
}
void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
@@ -144,17 +122,11 @@ void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
event->ignore();
}
void CardZone::addCard(CardItem *card, const bool reorganize, const int x, const int y)
void CardZone::addCard(CardItem *card, bool reorganize, int x, int y)
{
if (!card) {
qDebug() << "CardZone::addCard() card is null, this shouldn't normally happen";
return;
}
for (auto *view : views) {
if (view->getIsReversed() || (x <= view->getCards().size()) || (view->getNumberCards() == -1)) {
view->addCard(new CardItem(player, nullptr, card->getName(), card->getProviderId(), card->getId()),
reorganize, x, y);
if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) {
view->addCard(new CardItem(player, card->getName(), card->getId()), reorganize, x, y);
}
}
@@ -169,10 +141,10 @@ void CardZone::addCard(CardItem *card, const bool reorganize, const int x, const
CardItem *CardZone::getCard(int cardId, const QString &cardName)
{
CardItem *c = cards.findCard(cardId);
CardItem *c = cards.findCard(cardId, false);
if (!c) {
qDebug() << "CardZone::getCard: card id=" << cardId << "not found";
return nullptr;
return 0;
}
// If the card's id is -1, this zone is invisible,
// so we need to give the card an id and a name as it comes out.
@@ -198,14 +170,14 @@ CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/)
position = 0;
}
if (position >= cards.size())
return nullptr;
return 0;
CardItem *c = cards.takeAt(position);
for (auto *view : views) {
view->removeCard(position);
}
CardItem *c = cards.takeAt(position);
c->setId(cardId);
reorganizeCards();
@@ -215,11 +187,6 @@ CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/)
void CardZone::removeCard(CardItem *card)
{
if (!card) {
qDebug() << "CardZone::removeCard: card is null, this shouldn't normally happen";
return;
}
cards.removeOne(card);
reorganizeCards();
emit cardCountChanged();
@@ -247,4 +214,4 @@ void CardZone::moveAllToZone()
QPointF CardZone::closestGridPoint(const QPointF &point)
{
return point;
}
}

View File

@@ -1,9 +1,9 @@
#ifndef CARDZONE_H
#define CARDZONE_H
#include "../../client/translation.h"
#include "../board/abstract_graphics_item.h"
#include "../cards/card_list.h"
#include "abstractgraphicsitem.h"
#include "cardlist.h"
#include "translation.h"
#include <QString>
@@ -14,12 +14,6 @@ class QAction;
class QPainter;
class CardDragItem;
/**
* A zone in the game that can contain cards.
* This class contains methods to get and modify the cards that are contained inside this zone.
*
* The cards are stored as a list of `CardItem*`.
*/
class CardZone : public AbstractGraphicsItem
{
Q_OBJECT
@@ -43,9 +37,6 @@ public slots:
void moveAllToZone();
bool showContextMenu(const QPoint &screenPos);
private slots:
void refreshCardInfos();
public:
enum
{

View File

@@ -1,10 +1,10 @@
#include "chat_view.h"
#include "chatview.h"
#include "../../client/sound_engine.h"
#include "../../client/tabs/tab_account.h"
#include "../../client/ui/pixel_map_generator.h"
#include "../../settings/cache_settings.h"
#include "../user/user_context_menu.h"
#include "../pixmapgenerator.h"
#include "../settingscache.h"
#include "../soundengine.h"
#include "../tab_account.h"
#include "../user_context_menu.h"
#include "user_level.h"
#include <QApplication>
@@ -494,7 +494,6 @@ void ChatView::clearChat()
{
document()->clear();
lastSender = "";
evenNumber = true;
}
void ChatView::redactMessages(const QString &userName, int amount)
@@ -580,9 +579,9 @@ void ChatView::mousePressEvent(QMouseEvent *event)
case HoveredCard: {
if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton))
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
emit showCardInfoPopup(event->globalPosition().toPoint(), hoveredContent, QString());
emit showCardInfoPopup(event->globalPosition().toPoint(), hoveredContent);
#else
emit showCardInfoPopup(event->globalPos(), hoveredContent, QString());
emit showCardInfoPopup(event->globalPos(), hoveredContent);
#endif
break;
}

View File

@@ -1,11 +1,11 @@
#ifndef CHATVIEW_H
#define CHATVIEW_H
#include "../../client/tabs/tab_supervisor.h"
#include "../user/user_list.h"
#include "../tab_supervisor.h"
#include "../userlist.h"
#include "room_message_type.h"
#include "user_level.h"
#include "user_list_proxy.h"
#include "userlistProxy.h"
#include <QAction>
#include <QColor>
@@ -115,7 +115,7 @@ protected:
signals:
void openMessageDialog(const QString &userName, bool focus);
void cardNameHovered(QString cardName);
void showCardInfoPopup(const QPoint &pos, const QString &cardName, const QString &providerId);
void showCardInfoPopup(QPoint pos, QString cardName);
void deleteCardInfoPopup(QString cardName);
void addMentionTag(QString mentionTag);
void messageClickedSignal();

View File

@@ -1,35 +0,0 @@
#include "client_update_checker.h"
#include "../../settings/cache_settings.h"
#include "release_channel.h"
ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent)
{
}
void ClientUpdateChecker::check()
{
auto releaseChannel = SettingsCache::instance().getUpdateReleaseChannel();
finishedCheckConnection =
connect(releaseChannel, &ReleaseChannel::finishedCheck, this, &ClientUpdateChecker::actFinishedCheck);
errorConnection = connect(releaseChannel, &ReleaseChannel::error, this, &ClientUpdateChecker::actError);
releaseChannel->checkForUpdates();
}
void ClientUpdateChecker::actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release)
{
disconnect(finishedCheckConnection);
disconnect(errorConnection);
emit finishedCheck(needToUpdate, isCompatible, release);
}
void ClientUpdateChecker::actError(const QString &errorString)
{
disconnect(finishedCheckConnection);
disconnect(errorConnection);
emit error(errorString);
}

View File

@@ -1,45 +0,0 @@
#ifndef CLIENT_UPDATE_CHECKER_H
#define CLIENT_UPDATE_CHECKER_H
#include <QObject>
class Release;
/**
* We use a singleton instance of UpdateChannel, which can cause interference and feedback loops when multiple objects
* connect to it.
*
* This class encapsulates the usage of that UpdateChannel to ensure that the check only happens once per connection and
* the connection is destroyed after it's been used.
*/
class ClientUpdateChecker : public QObject
{
Q_OBJECT
QMetaObject::Connection finishedCheckConnection;
QMetaObject::Connection errorConnection;
void actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release);
void actError(const QString &errorString);
public:
explicit ClientUpdateChecker(QObject *parent = nullptr);
/**
* Actually performs the check, using the currently selected update channel in the settings.
* Any resulting signals will only be sent once.
* This method should only be called ONCE per instance.
*/
void check();
signals:
/**
* Forwarded from UpdateChannel::finishedCheck
*/
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
/**
* Forwarded from UpdateChannel::error
*/
void error(const QString &errorString);
};
#endif // CLIENT_UPDATE_CHECKER_H

View File

@@ -1,193 +0,0 @@
#include "replay_timeline_widget.h"
#include <QPainter>
#include <QPainterPath>
#include <QPalette>
#include <QTimer>
ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
: QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentVisualTime(0), currentProcessedTime(0),
currentEvent(0)
{
replayTimer = new QTimer(this);
connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout);
rewindBufferingTimer = new QTimer(this);
rewindBufferingTimer->setSingleShot(true);
connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayTimelineWidget::processRewind);
}
void ReplayTimelineWidget::setTimeline(const QList<int> &_replayTimeline)
{
replayTimeline = _replayTimeline;
histogram.clear();
int binEndTime = BIN_LENGTH - 1;
int binValue = 0;
for (int i : replayTimeline) {
if (i > binEndTime) {
histogram.append(binValue);
if (binValue > maxBinValue)
maxBinValue = binValue;
while (i > binEndTime + BIN_LENGTH) {
histogram.append(0);
binEndTime += BIN_LENGTH;
}
binValue = 1;
binEndTime += BIN_LENGTH;
} else
++binValue;
}
histogram.append(binValue);
if (!replayTimeline.isEmpty())
maxTime = replayTimeline.last();
update();
}
void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.drawRect(0, 0, width() - 1, height() - 1);
qreal binWidth = (qreal)width() / histogram.size();
QPainterPath path;
path.moveTo(0, height() - 1);
for (int i = 0; i < histogram.size(); ++i)
path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue));
path.lineTo(width() - 1, height() - 1);
path.lineTo(0, height() - 1);
painter.fillPath(path, Qt::black);
const QColor barColor = QColor::fromHsv(120, 255, 255, 100);
quint64 w = (quint64)(width() - 1) * (quint64)currentVisualTime / maxTime;
painter.fillRect(0, 0, static_cast<int>(w), height() - 1, barColor);
}
void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event)
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->position().x() / width());
#else
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->x() / width());
#endif
// don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering
skipToTime(newTime, false);
}
void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering)
{
// check boundary conditions
if (newTime < 0) {
newTime = 0;
}
if (newTime > maxTime) {
newTime = maxTime;
}
newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval
const bool isBackwardsSkip = newTime < currentProcessedTime;
currentVisualTime = newTime;
if (isBackwardsSkip) {
handleBackwardsSkip(doRewindBuffering);
} else {
processNewEvents(FORWARD_SKIP);
}
update();
}
/// @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind
/// is processed at the end. When false, the backwards skip will always cause an immediate rewind
void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering)
{
if (doRewindBuffering) {
// We use a one-shot timer to implement the rewind buffering.
// The rewind only happens once the timer runs out.
// If another backwards skip happens, the timer will just get reset instead of rewinding.
rewindBufferingTimer->stop();
rewindBufferingTimer->start(SettingsCache::instance().getRewindBufferingMs());
} else {
// otherwise, process the rewind immediately
processRewind();
}
}
void ReplayTimelineWidget::processRewind()
{
// stop any queued-up rewinds
rewindBufferingTimer->stop();
// process the rewind
currentEvent = 0;
emit rewound();
processNewEvents(BACKWARD_SKIP);
}
QSize ReplayTimelineWidget::sizeHint() const
{
return {-1, 50};
}
QSize ReplayTimelineWidget::minimumSizeHint() const
{
return {400, 50};
}
void ReplayTimelineWidget::replayTimerTimeout()
{
currentVisualTime += TIMER_INTERVAL_MS;
processNewEvents(NORMAL_PLAYBACK);
if (!(currentVisualTime % 1000))
update();
}
/// Processes all unprocessed events up to the current time.
void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode)
{
currentProcessedTime = currentVisualTime;
while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) {
Player::EventProcessingOptions options;
// backwards skip => always skip reveal windows
// forwards skip => skip reveal windows that don't happen within a big skip of the target
if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS)
options |= Player::EventProcessingOption::SKIP_REVEAL_WINDOW;
// backwards skip => always skip tap animation
if (playbackMode == BACKWARD_SKIP)
options |= Player::EventProcessingOption::SKIP_TAP_ANIMATION;
emit processNextEvent(options);
++currentEvent;
}
if (currentEvent == replayTimeline.size()) {
emit replayFinished();
replayTimer->stop();
}
}
void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor)
{
timeScaleFactor = _timeScaleFactor;
replayTimer->setInterval(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
}
void ReplayTimelineWidget::startReplay()
{
replayTimer->start(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
}
void ReplayTimelineWidget::stopReplay()
{
replayTimer->stop();
}
void ReplayTimelineWidget::skipByAmount(int amount)
{
skipToTime(currentVisualTime + amount, amount < 0);
}

View File

@@ -1,250 +0,0 @@
#include "tab_admin.h"
#include "../../server/pending_command.h"
#include "../game_logic/abstract_client.h"
#include "pb/admin_commands.pb.h"
#include "pb/event_replay_added.pb.h"
#include "pb/moderator_commands.pb.h"
#include "trice_limits.h"
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
{
QLabel *reasonLabel = new QLabel(tr("&Reason for shutdown:"));
reasonEdit = new QLineEdit;
reasonEdit->setMaxLength(MAX_TEXT_LENGTH);
reasonLabel->setBuddy(reasonEdit);
QLabel *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):"));
minutesEdit = new QSpinBox;
minutesLabel->setBuddy(minutesEdit);
minutesEdit->setMinimum(0);
minutesEdit->setValue(5);
minutesEdit->setMaximum(999);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &ShutdownDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ShutdownDialog::reject);
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(reasonLabel, 0, 0);
mainLayout->addWidget(reasonEdit, 0, 1);
mainLayout->addWidget(minutesLabel, 1, 0);
mainLayout->addWidget(minutesEdit, 1, 1);
mainLayout->addWidget(buttonBox, 2, 0, 1, 2);
setLayout(mainLayout);
setWindowTitle(tr("Shut down server"));
}
QString ShutdownDialog::getReason() const
{
return reasonEdit->text();
}
int ShutdownDialog::getMinutes() const
{
return minutesEdit->value();
}
TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent)
: Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin)
{
updateServerMessageButton = new QPushButton;
connect(updateServerMessageButton, &QPushButton::clicked, this, &TabAdmin::actUpdateServerMessage);
shutdownServerButton = new QPushButton;
connect(shutdownServerButton, &QPushButton::clicked, this, &TabAdmin::actShutdownServer);
reloadConfigButton = new QPushButton;
connect(reloadConfigButton, &QPushButton::clicked, this, &TabAdmin::actReloadConfig);
grantReplayAccessButton = new QPushButton;
grantReplayAccessButton->setEnabled(false);
connect(grantReplayAccessButton, &QPushButton::clicked, this, &TabAdmin::actGrantReplayAccess);
replayIdToGrant = new QLineEdit;
replayIdToGrant->setMaximumWidth(500);
replayIdToGrant->setValidator(new QIntValidator(0, INT_MAX, this));
connect(replayIdToGrant, &QLineEdit::textChanged, this,
[=, this]() { grantReplayAccessButton->setEnabled(!replayIdToGrant->text().isEmpty()); });
auto *grandReplayAccessLayout = new QGridLayout(this);
grandReplayAccessLayout->addWidget(replayIdToGrant, 0, 0);
grandReplayAccessLayout->addWidget(grantReplayAccessButton, 0, 1);
activateUserButton = new QPushButton;
activateUserButton->setEnabled(false);
connect(activateUserButton, &QPushButton::clicked, this, &TabAdmin::actForceActivateUser);
userToActivate = new QLineEdit;
userToActivate->setMaximumWidth(500);
connect(userToActivate, &QLineEdit::textChanged, this,
[=, this]() { activateUserButton->setEnabled(!userToActivate->text().isEmpty()); });
auto *activateUserLayout = new QGridLayout(this);
activateUserLayout->addWidget(userToActivate, 0, 0);
activateUserLayout->addWidget(activateUserButton, 0, 1);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(updateServerMessageButton);
vbox->addWidget(shutdownServerButton);
vbox->addWidget(reloadConfigButton);
vbox->addLayout(grandReplayAccessLayout);
vbox->addLayout(activateUserLayout);
vbox->addStretch();
adminGroupBox = new QGroupBox;
adminGroupBox->setLayout(vbox);
adminGroupBox->setEnabled(false);
unlockButton = new QPushButton;
connect(unlockButton, &QPushButton::clicked, this, &TabAdmin::actUnlock);
lockButton = new QPushButton;
lockButton->setEnabled(false);
connect(lockButton, &QPushButton::clicked, this, &TabAdmin::actLock);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(adminGroupBox);
mainLayout->addWidget(unlockButton);
mainLayout->addWidget(lockButton);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(mainLayout);
setCentralWidget(mainWidget);
actUnlock();
}
void TabAdmin::retranslateUi()
{
updateServerMessageButton->setText(tr("Update server &message"));
shutdownServerButton->setText(tr("&Shut down server"));
reloadConfigButton->setText(tr("&Reload configuration"));
adminGroupBox->setTitle(tr("Server administration functions"));
replayIdToGrant->setPlaceholderText(tr("Replay ID"));
grantReplayAccessButton->setText(tr("Grant Replay Access"));
userToActivate->setPlaceholderText(tr("Username to Activate"));
activateUserButton->setText(tr("Force Activate User"));
unlockButton->setText(tr("&Unlock functions"));
lockButton->setText(tr("&Lock functions"));
}
void TabAdmin::actUpdateServerMessage()
{
client->sendCommand(client->prepareAdminCommand(Command_UpdateServerMessage()));
}
void TabAdmin::actShutdownServer()
{
ShutdownDialog dlg;
if (dlg.exec()) {
Command_ShutdownServer cmd;
cmd.set_reason(dlg.getReason().toStdString());
cmd.set_minutes(dlg.getMinutes());
client->sendCommand(AbstractClient::prepareAdminCommand(cmd));
}
}
void TabAdmin::actReloadConfig()
{
Command_ReloadConfig cmd;
client->sendCommand(client->prepareAdminCommand(cmd));
}
void TabAdmin::actGrantReplayAccess()
{
if (!replayIdToGrant) {
return;
}
Command_GrantReplayAccess cmd;
cmd.set_replay_id(replayIdToGrant->text().toUInt());
cmd.set_moderator_name(client->getUserName().toStdString());
auto *pend = client->prepareModeratorCommand(cmd);
connect(pend,
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
this, &TabAdmin::grantReplayAccessProcessResponse);
client->sendCommand(pend);
}
void TabAdmin::actForceActivateUser()
{
if (!userToActivate) {
return;
}
Command_ForceActivateUser cmd;
cmd.set_username_to_activate(userToActivate->text().trimmed().toStdString());
cmd.set_moderator_name(client->getUserName().toStdString());
auto *pend = client->prepareModeratorCommand(cmd);
connect(pend,
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
this, &TabAdmin::activateUserProcessResponse);
client->sendCommand(pend);
}
void TabAdmin::grantReplayAccessProcessResponse(const Response &response, const CommandContainer &, const QVariant &)
{
auto *event = new Event_ReplayAdded();
switch (response.response_code()) {
case Response::RespOk:
client->replayAddedEventReceived(*event);
QMessageBox::information(this, tr("Success"), tr("Replay access granted"));
break;
case Response::RespContextError:
QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Replay ID invalid"));
break;
default:
QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Internal error"));
break;
}
}
void TabAdmin::activateUserProcessResponse(const Response &response, const CommandContainer &, const QVariant &)
{
switch (response.response_code()) {
case Response::RespActivationAccepted:
QMessageBox::information(this, tr("Success"), tr("User successfully activated"));
break;
case Response::RespNameNotFound:
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Username invalid"));
break;
case Response::RespActivationFailed:
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. User already active"));
break;
default:
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Internal error"));
break;
}
}
void TabAdmin::actUnlock()
{
if (fullAdmin)
adminGroupBox->setEnabled(true);
lockButton->setEnabled(true);
unlockButton->setEnabled(false);
locked = false;
emit adminLockChanged(false);
}
void TabAdmin::actLock()
{
if (fullAdmin)
adminGroupBox->setEnabled(false);
lockButton->setEnabled(false);
unlockButton->setEnabled(true);
locked = true;
emit adminLockChanged(true);
}

View File

@@ -1,449 +0,0 @@
#include "tab_replays.h"
#include "../../server/pending_command.h"
#include "../../server/remote/remote_replay_list_tree_widget.h"
#include "../../settings/cache_settings.h"
#include "../game_logic/abstract_client.h"
#include "pb/command_replay_delete_match.pb.h"
#include "pb/command_replay_download.pb.h"
#include "pb/command_replay_modify_match.pb.h"
#include "pb/event_replay_added.pb.h"
#include "pb/game_replay.pb.h"
#include "pb/response.pb.h"
#include "pb/response_replay_download.pb.h"
#include "tab_game.h"
#include <QAction>
#include <QApplication>
#include <QDesktopServices>
#include <QFileSystemModel>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QMessageBox>
#include <QToolBar>
#include <QTreeView>
#include <QUrl>
#include <QVBoxLayout>
TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
{
localDirModel = new QFileSystemModel(this);
localDirModel->setRootPath(SettingsCache::instance().getReplaysPath());
localDirModel->sort(0, Qt::AscendingOrder);
localDirView = new QTreeView;
localDirView->setModel(localDirModel);
localDirView->setColumnHidden(1, true);
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
localDirView->setSortingEnabled(true);
localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection);
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
// Left side layout
/* put an invisible dummy QToolBar in the leftmost column so that the main toolbar is centered.
* Really ugly workaround, but I couldn't figure out the proper way to make it centered */
QToolBar *dummyToolBar = new QToolBar(this);
QSizePolicy sizePolicy = dummyToolBar->sizePolicy();
sizePolicy.setRetainSizeWhenHidden(true);
dummyToolBar->setSizePolicy(sizePolicy);
dummyToolBar->setVisible(false);
leftToolBar = new QToolBar(this);
leftToolBar->setOrientation(Qt::Horizontal);
leftToolBar->setIconSize(QSize(32, 32));
QToolBar *leftRightmostToolBar = new QToolBar(this);
leftRightmostToolBar->setOrientation(Qt::Horizontal);
leftRightmostToolBar->setIconSize(QSize(32, 32));
QGridLayout *leftToolBarLayout = new QGridLayout;
leftToolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft);
leftToolBarLayout->addWidget(leftToolBar, 0, 1, Qt::AlignHCenter);
leftToolBarLayout->addWidget(leftRightmostToolBar, 0, 2, Qt::AlignRight);
QVBoxLayout *leftVbox = new QVBoxLayout;
leftVbox->addWidget(localDirView);
leftVbox->addLayout(leftToolBarLayout);
leftGroupBox = new QGroupBox;
leftGroupBox->setLayout(leftVbox);
// Right side layout
rightToolBar = new QToolBar;
rightToolBar->setOrientation(Qt::Horizontal);
rightToolBar->setIconSize(QSize(32, 32));
QHBoxLayout *rightToolBarLayout = new QHBoxLayout;
rightToolBarLayout->addStretch();
rightToolBarLayout->addWidget(rightToolBar);
rightToolBarLayout->addStretch();
serverDirView = new RemoteReplayList_TreeWidget(client);
QVBoxLayout *rightVbox = new QVBoxLayout;
rightVbox->addWidget(serverDirView);
rightVbox->addLayout(rightToolBarLayout);
rightGroupBox = new QGroupBox;
rightGroupBox->setLayout(rightVbox);
// combine layouts
QHBoxLayout *hbox = new QHBoxLayout;
hbox->addWidget(leftGroupBox);
hbox->addWidget(rightGroupBox);
// Left side actions
aOpenLocalReplay = new QAction(this);
aOpenLocalReplay->setIcon(QPixmap("theme:icons/view"));
connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay()));
connect(localDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenLocalReplay()));
aRenameLocal = new QAction(this);
aRenameLocal->setIcon(QPixmap("theme:icons/pencil"));
connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal);
aNewLocalFolder = new QAction(this);
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
connect(aNewLocalFolder, &QAction::triggered, this, &TabReplays::actNewLocalFolder);
aDeleteLocalReplay = new QAction(this);
aDeleteLocalReplay->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteLocalReplay, SIGNAL(triggered()), this, SLOT(actDeleteLocalReplay()));
aOpenReplaysFolder = new QAction(this);
aOpenReplaysFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon));
connect(aOpenReplaysFolder, &QAction::triggered, this, &TabReplays::actOpenReplaysFolder);
// Right side actions
aOpenRemoteReplay = new QAction(this);
aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view"));
connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay()));
connect(serverDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenRemoteReplay()));
aDownload = new QAction(this);
aDownload->setIcon(QPixmap("theme:icons/arrow_left_green"));
connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload()));
aKeep = new QAction(this);
aKeep->setIcon(QPixmap("theme:icons/lock"));
connect(aKeep, SIGNAL(triggered()), this, SLOT(actKeepRemoteReplay()));
aDeleteRemoteReplay = new QAction(this);
aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteRemoteReplay, SIGNAL(triggered()), this, SLOT(actDeleteRemoteReplay()));
// Add actions to toolbars
leftToolBar->addAction(aOpenLocalReplay);
leftToolBar->addAction(aRenameLocal);
leftToolBar->addAction(aNewLocalFolder);
leftToolBar->addAction(aDeleteLocalReplay);
leftRightmostToolBar->addAction(aOpenReplaysFolder);
rightToolBar->addAction(aOpenRemoteReplay);
rightToolBar->addAction(aDownload);
rightToolBar->addAction(aKeep);
rightToolBar->addAction(aDeleteRemoteReplay);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this,
SLOT(replayAddedEventReceived(const Event_ReplayAdded &)));
}
void TabReplays::retranslateUi()
{
leftGroupBox->setTitle(tr("Local file system"));
rightGroupBox->setTitle(tr("Server replay storage"));
aOpenLocalReplay->setText(tr("Watch replay"));
aRenameLocal->setText(tr("Rename"));
aNewLocalFolder->setText(tr("New folder"));
aDeleteLocalReplay->setText(tr("Delete"));
aOpenReplaysFolder->setText(tr("Open replays folder"));
aOpenRemoteReplay->setText(tr("Watch replay"));
aDownload->setText(tr("Download replay"));
aKeep->setText(tr("Toggle expiration lock"));
aDeleteRemoteReplay->setText(tr("Delete"));
}
void TabReplays::actLocalDoubleClick(const QModelIndex &curLeft)
{
if (!localDirModel->isDir(curLeft)) {
actOpenLocalReplay();
}
}
void TabReplays::actOpenLocalReplay()
{
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
for (const auto &curLeft : curLefts) {
if (localDirModel->isDir(curLeft))
continue;
QString filePath = localDirModel->filePath(curLeft);
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly))
continue;
QByteArray _data = f.readAll();
f.close();
GameReplay *replay = new GameReplay;
replay->ParseFromArray(_data.data(), _data.size());
emit openReplay(replay);
}
}
void TabReplays::actRenameLocal()
{
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
for (const auto &curLeft : curLefts) {
const QFileInfo info = localDirModel->fileInfo(curLeft);
const QString oldName = info.baseName();
const QString title = info.isDir() ? tr("Rename local folder") : tr("Rename local file");
bool ok;
QString newName = QInputDialog::getText(this, title, tr("New name:"), QLineEdit::Normal, oldName, &ok);
if (!ok) { // terminate all remaining selections if user cancels
return;
}
if (newName.isEmpty() || oldName == newName) {
continue;
}
QString newFileName = newName;
if (!info.suffix().isEmpty()) {
newFileName += "." + info.suffix();
}
const QString newFilePath = QFileInfo(info.dir(), newFileName).filePath();
if (!QFile::rename(info.filePath(), newFilePath)) {
QMessageBox::critical(this, tr("Error"), tr("Rename failed"));
}
}
}
void TabReplays::actNewLocalFolder()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
QModelIndex dirIndex;
if (curLeft.isValid() && !localDirModel->isDir(curLeft)) {
dirIndex = curLeft.parent();
} else {
dirIndex = curLeft;
}
bool ok;
QString folderName =
QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok);
if (!ok || folderName.isEmpty())
return;
localDirModel->mkdir(dirIndex, folderName);
}
void TabReplays::actDeleteLocalReplay()
{
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (curLefts.isEmpty()) {
return;
}
if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
return;
}
for (const auto &curLeft : curLefts) {
if (curLeft.isValid()) {
localDirModel->remove(curLeft);
}
}
}
void TabReplays::actOpenReplaysFolder()
{
QString dir = localDirModel->rootPath();
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
}
void TabReplays::actRemoteDoubleClick(const QModelIndex &curRight)
{
if (serverDirView->getReplay(curRight)) {
actOpenRemoteReplay();
}
}
void TabReplays::actOpenRemoteReplay()
{
auto const curRights = serverDirView->getSelectedReplays();
for (const auto curRight : curRights) {
if (!curRight) {
continue;
}
Command_ReplayDownload cmd;
cmd.set_replay_id(curRight->replay_id());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(openRemoteReplayFinished(const Response &)));
client->sendCommand(pend);
}
}
void TabReplays::openRemoteReplayFinished(const Response &r)
{
if (r.response_code() != Response::RespOk)
return;
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
GameReplay *replay = new GameReplay;
replay->ParseFromString(resp.replay_data());
emit openReplay(replay);
}
void TabReplays::actDownload()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
while (!localDirModel->isDir(curLeft)) {
curLeft = curLeft.parent();
}
for (const auto curRight : serverDirView->selectionModel()->selectedRows()) {
downloadNodeAtIndex(curLeft, curRight);
}
}
void TabReplays::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight)
{
if (const auto replayMatch = serverDirView->getReplayMatch(curRight)) {
// node at index is a folder
const QString name =
QString::number(replayMatch->game_id()) + "_" + QString::fromStdString(replayMatch->game_name());
const auto dirIndex = curLeft.isValid() ? curLeft : localDirModel->index(localDirModel->rootPath());
const auto newDirIndex = localDirModel->mkdir(dirIndex, name);
int rows = serverDirView->model()->rowCount(curRight);
for (int i = 0; i < rows; i++) {
const auto childIndex = serverDirView->model()->index(i, 0, curRight);
downloadNodeAtIndex(newDirIndex, childIndex);
}
} else if (const auto replay = serverDirView->getReplay(curRight)) {
// node at index is a replay
const QString dirPath = curLeft.isValid() ? localDirModel->filePath(curLeft) : localDirModel->rootPath();
const QString filePath = dirPath + QString("/replay_%1.cor").arg(replay->replay_id());
Command_ReplayDownload cmd;
cmd.set_replay_id(replay->replay_id());
PendingCommand *pend = client->prepareSessionCommand(cmd);
pend->setExtraData(filePath);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
client->sendCommand(pend);
}
// node at index was invalid
}
void TabReplays::downloadFinished(const Response &r,
const CommandContainer & /* commandContainer */,
const QVariant &extraData)
{
if (r.response_code() != Response::RespOk)
return;
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
QString filePath = extraData.toString();
const std::string &_data = resp.replay_data();
QFile f(filePath);
f.open(QIODevice::WriteOnly);
f.write((const char *)_data.data(), _data.size());
f.close();
}
void TabReplays::actKeepRemoteReplay()
{
const auto curRights = serverDirView->getSelectedReplayMatches();
if (curRights.isEmpty()) {
return;
}
for (const auto curRight : curRights) {
Command_ReplayModifyMatch cmd;
cmd.set_game_id(curRight->game_id());
cmd.set_do_not_hide(!curRight->do_not_hide());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(keepRemoteReplayFinished(Response, CommandContainer)));
client->sendCommand(pend);
}
}
void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
{
if (r.response_code() != Response::RespOk)
return;
const Command_ReplayModifyMatch &cmd =
commandContainer.session_command(0).GetExtension(Command_ReplayModifyMatch::ext);
ServerInfo_ReplayMatch temp;
temp.set_do_not_hide(cmd.do_not_hide());
serverDirView->updateMatchInfo(cmd.game_id(), temp);
}
void TabReplays::actDeleteRemoteReplay()
{
const auto curRights = serverDirView->getSelectedReplayMatches();
if (curRights.isEmpty()) {
return;
}
if (QMessageBox::warning(this, tr("Delete remote replay"),
tr("Are you sure you want to delete the selected replays?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
return;
}
for (const auto curRight : curRights) {
Command_ReplayDeleteMatch cmd;
cmd.set_game_id(curRight->game_id());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(deleteRemoteReplayFinished(Response, CommandContainer)));
client->sendCommand(pend);
}
}
void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
{
if (r.response_code() != Response::RespOk)
return;
const Command_ReplayDeleteMatch &cmd =
commandContainer.session_command(0).GetExtension(Command_ReplayDeleteMatch::ext);
serverDirView->removeMatchInfo(cmd.game_id());
}
void TabReplays::replayAddedEventReceived(const Event_ReplayAdded &event)
{
if (event.has_match_info()) {
// 99.9% of events will have match info (Normal Workflow)
serverDirView->addMatchInfo(event.match_info());
} else {
// When a Moderator force adds a replay, we need to refresh their view
serverDirView->refreshTree();
}
}

View File

@@ -1,104 +0,0 @@
#include "tab_deck_storage_visual.h"
#include "../../../game/cards/card_database_model.h"
#include "../tab_supervisor.h"
#include "pb/command_deck_del.pb.h"
#include <QAction>
#include <QDebug>
#include <QDirIterator>
#include <QFileSystemModel>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QMessageBox>
#include <QMouseEvent>
#include <QScreen>
#include <QToolBar>
#include <QTreeView>
class FlowLayout;
TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor, AbstractClient *_client)
: Tab(_tabSupervisor), client(_client)
{
deck_list_model = new DeckListModel(this);
deck_list_model->setObjectName("visualDeckModel");
QWidget *container = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(container);
container->setLayout(layout);
this->setCentralWidget(container);
leftToolBar = new QToolBar;
leftToolBar->setOrientation(Qt::Horizontal);
leftToolBar->setIconSize(QSize(32, 32));
QHBoxLayout *leftToolBarLayout = new QHBoxLayout(this);
leftToolBarLayout->addStretch();
leftToolBarLayout->addWidget(leftToolBar);
leftToolBarLayout->addStretch();
aOpenLocalDeck = new QAction(this);
aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil"));
connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck()));
aDeleteLocalDeck = new QAction(this);
aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck()));
connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addDeckEditorTab);
leftToolBar->addAction(aOpenLocalDeck);
leftToolBar->addAction(aDeleteLocalDeck);
visualDeckStorageWidget = new VisualDeckStorageWidget(this);
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::imageDoubleClicked, this,
&TabDeckStorageVisual::actOpenLocalDeck);
// layout->addWidget(leftToolBar);
layout->addWidget(visualDeckStorageWidget);
retranslateUi();
}
void TabDeckStorageVisual::closeRequest()
{
this->close();
}
void TabDeckStorageVisual::retranslateUi()
{
aOpenLocalDeck->setText(tr("Open in deck editor"));
aDeleteLocalDeck->setText(tr("Delete"));
}
QString TabDeckStorageVisual::getTargetPath() const
{
return {};
}
void TabDeckStorageVisual::actOpenLocalDeck(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
{
(void)event;
DeckLoader deckLoader;
if (!deckLoader.loadFromFile(instance->filePath, DeckLoader::CockatriceFormat))
return;
emit openDeckEditor(&deckLoader);
}
void TabDeckStorageVisual::actDeleteLocalDeck()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
if (localDirModel->isDir(curLeft))
return;
if (QMessageBox::warning(this, tr("Delete local file"),
tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
localDirModel->remove(curLeft);
}
void TabDeckStorageVisual::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
qDebug() << "Card update process finished with exit code:" << exitCode << "and exit status:" << exitStatus;
}

View File

@@ -1,56 +0,0 @@
#ifndef TAB_DECK_STORAGE_VISUAL_H
#define TAB_DECK_STORAGE_VISUAL_H
#include "../../../deck/deck_list_model.h"
#include "../../../deck/deck_view.h"
#include "../../ui/widgets/cards/deck_preview_card_picture_widget.h"
#include "../../ui/widgets/visual_deck_storage/visual_deck_storage_widget.h"
#include "../tab.h"
#include <QProcess>
class AbstractClient;
class QTreeView;
class QFileSystemModel;
class QToolBar;
class QTreeWidget;
class QTreeWidgetItem;
class QGroupBox;
class CommandContainer;
class Response;
class DeckLoader;
class TabDeckStorageVisual final : public Tab
{
Q_OBJECT
public:
TabDeckStorageVisual(TabSupervisor *_tabSupervisor, AbstractClient *_client);
void retranslateUi() override;
QString getTabText() const override
{
return tr("Visual Deck storage");
}
public slots:
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
void closeRequest() override;
void actOpenLocalDeck(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void actDeleteLocalDeck();
signals:
void openDeckEditor(const DeckLoader *deckLoader);
private:
QWidget *container;
QHBoxLayout *layout;
AbstractClient *client;
QTreeView *localDirView;
QFileSystemModel *localDirModel;
QToolBar *leftToolBar;
QGroupBox *leftGroupBox;
VisualDeckStorageWidget *visualDeckStorageWidget;
DeckListModel *deck_list_model;
QAction *aOpenLocalDeck, *aDeleteLocalDeck;
QString getTargetPath() const;
};
#endif

View File

@@ -1,337 +0,0 @@
/**
* @file flow_layout.cpp
* @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets
* in rows within the constraints of available width or parent scroll areas.
*/
#include "flow_layout.h"
#include "../widgets/general/layout_containers/flow_widget.h"
#include <QDebug>
#include <QLayoutItem>
#include <QScrollArea>
#include <QStyle>
/**
* @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values.
* @param parent The parent widget for this layout.
* @param margin The layout margin.
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
FlowLayout::FlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: QLayout(parent), horizontalMargin(hSpacing), verticalMargin(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
/**
* @brief Destructor for FlowLayout, which cleans up all items in the layout.
*/
FlowLayout::~FlowLayout()
{
QLayoutItem *item;
while ((item = FlowLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Indicates the layout's support for expansion in both horizontal and vertical directions.
* @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill.
*/
Qt::Orientations FlowLayout::expandingDirections() const
{
return Qt::Horizontal | Qt::Vertical;
}
/**
* @brief Indicates that this layout's height depends on its width.
* @return True, as the layout adjusts its height to fit the specified width.
*/
bool FlowLayout::hasHeightForWidth() const
{
return true;
}
/**
* @brief Calculates the required height to display all items within the specified width.
* @param width The available width for arranging items.
* @return The total height needed to fit all items in rows constrained by the specified width.
*/
int FlowLayout::heightForWidth(const int width) const
{
int height = 0;
int rowWidth = 0;
int rowHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemWidth = item->sizeHint().width() + horizontalSpacing();
if (rowWidth + itemWidth > width) { // Start a new row if the row width exceeds available width
height += rowHeight + verticalSpacing();
rowWidth = itemWidth;
rowHeight = item->sizeHint().height() + verticalSpacing();
} else {
rowWidth += itemWidth;
rowHeight = qMax(rowHeight, item->sizeHint().height());
}
}
height += rowHeight; // Add the final row's height
return height;
}
/**
* @brief Arranges layout items in rows within the specified rectangle bounds.
* @param rect The area within which to position layout items.
*/
void FlowLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom); // Retrieves the layout's content margins.
// Adjust the rectangle to exclude margins.
const QRect adjustedRect = rect.adjusted(+left, +top, -right, -bottom);
// Calculate the available width for items, considering either the adjusted rectangle's width
// or the parent scroll area width, if applicable.
const int availableWidth = qMax(adjustedRect.width(), getParentScrollAreaWidth());
// Arrange all rows of items within the available width and get the total height used.
const int totalHeight = layoutAllRows(adjustedRect.x(), adjustedRect.y(), availableWidth);
// If the layout's parent is a QWidget, update its minimum size to ensure it can accommodate
// the arranged items' dimensions.
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
}
}
/**
* @brief Arranges items in rows based on the available width.
* Items are added to a row until the row's width exceeds `availableWidth`.
* Then, a new row is started.
* @param originX The starting x-coordinate for the row layout.
* @param originY The starting y-coordinate for the row layout.
* @param availableWidth The available width to lay out items.
* @return The y-coordinate of the final row's end position.
*/
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
{
QVector<QLayoutItem *> rowItems; // Temporary storage for items in the current row.
int currentXPosition = originX; // Tracks the x-coordinate for placing items in the current row.
int currentYPosition = originY; // Tracks the y-coordinate, updated after each row.
int rowHeight = 0; // Tracks the maximum height of items in the current row.
// Iterate through all layout items to arrange them.
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the item.
const int itemWidth = itemSize.width() + horizontalSpacing();
// Check if the item fits in the current row's remaining width.
if (currentXPosition + itemWidth > availableWidth) {
// If not, layout the current row and start a new row.
layoutSingleRow(rowItems, originX, currentYPosition);
rowItems.clear(); // Clear the temporary storage for the new row.
currentXPosition = originX; // Reset x-position to the start of the new row.
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down for the new row.
rowHeight = 0; // Reset row height for the new row.
}
// Add the item to the current row.
rowItems.append(item);
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row height to the tallest item.
currentXPosition += itemSize.width() + horizontalSpacing(); // Move x-position for the next item.
}
// Layout the final row if there are remaining items.
layoutSingleRow(rowItems, originX, currentYPosition);
currentYPosition += rowHeight; // Add the final row's height
return currentYPosition;
}
/**
* @brief Helper function for arranging a single row of items within specified bounds.
* @param rowItems Items to be arranged in the row.
* @param x The x-coordinate for starting the row.
* @param y The y-coordinate for starting the row.
*/
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
{
// Iterate through each item in the row and position it.
for (QLayoutItem *item : rowItems) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemMaxSize = item->widget()->maximumSize(); // Get the item's maximum allowable size.
// Constrain the item's width and height to its size hint or maximum size.
int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the calculated size and position.
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the x-position for the next item, including horizontal spacing.
x += itemWidth + horizontalSpacing();
}
}
/**
* @brief Returns the preferred size for this layout.
* @return The maximum of all item size hints as a QSize.
*/
QSize FlowLayout::sizeHint() const
{
QSize size;
for (const QLayoutItem *item : items) {
if (item != nullptr && !item->isEmpty()) {
size = size.expandedTo(item->sizeHint());
}
}
return size.isValid() ? size : QSize(0, 0);
}
/**
* @brief Returns the minimum size required to display all layout items.
* @return The minimum QSize needed by the layout.
*/
QSize FlowLayout::minimumSize() const
{
QSize size;
for (const QLayoutItem *item : items) {
if (item != nullptr && !item->isEmpty()) {
size = size.expandedTo(item->minimumSize());
}
}
size.setWidth(qMin(size.width(), getParentScrollAreaWidth()));
size.setHeight(qMin(size.height(), getParentScrollAreaHeight()));
return size.isValid() ? size : QSize(0, 0);
}
/**
* @brief Adds a new item to the layout.
* @param item The layout item to add.
*/
void FlowLayout::addItem(QLayoutItem *item)
{
if (item != nullptr) {
items.append(item);
}
}
/**
* @brief Retrieves the count of items in the layout.
* @return The number of layout items.
*/
int FlowLayout::count() const
{
return static_cast<int>(items.size());
}
/**
* @brief Returns the layout item at the specified index.
* @param index The index of the item to retrieve.
* @return A pointer to the item at the specified index, or nullptr if out of range.
*/
QLayoutItem *FlowLayout::itemAt(const int index) const
{
return (index >= 0 && index < items.size()) ? items.value(index) : nullptr;
}
/**
* @brief Removes and returns the item at the specified index.
* @param index The index of the item to remove.
* @return A pointer to the removed item, or nullptr if out of range.
*/
QLayoutItem *FlowLayout::takeAt(const int index)
{
return (index >= 0 && index < items.size()) ? items.takeAt(index) : nullptr;
}
/**
* @brief Gets the horizontal spacing between items.
* @return The horizontal spacing if set, otherwise a smart default.
*/
int FlowLayout::horizontalSpacing() const
{
return (horizontalMargin >= 0) ? horizontalMargin : smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
}
/**
* @brief Gets the vertical spacing between items.
* @return The vertical spacing if set, otherwise a smart default.
*/
int FlowLayout::verticalSpacing() const
{
return (verticalMargin >= 0) ? verticalMargin : smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}
/**
* @brief Calculates smart spacing based on the parent widget style.
* @param pm The pixel metric to calculate.
* @return The calculated spacing value.
*/
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
{
QObject *parent = this->parent();
if (!parent) {
return -1;
}
if (parent->isWidgetType()) {
const auto *pw = dynamic_cast<QWidget *>(parent);
return pw->style()->pixelMetric(pm, nullptr, pw);
}
return dynamic_cast<QLayout *>(parent)->spacing();
}
/**
* @brief Gets the width of the parent scroll area, if any.
* @return The width of the scroll area's viewport, or 0 if not found.
*/
int FlowLayout::getParentScrollAreaWidth() const
{
QWidget *parent = parentWidget();
while (parent) {
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
return scrollArea->viewport()->width();
}
parent = parent->parentWidget();
}
return 0;
}
/**
* @brief Gets the height of the parent scroll area, if any.
* @return The height of the scroll area's viewport, or 0 if not found.
*/
int FlowLayout::getParentScrollAreaHeight() const
{
QWidget *parent = parentWidget();
while (parent) {
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
return scrollArea->viewport()->height();
}
parent = parent->parentWidget();
}
return 0;
}

View File

@@ -1,43 +0,0 @@
#ifndef FLOW_LAYOUT_H
#define FLOW_LAYOUT_H
#include <QLayout>
#include <QList>
#include <QWidget>
#include <qstyle.h>
class FlowLayout : public QLayout
{
public:
explicit FlowLayout(QWidget *parent = nullptr);
FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing);
~FlowLayout() override;
void addItem(QLayoutItem *item) override;
[[nodiscard]] int count() const override;
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
QLayoutItem *takeAt(int index) override;
[[nodiscard]] int horizontalSpacing() const;
[[nodiscard]] Qt::Orientations expandingDirections() const override;
[[nodiscard]] bool hasHeightForWidth() const override;
[[nodiscard]] int heightForWidth(int width) const override;
[[nodiscard]] int verticalSpacing() const;
[[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const;
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
[[nodiscard]] int getParentScrollAreaWidth() const;
[[nodiscard]] int getParentScrollAreaHeight() const;
void setGeometry(const QRect &rect) override;
virtual int layoutAllRows(int originX, int originY, int availableWidth);
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
[[nodiscard]] QSize sizeHint() const override;
[[nodiscard]] QSize minimumSize() const override;
protected:
QList<QLayoutItem *> items; // List to store layout items
int horizontalMargin;
int verticalMargin;
};
#endif // FLOW_LAYOUT_H

View File

@@ -1,142 +0,0 @@
#include "horizontal_flow_layout.h"
/**
* @brief Constructs a HorizontalFlowLayout instance with the specified parent widget.
* This layout arranges items in columns within the given height, automatically adjusting its width.
* @param parent The parent widget to which this layout belongs.
* @param margin The layout margin.
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
HorizontalFlowLayout::HorizontalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: FlowLayout(parent, margin, hSpacing, vSpacing)
{
}
/**
* @brief Destructor for HorizontalFlowLayout, responsible for cleaning up layout items.
*/
HorizontalFlowLayout::~HorizontalFlowLayout()
{
QLayoutItem *item;
while ((item = FlowLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Calculates the required width to display all items, given a specified height.
* This method arranges items into columns and determines the total width needed.
* @param height The available height for arranging layout items.
* @return The total width required to fit all items, organized in columns constrained by the given height.
*/
int HorizontalFlowLayout::heightForWidth(const int height) const
{
int width = 0;
int colWidth = 0;
int colHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemHeight = item->sizeHint().height();
if (colHeight + itemHeight > height) {
width += colWidth;
colHeight = itemHeight;
colWidth = item->sizeHint().width();
} else {
colHeight += itemHeight;
colWidth = qMax(colWidth, item->sizeHint().width());
}
}
width += colWidth; // Add width of the last column
return width;
}
/**
* @brief Sets the geometry of the layout items, arranging them in columns within the given height.
* @param rect The rectangle area defining the layout space.
*/
void HorizontalFlowLayout::setGeometry(const QRect &rect)
{
const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(totalWidth, availableHeight);
}
}
/**
* @brief Lays out items into columns according to the available height, starting from a given origin.
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
* @param originX The x-coordinate for the layout start position.
* @param originY The y-coordinate for the layout start position.
* @param availableHeight The height within which each column is constrained.
* @return The total width after arranging all columns.
*/
int HorizontalFlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
{
QVector<QLayoutItem *> colItems; // Holds items for the current column
int currentXPosition = originX; // Tracks the x-coordinate while placing items
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
int colWidth = 0; // Tracks the maximum width of items in the current column
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the current item
// Check if the current item fits in the remaining height of the current column
if (currentYPosition + itemSize.height() > availableHeight) {
// If not, layout the current column and start a new column
layoutSingleColumn(colItems, currentXPosition, originY);
colItems.clear(); // Reset the list for the new column
currentYPosition = originY; // Reset y-position to the column's start
currentXPosition += colWidth; // Move x-position to the next column
colWidth = 0; // Reset column width for the new column
}
// Add the item to the current column
colItems.append(item);
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
currentYPosition += itemSize.height(); // Move y-position for the next item
}
// Layout the final column if there are any remaining items
layoutSingleColumn(colItems, currentXPosition, originY);
// Return the total width used, including the last column's width
return currentXPosition + colWidth;
}
/**
* @brief Arranges a single column of items within specified x and y starting positions.
* @param colItems A list of items to be arranged in the column.
* @param x The starting x-coordinate for the column.
* @param y The starting y-coordinate for the column.
*/
void HorizontalFlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
{
for (QLayoutItem *item : colItems) {
if (item != nullptr && item->isEmpty()) {
continue;
}
// Get the maximum allowed size for the item
QSize itemMaxSize = item->widget()->maximumSize();
// Constrain the item's width and height to its size hint or maximum size
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the y-position down by the item's height to place the next item below
y += itemHeight;
}
}

View File

@@ -1,19 +0,0 @@
#ifndef HORIZONTAL_FLOW_LAYOUT_H
#define HORIZONTAL_FLOW_LAYOUT_H
#include "flow_layout.h"
class HorizontalFlowLayout : public FlowLayout
{
public:
explicit HorizontalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0);
~HorizontalFlowLayout() override;
[[nodiscard]] int heightForWidth(int height) const override;
void setGeometry(const QRect &rect) override;
int layoutAllColumns(int originX, int originY, int availableHeight);
static void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
};
#endif // HORIZONTAL_FLOW_LAYOUT_H

View File

@@ -1,474 +0,0 @@
#include "overlap_layout.h"
#include <QtMath>
/**
* @class OverlapLayout
* @brief Custom layout class to arrange widgets with overlapping positions.
*
* The OverlapLayout class is a QLayout subclass that arranges child widgets
* in an overlapping configuration, allowing control over the overlap percentage
* and the number of rows or columns based on the chosen layout direction. This
* layout is particularly useful for visualizing elements that need to partially
* stack over one another, either horizontally or vertically.
*/
/**
* @brief Constructs an OverlapLayout with the specified parameters.
*
* Initializes a new OverlapLayout with the given overlap percentage, row or column limit,
* and layout direction. The overlap percentage determines how much each widget will
* overlap with the previous one. If maxColumns or maxRows are set to zero, it implies
* no limit in that respective dimension.
*
* @param overlapPercentage An integer representing the percentage of overlap between items (0-100).
* @param maxColumns The maximum number of columns allowed in the layout when in horizontal orientation (0 for
* unlimited).
* @param maxRows The maximum number of rows allowed in the layout when in vertical orientation (0 for unlimited).
* @param direction The orientation direction of the layout, either Qt::Horizontal or Qt::Vertical.
* @param parent The parent widget of this layout.
*/
OverlapLayout::OverlapLayout(QWidget *parent,
const int overlapPercentage,
const int maxColumns,
const int maxRows,
const Qt::Orientation direction)
: QLayout(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows),
direction(direction)
{
}
/**
* @brief Destructor for OverlapLayout, ensuring cleanup of all layout items.
*
* Iterates through all layout items and deletes them. This prevents memory
* leaks by removing all child QLayoutItems stored in the layout.
*/
OverlapLayout::~OverlapLayout()
{
QLayoutItem *item;
while ((item = OverlapLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Adds a new item to the layout.
*
* Appends a QLayoutItem to the internal list, allowing it to be positioned within the
* layout during the next geometry update. This method does not directly arrange the
* items; it merely adds them to the layouts tracking.
*
* @param item Pointer to the QLayoutItem being added to this layout.
*/
void OverlapLayout::addItem(QLayoutItem *item)
{
if (item != nullptr) {
itemList.append(item);
}
}
/**
* @brief Retrieves the total count of items within the layout.
*
* Returns the number of items stored in the layout's internal item list.
* This count reflects how many widgets or spacers the layout is currently managing.
*
* @return Integer count of items in the layout.
*/
int OverlapLayout::count() const
{
return static_cast<int>(itemList.size());
}
/**
* @brief Provides access to a layout item at a specified index.
*
* Allows retrieval of a QLayoutItem from the layouts internal list
* by index. If the index is out of bounds, this function will return nullptr.
*
* @param index The index of the desired item.
* @return Pointer to the QLayoutItem at the specified index, or nullptr if index is invalid.
*/
QLayoutItem *OverlapLayout::itemAt(const int index) const
{
return (index >= 0 && index < itemList.size()) ? itemList.value(index) : nullptr;
}
/**
* @brief Removes and returns a layout item at the specified index.
*
* Removes a QLayoutItem from the layout at the given index, reducing the layout's count.
* If the index is invalid, this function returns nullptr without any effect.
*
* @param index The index of the item to remove.
* @return Pointer to the removed QLayoutItem, or nullptr if index is invalid.
*/
QLayoutItem *OverlapLayout::takeAt(const int index)
{
return (index >= 0 && index < itemList.size()) ? itemList.takeAt(index) : nullptr;
}
/**
* @brief Sets the geometry for the layout items, arranging them with the specified overlap.
* @param rect The rectangle defining the area within which the layout should arrange items.
*/
void OverlapLayout::setGeometry(const QRect &rect)
{
// Call the base class implementation to ensure standard layout behavior.
QLayout::setGeometry(rect);
// If there are no items to layout, exit early.
if (itemList.isEmpty()) {
return;
}
// Get the parent widget for size and margin calculations.
const QWidget *parentWidget = this->parentWidget();
if (!parentWidget) {
return;
}
// Calculate available width and height, subtracting the parent's margins.
int availableWidth = parentWidget->width();
int availableHeight = parentWidget->height();
const QMargins margins = parentWidget->contentsMargins();
availableWidth -= margins.left() + margins.right();
availableHeight -= margins.top() + margins.bottom();
// Determine the maximum item width and height among all layout items.
int maxItemWidth = 0;
int maxItemHeight = 0;
for (QLayoutItem *item : itemList) {
if (item != nullptr && item->widget()) {
QSize itemSize = item->widget()->sizeHint();
maxItemWidth = qMax(maxItemWidth, itemSize.width());
maxItemHeight = qMax(maxItemHeight, itemSize.height());
}
}
// Calculate the overlap offsets based on the layout direction and overlap percentage.
const int overlapOffsetWidth = (direction == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0;
const int overlapOffsetHeight = (direction == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0;
// Determine the number of columns based on layout constraints and available space.
int columns;
if (direction == Qt::Horizontal) {
if (maxColumns > 0) {
// Calculate the maximum possible columns given the available width and overlap.
const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth);
// Use the smaller of maxColumns and availableColumns.
columns = qMin(maxColumns, availableColumns);
} else {
// If no maxColumns constraint, allow as many columns as possible.
columns = INT_MAX;
}
} else {
// If not a horizontal layout, column count is irrelevant.
columns = INT_MAX;
}
// Determine the number of rows based on layout constraints and available space.
int rows;
if (direction == Qt::Vertical) {
if (maxRows > 0) {
// Calculate the maximum possible rows given the available height and overlap.
const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight);
// Use the smaller of maxRows and availableRows.
rows = qMin(maxRows, availableRows);
} else {
// If no maxRows constraint, allow as many rows as possible.
rows = INT_MAX;
}
} else {
// If not a vertical layout, row count is irrelevant.
rows = INT_MAX;
}
// Initialize row and column indices.
int currentRow = 0;
int currentColumn = 0;
// Loop through all items and position them based on the calculated offsets.
for (const auto item : itemList) {
if (item == nullptr) {
continue;
}
// Calculate the position of the current item.
const int xPos = rect.left() + currentColumn * (maxItemWidth - overlapOffsetWidth);
const int yPos = rect.top() + currentRow * (maxItemHeight - overlapOffsetHeight);
item->setGeometry(QRect(xPos, yPos, maxItemWidth, maxItemHeight));
// Update row and column indices based on the layout direction.
if (direction == Qt::Horizontal) {
currentColumn++;
if (currentColumn >= columns) {
currentColumn = 0;
currentRow++;
}
} else {
currentRow++;
if (currentRow >= rows) {
currentRow = 0;
currentColumn++;
}
}
}
}
/**
* @brief Calculates the preferred size for the layout, considering overlap and orientation.
* @return The preferred layout size as a QSize object.
*/
QSize OverlapLayout::calculatePreferredSize() const
{
// Determine the maximum item dimensions.
int maxItemWidth = 0;
int maxItemHeight = 0;
for (QLayoutItem *item : itemList) {
if (item == nullptr) {
continue;
}
if (!item->widget()) {
continue;
}
QSize itemSize = item->widget()->sizeHint();
maxItemWidth = qMax(maxItemWidth, itemSize.width());
maxItemHeight = qMax(maxItemHeight, itemSize.height());
}
// Calculate the overlap offsets.
const int extra_for_overlap = (100 - overlapPercentage);
const int overlapOffsetWidth =
(direction == Qt::Horizontal) ? qRound((maxItemWidth / 100.0) * extra_for_overlap) : 0;
const int overlapOffsetHeight =
(direction == Qt::Vertical) ? qRound((maxItemHeight / 100.0) * extra_for_overlap) : 0;
// Variables to hold the total dimensions based on layout orientation.
int totalWidth = 0;
int totalHeight = 0;
// Calculate the total size based on the layout direction and constraints.
if (direction == Qt::Horizontal) {
// Determine the number of columns:
// - Use maxColumns if it is greater than 0 (constraint set by the user).
// - Otherwise, set the number of columns to the total number of items in the layout.
const int numColumns = (maxColumns > 0) ? static_cast<int>(maxColumns) : static_cast<int>(itemList.size());
// Calculate the extra space required for overlaps between columns:
// - Each overlap reduces the effective width of subsequent items.
const int extra_space_for_overlaps = overlapOffsetWidth * (numColumns - 1);
// Total width:
// - The first item's width is fully included (maxItemWidth).
// - Add the space for all overlaps between adjacent items.
totalWidth = maxItemWidth + extra_space_for_overlaps;
// Determine the number of rows:
// - Use maxRows if maxColumns is set (to constrain the number of rows).
// - Otherwise, assume a single row.
const int numRows =
(maxColumns > 0) ? qCeil(static_cast<double>(itemList.size()) / static_cast<double>(numColumns)) : 1;
// Total height:
// - Multiply the number of rows by the item height (maxItemHeight).
// - Subtract the height overlap between rows to avoid double-counting.
totalHeight = maxItemHeight * numRows - (numRows - 1) * overlapOffsetHeight;
} else if (direction == Qt::Vertical) {
// Determine the number of columns:
// - Use maxRows to calculate how many columns are needed if a row constraint exists.
// - Otherwise, assume a single column.
const int numColumns =
(maxRows != 0) ? qCeil(static_cast<double>(itemList.size()) / static_cast<double>(maxRows)) : 1;
// Total width:
// - Multiply the number of columns by the item width (maxItemWidth).
// - Subtract the width overlap between columns to avoid double-counting.
totalWidth = maxItemWidth * numColumns - (numColumns - 1) * overlapOffsetWidth;
// Determine the number of rows:
// - Use maxRows if it is greater than 0 (constraint set by the user).
// - Otherwise, set the number of rows to the total number of items in the layout.
const int numRows = (maxRows > 0) ? static_cast<int>(maxRows) : static_cast<int>(itemList.size());
// Calculate the extra space required for overlaps between rows:
// - Each overlap reduces the effective height of subsequent items.
const int extraSpaceForOverlaps = overlapOffsetHeight * (numRows - 1);
// Total height:
// - The first item's height is fully included (maxItemHeight).
// - Add the space for all overlaps between adjacent items.
totalHeight = maxItemHeight + extraSpaceForOverlaps;
}
return {totalWidth, totalHeight};
}
/**
* @brief Returns the size hint for the layout, based on preferred size calculations.
*
* Provides a recommended size for the layout, useful for layouts that need to fit within
* a specific parent container size. This takes into account the preferred size and
* any specific item size requirements.
*
* @return The layout's recommended QSize.
*/
QSize OverlapLayout::sizeHint() const
{
return calculatePreferredSize();
}
/**
* @brief Provides the minimum size hint for the layout, ensuring functionality within constraints.
*
* Defines a minimum workable size for the layout to prevent excessive compression
* that could distort item arrangement.
*
* @return The minimum QSize for this layout.
*/
QSize OverlapLayout::minimumSize() const
{
return calculatePreferredSize();
}
/**
* @brief Sets the layout's orientation direction.
*
* @param _direction The new orientation direction (Qt::Horizontal or Qt::Vertical).
*/
void OverlapLayout::setDirection(const Qt::Orientation _direction)
{
direction = _direction;
}
/**
* @brief Sets the maximum number of columns for horizontal orientation.
*
* @param _maxColumns New maximum column count.
*/
void OverlapLayout::setMaxColumns(const int _maxColumns)
{
if (_maxColumns >= 0) {
maxColumns = _maxColumns;
}
}
/**
* @brief Sets the maximum number of rows for vertical orientation.
*
* @param _maxRows New maximum row count.
*/
void OverlapLayout::setMaxRows(const int _maxRows)
{
if (_maxRows >= 0) {
maxRows = _maxRows;
}
}
/**
* @brief Calculates the maximum number of columns for a vertical overlap layout based on the current width.
*
* This function determines the maximum number of columns that can fit within the layout's width
* given the overlap percentage and item size, based on the current layout direction.
*
* @return Maximum number of columns that can fit within the layout width.
*/
int OverlapLayout::calculateMaxColumns() const
{
if (direction != Qt::Vertical || itemList.isEmpty()) {
return 1; // Only relevant if the layout direction is vertical
}
// Determine maximum item width
int maxItemWidth = 0;
for (QLayoutItem *item : itemList) {
if (item == nullptr || !item->widget()) {
continue;
}
QSize itemSize = item->widget()->sizeHint();
maxItemWidth = qMax(maxItemWidth, itemSize.width());
}
const int availableWidth = parentWidget() ? parentWidget()->width() : 0;
// Determine the maximum number of columns that can fit
const int columns = availableWidth / maxItemWidth;
return qMax(1, columns);
}
/**
* @brief Calculates the maximum number of rows needed for a given number of columns in a vertical overlap layout.
*
* Determines how many rows are required to arrange all items given the calculated or specified number of columns.
*
* @param columns The number of columns available.
* @return The total number of rows required.
*/
int OverlapLayout::calculateRowsForColumns(const int columns) const
{
if (direction != Qt::Vertical || itemList.isEmpty() || columns <= 0) {
return 1; // Only relevant if the layout direction is vertical and there are items
}
const int totalItems = static_cast<int>(itemList.size());
return qCeil(totalItems / columns);
}
/**
* @brief Calculates the maximum number of rows for a horizontal overlap layout based on the current height.
*
* This function determines the maximum number of rows that can fit within the layout's height
* given the overlap percentage and item size, based on the current layout direction.
*
* @return Maximum number of rows that can fit within the layout height.
*/
int OverlapLayout::calculateMaxRows() const
{
if (direction != Qt::Horizontal || itemList.isEmpty()) {
return 1; // Only relevant if the layout direction is horizontal
}
// Determine maximum item height
int maxItemHeight = 0;
for (QLayoutItem *item : itemList) {
if (item == nullptr || !item->widget()) {
continue;
}
QSize itemSize = item->widget()->sizeHint();
maxItemHeight = qMax(maxItemHeight, itemSize.height());
}
// Calculate the effective height of each item with the overlap applied
const int overlapOffsetHeight = (maxItemHeight * (100 - overlapPercentage)) / 100;
const int availableHeight = parentWidget() ? parentWidget()->height() : 0;
// Determine the maximum number of rows that can fit
const int rows = availableHeight / overlapOffsetHeight;
return qMax(1, rows);
}
/**
* @brief Calculates the maximum number of columns needed for a given number of rows in a horizontal overlap layout.
*
* Determines how many columns are required to arrange all items given the calculated or specified number of rows.
*
* @param rows The number of rows available.
* @return The total number of columns required.
*/
int OverlapLayout::calculateColumnsForRows(const int rows) const
{
if (direction != Qt::Horizontal || itemList.isEmpty() || rows <= 0) {
return 1; // Only relevant if the layout direction is horizontal and there are items
}
const int totalItems = static_cast<int>(itemList.size());
return qCeil(totalItems / rows);
}

View File

@@ -1,44 +0,0 @@
#ifndef OVERLAP_LAYOUT_H
#define OVERLAP_LAYOUT_H
#include <QLayout>
#include <QList>
#include <QWidget>
class OverlapLayout : public QLayout
{
public:
OverlapLayout(QWidget *parent = nullptr,
int overlapPercentage = 10,
int maxColumns = 2,
int maxRows = 2,
Qt::Orientation direction = Qt::Horizontal);
~OverlapLayout();
void addItem(QLayoutItem *item) override;
int count() const override;
QLayoutItem *itemAt(int index) const override;
QLayoutItem *takeAt(int index) override;
void setGeometry(const QRect &rect) override;
QSize minimumSize() const override;
QSize sizeHint() const override;
void setMaxColumns(int _maxColumns);
void setMaxRows(int _maxRows);
int calculateMaxColumns() const;
int calculateRowsForColumns(int columns) const;
int calculateMaxRows() const;
int calculateColumnsForRows(int rows) const;
void setDirection(Qt::Orientation _direction);
private:
QList<QLayoutItem *> itemList;
int overlapPercentage;
int maxColumns;
int maxRows;
Qt::Orientation direction;
// Calculate the preferred size of the layout
QSize calculatePreferredSize() const;
};
#endif // OVERLAP_LAYOUT_H

View File

@@ -1,144 +0,0 @@
#include "vertical_flow_layout.h"
/**
* @brief Constructs a VerticalFlowLayout instance with the specified parent widget.
* This layout arranges items in rows within the given width, automatically adjusting its height.
* @param parent The parent widget to which this layout belongs.
* @param margin The layout margin.
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
VerticalFlowLayout::VerticalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: FlowLayout(parent, margin, hSpacing, vSpacing)
{
}
/**
* @brief Destructor for VerticalFlowLayout, responsible for cleaning up layout items.
*/
VerticalFlowLayout::~VerticalFlowLayout()
{
QLayoutItem *item;
while ((item = FlowLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Calculates the required height to display all items, given a specified width.
* This method arranges items into rows and determines the total height needed.
* @param width The available width for arranging layout items.
* @return The total height required to fit all items, organized in rows constrained by the given width.
*/
int VerticalFlowLayout::heightForWidth(const int width) const
{
int height = 0;
int rowWidth = 0;
int rowHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemWidth = item->sizeHint().width() + horizontalSpacing();
if (rowWidth + itemWidth > width) {
height += rowHeight + verticalSpacing();
rowWidth = itemWidth;
rowHeight = item->sizeHint().height();
} else {
rowWidth += itemWidth;
rowHeight = qMax(rowHeight, item->sizeHint().height());
}
}
height += rowHeight; // Add height of the last row
return height;
}
/**
* @brief Sets the geometry of the layout items, arranging them in rows within the given width.
* @param rect The rectangle area defining the layout space.
*/
void VerticalFlowLayout::setGeometry(const QRect &rect)
{
// If we have a parent scroll area, we're clamped to that, else we use our own rectangle.
const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth();
const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
}
}
/**
* @brief Lays out items into rows according to the available width, starting from a given origin.
* Each row is arranged within `availableWidth`, wrapping to a new row as necessary.
* @param originX The x-coordinate for the layout start position.
* @param originY The y-coordinate for the layout start position.
* @param availableWidth The width within which each row is constrained.
* @return The total height after arranging all rows.
*/
int VerticalFlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
{
QVector<QLayoutItem *> rowItems; // Holds items for the current row
int currentXPosition = originX; // Tracks the x-coordinate while placing items
int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row
int rowHeight = 0; // Tracks the maximum height of items in the current row
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the current item
int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
// Check if the current item fits in the remaining width of the current row
if (currentXPosition + itemWidth > availableWidth) {
// If not, layout the current row and start a new row
layoutSingleRow(rowItems, originX, currentYPosition);
rowItems.clear(); // Reset the list for the new row
currentXPosition = originX; // Reset x-position to the row's start
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
rowHeight = 0; // Reset row height for the new row
}
// Add the item to the current row
rowItems.append(item);
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
}
// Layout the final row if there are any remaining items
layoutSingleRow(rowItems, originX, currentYPosition);
// Return the total height used, including the last row's height
return currentYPosition + rowHeight;
}
/**
* @brief Arranges a single row of items within specified x and y starting positions.
* @param rowItems A list of items to be arranged in the row.
* @param x The starting x-coordinate for the row.
* @param y The starting y-coordinate for the row.
*/
void VerticalFlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
{
for (QLayoutItem *item : rowItems) {
if (item == nullptr || item->isEmpty()) {
continue;
}
// Get the maximum allowed size for the item
QSize itemMaxSize = item->widget()->maximumSize();
// Constrain the item's width and height to its size hint or maximum size
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the x-position to the right, leaving space for horizontal spacing
x += itemWidth + horizontalSpacing();
}
}

View File

@@ -1,19 +0,0 @@
#ifndef VERTICAL_FLOW_LAYOUT_H
#define VERTICAL_FLOW_LAYOUT_H
#include "flow_layout.h"
class VerticalFlowLayout : public FlowLayout
{
public:
explicit VerticalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0);
~VerticalFlowLayout() override;
[[nodiscard]] int heightForWidth(int width) const override;
void setGeometry(const QRect &rect) override;
int layoutAllRows(int originX, int originY, int availableWidth) override;
void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y) override;
};
#endif // VERTICAL_FLOW_LAYOUT_H

View File

@@ -1,39 +0,0 @@
#ifndef CARDINFOWIDGET_H
#define CARDINFOWIDGET_H
#include "../../../../game/cards/card_database.h"
#include <QComboBox>
#include <QFrame>
#include <QStringList>
class CardInfoPictureWidget;
class CardInfoTextWidget;
class AbstractCardItem;
class CardInfoDisplayWidget : public QFrame
{
Q_OBJECT
private:
qreal aspectRatio;
CardInfoPtr info;
CardInfoPictureWidget *pic;
CardInfoTextWidget *text;
public:
explicit CardInfoDisplayWidget(const QString &cardName,
const QString &providerId,
QWidget *parent = nullptr,
Qt::WindowFlags f = {});
public slots:
void setCard(CardInfoPtr card);
void setCard(const QString &cardName, const QString &providerId = QString());
void setCard(AbstractCardItem *card);
private slots:
void clear();
};
#endif

View File

@@ -1,95 +0,0 @@
#include "card_info_picture_enlarged_widget.h"
#include "../../picture_loader.h"
#include <QPainterPath>
#include <QStylePainter>
#include <utility>
/**
* @brief Constructs a CardPictureEnlargedWidget.
* @param parent The parent widget.
*
* Sets the widget's window flags to keep it displayed as a tooltip overlay.
*/
CardInfoPictureEnlargedWidget::CardInfoPictureEnlargedWidget(QWidget *parent)
: QWidget(parent), pixmapDirty(true), info(nullptr)
{
setWindowFlags(Qt::ToolTip); // Keeps this widget on top of everything
setAttribute(Qt::WA_TranslucentBackground);
}
/**
* @brief Loads the pixmap based on the given size and card information.
* @param size The desired size for the loaded pixmap.
*
* If card information is available, it loads the card's specific pixmap. Otherwise, it loads a default card back
* pixmap.
*/
void CardInfoPictureEnlargedWidget::loadPixmap(const QSize &size)
{
if (info) {
PictureLoader::getPixmap(enlargedPixmap, info, size);
} else {
PictureLoader::getCardBackPixmap(enlargedPixmap, size);
}
pixmapDirty = false;
}
/**
* @brief Sets the pixmap for the widget based on a provided card.
* @param card The card information to load.
* @param size The desired size for the pixmap.
*
* Sets the widget's pixmap to the card image and resizes the widget to match the specified size. Triggers a repaint.
*/
void CardInfoPictureEnlargedWidget::setCardPixmap(CardInfoPtr card, const QSize size)
{
info = std::move(card);
loadPixmap(size);
setFixedSize(size); // Set the widget size to the enlarged size
update(); // Trigger a repaint
}
/**
* @brief Custom paint event that draws the enlarged card image with rounded corners.
* @param event The paint event (unused).
*
* Checks if the pixmap is valid. Then, calculates the size and position for centering the
* scaled pixmap within the widget, applies rounded corners, and draws the pixmap.
*/
void CardInfoPictureEnlargedWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
if (width() == 0 || height() == 0 || enlargedPixmap.isNull()) {
return;
}
if (pixmapDirty) {
loadPixmap(size());
}
// Scale the size of the pixmap to fit the widget while maintaining the aspect ratio
QSize scaledSize = enlargedPixmap.size().scaled(size().width(), size().height(), Qt::KeepAspectRatio);
// Calculate the position to center the scaled pixmap
QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
// Define the radius for rounded corners
qreal radius = 0.05 * scaledSize.width(); // Adjust the radius as needed for rounded corners
QStylePainter painter(this);
// Fill the background with transparent color to ensure rounded corners are rendered properly
painter.fillRect(rect(), Qt::transparent); // Use the transparent background
QPainterPath shape;
shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius);
painter.setClipPath(shape); // Set the clipping path
// Draw the pixmap scaled to the calculated size
painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter,
enlargedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}

View File

@@ -1,38 +0,0 @@
#ifndef CARD_PICTURE_ENLARGED_WIDGET_H
#define CARD_PICTURE_ENLARGED_WIDGET_H
#include "../../../../game/cards/card_database.h"
#include <QPixmap>
#include <QWidget>
class CardInfoPictureEnlargedWidget final : public QWidget
{
Q_OBJECT
public:
// Constructor
explicit CardInfoPictureEnlargedWidget(QWidget *parent = nullptr);
// Sets the card pixmap to display
void setCardPixmap(CardInfoPtr card, QSize size);
protected:
// Handles the painting event for the enlarged card
void paintEvent(QPaintEvent *event) override;
private:
// Cached pixmap for the enlarged card
QPixmap enlargedPixmap;
// Tracks if the pixmap needs to be refreshed/redrawn
bool pixmapDirty;
// Card information (card data pointer)
CardInfoPtr info;
// Loads the enlarged card pixmap
void loadPixmap(const QSize &size);
};
#endif // CARD_PICTURE_ENLARGED_WIDGET_H

View File

@@ -1,307 +0,0 @@
#include "card_info_picture_widget.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/cards/card_item.h"
#include "../../../../settings/cache_settings.h"
#include "../../picture_loader.h"
#include <QMenu>
#include <QMouseEvent>
#include <QStylePainter>
#include <QWidget>
#include <utility>
/**
* @class CardInfoPictureWidget
* @brief Widget that displays an enlarged image of a card, loading the image based on the card's info or showing a
* default image.
*
* This widget can optionally display a larger version of the card's image when hovered over,
* depending on the `hoverToZoomEnabled` parameter.
*/
/**
* @brief Constructs a CardInfoPictureWidget.
* @param parent The parent widget, if any.
* @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over.
*
* Initializes the widget with a minimum height and sets the pixmap to a dirty state for initial loading.
*/
CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool hoverToZoomEnabled)
: QWidget(parent), info(nullptr), pixmapDirty(true), hoverToZoomEnabled(hoverToZoomEnabled)
{
setMinimumHeight(baseHeight);
if (hoverToZoomEnabled) {
setMouseTracking(true);
}
enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this);
enlargedPixmapWidget->hide();
hoverTimer = new QTimer(this);
hoverTimer->setSingleShot(true);
connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap);
}
/**
* @brief Sets the card to be displayed and updates the pixmap.
* @param card A shared pointer to the card information (CardInfoPtr).
*
* Disconnects any existing signal connections from the previous card info and connects to the `pixmapUpdated`
* signal of the new card to automatically update the pixmap when the card image changes.
*/
void CardInfoPictureWidget::setCard(CardInfoPtr card)
{
if (info) {
disconnect(info.data(), nullptr, this, nullptr);
}
info = std::move(card);
if (info) {
connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap()));
}
updatePixmap();
}
/**
* @brief Sets the hover to zoom feature.
* @param enabled If true, enables the hover-to-zoom functionality; otherwise, disables it.
*/
void CardInfoPictureWidget::setHoverToZoomEnabled(const bool enabled)
{
hoverToZoomEnabled = enabled;
setMouseTracking(enabled);
}
/**
* @brief Handles widget resizing by updating the pixmap size.
* @param event The resize event (unused).
*
* Calls `updatePixmap()` to ensure the image scales appropriately when the widget is resized.
*/
void CardInfoPictureWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updatePixmap();
}
/**
* @brief Sets the scale factor for the widget.
* @param scale The scale factor to apply.
*
* Adjusts the widget's size according to the scale factor and updates the pixmap.
*/
void CardInfoPictureWidget::setScaleFactor(const int scale)
{
const int newWidth = baseWidth * scale / 100;
const int newHeight = static_cast<int>(newWidth * aspectRatio);
scaleFactor = scale;
setFixedSize(newWidth, newHeight);
updatePixmap();
emit cardScaleFactorChanged(scale);
}
/**
* @brief Marks the pixmap as dirty and triggers a widget repaint.
*
* Sets `pixmapDirty` to true, indicating that the pixmap needs to be reloaded before the next display.
*/
void CardInfoPictureWidget::updatePixmap()
{
pixmapDirty = true;
update();
}
/**
* @brief Loads the appropriate pixmap based on the current card info.
*
* If `info` is valid, loads the card's image. Otherwise, loads a default card back image.
*/
void CardInfoPictureWidget::loadPixmap()
{
PictureLoader::getCardBackLoadingInProgressPixmap(resizedPixmap, size());
if (info) {
PictureLoader::getPixmap(resizedPixmap, info, size());
} else {
PictureLoader::getCardBackLoadingFailedPixmap(resizedPixmap, size());
}
pixmapDirty = false;
}
/**
* @brief Custom paint event that draws the card image with rounded corners.
* @param event The paint event (unused).
*
* Checks if the pixmap needs to be reloaded. Then, calculates the size and position for centering the
* scaled pixmap within the widget, applies rounded corners, and draws the pixmap.
*/
void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (width() == 0 || height() == 0) {
return;
}
if (pixmapDirty) {
loadPixmap();
}
QPixmap transformedPixmap = resizedPixmap; // Default pixmap
if (SettingsCache::instance().getAutoRotateSidewaysLayoutCards()) {
if (info && info->getLandscapeOrientation()) {
// Rotate pixmap 90 degrees to the left
QTransform transform;
transform.rotate(90);
transformedPixmap = resizedPixmap.transformed(transform, Qt::SmoothTransformation);
}
}
// Adjust scaling after rotation
const QSize availableSize = size(); // Size of the widget
const QSize scaledSize = transformedPixmap.size().scaled(availableSize, Qt::KeepAspectRatio);
const QRect targetRect{(availableSize.width() - scaledSize.width()) / 2,
(availableSize.height() - scaledSize.height()) / 2, scaledSize.width(), scaledSize.height()};
const qreal radius = 0.05 * scaledSize.width();
// Draw the pixmap with rounded corners
QStylePainter painter(this);
QPainterPath shape;
shape.addRoundedRect(targetRect, radius, radius);
painter.setClipPath(shape);
painter.drawPixmap(targetRect, transformedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
/**
* @brief Provides the recommended size for the widget based on the scale factor.
* @return The recommended widget size.
*/
QSize CardInfoPictureWidget::sizeHint() const
{
return {static_cast<int>(baseWidth * scaleFactor), static_cast<int>(baseHeight * scaleFactor)};
}
/**
* @brief Starts the hover timer to show the enlarged pixmap on hover.
* @param event The enter event.
*/
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void CardInfoPictureWidget::enterEvent(QEnterEvent *event)
#else
void CardInfoPictureWidget::enterEvent(QEvent *event)
#endif
{
QWidget::enterEvent(event); // Call the base class implementation
// If hover-to-zoom is enabled, start the hover timer
if (hoverToZoomEnabled) {
hoverTimer->start(hoverActivateThresholdInMs);
}
// Emit signal indicating a card is being hovered on
emit hoveredOnCard(info);
}
/**
* @brief Stops the hover timer and hides the enlarged pixmap when the mouse leaves.
* @param event The leave event.
*/
void CardInfoPictureWidget::leaveEvent(QEvent *event)
{
QWidget::leaveEvent(event);
if (hoverToZoomEnabled) {
hoverTimer->stop();
enlargedPixmapWidget->hide();
}
}
/**
* @brief Moves the enlarged pixmap widget to follow the mouse cursor.
* @param event The mouse move event.
*/
void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event)
{
QWidget::mouseMoveEvent(event);
if (hoverToZoomEnabled && enlargedPixmapWidget->isVisible()) {
const QPointF cursorPos = QCursor::pos();
enlargedPixmapWidget->move(QPoint(static_cast<int>(cursorPos.x()) + enlargedPixmapOffset,
static_cast<int>(cursorPos.y()) + enlargedPixmapOffset));
}
}
void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
if (event->button() == Qt::RightButton) {
createRightClickMenu()->popup(QCursor::pos());
}
}
QMenu *CardInfoPictureWidget::createRightClickMenu()
{
auto *cardMenu = new QMenu(this);
if (!info) {
return cardMenu;
}
auto viewRelatedCards = new QMenu(tr("View related cards"));
cardMenu->addMenu(viewRelatedCards);
bool atLeastOneGoodRelationFound = false;
QList<CardRelation *> relatedCards = info->getAllRelatedCards();
for (const CardRelation *cardRelation : relatedCards) {
CardInfoPtr relatedCard = CardDatabaseManager::getInstance()->getCard(cardRelation->getName());
if (relatedCard != nullptr) {
atLeastOneGoodRelationFound = true;
break;
}
}
if (!atLeastOneGoodRelationFound) {
viewRelatedCards->setEnabled(false);
return cardMenu;
}
for (const auto &relatedCard : relatedCards) {
const auto &relatedCardName = relatedCard->getName();
QAction *viewCard = viewRelatedCards->addAction(relatedCardName);
connect(viewCard, &QAction::triggered, this, [this, &relatedCardName] {
emit cardChanged(CardDatabaseManager::getInstance()->getCard(relatedCardName));
});
viewRelatedCards->addAction(viewCard);
}
return cardMenu;
}
/**
* @brief Displays the enlarged version of the card's pixmap near the cursor.
*
* If card information is available, the enlarged pixmap is loaded, positioned near the cursor,
* and displayed.
*/
void CardInfoPictureWidget::showEnlargedPixmap() const
{
if (!info) {
return;
}
const QSize enlargedSize(static_cast<int>(size().width() * scaleFactor),
static_cast<int>(size().width() * aspectRatio * scaleFactor));
enlargedPixmapWidget->setCardPixmap(info, enlargedSize);
const QPointF cursorPos = QCursor::pos();
enlargedPixmapWidget->move(static_cast<int>(cursorPos.x()) + enlargedPixmapOffset,
static_cast<int>(cursorPos.y()) + enlargedPixmapOffset);
enlargedPixmapWidget->show();
}

View File

@@ -1,73 +0,0 @@
#ifndef CARD_INFO_PICTURE_H
#define CARD_INFO_PICTURE_H
#include "../../../../game/cards/card_database.h"
#include "card_info_picture_enlarged_widget.h"
#include <QTimer>
#include <QWidget>
class AbstractCardItem;
class QMenu;
class CardInfoPictureWidget : public QWidget
{
Q_OBJECT
public:
explicit CardInfoPictureWidget(QWidget *parent = nullptr, bool hoverToZoomEnabled = false);
CardInfoPtr getInfo()
{
return info;
}
[[nodiscard]] QSize sizeHint() const override;
void setHoverToZoomEnabled(bool enabled);
public slots:
void setCard(CardInfoPtr card);
void setScaleFactor(int scale); // New slot for scaling
void updatePixmap();
signals:
void hoveredOnCard(CardInfoPtr hoveredCard);
void cardScaleFactorChanged(int _scale);
void cardChanged(CardInfoPtr card);
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent *event) override; // Qt6 signature
#else
void enterEvent(QEvent *event) override; // Qt5 signature
#endif
void leaveEvent(QEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void loadPixmap();
[[nodiscard]] const QPixmap &getResizedPixmap() const
{
return resizedPixmap;
}
void showEnlargedPixmap() const;
private:
CardInfoPtr info;
qreal magicTheGatheringCardAspectRatio = 1.396;
qreal yuGiOhCardAspectRatio = 1.457;
qreal aspectRatio = magicTheGatheringCardAspectRatio;
int baseWidth = 200;
int baseHeight = 200;
double scaleFactor = 1.5;
QPixmap resizedPixmap;
bool pixmapDirty;
bool hoverToZoomEnabled;
int hoverActivateThresholdInMs = 500;
CardInfoPictureEnlargedWidget *enlargedPixmapWidget;
int enlargedPixmapOffset = 10;
QTimer *hoverTimer;
QMenu *createRightClickMenu();
};
#endif

View File

@@ -1,243 +0,0 @@
#include "card_info_picture_with_text_overlay_widget.h"
#include <QFontMetrics>
#include <QPainterPath>
#include <QStylePainter>
#include <QTextOption>
/**
* @brief Constructs a CardPictureWithTextOverlay widget.
* @param parent The parent widget.
* @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over.
* @param textColor The color of the overlay text.
* @param outlineColor The color of the outline around the text.
* @param fontSize The font size of the overlay text.
* @param alignment The alignment of the text within the overlay.
*
* Sets the widget's size policy and default border style.
*/
CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidget *parent,
const bool hoverToZoomEnabled,
const QColor &textColor,
const QColor &outlineColor,
const int fontSize,
const Qt::Alignment alignment)
: CardInfoPictureWidget(parent, hoverToZoomEnabled), textColor(textColor), outlineColor(outlineColor),
fontSize(fontSize), textAlignment(alignment)
{
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
/**
* @brief Sets the overlay text to be displayed on the card.
* @param text The text to overlay.
*
* Updates the widget to display the new overlay text.
*/
void CardInfoPictureWithTextOverlayWidget::setOverlayText(const QString &text)
{
overlayText = text;
update(); // Trigger a redraw to display the updated text
}
/**
* @brief Sets the color of the overlay text.
* @param color The new text color.
*/
void CardInfoPictureWithTextOverlayWidget::setTextColor(const QColor &color)
{
textColor = color;
update();
}
/**
* @brief Sets the outline color around the overlay text.
* @param color The new outline color.
*/
void CardInfoPictureWithTextOverlayWidget::setOutlineColor(const QColor &color)
{
outlineColor = color;
update();
}
/**
* @brief Sets the font size for the overlay text.
* @param size The new font size.
*/
void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size)
{
fontSize = size > 0 ? size : 1;
update();
}
/**
* @brief Sets the alignment of the overlay text within the widget.
* @param alignment The new text alignment.
*/
void CardInfoPictureWithTextOverlayWidget::setTextAlignment(const Qt::Alignment alignment)
{
textAlignment = alignment;
update();
}
void CardInfoPictureWithTextOverlayWidget::mousePressEvent(QMouseEvent *event)
{
emit imageClicked(event, this);
}
/**
* @brief Paints the widget, including both the card image and the text overlay.
* @param event The paint event.
*
* Draws the card image first, then overlays text on top. The text is wrapped and centered within the image.
*/
void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
{
// Call the base class's paintEvent to draw the card image
CardInfoPictureWidget::paintEvent(event);
// If no overlay text, skip drawing the text
if (overlayText.isEmpty()) {
return;
}
QStylePainter painter(this);
// Get the pixmap from the base class using the getter
const QPixmap &pixmap = getResizedPixmap();
if (pixmap.isNull()) {
return;
}
// Calculate size and position for drawing
const QSize scaledSize = pixmap.size().scaled(size(), Qt::KeepAspectRatio);
const QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
const QRect pixmapRect(topLeft, scaledSize);
// Calculate the optimal font size
QFont font = painter.font();
int optimalFontSize = fontSize; // Start with the user-defined font size
const QFontMetrics baseMetrics(font);
int textWidth = pixmapRect.width();
// Reduce the font size until the text fits within the pixmap's width
do {
font.setPointSize(optimalFontSize);
QFontMetrics fm(font);
int currentWidth = 0;
for (const QString &word : overlayText.split(' ')) {
currentWidth = std::max(currentWidth, fm.horizontalAdvance(word));
}
if (currentWidth <= textWidth) {
break;
}
--optimalFontSize;
} while (optimalFontSize > 1);
// Apply the calculated font size
painter.setFont(font);
// Wrap the text to fit within the pixmap width
const QFontMetrics fontMetrics(font);
QString wrappedText;
QString currentLine;
QStringList words = overlayText.split(' ');
for (const QString &word : words) {
if (fontMetrics.horizontalAdvance(currentLine + " " + word) > textWidth) {
wrappedText += currentLine + '\n';
currentLine = word;
} else {
if (!currentLine.isEmpty()) {
currentLine += " ";
}
currentLine += word;
}
}
wrappedText += currentLine;
// Calculate total text block height
int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height();
// Adjust font size if the total text height exceeds the pixmap height
while (totalTextHeight > pixmapRect.height() && optimalFontSize > 1) {
--optimalFontSize;
font.setPointSize(optimalFontSize);
painter.setFont(font);
const QFontMetrics newMetrics(font);
totalTextHeight = wrappedText.count('\n') * newMetrics.height() + newMetrics.height();
}
// Set up the text layout options
QTextOption textOption;
textOption.setAlignment(textAlignment);
// Create a text rectangle centered vertically within the pixmap rect
auto textRect = QRect(pixmapRect.left(), pixmapRect.top(), pixmapRect.width(), totalTextHeight);
textRect.moveTop((pixmapRect.height() - totalTextHeight) / 2 + pixmapRect.top());
// Draw the outlined text
drawOutlinedText(painter, textRect, wrappedText, textOption);
}
/**
* @brief Draws text with an outline for visibility.
* @param painter The painter to draw the text.
* @param textRect The rectangle area to draw the text in.
* @param text The text to display.
* @param textOption The text layout options, such as alignment.
*
* Draws an outline around the text to enhance readability before drawing the main text.
*/
void CardInfoPictureWithTextOverlayWidget::drawOutlinedText(QPainter &painter,
const QRect &textRect,
const QString &text,
const QTextOption &textOption) const
{
painter.setPen(outlineColor);
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx != 0 || dy != 0) {
QRect shiftedTextRect = textRect.translated(dx, dy);
painter.drawText(shiftedTextRect, text, textOption);
}
}
}
// Draw the main text
painter.setPen(textColor);
painter.drawText(textRect, text, textOption);
}
/**
* @brief Provides the recommended size for this widget.
* @return The suggested widget size.
*/
QSize CardInfoPictureWithTextOverlayWidget::sizeHint() const
{
return CardInfoPictureWidget::sizeHint();
}
/**
* @brief Provides the minimum recommended size for this widget.
* @return The minimum widget size.
*/
QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const
{
// Same as sizeHint, but ensure that there is at least some space for the pixmap
const QPixmap &pixmap = getResizedPixmap();
const QSize pixmapSize = pixmap.isNull() ? QSize(0, 0) : pixmap.size();
// Get the font metrics for the overlay text
QFont font;
font.setPointSize(fontSize);
const QFontMetrics fontMetrics(font);
// Calculate the height required for the text
const QStringList lines = overlayText.split('\n');
const int totalTextHeight = static_cast<int>(lines.size()) * fontMetrics.height();
// Return the maximum width and combined height
return {pixmapSize.width(), pixmapSize.height() + totalTextHeight};
}

View File

@@ -1,50 +0,0 @@
#ifndef CARD_PICTURE_WITH_TEXT_OVERLAY_H
#define CARD_PICTURE_WITH_TEXT_OVERLAY_H
#include "card_info_picture_widget.h"
#include <QColor>
#include <QSize>
#include <QTextOption>
class CardInfoPictureWithTextOverlayWidget : public CardInfoPictureWidget
{
Q_OBJECT
public:
explicit CardInfoPictureWithTextOverlayWidget(QWidget *parent = nullptr,
bool hoverToZoomEnabled = false,
const QColor &textColor = Qt::white,
const QColor &outlineColor = Qt::black,
int fontSize = 12,
Qt::Alignment alignment = Qt::AlignCenter);
void setOverlayText(const QString &text);
void setTextColor(const QColor &color);
void setOutlineColor(const QColor &color);
void setFontSize(int size);
void setTextAlignment(Qt::Alignment alignment);
[[nodiscard]] QSize sizeHint() const override;
signals:
void imageClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
[[nodiscard]] QSize minimumSizeHint() const override;
private:
void drawOutlinedText(QPainter &painter,
const QRect &textRect,
const QString &text,
const QTextOption &textOption) const;
QString overlayText;
QColor textColor;
QColor outlineColor;
int fontSize;
Qt::Alignment textAlignment;
};
#endif // CARD_PICTURE_WITH_TEXT_OVERLAY_H

View File

@@ -1,61 +0,0 @@
#include "card_size_widget.h"
#include "../../../../settings/cache_settings.h"
#include "../printing_selector/printing_selector.h"
#include "../visual_deck_storage/visual_deck_storage_widget.h"
/**
* @class CardSizeWidget
* @brief A widget for adjusting card sizes using a slider.
*
* This widget allows users to dynamically change the card size in a linked FlowWidget
* and updates the application's settings accordingly.
*/
CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *flowWidget, int defaultValue)
: parent(parent), flowWidget(flowWidget)
{
cardSizeLayout = new QHBoxLayout(this);
cardSizeLayout->setContentsMargins(9, 0, 9, 0);
setLayout(cardSizeLayout);
cardSizeLabel = new QLabel(tr("Card Size"), this);
cardSizeLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
cardSizeSlider = new QSlider(Qt::Horizontal, this);
cardSizeSlider->setRange(50, 250); ///< Slider range for card size adjustment.
cardSizeSlider->setValue(defaultValue); ///< Initial slider value.
cardSizeLayout->addWidget(cardSizeLabel);
cardSizeLayout->addWidget(cardSizeSlider);
if (flowWidget != nullptr) {
connect(cardSizeSlider, &QSlider::valueChanged, flowWidget, &FlowWidget::setMinimumSizeToMaxSizeHint);
}
connect(cardSizeSlider, &QSlider::valueChanged, this, &CardSizeWidget::updateCardSizeSetting);
}
/**
* @brief Updates the card size setting in the application's cache.
*
* @param newValue The new card size value set by the slider.
*/
void CardSizeWidget::updateCardSizeSetting(int newValue)
{
// Check the type of the parent widget
if ((parent = qobject_cast<PrintingSelector *>(parentWidget()))) {
SettingsCache::instance().setPrintingSelectorCardSize(newValue);
} else if ((parent = qobject_cast<VisualDeckStorageWidget *>(parentWidget()))) {
SettingsCache::instance().setVisualDeckStorageCardSize(newValue);
}
}
/**
* @brief Gets the slider widget used for adjusting the card size.
*
* @return A pointer to the QSlider object.
*/
QSlider *CardSizeWidget::getSlider() const
{
return cardSizeSlider;
}

View File

@@ -1,29 +0,0 @@
#ifndef CARD_SIZE_WIDGET_H
#define CARD_SIZE_WIDGET_H
#include "../general/layout_containers/flow_widget.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QSlider>
#include <QWidget>
class CardSizeWidget : public QWidget
{
Q_OBJECT
public:
explicit CardSizeWidget(QWidget *parent, FlowWidget *flowWidget = nullptr, int defaultValue = 100);
[[nodiscard]] QSlider *getSlider() const;
QWidget *parent;
public slots:
void updateCardSizeSetting(int newValue);
private:
FlowWidget *flowWidget;
QHBoxLayout *cardSizeLayout;
QLabel *cardSizeLabel;
QSlider *cardSizeSlider;
};
#endif // CARD_SIZE_WIDGET_H

View File

@@ -1,53 +0,0 @@
#include "deck_preview_card_picture_widget.h"
#include <QApplication>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QPainterPath>
#include <QStylePainter>
#include <QTextOption>
/**
* @brief Constructs a CardPictureWithTextOverlay widget.
* @param parent The parent widget.
* @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over.
* @param textColor The color of the overlay text.
* @param outlineColor The color of the outline around the text.
* @param fontSize The font size of the overlay text.
* @param alignment The alignment of the text within the overlay.
*
* Sets the widget's size policy and default border style.
*/
DeckPreviewCardPictureWidget::DeckPreviewCardPictureWidget(QWidget *parent,
const bool hoverToZoomEnabled,
const QColor &textColor,
const QColor &outlineColor,
const int fontSize,
const Qt::Alignment alignment)
: CardInfoPictureWithTextOverlayWidget(parent, hoverToZoomEnabled, textColor, outlineColor, fontSize, alignment)
{
singleClickTimer = new QTimer(this);
singleClickTimer->setSingleShot(true);
connect(singleClickTimer, &QTimer::timeout, this, [this]() { emit imageClicked(lastMouseEvent, this); });
}
void DeckPreviewCardPictureWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
lastMouseEvent = event;
singleClickTimer->start(QApplication::doubleClickInterval());
}
}
void DeckPreviewCardPictureWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
singleClickTimer->stop(); // Prevent single-click logic
emit imageDoubleClicked(lastMouseEvent, this);
}
}
void DeckPreviewCardPictureWidget::setFilePath(const QString &_filePath)
{
filePath = _filePath;
}

View File

@@ -1,40 +0,0 @@
#ifndef DECK_PREVIEW_CARD_PICTURE_WIDGET_H
#define DECK_PREVIEW_CARD_PICTURE_WIDGET_H
#include "card_info_picture_with_text_overlay_widget.h"
#include <QColor>
#include <QSize>
#include <QTextOption>
class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlayWidget
{
Q_OBJECT
public:
explicit DeckPreviewCardPictureWidget(QWidget *parent = nullptr,
bool hoverToZoomEnabled = false,
const QColor &textColor = Qt::white,
const QColor &outlineColor = Qt::black,
int fontSize = 12,
Qt::Alignment alignment = Qt::AlignCenter);
QString filePath;
signals:
void imageClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void imageDoubleClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
public slots:
void setFilePath(const QString &filePath);
private:
QTimer *singleClickTimer;
QMouseEvent *lastMouseEvent = nullptr; // Store the last mouse event
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
};
#endif // DECK_PREVIEW_CARD_PICTURE_WIDGET_H

View File

@@ -1,137 +0,0 @@
#include "dynamic_font_size_label.h"
#define FONT_PRECISION (0.5)
#include <QDebug>
#include <QElapsedTimer>
DynamicFontSizeLabel::DynamicFontSizeLabel(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f)
{
setIndent(0);
}
void DynamicFontSizeLabel::mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event)
emit clicked();
}
void DynamicFontSizeLabel::paintEvent(QPaintEvent *event)
{
// QElapsedTimer timer;
// timer.start();
QFont newFont = font();
float fontSize = getWidgetMaximumFontSize(this, this->text());
newFont.setPointSizeF(fontSize);
setFont(newFont);
// qDebug() << "Font size set to" << fontSize;
QLabel::paintEvent(event);
// LOG(true, "Paint delay" << ((float)timer.nsecsElapsed())/1000000.0 << " mS");
}
float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text)
{
QFont font = widget->font();
const QRect widgetRect = widget->contentsRect();
const float widgetWidth = widgetRect.width();
const float widgetHeight = widgetRect.height();
QRectF newFontSizeRect;
float currentSize = font.pointSizeF();
float step = currentSize / 2.0;
/* If too small, increase step */
if (step <= FONT_PRECISION) {
step = FONT_PRECISION * 4.0;
}
float lastTestedSize = currentSize;
float currentHeight = 0;
float currentWidth = 0;
if (text == "") {
return currentSize;
}
if (currentSize < 0) {
return 1;
}
/* Only stop when step is small enough and new size is smaller than QWidget */
while (step > FONT_PRECISION || (currentHeight > widgetHeight) || (currentWidth > widgetWidth)) {
/* Keep last tested value */
lastTestedSize = currentSize;
/* Test label with its font */
font.setPointSizeF(currentSize);
/* Use font metrics to test */
QFontMetricsF fm(font);
/* Check if widget is QLabel */
QLabel *label = qobject_cast<QLabel *>(widget);
if (label) {
newFontSizeRect =
fm.boundingRect(widgetRect, (label->wordWrap() ? Qt::TextWordWrap : 0) | label->alignment(), text);
} else {
newFontSizeRect = fm.boundingRect(widgetRect, 0, text);
}
currentHeight = newFontSizeRect.height();
currentWidth = newFontSizeRect.width();
/* If new font size is too big, decrease it */
if ((currentHeight > widgetHeight) || (currentWidth > widgetWidth)) {
// qDebug() << "-- contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect"
// << newFontSizeRect << "Tight" << text << currentSize;
currentSize -= step;
/* if step is small enough, keep it constant, so it converge to biggest font size */
if (step > FONT_PRECISION) {
step /= 2.0;
}
/* Do not allow negative size */
if (currentSize <= 0) {
break;
}
}
/* If new font size is smaller than maximum possible size, increase it */
else {
// qDebug() << "++ contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect"
// << newFontSizeRect << "Tight" << text << currentSize;
currentSize += step;
}
}
return lastTestedSize;
}
void DynamicFontSizeLabel::setTextColor(QColor color)
{
if (color.isValid() && color != textColor) {
textColor = color;
setStyleSheet("color : " + color.name() + ";");
}
}
QColor DynamicFontSizeLabel::getTextColor()
{
return textColor;
}
void DynamicFontSizeLabel::setTextAndColor(const QString &text, QColor color)
{
setTextColor(color);
setText(text);
}
/* Do not give any size hint as it it changes during paintEvent */
QSize DynamicFontSizeLabel::minimumSizeHint() const
{
return QWidget::minimumSizeHint();
}
/* Do not give any size hint as it it changes during paintEvent */
QSize DynamicFontSizeLabel::sizeHint() const
{
return QWidget::sizeHint();
}

View File

@@ -1,41 +0,0 @@
#ifndef DYNAMICFONTSIZELABEL_H
#define DYNAMICFONTSIZELABEL_H
#include <QColor>
#include <QLabel>
class DynamicFontSizeLabel : public QLabel
{
Q_OBJECT
public:
explicit DynamicFontSizeLabel(QWidget *parent = NULL, Qt::WindowFlags f = Qt::WindowFlags());
~DynamicFontSizeLabel()
{
}
static float getWidgetMaximumFontSize(QWidget *widget, const QString &text);
/* This method overwrite stylesheet */
void setTextColor(QColor color);
QColor getTextColor();
void setTextAndColor(const QString &text, QColor color = QColor::Invalid);
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event);
QColor textColor;
// QWidget interface
protected:
void paintEvent(QPaintEvent *event);
// QWidget interface
public:
QSize minimumSizeHint() const;
QSize sizeHint() const;
};
#endif // DYNAMICFONTSIZELABEL_H

View File

@@ -1,80 +0,0 @@
#include "dynamic_font_size_push_button.h"
#include "dynamic_font_size_label.h"
#include <QDebug>
#include <QPainter>
DynamicFontSizePushButton::DynamicFontSizePushButton(QWidget *parent) : QPushButton(parent)
{
}
void DynamicFontSizePushButton::paintEvent(QPaintEvent *event)
{
// Call the base class paintEvent to preserve any other painting behavior
QPushButton::paintEvent(event);
// Adjust the font size dynamically based on the text
QFont newFont = font();
float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text());
newFont.setPointSizeF(fontSize);
setFont(newFont);
// Get painter for custom painting
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// Paint the background with a linear gradient (normal state)
QLinearGradient gradient(0, 0, 0, height());
if (isDown()) {
// Pressed state
gradient.setColorAt(0, QColor(128, 128, 128));
gradient.setColorAt(1, QColor(64, 64, 64));
} else if (underMouse()) {
// Hover state
gradient.setColorAt(0, QColor(96, 96, 96));
gradient.setColorAt(1, QColor(48, 48, 48));
} else {
// Normal state
gradient.setColorAt(0, QColor(64, 64, 64)); // start color
gradient.setColorAt(1, QColor(32, 32, 32)); // end color
}
painter.setBrush(gradient);
painter.setPen(Qt::NoPen); // No border
painter.drawRect(rect());
// Paint the button text
painter.setPen(QPen(textColor.isValid() ? textColor : QColor(255, 255, 255))); // Set text color
painter.drawText(rect(), Qt::AlignCenter, text());
}
void DynamicFontSizePushButton::setTextColor(QColor color)
{
if (color.isValid() && color != textColor) {
textColor = color;
update(); // Request a repaint to update the text color
}
}
void DynamicFontSizePushButton::setTextAndColor(const QString &text, QColor color)
{
setTextColor(color);
setText(text);
}
QColor DynamicFontSizePushButton::getTextColor()
{
return textColor;
}
/* Do not give any size hint as it it changes during paintEvent */
QSize DynamicFontSizePushButton::minimumSizeHint() const
{
return QWidget::minimumSizeHint();
}
/* Do not give any size hint as it it changes during paintEvent */
QSize DynamicFontSizePushButton::sizeHint() const
{
return QWidget::sizeHint();
}

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