mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-11 00:24:47 -07:00
Merge branch 'master' into tooomm-qt5
This commit is contained in:
commit
e0b3edc9b8
488 changed files with 28527 additions and 76214 deletions
|
|
@ -1,26 +0,0 @@
|
|||
FROM debian:11
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive 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 \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt5-image-formats-plugins \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM fedora:42
|
||||
FROM fedora:44
|
||||
|
||||
RUN dnf install -y \
|
||||
ccache \
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM debian:11
|
||||
FROM debian:12
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
|
|
@ -11,11 +11,11 @@ RUN apt-get update && \
|
|||
git \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt5sql5-mysql \
|
||||
libqt5websockets5-dev \
|
||||
libqt6sql6-mysql \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools \
|
||||
qt6-websockets-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive 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 \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt5-image-formats-plugins \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
|
@ -205,7 +205,11 @@ if [[ $RUNNER_OS == macOS ]]; then
|
|||
arch="x64"
|
||||
fi
|
||||
mkdir -p "$triplets_dir"
|
||||
cp "../vcpkg/triplets/$arch-osx.cmake" "$triplet_file"
|
||||
triplet_source="../vcpkg/triplets/$arch-osx.cmake"
|
||||
if [[ ! -f "$triplet_source" ]]; then
|
||||
triplet_source="../vcpkg/triplets/community/$arch-osx.cmake"
|
||||
fi
|
||||
cp "$triplet_source" "$triplet_file"
|
||||
echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
|
||||
echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
|
||||
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@ Available pre-compiled binaries for installation:
|
|||
<b>Linux</b>
|
||||
• <kbd>Ubuntu 26.04 LTS</kbd> <sub><i>Resolute Racoon</i></sub>
|
||||
• <kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub>
|
||||
• <kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</i></sub>
|
||||
• <kbd>Debian 13</kbd> <sub><i>Trixie</i></sub>
|
||||
• <kbd>Debian 12</kbd> <sub><i>Bookworm</i></sub>
|
||||
• <kbd>Debian 11</kbd> <sub><i>Bullseye</i></sub>
|
||||
• <kbd>Fedora 44</kbd>
|
||||
• <kbd>Fedora 43</kbd>
|
||||
• <kbd>Fedora 42</kbd>
|
||||
|
||||
<sub>We are also packaged in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>, courtesy of @FFY00.</sub>
|
||||
<sub>General Linux support is available via a <kbd>flatpak</kbd> package at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a>!</sub>
|
||||
|
|
@ -85,7 +83,6 @@ Remove empty headers when done.
|
|||
### Under the Hood
|
||||
### Oracle
|
||||
### Servatrice
|
||||
### Webatrice
|
||||
|
||||
</details>
|
||||
|
||||
|
|
|
|||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
|
|
@ -10,7 +10,7 @@ updates:
|
|||
# Look for `.gitmodules` in the `root` directory
|
||||
directory: "/"
|
||||
ignore:
|
||||
# Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) & macOS Intel triplet broken with newer releases)
|
||||
# Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used)
|
||||
- dependency-name: "vcpkg"
|
||||
# Check for updates once a month
|
||||
schedule:
|
||||
|
|
@ -39,13 +39,3 @@ updates:
|
|||
interval: "weekly"
|
||||
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
|
||||
open-pull-requests-limit: 2
|
||||
|
||||
# # Enable version updates for npm
|
||||
# - package-ecosystem: "npm"
|
||||
# # Look for `package.json` and `lock` files in the `webclient` subdirectory
|
||||
# directory: "/webclient"
|
||||
# # Check the npm registry for updates once a week
|
||||
# schedule:
|
||||
# interval: "weekly"
|
||||
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
|
||||
# open-pull-requests-limit: 5
|
||||
|
|
|
|||
42
.github/workflows/desktop-build.yml
vendored
42
.github/workflows/desktop-build.yml
vendored
|
|
@ -14,10 +14,8 @@ on:
|
|||
- '*/**' # matches all files not in root
|
||||
- '!**.md'
|
||||
- '!.github/**'
|
||||
- '!.husky/**'
|
||||
- '!.tx/**'
|
||||
- '!doc/**'
|
||||
- '!webclient/**'
|
||||
- '.github/workflows/desktop-build.yml'
|
||||
- 'CMakeLists.txt'
|
||||
- 'vcpkg.json'
|
||||
|
|
@ -29,10 +27,8 @@ on:
|
|||
- '*/**' # matches all files not in root
|
||||
- '!**.md'
|
||||
- '!.github/**'
|
||||
- '!.husky/**'
|
||||
- '!.tx/**'
|
||||
- '!doc/**'
|
||||
- '!webclient/**'
|
||||
- '.github/workflows/desktop-build.yml'
|
||||
- 'CMakeLists.txt'
|
||||
- 'vcpkg.json'
|
||||
|
|
@ -46,7 +42,7 @@ concurrency:
|
|||
jobs:
|
||||
configure:
|
||||
name: Configure
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
tag: ${{steps.configure.outputs.tag}}
|
||||
sha: ${{steps.configure.outputs.sha}}
|
||||
|
|
@ -111,12 +107,8 @@ jobs:
|
|||
package: skip # We are packaged in Arch already
|
||||
allow-failure: yes
|
||||
|
||||
- distro: Debian
|
||||
version: 11
|
||||
package: DEB
|
||||
|
||||
- distro: Servatrice_Debian
|
||||
version: 11
|
||||
version: 12
|
||||
package: DEB
|
||||
test: skip
|
||||
server_only: yes
|
||||
|
|
@ -131,22 +123,18 @@ jobs:
|
|||
package: DEB
|
||||
|
||||
- distro: Fedora
|
||||
version: 42
|
||||
version: 43
|
||||
package: RPM
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Fedora
|
||||
version: 43
|
||||
version: 44
|
||||
package: RPM
|
||||
|
||||
- distro: Ubuntu
|
||||
version: 22.04
|
||||
package: DEB
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Ubuntu
|
||||
version: 24.04
|
||||
package: DEB
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Ubuntu
|
||||
version: 26.04
|
||||
|
|
@ -265,7 +253,7 @@ jobs:
|
|||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{github.token}}
|
||||
run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
|
||||
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice
|
||||
|
||||
build-vcpkg:
|
||||
strategy:
|
||||
|
|
@ -281,7 +269,7 @@ jobs:
|
|||
override_target: 13
|
||||
make_package: 1
|
||||
package_suffix: "-macOS13_Intel"
|
||||
qt_version: 6.11.*
|
||||
qt_version: 6.11.0
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
|
|
@ -296,7 +284,7 @@ jobs:
|
|||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-macOS14"
|
||||
qt_version: 6.11.*
|
||||
qt_version: 6.11.0
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
|
|
@ -311,7 +299,7 @@ jobs:
|
|||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-macOS15"
|
||||
qt_version: 6.11.*
|
||||
qt_version: 6.11.0
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
|
|
@ -324,7 +312,7 @@ jobs:
|
|||
soc: Apple
|
||||
xcode: "16.4"
|
||||
type: Debug
|
||||
qt_version: 6.11.*
|
||||
qt_version: 6.11.0
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
|
|
@ -337,7 +325,7 @@ jobs:
|
|||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-Win10"
|
||||
qt_version: 6.11.*
|
||||
qt_version: 6.11.0
|
||||
qt_arch: win64_msvc2022_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: "Visual Studio 17 2022"
|
||||
|
|
@ -494,7 +482,7 @@ jobs:
|
|||
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}}
|
||||
/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
|
||||
|
|
@ -514,7 +502,7 @@ jobs:
|
|||
# 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"
|
||||
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
|
||||
|
|
@ -526,7 +514,7 @@ jobs:
|
|||
# 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}}
|
||||
xcrun stapler staple "${{steps.build.outputs.path}}"
|
||||
fi
|
||||
|
||||
- name: Upload artifact
|
||||
|
|
@ -573,4 +561,4 @@ jobs:
|
|||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{github.token}}
|
||||
run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
|
||||
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice
|
||||
|
|
|
|||
6
.github/workflows/desktop-lint.yml
vendored
6
.github/workflows/desktop-lint.yml
vendored
|
|
@ -8,10 +8,8 @@ on:
|
|||
- '!**.md'
|
||||
- '!.ci/**'
|
||||
- '!.github/**'
|
||||
- '!.husky/**'
|
||||
- '!.tx/**'
|
||||
- '!doc/**'
|
||||
- '!webclient/**'
|
||||
- '.ci/lint_cpp.sh'
|
||||
- '.github/workflows/desktop-lint.yml'
|
||||
- '.clang-format'
|
||||
|
|
@ -20,13 +18,13 @@ on:
|
|||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 20 # should be enough to find merge base
|
||||
fetch-depth: 20 # should be enough to find merge base
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
|
|
|
|||
15
.github/workflows/docker-release.yml
vendored
15
.github/workflows/docker-release.yml
vendored
|
|
@ -1,9 +1,10 @@
|
|||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- released # publishing of stable releases
|
||||
push:
|
||||
tags:
|
||||
- '*Release*'
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
|
@ -16,16 +17,18 @@ on:
|
|||
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
|
||||
concurrency:
|
||||
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
|
||||
cancel-in-progress: ${{ github.ref_type != 'tag' }}
|
||||
cancel-in-progress: ${{ github.event_name != 'release' }}
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: amd64 & arm64
|
||||
if: ${{ github.repository_owner == 'Cockatrice' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
|
@ -33,6 +36,8 @@ jobs:
|
|||
- name: Docker metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v6
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/cockatrice/servatrice
|
||||
|
|
@ -52,7 +57,7 @@ jobs:
|
|||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.ref_type == 'tag'
|
||||
if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master'
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
|
|
|
|||
2
.github/workflows/documentation-build.yml
vendored
2
.github/workflows/documentation-build.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
- name: Install Doxygen
|
||||
uses: ssciwr/doxygen-install@v2
|
||||
with:
|
||||
version: "1.14.0"
|
||||
version: "1.16.1"
|
||||
|
||||
- name: Update Doxygen Configuration
|
||||
run: |
|
||||
|
|
|
|||
3
.github/workflows/translations-pull.yml
vendored
3
.github/workflows/translations-pull.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
|
||||
|
||||
name: Pull languages
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
|
|
@ -38,7 +38,6 @@ jobs:
|
|||
add-paths: |
|
||||
cockatrice/translations/*.ts
|
||||
oracle/translations/*.ts
|
||||
webclient/public/locales/*/translation.json
|
||||
commit-message: Update translation files
|
||||
# author is the owner of the commit
|
||||
author: github-actions <github-actions@github.com>
|
||||
|
|
|
|||
4
.github/workflows/translations-push.yml
vendored
4
.github/workflows/translations-push.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
|
||||
|
||||
name: Push strings
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
|
|
@ -49,7 +49,7 @@ jobs:
|
|||
|
||||
- name: Render template
|
||||
id: template
|
||||
uses: chuhlomin/render-template@v1
|
||||
uses: chuhlomin/render-template/binary@v1
|
||||
with:
|
||||
template: .ci/update_translation_source_strings_template.md
|
||||
vars: |
|
||||
|
|
|
|||
54
.github/workflows/web-build.yml
vendored
54
.github/workflows/web-build.yml
vendored
|
|
@ -1,54 +0,0 @@
|
|||
name: Build Web
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.husky/**'
|
||||
- 'webclient/**'
|
||||
- '!**.md'
|
||||
- '.github/workflows/web-build.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.husky/**'
|
||||
- 'webclient/**'
|
||||
- '!**.md'
|
||||
- '.github/workflows/web-build.yml'
|
||||
|
||||
jobs:
|
||||
build-web:
|
||||
name: React (Node ${{matrix.node_version}})
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: webclient
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node_version:
|
||||
- 16
|
||||
- lts/*
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{matrix.node_version}}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'webclient/package-lock.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm clean-install
|
||||
|
||||
- name: Build app
|
||||
run: npm run build
|
||||
|
||||
- name: Test app
|
||||
run: npm run test
|
||||
33
.github/workflows/web-lint.yml
vendored
33
.github/workflows/web-lint.yml
vendored
|
|
@ -1,33 +0,0 @@
|
|||
name: Code Style (TypeScript)
|
||||
|
||||
on:
|
||||
# push trigger not needed for linting, we do not allow direct pushes to master
|
||||
pull_request:
|
||||
paths:
|
||||
- 'webclient/**'
|
||||
- '!**.md'
|
||||
- '.github/workflows/web-lint.yml'
|
||||
|
||||
jobs:
|
||||
ESLint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: webclient
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'webclient/package-lock.json'
|
||||
|
||||
- name: Install ESLint
|
||||
run: npm clean-install --ignore-scripts
|
||||
|
||||
- name: Run ESLint
|
||||
run: npm run lint
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd webclient
|
||||
npm run translate
|
||||
|
||||
git add src/i18n-default.json
|
||||
|
|
@ -16,11 +16,3 @@ source_file = oracle/oracle_en@source.ts
|
|||
file_filter = oracle/translations/oracle_<lang>.ts
|
||||
type = QT
|
||||
minimum_perc = 10
|
||||
|
||||
[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master]
|
||||
resource_name = Webclient
|
||||
source_lang = en
|
||||
source_file = webclient/src/i18n-default.json
|
||||
file_filter = webclient/public/locales/<lang>/translation.json
|
||||
type = KEYVALUEJSON
|
||||
minimum_perc = 10
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@ endif()
|
|||
|
||||
# A project name is needed for CPack
|
||||
# Version can be overriden by git tags, see cmake/getversion.cmake
|
||||
project("Cockatrice" VERSION 2.11.0)
|
||||
project("Cockatrice" VERSION 3.0.1)
|
||||
|
||||
# Set release name if not provided via env/cmake var
|
||||
if(NOT DEFINED GIT_TAG_RELEASENAME)
|
||||
set(GIT_TAG_RELEASENAME "Omenpath")
|
||||
set(GIT_TAG_RELEASENAME "Graduation Day")
|
||||
endif()
|
||||
|
||||
# Use c++20 for all targets
|
||||
|
|
@ -172,6 +172,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|||
-Wno-error=delete-non-virtual-dtor
|
||||
-Wno-error=sign-compare
|
||||
-Wno-error=missing-declarations
|
||||
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
|
||||
)
|
||||
|
||||
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# -------- Build Stage --------
|
||||
FROM ubuntu:24.04 AS build
|
||||
FROM ubuntu:26.04 AS build
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ RUN mkdir build && cd build && \
|
|||
|
||||
|
||||
# -------- Runtime Stage (clean) --------
|
||||
FROM ubuntu:24.04
|
||||
FROM ubuntu:26.04
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libprotobuf32t64 \
|
||||
|
|
|
|||
99
Doxyfile
99
Doxyfile
|
|
@ -1,4 +1,4 @@
|
|||
# Doxyfile 1.14.0
|
||||
# Doxyfile 1.16.1
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# Doxygen (www.doxygen.org) for a project.
|
||||
|
|
@ -361,6 +361,20 @@ EXTENSION_MAPPING =
|
|||
|
||||
MARKDOWN_SUPPORT = YES
|
||||
|
||||
# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as
|
||||
# Markdown formatted also in cases where Doxygen's native markup format
|
||||
# conflicts with that of Markdown. This is only relevant in cases where
|
||||
# backticks are used. Doxygen's native markup style allows a single quote to end
|
||||
# a text fragment started with a backtick and then treat it as a piece of quoted
|
||||
# text, whereas in Markdown such text fragment is treated as verbatim and only
|
||||
# ends when a second matching backtick is found. Also, Doxygen's native markup
|
||||
# format requires double quotes to be escaped when they appear in a backtick
|
||||
# section, whereas this is not needed for Markdown.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
|
||||
|
||||
MARKDOWN_STRICT = YES
|
||||
|
||||
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
|
||||
# to that level are automatically included in the table of contents, even if
|
||||
# they do not have an id attribute.
|
||||
|
|
@ -392,8 +406,8 @@ AUTOLINK_SUPPORT = YES
|
|||
|
||||
# This tag specifies a list of words that, when matching the start of a word in
|
||||
# the documentation, will suppress auto links generation, if it is enabled via
|
||||
# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \#
|
||||
# or the \link or commands.
|
||||
# AUTOLINK_SUPPORT. This list does not affect links explicitly created using #
|
||||
# or the \link or \ref commands.
|
||||
# This tag requires that the tag AUTOLINK_SUPPORT is set to YES.
|
||||
|
||||
AUTOLINK_IGNORE_WORDS =
|
||||
|
|
@ -510,9 +524,9 @@ LOOKUP_CACHE_SIZE = 0
|
|||
# which effectively disables parallel processing. Please report any issues you
|
||||
# encounter. Generating dot graphs in parallel is controlled by the
|
||||
# DOT_NUM_THREADS setting.
|
||||
# Minimum value: 0, maximum value: 32, default value: 1.
|
||||
# Minimum value: 0, maximum value: 512, default value: 1.
|
||||
|
||||
NUM_PROC_THREADS = 1
|
||||
NUM_PROC_THREADS = 0
|
||||
|
||||
# If the TIMESTAMP tag is set different from NO then each generated page will
|
||||
# contain the date or date and time when the page was generated. Setting this to
|
||||
|
|
@ -779,6 +793,27 @@ GENERATE_BUGLIST = YES
|
|||
|
||||
GENERATE_DEPRECATEDLIST= YES
|
||||
|
||||
# The GENERATE_REQUIREMENTS tag can be used to enable (YES) or disable (NO) the
|
||||
# requirements page. When enabled, this page is automatically created when at
|
||||
# least one comment block with a \requirement command appears in the input.
|
||||
# The default value is: YES.
|
||||
|
||||
GENERATE_REQUIREMENTS = YES
|
||||
|
||||
# The REQ_TRACEABILITY_INFO tag controls if traceability information is shown on
|
||||
# the requirements page (only relevant when using \requirement comment blocks).
|
||||
# The setting NO will disable the traceablility information altogether. The
|
||||
# setting UNSATISFIED_ONLY will show a list of requirements that are missing a
|
||||
# satisfies relation (through the command: \satisfies). Similarly the setting
|
||||
# UNVERIFIED_ONLY will show a list of requirements that are missing a verifies
|
||||
# relation (through the command: \verifies). Setting the tag to YES (the
|
||||
# default) will show both lists if applicable.
|
||||
# Possible values are: YES, NO, UNSATISFIED_ONLY and UNVERIFIED_ONLY.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_REQUIREMENTS is set to YES.
|
||||
|
||||
REQ_TRACEABILITY_INFO = YES
|
||||
|
||||
# The ENABLED_SECTIONS tag can be used to enable conditional documentation
|
||||
# sections, marked by \if <section_label> ... \endif and \cond <section_label>
|
||||
# ... \endcond blocks.
|
||||
|
|
@ -1070,8 +1105,7 @@ EXCLUDE = build/ \
|
|||
cmake/ \
|
||||
doc/doxygen/theme/docs/ \
|
||||
doc/doxygen/theme/include/ \
|
||||
vcpkg/ \
|
||||
webclient/
|
||||
vcpkg/
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
|
|
@ -1887,7 +1921,7 @@ USE_MATHJAX = NO
|
|||
# regards to the different settings, so it is possible that also other MathJax
|
||||
# settings have to be changed when switching between the different MathJax
|
||||
# versions.
|
||||
# Possible values are: MathJax_2 and MathJax_3.
|
||||
# Possible values are: MathJax_2, MathJax_3 and MathJax_4.
|
||||
# The default value is: MathJax_2.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
|
|
@ -1896,9 +1930,10 @@ MATHJAX_VERSION = MathJax_2
|
|||
# When MathJax is enabled you can set the default output format to be used for
|
||||
# the MathJax output. For more details about the output format see MathJax
|
||||
# version 2 (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
|
||||
# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see:
|
||||
# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/latest/web/components/output.html).
|
||||
# https://docs.mathjax.org/en/v4.0/output/index.htm).
|
||||
# Possible values are: HTML-CSS (which is slower, but has the best
|
||||
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
|
||||
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
|
||||
|
|
@ -1911,36 +1946,50 @@ MATHJAX_VERSION = MathJax_2
|
|||
MATHJAX_FORMAT = HTML-CSS
|
||||
|
||||
# When MathJax is enabled you need to specify the location relative to the HTML
|
||||
# output directory using the MATHJAX_RELPATH option. The destination directory
|
||||
# should contain the MathJax.js script. For instance, if the mathjax directory
|
||||
# is located at the same level as the HTML output directory, then
|
||||
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
|
||||
# Content Delivery Network so you can quickly see the result without installing
|
||||
# MathJax. However, it is strongly recommended to install a local copy of
|
||||
# MathJax from https://www.mathjax.org before deployment. The default value is:
|
||||
# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the
|
||||
# destination directory should contain the MathJax.js script. For instance, if
|
||||
# the mathjax directory is located at the same level as the HTML output
|
||||
# directory, then MATHJAX_RELPATH should be ../mathjax.s For Mathjax versions 3
|
||||
# and 4 the destination directory should contain the tex-<format>.js script
|
||||
# (where <format> is either chtml or svg). The default value points to the
|
||||
# MathJax Content Delivery Network so you can quickly see the result without
|
||||
# installing MathJax. However, it is strongly recommended to install a local
|
||||
# copy of MathJax from https://www.mathjax.org before deployment. The default
|
||||
# value is:
|
||||
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
|
||||
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
|
||||
# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_RELPATH =
|
||||
|
||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||
# extension names that should be enabled during MathJax rendering. For example
|
||||
# for MathJax version 2 (see
|
||||
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
||||
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html):
|
||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||
# For example for MathJax version 3 (see
|
||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||
# https://docs.mathjax.org/en/v3.2/input/tex/extensions/):
|
||||
# MATHJAX_EXTENSIONS = ams
|
||||
# For example for MathJax version 4 (see
|
||||
# https://docs.mathjax.org/en/v4.0/input/tex/extensions/):
|
||||
# MATHJAX_EXTENSIONS = units
|
||||
# Note that for Mathjax version 4 quite a few extensions are already
|
||||
# automatically loaded. To disable a package in Mathjax version 4 one can use
|
||||
# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS +=
|
||||
# -textmacros)
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_EXTENSIONS =
|
||||
|
||||
# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces
|
||||
# of code that will be used on startup of the MathJax code. See the MathJax site
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
|
||||
# example see the documentation.
|
||||
# of code that will be used on startup of the MathJax code. See the Mathjax site
|
||||
# for more details:
|
||||
# - MathJax version 2 (see:
|
||||
# https://docs.mathjax.org/en/v2.7/)
|
||||
# - MathJax version 3 (see:
|
||||
# https://docs.mathjax.org/en/v3.2/)
|
||||
# - MathJax version 4 (see:
|
||||
# https://docs.mathjax.org/en/v4.0/) For an example see the documentation.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_CODEFILE =
|
||||
|
|
@ -2601,7 +2650,7 @@ HAVE_DOT = YES
|
|||
# processors available in the system. You can set it explicitly to a value
|
||||
# larger than 0 to get control over the balance between CPU load and processing
|
||||
# speed.
|
||||
# Minimum value: 0, maximum value: 32, default value: 0.
|
||||
# Minimum value: 0, maximum value: 512, default value: 0.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_NUM_THREADS = 0
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<a href="#related-repositories">Related</a> <b>|</b>
|
||||
<a href="#community-resources-">Community</a> <b>|</b>
|
||||
<a href="#contribute">Contribute</a> <b>|</b>
|
||||
<a href="#build---">Build</a> <b>|</b>
|
||||
<a href="#build--">Build</a> <b>|</b>
|
||||
<a href="#run">Run</a>
|
||||
</p>
|
||||
|
||||
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.<br><br>
|
||||
This project uses <kbd>C++</kbd> and the <kbd>Qt</kbd> libraries.<br>
|
||||
First work on a webclient with <kbd>Typescript</kbd> was started as well.<br>
|
||||
|
||||
|
||||
# Download [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
|
||||
|
|
@ -48,6 +47,7 @@ Latest <kbd>beta</kbd> version:
|
|||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
|
||||
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
|
||||
- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
|
||||
- [Webatrice](https://github.com/Cockatrice/Webatrice): Web client for Cockatrice servers (TypeScript / React)
|
||||
|
||||
|
||||
# Community Resources [](https://discord.gg/3Z9yzmA)
|
||||
|
|
@ -107,12 +107,12 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
|
|||
|
||||
### Translation [](https://explore.transifex.com/cockatrice/cockatrice/)
|
||||
|
||||
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
|
||||
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd> and <kbd>Oracle</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/). The [Webatrice](https://github.com/seavor/Webatrice) web client manages its own translations in its repo.<br>
|
||||
|
||||
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!<br>
|
||||
|
||||
|
||||
# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
|
||||
# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush)
|
||||
|
||||
Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
|
||||
- [Qt](https://www.qt.io/developers/)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ SetCompressor LZMA
|
|||
Var NormalDestDir
|
||||
Var PortableDestDir
|
||||
Var PortableMode
|
||||
Var ReinstallMode
|
||||
|
||||
!include LogicLib.nsh
|
||||
!include FileFunc.nsh
|
||||
|
|
@ -28,13 +29,23 @@ Var PortableMode
|
|||
!define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now"
|
||||
!define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico"
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE"
|
||||
|
||||
Page Custom PortableModePageCreate PortableModePageLeave
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
|
@ -73,6 +84,7 @@ ${IfNot} ${Errors}
|
|||
MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\
|
||||
/PORTABLE : Install in portable mode$\n\
|
||||
/S : Silent install$\n\
|
||||
/R : Silent upgrade$\n\
|
||||
/D=%directory% : Specify destination directory$\n"
|
||||
Quit
|
||||
${EndIf}
|
||||
|
|
@ -90,6 +102,16 @@ ${Else}
|
|||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
ClearErrors
|
||||
${GetOptions} $9 "/R" $8
|
||||
${IfNot} ${Errors}
|
||||
StrCpy $ReinstallMode 1
|
||||
SetSilent silent
|
||||
SetAutoClose true
|
||||
${Else}
|
||||
StrCpy $ReinstallMode 0
|
||||
${EndIf}
|
||||
|
||||
${If} $InstDir == ""
|
||||
; User did not use /D to specify a directory,
|
||||
; we need to set a default based on the install mode
|
||||
|
|
@ -97,6 +119,22 @@ ${If} $InstDir == ""
|
|||
${EndIf}
|
||||
Call SetModeDestinationFromInstdir
|
||||
|
||||
; --- Detect portable install when using /R ---
|
||||
${If} $ReinstallMode = 1
|
||||
IfFileExists "$InstDir\portable.dat" 0 not_portable
|
||||
StrCpy $PortableMode 1
|
||||
Goto portable_done
|
||||
|
||||
not_portable:
|
||||
StrCpy $PortableMode 0
|
||||
|
||||
portable_done:
|
||||
${EndIf}
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
Call AutoUninstallIfNeeded
|
||||
${EndIf}
|
||||
|
||||
FunctionEnd
|
||||
|
||||
Function un.onInit
|
||||
|
|
@ -126,8 +164,46 @@ ${Else}
|
|||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function SkipIfReinstall
|
||||
${If} $ReinstallMode = 1
|
||||
Abort
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function AutoUninstallIfNeeded
|
||||
|
||||
SetShellVarContext all
|
||||
|
||||
; --- 32-bit uninstall ---
|
||||
SetRegView 32
|
||||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
||||
|
||||
StrCmp $R0 "" done32
|
||||
DetailPrint "Removing previous version (32-bit)..."
|
||||
ExecWait '$R0'
|
||||
|
||||
done32:
|
||||
|
||||
; --- 64-bit uninstall ---
|
||||
${If} ${RunningX64}
|
||||
SetRegView 64
|
||||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
||||
|
||||
StrCmp $R0 "" done64
|
||||
DetailPrint "Removing previous version (64-bit)..."
|
||||
ExecWait '$R0'
|
||||
|
||||
done64:
|
||||
${EndIf}
|
||||
|
||||
FunctionEnd
|
||||
|
||||
Function PortableModePageCreate
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory
|
||||
!insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice."
|
||||
nsDialogs::Create 1018
|
||||
|
|
@ -159,6 +235,11 @@ ${EndIf}
|
|||
FunctionEnd
|
||||
|
||||
Function componentsPagePre
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
Return
|
||||
${EndIf}
|
||||
|
||||
${If} $PortableMode = 0
|
||||
SetShellVarContext all
|
||||
|
||||
|
|
@ -168,8 +249,12 @@ ${If} $PortableMode = 0
|
|||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
|
||||
StrCmp $R0 "" done32
|
||||
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
|
||||
Abort
|
||||
${If} $ReinstallMode = 0
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
|
||||
Abort
|
||||
${Else}
|
||||
Goto uninst32
|
||||
${EndIf}
|
||||
|
||||
uninst32:
|
||||
ClearErrors
|
||||
|
|
@ -184,8 +269,12 @@ ${If} $PortableMode = 0
|
|||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
|
||||
StrCmp $R0 "" done64
|
||||
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
|
||||
Abort
|
||||
${If} $ReinstallMode = 0
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
|
||||
Abort
|
||||
${Else}
|
||||
Goto uninst64
|
||||
${EndIf}
|
||||
|
||||
uninst64:
|
||||
ClearErrors
|
||||
|
|
@ -277,6 +366,12 @@ ${Else}
|
|||
FileWrite $0 "PORTABLE"
|
||||
FileClose $0
|
||||
${EndIf}
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
IfFileExists "$INSTDIR\cockatrice.exe" 0 +2
|
||||
Exec '"$INSTDIR\cockatrice.exe"'
|
||||
${EndIf}
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section "Start menu item" SecStartMenu
|
||||
|
|
|
|||
|
|
@ -213,7 +213,8 @@ set(PROJECT_VERSION_FRIENDLY "${PROJECT_VERSION} (${GIT_COMMIT_DATE_FRIENDLY})")
|
|||
# Format: <program name>[-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label]
|
||||
set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}")
|
||||
if(PROJECT_VERSION_RELEASENAME)
|
||||
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME}")
|
||||
string(REPLACE " " "-" PROJECT_VERSION_RELEASENAME_SAFE "${PROJECT_VERSION_RELEASENAME}")
|
||||
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME_SAFE}")
|
||||
endif()
|
||||
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
cmake_minimum_required(VERSION 3.2)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(gtest-download LANGUAGES NONE)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(googletest
|
||||
URL https://github.com/google/googletest/archive/release-1.11.0.zip
|
||||
URL_HASH SHA1=9ffb7b5923f4a8fcdabf2f42c6540cce299f44c0
|
||||
externalproject_add(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip
|
||||
URL_HASH SHA1=f638fa0e724760e2ba07ff8cfba32cd644e1ce28
|
||||
SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
|
||||
BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${
|
|||
set(cockatrice_SOURCES
|
||||
${VERSION_STRING_CPP}
|
||||
# sort by alphabetical order, so that there is no debate about where to add new sources to the list
|
||||
src/client/network/connection_controller/remote_connection_controller.cpp
|
||||
src/client/network/update/client/update_downloader.cpp
|
||||
src/client/network/interfaces/deck_stats_interface.cpp
|
||||
src/client/network/interfaces/tapped_out_interface.cpp
|
||||
|
|
@ -216,6 +217,7 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/replay/replay_manager.cpp
|
||||
src/interface/widgets/replay/replay_timeline_widget.cpp
|
||||
src/interface/widgets/server/chat_view/chat_view.cpp
|
||||
src/interface/widgets/server/game_filter_configs.cpp
|
||||
src/interface/widgets/server/game_selector.cpp
|
||||
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
|
||||
src/interface/widgets/server/games_model.cpp
|
||||
|
|
@ -498,6 +500,7 @@ if(WIN32)
|
|||
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
|
||||
DESTINATION ./
|
||||
FILES_MATCHING
|
||||
PATTERN "CMakeFiles" EXCLUDE
|
||||
PATTERN "*.ini"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,581 @@
|
|||
#include "remote_connection_controller.h"
|
||||
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../interface/widgets/dialogs/dlg_connect.h"
|
||||
#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
|
||||
#include "../interface/widgets/dialogs/dlg_forgot_password_request.h"
|
||||
#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h"
|
||||
#include "../interface/widgets/dialogs/dlg_register.h"
|
||||
#include "../interface/widgets/utility/get_text_with_max.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QThread>
|
||||
#include <libcockatrice/network/client/remote/remote_client.h>
|
||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||
|
||||
ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent)
|
||||
: QObject(parent), dialogParent(dialogParent)
|
||||
{
|
||||
remoteClient = new RemoteClient(nullptr, &SettingsCache::instance());
|
||||
|
||||
clientThread = new QThread(this);
|
||||
remoteClient->moveToThread(clientThread);
|
||||
clientThread->start();
|
||||
|
||||
wireClientSignals();
|
||||
}
|
||||
|
||||
ConnectionController::~ConnectionController()
|
||||
{
|
||||
remoteClient->deleteLater();
|
||||
clientThread->wait();
|
||||
}
|
||||
|
||||
void ConnectionController::wireClientSignals()
|
||||
{
|
||||
connect(remoteClient, &RemoteClient::connectionClosedEventReceived, this,
|
||||
&ConnectionController::onConnectionClosedEvent);
|
||||
|
||||
connect(remoteClient, &RemoteClient::serverShutdownEventReceived, this,
|
||||
&ConnectionController::onServerShutdownEvent);
|
||||
|
||||
connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged);
|
||||
|
||||
connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived,
|
||||
Qt::BlockingQueuedConnection);
|
||||
|
||||
connect(remoteClient, &RemoteClient::loginError, this,
|
||||
[this](Response::ResponseCode r, QString rs, quint32 et, QList<QString> mf) {
|
||||
onLoginError(static_cast<int>(r), rs, et, mf);
|
||||
});
|
||||
|
||||
connect(remoteClient, &RemoteClient::registerError, this,
|
||||
[this](Response::ResponseCode r, QString rs, quint32 et) { onRegisterError(static_cast<int>(r), rs, et); });
|
||||
|
||||
connect(remoteClient, &RemoteClient::activateError, this, &ConnectionController::onActivateError);
|
||||
connect(remoteClient, &RemoteClient::socketError, this, &ConnectionController::onSocketError);
|
||||
connect(remoteClient, &RemoteClient::serverTimeout, this, &ConnectionController::onServerTimeout);
|
||||
|
||||
connect(remoteClient, &RemoteClient::protocolVersionMismatch, this,
|
||||
&ConnectionController::onProtocolVersionMismatch);
|
||||
|
||||
connect(remoteClient, &RemoteClient::registerAccepted, this, &ConnectionController::onRegisterAccepted);
|
||||
|
||||
connect(remoteClient, &RemoteClient::registerAcceptedNeedsActivate, this,
|
||||
&ConnectionController::onRegisterAcceptedNeedsActivate);
|
||||
|
||||
connect(remoteClient, &RemoteClient::activateAccepted, this, &ConnectionController::onActivateAccepted);
|
||||
|
||||
connect(remoteClient, &RemoteClient::notifyUserAboutUpdate, this, &ConnectionController::onNotifyUserAboutUpdate);
|
||||
|
||||
connect(remoteClient, &RemoteClient::sigForgotPasswordSuccess, this,
|
||||
&ConnectionController::onForgotPasswordSuccess);
|
||||
|
||||
connect(remoteClient, &RemoteClient::sigForgotPasswordError, this, &ConnectionController::onForgotPasswordError);
|
||||
|
||||
connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordReset, this,
|
||||
&ConnectionController::onPromptForgotPasswordReset);
|
||||
|
||||
connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordChallenge, this,
|
||||
&ConnectionController::onPromptForgotPasswordChallenge);
|
||||
}
|
||||
|
||||
void ConnectionController::connectToServer()
|
||||
{
|
||||
dlgConnect = new DlgConnect(dialogParent);
|
||||
connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &ConnectionController::forgotPasswordRequest);
|
||||
|
||||
if (dlgConnect->exec()) {
|
||||
remoteClient->connectToServer(dlgConnect->getHost(), static_cast<unsigned int>(dlgConnect->getPort()),
|
||||
dlgConnect->getPlayerName(), dlgConnect->getPassword());
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionController::connectToServerDirect(const QString &host,
|
||||
unsigned int port,
|
||||
const QString &playerName,
|
||||
const QString &password)
|
||||
{
|
||||
remoteClient->connectToServer(host, port, playerName, password);
|
||||
}
|
||||
|
||||
void ConnectionController::disconnectFromServer()
|
||||
{
|
||||
remoteClient->disconnectFromServer();
|
||||
}
|
||||
|
||||
void ConnectionController::registerToServer()
|
||||
{
|
||||
DlgRegister dlg(dialogParent);
|
||||
if (dlg.exec()) {
|
||||
remoteClient->registerToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()), dlg.getPlayerName(),
|
||||
dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName());
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionController::forgotPasswordRequest()
|
||||
{
|
||||
DlgForgotPasswordRequest dlg(dialogParent);
|
||||
if (dlg.exec()) {
|
||||
remoteClient->requestForgotPasswordToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
|
||||
dlg.getPlayerName());
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionController::onConnectionClosedEvent(const Event_ConnectionClosed &event)
|
||||
{
|
||||
remoteClient->disconnectFromServer();
|
||||
|
||||
QString reasonStr;
|
||||
switch (event.reason()) {
|
||||
case Event_ConnectionClosed::USER_LIMIT_REACHED: {
|
||||
reasonStr = tr("The server has reached its maximum user capacity, please check back later.");
|
||||
break;
|
||||
}
|
||||
case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: {
|
||||
reasonStr = tr("There are too many concurrent connections from your address.");
|
||||
break;
|
||||
}
|
||||
case Event_ConnectionClosed::BANNED: {
|
||||
reasonStr = tr("Banned by moderator");
|
||||
if (event.has_end_time())
|
||||
reasonStr.append(
|
||||
"\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString()));
|
||||
else
|
||||
reasonStr.append("\n" + tr("This ban lasts indefinitely."));
|
||||
if (event.has_reason_str())
|
||||
reasonStr.append("\n\n" + QString::fromStdString(event.reason_str()));
|
||||
break;
|
||||
}
|
||||
case Event_ConnectionClosed::SERVER_SHUTDOWN: {
|
||||
reasonStr = tr("Scheduled server shutdown.");
|
||||
break;
|
||||
}
|
||||
case Event_ConnectionClosed::USERNAMEINVALID: {
|
||||
reasonStr = tr("Invalid username.");
|
||||
break;
|
||||
}
|
||||
case Event_ConnectionClosed::LOGGEDINELSEWERE: {
|
||||
reasonStr = tr("You have been logged out due to logging in at another location.");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
reasonStr = QString::fromStdString(event.reason_str());
|
||||
}
|
||||
|
||||
QMessageBox::critical(dialogParent, tr("Connection closed"),
|
||||
tr("The server has terminated your connection.\nReason: %1").arg(reasonStr));
|
||||
}
|
||||
|
||||
void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &event)
|
||||
{
|
||||
serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running "
|
||||
"games will be lost.\nReason for shutdown: %1",
|
||||
"", event.minutes())
|
||||
.arg(QString::fromStdString(event.reason())));
|
||||
serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
|
||||
serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
|
||||
serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
|
||||
serverShutdownMessageBox.setVisible(true);
|
||||
}
|
||||
|
||||
void ConnectionController::onStatusChanged(ClientStatus status)
|
||||
{
|
||||
// Update the window title first, then let MainWindow handle its own UI
|
||||
// state via the forwarded signal
|
||||
updateWindowTitle();
|
||||
emit statusChanged(status);
|
||||
|
||||
// TabSupervisor::stop() needs calling on disconnect; start() is driven by
|
||||
// onUserInfoReceived → tabSupervisorStartRequested.
|
||||
if (status == StatusDisconnected) {
|
||||
emit tabSupervisorStopRequested();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionController::onUserInfoReceived(const ServerInfo_User &info)
|
||||
{
|
||||
emit tabSupervisorStartRequested(info);
|
||||
}
|
||||
|
||||
void ConnectionController::onLoginError(int r,
|
||||
QString reasonStr,
|
||||
quint32 endTime,
|
||||
const QList<QString> &missingFeatures)
|
||||
{
|
||||
switch (static_cast<Response::ResponseCode>(r)) {
|
||||
case Response::RespClientUpdateRequired: {
|
||||
QString formatted = "Missing Features: ";
|
||||
for (int i = 0; i < missingFeatures.size(); ++i) {
|
||||
formatted.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i));
|
||||
}
|
||||
|
||||
QMessageBox msgBox(dialogParent);
|
||||
msgBox.setIcon(QMessageBox::Critical);
|
||||
msgBox.setWindowTitle(tr("Failed Login"));
|
||||
msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") +
|
||||
"\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'."));
|
||||
msgBox.setDetailedText(formatted);
|
||||
msgBox.exec();
|
||||
break;
|
||||
}
|
||||
|
||||
case Response::RespWrongPassword: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("Incorrect username or password. "
|
||||
"Please check your authentication information and try again."));
|
||||
break;
|
||||
}
|
||||
|
||||
case Response::RespWouldOverwriteOldSession: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("There is already an active session using this user name.\n"
|
||||
"Please close that session first and re-login."));
|
||||
break;
|
||||
}
|
||||
|
||||
case Response::RespUserIsBanned: {
|
||||
QString bannedStr =
|
||||
endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
|
||||
: tr("You are banned indefinitely.");
|
||||
if (!reasonStr.isEmpty())
|
||||
bannedStr.append("\n\n" + reasonStr);
|
||||
QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
|
||||
break;
|
||||
}
|
||||
|
||||
case Response::RespUsernameInvalid: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
|
||||
break;
|
||||
}
|
||||
|
||||
case Response::RespRegistrationRequired: {
|
||||
if (QMessageBox::question(dialogParent, tr("Error"),
|
||||
tr("This server requires user registration. Do you want to register now?"),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
registerToServer();
|
||||
}
|
||||
return; // don't re-prompt connect
|
||||
}
|
||||
|
||||
case Response::RespClientIdRequired: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("This server requires client IDs. Your client is either failing to generate an "
|
||||
"ID or you are running a modified client.\n"
|
||||
"Please close and reopen your client to try again."));
|
||||
break;
|
||||
}
|
||||
|
||||
case Response::RespContextError: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("An internal error has occurred, please close and reopen Cockatrice before "
|
||||
"trying again.\nIf the error persists, ensure you are running the latest "
|
||||
"version of the software and if needed contact the software developers."));
|
||||
break;
|
||||
}
|
||||
|
||||
case Response::RespAccountNotActivated: {
|
||||
bool ok = false;
|
||||
QString token =
|
||||
getTextWithMax(dialogParent, tr("Account activation"),
|
||||
tr("Your account has not been activated yet.\n"
|
||||
"You need to provide the activation token received in the activation email."),
|
||||
QLineEdit::Normal, QString(), &ok);
|
||||
|
||||
if (ok && !token.isEmpty()) {
|
||||
remoteClient->activateToServer(token);
|
||||
return;
|
||||
}
|
||||
remoteClient->disconnectFromServer();
|
||||
return;
|
||||
}
|
||||
|
||||
case Response::RespServerFull: {
|
||||
QMessageBox::critical(dialogParent, tr("Server Full"),
|
||||
tr("The server has reached its maximum user capacity, please check back later."));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("Unknown login error: %1").arg(r) +
|
||||
tr("\nThis usually means that your client version is out of date, and the server "
|
||||
"sent a reply your client doesn't understand."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-open the connect dialog after any handled error
|
||||
connectToServer();
|
||||
}
|
||||
|
||||
void ConnectionController::onRegisterError(int r, QString reasonStr, quint32 endTime)
|
||||
{
|
||||
switch (static_cast<Response::ResponseCode>(r)) {
|
||||
case Response::RespRegistrationDisabled: {
|
||||
QMessageBox::critical(dialogParent, tr("Registration denied"),
|
||||
tr("Registration is currently disabled on this server"));
|
||||
break;
|
||||
}
|
||||
case Response::RespUserAlreadyExists: {
|
||||
QMessageBox::critical(dialogParent, tr("Registration denied"),
|
||||
tr("There is already an existing account with the same user name."));
|
||||
break;
|
||||
}
|
||||
case Response::RespEmailRequiredToRegister: {
|
||||
QMessageBox::critical(dialogParent, tr("Registration denied"),
|
||||
tr("It's mandatory to specify a valid email address when registering."));
|
||||
break;
|
||||
}
|
||||
case Response::RespEmailBlackListed: {
|
||||
if (reasonStr.isEmpty()) {
|
||||
reasonStr =
|
||||
"The email address provider used during registration has been blocked from use on this server.";
|
||||
}
|
||||
QMessageBox::critical(dialogParent, tr("Registration denied"), reasonStr);
|
||||
break;
|
||||
}
|
||||
case Response::RespTooManyRequests: {
|
||||
QMessageBox::critical(dialogParent, tr("Registration denied"),
|
||||
tr("It appears you are attempting to register a new account on this server yet you "
|
||||
"already have an account registered with the email provided. This server "
|
||||
"restricts the number of accounts a user can register per address. Please "
|
||||
"contact the server operator for further assistance or to obtain your "
|
||||
"credential information."));
|
||||
break;
|
||||
}
|
||||
case Response::RespPasswordTooShort: {
|
||||
QMessageBox::critical(dialogParent, tr("Registration denied"), tr("Password too short."));
|
||||
break;
|
||||
}
|
||||
case Response::RespUserIsBanned: {
|
||||
QString bannedStr =
|
||||
endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
|
||||
: tr("You are banned indefinitely.");
|
||||
if (!reasonStr.isEmpty())
|
||||
bannedStr.append("\n\n" + reasonStr);
|
||||
QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
|
||||
break;
|
||||
}
|
||||
case Response::RespUsernameInvalid: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
|
||||
break;
|
||||
}
|
||||
case Response::RespRegistrationFailed: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("Registration failed for a technical problem on the server."));
|
||||
break;
|
||||
}
|
||||
case Response::RespNotConnected: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"), tr("The connection to the server has been lost."));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("Unknown registration error: %1").arg(r) +
|
||||
tr("\nThis usually means that your client version is out of date, and the server "
|
||||
"sent a reply your client doesn't understand."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
registerToServer();
|
||||
}
|
||||
|
||||
void ConnectionController::onActivateError()
|
||||
{
|
||||
QMessageBox::critical(dialogParent, tr("Error"), tr("Account activation failed"));
|
||||
remoteClient->disconnectFromServer();
|
||||
connectToServer();
|
||||
}
|
||||
|
||||
void ConnectionController::onSocketError(const QString &errorStr)
|
||||
{
|
||||
QMessageBox::critical(dialogParent, tr("Error"), tr("Socket error: %1").arg(errorStr));
|
||||
connectToServer();
|
||||
}
|
||||
|
||||
void ConnectionController::onServerTimeout()
|
||||
{
|
||||
QMessageBox::critical(dialogParent, tr("Error"), tr("Server timeout"));
|
||||
connectToServer();
|
||||
}
|
||||
|
||||
void ConnectionController::onProtocolVersionMismatch(int localVersion, int remoteVersion)
|
||||
{
|
||||
if (localVersion > remoteVersion) {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice "
|
||||
"version or connect to a suitable server.\n"
|
||||
"Local version is %1, remote version is %2.")
|
||||
.arg(localVersion)
|
||||
.arg(remoteVersion));
|
||||
} else {
|
||||
QMessageBox::critical(dialogParent, tr("Error"),
|
||||
tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\n"
|
||||
"Local version is %1, remote version is %2.")
|
||||
.arg(localVersion)
|
||||
.arg(remoteVersion));
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionController::onRegisterAccepted()
|
||||
{
|
||||
QMessageBox::information(dialogParent, tr("Success"), tr("Registration accepted.\nWill now login."));
|
||||
}
|
||||
|
||||
void ConnectionController::onRegisterAcceptedNeedsActivate()
|
||||
{
|
||||
// Server will send activation email; nothing to display here.
|
||||
}
|
||||
|
||||
void ConnectionController::onActivateAccepted()
|
||||
{
|
||||
QMessageBox::information(dialogParent, tr("Success"), tr("Account activation accepted.\nWill now login."));
|
||||
}
|
||||
|
||||
void ConnectionController::onNotifyUserAboutUpdate()
|
||||
{
|
||||
QMessageBox::information(
|
||||
dialogParent, tr("Information"),
|
||||
tr("This server supports additional features that your client doesn't have.\n"
|
||||
"This is most likely not a problem, but this message might mean there is a new version of "
|
||||
"Cockatrice available or this server is running a custom or pre-release version.\n\n"
|
||||
"To update your client, go to Help -> Check for Updates."));
|
||||
}
|
||||
|
||||
void ConnectionController::onForgotPasswordSuccess()
|
||||
{
|
||||
QMessageBox::information(
|
||||
dialogParent, tr("Reset Password"),
|
||||
tr("Your password has been reset successfully, you can now log in using the new credentials."));
|
||||
SettingsCache::instance().servers().setFPHostName("");
|
||||
SettingsCache::instance().servers().setFPPort("");
|
||||
SettingsCache::instance().servers().setFPPlayerName("");
|
||||
}
|
||||
|
||||
void ConnectionController::onForgotPasswordError()
|
||||
{
|
||||
QMessageBox::warning(
|
||||
dialogParent, tr("Reset Password"),
|
||||
tr("Failed to reset user account password, please contact the server operator to reset your password."));
|
||||
SettingsCache::instance().servers().setFPHostName("");
|
||||
SettingsCache::instance().servers().setFPPort("");
|
||||
SettingsCache::instance().servers().setFPPlayerName("");
|
||||
}
|
||||
|
||||
void ConnectionController::onPromptForgotPasswordReset()
|
||||
{
|
||||
QMessageBox::information(dialogParent, tr("Reset Password"),
|
||||
tr("Activation request received, please check your email for an activation token."));
|
||||
DlgForgotPasswordReset dlg(dialogParent);
|
||||
if (dlg.exec()) {
|
||||
remoteClient->submitForgotPasswordResetToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
|
||||
dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionController::onPromptForgotPasswordChallenge()
|
||||
{
|
||||
DlgForgotPasswordChallenge dlg(dialogParent);
|
||||
if (dlg.exec()) {
|
||||
remoteClient->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
|
||||
dlg.getPlayerName(), dlg.getEmail());
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionController::updateWindowTitle()
|
||||
{
|
||||
const QString appName = QStringLiteral("Cockatrice");
|
||||
QString title;
|
||||
|
||||
switch (remoteClient->getStatus()) {
|
||||
case StatusConnecting: {
|
||||
title = appName + " - " + tr("Connecting to %1...").arg(remoteClient->peerName());
|
||||
break;
|
||||
}
|
||||
case StatusRegistering: {
|
||||
title = appName + " - " +
|
||||
tr("Registering to %1 as %2...").arg(remoteClient->peerName()).arg(remoteClient->getUserName());
|
||||
break;
|
||||
}
|
||||
case StatusDisconnected: {
|
||||
title = appName + " - " + tr("Disconnected");
|
||||
break;
|
||||
}
|
||||
case StatusLoggingIn: {
|
||||
title = appName + " - " + tr("Connected, logging in at %1").arg(remoteClient->peerName());
|
||||
break;
|
||||
}
|
||||
case StatusLoggedIn: {
|
||||
title = remoteClient->getUserName() + "@" + remoteClient->peerName();
|
||||
break;
|
||||
}
|
||||
case StatusRequestingForgotPassword:
|
||||
case StatusSubmitForgotPasswordChallenge:
|
||||
case StatusSubmitForgotPasswordReset:
|
||||
title = appName + " - " +
|
||||
tr("Requesting forgotten password to %1 as %2...")
|
||||
.arg(remoteClient->peerName())
|
||||
.arg(remoteClient->getUserName());
|
||||
break;
|
||||
default:
|
||||
title = appName;
|
||||
}
|
||||
|
||||
emit windowTitleChanged(title);
|
||||
}
|
||||
|
||||
// static
|
||||
QString ConnectionController::extractInvalidUsernameMessage(QString &in)
|
||||
{
|
||||
QString out = tr("Invalid username.") + "<br/>";
|
||||
QStringList rules = in.split(QChar('|'));
|
||||
|
||||
if (rules.size() == 7 || rules.size() == 9) {
|
||||
out += tr("Your username must respect these rules:") + "<ul>";
|
||||
|
||||
out += "<li>" + tr("is %1 - %2 characters long").arg(rules.at(0)).arg(rules.at(1)) + "</li>";
|
||||
out += "<li>" + tr("can %1 contain lowercase characters").arg((rules.at(2).toInt() > 0) ? "" : tr("NOT")) +
|
||||
"</li>";
|
||||
out += "<li>" + tr("can %1 contain uppercase characters").arg((rules.at(3).toInt() > 0) ? "" : tr("NOT")) +
|
||||
"</li>";
|
||||
out +=
|
||||
"<li>" + tr("can %1 contain numeric characters").arg((rules.at(4).toInt() > 0) ? "" : tr("NOT")) + "</li>";
|
||||
|
||||
if (rules.at(6).size() > 0)
|
||||
out += "<li>" + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "</li>";
|
||||
|
||||
out += "<li>" +
|
||||
tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) +
|
||||
"</li>";
|
||||
|
||||
if (rules.size() == 9) {
|
||||
if (rules.at(7).size() > 0) {
|
||||
QString words = rules.at(7).toHtmlEscaped();
|
||||
if (words.startsWith("\n")) {
|
||||
out += tr("no unacceptable language as specified by these server rules:",
|
||||
"note that the following lines will not be translated");
|
||||
for (QString &line : words.split("\n", Qt::SkipEmptyParts)) {
|
||||
out += "<li>" + line + "</li>";
|
||||
}
|
||||
} else {
|
||||
out += "<li>" + tr("can not contain any of the following words: %1").arg(words) + "</li>";
|
||||
}
|
||||
}
|
||||
|
||||
if (rules.at(8).size() > 0)
|
||||
out += "<li>" +
|
||||
tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) +
|
||||
"</li>";
|
||||
}
|
||||
|
||||
out += "</ul>";
|
||||
} else {
|
||||
out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username.");
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
#ifndef COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
|
||||
#define COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
|
||||
|
||||
#include "abstract_client.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <libcockatrice/protocol/pb/event_connection_closed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
|
||||
|
||||
class RemoteClient;
|
||||
class ServerInfo_User;
|
||||
class DlgConnect;
|
||||
|
||||
/**
|
||||
* Owns the RemoteClient and its worker thread.
|
||||
* Encapsulates all connection, authentication, and registration logic so that
|
||||
* MainWindow only needs to react to high-level signals.
|
||||
*/
|
||||
class ConnectionController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConnectionController(QWidget *dialogParent, QObject *parent = nullptr);
|
||||
~ConnectionController() override;
|
||||
|
||||
RemoteClient *client() const
|
||||
{
|
||||
return remoteClient;
|
||||
}
|
||||
|
||||
void registerToServer();
|
||||
void forgotPasswordRequest();
|
||||
void connectToServer();
|
||||
void
|
||||
connectToServerDirect(const QString &host, unsigned int port, const QString &playerName, const QString &password);
|
||||
void disconnectFromServer();
|
||||
|
||||
void refreshWindowTitle()
|
||||
{
|
||||
updateWindowTitle();
|
||||
}
|
||||
|
||||
signals:
|
||||
void windowTitleChanged(const QString &title);
|
||||
|
||||
void tabSupervisorStartRequested(const ServerInfo_User &info);
|
||||
void tabSupervisorStopRequested();
|
||||
|
||||
// Passes the raw ClientStatus through so MainWindow can drive its own
|
||||
// action enable/disable logic
|
||||
void statusChanged(ClientStatus status);
|
||||
|
||||
private slots:
|
||||
// Slots wired directly to RemoteClient signals
|
||||
void onStatusChanged(ClientStatus status);
|
||||
void onUserInfoReceived(const ServerInfo_User &info);
|
||||
void onLoginError(int r, QString reasonStr, quint32 endTime, const QList<QString> &missingFeatures);
|
||||
void onRegisterAccepted();
|
||||
void onRegisterAcceptedNeedsActivate();
|
||||
void onRegisterError(int r, QString reasonStr, quint32 endTime);
|
||||
void onActivateAccepted();
|
||||
void onActivateError();
|
||||
void onProtocolVersionMismatch(int localVersion, int remoteVersion);
|
||||
void onNotifyUserAboutUpdate();
|
||||
void onConnectionClosedEvent(const Event_ConnectionClosed &event);
|
||||
void onServerShutdownEvent(const Event_ServerShutdown &event);
|
||||
void onSocketError(const QString &errorStr);
|
||||
void onServerTimeout();
|
||||
|
||||
// Forgot-password flow
|
||||
void onForgotPasswordSuccess();
|
||||
void onForgotPasswordError();
|
||||
void onPromptForgotPasswordReset();
|
||||
void onPromptForgotPasswordChallenge();
|
||||
|
||||
private:
|
||||
void wireClientSignals();
|
||||
void updateWindowTitle();
|
||||
|
||||
/** Parse the server's pipe-delimited username-rule string into HTML. */
|
||||
static QString extractInvalidUsernameMessage(QString &in);
|
||||
|
||||
RemoteClient *remoteClient{nullptr};
|
||||
QThread *clientThread{nullptr};
|
||||
QWidget *dialogParent{nullptr}; // used as parent for QMessageBox / dialog calls
|
||||
|
||||
// Persistent so it can be updated in-place by onServerShutdownEvent
|
||||
QMessageBox serverShutdownMessageBox;
|
||||
|
||||
// Kept as a member so the forgot-password signal can be wired to it
|
||||
DlgConnect *dlgConnect{nullptr};
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
|
||||
|
|
@ -89,6 +89,8 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck)
|
|||
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
// we interpret the redirect and open it in the browser instead, do not follow redirects
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
|
||||
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ SettingsCache::SettingsCache()
|
|||
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
|
||||
cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt();
|
||||
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
|
||||
alwaysEnableNewSets = settings->value("personal/alwaysEnableNewSets", false).toBool();
|
||||
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
|
||||
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
|
||||
|
||||
|
|
@ -1246,6 +1247,12 @@ void SettingsCache::setLastCardUpdateCheck(QDate value)
|
|||
settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck);
|
||||
}
|
||||
|
||||
void SettingsCache::setAlwaysEnableNewSets(bool value)
|
||||
{
|
||||
alwaysEnableNewSets = value;
|
||||
settings->setValue("personal/alwaysEnableNewSets", alwaysEnableNewSets);
|
||||
}
|
||||
|
||||
void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
|
||||
{
|
||||
rememberGameSettings = _rememberGameSettings;
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ private:
|
|||
bool checkCardUpdatesOnStartup;
|
||||
int cardUpdateCheckInterval;
|
||||
QDate lastCardUpdateCheck;
|
||||
bool alwaysEnableNewSets;
|
||||
bool notifyAboutUpdates;
|
||||
bool notifyAboutNewVersion;
|
||||
bool showTipsOnStartup;
|
||||
|
|
@ -502,6 +503,10 @@ public:
|
|||
return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() &&
|
||||
getLastCardUpdateCheck() != QDateTime::currentDateTime().date();
|
||||
}
|
||||
[[nodiscard]] bool getAlwaysEnableNewSets() const
|
||||
{
|
||||
return alwaysEnableNewSets;
|
||||
}
|
||||
[[nodiscard]] bool getNotifyAboutUpdates() const override
|
||||
{
|
||||
return notifyAboutUpdates;
|
||||
|
|
@ -1125,6 +1130,7 @@ public slots:
|
|||
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
|
||||
void setCardUpdateCheckInterval(int value);
|
||||
void setLastCardUpdateCheck(QDate value);
|
||||
void setAlwaysEnableNewSets(bool value);
|
||||
void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate);
|
||||
void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion);
|
||||
void setUpdateReleaseChannelIndex(int value);
|
||||
|
|
|
|||
|
|
@ -664,6 +664,9 @@ private:
|
|||
{"Player/aRollDie", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Roll Dice..."),
|
||||
parseSequenceString("Ctrl+I"),
|
||||
ShortcutGroup::Gameplay)},
|
||||
{"Player/aFlipCoin", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Flip Coin"),
|
||||
parseSequenceString("Ctrl+Shift+I"),
|
||||
ShortcutGroup::Gameplay)},
|
||||
{"Player/aShuffle", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Library"),
|
||||
parseSequenceString("Ctrl+S"),
|
||||
ShortcutGroup::Gameplay)},
|
||||
|
|
|
|||
|
|
@ -396,6 +396,37 @@ void CardItem::playCard(bool faceDown)
|
|||
}
|
||||
}
|
||||
|
||||
QVariantList CardItem::parsePT(const QString &pt)
|
||||
{
|
||||
QVariantList ptList = QVariantList();
|
||||
if (!pt.isEmpty()) {
|
||||
int sep = pt.indexOf('/');
|
||||
if (sep == 0) {
|
||||
ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string
|
||||
} else {
|
||||
int start = 0;
|
||||
for (;;) {
|
||||
QString item = pt.mid(start, sep - start);
|
||||
if (item.isEmpty()) {
|
||||
ptList.append(QVariant(QString()));
|
||||
} else if (item[0] == '+') {
|
||||
ptList.append(QVariant(item.mid(1).toInt())); // add as int
|
||||
} else if (item[0] == '-') {
|
||||
ptList.append(QVariant(item.toInt())); // add as int
|
||||
} else {
|
||||
ptList.append(QVariant(item)); // add as qstring
|
||||
}
|
||||
if (sep == -1) {
|
||||
break;
|
||||
}
|
||||
start = sep + 1;
|
||||
sep = pt.indexOf('/', start);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ptList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
|
||||
* is nullptr.
|
||||
|
|
@ -460,9 +491,6 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
|||
|
||||
bool CardItem::animationEvent()
|
||||
{
|
||||
if (owner == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int rotation = ROTATION_DEGREES_PER_FRAME;
|
||||
bool animationIncomplete = true;
|
||||
if (!tapped)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,26 @@ public:
|
|||
void drawAttachArrow();
|
||||
void playCard(bool faceDown);
|
||||
|
||||
/**
|
||||
* @brief Parses a string representing a p/t in order to extract the values from it.
|
||||
*
|
||||
* If the string contains '/', the string will be split at the '/' and each side will be parsed separately,
|
||||
* which means the result list will have two elements.
|
||||
*
|
||||
* If '/' is not found, then the entire string is parsed together, which means the result list will
|
||||
* have a single element.
|
||||
*
|
||||
* If either side of the split is empty, there will also only be a single element in the result list.
|
||||
*
|
||||
* This function will attempt to parse each substring as an int first, handling plus and minus prefixes.
|
||||
* If successful, it will put the parsed value into the QVariant as an int.
|
||||
* If failed, it will just put the substring into the QVariant as a QString.
|
||||
*
|
||||
* @param pt The p/t string
|
||||
* @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString
|
||||
*/
|
||||
static QVariantList parsePT(const QString &pt);
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
|
|
|
|||
|
|
@ -429,13 +429,13 @@ void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, c
|
|||
if (!player)
|
||||
return;
|
||||
|
||||
player->clear();
|
||||
emit playerLeft(eventPlayerId);
|
||||
|
||||
emit logLeave(player, getLeaveReason(event.reason()));
|
||||
|
||||
game->getPlayerManager()->removePlayer(eventPlayerId);
|
||||
|
||||
player->clear();
|
||||
player->deleteLater();
|
||||
|
||||
// Rearrange all remaining zones so that attachment relationship updates take place
|
||||
|
|
|
|||
|
|
@ -58,6 +58,18 @@ void GameScene::retranslateUi()
|
|||
view->retranslateUi();
|
||||
}
|
||||
|
||||
QList<CardItem *> GameScene::selectedCards() const
|
||||
{
|
||||
QList<CardItem *> selectedCards;
|
||||
for (auto item : selectedItems()) {
|
||||
if (auto card = qgraphicsitem_cast<CardItem *>(item)) {
|
||||
selectedCards.append(card);
|
||||
}
|
||||
}
|
||||
|
||||
return selectedCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a player to the scene and stores their graphics item.
|
||||
* @param player Player to add.
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ public:
|
|||
/** Updates UI text for all zone views. */
|
||||
void retranslateUi();
|
||||
|
||||
/** Gets all selected CardItems */
|
||||
QList<CardItem *> selectedCards() const;
|
||||
|
||||
/**
|
||||
* @brief Adds a player to the scene and stores their graphics item.
|
||||
* @param player Player to add.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
|
|||
fromStr = tr(" from the top of their library");
|
||||
}
|
||||
}
|
||||
} else if (position >= zone->getCards().size() - 1) {
|
||||
} else if (position == zone->getCards().size()) {
|
||||
if (cardName.isEmpty()) {
|
||||
if (ownerChange) {
|
||||
cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
|
||||
|
|
|
|||
|
|
@ -75,17 +75,17 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
|
|||
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
auto *tempAddCounter = new QAction(this);
|
||||
tempAddCounter->setData(9 + i * 1000);
|
||||
auto *tempRemoveCounter = new QAction(this);
|
||||
tempRemoveCounter->setData(10 + i * 1000);
|
||||
auto *tempSetCounter = new QAction(this);
|
||||
tempSetCounter->setData(11 + i * 1000);
|
||||
aAddCounter.append(tempAddCounter);
|
||||
aRemoveCounter.append(tempRemoveCounter);
|
||||
aSetCounter.append(tempSetCounter);
|
||||
connect(tempAddCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger);
|
||||
connect(tempRemoveCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger);
|
||||
connect(tempSetCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger);
|
||||
connect(tempAddCounter, &QAction::triggered, playerActions,
|
||||
[playerActions, i] { playerActions->actAddCardCounter(i); });
|
||||
connect(tempRemoveCounter, &QAction::triggered, playerActions,
|
||||
[playerActions, i] { playerActions->actRemoveCardCounter(i); });
|
||||
connect(tempSetCounter, &QAction::triggered, playerActions,
|
||||
[playerActions, i] { playerActions->actSetCardCounter(i); });
|
||||
}
|
||||
|
||||
setShortcutsActive();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu)
|
|||
aRollDie = new QAction(this);
|
||||
connect(aRollDie, &QAction::triggered, playerActions, &PlayerActions::actRollDie);
|
||||
|
||||
aFlipCoin = new QAction(this);
|
||||
connect(aFlipCoin, &QAction::triggered, playerActions, &PlayerActions::actFlipCoin);
|
||||
|
||||
aCreateToken = new QAction(this);
|
||||
connect(aCreateToken, &QAction::triggered, playerActions, &PlayerActions::actCreateToken);
|
||||
|
||||
|
|
@ -38,6 +41,7 @@ UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu)
|
|||
playerMenu->addAction(aUntapAll);
|
||||
playerMenu->addSeparator();
|
||||
playerMenu->addAction(aRollDie);
|
||||
playerMenu->addAction(aFlipCoin);
|
||||
playerMenu->addSeparator();
|
||||
playerMenu->addAction(aCreateToken);
|
||||
playerMenu->addAction(aCreateAnotherToken);
|
||||
|
|
@ -50,6 +54,7 @@ UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu)
|
|||
aIncrementAllCardCounters = nullptr;
|
||||
aUntapAll = nullptr;
|
||||
aRollDie = nullptr;
|
||||
aFlipCoin = nullptr;
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
|
|
@ -89,6 +94,7 @@ void UtilityMenu::retranslateUi()
|
|||
aIncrementAllCardCounters->setText(tr("Increment all card counters"));
|
||||
aUntapAll->setText(tr("&Untap all permanents"));
|
||||
aRollDie->setText(tr("R&oll die..."));
|
||||
aFlipCoin->setText(tr("Flip coin"));
|
||||
aCreateToken->setText(tr("&Create token..."));
|
||||
aCreateAnotherToken->setText(tr("C&reate another token"));
|
||||
createPredefinedTokenMenu->setTitle(tr("Cr&eate predefined token"));
|
||||
|
|
@ -103,6 +109,7 @@ void UtilityMenu::setShortcutsActive()
|
|||
aIncrementAllCardCounters->setShortcuts(shortcuts.getShortcut("Player/aIncrementAllCardCounters"));
|
||||
aUntapAll->setShortcuts(shortcuts.getShortcut("Player/aUntapAll"));
|
||||
aRollDie->setShortcuts(shortcuts.getShortcut("Player/aRollDie"));
|
||||
aFlipCoin->setShortcuts(shortcuts.getShortcut("Player/aFlipCoin"));
|
||||
aCreateToken->setShortcuts(shortcuts.getShortcut("Player/aCreateToken"));
|
||||
aCreateAnotherToken->setShortcuts(shortcuts.getShortcut("Player/aCreateAnotherToken"));
|
||||
}
|
||||
|
|
@ -113,6 +120,7 @@ void UtilityMenu::setShortcutsInactive()
|
|||
if (player->getPlayerInfo()->getLocalOrJudge()) {
|
||||
aUntapAll->setShortcut(QKeySequence());
|
||||
aRollDie->setShortcut(QKeySequence());
|
||||
aFlipCoin->setShortcut(QKeySequence());
|
||||
aCreateToken->setShortcut(QKeySequence());
|
||||
aCreateAnotherToken->setShortcut(QKeySequence());
|
||||
aIncrementAllCardCounters->setShortcut(QKeySequence());
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ private:
|
|||
QMenu *createPredefinedTokenMenu;
|
||||
|
||||
QAction *aIncrementAllCardCounters;
|
||||
QAction *aUntapAll, *aRollDie;
|
||||
QAction *aUntapAll, *aRollDie, *aFlipCoin;
|
||||
QAction *aCreateToken, *aCreateAnotherToken;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -43,20 +43,6 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, A
|
|||
connect(this, &Player::activeChanged, graphicsItem, &PlayerGraphicsItem::onPlayerActiveChanged);
|
||||
|
||||
connect(this, &Player::openDeckEditor, game->getTab(), &TabGame::openDeckEditor);
|
||||
|
||||
forwardActionSignalsToEventHandler();
|
||||
}
|
||||
|
||||
// Event Handler is the controller i.e. everything hooks up to this to know about player state
|
||||
// Player should forward (private) signals to the event handler
|
||||
|
||||
void Player::forwardActionSignalsToEventHandler()
|
||||
{
|
||||
connect(playerActions, &PlayerActions::logSetTapped, playerEventHandler, &PlayerEventHandler::logSetTapped);
|
||||
connect(playerActions, &PlayerActions::logSetDoesntUntap, playerEventHandler,
|
||||
&PlayerEventHandler::logSetDoesntUntap);
|
||||
connect(playerActions, &PlayerActions::logSetAnnotation, playerEventHandler, &PlayerEventHandler::logSetAnnotation);
|
||||
connect(playerActions, &PlayerActions::logSetPT, playerEventHandler, &PlayerEventHandler::logSetPT);
|
||||
}
|
||||
|
||||
void Player::initializeZones()
|
||||
|
|
@ -323,19 +309,10 @@ void Player::clearCounters()
|
|||
|
||||
void Player::incrementAllCardCounters()
|
||||
{
|
||||
QList<CardItem *> cardsToUpdate;
|
||||
|
||||
auto selectedItems = getGameScene()->selectedItems();
|
||||
if (!selectedItems.isEmpty()) {
|
||||
// If cards are selected, only update those
|
||||
for (const auto &item : selectedItems) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
cardsToUpdate.append(card);
|
||||
}
|
||||
} else {
|
||||
auto cardsToUpdate = getGameScene()->selectedCards();
|
||||
if (cardsToUpdate.isEmpty()) {
|
||||
// If no cards selected, update all cards on table
|
||||
const CardList &tableCards = getTableZone()->getCards();
|
||||
cardsToUpdate = tableCards;
|
||||
cardsToUpdate = static_cast<QList<CardItem *>>(getTableZone()->getCards());
|
||||
}
|
||||
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ public slots:
|
|||
|
||||
public:
|
||||
Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent);
|
||||
void forwardActionSignalsToEventHandler();
|
||||
~Player() override;
|
||||
|
||||
void initializeZones();
|
||||
|
|
|
|||
|
|
@ -852,6 +852,14 @@ void PlayerActions::actRollDie()
|
|||
sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void PlayerActions::actFlipCoin()
|
||||
{
|
||||
Command_RollDie cmd;
|
||||
cmd.set_sides(2);
|
||||
cmd.set_count(1);
|
||||
sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void PlayerActions::actCreateToken()
|
||||
{
|
||||
DlgCreateToken dlg(player->getPlayerMenu()->getUtilityMenu()->getPredefinedTokens(), player->getGame()->getTab());
|
||||
|
|
@ -1149,61 +1157,6 @@ void PlayerActions::actSayMessage()
|
|||
sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void PlayerActions::setCardAttrHelper(const GameEventContext &context,
|
||||
CardItem *card,
|
||||
CardAttribute attribute,
|
||||
const QString &avalue,
|
||||
bool allCards,
|
||||
EventProcessingOptions options)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool moveCardContext = context.HasExtension(Context_MoveCard::ext);
|
||||
switch (attribute) {
|
||||
case AttrTapped: {
|
||||
bool tapped = avalue == "1";
|
||||
if (!(!tapped && card->getDoesntUntap() && allCards)) {
|
||||
if (!allCards) {
|
||||
emit logSetTapped(player, card, tapped);
|
||||
}
|
||||
bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext;
|
||||
card->setTapped(tapped, canAnimate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AttrAttacking: {
|
||||
card->setAttacking(avalue == "1");
|
||||
break;
|
||||
}
|
||||
case AttrFaceDown: {
|
||||
card->setFaceDown(avalue == "1");
|
||||
break;
|
||||
}
|
||||
case AttrColor: {
|
||||
card->setColor(avalue);
|
||||
break;
|
||||
}
|
||||
case AttrAnnotation: {
|
||||
emit logSetAnnotation(player, card, avalue);
|
||||
card->setAnnotation(avalue);
|
||||
break;
|
||||
}
|
||||
case AttrDoesntUntap: {
|
||||
bool value = (avalue == "1");
|
||||
emit logSetDoesntUntap(player, card, value);
|
||||
card->setDoesntUntap(value);
|
||||
break;
|
||||
}
|
||||
case AttrPT: {
|
||||
emit logSetPT(player, card, avalue);
|
||||
card->setPT(avalue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerActions::actMoveCardXCardsFromTop()
|
||||
{
|
||||
int deckSize = player->getDeckZone()->getCards().size() + 1; // add the card to move to the deck
|
||||
|
|
@ -1220,16 +1173,11 @@ void PlayerActions::actMoveCardXCardsFromTop()
|
|||
|
||||
defaultNumberTopCardsToPlaceBelow = number;
|
||||
|
||||
QList<QGraphicsItem *> sel = player->getGameScene()->selectedItems();
|
||||
if (sel.isEmpty()) {
|
||||
QList<CardItem *> cardList = player->getGameScene()->selectedCards();
|
||||
if (cardList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<CardItem *> cardList;
|
||||
while (!sel.isEmpty()) {
|
||||
cardList.append(qgraphicsitem_cast<CardItem *>(sel.takeFirst()));
|
||||
}
|
||||
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
ListOfCardsToMove idList;
|
||||
for (const auto &i : cardList) {
|
||||
|
|
@ -1261,10 +1209,9 @@ void PlayerActions::actIncPT(int deltaP, int deltaT)
|
|||
int playerid = player->getPlayerInfo()->getId();
|
||||
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
for (const auto &item : player->getGameScene()->selectedItems()) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
for (auto card : player->getGameScene()->selectedCards()) {
|
||||
QString pt = card->getPT();
|
||||
const auto ptList = parsePT(pt);
|
||||
const auto ptList = CardItem::parsePT(pt);
|
||||
QString newpt;
|
||||
if (ptList.isEmpty()) {
|
||||
newpt = QString::number(deltaP) + (deltaT ? "/" + QString::number(deltaT) : "");
|
||||
|
|
@ -1294,8 +1241,7 @@ void PlayerActions::actResetPT()
|
|||
{
|
||||
int playerid = player->getPlayerInfo()->getId();
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
for (const auto &item : player->getGameScene()->selectedItems()) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
for (auto card : player->getGameScene()->selectedCards()) {
|
||||
QString ptString;
|
||||
if (!card->getFaceDown()) { // leave the pt empty if the card is face down
|
||||
ExactCard ec = card->getCard();
|
||||
|
|
@ -1324,45 +1270,13 @@ void PlayerActions::actResetPT()
|
|||
}
|
||||
}
|
||||
|
||||
QVariantList PlayerActions::parsePT(const QString &pt)
|
||||
{
|
||||
QVariantList ptList = QVariantList();
|
||||
if (!pt.isEmpty()) {
|
||||
int sep = pt.indexOf('/');
|
||||
if (sep == 0) {
|
||||
ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string
|
||||
} else {
|
||||
int start = 0;
|
||||
for (;;) {
|
||||
QString item = pt.mid(start, sep - start);
|
||||
if (item.isEmpty()) {
|
||||
ptList.append(QVariant(QString()));
|
||||
} else if (item[0] == '+') {
|
||||
ptList.append(QVariant(item.mid(1).toInt())); // add as int
|
||||
} else if (item[0] == '-') {
|
||||
ptList.append(QVariant(item.toInt())); // add as int
|
||||
} else {
|
||||
ptList.append(QVariant(item)); // add as qstring
|
||||
}
|
||||
if (sep == -1) {
|
||||
break;
|
||||
}
|
||||
start = sep + 1;
|
||||
sep = pt.indexOf('/', start);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ptList;
|
||||
}
|
||||
|
||||
void PlayerActions::actSetPT()
|
||||
{
|
||||
QString oldPT;
|
||||
int playerid = player->getPlayerInfo()->getId();
|
||||
|
||||
auto sel = player->getGameScene()->selectedItems();
|
||||
for (const auto &item : sel) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
auto cards = player->getGameScene()->selectedCards();
|
||||
for (auto card : cards) {
|
||||
if (!card->getPT().isEmpty()) {
|
||||
oldPT = card->getPT();
|
||||
}
|
||||
|
|
@ -1376,16 +1290,15 @@ void PlayerActions::actSetPT()
|
|||
return;
|
||||
}
|
||||
|
||||
const auto ptList = parsePT(pt);
|
||||
const auto ptList = CardItem::parsePT(pt);
|
||||
bool empty = ptList.isEmpty();
|
||||
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
for (const auto &item : sel) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
for (auto card : cards) {
|
||||
auto *cmd = new Command_SetCardAttr;
|
||||
QString newpt = QString();
|
||||
if (!empty) {
|
||||
const auto oldpt = parsePT(card->getPT());
|
||||
const auto oldpt = CardItem::parsePT(card->getPT());
|
||||
int ptIter = 0;
|
||||
for (const auto &_item : ptList) {
|
||||
if (_item.typeId() == QMetaType::Type::Int) {
|
||||
|
|
@ -1474,9 +1387,8 @@ void AnnotationDialog::keyPressEvent(QKeyEvent *event)
|
|||
void PlayerActions::actSetAnnotation()
|
||||
{
|
||||
QString oldAnnotation;
|
||||
auto sel = player->getGameScene()->selectedItems();
|
||||
for (const auto &item : sel) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
auto cards = player->getGameScene()->selectedCards();
|
||||
for (auto card : cards) {
|
||||
if (!card->getAnnotation().isEmpty()) {
|
||||
oldAnnotation = card->getAnnotation();
|
||||
}
|
||||
|
|
@ -1496,8 +1408,7 @@ void PlayerActions::actSetAnnotation()
|
|||
QString annotation = dialog->textValue().left(MAX_NAME_LENGTH);
|
||||
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
for (const auto &item : sel) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
for (auto card : cards) {
|
||||
auto *cmd = new Command_SetCardAttr;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
cmd->set_card_id(card->getId());
|
||||
|
|
@ -1521,9 +1432,7 @@ void PlayerActions::actAttach()
|
|||
void PlayerActions::actUnattach()
|
||||
{
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
for (QGraphicsItem *item : player->getGameScene()->selectedItems()) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
|
||||
for (auto card : player->getGameScene()->selectedCards()) {
|
||||
if (!card->getAttachedTo()) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1536,80 +1445,73 @@ void PlayerActions::actUnattach()
|
|||
sendGameCommand(prepareGameCommand(commandList));
|
||||
}
|
||||
|
||||
void PlayerActions::actCardCounterTrigger()
|
||||
void PlayerActions::actAddCardCounter(int counterId)
|
||||
{
|
||||
offsetCardCounter(counterId, 1);
|
||||
}
|
||||
|
||||
void PlayerActions::actRemoveCardCounter(int counterId)
|
||||
{
|
||||
offsetCardCounter(counterId, -1);
|
||||
}
|
||||
|
||||
void PlayerActions::offsetCardCounter(int counterId, int offset)
|
||||
{
|
||||
auto *action = static_cast<QAction *>(sender());
|
||||
int counterId = action->data().toInt() / 1000;
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
switch (action->data().toInt() % 1000) {
|
||||
case 9: { // increment counter
|
||||
for (const auto &item : player->getGameScene()->selectedItems()) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
if (card->getCounters().value(counterId, 0) < MAX_COUNTERS_ON_CARD) {
|
||||
auto *cmd = new Command_SetCardCounter;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
cmd->set_card_id(card->getId());
|
||||
cmd->set_counter_id(counterId);
|
||||
cmd->set_counter_value(card->getCounters().value(counterId, 0) + 1);
|
||||
commandList.append(cmd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
for (auto card : player->getGameScene()->selectedCards()) {
|
||||
int oldValue = card->getCounters().value(counterId, 0);
|
||||
int newValue = oldValue + offset;
|
||||
|
||||
if (newValue >= 0 && newValue <= MAX_COUNTERS_ON_CARD) {
|
||||
auto *cmd = new Command_SetCardCounter;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
cmd->set_card_id(card->getId());
|
||||
cmd->set_counter_id(counterId);
|
||||
cmd->set_counter_value(newValue);
|
||||
commandList.append(cmd);
|
||||
}
|
||||
case 10: { // decrement counter
|
||||
for (const auto &item : player->getGameScene()->selectedItems()) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
if (card->getCounters().value(counterId, 0)) {
|
||||
auto *cmd = new Command_SetCardCounter;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
cmd->set_card_id(card->getId());
|
||||
cmd->set_counter_id(counterId);
|
||||
cmd->set_counter_value(card->getCounters().value(counterId, 0) - 1);
|
||||
commandList.append(cmd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 11: { // set counter with dialog
|
||||
player->setDialogSemaphore(true);
|
||||
|
||||
// If a single card is selected, we show the old value in the dialog. Otherwise, we show "x"
|
||||
QList<QGraphicsItem *> sel = player->getGameScene()->selectedItems();
|
||||
QString oldValueForDlg = "x";
|
||||
if (sel.size() == 1) {
|
||||
auto *card = dynamic_cast<CardItem *>(sel.first());
|
||||
oldValueForDlg = QString::number(card->getCounters().value(counterId, 0));
|
||||
}
|
||||
|
||||
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
|
||||
QString counterName = cardCounterSettings.displayName(counterId);
|
||||
|
||||
AbstractCounterDialog dialog(counterName, oldValueForDlg, player->getGame()->getTab());
|
||||
int ok = dialog.exec();
|
||||
|
||||
player->setDialogSemaphore(false);
|
||||
if (player->clearCardsToDelete() || !ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &item : sel) {
|
||||
auto *card = dynamic_cast<CardItem *>(item);
|
||||
|
||||
int oldValue = card->getCounters().value(counterId, 0);
|
||||
Expression exp(oldValue);
|
||||
int number = static_cast<int>(exp.parse(dialog.textValue()));
|
||||
|
||||
auto *cmd = new Command_SetCardCounter;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
cmd->set_card_id(card->getId());
|
||||
cmd->set_counter_id(counterId);
|
||||
cmd->set_counter_value(number);
|
||||
commandList.append(cmd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
|
||||
sendGameCommand(prepareGameCommand(commandList));
|
||||
}
|
||||
|
||||
void PlayerActions::actSetCardCounter(int counterId)
|
||||
{
|
||||
player->setDialogSemaphore(true);
|
||||
|
||||
// If a single card is selected, we show the old value in the dialog. Otherwise, we show "x"
|
||||
QList<CardItem *> sel = player->getGameScene()->selectedCards();
|
||||
QString oldValueForDlg = "x";
|
||||
if (sel.size() == 1) {
|
||||
auto *card = sel.first();
|
||||
oldValueForDlg = QString::number(card->getCounters().value(counterId, 0));
|
||||
}
|
||||
|
||||
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
|
||||
QString counterName = cardCounterSettings.displayName(counterId);
|
||||
|
||||
AbstractCounterDialog dialog(counterName, oldValueForDlg, player->getGame()->getTab());
|
||||
int ok = dialog.exec();
|
||||
|
||||
player->setDialogSemaphore(false);
|
||||
if (player->clearCardsToDelete() || !ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
for (auto card : sel) {
|
||||
int oldValue = card->getCounters().value(counterId, 0);
|
||||
Expression exp(oldValue);
|
||||
int number = static_cast<int>(exp.parse(dialog.textValue()));
|
||||
|
||||
auto *cmd = new Command_SetCardCounter;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
cmd->set_card_id(card->getId());
|
||||
cmd->set_counter_id(counterId);
|
||||
cmd->set_counter_value(number);
|
||||
commandList.append(cmd);
|
||||
}
|
||||
|
||||
sendGameCommand(prepareGameCommand(commandList));
|
||||
}
|
||||
|
||||
|
|
@ -1627,11 +1529,7 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
|
|||
|
||||
void PlayerActions::playSelectedCards(const bool faceDown)
|
||||
{
|
||||
QList<CardItem *> selectedCards;
|
||||
for (const auto &item : player->getGameScene()->selectedItems()) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
selectedCards.append(card);
|
||||
}
|
||||
QList<CardItem *> selectedCards = player->getGameScene()->selectedCards();
|
||||
|
||||
// CardIds will get shuffled downwards when cards leave the deck.
|
||||
// We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play
|
||||
|
|
@ -1658,7 +1556,7 @@ void PlayerActions::actPlayFacedown()
|
|||
|
||||
void PlayerActions::actHide()
|
||||
{
|
||||
for (const auto &item : player->getGameScene()->selectedItems()) {
|
||||
for (const auto &item : player->getGameScene()->selectedCards()) {
|
||||
auto *card = static_cast<CardItem *>(item);
|
||||
if (card && isUnwritableRevealZone(card->getZone())) {
|
||||
card->getZone()->removeCard(card);
|
||||
|
|
@ -1675,9 +1573,7 @@ void PlayerActions::actReveal(QAction *action)
|
|||
cmd.set_player_id(otherPlayerId);
|
||||
}
|
||||
|
||||
QList<QGraphicsItem *> sel = player->getGameScene()->selectedItems();
|
||||
while (!sel.isEmpty()) {
|
||||
const auto *card = qgraphicsitem_cast<CardItem *>(sel.takeFirst());
|
||||
for (auto card : player->getGameScene()->selectedCards()) {
|
||||
if (!cmd.has_zone_name()) {
|
||||
cmd.set_zone_name(card->getZone()->getName().toStdString());
|
||||
}
|
||||
|
|
@ -1762,11 +1658,7 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId)
|
|||
void PlayerActions::cardMenuAction()
|
||||
{
|
||||
auto *a = dynamic_cast<QAction *>(sender());
|
||||
QList<QGraphicsItem *> sel = player->getGameScene()->selectedItems();
|
||||
QList<CardItem *> cardList;
|
||||
while (!sel.isEmpty()) {
|
||||
cardList.append(qgraphicsitem_cast<CardItem *>(sel.takeFirst()));
|
||||
}
|
||||
QList<CardItem *> cardList = player->getGameScene()->selectedCards();
|
||||
|
||||
QList<const ::google::protobuf::Message *> commandList;
|
||||
if (a->data().toInt() <= (int)cmClone) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
#include <QObject>
|
||||
#include <libcockatrice/card/relation/card_relation_type.h>
|
||||
#include <libcockatrice/filters/filter_string.h>
|
||||
#include <libcockatrice/protocol/pb/card_attributes.pb.h>
|
||||
|
||||
namespace google
|
||||
{
|
||||
|
|
@ -35,12 +34,6 @@ class PlayerActions : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void logSetTapped(Player *player, CardItem *card, bool tapped);
|
||||
void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation);
|
||||
void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap);
|
||||
void logSetPT(Player *player, CardItem *card, QString newPT);
|
||||
|
||||
public:
|
||||
enum CardsToReveal
|
||||
{
|
||||
|
|
@ -55,13 +48,6 @@ public:
|
|||
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
|
||||
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
|
||||
|
||||
void setCardAttrHelper(const GameEventContext &context,
|
||||
CardItem *card,
|
||||
CardAttribute attribute,
|
||||
const QString &avalue,
|
||||
bool allCards,
|
||||
EventProcessingOptions options);
|
||||
|
||||
void moveOneCardUntil(CardItem *card);
|
||||
void stopMoveTopCardsUntil();
|
||||
|
||||
|
|
@ -77,6 +63,7 @@ public slots:
|
|||
|
||||
void actUntapAll();
|
||||
void actRollDie();
|
||||
void actFlipCoin();
|
||||
void actCreateToken();
|
||||
void actCreateAnotherToken();
|
||||
void actShuffle();
|
||||
|
|
@ -141,7 +128,9 @@ public slots:
|
|||
void actCreateAllRelatedCards();
|
||||
|
||||
void actMoveCardXCardsFromTop();
|
||||
void actCardCounterTrigger();
|
||||
void actRemoveCardCounter(int counterId);
|
||||
void actAddCardCounter(int counterId);
|
||||
void actSetCardCounter(int counterId);
|
||||
void actAttach();
|
||||
void actUnattach();
|
||||
void actDrawArrow();
|
||||
|
|
@ -197,7 +186,7 @@ private:
|
|||
void cmdSetTopCard(Command_MoveCard &cmd);
|
||||
void cmdSetBottomCard(Command_MoveCard &cmd);
|
||||
|
||||
QVariantList parsePT(const QString &pt);
|
||||
void offsetCardCounter(int counterId, int offset);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PLAYER_ACTIONS_H
|
||||
|
|
|
|||
|
|
@ -149,8 +149,8 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event,
|
|||
if (!event.has_card_id()) {
|
||||
const CardList &cards = zone->getCards();
|
||||
for (int i = 0; i < cards.size(); ++i) {
|
||||
player->getPlayerActions()->setCardAttrHelper(context, cards.at(i), event.attribute(),
|
||||
QString::fromStdString(event.attr_value()), true, options);
|
||||
setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), true,
|
||||
options);
|
||||
}
|
||||
if (event.attribute() == AttrTapped) {
|
||||
emit logSetTapped(player, nullptr, event.attr_value() == "1");
|
||||
|
|
@ -161,8 +161,62 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event,
|
|||
qWarning() << "PlayerEventHandler::eventSetCardAttr: card id=" << event.card_id() << "not found";
|
||||
return;
|
||||
}
|
||||
player->getPlayerActions()->setCardAttrHelper(context, card, event.attribute(),
|
||||
QString::fromStdString(event.attr_value()), false, options);
|
||||
setCardAttrHelper(context, card, event.attribute(), QString::fromStdString(event.attr_value()), false, options);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerEventHandler::setCardAttrHelper(const GameEventContext &context,
|
||||
CardItem *card,
|
||||
CardAttribute attribute,
|
||||
const QString &avalue,
|
||||
bool allCards,
|
||||
EventProcessingOptions options)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool moveCardContext = context.HasExtension(Context_MoveCard::ext);
|
||||
switch (attribute) {
|
||||
case AttrTapped: {
|
||||
bool tapped = avalue == "1";
|
||||
if (!(!tapped && card->getDoesntUntap() && allCards)) {
|
||||
if (!allCards) {
|
||||
emit logSetTapped(player, card, tapped);
|
||||
}
|
||||
bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext;
|
||||
card->setTapped(tapped, canAnimate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AttrAttacking: {
|
||||
card->setAttacking(avalue == "1");
|
||||
break;
|
||||
}
|
||||
case AttrFaceDown: {
|
||||
card->setFaceDown(avalue == "1");
|
||||
break;
|
||||
}
|
||||
case AttrColor: {
|
||||
card->setColor(avalue);
|
||||
break;
|
||||
}
|
||||
case AttrAnnotation: {
|
||||
emit logSetAnnotation(player, card, avalue);
|
||||
card->setAnnotation(avalue);
|
||||
break;
|
||||
}
|
||||
case AttrDoesntUntap: {
|
||||
bool value = (avalue == "1");
|
||||
emit logSetDoesntUntap(player, card, value);
|
||||
card->setDoesntUntap(value);
|
||||
break;
|
||||
}
|
||||
case AttrPT: {
|
||||
emit logSetPT(player, card, avalue);
|
||||
card->setPT(avalue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "event_processing_options.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <libcockatrice/protocol/pb/card_attributes.pb.h>
|
||||
#include <libcockatrice/protocol/pb/game_event.pb.h>
|
||||
#include <libcockatrice/protocol/pb/game_event_context.pb.h>
|
||||
|
||||
|
|
@ -110,6 +111,13 @@ public:
|
|||
|
||||
private:
|
||||
Player *player;
|
||||
|
||||
void setCardAttrHelper(const GameEventContext &context,
|
||||
CardItem *card,
|
||||
CardAttribute attribute,
|
||||
const QString &avalue,
|
||||
bool allCards,
|
||||
EventProcessingOptions options);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PLAYER_EVENT_HANDLER_H
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <QStyle>
|
||||
#include <QStyleFactory>
|
||||
#include <QStyleHints>
|
||||
#include <QWidget>
|
||||
#include <Qt>
|
||||
|
||||
#define NONE_THEME_NAME "Default"
|
||||
|
|
@ -112,6 +113,39 @@ void ThemeManager::ensureThemeDirectoryExists()
|
|||
}
|
||||
}
|
||||
|
||||
bool ThemeManager::isDarkMode()
|
||||
{
|
||||
auto themeName = SettingsCache::instance().getThemeName();
|
||||
// Explicit Dark Mode
|
||||
if (themeName == FUSION_THEME_NAME_LIGHT || themeName.endsWith("(Light)")) {
|
||||
return false;
|
||||
}
|
||||
// Explicit Light Mode
|
||||
if (themeName == FUSION_THEME_NAME_DARK || themeName.endsWith("(Dark)")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Auto detection on compatible Qt versions
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark &&
|
||||
(themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME || themeName.endsWith("(System Default)"))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// Default to light mode
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ThemeManager::isBuiltInTheme()
|
||||
{
|
||||
const auto themeName = SettingsCache::instance().getThemeName();
|
||||
|
||||
return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME || themeName == FUSION_THEME_NAME_LIGHT ||
|
||||
themeName == FUSION_THEME_NAME_DARK;
|
||||
}
|
||||
|
||||
QStringMap &ThemeManager::getAvailableThemes()
|
||||
{
|
||||
QDir dir;
|
||||
|
|
@ -335,26 +369,53 @@ void ThemeManager::themeChangedSlot()
|
|||
qApp->setStyleSheet("");
|
||||
}
|
||||
|
||||
QStyle *newStyle = nullptr;
|
||||
QPalette newPalette;
|
||||
|
||||
if (themeName == FUSION_THEME_NAME) {
|
||||
QStyle *fusionStyle = QStyleFactory::create("Fusion");
|
||||
qApp->setStyle(fusionStyle);
|
||||
newStyle = QStyleFactory::create("Fusion");
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
// Start from Fusion's own palette so dark mode is handled correctly,
|
||||
// then apply any tweaks on top of it.
|
||||
QPalette palette = fusionStyle->standardPalette();
|
||||
newPalette = newStyle->standardPalette();
|
||||
if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) {
|
||||
palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
|
||||
newPalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
|
||||
}
|
||||
qApp->setPalette(palette);
|
||||
#else
|
||||
newPalette = qApp->palette();
|
||||
#endif
|
||||
} else if (themeName == FUSION_THEME_NAME_LIGHT) {
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
qApp->setPalette(createLightGreenFusionPalette());
|
||||
newStyle = QStyleFactory::create("Fusion");
|
||||
newPalette = createLightGreenFusionPalette();
|
||||
} else if (themeName == FUSION_THEME_NAME_DARK) {
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
qApp->setPalette(createDarkGreenFusionPalette());
|
||||
newStyle = QStyleFactory::create("Fusion");
|
||||
newPalette = createDarkGreenFusionPalette();
|
||||
} else {
|
||||
qApp->setStyle(QStyleFactory::create(defaultStyleName)); // setting the style also sets the palette
|
||||
newStyle = QStyleFactory::create(defaultStyleName);
|
||||
// Use the style's default palette.
|
||||
newPalette = newStyle->standardPalette();
|
||||
}
|
||||
|
||||
// Apply palette FIRST.
|
||||
qApp->setPalette(newPalette);
|
||||
// Then apply style.
|
||||
qApp->setStyle(newStyle);
|
||||
|
||||
// Force every widget to re-polish and repaint immediately rather than
|
||||
// waiting for natural expose events, which produces a patchwork of old
|
||||
// and new colours during a live preview.
|
||||
// Note: we do NOT call widget->setPalette(base) here — qApp->setPalette()
|
||||
// already propagates to all widgets that haven't explicitly overridden their
|
||||
// palette (WA_SetPalette not set). Calling it unconditionally would clobber
|
||||
// intentional per-widget palette customisations across the whole app.
|
||||
for (QWidget *widget : qApp->allWidgets()) {
|
||||
if (widget->isVisible()) {
|
||||
newStyle->unpolish(widget);
|
||||
newStyle->polish(widget);
|
||||
|
||||
widget->update();
|
||||
}
|
||||
}
|
||||
|
||||
if (dirPath.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ protected:
|
|||
QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush);
|
||||
|
||||
public:
|
||||
bool isBuiltInTheme();
|
||||
bool isDarkMode();
|
||||
QStringMap &getAvailableThemes();
|
||||
|
||||
QBrush &getBgBrush(Role zone);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard)
|
|||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
||||
.arg(tr("Name:"), card->getName().toHtmlEscaped());
|
||||
|
||||
if (exactCard.getPrinting() != PrintingInfo()) {
|
||||
if (!exactCard.getPrinting().isEmpty()) {
|
||||
QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped();
|
||||
QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped();
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,14 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
removeButton = new QPushButton(this);
|
||||
saveButton = new QPushButton(this);
|
||||
loadButton = new QPushButton(this);
|
||||
includeSideboardCheckBox = new QCheckBox(this);
|
||||
includeSideboardCheckBox->setChecked(false);
|
||||
|
||||
controlLayout->addWidget(addButton);
|
||||
controlLayout->addWidget(removeButton);
|
||||
controlLayout->addWidget(saveButton);
|
||||
controlLayout->addWidget(loadButton);
|
||||
controlLayout->addWidget(includeSideboardCheckBox);
|
||||
|
||||
layout->addWidget(controlContainer);
|
||||
|
||||
|
|
@ -40,6 +44,7 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected);
|
||||
connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout);
|
||||
connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout);
|
||||
connect(includeSideboardCheckBox, &QCheckBox::clicked, this, &DeckAnalyticsWidget::includeSideboardChanged);
|
||||
|
||||
// Scroll area and container
|
||||
scrollArea = new QScrollArea(this);
|
||||
|
|
@ -66,6 +71,13 @@ void DeckAnalyticsWidget::retranslateUi()
|
|||
removeButton->setText(tr("Remove Panel"));
|
||||
saveButton->setText(tr("Save Layout"));
|
||||
loadButton->setText(tr("Load Layout"));
|
||||
includeSideboardCheckBox->setText(tr("Include Sideboard"));
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::includeSideboardChanged(bool checked)
|
||||
{
|
||||
statsAnalyzer->getConfig().includeSideboard = checked;
|
||||
updateDisplays();
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::updateDisplays()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QJsonObject>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
|
@ -29,6 +30,7 @@ public slots:
|
|||
public:
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
void retranslateUi();
|
||||
void includeSideboardChanged(bool checked);
|
||||
|
||||
private slots:
|
||||
void onAddPanel();
|
||||
|
|
@ -57,6 +59,8 @@ private:
|
|||
QPushButton *saveButton;
|
||||
QPushButton *loadButton;
|
||||
|
||||
QCheckBox *includeSideboardCheckBox;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
QWidget *panelContainer;
|
||||
QVBoxLayout *panelLayout;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,13 @@ void DeckListStatisticsAnalyzer::analyze()
|
|||
{
|
||||
clearData();
|
||||
|
||||
QList<const DecklistCardNode *> nodes = model->getCardNodes();
|
||||
QList<const DecklistCardNode *> nodes;
|
||||
|
||||
if (config.includeSideboard) {
|
||||
nodes = model->getCardNodes();
|
||||
} else {
|
||||
nodes = model->getCardNodesForZone(DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
for (auto node : nodes) {
|
||||
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ struct DeckListStatisticsAnalyzerConfig
|
|||
bool computeCategories = true;
|
||||
bool computeCurveBreakdowns = true;
|
||||
bool computeProbabilities = true;
|
||||
bool includeSideboard = false;
|
||||
};
|
||||
|
||||
class DeckListStatisticsAnalyzer : public QObject
|
||||
|
|
@ -133,6 +134,11 @@ public:
|
|||
return model;
|
||||
}
|
||||
|
||||
DeckListStatisticsAnalyzerConfig &getConfig()
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
signals:
|
||||
void statsUpdated();
|
||||
|
||||
|
|
|
|||
|
|
@ -22,41 +22,43 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
|
|||
{QTime(1, 0), tr("1 hour")},
|
||||
{QTime(2, 0), tr("2 hours")}})
|
||||
{
|
||||
const GameFilterConfigs &filters = gamesProxyModel->getFilters();
|
||||
|
||||
hideBuddiesOnlyGames = new QCheckBox(tr("Hide 'buddies only' games"));
|
||||
hideBuddiesOnlyGames->setChecked(gamesProxyModel->getHideBuddiesOnlyGames());
|
||||
hideBuddiesOnlyGames->setChecked(filters.hideBuddiesOnlyGames);
|
||||
|
||||
hideFullGames = new QCheckBox(tr("Hide full games"));
|
||||
hideFullGames->setChecked(gamesProxyModel->getHideFullGames());
|
||||
hideFullGames->setChecked(filters.hideFullGames);
|
||||
|
||||
hideGamesThatStarted = new QCheckBox(tr("Hide games that have started"));
|
||||
hideGamesThatStarted->setChecked(gamesProxyModel->getHideGamesThatStarted());
|
||||
hideGamesThatStarted->setChecked(filters.hideGamesThatStarted);
|
||||
|
||||
hidePasswordProtectedGames = new QCheckBox(tr("Hide password protected games"));
|
||||
hidePasswordProtectedGames->setChecked(gamesProxyModel->getHidePasswordProtectedGames());
|
||||
hidePasswordProtectedGames->setChecked(filters.hidePasswordProtectedGames);
|
||||
|
||||
hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games"));
|
||||
hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames());
|
||||
hideIgnoredUserGames->setChecked(filters.hideIgnoredUserGames);
|
||||
|
||||
hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddies"));
|
||||
hideNotBuddyCreatedGames->setChecked(gamesProxyModel->getHideNotBuddyCreatedGames());
|
||||
hideNotBuddyCreatedGames->setChecked(filters.hideNotBuddyCreatedGames);
|
||||
|
||||
hideOpenDecklistGames = new QCheckBox(tr("Hide games with forced open decklists"));
|
||||
hideOpenDecklistGames->setChecked(gamesProxyModel->getHideOpenDecklistGames());
|
||||
hideOpenDecklistGames->setChecked(filters.hideOpenDecklistGames);
|
||||
|
||||
maxGameAgeComboBox = new QComboBox();
|
||||
maxGameAgeComboBox->setEditable(false);
|
||||
maxGameAgeComboBox->addItems(gameAgeMap.values());
|
||||
QTime gameAge = gamesProxyModel->getMaxGameAge();
|
||||
QTime gameAge = filters.maxGameAge;
|
||||
maxGameAgeComboBox->setCurrentIndex(gameAgeMap.keys().indexOf(gameAge)); // index is -1 if unknown
|
||||
auto *maxGameAgeLabel = new QLabel(tr("&Newer than:"));
|
||||
maxGameAgeLabel->setBuddy(maxGameAgeComboBox);
|
||||
|
||||
gameNameFilterEdit = new QLineEdit;
|
||||
gameNameFilterEdit->setText(gamesProxyModel->getGameNameFilter());
|
||||
gameNameFilterEdit->setText(filters.gameNameFilter);
|
||||
auto *gameNameFilterLabel = new QLabel(tr("Game &description:"));
|
||||
gameNameFilterLabel->setBuddy(gameNameFilterEdit);
|
||||
creatorNameFilterEdit = new QLineEdit;
|
||||
creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilters().join(", "));
|
||||
creatorNameFilterEdit->setText(filters.creatorNameFilters.join(", "));
|
||||
auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:"));
|
||||
creatorNameFilterLabel->setBuddy(creatorNameFilterEdit);
|
||||
|
||||
|
|
@ -76,7 +78,7 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
|
|||
gameTypesIterator.next();
|
||||
|
||||
auto *temp = new QCheckBox(gameTypesIterator.value());
|
||||
temp->setChecked(gamesProxyModel->getGameTypeFilter().contains(gameTypesIterator.key()));
|
||||
temp->setChecked(filters.gameTypeFilter.contains(gameTypesIterator.key()));
|
||||
|
||||
gameTypeFilterCheckBoxes.insert(gameTypesIterator.key(), temp);
|
||||
gameTypeFilterLayout->addWidget(temp);
|
||||
|
|
@ -92,14 +94,14 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
|
|||
maxPlayersFilterMinSpinBox = new QSpinBox;
|
||||
maxPlayersFilterMinSpinBox->setMinimum(0);
|
||||
maxPlayersFilterMinSpinBox->setMaximum(99);
|
||||
maxPlayersFilterMinSpinBox->setValue(gamesProxyModel->getMaxPlayersFilterMin());
|
||||
maxPlayersFilterMinSpinBox->setValue(filters.maxPlayersFilterMin);
|
||||
maxPlayersFilterMinLabel->setBuddy(maxPlayersFilterMinSpinBox);
|
||||
|
||||
auto *maxPlayersFilterMaxLabel = new QLabel(tr("at &most:"));
|
||||
maxPlayersFilterMaxSpinBox = new QSpinBox;
|
||||
maxPlayersFilterMaxSpinBox->setMinimum(0);
|
||||
maxPlayersFilterMaxSpinBox->setMaximum(99);
|
||||
maxPlayersFilterMaxSpinBox->setValue(gamesProxyModel->getMaxPlayersFilterMax());
|
||||
maxPlayersFilterMaxSpinBox->setValue(filters.maxPlayersFilterMax);
|
||||
maxPlayersFilterMaxLabel->setBuddy(maxPlayersFilterMaxSpinBox);
|
||||
|
||||
auto *maxPlayersFilterLayout = new QGridLayout;
|
||||
|
|
@ -124,17 +126,17 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
|
|||
restrictionsGroupBox->setLayout(restrictionsLayout);
|
||||
|
||||
showOnlyIfSpectatorsCanWatch = new QCheckBox(tr("Show games only if &spectators can watch"));
|
||||
showOnlyIfSpectatorsCanWatch->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanWatch());
|
||||
showOnlyIfSpectatorsCanWatch->setChecked(filters.showOnlyIfSpectatorsCanWatch);
|
||||
connect(showOnlyIfSpectatorsCanWatch, &QCheckBox::toggled, this,
|
||||
&DlgFilterGames::toggleSpectatorCheckboxEnabledness);
|
||||
|
||||
showSpectatorPasswordProtected = new QCheckBox(tr("Show spectator password p&rotected games"));
|
||||
showSpectatorPasswordProtected->setChecked(gamesProxyModel->getShowSpectatorPasswordProtected());
|
||||
showSpectatorPasswordProtected->setChecked(filters.showSpectatorPasswordProtected);
|
||||
showOnlyIfSpectatorsCanChat = new QCheckBox(tr("Show only if spectators can ch&at"));
|
||||
showOnlyIfSpectatorsCanChat->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanChat());
|
||||
showOnlyIfSpectatorsCanChat->setChecked(filters.showOnlyIfSpectatorsCanChat);
|
||||
showOnlyIfSpectatorsCanSeeHands = new QCheckBox(tr("Show only if spectators can see &hands"));
|
||||
showOnlyIfSpectatorsCanSeeHands->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanSeeHands());
|
||||
toggleSpectatorCheckboxEnabledness(getShowOnlyIfSpectatorsCanWatch());
|
||||
showOnlyIfSpectatorsCanSeeHands->setChecked(filters.showOnlyIfSpectatorsCanSeeHands);
|
||||
toggleSpectatorCheckboxEnabledness(filters.showOnlyIfSpectatorsCanWatch);
|
||||
|
||||
auto *spectatorsLayout = new QGridLayout;
|
||||
spectatorsLayout->addWidget(showOnlyIfSpectatorsCanWatch, 0, 0);
|
||||
|
|
@ -180,6 +182,27 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
|
|||
setFixedHeight(sizeHint().height());
|
||||
}
|
||||
|
||||
GameFilterConfigs DlgFilterGames::getFilters() const
|
||||
{
|
||||
return {hideBuddiesOnlyGames->isChecked(),
|
||||
hideIgnoredUserGames->isChecked(),
|
||||
hideFullGames->isChecked(),
|
||||
hideGamesThatStarted->isChecked(),
|
||||
hidePasswordProtectedGames->isChecked(),
|
||||
hideNotBuddyCreatedGames->isChecked(),
|
||||
hideOpenDecklistGames->isChecked(),
|
||||
gameNameFilterEdit->text(),
|
||||
getCreatorNameFilters(),
|
||||
getGameTypeFilter(),
|
||||
maxPlayersFilterMinSpinBox->value(),
|
||||
maxPlayersFilterMaxSpinBox->value(),
|
||||
getMaxGameAge(),
|
||||
showOnlyIfSpectatorsCanWatch->isChecked(),
|
||||
getShowSpectatorPasswordProtected(),
|
||||
getShowOnlyIfSpectatorsCanChat(),
|
||||
getShowOnlyIfSpectatorsCanSeeHands()};
|
||||
}
|
||||
|
||||
void DlgFilterGames::actOk()
|
||||
{
|
||||
accept();
|
||||
|
|
@ -192,46 +215,6 @@ void DlgFilterGames::toggleSpectatorCheckboxEnabledness(bool spectatorsEnabled)
|
|||
showOnlyIfSpectatorsCanSeeHands->setDisabled(!spectatorsEnabled);
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getHideFullGames() const
|
||||
{
|
||||
return hideFullGames->isChecked();
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getHideGamesThatStarted() const
|
||||
{
|
||||
return hideGamesThatStarted->isChecked();
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getHideBuddiesOnlyGames() const
|
||||
{
|
||||
return hideBuddiesOnlyGames->isChecked();
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getHidePasswordProtectedGames() const
|
||||
{
|
||||
return hidePasswordProtectedGames->isChecked();
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getHideIgnoredUserGames() const
|
||||
{
|
||||
return hideIgnoredUserGames->isChecked();
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getHideNotBuddyCreatedGames() const
|
||||
{
|
||||
return hideNotBuddyCreatedGames->isChecked();
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getHideOpenDecklistGames() const
|
||||
{
|
||||
return hideOpenDecklistGames->isChecked();
|
||||
}
|
||||
|
||||
QString DlgFilterGames::getGameNameFilter() const
|
||||
{
|
||||
return gameNameFilterEdit->text();
|
||||
}
|
||||
|
||||
QStringList DlgFilterGames::getCreatorNameFilters() const
|
||||
{
|
||||
return creatorNameFilterEdit->text().split(",", Qt::SkipEmptyParts);
|
||||
|
|
@ -249,30 +232,15 @@ QSet<int> DlgFilterGames::getGameTypeFilter() const
|
|||
return result;
|
||||
}
|
||||
|
||||
int DlgFilterGames::getMaxPlayersFilterMin() const
|
||||
{
|
||||
return maxPlayersFilterMinSpinBox->value();
|
||||
}
|
||||
|
||||
int DlgFilterGames::getMaxPlayersFilterMax() const
|
||||
{
|
||||
return maxPlayersFilterMaxSpinBox->value();
|
||||
}
|
||||
|
||||
QTime DlgFilterGames::getMaxGameAge() const
|
||||
{
|
||||
int index = maxGameAgeComboBox->currentIndex();
|
||||
if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds
|
||||
return gamesProxyModel->getMaxGameAge(); // leave the setting unchanged
|
||||
if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds
|
||||
return gamesProxyModel->getFilters().maxGameAge; // leave the setting unchanged
|
||||
}
|
||||
return gameAgeMap.keys().at(index);
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getShowOnlyIfSpectatorsCanWatch() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanWatch->isChecked();
|
||||
}
|
||||
|
||||
bool DlgFilterGames::getShowSpectatorPasswordProtected() const
|
||||
{
|
||||
return showSpectatorPasswordProtected->isEnabled() && showSpectatorPasswordProtected->isChecked();
|
||||
|
|
|
|||
|
|
@ -48,6 +48,14 @@ private:
|
|||
|
||||
const QMap<int, QString> &allGameTypes;
|
||||
const GamesProxyModel *gamesProxyModel;
|
||||
const QMap<QTime, QString> gameAgeMap;
|
||||
|
||||
[[nodiscard]] QStringList getCreatorNameFilters() const;
|
||||
[[nodiscard]] QSet<int> getGameTypeFilter() const;
|
||||
[[nodiscard]] QTime getMaxGameAge() const;
|
||||
[[nodiscard]] bool getShowSpectatorPasswordProtected() const;
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const;
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const;
|
||||
|
||||
private slots:
|
||||
void actOk();
|
||||
|
|
@ -58,32 +66,7 @@ public:
|
|||
const GamesProxyModel *_gamesProxyModel,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool getHideFullGames() const;
|
||||
[[nodiscard]] bool getHideGamesThatStarted() const;
|
||||
[[nodiscard]] bool getHidePasswordProtectedGames() const;
|
||||
void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden);
|
||||
[[nodiscard]] bool getHideBuddiesOnlyGames() const;
|
||||
void setHideBuddiesOnlyGames(bool _hideBuddiesOnlyGames);
|
||||
[[nodiscard]] bool getHideOpenDecklistGames() const;
|
||||
void setHideOpenDecklistGames(bool _hideOpenDecklistGames);
|
||||
[[nodiscard]] bool getHideIgnoredUserGames() const;
|
||||
void setHideIgnoredUserGames(bool _hideIgnoredUserGames);
|
||||
[[nodiscard]] bool getHideNotBuddyCreatedGames() const;
|
||||
[[nodiscard]] QString getGameNameFilter() const;
|
||||
void setGameNameFilter(const QString &_gameNameFilter);
|
||||
[[nodiscard]] QStringList getCreatorNameFilters() const;
|
||||
void setCreatorNameFilter(const QString &_creatorNameFilter);
|
||||
[[nodiscard]] QSet<int> getGameTypeFilter() const;
|
||||
void setGameTypeFilter(const QSet<int> &_gameTypeFilter);
|
||||
[[nodiscard]] int getMaxPlayersFilterMin() const;
|
||||
[[nodiscard]] int getMaxPlayersFilterMax() const;
|
||||
void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax);
|
||||
[[nodiscard]] QTime getMaxGameAge() const;
|
||||
const QMap<QTime, QString> gameAgeMap;
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const;
|
||||
[[nodiscard]] bool getShowSpectatorPasswordProtected() const;
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const;
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const;
|
||||
[[nodiscard]] GameFilterConfigs getFilters() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
#include <QtConcurrentRun>
|
||||
#include <algorithm>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/models/database/card_set/card_sets_model.h>
|
||||
|
|
@ -253,6 +254,11 @@ void WndSets::actSave()
|
|||
model->save(CardDatabaseManager::getInstance());
|
||||
SettingsCache::instance().setIncludeRebalancedCards(includeRebalancedCards);
|
||||
CardPictureLoader::clearPixmapCache();
|
||||
const auto reloadOk1 = QtConcurrent::run([] {
|
||||
CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify();
|
||||
|
||||
SettingsCache::instance().downloads().sync();
|
||||
});
|
||||
close();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,9 @@ void DlgUpdate::downloadSuccessful(const QUrl &filepath)
|
|||
{
|
||||
setLabel(tr("Installing..."));
|
||||
// Try to open the installer. If it opens, quit Cockatrice
|
||||
if (QDesktopServices::openUrl(filepath)) {
|
||||
if (QProcess::startDetached(filepath.toLocalFile(),
|
||||
QStringList()
|
||||
<< "/R" << QString("/D=%1").arg(QCoreApplication::applicationDirPath()))) {
|
||||
QMetaObject::invokeMethod(static_cast<MainWindow *>(parent()), "close", Qt::QueuedConnection);
|
||||
qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice";
|
||||
close();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../../interface/widgets/tabs/tab_supervisor.h"
|
||||
#include "../../theme_manager.h"
|
||||
#include "../../window_main.h"
|
||||
#include "background_sources.h"
|
||||
#include "home_styled_button.h"
|
||||
|
|
@ -46,6 +47,8 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
|||
&HomeWidget::onBackgroundShuffleFrequencyChanged);
|
||||
// Lambda is cleaner to read than overloading this
|
||||
connect(&SettingsCache::instance(), &SettingsCache::homeTabDisplayCardNameChanged, this, [this] { repaint(); });
|
||||
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this,
|
||||
&HomeWidget::initializeBackgroundFromSource);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this,
|
||||
&HomeWidget::updateButtonsToBackgroundColor);
|
||||
}
|
||||
|
|
@ -256,7 +259,7 @@ void HomeWidget::updateConnectButton(const ClientStatus status)
|
|||
|
||||
QPair<QColor, QColor> HomeWidget::extractDominantColors(const QPixmap &pixmap)
|
||||
{
|
||||
if (SettingsCache::instance().getThemeName() == "Default" &&
|
||||
if (themeManager->isBuiltInTheme() &&
|
||||
SettingsCache::instance().getHomeTabBackgroundSource() == BackgroundSources::toId(BackgroundSources::Theme)) {
|
||||
return QPair<QColor, QColor>(QColor::fromRgb(20, 140, 60), QColor::fromRgb(120, 200, 80));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
#include "game_filter_configs.h"
|
||||
|
||||
bool GameFilterConfigs::isDefault() const
|
||||
{
|
||||
static const GameFilterConfigs DEFAULT = {};
|
||||
return *this == DEFAULT;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef COCKATRICE_GAME_FILTER_CONFIGS_H
|
||||
#define COCKATRICE_GAME_FILTER_CONFIGS_H
|
||||
|
||||
#include <QTime>
|
||||
|
||||
/**
|
||||
* @brief The possible game filter configs.
|
||||
*/
|
||||
struct GameFilterConfigs
|
||||
{
|
||||
static constexpr int DEFAULT_MAX_PLAYERS_MIN = 1;
|
||||
static constexpr int DEFAULT_MAX_PLAYERS_MAX = 99;
|
||||
|
||||
bool hideBuddiesOnlyGames = false;
|
||||
bool hideIgnoredUserGames = false;
|
||||
bool hideFullGames = false;
|
||||
bool hideGamesThatStarted = false;
|
||||
bool hidePasswordProtectedGames = false;
|
||||
bool hideNotBuddyCreatedGames = false;
|
||||
bool hideOpenDecklistGames = false;
|
||||
QString gameNameFilter = "";
|
||||
QStringList creatorNameFilters = {};
|
||||
QSet<int> gameTypeFilter = {};
|
||||
int maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN;
|
||||
int maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX;
|
||||
QTime maxGameAge = {};
|
||||
bool showOnlyIfSpectatorsCanWatch = false;
|
||||
bool showSpectatorPasswordProtected = false;
|
||||
bool showOnlyIfSpectatorsCanChat = false;
|
||||
bool showOnlyIfSpectatorsCanSeeHands = false;
|
||||
|
||||
bool operator==(const GameFilterConfigs &) const = default;
|
||||
|
||||
/**
|
||||
* @brief Checks if this config has exactly the default values.
|
||||
*
|
||||
* @return Whether this config has the default values
|
||||
*/
|
||||
bool isDefault() const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_GAME_FILTER_CONFIGS_H
|
||||
|
|
@ -37,8 +37,9 @@ GameSelector::GameSelector(AbstractClient *_client,
|
|||
connect(gameListView, &QTreeView::customContextMenuRequested, this, &GameSelector::customContextMenu);
|
||||
|
||||
gameListModel = new GamesModel(_rooms, _gameTypes, this);
|
||||
gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserListManager());
|
||||
|
||||
if (showFilters) {
|
||||
gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserListManager());
|
||||
gameListProxyModel->setSourceModel(gameListModel);
|
||||
gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
gameListView->setModel(gameListProxyModel);
|
||||
|
|
@ -91,6 +92,7 @@ GameSelector::GameSelector(AbstractClient *_client,
|
|||
bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults();
|
||||
clearFilterButton->setEnabled(!filtersSetToDefault);
|
||||
connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter);
|
||||
connect(gameListProxyModel, &GamesProxyModel::filtersChanged, this, &GameSelector::checkClearFilterButtonState);
|
||||
|
||||
if (room) {
|
||||
createButton = new QPushButton;
|
||||
|
|
@ -179,24 +181,19 @@ void GameSelector::actSetFilter()
|
|||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
gameListProxyModel->setGameFilters(
|
||||
dlg.getHideBuddiesOnlyGames(), dlg.getHideIgnoredUserGames(), dlg.getHideFullGames(),
|
||||
dlg.getHideGamesThatStarted(), dlg.getHidePasswordProtectedGames(), dlg.getHideNotBuddyCreatedGames(),
|
||||
dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilters(), dlg.getGameTypeFilter(),
|
||||
dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax(), dlg.getMaxGameAge(),
|
||||
dlg.getShowOnlyIfSpectatorsCanWatch(), dlg.getShowSpectatorPasswordProtected(),
|
||||
dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands());
|
||||
gameListProxyModel->setGameFilters(dlg.getFilters());
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::checkClearFilterButtonState()
|
||||
{
|
||||
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
|
||||
}
|
||||
|
||||
void GameSelector::actClearFilter()
|
||||
{
|
||||
clearFilterButton->setEnabled(false);
|
||||
|
||||
gameListProxyModel->resetFilterParameters();
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ private slots:
|
|||
* Updates the proxy model with selected filter parameters and refreshes the displayed game list.
|
||||
*/
|
||||
void actSetFilter();
|
||||
void checkClearFilterButtonState();
|
||||
|
||||
/**
|
||||
* @brief Clears all filters applied to the game list.
|
||||
|
|
|
|||
|
|
@ -18,33 +18,31 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
|
|||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(5);
|
||||
|
||||
const GameFilterConfigs &filters = model->getFilters();
|
||||
|
||||
searchBar = new QLineEdit(this);
|
||||
searchBar->setText(model->getCreatorNameFilters().join(", "));
|
||||
connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); });
|
||||
searchBar->setText(filters.gameNameFilter);
|
||||
connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) {
|
||||
applyFilters([&](GameFilterConfigs &configs) { configs.gameNameFilter = text; });
|
||||
});
|
||||
|
||||
hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this);
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames());
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setChecked(filters.hideNotBuddyCreatedGames);
|
||||
connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
if (checked) {
|
||||
QStringList buddyNames;
|
||||
for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) {
|
||||
buddyNames << QString::fromStdString(buddy.name());
|
||||
}
|
||||
model->setCreatorNameFilters(buddyNames);
|
||||
} else {
|
||||
model->setCreatorNameFilters({});
|
||||
}
|
||||
applyFilters([&](GameFilterConfigs &configs) { configs.hideNotBuddyCreatedGames = checked; });
|
||||
});
|
||||
|
||||
hideFullGamesCheckBox = new QCheckBox(this);
|
||||
hideFullGamesCheckBox->setChecked(model->getHideFullGames());
|
||||
connect(hideFullGamesCheckBox, &QCheckBox::toggled, this,
|
||||
[this](bool checked) { model->setHideFullGames(checked); });
|
||||
hideFullGamesCheckBox->setChecked(filters.hideFullGames);
|
||||
connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
applyFilters([&](GameFilterConfigs &configs) { configs.hideFullGames = checked; });
|
||||
});
|
||||
|
||||
hideStartedGamesCheckBox = new QCheckBox(this);
|
||||
hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted());
|
||||
connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this,
|
||||
[this](bool checked) { model->setHideGamesThatStarted(checked); });
|
||||
hideStartedGamesCheckBox->setChecked(filters.hideGamesThatStarted);
|
||||
connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
applyFilters([&](GameFilterConfigs &configs) { configs.hideGamesThatStarted = checked; });
|
||||
});
|
||||
|
||||
filterToFormatComboBox = new QComboBox(this);
|
||||
|
||||
|
|
@ -57,7 +55,7 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
|
|||
filterToFormatComboBox->addItem(i.value(), i.key()); // text = name, data = type ID
|
||||
}
|
||||
|
||||
QSet<int> currentTypes = model->getGameTypeFilter();
|
||||
QSet<int> currentTypes = filters.gameTypeFilter;
|
||||
if (currentTypes.size() == 1) {
|
||||
int typeId = *currentTypes.begin();
|
||||
int index = filterToFormatComboBox->findData(typeId);
|
||||
|
|
@ -69,13 +67,14 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
|
|||
|
||||
// Update proxy model on selection change
|
||||
connect(filterToFormatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
QVariant data = filterToFormatComboBox->itemData(index);
|
||||
if (!data.isValid()) {
|
||||
model->setGameTypeFilter({}); // empty = no filter
|
||||
} else {
|
||||
int typeId = data.toInt();
|
||||
model->setGameTypeFilter({typeId});
|
||||
}
|
||||
applyFilters([&](GameFilterConfigs &configs) {
|
||||
QVariant data = filterToFormatComboBox->itemData(index);
|
||||
if (!data.isValid()) {
|
||||
configs.gameTypeFilter.clear();
|
||||
} else {
|
||||
configs.gameTypeFilter = {data.toInt()};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20);
|
||||
|
|
@ -96,9 +95,47 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
|
|||
|
||||
setLayout(mainLayout);
|
||||
|
||||
syncFromModel();
|
||||
|
||||
connect(model, &GamesProxyModel::filtersChanged, this, &GameSelectorQuickFilterToolBar::syncFromModel);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void GameSelectorQuickFilterToolBar::syncFromModel()
|
||||
{
|
||||
QSignalBlocker b1(searchBar);
|
||||
QSignalBlocker b2(filterToFormatComboBox);
|
||||
QSignalBlocker b3(hideGamesNotCreatedByBuddiesCheckBox);
|
||||
QSignalBlocker b4(hideFullGamesCheckBox);
|
||||
QSignalBlocker b5(hideStartedGamesCheckBox);
|
||||
|
||||
const GameFilterConfigs filters = model->getFilters();
|
||||
|
||||
searchBar->setText(filters.gameNameFilter);
|
||||
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setChecked(filters.hideNotBuddyCreatedGames);
|
||||
hideFullGamesCheckBox->setChecked(filters.hideFullGames);
|
||||
hideStartedGamesCheckBox->setChecked(filters.hideGamesThatStarted);
|
||||
|
||||
QSet<int> types = filters.gameTypeFilter;
|
||||
if (types.size() == 1) {
|
||||
int idx = filterToFormatComboBox->findData(*types.begin());
|
||||
filterToFormatComboBox->setCurrentIndex(idx >= 0 ? idx : 0);
|
||||
} else {
|
||||
filterToFormatComboBox->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
void GameSelectorQuickFilterToolBar::applyFilters(std::function<void(GameFilterConfigs &)> mutator)
|
||||
{
|
||||
GameFilterConfigs configs = model->getFilters();
|
||||
|
||||
mutator(configs);
|
||||
|
||||
model->setGameFilters(configs);
|
||||
}
|
||||
|
||||
void GameSelectorQuickFilterToolBar::retranslateUi()
|
||||
{
|
||||
searchBar->setPlaceholderText(tr("Filter by game name..."));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ public:
|
|||
TabSupervisor *tabSupervisor,
|
||||
GamesProxyModel *model,
|
||||
const QMap<int, QString> &allGameTypes);
|
||||
void syncFromModel();
|
||||
void applyFilters(std::function<void(GameFilterConfigs &)> mutator);
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ enum GameListColumn
|
|||
SPECTATORS
|
||||
};
|
||||
|
||||
const int DEFAULT_MAX_PLAYERS_MIN = 1;
|
||||
const int DEFAULT_MAX_PLAYERS_MAX = 99;
|
||||
constexpr auto DEFAULT_MAX_GAME_AGE = QTime();
|
||||
|
||||
const QString GamesModel::getGameCreatedString(const int secs)
|
||||
{
|
||||
static const QTime zeroTime{0, 0};
|
||||
|
|
@ -283,49 +279,18 @@ GamesProxyModel::GamesProxyModel(QObject *parent, const UserListProxy *_userList
|
|||
setDynamicSortFilter(true);
|
||||
}
|
||||
|
||||
void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames,
|
||||
bool _hideIgnoredUserGames,
|
||||
bool _hideFullGames,
|
||||
bool _hideGamesThatStarted,
|
||||
bool _hidePasswordProtectedGames,
|
||||
bool _hideNotBuddyCreatedGames,
|
||||
bool _hideOpenDecklistGames,
|
||||
const QString &_gameNameFilter,
|
||||
const QStringList &_creatorNameFilters,
|
||||
const QSet<int> &_gameTypeFilter,
|
||||
int _maxPlayersFilterMin,
|
||||
int _maxPlayersFilterMax,
|
||||
const QTime &_maxGameAge,
|
||||
bool _showOnlyIfSpectatorsCanWatch,
|
||||
bool _showSpectatorPasswordProtected,
|
||||
bool _showOnlyIfSpectatorsCanChat,
|
||||
bool _showOnlyIfSpectatorsCanSeeHands)
|
||||
void GamesProxyModel::setGameFilters(const GameFilterConfigs &_filters)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0))
|
||||
beginFilterChange();
|
||||
#endif
|
||||
hideBuddiesOnlyGames = _hideBuddiesOnlyGames;
|
||||
hideIgnoredUserGames = _hideIgnoredUserGames;
|
||||
hideFullGames = _hideFullGames;
|
||||
hideGamesThatStarted = _hideGamesThatStarted;
|
||||
hidePasswordProtectedGames = _hidePasswordProtectedGames;
|
||||
hideNotBuddyCreatedGames = _hideNotBuddyCreatedGames;
|
||||
hideOpenDecklistGames = _hideOpenDecklistGames;
|
||||
gameNameFilter = _gameNameFilter;
|
||||
creatorNameFilters = _creatorNameFilters;
|
||||
gameTypeFilter = _gameTypeFilter;
|
||||
maxPlayersFilterMin = _maxPlayersFilterMin;
|
||||
maxPlayersFilterMax = _maxPlayersFilterMax;
|
||||
maxGameAge = _maxGameAge;
|
||||
showOnlyIfSpectatorsCanWatch = _showOnlyIfSpectatorsCanWatch;
|
||||
showSpectatorPasswordProtected = _showSpectatorPasswordProtected;
|
||||
showOnlyIfSpectatorsCanChat = _showOnlyIfSpectatorsCanChat;
|
||||
showOnlyIfSpectatorsCanSeeHands = _showOnlyIfSpectatorsCanSeeHands;
|
||||
filters = _filters;
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 10, 0))
|
||||
endFilterChange(QSortFilterProxyModel::Direction::Rows);
|
||||
#else
|
||||
invalidateFilter();
|
||||
#endif
|
||||
emit filtersChanged();
|
||||
}
|
||||
|
||||
int GamesProxyModel::getNumFilteredGames() const
|
||||
|
|
@ -345,18 +310,12 @@ int GamesProxyModel::getNumFilteredGames() const
|
|||
|
||||
void GamesProxyModel::resetFilterParameters()
|
||||
{
|
||||
setGameFilters(false, false, false, false, false, false, false, QString(), QStringList(), {},
|
||||
DEFAULT_MAX_PLAYERS_MIN, DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false);
|
||||
setGameFilters({});
|
||||
}
|
||||
|
||||
bool GamesProxyModel::areFilterParametersSetToDefaults() const
|
||||
{
|
||||
return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames &&
|
||||
!hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() &&
|
||||
creatorNameFilters.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN &&
|
||||
maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE &&
|
||||
!showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat &&
|
||||
!showOnlyIfSpectatorsCanSeeHands;
|
||||
return filters.isDefault();
|
||||
}
|
||||
|
||||
void GamesProxyModel::loadFilterParameters(const QMap<int, QString> &allGameTypes)
|
||||
|
|
@ -372,44 +331,44 @@ void GamesProxyModel::loadFilterParameters(const QMap<int, QString> &allGameType
|
|||
}
|
||||
}
|
||||
|
||||
setGameFilters(gameFilters.isHideBuddiesOnlyGames(), gameFilters.isHideIgnoredUserGames(),
|
||||
gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(),
|
||||
gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(),
|
||||
gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(),
|
||||
gameFilters.getCreatorNameFilters(), newGameTypeFilter, gameFilters.getMinPlayers(),
|
||||
gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(),
|
||||
gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(),
|
||||
gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands());
|
||||
setGameFilters({gameFilters.isHideBuddiesOnlyGames(), gameFilters.isHideIgnoredUserGames(),
|
||||
gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(),
|
||||
gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(),
|
||||
gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(),
|
||||
gameFilters.getCreatorNameFilters(), newGameTypeFilter, gameFilters.getMinPlayers(),
|
||||
gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(),
|
||||
gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(),
|
||||
gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands()});
|
||||
}
|
||||
|
||||
void GamesProxyModel::saveFilterParameters(const QMap<int, QString> &allGameTypes)
|
||||
{
|
||||
GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters();
|
||||
gameFilters.setHideBuddiesOnlyGames(hideBuddiesOnlyGames);
|
||||
gameFilters.setHideFullGames(hideFullGames);
|
||||
gameFilters.setHideGamesThatStarted(hideGamesThatStarted);
|
||||
gameFilters.setHidePasswordProtectedGames(hidePasswordProtectedGames);
|
||||
gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames);
|
||||
gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames);
|
||||
gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames);
|
||||
gameFilters.setGameNameFilter(gameNameFilter);
|
||||
gameFilters.setCreatorNameFilters(creatorNameFilters);
|
||||
gameFilters.setHideBuddiesOnlyGames(filters.hideBuddiesOnlyGames);
|
||||
gameFilters.setHideFullGames(filters.hideFullGames);
|
||||
gameFilters.setHideGamesThatStarted(filters.hideGamesThatStarted);
|
||||
gameFilters.setHidePasswordProtectedGames(filters.hidePasswordProtectedGames);
|
||||
gameFilters.setHideIgnoredUserGames(filters.hideIgnoredUserGames);
|
||||
gameFilters.setHideNotBuddyCreatedGames(filters.hideNotBuddyCreatedGames);
|
||||
gameFilters.setHideOpenDecklistGames(filters.hideOpenDecklistGames);
|
||||
gameFilters.setGameNameFilter(filters.gameNameFilter);
|
||||
gameFilters.setCreatorNameFilters(filters.creatorNameFilters);
|
||||
|
||||
QMapIterator<int, QString> gameTypeIterator(allGameTypes);
|
||||
while (gameTypeIterator.hasNext()) {
|
||||
gameTypeIterator.next();
|
||||
bool enabled = gameTypeFilter.contains(gameTypeIterator.key());
|
||||
bool enabled = filters.gameTypeFilter.contains(gameTypeIterator.key());
|
||||
gameFilters.setGameTypeEnabled(gameTypeIterator.value(), enabled);
|
||||
}
|
||||
|
||||
gameFilters.setMinPlayers(maxPlayersFilterMin);
|
||||
gameFilters.setMaxPlayers(maxPlayersFilterMax);
|
||||
gameFilters.setMaxGameAge(maxGameAge);
|
||||
gameFilters.setMinPlayers(filters.maxPlayersFilterMin);
|
||||
gameFilters.setMaxPlayers(filters.maxPlayersFilterMax);
|
||||
gameFilters.setMaxGameAge(filters.maxGameAge);
|
||||
|
||||
gameFilters.setShowOnlyIfSpectatorsCanWatch(showOnlyIfSpectatorsCanWatch);
|
||||
gameFilters.setShowSpectatorPasswordProtected(showSpectatorPasswordProtected);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanChat(showOnlyIfSpectatorsCanChat);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanSeeHands(showOnlyIfSpectatorsCanSeeHands);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanWatch(filters.showOnlyIfSpectatorsCanWatch);
|
||||
gameFilters.setShowSpectatorPasswordProtected(filters.showSpectatorPasswordProtected);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanChat(filters.showOnlyIfSpectatorsCanChat);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanSeeHands(filters.showOnlyIfSpectatorsCanSeeHands);
|
||||
}
|
||||
|
||||
bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
|
|
@ -430,33 +389,35 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const
|
|||
|
||||
const ServerInfo_Game &game = model->getGame(sourceRow);
|
||||
|
||||
if (hideBuddiesOnlyGames && game.only_buddies()) {
|
||||
if (filters.hideBuddiesOnlyGames && game.only_buddies()) {
|
||||
return false;
|
||||
}
|
||||
if (hideOpenDecklistGames && game.share_decklists_on_load()) {
|
||||
if (filters.hideOpenDecklistGames && game.share_decklists_on_load()) {
|
||||
return false;
|
||||
}
|
||||
if (hideIgnoredUserGames && userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) {
|
||||
if (filters.hideIgnoredUserGames &&
|
||||
userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) {
|
||||
return false;
|
||||
}
|
||||
if (hideNotBuddyCreatedGames && !userListProxy->isUserBuddy(QString::fromStdString(game.creator_info().name()))) {
|
||||
if (filters.hideNotBuddyCreatedGames &&
|
||||
!userListProxy->isUserBuddy(QString::fromStdString(game.creator_info().name()))) {
|
||||
return false;
|
||||
}
|
||||
if (hideFullGames && game.player_count() == game.max_players())
|
||||
if (filters.hideFullGames && game.player_count() == game.max_players())
|
||||
return false;
|
||||
if (hideGamesThatStarted && game.started())
|
||||
if (filters.hideGamesThatStarted && game.started())
|
||||
return false;
|
||||
if (!userListProxy->isOwnUserRegistered())
|
||||
if (game.only_registered())
|
||||
return false;
|
||||
if (hidePasswordProtectedGames && game.with_password())
|
||||
if (filters.hidePasswordProtectedGames && game.with_password())
|
||||
return false;
|
||||
if (!gameNameFilter.isEmpty())
|
||||
if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive))
|
||||
if (!filters.gameNameFilter.isEmpty())
|
||||
if (!QString::fromStdString(game.description()).contains(filters.gameNameFilter, Qt::CaseInsensitive))
|
||||
return false;
|
||||
if (!creatorNameFilters.isEmpty()) {
|
||||
if (!filters.creatorNameFilters.isEmpty()) {
|
||||
bool found = false;
|
||||
for (const auto &createNameFilter : creatorNameFilters) {
|
||||
for (const auto &createNameFilter : filters.creatorNameFilters) {
|
||||
if (QString::fromStdString(game.creator_info().name()).contains(createNameFilter, Qt::CaseInsensitive)) {
|
||||
found = true;
|
||||
}
|
||||
|
|
@ -469,33 +430,34 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const
|
|||
QSet<int> gameTypes;
|
||||
for (int i = 0; i < game.game_types_size(); ++i)
|
||||
gameTypes.insert(game.game_types(i));
|
||||
if (!gameTypeFilter.isEmpty() && gameTypes.intersect(gameTypeFilter).isEmpty())
|
||||
if (!filters.gameTypeFilter.isEmpty() && gameTypes.intersect(filters.gameTypeFilter).isEmpty())
|
||||
return false;
|
||||
|
||||
if (game.max_players() < maxPlayersFilterMin)
|
||||
if (static_cast<int>(game.max_players()) < filters.maxPlayersFilterMin)
|
||||
return false;
|
||||
if (game.max_players() > maxPlayersFilterMax)
|
||||
if (static_cast<int>(game.max_players()) > filters.maxPlayersFilterMax)
|
||||
return false;
|
||||
|
||||
if (maxGameAge.isValid()) {
|
||||
if (filters.maxGameAge.isValid()) {
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
qint64 signed_start_time = game.start_time(); // cast to 64 bit value to allow signed value
|
||||
QDateTime total = now.addSecs(-signed_start_time); // a 32 bit value would wrap at 2038-1-19
|
||||
// games shouldn't have negative ages but we'll not filter them
|
||||
// because qtime wraps after a day we consider all games older than a day to be too old
|
||||
if (total.isValid() && total.date() >= epochDate && (total.date() > epochDate || total.time() > maxGameAge)) {
|
||||
if (total.isValid() && total.date() >= epochDate &&
|
||||
(total.date() > epochDate || total.time() > filters.maxGameAge)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (showOnlyIfSpectatorsCanWatch) {
|
||||
if (filters.showOnlyIfSpectatorsCanWatch) {
|
||||
if (!game.spectators_allowed())
|
||||
return false;
|
||||
if (!showSpectatorPasswordProtected && game.spectators_need_password())
|
||||
if (!filters.showSpectatorPasswordProtected && game.spectators_need_password())
|
||||
return false;
|
||||
if (showOnlyIfSpectatorsCanChat && !game.spectators_can_chat())
|
||||
if (filters.showOnlyIfSpectatorsCanChat && !game.spectators_can_chat())
|
||||
return false;
|
||||
if (showOnlyIfSpectatorsCanSeeHands && !game.spectators_omniscient())
|
||||
if (filters.showOnlyIfSpectatorsCanSeeHands && !game.spectators_omniscient())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef GAMESMODEL_H
|
||||
#define GAMESMODEL_H
|
||||
|
||||
#include "game_filter_configs.h"
|
||||
#include "game_type_map.h"
|
||||
|
||||
#include <QList>
|
||||
|
|
@ -121,22 +122,10 @@ private:
|
|||
// - loadFilterParameters()
|
||||
// - saveFilterParameters()
|
||||
// - filterAcceptsRow()
|
||||
bool hideBuddiesOnlyGames;
|
||||
bool hideIgnoredUserGames;
|
||||
bool hideFullGames;
|
||||
bool hideGamesThatStarted;
|
||||
bool hidePasswordProtectedGames;
|
||||
bool hideNotBuddyCreatedGames;
|
||||
bool hideOpenDecklistGames;
|
||||
QString gameNameFilter;
|
||||
QStringList creatorNameFilters;
|
||||
QSet<int> gameTypeFilter;
|
||||
quint32 maxPlayersFilterMin, maxPlayersFilterMax;
|
||||
QTime maxGameAge;
|
||||
bool showOnlyIfSpectatorsCanWatch;
|
||||
bool showSpectatorPasswordProtected;
|
||||
bool showOnlyIfSpectatorsCanChat;
|
||||
bool showOnlyIfSpectatorsCanSeeHands;
|
||||
GameFilterConfigs filters;
|
||||
|
||||
signals:
|
||||
void filtersChanged();
|
||||
|
||||
public:
|
||||
/**
|
||||
|
|
@ -147,182 +136,15 @@ public:
|
|||
explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr);
|
||||
|
||||
// Getters for filter parameters
|
||||
[[nodiscard]] bool getHideBuddiesOnlyGames() const
|
||||
[[nodiscard]] const GameFilterConfigs &getFilters() const
|
||||
{
|
||||
return hideBuddiesOnlyGames;
|
||||
}
|
||||
[[nodiscard]] bool getHideIgnoredUserGames() const
|
||||
{
|
||||
return hideIgnoredUserGames;
|
||||
}
|
||||
[[nodiscard]] bool getHideFullGames() const
|
||||
{
|
||||
return hideFullGames;
|
||||
}
|
||||
[[nodiscard]] bool getHideGamesThatStarted() const
|
||||
{
|
||||
return hideGamesThatStarted;
|
||||
}
|
||||
[[nodiscard]] bool getHidePasswordProtectedGames() const
|
||||
{
|
||||
return hidePasswordProtectedGames;
|
||||
}
|
||||
[[nodiscard]] bool getHideNotBuddyCreatedGames() const
|
||||
{
|
||||
return hideNotBuddyCreatedGames;
|
||||
}
|
||||
[[nodiscard]] bool getHideOpenDecklistGames() const
|
||||
{
|
||||
return hideOpenDecklistGames;
|
||||
}
|
||||
[[nodiscard]] QString getGameNameFilter() const
|
||||
{
|
||||
return gameNameFilter;
|
||||
}
|
||||
[[nodiscard]] QStringList getCreatorNameFilters() const
|
||||
{
|
||||
return creatorNameFilters;
|
||||
}
|
||||
[[nodiscard]] QSet<int> getGameTypeFilter() const
|
||||
{
|
||||
return gameTypeFilter;
|
||||
}
|
||||
[[nodiscard]] int getMaxPlayersFilterMin() const
|
||||
{
|
||||
return maxPlayersFilterMin;
|
||||
}
|
||||
[[nodiscard]] int getMaxPlayersFilterMax() const
|
||||
{
|
||||
return maxPlayersFilterMax;
|
||||
}
|
||||
[[nodiscard]] const QTime &getMaxGameAge() const
|
||||
{
|
||||
return maxGameAge;
|
||||
}
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanWatch;
|
||||
}
|
||||
[[nodiscard]] bool getShowSpectatorPasswordProtected() const
|
||||
{
|
||||
return showSpectatorPasswordProtected;
|
||||
}
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanChat;
|
||||
}
|
||||
[[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanSeeHands;
|
||||
return filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets all game filters at once.
|
||||
*/
|
||||
void setGameFilters(bool _hideBuddiesOnlyGames,
|
||||
bool _hideIgnoredUserGames,
|
||||
bool _hideFullGames,
|
||||
bool _hideGamesThatStarted,
|
||||
bool _hidePasswordProtectedGames,
|
||||
bool _hideNotBuddyCreatedGames,
|
||||
bool _hideOpenDecklistGames,
|
||||
const QString &_gameNameFilter,
|
||||
const QStringList &_creatorNameFilter,
|
||||
const QSet<int> &_gameTypeFilter,
|
||||
int _maxPlayersFilterMin,
|
||||
int _maxPlayersFilterMax,
|
||||
const QTime &_maxGameAge,
|
||||
bool _showOnlyIfSpectatorsCanWatch,
|
||||
bool _showSpectatorPasswordProtected,
|
||||
bool _showOnlyIfSpectatorsCanChat,
|
||||
bool _showOnlyIfSpectatorsCanSeeHands);
|
||||
|
||||
// Individual setters
|
||||
void setHideBuddiesOnlyGames(bool value)
|
||||
{
|
||||
hideBuddiesOnlyGames = value;
|
||||
refresh();
|
||||
}
|
||||
void setHideIgnoredUserGames(bool value)
|
||||
{
|
||||
hideIgnoredUserGames = value;
|
||||
refresh();
|
||||
}
|
||||
void setHideFullGames(bool value)
|
||||
{
|
||||
hideFullGames = value;
|
||||
refresh();
|
||||
}
|
||||
void setHideGamesThatStarted(bool value)
|
||||
{
|
||||
hideGamesThatStarted = value;
|
||||
refresh();
|
||||
}
|
||||
void setHidePasswordProtectedGames(bool value)
|
||||
{
|
||||
hidePasswordProtectedGames = value;
|
||||
refresh();
|
||||
}
|
||||
void setHideNotBuddyCreatedGames(bool value)
|
||||
{
|
||||
hideNotBuddyCreatedGames = value;
|
||||
refresh();
|
||||
}
|
||||
void setHideOpenDecklistGames(bool value)
|
||||
{
|
||||
hideOpenDecklistGames = value;
|
||||
refresh();
|
||||
}
|
||||
void setGameNameFilter(const QString &value)
|
||||
{
|
||||
gameNameFilter = value;
|
||||
refresh();
|
||||
}
|
||||
void setCreatorNameFilters(const QStringList &values)
|
||||
{
|
||||
creatorNameFilters = values;
|
||||
refresh();
|
||||
}
|
||||
void setGameTypeFilter(const QSet<int> &value)
|
||||
{
|
||||
gameTypeFilter = value;
|
||||
refresh();
|
||||
}
|
||||
void setMaxPlayersFilterMin(int value)
|
||||
{
|
||||
maxPlayersFilterMin = value;
|
||||
refresh();
|
||||
}
|
||||
void setMaxPlayersFilterMax(int value)
|
||||
{
|
||||
maxPlayersFilterMax = value;
|
||||
refresh();
|
||||
}
|
||||
void setMaxGameAge(const QTime &value)
|
||||
{
|
||||
maxGameAge = value;
|
||||
refresh();
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanWatch(bool value)
|
||||
{
|
||||
showOnlyIfSpectatorsCanWatch = value;
|
||||
refresh();
|
||||
}
|
||||
void setShowSpectatorPasswordProtected(bool value)
|
||||
{
|
||||
showSpectatorPasswordProtected = value;
|
||||
refresh();
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanChat(bool value)
|
||||
{
|
||||
showOnlyIfSpectatorsCanChat = value;
|
||||
refresh();
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanSeeHands(bool value)
|
||||
{
|
||||
showOnlyIfSpectatorsCanSeeHands = value;
|
||||
refresh();
|
||||
}
|
||||
void setGameFilters(const GameFilterConfigs &_filters);
|
||||
|
||||
/**
|
||||
* @brief Returns the number of games filtered out by the current filter.
|
||||
|
|
|
|||
|
|
@ -4,10 +4,29 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json)
|
|||
{
|
||||
id = json.value("id").toInt();
|
||||
|
||||
categories.clear();
|
||||
|
||||
auto categoriesJson = json.value("categories").toArray();
|
||||
|
||||
for (auto category : categoriesJson) {
|
||||
categories.append(category.toString());
|
||||
for (const auto &categoryValue : categoriesJson) {
|
||||
Category cat;
|
||||
|
||||
if (categoryValue.isObject()) {
|
||||
QJsonObject obj = categoryValue.toObject();
|
||||
|
||||
cat.id = obj.value("id").toInt();
|
||||
cat.name = obj.value("name").toString();
|
||||
cat.isPremier = obj.value("isPremier").toBool();
|
||||
cat.includedInDeck = obj.value("includedInDeck").toBool();
|
||||
cat.includedInPrice = obj.value("includedInPrice").toBool();
|
||||
} else if (categoryValue.isString()) {
|
||||
cat.name = categoryValue.toString();
|
||||
|
||||
// assume mainboard unless known otherwise
|
||||
cat.includedInDeck = true;
|
||||
}
|
||||
|
||||
categories.append(cat);
|
||||
}
|
||||
|
||||
companion = json.value("companion").toBool();
|
||||
|
|
@ -27,7 +46,13 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json)
|
|||
void ArchidektApiResponseCardEntry::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "Categories:" << categories;
|
||||
for (auto category : categories) {
|
||||
qDebug() << "Category ID:" << category.id;
|
||||
qDebug() << "Category Name:" << category.name;
|
||||
qDebug() << "Category Premier:" << category.isPremier;
|
||||
qDebug() << "Category Included in Deck:" << category.includedInDeck;
|
||||
qDebug() << "Category Included in Price:" << category.includedInPrice;
|
||||
}
|
||||
qDebug() << "Companion:" << companion;
|
||||
qDebug() << "FlippedDefault:" << flippedDefault;
|
||||
qDebug() << "Label:" << label;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@
|
|||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
struct Category
|
||||
{
|
||||
int id;
|
||||
QString name;
|
||||
bool isPremier;
|
||||
bool includedInDeck;
|
||||
bool includedInPrice;
|
||||
};
|
||||
|
||||
class ArchidektApiResponseCardEntry
|
||||
{
|
||||
public:
|
||||
|
|
@ -26,7 +35,7 @@ public:
|
|||
return card;
|
||||
};
|
||||
|
||||
QStringList getCategories() const
|
||||
QList<Category> getCategories() const
|
||||
{
|
||||
return categories;
|
||||
}
|
||||
|
|
@ -38,7 +47,7 @@ public:
|
|||
|
||||
private:
|
||||
int id;
|
||||
QStringList categories;
|
||||
QList<Category> categories;
|
||||
bool companion;
|
||||
bool flippedDefault;
|
||||
QString label;
|
||||
|
|
|
|||
|
|
@ -63,16 +63,60 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
|
|||
QString tempDeck;
|
||||
QTextStream deckStream(&tempDeck);
|
||||
|
||||
for (auto card : response.getCards()) {
|
||||
QString mainboardText;
|
||||
QString sideboardText;
|
||||
|
||||
QTextStream mainStream(&mainboardText);
|
||||
QTextStream sideStream(&sideboardText);
|
||||
|
||||
for (const auto &card : response.getCards()) {
|
||||
QString fullName = card.getCard().getOracleCard().value("name").toString();
|
||||
// We don't really care about the second card, the card database already has it as a relation
|
||||
QString cleanName = fullName.split("//").first().trimmed();
|
||||
|
||||
tempDeck += QString("%1 %2 (%3) %4\n")
|
||||
.arg(card.getQuantity())
|
||||
.arg(cleanName)
|
||||
.arg(card.getCard().getEdition().getEditionCode().toUpper())
|
||||
.arg(card.getCard().getCollectorNumber());
|
||||
QString line = QString("%1 %2 (%3) %4\n")
|
||||
.arg(card.getQuantity())
|
||||
.arg(cleanName)
|
||||
.arg(card.getCard().getEdition().getEditionCode().toUpper())
|
||||
.arg(card.getCard().getCollectorNumber());
|
||||
|
||||
bool isCommander = false;
|
||||
bool isSideboardCategory = false;
|
||||
bool includedInDeck = false;
|
||||
|
||||
for (const auto &cat : card.getCategories()) {
|
||||
|
||||
if (cat.name.compare("Commander", Qt::CaseInsensitive) == 0) {
|
||||
isCommander = true;
|
||||
}
|
||||
|
||||
if (cat.name.compare("Sideboard", Qt::CaseInsensitive) == 0 ||
|
||||
cat.name.compare("Maybeboard", Qt::CaseInsensitive) == 0) {
|
||||
isSideboardCategory = true;
|
||||
}
|
||||
|
||||
if (cat.includedInDeck) {
|
||||
includedInDeck = true;
|
||||
}
|
||||
}
|
||||
|
||||
QString target;
|
||||
|
||||
if (isCommander || isSideboardCategory) {
|
||||
sideStream << line;
|
||||
} else if (includedInDeck) {
|
||||
mainStream << line;
|
||||
} else {
|
||||
sideStream << line;
|
||||
}
|
||||
}
|
||||
|
||||
// Combine with blank line separator
|
||||
tempDeck = mainboardText;
|
||||
|
||||
if (!sideboardText.isEmpty()) {
|
||||
tempDeck += "\n";
|
||||
tempDeck += sideboardText;
|
||||
}
|
||||
|
||||
model = new DeckListModel(this);
|
||||
|
|
|
|||
|
|
@ -259,6 +259,9 @@ TabGame::~TabGame()
|
|||
if (replayManager) {
|
||||
delete replayManager->replay;
|
||||
}
|
||||
for (auto &player : game->getPlayerManager()->getPlayers()) {
|
||||
player->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TabGame::updatePlayerListDockTitle()
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent,
|
|||
this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display"));
|
||||
this->addNewTab(deckAnalytics, tr("Deck Analytics"));
|
||||
this->addNewTab(sampleHandWidget, tr("Sample Hand"));
|
||||
|
||||
setTabsClosable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
|
||||
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
const QString visualDatabaseDisplayFilterButtonStyle = QString(R"(
|
||||
QPushButton {
|
||||
background-color: palette(button);
|
||||
color: palette(button-text);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid palette(dark);
|
||||
}
|
||||
QPushButton:checked {
|
||||
background-color: palette(highlight);
|
||||
color: palette(highlighted-text);
|
||||
border: 1px solid palette(shadow);
|
||||
}
|
||||
)");
|
||||
|
||||
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "visual_database_display_format_legality_filter_widget.h"
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
|
@ -80,8 +81,7 @@ void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons()
|
|||
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
|
||||
auto *button = new QPushButton(it.key(), flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
formatButtons[it.key()] = button;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "visual_database_display_main_type_filter_widget.h"
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
|
@ -75,8 +76,8 @@ void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons()
|
|||
for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) {
|
||||
auto *button = new QPushButton(it.key(), flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
typeButtons[it.key()] = button;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h"
|
||||
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
|
||||
#include "../deck_editor/deck_state_manager.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
|
|
@ -95,8 +96,8 @@ void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name
|
|||
|
||||
// Create a button for the filter
|
||||
auto *button = new QPushButton(name, flowWidget);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:hover { background-color: red; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
connect(button, &QPushButton::clicked, this, [this, name]() {
|
||||
removeNameFilter(name);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
|
@ -101,8 +102,8 @@ void VisualDatabaseDisplaySetFilterWidget::createSetButtons()
|
|||
|
||||
auto *button = new QPushButton(longName + " (" + shortName + ")", flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
setButtons[shortName] = button;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "visual_database_display_sub_type_filter_widget.h"
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
|
|
@ -80,8 +81,8 @@ void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons()
|
|||
for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) {
|
||||
auto *button = new QPushButton(it.key(), flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
typeButtons[it.key()] = button;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
|||
: QWidget(parent), deckEditor(_deckEditor), databaseModel(database_model),
|
||||
databaseDisplayModel(database_display_model)
|
||||
{
|
||||
debounceTimer = new QTimer(this);
|
||||
debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout
|
||||
|
||||
connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::onSearchModelChanged);
|
||||
|
||||
cards = new QList<ExactCard>;
|
||||
connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this,
|
||||
&VisualDatabaseDisplayWidget::modelDirty);
|
||||
|
|
@ -140,6 +145,7 @@ void VisualDatabaseDisplayWidget::initialize()
|
|||
databaseLoadIndicator->setVisible(false);
|
||||
|
||||
filterContainer->initialize();
|
||||
filterContainer->setVisible(true);
|
||||
|
||||
searchLayout->addWidget(colorFilterWidget);
|
||||
searchLayout->addWidget(clearFilterWidget);
|
||||
|
|
@ -156,11 +162,6 @@ void VisualDatabaseDisplayWidget::initialize()
|
|||
|
||||
mainLayout->addWidget(cardSizeWidget);
|
||||
|
||||
debounceTimer = new QTimer(this);
|
||||
debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout
|
||||
|
||||
connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::onSearchModelChanged);
|
||||
|
||||
databaseDisplayModel->setFilterTree(filterModel->filterTree());
|
||||
|
||||
connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::onSearchModelChanged);
|
||||
|
|
@ -285,7 +286,9 @@ void VisualDatabaseDisplayWidget::loadNextPage()
|
|||
}
|
||||
|
||||
// Load the next page of cards and add them to the flow widget
|
||||
flowWidget->setUpdatesEnabled(false);
|
||||
loadPage(start, end);
|
||||
flowWidget->setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayWidget::loadPage(int start, int end)
|
||||
|
|
|
|||
|
|
@ -22,14 +22,9 @@
|
|||
#include "../client/network/update/client/client_update_checker.h"
|
||||
#include "../client/network/update/client/release_channel.h"
|
||||
#include "../client/settings/cache_settings.h"
|
||||
#include "../interface/widgets/dialogs/dlg_connect.h"
|
||||
#include "../interface/widgets/dialogs/dlg_edit_tokens.h"
|
||||
#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
|
||||
#include "../interface/widgets/dialogs/dlg_forgot_password_request.h"
|
||||
#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h"
|
||||
#include "../interface/widgets/dialogs/dlg_local_game_options.h"
|
||||
#include "../interface/widgets/dialogs/dlg_manage_sets.h"
|
||||
#include "../interface/widgets/dialogs/dlg_register.h"
|
||||
#include "../interface/widgets/dialogs/dlg_settings.h"
|
||||
#include "../interface/widgets/dialogs/dlg_startup_card_check.h"
|
||||
#include "../interface/widgets/dialogs/dlg_tip_of_the_day.h"
|
||||
|
|
@ -40,6 +35,8 @@
|
|||
#include "../main.h"
|
||||
#include "logger.h"
|
||||
#include "version_string.h"
|
||||
#include "widgets/dialogs/dlg_connect.h"
|
||||
#include "widgets/server/handle_public_servers.h"
|
||||
#include "widgets/utility/get_text_with_max.h"
|
||||
|
||||
#include <QAction>
|
||||
|
|
@ -67,8 +64,6 @@
|
|||
#include <libcockatrice/network/client/remote/remote_client.h>
|
||||
#include <libcockatrice/network/server/local/local_server.h>
|
||||
#include <libcockatrice/network/server/local/local_server_interface.h>
|
||||
#include <libcockatrice/protocol/pb/event_connection_closed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
|
||||
#include <libcockatrice/protocol/pb/game_replay.pb.h>
|
||||
#include <libcockatrice/protocol/pb/room_commands.pb.h>
|
||||
|
||||
|
|
@ -100,59 +95,9 @@ void MainWindow::updateTabMenu(const QList<QMenu *> &newMenuList)
|
|||
menuBar()->insertMenu(helpMenu->menuAction(), tabMenu);
|
||||
}
|
||||
|
||||
void MainWindow::processConnectionClosedEvent(const Event_ConnectionClosed &event)
|
||||
{
|
||||
client->disconnectFromServer();
|
||||
QString reasonStr;
|
||||
switch (event.reason()) {
|
||||
case Event_ConnectionClosed::USER_LIMIT_REACHED:
|
||||
reasonStr = tr("The server has reached its maximum user capacity, please check back later.");
|
||||
break;
|
||||
case Event_ConnectionClosed::TOO_MANY_CONNECTIONS:
|
||||
reasonStr = tr("There are too many concurrent connections from your address.");
|
||||
break;
|
||||
case Event_ConnectionClosed::BANNED: {
|
||||
reasonStr = tr("Banned by moderator");
|
||||
if (event.has_end_time())
|
||||
reasonStr.append(
|
||||
"\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString()));
|
||||
else
|
||||
reasonStr.append("\n" + tr("This ban lasts indefinitely."));
|
||||
if (event.has_reason_str())
|
||||
reasonStr.append("\n\n" + QString::fromStdString(event.reason_str()));
|
||||
break;
|
||||
}
|
||||
case Event_ConnectionClosed::SERVER_SHUTDOWN:
|
||||
reasonStr = tr("Scheduled server shutdown.");
|
||||
break;
|
||||
case Event_ConnectionClosed::USERNAMEINVALID:
|
||||
reasonStr = tr("Invalid username.");
|
||||
break;
|
||||
case Event_ConnectionClosed::LOGGEDINELSEWERE:
|
||||
reasonStr = tr("You have been logged out due to logging in at another location.");
|
||||
break;
|
||||
default:
|
||||
reasonStr = QString::fromStdString(event.reason_str());
|
||||
}
|
||||
QMessageBox::critical(this, tr("Connection closed"),
|
||||
tr("The server has terminated your connection.\nReason: %1").arg(reasonStr));
|
||||
}
|
||||
|
||||
void MainWindow::processServerShutdownEvent(const Event_ServerShutdown &event)
|
||||
{
|
||||
serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running "
|
||||
"games will be lost.\nReason for shutdown: %1",
|
||||
"", event.minutes())
|
||||
.arg(QString::fromStdString(event.reason())));
|
||||
serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
|
||||
serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
|
||||
serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
|
||||
serverShutdownMessageBox.setVisible(true);
|
||||
}
|
||||
|
||||
void MainWindow::statusChanged(ClientStatus _status)
|
||||
{
|
||||
setClientStatusTitle();
|
||||
connectionController->refreshWindowTitle();
|
||||
switch (_status) {
|
||||
case StatusDisconnected:
|
||||
tabSupervisor->stop();
|
||||
|
|
@ -177,51 +122,16 @@ void MainWindow::statusChanged(ClientStatus _status)
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::userInfoReceived(const ServerInfo_User &info)
|
||||
{
|
||||
tabSupervisor->start(info);
|
||||
}
|
||||
|
||||
void MainWindow::registerAccepted()
|
||||
{
|
||||
QMessageBox::information(this, tr("Success"), tr("Registration accepted.\nWill now login."));
|
||||
}
|
||||
|
||||
void MainWindow::registerAcceptedNeedsActivate()
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
void MainWindow::activateAccepted()
|
||||
{
|
||||
QMessageBox::information(this, tr("Success"), tr("Account activation accepted.\nWill now login."));
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
void MainWindow::actConnect()
|
||||
{
|
||||
dlgConnect = new DlgConnect(this);
|
||||
connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &MainWindow::actForgotPasswordRequest);
|
||||
|
||||
if (dlgConnect->exec()) {
|
||||
client->connectToServer(dlgConnect->getHost(), static_cast<unsigned int>(dlgConnect->getPort()),
|
||||
dlgConnect->getPlayerName(), dlgConnect->getPassword());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::actRegister()
|
||||
{
|
||||
DlgRegister dlg(this);
|
||||
if (dlg.exec()) {
|
||||
client->registerToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()), dlg.getPlayerName(),
|
||||
dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName());
|
||||
}
|
||||
connectionController->connectToServer();
|
||||
}
|
||||
|
||||
void MainWindow::actDisconnect()
|
||||
{
|
||||
client->disconnectFromServer();
|
||||
connectionController->disconnectFromServer();
|
||||
}
|
||||
|
||||
void MainWindow::actSinglePlayer()
|
||||
|
|
@ -373,292 +283,9 @@ void MainWindow::actOpenSettingsFolder()
|
|||
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
|
||||
}
|
||||
|
||||
void MainWindow::serverTimeout()
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Server timeout"));
|
||||
actConnect();
|
||||
}
|
||||
|
||||
void MainWindow::loginError(Response::ResponseCode r,
|
||||
QString reasonStr,
|
||||
quint32 endTime,
|
||||
QList<QString> missingFeatures)
|
||||
{
|
||||
switch (r) {
|
||||
case Response::RespClientUpdateRequired: {
|
||||
QString formattedMissingFeatures;
|
||||
formattedMissingFeatures = "Missing Features: ";
|
||||
for (int i = 0; i < missingFeatures.size(); ++i)
|
||||
formattedMissingFeatures.append(QString("\n %1").arg(QChar(0x2022)) + " " +
|
||||
missingFeatures.value(i));
|
||||
|
||||
QMessageBox msgBox;
|
||||
msgBox.setIcon(QMessageBox::Critical);
|
||||
msgBox.setWindowTitle(tr("Failed Login"));
|
||||
msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") +
|
||||
"\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'."));
|
||||
msgBox.setDetailedText(formattedMissingFeatures);
|
||||
msgBox.exec();
|
||||
break;
|
||||
}
|
||||
case Response::RespWrongPassword:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Incorrect username or password. Please check your authentication information and try again."));
|
||||
break;
|
||||
case Response::RespWouldOverwriteOldSession:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("There is already an active session using this user name.\nPlease close that "
|
||||
"session first and re-login."));
|
||||
break;
|
||||
case Response::RespUserIsBanned: {
|
||||
QString bannedStr;
|
||||
if (endTime)
|
||||
bannedStr = tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString());
|
||||
else
|
||||
bannedStr = tr("You are banned indefinitely.");
|
||||
if (!reasonStr.isEmpty())
|
||||
bannedStr.append("\n\n" + reasonStr);
|
||||
|
||||
QMessageBox::critical(this, tr("Error"), bannedStr);
|
||||
break;
|
||||
}
|
||||
case Response::RespUsernameInvalid: {
|
||||
QMessageBox::critical(this, tr("Error"), extractInvalidUsernameMessage(reasonStr));
|
||||
break;
|
||||
}
|
||||
case Response::RespRegistrationRequired:
|
||||
if (QMessageBox::question(this, tr("Error"),
|
||||
tr("This server requires user registration. Do you want to register now?"),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
actRegister();
|
||||
}
|
||||
break;
|
||||
case Response::RespClientIdRequired:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("This server requires client IDs. Your client is either failing to generate an ID or you are "
|
||||
"running a modified client.\nPlease close and reopen your client to try again."));
|
||||
break;
|
||||
case Response::RespContextError:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("An internal error has occurred, please close and reopen Cockatrice before trying "
|
||||
"again.\nIf the error persists, ensure you are running the latest version of the "
|
||||
"software and if needed contact the software developers."));
|
||||
break;
|
||||
case Response::RespAccountNotActivated: {
|
||||
bool ok = false;
|
||||
QString token = getTextWithMax(this, tr("Account activation"),
|
||||
tr("Your account has not been activated yet.\nYou need to provide "
|
||||
"the activation token received in the activation email."),
|
||||
QLineEdit::Normal, QString(), &ok);
|
||||
if (ok && !token.isEmpty()) {
|
||||
client->activateToServer(token);
|
||||
return;
|
||||
}
|
||||
client->disconnectFromServer();
|
||||
break;
|
||||
}
|
||||
case Response::RespServerFull: {
|
||||
QMessageBox::critical(this, tr("Server Full"),
|
||||
tr("The server has reached its maximum user capacity, please check back later."));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Unknown login error: %1").arg(static_cast<int>(r)) +
|
||||
tr("\nThis usually means that your client version is out of date, and the server "
|
||||
"sent a reply your client doesn't understand."));
|
||||
break;
|
||||
}
|
||||
actConnect();
|
||||
}
|
||||
|
||||
QString MainWindow::extractInvalidUsernameMessage(QString &in)
|
||||
{
|
||||
QString out = tr("Invalid username.") + "<br/>";
|
||||
QStringList rules = in.split(QChar('|'));
|
||||
if (rules.size() == 7 || rules.size() == 9) {
|
||||
out += tr("Your username must respect these rules:") + "<ul>";
|
||||
|
||||
out += "<li>" + tr("is %1 - %2 characters long").arg(rules.at(0)).arg(rules.at(1)) + "</li>";
|
||||
out += "<li>" + tr("can %1 contain lowercase characters").arg((rules.at(2).toInt() > 0) ? "" : tr("NOT")) +
|
||||
"</li>";
|
||||
out += "<li>" + tr("can %1 contain uppercase characters").arg((rules.at(3).toInt() > 0) ? "" : tr("NOT")) +
|
||||
"</li>";
|
||||
out +=
|
||||
"<li>" + tr("can %1 contain numeric characters").arg((rules.at(4).toInt() > 0) ? "" : tr("NOT")) + "</li>";
|
||||
|
||||
if (rules.at(6).size() > 0)
|
||||
out += "<li>" + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "</li>";
|
||||
|
||||
out += "<li>" +
|
||||
tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) +
|
||||
"</li>";
|
||||
|
||||
if (rules.size() == 9) {
|
||||
if (rules.at(7).size() > 0) {
|
||||
QString words = rules.at(7).toHtmlEscaped();
|
||||
if (words.startsWith("\n")) {
|
||||
out += tr("no unacceptable language as specified by these server rules:",
|
||||
"note that the following lines will not be translated");
|
||||
for (QString &line : words.split("\n", Qt::SkipEmptyParts)) {
|
||||
out += "<li>" + line + "</li>";
|
||||
}
|
||||
} else {
|
||||
out += "<li>" + tr("can not contain any of the following words: %1").arg(words) + "</li>";
|
||||
}
|
||||
}
|
||||
|
||||
if (rules.at(8).size() > 0)
|
||||
out += "<li>" +
|
||||
tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) +
|
||||
"</li>";
|
||||
}
|
||||
|
||||
out += "</ul>";
|
||||
} else {
|
||||
out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username.");
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime)
|
||||
{
|
||||
switch (r) {
|
||||
case Response::RespRegistrationDisabled:
|
||||
QMessageBox::critical(this, tr("Registration denied"),
|
||||
tr("Registration is currently disabled on this server"));
|
||||
break;
|
||||
case Response::RespUserAlreadyExists:
|
||||
QMessageBox::critical(this, tr("Registration denied"),
|
||||
tr("There is already an existing account with the same user name."));
|
||||
break;
|
||||
case Response::RespEmailRequiredToRegister:
|
||||
QMessageBox::critical(this, tr("Registration denied"),
|
||||
tr("It's mandatory to specify a valid email address when registering."));
|
||||
break;
|
||||
case Response::RespEmailBlackListed:
|
||||
if (reasonStr.isEmpty()) {
|
||||
reasonStr =
|
||||
"The email address provider used during registration has been blocked from use on this server.";
|
||||
}
|
||||
QMessageBox::critical(this, tr("Registration denied"), reasonStr);
|
||||
break;
|
||||
case Response::RespTooManyRequests:
|
||||
QMessageBox::critical(
|
||||
this, tr("Registration denied"),
|
||||
tr("It appears you are attempting to register a new account on this server yet you already have an "
|
||||
"account registered with the email provided. This server restricts the number of accounts a user "
|
||||
"can register per address. Please contact the server operator for further assistance or to obtain "
|
||||
"your credential information."));
|
||||
break;
|
||||
case Response::RespPasswordTooShort:
|
||||
QMessageBox::critical(this, tr("Registration denied"), tr("Password too short."));
|
||||
break;
|
||||
case Response::RespUserIsBanned: {
|
||||
QString bannedStr;
|
||||
if (endTime)
|
||||
bannedStr = tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString());
|
||||
else
|
||||
bannedStr = tr("You are banned indefinitely.");
|
||||
if (!reasonStr.isEmpty())
|
||||
bannedStr.append("\n\n" + reasonStr);
|
||||
|
||||
QMessageBox::critical(this, tr("Error"), bannedStr);
|
||||
break;
|
||||
}
|
||||
case Response::RespUsernameInvalid: {
|
||||
QMessageBox::critical(this, tr("Error"), extractInvalidUsernameMessage(reasonStr));
|
||||
break;
|
||||
}
|
||||
case Response::RespRegistrationFailed:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Registration failed for a technical problem on the server."));
|
||||
break;
|
||||
case Response::RespNotConnected:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The connection to the server has been lost."));
|
||||
break;
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Unknown registration error: %1").arg(static_cast<int>(r)) +
|
||||
tr("\nThis usually means that your client version is out of date, and the server "
|
||||
"sent a reply your client doesn't understand."));
|
||||
}
|
||||
actRegister();
|
||||
}
|
||||
|
||||
void MainWindow::activateError()
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Account activation failed"));
|
||||
client->disconnectFromServer();
|
||||
actConnect();
|
||||
}
|
||||
|
||||
void MainWindow::socketError(const QString &errorStr)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Socket error: %1").arg(errorStr));
|
||||
actConnect();
|
||||
}
|
||||
|
||||
void MainWindow::protocolVersionMismatch(int localVersion, int remoteVersion)
|
||||
{
|
||||
if (localVersion > remoteVersion)
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice "
|
||||
"version or connect to a suitable server.\nLocal version is %1, remote version is %2.")
|
||||
.arg(localVersion)
|
||||
.arg(remoteVersion));
|
||||
else
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\nLocal "
|
||||
"version is %1, remote version is %2.")
|
||||
.arg(localVersion)
|
||||
.arg(remoteVersion));
|
||||
}
|
||||
|
||||
void MainWindow::setClientStatusTitle()
|
||||
{
|
||||
switch (client->getStatus()) {
|
||||
case StatusConnecting:
|
||||
setWindowTitle(appName + " - " + tr("Connecting to %1...").arg(client->peerName()));
|
||||
break;
|
||||
case StatusRegistering:
|
||||
setWindowTitle(appName + " - " +
|
||||
tr("Registering to %1 as %2...").arg(client->peerName()).arg(client->getUserName()));
|
||||
break;
|
||||
case StatusDisconnected:
|
||||
setWindowTitle(appName + " - " + tr("Disconnected"));
|
||||
break;
|
||||
case StatusLoggingIn:
|
||||
setWindowTitle(appName + " - " + tr("Connected, logging in at %1").arg(client->peerName()));
|
||||
break;
|
||||
case StatusLoggedIn:
|
||||
setWindowTitle(client->getUserName() + "@" + client->peerName());
|
||||
break;
|
||||
case StatusRequestingForgotPassword:
|
||||
setWindowTitle(
|
||||
appName + " - " +
|
||||
tr("Requesting forgotten password to %1 as %2...").arg(client->peerName()).arg(client->getUserName()));
|
||||
break;
|
||||
case StatusSubmitForgotPasswordChallenge:
|
||||
setWindowTitle(
|
||||
appName + " - " +
|
||||
tr("Requesting forgotten password to %1 as %2...").arg(client->peerName()).arg(client->getUserName()));
|
||||
break;
|
||||
case StatusSubmitForgotPasswordReset:
|
||||
setWindowTitle(
|
||||
appName + " - " +
|
||||
tr("Requesting forgotten password to %1 as %2...").arg(client->peerName()).arg(client->getUserName()));
|
||||
break;
|
||||
default:
|
||||
setWindowTitle(appName);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::retranslateUi()
|
||||
{
|
||||
setClientStatusTitle();
|
||||
connectionController->refreshWindowTitle();
|
||||
|
||||
aConnect->setText(tr("&Connect..."));
|
||||
aDisconnect->setText(tr("&Disconnect"));
|
||||
|
|
@ -717,9 +344,9 @@ void MainWindow::createActions()
|
|||
aFullScreen->setCheckable(true);
|
||||
connect(aFullScreen, &QAction::toggled, this, &MainWindow::actFullScreen);
|
||||
aRegister = new QAction(this);
|
||||
connect(aRegister, &QAction::triggered, this, &MainWindow::actRegister);
|
||||
connect(aRegister, &QAction::triggered, connectionController, &ConnectionController::registerToServer);
|
||||
aForgotPassword = new QAction(this);
|
||||
connect(aForgotPassword, &QAction::triggered, this, &MainWindow::actForgotPasswordRequest);
|
||||
connect(aForgotPassword, &QAction::triggered, connectionController, &ConnectionController::forgotPasswordRequest);
|
||||
aSettings = new QAction(this);
|
||||
connect(aSettings, &QAction::triggered, this, &MainWindow::actSettings);
|
||||
aExit = new QAction(this);
|
||||
|
|
@ -844,38 +471,22 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
&MainWindow::pixmapCacheSizeChanged);
|
||||
pixmapCacheSizeChanged(SettingsCache::instance().getPixmapCacheSize());
|
||||
|
||||
client = new RemoteClient(nullptr, &SettingsCache::instance());
|
||||
connect(client, &RemoteClient::connectionClosedEventReceived, this, &MainWindow::processConnectionClosedEvent);
|
||||
connect(client, &RemoteClient::serverShutdownEventReceived, this, &MainWindow::processServerShutdownEvent);
|
||||
connect(client, &RemoteClient::loginError, this, &MainWindow::loginError);
|
||||
connect(client, &RemoteClient::socketError, this, &MainWindow::socketError);
|
||||
connect(client, &RemoteClient::serverTimeout, this, &MainWindow::serverTimeout);
|
||||
connect(client, &RemoteClient::statusChanged, this, &MainWindow::statusChanged);
|
||||
connect(client, &RemoteClient::protocolVersionMismatch, this, &MainWindow::protocolVersionMismatch);
|
||||
connect(client, &RemoteClient::userInfoChanged, this, &MainWindow::userInfoReceived, Qt::BlockingQueuedConnection);
|
||||
connect(client, &RemoteClient::notifyUserAboutUpdate, this, &MainWindow::notifyUserAboutUpdate);
|
||||
connect(client, &RemoteClient::registerAccepted, this, &MainWindow::registerAccepted);
|
||||
connect(client, &RemoteClient::registerAcceptedNeedsActivate, this, &MainWindow::registerAcceptedNeedsActivate);
|
||||
connect(client, &RemoteClient::registerError, this, &MainWindow::registerError);
|
||||
connect(client, &RemoteClient::activateAccepted, this, &MainWindow::activateAccepted);
|
||||
connect(client, &RemoteClient::activateError, this, &MainWindow::activateError);
|
||||
connect(client, &RemoteClient::sigForgotPasswordSuccess, this, &MainWindow::forgotPasswordSuccess);
|
||||
connect(client, &RemoteClient::sigForgotPasswordError, this, &MainWindow::forgotPasswordError);
|
||||
connect(client, &RemoteClient::sigPromptForForgotPasswordReset, this, &MainWindow::promptForgotPasswordReset);
|
||||
connect(client, &RemoteClient::sigPromptForForgotPasswordChallenge, this,
|
||||
&MainWindow::promptForgotPasswordChallenge);
|
||||
|
||||
clientThread = new QThread(this);
|
||||
client->moveToThread(clientThread);
|
||||
clientThread->start();
|
||||
connectionController = new ConnectionController(this, this);
|
||||
|
||||
createActions();
|
||||
createMenus();
|
||||
|
||||
tabSupervisor = new TabSupervisor(client, tabsMenu, this);
|
||||
connect(connectionController, &ConnectionController::windowTitleChanged, this, &MainWindow::setWindowTitle);
|
||||
connect(connectionController, &ConnectionController::statusChanged, this, &MainWindow::statusChanged);
|
||||
|
||||
tabSupervisor = new TabSupervisor(connectionController->client(), tabsMenu, this);
|
||||
connect(tabSupervisor, &TabSupervisor::setMenu, this, &MainWindow::updateTabMenu);
|
||||
connect(tabSupervisor, &TabSupervisor::localGameEnded, this, &MainWindow::localGameEnded);
|
||||
connect(tabSupervisor, &TabSupervisor::showWindowIfHidden, this, &MainWindow::showWindowIfHidden);
|
||||
connect(connectionController, &ConnectionController::tabSupervisorStartRequested, tabSupervisor,
|
||||
&TabSupervisor::start);
|
||||
connect(connectionController, &ConnectionController::tabSupervisorStopRequested, tabSupervisor,
|
||||
&TabSupervisor::stop);
|
||||
tabSupervisor->initStartupTabs();
|
||||
|
||||
setCentralWidget(tabSupervisor);
|
||||
|
|
@ -1043,9 +654,6 @@ MainWindow::~MainWindow()
|
|||
cardUpdateProcess->waitForFinished(1000);
|
||||
cardUpdateProcess = nullptr;
|
||||
}
|
||||
|
||||
client->deleteLater();
|
||||
clientThread->wait();
|
||||
}
|
||||
|
||||
void MainWindow::createTrayIcon()
|
||||
|
|
@ -1079,14 +687,6 @@ void MainWindow::actShow()
|
|||
});
|
||||
}
|
||||
|
||||
void MainWindow::promptForgotPasswordChallenge()
|
||||
{
|
||||
DlgForgotPasswordChallenge dlg(this);
|
||||
if (dlg.exec())
|
||||
client->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
|
||||
dlg.getPlayerName(), dlg.getEmail());
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
// workaround Qt bug where closeEvent gets called twice
|
||||
|
|
@ -1116,13 +716,14 @@ void MainWindow::changeEvent(QEvent *event)
|
|||
bHasActivated = true;
|
||||
if (!connectTo.isEmpty()) {
|
||||
qCInfo(WindowMainStartupAutoconnectLog) << "Command line connect to " << connectTo;
|
||||
client->connectToServer(connectTo.host(), connectTo.port(), connectTo.userName(), connectTo.password());
|
||||
connectionController->connectToServerDirect(connectTo.host(), connectTo.port(), connectTo.userName(),
|
||||
connectTo.password());
|
||||
} else if (SettingsCache::instance().servers().getAutoConnect() &&
|
||||
!SettingsCache::instance().debug().getLocalGameOnStartup()) {
|
||||
qCInfo(WindowMainStartupAutoconnectLog) << "Attempting auto-connect...";
|
||||
DlgConnect dlg(this);
|
||||
client->connectToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()), dlg.getPlayerName(),
|
||||
dlg.getPassword());
|
||||
connectionController->connectToServerDirect(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
|
||||
dlg.getPlayerName(), dlg.getPassword());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1173,6 +774,13 @@ void MainWindow::cardDatabaseLoadingFailed()
|
|||
|
||||
void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames)
|
||||
{
|
||||
if (SettingsCache::instance().getAlwaysEnableNewSets()) {
|
||||
CardDatabaseManager::getInstance()->enableAllUnknownSets();
|
||||
const auto reloadOk1 =
|
||||
QtConcurrent::run([] { CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); });
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("New sets found"));
|
||||
msgBox.setIcon(QMessageBox::Question);
|
||||
|
|
@ -1183,6 +791,7 @@ void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknow
|
|||
.arg(unknownSetsNames.join(", ")));
|
||||
|
||||
QPushButton *yesButton = msgBox.addButton(tr("Yes"), QMessageBox::YesRole);
|
||||
QPushButton *yesAlwaysButton = msgBox.addButton(tr("Yes, always enable"), QMessageBox::YesRole);
|
||||
QPushButton *noButton = msgBox.addButton(tr("No"), QMessageBox::NoRole);
|
||||
QPushButton *settingsButton = msgBox.addButton(tr("View sets"), QMessageBox::ActionRole);
|
||||
msgBox.setDefaultButton(yesButton);
|
||||
|
|
@ -1191,7 +800,13 @@ void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknow
|
|||
|
||||
if (msgBox.clickedButton() == yesButton) {
|
||||
CardDatabaseManager::getInstance()->enableAllUnknownSets();
|
||||
const auto reloadOk1 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
|
||||
const auto reloadOk1 =
|
||||
QtConcurrent::run([] { CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); });
|
||||
} else if (msgBox.clickedButton() == yesAlwaysButton) {
|
||||
CardDatabaseManager::getInstance()->enableAllUnknownSets();
|
||||
const auto reloadOk1 =
|
||||
QtConcurrent::run([] { CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); });
|
||||
SettingsCache::instance().setAlwaysEnableNewSets(true);
|
||||
} else if (msgBox.clickedButton() == noButton) {
|
||||
CardDatabaseManager::getInstance()->markAllSetsAsKnown();
|
||||
} else if (msgBox.clickedButton() == settingsButton) {
|
||||
|
|
@ -1381,15 +996,6 @@ void MainWindow::refreshShortcuts()
|
|||
aStatusBar->setShortcuts(shortcuts.getShortcut("MainWindow/aStatusBar"));
|
||||
}
|
||||
|
||||
void MainWindow::notifyUserAboutUpdate()
|
||||
{
|
||||
QMessageBox::information(
|
||||
this, tr("Information"),
|
||||
tr("This server supports additional features that your client doesn't have.\nThis is most likely not a "
|
||||
"problem, but this message might mean there is a new version of Cockatrice available or this server is "
|
||||
"running a custom or pre-release version.\n\nTo update your client, go to Help -> Check for Updates."));
|
||||
}
|
||||
|
||||
void MainWindow::actOpenCustomFolder()
|
||||
{
|
||||
QString dir = SettingsCache::instance().getCustomPicsPath();
|
||||
|
|
@ -1473,7 +1079,7 @@ int MainWindow::getNextCustomSetPrefix(QDir dataDir)
|
|||
void MainWindow::actReloadCardDatabase()
|
||||
{
|
||||
const auto reloadOk1 = QtConcurrent::run([] {
|
||||
CardDatabaseManager::getInstance()->loadCardDatabases();
|
||||
CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify();
|
||||
SettingsCache::instance().downloads().sync();
|
||||
});
|
||||
}
|
||||
|
|
@ -1490,42 +1096,3 @@ void MainWindow::actEditTokens()
|
|||
dlg.exec();
|
||||
CardDatabaseManager::getInstance()->saveCustomTokensToFile();
|
||||
}
|
||||
|
||||
void MainWindow::actForgotPasswordRequest()
|
||||
{
|
||||
DlgForgotPasswordRequest dlg(this);
|
||||
if (dlg.exec())
|
||||
client->requestForgotPasswordToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
|
||||
dlg.getPlayerName());
|
||||
}
|
||||
|
||||
void MainWindow::forgotPasswordSuccess()
|
||||
{
|
||||
QMessageBox::information(
|
||||
this, tr("Reset Password"),
|
||||
tr("Your password has been reset successfully, you can now log in using the new credentials."));
|
||||
SettingsCache::instance().servers().setFPHostName("");
|
||||
SettingsCache::instance().servers().setFPPort("");
|
||||
SettingsCache::instance().servers().setFPPlayerName("");
|
||||
}
|
||||
|
||||
void MainWindow::forgotPasswordError()
|
||||
{
|
||||
QMessageBox::warning(
|
||||
this, tr("Reset Password"),
|
||||
tr("Failed to reset user account password, please contact the server operator to reset your password."));
|
||||
SettingsCache::instance().servers().setFPHostName("");
|
||||
SettingsCache::instance().servers().setFPPort("");
|
||||
SettingsCache::instance().servers().setFPPlayerName("");
|
||||
}
|
||||
|
||||
void MainWindow::promptForgotPasswordReset()
|
||||
{
|
||||
QMessageBox::information(this, tr("Reset Password"),
|
||||
tr("Activation request received, please check your email for an activation token."));
|
||||
DlgForgotPasswordReset dlg(this);
|
||||
if (dlg.exec()) {
|
||||
client->submitForgotPasswordResetToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
|
||||
dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
|
||||
#include "connection_controller/remote_connection_controller.h"
|
||||
#include "widgets/dialogs/dlg_local_game_options.h"
|
||||
|
||||
#include <QList>
|
||||
|
|
@ -68,38 +69,19 @@ public slots:
|
|||
private slots:
|
||||
void updateTabMenu(const QList<QMenu *> &newMenuList);
|
||||
void statusChanged(ClientStatus _status);
|
||||
void processConnectionClosedEvent(const Event_ConnectionClosed &event);
|
||||
void processServerShutdownEvent(const Event_ServerShutdown &event);
|
||||
void serverTimeout();
|
||||
void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime, QList<QString> missingFeatures);
|
||||
void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime);
|
||||
void activateError();
|
||||
void socketError(const QString &errorStr);
|
||||
void protocolVersionMismatch(int localVersion, int remoteVersion);
|
||||
void userInfoReceived(const ServerInfo_User &userInfo);
|
||||
void registerAccepted();
|
||||
void registerAcceptedNeedsActivate();
|
||||
void activateAccepted();
|
||||
void localGameEnded();
|
||||
void pixmapCacheSizeChanged(int newSizeInMBs);
|
||||
void notifyUserAboutUpdate();
|
||||
void actDisconnect();
|
||||
void actSinglePlayer();
|
||||
void actWatchReplay();
|
||||
void actFullScreen(bool checked);
|
||||
void actRegister();
|
||||
void actSettings();
|
||||
void actForgotPasswordRequest();
|
||||
void actAbout();
|
||||
void actTips();
|
||||
void actUpdate();
|
||||
void actViewLog();
|
||||
void actOpenSettingsFolder();
|
||||
void forgotPasswordSuccess();
|
||||
void forgotPasswordError();
|
||||
void promptForgotPasswordReset();
|
||||
void actShow();
|
||||
void promptForgotPasswordChallenge();
|
||||
void showWindowIfHidden();
|
||||
|
||||
void cardUpdateError(QProcess::ProcessError err);
|
||||
|
|
@ -125,7 +107,6 @@ private slots:
|
|||
private:
|
||||
static const QString appName;
|
||||
static const QStringList fileNameFilters;
|
||||
void setClientStatusTitle();
|
||||
void retranslateUi();
|
||||
void createActions();
|
||||
void createMenus();
|
||||
|
|
@ -152,14 +133,11 @@ private:
|
|||
|
||||
TabSupervisor *tabSupervisor;
|
||||
WndSets *wndSets;
|
||||
RemoteClient *client;
|
||||
QThread *clientThread;
|
||||
ConnectionController *connectionController;
|
||||
LocalServer *localServer;
|
||||
bool bHasActivated, askedForDbUpdater;
|
||||
QMessageBox serverShutdownMessageBox;
|
||||
QProcess *cardUpdateProcess;
|
||||
DlgViewLog *logviewDialog;
|
||||
DlgConnect *dlgConnect;
|
||||
GameReplay *replay;
|
||||
DlgTipOfTheDay *tip;
|
||||
QUrl connectTo;
|
||||
|
|
@ -180,7 +158,6 @@ public:
|
|||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void changeEvent(QEvent *event) override;
|
||||
QString extractInvalidUsernameMessage(QString &in);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
|||
#include "card_database.h"
|
||||
|
||||
#include "../relation/card_relation.h"
|
||||
#include "card_database_manager.h"
|
||||
#include "parser/cockatrice_xml_4.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
|
@ -60,6 +61,15 @@ void CardDatabase::loadCardDatabases()
|
|||
loadStatus = loader->loadCardDatabases();
|
||||
}
|
||||
|
||||
void CardDatabase::reloadCardDatabasesAndNotify()
|
||||
{
|
||||
loadCardDatabases();
|
||||
|
||||
if (loadStatus == Ok) {
|
||||
notifyEnabledSetsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool CardDatabase::saveCustomTokensToFile()
|
||||
{
|
||||
return loader->saveCustomTokensToFile();
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue