mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
Compare commits
50 commits
ab243cc499
...
2b33cbfb66
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b33cbfb66 | ||
|
|
ff3e45fd9c | ||
|
|
4f998dd9fc | ||
|
|
6179765d30 | ||
|
|
de08532b9a | ||
|
|
1dc20176b3 | ||
|
|
1ff7ffcaa4 | ||
|
|
7d867b9745 | ||
|
|
69e8f80fa1 | ||
|
|
df3defa890 | ||
|
|
030e4f5871 | ||
|
|
6e5c58069b | ||
|
|
ebeae48652 | ||
|
|
36f998e466 | ||
|
|
760fe88fa3 | ||
|
|
fff506dbbe | ||
|
|
8e0bdafb14 | ||
|
|
9b0d62c152 | ||
|
|
6cdeb0c428 | ||
|
|
745e94f332 | ||
|
|
b01e107908 | ||
|
|
8d30ac54f0 | ||
|
|
fb482f037e | ||
|
|
0f0e46a177 | ||
|
|
2fe59d6326 | ||
|
|
048fe247f4 | ||
|
|
0f003eabf9 | ||
|
|
ada774f5cc | ||
|
|
b0e566ed54 | ||
|
|
0d09e633e3 | ||
|
|
9677fad342 | ||
|
|
e8ec28572f | ||
|
|
0c725f9a03 | ||
|
|
c011ea7ceb | ||
|
|
1dc54617ba | ||
|
|
aa96d81e4b | ||
|
|
14ecfff700 | ||
|
|
61e6a9913e | ||
|
|
4d4ddd4278 | ||
|
|
d6fbfb32a1 | ||
|
|
3ec62df3e7 | ||
|
|
fcfb14cf56 | ||
|
|
35ebae8d7f | ||
|
|
4e9d148163 | ||
|
|
425b16ea0d | ||
|
|
45c7ff6f87 | ||
|
|
d974501277 | ||
|
|
9bf2202739 | ||
|
|
03de1af678 | ||
|
|
3dc9dba67a |
150 changed files with 5969 additions and 1515 deletions
|
|
@ -8,6 +8,7 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
|
||||||
gtest \
|
gtest \
|
||||||
mariadb-libs \
|
mariadb-libs \
|
||||||
ninja \
|
ninja \
|
||||||
|
openssl \
|
||||||
protobuf \
|
protobuf \
|
||||||
qt6-base \
|
qt6-base \
|
||||||
qt6-declarative \
|
qt6-declarative \
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ RUN apt-get update && \
|
||||||
libprotobuf-dev \
|
libprotobuf-dev \
|
||||||
libqt6multimedia6 \
|
libqt6multimedia6 \
|
||||||
libqt6sql6-mysql \
|
libqt6sql6-mysql \
|
||||||
|
libssl-dev \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
protobuf-compiler \
|
protobuf-compiler \
|
||||||
qt6-image-formats-plugins \
|
qt6-image-formats-plugins \
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ RUN apt-get update && \
|
||||||
libprotobuf-dev \
|
libprotobuf-dev \
|
||||||
libqt6multimedia6 \
|
libqt6multimedia6 \
|
||||||
libqt6sql6-mysql \
|
libqt6sql6-mysql \
|
||||||
|
libssl-dev \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
protobuf-compiler \
|
protobuf-compiler \
|
||||||
qt6-image-formats-plugins \
|
qt6-image-formats-plugins \
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ RUN dnf install -y \
|
||||||
git \
|
git \
|
||||||
mariadb-devel \
|
mariadb-devel \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
|
openssl-devel \
|
||||||
protobuf-devel \
|
protobuf-devel \
|
||||||
qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
||||||
qt6-qtimageformats \
|
qt6-qtimageformats \
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ RUN dnf install -y \
|
||||||
git \
|
git \
|
||||||
mariadb-devel \
|
mariadb-devel \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
|
openssl-devel \
|
||||||
protobuf-devel \
|
protobuf-devel \
|
||||||
qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
||||||
qt6-qtimageformats \
|
qt6-qtimageformats \
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ RUN apt-get update && \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libprotobuf-dev \
|
libprotobuf-dev \
|
||||||
libqt6sql6-mysql \
|
libqt6sql6-mysql \
|
||||||
|
libssl-dev \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
protobuf-compiler \
|
protobuf-compiler \
|
||||||
qt6-tools-dev \
|
qt6-tools-dev \
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ RUN apt-get update && \
|
||||||
libprotobuf-dev \
|
libprotobuf-dev \
|
||||||
libqt6multimedia6 \
|
libqt6multimedia6 \
|
||||||
libqt6sql6-mysql \
|
libqt6sql6-mysql \
|
||||||
|
libssl-dev \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
protobuf-compiler \
|
protobuf-compiler \
|
||||||
qt6-image-formats-plugins \
|
qt6-image-formats-plugins \
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ RUN apt-get update && \
|
||||||
libprotobuf-dev \
|
libprotobuf-dev \
|
||||||
libqt6multimedia6 \
|
libqt6multimedia6 \
|
||||||
libqt6sql6-mysql \
|
libqt6sql6-mysql \
|
||||||
|
libssl-dev \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
protobuf-compiler \
|
protobuf-compiler \
|
||||||
qt6-image-formats-plugins \
|
qt6-image-formats-plugins \
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,9 @@ if [[ $MAKE_TEST ]]; then
|
||||||
fi
|
fi
|
||||||
if [[ $USE_CCACHE ]]; then
|
if [[ $USE_CCACHE ]]; then
|
||||||
flags+=("-DUSE_CCACHE=1")
|
flags+=("-DUSE_CCACHE=1")
|
||||||
|
# PCH-aware caching is required or ccache refuses to cache any TU that
|
||||||
|
# consumes a precompiled header, silently recompiling everything on every run.
|
||||||
|
ccache --set-config sloppiness=pch_defines,time_macros
|
||||||
if [[ $CCACHE_SIZE ]]; then
|
if [[ $CCACHE_SIZE ]]; then
|
||||||
# note, this setting persists after running the script
|
# note, this setting persists after running the script
|
||||||
ccache --max-size "$CCACHE_SIZE"
|
ccache --max-size "$CCACHE_SIZE"
|
||||||
|
|
|
||||||
12
.github/workflows/desktop-build.yml
vendored
12
.github/workflows/desktop-build.yml
vendored
|
|
@ -152,7 +152,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
|
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
|
||||||
CCACHE_EVICTION_AGE: 7d
|
CCACHE_EVICTION_AGE: 7d
|
||||||
CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
||||||
CMAKE_GENERATOR: 'Ninja'
|
CMAKE_GENERATOR: 'Ninja'
|
||||||
NAME: ${{ matrix.distro }}${{ matrix.version }}
|
NAME: ${{ matrix.distro }}${{ matrix.version }}
|
||||||
|
|
||||||
|
|
@ -176,8 +176,12 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
source .ci/docker.sh
|
source .ci/docker.sh
|
||||||
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
|
args=()
|
||||||
--cmake-generator "$CMAKE_GENERATOR"
|
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
|
||||||
|
args+=(--ccache "$CCACHE_SIZE")
|
||||||
|
args+=(--cmake-generator "$CMAKE_GENERATOR")
|
||||||
|
|
||||||
|
RUN --server --debug --test "${args[@]}"
|
||||||
|
|
||||||
- name: "Build release package"
|
- name: "Build release package"
|
||||||
id: build
|
id: build
|
||||||
|
|
@ -338,7 +342,7 @@ jobs:
|
||||||
timeout-minutes: 100
|
timeout-minutes: 100
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.cache/
|
CCACHE_DIR: ${{ github.workspace }}/.cache/
|
||||||
CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
# This file sets all the variables shared between the projects
|
# This file sets all the variables shared between the projects
|
||||||
# like the installation path, compilation flags etc..
|
# like the installation path, compilation flags etc..
|
||||||
|
|
||||||
# cmake 3.16 is required if using qt6
|
# 3.16 required for Qt6 and target_precompile_headers()
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# Use compiler cache (ccache)
|
# Use compiler cache (ccache)
|
||||||
option(USE_CCACHE "Cache the build results with ccache" OFF)
|
option(USE_CCACHE "Cache the build results with ccache" ON)
|
||||||
# Treat warnings as errors (Debug builds only)
|
# Treat warnings as errors (Debug builds only)
|
||||||
option(WARNING_AS_ERROR "Treat warnings as errors in debug builds" ON)
|
option(WARNING_AS_ERROR "Treat warnings as errors in debug builds" ON)
|
||||||
# Check for translation updates
|
# Check for translation updates
|
||||||
|
|
@ -39,13 +39,24 @@ else()
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_CCACHE)
|
# ccache does not support MSVC and must not auto-engage on Windows
|
||||||
|
# (it is installed unintentionally on the Windows CI runner).
|
||||||
|
# NOTE: this keys off the target OS, so a mingw/Ninja configuration on Windows
|
||||||
|
# also opts out of ccache even though the GNUCXX branch below supports it.
|
||||||
|
if(USE_CCACHE AND NOT WIN32)
|
||||||
find_program(CCACHE_PROGRAM ccache)
|
find_program(CCACHE_PROGRAM ccache)
|
||||||
if(CCACHE_PROGRAM)
|
if(CCACHE_PROGRAM)
|
||||||
# Support Unix Makefiles and Ninja
|
# Support Unix Makefiles and Ninja
|
||||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
|
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
|
||||||
|
# PCH-aware caching, matching .ci/compile.sh: without this ccache refuses
|
||||||
|
# to cache any TU that consumes a precompiled header, so every PCH-backed
|
||||||
|
# target recompiles from scratch on each build.
|
||||||
|
execute_process(COMMAND ${CCACHE_PROGRAM} --set-config sloppiness=pch_defines,time_macros)
|
||||||
message(STATUS "Found CCache ${CCACHE_PROGRAM}")
|
message(STATUS "Found CCache ${CCACHE_PROGRAM}")
|
||||||
endif()
|
endif()
|
||||||
|
elseif(USE_CCACHE AND WIN32)
|
||||||
|
# An explicit opt-in must not disappear silently on Windows.
|
||||||
|
message(STATUS "ccache disabled: not supported for the MSVC toolchain on Windows")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 OR USE_VCPKG)
|
if(WIN32 OR USE_VCPKG)
|
||||||
|
|
@ -184,6 +195,9 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# Reduce compiler I/O by using pipes between stages instead of temp files
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
|
||||||
else()
|
else()
|
||||||
# other: osx/llvm, bsd/llvm
|
# other: osx/llvm, bsd/llvm
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
|
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
|
||||||
|
|
@ -192,6 +206,9 @@ else()
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
|
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Reduce compiler I/O by using pipes between stages instead of temp files
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
|
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
|
||||||
|
|
@ -239,11 +256,6 @@ if(WIN32)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
if(OPENSSL_FOUND)
|
if(OPENSSL_FOUND)
|
||||||
include_directories(${OPENSSL_INCLUDE_DIRS})
|
include_directories(${OPENSSL_INCLUDE_DIRS})
|
||||||
else()
|
|
||||||
message(
|
|
||||||
WARNING
|
|
||||||
"Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime."
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ RUN apt-get update \
|
||||||
libmariadb-dev-compat \
|
libmariadb-dev-compat \
|
||||||
libprotobuf-dev \
|
libprotobuf-dev \
|
||||||
libqt6sql6-mysql \
|
libqt6sql6-mysql \
|
||||||
|
libssl-dev \
|
||||||
qt6-websockets-dev \
|
qt6-websockets-dev \
|
||||||
protobuf-compiler \
|
protobuf-compiler \
|
||||||
qt6-tools-dev \
|
qt6-tools-dev \
|
||||||
|
|
@ -42,6 +43,7 @@ RUN apt-get update \
|
||||||
libprotobuf32t64 \
|
libprotobuf32t64 \
|
||||||
libqt6sql6-mysql \
|
libqt6sql6-mysql \
|
||||||
libqt6websockets6 \
|
libqt6websockets6 \
|
||||||
|
libssl3 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
|
||||||
18
README.md
18
README.md
|
|
@ -149,15 +149,15 @@ You can then
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
The following flags (with their non-default values) can be passed to `cmake`:
|
The following flags (with their non-default values) can be passed to `cmake`:
|
||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
| --- | --- |
|
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `-DWITH_SERVER=1` | Build <kbd>Servatrice</kbd> server |
|
| `-DWITH_SERVER=1` | Build <kbd>Servatrice</kbd> server |
|
||||||
| `-DWITH_CLIENT=0` | Don't build <kbd>Cockatrice</kbd> client |
|
| `-DWITH_CLIENT=0` | Don't build <kbd>Cockatrice</kbd> client |
|
||||||
| `-DWITH_ORACLE=0` | Don't build <kbd>Oracle</kbd> card database tool |
|
| `-DWITH_ORACLE=0` | Don't build <kbd>Oracle</kbd> card database tool |
|
||||||
| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode<br> Enables extra logging output, debug symbols, and much more verbose compiler warnings |
|
| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode<br> Enables extra logging output, debug symbols, and much more verbose compiler warnings |
|
||||||
| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
|
| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
|
||||||
| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code<br> **Note:** `make clean` will remove the .ts files |
|
| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code<br> **Note:** `make clean` will remove the .ts files |
|
||||||
| `-DTEST=1` | Enable regression tests<br> **Note:** `make test` to run tests, *googletest* will be downloaded if not available |
|
| `-DTEST=1` | Enable regression tests<br> **Note:** `make test` to run tests, *googletest* will be downloaded if not available |
|
||||||
|
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
|
|
|
||||||
24
cmake/pch/qtcore_pch.h
Normal file
24
cmake/pch/qtcore_pch.h
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/** @file qtcore_pch.h
|
||||||
|
* @brief Precompiled header for all Qt targets (Qt Core only).
|
||||||
|
*
|
||||||
|
* Safe for every target that links Qt Core, including the headless
|
||||||
|
* Servatrice binary. Keep this header free of any widget/gui types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QBasicTimer>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QList>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QMetaObject>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVariant>
|
||||||
30
cmake/pch/qtwidgets_pch.h
Normal file
30
cmake/pch/qtwidgets_pch.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/** @file qtwidgets_pch.h
|
||||||
|
* @brief Precompiled header for GUI targets (Cockatrice client, Oracle).
|
||||||
|
*
|
||||||
|
* Includes the Qt Core precompiled header plus the heavy Gui, Widgets and
|
||||||
|
* Network layers that virtually every client translation unit re-parses.
|
||||||
|
* Do not use on Servatrice (headless, QT_DONT_USE_QTGUI).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qtcore_pch.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QGraphicsItem>
|
||||||
|
#include <QGraphicsScene>
|
||||||
|
#include <QGraphicsView>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include <QToolBar>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QWidget>
|
||||||
|
|
@ -166,6 +166,7 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
|
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
|
||||||
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
|
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
|
||||||
src/interface/widgets/cards/art_crop_attribution.cpp
|
src/interface/widgets/cards/art_crop_attribution.cpp
|
||||||
|
src/interface/widgets/cards/card_art_utils.cpp
|
||||||
src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
|
src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
|
||||||
src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
|
src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
|
||||||
src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
|
src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
|
||||||
|
|
@ -214,6 +215,7 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
|
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
|
||||||
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
||||||
src/interface/widgets/deck_editor/deck_state_manager.cpp
|
src/interface/widgets/deck_editor/deck_state_manager.cpp
|
||||||
|
src/interface/widgets/deck_editor/deck_zone_dialog.cpp
|
||||||
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
|
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
|
||||||
src/interface/widgets/general/background_sources.cpp
|
src/interface/widgets/general/background_sources.cpp
|
||||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||||
|
|
@ -517,6 +519,8 @@ qt6_add_executable(
|
||||||
MANUAL_FINALIZATION
|
MANUAL_FINALIZATION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_precompile_headers(cockatrice PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtwidgets_pch.h")
|
||||||
|
|
||||||
qt6_add_shaders(
|
qt6_add_shaders(
|
||||||
cockatrice
|
cockatrice
|
||||||
"onboarding_shaders"
|
"onboarding_shaders"
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ In this list of examples below, each entry has an explanation and can be clicked
|
||||||
<dt><u>E</u>dition:</dt>
|
<dt><u>E</u>dition:</dt>
|
||||||
<dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd>
|
<dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd>
|
||||||
<dd>[e:lea OR e:leb](#e:lea OR e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
|
<dd>[e:lea OR e:leb](#e:lea OR e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
|
||||||
|
<dd>[e<8ED](#e<8ED) <small>(Cards that appear before 8th edition)</small></dd>
|
||||||
|
|
||||||
<dt>Negate:</dt>
|
<dt>Negate:</dt>
|
||||||
<dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd>
|
<dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd>
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject
|
||||||
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent);
|
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CounterState::setValue(int newValue)
|
void CounterState::setValue(int newValue, bool skipDamageAnimation)
|
||||||
{
|
{
|
||||||
if (newValue == value) {
|
if (newValue == value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int old = value;
|
int old = value;
|
||||||
value = newValue;
|
value = newValue;
|
||||||
emit valueChanged(old, newValue);
|
emit valueChanged(old, newValue, skipDamageAnimation);
|
||||||
}
|
}
|
||||||
|
|
@ -35,10 +35,23 @@ public:
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setValue(int newValue);
|
/**
|
||||||
|
* @brief Set the counter value.
|
||||||
|
* @param newValue The new value.
|
||||||
|
* @param skipDamageAnimation When true, valueChanged is emitted with skipDamageAnimation=true, letting views
|
||||||
|
* suppress damage-related feedback (e.g. battlefield shimmer, life counter flash) for values set during replay
|
||||||
|
* rewinds.
|
||||||
|
*/
|
||||||
|
void setValue(int newValue, bool skipDamageAnimation = false);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valueChanged(int oldValue, int newValue);
|
/**
|
||||||
|
* @brief Emitted whenever the value changes.
|
||||||
|
* @param oldValue The previous value.
|
||||||
|
* @param newValue The new value.
|
||||||
|
* @param skipDamageAnimation True when the change should not trigger damage/life-change feedback in views.
|
||||||
|
*/
|
||||||
|
void valueChanged(int oldValue, int newValue, bool skipDamageAnimation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int id;
|
int id;
|
||||||
|
|
|
||||||
|
|
@ -430,12 +430,13 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/,
|
||||||
QString playerName = QString::fromStdString(playerInfo.user_info().name());
|
QString playerName = QString::fromStdString(playerInfo.user_info().name());
|
||||||
emit addPlayerToAutoCompleteList(playerName);
|
emit addPlayerToAutoCompleteList(playerName);
|
||||||
|
|
||||||
if (game->getPlayerManager()->getPlayers().contains(playerId)) {
|
PlayerManager *playerManager = game->getPlayerManager();
|
||||||
|
if (playerManager->getPlayers().contains(playerId) || playerManager->getSpectators().contains(playerId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerInfo.spectator()) {
|
if (playerInfo.spectator()) {
|
||||||
game->getPlayerManager()->addSpectator(playerId, playerInfo);
|
playerManager->addSpectator(playerId, playerInfo);
|
||||||
emit logJoinSpectator(playerName);
|
emit logJoinSpectator(playerName);
|
||||||
emit spectatorJoined(playerInfo);
|
emit spectatorJoined(playerInfo);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
enum EventProcessingOption
|
enum EventProcessingOption
|
||||||
{
|
{
|
||||||
SKIP_REVEAL_WINDOW = 0x0001,
|
SKIP_REVEAL_WINDOW = 0x0001,
|
||||||
SKIP_TAP_ANIMATION = 0x0002
|
SKIP_TAP_ANIMATION = 0x0002,
|
||||||
|
SKIP_DAMAGE_ANIMATION = 0x0004
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrap it in a QFlags typedef
|
// Wrap it in a QFlags typedef
|
||||||
|
|
|
||||||
|
|
@ -262,14 +262,15 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event)
|
||||||
player->addCounter(event.counter_info());
|
player->addCounter(event.counter_info());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
|
void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options)
|
||||||
{
|
{
|
||||||
CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr);
|
CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr);
|
||||||
if (!ctr) {
|
if (!ctr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int oldValue = ctr->getValue();
|
int oldValue = ctr->getValue();
|
||||||
ctr->setValue(event.value());
|
const bool skipDamageAnimation = options.testFlag(SKIP_DAMAGE_ANIMATION);
|
||||||
|
ctr->setValue(event.value(), skipDamageAnimation);
|
||||||
emit logSetCounter(player, ctr->getName(), event.value(), oldValue);
|
emit logSetCounter(player, ctr->getName(), event.value(), oldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -625,7 +626,7 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
|
||||||
eventCreateCounter(event.GetExtension(Event_CreateCounter::ext));
|
eventCreateCounter(event.GetExtension(Event_CreateCounter::ext));
|
||||||
break;
|
break;
|
||||||
case GameEvent::SET_COUNTER:
|
case GameEvent::SET_COUNTER:
|
||||||
eventSetCounter(event.GetExtension(Event_SetCounter::ext));
|
eventSetCounter(event.GetExtension(Event_SetCounter::ext), options);
|
||||||
break;
|
break;
|
||||||
case GameEvent::DEL_COUNTER:
|
case GameEvent::DEL_COUNTER:
|
||||||
eventDelCounter(event.GetExtension(Event_DelCounter::ext));
|
eventDelCounter(event.GetExtension(Event_DelCounter::ext));
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ public:
|
||||||
void eventCreateCounter(const Event_CreateCounter &event);
|
void eventCreateCounter(const Event_CreateCounter &event);
|
||||||
|
|
||||||
/// Set a player-level counter value.
|
/// Set a player-level counter value.
|
||||||
void eventSetCounter(const Event_SetCounter &event);
|
void eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options);
|
||||||
|
|
||||||
/// Delete a player-level counter.
|
/// Delete a player-level counter.
|
||||||
void eventDelCounter(const Event_DelCounter &event);
|
void eventDelCounter(const Event_DelCounter &event);
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,15 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
|
||||||
const ServerInfo_Card &cardInfo = zoneInfo.card_list(j);
|
const ServerInfo_Card &cardInfo = zoneInfo.card_list(j);
|
||||||
auto *card = new CardItem(this);
|
auto *card = new CardItem(this);
|
||||||
card->processCardInfo(cardInfo);
|
card->processCardInfo(cardInfo);
|
||||||
zone->addCard(card, false, cardInfo.x(), cardInfo.y());
|
// Zones without coordinates (hand, piles, stack) preserve the order
|
||||||
|
// they arrive in on the server in the positions of their cards list.
|
||||||
|
// The x coordinate of such cards is always 0, so inserting at it
|
||||||
|
// would reverse the list on reconnect. Append instead.
|
||||||
|
if (zoneInfo.with_coords()) {
|
||||||
|
zone->addCard(card, false, cardInfo.x(), cardInfo.y());
|
||||||
|
} else {
|
||||||
|
zone->addCard(card, false, -1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (zoneInfo.has_always_reveal_top_card()) {
|
if (zoneInfo.has_always_reveal_top_card()) {
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,14 @@ PlayerLogic *PlayerManager::getPlayer(int playerId) const
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerManager::clearSpectators()
|
||||||
|
{
|
||||||
|
const QList<int> spectatorIds = spectators.keys();
|
||||||
|
for (int spectatorId : spectatorIds) {
|
||||||
|
removeSpectator(spectatorId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerManager::onPlayerConceded(int playerId, bool conceded)
|
void PlayerManager::onPlayerConceded(int playerId, bool conceded)
|
||||||
{
|
{
|
||||||
// Everything else cares about this
|
// Everything else cares about this
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,9 @@ public:
|
||||||
emit spectatorRemoved(spectatorId, spectatorInfo);
|
emit spectatorRemoved(spectatorId, spectatorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Remove all spectators, emitting the removal signal for each. */
|
||||||
|
void clearSpectators();
|
||||||
|
|
||||||
[[nodiscard]] AbstractGame *getGame() const
|
[[nodiscard]] AbstractGame *getGame() const
|
||||||
{
|
{
|
||||||
return game;
|
return game;
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ AbstractCounter::AbstractCounter(CounterState *state,
|
||||||
{
|
{
|
||||||
setAcceptHoverEvents(true);
|
setAcceptHoverEvents(true);
|
||||||
|
|
||||||
connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
|
connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) {
|
||||||
value = newValue;
|
value = newValue;
|
||||||
onValueChanged(oldValue, newValue);
|
onValueChanged(oldValue, newValue, skipDamageAnimation);
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -230,7 +230,7 @@ void AbstractCounterDialog::changeValue(int diff)
|
||||||
setTextValue(QString::number(curValue));
|
setTextValue(QString::number(curValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/)
|
void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/, bool /*skipDamageAnimation*/)
|
||||||
{
|
{
|
||||||
// Default: no feedback. Subclasses such as PlayerCounter override this to
|
// Default: no feedback. Subclasses such as PlayerCounter override this to
|
||||||
// flash the counter on meaningful changes (life gain/loss).
|
// flash the counter on meaningful changes (life gain/loss).
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,9 @@ protected:
|
||||||
* @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash).
|
* @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash).
|
||||||
*
|
*
|
||||||
* Called whenever the counter's value changes, before the item repaints.
|
* Called whenever the counter's value changes, before the item repaints.
|
||||||
|
* @param skipDamageAnimation True when damage-related feedback should be suppressed (replay rewinds).
|
||||||
*/
|
*/
|
||||||
virtual void onValueChanged(int oldValue, int newValue);
|
virtual void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation);
|
||||||
|
|
||||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||||
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ void CardItem::drawAttachArrow()
|
||||||
|
|
||||||
for (const auto &item : scene()->selectedItems()) {
|
for (const auto &item : scene()->selectedItems()) {
|
||||||
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
|
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
|
||||||
if (card == nullptr) {
|
if (card == nullptr || card == this) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (card->getZone() != state->getZone()) {
|
if (card->getZone() != state->getZone()) {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <libcockatrice/card/card_info.h>
|
#include <libcockatrice/card/card_info.h>
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
|
||||||
#include <libcockatrice/settings/cards_display_settings.h>
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
|
|
||||||
DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item,
|
DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item,
|
||||||
|
|
@ -381,12 +380,10 @@ void DeckViewScene::rebuildTree()
|
||||||
addItem(container);
|
addItem(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j = 0; j < currentZone->size(); j++) {
|
// Cards in custom zones nested under a board are regular board cards in-game.
|
||||||
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
// They are collected recursively (like every other consumer) and reported with
|
||||||
if (!currentCard) {
|
// the top-level board zone as their origin, so that sideboard plans keep working.
|
||||||
continue;
|
for (auto *currentCard : deck->getCardNodes({currentZone->getName()})) {
|
||||||
}
|
|
||||||
|
|
||||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||||
auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
|
auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
|
||||||
container->addCard(newCard);
|
container->addCard(newCard);
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,16 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
|
||||||
|
|
||||||
GameScene::~GameScene()
|
GameScene::~GameScene()
|
||||||
{
|
{
|
||||||
// Sever all incoming connections (animated item destroy-tracking) before the
|
// Sever all destroyed->removeAnimatedItem connections before the members below
|
||||||
// members below are destroyed: the base QGraphicsScene destructor destroys the
|
// are destroyed: the base QGraphicsScene destructor destroys the remaining items,
|
||||||
// remaining items, and their destroyed() signals must not reach slots that
|
// and their destroyed() signals must not reach slots that reference members that
|
||||||
// reference members that no longer exist.
|
// no longer exist. The connection handle overload is used because the string-based
|
||||||
QObject::disconnect(nullptr, nullptr, this, nullptr);
|
// disconnect(nullptr, nullptr, this, nullptr) is invalid (the sender must never be
|
||||||
|
// nullptr) and would otherwise fail to sever these pointer-to-member connections.
|
||||||
|
for (auto it = animationItemConnections.constBegin(); it != animationItemConnections.constEnd(); ++it) {
|
||||||
|
QObject::disconnect(*it);
|
||||||
|
}
|
||||||
|
animationItemConnections.clear();
|
||||||
|
|
||||||
delete animationTimer;
|
delete animationTimer;
|
||||||
animationTimer = nullptr;
|
animationTimer = nullptr;
|
||||||
|
|
@ -216,7 +221,12 @@ void GameScene::removePlayer(PlayerLogic *player)
|
||||||
|
|
||||||
clearArrowsForPlayer(player->getPlayerInfo()->getId());
|
clearArrowsForPlayer(player->getPlayerInfo()->getId());
|
||||||
|
|
||||||
for (ZoneViewWidget *zone : zoneViews) {
|
// Closing a view removes it from zoneViews synchronously, so iterate over a
|
||||||
|
// copy: otherwise a player with several open views (e.g. library and hand)
|
||||||
|
// only has the first one closed here and the remaining views are left
|
||||||
|
// pointing at a player that is about to be deleted.
|
||||||
|
const QList<ZoneViewWidget *> zoneViewCopy = zoneViews;
|
||||||
|
for (ZoneViewWidget *zone : zoneViewCopy) {
|
||||||
if (zone->getPlayer() == player) {
|
if (zone->getPlayer() == player) {
|
||||||
zone->close();
|
zone->close();
|
||||||
}
|
}
|
||||||
|
|
@ -659,7 +669,10 @@ CardItem *GameScene::findTopmostCardInZone(const QList<QGraphicsItem *> &items,
|
||||||
*/
|
*/
|
||||||
void GameScene::toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed)
|
void GameScene::toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed)
|
||||||
{
|
{
|
||||||
for (auto &view : zoneViews) {
|
// Closing a view removes it from zoneViews synchronously, so iterate over a
|
||||||
|
// copy to make sure every already-open matching view is closed.
|
||||||
|
const QList<ZoneViewWidget *> zoneViewCopy = zoneViews;
|
||||||
|
for (auto *view : zoneViewCopy) {
|
||||||
ZoneViewZone *temp = view->getZone();
|
ZoneViewZone *temp = view->getZone();
|
||||||
if (temp->getLogic()->getName() == zoneName && temp->getLogic()->getPlayer() == player &&
|
if (temp->getLogic()->getName() == zoneName && temp->getLogic()->getPlayer() == player &&
|
||||||
qobject_cast<ZoneViewZoneLogic *>(temp->getLogic())->getNumberCards() == numberCards) {
|
qobject_cast<ZoneViewZoneLogic *>(temp->getLogic())->getNumberCards() == numberCards) {
|
||||||
|
|
@ -777,8 +790,15 @@ void GameScene::registerAnimationItem(IAnimatedItem *item)
|
||||||
if (!object) {
|
if (!object) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!animatedItems.contains(object)) {
|
// Guard against duplicate connections using the connection map, not
|
||||||
connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem);
|
// animatedItems: the animation timer removes entries from animatedItems when an
|
||||||
|
// animation completes, but the destroyed->removeAnimatedItem connection must
|
||||||
|
// persist until the object is destroyed. Relying on animatedItems here would let
|
||||||
|
// a re-registered item (e.g. a life counter that flashes repeatedly) accumulate
|
||||||
|
// duplicate destroyed connections, the older ones of which would survive teardown.
|
||||||
|
if (!animationItemConnections.contains(object)) {
|
||||||
|
animationItemConnections.insert(object,
|
||||||
|
connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem));
|
||||||
}
|
}
|
||||||
animatedItems.insert(object, item);
|
animatedItems.insert(object, item);
|
||||||
if (animationTimer && !animationTimer->isActive()) {
|
if (animationTimer && !animationTimer->isActive()) {
|
||||||
|
|
@ -797,6 +817,7 @@ void GameScene::unregisterAnimationItem(IAnimatedItem *item)
|
||||||
void GameScene::removeAnimatedItem(QObject *item)
|
void GameScene::removeAnimatedItem(QObject *item)
|
||||||
{
|
{
|
||||||
animatedItems.remove(item);
|
animatedItems.remove(item);
|
||||||
|
animationItemConnections.remove(item);
|
||||||
if (animationTimer && animatedItems.isEmpty()) {
|
if (animationTimer && animatedItems.isEmpty()) {
|
||||||
animationTimer->stop();
|
animationTimer->stop();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,11 @@ private:
|
||||||
QPointer<CardItem> hoveredCard; ///< Currently hovered card
|
QPointer<CardItem> hoveredCard; ///< Currently hovered card
|
||||||
QBasicTimer *animationTimer; ///< Timer for scene animations
|
QBasicTimer *animationTimer; ///< Timer for scene animations
|
||||||
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
|
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
|
||||||
int playerRotation; ///< Rotation offset for player layout
|
QHash<QObject *, QMetaObject::Connection>
|
||||||
bool rearranging = false; ///< Guard against re-entrant rearrange
|
animationItemConnections; ///< destroyed->removeAnimatedItem handles per animated item
|
||||||
bool needsReArrange = false; ///< Pending rearrange requested during a pass
|
int playerRotation; ///< Rotation offset for player layout
|
||||||
|
bool rearranging = false; ///< Guard against re-entrant rearrange
|
||||||
|
bool needsReArrange = false; ///< Pending rearrange requested during a pass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates which card is currently hovered based on scene coordinates.
|
* @brief Updates which card is currently hovered based on scene coordinates.
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,13 @@ TallyMenu::TallyMenu()
|
||||||
aTallyNone = createTallyAction(TallyType::None);
|
aTallyNone = createTallyAction(TallyType::None);
|
||||||
aTallySubtypes = createTallyAction(TallyType::Subtypes);
|
aTallySubtypes = createTallyAction(TallyType::Subtypes);
|
||||||
aTallyTotalPower = createTallyAction(TallyType::TotalPower);
|
aTallyTotalPower = createTallyAction(TallyType::TotalPower);
|
||||||
|
aTallyTotalToughness = createTallyAction(TallyType::TotalToughness);
|
||||||
|
|
||||||
addAction(aTallyNone);
|
addAction(aTallyNone);
|
||||||
addSeparator();
|
addSeparator();
|
||||||
addAction(aTallySubtypes);
|
addAction(aTallySubtypes);
|
||||||
addAction(aTallyTotalPower);
|
addAction(aTallyTotalPower);
|
||||||
|
addAction(aTallyTotalToughness);
|
||||||
|
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
}
|
}
|
||||||
|
|
@ -54,4 +56,5 @@ void TallyMenu::retranslateUi()
|
||||||
aTallyNone->setText(tr("None"));
|
aTallyNone->setText(tr("None"));
|
||||||
aTallySubtypes->setText(tr("Subtypes"));
|
aTallySubtypes->setText(tr("Subtypes"));
|
||||||
aTallyTotalPower->setText(tr("Total Power"));
|
aTallyTotalPower->setText(tr("Total Power"));
|
||||||
|
aTallyTotalToughness->setText(tr("Total Toughness"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ private:
|
||||||
QAction *aTallyNone = nullptr;
|
QAction *aTallyNone = nullptr;
|
||||||
QAction *aTallySubtypes = nullptr;
|
QAction *aTallySubtypes = nullptr;
|
||||||
QAction *aTallyTotalPower = nullptr;
|
QAction *aTallyTotalPower = nullptr;
|
||||||
|
QAction *aTallyTotalToughness = nullptr;
|
||||||
|
|
||||||
QAction *createTallyAction(TallyType tallyType);
|
QAction *createTallyAction(TallyType tallyType);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "../../game/player/player_actions.h"
|
#include "../../game/player/player_actions.h"
|
||||||
#include "../../interface/card_picture_loader/card_picture_loader.h"
|
#include "../../interface/card_picture_loader/card_picture_loader.h"
|
||||||
#include "../../interface/widgets/cards/art_crop_attribution.h"
|
#include "../../interface/widgets/cards/art_crop_attribution.h"
|
||||||
|
#include "../../interface/widgets/cards/card_art_utils.h"
|
||||||
#include "../../interface/widgets/playmat/playmat_utils.h"
|
#include "../../interface/widgets/playmat/playmat_utils.h"
|
||||||
#include "../../interface/widgets/tabs/tab_game.h"
|
#include "../../interface/widgets/tabs/tab_game.h"
|
||||||
#include "../board/abstract_card_item.h"
|
#include "../board/abstract_card_item.h"
|
||||||
|
|
@ -251,8 +252,8 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
|
||||||
AbstractCounter *widget;
|
AbstractCounter *widget;
|
||||||
if (state->getName() == "life") {
|
if (state->getName() == "life") {
|
||||||
widget = playerTarget->addCounter(state);
|
widget = playerTarget->addCounter(state);
|
||||||
connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
|
connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) {
|
||||||
if (newValue < oldValue) {
|
if (newValue < oldValue && !skipDamageAnimation) {
|
||||||
tableZoneGraphicsItem->triggerDamageShimmer();
|
tableZoneGraphicsItem->triggerDamageShimmer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -442,7 +443,7 @@ void PlayerGraphicsItem::updatePlaymat()
|
||||||
hasPlaymat = true;
|
hasPlaymat = true;
|
||||||
emit playmatChanged(true);
|
emit playmatChanged(true);
|
||||||
}
|
}
|
||||||
playmatPixmap = fullRes;
|
playmatPixmap = CardArtUtils::rotateSidewaysLayoutArt(fullRes, card);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,11 @@ void PlayerListWidget::retranslateUi()
|
||||||
|
|
||||||
void PlayerListWidget::addPlayer(const ServerInfo_PlayerProperties &player)
|
void PlayerListWidget::addPlayer(const ServerInfo_PlayerProperties &player)
|
||||||
{
|
{
|
||||||
|
if (players.contains(player.player_id())) {
|
||||||
|
updatePlayerProperties(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QTreeWidgetItem *newPlayer = new PlayerListTWI;
|
QTreeWidgetItem *newPlayer = new PlayerListTWI;
|
||||||
players.insert(player.player_id(), newPlayer);
|
players.insert(player.player_id(), newPlayer);
|
||||||
updatePlayerProperties(player);
|
updatePlayerProperties(player);
|
||||||
|
|
@ -176,6 +181,17 @@ void PlayerListWidget::removePlayer(int playerId)
|
||||||
delete takeTopLevelItem(indexOfTopLevelItem(player));
|
delete takeTopLevelItem(indexOfTopLevelItem(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerListWidget::clearSpectators()
|
||||||
|
{
|
||||||
|
const QList<int> playerIds = players.keys();
|
||||||
|
for (int playerId : playerIds) {
|
||||||
|
QTreeWidgetItem *player = players.value(playerId, 0);
|
||||||
|
if (player && !player->data(1, Qt::UserRole).toBool()) {
|
||||||
|
removePlayer(playerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerListWidget::setActivePlayer(int playerId)
|
void PlayerListWidget::setActivePlayer(int playerId)
|
||||||
{
|
{
|
||||||
QMapIterator<int, QTreeWidgetItem *> i(players);
|
QMapIterator<int, QTreeWidgetItem *> i(players);
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ public slots:
|
||||||
void addPlayer(const ServerInfo_PlayerProperties &player);
|
void addPlayer(const ServerInfo_PlayerProperties &player);
|
||||||
void removePlayer(int playerId);
|
void removePlayer(int playerId);
|
||||||
void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1);
|
void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1);
|
||||||
|
void clearSpectators();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerCounter::onValueChanged(int oldValue, int newValue)
|
void PlayerCounter::onValueChanged(int oldValue, int newValue, bool skipDamageAnimation)
|
||||||
{
|
{
|
||||||
flashDelta = newValue - oldValue;
|
flashDelta = newValue - oldValue;
|
||||||
if (flashDelta == 0) {
|
if (flashDelta == 0) {
|
||||||
|
|
@ -81,6 +81,11 @@ void PlayerCounter::onValueChanged(int oldValue, int newValue)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skipDamageAnimation) {
|
||||||
|
flashAlpha = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
flashAlpha = 1.0;
|
flashAlpha = 1.0;
|
||||||
flashClock.start();
|
flashClock.start();
|
||||||
if (scene()) {
|
if (scene()) {
|
||||||
|
|
@ -132,8 +137,18 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o
|
||||||
QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect);
|
QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect);
|
||||||
QSize translatedSize = translatedRect.size().toSize();
|
QSize translatedSize = translatedRect.size().toSize();
|
||||||
QPixmap cachedPixmap;
|
QPixmap cachedPixmap;
|
||||||
|
// The key must cover everything the generated pawn depends on: the rendered
|
||||||
|
// size, the user level, and the pixmap being drawn. fullPixmap.cacheKey() is
|
||||||
|
// 0 for every null pixmap, so the default-pawn branch additionally needs the
|
||||||
|
// pawn's privlevel (lowercased, matching UserLevelPixmapGenerator) and colors
|
||||||
|
// in the key — otherwise two players without a custom avatar (and the same
|
||||||
|
// user level) would share one cached pawn.
|
||||||
const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" +
|
const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" +
|
||||||
QString::number(info->user_level()) + "_" + QString::number(fullPixmap.cacheKey());
|
QString::number(translatedSize.height()) + "_" + QString::number(info->user_level()) +
|
||||||
|
"_" + QString::number(fullPixmap.cacheKey()) + "_" +
|
||||||
|
QString::fromStdString(info->privlevel()).toLower() + "_" +
|
||||||
|
QString::fromStdString(info->pawn_colors().left_side()) + "_" +
|
||||||
|
QString::fromStdString(info->pawn_colors().right_side());
|
||||||
if (!QPixmapCache::find(cacheKey, &cachedPixmap)) {
|
if (!QPixmapCache::find(cacheKey, &cachedPixmap)) {
|
||||||
cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height());
|
cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class PlayerCounter : public AbstractCounter, public IAnimatedItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
void onValueChanged(int oldValue, int newValue) override;
|
void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr qreal flashDurationMs = 450.0;
|
static constexpr qreal flashDurationMs = 450.0;
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,31 @@ QList<TallyRow> StatsTally::computeTotalPower(const QList<CardItem *> &cards)
|
||||||
QString name = QCoreApplication::translate("StatsTally", "Total Power");
|
QString name = QCoreApplication::translate("StatsTally", "Total Power");
|
||||||
return {TallyRow{name, QString::number(total)}};
|
return {TallyRow{name, QString::number(total)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sumToughness(const QList<CardItem *> &cards)
|
||||||
|
{
|
||||||
|
int total = 0;
|
||||||
|
for (auto card : cards) {
|
||||||
|
QVariantList parsed = CardItem::parsePT(card->getPT());
|
||||||
|
if (parsed.size() == 2) {
|
||||||
|
int toughness = parsed.at(1).toInt(); // toInt will default to 0 if it's not an int
|
||||||
|
total += qMax(toughness, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<TallyRow> StatsTally::computeTotalToughness(const QList<CardItem *> &cards)
|
||||||
|
{
|
||||||
|
// don't bother if none of the cards have pt
|
||||||
|
bool hasPT =
|
||||||
|
std::any_of(cards.cbegin(), cards.cend(), [](const CardItem *card) { return !card->getPT().isEmpty(); });
|
||||||
|
if (!hasPT) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = sumToughness(cards);
|
||||||
|
|
||||||
|
QString name = QCoreApplication::translate("StatsTally", "Total Toughness");
|
||||||
|
return {TallyRow{name, QString::number(total)}};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,14 @@ namespace StatsTally
|
||||||
*/
|
*/
|
||||||
QList<TallyRow> computeTotalPower(const QList<CardItem *> &cards);
|
QList<TallyRow> computeTotalPower(const QList<CardItem *> &cards);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sums the toughness of all selected cards
|
||||||
|
*
|
||||||
|
* @param cards The list of selected card items to analyze.
|
||||||
|
* @return A single row containing the total, or an empty list if none of the cards have pt
|
||||||
|
*/
|
||||||
|
QList<TallyRow> computeTotalToughness(const QList<CardItem *> &cards);
|
||||||
|
|
||||||
} // namespace StatsTally
|
} // namespace StatsTally
|
||||||
|
|
||||||
#endif // COCKATRICE_STATS_TALLY_H
|
#endif // COCKATRICE_STATS_TALLY_H
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ QList<TallyRow> Tally::compute(const QList<CardItem *> &cards, const TallyType t
|
||||||
return SubtypeTally::countSubtypes(cards);
|
return SubtypeTally::countSubtypes(cards);
|
||||||
case TallyType::TotalPower:
|
case TallyType::TotalPower:
|
||||||
return StatsTally::computeTotalPower(cards);
|
return StatsTally::computeTotalPower(cards);
|
||||||
|
case TallyType::TotalToughness:
|
||||||
|
return StatsTally::computeTotalToughness(cards);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ enum class TallyType
|
||||||
None,
|
None,
|
||||||
Subtypes,
|
Subtypes,
|
||||||
TotalPower,
|
TotalPower,
|
||||||
MaxValue = TotalPower // sentinel value
|
TotalToughness,
|
||||||
|
MaxValue = TotalToughness // sentinel value
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Tally
|
namespace Tally
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
x = calcDropIndexFromY(dropPoint.y());
|
bool sameZone = startZone == getLogic();
|
||||||
|
x = calcDropIndexFromY(dropPoint.y(), !sameZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
Command_MoveCard cmd;
|
Command_MoveCard cmd;
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ SelectZone::StackLayoutParams SelectZone::buildStackParams(qreal minOffset) cons
|
||||||
return {cardCount, boundingRect().height(), cardHeight, offset, minOffset};
|
return {cardCount, boundingRect().height(), cardHeight, offset, minOffset};
|
||||||
}
|
}
|
||||||
|
|
||||||
int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
|
int SelectZone::calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset) const
|
||||||
{
|
{
|
||||||
const auto &cards = getLogic()->getCards();
|
const auto &cards = getLogic()->getCards();
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
|
|
@ -94,7 +94,8 @@ int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
|
||||||
if (effectiveOffset <= 0.0) {
|
if (effectiveOffset <= 0.0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return qBound(0, qRound((dropY - start) / effectiveOffset), params.cardCount - 1);
|
int max = allowCountExpand ? params.cardCount : params.cardCount - 1;
|
||||||
|
return qBound(0, qRound((dropY - start) / effectiveOffset), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectZone::restoreStaleEscapedCards()
|
void SelectZone::restoreStaleEscapedCards()
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,12 @@ protected:
|
||||||
/**
|
/**
|
||||||
* @brief Computes the card index at a given y-coordinate within the zone's vertical layout.
|
* @brief Computes the card index at a given y-coordinate within the zone's vertical layout.
|
||||||
* Returns 0 if the zone has no cards or the offset is zero.
|
* Returns 0 if the zone has no cards or the offset is zero.
|
||||||
|
*
|
||||||
|
* @param dropY The y-coordinate that the card was dropped at
|
||||||
|
* @param allowCountExpand If false, clamps the index at the number of cards minus 1
|
||||||
|
* @param minOffset Minimum offset to preserve
|
||||||
*/
|
*/
|
||||||
int calcDropIndexFromY(qreal dropY, qreal minOffset = 0.0) const;
|
int calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset = 0.0) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Positions cards vertically with alternating left/right x-offsets.
|
* @brief Positions cards vertically with alternating left/right x-offsets.
|
||||||
|
|
|
||||||
|
|
@ -57,18 +57,14 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &cards = getLogic()->getCards();
|
bool sameZone = startZone == getLogic();
|
||||||
int index;
|
int index = calcDropIndexFromY(dropPoint.y(), !sameZone, MIN_CARD_VISIBLE);
|
||||||
if (startZone == getLogic()) {
|
if (sameZone) {
|
||||||
// Reordering within the zone: use drop position
|
|
||||||
index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE);
|
|
||||||
// Same-zone no-op: don't move a card onto itself
|
// Same-zone no-op: don't move a card onto itself
|
||||||
|
const auto &cards = getLogic()->getCards();
|
||||||
if (!cards.isEmpty() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
if (!cards.isEmpty() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Coming from another zone: append at end (top of stack, rendered on top)
|
|
||||||
index = static_cast<int>(cards.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Command_MoveCard cmd;
|
Command_MoveCard cmd;
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,8 @@ void CardPictureLoader::getPixmap(QPixmap &pixmap, const ExactCard &card, QSize
|
||||||
QPixmap bigPixmap;
|
QPixmap bigPixmap;
|
||||||
if (QPixmapCache::find(key, &bigPixmap)) {
|
if (QPixmapCache::find(key, &bigPixmap)) {
|
||||||
if (bigPixmap.isNull()) {
|
if (bigPixmap.isNull()) {
|
||||||
getCardBackLoadingFailedPixmap(pixmap, size);
|
// Leave the pixmap null so callers fall back to a solid color
|
||||||
|
// instead of showing the card back.
|
||||||
QDateTime failedAtTime = getInstance().failedAt.value(key);
|
QDateTime failedAtTime = getInstance().failedAt.value(key);
|
||||||
if (!failedAtTime.isValid() ||
|
if (!failedAtTime.isValid() ||
|
||||||
failedAtTime.addSecs(RETRY_FAILED_CARDS_SECS) < QDateTime::currentDateTime()) {
|
failedAtTime.addSecs(RETRY_FAILED_CARDS_SECS) < QDateTime::currentDateTime()) {
|
||||||
|
|
|
||||||
|
|
@ -375,15 +375,32 @@ void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckL
|
||||||
void DeckLoader::saveToStream_DeckZone(QTextStream &out,
|
void DeckLoader::saveToStream_DeckZone(QTextStream &out,
|
||||||
const InnerDecklistNode *zoneNode,
|
const InnerDecklistNode *zoneNode,
|
||||||
bool addComments,
|
bool addComments,
|
||||||
bool addSetNameAndNumber)
|
bool addSetNameAndNumber,
|
||||||
|
const QString &boardZoneName)
|
||||||
{
|
{
|
||||||
|
// Nested sub-zones keep their owning board's identity: the top-level call
|
||||||
|
// passes no board, so the zone's own name is used; recursive calls carry the
|
||||||
|
// owning board down so the sideboard marker survives sub-zone nesting.
|
||||||
|
const QString owningBoardZoneName = boardZoneName.isEmpty() ? zoneNode->getName() : boardZoneName;
|
||||||
|
|
||||||
// group cards by card type and count the subtotals
|
// group cards by card type and count the subtotals
|
||||||
QMultiMap<QString, DecklistCardNode *> cardsByType;
|
QMultiMap<QString, DecklistCardNode *> cardsByType;
|
||||||
QMap<QString, int> cardTotalByType;
|
QMap<QString, int> cardTotalByType;
|
||||||
int cardTotal = 0;
|
int cardTotal = 0;
|
||||||
|
QList<const InnerDecklistNode *> subZones;
|
||||||
|
|
||||||
for (int j = 0; j < zoneNode->size(); j++) {
|
for (int j = 0; j < zoneNode->size(); j++) {
|
||||||
auto *card = dynamic_cast<DecklistCardNode *>(zoneNode->at(j));
|
auto *card = dynamic_cast<DecklistCardNode *>(zoneNode->at(j));
|
||||||
|
if (!card) {
|
||||||
|
// Cards collected in nested sub-zones are exported by recursion so
|
||||||
|
// they don't end up invisible in the plain text output. They are
|
||||||
|
// deferred until after this zone's own header and cards so they read
|
||||||
|
// as part of this zone's block.
|
||||||
|
if (auto *subZone = dynamic_cast<const InnerDecklistNode *>(zoneNode->at(j))) {
|
||||||
|
subZones.append(subZone);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(card->getName());
|
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(card->getName());
|
||||||
QString cardType = info ? info->getMainCardType() : "unknown";
|
QString cardType = info ? info->getMainCardType() : "unknown";
|
||||||
|
|
@ -411,25 +428,30 @@ void DeckLoader::saveToStream_DeckZone(QTextStream &out,
|
||||||
|
|
||||||
QList<DecklistCardNode *> cards = cardsByType.values(cardType);
|
QList<DecklistCardNode *> cards = cardsByType.values(cardType);
|
||||||
|
|
||||||
saveToStream_DeckZoneCards(out, zoneNode, cards, addComments, addSetNameAndNumber);
|
saveToStream_DeckZoneCards(out, cards, addComments, addSetNameAndNumber, owningBoardZoneName);
|
||||||
|
|
||||||
if (addComments) {
|
if (addComments) {
|
||||||
out << "\n";
|
out << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nested sub-zones come last, after the parent's own header and cards.
|
||||||
|
for (const auto *subZone : subZones) {
|
||||||
|
saveToStream_DeckZone(out, subZone, addComments, addSetNameAndNumber, owningBoardZoneName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
|
void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
|
||||||
const InnerDecklistNode *zoneNode,
|
|
||||||
QList<DecklistCardNode *> cards,
|
QList<DecklistCardNode *> cards,
|
||||||
bool addComments,
|
bool addComments,
|
||||||
bool addSetNameAndNumber)
|
bool addSetNameAndNumber,
|
||||||
|
const QString &boardZoneName)
|
||||||
{
|
{
|
||||||
// QMultiMap sorts values in reverse order
|
// QMultiMap sorts values in reverse order
|
||||||
for (int i = cards.size() - 1; i >= 0; --i) {
|
for (int i = cards.size() - 1; i >= 0; --i) {
|
||||||
DecklistCardNode *card = cards[i];
|
DecklistCardNode *card = cards[i];
|
||||||
|
|
||||||
if (zoneNode->getName() == DECK_ZONE_SIDE && addComments) {
|
if (boardZoneName == DECK_ZONE_SIDE && addComments) {
|
||||||
out << "SB: ";
|
out << "SB: ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,9 +532,26 @@ bool DeckLoader::convertToCockatriceFormat(LoadedDeck &deck)
|
||||||
|
|
||||||
void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node)
|
void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node)
|
||||||
{
|
{
|
||||||
|
if (!node || node->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const int totalColumns = 2;
|
const int totalColumns = 2;
|
||||||
|
|
||||||
if (node->height() == 1) {
|
// Dispatch children by type instead of trusting a whole-node height: a deck
|
||||||
|
// node may hold direct cards and nested zones side by side (custom zones),
|
||||||
|
// and an empty node would previously crash on at(0).
|
||||||
|
QVector<const AbstractDecklistCardNode *> cards;
|
||||||
|
QVector<const InnerDecklistNode *> subZones;
|
||||||
|
for (int i = 0; i < node->size(); i++) {
|
||||||
|
if (auto *card = dynamic_cast<const AbstractDecklistCardNode *>(node->at(i))) {
|
||||||
|
cards.append(card);
|
||||||
|
} else if (auto *zone = dynamic_cast<const InnerDecklistNode *>(node->at(i))) {
|
||||||
|
subZones.append(zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cards.isEmpty()) {
|
||||||
QTextBlockFormat blockFormat;
|
QTextBlockFormat blockFormat;
|
||||||
QTextCharFormat charFormat;
|
QTextCharFormat charFormat;
|
||||||
charFormat.setFontPointSize(11);
|
charFormat.setFontPointSize(11);
|
||||||
|
|
@ -523,9 +562,9 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
|
||||||
tableFormat.setCellPadding(0);
|
tableFormat.setCellPadding(0);
|
||||||
tableFormat.setCellSpacing(0);
|
tableFormat.setCellSpacing(0);
|
||||||
tableFormat.setBorder(0);
|
tableFormat.setBorder(0);
|
||||||
QTextTable *table = cursor->insertTable(node->size() + 1, totalColumns, tableFormat);
|
QTextTable *table = cursor->insertTable(cards.size() + 1, totalColumns, tableFormat);
|
||||||
for (int i = 0; i < node->size(); i++) {
|
for (int i = 0; i < cards.size(); i++) {
|
||||||
auto *card = dynamic_cast<AbstractDecklistCardNode *>(node->at(i));
|
const AbstractDecklistCardNode *card = cards[i];
|
||||||
|
|
||||||
QTextCharFormat cellCharFormat;
|
QTextCharFormat cellCharFormat;
|
||||||
cellCharFormat.setFontPointSize(9);
|
cellCharFormat.setFontPointSize(9);
|
||||||
|
|
@ -540,7 +579,13 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
|
||||||
cellCursor = cell.firstCursorPosition();
|
cellCursor = cell.firstCursorPosition();
|
||||||
cellCursor.insertText(card->getName());
|
cellCursor.insertText(card->getName());
|
||||||
}
|
}
|
||||||
} else if (node->height() == 2) {
|
}
|
||||||
|
|
||||||
|
for (const InnerDecklistNode *subZone : subZones) {
|
||||||
|
if (subZone->isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
QTextBlockFormat blockFormat;
|
QTextBlockFormat blockFormat;
|
||||||
QTextCharFormat charFormat;
|
QTextCharFormat charFormat;
|
||||||
charFormat.setFontPointSize(14);
|
charFormat.setFontPointSize(14);
|
||||||
|
|
@ -559,10 +604,8 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
|
||||||
tableFormat.setColumnWidthConstraints(constraints);
|
tableFormat.setColumnWidthConstraints(constraints);
|
||||||
|
|
||||||
QTextTable *table = cursor->insertTable(1, totalColumns, tableFormat);
|
QTextTable *table = cursor->insertTable(1, totalColumns, tableFormat);
|
||||||
for (int i = 0; i < node->size(); i++) {
|
QTextCursor cellCursor = table->cellAt(0, 0).firstCursorPosition();
|
||||||
QTextCursor cellCursor = table->cellAt(0, (i * totalColumns) / node->size()).lastCursorPosition();
|
printDeckListNode(&cellCursor, subZone);
|
||||||
printDeckListNode(&cellCursor, dynamic_cast<InnerDecklistNode *>(node->at(i)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor->movePosition(QTextCursor::End);
|
cursor->movePosition(QTextCursor::End);
|
||||||
|
|
|
||||||
|
|
@ -159,12 +159,13 @@ private:
|
||||||
static void saveToStream_DeckZone(QTextStream &out,
|
static void saveToStream_DeckZone(QTextStream &out,
|
||||||
const InnerDecklistNode *zoneNode,
|
const InnerDecklistNode *zoneNode,
|
||||||
bool addComments = true,
|
bool addComments = true,
|
||||||
bool addSetNameAndNumber = true);
|
bool addSetNameAndNumber = true,
|
||||||
|
const QString &boardZoneName = QString());
|
||||||
static void saveToStream_DeckZoneCards(QTextStream &out,
|
static void saveToStream_DeckZoneCards(QTextStream &out,
|
||||||
const InnerDecklistNode *zoneNode,
|
|
||||||
QList<DecklistCardNode *> cards,
|
QList<DecklistCardNode *> cards,
|
||||||
bool addComments = true,
|
bool addComments = true,
|
||||||
bool addSetNameAndNumber = true);
|
bool addSetNameAndNumber = true,
|
||||||
|
const QString &boardZoneName = QString());
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff";
|
#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff";
|
||||||
#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
|
#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
|
||||||
#define DEFAULT_COLOR_ADMIN "#ff2701";
|
#define DEFAULT_COLOR_ADMIN "#ff2701";
|
||||||
#define DEFAULT_COLOR_DEVELOPER "#800020"
|
#define DEFAULT_COLOR_DEVELOPER "#B8B8B8"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clamps an svg render size so that rendering does not exceed a multiple of the requested size.
|
* Clamps an svg render size so that rendering does not exceed a multiple of the requested size.
|
||||||
|
|
|
||||||
18
cockatrice/src/interface/widgets/cards/card_art_utils.cpp
Normal file
18
cockatrice/src/interface/widgets/cards/card_art_utils.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include "card_art_utils.h"
|
||||||
|
|
||||||
|
#include <QTransform>
|
||||||
|
#include <libcockatrice/card/printing/exact_card.h>
|
||||||
|
|
||||||
|
namespace CardArtUtils
|
||||||
|
{
|
||||||
|
QPixmap rotateSidewaysLayoutArt(const QPixmap &art, const ExactCard &card)
|
||||||
|
{
|
||||||
|
if (!card.getInfo().getUiAttributes().landscapeOrientation) {
|
||||||
|
return art;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTransform transform;
|
||||||
|
transform.rotate(90);
|
||||||
|
return art.transformed(transform, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
} // namespace CardArtUtils
|
||||||
25
cockatrice/src/interface/widgets/cards/card_art_utils.h
Normal file
25
cockatrice/src/interface/widgets/cards/card_art_utils.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef CARD_ART_UTILS_H
|
||||||
|
#define CARD_ART_UTILS_H
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
|
||||||
|
class ExactCard;
|
||||||
|
|
||||||
|
namespace CardArtUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Rotates a card's art upright when its layout shows sideways.
|
||||||
|
*
|
||||||
|
* Sideways-layout cards (planes, sieges/battles, split cards) store their
|
||||||
|
* landscape artwork rotated 90° inside a portrait frame. Art-crop displays,
|
||||||
|
* playmat art, and the card-info picture must show such art upright before
|
||||||
|
* sampling or painting. Portrait cards are returned unchanged.
|
||||||
|
*
|
||||||
|
* @param art The card pixmap to orient.
|
||||||
|
* @param card The card describing the art orientation.
|
||||||
|
* @return @p art rotated 90° clockwise for sideways-layout cards, else @p art.
|
||||||
|
*/
|
||||||
|
QPixmap rotateSidewaysLayoutArt(const QPixmap &art, const ExactCard &card);
|
||||||
|
} // namespace CardArtUtils
|
||||||
|
|
||||||
|
#endif // CARD_ART_UTILS_H
|
||||||
|
|
@ -174,16 +174,18 @@ void CardGroupDisplayWidget::updateCardDisplays()
|
||||||
QModelIndex sourceIndex = proxy.mapToSource(proxyIndex);
|
QModelIndex sourceIndex = proxy.mapToSource(proxyIndex);
|
||||||
|
|
||||||
// 4. persist the source index
|
// 4. persist the source index
|
||||||
QPersistentModelIndex persistent(sourceIndex);
|
addCardWidgets(QPersistentModelIndex(sourceIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the card amount
|
void CardGroupDisplayWidget::addCardWidgets(const QPersistentModelIndex &persistent)
|
||||||
int cardAmount =
|
{
|
||||||
sourceIndex.sibling(sourceIndex.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt();
|
// Get the card amount
|
||||||
|
int cardAmount = persistent.sibling(persistent.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt();
|
||||||
|
|
||||||
// Create multiple widgets for the card count
|
// Create multiple widgets for the card count
|
||||||
for (int copy = 0; copy < cardAmount; ++copy) {
|
for (int copy = 0; copy < cardAmount; ++copy) {
|
||||||
addToLayout(constructWidgetForIndex(persistent));
|
addToLayout(constructWidgetForIndex(persistent));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public:
|
||||||
void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
|
void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
|
||||||
void refreshSelectionForIndex(const QPersistentModelIndex &persistent);
|
void refreshSelectionForIndex(const QPersistentModelIndex &persistent);
|
||||||
void clearAllDisplayWidgets();
|
void clearAllDisplayWidgets();
|
||||||
|
void addCardWidgets(const QPersistentModelIndex &persistent);
|
||||||
|
|
||||||
DeckListModel *deckListModel;
|
DeckListModel *deckListModel;
|
||||||
QItemSelectionModel *selectionModel;
|
QItemSelectionModel *selectionModel;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "../../../interface/card_picture_loader/card_picture_loader.h"
|
#include "../../../interface/card_picture_loader/card_picture_loader.h"
|
||||||
#include "../../../interface/widgets/tabs/tab_supervisor.h"
|
#include "../../../interface/widgets/tabs/tab_supervisor.h"
|
||||||
#include "../../window_main.h"
|
#include "../../window_main.h"
|
||||||
|
#include "card_art_utils.h"
|
||||||
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
|
@ -193,12 +194,7 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
|
||||||
|
|
||||||
QPixmap transformedPixmap = resizedPixmap; // Default pixmap
|
QPixmap transformedPixmap = resizedPixmap; // Default pixmap
|
||||||
if (SettingsCache::instance().cardsDisplay().getAutoRotateSidewaysLayoutCards()) {
|
if (SettingsCache::instance().cardsDisplay().getAutoRotateSidewaysLayoutCards()) {
|
||||||
if (exactCard.getInfo().getUiAttributes().landscapeOrientation) {
|
transformedPixmap = CardArtUtils::rotateSidewaysLayoutArt(resizedPixmap, exactCard);
|
||||||
// Rotate pixmap 90 degrees to the left
|
|
||||||
QTransform transform;
|
|
||||||
transform.rotate(90);
|
|
||||||
transformedPixmap = resizedPixmap.transformed(transform, Qt::SmoothTransformation);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle DPI scaling
|
// Handle DPI scaling
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "libcockatrice/card/database/card_database_manager.h"
|
#include "libcockatrice/card/database/card_database_manager.h"
|
||||||
|
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
|
#include <algorithm>
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
|
|
||||||
DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
|
DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
|
||||||
|
|
@ -51,11 +52,6 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
|
||||||
// User Interaction
|
// User Interaction
|
||||||
// =====================================================================================================================
|
// =====================================================================================================================
|
||||||
|
|
||||||
void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, const ExactCard &card)
|
|
||||||
{
|
|
||||||
emit cardClicked(event, card, zoneName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeckCardZoneDisplayWidget::onHover(const ExactCard &card)
|
void DeckCardZoneDisplayWidget::onHover(const ExactCard &card)
|
||||||
{
|
{
|
||||||
emit cardHovered(card);
|
emit cardHovered(card);
|
||||||
|
|
@ -95,12 +91,18 @@ void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
// Cards in a custom zone belong to that zone, not the board zone, so that
|
||||||
|
// increment/decrement/swap actions target the custom zone.
|
||||||
|
const bool isCustomZone = index.data(DeckRoles::IsCustomZoneRole).toBool();
|
||||||
|
const QString effectiveZoneName = isCustomZone ? categoryName : zoneName;
|
||||||
|
const auto routeCardClick = [this, effectiveZoneName](QMouseEvent *event, const ExactCard &card) {
|
||||||
|
emit cardClicked(event, card, effectiveZoneName);
|
||||||
|
};
|
||||||
if (displayType == DisplayType::Overlap) {
|
if (displayType == DisplayType::Overlap) {
|
||||||
auto *displayWidget = new OverlappedCardGroupDisplayWidget(
|
auto *displayWidget = new OverlappedCardGroupDisplayWidget(
|
||||||
cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria,
|
cardGroupContainer, deckListModel, selectionModel, index, effectiveZoneName, categoryName,
|
||||||
activeSortCriteria, subBannerOpacity, cardSizeWidget);
|
activeGroupCriteria, activeSortCriteria, subBannerOpacity, cardSizeWidget);
|
||||||
connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardClicked, this,
|
connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardClicked, this, routeCardClick);
|
||||||
&DeckCardZoneDisplayWidget::onClick);
|
|
||||||
connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardHovered, this,
|
connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardHovered, this,
|
||||||
&DeckCardZoneDisplayWidget::onHover);
|
&DeckCardZoneDisplayWidget::onHover);
|
||||||
connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this,
|
connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this,
|
||||||
|
|
@ -111,9 +113,9 @@ void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex
|
||||||
indexToWidgetMap.insert(index, displayWidget);
|
indexToWidgetMap.insert(index, displayWidget);
|
||||||
} else if (displayType == DisplayType::Flat) {
|
} else if (displayType == DisplayType::Flat) {
|
||||||
auto *displayWidget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, selectionModel, index,
|
auto *displayWidget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, selectionModel, index,
|
||||||
zoneName, categoryName, activeGroupCriteria,
|
effectiveZoneName, categoryName, activeGroupCriteria,
|
||||||
activeSortCriteria, subBannerOpacity, cardSizeWidget);
|
activeSortCriteria, subBannerOpacity, cardSizeWidget);
|
||||||
connect(displayWidget, &FlatCardGroupDisplayWidget::cardClicked, this, &DeckCardZoneDisplayWidget::onClick);
|
connect(displayWidget, &FlatCardGroupDisplayWidget::cardClicked, this, routeCardClick);
|
||||||
connect(displayWidget, &FlatCardGroupDisplayWidget::cardHovered, this, &DeckCardZoneDisplayWidget::onHover);
|
connect(displayWidget, &FlatCardGroupDisplayWidget::cardHovered, this, &DeckCardZoneDisplayWidget::onHover);
|
||||||
connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this,
|
connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this,
|
||||||
&DeckCardZoneDisplayWidget::cleanupInvalidCardGroup);
|
&DeckCardZoneDisplayWidget::cleanupInvalidCardGroup);
|
||||||
|
|
@ -126,24 +128,18 @@ void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex
|
||||||
|
|
||||||
void DeckCardZoneDisplayWidget::displayCards()
|
void DeckCardZoneDisplayWidget::displayCards()
|
||||||
{
|
{
|
||||||
QSortFilterProxyModel proxy;
|
if (!trackedIndex.isValid()) {
|
||||||
proxy.setSourceModel(deckListModel);
|
return;
|
||||||
proxy.setSortRole(Qt::EditRole);
|
}
|
||||||
proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
|
|
||||||
|
|
||||||
// 1. trackedIndex is a source index → map it to proxy space
|
// Iterate the direct children of the tracked zone, keeping the tree view's row
|
||||||
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
// order (criteria groups first, then custom zones, both in the model's sort order).
|
||||||
|
QList<QPersistentModelIndex> rows;
|
||||||
// 2. iterate children under the proxy parent
|
for (int i = 0; i < deckListModel->rowCount(trackedIndex); ++i) {
|
||||||
for (int i = 0; i < proxy.rowCount(proxyParent); ++i) {
|
rows.append(QPersistentModelIndex(deckListModel->index(i, 0, trackedIndex)));
|
||||||
QModelIndex proxyIndex = proxy.index(i, 0, proxyParent);
|
}
|
||||||
|
|
||||||
// 3. map back to source
|
|
||||||
QModelIndex sourceIndex = proxy.mapToSource(proxyIndex);
|
|
||||||
|
|
||||||
// 4. persist the source index
|
|
||||||
QPersistentModelIndex persistent(sourceIndex);
|
|
||||||
|
|
||||||
|
for (const QPersistentModelIndex &persistent : rows) {
|
||||||
constructAppropriateWidget(persistent);
|
constructAppropriateWidget(persistent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ public:
|
||||||
void addCardsToOverlapWidget();
|
void addCardsToOverlapWidget();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onClick(QMouseEvent *event, const ExactCard &card);
|
|
||||||
void onHover(const ExactCard &card);
|
void onHover(const ExactCard &card);
|
||||||
void cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget);
|
void cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget);
|
||||||
void constructAppropriateWidget(QPersistentModelIndex index);
|
void constructAppropriateWidget(QPersistentModelIndex index);
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,13 @@ void CardDatabaseView::decrementCard(const QString &zoneName)
|
||||||
emit cardDecremented(currentCardName(), zoneName);
|
emit cardDecremented(currentCardName(), zoneName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CardDatabaseView::setZoneMenuProvider(const std::function<QList<QPair<QString, QStringList>>()> &provider,
|
||||||
|
const std::function<QString()> &newZoneHandler)
|
||||||
|
{
|
||||||
|
zoneMenuProvider = provider;
|
||||||
|
this->newZoneHandler = newZoneHandler;
|
||||||
|
}
|
||||||
|
|
||||||
void CardDatabaseView::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
void CardDatabaseView::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||||
{
|
{
|
||||||
if (!current.isValid()) {
|
if (!current.isValid()) {
|
||||||
|
|
@ -142,6 +149,50 @@ void CardDatabaseView::openCustomMenu(QPoint point)
|
||||||
[this, card] { emit cardAdded(card->getName(), DECK_ZONE_SIDE); });
|
[this, card] { emit cardAdded(card->getName(), DECK_ZONE_SIDE); });
|
||||||
connect(selectPrinting, &QAction::triggered, this, &CardDatabaseView::selectPrintingClicked);
|
connect(selectPrinting, &QAction::triggered, this, &CardDatabaseView::selectPrintingClicked);
|
||||||
|
|
||||||
|
if (zoneMenuProvider) {
|
||||||
|
QMenu *addToZoneMenu = menu.addMenu(tr("Add to Zone"));
|
||||||
|
const auto zoneBoards = zoneMenuProvider();
|
||||||
|
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||||
|
// Boards with zones nest their children so no two menu entries
|
||||||
|
// share a visible name: "Maindeck ▸ { Maindeck (whole board), … }".
|
||||||
|
const QStringList customZones = [&zoneBoards, boardName] {
|
||||||
|
for (const auto &zoneBoard : zoneBoards) {
|
||||||
|
if (zoneBoard.first == boardName) {
|
||||||
|
return zoneBoard.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QStringList();
|
||||||
|
}();
|
||||||
|
if (customZones.isEmpty()) {
|
||||||
|
QAction *action = addToZoneMenu->addAction(InnerDecklistNode::visibleNameFromName(boardName));
|
||||||
|
connect(action, &QAction::triggered, this,
|
||||||
|
[this, card, boardName] { emit cardAdded(card->getName(), boardName); });
|
||||||
|
} else {
|
||||||
|
QMenu *boardSubmenu = addToZoneMenu->addMenu(InnerDecklistNode::visibleNameFromName(boardName));
|
||||||
|
QAction *wholeBoardAction = boardSubmenu->addAction(InnerDecklistNode::visibleNameFromName(boardName));
|
||||||
|
connect(wholeBoardAction, &QAction::triggered, this,
|
||||||
|
[this, card, boardName] { emit cardAdded(card->getName(), boardName); });
|
||||||
|
for (const QString &zoneName : customZones) {
|
||||||
|
QAction *action = boardSubmenu->addAction(zoneName);
|
||||||
|
connect(action, &QAction::triggered, this,
|
||||||
|
[this, card, zoneName] { emit cardAdded(card->getName(), zoneName); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newZoneHandler) {
|
||||||
|
addToZoneMenu->addSeparator();
|
||||||
|
|
||||||
|
QAction *newZoneAction = addToZoneMenu->addAction(tr("Create &new zone..."));
|
||||||
|
connect(newZoneAction, &QAction::triggered, this, [this, card] {
|
||||||
|
const QString zoneName = newZoneHandler();
|
||||||
|
if (!zoneName.isEmpty()) {
|
||||||
|
emit cardAdded(card->getName(), zoneName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (canBeCommander(*card)) {
|
if (canBeCommander(*card)) {
|
||||||
QAction *edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)"));
|
QAction *edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)"));
|
||||||
connect(edhRecCommander, &QAction::triggered, this, [this, card] { emit edhrecClicked(card, true); });
|
connect(edhRecCommander, &QAction::triggered, this, [this, card] { emit edhrecClicked(card, true); });
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "../../key_signals.h"
|
#include "../../key_signals.h"
|
||||||
|
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
#include <functional>
|
||||||
#include <libcockatrice/card/card_info.h>
|
#include <libcockatrice/card/card_info.h>
|
||||||
|
|
||||||
class CardDatabaseModel;
|
class CardDatabaseModel;
|
||||||
|
|
@ -19,6 +20,13 @@ class CardDatabaseView : public QTreeView
|
||||||
KeySignals searchKeySignals;
|
KeySignals searchKeySignals;
|
||||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||||
|
|
||||||
|
/// Provides the custom zones available in the current deck, grouped by board zone.
|
||||||
|
/// The list contains (board zone name, custom zone names) pairs for every board.
|
||||||
|
std::function<QList<QPair<QString, QStringList>>()> zoneMenuProvider;
|
||||||
|
/// Handler invoked when the user picks "New zone..." from the add-to-zone menu.
|
||||||
|
/// Returns the name of the created zone, or an empty string if creation was cancelled.
|
||||||
|
std::function<QString()> newZoneHandler;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CardDatabaseView(QWidget *parent, CardDatabaseDisplayModel *model);
|
explicit CardDatabaseView(QWidget *parent, CardDatabaseDisplayModel *model);
|
||||||
|
|
||||||
|
|
@ -33,6 +41,17 @@ public:
|
||||||
return &searchKeySignals;
|
return &searchKeySignals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the provider used to populate the "Add to zone" submenu of the context menu.
|
||||||
|
* If no provider is set, the submenu is not shown.
|
||||||
|
*
|
||||||
|
* @param provider Returns the custom zones of the current deck, grouped by board zone
|
||||||
|
* @param newZoneHandler Creates a new custom zone and returns its name, or an empty string
|
||||||
|
* if creation was cancelled. The menu entry is hidden when not provided.
|
||||||
|
*/
|
||||||
|
void setZoneMenuProvider(const std::function<QList<QPair<QString, QStringList>>()> &provider,
|
||||||
|
const std::function<QString()> &newZoneHandler);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void cardChanged(const QString &cardName);
|
void cardChanged(const QString &cardName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
#include "deck_editor_card_database_dock_widget.h"
|
#include "deck_editor_card_database_dock_widget.h"
|
||||||
|
|
||||||
|
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
|
||||||
|
#include "card_database_view.h"
|
||||||
|
#include "deck_state_manager.h"
|
||||||
|
#include "deck_zone_dialog.h"
|
||||||
|
|
||||||
|
#include <libcockatrice/deck_list/deck_list_node_tree.h>
|
||||||
|
|
||||||
DeckEditorCardDatabaseDockWidget::DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent)
|
DeckEditorCardDatabaseDockWidget::DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent)
|
||||||
{
|
{
|
||||||
setObjectName("databaseDisplayDock");
|
setObjectName("databaseDisplayDock");
|
||||||
|
|
@ -15,6 +22,27 @@ void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeck
|
||||||
{
|
{
|
||||||
databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor->databaseModel);
|
databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor->databaseModel);
|
||||||
|
|
||||||
|
databaseDisplayWidget->getDatabaseView()->setZoneMenuProvider(
|
||||||
|
[deckEditor]() -> QList<QPair<QString, QStringList>> {
|
||||||
|
QList<QPair<QString, QStringList>> result;
|
||||||
|
auto *deckListModel = deckEditor->deckStateManager->getModel();
|
||||||
|
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||||
|
result.append({boardName, deckListModel->getCustomZoneNames(boardName)});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
[this, deckEditor]() -> QString {
|
||||||
|
QString boardName;
|
||||||
|
const QString zoneName =
|
||||||
|
DeckZoneDialog::promptForNewZone(this, {}, &boardName, [deckEditor](const QString &candidate) {
|
||||||
|
return deckEditor->deckStateManager->validateNewZoneName(candidate);
|
||||||
|
});
|
||||||
|
if (!zoneName.isEmpty()) {
|
||||||
|
deckEditor->deckStateManager->createCustomZone(boardName, zoneName);
|
||||||
|
}
|
||||||
|
return zoneName;
|
||||||
|
});
|
||||||
|
|
||||||
auto *frame = new QVBoxLayout;
|
auto *frame = new QVBoxLayout;
|
||||||
frame->setObjectName("databaseDisplayFrame");
|
frame->setObjectName("databaseDisplayFrame");
|
||||||
frame->addWidget(databaseDisplayWidget);
|
frame->addWidget(databaseDisplayWidget);
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,18 @@
|
||||||
#include "../tabs/api/commander_spellbook/commander_bracket_widget.h"
|
#include "../tabs/api/commander_spellbook/commander_bracket_widget.h"
|
||||||
#include "deck_list_style_proxy.h"
|
#include "deck_list_style_proxy.h"
|
||||||
#include "deck_state_manager.h"
|
#include "deck_state_manager.h"
|
||||||
|
#include "deck_zone_dialog.h"
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
|
#include <libcockatrice/deck_list/deck_list_node_tree.h>
|
||||||
#include <libcockatrice/settings/deck_editor_settings.h>
|
#include <libcockatrice/settings/deck_editor_settings.h>
|
||||||
#include <libcockatrice/settings/interface_settings.h>
|
#include <libcockatrice/settings/interface_settings.h>
|
||||||
#include <libcockatrice/utility/macros.h>
|
#include <libcockatrice/utility/macros.h>
|
||||||
|
|
@ -772,14 +775,213 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool i
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
||||||
{
|
{
|
||||||
|
const QModelIndex sourceIndex = proxy->mapToSource(deckView->indexAt(point));
|
||||||
|
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
|
|
||||||
|
const bool isCustomZoneRow = sourceIndex.isValid() && sourceIndex.data(DeckRoles::IsCustomZoneRole).toBool();
|
||||||
|
const bool isBoardZoneRow = sourceIndex.isValid() && !isCustomZoneRow && !sourceIndex.parent().isValid();
|
||||||
|
const bool isCardRow =
|
||||||
|
sourceIndex.isValid() && !isCustomZoneRow && !isBoardZoneRow && !getModel()->hasChildren(sourceIndex);
|
||||||
|
|
||||||
|
// Walk the row up to its top-level node to find the hosting board. Cards in
|
||||||
|
// the tokens board cannot be moved (moveCardToZone bails for it), so the
|
||||||
|
// move menu is skipped for them.
|
||||||
|
QString currentBoardName;
|
||||||
|
QModelIndex board = sourceIndex.parent();
|
||||||
|
while (board.isValid() && board.parent().isValid()) {
|
||||||
|
board = board.parent();
|
||||||
|
}
|
||||||
|
if (board.isValid()) {
|
||||||
|
currentBoardName = board.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCardRow) {
|
||||||
|
if (currentBoardName != DECK_ZONE_TOKENS) {
|
||||||
|
addMoveToZoneMenu(&menu, sourceIndex, currentBoardName);
|
||||||
|
menu.addSeparator();
|
||||||
|
}
|
||||||
|
} else if (isCustomZoneRow) {
|
||||||
|
const QString zoneName =
|
||||||
|
sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
|
||||||
|
QAction *renameAction = menu.addAction(tr("&Rename zone..."));
|
||||||
|
connect(renameAction, &QAction::triggered, this, [this, zoneName] {
|
||||||
|
// The unchanged name must not validate as a duplicate.
|
||||||
|
const QString newName =
|
||||||
|
DeckZoneDialog::promptForRename(this, zoneName, [this, zoneName](const QString &candidate) {
|
||||||
|
return candidate == zoneName ? QString() : deckStateManager->validateNewZoneName(candidate);
|
||||||
|
});
|
||||||
|
if (!newName.isEmpty() && newName != zoneName) {
|
||||||
|
deckStateManager->renameCustomZone(zoneName, newName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QMenu *boardMenu = menu.addMenu(tr("Change &board"));
|
||||||
|
addChangeBoardMenu(boardMenu, zoneName);
|
||||||
|
|
||||||
|
QAction *deleteAction = menu.addAction(tr("&Delete zone"));
|
||||||
|
const bool zoneHasCards = getModel()->hasChildren(sourceIndex);
|
||||||
|
deleteAction->setEnabled(!zoneHasCards);
|
||||||
|
if (zoneHasCards) {
|
||||||
|
deleteAction->setToolTip(tr("Move or remove all cards first."));
|
||||||
|
menu.setToolTipsVisible(true);
|
||||||
|
}
|
||||||
|
connect(deleteAction, &QAction::triggered, this, [this, zoneName] {
|
||||||
|
const auto result =
|
||||||
|
QMessageBox::warning(this, tr("Delete zone"), tr("Delete the zone \"%1\"?").arg(zoneName),
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||||
|
if (result == QMessageBox::Yes) {
|
||||||
|
deckStateManager->removeCustomZone(zoneName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
menu.addSeparator();
|
||||||
|
} else if (isBoardZoneRow) {
|
||||||
|
const QString boardName =
|
||||||
|
sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
// Tokens cannot host custom zones, so only offer the action on real boards.
|
||||||
|
const bool canHostCustomZones =
|
||||||
|
boardName == DECK_ZONE_MAIN || boardName == DECK_ZONE_SIDE || boardName == DECK_ZONE_MAYBEBOARD;
|
||||||
|
if (canHostCustomZones) {
|
||||||
|
addNewZoneAction(&menu, boardName);
|
||||||
|
menu.addSeparator();
|
||||||
|
}
|
||||||
|
} else if (!sourceIndex.isValid()) {
|
||||||
|
addNewZoneAction(&menu);
|
||||||
|
menu.addSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
|
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
|
||||||
connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector);
|
connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector);
|
||||||
|
|
||||||
menu.exec(deckView->mapToGlobal(point));
|
menu.exec(deckView->mapToGlobal(point));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::addMoveToZoneMenu(QMenu *menu,
|
||||||
|
const QModelIndex &sourceCardIndex,
|
||||||
|
const QString ¤tBoardName)
|
||||||
|
{
|
||||||
|
// The card's current *zone*, derived with the same ancestor walk as
|
||||||
|
// DeckStateManager::moveCardToZone (nearest custom-zone ancestor, else the
|
||||||
|
// top-level board/zone): a card inside "Removal" under the maindeck lives in
|
||||||
|
// "Removal", not "main". Comparing against that instead of the board keeps
|
||||||
|
// the enabled state and the same-zone no-op consistent with the move logic.
|
||||||
|
QString currentZoneName;
|
||||||
|
for (QModelIndex ancestor = sourceCardIndex.parent(); ancestor.isValid(); ancestor = ancestor.parent()) {
|
||||||
|
if (ancestor.data(DeckRoles::IsCustomZoneRole).toBool() || !ancestor.parent().isValid()) {
|
||||||
|
currentZoneName = ancestor.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto addMoveAction = [this, sourceCardIndex](QMenu *targetMenu, const QString &targetZoneName,
|
||||||
|
const QString &label, bool enabled) {
|
||||||
|
QAction *action = targetMenu->addAction(label);
|
||||||
|
action->setEnabled(enabled);
|
||||||
|
if (enabled) {
|
||||||
|
connect(action, &QAction::triggered, this, [this, sourceCardIndex, targetZoneName] {
|
||||||
|
deckStateManager->moveCardToZone(sourceCardIndex, targetZoneName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto tree = deckStateManager->getDeckListShared()->getTree();
|
||||||
|
|
||||||
|
QMenu *moveMenu = menu->addMenu(tr("Move to &zone"));
|
||||||
|
|
||||||
|
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||||
|
const QString boardLabel = InnerDecklistNode::visibleNameFromName(boardName);
|
||||||
|
const auto customZones = tree->getCustomZones(boardName);
|
||||||
|
|
||||||
|
// Boards with zones nest their children so no two menu entries share a
|
||||||
|
// visible name: "Maindeck ▸ { Maindeck (whole board), Removal, … }".
|
||||||
|
// The board the card already lives on is marked instead of offered.
|
||||||
|
if (!customZones.isEmpty()) {
|
||||||
|
QMenu *boardSubmenu = moveMenu->addMenu(boardLabel);
|
||||||
|
addMoveAction(boardSubmenu, boardName, boardLabel, boardName != currentZoneName);
|
||||||
|
for (const auto *customZone : customZones) {
|
||||||
|
addMoveAction(boardSubmenu, customZone->getName(), customZone->getName(),
|
||||||
|
customZone->getName() != currentZoneName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addMoveAction(moveMenu, boardName, boardLabel, boardName != currentZoneName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveMenu->addSeparator();
|
||||||
|
|
||||||
|
QAction *newZoneAction = moveMenu->addAction(tr("Create new zone and move &here..."));
|
||||||
|
connect(newZoneAction, &QAction::triggered, this, [this, sourceCardIndex, currentBoardName, currentZoneName] {
|
||||||
|
// Resolve the card's identity before creating the zone:
|
||||||
|
// createNewCustomZone rebuilds the model tree, so sourceCardIndex's
|
||||||
|
// internal pointer is freed by the time it would be used.
|
||||||
|
const QString cardName =
|
||||||
|
sourceCardIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
const QString providerId =
|
||||||
|
sourceCardIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString();
|
||||||
|
const QString collectorNumber = sourceCardIndex.siblingAtColumn(DeckListModelColumns::CARD_COLLECTOR_NUMBER)
|
||||||
|
.data(Qt::DisplayRole)
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
const QString zoneName = createNewCustomZone(currentBoardName);
|
||||||
|
if (!zoneName.isEmpty()) {
|
||||||
|
// Re-find the card: the old index is no longer safe since rows were
|
||||||
|
// rebuilt. Mirror DeckStateManager::decrementCard's re-find pattern.
|
||||||
|
const QModelIndex refreshed = getModel()->findCard(cardName, currentZoneName, providerId, collectorNumber);
|
||||||
|
if (refreshed.isValid()) {
|
||||||
|
deckStateManager->moveCardToZone(refreshed, zoneName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::addChangeBoardMenu(QMenu *menu, const QString &zoneName)
|
||||||
|
{
|
||||||
|
const auto tree = deckStateManager->getDeckListShared()->getTree();
|
||||||
|
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||||
|
QAction *action = menu->addAction(InnerDecklistNode::visibleNameFromName(boardName));
|
||||||
|
|
||||||
|
// The board currently holding the zone is marked instead of offered.
|
||||||
|
// Duplicate names cannot come up through the editor, so this doubles as
|
||||||
|
// the uniqueness guard for imported decks.
|
||||||
|
bool holdsTheZone = false;
|
||||||
|
for (const auto *customZone : tree->getCustomZones(boardName)) {
|
||||||
|
if (customZone->getName() == zoneName) {
|
||||||
|
holdsTheZone = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (holdsTheZone) {
|
||||||
|
action->setCheckable(true);
|
||||||
|
action->setChecked(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(action, &QAction::triggered, this,
|
||||||
|
[this, zoneName, boardName] { deckStateManager->moveCustomZone(zoneName, boardName); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::addNewZoneAction(QMenu *menu, const QString &initialBoardName)
|
||||||
|
{
|
||||||
|
QAction *newZoneAction = menu->addAction(tr("Create &new zone..."));
|
||||||
|
connect(newZoneAction, &QAction::triggered, this,
|
||||||
|
[this, initialBoardName] { createNewCustomZone(initialBoardName); });
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeckEditorDeckDockWidget::createNewCustomZone(const QString &initialBoardName)
|
||||||
|
{
|
||||||
|
QString boardName;
|
||||||
|
const QString zoneName =
|
||||||
|
DeckZoneDialog::promptForNewZone(this, initialBoardName, &boardName, [this](const QString &candidate) {
|
||||||
|
return deckStateManager->validateNewZoneName(candidate);
|
||||||
|
});
|
||||||
|
if (!zoneName.isEmpty()) {
|
||||||
|
deckStateManager->createCustomZone(boardName, zoneName);
|
||||||
|
}
|
||||||
|
return zoneName;
|
||||||
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::refreshShortcuts()
|
void DeckEditorDeckDockWidget::refreshShortcuts()
|
||||||
{
|
{
|
||||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QMenu>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
|
@ -102,6 +103,11 @@ private:
|
||||||
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
|
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
|
||||||
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
|
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
|
||||||
|
|
||||||
|
void addMoveToZoneMenu(QMenu *menu, const QModelIndex &sourceCardIndex, const QString ¤tBoardName);
|
||||||
|
void addChangeBoardMenu(QMenu *menu, const QString &zoneName);
|
||||||
|
QString createNewCustomZone(const QString &initialBoardName = {});
|
||||||
|
void addNewZoneAction(QMenu *menu, const QString &initialBoardName = {});
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void decklistCustomMenu(QPoint point);
|
void decklistCustomMenu(QPoint point);
|
||||||
void updateCard(QModelIndex, const QModelIndex ¤t);
|
void updateCard(QModelIndex, const QModelIndex ¤t);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
||||||
|
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
|
||||||
|
|
||||||
DeckStateManager::DeckStateManager(QObject *parent)
|
DeckStateManager::DeckStateManager(QObject *parent)
|
||||||
: QObject(parent), deckList(QSharedPointer<DeckList>(new DeckList)),
|
: QObject(parent), deckList(QSharedPointer<DeckList>(new DeckList)),
|
||||||
|
|
@ -307,6 +308,170 @@ bool DeckStateManager::decrementCountAtIndex(const QModelIndex &idx)
|
||||||
return offsetCountAtIndex(idx, -1);
|
return offsetCountAtIndex(idx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DeckStateManager::moveCardToZone(const QModelIndex &idx, const QString &targetZoneName)
|
||||||
|
{
|
||||||
|
if (!idx.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only actual card rows can be moved. Group or zone rows report an
|
||||||
|
// aggregate amount and must never be deleted by this operation.
|
||||||
|
if (!idx.data(DeckRoles::IsCardRole).toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString();
|
||||||
|
int copies = idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt();
|
||||||
|
|
||||||
|
if (copies <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokens only live in the tokens zone and cannot be moved into decks.
|
||||||
|
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(cardName);
|
||||||
|
if (info && info->getIsToken()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the zone the card currently lives in: the enclosing custom
|
||||||
|
// zone, or the nearest top-level zone (board zone or legacy zone).
|
||||||
|
QString currentZoneName;
|
||||||
|
for (QModelIndex ancestor = idx.parent(); ancestor.isValid(); ancestor = ancestor.parent()) {
|
||||||
|
bool isCustomZone = ancestor.data(DeckRoles::IsCustomZoneRole).toBool();
|
||||||
|
if (isCustomZone || !ancestor.parent().isValid()) {
|
||||||
|
currentZoneName = ancestor.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentZoneName == targetZoneName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString reason = tr("Moved %1 × \"%2\" (%3) to %4")
|
||||||
|
.arg(copies)
|
||||||
|
.arg(cardName)
|
||||||
|
.arg(providerId)
|
||||||
|
.arg(InnerDecklistNode::visibleNameFromName(targetZoneName));
|
||||||
|
|
||||||
|
return modifyDeck(reason, [&idx, &cardName, &providerId, &targetZoneName, copies](auto model) {
|
||||||
|
if (!model->removeRow(idx.row(), idx.parent())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, providerId})) {
|
||||||
|
for (int i = 0; i < copies; ++i) {
|
||||||
|
model->addCard(card, targetZoneName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < copies; ++i) {
|
||||||
|
model->addPreferredPrintingCard(cardName, targetZoneName, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckStateManager::createCustomZone(const QString &boardZoneName, const QString &zoneName)
|
||||||
|
{
|
||||||
|
const QString trimmedZoneName = zoneName.trimmed();
|
||||||
|
if (trimmedZoneName.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString reason =
|
||||||
|
tr("Created zone \"%1\" in %2").arg(trimmedZoneName, InnerDecklistNode::visibleNameFromName(boardZoneName));
|
||||||
|
|
||||||
|
return modifyTree(reason, [&boardZoneName, &trimmedZoneName](DecklistNodeTree *tree) {
|
||||||
|
return tree->addCustomZone(boardZoneName, trimmedZoneName) != nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckStateManager::renameCustomZone(const QString &oldZoneName, const QString &newZoneName)
|
||||||
|
{
|
||||||
|
const QString trimmedNewZoneName = newZoneName.trimmed();
|
||||||
|
if (trimmedNewZoneName.isEmpty() || oldZoneName == trimmedNewZoneName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString reason = tr("Renamed zone \"%1\" to \"%2\"").arg(oldZoneName, trimmedNewZoneName);
|
||||||
|
|
||||||
|
return modifyTree(reason, [&oldZoneName, &trimmedNewZoneName](DecklistNodeTree *tree) {
|
||||||
|
return tree->renameCustomZone(oldZoneName, trimmedNewZoneName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckStateManager::moveCustomZone(const QString &zoneName, const QString &newBoardZoneName)
|
||||||
|
{
|
||||||
|
const auto *tree = deckList->getTree();
|
||||||
|
|
||||||
|
// Locate the zone through the tree's own lookup, which walks every top-level
|
||||||
|
// zone (not just the standard boards) and covers the same-board no-op below.
|
||||||
|
const auto *zone = tree->findCustomZoneByName(zoneName);
|
||||||
|
if (!zone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same-board moves are no-ops and must not pollute the history.
|
||||||
|
const QString currentBoardName = zone->getParent() ? zone->getParent()->getName() : QString();
|
||||||
|
if (currentBoardName == newBoardZoneName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone names are deck-unique among zones created through this manager, so a
|
||||||
|
// same-named zone on the target board can only come from an imported deck.
|
||||||
|
// Refuse the move instead of silently stacking same-named zones.
|
||||||
|
for (const auto *targetZone : tree->getCustomZones(newBoardZoneName)) {
|
||||||
|
if (targetZone->getName() == zoneName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString reason =
|
||||||
|
tr("Moved zone \"%1\" to %2").arg(zoneName, InnerDecklistNode::visibleNameFromName(newBoardZoneName));
|
||||||
|
|
||||||
|
return modifyTree(reason, [&zoneName, &newBoardZoneName](DecklistNodeTree *tree) {
|
||||||
|
return tree->moveCustomZone(zoneName, newBoardZoneName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeckStateManager::removeCustomZone(const QString &zoneName)
|
||||||
|
{
|
||||||
|
QString reason = tr("Deleted zone \"%1\"").arg(zoneName);
|
||||||
|
|
||||||
|
return modifyTree(reason, [&zoneName](DecklistNodeTree *tree) { return tree->removeCustomZone(zoneName); });
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeckStateManager::validateNewZoneName(const QString &zoneName) const
|
||||||
|
{
|
||||||
|
if (zoneName.trimmed().isEmpty()) {
|
||||||
|
return tr("Enter a zone name.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString trimmedZoneName = zoneName.trimmed();
|
||||||
|
|
||||||
|
// The standard zone names are reserved even before they exist.
|
||||||
|
if (trimmedZoneName == DECK_ZONE_MAIN || trimmedZoneName == DECK_ZONE_SIDE ||
|
||||||
|
trimmedZoneName == DECK_ZONE_MAYBEBOARD || trimmedZoneName == DECK_ZONE_TOKENS) {
|
||||||
|
return tr("This name is reserved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto *tree = deckList->getTree();
|
||||||
|
|
||||||
|
// Reuse the tree's own uniqueness contract: any top-level zone and any
|
||||||
|
// custom zone on *every* board claims the name (hasZoneName also reserves
|
||||||
|
// the standard board names, which we already rejected with a dedicated
|
||||||
|
// message above). Scanning only the standard boards here would miss a
|
||||||
|
// custom zone an imported deck carries under `tokens`.
|
||||||
|
if (tree->hasZoneName(trimmedZoneName)) {
|
||||||
|
return tr("A zone with this name already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool DeckStateManager::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
bool DeckStateManager::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
||||||
{
|
{
|
||||||
if (!idx.isValid()) {
|
if (!idx.isValid()) {
|
||||||
|
|
@ -367,6 +532,25 @@ void DeckStateManager::requestHistorySave(const QString &reason)
|
||||||
historyManager->save(deckList->createMemento(reason));
|
historyManager->save(deckList->createMemento(reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DeckStateManager::modifyTree(const QString &reason, const std::function<bool(DecklistNodeTree *)> &operation)
|
||||||
|
{
|
||||||
|
DeckListMemento memento = deckList->createMemento(reason);
|
||||||
|
bool success = operation(deckList->getTree());
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
historyManager->save(memento);
|
||||||
|
deckListModel->rebuildTree();
|
||||||
|
deckList->refreshDeckHash();
|
||||||
|
emit deckListModel->deckHashChanged();
|
||||||
|
// removeCustomZone can drop whole card sets the model never notified
|
||||||
|
// about (rebuildTree emits no cardNodesChanged), so tell the consumers.
|
||||||
|
emit deckListModel->cardNodesChanged();
|
||||||
|
doCardModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Handles updating state and emitting signals whenever the cards are modified
|
* @brief Handles updating state and emitting signals whenever the cards are modified
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "deck_list_model.h"
|
#include "deck_list_model.h"
|
||||||
|
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
#include <functional>
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
|
|
||||||
class DeckListHistoryManager;
|
class DeckListHistoryManager;
|
||||||
|
|
@ -236,6 +237,68 @@ public:
|
||||||
*/
|
*/
|
||||||
bool decrementCountAtIndex(const QModelIndex &idx);
|
bool decrementCountAtIndex(const QModelIndex &idx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Moves all copies of the card at the given index to the given zone.
|
||||||
|
* No-ops if the index is invalid, not a card node, the card is a token, or the
|
||||||
|
* card is already in the target zone.
|
||||||
|
* Saves the operation to history if successful.
|
||||||
|
*
|
||||||
|
* @param idx The model index of the card to move
|
||||||
|
* @param targetZoneName The zone to move the card to (board zone or custom zone name)
|
||||||
|
* @return Whether the operation was successfully performed
|
||||||
|
*/
|
||||||
|
bool moveCardToZone(const QModelIndex &idx, const QString &targetZoneName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a new custom zone nested under a board zone.
|
||||||
|
* Saves the operation to history if successful.
|
||||||
|
*
|
||||||
|
* @param boardZoneName The board zone to nest the custom zone under
|
||||||
|
* @param zoneName The name of the new custom zone. Gets trimmed and must be
|
||||||
|
* unique across the deck.
|
||||||
|
* @return Whether the zone was created
|
||||||
|
*/
|
||||||
|
bool createCustomZone(const QString &boardZoneName, const QString &zoneName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renames a custom zone.
|
||||||
|
* Saves the operation to history if successful.
|
||||||
|
*
|
||||||
|
* @param oldZoneName The current name of the custom zone
|
||||||
|
* @param newZoneName The new name. Gets trimmed and must be unique across the deck.
|
||||||
|
* @return Whether the rename succeeded
|
||||||
|
*/
|
||||||
|
bool renameCustomZone(const QString &oldZoneName, const QString &newZoneName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Moves a custom zone (and its cards) to a different board zone.
|
||||||
|
* Same-board moves succeed without creating a history entry.
|
||||||
|
* Saves the operation to history if successful.
|
||||||
|
*
|
||||||
|
* @param zoneName The custom zone to move
|
||||||
|
* @param newBoardZoneName The board zone to move the custom zone under
|
||||||
|
* @return Whether the move succeeded
|
||||||
|
*/
|
||||||
|
bool moveCustomZone(const QString &zoneName, const QString &newBoardZoneName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes a custom zone and all its cards.
|
||||||
|
* Saves the operation to history if successful.
|
||||||
|
*
|
||||||
|
* @param zoneName The custom zone to remove
|
||||||
|
* @return Whether the zone was removed
|
||||||
|
*/
|
||||||
|
bool removeCustomZone(const QString &zoneName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a candidate name is usable for a new custom zone.
|
||||||
|
*
|
||||||
|
* @param zoneName The candidate name
|
||||||
|
* @return An empty string when the name is usable, otherwise a user-facing
|
||||||
|
* error message describing the problem
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString validateNewZoneName(const QString &zoneName) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undoes n steps of the history, setting the decklist state and updating the current step in the historyManager.
|
* Undoes n steps of the history, setting the decklist state and updating the current step in the historyManager.
|
||||||
* @param steps Number of steps to undo.
|
* @param steps Number of steps to undo.
|
||||||
|
|
@ -257,6 +320,7 @@ public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool offsetCountAtIndex(const QModelIndex &idx, int offset);
|
bool offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||||
|
bool modifyTree(const QString &reason, const std::function<bool(DecklistNodeTree *)> &operation);
|
||||||
void doCardModified();
|
void doCardModified();
|
||||||
void doMetadataModified();
|
void doMetadataModified();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
#include "deck_zone_dialog.h"
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
|
||||||
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
|
DeckZoneDialog::DeckZoneDialog(QWidget *parent,
|
||||||
|
const QString &initialBoardName,
|
||||||
|
const std::function<QString(const QString &)> &_nameValidator,
|
||||||
|
bool _allowBoardSelection)
|
||||||
|
: QDialog(parent), nameValidator(_nameValidator), allowBoardSelection(_allowBoardSelection)
|
||||||
|
{
|
||||||
|
nameLabel = new QLabel(this);
|
||||||
|
nameEdit = new QLineEdit(this);
|
||||||
|
nameEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||||
|
|
||||||
|
errorLabel = new QLabel(this);
|
||||||
|
errorLabel->hide();
|
||||||
|
|
||||||
|
boardLabel = new QLabel(this);
|
||||||
|
boardCombo = new QComboBox(this);
|
||||||
|
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||||
|
// Use the icon overload explicitly so `boardName` lands in the user data role
|
||||||
|
// (visible text is applied below in retranslateUi). The two-argument form
|
||||||
|
// addItem({}, boardName) would be ambiguous and resolve to the icon overload
|
||||||
|
// with empty user data, yielding empty entries and an empty getBoardName().
|
||||||
|
boardCombo->addItem({}, {}, boardName);
|
||||||
|
}
|
||||||
|
if (!initialBoardName.isEmpty()) {
|
||||||
|
int idx = boardCombo->findData(initialBoardName);
|
||||||
|
if (idx != -1) {
|
||||||
|
boardCombo->setCurrentIndex(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
layout->addWidget(nameLabel);
|
||||||
|
layout->addWidget(nameEdit);
|
||||||
|
layout->addWidget(errorLabel);
|
||||||
|
if (allowBoardSelection) {
|
||||||
|
layout->addWidget(boardLabel);
|
||||||
|
layout->addWidget(boardCombo);
|
||||||
|
} else {
|
||||||
|
boardLabel->hide();
|
||||||
|
boardCombo->hide();
|
||||||
|
}
|
||||||
|
layout->addWidget(buttonBox);
|
||||||
|
|
||||||
|
retranslateUi();
|
||||||
|
|
||||||
|
connect(nameEdit, &QLineEdit::textChanged, this, [this] { validateName(); });
|
||||||
|
validateName();
|
||||||
|
|
||||||
|
nameEdit->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeckZoneDialog::getZoneName() const
|
||||||
|
{
|
||||||
|
return nameEdit->text().trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeckZoneDialog::getBoardName() const
|
||||||
|
{
|
||||||
|
return boardCombo->currentData().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckZoneDialog::setZoneName(const QString &zoneName)
|
||||||
|
{
|
||||||
|
nameEdit->setText(zoneName);
|
||||||
|
nameEdit->selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckZoneDialog::changeEvent(QEvent *event)
|
||||||
|
{
|
||||||
|
QDialog::changeEvent(event);
|
||||||
|
|
||||||
|
if (event->type() == QEvent::LanguageChange) {
|
||||||
|
retranslateUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckZoneDialog::retranslateUi()
|
||||||
|
{
|
||||||
|
setWindowTitle(allowBoardSelection ? tr("New zone") : tr("Rename zone"));
|
||||||
|
|
||||||
|
nameLabel->setText(tr("Zone &name:"));
|
||||||
|
nameLabel->setBuddy(nameEdit);
|
||||||
|
|
||||||
|
boardLabel->setText(tr("&Parent zone:"));
|
||||||
|
boardLabel->setBuddy(boardCombo);
|
||||||
|
|
||||||
|
for (int i = 0; i < boardCombo->count(); i++) {
|
||||||
|
boardCombo->setItemText(i, InnerDecklistNode::visibleNameFromName(boardCombo->itemData(i).toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckZoneDialog::validateName()
|
||||||
|
{
|
||||||
|
const QString zoneName = nameEdit->text().trimmed();
|
||||||
|
QString error;
|
||||||
|
if (zoneName.isEmpty()) {
|
||||||
|
error = tr("Enter a zone name.");
|
||||||
|
} else if (nameValidator) {
|
||||||
|
error = nameValidator(zoneName);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorLabel->setText(error);
|
||||||
|
errorLabel->setVisible(!error.isEmpty());
|
||||||
|
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(error.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeckZoneDialog::promptForNewZone(QWidget *parent,
|
||||||
|
const QString &initialBoardName,
|
||||||
|
QString *chosenBoardName,
|
||||||
|
const std::function<QString(const QString &)> &nameValidator)
|
||||||
|
{
|
||||||
|
DeckZoneDialog dialog(parent, initialBoardName, nameValidator);
|
||||||
|
if (dialog.exec() != QDialog::Accepted) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chosenBoardName) {
|
||||||
|
*chosenBoardName = dialog.getBoardName();
|
||||||
|
}
|
||||||
|
return dialog.getZoneName();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeckZoneDialog::promptForRename(QWidget *parent,
|
||||||
|
const QString ¤tZoneName,
|
||||||
|
const std::function<QString(const QString &)> &nameValidator)
|
||||||
|
{
|
||||||
|
DeckZoneDialog dialog(parent, {}, nameValidator, false);
|
||||||
|
dialog.setZoneName(currentZoneName);
|
||||||
|
return dialog.exec() == QDialog::Accepted ? dialog.getZoneName() : QString();
|
||||||
|
}
|
||||||
123
cockatrice/src/interface/widgets/deck_editor/deck_zone_dialog.h
Normal file
123
cockatrice/src/interface/widgets/deck_editor/deck_zone_dialog.h
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* @file deck_zone_dialog.h
|
||||||
|
* @ingroup DeckEditorWidgets
|
||||||
|
* @brief Shared dialog for creating custom deck zones.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DECK_ZONE_DIALOG_H
|
||||||
|
#define DECK_ZONE_DIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QString>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class QComboBox;
|
||||||
|
class QDialogButtonBox;
|
||||||
|
class QLabel;
|
||||||
|
class QLineEdit;
|
||||||
|
class QWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modal dialog asking for the name and parent zone of a new custom deck zone.
|
||||||
|
*
|
||||||
|
* Menus construct the dialog transiently around exec(), so validation state only
|
||||||
|
* ever reflects the name currently typed.
|
||||||
|
*/
|
||||||
|
class DeckZoneDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs the dialog and runs the initial validation pass.
|
||||||
|
*
|
||||||
|
* @param parent The parent widget for the dialog
|
||||||
|
* @param initialBoardName The board zone to preselect in the combo. Unknown names
|
||||||
|
* fall back to main.
|
||||||
|
* @param _nameValidator Given the trimmed candidate name, returns an empty string
|
||||||
|
* when it is usable, otherwise a user-facing error message. May be empty.
|
||||||
|
* @param _allowBoardSelection When false the parent-zone combo is hidden and the
|
||||||
|
* dialog acts as a rename prompt for an existing zone.
|
||||||
|
*/
|
||||||
|
explicit DeckZoneDialog(QWidget *parent = nullptr,
|
||||||
|
const QString &initialBoardName = {},
|
||||||
|
const std::function<QString(const QString &)> &_nameValidator = {},
|
||||||
|
bool _allowBoardSelection = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The trimmed zone name entered by the user.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString getZoneName() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The internal name of the board zone selected in the combo.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString getBoardName() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prefills the name field, e.g. with the current name when renaming.
|
||||||
|
*
|
||||||
|
* @param zoneName The text to put into the name field, selected for quick editing
|
||||||
|
*/
|
||||||
|
void setZoneName(const QString &zoneName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prompts the user for a new custom zone name and the board zone to nest it under.
|
||||||
|
*
|
||||||
|
* Convenience wrapper that runs DeckZoneDialog modally.
|
||||||
|
*
|
||||||
|
* @param parent The parent widget for the dialog
|
||||||
|
* @param initialBoardName The board zone to preselect in the dialog. Unknown names fall
|
||||||
|
* back to main.
|
||||||
|
* @param chosenBoardName (out) The internal name of the board zone the user chose
|
||||||
|
* @param nameValidator Optional validator forwarded to the dialog
|
||||||
|
* @return The trimmed zone name, or an empty string if the user cancelled
|
||||||
|
*/
|
||||||
|
static QString promptForNewZone(QWidget *parent,
|
||||||
|
const QString &initialBoardName,
|
||||||
|
QString *chosenBoardName,
|
||||||
|
const std::function<QString(const QString &)> &nameValidator = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prompts the user for a new name for an existing custom zone.
|
||||||
|
*
|
||||||
|
* Same inline validation as promptForNewZone, but without a parent-zone picker.
|
||||||
|
*
|
||||||
|
* @param parent The parent widget for the dialog
|
||||||
|
* @param currentZoneName The current name, prefilled for editing
|
||||||
|
* @param nameValidator Validator deciding whether a candidate name is usable. It sees
|
||||||
|
* the current name too, so callers wanting to allow unchanged names must
|
||||||
|
* special-case that themselves.
|
||||||
|
* @return The trimmed new name, or an empty string if the user cancelled
|
||||||
|
*/
|
||||||
|
static QString promptForRename(QWidget *parent,
|
||||||
|
const QString ¤tZoneName,
|
||||||
|
const std::function<QString(const QString &)> &nameValidator = {});
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void changeEvent(QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Sets every user-visible string. Runs on construction and on runtime
|
||||||
|
* language changes.
|
||||||
|
*/
|
||||||
|
void retranslateUi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Validates the current input, toggling Ok and the inline error label.
|
||||||
|
*/
|
||||||
|
void validateName();
|
||||||
|
|
||||||
|
QLabel *nameLabel;
|
||||||
|
QLineEdit *nameEdit;
|
||||||
|
QLabel *errorLabel;
|
||||||
|
QLabel *boardLabel;
|
||||||
|
QComboBox *boardCombo;
|
||||||
|
QDialogButtonBox *buttonBox;
|
||||||
|
std::function<QString(const QString &)> nameValidator;
|
||||||
|
bool allowBoardSelection;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DECK_ZONE_DIALOG_H
|
||||||
|
|
@ -132,7 +132,7 @@ void DlgSettings::setupUi()
|
||||||
pagesWidget->addWidget(makeScrollable(userInterfacePage));
|
pagesWidget->addWidget(makeScrollable(userInterfacePage));
|
||||||
pagesWidget->addWidget(makeScrollable(deckEditorPage));
|
pagesWidget->addWidget(makeScrollable(deckEditorPage));
|
||||||
pagesWidget->addWidget(makeScrollable(storagePage));
|
pagesWidget->addWidget(makeScrollable(storagePage));
|
||||||
pagesWidget->addWidget(messagesPage);
|
pagesWidget->addWidget(makeScrollable(messagesPage));
|
||||||
pagesWidget->addWidget(soundPage);
|
pagesWidget->addWidget(soundPage);
|
||||||
pagesWidget->addWidget(shortcutsPage);
|
pagesWidget->addWidget(shortcutsPage);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,13 @@ void FirstRunWizard::onCardDatabaseUpdateFinished(bool success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FirstRunWizard::onCardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total)
|
||||||
|
{
|
||||||
|
if (cardDatabasePage) {
|
||||||
|
cardDatabasePage->onUpdateProgress(stage, done, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FirstRunWizard::finish()
|
void FirstRunWizard::finish()
|
||||||
{
|
{
|
||||||
accept();
|
accept();
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@ public slots:
|
||||||
/** @brief Forwarded from MainWindow once the background card database update process exits. */
|
/** @brief Forwarded from MainWindow once the background card database update process exits. */
|
||||||
void onCardDatabaseUpdateFinished(bool success);
|
void onCardDatabaseUpdateFinished(bool success);
|
||||||
|
|
||||||
|
/** @brief Forwarded from MainWindow while the background card database update process runs. */
|
||||||
|
void onCardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
void changeEvent(QEvent *event) override;
|
void changeEvent(QEvent *event) override;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <climits>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
#include <libcockatrice/settings/updates_settings.h>
|
#include <libcockatrice/settings/updates_settings.h>
|
||||||
|
|
||||||
|
|
@ -179,6 +180,25 @@ void CardDatabaseSetupPage::onUpdateFinished(bool success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CardDatabaseSetupPage::onUpdateProgress(const QString &stage, qint64 done, qint64 total)
|
||||||
|
{
|
||||||
|
if (state != State::Running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progressBar->setRange(0, total > 0 ? static_cast<int>(qMin<qint64>(total, INT_MAX)) : 0);
|
||||||
|
progressBar->setValue(static_cast<int>(qMin<qint64>(done, INT_MAX)));
|
||||||
|
if (total > 0) {
|
||||||
|
const int percent = static_cast<int>((100.0 * done) / total);
|
||||||
|
if (stage == QLatin1String("download")) {
|
||||||
|
statusLabel->setText(tr("Downloading the card database (%1%)…").arg(percent));
|
||||||
|
} else if (stage == QLatin1String("scan")) {
|
||||||
|
statusLabel->setText(tr("Parsing the card database (%1%)…").arg(percent));
|
||||||
|
} else if (stage == QLatin1String("import")) {
|
||||||
|
statusLabel->setText(tr("Importing cards (%1%)…").arg(percent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString CardDatabaseSetupPage::nextButtonText() const
|
QString CardDatabaseSetupPage::nextButtonText() const
|
||||||
{
|
{
|
||||||
return state == State::NotStarted ? tr("Download") : QString();
|
return state == State::NotStarted ? tr("Download") : QString();
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ public:
|
||||||
void retranslateUi() override;
|
void retranslateUi() override;
|
||||||
|
|
||||||
void onUpdateFinished(bool success);
|
void onUpdateFinished(bool success);
|
||||||
|
void onUpdateProgress(const QString &stage, qint64 done, qint64 total);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateRequested();
|
void updateRequested();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "../../card_picture_loader/card_picture_loader.h"
|
#include "../../card_picture_loader/card_picture_loader.h"
|
||||||
#include "../cards/art_crop_attribution.h"
|
#include "../cards/art_crop_attribution.h"
|
||||||
|
#include "../cards/card_art_utils.h"
|
||||||
#include "../utility/completer_utils.h"
|
#include "../utility/completer_utils.h"
|
||||||
#include "card_database_display_model.h"
|
#include "card_database_display_model.h"
|
||||||
#include "card_database_model.h"
|
#include "card_database_model.h"
|
||||||
|
|
@ -276,7 +277,7 @@ void PlaymatSettingsDialog::reloadPreview()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPixmap = fullRes;
|
currentPixmap = CardArtUtils::rotateSidewaysLayoutArt(fullRes, card);
|
||||||
preview->setPixmap(currentPixmap);
|
preview->setPixmap(currentPixmap);
|
||||||
preview->setParams(currentParams);
|
preview->setParams(currentParams);
|
||||||
preview->setAttribution(buildArtAttribution(card));
|
preview->setAttribution(buildArtAttribution(card));
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,10 @@ void ReplayManager::processNewEvents(PlaybackMode playbackMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// backwards skip => always skip tap animation
|
// backwards skip => always skip tap animation
|
||||||
|
// backwards skip => always skip damage animation (battlefield shimmer / life counter flash)
|
||||||
if (playbackMode == BACKWARD_SKIP) {
|
if (playbackMode == BACKWARD_SKIP) {
|
||||||
options |= SKIP_TAP_ANIMATION;
|
options |= SKIP_TAP_ANIMATION;
|
||||||
|
options |= SKIP_DAMAGE_ANIMATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit eventReplayed(replay->event_list(currentEvent), options);
|
emit eventReplayed(replay->event_list(currentEvent), options);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "user_card_art_provider.h"
|
#include "user_card_art_provider.h"
|
||||||
|
|
||||||
#include "../../../card_picture_loader/card_picture_loader.h"
|
#include "../../../card_picture_loader/card_picture_loader.h"
|
||||||
|
#include "../../cards/card_art_utils.h"
|
||||||
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
|
|
@ -52,16 +53,25 @@ void UserCardArtProvider::requestCardArt(const QString &userName, const QString
|
||||||
processQueue();
|
processQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap UserCardArtProvider::cropCardArt(const QPixmap &fullRes)
|
QPixmap UserCardArtProvider::cropCardArt(const QPixmap &fullRes, const ExactCard &card)
|
||||||
{
|
{
|
||||||
const QSize sz = fullRes.size();
|
QPixmap source = fullRes;
|
||||||
|
|
||||||
|
// Sideways-layout cards (plane, siege/battle, split) store their landscape
|
||||||
|
// artwork rotated 90° inside a portrait frame. Rotate it upright first so
|
||||||
|
// the crop below lands on the horizontal art, mirroring the way
|
||||||
|
// CardInfoPictureWidget displays these cards.
|
||||||
|
const bool landscape = card.getInfo().getUiAttributes().landscapeOrientation;
|
||||||
|
source = CardArtUtils::rotateSidewaysLayoutArt(source, card);
|
||||||
|
|
||||||
|
const QSize sz = source.size();
|
||||||
const int marginX = sz.width() * 0.07;
|
const int marginX = sz.width() * 0.07;
|
||||||
const int topMargin = sz.height() * 0.11;
|
const int topMargin = landscape ? sz.height() * 0.05 : sz.height() * 0.11;
|
||||||
const int bottomMargin = sz.height() * 0.45;
|
const int bottomMargin = landscape ? sz.height() * 0.42 : sz.height() * 0.45;
|
||||||
|
|
||||||
const QRect foilRect(marginX, topMargin, sz.width() - 2 * marginX, sz.height() - topMargin - bottomMargin);
|
const QRect artRect(marginX, topMargin, sz.width() - 2 * marginX, sz.height() - topMargin - bottomMargin);
|
||||||
|
|
||||||
return fullRes.copy(foilRect.intersected(fullRes.rect()));
|
return source.copy(artRect.intersected(source.rect()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserCardArtProvider::insertIntoCache(const QString &key, const QPixmap &pixmap)
|
void UserCardArtProvider::insertIntoCache(const QString &key, const QPixmap &pixmap)
|
||||||
|
|
@ -111,7 +121,7 @@ void UserCardArtProvider::processQueue()
|
||||||
|
|
||||||
// Synchronous hit (already loaded/on disk)
|
// Synchronous hit (already loaded/on disk)
|
||||||
if (!fullRes.isNull()) {
|
if (!fullRes.isNull()) {
|
||||||
insertIntoCache(key, cropCardArt(fullRes));
|
insertIntoCache(key, cropCardArt(fullRes, card));
|
||||||
pending.remove(key);
|
pending.remove(key);
|
||||||
|
|
||||||
emit cardArtUpdated(userName);
|
emit cardArtUpdated(userName);
|
||||||
|
|
@ -135,7 +145,7 @@ void UserCardArtProvider::processQueue()
|
||||||
CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
|
CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
|
||||||
|
|
||||||
if (!fullRes.isNull()) {
|
if (!fullRes.isNull()) {
|
||||||
self->insertIntoCache(key, self->cropCardArt(fullRes));
|
self->insertIntoCache(key, self->cropCardArt(fullRes, card));
|
||||||
}
|
}
|
||||||
|
|
||||||
self->pending.remove(key);
|
self->pending.remove(key);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
#include <libcockatrice/card/printing/exact_card.h>
|
||||||
|
|
||||||
class UserCardArtProvider : public QObject
|
class UserCardArtProvider : public QObject
|
||||||
{
|
{
|
||||||
|
|
@ -16,7 +17,7 @@ public:
|
||||||
|
|
||||||
void requestCardArt(const QString &userName, const QString &cardName, const QString &providerId);
|
void requestCardArt(const QString &userName, const QString &cardName, const QString &providerId);
|
||||||
const QMap<QString, QPixmap> &cache() const;
|
const QMap<QString, QPixmap> &cache() const;
|
||||||
static QPixmap cropCardArt(const QPixmap &fullRes);
|
static QPixmap cropCardArt(const QPixmap &fullRes, const ExactCard &card);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void cardArtUpdated(const QString &userName);
|
void cardArtUpdated(const QString &userName);
|
||||||
|
|
|
||||||
|
|
@ -560,7 +560,7 @@ void UserCardArtSettingsDialog::reloadPreview()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPixmap = UserCardArtProvider::cropCardArt(fullRes);
|
currentPixmap = UserCardArtProvider::cropCardArt(fullRes, card);
|
||||||
preview->setPixmap(currentPixmap);
|
preview->setPixmap(currentPixmap);
|
||||||
preview->setParams(currentParams);
|
preview->setParams(currentParams);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -452,7 +452,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aDetails->setEnabled(true);
|
aDetails->setEnabled(true);
|
||||||
aChat->setEnabled(anotherUser && online);
|
aChat->setEnabled(anotherUser && online && !userListProxy->isUserIgnored(userName));
|
||||||
aShowGames->setEnabled(online);
|
aShowGames->setEnabled(online);
|
||||||
aReport->setEnabled(anotherUser);
|
aReport->setEnabled(anotherUser);
|
||||||
aAddToBuddyList->setEnabled(anotherUser);
|
aAddToBuddyList->setEnabled(anotherUser);
|
||||||
|
|
@ -625,7 +625,15 @@ void UserContextMenu::execAddToIgnore(const QString &userName)
|
||||||
Command_AddToList cmd;
|
Command_AddToList cmd;
|
||||||
cmd.set_list("ignore");
|
cmd.set_list("ignore");
|
||||||
cmd.set_user_name(userName.toStdString());
|
cmd.set_user_name(userName.toStdString());
|
||||||
client->sendCommand(client->prepareSessionCommand(cmd));
|
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||||
|
connect(pend, &PendingCommand::finished, this,
|
||||||
|
[this, userName](const Response &response, const CommandContainer &, const QVariant &) {
|
||||||
|
if (response.response_code() == Response::RespOk) {
|
||||||
|
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Ignore list"),
|
||||||
|
tr("%1 has been added to your ignore list.").arg(userName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client->sendCommand(pend);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserContextMenu::execRemoveFromIgnore(const QString &userName)
|
void UserContextMenu::execRemoveFromIgnore(const QString &userName)
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,48 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||||
homeTabGroupBox = new QGroupBox;
|
homeTabGroupBox = new QGroupBox;
|
||||||
homeTabGroupBox->setLayout(homeTabGrid);
|
homeTabGroupBox->setLayout(homeTabGrid);
|
||||||
|
|
||||||
|
// Playmat settings
|
||||||
|
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
|
||||||
|
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
|
||||||
|
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
|
||||||
|
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
|
||||||
|
if (visIdx >= 0) {
|
||||||
|
playmatVisibilityCombo.setCurrentIndex(visIdx);
|
||||||
|
}
|
||||||
|
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||||
|
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
|
||||||
|
});
|
||||||
|
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
|
||||||
|
|
||||||
|
// Playmat mode: Override / Fallback / Deck-only
|
||||||
|
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
|
||||||
|
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
|
||||||
|
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
|
||||||
|
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
|
||||||
|
if (modeIdx >= 0) {
|
||||||
|
playmatModeCombo.setCurrentIndex(modeIdx);
|
||||||
|
}
|
||||||
|
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||||
|
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
|
||||||
|
});
|
||||||
|
playmatModeLabel.setBuddy(&playmatModeCombo);
|
||||||
|
|
||||||
|
// User-level playmat settings: fallback collection.
|
||||||
|
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
|
||||||
|
&AppearanceSettingsPage::openPlaymatCollectionDialog);
|
||||||
|
|
||||||
|
auto *playmatGrid = new QGridLayout;
|
||||||
|
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
|
||||||
|
|
||||||
|
playmatGroupBox = new QGroupBox;
|
||||||
|
playmatGroupBox->setLayout(playmatGrid);
|
||||||
|
|
||||||
|
// Styling settings
|
||||||
styleUserListCheckBox.setChecked(settings.appearance().getStyleUserList());
|
styleUserListCheckBox.setChecked(settings.appearance().getStyleUserList());
|
||||||
connect(&styleUserListCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
|
connect(&styleUserListCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
|
||||||
&AppearanceSettings::setStyleUserList);
|
&AppearanceSettings::setStyleUserList);
|
||||||
|
|
@ -259,7 +301,6 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||||
cardLayoutGroupBox->setLayout(cardLayoutGrid);
|
cardLayoutGroupBox->setLayout(cardLayoutGrid);
|
||||||
|
|
||||||
// Card counter colors
|
// Card counter colors
|
||||||
|
|
||||||
auto *cardCounterColorsLayout = new QGridLayout;
|
auto *cardCounterColorsLayout = new QGridLayout;
|
||||||
cardCounterColorsLayout->setColumnStretch(1, 1);
|
cardCounterColorsLayout->setColumnStretch(1, 1);
|
||||||
cardCounterColorsLayout->setColumnStretch(3, 1);
|
cardCounterColorsLayout->setColumnStretch(3, 1);
|
||||||
|
|
@ -339,47 +380,6 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||||
tableGroupBox = new QGroupBox;
|
tableGroupBox = new QGroupBox;
|
||||||
tableGroupBox->setLayout(tableGrid);
|
tableGroupBox->setLayout(tableGrid);
|
||||||
|
|
||||||
// Playmat settings
|
|
||||||
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
|
|
||||||
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
|
|
||||||
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
|
|
||||||
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
|
|
||||||
if (visIdx >= 0) {
|
|
||||||
playmatVisibilityCombo.setCurrentIndex(visIdx);
|
|
||||||
}
|
|
||||||
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
|
||||||
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
|
|
||||||
});
|
|
||||||
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
|
|
||||||
|
|
||||||
// Playmat mode: Override / Fallback / Deck-only
|
|
||||||
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
|
|
||||||
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
|
|
||||||
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
|
|
||||||
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
|
|
||||||
if (modeIdx >= 0) {
|
|
||||||
playmatModeCombo.setCurrentIndex(modeIdx);
|
|
||||||
}
|
|
||||||
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
|
||||||
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
|
|
||||||
});
|
|
||||||
playmatModeLabel.setBuddy(&playmatModeCombo);
|
|
||||||
|
|
||||||
// User-level playmat settings: fallback collection.
|
|
||||||
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
|
|
||||||
&AppearanceSettingsPage::openPlaymatCollectionDialog);
|
|
||||||
|
|
||||||
auto *playmatGrid = new QGridLayout;
|
|
||||||
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
|
|
||||||
|
|
||||||
playmatGroupBox = new QGroupBox;
|
|
||||||
playmatGroupBox->setLayout(playmatGrid);
|
|
||||||
|
|
||||||
// putting it all together
|
// putting it all together
|
||||||
auto *mainLayout = new QVBoxLayout;
|
auto *mainLayout = new QVBoxLayout;
|
||||||
mainLayout->addWidget(themeGroupBox);
|
mainLayout->addWidget(themeGroupBox);
|
||||||
|
|
@ -512,6 +512,12 @@ void AppearanceSettingsPage::retranslateUi()
|
||||||
homeTabButtonColorSourceBox.setToolTip(
|
homeTabButtonColorSourceBox.setToolTip(
|
||||||
tr("Automatic: extract from background if present, otherwise use theme default"));
|
tr("Automatic: extract from background if present, otherwise use theme default"));
|
||||||
|
|
||||||
|
playmatGroupBox->setTitle(tr("Playmat settings"));
|
||||||
|
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
|
||||||
|
playmatModeLabel.setText(tr("Default collection behavior:"));
|
||||||
|
playmatDefaultLabel.setText(tr("Default playmat collection:"));
|
||||||
|
playmatDefaultEditButton.setText(tr("Edit..."));
|
||||||
|
|
||||||
stylingGroupBox->setTitle(tr("Styling settings"));
|
stylingGroupBox->setTitle(tr("Styling settings"));
|
||||||
styleUserListCheckBox.setText(tr("Style user list"));
|
styleUserListCheckBox.setText(tr("Style user list"));
|
||||||
|
|
||||||
|
|
@ -554,9 +560,4 @@ void AppearanceSettingsPage::retranslateUi()
|
||||||
tableGroupBox->setTitle(tr("Table grid layout"));
|
tableGroupBox->setTitle(tr("Table grid layout"));
|
||||||
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
|
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
|
||||||
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
|
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
|
||||||
playmatGroupBox->setTitle(tr("Playmat settings"));
|
|
||||||
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
|
|
||||||
playmatModeLabel.setText(tr("Default collection behavior:"));
|
|
||||||
playmatDefaultLabel.setText(tr("Default playmat collection:"));
|
|
||||||
playmatDefaultEditButton.setText(tr("Edit..."));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,46 +44,55 @@ private:
|
||||||
QLabel homeTabButtonColorSourceLabel;
|
QLabel homeTabButtonColorSourceLabel;
|
||||||
QComboBox homeTabButtonColorSourceBox;
|
QComboBox homeTabButtonColorSourceBox;
|
||||||
|
|
||||||
QCheckBox styleUserListCheckBox;
|
|
||||||
QCheckBox showShortcutsCheckBox;
|
|
||||||
QCheckBox showGameSelectorFilterToolbarCheckBox;
|
|
||||||
QLabel minPlayersForMultiColumnLayoutLabel;
|
|
||||||
QLabel maxFontSizeForCardsLabel;
|
|
||||||
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
|
|
||||||
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
|
|
||||||
QCheckBox displayCardNamesCheckBox;
|
|
||||||
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
|
|
||||||
QCheckBox cardScalingCheckBox;
|
|
||||||
QCheckBox roundCardCornersCheckBox;
|
|
||||||
QLabel verticalCardOverlapPercentLabel;
|
|
||||||
QSpinBox verticalCardOverlapPercentBox;
|
|
||||||
QLabel cardViewInitialRowsMaxLabel;
|
|
||||||
QSpinBox cardViewInitialRowsMaxBox;
|
|
||||||
QLabel cardViewExpandedRowsMaxLabel;
|
|
||||||
QSpinBox cardViewExpandedRowsMaxBox;
|
|
||||||
QCheckBox horizontalHandCheckBox;
|
|
||||||
QCheckBox leftJustifiedHandCheckBox;
|
|
||||||
QCheckBox invertVerticalCoordinateCheckBox;
|
|
||||||
QLabel playmatVisibilityLabel;
|
QLabel playmatVisibilityLabel;
|
||||||
QComboBox playmatVisibilityCombo;
|
QComboBox playmatVisibilityCombo;
|
||||||
QLabel playmatModeLabel;
|
QLabel playmatModeLabel;
|
||||||
QComboBox playmatModeCombo;
|
QComboBox playmatModeCombo;
|
||||||
QLabel playmatDefaultLabel;
|
QLabel playmatDefaultLabel;
|
||||||
QPushButton playmatDefaultEditButton;
|
QPushButton playmatDefaultEditButton;
|
||||||
|
|
||||||
|
QCheckBox styleUserListCheckBox;
|
||||||
|
|
||||||
|
QCheckBox showShortcutsCheckBox;
|
||||||
|
QCheckBox showGameSelectorFilterToolbarCheckBox;
|
||||||
|
|
||||||
|
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
|
||||||
|
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
|
||||||
|
|
||||||
|
QCheckBox displayCardNamesCheckBox;
|
||||||
|
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
|
||||||
|
QCheckBox cardScalingCheckBox;
|
||||||
|
QCheckBox roundCardCornersCheckBox;
|
||||||
|
QLabel maxFontSizeForCardsLabel;
|
||||||
|
QSpinBox maxFontSizeForCardsEdit;
|
||||||
|
|
||||||
|
QLabel verticalCardOverlapPercentLabel;
|
||||||
|
QSpinBox verticalCardOverlapPercentBox;
|
||||||
|
QLabel cardViewInitialRowsMaxLabel;
|
||||||
|
QSpinBox cardViewInitialRowsMaxBox;
|
||||||
|
QLabel cardViewExpandedRowsMaxLabel;
|
||||||
|
QSpinBox cardViewExpandedRowsMaxBox;
|
||||||
|
|
||||||
|
QList<QLabel *> cardCounterNames;
|
||||||
|
|
||||||
|
QCheckBox horizontalHandCheckBox;
|
||||||
|
QCheckBox leftJustifiedHandCheckBox;
|
||||||
|
|
||||||
|
QCheckBox invertVerticalCoordinateCheckBox;
|
||||||
|
QLabel minPlayersForMultiColumnLayoutLabel;
|
||||||
|
QSpinBox minPlayersForMultiColumnLayoutEdit;
|
||||||
|
|
||||||
QGroupBox *themeGroupBox;
|
QGroupBox *themeGroupBox;
|
||||||
QGroupBox *homeTabGroupBox;
|
QGroupBox *homeTabGroupBox;
|
||||||
|
QGroupBox *playmatGroupBox;
|
||||||
QGroupBox *stylingGroupBox;
|
QGroupBox *stylingGroupBox;
|
||||||
QGroupBox *menuGroupBox;
|
QGroupBox *menuGroupBox;
|
||||||
QGroupBox *printingsGroupBox;
|
QGroupBox *printingsGroupBox;
|
||||||
QGroupBox *cardsGroupBox;
|
QGroupBox *cardsGroupBox;
|
||||||
QGroupBox *cardLayoutGroupBox;
|
QGroupBox *cardLayoutGroupBox;
|
||||||
QGroupBox *handGroupBox;
|
|
||||||
QGroupBox *playmatGroupBox;
|
|
||||||
QGroupBox *tableGroupBox;
|
|
||||||
QGroupBox *cardCountersGroupBox;
|
QGroupBox *cardCountersGroupBox;
|
||||||
QList<QLabel *> cardCounterNames;
|
QGroupBox *handGroupBox;
|
||||||
QSpinBox minPlayersForMultiColumnLayoutEdit;
|
QGroupBox *tableGroupBox;
|
||||||
QSpinBox maxFontSizeForCardsEdit;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AppearanceSettingsPage();
|
AppearanceSettingsPage();
|
||||||
|
|
|
||||||
|
|
@ -425,29 +425,28 @@ void GeneralSettingsPage::updateStartupServerControlsVisibility()
|
||||||
|
|
||||||
void GeneralSettingsPage::retranslateUi()
|
void GeneralSettingsPage::retranslateUi()
|
||||||
{
|
{
|
||||||
|
const auto &settings = SettingsCache::instance();
|
||||||
|
|
||||||
languageGroupBox->setTitle(tr("Language settings"));
|
languageGroupBox->setTitle(tr("Language settings"));
|
||||||
languageLabel.setText(tr("Language:"));
|
languageLabel.setText(tr("Language:"));
|
||||||
|
|
||||||
versionGroupBox->setTitle(tr("Version settings"));
|
|
||||||
cardDatabaseGroupBox->setTitle(tr("Card database"));
|
|
||||||
startupGroupBox->setTitle(tr("Startup settings"));
|
|
||||||
|
|
||||||
if (SettingsCache::instance().getIsPortableBuild()) {
|
|
||||||
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
|
|
||||||
} else {
|
|
||||||
pathsGroupBox->setTitle(tr("Paths"));
|
|
||||||
}
|
|
||||||
advertiseTranslationPageLabel.setText(
|
advertiseTranslationPageLabel.setText(
|
||||||
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
|
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
|
||||||
deckPathLabel.setText(tr("Decks directory:"));
|
|
||||||
filtersPathLabel.setText(tr("Filters directory:"));
|
versionGroupBox->setTitle(tr("Version settings"));
|
||||||
replaysPathLabel.setText(tr("Replays directory:"));
|
|
||||||
picsPathLabel.setText(tr("Pictures directory:"));
|
|
||||||
cardDatabasePathLabel.setText(tr("Card database:"));
|
|
||||||
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
|
|
||||||
tokenDatabasePathLabel.setText(tr("Token database:"));
|
|
||||||
updateReleaseChannelLabel.setText(tr("Update channel"));
|
updateReleaseChannelLabel.setText(tr("Update channel"));
|
||||||
startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup"));
|
startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup"));
|
||||||
|
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
|
||||||
|
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
|
||||||
|
|
||||||
|
// We can't change the strings after they're put into the QComboBox, so this is our workaround
|
||||||
|
int oldIndex = updateReleaseChannelBox.currentIndex();
|
||||||
|
updateReleaseChannelBox.clear();
|
||||||
|
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
|
||||||
|
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
|
||||||
|
}
|
||||||
|
updateReleaseChannelBox.setCurrentIndex(oldIndex);
|
||||||
|
|
||||||
|
cardDatabaseGroupBox->setTitle(tr("Card database"));
|
||||||
startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup"));
|
startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup"));
|
||||||
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check"));
|
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check"));
|
||||||
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt,
|
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt,
|
||||||
|
|
@ -456,8 +455,13 @@ void GeneralSettingsPage::retranslateUi()
|
||||||
tr("Always update in the background"));
|
tr("Always update in the background"));
|
||||||
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
|
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
|
||||||
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
|
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
|
||||||
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
|
|
||||||
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
|
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
|
||||||
|
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
|
||||||
|
lastCardUpdateCheckDateLabel.setText(
|
||||||
|
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
|
||||||
|
|
||||||
|
startupGroupBox->setTitle(tr("Startup settings"));
|
||||||
showTipsOnStartup.setText(tr("Show tips on startup"));
|
showTipsOnStartup.setText(tr("Show tips on startup"));
|
||||||
startupTabLabel.setText(tr("Startup tab:"));
|
startupTabLabel.setText(tr("Startup tab:"));
|
||||||
startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home"));
|
startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home"));
|
||||||
|
|
@ -473,21 +477,18 @@ void GeneralSettingsPage::retranslateUi()
|
||||||
startupServerLabel.setText(tr("Server:"));
|
startupServerLabel.setText(tr("Server:"));
|
||||||
startupRoomLabel.setText(tr("Room:"));
|
startupRoomLabel.setText(tr("Room:"));
|
||||||
startupRoomNameEdit->setPlaceholderText(tr("Room name"));
|
startupRoomNameEdit->setPlaceholderText(tr("Room name"));
|
||||||
resetAllPathsButton->setText(tr("Reset all paths"));
|
|
||||||
|
|
||||||
const auto &settings = SettingsCache::instance();
|
if (settings.getIsPortableBuild()) {
|
||||||
|
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
|
||||||
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
|
} else {
|
||||||
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
|
pathsGroupBox->setTitle(tr("Paths"));
|
||||||
|
|
||||||
lastCardUpdateCheckDateLabel.setText(
|
|
||||||
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
|
|
||||||
|
|
||||||
// We can't change the strings after they're put into the QComboBox, so this is our workaround
|
|
||||||
int oldIndex = updateReleaseChannelBox.currentIndex();
|
|
||||||
updateReleaseChannelBox.clear();
|
|
||||||
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
|
|
||||||
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
|
|
||||||
}
|
}
|
||||||
updateReleaseChannelBox.setCurrentIndex(oldIndex);
|
deckPathLabel.setText(tr("Decks directory:"));
|
||||||
}
|
filtersPathLabel.setText(tr("Filters directory:"));
|
||||||
|
replaysPathLabel.setText(tr("Replays directory:"));
|
||||||
|
picsPathLabel.setText(tr("Pictures directory:"));
|
||||||
|
cardDatabasePathLabel.setText(tr("Card database:"));
|
||||||
|
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
|
||||||
|
tokenDatabasePathLabel.setText(tr("Token database:"));
|
||||||
|
resetAllPathsButton->setText(tr("Reset all paths"));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,37 @@ private:
|
||||||
QGroupBox *startupGroupBox;
|
QGroupBox *startupGroupBox;
|
||||||
QGroupBox *pathsGroupBox;
|
QGroupBox *pathsGroupBox;
|
||||||
|
|
||||||
|
QLabel languageLabel;
|
||||||
|
QComboBox languageBox;
|
||||||
|
QLabel advertiseTranslationPageLabel;
|
||||||
|
|
||||||
|
QLabel updateReleaseChannelLabel;
|
||||||
|
QComboBox updateReleaseChannelBox;
|
||||||
|
QCheckBox startupUpdateCheckCheckBox;
|
||||||
|
QCheckBox updateNotificationCheckBox;
|
||||||
|
QCheckBox newVersionOracleCheckBox;
|
||||||
|
|
||||||
|
QLabel startupCardUpdateCheckBehaviorLabel;
|
||||||
|
QComboBox startupCardUpdateCheckBehaviorSelector;
|
||||||
|
QLabel cardUpdateCheckIntervalLabel;
|
||||||
|
QSpinBox cardUpdateCheckIntervalSpinBox;
|
||||||
|
QLabel lastCardUpdateCheckDateLabel;
|
||||||
|
|
||||||
|
QCheckBox showTipsOnStartup;
|
||||||
|
QLabel startupTabLabel;
|
||||||
|
QComboBox startupTabSelector;
|
||||||
|
QLabel startupServerLabel;
|
||||||
|
QComboBox startupServerSelector;
|
||||||
|
QLabel startupRoomLabel;
|
||||||
|
QLineEdit *startupRoomNameEdit;
|
||||||
|
|
||||||
|
QLabel deckPathLabel;
|
||||||
|
QLabel filtersPathLabel;
|
||||||
|
QLabel replaysPathLabel;
|
||||||
|
QLabel picsPathLabel;
|
||||||
|
QLabel cardDatabasePathLabel;
|
||||||
|
QLabel customCardDatabasePathLabel;
|
||||||
|
QLabel tokenDatabasePathLabel;
|
||||||
QLineEdit *deckPathEdit;
|
QLineEdit *deckPathEdit;
|
||||||
QLineEdit *filtersPathEdit;
|
QLineEdit *filtersPathEdit;
|
||||||
QLineEdit *replaysPathEdit;
|
QLineEdit *replaysPathEdit;
|
||||||
|
|
@ -51,33 +82,6 @@ private:
|
||||||
QLineEdit *tokenDatabasePathEdit;
|
QLineEdit *tokenDatabasePathEdit;
|
||||||
QPushButton *resetAllPathsButton;
|
QPushButton *resetAllPathsButton;
|
||||||
QLabel *allPathsResetLabel;
|
QLabel *allPathsResetLabel;
|
||||||
QComboBox languageBox;
|
|
||||||
QCheckBox startupUpdateCheckCheckBox;
|
|
||||||
QLabel startupCardUpdateCheckBehaviorLabel;
|
|
||||||
QComboBox startupCardUpdateCheckBehaviorSelector;
|
|
||||||
QLabel cardUpdateCheckIntervalLabel;
|
|
||||||
QSpinBox cardUpdateCheckIntervalSpinBox;
|
|
||||||
QLabel lastCardUpdateCheckDateLabel;
|
|
||||||
QCheckBox updateNotificationCheckBox;
|
|
||||||
QCheckBox newVersionOracleCheckBox;
|
|
||||||
QComboBox updateReleaseChannelBox;
|
|
||||||
QLabel languageLabel;
|
|
||||||
QLabel deckPathLabel;
|
|
||||||
QLabel filtersPathLabel;
|
|
||||||
QLabel replaysPathLabel;
|
|
||||||
QLabel picsPathLabel;
|
|
||||||
QLabel cardDatabasePathLabel;
|
|
||||||
QLabel customCardDatabasePathLabel;
|
|
||||||
QLabel tokenDatabasePathLabel;
|
|
||||||
QLabel updateReleaseChannelLabel;
|
|
||||||
QLabel advertiseTranslationPageLabel;
|
|
||||||
QCheckBox showTipsOnStartup;
|
|
||||||
QLabel startupTabLabel;
|
|
||||||
QComboBox startupTabSelector;
|
|
||||||
QLabel startupServerLabel;
|
|
||||||
QComboBox startupServerSelector;
|
|
||||||
QLabel startupRoomLabel;
|
|
||||||
QLineEdit *startupRoomNameEdit;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H
|
#endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ MessagesSettingsPage::MessagesSettingsPage()
|
||||||
connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().chat(),
|
connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().chat(),
|
||||||
&ChatSettings::setRoomHistory);
|
&ChatSettings::setRoomHistory);
|
||||||
|
|
||||||
|
ignoreAllPrivateMessagesCheckBox.setChecked(SettingsCache::instance().chat().getIgnoreAllPrivateMessages());
|
||||||
|
connect(&ignoreAllPrivateMessagesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().chat(),
|
||||||
|
&ChatSettings::setIgnoreAllPrivateMessages);
|
||||||
|
|
||||||
customAlertString = new QLineEdit();
|
customAlertString = new QLineEdit();
|
||||||
customAlertString->setText(SettingsCache::instance().chat().getHighlightWords());
|
customAlertString->setText(SettingsCache::instance().chat().getHighlightWords());
|
||||||
connect(customAlertString, &QLineEdit::textChanged, &SettingsCache::instance().chat(),
|
connect(customAlertString, &QLineEdit::textChanged, &SettingsCache::instance().chat(),
|
||||||
|
|
@ -76,6 +80,7 @@ MessagesSettingsPage::MessagesSettingsPage()
|
||||||
chatGrid->addWidget(&messagePopups, 5, 0);
|
chatGrid->addWidget(&messagePopups, 5, 0);
|
||||||
chatGrid->addWidget(&mentionPopups, 6, 0);
|
chatGrid->addWidget(&mentionPopups, 6, 0);
|
||||||
chatGrid->addWidget(&roomHistory, 7, 0);
|
chatGrid->addWidget(&roomHistory, 7, 0);
|
||||||
|
chatGrid->addWidget(&ignoreAllPrivateMessagesCheckBox, 8, 0);
|
||||||
chatGroupBox = new QGroupBox;
|
chatGroupBox = new QGroupBox;
|
||||||
chatGroupBox->setLayout(chatGrid);
|
chatGroupBox->setLayout(chatGrid);
|
||||||
|
|
||||||
|
|
@ -256,6 +261,7 @@ void MessagesSettingsPage::retranslateUi()
|
||||||
messagePopups.setText(tr("Enable desktop notifications for private messages"));
|
messagePopups.setText(tr("Enable desktop notifications for private messages"));
|
||||||
mentionPopups.setText(tr("Enable desktop notification for mentions"));
|
mentionPopups.setText(tr("Enable desktop notification for mentions"));
|
||||||
roomHistory.setText(tr("Enable room message history on join"));
|
roomHistory.setText(tr("Enable room message history on join"));
|
||||||
|
ignoreAllPrivateMessagesCheckBox.setText(tr("Ignore all private messages"));
|
||||||
hexLabel.setText(tr("(Color is hexadecimal)"));
|
hexLabel.setText(tr("(Color is hexadecimal)"));
|
||||||
hexHighlightLabel.setText(tr("(Color is hexadecimal)"));
|
hexHighlightLabel.setText(tr("(Color is hexadecimal)"));
|
||||||
customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only"));
|
customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only"));
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ private:
|
||||||
QCheckBox messagePopups;
|
QCheckBox messagePopups;
|
||||||
QCheckBox mentionPopups;
|
QCheckBox mentionPopups;
|
||||||
QCheckBox roomHistory;
|
QCheckBox roomHistory;
|
||||||
|
QCheckBox ignoreAllPrivateMessagesCheckBox;
|
||||||
QGroupBox *chatGroupBox;
|
QGroupBox *chatGroupBox;
|
||||||
QGroupBox *highlightGroupBox;
|
QGroupBox *highlightGroupBox;
|
||||||
QGroupBox *messageGroupBox;
|
QGroupBox *messageGroupBox;
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,7 @@ enum visualDeckStoragePromptForConversionIndex
|
||||||
|
|
||||||
UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
||||||
{
|
{
|
||||||
// general settings and notification settings
|
// general settings
|
||||||
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
|
||||||
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
|
||||||
&InterfaceSettings::setNotificationsEnabled);
|
|
||||||
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
|
|
||||||
&UserInterfaceSettingsPage::setNotificationEnabled);
|
|
||||||
|
|
||||||
specNotificationsEnabledCheckBox.setChecked(
|
|
||||||
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
|
|
||||||
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
|
||||||
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
|
||||||
&InterfaceSettings::setSpectatorNotificationsEnabled);
|
|
||||||
|
|
||||||
buddyConnectNotificationsEnabledCheckBox.setChecked(
|
|
||||||
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
|
|
||||||
buddyConnectNotificationsEnabledCheckBox.setEnabled(
|
|
||||||
SettingsCache::instance().userInterface().getNotificationsEnabled());
|
|
||||||
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
|
|
||||||
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
|
|
||||||
|
|
||||||
doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().userInterface().getDoubleClickToPlay());
|
doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().userInterface().getDoubleClickToPlay());
|
||||||
connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
||||||
&InterfaceSettings::setDoubleClickToPlay);
|
&InterfaceSettings::setDoubleClickToPlay);
|
||||||
|
|
@ -103,6 +84,26 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
||||||
generalGroupBox = new QGroupBox;
|
generalGroupBox = new QGroupBox;
|
||||||
generalGroupBox->setLayout(generalGrid);
|
generalGroupBox->setLayout(generalGrid);
|
||||||
|
|
||||||
|
// notification settings
|
||||||
|
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
||||||
|
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
||||||
|
&InterfaceSettings::setNotificationsEnabled);
|
||||||
|
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
|
||||||
|
&UserInterfaceSettingsPage::setNotificationEnabled);
|
||||||
|
|
||||||
|
specNotificationsEnabledCheckBox.setChecked(
|
||||||
|
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
|
||||||
|
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
||||||
|
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
||||||
|
&InterfaceSettings::setSpectatorNotificationsEnabled);
|
||||||
|
|
||||||
|
buddyConnectNotificationsEnabledCheckBox.setChecked(
|
||||||
|
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
|
||||||
|
buddyConnectNotificationsEnabledCheckBox.setEnabled(
|
||||||
|
SettingsCache::instance().userInterface().getNotificationsEnabled());
|
||||||
|
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
|
||||||
|
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
|
||||||
|
|
||||||
auto *notificationsGrid = new QGridLayout;
|
auto *notificationsGrid = new QGridLayout;
|
||||||
notificationsGrid->addWidget(¬ificationsEnabledCheckBox, 0, 0);
|
notificationsGrid->addWidget(¬ificationsEnabledCheckBox, 0, 0);
|
||||||
notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0);
|
notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0);
|
||||||
|
|
@ -355,6 +356,7 @@ void UserInterfaceSettingsPage::retranslateUi()
|
||||||
notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar"));
|
notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar"));
|
||||||
specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating"));
|
specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating"));
|
||||||
buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect"));
|
buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect"));
|
||||||
|
|
||||||
animationGroupBox->setTitle(tr("Animation settings"));
|
animationGroupBox->setTitle(tr("Animation settings"));
|
||||||
enableAllAnimationsButton.setText(tr("&Enable all animations"));
|
enableAllAnimationsButton.setText(tr("&Enable all animations"));
|
||||||
disableAllAnimationsButton.setText(tr("&Disable all animations"));
|
disableAllAnimationsButton.setText(tr("&Disable all animations"));
|
||||||
|
|
@ -362,6 +364,7 @@ void UserInterfaceSettingsPage::retranslateUi()
|
||||||
arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation"));
|
arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation"));
|
||||||
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
|
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
|
||||||
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
|
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
|
||||||
|
|
||||||
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
|
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
|
||||||
openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default"));
|
openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default"));
|
||||||
visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby"));
|
visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby"));
|
||||||
|
|
@ -397,8 +400,8 @@ void UserInterfaceSettingsPage::retranslateUi()
|
||||||
0, CommanderBracketNames::CommanderSpellbookBracketNames);
|
0, CommanderBracketNames::CommanderSpellbookBracketNames);
|
||||||
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
|
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
|
||||||
1, CommanderBracketNames::OfficialCommanderBracketNames);
|
1, CommanderBracketNames::OfficialCommanderBracketNames);
|
||||||
|
|
||||||
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
|
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
|
||||||
|
|
||||||
replayGroupBox->setTitle(tr("Replay settings"));
|
replayGroupBox->setTitle(tr("Replay settings"));
|
||||||
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
|
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
|
||||||
rewindBufferingMsBox.setSuffix(" ms");
|
rewindBufferingMsBox.setSuffix(" ms");
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ private slots:
|
||||||
void updateCommanderSpellbookUiState();
|
void updateCommanderSpellbookUiState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QCheckBox notificationsEnabledCheckBox;
|
|
||||||
QCheckBox specNotificationsEnabledCheckBox;
|
|
||||||
QCheckBox buddyConnectNotificationsEnabledCheckBox;
|
|
||||||
QCheckBox doubleClickToPlayCheckBox;
|
QCheckBox doubleClickToPlayCheckBox;
|
||||||
QCheckBox clickPlaysAllSelectedCheckBox;
|
QCheckBox clickPlaysAllSelectedCheckBox;
|
||||||
QCheckBox playToStackCheckBox;
|
QCheckBox playToStackCheckBox;
|
||||||
|
|
@ -37,12 +34,18 @@ private:
|
||||||
QCheckBox showTotalSelectionCountCheckBox;
|
QCheckBox showTotalSelectionCountCheckBox;
|
||||||
QCheckBox useTearOffMenusCheckBox;
|
QCheckBox useTearOffMenusCheckBox;
|
||||||
QCheckBox keepGameChatFocusCheckBox;
|
QCheckBox keepGameChatFocusCheckBox;
|
||||||
|
|
||||||
|
QCheckBox notificationsEnabledCheckBox;
|
||||||
|
QCheckBox specNotificationsEnabledCheckBox;
|
||||||
|
QCheckBox buddyConnectNotificationsEnabledCheckBox;
|
||||||
|
|
||||||
QPushButton enableAllAnimationsButton;
|
QPushButton enableAllAnimationsButton;
|
||||||
QPushButton disableAllAnimationsButton;
|
QPushButton disableAllAnimationsButton;
|
||||||
QCheckBox tapAnimationCheckBox;
|
QCheckBox tapAnimationCheckBox;
|
||||||
QCheckBox arrowDrawAnimationCheckBox;
|
QCheckBox arrowDrawAnimationCheckBox;
|
||||||
QCheckBox lifeCounterAnimationsCheckBox;
|
QCheckBox lifeCounterAnimationsCheckBox;
|
||||||
QCheckBox battlefieldFlashCheckBox;
|
QCheckBox battlefieldFlashCheckBox;
|
||||||
|
|
||||||
QCheckBox openDeckInNewTabCheckBox;
|
QCheckBox openDeckInNewTabCheckBox;
|
||||||
QLabel visualDeckStoragePromptForConversionLabel;
|
QLabel visualDeckStoragePromptForConversionLabel;
|
||||||
QComboBox visualDeckStoragePromptForConversionSelector;
|
QComboBox visualDeckStoragePromptForConversionSelector;
|
||||||
|
|
@ -57,8 +60,10 @@ private:
|
||||||
QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel;
|
QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel;
|
||||||
QToolButton commanderSpellbookIntegrationUseOfficialBracketNamesExplainer;
|
QToolButton commanderSpellbookIntegrationUseOfficialBracketNamesExplainer;
|
||||||
QComboBox commanderSpellbookIntegrationBracketNamingSelector;
|
QComboBox commanderSpellbookIntegrationBracketNamingSelector;
|
||||||
|
|
||||||
QLabel rewindBufferingMsLabel;
|
QLabel rewindBufferingMsLabel;
|
||||||
QSpinBox rewindBufferingMsBox;
|
QSpinBox rewindBufferingMsBox;
|
||||||
|
|
||||||
QGroupBox *generalGroupBox;
|
QGroupBox *generalGroupBox;
|
||||||
QGroupBox *notificationsGroupBox;
|
QGroupBox *notificationsGroupBox;
|
||||||
QGroupBox *animationGroupBox;
|
QGroupBox *animationGroupBox;
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,11 @@ void TabAccount::retranslateUi()
|
||||||
buddyList->retranslateUi();
|
buddyList->retranslateUi();
|
||||||
ignoreList->retranslateUi();
|
ignoreList->retranslateUi();
|
||||||
userInfoBox->retranslateUi();
|
userInfoBox->retranslateUi();
|
||||||
|
|
||||||
|
buddyList->setToolTip(tr("Buddies are marked with a star in chat, a sound plays when they join or leave the "
|
||||||
|
"server, and they can be invited to buddy-only games."));
|
||||||
|
ignoreList->setToolTip(tr("Ignored users' chat messages are hidden from you, and they cannot send you private "
|
||||||
|
"messages or join your games."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabAccount::processListUsersResponse(const Response &response)
|
void TabAccount::processListUsersResponse(const Response &response)
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,10 @@ void TabGame::resetChatAndPhase()
|
||||||
|
|
||||||
// reset phase markers
|
// reset phase markers
|
||||||
game->getGameState()->setCurrentPhase(-1);
|
game->getGameState()->setCurrentPhase(-1);
|
||||||
|
|
||||||
|
// reset spectator state so the replay can rebuild it from the start
|
||||||
|
game->getPlayerManager()->clearSpectators();
|
||||||
|
playerListWidget->clearSpectators();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabGame::emitUserEvent()
|
void TabGame::emitUserEvent()
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,12 @@ void TabMessage::closeEvent(QCloseEvent *event)
|
||||||
|
|
||||||
void TabMessage::sendPrivateMessage(const QString &text)
|
void TabMessage::sendPrivateMessage(const QString &text)
|
||||||
{
|
{
|
||||||
|
if (tabSupervisor->getUserListManager()->isUserIgnored(getUserName())) {
|
||||||
|
chatView->appendMessage(tr("You have ignored %1; your messages are not delivered.")
|
||||||
|
.arg(QString::fromStdString(otherUserInfo->name())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Command_Message cmd;
|
Command_Message cmd;
|
||||||
cmd.set_user_name(otherUserInfo->name());
|
cmd.set_user_name(otherUserInfo->name());
|
||||||
cmd.set_message(text.toStdString());
|
cmd.set_message(text.toStdString());
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,10 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/)
|
||||||
}
|
}
|
||||||
|
|
||||||
TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent)
|
TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent)
|
||||||
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabVisualDeckStorage(nullptr),
|
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabHome(nullptr),
|
||||||
tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr), tabReplays(nullptr), tabAdmin(nullptr),
|
tabVisualDeckStorage(nullptr), tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr),
|
||||||
tabLog(nullptr), tabReport(nullptr), tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false)
|
tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr), tabReport(nullptr),
|
||||||
|
tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false)
|
||||||
{
|
{
|
||||||
setElideMode(Qt::ElideRight);
|
setElideMode(Qt::ElideRight);
|
||||||
setMovable(true);
|
setMovable(true);
|
||||||
|
|
@ -250,6 +251,7 @@ void TabSupervisor::retranslateUi()
|
||||||
aTabLog->setText(tr("Logs"));
|
aTabLog->setText(tr("Logs"));
|
||||||
aTabReport->setText(tr("Report Queue"));
|
aTabReport->setText(tr("Report Queue"));
|
||||||
aTabModeration->setText(tr("Moderation"));
|
aTabModeration->setText(tr("Moderation"));
|
||||||
|
aTabCardArtRules->setText(tr("Card Art Rules"));
|
||||||
aTabDeveloper->setText(tr("Developer"));
|
aTabDeveloper->setText(tr("Developer"));
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
|
|
@ -262,6 +264,7 @@ void TabSupervisor::retranslateUi()
|
||||||
tabs.append(tabLog);
|
tabs.append(tabLog);
|
||||||
tabs.append(tabReport);
|
tabs.append(tabReport);
|
||||||
tabs.append(tabModeration);
|
tabs.append(tabModeration);
|
||||||
|
tabs.append(tabCardArtRules);
|
||||||
tabs.append(tabDeveloper);
|
tabs.append(tabDeveloper);
|
||||||
QMapIterator<int, TabRoom *> roomIterator(roomTabs);
|
QMapIterator<int, TabRoom *> roomIterator(roomTabs);
|
||||||
while (roomIterator.hasNext()) {
|
while (roomIterator.hasNext()) {
|
||||||
|
|
@ -527,7 +530,9 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo)
|
||||||
if (SettingsCache::instance().tabs().getTabModerationOpen()) {
|
if (SettingsCache::instance().tabs().getTabModerationOpen()) {
|
||||||
openTabModeration();
|
openTabModeration();
|
||||||
}
|
}
|
||||||
openTabCardArtRules();
|
if (SettingsCache::instance().tabs().getTabCardArtRulesOpen()) {
|
||||||
|
openTabCardArtRules();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userInfo->user_level() & ServerInfo_User::IsDeveloper) {
|
if (userInfo->user_level() & ServerInfo_User::IsDeveloper) {
|
||||||
|
|
@ -603,6 +608,9 @@ void TabSupervisor::stop()
|
||||||
if (tabModeration) {
|
if (tabModeration) {
|
||||||
tabModeration->close();
|
tabModeration->close();
|
||||||
}
|
}
|
||||||
|
if (tabCardArtRules) {
|
||||||
|
tabCardArtRules->close();
|
||||||
|
}
|
||||||
if (tabDeveloper) {
|
if (tabDeveloper) {
|
||||||
tabDeveloper->close();
|
tabDeveloper->close();
|
||||||
}
|
}
|
||||||
|
|
@ -799,6 +807,7 @@ void TabSupervisor::openTabAdmin()
|
||||||
|
|
||||||
void TabSupervisor::actTabCardArtRules(bool checked)
|
void TabSupervisor::actTabCardArtRules(bool checked)
|
||||||
{
|
{
|
||||||
|
SettingsCache::instance().tabs().setTabCardArtRulesOpen(checked);
|
||||||
if (checked && !tabCardArtRules) {
|
if (checked && !tabCardArtRules) {
|
||||||
openTabCardArtRules();
|
openTabCardArtRules();
|
||||||
setCurrentWidget(tabCardArtRules);
|
setCurrentWidget(tabCardArtRules);
|
||||||
|
|
@ -1105,6 +1114,13 @@ TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (focus && userListManager->isUserIgnored(receiverName)) {
|
||||||
|
QMessageBox::information(
|
||||||
|
this, tr("Ignored user"),
|
||||||
|
tr("You have ignored %1. Remove them from your ignore list to open a private chat.").arg(receiverName));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
tab = new TabMessage(this, client, *userInfo, otherUser, userOnline);
|
tab = new TabMessage(this, client, *userInfo, otherUser, userOnline);
|
||||||
connect(tab, &TabMessage::talkClosing, this, &TabSupervisor::talkLeft);
|
connect(tab, &TabMessage::talkClosing, this, &TabSupervisor::talkLeft);
|
||||||
connect(tab, &TabMessage::maximizeClient, this, &TabSupervisor::maximizeMainWindow);
|
connect(tab, &TabMessage::maximizeClient, this, &TabSupervisor::maximizeMainWindow);
|
||||||
|
|
@ -1319,7 +1335,21 @@ void TabSupervisor::processGameEventContainer(const GameEventContainer &cont)
|
||||||
|
|
||||||
void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event)
|
void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event)
|
||||||
{
|
{
|
||||||
|
// "Ignore all private messages" silences every PM, including messages to
|
||||||
|
// already-open tabs — unlike the unregistered/non-buddy filters below,
|
||||||
|
// which only apply when creating a new tab. Messages from moderators/admins
|
||||||
|
// are exempt to ensure warnings still reach users.
|
||||||
QString senderName = QString::fromStdString(event.sender_name());
|
QString senderName = QString::fromStdString(event.sender_name());
|
||||||
|
if (SettingsCache::instance().chat().getIgnoreAllPrivateMessages()) {
|
||||||
|
const ServerInfo_User *onlineUserInfo = userListManager->getOnlineUser(senderName);
|
||||||
|
if (!onlineUserInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const UserLevelFlags userLevel(onlineUserInfo->user_level());
|
||||||
|
if (!userLevel.testFlag(ServerInfo_User::IsModerator) && !userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
TabMessage *tab = messageTabs.value(senderName);
|
TabMessage *tab = messageTabs.value(senderName);
|
||||||
if (!tab) {
|
if (!tab) {
|
||||||
tab = messageTabs.value(QString::fromStdString(event.receiver_name()));
|
tab = messageTabs.value(QString::fromStdString(event.receiver_name()));
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "../../../../client/settings/shortcuts_settings.h"
|
#include "../../../../client/settings/shortcuts_settings.h"
|
||||||
#include "../../cards/card_info_display_widget.h"
|
#include "../../cards/card_info_display_widget.h"
|
||||||
#include "../../deck_editor/deck_state_manager.h"
|
#include "../../deck_editor/deck_state_manager.h"
|
||||||
|
#include "../../deck_editor/deck_zone_dialog.h"
|
||||||
#include "../../filters/filter_builder.h"
|
#include "../../filters/filter_builder.h"
|
||||||
#include "../../interface/pixel_map_generator.h"
|
#include "../../interface/pixel_map_generator.h"
|
||||||
#include "../../interface/widgets/cards/card_info_frame_widget.h"
|
#include "../../interface/widgets/cards/card_info_frame_widget.h"
|
||||||
|
|
@ -84,6 +85,7 @@ void TabDeckEditorVisual::createCentralFrame()
|
||||||
connect(tabContainer, &TabDeckEditorVisualTabWidget::printingSelectorRequested, this,
|
connect(tabContainer, &TabDeckEditorVisualTabWidget::printingSelectorRequested, this,
|
||||||
&TabDeckEditorVisual::showPrintingSelector);
|
&TabDeckEditorVisual::showPrintingSelector);
|
||||||
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardInfoRequested, this, &TabDeckEditorVisual::updateCardInfo);
|
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardInfoRequested, this, &TabDeckEditorVisual::updateCardInfo);
|
||||||
|
tabContainer->visualDatabaseDisplay->setNewZoneCreator([this] { return createNewZone(); });
|
||||||
|
|
||||||
centralFrame->addWidget(tabContainer);
|
centralFrame->addWidget(tabContainer);
|
||||||
setCentralWidget(centralWidget);
|
setCentralWidget(centralWidget);
|
||||||
|
|
@ -269,6 +271,19 @@ bool TabDeckEditorVisual::actSaveDeckAs()
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Prompts for and creates a new custom deck zone. Returns the name of the created zone. */
|
||||||
|
QString TabDeckEditorVisual::createNewZone()
|
||||||
|
{
|
||||||
|
QString boardName;
|
||||||
|
const QString zoneName = DeckZoneDialog::promptForNewZone(this, {}, &boardName, [this](const QString &candidate) {
|
||||||
|
return deckStateManager->validateNewZoneName(candidate);
|
||||||
|
});
|
||||||
|
if (!zoneName.isEmpty()) {
|
||||||
|
deckStateManager->createCustomZone(boardName, zoneName);
|
||||||
|
}
|
||||||
|
return zoneName;
|
||||||
|
}
|
||||||
|
|
||||||
/** @brief Refreshes keyboard shortcuts for this tab from settings. */
|
/** @brief Refreshes keyboard shortcuts for this tab from settings. */
|
||||||
void TabDeckEditorVisual::refreshShortcuts()
|
void TabDeckEditorVisual::refreshShortcuts()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,12 @@ public slots:
|
||||||
*/
|
*/
|
||||||
bool actSaveDeckAs() override;
|
bool actSaveDeckAs() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prompts for and creates a new custom deck zone.
|
||||||
|
* @return The name of the created zone, or an empty string if creation was cancelled.
|
||||||
|
*/
|
||||||
|
QString createNewZone();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief Sets the deck for this tab and selects the sub-tab to open on
|
* @brief Sets the deck for this tab and selects the sub-tab to open on
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
#include <libcockatrice/card/card_info_comparator.h>
|
#include <libcockatrice/card/card_info_comparator.h>
|
||||||
#include <libcockatrice/card/database/card_database.h>
|
#include <libcockatrice/card/database/card_database.h>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
|
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
|
||||||
#include <libcockatrice/settings/cards_display_settings.h>
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|
@ -89,6 +90,19 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
||||||
databaseView->setItemDelegate(nullptr);
|
databaseView->setItemDelegate(nullptr);
|
||||||
databaseView->setVisible(false);
|
databaseView->setVisible(false);
|
||||||
|
|
||||||
|
// Without a deck model there is nothing to add cards to, so the zone menu stays hidden.
|
||||||
|
if (deckListModel) {
|
||||||
|
databaseView->setZoneMenuProvider(
|
||||||
|
[deckListModel]() -> QList<QPair<QString, QStringList>> {
|
||||||
|
QList<QPair<QString, QStringList>> result;
|
||||||
|
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||||
|
result.append({boardName, deckListModel->getCustomZoneNames(boardName)});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
[this] { return newZoneCreator ? newZoneCreator() : QString(); });
|
||||||
|
}
|
||||||
|
|
||||||
searchEdit->setTreeView(databaseView);
|
searchEdit->setTreeView(databaseView);
|
||||||
searchEdit->installEventFilter(databaseView->getKeySignals());
|
searchEdit->installEventFilter(databaseView->getKeySignals());
|
||||||
|
|
||||||
|
|
@ -195,6 +209,11 @@ void VisualDatabaseDisplayWidget::showEvent(QShowEvent *event)
|
||||||
initializeFilters();
|
initializeFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VisualDatabaseDisplayWidget::setNewZoneCreator(const std::function<QString()> &creator)
|
||||||
|
{
|
||||||
|
newZoneCreator = creator;
|
||||||
|
}
|
||||||
|
|
||||||
void VisualDatabaseDisplayWidget::retranslateUi()
|
void VisualDatabaseDisplayWidget::retranslateUi()
|
||||||
{
|
{
|
||||||
databaseLoadIndicator->setText(tr("Loading database ..."));
|
databaseLoadIndicator->setText(tr("Loading database ..."));
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWheelEvent>
|
#include <QWheelEvent>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <functional>
|
||||||
#include <libcockatrice/models/database/card_database_model.h>
|
#include <libcockatrice/models/database/card_database_model.h>
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
#include <qscrollarea.h>
|
#include <qscrollarea.h>
|
||||||
|
|
@ -46,6 +47,12 @@ public:
|
||||||
void sortCardList(const QStringList &properties, Qt::SortOrder order) const;
|
void sortCardList(const QStringList &properties, Qt::SortOrder order) const;
|
||||||
void setDeckList(const DeckList &new_deck_list_model);
|
void setDeckList(const DeckList &new_deck_list_model);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the callback used to create a custom zone from the add-to-zone menu.
|
||||||
|
* The callback returns the name of the created zone, or an empty string if creation was cancelled.
|
||||||
|
*/
|
||||||
|
void setNewZoneCreator(const std::function<QString()> &creator);
|
||||||
|
|
||||||
CardDatabaseDisplayModel *getDatabaseDisplayModel()
|
CardDatabaseDisplayModel *getDatabaseDisplayModel()
|
||||||
{
|
{
|
||||||
return databaseDisplayModel;
|
return databaseDisplayModel;
|
||||||
|
|
@ -106,6 +113,7 @@ private:
|
||||||
VisualDatabaseDisplayFilterToolbarWidget *filterContainer;
|
VisualDatabaseDisplayFilterToolbarWidget *filterContainer;
|
||||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||||
CardDatabaseView *databaseView;
|
CardDatabaseView *databaseView;
|
||||||
|
std::function<QString()> newZoneCreator;
|
||||||
QList<ExactCard> *cards;
|
QList<ExactCard> *cards;
|
||||||
QVBoxLayout *mainLayout;
|
QVBoxLayout *mainLayout;
|
||||||
QScrollArea *scrollArea;
|
QScrollArea *scrollArea;
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,7 @@ void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool matches = index.data(VisualDeckStorageRoles::FilterMatchRole).toBool();
|
const bool matches = index.data(VisualDeckStorageRoles::FilterMatchRole).toBool();
|
||||||
if (matches == deckPreviewWidget->isHidden()) {
|
deckPreviewWidget->setVisible(matches);
|
||||||
deckPreviewWidget->setVisible(matches);
|
|
||||||
}
|
|
||||||
if (matches) {
|
if (matches) {
|
||||||
++visibleDeckCount;
|
++visibleDeckCount;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,7 @@ void MainWindow::runFirstRunWizard()
|
||||||
connect(wizard, &FirstRunWizard::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdatesBackground);
|
connect(wizard, &FirstRunWizard::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdatesBackground);
|
||||||
connect(wizard, &FirstRunWizard::manualCardDatabaseSetupRequested, this, &MainWindow::actCheckCardUpdates);
|
connect(wizard, &FirstRunWizard::manualCardDatabaseSetupRequested, this, &MainWindow::actCheckCardUpdates);
|
||||||
connect(this, &MainWindow::cardDatabaseUpdateFinished, wizard, &FirstRunWizard::onCardDatabaseUpdateFinished);
|
connect(this, &MainWindow::cardDatabaseUpdateFinished, wizard, &FirstRunWizard::onCardDatabaseUpdateFinished);
|
||||||
|
connect(this, &MainWindow::cardDatabaseUpdateProgress, wizard, &FirstRunWizard::onCardDatabaseUpdateProgress);
|
||||||
connect(wizard, &FirstRunWizard::registerRequested, connectionController, &ConnectionController::registerToServer);
|
connect(wizard, &FirstRunWizard::registerRequested, connectionController, &ConnectionController::registerToServer);
|
||||||
connect(wizard, &FirstRunWizard::connectRequested, connectionController, &ConnectionController::connectToServer);
|
connect(wizard, &FirstRunWizard::connectRequested, connectionController, &ConnectionController::connectToServer);
|
||||||
|
|
||||||
|
|
@ -843,6 +844,17 @@ void MainWindow::closeEvent(QCloseEvent *event)
|
||||||
}
|
}
|
||||||
bClosingDown = true;
|
bClosingDown = true;
|
||||||
|
|
||||||
|
if (cardUpdateProcess && cardUpdateProcess->state() != QProcess::NotRunning) {
|
||||||
|
if (QMessageBox::question(this, tr("Are you sure?"),
|
||||||
|
tr("A card database update is still running. Quitting now will cancel it.\n"
|
||||||
|
"Are you sure you want to quit?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
|
||||||
|
event->ignore();
|
||||||
|
bClosingDown = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!tabSupervisor->close()) {
|
if (!tabSupervisor->close()) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
bClosingDown = false;
|
bClosingDown = false;
|
||||||
|
|
@ -1057,11 +1069,45 @@ void MainWindow::createCardUpdateProcess(bool background)
|
||||||
if (!background) {
|
if (!background) {
|
||||||
cardUpdateProcess->start(updaterCmd, QStringList());
|
cardUpdateProcess->start(updaterCmd, QStringList());
|
||||||
} else {
|
} else {
|
||||||
|
cardUpdateOutputBuffer.clear();
|
||||||
|
connect(cardUpdateProcess, &QProcess::readyReadStandardOutput, this, &MainWindow::cardUpdateProgressOutput);
|
||||||
cardUpdateProcess->start(updaterCmd, QStringList("-b"));
|
cardUpdateProcess->start(updaterCmd, QStringList("-b"));
|
||||||
statusBar()->showMessage(tr("Card database update running."));
|
statusBar()->showMessage(tr("Card database update running."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::cardUpdateProgressOutput()
|
||||||
|
{
|
||||||
|
if (!cardUpdateProcess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cardUpdateOutputBuffer.append(cardUpdateProcess->readAllStandardOutput());
|
||||||
|
while (true) {
|
||||||
|
const int newline = cardUpdateOutputBuffer.indexOf('\n');
|
||||||
|
if (newline < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const QByteArray line = cardUpdateOutputBuffer.left(newline).trimmed();
|
||||||
|
cardUpdateOutputBuffer.remove(0, newline + 1);
|
||||||
|
// Protocol emitted by `oracle -b`: "PROGRESS <stage> <done> <total>"
|
||||||
|
if (!line.startsWith("PROGRESS ")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const QList<QByteArray> parts = line.split(' ');
|
||||||
|
if (parts.size() != 4) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool doneOk = false;
|
||||||
|
bool totalOk = false;
|
||||||
|
const qint64 done = parts.at(2).toLongLong(&doneOk);
|
||||||
|
const qint64 total = parts.at(3).toLongLong(&totalOk);
|
||||||
|
if (!doneOk || !totalOk || done < 0 || total < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
emit cardDatabaseUpdateProgress(QString::fromLatin1(parts.at(1)), done, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::exitCardDatabaseUpdate()
|
void MainWindow::exitCardDatabaseUpdate()
|
||||||
{
|
{
|
||||||
if (!cardUpdateProcess) {
|
if (!cardUpdateProcess) {
|
||||||
|
|
@ -1109,6 +1155,8 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err)
|
||||||
|
|
||||||
void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||||
{
|
{
|
||||||
|
cardUpdateProgressOutput(); // drain any progress lines not yet parsed
|
||||||
|
|
||||||
const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0);
|
const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0);
|
||||||
if (exitStatus == QProcess::NormalExit) {
|
if (exitStatus == QProcess::NormalExit) {
|
||||||
SettingsCache::instance().updates().setLastCardUpdateCheck(QDateTime::currentDateTime().date());
|
SettingsCache::instance().updates().setLastCardUpdateCheck(QDateTime::currentDateTime().date());
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,11 @@ signals:
|
||||||
/** @brief Emitted after the background card-database update subprocess exits. */
|
/** @brief Emitted after the background card-database update subprocess exits. */
|
||||||
void cardDatabaseUpdateFinished(bool success);
|
void cardDatabaseUpdateFinished(bool success);
|
||||||
|
|
||||||
|
/** @brief Emitted while the background card-database update subprocess runs.
|
||||||
|
* @p stage is one of "download", "scan" or "import"; @p done/@p total
|
||||||
|
* are byte counts for the first two stages and set indices for "import". */
|
||||||
|
void cardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void actCheckCardUpdates();
|
void actCheckCardUpdates();
|
||||||
void actCheckCardUpdatesBackground();
|
void actCheckCardUpdatesBackground();
|
||||||
|
|
@ -96,6 +101,7 @@ private slots:
|
||||||
|
|
||||||
void cardUpdateError(QProcess::ProcessError err);
|
void cardUpdateError(QProcess::ProcessError err);
|
||||||
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||||
|
void cardUpdateProgressOutput();
|
||||||
void refreshShortcuts();
|
void refreshShortcuts();
|
||||||
void cardDatabaseLoadingFailed();
|
void cardDatabaseLoadingFailed();
|
||||||
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
||||||
|
|
@ -159,6 +165,7 @@ private:
|
||||||
LatencyStatusWidget *latencyStatus = nullptr; ///< status bar widget with live round-trip stats and history graph
|
LatencyStatusWidget *latencyStatus = nullptr; ///< status bar widget with live round-trip stats and history graph
|
||||||
bool bHasActivated, askedForDbUpdater;
|
bool bHasActivated, askedForDbUpdater;
|
||||||
QProcess *cardUpdateProcess;
|
QProcess *cardUpdateProcess;
|
||||||
|
QByteArray cardUpdateOutputBuffer;
|
||||||
DlgViewLog *logviewDialog;
|
DlgViewLog *logviewDialog;
|
||||||
GameReplay *replay;
|
GameReplay *replay;
|
||||||
DlgTipOfTheDay *tip;
|
DlgTipOfTheDay *tip;
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@
|
||||||
#include <libcockatrice/settings/interface_settings.h>
|
#include <libcockatrice/settings/interface_settings.h>
|
||||||
#include <libcockatrice/settings/network_settings.h>
|
#include <libcockatrice/settings/network_settings.h>
|
||||||
#include <libcockatrice/settings/personal_settings.h>
|
#include <libcockatrice/settings/personal_settings.h>
|
||||||
|
#include <libcockatrice/utility/cryptoutil.h>
|
||||||
|
|
||||||
QTranslator *translator, *qtTranslator;
|
QTranslator *translator, *qtTranslator;
|
||||||
RNG_Abstract *rng;
|
RNG_Abstract *rng;
|
||||||
|
|
@ -292,7 +293,7 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rng = new RNG_SFMT;
|
rng = new RNG_SFMT(CryptoUtil::randomUInt64());
|
||||||
themeManager = new ThemeManager;
|
themeManager = new ThemeManager;
|
||||||
soundEngine = new SoundEngine;
|
soundEngine = new SoundEngine;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ include=("cockatrice/src" \
|
||||||
libcockatrice_* \
|
libcockatrice_* \
|
||||||
"oracle/src" \
|
"oracle/src" \
|
||||||
"servatrice/src" \
|
"servatrice/src" \
|
||||||
|
"cmake/pch" \
|
||||||
"tests")
|
"tests")
|
||||||
exclude=("libcockatrice_rng/libcockatrice/rng/sfmt/" \
|
exclude=("libcockatrice_rng/libcockatrice/rng/sfmt/" \
|
||||||
"libcockatrice_utility/libcockatrice/utility/peglib.h" \
|
"libcockatrice_utility/libcockatrice/utility/peglib.h" \
|
||||||
"oracle/src/lzma/" \
|
"oracle/src/lzma/" \
|
||||||
"oracle/src/qt-json/" \
|
|
||||||
"oracle/src/zip/" \
|
"oracle/src/zip/" \
|
||||||
"servatrice/src/smtp/")
|
"servatrice/src/smtp/")
|
||||||
exts=("cpp" "h" "proto")
|
exts=("cpp" "h" "proto")
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,25 @@ public:
|
||||||
*/
|
*/
|
||||||
QList<const InnerDecklistNode *> getCustomZones(const QString &boardZoneName) const;
|
QList<const InnerDecklistNode *> getCustomZones(const QString &boardZoneName) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a zone name is taken anywhere in the deck.
|
||||||
|
*
|
||||||
|
* Covers the standard board names and any top-level or nested custom zone.
|
||||||
|
* @param zoneName The checked name.
|
||||||
|
* @return true if the name is reserved or already in use.
|
||||||
|
*/
|
||||||
|
bool hasZoneName(const QString &zoneName) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds a custom zone anywhere in the deck by name.
|
||||||
|
*
|
||||||
|
* Walks the children of every top-level zone, so a zone nested under any
|
||||||
|
* board (and not just the standard ones) is found.
|
||||||
|
* @param zoneName The zone name to find.
|
||||||
|
* @return The matching zone node, or nullptr if none exists.
|
||||||
|
*/
|
||||||
|
InnerDecklistNode *findCustomZoneByName(const QString &zoneName) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Applies a function to every card in the deck tree. This can modify the cards.
|
* @brief Applies a function to every card in the deck tree. This can modify the cards.
|
||||||
*
|
*
|
||||||
|
|
@ -128,8 +147,6 @@ private:
|
||||||
InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const;
|
InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const;
|
||||||
InnerDecklistNode *findBoardZone(const QString &boardZoneName) const;
|
InnerDecklistNode *findBoardZone(const QString &boardZoneName) const;
|
||||||
InnerDecklistNode *findOrCreateBoardZone(const QString &boardZoneName);
|
InnerDecklistNode *findOrCreateBoardZone(const QString &boardZoneName);
|
||||||
InnerDecklistNode *findCustomZoneByName(const QString &zoneName) const;
|
|
||||||
bool hasZoneName(const QString &zoneName) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_DECKLIST_NODE_TREE_H
|
#endif // COCKATRICE_DECKLIST_NODE_TREE_H
|
||||||
|
|
|
||||||
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