mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2025-12-21 06:42:41 -08:00
Compare commits
404 Commits
2022-09-01
...
2024-12-28
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7679546e30 | ||
|
|
45b11dc984 | ||
|
|
25d21a3da6 | ||
|
|
c8d49b5bf9 | ||
|
|
f737d9a794 | ||
|
|
df9c5ae53c | ||
|
|
e0829a75d2 | ||
|
|
1f58f7e93d | ||
|
|
14807ba036 | ||
|
|
75fb3894a6 | ||
|
|
18119bd11b | ||
|
|
4c7796537f | ||
|
|
3452cb01d0 | ||
|
|
6a151ef97a | ||
|
|
e3d651668c | ||
|
|
7a5704beaa | ||
|
|
37b78a9a4c | ||
|
|
8bc5a9d581 | ||
|
|
57ed162b79 | ||
|
|
3524231500 | ||
|
|
5cfe2b4762 | ||
|
|
a8bac1e468 | ||
|
|
4f798286af | ||
|
|
8a04b2d69d | ||
|
|
17893d9747 | ||
|
|
8af49406cd | ||
|
|
3b068b79fe | ||
|
|
ce14e83e78 | ||
|
|
f213d6fda7 | ||
|
|
83db00d7a3 | ||
|
|
7e9bd88eb4 | ||
|
|
ea716ca440 | ||
|
|
3cd7a04002 | ||
|
|
914002f846 | ||
|
|
17b82a186f | ||
|
|
7a8e957476 | ||
|
|
6dfd354973 | ||
|
|
956c12eb32 | ||
|
|
d5ae4eed26 | ||
|
|
ca486e5ed9 | ||
|
|
de63066b0b | ||
|
|
c7ca55ceb5 | ||
|
|
024bef7ded | ||
|
|
34d3d60f95 | ||
|
|
ed907d7c6f | ||
|
|
9d7fd66546 | ||
|
|
9934841950 | ||
|
|
432fe1100b | ||
|
|
d987628935 | ||
|
|
4c3ceae0e4 | ||
|
|
2b9d7538bf | ||
|
|
4ca1fc083d | ||
|
|
e7585271fb | ||
|
|
6e6824117d | ||
|
|
3e5f2fd8b2 | ||
|
|
6e470d788e | ||
|
|
a40d8092ce | ||
|
|
0234a70bfd | ||
|
|
705b1e0c2b | ||
|
|
69379334f9 | ||
|
|
12e50a1f2f | ||
|
|
ec17a477be | ||
|
|
205e1c7a59 | ||
|
|
ffb60c06cb | ||
|
|
2280f59ee6 | ||
|
|
0d4dd63edc | ||
|
|
69f1f4c1a5 | ||
|
|
d930d9c237 | ||
|
|
9c782d130f | ||
|
|
f12053f39d | ||
|
|
bcf6ca4f87 | ||
|
|
46619bb425 | ||
|
|
cdd870a129 | ||
|
|
7a1b7b9438 | ||
|
|
2183ada1f2 | ||
|
|
1d9e64ec73 | ||
|
|
5339be318e | ||
|
|
e1ba39c437 | ||
|
|
07ee271478 | ||
|
|
4823cce622 | ||
|
|
23099f7e8b | ||
|
|
5bdbd51fa8 | ||
|
|
a0e5871c6e | ||
|
|
3cf0904651 | ||
|
|
2bd06ff0fd | ||
|
|
6ea333d0f1 | ||
|
|
91d2485940 | ||
|
|
0d99b2bcf4 | ||
|
|
a54a424f84 | ||
|
|
3514699f5b | ||
|
|
d196988cab | ||
|
|
03aff83135 | ||
|
|
17e6bfaca6 | ||
|
|
90281262be | ||
|
|
5bbc118920 | ||
|
|
dde2f8b9ad | ||
|
|
d41aa30e10 | ||
|
|
231d0380a7 | ||
|
|
a5c509981b | ||
|
|
71b01e6110 | ||
|
|
c716f85962 | ||
|
|
245d51caea | ||
|
|
e588917f6c | ||
|
|
27e5d21b6b | ||
|
|
b894b75e6a | ||
|
|
116397cdb3 | ||
|
|
a6b5abf271 | ||
|
|
fd5a649246 | ||
|
|
e8e57989ba | ||
|
|
03db4ccce6 | ||
|
|
c9d5d5609c | ||
|
|
ac16206ddb | ||
|
|
5f8bcbd02d | ||
|
|
a0f74134bb | ||
|
|
0463a6fd70 | ||
|
|
a5de633c64 | ||
|
|
b2ad2acff3 | ||
|
|
4ee6ff73e0 | ||
|
|
628bdde939 | ||
|
|
e9b78c1c59 | ||
|
|
315cbc0925 | ||
|
|
69741d858c | ||
|
|
20d99a78b6 | ||
|
|
2d68393e07 | ||
|
|
8cb1470643 | ||
|
|
8d9b27bf47 | ||
|
|
0c5d9f1a7d | ||
|
|
a7d88c06c1 | ||
|
|
2735000fcf | ||
|
|
a39de270cd | ||
|
|
10f11213d3 | ||
|
|
3b49cbf73b | ||
|
|
e4cfe08113 | ||
|
|
fa02cb885c | ||
|
|
69b864fa02 | ||
|
|
b9ed9a6c0b | ||
|
|
5156495b47 | ||
|
|
b92047bc3f | ||
|
|
70559d32df | ||
|
|
bb84b75db9 | ||
|
|
f634177973 | ||
|
|
e33ff37c82 | ||
|
|
d2bc7f6ac0 | ||
|
|
5ef1ca06f5 | ||
|
|
1d8651bc00 | ||
|
|
17eabf2004 | ||
|
|
37bb1367db | ||
|
|
24b5dab456 | ||
|
|
c6bfc8b8ea | ||
|
|
f2b0fa164e | ||
|
|
0ca8bdb3a8 | ||
|
|
a8471f62bc | ||
|
|
5f1c03682f | ||
|
|
3255ed3ffb | ||
|
|
c51b54c0c5 | ||
|
|
a3f0807d47 | ||
|
|
27055944df | ||
|
|
7b1653034b | ||
|
|
39d8ca050f | ||
|
|
50274cb66d | ||
|
|
bd60a9fd2e | ||
|
|
83409c32c4 | ||
|
|
1bc92623dc | ||
|
|
f73196841a | ||
|
|
86a4b130ff | ||
|
|
f4e2f117c3 | ||
|
|
8ef92d26c5 | ||
|
|
c8336df49d | ||
|
|
c2fe3cda35 | ||
|
|
c54f47efbf | ||
|
|
3c40cc4b7d | ||
|
|
f0fb77bade | ||
|
|
e894e78346 | ||
|
|
dd04c610ec | ||
|
|
2e674efe50 | ||
|
|
4d394c31f9 | ||
|
|
11d58abbc3 | ||
|
|
5f4ad87a47 | ||
|
|
e43a21866c | ||
|
|
6652012f4c | ||
|
|
0c4e8ca290 | ||
|
|
230a2c5c62 | ||
|
|
590fb7f533 | ||
|
|
e8b88248f2 | ||
|
|
c6ba1b6a4e | ||
|
|
c4c52bd8c0 | ||
|
|
c633a792f5 | ||
|
|
8d5421d9da | ||
|
|
b041f4ace2 | ||
|
|
d26f96db9e | ||
|
|
fa999880ee | ||
|
|
d1e0f9dfc5 | ||
|
|
2d86938375 | ||
|
|
4865269a73 | ||
|
|
038ce3dcec | ||
|
|
43b997fe40 | ||
|
|
44e92f61ca | ||
|
|
b4bfa17cee | ||
|
|
500b694cc6 | ||
|
|
b998282304 | ||
|
|
b704216553 | ||
|
|
03ec02a749 | ||
|
|
248ea82573 | ||
|
|
bbe125beee | ||
|
|
95cd1c6f87 | ||
|
|
1c2107ae8f | ||
|
|
e826e17c6c | ||
|
|
b111f0921c | ||
|
|
090a48515c | ||
|
|
b8555d8c42 | ||
|
|
cf1f4f12a9 | ||
|
|
ef4413633a | ||
|
|
c5bb38e907 | ||
|
|
9f515fc804 | ||
|
|
245edcefdd | ||
|
|
153f73c308 | ||
|
|
315837b267 | ||
|
|
ea8da24215 | ||
|
|
1ab723ca64 | ||
|
|
f8bc6cf998 | ||
|
|
8687163cca | ||
|
|
e261e16d99 | ||
|
|
bdcd083eea | ||
|
|
c4bf9eb61c | ||
|
|
0994d10410 | ||
|
|
291c535edb | ||
|
|
f04702fdd1 | ||
|
|
b7fbc12ac0 | ||
|
|
e2ab8db958 | ||
|
|
34d70980e8 | ||
|
|
e45c4042fe | ||
|
|
ce8092318e | ||
|
|
c95cc1dd9d | ||
|
|
1f72877728 | ||
|
|
93b40343d9 | ||
|
|
ba10108207 | ||
|
|
c28f66d673 | ||
|
|
59f327f97a | ||
|
|
872c92a244 | ||
|
|
2303880b87 | ||
|
|
0e97cc1712 | ||
|
|
d550e42441 | ||
|
|
4279753030 | ||
|
|
2f6c018b7a | ||
|
|
be5d42baba | ||
|
|
f174614496 | ||
|
|
e8c7fba8b0 | ||
|
|
5c49283023 | ||
|
|
ad56b431a3 | ||
|
|
b0d8a33d5f | ||
|
|
1715bcb216 | ||
|
|
96caeaca72 | ||
|
|
c8723ae935 | ||
|
|
94e39c044c | ||
|
|
9776ea53c9 | ||
|
|
675d07dac0 | ||
|
|
1217820288 | ||
|
|
90e1a3cb76 | ||
|
|
7c1095ea50 | ||
|
|
203e916a07 | ||
|
|
7201e34b38 | ||
|
|
6d032c378f | ||
|
|
badd8952f5 | ||
|
|
7209eddb2d | ||
|
|
a7ffd43b29 | ||
|
|
724ba69483 | ||
|
|
1716801437 | ||
|
|
fa727524dc | ||
|
|
28f80e18a0 | ||
|
|
4acc8bfe80 | ||
|
|
9f86ed7887 | ||
|
|
cb18a55338 | ||
|
|
78a928464c | ||
|
|
07a8cd0a5f | ||
|
|
b73ef58567 | ||
|
|
519531f3a0 | ||
|
|
4b8e47d079 | ||
|
|
ed170f7e07 | ||
|
|
6bb559874c | ||
|
|
9cd68e25b3 | ||
|
|
72ac441598 | ||
|
|
f5fe56c85d | ||
|
|
d4fc1be2cc | ||
|
|
7b3617a273 | ||
|
|
3e8adae3de | ||
|
|
9943133d6d | ||
|
|
a5706a47af | ||
|
|
b3b911c64d | ||
|
|
05beb4fcaf | ||
|
|
324b50e381 | ||
|
|
186f4289e9 | ||
|
|
cb90a8356b | ||
|
|
e9c502ab32 | ||
|
|
f728520e97 | ||
|
|
c1b0d50237 | ||
|
|
b9cfc29059 | ||
|
|
6bf7c79891 | ||
|
|
2bd0e58354 | ||
|
|
ee674cb0cf | ||
|
|
dd1b354d48 | ||
|
|
d3e96f4a99 | ||
|
|
90e2eb3db9 | ||
|
|
102be6a350 | ||
|
|
be6152948c | ||
|
|
f14bf4b205 | ||
|
|
123ac2ca25 | ||
|
|
7216b976ec | ||
|
|
7fb698cfbf | ||
|
|
b0470ab678 | ||
|
|
0deb037035 | ||
|
|
064b362d60 | ||
|
|
6bbe228a84 | ||
|
|
a8ba8b6ab5 | ||
|
|
e850f6c2a5 | ||
|
|
e9eb7d6db1 | ||
|
|
56d21321d0 | ||
|
|
3888a74212 | ||
|
|
0035e29f9e | ||
|
|
90679d5669 | ||
|
|
ac5dc2578a | ||
|
|
671e6823be | ||
|
|
0d76662311 | ||
|
|
8dd59cf3cf | ||
|
|
332d25dc00 | ||
|
|
0fa81a77dc | ||
|
|
9a74d8f72d | ||
|
|
8c539351e3 | ||
|
|
e3552fc0ae | ||
|
|
ca308636c3 | ||
|
|
244cb847fb | ||
|
|
176c52daf2 | ||
|
|
ee3525ec64 | ||
|
|
adce921be7 | ||
|
|
20ceb1c284 | ||
|
|
db4364b8f8 | ||
|
|
48d6435e09 | ||
|
|
7c20e9ab34 | ||
|
|
cb52605928 | ||
|
|
bd3100dcda | ||
|
|
afb7c35cfd | ||
|
|
5b694a55d2 | ||
|
|
f750a4cd72 | ||
|
|
eddeaaf52a | ||
|
|
2b42bee424 | ||
|
|
b9706c0cc1 | ||
|
|
800b21b000 | ||
|
|
d1736a25bb | ||
|
|
f269e5fe58 | ||
|
|
45a5296013 | ||
|
|
5f0ab2a177 | ||
|
|
70ab02987a | ||
|
|
421da882d8 | ||
|
|
1a40102f71 | ||
|
|
1fbc10cd77 | ||
|
|
87462398d8 | ||
|
|
b33246b29f | ||
|
|
dbffe30f63 | ||
|
|
9ce450d0b0 | ||
|
|
f5f8acf1fd | ||
|
|
304ed3cd60 | ||
|
|
07248692ce | ||
|
|
cef99cba71 | ||
|
|
cab5f29b57 | ||
|
|
55a2f75d16 | ||
|
|
a416ee8f2b | ||
|
|
c14936c63c | ||
|
|
42e7a8b423 | ||
|
|
4558b1c7ef | ||
|
|
f444ba9665 | ||
|
|
787c8d740b | ||
|
|
ef38a8bb2b | ||
|
|
b5d35d346a | ||
|
|
9a7b15d19b | ||
|
|
ba35a11e82 | ||
|
|
00c9efe541 | ||
|
|
44d1ab348b | ||
|
|
f25e4785ae | ||
|
|
4c290aec57 | ||
|
|
42d1d66d9b | ||
|
|
06c25301a5 | ||
|
|
43dbb45cc6 | ||
|
|
9fb62de5cb | ||
|
|
b282df2e27 | ||
|
|
da8f57f397 | ||
|
|
e9f1992c7f | ||
|
|
2c94a6a64e | ||
|
|
6a5e0a8501 | ||
|
|
26d7fe2ff0 | ||
|
|
5854a635ca | ||
|
|
3d4858b840 | ||
|
|
dec2a252fa | ||
|
|
f619ef23fd | ||
|
|
72743e834e | ||
|
|
8e4ddf366c | ||
|
|
b99bd0176a | ||
|
|
a68b98b245 | ||
|
|
a69d6ff1b4 | ||
|
|
ec679e95fd | ||
|
|
3f78235a74 | ||
|
|
c8a2fd78b0 | ||
|
|
45cf08111a | ||
|
|
527ac36129 | ||
|
|
a7232513a7 | ||
|
|
90f187e885 |
@@ -8,9 +8,11 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
|
||||
gtest \
|
||||
mariadb-libs \
|
||||
protobuf \
|
||||
qt5-base \
|
||||
qt5-multimedia \
|
||||
qt5-svg \
|
||||
qt5-tools \
|
||||
qt5-websockets \
|
||||
qt6-base \
|
||||
qt6-imageformats \
|
||||
qt6-multimedia \
|
||||
qt6-svg \
|
||||
qt6-tools \
|
||||
qt6-translations \
|
||||
qt6-websockets \
|
||||
&& pacman --sync --clean --clean --noconfirm
|
||||
@@ -1,25 +0,0 @@
|
||||
FROM debian:10
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ccache \
|
||||
clang-format \
|
||||
cmake \
|
||||
file \
|
||||
g++ \
|
||||
git \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5sql5-mysql \
|
||||
libqt5svg5-dev \
|
||||
libqt5websockets5-dev \
|
||||
protobuf-compiler \
|
||||
qt5-default \
|
||||
qtbase5-dev \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -17,6 +17,7 @@ RUN apt-get update && \
|
||||
libqt5svg5-dev \
|
||||
libqt5websockets5-dev \
|
||||
protobuf-compiler \
|
||||
qt5-image-formats-plugins \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
|
||||
27
.ci/Debian12/Dockerfile
Normal file
27
.ci/Debian12/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM debian:12
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ccache \
|
||||
clang-format \
|
||||
cmake \
|
||||
file \
|
||||
g++ \
|
||||
git \
|
||||
libgl-dev \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt6multimedia6 \
|
||||
libqt6sql6-mysql \
|
||||
qt6-svg-dev \
|
||||
qt6-websockets-dev \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
qt6-multimedia-dev \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -1,21 +0,0 @@
|
||||
FROM fedora:35
|
||||
|
||||
RUN dnf install -y \
|
||||
@development-tools \
|
||||
ccache \
|
||||
cmake \
|
||||
desktop-file-utils \
|
||||
file \
|
||||
gcc-c++ \
|
||||
git \
|
||||
hicolor-icon-theme \
|
||||
libappstream-glib \
|
||||
mariadb-devel \
|
||||
protobuf-devel \
|
||||
qt5-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
||||
rpm-build \
|
||||
sqlite-devel \
|
||||
wget \
|
||||
xz-devel \
|
||||
zlib-devel \
|
||||
&& dnf clean all
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM fedora:36
|
||||
FROM fedora:40
|
||||
|
||||
RUN dnf install -y \
|
||||
ccache \
|
||||
@@ -7,7 +7,8 @@ RUN dnf install -y \
|
||||
git \
|
||||
mariadb-devel \
|
||||
protobuf-devel \
|
||||
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets,qt5compat}-devel \
|
||||
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
||||
qt6-qtimageformats \
|
||||
rpm-build \
|
||||
xz-devel \
|
||||
zlib-devel \
|
||||
15
.ci/Fedora41/Dockerfile
Normal file
15
.ci/Fedora41/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM fedora:41
|
||||
|
||||
RUN dnf install -y \
|
||||
ccache \
|
||||
cmake \
|
||||
gcc-c++ \
|
||||
git \
|
||||
mariadb-devel \
|
||||
protobuf-devel \
|
||||
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
||||
qt6-qtimageformats \
|
||||
rpm-build \
|
||||
xz-devel \
|
||||
zlib-devel \
|
||||
&& dnf clean all
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal
|
||||
FROM ubuntu:20.04
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
@@ -18,6 +18,7 @@ RUN apt-get update && \
|
||||
libqt5websockets5-dev \
|
||||
protobuf-compiler \
|
||||
qt5-default \
|
||||
qt5-image-formats-plugins \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:jammy
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
@@ -13,12 +13,12 @@ RUN apt-get update && \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt6core5compat6-dev \
|
||||
libqt6multimedia6 \
|
||||
libqt6sql6-mysql \
|
||||
libqt6svg6-dev \
|
||||
libqt6websockets6-dev \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
qt6-multimedia-dev \
|
||||
qt6-tools-dev \
|
||||
27
.ci/Ubuntu24.04/Dockerfile
Normal file
27
.ci/Ubuntu24.04/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ccache \
|
||||
clang-format \
|
||||
cmake \
|
||||
file \
|
||||
g++ \
|
||||
git \
|
||||
libgl-dev \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt6multimedia6 \
|
||||
libqt6sql6-mysql \
|
||||
qt6-svg-dev \
|
||||
qt6-websockets-dev \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
qt6-multimedia-dev \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -1,24 +0,0 @@
|
||||
FROM ubuntu:bionic
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ccache \
|
||||
clang-format \
|
||||
cmake \
|
||||
file \
|
||||
g++ \
|
||||
git \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5sql5-mysql \
|
||||
libqt5svg5-dev \
|
||||
libqt5websockets5-dev \
|
||||
protobuf-compiler \
|
||||
qt5-default \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -127,7 +127,7 @@ fi
|
||||
# Add cmake --build flags
|
||||
buildflags=(--config "$BUILDTYPE")
|
||||
if [[ $PARALLEL_COUNT ]]; then
|
||||
if [[ $(cmake --build /not_a_dir --parallel |& head -1) =~ parallel ]]; then
|
||||
if [[ $(cmake --build /not_a_dir --parallel 2>&1 | head -1) =~ parallel ]]; then
|
||||
# workaround for bionic having an old cmake
|
||||
echo "this version of cmake does not support --parallel, using native build tool -j instead"
|
||||
buildflags+=(-- -j "$PARALLEL_COUNT")
|
||||
@@ -160,7 +160,13 @@ cmake .. "${flags[@]}"
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Build project"
|
||||
cmake --build . "${buildflags[@]}"
|
||||
if [[ $RUNNER_OS == Windows ]]; then
|
||||
# Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
|
||||
# and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
|
||||
cmake --build . "${buildflags[@]}" -- -p:UseMultiToolTask=true -p:EnableClServerMode=true
|
||||
else
|
||||
cmake --build . "${buildflags[@]}"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
if [[ $USE_CCACHE ]]; then
|
||||
@@ -171,7 +177,7 @@ fi
|
||||
|
||||
if [[ $MAKE_TEST ]]; then
|
||||
echo "::group::Run tests"
|
||||
ctest -C "$BUILDTYPE"
|
||||
ctest -C "$BUILDTYPE" --output-on-failure
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
|
||||
@@ -183,6 +189,12 @@ fi
|
||||
|
||||
if [[ $MAKE_PACKAGE ]]; then
|
||||
echo "::group::Create package"
|
||||
|
||||
if [[ $RUNNER_OS == macOS ]]; then
|
||||
# Workaround https://github.com/actions/runner-images/issues/7522
|
||||
echo "killing XProtectBehaviorService"; sudo pkill -9 XProtect >/dev/null || true;
|
||||
echo "waiting for XProtectBehaviorService kill"; while pgrep "XProtect"; do sleep 3; done;
|
||||
fi
|
||||
cmake --build . --target package --config "$BUILDTYPE"
|
||||
echo "::endgroup::"
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# where SUFFIX is either available in the environment or as the first arg
|
||||
# if MAKE_ZIP is set instead a zip is made
|
||||
# expected to be run in the build directory unless BUILD_DIR is set
|
||||
# adds output to GITHUB_OUTPUT
|
||||
builddir="${BUILD_DIR:=.}"
|
||||
findrx="Cockatrice-*.*"
|
||||
|
||||
@@ -50,5 +51,5 @@ fi
|
||||
cd "$oldpwd"
|
||||
relative_path="$path/$filename"
|
||||
ls -l "$relative_path"
|
||||
echo "::set-output name=path::$relative_path"
|
||||
echo "::set-output name=name::$filename"
|
||||
echo "path=$relative_path" >>"$GITHUB_OUTPUT"
|
||||
echo "name=$filename" >>"$GITHUB_OUTPUT"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# the releases are first made as drafts and will be vetted by a human
|
||||
# it just has to provide a template
|
||||
# this requires the repo to be unshallowed
|
||||
# adds output to GITHUB_OUTPUT
|
||||
template_path=".ci/release_template.md"
|
||||
body_path="/tmp/release.md"
|
||||
beta_regex='beta'
|
||||
@@ -22,22 +23,22 @@ fi
|
||||
|
||||
# create title
|
||||
if [[ $TAG =~ $beta_regex ]]; then
|
||||
echo "::set-output name=is_beta::yes"
|
||||
echo "is_beta=yes" >>"$GITHUB_OUTPUT"
|
||||
title="$TAG"
|
||||
echo "creating beta release '$title'"
|
||||
elif [[ ! $(cat CMakeLists.txt) =~ $name_regex ]]; then
|
||||
echo "::error file=$0::could not find releasename in CMakeLists.txt"
|
||||
exit 1
|
||||
else
|
||||
echo "::set-output name=is_beta::no"
|
||||
echo "is_beta=no" >>"$GITHUB_OUTPUT"
|
||||
name="${BASH_REMATCH[1]}"
|
||||
version="${TAG##*-}"
|
||||
title="Cockatrice $version: $name"
|
||||
no_beta=1
|
||||
echo "::set-output name=friendly_name::$name"
|
||||
echo "friendly_name=$name" >>"$GITHUB_OUTPUT"
|
||||
echo "creating full release '$title'"
|
||||
fi
|
||||
echo "::set-output name=title::$title"
|
||||
echo "title=$title" >>"$GITHUB_OUTPUT"
|
||||
|
||||
# add release notes template
|
||||
if [[ $no_beta ]]; then
|
||||
@@ -60,9 +61,9 @@ fi
|
||||
all_tags="
|
||||
$(git tag)" # tags are ordered alphabetically
|
||||
before="${all_tags%%
|
||||
$TAG*}" # strip line with current tag an all lines after it
|
||||
"$TAG"*}" # strip line with current tag an all lines after it
|
||||
# note the extra newlines are needed to always have a last line
|
||||
if [[ $all_tags == $before ]]; then
|
||||
if [[ $all_tags == "$before" ]]; then
|
||||
echo "::warning file=$0::could not find current tag"
|
||||
else
|
||||
while
|
||||
@@ -74,7 +75,7 @@ else
|
||||
beta_list+=" $previous" # add to list of skipped betas
|
||||
next_before="${before%
|
||||
*}" # strip the last line
|
||||
if [[ $next_before == $before ]]; then
|
||||
if [[ $next_before == "$before" ]]; then
|
||||
unset previous
|
||||
break
|
||||
fi
|
||||
@@ -108,5 +109,5 @@ else
|
||||
fi
|
||||
|
||||
# write to file
|
||||
echo "::set-output name=body_path::$body_path"
|
||||
echo "body_path=$body_path" >>"$GITHUB_OUTPUT"
|
||||
echo "$body" >"$body_path"
|
||||
|
||||
@@ -4,25 +4,24 @@
|
||||
git push -d origin --REPLACE-WITH-BETA-LIST--
|
||||
-->
|
||||
|
||||
<!-- This list of binaries should be updated every time the ci is changed to
|
||||
<!-- This list of binaries should be updated every time the CI is changed to
|
||||
include different targets -->
|
||||
<pre>
|
||||
<b>Pre-compiled binaries we serve:</b>
|
||||
- <kbd>Windows 7/8/10/11 (32-bit)</kbd>
|
||||
- <kbd>Windows 7/8 (64-bit)</kbd>
|
||||
- <kbd>Windows 10/11 (64-bit)</kbd>
|
||||
- <kbd>macOS 10.14</kbd> ("Mojave")
|
||||
- <kbd>macOS 10.15</kbd> ("Catalina")
|
||||
- <kbd>macOS 11.0+</kbd> ("Big Sur")
|
||||
- <kbd>Ubuntu 18.04</kbd> ("Bionic Beaver")
|
||||
- <kbd>Ubuntu 20.04</kbd> ("Focal Fossa")
|
||||
- <kbd>Ubuntu 22.04</kbd> ("Jammy Jellyfish")
|
||||
- <kbd>Debian 10</kbd> ("Buster")
|
||||
- <kbd>Windows 10+</kbd>
|
||||
- <kbd>Windows 7+</kbd>
|
||||
- <kbd>macOS 14+</kbd> ("Sonoma") / Apple M
|
||||
- <kbd>macOS 13+</kbd> ("Ventura") / Intel
|
||||
- <kbd>Ubuntu 24.04 LTS</kbd> ("Noble Numbat")
|
||||
- <kbd>Ubuntu 22.04 LTS</kbd> ("Jammy Jellyfish")
|
||||
- <kbd>Ubuntu 20.04 LTS</kbd> ("Focal Fossa")
|
||||
- <kbd>Debian 12</kbd> ("Bookworm")
|
||||
- <kbd>Debian 11</kbd> ("Bullseye")
|
||||
- <kbd>Fedora 35</kbd>
|
||||
- <kbd>Fedora 36</kbd>
|
||||
<kbd>We are also packaged in Arch Linux's official community repository, courtesy of @FFY00</kbd></i>
|
||||
<kbd>General linux support is available via a flatpak package (Flathub)</kbd></i>
|
||||
- <kbd>Fedora 41</kbd>
|
||||
- <kbd>Fedora 40</kbd>
|
||||
|
||||
<i>We are also packaged in <kbd>Arch Linux</kbd>'s official "extra" repository, courtesy of @FFY00</i>
|
||||
<i>General Linux support is available via a <kbd>flatpak</kbd> package (Flathub)</i>
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -30,22 +29,24 @@ include different targets -->
|
||||
|
||||
We're pleased to announce the newest official release: <kbd>--REPLACE-WITH-RELEASE-TITLE--</kbd>
|
||||
|
||||
We hope you enjoy the changes made and we have listed all changes, with their corresponding tickets, since the last version of Cockatrice was released for your convenience.
|
||||
We hope you enjoy the changes made! All improvements with their corresponding tickets since the last version of Cockatrice are listed in the changelog below.
|
||||
|
||||
If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a GitHub Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
|
||||
If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
|
||||
|
||||
For any information relating to Cockatrice, please take a look at our official site: **https://cockatrice.github.io**
|
||||
For basic information related to the app and getting started, please take a look at our official site: **https://cockatrice.github.io**
|
||||
|
||||
If you'd like to help contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution.
|
||||
If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-).
|
||||
We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution.
|
||||
|
||||
|
||||
## Upgrading Cockatrice
|
||||
<!-- this optional section puts a warning banner for problems with updating
|
||||
> ⚠️ **With this release, we no longer provide a ready-to-install binary for:**
|
||||
> [!IMPORTANT]
|
||||
> **With this release, we no longer provide a ready-to-install binary for:**
|
||||
> --DEPRECATED-OS-HERE--
|
||||
-->
|
||||
|
||||
- Run the internal software updater: <kbd>Help → Check for Client Updates</kbd>
|
||||
Run the internal software updater: <kbd>Help → Check for Client Updates</kbd>
|
||||
|
||||
Don't forget to update your card database right after! (<kbd>Help → Check for Card Updates...</kbd>)
|
||||
|
||||
@@ -62,14 +63,14 @@ Remove empty headers when done.
|
||||
-->
|
||||
|
||||
<!-- Highlights of the release -->
|
||||
### ⚠️ Important:
|
||||
### 🔖 Highlights:
|
||||
### ✨ New Features:
|
||||
### 🐛 Fixed Bugs / Resolved issues:
|
||||
### 🐛 Fixed Bugs / Resolved Issues:
|
||||
|
||||
<!-- Complete list of changes (foldable) -->
|
||||
<details>
|
||||
<summary>
|
||||
📘 <b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits)
|
||||
<b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits)
|
||||
</summary>
|
||||
|
||||
### User Interface
|
||||
@@ -90,5 +91,6 @@ Remove empty headers when done.
|
||||
|
||||
## Special Thanks
|
||||
<!-- Personalise this a bit! -->
|
||||
We continue to find it amazing that so many people contribute their time, knowledge, code, testing and more to the project. We'd like to thank the entire Cockatrice community for their efforts.
|
||||
It's amazing that so many people contribute their time, knowledge, code, testing and more to the project.
|
||||
We'd like to thank the entire Cockatrice community for their efforts! 🙏
|
||||
<!-- We'd like to especially recognize @ZeldaZach, --ADD-CONTRIBUTORS-HERE-- for their help in preparing so many amazing new features for the user base. -->
|
||||
|
||||
58
.ci/update_translation_source_strings.sh
Executable file
58
.ci/update_translation_source_strings.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ci script to update translation files
|
||||
# usage:
|
||||
# $0 cockatrice/cockatrice_en@source.ts cockatrice/src common
|
||||
# or
|
||||
# FILE="cockatrice/cockatrice_en@source.ts"
|
||||
# DIRS="cockatrice/src common"
|
||||
# $0
|
||||
# note: directories can't contain spaces
|
||||
|
||||
# check parameters
|
||||
if [[ ! $FILE ]]; then
|
||||
FILE="$1"
|
||||
shift
|
||||
fi
|
||||
if [[ ! $FILE ]]; then
|
||||
echo "no output file selected" >&2
|
||||
exit 2;
|
||||
fi
|
||||
if [[ ! $DIRS ]]; then
|
||||
DIRS="$*"
|
||||
fi
|
||||
if [[ ! $DIRS ]]; then
|
||||
echo "no source directories selected to translate" >&2
|
||||
exit 2;
|
||||
fi
|
||||
if [[ ! -e $FILE ]]; then
|
||||
echo "output file does not exist at: $FILE" >&2
|
||||
exit 3;
|
||||
fi
|
||||
|
||||
# print version
|
||||
if ! lupdate -version; then
|
||||
echo "failed to run lupdate" >&2
|
||||
exit 4;
|
||||
fi
|
||||
|
||||
# run lupdate, duplicating the output in stderr and saving it
|
||||
# for convenience we ignore that $DIRS will be split on spaces
|
||||
# shellcheck disable=SC2086
|
||||
if ! got="$(lupdate $DIRS -ts "$FILE" | tee /dev/stderr)"; then
|
||||
echo "failed to update $FILE with $DIRS" >&2
|
||||
exit 4;
|
||||
fi
|
||||
|
||||
# trim output
|
||||
# the line we are interested in is:
|
||||
# Found xxx source text(s) (x new and xxx already existing)
|
||||
output="${got##* source text(s) (}" # get stuff in between brackets
|
||||
output="${output%%)*}" # trim everything after first )
|
||||
if [[ $output == "$got" ]]; then
|
||||
echo "could not parse generated output" >&2
|
||||
exit 4;
|
||||
fi
|
||||
|
||||
# write output to ci environment file
|
||||
echo "output=$output" >> "$GITHUB_OUTPUT"
|
||||
14
.ci/update_translation_source_strings_template.md
Normal file
14
.ci/update_translation_source_strings_template.md
Normal file
@@ -0,0 +1,14 @@
|
||||
Updated source strings for translations:
|
||||
- {{ .cockatrice_output }} (Cockatrice)
|
||||
- {{ .oracle_output }} (Oracle)
|
||||
|
||||
<br>
|
||||
|
||||
Last changes are based on commit {{ .commit }}.
|
||||
|
||||
---
|
||||
*This PR is automatically generated and updated by the workflow at `.github/workflows/translations-push.yml`. Review [action runs][2].*<br>
|
||||
*After merging, all changes to the source language are available for translation at [Transifex][1] shortly.*
|
||||
|
||||
[1]: https://app.transifex.com/cockatrice/cockatrice/
|
||||
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-push.yml?query=branch%3Amaster
|
||||
73
.github/CONTRIBUTING.md
vendored
73
.github/CONTRIBUTING.md
vendored
@@ -290,20 +290,21 @@ be included in the next release 👍
|
||||
|
||||
Basic workflow for translations:
|
||||
1. Developer adds a `tr("foo")` string in the code;
|
||||
2. Every few days, a maintainer updates the `*_en@source.ts files` with the new strings;
|
||||
3. Transifex picks up the new files from GitHub every 24 hours;
|
||||
4. Translators translate the new untranslated strings on Transifex;
|
||||
5. Before a release, a maintainer fetches the updated translations from Transifex.
|
||||
2. CI updates the `*_en@source.ts files` regularly and creates a PR automatically;
|
||||
3. Maintainer verifies and merges the change;
|
||||
4. Transifex picks up the new files from GitHub automatically;
|
||||
5. Translators translate the new untranslated strings on Transifex;
|
||||
6. Before a release, a maintainer fetches the updated translations from Transifex.
|
||||
|
||||
### Using Translations (for developers) ###
|
||||
|
||||
All the user-interface strings inside Cockatrice's source code must be written
|
||||
in English(US).
|
||||
All user interface strings inside Cockatrice's source code must be written
|
||||
in English (US).
|
||||
Translations to other languages are managed using [Transifex](
|
||||
https://www.transifex.com/projects/p/cockatrice/).
|
||||
|
||||
Adding a new string to translate is as easy as adding the string in the
|
||||
'tr("")' function, the string will be picked up as translatable automatically
|
||||
`tr("")` function, the string will be picked up as translatable automatically
|
||||
and translated as needed.
|
||||
For example, setting the text of a label in the way that the string
|
||||
`"My name is:"` can be translated:
|
||||
@@ -312,7 +313,7 @@ nameLabel.setText(tr("My name is:"));
|
||||
```
|
||||
|
||||
To translate a string that would have plural forms you can add the amount to
|
||||
the tr call, also you can add an extra string as a hint for translators:
|
||||
the tr() call, also you can add an extra string as a hint for translators:
|
||||
```c++
|
||||
QString message = tr("Everyone draws %n cards", "pop up message", amount);
|
||||
```
|
||||
@@ -321,20 +322,46 @@ https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals)
|
||||
|
||||
If you're about to propose a change that adds or modifies any translatable
|
||||
string in the code, you don't need to take care of adding the new strings to
|
||||
the translation files.
|
||||
Every few days, or when a lot of new strings have been added, someone from the
|
||||
development team will take care of extracting all the new strings and adding
|
||||
them to the english translation files and making them available to translators
|
||||
on Transifex.
|
||||
the translation files.<br>
|
||||
We have an automated process to update our language source files on a schedule
|
||||
and provide the translators on Transifex with the new contents.<br>
|
||||
Maintainers can also manually trigger this on demand.
|
||||
|
||||
### Maintaining Translations (for maintainers) ###
|
||||
|
||||
When new translatable strings have been added to the code, a maintainer should
|
||||
make them available to translators on Transifex. Every few days, or when a lot
|
||||
of new strings have been added, a maintainer should take care of extracting all
|
||||
the new strings and add them to the english translation files.
|
||||
When new translatable strings have been added to the code, a maintainer has to
|
||||
make them available to translators on Transifex.
|
||||
|
||||
To update the english translation files, re-run cmake enabling the appropriate
|
||||
To help with that, we have an automated CI workflow, that regularly looks at the
|
||||
code in the master branch, extracts all strings and updates dedicated source string
|
||||
files with any changes. These updates are not commited right away, the CI creates a
|
||||
PR for reviewing instead.<br>
|
||||
After approval, our translation tool automatically picks the changes up and deploys
|
||||
them to our translators. Be mindful when merging only a few changes!
|
||||
|
||||
Once a release is planned, or when a lot of strings have been added or changed, a
|
||||
maintainer can manually trigger a CI run to extract all strings on demand.
|
||||
|
||||
<details>
|
||||
<summary><b>Manually trigger CI run (Workflow Dispatch)</b></summary>
|
||||
|
||||
Maintainers can always request the CI to run on demand if it's required.
|
||||
|
||||
Go to the `Actions` tab and select our dedicated translation workflow:
|
||||
https://github.com/Cockatrice/Cockatrice/actions/workflows/translations.yml
|
||||
|
||||
You see a "This workflow has a workflow_dispatch event trigger." hint at the top of
|
||||
the list.<br>
|
||||
Select `Run workflow` on the right and trigger a run from master branch.
|
||||
|
||||
The CI will now check for changed strings and create a PR if there are any updates.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Manually update source strings locally</b></summary>
|
||||
|
||||
To update the english source files for translation, re-run cmake enabling the appropriate
|
||||
parameter and then run make:
|
||||
```sh
|
||||
cd cockatrice/build
|
||||
@@ -357,11 +384,13 @@ It is recommended to disable the parameter afterwards using:
|
||||
```sh
|
||||
cmake .. -DUPDATE_TRANSLATIONS=OFF
|
||||
```
|
||||
Now you are ready to propose your change.
|
||||
Now you are ready to commit your changes and open a PR.
|
||||
|
||||
Once your change gets merged, Transifex will pick up the modified files
|
||||
automatically (checked every 24 hours) and update the interface where
|
||||
translators will be able to translate the new strings.
|
||||
</details>
|
||||
|
||||
Once the changes get merged, Transifex will pick up the modified files
|
||||
automatically (checked every few hours) and update their online editor where
|
||||
translators will be able to translate the new strings right in the browser.
|
||||
|
||||
### Releasing Translations (for maintainers) ###
|
||||
|
||||
|
||||
49
.github/dependabot.yml
vendored
Normal file
49
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# Configuration options: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# # Enable version updates for git submodules
|
||||
# Not yet possible to bump only on tags or releases, see:
|
||||
# https://github.com/dependabot/dependabot-core/issues/1639
|
||||
# https://github.com/dependabot/dependabot-core/issues/2192
|
||||
# Alternative: Action that updates submodule and can be manually run on demand (workflow_dispatch)
|
||||
# - package-ecosystem: "gitsubmodule"
|
||||
# # Look for `.gitmodules` in the `root` directory
|
||||
# directory: "/"
|
||||
# # Check for updates once a month
|
||||
# schedule:
|
||||
# interval: "monthly"
|
||||
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
|
||||
# open-pull-requests-limit: 1
|
||||
|
||||
# # Enable version updates for Docker
|
||||
# Not yet possible to bump from one LTS version to the next and skip others, see:
|
||||
# https://github.com/dependabot/dependabot-core/issues/2247
|
||||
# - package-ecosystem: "docker"
|
||||
# # Look for a `Dockerfile` in the `root` directory
|
||||
# directory: "/"
|
||||
# # Check for updates once a week
|
||||
# schedule:
|
||||
# interval: "weekly"
|
||||
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
|
||||
# open-pull-requests-limit: 1
|
||||
|
||||
# Enable version updates for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
# Directory must be set to "/" to check for workflow files in .github/workflows
|
||||
directory: "/"
|
||||
# Check for updates to GitHub Actions once a week
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
|
||||
open-pull-requests-limit: 2
|
||||
|
||||
# # Enable version updates for npm
|
||||
# - package-ecosystem: "npm"
|
||||
# # Look for `package.json` and `lock` files in the `webclient` subdirectory
|
||||
# directory: "/webclient"
|
||||
# # Check the npm registry for updates once a week
|
||||
# schedule:
|
||||
# interval: "weekly"
|
||||
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
|
||||
# open-pull-requests-limit: 5
|
||||
324
.github/workflows/desktop-build.yml
vendored
324
.github/workflows/desktop-build.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
- '**.md'
|
||||
- 'webclient/**'
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.github/workflows/translations-*.yml'
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
@@ -15,6 +16,12 @@ on:
|
||||
- '**.md'
|
||||
- 'webclient/**'
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.github/workflows/translations-*.yml'
|
||||
|
||||
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on master)
|
||||
concurrency:
|
||||
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
|
||||
cancel-in-progress: ${{ github.ref_name != 'master' }}
|
||||
|
||||
jobs:
|
||||
configure:
|
||||
@@ -23,33 +30,27 @@ jobs:
|
||||
outputs:
|
||||
tag: ${{steps.configure.outputs.tag}}
|
||||
sha: ${{steps.configure.outputs.sha}}
|
||||
upload_url: ${{steps.create_release.outputs.upload_url}}
|
||||
|
||||
steps:
|
||||
- name: Cancel previous runs
|
||||
uses: styfle/cancel-workflow-action@0.6.0
|
||||
with:
|
||||
access_token: ${{github.token}} # needs other token https://github.com/styfle/cancel-workflow-action/issues/7
|
||||
|
||||
steps:
|
||||
- name: Configure
|
||||
id: configure
|
||||
shell: bash
|
||||
run: |
|
||||
tag_regex='^refs/tags/'
|
||||
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
|
||||
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
|
||||
sha="${{github.event.pull_request.head.sha}}"
|
||||
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
|
||||
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
|
||||
sha="$GITHUB_SHA"
|
||||
tag="${GITHUB_REF/refs\/tags\//}"
|
||||
echo "::set-output name=tag::$tag"
|
||||
else # push to branch
|
||||
echo "tag=$tag" >>"$GITHUB_OUTPUT"
|
||||
else # push to branch
|
||||
sha="$GITHUB_SHA"
|
||||
fi
|
||||
echo "::set-output name=sha::$sha"
|
||||
echo "sha=$sha" >>"$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout
|
||||
if: steps.configure.outputs.tag != null
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -64,170 +65,174 @@ jobs:
|
||||
- name: Create release
|
||||
if: steps.configure.outputs.tag != null
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{github.token}}
|
||||
with:
|
||||
tag_name: ${{github.ref}}
|
||||
GH_TOKEN: ${{github.token}}
|
||||
tag_name: ${{steps.configure.outputs.tag}}
|
||||
target: ${{steps.configure.outputs.sha}}
|
||||
release_name: ${{steps.prepare.outputs.title}}
|
||||
body_path: ${{steps.prepare.outputs.body_path}}
|
||||
draft: true
|
||||
prerelease: ${{steps.prepare.outputs.is_beta == 'yes'}}
|
||||
prerelease: ${{steps.prepare.outputs.is_beta}}
|
||||
run: |
|
||||
if [[ $prerelease == yes ]]; then
|
||||
args="--prerelease"
|
||||
fi
|
||||
gh release create "$tag_name" --draft --verify-tag $args \
|
||||
--target "$target" --title "$release_name" \
|
||||
--notes-file "$body_path"
|
||||
|
||||
build-linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# these names correspond to the files in .ci/$distro
|
||||
# These names correspond to the files in ".ci/$distro$version"
|
||||
include:
|
||||
- distro: ArchLinux
|
||||
package: skip # we are packaged in arch already
|
||||
- distro: Arch
|
||||
package: skip # We are packaged in Arch already
|
||||
allow-failure: yes
|
||||
|
||||
- distro: Debian10
|
||||
- distro: Debian
|
||||
version: 11
|
||||
package: DEB
|
||||
test: skip # running tests on all distros is superfluous
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Debian11
|
||||
- distro: Debian
|
||||
version: 12
|
||||
package: DEB
|
||||
|
||||
- distro: Fedora35
|
||||
- distro: Fedora
|
||||
version: 40
|
||||
package: RPM
|
||||
test: skip
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Fedora36
|
||||
- distro: Fedora
|
||||
version: 41
|
||||
package: RPM
|
||||
|
||||
- distro: UbuntuBionic
|
||||
- distro: Ubuntu
|
||||
version: 20.04
|
||||
package: DEB
|
||||
test: skip # Ubuntu 20.04 has a broken Qt for debug builds
|
||||
|
||||
- distro: Ubuntu
|
||||
version: 22.04
|
||||
package: DEB
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Ubuntu
|
||||
version: 24.04
|
||||
package: DEB
|
||||
|
||||
- distro: UbuntuFocal
|
||||
package: DEB
|
||||
test: skip # UbuntuFocal has a broken qt for debug builds
|
||||
|
||||
- distro: UbuntuJammy
|
||||
package: DEB
|
||||
|
||||
name: ${{matrix.distro}}
|
||||
name: ${{matrix.distro}} ${{matrix.version}}
|
||||
needs: configure
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{matrix.allow-failure == 'yes'}}
|
||||
env:
|
||||
NAME: ${{matrix.distro}}
|
||||
CACHE: /tmp/${{matrix.distro}}-cache # ${{runner.temp}} does not work?
|
||||
# cache size over the entire repo is 10Gi link:
|
||||
NAME: ${{matrix.distro}}${{matrix.version}}
|
||||
CACHE: /tmp/${{matrix.distro}}${{matrix.version}}-cache # ${{runner.temp}} does not work?
|
||||
# Cache size over the entire repo is 10Gi:
|
||||
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
||||
CCACHE_SIZE: 200M
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache timestamp
|
||||
- name: Generate cache timestamp
|
||||
id: cache_timestamp
|
||||
shell: bash
|
||||
run: echo "::set-output name=timestamp::$(date -u '+%Y%m%d%H%M%S')"
|
||||
run: echo "timestamp=$(date -u '+%Y%m%d%H%M%S')" >>"$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
timestamp: ${{steps.cache_timestamp.outputs.timestamp}}
|
||||
with:
|
||||
path: ${{env.CACHE}}
|
||||
key: docker-${{matrix.distro}}-cache-${{env.timestamp}}
|
||||
key: docker-${{matrix.distro}}${{matrix.version}}-cache-${{env.timestamp}}
|
||||
restore-keys: |
|
||||
docker-${{matrix.distro}}-cache-
|
||||
docker-${{matrix.distro}}${{matrix.version}}-cache-
|
||||
|
||||
- name: Build ${{matrix.distro}} Docker image
|
||||
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
|
||||
shell: bash
|
||||
run: source .ci/docker.sh --build
|
||||
|
||||
- name: Build debug and test
|
||||
if: matrix.test != 'skip'
|
||||
shell: bash
|
||||
env:
|
||||
distro: '${{matrix.distro}}'
|
||||
run: |
|
||||
source .ci/docker.sh
|
||||
RUN --server --debug --test --ccache "$CCACHE_SIZE" --parallel 2
|
||||
RUN --server --debug --test --ccache "$CCACHE_SIZE" --parallel 4
|
||||
|
||||
- name: Build release package
|
||||
id: package
|
||||
id: build
|
||||
if: matrix.package != 'skip'
|
||||
shell: bash
|
||||
env:
|
||||
suffix: '-${{matrix.distro}}'
|
||||
BUILD_DIR: build
|
||||
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
|
||||
type: '${{matrix.package}}'
|
||||
distro: '${{matrix.distro}}'
|
||||
run: |
|
||||
source .ci/docker.sh
|
||||
RUN --server --release --package "$type" --suffix "$suffix" \
|
||||
--ccache "$CCACHE_SIZE" --parallel 2
|
||||
RUN --server --release --package "$type" --dir "$BUILD_DIR" \
|
||||
--ccache "$CCACHE_SIZE" --parallel 4
|
||||
.ci/name_build.sh
|
||||
|
||||
- name: Upload artifact
|
||||
if: matrix.package != 'skip'
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{matrix.distro}}-package
|
||||
path: ${{steps.package.outputs.path}}
|
||||
name: ${{matrix.distro}}${{matrix.version}}-package
|
||||
path: ${{steps.build.outputs.path}}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload to release
|
||||
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
|
||||
uses: actions/upload-release-asset@v1
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{github.token}}
|
||||
with:
|
||||
upload_url: ${{needs.configure.outputs.upload_url}}
|
||||
asset_path: ${{steps.package.outputs.path}}
|
||||
asset_name: ${{steps.package.outputs.name}}
|
||||
asset_content_type: application/octet-stream
|
||||
GH_TOKEN: ${{github.token}}
|
||||
tag_name: ${{needs.configure.outputs.tag}}
|
||||
asset_path: ${{steps.build.outputs.path}}
|
||||
asset_name: ${{steps.build.outputs.name}}
|
||||
run: gh release upload "$tag_name" "$asset_path#$asset_name"
|
||||
|
||||
build-macos:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: Debug # tests only
|
||||
os: macos-latest
|
||||
xcode: 12.5.1
|
||||
qt_version: 6
|
||||
- target: 13
|
||||
soc: Intel
|
||||
os: macos-13
|
||||
xcode: "14.3.1"
|
||||
type: Release
|
||||
core_count: 4
|
||||
make_package: 1
|
||||
|
||||
- target: 14
|
||||
soc: Apple
|
||||
os: macos-14
|
||||
xcode: "15.4"
|
||||
type: Release
|
||||
core_count: 3
|
||||
make_package: 1
|
||||
|
||||
- target: 15
|
||||
soc: Apple
|
||||
os: macos-15
|
||||
xcode: "16.2"
|
||||
type: Release
|
||||
core_count: 3
|
||||
make_package: 1
|
||||
|
||||
- target: 15
|
||||
soc: Apple
|
||||
os: macos-15
|
||||
xcode: "16.2"
|
||||
type: Debug
|
||||
do_tests: 1
|
||||
core_count: 3
|
||||
|
||||
- target: 10.14_Mojave
|
||||
os: macos-10.15 # runs on Catalina
|
||||
xcode: 10.3 # allows compatibility with macOS 10.14
|
||||
qt_version: 5
|
||||
type: Release
|
||||
# do_tests: 1 # tests do not work on qt5?
|
||||
make_package: 1
|
||||
|
||||
- target: 10.15_Catalina
|
||||
os: macos-10.15
|
||||
xcode: 12.4
|
||||
qt_version: 6
|
||||
type: Release
|
||||
do_tests: 1
|
||||
make_package: 1
|
||||
|
||||
- target: 11_Big_Sur
|
||||
os: macos-11
|
||||
xcode: 13.2
|
||||
qt_version: 6
|
||||
type: Release
|
||||
do_tests: 1
|
||||
make_package: 1
|
||||
|
||||
# - target: 12_Monterey
|
||||
# os: macos-12
|
||||
# xcode: 13.3
|
||||
# qt_version: 6
|
||||
# type: Release
|
||||
# do_tests: 1
|
||||
# make_package: 1
|
||||
|
||||
name: macOS ${{matrix.target}}
|
||||
name: macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
|
||||
needs: configure
|
||||
runs-on: ${{matrix.os}}
|
||||
continue-on-error: ${{matrix.allow-failure == 'yes'}}
|
||||
@@ -237,140 +242,125 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies using homebrew
|
||||
- name: Install dependencies using Homebrew
|
||||
shell: bash
|
||||
# cmake cannot find the mysql connector
|
||||
# neither of these works: mariadb-connector-c mysql-connector-c++
|
||||
# CMake cannot find the MySQL connector
|
||||
# Neither of these works: mariadb-connector-c mysql-connector-c++
|
||||
env:
|
||||
qt_version: 'qt@${{matrix.qt_version}}'
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
run: |
|
||||
brew update
|
||||
brew install protobuf
|
||||
brew install "$qt_version" --force-bottle
|
||||
brew install protobuf qt --force-bottle
|
||||
|
||||
- name: Build on Xcode ${{matrix.xcode}}
|
||||
shell: bash
|
||||
id: build
|
||||
env:
|
||||
BUILDTYPE: '${{matrix.type}}'
|
||||
MAKE_TEST: '${{matrix.do_tests}}'
|
||||
MAKE_TEST: 1
|
||||
MAKE_PACKAGE: '${{matrix.make_package}}'
|
||||
PACKAGE_SUFFIX: '-macOS-${{matrix.target}}'
|
||||
# set QTDIR to find qt5, qt6 can be found without this
|
||||
QTDIR: '/usr/local/opt/qt5'
|
||||
# Mac machines actually have 3 cores
|
||||
run: .ci/compile.sh --server --parallel 3
|
||||
PACKAGE_SUFFIX: '-macOS${{matrix.target}}_${{matrix.soc}}'
|
||||
# macOS runner have 3 cores usually - only the macos-13 image has 4:
|
||||
# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
|
||||
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
||||
run: .ci/compile.sh --server --parallel ${{matrix.core_count}}
|
||||
|
||||
- name: Upload artifact
|
||||
if: matrix.make_package
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macOS-${{matrix.target}}-dmg
|
||||
name: macOS${{matrix.target}}${{ matrix.soc == 'Intel' && '_Intel' || '' }}${{ matrix.type == 'Debug' && '_Debug' || '' }}-dmg
|
||||
path: ${{steps.build.outputs.path}}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload to release
|
||||
if: matrix.make_package && needs.configure.outputs.tag != null
|
||||
uses: actions/upload-release-asset@v1
|
||||
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{github.token}}
|
||||
with:
|
||||
upload_url: ${{needs.configure.outputs.upload_url}}
|
||||
GH_TOKEN: ${{github.token}}
|
||||
tag_name: ${{needs.configure.outputs.tag}}
|
||||
asset_path: ${{steps.build.outputs.path}}
|
||||
asset_name: ${{steps.build.outputs.name}}
|
||||
asset_content_type: application/octet-stream
|
||||
run: gh release upload "$tag_name" "$asset_path#$asset_name"
|
||||
|
||||
build-windows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: Win-32bit
|
||||
bit: 32
|
||||
arch: x86
|
||||
cmake_generator_platform: Win32
|
||||
qt_version: 5.15.*
|
||||
qt_arch: msvc2019
|
||||
qt_tools: "tools_openssl_x86"
|
||||
|
||||
- target: Win7+-64bit
|
||||
bit: 64
|
||||
arch: x64
|
||||
cmake_generator_platform: x64
|
||||
- target: 7
|
||||
qt_version: 5.15.*
|
||||
qt_arch: msvc2019_64
|
||||
qt_tools: "tools_openssl_x64"
|
||||
|
||||
- target: Win10+-64bit
|
||||
bit: 64
|
||||
arch: x64
|
||||
cmake_generator_platform: x64
|
||||
qt_version: 6.3.*
|
||||
- target: 10
|
||||
qt_version: 6.6.*
|
||||
qt_arch: msvc2019_64
|
||||
qt_tools: "tools_openssl_x64"
|
||||
qt_modules: "qt5compat qtmultimedia qtwebsockets"
|
||||
qt_modules: "qtimageformats qtmultimedia qtwebsockets"
|
||||
|
||||
name: ${{matrix.target}}
|
||||
name: Windows ${{matrix.target}}
|
||||
needs: configure
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
CMAKE_GENERATOR: 'Visual Studio 16 2019'
|
||||
CMAKE_GENERATOR: 'Visual Studio 17 2022'
|
||||
|
||||
steps:
|
||||
- name: Add msbuild to PATH
|
||||
id: add-msbuild
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
msbuild-architecture: x64
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Qt ${{matrix.qt_version}} for ${{matrix.target}}
|
||||
uses: jurplel/install-qt-action@v3
|
||||
- name: Install Qt ${{matrix.qt_version}}
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
cache: true
|
||||
setup-python: false
|
||||
version: ${{matrix.qt_version}}
|
||||
arch: win${{matrix.bit}}_${{matrix.qt_arch}}
|
||||
arch: win64_${{matrix.qt_arch}}
|
||||
tools: ${{matrix.qt_tools}}
|
||||
modules: ${{matrix.qt_modules}}
|
||||
|
||||
- name: Run vcpkg
|
||||
uses: lukka/run-vcpkg@v10.2
|
||||
uses: lukka/run-vcpkg@v11
|
||||
with:
|
||||
runVcpkgInstall: true
|
||||
appendedCacheKey: ${{matrix.bit}}-bit
|
||||
doNotCache: false
|
||||
env:
|
||||
VCPKG_DEFAULT_TRIPLET: '${{matrix.arch}}-windows'
|
||||
VCPKG_DEFAULT_TRIPLET: 'x64-windows'
|
||||
VCPKG_DISABLE_METRICS: 1
|
||||
|
||||
- name: Build Cockatrice
|
||||
id: build
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_SUFFIX: '-${{matrix.target}}'
|
||||
PACKAGE_SUFFIX: '-Win${{matrix.target}}'
|
||||
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
|
||||
CMAKE_GENERATOR_PLATFORM: '${{matrix.cmake_generator_platform}}'
|
||||
QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win${{matrix.bit}}_${{matrix.qt_arch}}'
|
||||
run: .ci/compile.sh --server --release --test --package --parallel 2
|
||||
CMAKE_GENERATOR_PLATFORM: 'x64'
|
||||
QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win64_${{matrix.qt_arch}}'
|
||||
# No need for --parallel flag, MTT is added in the compile script to let cmake/msbuild manage core count,
|
||||
# project and process parallelism: https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
|
||||
run: .ci/compile.sh --server --release --test --package
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{matrix.target}}-installer
|
||||
name: Windows${{matrix.target}}-installer
|
||||
path: ${{steps.build.outputs.path}}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload to release
|
||||
if: needs.configure.outputs.tag != null
|
||||
uses: actions/upload-release-asset@v1
|
||||
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{github.token}}
|
||||
with:
|
||||
upload_url: ${{needs.configure.outputs.upload_url}}
|
||||
GH_TOKEN: ${{github.token}}
|
||||
tag_name: ${{needs.configure.outputs.tag}}
|
||||
asset_path: ${{steps.build.outputs.path}}
|
||||
asset_name: ${{steps.build.outputs.name}}
|
||||
asset_content_type: application/octet-stream
|
||||
run: gh release upload "$tag_name" "$asset_path#$asset_name"
|
||||
|
||||
3
.github/workflows/desktop-lint.yml
vendored
3
.github/workflows/desktop-lint.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
- '**.md'
|
||||
- 'webclient/**'
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.github/workflows/translations-*.yml'
|
||||
|
||||
jobs:
|
||||
format:
|
||||
@@ -13,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 20 # should be enough to find merge base
|
||||
|
||||
|
||||
72
.github/workflows/translations-pull.yml
vendored
Normal file
72
.github/workflows/translations-pull.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Update Translations
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
|
||||
- cron: '0 0 15 1,4,7,10 *'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/translations-pull.yml'
|
||||
|
||||
jobs:
|
||||
translations:
|
||||
# Do not run the scheduled workflow on forks
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
|
||||
|
||||
name: Pull languages
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Pull translated strings from Transifex
|
||||
uses: transifex/cli-action@v2
|
||||
with:
|
||||
# used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
|
||||
# https://github.com/transifex/cli#pulling-files-from-transifex
|
||||
token: ${{ secrets.TX_TOKEN }}
|
||||
args: pull --force --all
|
||||
|
||||
- name: Create pull request
|
||||
if: github.event_name != 'pull_request'
|
||||
id: create_pr
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
add-paths: |
|
||||
cockatrice/translations/*.ts
|
||||
oracle/translations/*.ts
|
||||
webclient/public/locales/*/translation.json
|
||||
commit-message: Update translation files
|
||||
# author is the owner of the commit
|
||||
author: github-actions <github-actions@github.com>
|
||||
branch: ci-update_translations
|
||||
delete-branch: true
|
||||
title: 'Update translations'
|
||||
body: |
|
||||
Pulled all translated strings from [Transifex][1].
|
||||
|
||||
---
|
||||
*This PR is automatically generated and updated by the workflow at `.github/workflows/translations-pull.yml`. Review [action runs][2].*<br>
|
||||
*After merging, all new languages and translations are available in the next build.*
|
||||
|
||||
[1]: https://app.transifex.com/cockatrice/cockatrice/
|
||||
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
|
||||
labels: |
|
||||
CI
|
||||
Translation
|
||||
draft: false
|
||||
|
||||
- name: PR Status
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
env:
|
||||
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
|
||||
run: |
|
||||
if [[ "$STATUS" == "none" ]]; then
|
||||
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY
|
||||
87
.github/workflows/translations-push.yml
vendored
Normal file
87
.github/workflows/translations-push.yml
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
name: Update Translation Source
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# runs at the start of each quarter (UTC)
|
||||
- cron: '0 0 1 1,4,7,10 *'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/translations-push.yml'
|
||||
|
||||
jobs:
|
||||
translations:
|
||||
# Do not run the scheduled workflow on forks
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
|
||||
|
||||
name: Push strings
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install lupdate
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends qttools5-dev-tools
|
||||
|
||||
- name: Update Cockatrice translation source
|
||||
id: cockatrice
|
||||
shell: bash
|
||||
env:
|
||||
FILE: 'cockatrice/cockatrice_en@source.ts'
|
||||
DIRS: 'cockatrice/src common'
|
||||
run: .ci/update_translation_source_strings.sh
|
||||
|
||||
- name: Update Oracle translation source
|
||||
id: oracle
|
||||
shell: bash
|
||||
env:
|
||||
FILE: 'oracle/oracle_en@source.ts'
|
||||
DIRS: 'oracle/src'
|
||||
run: .ci/update_translation_source_strings.sh
|
||||
|
||||
- name: Render template
|
||||
id: template
|
||||
uses: chuhlomin/render-template@v1
|
||||
with:
|
||||
template: .ci/update_translation_source_strings_template.md
|
||||
vars: |
|
||||
cockatrice_output: ${{ steps.cockatrice.outputs.output }}
|
||||
oracle_output: ${{ steps.oracle.outputs.output }}
|
||||
commit: ${{ github.sha }}
|
||||
|
||||
- name: Create pull request
|
||||
if: github.event_name != 'pull_request'
|
||||
id: create_pr
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
add-paths: |
|
||||
cockatrice/cockatrice_en@source.ts
|
||||
oracle/oracle_en@source.ts
|
||||
commit-message: Update translation source strings
|
||||
# author is the owner of the commit
|
||||
author: github-actions <github-actions@github.com>
|
||||
branch: ci-update_translation_source
|
||||
delete-branch: true
|
||||
title: 'Update source strings'
|
||||
body: ${{ steps.template.outputs.result }}
|
||||
labels: |
|
||||
CI
|
||||
Translation
|
||||
draft: false
|
||||
|
||||
- name: PR Status
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
env:
|
||||
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
|
||||
run: |
|
||||
if [[ "$STATUS" == "none" ]]; then
|
||||
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY
|
||||
66
.github/workflows/translations.yml
vendored
66
.github/workflows/translations.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Update translation source
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# runs once per month
|
||||
- cron: '0 0 1 * *'
|
||||
|
||||
jobs:
|
||||
translations:
|
||||
# Do not run the scheduled workflow on forks
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Install lupdate
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends qttools5-dev-tools
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Update cockatrice translations
|
||||
shell: bash
|
||||
run: |
|
||||
shopt -s globstar # globstar is needed for recursive **
|
||||
lupdate -version
|
||||
echo "reading the following source files:"
|
||||
# note: there are three strings to translate in common right now
|
||||
echo {cockatrice,common}/**/*.{cpp,h}
|
||||
echo "$(echo {cockatrice,common}/**/*.{cpp,h} | wc -w) files total"
|
||||
lupdate {cockatrice,common}/**/*.{cpp,h} -ts cockatrice/translations/cockatrice_en@source.ts
|
||||
|
||||
- name: Update oracle translations
|
||||
shell: bash
|
||||
run: |
|
||||
shopt -s globstar # globstar is needed for recursive **
|
||||
lupdate -version
|
||||
echo "reading the following source files:"
|
||||
echo oracle/**/*.{cpp,h}
|
||||
echo "$(echo oracle/**/*.{cpp,h} | wc -w) files total"
|
||||
lupdate oracle/**/*.{cpp,h} -ts oracle/translations/oracle_en@source.ts
|
||||
|
||||
- name: Check for updates
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
set +e # do not fail, just save the exit state
|
||||
git diff --exit-code
|
||||
echo "::set-output name=deploy::$?"
|
||||
|
||||
- name: Commit changes
|
||||
if: steps.check.outputs.deploy == '1'
|
||||
shell: bash
|
||||
working-directory: ${{env.OUTPUT_PATH}}
|
||||
run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git add cockatrice/translations/cockatrice_en@source.ts oracle/translations/oracle_en@source.ts
|
||||
git commit -m "Automated translation update ( $GITHUB_SHA )"
|
||||
git push
|
||||
deploy_commit=$(git rev-parse HEAD)
|
||||
echo "Created commit: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$deploy_commit"
|
||||
7
.github/workflows/web-build.yml
vendored
7
.github/workflows/web-build.yml
vendored
@@ -28,15 +28,15 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node_version:
|
||||
- 12
|
||||
- 16
|
||||
- lts/*
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{matrix.node_version}}
|
||||
cache: 'npm'
|
||||
@@ -50,4 +50,3 @@ jobs:
|
||||
|
||||
- name: Test app
|
||||
run: npm run test
|
||||
|
||||
|
||||
4
.github/workflows/web-lint.yml
vendored
4
.github/workflows/web-lint.yml
vendored
@@ -17,10 +17,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'webclient/package-lock.json'
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,8 +6,10 @@ mysql.cnf
|
||||
.DS_Store
|
||||
.idea/
|
||||
*.aps
|
||||
cmake-build-debug*
|
||||
cmake-build*
|
||||
preferences
|
||||
compile_commands.json
|
||||
.vs/
|
||||
.vscode/
|
||||
.cache
|
||||
.gdb_history
|
||||
|
||||
27
.tx/config
27
.tx/config
@@ -1,13 +1,26 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
host = https://app.transifex.com
|
||||
|
||||
[cockatrice.cockatrice-translations-cockatrice-en-source-ts--master]
|
||||
[o:cockatrice:p:cockatrice:r:cockatrice-cockatrice-en-source-ts--master]
|
||||
resource_name = Cockatrice
|
||||
source_lang = en
|
||||
source_file = cockatrice/cockatrice_en@source.ts
|
||||
file_filter = cockatrice/translations/cockatrice_<lang>.ts
|
||||
source_file = cockatrice/translations/cockatrice_en@source.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 10
|
||||
|
||||
[cockatrice.oracle-translations-oracle-en-source-ts--master]
|
||||
[o:cockatrice:p:cockatrice:r:oracle-oracle-en-source-ts--master]
|
||||
resource_name = Oracle
|
||||
source_lang = en
|
||||
source_file = oracle/oracle_en@source.ts
|
||||
file_filter = oracle/translations/oracle_<lang>.ts
|
||||
source_file = oracle/translations/oracle_en@source.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 10
|
||||
|
||||
[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master]
|
||||
resource_name = Webclient
|
||||
source_lang = en
|
||||
source_file = webclient/src/i18n-default.json
|
||||
file_filter = webclient/public/locales/<lang>/translation.json
|
||||
type = KEYVALUEJSON
|
||||
minimum_perc = 10
|
||||
|
||||
@@ -74,16 +74,16 @@ endif()
|
||||
|
||||
# A project name is needed for CPack
|
||||
# Version can be overriden by git tags, see cmake/getversion.cmake
|
||||
project("Cockatrice" VERSION 2.8.1)
|
||||
project("Cockatrice" VERSION 2.10.0)
|
||||
|
||||
# Set release name if not provided via env/cmake var
|
||||
if(NOT DEFINED GIT_TAG_RELEASENAME)
|
||||
set(GIT_TAG_RELEASENAME "Prismatic Bridge")
|
||||
set(GIT_TAG_RELEASENAME "Rings of the Wild")
|
||||
endif()
|
||||
|
||||
# Use c++17 for all targets
|
||||
# Use c++20 for all targets
|
||||
set(CMAKE_CXX_STANDARD
|
||||
17
|
||||
20
|
||||
CACHE STRING "C++ ISO Standard"
|
||||
)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
@@ -140,10 +140,14 @@ endif()
|
||||
|
||||
# Define proper compilation flags
|
||||
if(MSVC)
|
||||
# Visual Studio: Maximum optimization, disable warning C4251, establish C++17 compatibility
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251 /Zc:__cplusplus /std:c++17 /permissive-")
|
||||
# Generate complete debugging information
|
||||
#set(CMAKE_CXX_FLAGS_DEBUG "/Zi")
|
||||
# Visual Studio: Disable Warning C4251, C++20 compatibility, Multi-threaded Builds, Warn Detection, Unwind Semantics
|
||||
set(CMAKE_CXX_FLAGS "/wd4251 /Zc:__cplusplus /std:c++20 /permissive- /W4 /MP /EHsc")
|
||||
# Visual Studio: Maximum Optimization, Multi-threaded DLL
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD")
|
||||
# Visual Studio: No Optimization, Multi-threaded Debug DLL, Debug Symbols
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/Od /MDd /Zi")
|
||||
|
||||
add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
|
||||
elseif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
# linux/gcc, bsd/gcc, windows/mingw
|
||||
include(CheckCXXCompilerFlag)
|
||||
@@ -156,7 +160,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++20")
|
||||
endif()
|
||||
|
||||
set(ADDITIONAL_DEBUG_FLAGS
|
||||
@@ -179,7 +183,7 @@ else()
|
||||
# other: osx/llvm, bsd/llvm
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
|
||||
if(WARNING_AS_ERROR)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Werror")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Werror -Wno-unused-parameter")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
|
||||
endif()
|
||||
@@ -216,16 +220,26 @@ include(FindQtRuntime)
|
||||
set(CMAKE_AUTOMOC TRUE)
|
||||
|
||||
# Find other needed libraries
|
||||
find_package(Protobuf REQUIRED)
|
||||
if(NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
|
||||
find_package(Protobuf CONFIG)
|
||||
if(NOT Protobuf_FOUND)
|
||||
find_package(Protobuf REQUIRED)
|
||||
endif()
|
||||
|
||||
if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0" AND NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
|
||||
message(FATAL_ERROR "No protoc command found!")
|
||||
else()
|
||||
message(STATUS "Found Protobuf ${Protobuf_VERSION} at: ${Protobuf_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
#Find OpenSSL
|
||||
if(WIN32)
|
||||
find_package(Win32SslRuntime)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if(OPENSSL_FOUND)
|
||||
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()
|
||||
|
||||
#Find VCredist
|
||||
@@ -252,13 +266,15 @@ if(UNIX)
|
||||
set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME}")
|
||||
set(CPACK_SYSTEM_NAME "OSX")
|
||||
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice/resources/appicon.icns")
|
||||
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeDMGSetup.script")
|
||||
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dmgBackground.tif")
|
||||
else()
|
||||
# linux
|
||||
if(CPACK_GENERATOR STREQUAL "RPM")
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
|
||||
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
|
||||
if(Qt6_FOUND)
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
|
||||
elseif(Qt5_FOUND)
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt5-qttools, qt5-qtsvg, qt5-qtmultimedia")
|
||||
endif()
|
||||
@@ -280,7 +296,7 @@ if(UNIX)
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
|
||||
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
|
||||
if(Qt6_FOUND)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
|
||||
elseif(Qt5_FOUND)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
|
||||
endif()
|
||||
|
||||
18
README.md
18
README.md
@@ -5,7 +5,7 @@
|
||||
<p align='center'>
|
||||
<a href="#cockatrice"><b>Cockatrice</b></a> <b>|</b>
|
||||
<a href="#download-">Download</a> <b>|</b>
|
||||
<a href="#get-involved--">Get Involved</a> <b>|</b>
|
||||
<a href="#get-involved-">Get Involved</a> <b>|</b>
|
||||
<a href="#community-resources">Community</a> <b>|</b>
|
||||
<a href="#translations-">Translations</a> <b>|</b>
|
||||
<a href="#build--">Build</a> <b>|</b>
|
||||
@@ -18,7 +18,7 @@
|
||||
<br><pre>
|
||||
<b>To get started, ⇢ [view our webpage](https://cockatrice.github.io/)</b><br>
|
||||
<b>To get support or suggest changes ⇢ [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))</b>
|
||||
<b>To help with development, see how to [get involved](#get-involved--)</b>
|
||||
<b>To help with development, see how to [get involved](#get-involved-)</b>
|
||||
</pre><br>
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ Downloads are available for full releases and the current beta version in develo
|
||||
- To be a Cockatrice Beta Tester, use this version. Find more information [here](https://github.com/Cockatrice/Cockatrice/wiki/Release-Channels)!
|
||||
|
||||
|
||||
# Get Involved [](https://discord.gg/3Z9yzmA) [](https://gitter.im/Cockatrice/Cockatrice)
|
||||
# Get Involved [](https://discord.gg/3Z9yzmA)
|
||||
|
||||
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project or fellow users of the app. The Cockatrice developers are also available on [Gitter](https://gitter.im/Cockatrice/Cockatrice). Come here to talk about the application, features, or just to hang out.<br>
|
||||
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project, contributors or fellow users of the app. Come here to talk about the application, features, or just to hang out.<br>
|
||||
For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.<br>
|
||||
|
||||
To contribute code to the project, please review [the guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md).
|
||||
@@ -52,8 +52,6 @@ We maintain two tags for contributors to find issues to work on:
|
||||
|
||||
For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code review for changes they submit.
|
||||
|
||||
Read the long-term project **roadmap** to see planned edits and milestones [here](https://docs.google.com/document/d/1Ewe5uSaRE2nR2pNPMaGmP6gVZdqgFbBgwSscGqIr4W0/edit).
|
||||
|
||||
We try to be responsive to new issues. We'll provide advice on how best to implement a feature; alternately, we can show you where the codebase is doing something similar before you get too far along.
|
||||
|
||||
Cockatrice uses the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
|
||||
@@ -67,13 +65,9 @@ Cockatrice uses the [Google Developer Documentation Style Guide](https://develop
|
||||
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
|
||||
|
||||
|
||||
# Translations [](https://www.transifex.com/projects/p/cockatrice/)
|
||||
# Translations [](https://transifex.com/cockatrice/cockatrice/)
|
||||
|
||||
Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Oracle to your language or just edit single wordings right from within your browser by visiting our [Transifex project page](https://www.transifex.com/projects/p/cockatrice/).<br>
|
||||
|
||||
| Cockatrice | Oracle |
|
||||
|:-:|:-:|
|
||||
| [](https://www.transifex.com/projects/p/cockatrice/) | [](https://www.transifex.com/projects/p/cockatrice/) |
|
||||
Cockatrice uses Transifex for translations. You can help us bring Cockatrice, Oracle and Webatrice to your language or just adjust single wordings right from within your browser by visiting our [Transifex project page](https://transifex.com/cockatrice/cockatrice/).<br>
|
||||
|
||||
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!<br>
|
||||
|
||||
|
||||
55
cmake/CMakeDMGSetup.script
Normal file
55
cmake/CMakeDMGSetup.script
Normal file
@@ -0,0 +1,55 @@
|
||||
on run argv
|
||||
set image_name to item 1 of argv
|
||||
|
||||
tell application "Finder"
|
||||
tell disk image_name
|
||||
|
||||
-- wait for the image to finish mounting
|
||||
set open_attempts to 0
|
||||
repeat while open_attempts < 4
|
||||
try
|
||||
open
|
||||
delay 1
|
||||
set open_attempts to 5
|
||||
close
|
||||
on error errStr number errorNumber
|
||||
set open_attempts to open_attempts + 1
|
||||
delay 10
|
||||
end try
|
||||
end repeat
|
||||
delay 5
|
||||
|
||||
-- open the image the first time and save a DS_Store with just
|
||||
-- background and icon setup
|
||||
open
|
||||
set current view of container window to icon view
|
||||
set theViewOptions to the icon view options of container window
|
||||
set background picture of theViewOptions to file ".background:background.tif"
|
||||
set arrangement of theViewOptions to not arranged
|
||||
set icon size of theViewOptions to 128
|
||||
delay 5
|
||||
close
|
||||
|
||||
-- next setup the position of the app and Applications symlink
|
||||
-- plus hide all the window decoration
|
||||
open
|
||||
update without registering applications
|
||||
tell container window
|
||||
set sidebar width to 0
|
||||
set statusbar visible to false
|
||||
set toolbar visible to false
|
||||
set the bounds to { 400, 100, 1400, 922 }
|
||||
set position of item "Cockatrice.app" to { 139, 214 }
|
||||
set position of item "Oracle.app" to { 139, 414 }
|
||||
set position of item "Servatrice.app" to { 139, 614 }
|
||||
set position of item "dbconverter.app" to { 1400, 1400 }
|
||||
set position of item "Applications" to { 861, 414 }
|
||||
end tell
|
||||
update without registering applications
|
||||
delay 5
|
||||
close
|
||||
|
||||
end tell
|
||||
delay 1
|
||||
end tell
|
||||
end run
|
||||
@@ -21,8 +21,8 @@ if(WITH_CLIENT)
|
||||
Network
|
||||
PrintSupport
|
||||
Svg
|
||||
Widgets
|
||||
WebSockets
|
||||
Widgets
|
||||
)
|
||||
endif()
|
||||
if(WITH_ORACLE)
|
||||
@@ -41,27 +41,24 @@ set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_CO
|
||||
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
|
||||
|
||||
if(NOT FORCE_USE_QT5)
|
||||
# Core5Compat is Qt6 Only, Linguist is now a component in Qt6 instead of an external package
|
||||
# Linguist is now a component in Qt6 instead of an external package
|
||||
find_package(
|
||||
Qt6 6.2.3
|
||||
COMPONENTS Core5Compat ${REQUIRED_QT_COMPONENTS}
|
||||
OPTIONAL_COMPONENTS Linguist
|
||||
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
|
||||
QUIET HINTS ${Qt6_DIR}
|
||||
)
|
||||
endif()
|
||||
if(Qt6_FOUND)
|
||||
set(COCKATRICE_QT_VERSION_NAME Qt6)
|
||||
|
||||
if(Qt6LinguistTools_FOUND)
|
||||
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
|
||||
if(QT6_LRELEASE_INDEX EQUAL -1)
|
||||
message(WARNING "Qt6 lrelease not found.")
|
||||
endif()
|
||||
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
|
||||
if(QT6_LRELEASE_INDEX EQUAL -1)
|
||||
message(WARNING "Qt6 lrelease not found.")
|
||||
endif()
|
||||
|
||||
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
|
||||
if(QT6_LUPDATE_INDEX EQUAL -1)
|
||||
message(WARNING "Qt6 lupdate not found.")
|
||||
endif()
|
||||
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
|
||||
if(QT6_LUPDATE_INDEX EQUAL -1)
|
||||
message(WARNING "Qt6 lupdate not found.")
|
||||
endif()
|
||||
else()
|
||||
find_package(
|
||||
@@ -116,10 +113,5 @@ string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_Q
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}")
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}")
|
||||
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}")
|
||||
if(Qt6_FOUND)
|
||||
list(APPEND SERVATRICE_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Core5Compat)
|
||||
list(APPEND COCKATRICE_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Core5Compat)
|
||||
list(APPEND ORACLE_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Core5Compat)
|
||||
endif()
|
||||
|
||||
message(STATUS "Found Qt ${${COCKATRICE_QT_VERSION_NAME}_VERSION} at: ${${COCKATRICE_QT_VERSION_NAME}_DIR}")
|
||||
|
||||
@@ -9,7 +9,8 @@ if(WIN32)
|
||||
set(REDIST_ARCH x86)
|
||||
endif()
|
||||
|
||||
set(REDIST_FILE vcredist_${REDIST_ARCH}.exe)
|
||||
# VS 2017 uses vcredist_ARCH.exe, VS 2022 uses vc_redist.ARCH.exe
|
||||
set(REDIST_FILE_NAMES vcredist_${REDIST_ARCH}.exe vcredist.${REDIST_ARCH}.exe vc_redist.${REDIST_ARCH}.exe)
|
||||
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
|
||||
include(InstallRequiredSystemLibraries)
|
||||
@@ -22,10 +23,13 @@ if(WIN32)
|
||||
get_filename_component(_path ${_path} DIRECTORY)
|
||||
get_filename_component(_path ${_path}/../../ ABSOLUTE)
|
||||
|
||||
if(EXISTS "${_path}/${REDIST_FILE}") # VS 2017
|
||||
set(VCREDISTRUNTIME_FOUND "YES")
|
||||
set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE})
|
||||
endif()
|
||||
foreach(redist_file ${REDIST_FILE_NAMES})
|
||||
if(EXISTS "${_path}/${redist_file}")
|
||||
set(VCREDISTRUNTIME_FOUND "YES")
|
||||
set(VCREDISTRUNTIME_FILE ${_path}/${redist_file})
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(VCREDISTRUNTIME_FOUND)
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# Find the OpenSSL runtime libraries (.dll) for Windows that
|
||||
# will be needed by Qt in order to access https urls.
|
||||
if(NOT DEFINED WIN32 OR NOT ${WIN32})
|
||||
message(STATUS "Non-Windows device trying to execute FindWin32SslRuntime, skipping")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64")
|
||||
message(STATUS "Looking for OpenSSL for ${CMAKE_GENERATOR_PLATFORM}")
|
||||
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
|
||||
set(_OPENSSL_ROOT_PATHS
|
||||
"$ENV{VCPKG_PACKAGES_DIR}/x64-windows/bin" "C:/OpenSSL-Win64/bin" "C:/OpenSSL-Win64"
|
||||
"C:/Tools/vcpkg/installed/x64-windows/bin" "${_programfiles}/OpenSSL-Win64"
|
||||
"D:/a/Cockatrice/Qt/Tools/OpenSSL/Win_x64/bin"
|
||||
)
|
||||
unset(_programfiles)
|
||||
elseif("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win32")
|
||||
message(STATUS "Looking for OpenSSL for ${CMAKE_GENERATOR_PLATFORM}")
|
||||
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
|
||||
set(_OPENSSL_ROOT_PATHS
|
||||
"$ENV{VCPKG_PACKAGES_DIR}/x86-windows/bin"
|
||||
"C:/OpenSSL-Win32/bin"
|
||||
"C:/OpenSSL-Win32"
|
||||
"C:/OpenSSL"
|
||||
"C:/Tools/vcpkg/installed/x86-windows/bin"
|
||||
"${_programfiles}/OpenSSL"
|
||||
"${_programfiles}/OpenSSL-Win32"
|
||||
"D:/a/Cockatrice/Qt/Tools/OpenSSL/Win_x86/bin"
|
||||
)
|
||||
unset(_programfiles)
|
||||
endif()
|
||||
|
||||
message(STATUS "Looking for OpenSSL @ $ENV{CMAKE_GENERATOR_PLATFORM} in ${_OPENSSL_ROOT_PATHS}")
|
||||
if("$ENV{CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64")
|
||||
find_file(
|
||||
WIN32SSLRUNTIME_LIBEAY
|
||||
NAMES libcrypto-1_1-x64.dll libcrypto.dll
|
||||
PATHS ${_OPENSSL_ROOT_PATHS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
find_file(
|
||||
WIN32SSLRUNTIME_SSLEAY
|
||||
NAMES libssl-1_1-x64.dll libssl.dll
|
||||
PATHS ${_OPENSSL_ROOT_PATHS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
elseif("$ENV{CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win32")
|
||||
find_file(
|
||||
WIN32SSLRUNTIME_LIBEAY
|
||||
NAMES libcrypto-1_1.dll libcrypto.dll
|
||||
PATHS ${_OPENSSL_ROOT_PATHS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
find_file(
|
||||
WIN32SSLRUNTIME_SSLEAY
|
||||
NAMES libssl-1_1.dll libssl.dll
|
||||
PATHS ${_OPENSSL_ROOT_PATHS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32SSLRUNTIME_LIBEAY AND WIN32SSLRUNTIME_SSLEAY)
|
||||
set(WIN32SSLRUNTIME_LIBRARIES "${WIN32SSLRUNTIME_LIBEAY}" "${WIN32SSLRUNTIME_SSLEAY}")
|
||||
set(WIN32SSLRUNTIME_FOUND "YES")
|
||||
message(STATUS "Found OpenSSL ${WIN32SSLRUNTIME_LIBRARIES}")
|
||||
else()
|
||||
set(WIN32SSLRUNTIME_FOUND "NO")
|
||||
message(
|
||||
WARNING
|
||||
"Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime."
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(WIN32SSLRUNTIME_LIBEAY WIN32SSLRUNTIME_SSLEAY)
|
||||
@@ -235,6 +235,13 @@ ${If} $PortableMode = 0
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
|
||||
; Enable Windows User-Mode Dumps
|
||||
; https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpFolder" "%LOCALAPPDATA%\CrashDumps\Cockatrice"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpCount" "5"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpType" "2"
|
||||
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayIcon" "$INSTDIR\cockatrice.exe"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayName" "Cockatrice"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayVersion" "@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@"
|
||||
@@ -248,20 +255,20 @@ ${If} $PortableMode = 0
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMajor" "@CPACK_PACKAGE_VERSION_MAJOR@"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMinor" "@CPACK_PACKAGE_VERSION_MINOR@"
|
||||
|
||||
IfFileExists "$INSTDIR\vcredist_x86.exe" VcRedist86Exists PastVcRedist86Check
|
||||
IfFileExists "$INSTDIR\vc_redist.x86.exe" VcRedist86Exists PastVcRedist86Check
|
||||
VcRedist86Exists:
|
||||
ExecWait '"$INSTDIR\vcredist_x86.exe" /passive /norestart'
|
||||
ExecWait '"$INSTDIR\vc_redist.x86.exe" /passive /norestart'
|
||||
DetailPrint "Wait to ensure unlock of vc_redist file after installation..."
|
||||
Sleep 3000
|
||||
Delete "$INSTDIR\vcredist_x86.exe"
|
||||
Delete "$INSTDIR\vc_redist.x86.exe"
|
||||
PastVcRedist86Check:
|
||||
|
||||
IfFileExists "$INSTDIR\vcredist_x64.exe" VcRedist64Exists PastVcRedist64Check
|
||||
IfFileExists "$INSTDIR\vc_redist.x64.exe" VcRedist64Exists PastVcRedist64Check
|
||||
VcRedist64Exists:
|
||||
ExecWait '"$INSTDIR\vcredist_x64.exe" /passive /norestart'
|
||||
ExecWait '"$INSTDIR\vc_redist.x64.exe" /passive /norestart'
|
||||
DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
|
||||
Sleep 3000
|
||||
Delete "$INSTDIR\vcredist_x64.exe"
|
||||
Delete "$INSTDIR\vc_redist.x64.exe"
|
||||
PastVcRedist64Check:
|
||||
|
||||
${Else}
|
||||
|
||||
BIN
cmake/dmgBackground.tif
Normal file
BIN
cmake/dmgBackground.tif
Normal file
Binary file not shown.
@@ -19,7 +19,7 @@ function(get_commit_id)
|
||||
PARENT_SCOPE
|
||||
)
|
||||
set(PROJECT_VERSION_LABEL
|
||||
"custom(${GIT_COM_ID})"
|
||||
"custom-${GIT_COM_ID}"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
endfunction()
|
||||
|
||||
@@ -5,127 +5,159 @@
|
||||
project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
|
||||
|
||||
set(cockatrice_SOURCES
|
||||
src/abstractcarddragitem.cpp
|
||||
src/abstractcarditem.cpp
|
||||
src/abstractclient.cpp
|
||||
src/abstractcounter.cpp
|
||||
src/abstractgraphicsitem.cpp
|
||||
src/arrowitem.cpp
|
||||
src/arrowtarget.cpp
|
||||
src/carddatabase.cpp
|
||||
src/carddatabasemodel.cpp
|
||||
src/carddbparser/carddatabaseparser.cpp
|
||||
src/carddbparser/cockatricexml3.cpp
|
||||
src/carddbparser/cockatricexml4.cpp
|
||||
src/carddragitem.cpp
|
||||
src/cardfilter.cpp
|
||||
src/cardframe.cpp
|
||||
src/cardinfopicture.cpp
|
||||
src/cardinfotext.cpp
|
||||
src/cardinfowidget.cpp
|
||||
src/carditem.cpp
|
||||
src/cardlist.cpp
|
||||
src/cardzone.cpp
|
||||
src/chatview/chatview.cpp
|
||||
src/counter_general.cpp
|
||||
src/customlineedit.cpp
|
||||
src/deck_loader.cpp
|
||||
src/decklistmodel.cpp
|
||||
src/deckstats_interface.cpp
|
||||
src/deckview.cpp
|
||||
src/dlg_connect.cpp
|
||||
src/dlg_create_token.cpp
|
||||
src/dlg_creategame.cpp
|
||||
src/dlg_edit_avatar.cpp
|
||||
src/dlg_edit_password.cpp
|
||||
src/dlg_edit_tokens.cpp
|
||||
src/dlg_edit_user.cpp
|
||||
src/dlg_filter_games.cpp
|
||||
src/dlg_forgotpasswordchallenge.cpp
|
||||
src/dlg_forgotpasswordrequest.cpp
|
||||
src/dlg_forgotpasswordreset.cpp
|
||||
src/dlg_load_deck_from_clipboard.cpp
|
||||
src/dlg_load_remote_deck.cpp
|
||||
src/dlg_manage_sets.cpp
|
||||
src/dlg_register.cpp
|
||||
src/dlg_settings.cpp
|
||||
src/dlg_tip_of_the_day.cpp
|
||||
src/dlg_update.cpp
|
||||
src/dlg_viewlog.cpp
|
||||
src/filter_string.cpp
|
||||
src/filterbuilder.cpp
|
||||
src/filtertree.cpp
|
||||
src/filtertreemodel.cpp
|
||||
src/gamescene.cpp
|
||||
src/gameselector.cpp
|
||||
src/gamesmodel.cpp
|
||||
src/gameview.cpp
|
||||
src/gettextwithmax.cpp
|
||||
src/handcounter.cpp
|
||||
src/handle_public_servers.cpp
|
||||
src/handzone.cpp
|
||||
src/keysignals.cpp
|
||||
src/lineeditcompleter.cpp
|
||||
src/localclient.cpp
|
||||
src/localserver.cpp
|
||||
src/localserverinterface.cpp
|
||||
src/logger.cpp
|
||||
src/game/cards/abstract_card_drag_item.cpp
|
||||
src/game/cards/abstract_card_item.cpp
|
||||
src/client/game_logic/abstract_client.cpp
|
||||
src/game/board/abstract_counter.cpp
|
||||
src/game/board/abstract_graphics_item.cpp
|
||||
src/game/board/arrow_item.cpp
|
||||
src/game/board/arrow_target.cpp
|
||||
src/game/cards/card_database.cpp
|
||||
src/game/cards/card_database_manager.cpp
|
||||
src/game/cards/card_database_model.cpp
|
||||
src/game/cards/card_database_parser/card_database_parser.cpp
|
||||
src/game/cards/card_database_parser/cockatrice_xml_3.cpp
|
||||
src/game/cards/card_database_parser/cockatrice_xml_4.cpp
|
||||
src/game/cards/card_drag_item.cpp
|
||||
src/game/filters/filter_card.cpp
|
||||
src/client/ui/widgets/cards/card_info_frame_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_picture_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_text_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_display_widget.cpp
|
||||
src/client/ui/widgets/cards/card_size_widget.cpp
|
||||
src/game/cards/card_item.cpp
|
||||
src/game/cards/card_list.cpp
|
||||
src/game/zones/card_zone.cpp
|
||||
src/server/chat_view/chat_view.cpp
|
||||
src/game/board/counter_general.cpp
|
||||
src/deck/custom_line_edit.cpp
|
||||
src/deck/deck_loader.cpp
|
||||
src/deck/deck_list_model.cpp
|
||||
src/deck/deck_stats_interface.cpp
|
||||
src/deck/deck_view.cpp
|
||||
src/dialogs/dlg_connect.cpp
|
||||
src/dialogs/dlg_create_token.cpp
|
||||
src/dialogs/dlg_create_game.cpp
|
||||
src/dialogs/dlg_edit_avatar.cpp
|
||||
src/dialogs/dlg_edit_password.cpp
|
||||
src/dialogs/dlg_edit_tokens.cpp
|
||||
src/dialogs/dlg_edit_user.cpp
|
||||
src/dialogs/dlg_filter_games.cpp
|
||||
src/dialogs/dlg_forgot_password_challenge.cpp
|
||||
src/dialogs/dlg_forgot_password_request.cpp
|
||||
src/dialogs/dlg_forgot_password_reset.cpp
|
||||
src/dialogs/dlg_load_deck_from_clipboard.cpp
|
||||
src/dialogs/dlg_load_remote_deck.cpp
|
||||
src/dialogs/dlg_manage_sets.cpp
|
||||
src/dialogs/dlg_move_top_cards_until.cpp
|
||||
src/dialogs/dlg_register.cpp
|
||||
src/dialogs/dlg_roll_dice.cpp
|
||||
src/dialogs/dlg_settings.cpp
|
||||
src/dialogs/dlg_tip_of_the_day.cpp
|
||||
src/dialogs/dlg_update.cpp
|
||||
src/dialogs/dlg_view_log.cpp
|
||||
src/game/filters/filter_string.cpp
|
||||
src/game/filters/filter_builder.cpp
|
||||
src/game/filters/filter_tree.cpp
|
||||
src/game/filters/filter_tree_model.cpp
|
||||
src/client/ui/layouts/flow_layout.cpp
|
||||
src/client/ui/layouts/horizontal_flow_layout.cpp
|
||||
src/client/ui/layouts/vertical_flow_layout.cpp
|
||||
src/client/ui/widgets/general/layout_containers/flow_widget.cpp
|
||||
src/game/game_scene.cpp
|
||||
src/game/game_selector.cpp
|
||||
src/game/games_model.cpp
|
||||
src/game/game_view.cpp
|
||||
src/client/get_text_with_max.cpp
|
||||
src/game/hand_counter.cpp
|
||||
src/server/handle_public_servers.cpp
|
||||
src/game/zones/hand_zone.cpp
|
||||
src/client/game_logic/key_signals.cpp
|
||||
src/client/ui/line_edit_completer.cpp
|
||||
src/server/local_client.cpp
|
||||
src/server/local_server.cpp
|
||||
src/server/local_server_interface.cpp
|
||||
src/utility/logger.cpp
|
||||
src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
|
||||
src/client/ui/widgets/general/display/labeled_input.cpp
|
||||
src/client/ui/widgets/general/display/dynamic_font_size_label.cpp
|
||||
src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||
src/client/ui/widgets/general/display/shadow_background_label.cpp
|
||||
src/main.cpp
|
||||
src/messagelogwidget.cpp
|
||||
src/pending_command.cpp
|
||||
src/phase.cpp
|
||||
src/phasestoolbar.cpp
|
||||
src/pictureloader.cpp
|
||||
src/pilezone.cpp
|
||||
src/pixmapgenerator.cpp
|
||||
src/player.cpp
|
||||
src/playerlistwidget.cpp
|
||||
src/playertarget.cpp
|
||||
src/releasechannel.cpp
|
||||
src/remoteclient.cpp
|
||||
src/remotedecklist_treewidget.cpp
|
||||
src/remotereplaylist_treewidget.cpp
|
||||
src/replay_timeline_widget.cpp
|
||||
src/selectzone.cpp
|
||||
src/sequenceEdit/sequenceedit.cpp
|
||||
src/setsmodel.cpp
|
||||
src/settings/carddatabasesettings.cpp
|
||||
src/settings/downloadsettings.cpp
|
||||
src/settings/gamefilterssettings.cpp
|
||||
src/settings/layoutssettings.cpp
|
||||
src/settings/messagesettings.cpp
|
||||
src/settings/serverssettings.cpp
|
||||
src/settings/settingsmanager.cpp
|
||||
src/settingscache.cpp
|
||||
src/shortcutssettings.cpp
|
||||
src/soundengine.cpp
|
||||
src/spoilerbackgroundupdater.cpp
|
||||
src/stackzone.cpp
|
||||
src/tab.cpp
|
||||
src/tab_account.cpp
|
||||
src/tab_admin.cpp
|
||||
src/tab_deck_editor.cpp
|
||||
src/tab_deck_storage.cpp
|
||||
src/tab_game.cpp
|
||||
src/tab_logs.cpp
|
||||
src/tab_message.cpp
|
||||
src/tab_replays.cpp
|
||||
src/tab_room.cpp
|
||||
src/tab_server.cpp
|
||||
src/tab_supervisor.cpp
|
||||
src/tablezone.cpp
|
||||
src/tappedout_interface.cpp
|
||||
src/thememanager.cpp
|
||||
src/tip_of_the_day.cpp
|
||||
src/translatecountername.cpp
|
||||
src/update_downloader.cpp
|
||||
src/user_context_menu.cpp
|
||||
src/userconnection_information.cpp
|
||||
src/userinfobox.cpp
|
||||
src/userlist.cpp
|
||||
src/window_main.cpp
|
||||
src/zoneviewwidget.cpp
|
||||
src/zoneviewzone.cpp
|
||||
src/server/message_log_widget.cpp
|
||||
src/client/ui/layouts/overlap_layout.cpp
|
||||
src/client/ui/widgets/general/layout_containers/overlap_widget.cpp
|
||||
src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp
|
||||
src/server/pending_command.cpp
|
||||
src/game/phase.cpp
|
||||
src/client/ui/phases_toolbar.cpp
|
||||
src/client/ui/picture_loader.cpp
|
||||
src/game/zones/pile_zone.cpp
|
||||
src/client/ui/pixel_map_generator.cpp
|
||||
src/game/player/player.cpp
|
||||
src/game/player/player_list_widget.cpp
|
||||
src/game/player/player_target.cpp
|
||||
src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/card_amount_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_view_options_toolbar_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_view_options_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
|
||||
src/client/network/release_channel.cpp
|
||||
src/client/network/client_update_checker.cpp
|
||||
src/server/remote/remote_client.cpp
|
||||
src/server/remote/remote_decklist_tree_widget.cpp
|
||||
src/server/remote/remote_replay_list_tree_widget.cpp
|
||||
src/client/network/replay_timeline_widget.cpp
|
||||
src/game/zones/select_zone.cpp
|
||||
src/utility/sequence_edit.cpp
|
||||
src/client/network/sets_model.cpp
|
||||
src/settings/card_database_settings.cpp
|
||||
src/settings/download_settings.cpp
|
||||
src/settings/game_filters_settings.cpp
|
||||
src/settings/layouts_settings.cpp
|
||||
src/settings/message_settings.cpp
|
||||
src/settings/recents_settings.cpp
|
||||
src/settings/servers_settings.cpp
|
||||
src/settings/settings_manager.cpp
|
||||
src/settings/cache_settings.cpp
|
||||
src/settings/shortcuts_settings.cpp
|
||||
src/settings/shortcut_treeview.cpp
|
||||
src/settings/card_override_settings.cpp
|
||||
src/client/sound_engine.cpp
|
||||
src/client/network/spoiler_background_updater.cpp
|
||||
src/game/zones/stack_zone.cpp
|
||||
src/client/tabs/tab.cpp
|
||||
src/client/tabs/tab_account.cpp
|
||||
src/client/tabs/tab_admin.cpp
|
||||
src/client/tabs/tab_deck_editor.cpp
|
||||
src/client/tabs/tab_deck_storage.cpp
|
||||
src/client/tabs/tab_game.cpp
|
||||
src/client/tabs/tab_logs.cpp
|
||||
src/client/tabs/tab_message.cpp
|
||||
src/client/tabs/tab_replays.cpp
|
||||
src/client/tabs/tab_room.cpp
|
||||
src/client/tabs/tab_server.cpp
|
||||
src/client/tabs/tab_supervisor.cpp
|
||||
src/game/zones/table_zone.cpp
|
||||
src/client/tapped_out_interface.cpp
|
||||
src/client/ui/theme_manager.cpp
|
||||
src/client/ui/tip_of_the_day.cpp
|
||||
src/client/translate_counter_name.cpp
|
||||
src/client/update_downloader.cpp
|
||||
src/server/user/user_context_menu.cpp
|
||||
src/server/user/user_info_connection.cpp
|
||||
src/server/user/user_info_box.cpp
|
||||
src/server/user/user_list.cpp
|
||||
src/client/ui/window_main.cpp
|
||||
src/game/zones/view_zone_widget.cpp
|
||||
src/game/zones/view_zone.cpp
|
||||
${VERSION_STRING_CPP}
|
||||
)
|
||||
|
||||
@@ -311,15 +343,9 @@ if(WIN32)
|
||||
PATTERN "audio/qtaudio_wasapi.dll"
|
||||
PATTERN "audio/qtaudio_windows.dll"
|
||||
PATTERN "iconengines/qsvgicon.dll"
|
||||
PATTERN "imageformats/qgif.dll"
|
||||
PATTERN "imageformats/qicns.dll"
|
||||
PATTERN "imageformats/qico.dll"
|
||||
PATTERN "imageformats/qjpeg.dll"
|
||||
PATTERN "imageformats/qsvg.dll"
|
||||
PATTERN "imageformats/qtga.dll"
|
||||
PATTERN "imageformats/qtiff.dll"
|
||||
PATTERN "imageformats/qwbmp.dll"
|
||||
PATTERN "imageformats/qwebp.dll"
|
||||
PATTERN "imageformats/*.dll"
|
||||
PATTERN "mediaservice/dsengine.dll"
|
||||
PATTERN "mediaservice/wmfengine.dll"
|
||||
PATTERN "platforms/qdirect2d.dll"
|
||||
PATTERN "platforms/qminimal.dll"
|
||||
PATTERN "platforms/qoffscreen.dll"
|
||||
@@ -355,12 +381,12 @@ Data = Resources\")
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
if(WIN32SSLRUNTIME_FOUND)
|
||||
install(FILES ${WIN32SSLRUNTIME_LIBRARIES} DESTINATION ./)
|
||||
if(OPENSSL_FOUND)
|
||||
install(FILES ${OPENSSL_INCLUDE_DIRS} DESTINATION ./)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(Qt6LinguistTools_FOUND)
|
||||
if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
|
||||
#Qt6 Translations happen after the executable is built up
|
||||
if(UPDATE_TRANSLATIONS)
|
||||
qt6_add_translations(
|
||||
|
||||
@@ -351,10 +351,10 @@
|
||||
<file>resources/tips/images/cockatrice_wiki.png</file>
|
||||
<file>resources/tips/images/coin_flip.png</file>
|
||||
<file>resources/tips/images/counter_expression.png</file>
|
||||
<file>resources/tips/images/discord.png</file>
|
||||
<file>resources/tips/images/face_down.png</file>
|
||||
<file>resources/tips/images/filter_games.png</file>
|
||||
<file>resources/tips/images/github_logo.png</file>
|
||||
<file>resources/tips/images/gitter.png</file>
|
||||
<file>resources/tips/images/setpt.png</file>
|
||||
<file>resources/tips/images/shortcuts.png</file>
|
||||
<file>resources/tips/images/themes.png</file>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
## Syntax Help
|
||||
## Search Syntax Help
|
||||
-----
|
||||
The search bar recognizes a set of special commands similar to some other card databases. Here is a list with examples. Each entry can be clicked to test the query and has a small explanation. Note that all searches are case insensitive.
|
||||
The search bar recognizes a set of special commands similar to some other card databases.<br>
|
||||
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
|
||||
<dl>
|
||||
<dt>Name:</dt>
|
||||
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
|
||||
@@ -46,8 +47,7 @@ The search bar recognizes a set of special commands similar to some other card d
|
||||
|
||||
<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>[e:lea,leb](#e:lea,leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
|
||||
<dd><a href="#e:lea,leb -(e:lea e:leb)">e:lea,leb -(e:lea e:leb)</a> <small>(Cards that appear in Alpha or Beta but not in both editions)</small></dd>
|
||||
<dd>[e:lea or e:leb](#e:lea or e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
|
||||
|
||||
<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>
|
||||
|
||||
BIN
cockatrice/resources/tips/images/discord.png
Normal file
BIN
cockatrice/resources/tips/images/discord.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -7,9 +7,9 @@
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Suggesting New Tips</title>
|
||||
<text>You can suggest new Tips of the Day by reaching out to the development team on <a href="https://gitter.im/cockatrice/cockatrice">Gitter</a>!</text>
|
||||
<image>gitter.png</image>
|
||||
<date>2018-03-01</date>
|
||||
<text>You can suggest new Tips of the Day by reaching out to the development team on <a href="https://discord.gg/3Z9yzmA">Discord</a>!</text>
|
||||
<image>discord.png</image>
|
||||
<date>2023-10-18</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Reporting Bugs</title>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "cardinfopicture.h"
|
||||
|
||||
#include "carditem.h"
|
||||
#include "main.h"
|
||||
#include "pictureloader.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QWidget>
|
||||
|
||||
CardInfoPicture::CardInfoPicture(QWidget *parent) : QWidget(parent), info(nullptr), pixmapDirty(true)
|
||||
{
|
||||
setMinimumHeight(100);
|
||||
}
|
||||
|
||||
void CardInfoPicture::setCard(CardInfoPtr card)
|
||||
{
|
||||
if (info) {
|
||||
disconnect(info.data(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
info = card;
|
||||
|
||||
if (info) {
|
||||
connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap()));
|
||||
}
|
||||
|
||||
updatePixmap();
|
||||
}
|
||||
|
||||
void CardInfoPicture::resizeEvent(QResizeEvent *)
|
||||
{
|
||||
updatePixmap();
|
||||
}
|
||||
|
||||
void CardInfoPicture::updatePixmap()
|
||||
{
|
||||
pixmapDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void CardInfoPicture::loadPixmap()
|
||||
{
|
||||
if (info)
|
||||
PictureLoader::getPixmap(resizedPixmap, info, size());
|
||||
else
|
||||
PictureLoader::getCardBackPixmap(resizedPixmap, size());
|
||||
}
|
||||
|
||||
void CardInfoPicture::paintEvent(QPaintEvent *)
|
||||
{
|
||||
if (width() == 0 || height() == 0)
|
||||
return;
|
||||
|
||||
if (pixmapDirty)
|
||||
loadPixmap();
|
||||
|
||||
QPainter painter(this);
|
||||
style()->drawItemPixmap(&painter, rect(), Qt::AlignHCenter, resizedPixmap);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef CARDINFOPICTURE_H
|
||||
#define CARDINFOPICTURE_H
|
||||
|
||||
#include "carddatabase.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class AbstractCardItem;
|
||||
|
||||
class CardInfoPicture : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CardInfoPtr info;
|
||||
QPixmap resizedPixmap;
|
||||
bool pixmapDirty;
|
||||
|
||||
public:
|
||||
CardInfoPicture(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
void paintEvent(QPaintEvent *);
|
||||
void loadPixmap();
|
||||
public slots:
|
||||
void setCard(CardInfoPtr card);
|
||||
void updatePixmap();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "cardlist.h"
|
||||
|
||||
#include "carddatabase.h"
|
||||
#include "carditem.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), contentsKnown(_contentsKnown)
|
||||
{
|
||||
}
|
||||
|
||||
CardItem *CardList::findCard(const int id, const bool remove, int *position)
|
||||
{
|
||||
if (!contentsKnown) {
|
||||
if (empty())
|
||||
return 0;
|
||||
CardItem *temp = at(0);
|
||||
if (remove)
|
||||
removeAt(0);
|
||||
if (position)
|
||||
*position = id;
|
||||
return temp;
|
||||
} else
|
||||
for (int i = 0; i < size(); i++) {
|
||||
CardItem *temp = at(i);
|
||||
if (temp->getId() == id) {
|
||||
if (remove)
|
||||
removeAt(i);
|
||||
if (position)
|
||||
*position = i;
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class CardList::compareFunctor
|
||||
{
|
||||
private:
|
||||
int flags;
|
||||
|
||||
public:
|
||||
explicit compareFunctor(int _flags) : flags(_flags)
|
||||
{
|
||||
}
|
||||
inline bool operator()(CardItem *a, CardItem *b) const
|
||||
{
|
||||
if (flags & SortByType) {
|
||||
QString t1 = a->getInfo() ? a->getInfo()->getMainCardType() : QString();
|
||||
QString t2 = b->getInfo() ? b->getInfo()->getMainCardType() : QString();
|
||||
if ((t1 == t2) && (flags & SortByName))
|
||||
return a->getName() < b->getName();
|
||||
return t1 < t2;
|
||||
} else
|
||||
return a->getName() < b->getName();
|
||||
}
|
||||
};
|
||||
|
||||
void CardList::sort(int flags)
|
||||
{
|
||||
compareFunctor cf(flags);
|
||||
std::sort(begin(), end(), cf);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef CARDLIST_H
|
||||
#define CARDLIST_H
|
||||
|
||||
#include <QList>
|
||||
|
||||
class CardItem;
|
||||
|
||||
class CardList : public QList<CardItem *>
|
||||
{
|
||||
private:
|
||||
class compareFunctor;
|
||||
|
||||
protected:
|
||||
bool contentsKnown;
|
||||
|
||||
public:
|
||||
enum SortFlags
|
||||
{
|
||||
SortByName = 1,
|
||||
SortByType = 2
|
||||
};
|
||||
CardList(bool _contentsKnown);
|
||||
CardItem *findCard(const int id, const bool remove, int *position = NULL);
|
||||
bool getContentsKnown() const
|
||||
{
|
||||
return contentsKnown;
|
||||
}
|
||||
void sort(int flags = SortByName);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "abstractclient.h"
|
||||
#include "abstract_client.h"
|
||||
|
||||
#include "client_metatypes.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "featureset.h"
|
||||
#include "get_pb_extension.h"
|
||||
#include "pb/commands.pb.h"
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "pb/event_user_left.pb.h"
|
||||
#include "pb/event_user_message.pb.h"
|
||||
#include "pb/server_message.pb.h"
|
||||
#include "pending_command.h"
|
||||
|
||||
#include <google/protobuf/descriptor.h>
|
||||
|
||||
@@ -53,7 +52,7 @@ AbstractClient::AbstractClient(QObject *parent)
|
||||
FeatureSet features;
|
||||
features.initalizeFeatureList(clientFeatures);
|
||||
|
||||
connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *)));
|
||||
connect(this, &AbstractClient::sigQueuePendingCommand, this, &AbstractClient::queuePendingCommand);
|
||||
}
|
||||
|
||||
AbstractClient::~AbstractClient()
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "keysignals.h"
|
||||
#include "key_signals.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "gettextwithmax.h"
|
||||
#include "get_text_with_max.h"
|
||||
|
||||
QString getTextWithMax(QWidget *parent,
|
||||
const QString &title,
|
||||
@@ -2,7 +2,7 @@
|
||||
#ifndef GETTEXTWITHMAX_H
|
||||
#define GETTEXTWITHMAX_H
|
||||
|
||||
#include "stringsizes.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
|
||||
35
cockatrice/src/client/network/client_update_checker.cpp
Normal file
35
cockatrice/src/client/network/client_update_checker.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "client_update_checker.h"
|
||||
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "release_channel.h"
|
||||
|
||||
ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ClientUpdateChecker::check()
|
||||
{
|
||||
auto releaseChannel = SettingsCache::instance().getUpdateReleaseChannel();
|
||||
|
||||
finishedCheckConnection =
|
||||
connect(releaseChannel, &ReleaseChannel::finishedCheck, this, &ClientUpdateChecker::actFinishedCheck);
|
||||
errorConnection = connect(releaseChannel, &ReleaseChannel::error, this, &ClientUpdateChecker::actError);
|
||||
|
||||
releaseChannel->checkForUpdates();
|
||||
}
|
||||
|
||||
void ClientUpdateChecker::actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release)
|
||||
{
|
||||
disconnect(finishedCheckConnection);
|
||||
disconnect(errorConnection);
|
||||
|
||||
emit finishedCheck(needToUpdate, isCompatible, release);
|
||||
}
|
||||
|
||||
void ClientUpdateChecker::actError(const QString &errorString)
|
||||
{
|
||||
disconnect(finishedCheckConnection);
|
||||
disconnect(errorConnection);
|
||||
|
||||
emit error(errorString);
|
||||
}
|
||||
45
cockatrice/src/client/network/client_update_checker.h
Normal file
45
cockatrice/src/client/network/client_update_checker.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef CLIENT_UPDATE_CHECKER_H
|
||||
#define CLIENT_UPDATE_CHECKER_H
|
||||
#include <QObject>
|
||||
|
||||
class Release;
|
||||
|
||||
/**
|
||||
* We use a singleton instance of UpdateChannel, which can cause interference and feedback loops when multiple objects
|
||||
* connect to it.
|
||||
*
|
||||
* This class encapsulates the usage of that UpdateChannel to ensure that the check only happens once per connection and
|
||||
* the connection is destroyed after it's been used.
|
||||
*/
|
||||
class ClientUpdateChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QMetaObject::Connection finishedCheckConnection;
|
||||
QMetaObject::Connection errorConnection;
|
||||
|
||||
void actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release);
|
||||
void actError(const QString &errorString);
|
||||
|
||||
public:
|
||||
explicit ClientUpdateChecker(QObject *parent = nullptr);
|
||||
/**
|
||||
* Actually performs the check, using the currently selected update channel in the settings.
|
||||
* Any resulting signals will only be sent once.
|
||||
* This method should only be called ONCE per instance.
|
||||
*/
|
||||
void check();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* Forwarded from UpdateChannel::finishedCheck
|
||||
*/
|
||||
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
|
||||
|
||||
/**
|
||||
* Forwarded from UpdateChannel::error
|
||||
*/
|
||||
void error(const QString &errorString);
|
||||
};
|
||||
|
||||
#endif // CLIENT_UPDATE_CHECKER_H
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "releasechannel.h"
|
||||
#include "release_channel.h"
|
||||
|
||||
#include "version_string.h"
|
||||
|
||||
@@ -21,11 +21,8 @@
|
||||
|
||||
#define GIT_SHORT_HASH_LEN 7
|
||||
|
||||
int ReleaseChannel::sharedIndex = 0;
|
||||
|
||||
ReleaseChannel::ReleaseChannel() : netMan(new QNetworkAccessManager(this)), response(nullptr), lastRelease(nullptr)
|
||||
{
|
||||
index = sharedIndex++;
|
||||
}
|
||||
|
||||
ReleaseChannel::~ReleaseChannel()
|
||||
@@ -38,11 +35,11 @@ void ReleaseChannel::checkForUpdates()
|
||||
QString releaseChannelUrl = getReleaseChannelUrl();
|
||||
qDebug() << "Searching for updates on the channel: " << releaseChannelUrl;
|
||||
response = netMan->get(QNetworkRequest(releaseChannelUrl));
|
||||
connect(response, SIGNAL(finished()), this, SLOT(releaseListFinished()));
|
||||
connect(response, &QNetworkReply::finished, this, &ReleaseChannel::releaseListFinished);
|
||||
}
|
||||
|
||||
// Different release channel checking functions for different operating systems
|
||||
#if defined(Q_OS_OSX)
|
||||
#if defined(Q_OS_MACOS)
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
{
|
||||
static QRegularExpression version_regex("macOS-(\\d+)\\.(\\d+)");
|
||||
@@ -62,9 +59,14 @@ bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
{
|
||||
#if Q_PROCESSOR_WORDSIZE == 4
|
||||
return fileName.contains("win32");
|
||||
return fileName.contains("32bit");
|
||||
#elif Q_PROCESSOR_WORDSIZE == 8
|
||||
return fileName.contains("win64");
|
||||
const QString &version = QSysInfo::productVersion();
|
||||
if (version.startsWith("7") || version.startsWith("8")) {
|
||||
return fileName.contains("Win7");
|
||||
} else {
|
||||
return fileName.contains("Win10");
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@@ -107,7 +109,7 @@ void StableReleaseChannel::releaseListFinished()
|
||||
QVariantMap resultMap = jsonResponse.toVariant().toMap();
|
||||
if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") &&
|
||||
resultMap.contains("published_at"))) {
|
||||
qWarning() << "Invalid received from the release update server.";
|
||||
qWarning() << "Invalid received from the release update server:" << resultMap;
|
||||
emit error(tr("Invalid reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
@@ -153,7 +155,7 @@ void StableReleaseChannel::releaseListFinished()
|
||||
QString url = QString(STABLETAG_URL) + tagName;
|
||||
qDebug() << "Searching for commit hash corresponding to stable channel tag: " << tagName;
|
||||
response = netMan->get(QNetworkRequest(url));
|
||||
connect(response, SIGNAL(finished()), this, SLOT(tagListFinished()));
|
||||
connect(response, &QNetworkReply::finished, this, &StableReleaseChannel::tagListFinished);
|
||||
}
|
||||
|
||||
void StableReleaseChannel::tagListFinished()
|
||||
@@ -255,7 +257,7 @@ void BetaReleaseChannel::releaseListFinished()
|
||||
|
||||
qDebug() << "Searching for a corresponding file on the beta channel: " << betaBuildDownloadUrl;
|
||||
response = netMan->get(QNetworkRequest(betaBuildDownloadUrl));
|
||||
connect(response, SIGNAL(finished()), this, SLOT(fileListFinished()));
|
||||
connect(response, &QNetworkReply::finished, this, &BetaReleaseChannel::fileListFinished);
|
||||
}
|
||||
|
||||
void BetaReleaseChannel::fileListFinished()
|
||||
@@ -82,9 +82,6 @@ public:
|
||||
~ReleaseChannel() override;
|
||||
|
||||
protected:
|
||||
// shared by all instances
|
||||
static int sharedIndex;
|
||||
int index;
|
||||
QNetworkAccessManager *netMan;
|
||||
QNetworkReply *response;
|
||||
Release *lastRelease;
|
||||
@@ -94,10 +91,6 @@ protected:
|
||||
virtual QString getReleaseChannelUrl() const = 0;
|
||||
|
||||
public:
|
||||
int getIndex() const
|
||||
{
|
||||
return index;
|
||||
}
|
||||
Release *getLastRelease()
|
||||
{
|
||||
return lastRelease;
|
||||
193
cockatrice/src/client/network/replay_timeline_widget.cpp
Normal file
193
cockatrice/src/client/network/replay_timeline_widget.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include "replay_timeline_widget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPalette>
|
||||
#include <QTimer>
|
||||
|
||||
ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
|
||||
: QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentVisualTime(0), currentProcessedTime(0),
|
||||
currentEvent(0)
|
||||
{
|
||||
replayTimer = new QTimer(this);
|
||||
connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout);
|
||||
|
||||
rewindBufferingTimer = new QTimer(this);
|
||||
rewindBufferingTimer->setSingleShot(true);
|
||||
connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayTimelineWidget::processRewind);
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::setTimeline(const QList<int> &_replayTimeline)
|
||||
{
|
||||
replayTimeline = _replayTimeline;
|
||||
histogram.clear();
|
||||
int binEndTime = BIN_LENGTH - 1;
|
||||
int binValue = 0;
|
||||
for (int i : replayTimeline) {
|
||||
if (i > binEndTime) {
|
||||
histogram.append(binValue);
|
||||
if (binValue > maxBinValue)
|
||||
maxBinValue = binValue;
|
||||
while (i > binEndTime + BIN_LENGTH) {
|
||||
histogram.append(0);
|
||||
binEndTime += BIN_LENGTH;
|
||||
}
|
||||
binValue = 1;
|
||||
binEndTime += BIN_LENGTH;
|
||||
} else
|
||||
++binValue;
|
||||
}
|
||||
histogram.append(binValue);
|
||||
if (!replayTimeline.isEmpty())
|
||||
maxTime = replayTimeline.last();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.drawRect(0, 0, width() - 1, height() - 1);
|
||||
|
||||
qreal binWidth = (qreal)width() / histogram.size();
|
||||
QPainterPath path;
|
||||
path.moveTo(0, height() - 1);
|
||||
for (int i = 0; i < histogram.size(); ++i)
|
||||
path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue));
|
||||
path.lineTo(width() - 1, height() - 1);
|
||||
path.lineTo(0, height() - 1);
|
||||
painter.fillPath(path, Qt::black);
|
||||
|
||||
const QColor barColor = QColor::fromHsv(120, 255, 255, 100);
|
||||
quint64 w = (quint64)(width() - 1) * (quint64)currentVisualTime / maxTime;
|
||||
painter.fillRect(0, 0, static_cast<int>(w), height() - 1, barColor);
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->position().x() / width());
|
||||
#else
|
||||
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->x() / width());
|
||||
#endif
|
||||
// don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering
|
||||
skipToTime(newTime, false);
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering)
|
||||
{
|
||||
// check boundary conditions
|
||||
if (newTime < 0) {
|
||||
newTime = 0;
|
||||
}
|
||||
if (newTime > maxTime) {
|
||||
newTime = maxTime;
|
||||
}
|
||||
|
||||
newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval
|
||||
|
||||
const bool isBackwardsSkip = newTime < currentProcessedTime;
|
||||
currentVisualTime = newTime;
|
||||
|
||||
if (isBackwardsSkip) {
|
||||
handleBackwardsSkip(doRewindBuffering);
|
||||
} else {
|
||||
processNewEvents(FORWARD_SKIP);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
/// @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind
|
||||
/// is processed at the end. When false, the backwards skip will always cause an immediate rewind
|
||||
void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering)
|
||||
{
|
||||
if (doRewindBuffering) {
|
||||
// We use a one-shot timer to implement the rewind buffering.
|
||||
// The rewind only happens once the timer runs out.
|
||||
// If another backwards skip happens, the timer will just get reset instead of rewinding.
|
||||
rewindBufferingTimer->stop();
|
||||
rewindBufferingTimer->start(SettingsCache::instance().getRewindBufferingMs());
|
||||
} else {
|
||||
// otherwise, process the rewind immediately
|
||||
processRewind();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::processRewind()
|
||||
{
|
||||
// stop any queued-up rewinds
|
||||
rewindBufferingTimer->stop();
|
||||
|
||||
// process the rewind
|
||||
currentEvent = 0;
|
||||
emit rewound();
|
||||
processNewEvents(BACKWARD_SKIP);
|
||||
}
|
||||
|
||||
QSize ReplayTimelineWidget::sizeHint() const
|
||||
{
|
||||
return {-1, 50};
|
||||
}
|
||||
|
||||
QSize ReplayTimelineWidget::minimumSizeHint() const
|
||||
{
|
||||
return {400, 50};
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::replayTimerTimeout()
|
||||
{
|
||||
currentVisualTime += TIMER_INTERVAL_MS;
|
||||
|
||||
processNewEvents(NORMAL_PLAYBACK);
|
||||
|
||||
if (!(currentVisualTime % 1000))
|
||||
update();
|
||||
}
|
||||
|
||||
/// Processes all unprocessed events up to the current time.
|
||||
void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode)
|
||||
{
|
||||
currentProcessedTime = currentVisualTime;
|
||||
|
||||
while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) {
|
||||
Player::EventProcessingOptions options;
|
||||
|
||||
// backwards skip => always skip reveal windows
|
||||
// forwards skip => skip reveal windows that don't happen within a big skip of the target
|
||||
if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS)
|
||||
options |= Player::EventProcessingOption::SKIP_REVEAL_WINDOW;
|
||||
|
||||
// backwards skip => always skip tap animation
|
||||
if (playbackMode == BACKWARD_SKIP)
|
||||
options |= Player::EventProcessingOption::SKIP_TAP_ANIMATION;
|
||||
|
||||
emit processNextEvent(options);
|
||||
++currentEvent;
|
||||
}
|
||||
if (currentEvent == replayTimeline.size()) {
|
||||
emit replayFinished();
|
||||
replayTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor)
|
||||
{
|
||||
timeScaleFactor = _timeScaleFactor;
|
||||
replayTimer->setInterval(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::startReplay()
|
||||
{
|
||||
replayTimer->start(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::stopReplay()
|
||||
{
|
||||
replayTimer->stop();
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::skipByAmount(int amount)
|
||||
{
|
||||
skipToTime(currentVisualTime + amount, amount < 0);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef REPLAY_TIMELINE_WIDGET
|
||||
#define REPLAY_TIMELINE_WIDGET
|
||||
|
||||
#include "../../game/player/player.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMouseEvent>
|
||||
#include <QWidget>
|
||||
@@ -12,23 +14,44 @@ class ReplayTimelineWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void processNextEvent();
|
||||
void processNextEvent(Player::EventProcessingOptions options);
|
||||
void replayFinished();
|
||||
void rewound();
|
||||
|
||||
private:
|
||||
enum PlaybackMode
|
||||
{
|
||||
NORMAL_PLAYBACK,
|
||||
FORWARD_SKIP,
|
||||
BACKWARD_SKIP
|
||||
};
|
||||
|
||||
static constexpr int TIMER_INTERVAL_MS = 200;
|
||||
static constexpr int BIN_LENGTH = 5000;
|
||||
|
||||
QTimer *replayTimer;
|
||||
QTimer *rewindBufferingTimer;
|
||||
QList<int> replayTimeline;
|
||||
QList<int> histogram;
|
||||
static const int binLength;
|
||||
int maxBinValue, maxTime;
|
||||
qreal timeScaleFactor;
|
||||
int currentTime;
|
||||
int currentVisualTime; // time currently displayed by the timeline
|
||||
int currentProcessedTime; // time that events are currently processed up to. Could differ from visual time due to
|
||||
// rewind buffering
|
||||
int currentEvent;
|
||||
|
||||
void skipToTime(int newTime, bool doRewindBuffering);
|
||||
void handleBackwardsSkip(bool doRewindBuffering);
|
||||
void processRewind();
|
||||
void processNewEvents(PlaybackMode playbackMode);
|
||||
private slots:
|
||||
void replayTimerTimeout();
|
||||
|
||||
public:
|
||||
static constexpr int SMALL_SKIP_MS = 1000;
|
||||
static constexpr int BIG_SKIP_MS = 10000;
|
||||
static constexpr qreal FAST_FORWARD_SCALE_FACTOR = 10.0;
|
||||
|
||||
explicit ReplayTimelineWidget(QWidget *parent = nullptr);
|
||||
void setTimeline(const QList<int> &_replayTimeline);
|
||||
QSize sizeHint() const override;
|
||||
@@ -41,6 +64,7 @@ public:
|
||||
public slots:
|
||||
void startReplay();
|
||||
void stopReplay();
|
||||
void skipByAmount(int amount); // use a negative amount to skip backwards
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "setsmodel.h"
|
||||
#include "sets_model.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
@@ -195,6 +195,13 @@ void SetsModel::swapRows(int oldRow, int newRow)
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::restoreOriginalOrder()
|
||||
{
|
||||
int numRows = rowCount();
|
||||
sets.defaultSort();
|
||||
emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
QMultiMap<QString, CardSetPtr> setMap;
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef SETSMODEL_H
|
||||
#define SETSMODEL_H
|
||||
|
||||
#include "carddatabase.h"
|
||||
#include "../../game/cards/card_database.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QMimeData>
|
||||
@@ -49,7 +49,8 @@ public:
|
||||
LongNameCol,
|
||||
ShortNameCol,
|
||||
SetTypeCol,
|
||||
ReleaseDateCol
|
||||
ReleaseDateCol,
|
||||
PriorityCol
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
@@ -80,6 +81,7 @@ public:
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
|
||||
void save(CardDatabase *db);
|
||||
void restore(CardDatabase *db);
|
||||
void restoreOriginalOrder();
|
||||
};
|
||||
|
||||
class SetsDisplayModel : public QSortFilterProxyModel
|
||||
@@ -1,9 +1,10 @@
|
||||
#include "spoilerbackgroundupdater.h"
|
||||
#include "spoiler_background_updater.h"
|
||||
|
||||
#include "carddatabase.h"
|
||||
#include "main.h"
|
||||
#include "settingscache.h"
|
||||
#include "window_main.h"
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../../game/cards/card_database_manager.h"
|
||||
#include "../../main.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../ui/window_main.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCryptographicHash>
|
||||
@@ -44,10 +45,10 @@ void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
|
||||
|
||||
if (saveResults) {
|
||||
// This will write out to the file (used for spoiler.xml)
|
||||
connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile()));
|
||||
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile);
|
||||
} else {
|
||||
// This will check the status (used to see if we're in spoiler season or not)
|
||||
connect(reply, SIGNAL(finished()), this, SLOT(actCheckIfSpoilerSeasonEnabled()));
|
||||
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
||||
|
||||
// Data written, so reload the card database
|
||||
qDebug() << "Spoiler Service Data Written";
|
||||
const auto reloadOk = QtConcurrent::run([] { db->loadCardDatabases(); });
|
||||
const auto reloadOk = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
|
||||
|
||||
// If the user has notifications enabled, let them know
|
||||
// when the database was last updated
|
||||
@@ -172,7 +173,11 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
||||
timeStamp.chop(6); // Remove " (UTC)"
|
||||
|
||||
auto utcTime = QLocale().toDateTime(timeStamp, "ddd, MMM dd yyyy, hh:mm:ss");
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
utcTime.setTimeZone(QTimeZone::UTC);
|
||||
#else
|
||||
utcTime.setTimeSpec(Qt::UTC);
|
||||
#endif
|
||||
|
||||
QString localTime = utcTime.toLocalTime().toString("MMM d, hh:mm");
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
#include "soundengine.h"
|
||||
#include "sound_engine.h"
|
||||
|
||||
#include "settingscache.h"
|
||||
#include "../settings/cache_settings.h"
|
||||
|
||||
#include <QAudioOutput>
|
||||
#include <QDir>
|
||||
#include <QMediaPlayer>
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
#include <QAudioOutput>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_THEME_NAME "Default"
|
||||
#define TEST_SOUND_FILENAME "player_join"
|
||||
|
||||
SoundEngine::SoundEngine(QObject *parent) : QObject(parent), player(nullptr)
|
||||
{
|
||||
ensureThemeDirectoryExists();
|
||||
connect(&SettingsCache::instance(), SIGNAL(soundThemeChanged()), this, SLOT(themeChangedSlot()));
|
||||
connect(&SettingsCache::instance(), SIGNAL(soundEnabledChanged()), this, SLOT(soundEnabledChanged()));
|
||||
connect(&SettingsCache::instance(), &SettingsCache::soundThemeChanged, this, &SoundEngine::themeChangedSlot);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::soundEnabledChanged, this, &SoundEngine::soundEnabledChanged);
|
||||
|
||||
soundEnabledChanged();
|
||||
themeChangedSlot();
|
||||
@@ -23,7 +26,7 @@ SoundEngine::~SoundEngine()
|
||||
{
|
||||
if (player) {
|
||||
player->deleteLater();
|
||||
player = 0;
|
||||
player = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +51,7 @@ void SoundEngine::soundEnabledChanged()
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEngine::playSound(QString fileName)
|
||||
void SoundEngine::playSound(const QString &fileName)
|
||||
{
|
||||
if (!player) {
|
||||
return;
|
||||
@@ -15,9 +15,9 @@ class SoundEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SoundEngine(QObject *parent = nullptr);
|
||||
~SoundEngine();
|
||||
void playSound(QString fileName);
|
||||
explicit SoundEngine(QObject *parent = nullptr);
|
||||
~SoundEngine() override;
|
||||
void playSound(const QString &fileName);
|
||||
QStringMap &getAvailableThemes();
|
||||
|
||||
private:
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "tab.h"
|
||||
|
||||
#include "cardinfowidget.h"
|
||||
#include "../ui/widgets/cards/card_info_display_widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
@@ -18,7 +18,7 @@ void Tab::showCardInfoPopup(const QPoint &pos, const QString &cardName)
|
||||
infoPopup->deleteLater();
|
||||
}
|
||||
currentCardName = cardName;
|
||||
infoPopup = new CardInfoWidget(
|
||||
infoPopup = new CardInfoDisplayWidget(
|
||||
cardName, 0, Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint);
|
||||
infoPopup->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
class QMenu;
|
||||
class TabSupervisor;
|
||||
class CardInfoWidget;
|
||||
class CardInfoDisplayWidget;
|
||||
|
||||
class Tab : public QMainWindow
|
||||
{
|
||||
@@ -27,7 +27,7 @@ protected slots:
|
||||
private:
|
||||
QString currentCardName;
|
||||
bool contentsChanged;
|
||||
CardInfoWidget *infoPopup;
|
||||
CardInfoDisplayWidget *infoPopup;
|
||||
QList<QMenu *> tabMenus;
|
||||
|
||||
public:
|
||||
@@ -1,18 +1,18 @@
|
||||
#include "tab_account.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "customlineedit.h"
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/user/user_info_box.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "../sound_engine.h"
|
||||
#include "pb/event_add_to_list.pb.h"
|
||||
#include "pb/event_remove_from_list.pb.h"
|
||||
#include "pb/event_user_joined.pb.h"
|
||||
#include "pb/event_user_left.pb.h"
|
||||
#include "pb/response_list_users.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "soundengine.h"
|
||||
#include "stringsizes.h"
|
||||
#include "userinfobox.h"
|
||||
#include "userlist.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
@@ -30,29 +30,22 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
|
||||
userInfoBox = new UserInfoBox(client, true);
|
||||
userInfoBox->updateInfo(userInfo);
|
||||
|
||||
connect(allUsersList, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SIGNAL(openMessageDialog(const QString &, bool)));
|
||||
connect(buddyList, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SIGNAL(openMessageDialog(const QString &, bool)));
|
||||
connect(ignoreList, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SIGNAL(openMessageDialog(const QString &, bool)));
|
||||
connect(allUsersList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
|
||||
connect(buddyList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
|
||||
connect(ignoreList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
|
||||
|
||||
connect(client, SIGNAL(userJoinedEventReceived(const Event_UserJoined &)), this,
|
||||
SLOT(processUserJoinedEvent(const Event_UserJoined &)));
|
||||
connect(client, SIGNAL(userLeftEventReceived(const Event_UserLeft &)), this,
|
||||
SLOT(processUserLeftEvent(const Event_UserLeft &)));
|
||||
connect(client, SIGNAL(buddyListReceived(const QList<ServerInfo_User> &)), this,
|
||||
SLOT(buddyListReceived(const QList<ServerInfo_User> &)));
|
||||
connect(client, SIGNAL(ignoreListReceived(const QList<ServerInfo_User> &)), this,
|
||||
SLOT(ignoreListReceived(const QList<ServerInfo_User> &)));
|
||||
connect(client, SIGNAL(addToListEventReceived(const Event_AddToList &)), this,
|
||||
SLOT(processAddToListEvent(const Event_AddToList &)));
|
||||
connect(client, SIGNAL(removeFromListEventReceived(const Event_RemoveFromList &)), this,
|
||||
SLOT(processRemoveFromListEvent(const Event_RemoveFromList &)));
|
||||
connect(client, &AbstractClient::userJoinedEventReceived, this, &TabUserLists::processUserJoinedEvent);
|
||||
connect(client, &AbstractClient::userLeftEventReceived, this, &TabUserLists::processUserLeftEvent);
|
||||
connect(client, &AbstractClient::buddyListReceived, this, &TabUserLists::buddyListReceived);
|
||||
connect(client, &AbstractClient::ignoreListReceived, this, &TabUserLists::ignoreListReceived);
|
||||
connect(client, &AbstractClient::addToListEventReceived, this, &TabUserLists::processAddToListEvent);
|
||||
connect(client, &AbstractClient::removeFromListEventReceived, this, &TabUserLists::processRemoveFromListEvent);
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers());
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(processListUsersResponse(const Response &)));
|
||||
connect(pend,
|
||||
static_cast<void (PendingCommand::*)(const Response &, const CommandContainer &, const QVariant &)>(
|
||||
&PendingCommand::finished),
|
||||
this, &TabUserLists::processListUsersResponse);
|
||||
client->sendCommand(pend);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
@@ -63,9 +56,9 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
|
||||
addBuddyEdit = new LineEditUnfocusable;
|
||||
addBuddyEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
addBuddyEdit->setPlaceholderText(tr("Add to Buddy List"));
|
||||
connect(addBuddyEdit, SIGNAL(returnPressed()), this, SLOT(addToBuddyList()));
|
||||
connect(addBuddyEdit, &LineEditUnfocusable::returnPressed, this, &TabUserLists::addToBuddyList);
|
||||
QPushButton *addBuddyButton = new QPushButton("Add");
|
||||
connect(addBuddyButton, SIGNAL(clicked()), this, SLOT(addToBuddyList()));
|
||||
connect(addBuddyButton, &QPushButton::clicked, this, &TabUserLists::addToBuddyList);
|
||||
addToBuddyList->addWidget(addBuddyEdit);
|
||||
addToBuddyList->addWidget(addBuddyButton);
|
||||
|
||||
@@ -73,9 +66,9 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
|
||||
addIgnoreEdit = new LineEditUnfocusable;
|
||||
addIgnoreEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
addIgnoreEdit->setPlaceholderText(tr("Add to Ignore List"));
|
||||
connect(addIgnoreEdit, SIGNAL(returnPressed()), this, SLOT(addToIgnoreList()));
|
||||
connect(addIgnoreEdit, &LineEditUnfocusable::returnPressed, this, &TabUserLists::addToIgnoreList);
|
||||
QPushButton *addIgnoreButton = new QPushButton("Add");
|
||||
connect(addIgnoreButton, SIGNAL(clicked()), this, SLOT(addToIgnoreList()));
|
||||
connect(addIgnoreButton, &QPushButton::clicked, this, &TabUserLists::addToIgnoreList);
|
||||
addToIgnoreList->addWidget(addIgnoreEdit);
|
||||
addToIgnoreList->addWidget(addIgnoreButton);
|
||||
|
||||
250
cockatrice/src/client/tabs/tab_admin.cpp
Normal file
250
cockatrice/src/client/tabs/tab_admin.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
#include "tab_admin.h"
|
||||
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "pb/admin_commands.pb.h"
|
||||
#include "pb/event_replay_added.pb.h"
|
||||
#include "pb/moderator_commands.pb.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
|
||||
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
QLabel *reasonLabel = new QLabel(tr("&Reason for shutdown:"));
|
||||
reasonEdit = new QLineEdit;
|
||||
reasonEdit->setMaxLength(MAX_TEXT_LENGTH);
|
||||
reasonLabel->setBuddy(reasonEdit);
|
||||
QLabel *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):"));
|
||||
minutesEdit = new QSpinBox;
|
||||
minutesLabel->setBuddy(minutesEdit);
|
||||
minutesEdit->setMinimum(0);
|
||||
minutesEdit->setValue(5);
|
||||
minutesEdit->setMaximum(999);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &ShutdownDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &ShutdownDialog::reject);
|
||||
|
||||
QGridLayout *mainLayout = new QGridLayout;
|
||||
mainLayout->addWidget(reasonLabel, 0, 0);
|
||||
mainLayout->addWidget(reasonEdit, 0, 1);
|
||||
mainLayout->addWidget(minutesLabel, 1, 0);
|
||||
mainLayout->addWidget(minutesEdit, 1, 1);
|
||||
mainLayout->addWidget(buttonBox, 2, 0, 1, 2);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(tr("Shut down server"));
|
||||
}
|
||||
|
||||
QString ShutdownDialog::getReason() const
|
||||
{
|
||||
return reasonEdit->text();
|
||||
}
|
||||
|
||||
int ShutdownDialog::getMinutes() const
|
||||
{
|
||||
return minutesEdit->value();
|
||||
}
|
||||
|
||||
TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent)
|
||||
: Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin)
|
||||
{
|
||||
updateServerMessageButton = new QPushButton;
|
||||
connect(updateServerMessageButton, &QPushButton::clicked, this, &TabAdmin::actUpdateServerMessage);
|
||||
shutdownServerButton = new QPushButton;
|
||||
connect(shutdownServerButton, &QPushButton::clicked, this, &TabAdmin::actShutdownServer);
|
||||
reloadConfigButton = new QPushButton;
|
||||
connect(reloadConfigButton, &QPushButton::clicked, this, &TabAdmin::actReloadConfig);
|
||||
|
||||
grantReplayAccessButton = new QPushButton;
|
||||
grantReplayAccessButton->setEnabled(false);
|
||||
connect(grantReplayAccessButton, &QPushButton::clicked, this, &TabAdmin::actGrantReplayAccess);
|
||||
replayIdToGrant = new QLineEdit;
|
||||
replayIdToGrant->setMaximumWidth(500);
|
||||
replayIdToGrant->setValidator(new QIntValidator(0, INT_MAX, this));
|
||||
connect(replayIdToGrant, &QLineEdit::textChanged, this,
|
||||
[=, this]() { grantReplayAccessButton->setEnabled(!replayIdToGrant->text().isEmpty()); });
|
||||
auto *grandReplayAccessLayout = new QGridLayout(this);
|
||||
grandReplayAccessLayout->addWidget(replayIdToGrant, 0, 0);
|
||||
grandReplayAccessLayout->addWidget(grantReplayAccessButton, 0, 1);
|
||||
|
||||
activateUserButton = new QPushButton;
|
||||
activateUserButton->setEnabled(false);
|
||||
connect(activateUserButton, &QPushButton::clicked, this, &TabAdmin::actForceActivateUser);
|
||||
userToActivate = new QLineEdit;
|
||||
userToActivate->setMaximumWidth(500);
|
||||
connect(userToActivate, &QLineEdit::textChanged, this,
|
||||
[=, this]() { activateUserButton->setEnabled(!userToActivate->text().isEmpty()); });
|
||||
auto *activateUserLayout = new QGridLayout(this);
|
||||
activateUserLayout->addWidget(userToActivate, 0, 0);
|
||||
activateUserLayout->addWidget(activateUserButton, 0, 1);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(updateServerMessageButton);
|
||||
vbox->addWidget(shutdownServerButton);
|
||||
vbox->addWidget(reloadConfigButton);
|
||||
vbox->addLayout(grandReplayAccessLayout);
|
||||
vbox->addLayout(activateUserLayout);
|
||||
vbox->addStretch();
|
||||
|
||||
adminGroupBox = new QGroupBox;
|
||||
adminGroupBox->setLayout(vbox);
|
||||
adminGroupBox->setEnabled(false);
|
||||
|
||||
unlockButton = new QPushButton;
|
||||
connect(unlockButton, &QPushButton::clicked, this, &TabAdmin::actUnlock);
|
||||
lockButton = new QPushButton;
|
||||
lockButton->setEnabled(false);
|
||||
connect(lockButton, &QPushButton::clicked, this, &TabAdmin::actLock);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(adminGroupBox);
|
||||
mainLayout->addWidget(unlockButton);
|
||||
mainLayout->addWidget(lockButton);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(mainLayout);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
actUnlock();
|
||||
}
|
||||
|
||||
void TabAdmin::retranslateUi()
|
||||
{
|
||||
updateServerMessageButton->setText(tr("Update server &message"));
|
||||
shutdownServerButton->setText(tr("&Shut down server"));
|
||||
reloadConfigButton->setText(tr("&Reload configuration"));
|
||||
adminGroupBox->setTitle(tr("Server administration functions"));
|
||||
|
||||
replayIdToGrant->setPlaceholderText(tr("Replay ID"));
|
||||
grantReplayAccessButton->setText(tr("Grant Replay Access"));
|
||||
|
||||
userToActivate->setPlaceholderText(tr("Username to Activate"));
|
||||
activateUserButton->setText(tr("Force Activate User"));
|
||||
|
||||
unlockButton->setText(tr("&Unlock functions"));
|
||||
lockButton->setText(tr("&Lock functions"));
|
||||
}
|
||||
|
||||
void TabAdmin::actUpdateServerMessage()
|
||||
{
|
||||
client->sendCommand(client->prepareAdminCommand(Command_UpdateServerMessage()));
|
||||
}
|
||||
|
||||
void TabAdmin::actShutdownServer()
|
||||
{
|
||||
ShutdownDialog dlg;
|
||||
if (dlg.exec()) {
|
||||
Command_ShutdownServer cmd;
|
||||
cmd.set_reason(dlg.getReason().toStdString());
|
||||
cmd.set_minutes(dlg.getMinutes());
|
||||
|
||||
client->sendCommand(AbstractClient::prepareAdminCommand(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void TabAdmin::actReloadConfig()
|
||||
{
|
||||
Command_ReloadConfig cmd;
|
||||
client->sendCommand(client->prepareAdminCommand(cmd));
|
||||
}
|
||||
|
||||
void TabAdmin::actGrantReplayAccess()
|
||||
{
|
||||
if (!replayIdToGrant) {
|
||||
return;
|
||||
}
|
||||
|
||||
Command_GrantReplayAccess cmd;
|
||||
cmd.set_replay_id(replayIdToGrant->text().toUInt());
|
||||
cmd.set_moderator_name(client->getUserName().toStdString());
|
||||
|
||||
auto *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend,
|
||||
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
|
||||
this, &TabAdmin::grantReplayAccessProcessResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabAdmin::actForceActivateUser()
|
||||
{
|
||||
if (!userToActivate) {
|
||||
return;
|
||||
}
|
||||
|
||||
Command_ForceActivateUser cmd;
|
||||
cmd.set_username_to_activate(userToActivate->text().trimmed().toStdString());
|
||||
cmd.set_moderator_name(client->getUserName().toStdString());
|
||||
|
||||
auto *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend,
|
||||
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
|
||||
this, &TabAdmin::activateUserProcessResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabAdmin::grantReplayAccessProcessResponse(const Response &response, const CommandContainer &, const QVariant &)
|
||||
{
|
||||
auto *event = new Event_ReplayAdded();
|
||||
|
||||
switch (response.response_code()) {
|
||||
case Response::RespOk:
|
||||
client->replayAddedEventReceived(*event);
|
||||
QMessageBox::information(this, tr("Success"), tr("Replay access granted"));
|
||||
break;
|
||||
case Response::RespContextError:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Replay ID invalid"));
|
||||
break;
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Internal error"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TabAdmin::activateUserProcessResponse(const Response &response, const CommandContainer &, const QVariant &)
|
||||
{
|
||||
switch (response.response_code()) {
|
||||
case Response::RespActivationAccepted:
|
||||
QMessageBox::information(this, tr("Success"), tr("User successfully activated"));
|
||||
break;
|
||||
case Response::RespNameNotFound:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Username invalid"));
|
||||
break;
|
||||
case Response::RespActivationFailed:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. User already active"));
|
||||
break;
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Internal error"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TabAdmin::actUnlock()
|
||||
{
|
||||
if (fullAdmin)
|
||||
adminGroupBox->setEnabled(true);
|
||||
lockButton->setEnabled(true);
|
||||
unlockButton->setEnabled(false);
|
||||
locked = false;
|
||||
emit adminLockChanged(false);
|
||||
}
|
||||
|
||||
void TabAdmin::actLock()
|
||||
{
|
||||
if (fullAdmin)
|
||||
adminGroupBox->setEnabled(false);
|
||||
lockButton->setEnabled(false);
|
||||
unlockButton->setEnabled(true);
|
||||
locked = true;
|
||||
emit adminLockChanged(true);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef TAB_ADMIN_H
|
||||
#define TAB_ADMIN_H
|
||||
|
||||
#include "pb/commands.pb.h"
|
||||
#include "pb/response.pb.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QDialog>
|
||||
@@ -32,15 +34,21 @@ private:
|
||||
bool locked;
|
||||
AbstractClient *client;
|
||||
bool fullAdmin;
|
||||
QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton;
|
||||
QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton, *grantReplayAccessButton,
|
||||
*activateUserButton;
|
||||
QGroupBox *adminGroupBox;
|
||||
QPushButton *unlockButton, *lockButton;
|
||||
QLineEdit *replayIdToGrant, *userToActivate;
|
||||
signals:
|
||||
void adminLockChanged(bool lock);
|
||||
private slots:
|
||||
void actUpdateServerMessage();
|
||||
void actShutdownServer();
|
||||
void actReloadConfig();
|
||||
void actGrantReplayAccess();
|
||||
void actForceActivateUser();
|
||||
void grantReplayAccessProcessResponse(const Response &resp, const CommandContainer &, const QVariant &);
|
||||
void activateUserProcessResponse(const Response &response, const CommandContainer &, const QVariant &);
|
||||
|
||||
void actUnlock();
|
||||
void actLock();
|
||||
@@ -1,23 +1,25 @@
|
||||
#include "tab_deck_editor.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "carddatabasemodel.h"
|
||||
#include "cardframe.h"
|
||||
#include "decklistmodel.h"
|
||||
#include "deckstats_interface.h"
|
||||
#include "dlg_load_deck_from_clipboard.h"
|
||||
#include "filterbuilder.h"
|
||||
#include "filtertreemodel.h"
|
||||
#include "main.h"
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../client/tapped_out_interface.h"
|
||||
#include "../../client/ui/widgets/cards/card_info_frame_widget.h"
|
||||
#include "../../deck/deck_list_model.h"
|
||||
#include "../../deck/deck_stats_interface.h"
|
||||
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
|
||||
#include "../../game/cards/card_database_manager.h"
|
||||
#include "../../game/cards/card_database_model.h"
|
||||
#include "../../game/filters/filter_builder.h"
|
||||
#include "../../game/filters/filter_tree_model.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../ui/picture_loader.h"
|
||||
#include "../ui/pixel_map_generator.h"
|
||||
#include "../ui/widgets/printing_selector/printing_selector.h"
|
||||
#include "pb/command_deck_upload.pb.h"
|
||||
#include "pb/response.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "pictureloader.h"
|
||||
#include "pixmapgenerator.h"
|
||||
#include "settingscache.h"
|
||||
#include "stringsizes.h"
|
||||
#include "tab_supervisor.h"
|
||||
#include "tappedout_interface.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
@@ -48,13 +50,6 @@
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
void SearchLineEdit::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (treeView && ((event->key() == Qt::Key_Up) || (event->key() == Qt::Key_Down)))
|
||||
QCoreApplication::sendEvent(treeView, event);
|
||||
LineEditUnfocusable::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void TabDeckEditor::createDeckDock()
|
||||
{
|
||||
deckModel = new DeckListModel(this);
|
||||
@@ -68,9 +63,13 @@ void TabDeckEditor::createDeckDock()
|
||||
deckView->sortByColumn(1, Qt::AscendingOrder);
|
||||
deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
deckView->installEventFilter(&deckViewKeySignals);
|
||||
deckView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
|
||||
SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &)));
|
||||
connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
|
||||
SLOT(updatePrintingSelectorDeckView(const QModelIndex &, const QModelIndex &)));
|
||||
connect(deckView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actSwapCard()));
|
||||
connect(deckView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(decklistCustomMenu(QPoint)));
|
||||
connect(&deckViewKeySignals, SIGNAL(onShiftS()), this, SLOT(actSwapCard()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onEnter()), this, SLOT(actIncrement()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actIncrement()));
|
||||
@@ -162,7 +161,6 @@ void TabDeckEditor::createDeckDock()
|
||||
deckDock = new QDockWidget(this);
|
||||
deckDock->setObjectName("deckDock");
|
||||
|
||||
deckDock->setMinimumSize(QSize(200, 41));
|
||||
deckDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
deckDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
|
||||
QDockWidget::DockWidgetMovable);
|
||||
@@ -177,7 +175,7 @@ void TabDeckEditor::createDeckDock()
|
||||
|
||||
void TabDeckEditor::createCardInfoDock()
|
||||
{
|
||||
cardInfo = new CardFrame();
|
||||
cardInfo = new CardInfoFrameWidget();
|
||||
cardInfo->setObjectName("cardInfo");
|
||||
auto *cardInfoFrame = new QVBoxLayout;
|
||||
cardInfoFrame->setObjectName("cardInfoFrame");
|
||||
@@ -186,7 +184,6 @@ void TabDeckEditor::createCardInfoDock()
|
||||
cardInfoDock = new QDockWidget(this);
|
||||
cardInfoDock->setObjectName("cardInfoDock");
|
||||
|
||||
cardInfoDock->setMinimumSize(QSize(200, 41));
|
||||
cardInfoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
cardInfoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
|
||||
QDockWidget::DockWidgetMovable);
|
||||
@@ -261,6 +258,32 @@ void TabDeckEditor::createFiltersDock()
|
||||
connect(filterDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
|
||||
}
|
||||
|
||||
void TabDeckEditor::createPrintingSelectorDock()
|
||||
{
|
||||
printingSelector = new PrintingSelector(this, this, deckModel, deckView);
|
||||
printingSelector->setObjectName("printingSelector");
|
||||
auto *printingSelectorFrame = new QVBoxLayout;
|
||||
printingSelectorFrame->setObjectName("printingSelectorFrame");
|
||||
printingSelectorFrame->addWidget(printingSelector);
|
||||
|
||||
printingSelectorDock = new QDockWidget(this);
|
||||
printingSelectorDock->setObjectName("printingSelectorDock");
|
||||
|
||||
printingSelectorDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
printingSelectorDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
|
||||
QDockWidget::DockWidgetMovable);
|
||||
auto *printingSelectorDockContents = new QWidget();
|
||||
printingSelectorDockContents->setObjectName("printingSelectorDockContents");
|
||||
printingSelectorDockContents->setLayout(printingSelectorFrame);
|
||||
printingSelectorDock->setWidget(printingSelectorDockContents);
|
||||
|
||||
printingSelectorDock->installEventFilter(this);
|
||||
connect(printingSelectorDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
|
||||
|
||||
addDockWidget(Qt::RightDockWidgetArea, printingSelectorDock);
|
||||
printingSelectorDock->setFloating(false);
|
||||
}
|
||||
|
||||
void TabDeckEditor::createMenus()
|
||||
{
|
||||
aNewDeck = new QAction(QString(), this);
|
||||
@@ -269,6 +292,15 @@ void TabDeckEditor::createMenus()
|
||||
aLoadDeck = new QAction(QString(), this);
|
||||
connect(aLoadDeck, SIGNAL(triggered()), this, SLOT(actLoadDeck()));
|
||||
|
||||
loadRecentDeckMenu = new QMenu(this);
|
||||
connect(&SettingsCache::instance().recents(), &RecentsSettings::recentlyOpenedDeckPathsChanged, this,
|
||||
&TabDeckEditor::updateRecentlyOpened);
|
||||
|
||||
aClearRecents = new QAction(QString(), this);
|
||||
connect(aClearRecents, &QAction::triggered, this, &TabDeckEditor::actClearRecents);
|
||||
|
||||
updateRecentlyOpened();
|
||||
|
||||
aSaveDeck = new QAction(QString(), this);
|
||||
connect(aSaveDeck, SIGNAL(triggered()), this, SLOT(actSaveDeck()));
|
||||
|
||||
@@ -319,6 +351,7 @@ void TabDeckEditor::createMenus()
|
||||
deckMenu = new QMenu(this);
|
||||
deckMenu->addAction(aNewDeck);
|
||||
deckMenu->addAction(aLoadDeck);
|
||||
deckMenu->addMenu(loadRecentDeckMenu);
|
||||
deckMenu->addAction(aSaveDeck);
|
||||
deckMenu->addAction(aSaveDeckAs);
|
||||
deckMenu->addSeparator();
|
||||
@@ -339,6 +372,7 @@ void TabDeckEditor::createMenus()
|
||||
cardInfoDockMenu = viewMenu->addMenu(QString());
|
||||
deckDockMenu = viewMenu->addMenu(QString());
|
||||
filterDockMenu = viewMenu->addMenu(QString());
|
||||
printingSelectorDockMenu = viewMenu->addMenu(QString());
|
||||
|
||||
aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
|
||||
aCardInfoDockVisible->setCheckable(true);
|
||||
@@ -361,6 +395,13 @@ void TabDeckEditor::createMenus()
|
||||
aFilterDockFloating->setCheckable(true);
|
||||
connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
|
||||
|
||||
aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString());
|
||||
aPrintingSelectorDockVisible->setCheckable(true);
|
||||
connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
|
||||
aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString());
|
||||
aPrintingSelectorDockFloating->setCheckable(true);
|
||||
connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
|
||||
|
||||
viewMenu->addSeparator();
|
||||
|
||||
aResetLayout = viewMenu->addAction(QString());
|
||||
@@ -376,7 +417,7 @@ void TabDeckEditor::createCentralFrame()
|
||||
{
|
||||
searchEdit = new SearchLineEdit;
|
||||
searchEdit->setObjectName("searchEdit");
|
||||
searchEdit->setPlaceholderText(tr("Search by card name"));
|
||||
searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
|
||||
searchEdit->setClearButtonEnabled(true);
|
||||
searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
|
||||
auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
|
||||
@@ -397,7 +438,7 @@ void TabDeckEditor::createCentralFrame()
|
||||
connect(&searchKeySignals, SIGNAL(onCtrlC()), this, SLOT(copyDatabaseCellContents()));
|
||||
connect(help, &QAction::triggered, this, &TabDeckEditor::showSearchSyntaxHelp);
|
||||
|
||||
databaseModel = new CardDatabaseModel(db, true, this);
|
||||
databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this);
|
||||
databaseModel->setObjectName("databaseModel");
|
||||
databaseDisplayModel = new CardDatabaseDisplayModel(this);
|
||||
databaseDisplayModel->setSourceModel(databaseModel);
|
||||
@@ -416,6 +457,8 @@ void TabDeckEditor::createCentralFrame()
|
||||
connect(databaseView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(databaseCustomMenu(QPoint)));
|
||||
connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
|
||||
SLOT(updateCardInfoLeft(const QModelIndex &, const QModelIndex &)));
|
||||
connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
|
||||
SLOT(updatePrintingSelectorDatabase(const QModelIndex &, const QModelIndex &)));
|
||||
connect(databaseView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actAddCard()));
|
||||
|
||||
QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState();
|
||||
@@ -455,6 +498,7 @@ void TabDeckEditor::createCentralFrame()
|
||||
centralWidget = new QWidget(this);
|
||||
centralWidget->setObjectName("centralWidget");
|
||||
centralWidget->setLayout(centralFrame);
|
||||
centralWidget->setMaximumSize(900, 5000);
|
||||
setCentralWidget(centralWidget);
|
||||
setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
|
||||
}
|
||||
@@ -465,11 +509,14 @@ void TabDeckEditor::databaseCustomMenu(QPoint point)
|
||||
const CardInfoPtr info = currentCardInfo();
|
||||
|
||||
// add to deck and sideboard options
|
||||
QAction *addToDeck, *addToSideboard;
|
||||
QAction *addToDeck, *addToSideboard, *selectPrinting;
|
||||
addToDeck = menu.addAction(tr("Add to Deck"));
|
||||
addToSideboard = menu.addAction(tr("Add to Sideboard"));
|
||||
selectPrinting = menu.addAction(tr("Select Printing"));
|
||||
|
||||
connect(addToDeck, SIGNAL(triggered()), this, SLOT(actAddCard()));
|
||||
connect(addToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard()));
|
||||
connect(selectPrinting, &QAction::triggered, this, [this, info] { this->showPrintingSelector(); });
|
||||
|
||||
// filling out the related cards submenu
|
||||
auto *relatedMenu = new QMenu(tr("Show Related cards"));
|
||||
@@ -488,36 +535,58 @@ void TabDeckEditor::databaseCustomMenu(QPoint point)
|
||||
menu.exec(databaseView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void TabDeckEditor::decklistCustomMenu(QPoint point)
|
||||
{
|
||||
QMenu menu;
|
||||
const CardInfoPtr info = cardInfo->getInfo();
|
||||
|
||||
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
|
||||
|
||||
connect(selectPrinting, &QAction::triggered, this, &TabDeckEditor::showPrintingSelector);
|
||||
|
||||
menu.exec(deckView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void TabDeckEditor::showPrintingSelector()
|
||||
{
|
||||
printingSelector->setCard(cardInfo->getInfo(), DECK_ZONE_MAIN);
|
||||
printingSelector->updateDisplay();
|
||||
aPrintingSelectorDockVisible->setChecked(true);
|
||||
printingSelectorDock->setVisible(true);
|
||||
}
|
||||
|
||||
void TabDeckEditor::restartLayout()
|
||||
{
|
||||
deckDock->setVisible(true);
|
||||
cardInfoDock->setVisible(true);
|
||||
filterDock->setVisible(true);
|
||||
printingSelectorDock->setVisible(false);
|
||||
|
||||
deckDock->setFloating(false);
|
||||
cardInfoDock->setFloating(false);
|
||||
filterDock->setFloating(false);
|
||||
printingSelectorDock->setFloating(false);
|
||||
|
||||
aCardInfoDockVisible->setChecked(true);
|
||||
aDeckDockVisible->setChecked(true);
|
||||
aFilterDockVisible->setChecked(true);
|
||||
aPrintingSelectorDockVisible->setChecked(false);
|
||||
|
||||
aCardInfoDockFloating->setChecked(false);
|
||||
aDeckDockFloating->setChecked(false);
|
||||
aFilterDockFloating->setChecked(false);
|
||||
aPrintingSelectorDockFloating->setChecked(false);
|
||||
|
||||
addDockWidget(static_cast<Qt::DockWidgetArea>(2), deckDock);
|
||||
addDockWidget(static_cast<Qt::DockWidgetArea>(2), cardInfoDock);
|
||||
addDockWidget(static_cast<Qt::DockWidgetArea>(2), filterDock);
|
||||
addDockWidget(static_cast<Qt::DockWidgetArea>(2), printingSelectorDock);
|
||||
|
||||
splitDockWidget(cardInfoDock, deckDock, Qt::Horizontal);
|
||||
splitDockWidget(cardInfoDock, printingSelectorDock, Qt::Horizontal);
|
||||
splitDockWidget(printingSelectorDock, deckDock, Qt::Horizontal);
|
||||
splitDockWidget(cardInfoDock, printingSelectorDock, Qt::Horizontal);
|
||||
splitDockWidget(cardInfoDock, filterDock, Qt::Vertical);
|
||||
|
||||
deckDock->setMinimumWidth(360);
|
||||
deckDock->setMaximumWidth(360);
|
||||
|
||||
cardInfoDock->setMinimumSize(250, 360);
|
||||
cardInfoDock->setMaximumSize(250, 360);
|
||||
QTimer::singleShot(100, this, SLOT(freeDocksSize()));
|
||||
}
|
||||
|
||||
@@ -531,6 +600,11 @@ void TabDeckEditor::freeDocksSize()
|
||||
|
||||
filterDock->setMinimumSize(100, 100);
|
||||
filterDock->setMaximumSize(5000, 5000);
|
||||
|
||||
printingSelectorDock->setMinimumSize(525, 100);
|
||||
printingSelectorDock->setMaximumSize(5000, 5000);
|
||||
|
||||
centralWidget->setMaximumSize(900, 5000);
|
||||
}
|
||||
|
||||
void TabDeckEditor::refreshShortcuts()
|
||||
@@ -573,14 +647,17 @@ void TabDeckEditor::loadLayout()
|
||||
aCardInfoDockVisible->setChecked(cardInfoDock->isVisible());
|
||||
aFilterDockVisible->setChecked(filterDock->isVisible());
|
||||
aDeckDockVisible->setChecked(deckDock->isVisible());
|
||||
aPrintingSelectorDockVisible->setChecked(printingSelectorDock->isVisible());
|
||||
|
||||
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
|
||||
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
|
||||
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
|
||||
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
|
||||
|
||||
aCardInfoDockFloating->setChecked(cardInfoDock->isFloating());
|
||||
aFilterDockFloating->setChecked(filterDock->isFloating());
|
||||
aDeckDockFloating->setChecked(deckDock->isFloating());
|
||||
aPrintingSelectorDockFloating->setChecked(printingSelectorDock->isFloating());
|
||||
|
||||
cardInfoDock->setMinimumSize(layouts.getDeckEditorCardSize());
|
||||
cardInfoDock->setMaximumSize(layouts.getDeckEditorCardSize());
|
||||
@@ -591,6 +668,9 @@ void TabDeckEditor::loadLayout()
|
||||
deckDock->setMinimumSize(layouts.getDeckEditorDeckSize());
|
||||
deckDock->setMaximumSize(layouts.getDeckEditorDeckSize());
|
||||
|
||||
printingSelectorDock->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize());
|
||||
printingSelectorDock->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize());
|
||||
|
||||
QTimer::singleShot(100, this, SLOT(freeDocksSize()));
|
||||
}
|
||||
|
||||
@@ -606,6 +686,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent)
|
||||
createDeckDock();
|
||||
createCardInfoDock();
|
||||
createFiltersDock();
|
||||
createPrintingSelectorDock();
|
||||
|
||||
this->installEventFilter(this);
|
||||
|
||||
@@ -634,6 +715,8 @@ void TabDeckEditor::retranslateUi()
|
||||
|
||||
aNewDeck->setText(tr("&New deck"));
|
||||
aLoadDeck->setText(tr("&Load deck..."));
|
||||
loadRecentDeckMenu->setTitle(tr("Load recent deck..."));
|
||||
aClearRecents->setText(tr("Clear"));
|
||||
aSaveDeck->setText(tr("&Save deck"));
|
||||
aSaveDeckAs->setText(tr("Save deck &as..."));
|
||||
aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard..."));
|
||||
@@ -665,11 +748,13 @@ void TabDeckEditor::retranslateUi()
|
||||
cardInfoDock->setWindowTitle(tr("Card Info"));
|
||||
deckDock->setWindowTitle(tr("Deck"));
|
||||
filterDock->setWindowTitle(tr("Filters"));
|
||||
printingSelectorDock->setWindowTitle(tr("Printing Selector"));
|
||||
|
||||
viewMenu->setTitle(tr("&View"));
|
||||
cardInfoDockMenu->setTitle(tr("Card Info"));
|
||||
deckDockMenu->setTitle(tr("Deck"));
|
||||
filterDockMenu->setTitle(tr("Filters"));
|
||||
printingSelectorDockMenu->setTitle(tr("Printing"));
|
||||
|
||||
aCardInfoDockVisible->setText(tr("Visible"));
|
||||
aCardInfoDockFloating->setText(tr("Floating"));
|
||||
@@ -680,6 +765,9 @@ void TabDeckEditor::retranslateUi()
|
||||
aFilterDockVisible->setText(tr("Visible"));
|
||||
aFilterDockFloating->setText(tr("Floating"));
|
||||
|
||||
aPrintingSelectorDockVisible->setText(tr("Visible"));
|
||||
aPrintingSelectorDockFloating->setText(tr("Floating"));
|
||||
|
||||
aResetLayout->setText(tr("Reset layout"));
|
||||
}
|
||||
|
||||
@@ -705,6 +793,11 @@ void TabDeckEditor::updateComments()
|
||||
setSaveStatus(true);
|
||||
}
|
||||
|
||||
void TabDeckEditor::updateCardInfo(CardInfoPtr _card)
|
||||
{
|
||||
cardInfo->setCard(_card);
|
||||
}
|
||||
|
||||
void TabDeckEditor::updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||
{
|
||||
cardInfo->setCard(current.sibling(current.row(), 0).data().toString());
|
||||
@@ -714,8 +807,47 @@ void TabDeckEditor::updateCardInfoRight(const QModelIndex ¤t, const QModel
|
||||
{
|
||||
if (!current.isValid())
|
||||
return;
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0)))
|
||||
cardInfo->setCard(current.sibling(current.row(), 1).data().toString());
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
||||
cardInfo->setCard(current.sibling(current.row(), 1).data().toString(),
|
||||
current.sibling(current.row(), 4).data().toString());
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckEditor::updatePrintingSelectorDatabase(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||
{
|
||||
const QString cardName = current.sibling(current.row(), 0).data().toString();
|
||||
const QString cardProviderID = CardDatabaseManager::getInstance()->getPreferredPrintingProviderIdForCard(cardName);
|
||||
|
||||
if (!current.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
||||
printingSelector->setCard(
|
||||
CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, cardProviderID), DECK_ZONE_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckEditor::updatePrintingSelectorDeckView(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||
{
|
||||
const QString cardName = current.sibling(current.row(), 1).data().toString();
|
||||
const QString cardProviderID = current.sibling(current.row(), 4).data().toString();
|
||||
const QModelIndex gparent = current.parent().parent();
|
||||
|
||||
if (!gparent.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
|
||||
|
||||
if (!current.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
||||
printingSelector->setCard(
|
||||
CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, cardProviderID), zoneName);
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckEditor::updateSearch(const QString &search)
|
||||
@@ -732,13 +864,25 @@ void TabDeckEditor::updateHash()
|
||||
hashLabel->setText(deckModel->getDeckList()->getDeckHash());
|
||||
}
|
||||
|
||||
void TabDeckEditor::updateRecentlyOpened()
|
||||
{
|
||||
loadRecentDeckMenu->clear();
|
||||
for (const auto &deckPath : SettingsCache::instance().recents().getRecentlyOpenedDeckPaths()) {
|
||||
QAction *aRecentlyOpenedDeck = new QAction(deckPath, this);
|
||||
loadRecentDeckMenu->addAction(aRecentlyOpenedDeck);
|
||||
connect(aRecentlyOpenedDeck, &QAction::triggered, this,
|
||||
[=, this] { actOpenRecent(aRecentlyOpenedDeck->text()); });
|
||||
}
|
||||
loadRecentDeckMenu->addSeparator();
|
||||
loadRecentDeckMenu->addAction(aClearRecents);
|
||||
aClearRecents->setEnabled(SettingsCache::instance().recents().getRecentlyOpenedDeckPaths().length() > 0);
|
||||
}
|
||||
|
||||
bool TabDeckEditor::confirmClose()
|
||||
{
|
||||
if (modified) {
|
||||
tabSupervisor->setCurrentWidget(this);
|
||||
QMessageBox::StandardButton ret = QMessageBox::warning(
|
||||
this, tr("Are you sure?"), tr("The decklist has been modified.\nDo you want to save the changes?"),
|
||||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
||||
int ret = createSaveConfirmationWindow()->exec();
|
||||
if (ret == QMessageBox::Save)
|
||||
return actSaveDeck();
|
||||
else if (ret == QMessageBox::Cancel)
|
||||
@@ -755,8 +899,16 @@ void TabDeckEditor::closeRequest()
|
||||
|
||||
void TabDeckEditor::actNewDeck()
|
||||
{
|
||||
if (!confirmClose())
|
||||
auto deckOpenLocation = confirmOpen(false);
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
deckModel->cleanList();
|
||||
nameEdit->setText(QString());
|
||||
@@ -768,8 +920,11 @@ void TabDeckEditor::actNewDeck()
|
||||
|
||||
void TabDeckEditor::actLoadDeck()
|
||||
{
|
||||
if (!confirmClose())
|
||||
auto deckOpenLocation = confirmOpen();
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFileDialog dialog(this, tr("Load deck"));
|
||||
dialog.setDirectory(SettingsCache::instance().getDeckPath());
|
||||
@@ -778,17 +933,50 @@ void TabDeckEditor::actLoadDeck()
|
||||
return;
|
||||
|
||||
QString fileName = dialog.selectedFiles().at(0);
|
||||
openDeckFromFile(fileName, deckOpenLocation);
|
||||
}
|
||||
|
||||
void TabDeckEditor::actOpenRecent(const QString &fileName)
|
||||
{
|
||||
auto deckOpenLocation = confirmOpen();
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
openDeckFromFile(fileName, deckOpenLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually opens the deck from file
|
||||
* @param fileName The path of the deck to open
|
||||
* @param deckOpenLocation Which tab to open the deck
|
||||
*/
|
||||
void TabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation)
|
||||
{
|
||||
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
|
||||
|
||||
auto *l = new DeckLoader;
|
||||
if (l->loadFromFile(fileName, fmt)) {
|
||||
setSaveStatus(false);
|
||||
setDeck(l);
|
||||
} else
|
||||
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(l);
|
||||
} else {
|
||||
setSaveStatus(false);
|
||||
setDeck(l);
|
||||
}
|
||||
} else {
|
||||
delete l;
|
||||
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
|
||||
}
|
||||
setSaveStatus(true);
|
||||
}
|
||||
|
||||
void TabDeckEditor::actClearRecents()
|
||||
{
|
||||
SettingsCache::instance().recents().clearRecentlyOpenedDeckPaths();
|
||||
}
|
||||
|
||||
void TabDeckEditor::saveDeckRemoteFinished(const Response &response)
|
||||
{
|
||||
if (response.response_code() != Response::RespOk)
|
||||
@@ -855,15 +1043,23 @@ bool TabDeckEditor::actSaveDeckAs()
|
||||
|
||||
void TabDeckEditor::actLoadDeckFromClipboard()
|
||||
{
|
||||
if (!confirmClose())
|
||||
auto deckOpenLocation = confirmOpen();
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
DlgLoadDeckFromClipboard dlg(this);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
setDeck(dlg.getDeckList());
|
||||
setModified(true);
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(dlg.getDeckList());
|
||||
} else {
|
||||
setDeck(dlg.getDeckList());
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
setSaveStatus(true);
|
||||
}
|
||||
|
||||
@@ -957,6 +1153,79 @@ void TabDeckEditor::recursiveExpand(const QModelIndex &index)
|
||||
deckView->expand(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Displays the save confirmation dialogue that is shown before loading a deck, if required. Takes into
|
||||
* account the `openDeckInNewTab` settting.
|
||||
*
|
||||
* @param openInSameTabIfBlank Open the deck in the same tab instead of a new tab if the current tab is completely
|
||||
* blank. Only relevant when the `openDeckInNewTab` setting is enabled.
|
||||
*
|
||||
* @returns An enum that indicates if and where to load the deck
|
||||
*/
|
||||
TabDeckEditor::DeckOpenLocation TabDeckEditor::confirmOpen(const bool openInSameTabIfBlank)
|
||||
{
|
||||
// handle `openDeckInNewTab` setting
|
||||
if (SettingsCache::instance().getOpenDeckInNewTab()) {
|
||||
if (openInSameTabIfBlank && isBlankNewDeck()) {
|
||||
return SAME_TAB;
|
||||
} else {
|
||||
return NEW_TAB;
|
||||
}
|
||||
}
|
||||
|
||||
// early return if deck is unmodified
|
||||
if (!modified) {
|
||||
return SAME_TAB;
|
||||
}
|
||||
|
||||
// do the save confirmation dialogue
|
||||
tabSupervisor->setCurrentWidget(this);
|
||||
|
||||
QMessageBox *msgBox = createSaveConfirmationWindow();
|
||||
QPushButton *newTabButton = msgBox->addButton(tr("Open in new tab"), QMessageBox::ApplyRole);
|
||||
|
||||
int ret = msgBox->exec();
|
||||
|
||||
// `exec()` returns an opaque value if a non-standard button was clicked.
|
||||
// Directly check if newTabButton was clicked before switching over the standard buttons.
|
||||
if (msgBox->clickedButton() == newTabButton) {
|
||||
return NEW_TAB;
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case QMessageBox::Save:
|
||||
return actSaveDeck() ? SAME_TAB : CANCELLED;
|
||||
case QMessageBox::Discard:
|
||||
return SAME_TAB;
|
||||
default:
|
||||
return CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates the base save confirmation dialogue box.
|
||||
*
|
||||
* @returns A QMessageBox that can be further modified
|
||||
*/
|
||||
QMessageBox *TabDeckEditor::createSaveConfirmationWindow()
|
||||
{
|
||||
QMessageBox *msgBox = new QMessageBox(this);
|
||||
msgBox->setIcon(QMessageBox::Warning);
|
||||
msgBox->setWindowTitle(tr("Are you sure?"));
|
||||
msgBox->setText(tr("The decklist has been modified.\nDo you want to save the changes?"));
|
||||
msgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
||||
return msgBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action.
|
||||
*/
|
||||
bool TabDeckEditor::isBlankNewDeck() const
|
||||
{
|
||||
DeckLoader *const deck = deckModel->getDeckList();
|
||||
return !modified && deck->getLastFileName().isEmpty() && deck->getLastRemoteDeckId() == -1;
|
||||
}
|
||||
|
||||
CardInfoPtr TabDeckEditor::currentCardInfo() const
|
||||
{
|
||||
const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex();
|
||||
@@ -966,7 +1235,7 @@ CardInfoPtr TabDeckEditor::currentCardInfo() const
|
||||
|
||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
|
||||
|
||||
return db->getCard(cardName);
|
||||
return CardDatabaseManager::getInstance()->getCard(cardName);
|
||||
}
|
||||
|
||||
void TabDeckEditor::addCardHelper(QString zoneName)
|
||||
@@ -977,7 +1246,7 @@ void TabDeckEditor::addCardHelper(QString zoneName)
|
||||
if (info->getIsToken())
|
||||
zoneName = DECK_ZONE_TOKENS;
|
||||
|
||||
QModelIndex newCardIndex = deckModel->addCard(info->getName(), zoneName);
|
||||
QModelIndex newCardIndex = deckModel->addPreferredPrintingCard(info->getName(), zoneName, false);
|
||||
recursiveExpand(newCardIndex);
|
||||
deckView->setCurrentIndex(newCardIndex);
|
||||
setModified(true);
|
||||
@@ -990,6 +1259,7 @@ void TabDeckEditor::actSwapCard()
|
||||
if (!currentIndex.isValid())
|
||||
return;
|
||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString();
|
||||
const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 4).data().toString();
|
||||
const QModelIndex gparent = currentIndex.parent().parent();
|
||||
|
||||
if (!gparent.isValid())
|
||||
@@ -999,8 +1269,10 @@ void TabDeckEditor::actSwapCard()
|
||||
actDecrement();
|
||||
const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
||||
|
||||
// Third argument (true) says create the card no mater what, even if not in DB
|
||||
QModelIndex newCardIndex = deckModel->addCard(cardName, otherZoneName, true);
|
||||
// Third argument (true) says create the card no matter what, even if not in DB
|
||||
QModelIndex newCardIndex = deckModel->addCard(
|
||||
cardName, CardDatabaseManager::getInstance()->getSpecificSetForCard(cardName, cardProviderID), otherZoneName,
|
||||
true);
|
||||
recursiveExpand(newCardIndex);
|
||||
|
||||
setModified(true);
|
||||
@@ -1076,8 +1348,8 @@ void TabDeckEditor::actDecrementCardFromSideboard()
|
||||
|
||||
void TabDeckEditor::copyDatabaseCellContents()
|
||||
{
|
||||
QVariant data = databaseView->selectionModel()->currentIndex().data();
|
||||
QApplication::clipboard()->setText(data.toString());
|
||||
auto _data = databaseView->selectionModel()->currentIndex().data();
|
||||
QApplication::clipboard()->setText(_data.toString());
|
||||
}
|
||||
|
||||
void TabDeckEditor::actIncrement()
|
||||
@@ -1103,7 +1375,8 @@ void TabDeckEditor::setDeck(DeckLoader *_deck)
|
||||
deckView->expandAll();
|
||||
setModified(false);
|
||||
|
||||
PictureLoader::cacheCardPixmaps(db->getCards(deckModel->getDeckList()->getCardList()));
|
||||
PictureLoader::cacheCardPixmaps(
|
||||
CardDatabaseManager::getInstance()->getCards(deckModel->getDeckList()->getCardList()));
|
||||
deckView->expandAll();
|
||||
setModified(false);
|
||||
|
||||
@@ -1160,6 +1433,9 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e)
|
||||
} else if (o == filterDock) {
|
||||
aFilterDockVisible->setChecked(false);
|
||||
aFilterDockFloating->setEnabled(false);
|
||||
} else if (o == printingSelectorDock) {
|
||||
aPrintingSelectorDockVisible->setChecked(false);
|
||||
aPrintingSelectorDockFloating->setEnabled(false);
|
||||
}
|
||||
}
|
||||
if (o == this && e->type() == QEvent::Hide) {
|
||||
@@ -1169,6 +1445,7 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e)
|
||||
layouts.setDeckEditorCardSize(cardInfoDock->size());
|
||||
layouts.setDeckEditorFilterSize(filterDock->size());
|
||||
layouts.setDeckEditorDeckSize(deckDock->size());
|
||||
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDock->size());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1193,6 +1470,12 @@ void TabDeckEditor::dockVisibleTriggered()
|
||||
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
|
||||
return;
|
||||
}
|
||||
|
||||
if (o == aPrintingSelectorDockVisible) {
|
||||
printingSelectorDock->setVisible(aPrintingSelectorDockVisible->isChecked());
|
||||
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckEditor::dockFloatingTriggered()
|
||||
@@ -1212,6 +1495,11 @@ void TabDeckEditor::dockFloatingTriggered()
|
||||
filterDock->setFloating(aFilterDockFloating->isChecked());
|
||||
return;
|
||||
}
|
||||
|
||||
if (o == aPrintingSelectorDockFloating) {
|
||||
printingSelectorDock->setFloating(aPrintingSelectorDockFloating->isChecked());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckEditor::dockTopLevelChanged(bool topLevel)
|
||||
@@ -1231,6 +1519,11 @@ void TabDeckEditor::dockTopLevelChanged(bool topLevel)
|
||||
aFilterDockFloating->setChecked(topLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (o == printingSelectorDock) {
|
||||
aPrintingSelectorDockFloating->setChecked(topLevel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckEditor::saveDbHeaderState()
|
||||
@@ -1281,6 +1574,6 @@ void TabDeckEditor::showSearchSyntaxHelp()
|
||||
browser->document()->setDefaultStyleSheet(sheet);
|
||||
|
||||
browser->setHtml(text);
|
||||
connect(browser, &QTextBrowser::anchorClicked, [=](const QUrl &link) { searchEdit->setText(link.fragment()); });
|
||||
connect(browser, &QTextBrowser::anchorClicked, [this](const QUrl &link) { searchEdit->setText(link.fragment()); });
|
||||
browser->show();
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef WINDOW_DECKEDITOR_H
|
||||
#define WINDOW_DECKEDITOR_H
|
||||
|
||||
#include "carddatabase.h"
|
||||
#include "customlineedit.h"
|
||||
#include "keysignals.h"
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../game_logic/key_signals.h"
|
||||
#include "../ui/widgets/printing_selector/printing_selector.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
@@ -14,7 +15,7 @@ class CardDatabaseDisplayModel;
|
||||
class DeckListModel;
|
||||
class QTreeView;
|
||||
|
||||
class CardFrame;
|
||||
class CardInfoFrameWidget;
|
||||
class QTextEdit;
|
||||
class QLabel;
|
||||
class DeckLoader;
|
||||
@@ -22,29 +23,12 @@ class Response;
|
||||
class FilterTreeModel;
|
||||
class FilterBuilder;
|
||||
class QGroupBox;
|
||||
class QMessageBox;
|
||||
class QHBoxLayout;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
class QDockWidget;
|
||||
|
||||
class SearchLineEdit : public LineEditUnfocusable
|
||||
{
|
||||
private:
|
||||
QTreeView *treeView;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
public:
|
||||
SearchLineEdit() : LineEditUnfocusable(), treeView(nullptr)
|
||||
{
|
||||
}
|
||||
void setTreeView(QTreeView *_treeView)
|
||||
{
|
||||
treeView = _treeView;
|
||||
}
|
||||
};
|
||||
|
||||
class TabDeckEditor : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -54,11 +38,17 @@ private slots:
|
||||
void updateHash();
|
||||
void updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updatePrintingSelectorDatabase(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updatePrintingSelectorDeckView(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updateSearch(const QString &search);
|
||||
void databaseCustomMenu(QPoint point);
|
||||
void decklistCustomMenu(QPoint point);
|
||||
void updateRecentlyOpened();
|
||||
|
||||
void actNewDeck();
|
||||
void actLoadDeck();
|
||||
void actOpenRecent(const QString &fileName);
|
||||
void actClearRecents();
|
||||
bool actSaveDeck();
|
||||
bool actSaveDeckAs();
|
||||
void actLoadDeckFromClipboard();
|
||||
@@ -100,11 +90,26 @@ private slots:
|
||||
void showSearchSyntaxHelp();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Which tab to open the new deck in
|
||||
*/
|
||||
enum DeckOpenLocation
|
||||
{
|
||||
CANCELLED,
|
||||
SAME_TAB,
|
||||
NEW_TAB
|
||||
};
|
||||
|
||||
DeckOpenLocation confirmOpen(const bool openInSameTabIfBlank = true);
|
||||
QMessageBox *createSaveConfirmationWindow();
|
||||
|
||||
bool isBlankNewDeck() const;
|
||||
CardInfoPtr currentCardInfo() const;
|
||||
void addCardHelper(QString zoneName);
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void decrementCardHelper(QString zoneName);
|
||||
void recursiveExpand(const QModelIndex &index);
|
||||
void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
|
||||
|
||||
CardDatabaseModel *databaseModel;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
@@ -113,7 +118,8 @@ private:
|
||||
|
||||
QTreeView *deckView;
|
||||
KeySignals deckViewKeySignals;
|
||||
CardFrame *cardInfo;
|
||||
CardInfoFrameWidget *cardInfo;
|
||||
PrintingSelector *printingSelector;
|
||||
SearchLineEdit *searchEdit;
|
||||
KeySignals searchKeySignals;
|
||||
|
||||
@@ -128,16 +134,16 @@ private:
|
||||
KeySignals filterViewKeySignals;
|
||||
QWidget *filterBox;
|
||||
|
||||
QMenu *deckMenu, *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *analyzeDeckMenu,
|
||||
*saveDeckToClipboardMenu;
|
||||
QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard,
|
||||
*aSaveDeckToClipboardRaw, *aPrintDeck, *aExportDeckDecklist, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout,
|
||||
*aClose;
|
||||
QMenu *deckMenu, *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu,
|
||||
*analyzeDeckMenu, *saveDeckToClipboardMenu, *loadRecentDeckMenu;
|
||||
QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard,
|
||||
*aSaveDeckToClipboard, *aSaveDeckToClipboardRaw, *aPrintDeck, *aExportDeckDecklist, *aAnalyzeDeckDeckstats,
|
||||
*aAnalyzeDeckTappedout, *aClose;
|
||||
QAction *aClearFilterAll, *aClearFilterOne;
|
||||
QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement;
|
||||
QAction *aResetLayout;
|
||||
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating, *aFilterDockVisible,
|
||||
*aFilterDockFloating;
|
||||
*aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
|
||||
|
||||
bool modified;
|
||||
QVBoxLayout *centralFrame;
|
||||
@@ -145,6 +151,7 @@ private:
|
||||
QDockWidget *cardInfoDock;
|
||||
QDockWidget *deckDock;
|
||||
QDockWidget *filterDock;
|
||||
QDockWidget *printingSelectorDock;
|
||||
QWidget *centralWidget;
|
||||
|
||||
public:
|
||||
@@ -158,12 +165,16 @@ public:
|
||||
void createDeckDock();
|
||||
void createCardInfoDock();
|
||||
void createFiltersDock();
|
||||
void createPrintingSelectorDock();
|
||||
void createMenus();
|
||||
void createCentralFrame();
|
||||
void updateCardInfo(CardInfoPtr _card);
|
||||
|
||||
public slots:
|
||||
void closeRequest() override;
|
||||
void showPrintingSelector();
|
||||
signals:
|
||||
void openDeckEditor(const DeckLoader *deckLoader);
|
||||
void deckEditorClosing(TabDeckEditor *tab);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "tab_deck_storage.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "deck_loader.h"
|
||||
#include "../../deck/deck_loader.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/remote/remote_decklist_tree_widget.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "../get_text_with_max.h"
|
||||
#include "decklist.h"
|
||||
#include "gettextwithmax.h"
|
||||
#include "pb/command_deck_del.pb.h"
|
||||
#include "pb/command_deck_del_dir.pb.h"
|
||||
#include "pb/command_deck_download.pb.h"
|
||||
@@ -12,13 +15,11 @@
|
||||
#include "pb/response.pb.h"
|
||||
#include "pb/response_deck_download.pb.h"
|
||||
#include "pb/response_deck_upload.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "remotedecklist_treewidget.h"
|
||||
#include "settingscache.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileSystemModel>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
@@ -27,6 +28,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client)
|
||||
@@ -41,16 +43,33 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
|
||||
localDirView->setColumnHidden(1, true);
|
||||
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
|
||||
localDirView->setSortingEnabled(true);
|
||||
localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
|
||||
leftToolBar = new QToolBar;
|
||||
connect(localDirView, &QTreeView::doubleClicked, this, &TabDeckStorage::actLocalDoubleClick);
|
||||
|
||||
// Left side layout
|
||||
/* put an invisible dummy QToolBar in the leftmost column so that the main toolbar is centered.
|
||||
* Really ugly workaround, but I couldn't figure out the proper way to make it centered */
|
||||
QToolBar *dummyToolBar = new QToolBar(this);
|
||||
QSizePolicy sizePolicy = dummyToolBar->sizePolicy();
|
||||
sizePolicy.setRetainSizeWhenHidden(true);
|
||||
dummyToolBar->setSizePolicy(sizePolicy);
|
||||
dummyToolBar->setVisible(false);
|
||||
|
||||
leftToolBar = new QToolBar(this);
|
||||
leftToolBar->setOrientation(Qt::Horizontal);
|
||||
leftToolBar->setIconSize(QSize(32, 32));
|
||||
QHBoxLayout *leftToolBarLayout = new QHBoxLayout;
|
||||
leftToolBarLayout->addStretch();
|
||||
leftToolBarLayout->addWidget(leftToolBar);
|
||||
leftToolBarLayout->addStretch();
|
||||
|
||||
QToolBar *leftRightmostToolBar = new QToolBar(this);
|
||||
leftRightmostToolBar->setOrientation(Qt::Horizontal);
|
||||
leftRightmostToolBar->setIconSize(QSize(32, 32));
|
||||
|
||||
QGridLayout *leftToolBarLayout = new QGridLayout;
|
||||
leftToolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft);
|
||||
leftToolBarLayout->addWidget(leftToolBar, 0, 1, Qt::AlignHCenter);
|
||||
leftToolBarLayout->addWidget(leftRightmostToolBar, 0, 2, Qt::AlignRight);
|
||||
|
||||
QVBoxLayout *leftVbox = new QVBoxLayout;
|
||||
leftVbox->addWidget(localDirView);
|
||||
@@ -58,6 +77,7 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
|
||||
leftGroupBox = new QGroupBox;
|
||||
leftGroupBox->setLayout(leftVbox);
|
||||
|
||||
// Right side layout
|
||||
rightToolBar = new QToolBar;
|
||||
rightToolBar->setOrientation(Qt::Horizontal);
|
||||
rightToolBar->setIconSize(QSize(32, 32));
|
||||
@@ -68,25 +88,38 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
|
||||
|
||||
serverDirView = new RemoteDeckList_TreeWidget(client);
|
||||
|
||||
connect(serverDirView, &QTreeView::doubleClicked, this, &TabDeckStorage::actRemoteDoubleClick);
|
||||
|
||||
QVBoxLayout *rightVbox = new QVBoxLayout;
|
||||
rightVbox->addWidget(serverDirView);
|
||||
rightVbox->addLayout(rightToolBarLayout);
|
||||
rightGroupBox = new QGroupBox;
|
||||
rightGroupBox->setLayout(rightVbox);
|
||||
|
||||
// combine layouts
|
||||
QHBoxLayout *hbox = new QHBoxLayout;
|
||||
hbox->addWidget(leftGroupBox);
|
||||
hbox->addWidget(rightGroupBox);
|
||||
|
||||
// Left side actions
|
||||
aOpenLocalDeck = new QAction(this);
|
||||
aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil"));
|
||||
connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck()));
|
||||
aUpload = new QAction(this);
|
||||
aUpload->setIcon(QPixmap("theme:icons/arrow_right_green"));
|
||||
connect(aUpload, SIGNAL(triggered()), this, SLOT(actUpload()));
|
||||
aNewLocalFolder = new QAction(this);
|
||||
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
|
||||
connect(aNewLocalFolder, &QAction::triggered, this, &TabDeckStorage::actNewLocalFolder);
|
||||
aDeleteLocalDeck = new QAction(this);
|
||||
aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck()));
|
||||
|
||||
aOpenDecksFolder = new QAction(this);
|
||||
aOpenDecksFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon));
|
||||
connect(aOpenDecksFolder, &QAction::triggered, this, &TabDeckStorage::actOpenDecksFolder);
|
||||
|
||||
// Right side actions
|
||||
aOpenRemoteDeck = new QAction(this);
|
||||
aOpenRemoteDeck->setIcon(QPixmap("theme:icons/pencil"));
|
||||
connect(aOpenRemoteDeck, SIGNAL(triggered()), this, SLOT(actOpenRemoteDeck()));
|
||||
@@ -100,9 +133,14 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
|
||||
aDeleteRemoteDeck->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteRemoteDeck, SIGNAL(triggered()), this, SLOT(actDeleteRemoteDeck()));
|
||||
|
||||
// Add actions to toolbars
|
||||
leftToolBar->addAction(aOpenLocalDeck);
|
||||
leftToolBar->addAction(aUpload);
|
||||
leftToolBar->addAction(aNewLocalFolder);
|
||||
leftToolBar->addAction(aDeleteLocalDeck);
|
||||
|
||||
leftRightmostToolBar->addAction(aOpenDecksFolder);
|
||||
|
||||
rightToolBar->addAction(aOpenRemoteDeck);
|
||||
rightToolBar->addAction(aDownload);
|
||||
rightToolBar->addAction(aNewFolder);
|
||||
@@ -124,9 +162,11 @@ void TabDeckStorage::retranslateUi()
|
||||
aUpload->setText(tr("Upload deck"));
|
||||
aOpenRemoteDeck->setText(tr("Open in deck editor"));
|
||||
aDownload->setText(tr("Download deck"));
|
||||
aNewLocalFolder->setText(tr("New folder"));
|
||||
aNewFolder->setText(tr("New folder"));
|
||||
aDeleteLocalDeck->setText(tr("Delete"));
|
||||
aDeleteRemoteDeck->setText(tr("Delete"));
|
||||
aOpenDecksFolder->setText(tr("Open decks folder"));
|
||||
}
|
||||
|
||||
QString TabDeckStorage::getTargetPath() const
|
||||
@@ -147,43 +187,61 @@ QString TabDeckStorage::getTargetPath() const
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actLocalDoubleClick(const QModelIndex &curLeft)
|
||||
{
|
||||
if (!localDirModel->isDir(curLeft)) {
|
||||
actOpenLocalDeck();
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actOpenLocalDeck()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (localDirModel->isDir(curLeft))
|
||||
return;
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
for (const auto &curLeft : curLefts) {
|
||||
if (localDirModel->isDir(curLeft))
|
||||
continue;
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
|
||||
DeckLoader deckLoader;
|
||||
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat))
|
||||
return;
|
||||
DeckLoader deckLoader;
|
||||
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat))
|
||||
continue;
|
||||
|
||||
emit openDeckEditor(&deckLoader);
|
||||
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(filePath);
|
||||
|
||||
emit openDeckEditor(&deckLoader);
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actUpload()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (localDirModel->isDir(curLeft))
|
||||
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
if (curLefts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString targetPath = getTargetPath();
|
||||
if (targetPath.length() > MAX_NAME_LENGTH) {
|
||||
qCritical() << "target path to upload to is too long" << targetPath;
|
||||
return;
|
||||
}
|
||||
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
for (const auto &curLeft : curLefts) {
|
||||
if (localDirModel->isDir(curLeft)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
uploadDeck(filePath, targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPath)
|
||||
{
|
||||
QFile deckFile(filePath);
|
||||
QFileInfo deckFileInfo(deckFile);
|
||||
|
||||
QString deckString;
|
||||
DeckLoader deck;
|
||||
bool error = !deck.loadFromFile(filePath, DeckLoader::CockatriceFormat);
|
||||
if (!error) {
|
||||
deckString = deck.writeToString_Native();
|
||||
error = deckString.length() > MAX_FILE_LENGTH;
|
||||
}
|
||||
if (error) {
|
||||
if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
|
||||
return;
|
||||
}
|
||||
@@ -202,6 +260,12 @@ void TabDeckStorage::actUpload()
|
||||
deck.setName(deck.getName().left(MAX_NAME_LENGTH));
|
||||
}
|
||||
|
||||
QString deckString = deck.writeToString_Native();
|
||||
if (deckString.length() > MAX_FILE_LENGTH) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
|
||||
return;
|
||||
}
|
||||
|
||||
Command_DeckUpload cmd;
|
||||
cmd.set_path(targetPath.toStdString());
|
||||
cmd.set_deck_list(deckString.toStdString());
|
||||
@@ -226,34 +290,73 @@ void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &c
|
||||
serverDirView->addFileToTree(resp.new_file(), serverDirView->getNodeByPath(QString::fromStdString(cmd.path())));
|
||||
}
|
||||
|
||||
void TabDeckStorage::actDeleteLocalDeck()
|
||||
void TabDeckStorage::actNewLocalFolder()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (localDirModel->isDir(curLeft))
|
||||
|
||||
QModelIndex dirIndex;
|
||||
if (curLeft.isValid() && !localDirModel->isDir(curLeft)) {
|
||||
dirIndex = curLeft.parent();
|
||||
} else {
|
||||
dirIndex = curLeft;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
QString folderName =
|
||||
QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok);
|
||||
if (!ok || folderName.isEmpty())
|
||||
return;
|
||||
|
||||
if (QMessageBox::warning(this, tr("Delete local file"),
|
||||
tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)),
|
||||
localDirModel->mkdir(dirIndex, folderName);
|
||||
}
|
||||
|
||||
void TabDeckStorage::actDeleteLocalDeck()
|
||||
{
|
||||
const QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
|
||||
if (curLefts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
localDirModel->remove(curLeft);
|
||||
for (const auto &curLeft : curLefts) {
|
||||
if (curLeft.isValid()) {
|
||||
localDirModel->remove(curLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actOpenDecksFolder()
|
||||
{
|
||||
QString dir = localDirModel->rootPath();
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
|
||||
}
|
||||
|
||||
void TabDeckStorage::actRemoteDoubleClick(const QModelIndex &curRight)
|
||||
{
|
||||
if (dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getNode(curRight))) {
|
||||
actOpenRemoteDeck();
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actOpenRemoteDeck()
|
||||
{
|
||||
RemoteDeckList_TreeModel::FileNode *curRight =
|
||||
dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getCurrentItem());
|
||||
if (!curRight)
|
||||
return;
|
||||
for (const auto &curRight : serverDirView->getCurrentSelection()) {
|
||||
RemoteDeckList_TreeModel::FileNode *node = dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(curRight);
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
Command_DeckDownload cmd;
|
||||
cmd.set_deck_id(curRight->getId());
|
||||
Command_DeckDownload cmd;
|
||||
cmd.set_deck_id(node->getId());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(openRemoteDeckFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(openRemoteDeckFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer)
|
||||
@@ -273,30 +376,46 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont
|
||||
|
||||
void TabDeckStorage::actDownload()
|
||||
{
|
||||
QString filePath;
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (!curLeft.isValid())
|
||||
filePath = localDirModel->rootPath();
|
||||
else {
|
||||
while (!localDirModel->isDir(curLeft))
|
||||
curLeft = curLeft.parent();
|
||||
filePath = localDirModel->filePath(curLeft);
|
||||
while (!localDirModel->isDir(curLeft)) {
|
||||
curLeft = curLeft.parent();
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::FileNode *curRight =
|
||||
dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getCurrentItem());
|
||||
if (!curRight)
|
||||
return;
|
||||
filePath += QString("/deck_%1.cod").arg(curRight->getId());
|
||||
for (const auto curRight : serverDirView->selectionModel()->selectedRows()) {
|
||||
downloadNodeAtIndex(curLeft, curRight);
|
||||
}
|
||||
}
|
||||
|
||||
Command_DeckDownload cmd;
|
||||
cmd.set_deck_id(curRight->getId());
|
||||
void TabDeckStorage::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight)
|
||||
{
|
||||
auto node = serverDirView->getNode(curRight);
|
||||
if (const auto dirNode = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(node)) {
|
||||
// node at index is a folder
|
||||
const QString name = dirNode->getName();
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
pend->setExtraData(filePath);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
|
||||
client->sendCommand(pend);
|
||||
const auto dirIndex = curLeft.isValid() ? curLeft : localDirModel->index(localDirModel->rootPath());
|
||||
const auto newDirIndex = localDirModel->mkdir(dirIndex, name);
|
||||
|
||||
int rows = serverDirView->model()->rowCount(curRight);
|
||||
for (int i = 0; i < rows; i++) {
|
||||
const auto childIndex = serverDirView->model()->index(i, 0, curRight);
|
||||
downloadNodeAtIndex(newDirIndex, childIndex);
|
||||
}
|
||||
} else if (const auto fileNode = dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(node)) {
|
||||
// node at index is a deck
|
||||
const QString dirPath = curLeft.isValid() ? localDirModel->filePath(curLeft) : localDirModel->rootPath();
|
||||
const QString filePath = dirPath + QString("/deck_%1.cod").arg(fileNode->getId());
|
||||
|
||||
Command_DeckDownload cmd;
|
||||
cmd.set_deck_id(fileNode->getId());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
pend->setExtraData(filePath);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
// node at index is invalid
|
||||
}
|
||||
|
||||
void TabDeckStorage::downloadFinished(const Response &r,
|
||||
@@ -352,12 +471,30 @@ void TabDeckStorage::newFolderFinished(const Response &response, const CommandCo
|
||||
|
||||
void TabDeckStorage::actDeleteRemoteDeck()
|
||||
{
|
||||
PendingCommand *pend;
|
||||
RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem();
|
||||
if (!curRight)
|
||||
auto curRights = serverDirView->getCurrentSelection();
|
||||
|
||||
if (curRights.isEmpty()) {
|
||||
return;
|
||||
RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(curRight);
|
||||
if (dir) {
|
||||
}
|
||||
|
||||
if (QMessageBox::warning(this, tr("Delete remote decks"), tr("Are you sure you want to delete the selected decks?"),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &curRight : curRights) {
|
||||
deleteRemoteDeck(curRight);
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *curRight)
|
||||
{
|
||||
if (!curRight) {
|
||||
return;
|
||||
}
|
||||
|
||||
PendingCommand *pend;
|
||||
if (const auto *dir = dynamic_cast<const RemoteDeckList_TreeModel::DirectoryNode *>(curRight)) {
|
||||
QString targetPath = dir->getPath();
|
||||
if (targetPath.isEmpty())
|
||||
return;
|
||||
@@ -365,22 +502,13 @@ void TabDeckStorage::actDeleteRemoteDeck()
|
||||
qCritical() << "target path to delete is too long" << targetPath;
|
||||
return;
|
||||
}
|
||||
if (QMessageBox::warning(this, tr("Delete remote folder"),
|
||||
tr("Are you sure you want to delete \"%1\"?").arg(targetPath),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
Command_DeckDelDir cmd;
|
||||
cmd.set_path(targetPath.toStdString());
|
||||
pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(deleteFolderFinished(Response, CommandContainer)));
|
||||
} else {
|
||||
RemoteDeckList_TreeModel::FileNode *deckNode = dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(curRight);
|
||||
if (QMessageBox::warning(this, tr("Delete remote deck"),
|
||||
tr("Are you sure you want to delete \"%1\"?").arg(deckNode->getName()),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
const auto *deckNode = dynamic_cast<const RemoteDeckList_TreeModel::FileNode *>(curRight);
|
||||
Command_DeckDel cmd;
|
||||
cmd.set_deck_id(deckNode->getId());
|
||||
pend = client->prepareSessionCommand(cmd);
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef TAB_DECK_STORAGE_H
|
||||
#define TAB_DECK_STORAGE_H
|
||||
|
||||
#include "../../server/remote/remote_decklist_tree_widget.h"
|
||||
#include "tab.h"
|
||||
|
||||
class AbstractClient;
|
||||
@@ -10,7 +11,6 @@ class QToolBar;
|
||||
class QTreeWidget;
|
||||
class QTreeWidgetItem;
|
||||
class QGroupBox;
|
||||
class RemoteDeckList_TreeWidget;
|
||||
class CommandContainer;
|
||||
class Response;
|
||||
class DeckLoader;
|
||||
@@ -26,16 +26,29 @@ private:
|
||||
RemoteDeckList_TreeWidget *serverDirView;
|
||||
QGroupBox *leftGroupBox, *rightGroupBox;
|
||||
|
||||
QAction *aOpenLocalDeck, *aUpload, *aDeleteLocalDeck, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
|
||||
QAction *aOpenLocalDeck, *aUpload, *aNewLocalFolder, *aDeleteLocalDeck;
|
||||
QAction *aOpenDecksFolder;
|
||||
QAction *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
|
||||
QString getTargetPath() const;
|
||||
|
||||
void uploadDeck(const QString &filePath, const QString &targetPath);
|
||||
void deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *node);
|
||||
|
||||
void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight);
|
||||
|
||||
private slots:
|
||||
void actLocalDoubleClick(const QModelIndex &curLeft);
|
||||
void actOpenLocalDeck();
|
||||
|
||||
void actUpload();
|
||||
void uploadFinished(const Response &r, const CommandContainer &commandContainer);
|
||||
|
||||
void actNewLocalFolder();
|
||||
void actDeleteLocalDeck();
|
||||
|
||||
void actOpenDecksFolder();
|
||||
|
||||
void actRemoteDoubleClick(const QModelIndex &curRight);
|
||||
void actOpenRemoteDeck();
|
||||
void openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer);
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
#include "tab_game.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "arrowitem.h"
|
||||
#include "carddatabase.h"
|
||||
#include "cardframe.h"
|
||||
#include "carditem.h"
|
||||
#include "deck_loader.h"
|
||||
#include "deckview.h"
|
||||
#include "dlg_creategame.h"
|
||||
#include "dlg_load_remote_deck.h"
|
||||
#include "dlg_manage_sets.h"
|
||||
#include "gamescene.h"
|
||||
#include "gameview.h"
|
||||
#include "../../client/ui/widgets/cards/card_info_frame_widget.h"
|
||||
#include "../../deck/deck_loader.h"
|
||||
#include "../../deck/deck_view.h"
|
||||
#include "../../dialogs/dlg_create_game.h"
|
||||
#include "../../dialogs/dlg_load_remote_deck.h"
|
||||
#include "../../dialogs/dlg_manage_sets.h"
|
||||
#include "../../game/board/arrow_item.h"
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../../game/cards/card_database_manager.h"
|
||||
#include "../../game/cards/card_item.h"
|
||||
#include "../../game/game_scene.h"
|
||||
#include "../../game/game_view.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../../game/player/player_list_widget.h"
|
||||
#include "../../game/zones/view_zone.h"
|
||||
#include "../../game/zones/view_zone_widget.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/message_log_widget.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "../network/replay_timeline_widget.h"
|
||||
#include "../ui/line_edit_completer.h"
|
||||
#include "../ui/phases_toolbar.h"
|
||||
#include "../ui/picture_loader.h"
|
||||
#include "../ui/window_main.h"
|
||||
#include "get_pb_extension.h"
|
||||
#include "lineeditcompleter.h"
|
||||
#include "main.h"
|
||||
#include "messagelogwidget.h"
|
||||
#include "pb/command_concede.pb.h"
|
||||
#include "pb/command_deck_select.pb.h"
|
||||
#include "pb/command_delete_arrow.pb.h"
|
||||
@@ -45,18 +56,8 @@
|
||||
#include "pb/game_event_container.pb.h"
|
||||
#include "pb/game_replay.pb.h"
|
||||
#include "pb/response_deck_download.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "phasestoolbar.h"
|
||||
#include "pictureloader.h"
|
||||
#include "player.h"
|
||||
#include "playerlistwidget.h"
|
||||
#include "replay_timeline_widget.h"
|
||||
#include "settingscache.h"
|
||||
#include "stringsizes.h"
|
||||
#include "tab_supervisor.h"
|
||||
#include "window_main.h"
|
||||
#include "zoneviewwidget.h"
|
||||
#include "zoneviewzone.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QCompleter>
|
||||
@@ -105,11 +106,14 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
|
||||
loadRemoteButton = new QPushButton;
|
||||
readyStartButton = new ToggleButton;
|
||||
readyStartButton->setEnabled(false);
|
||||
forceStartGameButton = new QPushButton;
|
||||
forceStartGameButton->setEnabled(parent->isHost());
|
||||
sideboardLockButton = new ToggleButton;
|
||||
sideboardLockButton->setEnabled(false);
|
||||
|
||||
connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck()));
|
||||
connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart()));
|
||||
connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart);
|
||||
connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked()));
|
||||
connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText()));
|
||||
|
||||
@@ -124,6 +128,9 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
|
||||
buttonHBox->addWidget(loadRemoteButton);
|
||||
buttonHBox->addWidget(readyStartButton);
|
||||
buttonHBox->addWidget(sideboardLockButton);
|
||||
if (forceStartGameButton->isEnabled()) {
|
||||
buttonHBox->addWidget(forceStartGameButton);
|
||||
}
|
||||
buttonHBox->setContentsMargins(0, 0, 0, 0);
|
||||
buttonHBox->addStretch();
|
||||
deckView = new DeckView;
|
||||
@@ -146,6 +153,7 @@ void DeckViewContainer::retranslateUi()
|
||||
loadLocalButton->setText(tr("Load deck..."));
|
||||
loadRemoteButton->setText(tr("Load remote deck..."));
|
||||
readyStartButton->setText(tr("Ready to start"));
|
||||
forceStartGameButton->setText(tr("Force start"));
|
||||
updateSideboardLockButtonText();
|
||||
}
|
||||
|
||||
@@ -154,6 +162,7 @@ void DeckViewContainer::setButtonsVisible(bool _visible)
|
||||
loadLocalButton->setVisible(_visible);
|
||||
loadRemoteButton->setVisible(_visible);
|
||||
readyStartButton->setVisible(_visible);
|
||||
forceStartGameButton->setVisible(_visible);
|
||||
sideboardLockButton->setVisible(_visible);
|
||||
}
|
||||
|
||||
@@ -255,7 +264,25 @@ void TabGame::refreshShortcuts()
|
||||
aResetLayout->setShortcuts(shortcuts.getShortcut("Player/aResetLayout"));
|
||||
}
|
||||
if (aFocusChat) {
|
||||
aFocusChat->setShortcuts(shortcuts.getShortcut("tab_game/aFocusChat"));
|
||||
aFocusChat->setShortcuts(shortcuts.getShortcut("Player/aFocusChat"));
|
||||
}
|
||||
if (aReplaySkipForward) {
|
||||
aReplaySkipForward->setShortcuts(shortcuts.getShortcut("Replays/aSkipForward"));
|
||||
}
|
||||
if (aReplaySkipBackward) {
|
||||
aReplaySkipBackward->setShortcuts(shortcuts.getShortcut("Replays/aSkipBackward"));
|
||||
}
|
||||
if (aReplaySkipForwardBig) {
|
||||
aReplaySkipForwardBig->setShortcuts(shortcuts.getShortcut("Replays/aSkipForwardBig"));
|
||||
}
|
||||
if (aReplaySkipBackwardBig) {
|
||||
aReplaySkipBackwardBig->setShortcuts(shortcuts.getShortcut("Replays/aSkipBackwardBig"));
|
||||
}
|
||||
if (replayPlayButton) {
|
||||
replayPlayButton->setShortcut(shortcuts.getSingleShortcut("Replays/playButton"));
|
||||
}
|
||||
if (replayFastForwardButton) {
|
||||
replayFastForwardButton->setShortcut(shortcuts.getSingleShortcut("Replays/fastForwardButton"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +334,8 @@ void DeckViewContainer::deckSelectFinished(const Response &r)
|
||||
{
|
||||
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
|
||||
DeckLoader newDeck(QString::fromStdString(resp.deck()));
|
||||
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
|
||||
// TODO CHANGE THIS TO BE SELECTED BY UUID
|
||||
PictureLoader::cacheCardPixmaps(CardDatabaseManager::getInstance()->getCards(newDeck.getCardList()));
|
||||
setDeck(newDeck);
|
||||
}
|
||||
|
||||
@@ -318,6 +346,14 @@ void DeckViewContainer::readyStart()
|
||||
parentGame->sendGameCommand(cmd, playerId);
|
||||
}
|
||||
|
||||
void DeckViewContainer::forceStart()
|
||||
{
|
||||
Command_ReadyStart cmd;
|
||||
cmd.set_force_start(true);
|
||||
cmd.set_ready(true);
|
||||
parentGame->sendGameCommand(cmd, playerId);
|
||||
}
|
||||
|
||||
void DeckViewContainer::sideboardLockButtonClicked()
|
||||
{
|
||||
Command_SetSideboardLock cmd;
|
||||
@@ -422,7 +458,9 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
|
||||
: Tab(_tabSupervisor), clients(_clients), gameInfo(event.game_info()), roomGameTypes(_roomGameTypes),
|
||||
hostId(event.host_id()), localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()),
|
||||
spectator(event.spectator()), judge(event.judge()), gameStateKnown(false), resuming(event.resuming()),
|
||||
currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(nullptr), replayDock(nullptr)
|
||||
currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(nullptr), replayPlayButton(nullptr),
|
||||
replayFastForwardButton(nullptr), aReplaySkipForward(nullptr), aReplaySkipBackward(nullptr),
|
||||
aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr), replayDock(nullptr)
|
||||
{
|
||||
// THIS CTOR IS USED ON GAMES
|
||||
gameInfo.set_started(false);
|
||||
@@ -498,6 +536,12 @@ void TabGame::updatePlayerListDockTitle()
|
||||
(playerListDock->isWindow() ? tabText : QString()));
|
||||
}
|
||||
|
||||
bool TabGame::isMainPlayerConceded() const
|
||||
{
|
||||
Player *player = players.value(localPlayerId, nullptr);
|
||||
return player && player->getConceded();
|
||||
}
|
||||
|
||||
void TabGame::retranslateUi()
|
||||
{
|
||||
QString tabText = " | " + (replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id());
|
||||
@@ -539,7 +583,11 @@ void TabGame::retranslateUi()
|
||||
if (aGameInfo)
|
||||
aGameInfo->setText(tr("Game &information"));
|
||||
if (aConcede) {
|
||||
aConcede->setText(tr("&Concede"));
|
||||
if (isMainPlayerConceded()) {
|
||||
aConcede->setText(tr("Un&concede"));
|
||||
} else {
|
||||
aConcede->setText(tr("&Concede"));
|
||||
}
|
||||
}
|
||||
if (aLeaveGame) {
|
||||
aLeaveGame->setText(tr("&Leave game"));
|
||||
@@ -576,7 +624,7 @@ void TabGame::retranslateUi()
|
||||
|
||||
aResetLayout->setText(tr("Reset layout"));
|
||||
|
||||
cardInfo->retranslateUi();
|
||||
cardInfoFrameWidget->retranslateUi();
|
||||
|
||||
QMapIterator<int, Player *> i(players);
|
||||
while (i.hasNext())
|
||||
@@ -593,39 +641,40 @@ void TabGame::closeRequest()
|
||||
actLeaveGame();
|
||||
}
|
||||
|
||||
void TabGame::replayNextEvent()
|
||||
void TabGame::replayNextEvent(Player::EventProcessingOptions options)
|
||||
{
|
||||
processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr);
|
||||
processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr, options);
|
||||
}
|
||||
|
||||
void TabGame::replayFinished()
|
||||
{
|
||||
replayStartButton->setEnabled(true);
|
||||
replayPauseButton->setEnabled(false);
|
||||
replayFastForwardButton->setEnabled(false);
|
||||
replayPlayButton->setChecked(false);
|
||||
}
|
||||
|
||||
void TabGame::replayStartButtonClicked()
|
||||
void TabGame::replayPlayButtonToggled(bool checked)
|
||||
{
|
||||
replayStartButton->setEnabled(false);
|
||||
replayPauseButton->setEnabled(true);
|
||||
replayFastForwardButton->setEnabled(true);
|
||||
|
||||
timelineWidget->startReplay();
|
||||
}
|
||||
|
||||
void TabGame::replayPauseButtonClicked()
|
||||
{
|
||||
replayStartButton->setEnabled(true);
|
||||
replayPauseButton->setEnabled(false);
|
||||
replayFastForwardButton->setEnabled(false);
|
||||
|
||||
timelineWidget->stopReplay();
|
||||
if (checked) { // start replay
|
||||
timelineWidget->startReplay();
|
||||
} else { // pause replay
|
||||
timelineWidget->stopReplay();
|
||||
}
|
||||
}
|
||||
|
||||
void TabGame::replayFastForwardButtonToggled(bool checked)
|
||||
{
|
||||
timelineWidget->setTimeScaleFactor(checked ? 10.0 : 1.0);
|
||||
timelineWidget->setTimeScaleFactor(checked ? ReplayTimelineWidget::FAST_FORWARD_SCALE_FACTOR : 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles everything that needs to be reset when doing a replay rewind.
|
||||
*/
|
||||
void TabGame::replayRewind()
|
||||
{
|
||||
// reset chat log
|
||||
messageLog->clearChat();
|
||||
|
||||
// reset phase markers
|
||||
setActivePhase(-1);
|
||||
}
|
||||
|
||||
void TabGame::incrementGameTime()
|
||||
@@ -835,11 +884,16 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info)
|
||||
}
|
||||
}
|
||||
|
||||
// update menu text when player concedes so that "concede" gets updated to "unconcede"
|
||||
connect(newPlayer, &Player::playerCountChanged, this, &TabGame::retranslateUi);
|
||||
|
||||
emit playerAdded(newPlayer);
|
||||
return newPlayer;
|
||||
}
|
||||
|
||||
void TabGame::processGameEventContainer(const GameEventContainer &cont, AbstractClient *client)
|
||||
void TabGame::processGameEventContainer(const GameEventContainer &cont,
|
||||
AbstractClient *client,
|
||||
Player::EventProcessingOptions options)
|
||||
{
|
||||
const GameEventContext &context = cont.context();
|
||||
messageLog->containerProcessingStarted(context);
|
||||
@@ -914,7 +968,7 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont, Abstract
|
||||
qDebug() << "unhandled game event: invalid player id";
|
||||
break;
|
||||
}
|
||||
player->processGameEvent(eventType, event, context);
|
||||
player->processGameEvent(eventType, event, context, options);
|
||||
emitUserEvent();
|
||||
}
|
||||
}
|
||||
@@ -986,7 +1040,7 @@ PendingCommand *TabGame::prepareGameCommand(const QList<const ::google::protobuf
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
void TabGame::startGame(bool resuming)
|
||||
void TabGame::startGame(bool _resuming)
|
||||
{
|
||||
currentPhase = -1;
|
||||
|
||||
@@ -999,7 +1053,7 @@ void TabGame::startGame(bool resuming)
|
||||
|
||||
mainWidget->setCurrentWidget(gamePlayAreaWidget);
|
||||
|
||||
if (!resuming) {
|
||||
if (!_resuming) {
|
||||
QMapIterator<int, Player *> playerIterator(players);
|
||||
while (playerIterator.hasNext())
|
||||
playerIterator.next().value()->setGameStarted();
|
||||
@@ -1087,7 +1141,8 @@ void TabGame::eventGameStateChanged(const Event_GameStateChanged &event,
|
||||
DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
|
||||
if (playerInfo.has_deck_list()) {
|
||||
DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list()));
|
||||
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
|
||||
PictureLoader::cacheCardPixmaps(
|
||||
CardDatabaseManager::getInstance()->getCards(newDeck.getCardList()));
|
||||
deckViewContainer->setDeck(newDeck);
|
||||
player->setDeck(newDeck);
|
||||
}
|
||||
@@ -1354,7 +1409,7 @@ void TabGame::eventSetActivePhase(const Event_SetActivePhase &event,
|
||||
|
||||
void TabGame::newCardAdded(AbstractCardItem *card)
|
||||
{
|
||||
connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfo, SLOT(setCard(AbstractCardItem *)));
|
||||
connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfoFrameWidget, SLOT(setCard(AbstractCardItem *)));
|
||||
connect(card, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString)));
|
||||
connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
|
||||
connect(card, SIGNAL(cardShiftClicked(QString)), this, SLOT(linkCardToChat(QString)));
|
||||
@@ -1704,32 +1759,55 @@ void TabGame::createPlayAreaWidget(bool bReplay)
|
||||
|
||||
void TabGame::createReplayDock()
|
||||
{
|
||||
// timeline widget
|
||||
timelineWidget = new ReplayTimelineWidget;
|
||||
timelineWidget->setTimeline(replayTimeline);
|
||||
connect(timelineWidget, SIGNAL(processNextEvent()), this, SLOT(replayNextEvent()));
|
||||
connect(timelineWidget, SIGNAL(processNextEvent(Player::EventProcessingOptions)), this,
|
||||
SLOT(replayNextEvent(Player::EventProcessingOptions)));
|
||||
connect(timelineWidget, SIGNAL(replayFinished()), this, SLOT(replayFinished()));
|
||||
connect(timelineWidget, &ReplayTimelineWidget::rewound, messageLog, &ChatView::clearChat);
|
||||
connect(timelineWidget, &ReplayTimelineWidget::rewound, this, &TabGame::replayRewind);
|
||||
|
||||
// timeline skip shortcuts
|
||||
aReplaySkipForward = new QAction(timelineWidget);
|
||||
timelineWidget->addAction(aReplaySkipForward);
|
||||
connect(aReplaySkipForward, &QAction::triggered, this,
|
||||
[this] { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); });
|
||||
|
||||
aReplaySkipBackward = new QAction(timelineWidget);
|
||||
timelineWidget->addAction(aReplaySkipBackward);
|
||||
connect(aReplaySkipBackward, &QAction::triggered, this,
|
||||
[this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); });
|
||||
|
||||
aReplaySkipForwardBig = new QAction(timelineWidget);
|
||||
timelineWidget->addAction(aReplaySkipForwardBig);
|
||||
connect(aReplaySkipForwardBig, &QAction::triggered, this,
|
||||
[this] { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); });
|
||||
|
||||
aReplaySkipBackwardBig = new QAction(timelineWidget);
|
||||
timelineWidget->addAction(aReplaySkipBackwardBig);
|
||||
connect(aReplaySkipBackwardBig, &QAction::triggered, this,
|
||||
[this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); });
|
||||
|
||||
// buttons
|
||||
replayPlayButton = new QToolButton;
|
||||
replayPlayButton->setIconSize(QSize(32, 32));
|
||||
QIcon playButtonIcon = QIcon();
|
||||
playButtonIcon.addPixmap(QPixmap("theme:replay/start"), QIcon::Normal, QIcon::Off);
|
||||
playButtonIcon.addPixmap(QPixmap("theme:replay/pause"), QIcon::Normal, QIcon::On);
|
||||
replayPlayButton->setIcon(playButtonIcon);
|
||||
replayPlayButton->setCheckable(true);
|
||||
connect(replayPlayButton, SIGNAL(toggled(bool)), this, SLOT(replayPlayButtonToggled(bool)));
|
||||
|
||||
replayStartButton = new QToolButton;
|
||||
replayStartButton->setIconSize(QSize(32, 32));
|
||||
replayStartButton->setIcon(QPixmap("theme:replay/start"));
|
||||
connect(replayStartButton, SIGNAL(clicked()), this, SLOT(replayStartButtonClicked()));
|
||||
replayPauseButton = new QToolButton;
|
||||
replayPauseButton->setIconSize(QSize(32, 32));
|
||||
replayPauseButton->setEnabled(false);
|
||||
replayPauseButton->setIcon(QPixmap("theme:replay/pause"));
|
||||
connect(replayPauseButton, SIGNAL(clicked()), this, SLOT(replayPauseButtonClicked()));
|
||||
replayFastForwardButton = new QToolButton;
|
||||
replayFastForwardButton->setIconSize(QSize(32, 32));
|
||||
replayFastForwardButton->setEnabled(false);
|
||||
replayFastForwardButton->setIcon(QPixmap("theme:replay/fastforward"));
|
||||
replayFastForwardButton->setCheckable(true);
|
||||
connect(replayFastForwardButton, SIGNAL(toggled(bool)), this, SLOT(replayFastForwardButtonToggled(bool)));
|
||||
|
||||
// putting everything together
|
||||
replayControlLayout = new QHBoxLayout;
|
||||
replayControlLayout->addWidget(timelineWidget, 10);
|
||||
replayControlLayout->addWidget(replayStartButton);
|
||||
replayControlLayout->addWidget(replayPauseButton);
|
||||
replayControlLayout->addWidget(replayPlayButton);
|
||||
replayControlLayout->addWidget(replayFastForwardButton);
|
||||
|
||||
replayControlWidget = new QWidget();
|
||||
@@ -1758,20 +1836,20 @@ void TabGame::createDeckViewContainerWidget(bool bReplay)
|
||||
deckViewContainerWidget->setLayout(deckViewContainerLayout);
|
||||
}
|
||||
|
||||
void TabGame::viewCardInfo(const QString &cardName)
|
||||
void TabGame::viewCardInfo(const QString &cardName, const QString &providerId) const
|
||||
{
|
||||
cardInfo->setCard(cardName);
|
||||
cardInfoFrameWidget->setCard(cardName, providerId);
|
||||
}
|
||||
|
||||
void TabGame::createCardInfoDock(bool bReplay)
|
||||
{
|
||||
Q_UNUSED(bReplay);
|
||||
|
||||
cardInfo = new CardFrame();
|
||||
cardInfoFrameWidget = new CardInfoFrameWidget();
|
||||
cardHInfoLayout = new QHBoxLayout;
|
||||
cardVInfoLayout = new QVBoxLayout;
|
||||
cardVInfoLayout->setContentsMargins(0, 0, 0, 0);
|
||||
cardVInfoLayout->addWidget(cardInfo);
|
||||
cardVInfoLayout->addWidget(cardInfoFrameWidget);
|
||||
cardVInfoLayout->addLayout(cardHInfoLayout);
|
||||
|
||||
cardBoxLayoutWidget = new QWidget;
|
||||
@@ -1813,7 +1891,7 @@ void TabGame::createPlayerListDock(bool bReplay)
|
||||
void TabGame::createMessageDock(bool bReplay)
|
||||
{
|
||||
messageLog = new MessageLogWidget(tabSupervisor, tabSupervisor, this);
|
||||
connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfo, SLOT(setCard(QString)));
|
||||
connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfoFrameWidget, SLOT(setCard(QString)));
|
||||
connect(messageLog, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString)));
|
||||
connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef TAB_GAME_H
|
||||
#define TAB_GAME_H
|
||||
|
||||
#include "../../client/tearoff_menu.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "pb/event_leave.pb.h"
|
||||
#include "pb/serverinfo_game.pb.h"
|
||||
#include "tab.h"
|
||||
#include "tearoffmenu.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QMap>
|
||||
@@ -15,7 +16,7 @@ class CardDatabase;
|
||||
class GameView;
|
||||
class DeckView;
|
||||
class GameScene;
|
||||
class CardFrame;
|
||||
class CardInfoFrameWidget;
|
||||
class MessageLogWidget;
|
||||
class QTimer;
|
||||
class QSplitter;
|
||||
@@ -47,7 +48,6 @@ class Event_Ping;
|
||||
class Event_GameSay;
|
||||
class Event_Kicked;
|
||||
class Event_ReverseTurn;
|
||||
class Player;
|
||||
class CardZone;
|
||||
class AbstractCardItem;
|
||||
class CardItem;
|
||||
@@ -86,7 +86,7 @@ class DeckViewContainer : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QPushButton *loadLocalButton, *loadRemoteButton;
|
||||
QPushButton *loadLocalButton, *loadRemoteButton, *forceStartGameButton;
|
||||
ToggleButton *readyStartButton, *sideboardLockButton;
|
||||
DeckView *deckView;
|
||||
TabGame *parentGame;
|
||||
@@ -95,6 +95,7 @@ private slots:
|
||||
void loadLocalDeck();
|
||||
void loadRemoteDeck();
|
||||
void readyStart();
|
||||
void forceStart();
|
||||
void deckSelectFinished(const Response &r);
|
||||
void sideboardPlanChanged();
|
||||
void sideboardLockButtonClicked();
|
||||
@@ -146,9 +147,10 @@ private:
|
||||
int currentReplayStep;
|
||||
QList<int> replayTimeline;
|
||||
ReplayTimelineWidget *timelineWidget;
|
||||
QToolButton *replayStartButton, *replayPauseButton, *replayFastForwardButton;
|
||||
QToolButton *replayPlayButton, *replayFastForwardButton;
|
||||
QAction *aReplaySkipForward, *aReplaySkipBackward, *aReplaySkipForwardBig, *aReplaySkipBackwardBig;
|
||||
|
||||
CardFrame *cardInfo;
|
||||
CardInfoFrameWidget *cardInfoFrameWidget;
|
||||
PlayerListWidget *playerListWidget;
|
||||
QLabel *timeElapsedLabel;
|
||||
MessageLogWidget *messageLog;
|
||||
@@ -175,6 +177,8 @@ private:
|
||||
|
||||
Player *addPlayer(int playerId, const ServerInfo_User &info);
|
||||
|
||||
bool isMainPlayerConceded() const;
|
||||
|
||||
void startGame(bool resuming);
|
||||
void stopGame();
|
||||
void closeGame();
|
||||
@@ -218,11 +222,11 @@ signals:
|
||||
void openDeckEditor(const DeckLoader *deck);
|
||||
void notIdle();
|
||||
private slots:
|
||||
void replayNextEvent();
|
||||
void replayNextEvent(Player::EventProcessingOptions options);
|
||||
void replayFinished();
|
||||
void replayStartButtonClicked();
|
||||
void replayPauseButtonClicked();
|
||||
void replayPlayButtonToggled(bool checked);
|
||||
void replayFastForwardButtonToggled(bool checked);
|
||||
void replayRewind();
|
||||
|
||||
void incrementGameTime();
|
||||
void adminLockChanged(bool lock);
|
||||
@@ -304,13 +308,15 @@ public:
|
||||
return activeCard;
|
||||
}
|
||||
|
||||
void processGameEventContainer(const GameEventContainer &cont, AbstractClient *client);
|
||||
void processGameEventContainer(const GameEventContainer &cont,
|
||||
AbstractClient *client,
|
||||
Player::EventProcessingOptions options);
|
||||
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
|
||||
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
|
||||
public slots:
|
||||
void sendGameCommand(PendingCommand *pend, int playerId = -1);
|
||||
void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1);
|
||||
void viewCardInfo(const QString &cardName);
|
||||
void viewCardInfo(const QString &cardName, const QString &providerId = "") const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "tab_logs.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "customlineedit.h"
|
||||
#include "dlg_manage_sets.h"
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../dialogs/dlg_manage_sets.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "pb/moderator_commands.pb.h"
|
||||
#include "pb/response_viewlog_history.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "stringsizes.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
@@ -89,7 +89,7 @@ void TabLog::getClicked()
|
||||
if (maximumResults->value() == 0)
|
||||
maximumResults->setValue(1000);
|
||||
|
||||
int dateRange;
|
||||
int dateRange = 0;
|
||||
if (lastHour->isChecked())
|
||||
dateRange = 1;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#include "tab_message.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "chatview/chatview.h"
|
||||
#include "customlineedit.h"
|
||||
#include "main.h"
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/chat_view/chat_view.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "../sound_engine.h"
|
||||
#include "pb/event_user_message.pb.h"
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "settingscache.h"
|
||||
#include "soundengine.h"
|
||||
#include "stringsizes.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
@@ -159,7 +159,7 @@ void TabMessage::showSystemPopup(const Event_UserMessage &event)
|
||||
void TabMessage::messageClicked()
|
||||
{
|
||||
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
|
||||
QApplication::setActiveWindow(this);
|
||||
activateWindow();
|
||||
emit maximizeClient();
|
||||
}
|
||||
|
||||
449
cockatrice/src/client/tabs/tab_replays.cpp
Normal file
449
cockatrice/src/client/tabs/tab_replays.cpp
Normal file
@@ -0,0 +1,449 @@
|
||||
#include "tab_replays.h"
|
||||
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/remote/remote_replay_list_tree_widget.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "pb/command_replay_delete_match.pb.h"
|
||||
#include "pb/command_replay_download.pb.h"
|
||||
#include "pb/command_replay_modify_match.pb.h"
|
||||
#include "pb/event_replay_added.pb.h"
|
||||
#include "pb/game_replay.pb.h"
|
||||
#include "pb/response.pb.h"
|
||||
#include "pb/response_replay_download.pb.h"
|
||||
#include "tab_game.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileSystemModel>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
|
||||
{
|
||||
localDirModel = new QFileSystemModel(this);
|
||||
localDirModel->setRootPath(SettingsCache::instance().getReplaysPath());
|
||||
localDirModel->sort(0, Qt::AscendingOrder);
|
||||
|
||||
localDirView = new QTreeView;
|
||||
localDirView->setModel(localDirModel);
|
||||
localDirView->setColumnHidden(1, true);
|
||||
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
|
||||
localDirView->setSortingEnabled(true);
|
||||
localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
|
||||
// Left side layout
|
||||
/* put an invisible dummy QToolBar in the leftmost column so that the main toolbar is centered.
|
||||
* Really ugly workaround, but I couldn't figure out the proper way to make it centered */
|
||||
QToolBar *dummyToolBar = new QToolBar(this);
|
||||
QSizePolicy sizePolicy = dummyToolBar->sizePolicy();
|
||||
sizePolicy.setRetainSizeWhenHidden(true);
|
||||
dummyToolBar->setSizePolicy(sizePolicy);
|
||||
dummyToolBar->setVisible(false);
|
||||
|
||||
leftToolBar = new QToolBar(this);
|
||||
leftToolBar->setOrientation(Qt::Horizontal);
|
||||
leftToolBar->setIconSize(QSize(32, 32));
|
||||
|
||||
QToolBar *leftRightmostToolBar = new QToolBar(this);
|
||||
leftRightmostToolBar->setOrientation(Qt::Horizontal);
|
||||
leftRightmostToolBar->setIconSize(QSize(32, 32));
|
||||
|
||||
QGridLayout *leftToolBarLayout = new QGridLayout;
|
||||
leftToolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft);
|
||||
leftToolBarLayout->addWidget(leftToolBar, 0, 1, Qt::AlignHCenter);
|
||||
leftToolBarLayout->addWidget(leftRightmostToolBar, 0, 2, Qt::AlignRight);
|
||||
|
||||
QVBoxLayout *leftVbox = new QVBoxLayout;
|
||||
leftVbox->addWidget(localDirView);
|
||||
leftVbox->addLayout(leftToolBarLayout);
|
||||
leftGroupBox = new QGroupBox;
|
||||
leftGroupBox->setLayout(leftVbox);
|
||||
|
||||
// Right side layout
|
||||
rightToolBar = new QToolBar;
|
||||
rightToolBar->setOrientation(Qt::Horizontal);
|
||||
rightToolBar->setIconSize(QSize(32, 32));
|
||||
QHBoxLayout *rightToolBarLayout = new QHBoxLayout;
|
||||
rightToolBarLayout->addStretch();
|
||||
rightToolBarLayout->addWidget(rightToolBar);
|
||||
rightToolBarLayout->addStretch();
|
||||
|
||||
serverDirView = new RemoteReplayList_TreeWidget(client);
|
||||
|
||||
QVBoxLayout *rightVbox = new QVBoxLayout;
|
||||
rightVbox->addWidget(serverDirView);
|
||||
rightVbox->addLayout(rightToolBarLayout);
|
||||
rightGroupBox = new QGroupBox;
|
||||
rightGroupBox->setLayout(rightVbox);
|
||||
|
||||
// combine layouts
|
||||
QHBoxLayout *hbox = new QHBoxLayout;
|
||||
hbox->addWidget(leftGroupBox);
|
||||
hbox->addWidget(rightGroupBox);
|
||||
|
||||
// Left side actions
|
||||
aOpenLocalReplay = new QAction(this);
|
||||
aOpenLocalReplay->setIcon(QPixmap("theme:icons/view"));
|
||||
connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay()));
|
||||
connect(localDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenLocalReplay()));
|
||||
aRenameLocal = new QAction(this);
|
||||
aRenameLocal->setIcon(QPixmap("theme:icons/pencil"));
|
||||
connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal);
|
||||
aNewLocalFolder = new QAction(this);
|
||||
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
|
||||
connect(aNewLocalFolder, &QAction::triggered, this, &TabReplays::actNewLocalFolder);
|
||||
aDeleteLocalReplay = new QAction(this);
|
||||
aDeleteLocalReplay->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteLocalReplay, SIGNAL(triggered()), this, SLOT(actDeleteLocalReplay()));
|
||||
|
||||
aOpenReplaysFolder = new QAction(this);
|
||||
aOpenReplaysFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon));
|
||||
connect(aOpenReplaysFolder, &QAction::triggered, this, &TabReplays::actOpenReplaysFolder);
|
||||
|
||||
// Right side actions
|
||||
aOpenRemoteReplay = new QAction(this);
|
||||
aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view"));
|
||||
connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay()));
|
||||
connect(serverDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenRemoteReplay()));
|
||||
aDownload = new QAction(this);
|
||||
aDownload->setIcon(QPixmap("theme:icons/arrow_left_green"));
|
||||
connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload()));
|
||||
aKeep = new QAction(this);
|
||||
aKeep->setIcon(QPixmap("theme:icons/lock"));
|
||||
connect(aKeep, SIGNAL(triggered()), this, SLOT(actKeepRemoteReplay()));
|
||||
aDeleteRemoteReplay = new QAction(this);
|
||||
aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteRemoteReplay, SIGNAL(triggered()), this, SLOT(actDeleteRemoteReplay()));
|
||||
|
||||
// Add actions to toolbars
|
||||
leftToolBar->addAction(aOpenLocalReplay);
|
||||
leftToolBar->addAction(aRenameLocal);
|
||||
leftToolBar->addAction(aNewLocalFolder);
|
||||
leftToolBar->addAction(aDeleteLocalReplay);
|
||||
|
||||
leftRightmostToolBar->addAction(aOpenReplaysFolder);
|
||||
|
||||
rightToolBar->addAction(aOpenRemoteReplay);
|
||||
rightToolBar->addAction(aDownload);
|
||||
rightToolBar->addAction(aKeep);
|
||||
rightToolBar->addAction(aDeleteRemoteReplay);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(hbox);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this,
|
||||
SLOT(replayAddedEventReceived(const Event_ReplayAdded &)));
|
||||
}
|
||||
|
||||
void TabReplays::retranslateUi()
|
||||
{
|
||||
leftGroupBox->setTitle(tr("Local file system"));
|
||||
rightGroupBox->setTitle(tr("Server replay storage"));
|
||||
|
||||
aOpenLocalReplay->setText(tr("Watch replay"));
|
||||
aRenameLocal->setText(tr("Rename"));
|
||||
aNewLocalFolder->setText(tr("New folder"));
|
||||
aDeleteLocalReplay->setText(tr("Delete"));
|
||||
aOpenReplaysFolder->setText(tr("Open replays folder"));
|
||||
aOpenRemoteReplay->setText(tr("Watch replay"));
|
||||
aDownload->setText(tr("Download replay"));
|
||||
aKeep->setText(tr("Toggle expiration lock"));
|
||||
aDeleteRemoteReplay->setText(tr("Delete"));
|
||||
}
|
||||
|
||||
void TabReplays::actLocalDoubleClick(const QModelIndex &curLeft)
|
||||
{
|
||||
if (!localDirModel->isDir(curLeft)) {
|
||||
actOpenLocalReplay();
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::actOpenLocalReplay()
|
||||
{
|
||||
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
for (const auto &curLeft : curLefts) {
|
||||
if (localDirModel->isDir(curLeft))
|
||||
continue;
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
continue;
|
||||
QByteArray _data = f.readAll();
|
||||
f.close();
|
||||
|
||||
GameReplay *replay = new GameReplay;
|
||||
replay->ParseFromArray(_data.data(), _data.size());
|
||||
|
||||
emit openReplay(replay);
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::actRenameLocal()
|
||||
{
|
||||
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
for (const auto &curLeft : curLefts) {
|
||||
const QFileInfo info = localDirModel->fileInfo(curLeft);
|
||||
|
||||
const QString oldName = info.baseName();
|
||||
const QString title = info.isDir() ? tr("Rename local folder") : tr("Rename local file");
|
||||
|
||||
bool ok;
|
||||
QString newName = QInputDialog::getText(this, title, tr("New name:"), QLineEdit::Normal, oldName, &ok);
|
||||
if (!ok) { // terminate all remaining selections if user cancels
|
||||
return;
|
||||
}
|
||||
if (newName.isEmpty() || oldName == newName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString newFileName = newName;
|
||||
if (!info.suffix().isEmpty()) {
|
||||
newFileName += "." + info.suffix();
|
||||
}
|
||||
const QString newFilePath = QFileInfo(info.dir(), newFileName).filePath();
|
||||
|
||||
if (!QFile::rename(info.filePath(), newFilePath)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Rename failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::actNewLocalFolder()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
|
||||
QModelIndex dirIndex;
|
||||
if (curLeft.isValid() && !localDirModel->isDir(curLeft)) {
|
||||
dirIndex = curLeft.parent();
|
||||
} else {
|
||||
dirIndex = curLeft;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
QString folderName =
|
||||
QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok);
|
||||
if (!ok || folderName.isEmpty())
|
||||
return;
|
||||
|
||||
localDirModel->mkdir(dirIndex, folderName);
|
||||
}
|
||||
|
||||
void TabReplays::actDeleteLocalReplay()
|
||||
{
|
||||
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
|
||||
if (curLefts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &curLeft : curLefts) {
|
||||
if (curLeft.isValid()) {
|
||||
localDirModel->remove(curLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::actOpenReplaysFolder()
|
||||
{
|
||||
QString dir = localDirModel->rootPath();
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
|
||||
}
|
||||
|
||||
void TabReplays::actRemoteDoubleClick(const QModelIndex &curRight)
|
||||
{
|
||||
if (serverDirView->getReplay(curRight)) {
|
||||
actOpenRemoteReplay();
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::actOpenRemoteReplay()
|
||||
{
|
||||
auto const curRights = serverDirView->getSelectedReplays();
|
||||
|
||||
for (const auto curRight : curRights) {
|
||||
if (!curRight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Command_ReplayDownload cmd;
|
||||
cmd.set_replay_id(curRight->replay_id());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(openRemoteReplayFinished(const Response &)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::openRemoteReplayFinished(const Response &r)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
|
||||
GameReplay *replay = new GameReplay;
|
||||
replay->ParseFromString(resp.replay_data());
|
||||
|
||||
emit openReplay(replay);
|
||||
}
|
||||
|
||||
void TabReplays::actDownload()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
while (!localDirModel->isDir(curLeft)) {
|
||||
curLeft = curLeft.parent();
|
||||
}
|
||||
|
||||
for (const auto curRight : serverDirView->selectionModel()->selectedRows()) {
|
||||
downloadNodeAtIndex(curLeft, curRight);
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight)
|
||||
{
|
||||
if (const auto replayMatch = serverDirView->getReplayMatch(curRight)) {
|
||||
// node at index is a folder
|
||||
const QString name =
|
||||
QString::number(replayMatch->game_id()) + "_" + QString::fromStdString(replayMatch->game_name());
|
||||
|
||||
const auto dirIndex = curLeft.isValid() ? curLeft : localDirModel->index(localDirModel->rootPath());
|
||||
const auto newDirIndex = localDirModel->mkdir(dirIndex, name);
|
||||
|
||||
int rows = serverDirView->model()->rowCount(curRight);
|
||||
for (int i = 0; i < rows; i++) {
|
||||
const auto childIndex = serverDirView->model()->index(i, 0, curRight);
|
||||
downloadNodeAtIndex(newDirIndex, childIndex);
|
||||
}
|
||||
} else if (const auto replay = serverDirView->getReplay(curRight)) {
|
||||
// node at index is a replay
|
||||
const QString dirPath = curLeft.isValid() ? localDirModel->filePath(curLeft) : localDirModel->rootPath();
|
||||
const QString filePath = dirPath + QString("/replay_%1.cor").arg(replay->replay_id());
|
||||
|
||||
Command_ReplayDownload cmd;
|
||||
cmd.set_replay_id(replay->replay_id());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
pend->setExtraData(filePath);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
// node at index was invalid
|
||||
}
|
||||
|
||||
void TabReplays::downloadFinished(const Response &r,
|
||||
const CommandContainer & /* commandContainer */,
|
||||
const QVariant &extraData)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
|
||||
QString filePath = extraData.toString();
|
||||
|
||||
const std::string &_data = resp.replay_data();
|
||||
QFile f(filePath);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write((const char *)_data.data(), _data.size());
|
||||
f.close();
|
||||
}
|
||||
|
||||
void TabReplays::actKeepRemoteReplay()
|
||||
{
|
||||
const auto curRights = serverDirView->getSelectedReplayMatches();
|
||||
|
||||
if (curRights.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto curRight : curRights) {
|
||||
Command_ReplayModifyMatch cmd;
|
||||
cmd.set_game_id(curRight->game_id());
|
||||
cmd.set_do_not_hide(!curRight->do_not_hide());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(keepRemoteReplayFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Command_ReplayModifyMatch &cmd =
|
||||
commandContainer.session_command(0).GetExtension(Command_ReplayModifyMatch::ext);
|
||||
|
||||
ServerInfo_ReplayMatch temp;
|
||||
temp.set_do_not_hide(cmd.do_not_hide());
|
||||
|
||||
serverDirView->updateMatchInfo(cmd.game_id(), temp);
|
||||
}
|
||||
|
||||
void TabReplays::actDeleteRemoteReplay()
|
||||
{
|
||||
const auto curRights = serverDirView->getSelectedReplayMatches();
|
||||
|
||||
if (curRights.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (QMessageBox::warning(this, tr("Delete remote replay"),
|
||||
tr("Are you sure you want to delete the selected replays?"),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto curRight : curRights) {
|
||||
Command_ReplayDeleteMatch cmd;
|
||||
cmd.set_game_id(curRight->game_id());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(deleteRemoteReplayFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
}
|
||||
|
||||
void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Command_ReplayDeleteMatch &cmd =
|
||||
commandContainer.session_command(0).GetExtension(Command_ReplayDeleteMatch::ext);
|
||||
serverDirView->removeMatchInfo(cmd.game_id());
|
||||
}
|
||||
|
||||
void TabReplays::replayAddedEventReceived(const Event_ReplayAdded &event)
|
||||
{
|
||||
if (event.has_match_info()) {
|
||||
// 99.9% of events will have match info (Normal Workflow)
|
||||
serverDirView->addMatchInfo(event.match_info());
|
||||
} else {
|
||||
// When a Moderator force adds a replay, we need to refresh their view
|
||||
serverDirView->refreshTree();
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,22 @@ private:
|
||||
RemoteReplayList_TreeWidget *serverDirView;
|
||||
QGroupBox *leftGroupBox, *rightGroupBox;
|
||||
|
||||
QAction *aOpenLocalReplay, *aDeleteLocalReplay, *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay;
|
||||
private slots:
|
||||
void actOpenLocalReplay();
|
||||
QAction *aOpenLocalReplay, *aRenameLocal, *aNewLocalFolder, *aDeleteLocalReplay;
|
||||
QAction *aOpenReplaysFolder;
|
||||
QAction *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay;
|
||||
|
||||
void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight);
|
||||
|
||||
private slots:
|
||||
void actLocalDoubleClick(const QModelIndex &curLeft);
|
||||
void actRenameLocal();
|
||||
void actOpenLocalReplay();
|
||||
void actNewLocalFolder();
|
||||
void actDeleteLocalReplay();
|
||||
|
||||
void actOpenReplaysFolder();
|
||||
|
||||
void actRemoteDoubleClick(const QModelIndex &curLeft);
|
||||
void actOpenRemoteReplay();
|
||||
void openRemoteReplayFinished(const Response &r);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#include "tab_room.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "chatview/chatview.h"
|
||||
#include "dlg_settings.h"
|
||||
#include "gameselector.h"
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../dialogs/dlg_settings.h"
|
||||
#include "../../game/game_selector.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/chat_view/chat_view.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "get_pb_extension.h"
|
||||
#include "main.h"
|
||||
#include "pb/event_join_room.pb.h"
|
||||
#include "pb/event_leave_room.pb.h"
|
||||
#include "pb/event_list_games.pb.h"
|
||||
@@ -13,12 +16,9 @@
|
||||
#include "pb/event_room_say.pb.h"
|
||||
#include "pb/room_commands.pb.h"
|
||||
#include "pb/serverinfo_room.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "settingscache.h"
|
||||
#include "stringsizes.h"
|
||||
#include "tab_account.h"
|
||||
#include "tab_supervisor.h"
|
||||
#include "userlist.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCompleter>
|
||||
@@ -155,7 +155,7 @@ void TabRoom::retranslateUi()
|
||||
|
||||
void TabRoom::focusTab()
|
||||
{
|
||||
QApplication::setActiveWindow(this);
|
||||
activateWindow();
|
||||
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
|
||||
emit maximizeClient();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef TAB_ROOM_H
|
||||
#define TAB_ROOM_H
|
||||
|
||||
#include "lineeditcompleter.h"
|
||||
#include "../ui/line_edit_completer.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QFocusEvent>
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "tab_server.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "pb/event_list_rooms.pb.h"
|
||||
#include "pb/event_server_message.pb.h"
|
||||
#include "pb/response_join_room.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
#include "pending_command.h"
|
||||
#include "tab_supervisor.h"
|
||||
#include "userlist.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "tab_supervisor.h"
|
||||
|
||||
#include "abstractclient.h"
|
||||
#include "main.h"
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../ui/pixel_map_generator.h"
|
||||
#include "pb/event_game_joined.pb.h"
|
||||
#include "pb/event_notify_user.pb.h"
|
||||
#include "pb/event_user_message.pb.h"
|
||||
@@ -11,8 +14,6 @@
|
||||
#include "pb/room_event.pb.h"
|
||||
#include "pb/serverinfo_room.pb.h"
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
#include "pixmapgenerator.h"
|
||||
#include "settingscache.h"
|
||||
#include "tab_account.h"
|
||||
#include "tab_admin.h"
|
||||
#include "tab_deck_editor.h"
|
||||
@@ -23,7 +24,6 @@
|
||||
#include "tab_replays.h"
|
||||
#include "tab_room.h"
|
||||
#include "tab_server.h"
|
||||
#include "userlist.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
@@ -496,6 +496,7 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
|
||||
if (deckToOpen)
|
||||
tab->setDeck(new DeckLoader(*deckToOpen));
|
||||
connect(tab, SIGNAL(deckEditorClosing(TabDeckEditor *)), this, SLOT(deckEditorClosed(TabDeckEditor *)));
|
||||
connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *)));
|
||||
int tabIndex = myAddTab(tab);
|
||||
addCloseButtonToTab(tab, tabIndex);
|
||||
deckEditorTabs.append(tab);
|
||||
@@ -541,7 +542,7 @@ void TabSupervisor::processGameEventContainer(const GameEventContainer &cont)
|
||||
{
|
||||
TabGame *tab = gameTabs.value(cont.game_id());
|
||||
if (tab)
|
||||
tab->processGameEventContainer(cont, qobject_cast<AbstractClient *>(sender()));
|
||||
tab->processGameEventContainer(cont, qobject_cast<AbstractClient *>(sender()), {});
|
||||
else
|
||||
qDebug() << "gameEvent: invalid gameId";
|
||||
}
|
||||
@@ -687,12 +688,12 @@ void TabSupervisor::processNotifyUserEvent(const Event_NotifyUser &event)
|
||||
|
||||
bool TabSupervisor::isOwnUserRegistered() const
|
||||
{
|
||||
return static_cast<bool>(getUserInfo()->user_level() & ServerInfo_User::IsRegistered);
|
||||
return userInfo != nullptr && (userInfo->user_level() & ServerInfo_User::IsRegistered) != 0;
|
||||
}
|
||||
|
||||
QString TabSupervisor::getOwnUsername() const
|
||||
{
|
||||
return userInfo ? QString::fromStdString(userInfo->name()) : QString();
|
||||
return userInfo != nullptr ? QString::fromStdString(userInfo->name()) : QString();
|
||||
}
|
||||
|
||||
bool TabSupervisor::isUserBuddy(const QString &userName) const
|
||||
@@ -729,9 +730,22 @@ const ServerInfo_User *TabSupervisor::getOnlineUser(const QString &userName) con
|
||||
|
||||
for (i = userList.begin(); i != userList.end(); ++i)
|
||||
if (i.key().toLower() == userNameToMatchLower) {
|
||||
const ServerInfo_User &userInfo = i.value()->getUserInfo();
|
||||
return &userInfo;
|
||||
const ServerInfo_User &_userInfo = i.value()->getUserInfo();
|
||||
return &_userInfo;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
bool TabSupervisor::switchToGameTabIfAlreadyExists(const int gameId)
|
||||
{
|
||||
bool isGameTabExists = false;
|
||||
if (gameTabs.contains(gameId)) {
|
||||
isGameTabExists = true;
|
||||
TabGame *tabGame = gameTabs[gameId];
|
||||
const int gameTabIndex = indexOf(tabGame);
|
||||
setCurrentIndex(gameTabIndex);
|
||||
}
|
||||
|
||||
return isGameTabExists;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef TAB_SUPERVISOR_H
|
||||
#define TAB_SUPERVISOR_H
|
||||
|
||||
#include "chatview/userlistProxy.h"
|
||||
#include "deck_loader.h"
|
||||
#include "../../deck/deck_loader.h"
|
||||
#include "../../server/chat_view/user_list_proxy.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QCommonStyle>
|
||||
@@ -120,6 +120,7 @@ public:
|
||||
bool isUserBuddy(const QString &userName) const;
|
||||
bool isUserIgnored(const QString &userName) const;
|
||||
const ServerInfo_User *getOnlineUser(const QString &userName) const;
|
||||
bool switchToGameTabIfAlreadyExists(const int gameId);
|
||||
void actShowPopup(const QString &message);
|
||||
signals:
|
||||
void setMenu(const QList<QMenu *> &newMenuList = QList<QMenu *>());
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "tappedout_interface.h"
|
||||
#include "tapped_out_interface.h"
|
||||
|
||||
#include "decklist.h"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
|
||||
@@ -15,13 +14,13 @@ TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *par
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
{
|
||||
manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(queryFinished(QNetworkReply *)));
|
||||
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
|
||||
}
|
||||
|
||||
void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(0, tr("Error"), reply->errorString());
|
||||
QMessageBox::critical(nullptr, tr("Error"), reply->errorString());
|
||||
reply->deleteLater();
|
||||
deleteLater();
|
||||
return;
|
||||
@@ -42,26 +41,26 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
||||
* from the html. Css pseudo selector for errors: $("div.alert-danger > ul > li")
|
||||
*/
|
||||
QString data(reply->readAll());
|
||||
QString errorMessage = tr("Unable to analyze the deck.");
|
||||
QStringList errorMessageList = {tr("Unable to analyze the deck.")};
|
||||
|
||||
QRegExp rx("<div class=\"alert alert-danger.*<ul>(.*)</ul>");
|
||||
rx.setMinimal(true);
|
||||
int found = rx.indexIn(data);
|
||||
if (found >= 0) {
|
||||
QString errors = rx.cap(1);
|
||||
QRegExp rx2("<li>(.*)</li>");
|
||||
rx2.setMinimal(true);
|
||||
|
||||
int captures = rx2.captureCount();
|
||||
for (int i = 1; i <= captures; i++) {
|
||||
errorMessage += QString("\n") + rx2.cap(i).remove(QRegularExpression("<[^>]*>")).simplified();
|
||||
static const QRegularExpression rx("<div class=\"alert alert-danger.*?<ul>(.*?)</ul>");
|
||||
auto match = rx.match(data);
|
||||
if (match.hasMatch()) {
|
||||
QString errors = match.captured(1);
|
||||
static const QRegularExpression rx2("<li>(.*?)</li>");
|
||||
static const QRegularExpression rxremove("<[^>]*>");
|
||||
auto matchIterator = rx2.globalMatch(errors);
|
||||
while (matchIterator.hasNext()) {
|
||||
auto match2 = matchIterator.next();
|
||||
errorMessageList.append(match2.captured(1).remove(rxremove).simplified());
|
||||
}
|
||||
}
|
||||
|
||||
QString errorMessage = errorMessageList.join("\n");
|
||||
qDebug() << "Tappedout: bad reply, http status" << httpStatus << "size" << data.size() << "message"
|
||||
<< errorMessage;
|
||||
|
||||
QMessageBox::critical(0, tr("Error"), errorMessage);
|
||||
QMessageBox::critical(nullptr, tr("Error"), errorMessage);
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef TAPPEDOUT_INTERFACE_H
|
||||
#define TAPPEDOUT_INTERFACE_H
|
||||
|
||||
#include "carddatabase.h"
|
||||
#include "../game/cards/card_database.h"
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QObject>
|
||||
@@ -1,29 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "settingscache.h"
|
||||
#include "../settings/cache_settings.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
class TearOffMenu : public QMenu
|
||||
{
|
||||
public:
|
||||
TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
|
||||
explicit TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
|
||||
{
|
||||
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
|
||||
[=](bool state) { setTearOffEnabled(state); });
|
||||
[this](const bool state) { setTearOffEnabled(state); });
|
||||
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
|
||||
}
|
||||
|
||||
TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
|
||||
explicit TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
|
||||
{
|
||||
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
|
||||
[=](bool state) { setTearOffEnabled(state); });
|
||||
[this](const bool state) { setTearOffEnabled(state); });
|
||||
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
|
||||
}
|
||||
|
||||
TearOffMenu *addTearOffMenu(const QString &title)
|
||||
{
|
||||
TearOffMenu *menu = new TearOffMenu(title, this);
|
||||
auto *menu = new TearOffMenu(title, this);
|
||||
addMenu(menu);
|
||||
return menu;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "translatecountername.h"
|
||||
#include "translate_counter_name.h"
|
||||
|
||||
const QMap<QString, QString> TranslateCounterName::translated = {
|
||||
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},
|
||||
337
cockatrice/src/client/ui/layouts/flow_layout.cpp
Normal file
337
cockatrice/src/client/ui/layouts/flow_layout.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* @file flow_layout.cpp
|
||||
* @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets
|
||||
* in rows within the constraints of available width or parent scroll areas.
|
||||
*/
|
||||
|
||||
#include "flow_layout.h"
|
||||
|
||||
#include "../widgets/general/layout_containers/flow_widget.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QLayoutItem>
|
||||
#include <QScrollArea>
|
||||
#include <QStyle>
|
||||
|
||||
/**
|
||||
* @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values.
|
||||
* @param parent The parent widget for this layout.
|
||||
* @param margin The layout margin.
|
||||
* @param hSpacing The horizontal spacing between items.
|
||||
* @param vSpacing The vertical spacing between items.
|
||||
*/
|
||||
FlowLayout::FlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
|
||||
: QLayout(parent), horizontalMargin(hSpacing), verticalMargin(vSpacing)
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor for FlowLayout, which cleans up all items in the layout.
|
||||
*/
|
||||
FlowLayout::~FlowLayout()
|
||||
{
|
||||
QLayoutItem *item;
|
||||
while ((item = FlowLayout::takeAt(0))) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates the layout's support for expansion in both horizontal and vertical directions.
|
||||
* @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill.
|
||||
*/
|
||||
Qt::Orientations FlowLayout::expandingDirections() const
|
||||
{
|
||||
return Qt::Horizontal | Qt::Vertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates that this layout's height depends on its width.
|
||||
* @return True, as the layout adjusts its height to fit the specified width.
|
||||
*/
|
||||
bool FlowLayout::hasHeightForWidth() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the required height to display all items within the specified width.
|
||||
* @param width The available width for arranging items.
|
||||
* @return The total height needed to fit all items in rows constrained by the specified width.
|
||||
*/
|
||||
int FlowLayout::heightForWidth(const int width) const
|
||||
{
|
||||
int height = 0;
|
||||
int rowWidth = 0;
|
||||
int rowHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int itemWidth = item->sizeHint().width() + horizontalSpacing();
|
||||
if (rowWidth + itemWidth > width) { // Start a new row if the row width exceeds available width
|
||||
height += rowHeight + verticalSpacing();
|
||||
rowWidth = itemWidth;
|
||||
rowHeight = item->sizeHint().height() + verticalSpacing();
|
||||
} else {
|
||||
rowWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, item->sizeHint().height());
|
||||
}
|
||||
}
|
||||
height += rowHeight; // Add the final row's height
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges layout items in rows within the specified rectangle bounds.
|
||||
* @param rect The area within which to position layout items.
|
||||
*/
|
||||
void FlowLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
|
||||
|
||||
int left, top, right, bottom;
|
||||
getContentsMargins(&left, &top, &right, &bottom); // Retrieves the layout's content margins.
|
||||
|
||||
// Adjust the rectangle to exclude margins.
|
||||
const QRect adjustedRect = rect.adjusted(+left, +top, -right, -bottom);
|
||||
|
||||
// Calculate the available width for items, considering either the adjusted rectangle's width
|
||||
// or the parent scroll area width, if applicable.
|
||||
const int availableWidth = qMax(adjustedRect.width(), getParentScrollAreaWidth());
|
||||
|
||||
// Arrange all rows of items within the available width and get the total height used.
|
||||
const int totalHeight = layoutAllRows(adjustedRect.x(), adjustedRect.y(), availableWidth);
|
||||
|
||||
// If the layout's parent is a QWidget, update its minimum size to ensure it can accommodate
|
||||
// the arranged items' dimensions.
|
||||
if (QWidget *parentWidgetPtr = parentWidget()) {
|
||||
parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges items in rows based on the available width.
|
||||
* Items are added to a row until the row's width exceeds `availableWidth`.
|
||||
* Then, a new row is started.
|
||||
* @param originX The starting x-coordinate for the row layout.
|
||||
* @param originY The starting y-coordinate for the row layout.
|
||||
* @param availableWidth The available width to lay out items.
|
||||
* @return The y-coordinate of the final row's end position.
|
||||
*/
|
||||
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
|
||||
{
|
||||
QVector<QLayoutItem *> rowItems; // Temporary storage for items in the current row.
|
||||
int currentXPosition = originX; // Tracks the x-coordinate for placing items in the current row.
|
||||
int currentYPosition = originY; // Tracks the y-coordinate, updated after each row.
|
||||
|
||||
int rowHeight = 0; // Tracks the maximum height of items in the current row.
|
||||
|
||||
// Iterate through all layout items to arrange them.
|
||||
for (QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the item.
|
||||
const int itemWidth = itemSize.width() + horizontalSpacing();
|
||||
|
||||
// Check if the item fits in the current row's remaining width.
|
||||
if (currentXPosition + itemWidth > availableWidth) {
|
||||
// If not, layout the current row and start a new row.
|
||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
||||
rowItems.clear(); // Clear the temporary storage for the new row.
|
||||
currentXPosition = originX; // Reset x-position to the start of the new row.
|
||||
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down for the new row.
|
||||
rowHeight = 0; // Reset row height for the new row.
|
||||
}
|
||||
|
||||
// Add the item to the current row.
|
||||
rowItems.append(item);
|
||||
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row height to the tallest item.
|
||||
currentXPosition += itemSize.width() + horizontalSpacing(); // Move x-position for the next item.
|
||||
}
|
||||
|
||||
// Layout the final row if there are remaining items.
|
||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
||||
|
||||
currentYPosition += rowHeight; // Add the final row's height
|
||||
return currentYPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function for arranging a single row of items within specified bounds.
|
||||
* @param rowItems Items to be arranged in the row.
|
||||
* @param x The x-coordinate for starting the row.
|
||||
* @param y The y-coordinate for starting the row.
|
||||
*/
|
||||
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
|
||||
{
|
||||
// Iterate through each item in the row and position it.
|
||||
for (QLayoutItem *item : rowItems) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemMaxSize = item->widget()->maximumSize(); // Get the item's maximum allowable size.
|
||||
// Constrain the item's width and height to its size hint or maximum size.
|
||||
int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
||||
int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
||||
// Set the item's geometry based on the calculated size and position.
|
||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||
// Move the x-position for the next item, including horizontal spacing.
|
||||
x += itemWidth + horizontalSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the preferred size for this layout.
|
||||
* @return The maximum of all item size hints as a QSize.
|
||||
*/
|
||||
QSize FlowLayout::sizeHint() const
|
||||
{
|
||||
QSize size;
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item != nullptr && !item->isEmpty()) {
|
||||
size = size.expandedTo(item->sizeHint());
|
||||
}
|
||||
}
|
||||
return size.isValid() ? size : QSize(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the minimum size required to display all layout items.
|
||||
* @return The minimum QSize needed by the layout.
|
||||
*/
|
||||
QSize FlowLayout::minimumSize() const
|
||||
{
|
||||
QSize size;
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item != nullptr && !item->isEmpty()) {
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
}
|
||||
}
|
||||
|
||||
size.setWidth(qMin(size.width(), getParentScrollAreaWidth()));
|
||||
size.setHeight(qMin(size.height(), getParentScrollAreaHeight()));
|
||||
|
||||
return size.isValid() ? size : QSize(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a new item to the layout.
|
||||
* @param item The layout item to add.
|
||||
*/
|
||||
void FlowLayout::addItem(QLayoutItem *item)
|
||||
{
|
||||
if (item != nullptr) {
|
||||
items.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the count of items in the layout.
|
||||
* @return The number of layout items.
|
||||
*/
|
||||
int FlowLayout::count() const
|
||||
{
|
||||
return static_cast<int>(items.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the layout item at the specified index.
|
||||
* @param index The index of the item to retrieve.
|
||||
* @return A pointer to the item at the specified index, or nullptr if out of range.
|
||||
*/
|
||||
QLayoutItem *FlowLayout::itemAt(const int index) const
|
||||
{
|
||||
return (index >= 0 && index < items.size()) ? items.value(index) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes and returns the item at the specified index.
|
||||
* @param index The index of the item to remove.
|
||||
* @return A pointer to the removed item, or nullptr if out of range.
|
||||
*/
|
||||
QLayoutItem *FlowLayout::takeAt(const int index)
|
||||
{
|
||||
return (index >= 0 && index < items.size()) ? items.takeAt(index) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the horizontal spacing between items.
|
||||
* @return The horizontal spacing if set, otherwise a smart default.
|
||||
*/
|
||||
int FlowLayout::horizontalSpacing() const
|
||||
{
|
||||
return (horizontalMargin >= 0) ? horizontalMargin : smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the vertical spacing between items.
|
||||
* @return The vertical spacing if set, otherwise a smart default.
|
||||
*/
|
||||
int FlowLayout::verticalSpacing() const
|
||||
{
|
||||
return (verticalMargin >= 0) ? verticalMargin : smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates smart spacing based on the parent widget style.
|
||||
* @param pm The pixel metric to calculate.
|
||||
* @return The calculated spacing value.
|
||||
*/
|
||||
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
|
||||
{
|
||||
QObject *parent = this->parent();
|
||||
|
||||
if (!parent) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (parent->isWidgetType()) {
|
||||
const auto *pw = dynamic_cast<QWidget *>(parent);
|
||||
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||
}
|
||||
|
||||
return dynamic_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the width of the parent scroll area, if any.
|
||||
* @return The width of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaWidth() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->width();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the height of the parent scroll area, if any.
|
||||
* @return The height of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaHeight() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->height();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
43
cockatrice/src/client/ui/layouts/flow_layout.h
Normal file
43
cockatrice/src/client/ui/layouts/flow_layout.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef FLOW_LAYOUT_H
|
||||
#define FLOW_LAYOUT_H
|
||||
|
||||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
#include <qstyle.h>
|
||||
|
||||
class FlowLayout : public QLayout
|
||||
{
|
||||
public:
|
||||
explicit FlowLayout(QWidget *parent = nullptr);
|
||||
FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing);
|
||||
~FlowLayout() override;
|
||||
|
||||
void addItem(QLayoutItem *item) override;
|
||||
[[nodiscard]] int count() const override;
|
||||
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
|
||||
QLayoutItem *takeAt(int index) override;
|
||||
[[nodiscard]] int horizontalSpacing() const;
|
||||
|
||||
[[nodiscard]] Qt::Orientations expandingDirections() const override;
|
||||
[[nodiscard]] bool hasHeightForWidth() const override;
|
||||
[[nodiscard]] int heightForWidth(int width) const override;
|
||||
[[nodiscard]] int verticalSpacing() const;
|
||||
[[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const;
|
||||
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
[[nodiscard]] int getParentScrollAreaWidth() const;
|
||||
[[nodiscard]] int getParentScrollAreaHeight() const;
|
||||
|
||||
void setGeometry(const QRect &rect) override;
|
||||
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
||||
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
|
||||
[[nodiscard]] QSize sizeHint() const override;
|
||||
[[nodiscard]] QSize minimumSize() const override;
|
||||
|
||||
protected:
|
||||
QList<QLayoutItem *> items; // List to store layout items
|
||||
int horizontalMargin;
|
||||
int verticalMargin;
|
||||
};
|
||||
|
||||
#endif // FLOW_LAYOUT_H
|
||||
142
cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp
Normal file
142
cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "horizontal_flow_layout.h"
|
||||
|
||||
/**
|
||||
* @brief Constructs a HorizontalFlowLayout instance with the specified parent widget.
|
||||
* This layout arranges items in columns within the given height, automatically adjusting its width.
|
||||
* @param parent The parent widget to which this layout belongs.
|
||||
* @param margin The layout margin.
|
||||
* @param hSpacing The horizontal spacing between items.
|
||||
* @param vSpacing The vertical spacing between items.
|
||||
*/
|
||||
HorizontalFlowLayout::HorizontalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
|
||||
: FlowLayout(parent, margin, hSpacing, vSpacing)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor for HorizontalFlowLayout, responsible for cleaning up layout items.
|
||||
*/
|
||||
HorizontalFlowLayout::~HorizontalFlowLayout()
|
||||
{
|
||||
QLayoutItem *item;
|
||||
while ((item = FlowLayout::takeAt(0))) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the required width to display all items, given a specified height.
|
||||
* This method arranges items into columns and determines the total width needed.
|
||||
* @param height The available height for arranging layout items.
|
||||
* @return The total width required to fit all items, organized in columns constrained by the given height.
|
||||
*/
|
||||
int HorizontalFlowLayout::heightForWidth(const int height) const
|
||||
{
|
||||
int width = 0;
|
||||
int colWidth = 0;
|
||||
int colHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int itemHeight = item->sizeHint().height();
|
||||
if (colHeight + itemHeight > height) {
|
||||
width += colWidth;
|
||||
colHeight = itemHeight;
|
||||
colWidth = item->sizeHint().width();
|
||||
} else {
|
||||
colHeight += itemHeight;
|
||||
colWidth = qMax(colWidth, item->sizeHint().width());
|
||||
}
|
||||
}
|
||||
width += colWidth; // Add width of the last column
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the geometry of the layout items, arranging them in columns within the given height.
|
||||
* @param rect The rectangle area defining the layout space.
|
||||
*/
|
||||
void HorizontalFlowLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
|
||||
|
||||
const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
|
||||
|
||||
if (QWidget *parentWidgetPtr = parentWidget()) {
|
||||
parentWidgetPtr->setMinimumSize(totalWidth, availableHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lays out items into columns according to the available height, starting from a given origin.
|
||||
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
|
||||
* @param originX The x-coordinate for the layout start position.
|
||||
* @param originY The y-coordinate for the layout start position.
|
||||
* @param availableHeight The height within which each column is constrained.
|
||||
* @return The total width after arranging all columns.
|
||||
*/
|
||||
int HorizontalFlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
|
||||
{
|
||||
QVector<QLayoutItem *> colItems; // Holds items for the current column
|
||||
int currentXPosition = originX; // Tracks the x-coordinate while placing items
|
||||
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
|
||||
|
||||
int colWidth = 0; // Tracks the maximum width of items in the current column
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
||||
|
||||
// Check if the current item fits in the remaining height of the current column
|
||||
if (currentYPosition + itemSize.height() > availableHeight) {
|
||||
// If not, layout the current column and start a new column
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
colItems.clear(); // Reset the list for the new column
|
||||
currentYPosition = originY; // Reset y-position to the column's start
|
||||
currentXPosition += colWidth; // Move x-position to the next column
|
||||
colWidth = 0; // Reset column width for the new column
|
||||
}
|
||||
|
||||
// Add the item to the current column
|
||||
colItems.append(item);
|
||||
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
|
||||
currentYPosition += itemSize.height(); // Move y-position for the next item
|
||||
}
|
||||
|
||||
// Layout the final column if there are any remaining items
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
|
||||
// Return the total width used, including the last column's width
|
||||
return currentXPosition + colWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges a single column of items within specified x and y starting positions.
|
||||
* @param colItems A list of items to be arranged in the column.
|
||||
* @param x The starting x-coordinate for the column.
|
||||
* @param y The starting y-coordinate for the column.
|
||||
*/
|
||||
void HorizontalFlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
|
||||
{
|
||||
for (QLayoutItem *item : colItems) {
|
||||
if (item != nullptr && item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the maximum allowed size for the item
|
||||
QSize itemMaxSize = item->widget()->maximumSize();
|
||||
// Constrain the item's width and height to its size hint or maximum size
|
||||
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
||||
// Set the item's geometry based on the computed size and position
|
||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||
// Move the y-position down by the item's height to place the next item below
|
||||
y += itemHeight;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user