Compare commits
455 Commits
2025-03-25
...
oracle-mem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c0c8b416a | ||
|
|
65a3423009 | ||
|
|
4ddbc8d018 | ||
|
|
d4bf40694a | ||
|
|
ec98bcf95d | ||
|
|
a799cd097a | ||
|
|
b4e3f2cba9 | ||
|
|
658ae83157 | ||
|
|
d29e72ce72 | ||
|
|
30cc8ad6f9 | ||
|
|
f0ebd28148 | ||
|
|
364d0ca52b | ||
|
|
3ff2df2796 | ||
|
|
d57bec8ec6 | ||
|
|
2b64e65f45 | ||
|
|
eab4d435f8 | ||
|
|
de13c22552 | ||
|
|
8ee7163014 | ||
|
|
c5fde071e7 | ||
|
|
8abd04dab1 | ||
|
|
858361e6d3 | ||
|
|
9ece4bfd9b | ||
|
|
a1a3b02d3a | ||
|
|
bc2ae6c486 | ||
|
|
587a8bc524 | ||
|
|
122926c6cd | ||
|
|
bac6beeb50 | ||
|
|
c75a483ee6 | ||
|
|
1c5bfdbabe | ||
|
|
553952132f | ||
|
|
1931eb11a9 | ||
|
|
65aef396fb | ||
|
|
a21e45ed36 | ||
|
|
adee67115c | ||
|
|
aea468bc7f | ||
|
|
621c6a8d73 | ||
|
|
73591d5d0f | ||
|
|
846f16ddaa | ||
|
|
c46f6d1178 | ||
|
|
ab5d6db8a2 | ||
|
|
9957cb20e2 | ||
|
|
8788a7aada | ||
|
|
16392c28c5 | ||
|
|
a8ee0d7648 | ||
|
|
a405758222 | ||
|
|
537e29d937 | ||
|
|
9a3104c5ac | ||
|
|
722344967f | ||
|
|
73ce5e051c | ||
|
|
b8bbe141a0 | ||
|
|
3285596a93 | ||
|
|
73763b5ee6 | ||
|
|
27708d5964 | ||
|
|
827f22ed37 | ||
|
|
ace4063371 | ||
|
|
f62e29f5d5 | ||
|
|
5df00de246 | ||
|
|
28dfd62163 | ||
|
|
1c1599a9f4 | ||
|
|
6dff230e10 | ||
|
|
0f60824749 | ||
|
|
84e0732fb1 | ||
|
|
ae123587d7 | ||
|
|
2efcb48b7e | ||
|
|
3d9cae717d | ||
|
|
cc73a8cc85 | ||
|
|
648f028a63 | ||
|
|
840ee1379f | ||
|
|
3c85ca9cbc | ||
|
|
8e88749078 | ||
|
|
4c431e98a6 | ||
|
|
40cf3ced1a | ||
|
|
c9ccab8771 | ||
|
|
7d2700ca65 | ||
|
|
bfedc12fa8 | ||
|
|
c16267e60f | ||
|
|
0bd9b84931 | ||
|
|
e9a9475ed7 | ||
|
|
f00d415dd7 | ||
|
|
1e7ff3dbdf | ||
|
|
eb1c257484 | ||
|
|
4d652210dc | ||
|
|
9f2ac78609 | ||
|
|
484e8e64a6 | ||
|
|
e5d5dfa8d8 | ||
|
|
0ad31fea46 | ||
|
|
ec2d8f231d | ||
|
|
aeec56f800 | ||
|
|
7e6cad974f | ||
|
|
757e9f3415 | ||
|
|
6bc2293292 | ||
|
|
55aaca0e0d | ||
|
|
a8a3fca8c9 | ||
|
|
fb30515f72 | ||
|
|
9a39af6da0 | ||
|
|
6d75ce4b1c | ||
|
|
dbd1d30ca8 | ||
|
|
8f80996515 | ||
|
|
d206a70b8a | ||
|
|
bbec4d2c7e | ||
|
|
f24c36d6b1 | ||
|
|
adff828415 | ||
|
|
d914667238 | ||
|
|
1c209b3320 | ||
|
|
aa61032cdf | ||
|
|
3ae4a7d8a7 | ||
|
|
9fdecf21f2 | ||
|
|
e4d256790f | ||
|
|
d9f4faf4ec | ||
|
|
609a364971 | ||
|
|
2152ddd99b | ||
|
|
8caaf8515e | ||
|
|
ac822fa084 | ||
|
|
a265b865f6 | ||
|
|
8efc4f4817 | ||
|
|
817a3f979e | ||
|
|
8ebfc40de5 | ||
|
|
c42e953199 | ||
|
|
636aa72141 | ||
|
|
14e6e6eff4 | ||
|
|
474c1d0d89 | ||
|
|
b8983f27ab | ||
|
|
d9c65d4ae0 | ||
|
|
1ef07309d6 | ||
|
|
be1403c920 | ||
|
|
03e32f0a7c | ||
|
|
f4361d1b43 | ||
|
|
e1259e67d3 | ||
|
|
ca1b9bf75f | ||
|
|
3cff55b0bb | ||
|
|
c25b153185 | ||
|
|
9c58e6f90f | ||
|
|
cff16346ef | ||
|
|
30e6b52783 | ||
|
|
015570c833 | ||
|
|
7c31197b78 | ||
|
|
a69bfb8cb8 | ||
|
|
c5b361e94d | ||
|
|
201750c89f | ||
|
|
89a8d0f6b8 | ||
|
|
835e4af3e4 | ||
|
|
c33106eab4 | ||
|
|
bea8c3dbec | ||
|
|
b51d5d007b | ||
|
|
f8c4f774cf | ||
|
|
22c6756ce0 | ||
|
|
e318815025 | ||
|
|
0833f94502 | ||
|
|
ddbf5e1457 | ||
|
|
2a032f3116 | ||
|
|
5381562a5e | ||
|
|
f2ce5e9693 | ||
|
|
ed50fd98cd | ||
|
|
14991e1f9e | ||
|
|
5fa06746f1 | ||
|
|
d31b044529 | ||
|
|
754dd904d2 | ||
|
|
1503394662 | ||
|
|
436d69b710 | ||
|
|
891e7bf6e4 | ||
|
|
fad1280185 | ||
|
|
6187c7268f | ||
|
|
762ea47b8e | ||
|
|
23612ba6ec | ||
|
|
217646f031 | ||
|
|
91667d9ecd | ||
|
|
3501ee9a9d | ||
|
|
f0c3860032 | ||
|
|
17dcaf9afa | ||
|
|
f484c98152 | ||
|
|
46f68115b2 | ||
|
|
7ac22a6ce8 | ||
|
|
bed79ef89e | ||
|
|
54095b9a89 | ||
|
|
4b58060ab6 | ||
|
|
dbbb554735 | ||
|
|
9c3be1b851 | ||
|
|
190ab211e3 | ||
|
|
f4fbe90a72 | ||
|
|
a9cbd5a172 | ||
|
|
94ba1c83c6 | ||
|
|
9b3756e591 | ||
|
|
aff775f488 | ||
|
|
4de5274996 | ||
|
|
4e57868037 | ||
|
|
ab6b32b8ba | ||
|
|
46285a499e | ||
|
|
ce6cad5dfe | ||
|
|
d5ea86bc81 | ||
|
|
41ea424359 | ||
|
|
87b0259b97 | ||
|
|
2490e97ea0 | ||
|
|
eecfe9d387 | ||
|
|
9ca5ee52e7 | ||
|
|
fb23cc8c7a | ||
|
|
ff7ce39841 | ||
|
|
0f05d6bd74 | ||
|
|
93c15c8151 | ||
|
|
22c8268f02 | ||
|
|
216cd491cc | ||
|
|
5efc573783 | ||
|
|
bca0da6bd4 | ||
|
|
9601a1fa4e | ||
|
|
b8e545bfa4 | ||
|
|
5c16f0d027 | ||
|
|
1b4441baac | ||
|
|
0147a1d41f | ||
|
|
0f11fbe599 | ||
|
|
9c18e99fe2 | ||
|
|
6e0a7de9cc | ||
|
|
b141a65838 | ||
|
|
7f842bb1e8 | ||
|
|
bd65aae81e | ||
|
|
b8dedb568c | ||
|
|
ec94c29ed9 | ||
|
|
c77943d01c | ||
|
|
fc5fb956df | ||
|
|
2eba126ed7 | ||
|
|
da52d677c7 | ||
|
|
ab4373d025 | ||
|
|
5e88a0f0cc | ||
|
|
ba794c2b60 | ||
|
|
268559d8de | ||
|
|
473d147333 | ||
|
|
d5d9f9bedc | ||
|
|
f31d30bf84 | ||
|
|
03b216a6b4 | ||
|
|
3e6510b935 | ||
|
|
e87b35e0bb | ||
|
|
322fdb14de | ||
|
|
09381575a7 | ||
|
|
881243da6a | ||
|
|
851fad3e3f | ||
|
|
46d65f0b7e | ||
|
|
03bebbe4c2 | ||
|
|
1649f30389 | ||
|
|
38f76d449a | ||
|
|
f2cbdae829 | ||
|
|
3a42354efd | ||
|
|
fe7853a389 | ||
|
|
06738cae93 | ||
|
|
d6243a2dd2 | ||
|
|
04be0fe634 | ||
|
|
fd12a1f6be | ||
|
|
e10dd4ef42 | ||
|
|
62c02e3fce | ||
|
|
ae2c55c33b | ||
|
|
4a2a646943 | ||
|
|
ae47ee802b | ||
|
|
4fd2f1f974 | ||
|
|
b9f16e8cce | ||
|
|
70b4843bc4 | ||
|
|
95190c321c | ||
|
|
a9b3be33e0 | ||
|
|
e05dad4267 | ||
|
|
83b90d472f | ||
|
|
ee4ff6e732 | ||
|
|
2267d38352 | ||
|
|
4fbb47300e | ||
|
|
836e168a6c | ||
|
|
a9684f67cc | ||
|
|
686e90d0ed | ||
|
|
0b9b39fef7 | ||
|
|
67a3b03b07 | ||
|
|
388db4e995 | ||
|
|
a28a1aa601 | ||
|
|
ed82106359 | ||
|
|
c57b84cb17 | ||
|
|
2dfe9fcf45 | ||
|
|
51f978ac72 | ||
|
|
c216677e1e | ||
|
|
8a5d275136 | ||
|
|
a36b76ba15 | ||
|
|
f3913949b2 | ||
|
|
76fdbfaa2f | ||
|
|
6b44b9ae1e | ||
|
|
8615c4c3b0 | ||
|
|
f976bd3fff | ||
|
|
db55a2664f | ||
|
|
4f1b4b1283 | ||
|
|
208f8349a6 | ||
|
|
32e71b0386 | ||
|
|
1c687e7a45 | ||
|
|
53ed028663 | ||
|
|
2a4ebe1b3e | ||
|
|
df863355b7 | ||
|
|
66e44f3448 | ||
|
|
c1f12f52ae | ||
|
|
b69091a51a | ||
|
|
0fe30ebe49 | ||
|
|
34f5552c7d | ||
|
|
53e27ff4d3 | ||
|
|
f4569c513f | ||
|
|
90ce5f2c57 | ||
|
|
6f3a07b756 | ||
|
|
d42bfa88e1 | ||
|
|
61a6b32137 | ||
|
|
6cb4e203f1 | ||
|
|
2d27a721f8 | ||
|
|
867a8e855b | ||
|
|
d5dc70ccee | ||
|
|
18d9c1d609 | ||
|
|
c388cee1fe | ||
|
|
da2488f7d8 | ||
|
|
f059643187 | ||
|
|
30730fe632 | ||
|
|
39df168891 | ||
|
|
33946e61bb | ||
|
|
0b34d20716 | ||
|
|
8d0b36d2d4 | ||
|
|
7e08f7df67 | ||
|
|
a688a5fe72 | ||
|
|
fe57efb1a8 | ||
|
|
9af3fbc35f | ||
|
|
7495d2dc65 | ||
|
|
456da93465 | ||
|
|
e7a6126fbd | ||
|
|
aa41eb5da4 | ||
|
|
87767be4a6 | ||
|
|
1e0a356cd2 | ||
|
|
1b40c9e692 | ||
|
|
2cc7565841 | ||
|
|
cee67f4301 | ||
|
|
452bf61ef9 | ||
|
|
50d3dfb98b | ||
|
|
d729df5cba | ||
|
|
a5638ccc3b | ||
|
|
46643065ef | ||
|
|
b270562a44 | ||
|
|
cfbe59868b | ||
|
|
207211facc | ||
|
|
269523a034 | ||
|
|
1eee314d17 | ||
|
|
8cc64bf44e | ||
|
|
5dd027ad63 | ||
|
|
d51620640b | ||
|
|
17c767fa42 | ||
|
|
b2749a0c4e | ||
|
|
3b0c7a3a30 | ||
|
|
797681883b | ||
|
|
48b6e1590c | ||
|
|
b423edf2b5 | ||
|
|
9cf979d154 | ||
|
|
033c8b269d | ||
|
|
c4e42b94f9 | ||
|
|
5b9cb4fc8d | ||
|
|
99d9ce10c3 | ||
|
|
34400c7f60 | ||
|
|
05914e38f0 | ||
|
|
bddb54ef4c | ||
|
|
46e146b34a | ||
|
|
f16ba6861b | ||
|
|
fb6af544e2 | ||
|
|
4c3cfc8c2d | ||
|
|
286a7494d3 | ||
|
|
a07c1badd8 | ||
|
|
29d93fb9c1 | ||
|
|
4a54412d47 | ||
|
|
bd8306bd33 | ||
|
|
69107f79e3 | ||
|
|
2687a34019 | ||
|
|
9ae6357c34 | ||
|
|
baa7e25e30 | ||
|
|
700feb68af | ||
|
|
57c6f2716f | ||
|
|
24e27d3c31 | ||
|
|
77d13090b5 | ||
|
|
fc40fea97a | ||
|
|
bb8213deb5 | ||
|
|
e3465be8c1 | ||
|
|
42ce9f4d89 | ||
|
|
1409dcc2e8 | ||
|
|
6fd1e9a4c4 | ||
|
|
95a86703b3 | ||
|
|
bcaa6c6b8a | ||
|
|
ffe02e59c7 | ||
|
|
f7152befec | ||
|
|
ca73033aea | ||
|
|
a1499854f9 | ||
|
|
873e0d346e | ||
|
|
44ac782978 | ||
|
|
6b39f6f6fa | ||
|
|
dcbb8bab75 | ||
|
|
acd9a163f0 | ||
|
|
795149e776 | ||
|
|
55bff6b52f | ||
|
|
82be0a8898 | ||
|
|
f98aad57d3 | ||
|
|
81a911dc11 | ||
|
|
39f87a5e78 | ||
|
|
aff4ffdf83 | ||
|
|
574ea01e08 | ||
|
|
26dcb015ce | ||
|
|
1d259a86c1 | ||
|
|
0c02d15e0d | ||
|
|
1e01c684c4 | ||
|
|
3b1d6e394d | ||
|
|
2fe639676b | ||
|
|
a35707ae18 | ||
|
|
b1cf8ff0bb | ||
|
|
adaa31b34d | ||
|
|
00095cb71c | ||
|
|
8af1f2b6d9 | ||
|
|
2dc1b875d2 | ||
|
|
653362567b | ||
|
|
ca538399f6 | ||
|
|
36f9f65798 | ||
|
|
2189fc0a96 | ||
|
|
854208ea0a | ||
|
|
67db245aea | ||
|
|
42c56898d5 | ||
|
|
a55a287a9d | ||
|
|
b4024ee552 | ||
|
|
181bef0057 | ||
|
|
e9d05e6271 | ||
|
|
15415afa9a | ||
|
|
728c87589f | ||
|
|
686717e544 | ||
|
|
c4d0921a15 | ||
|
|
ae90b6c93f | ||
|
|
ac1ae4fed5 | ||
|
|
a1f2617931 | ||
|
|
300a37a199 | ||
|
|
351c77182c | ||
|
|
b214933da9 | ||
|
|
9463390e80 | ||
|
|
3b758962e4 | ||
|
|
ad06814ac7 | ||
|
|
13d18986b2 | ||
|
|
6171658c0c | ||
|
|
9b5f5595b0 | ||
|
|
676ea0d5a7 | ||
|
|
80b6d6a31f | ||
|
|
6661a5d946 | ||
|
|
53e9a91dc6 | ||
|
|
56bbd8a172 | ||
|
|
0bd53d6dc7 | ||
|
|
61cb3d1d7c | ||
|
|
e808d030db | ||
|
|
4a68d9d3ea | ||
|
|
730305f4d2 | ||
|
|
d17523ff17 | ||
|
|
195116d1de | ||
|
|
92d7828a77 | ||
|
|
2a3b9a9a5e | ||
|
|
0e7d7ffcb2 | ||
|
|
b1fca404b7 | ||
|
|
b6e6328e6a | ||
|
|
ecf0327378 | ||
|
|
787c551f5f | ||
|
|
d662152088 | ||
|
|
2fcdb52157 | ||
|
|
70f2a32fad | ||
|
|
37356317a4 | ||
|
|
08f3a56285 |
@@ -7,6 +7,7 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
|
||||
git \
|
||||
gtest \
|
||||
mariadb-libs \
|
||||
ninja \
|
||||
protobuf \
|
||||
qt6-base \
|
||||
qt6-imageformats \
|
||||
|
||||
@@ -16,6 +16,7 @@ RUN apt-get update && \
|
||||
libqt5sql5-mysql \
|
||||
libqt5svg5-dev \
|
||||
libqt5websockets5-dev \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt5-image-formats-plugins \
|
||||
qtmultimedia5-dev \
|
||||
|
||||
@@ -15,13 +15,14 @@ RUN apt-get update && \
|
||||
libprotobuf-dev \
|
||||
libqt6multimedia6 \
|
||||
libqt6sql6-mysql \
|
||||
qt6-svg-dev \
|
||||
qt6-websockets-dev \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
qt6-multimedia-dev \
|
||||
qt6-svg-dev \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools \
|
||||
qt6-websockets-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
29
.ci/Debian13/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
FROM debian:13
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
ccache \
|
||||
clang-format \
|
||||
cmake \
|
||||
file \
|
||||
g++ \
|
||||
git \
|
||||
libgl-dev \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt6multimedia6 \
|
||||
libqt6sql6-mysql \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
qt6-multimedia-dev \
|
||||
qt6-svg-dev \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools \
|
||||
qt6-websockets-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM fedora:40
|
||||
FROM fedora:42
|
||||
|
||||
RUN dnf install -y \
|
||||
ccache \
|
||||
@@ -6,6 +6,7 @@ RUN dnf install -y \
|
||||
gcc-c++ \
|
||||
git \
|
||||
mariadb-devel \
|
||||
ninja-build \
|
||||
protobuf-devel \
|
||||
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
||||
qt6-qtimageformats \
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM fedora:41
|
||||
FROM fedora:43
|
||||
|
||||
RUN dnf install -y \
|
||||
ccache \
|
||||
@@ -6,6 +6,7 @@ RUN dnf install -y \
|
||||
gcc-c++ \
|
||||
git \
|
||||
mariadb-devel \
|
||||
ninja-build \
|
||||
protobuf-devel \
|
||||
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
|
||||
qt6-qtimageformats \
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:20.04
|
||||
FROM debian:11
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
@@ -9,17 +9,12 @@ RUN apt-get update && \
|
||||
file \
|
||||
g++ \
|
||||
git \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5sql5-mysql \
|
||||
libqt5svg5-dev \
|
||||
libqt5websockets5-dev \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt5-default \
|
||||
qt5-image-formats-plugins \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
&& apt-get clean \
|
||||
@@ -17,6 +17,7 @@ RUN apt-get update && \
|
||||
libqt6sql6-mysql \
|
||||
libqt6svg6-dev \
|
||||
libqt6websockets6-dev \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
|
||||
@@ -15,13 +15,14 @@ RUN apt-get update && \
|
||||
libprotobuf-dev \
|
||||
libqt6multimedia6 \
|
||||
libqt6sql6-mysql \
|
||||
qt6-svg-dev \
|
||||
qt6-websockets-dev \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
qt6-multimedia-dev \
|
||||
qt6-svg-dev \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools \
|
||||
qt6-websockets-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
134
.ci/compile.sh
@@ -11,9 +11,9 @@
|
||||
# --debug or --release sets the build type ie CMAKE_BUILD_TYPE
|
||||
# --ccache [<size>] uses ccache and shows stats, optionally provide size
|
||||
# --dir <dir> sets the name of the build dir, default is "build"
|
||||
# --parallel <core count> sets how many cores cmake should build with in parallel
|
||||
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR PARALLEL_COUNT
|
||||
# (correspond to args: --debug/--release --install --package <package type> --suffix <suffix> --server --test --ccache <ccache_size> --dir <dir> --parallel <core_count>)
|
||||
# --target-macos-version <version> sets the min os version - only used for macOS builds
|
||||
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION
|
||||
# (correspond to args: --debug/--release --install --package <package type> --suffix <suffix> --server --test --ccache <ccache_size> --dir <dir>)
|
||||
# exitcode: 1 for failure, 3 for invalid arguments
|
||||
|
||||
# Read arguments
|
||||
@@ -47,6 +47,10 @@ while [[ $# != 0 ]]; do
|
||||
MAKE_SERVER=1
|
||||
shift
|
||||
;;
|
||||
'--no-client')
|
||||
MAKE_NO_CLIENT=1
|
||||
shift
|
||||
;;
|
||||
'--test')
|
||||
MAKE_TEST=1
|
||||
shift
|
||||
@@ -67,6 +71,10 @@ while [[ $# != 0 ]]; do
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
'--vcpkg')
|
||||
USE_VCPKG=1
|
||||
shift
|
||||
;;
|
||||
'--dir')
|
||||
shift
|
||||
if [[ $# == 0 ]]; then
|
||||
@@ -76,13 +84,13 @@ while [[ $# != 0 ]]; do
|
||||
BUILD_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
'--parallel')
|
||||
'--target-macos-version')
|
||||
shift
|
||||
if [[ $# == 0 ]]; then
|
||||
echo "::error file=$0::--parallel expects an argument"
|
||||
echo "::error file=$0::--target-macos-version expects an argument"
|
||||
exit 3
|
||||
fi
|
||||
PARALLEL_COUNT="$1"
|
||||
TARGET_MACOS_VERSION="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
@@ -105,11 +113,17 @@ fi
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# Set minimum CMake Version
|
||||
export CMAKE_POLICY_VERSION_MINIMUM=3.10
|
||||
|
||||
# Add cmake flags
|
||||
flags=("-DCMAKE_BUILD_TYPE=$BUILDTYPE")
|
||||
if [[ $MAKE_SERVER ]]; then
|
||||
flags+=("-DWITH_SERVER=1")
|
||||
fi
|
||||
if [[ $MAKE_NO_CLIENT ]]; then
|
||||
flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0" "-DWITH_DBCONVERTER=0")
|
||||
fi
|
||||
if [[ $MAKE_TEST ]]; then
|
||||
flags+=("-DTEST=1")
|
||||
fi
|
||||
@@ -123,19 +137,12 @@ fi
|
||||
if [[ $PACKAGE_TYPE ]]; then
|
||||
flags+=("-DCPACK_GENERATOR=$PACKAGE_TYPE")
|
||||
fi
|
||||
if [[ $USE_VCPKG ]]; then
|
||||
flags+=("-DUSE_VCPKG=1")
|
||||
fi
|
||||
|
||||
# Add cmake --build flags
|
||||
buildflags=(--config "$BUILDTYPE")
|
||||
if [[ $PARALLEL_COUNT ]]; 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")
|
||||
# note, no normal build flags should be added after this
|
||||
else
|
||||
buildflags+=(--parallel "$PARALLEL_COUNT")
|
||||
fi
|
||||
fi
|
||||
|
||||
function ccachestatsverbose() {
|
||||
# note, verbose only works on newer ccache, discard the error
|
||||
@@ -148,6 +155,72 @@ function ccachestatsverbose() {
|
||||
}
|
||||
|
||||
# Compile
|
||||
if [[ $RUNNER_OS == macOS ]]; then
|
||||
|
||||
if [[ $TARGET_MACOS_VERSION ]]; then
|
||||
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
|
||||
flags+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=$TARGET_MACOS_VERSION")
|
||||
|
||||
# vcpkg dependencies need a vcpkg triplet file to compile to the target macOS version
|
||||
# an easy way is to copy the x64-osx.cmake file and modify it
|
||||
triplets_dir="/tmp/cmake/triplets"
|
||||
triplet_version="custom-triplet"
|
||||
triplet_file="$triplets_dir/$triplet_version.cmake"
|
||||
arch=$(uname -m)
|
||||
if [[ $arch == x86_64 ]]; then
|
||||
arch="x64"
|
||||
fi
|
||||
mkdir -p "$triplets_dir"
|
||||
cp "../vcpkg/triplets/$arch-osx.cmake" "$triplet_file"
|
||||
echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
|
||||
echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
|
||||
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")
|
||||
flags+=("-DVCPKG_HOST_TRIPLET=$triplet_version")
|
||||
flags+=("-DVCPKG_TARGET_TRIPLET=$triplet_version")
|
||||
echo "::group::Generated triplet $triplet_file"
|
||||
cat "$triplet_file"
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
|
||||
echo "::group::Signing Certificate"
|
||||
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]; then
|
||||
echo "$MACOS_CERTIFICATE" | base64 --decode >"certificate.p12"
|
||||
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security set-keychain-settings -t 3600 -l build.keychain
|
||||
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
echo "macOS signing certificate successfully imported and keychain configured."
|
||||
else
|
||||
echo "No signing certificate configured. Skipping set up of keychain in macOS environment."
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
if [[ $MAKE_PACKAGE ]]; then
|
||||
# Workaround https://github.com/actions/runner-images/issues/7522
|
||||
# have hdiutil repeat the command 10 times in hope of success
|
||||
hdiutil_script="/tmp/hdiutil.sh"
|
||||
# shellcheck disable=SC2016
|
||||
echo '#!/bin/bash
|
||||
i=0
|
||||
while ! hdiutil "$@"; do
|
||||
if (( ++i >= 10 )); then
|
||||
echo "Error: hdiutil failed $i times!" >&2
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done' >"$hdiutil_script"
|
||||
chmod +x "$hdiutil_script"
|
||||
flags+=(-DCPACK_COMMAND_HDIUTIL="$hdiutil_script")
|
||||
fi
|
||||
|
||||
elif [[ $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
|
||||
buildflags+=(-- -p:UseMultiToolTask=true -p:EnableClServerMode=true)
|
||||
fi
|
||||
|
||||
if [[ $USE_CCACHE ]]; then
|
||||
echo "::group::Show ccache stats"
|
||||
ccachestatsverbose
|
||||
@@ -156,17 +229,13 @@ fi
|
||||
|
||||
echo "::group::Configure cmake"
|
||||
cmake --version
|
||||
echo "Running cmake with flags: ${flags[*]}"
|
||||
cmake .. "${flags[@]}"
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Build project"
|
||||
if [[ $RUNNER_OS == Windows ]]; then
|
||||
# Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
|
||||
# and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
|
||||
cmake --build . "${buildflags[@]}" -- -p:UseMultiToolTask=true -p:EnableClServerMode=true
|
||||
else
|
||||
cmake --build . "${buildflags[@]}"
|
||||
fi
|
||||
echo "Running cmake --build with flags: ${buildflags[*]}"
|
||||
cmake --build . "${buildflags[@]}"
|
||||
echo "::endgroup::"
|
||||
|
||||
if [[ $USE_CCACHE ]]; then
|
||||
@@ -175,6 +244,19 @@ if [[ $USE_CCACHE ]]; then
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
|
||||
if [[ $RUNNER_OS == macOS ]]; then
|
||||
echo "::group::Inspect Mach-O binaries"
|
||||
for app in cockatrice oracle servatrice dbconverter; do
|
||||
binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app"
|
||||
echo "Inspecting $app..."
|
||||
vtool -show-build "$binary"
|
||||
file "$binary"
|
||||
lipo -info "$binary"
|
||||
echo ""
|
||||
done
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
|
||||
if [[ $MAKE_TEST ]]; then
|
||||
echo "::group::Run tests"
|
||||
ctest -C "$BUILDTYPE" --output-on-failure
|
||||
@@ -189,12 +271,6 @@ fi
|
||||
|
||||
if [[ $MAKE_PACKAGE ]]; then
|
||||
echo "::group::Create package"
|
||||
|
||||
if [[ $RUNNER_OS == macOS ]]; then
|
||||
# Workaround https://github.com/actions/runner-images/issues/7522
|
||||
echo "killing XProtectBehaviorService"; sudo pkill -9 XProtect >/dev/null || true;
|
||||
echo "waiting for XProtectBehaviorService kill"; while pgrep "XProtect"; do sleep 3; done;
|
||||
fi
|
||||
cmake --build . --target package --config "$BUILDTYPE"
|
||||
echo "::endgroup::"
|
||||
|
||||
|
||||
@@ -137,10 +137,11 @@ if [[ $SAVE ]]; then
|
||||
fi
|
||||
|
||||
# Set compile function, runs the compile script on the image, passes arguments to the script
|
||||
# shellcheck disable=2120
|
||||
function RUN ()
|
||||
{
|
||||
echo "running image:"
|
||||
if [[ $(docker images) =~ "$IMAGE_NAME" ]]; then
|
||||
if [[ $(docker images) =~ $IMAGE_NAME ]]; then
|
||||
local args=(--mount "type=bind,source=$PWD,target=/src")
|
||||
args+=(--workdir "/src")
|
||||
args+=(--user "$(id -u):$(id -g)")
|
||||
@@ -148,6 +149,10 @@ function RUN ()
|
||||
args+=(--mount "type=bind,source=$CCACHE_DIR,target=/.ccache")
|
||||
args+=(--env "CCACHE_DIR=/.ccache")
|
||||
fi
|
||||
if [[ -n "$CMAKE_GENERATOR" ]]; then
|
||||
args+=(--env "CMAKE_GENERATOR=$CMAKE_GENERATOR")
|
||||
fi
|
||||
# shellcheck disable=2086
|
||||
docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@"
|
||||
return $?
|
||||
else
|
||||
@@ -161,5 +166,6 @@ function RUN ()
|
||||
if [[ $INTERACTIVE ]]; then
|
||||
export BUILD_SCRIPT="-i"
|
||||
export RUN_ARGS="$RUN_ARGS -it"
|
||||
# shellcheck disable=2119
|
||||
RUN
|
||||
fi
|
||||
|
||||
@@ -11,11 +11,19 @@ if ! git merge-base origin/master HEAD; then
|
||||
fi
|
||||
|
||||
# Check formatting using format.sh
|
||||
echo "Checking your code using clang-format/cmake-format..."
|
||||
echo "Checking your code using format.sh..."
|
||||
|
||||
diff="$(./format.sh --diff --cmake --cf-version --branch origin/master)"
|
||||
diff="$(./format.sh --diff --cmake --shell --print-version --branch origin/master)"
|
||||
err=$?
|
||||
|
||||
sep="
|
||||
----------
|
||||
"
|
||||
used_version="${diff%%"$sep"*}"
|
||||
diff="${diff#*"$sep"}"
|
||||
changes_to_make="${diff%%"$sep"*}"
|
||||
files_to_edit="${diff#*"$sep"}"
|
||||
|
||||
case $err in
|
||||
1)
|
||||
cat <<EOM
|
||||
@@ -33,14 +41,13 @@ case $err in
|
||||
***********************************************************
|
||||
|
||||
Used version:
|
||||
${diff%%
|
||||
----------
|
||||
*}
|
||||
$used_version
|
||||
|
||||
Affected files:
|
||||
$files_to_edit
|
||||
|
||||
The following changes should be made:
|
||||
${diff#*
|
||||
----------
|
||||
}
|
||||
$changes_to_make
|
||||
|
||||
Exiting...
|
||||
EOM
|
||||
@@ -58,6 +65,9 @@ EOM
|
||||
*** ***
|
||||
***********************************************************
|
||||
|
||||
Used version:
|
||||
$used_version
|
||||
|
||||
Exiting...
|
||||
EOM
|
||||
exit 0
|
||||
|
||||
@@ -4,24 +4,30 @@
|
||||
git push -d origin --REPLACE-WITH-BETA-LIST--
|
||||
-->
|
||||
|
||||
<!-- This list of binaries should be updated every time the CI is changed to
|
||||
include different targets -->
|
||||
<!-- This list of binaries should be updated every time the CI is changed to include all targets -->
|
||||
<pre>
|
||||
<b>Pre-compiled binaries we serve:</b>
|
||||
- <kbd>Windows 10+</kbd>
|
||||
- <kbd>Windows 7+</kbd>
|
||||
- <kbd>macOS 14+</kbd> ("Sonoma") / Apple M
|
||||
- <kbd>macOS 13+</kbd> ("Ventura") / Intel
|
||||
- <kbd>Ubuntu 24.04 LTS</kbd> ("Noble Numbat")
|
||||
- <kbd>Ubuntu 22.04 LTS</kbd> ("Jammy Jellyfish")
|
||||
- <kbd>Ubuntu 20.04 LTS</kbd> ("Focal Fossa")
|
||||
- <kbd>Debian 12</kbd> ("Bookworm")
|
||||
- <kbd>Debian 11</kbd> ("Bullseye")
|
||||
- <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>
|
||||
Available pre-compiled binaries for installation:
|
||||
|
||||
<b>Windows</b>
|
||||
• <kbd>Windows 10+</kbd>
|
||||
• <kbd>Windows 7+</kbd>
|
||||
|
||||
<b>macOS</b>
|
||||
• <kbd>macOS 15+</kbd> <sub><i>Sequoia</i></sub> <sub>Apple M</sub>
|
||||
• <kbd>macOS 14+</kbd> <sub><i>Sonoma</i></sub> <sub>Apple M</sub>
|
||||
• <kbd>macOS 13+</kbd> <sub><i>Ventura</i></sub> <sub>Intel</sub>
|
||||
|
||||
<b>Linux</b>
|
||||
• <kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub>
|
||||
• <kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</i></sub>
|
||||
• <kbd>Debian 13</kbd> <sub><i>Trixie</i></sub>
|
||||
• <kbd>Debian 12</kbd> <sub><i>Bookworm</i></sub>
|
||||
• <kbd>Debian 11</kbd> <sub><i>Bullseye</i></sub>
|
||||
• <kbd>Fedora 43</kbd>
|
||||
• <kbd>Fedora 42</kbd>
|
||||
|
||||
<sub>We are also packaged in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>, courtesy of @FFY00.</sub>
|
||||
<sub>General Linux support is available via a <kbd>flatpak</kbd> package at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a>!</sub>
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -35,7 +41,7 @@ If you ever encounter a bug, have a suggestion or idea, or feel a need for a dev
|
||||
|
||||
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 and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-).
|
||||
If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#contribute).
|
||||
We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution.
|
||||
|
||||
|
||||
|
||||
@@ -10,5 +10,5 @@ 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/
|
||||
[1]: https://explore.transifex.com/cockatrice/cockatrice/
|
||||
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-push.yml?query=branch%3Amaster
|
||||
|
||||
@@ -25,6 +25,9 @@ IndentCaseLabels: true
|
||||
PointerAlignment: Right
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Regroup
|
||||
StatementAttributeLikeMacros: [emit]
|
||||
# requires clang-format 16
|
||||
# RemoveSemicolon: true
|
||||
---
|
||||
Language: Proto
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
|
||||
2
.github/CONTRIBUTING.md
vendored
@@ -1,3 +1,5 @@
|
||||
<!--! @page contributing Contributing -->
|
||||
|
||||
[Introduction](#contributing-to-cockatrice) | [Code Style Guide](
|
||||
#code-style-guide) | [Translations](#translations) | [Release Management](
|
||||
#release-management)
|
||||
|
||||
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,20 +2,31 @@
|
||||
name: "🐛 Bug Report"
|
||||
about: Report an issue encountered while using Cockatrice
|
||||
title: ''
|
||||
labels: 'Bug'
|
||||
type: 'Bug'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- READ THIS BEFORE POSTING
|
||||
Go to "Help → View Debug Log" in Cockatrice and copy all information at the
|
||||
top (above the separation line) below "System Information" in this ticket!
|
||||
In Cockatrice, go to "Help" → "View Debug Log" and copy all information displayed at the
|
||||
top (above the separation line "----"), to below "System Information" section in this ticket!
|
||||
If you can't start Cockatrice to access these details, make
|
||||
sure to post your OS and the file name of the setup binary instead. -->
|
||||
sure to post your OS and the file name of the setup binary instead.
|
||||
-->
|
||||
|
||||
**System Information:**
|
||||
<!-- Read the hint above on where to find the important information to provide here! -->
|
||||
|
||||
|
||||
<details><summary>Debug Log:</summary>
|
||||
<!--
|
||||
In Cockatrice, go to "Help" → "View Debug Log", click the "Copy to clickboard" button and paste the output here.
|
||||
-->
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
_______________________________________________________________________________________
|
||||
|
||||
<!-- Explain your issue in detail here! Please attach screenshots if possible. -->
|
||||
@@ -25,7 +36,7 @@ ________________________________________________________________________________
|
||||
|
||||
_______________________________________________________________________________________
|
||||
|
||||
<!-- Describe the sequence of actions needed to experience the bug -->
|
||||
<!-- Describe the sequence of actions needed to experience the bug. -->
|
||||
|
||||
**Steps to reproduce:**
|
||||
- Do A
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -4,6 +4,6 @@ contact_links:
|
||||
url: https://discord.gg/3Z9yzmA
|
||||
about: Need help with using the client? Want to find some games? Try the Discord server!
|
||||
- name: 🌐 Translations (Help improve the localization of the app)
|
||||
url: https://www.transifex.com/cockatrice/cockatrice/
|
||||
url: https://explore.transifex.com/cockatrice/cockatrice/
|
||||
# it is not possible to add a link to the wiki to this description
|
||||
about: For more information and guidance check our Translation FAQ on our wiki!
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,8 +1,9 @@
|
||||
---
|
||||
name: "💡 Feature Request"
|
||||
about: Request a new feature
|
||||
about: Request a new feature for Cockatrice
|
||||
title: ''
|
||||
labels: 'Feature Request'
|
||||
type: 'Feature'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
415
.github/workflows/desktop-build.yml
vendored
@@ -1,22 +1,42 @@
|
||||
name: Build Desktop
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
actions: write # needed for ccache action to be able to delete gha caches
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'webclient/**'
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.github/workflows/translations-*.yml'
|
||||
paths:
|
||||
- '*/**' # matches all files not in root
|
||||
- '!**.md'
|
||||
- '!.github/**'
|
||||
- '!.husky/**'
|
||||
- '!.tx/**'
|
||||
- '!doc/**'
|
||||
- '!webclient/**'
|
||||
- '.github/workflows/desktop-build.yml'
|
||||
- 'CMakeLists.txt'
|
||||
- 'vcpkg.json'
|
||||
- 'vcpkg'
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'webclient/**'
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.github/workflows/translations-*.yml'
|
||||
paths:
|
||||
- '*/**' # matches all files not in root
|
||||
- '!**.md'
|
||||
- '!.github/**'
|
||||
- '!.husky/**'
|
||||
- '!.tx/**'
|
||||
- '!doc/**'
|
||||
- '!webclient/**'
|
||||
- '.github/workflows/desktop-build.yml'
|
||||
- 'CMakeLists.txt'
|
||||
- 'vcpkg.json'
|
||||
- 'vcpkg'
|
||||
|
||||
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on master)
|
||||
concurrency:
|
||||
@@ -50,7 +70,7 @@ jobs:
|
||||
|
||||
- name: Checkout
|
||||
if: steps.configure.outputs.tag != null
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -94,26 +114,31 @@ jobs:
|
||||
- distro: Debian
|
||||
version: 11
|
||||
package: DEB
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Servatrice_Debian
|
||||
version: 11
|
||||
package: DEB
|
||||
test: skip
|
||||
server_only: yes
|
||||
|
||||
- distro: Debian
|
||||
version: 12
|
||||
package: DEB
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Debian
|
||||
version: 13
|
||||
package: DEB
|
||||
|
||||
- distro: Fedora
|
||||
version: 40
|
||||
version: 42
|
||||
package: RPM
|
||||
test: skip # Running tests on all distros is superfluous
|
||||
|
||||
- distro: Fedora
|
||||
version: 41
|
||||
version: 43
|
||||
package: RPM
|
||||
|
||||
- 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
|
||||
@@ -129,29 +154,25 @@ jobs:
|
||||
continue-on-error: ${{matrix.allow-failure == 'yes'}}
|
||||
env:
|
||||
NAME: ${{matrix.distro}}${{matrix.version}}
|
||||
CACHE: /tmp/${{matrix.distro}}${{matrix.version}}-cache # ${{runner.temp}} does not work?
|
||||
CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache
|
||||
# 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
|
||||
CCACHE_SIZE: 500M
|
||||
CMAKE_GENERATOR: 'Ninja'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Generate cache timestamp
|
||||
id: cache_timestamp
|
||||
shell: bash
|
||||
run: echo "timestamp=$(date -u '+%Y%m%d%H%M%S')" >>"$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v4
|
||||
- name: Restore compiler cache (ccache)
|
||||
id: ccache_restore
|
||||
uses: actions/cache/restore@v4
|
||||
env:
|
||||
timestamp: ${{steps.cache_timestamp.outputs.timestamp}}
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
with:
|
||||
path: ${{env.CACHE}}
|
||||
key: docker-${{matrix.distro}}${{matrix.version}}-cache-${{env.timestamp}}
|
||||
restore-keys: |
|
||||
docker-${{matrix.distro}}${{matrix.version}}-cache-
|
||||
key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}}
|
||||
restore-keys: ccache-${{matrix.distro}}${{matrix.version}}-
|
||||
|
||||
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
|
||||
shell: bash
|
||||
@@ -160,9 +181,11 @@ jobs:
|
||||
- name: Build debug and test
|
||||
if: matrix.test != 'skip'
|
||||
shell: bash
|
||||
env:
|
||||
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
|
||||
run: |
|
||||
source .ci/docker.sh
|
||||
RUN --server --debug --test --ccache "$CCACHE_SIZE" --parallel 4
|
||||
RUN --server --debug --test --ccache "$CCACHE_SIZE"
|
||||
|
||||
- name: Build release package
|
||||
id: build
|
||||
@@ -171,22 +194,33 @@ jobs:
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
|
||||
type: '${{matrix.package}}'
|
||||
package: '${{matrix.package}}'
|
||||
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
|
||||
NO_CLIENT: ${{matrix.server_only == 'yes' && '--no-client' || '' }}
|
||||
run: |
|
||||
source .ci/docker.sh
|
||||
RUN --server --release --package "$type" --dir "$BUILD_DIR" \
|
||||
--ccache "$CCACHE_SIZE" --parallel 4
|
||||
RUN --server --release --package "$package" --dir "$BUILD_DIR" \
|
||||
--ccache "$CCACHE_SIZE" $NO_CLIENT
|
||||
.ci/name_build.sh
|
||||
|
||||
- name: Save compiler cache (ccache)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{env.CACHE}}
|
||||
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
||||
|
||||
- name: Upload artifact
|
||||
id: upload_artifact
|
||||
if: matrix.package != 'skip'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{matrix.distro}}${{matrix.version}}-package
|
||||
path: ${{steps.build.outputs.path}}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload to release
|
||||
id: upload_release
|
||||
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
|
||||
shell: bash
|
||||
env:
|
||||
@@ -196,94 +230,181 @@ jobs:
|
||||
asset_name: ${{steps.build.outputs.name}}
|
||||
run: gh release upload "$tag_name" "$asset_path#$asset_name"
|
||||
|
||||
build-macos:
|
||||
- name: Attest binary provenance
|
||||
id: attestation
|
||||
if: steps.upload_release.outcome == 'success'
|
||||
uses: actions/attest-build-provenance@v3
|
||||
with:
|
||||
subject-name: ${{steps.build.outputs.name}}
|
||||
subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
|
||||
|
||||
- name: Verify binary attestation
|
||||
if: steps.attestation.outcome == 'success'
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{github.token}}
|
||||
run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice
|
||||
|
||||
build-vcpkg:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: 13
|
||||
- os: macOS
|
||||
target: 13
|
||||
runner: macos-15-intel
|
||||
soc: Intel
|
||||
os: macos-13
|
||||
xcode: "14.3.1"
|
||||
xcode: "16.4"
|
||||
type: Release
|
||||
core_count: 4
|
||||
override_target: 13
|
||||
make_package: 1
|
||||
package_suffix: "-macOS13_Intel"
|
||||
artifact_name: macOS13_Intel-package
|
||||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false # qt caches take too much space for macOS (1.1Gi)
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
- target: 14
|
||||
- os: macOS
|
||||
target: 14
|
||||
runner: macos-14
|
||||
soc: Apple
|
||||
os: macos-14
|
||||
xcode: "15.4"
|
||||
type: Release
|
||||
core_count: 3
|
||||
make_package: 1
|
||||
package_suffix: "-macOS14"
|
||||
artifact_name: macOS14-package
|
||||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
- target: 15
|
||||
- os: macOS
|
||||
target: 15
|
||||
runner: macos-15
|
||||
soc: Apple
|
||||
os: macos-15
|
||||
xcode: "16.2"
|
||||
xcode: "16.4"
|
||||
type: Release
|
||||
core_count: 3
|
||||
make_package: 1
|
||||
package_suffix: "-macOS15"
|
||||
artifact_name: macOS15-package
|
||||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
- target: 15
|
||||
- os: macOS
|
||||
target: 15
|
||||
runner: macos-15
|
||||
soc: Apple
|
||||
os: macos-15
|
||||
xcode: "16.2"
|
||||
xcode: "16.4"
|
||||
type: Debug
|
||||
core_count: 3
|
||||
qt_version: 6.6.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: false
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
|
||||
name: macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
|
||||
- os: Windows
|
||||
target: 7
|
||||
runner: windows-2022
|
||||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-Win7"
|
||||
artifact_name: Windows7-installer
|
||||
qt_version: 5.15.*
|
||||
qt_arch: win64_msvc2019_64
|
||||
cache_qt: true
|
||||
cmake_generator: "Visual Studio 17 2022"
|
||||
cmake_generator_platform: x64
|
||||
|
||||
- os: Windows
|
||||
target: 10
|
||||
runner: windows-2022
|
||||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-Win10"
|
||||
artifact_name: Windows10-installer
|
||||
qt_version: 6.6.*
|
||||
qt_arch: win64_msvc2019_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cache_qt: true
|
||||
cmake_generator: "Visual Studio 17 2022"
|
||||
cmake_generator_platform: x64
|
||||
|
||||
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
|
||||
needs: configure
|
||||
runs-on: ${{matrix.os}}
|
||||
continue-on-error: ${{matrix.allow-failure == 'yes'}}
|
||||
env:
|
||||
DEVELOPER_DIR:
|
||||
/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer
|
||||
runs-on: ${{matrix.runner}}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies using Homebrew
|
||||
shell: bash
|
||||
# CMake cannot find the MySQL connector
|
||||
# Neither of these works: mariadb-connector-c mysql-connector-c++
|
||||
env:
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
run: |
|
||||
brew update
|
||||
brew install protobuf qt --force-bottle
|
||||
- name: Add msbuild to PATH
|
||||
if: matrix.os == 'Windows'
|
||||
id: add-msbuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
msbuild-architecture: x64
|
||||
|
||||
- name: Build & Sign on Xcode ${{matrix.xcode}}
|
||||
shell: bash
|
||||
# Using jianmingyong/ccache-action to setup ccache without using brew
|
||||
# It tries to download a binary of ccache from GitHub Release and falls back to building from source if it fails
|
||||
- name: Setup ccache
|
||||
if: matrix.use_ccache == 1
|
||||
uses: jianmingyong/ccache-action@v1
|
||||
with:
|
||||
install-type: "binary"
|
||||
ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}
|
||||
max-size: 500M
|
||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Qt ${{matrix.qt_version}}
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{matrix.qt_version}}
|
||||
arch: ${{matrix.qt_arch}}
|
||||
modules: ${{matrix.qt_modules}}
|
||||
cache: ${{matrix.cache_qt}}
|
||||
|
||||
- name: Setup vcpkg cache
|
||||
id: vcpkg-cache
|
||||
uses: TAServers/vcpkg-cache@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# uses environment variables, see compile.sh for more details
|
||||
- name: Build Cockatrice
|
||||
id: build
|
||||
shell: bash
|
||||
env:
|
||||
BUILDTYPE: '${{matrix.type}}'
|
||||
MAKE_TEST: 1
|
||||
MAKE_PACKAGE: '${{matrix.make_package}}'
|
||||
PACKAGE_SUFFIX: '-macOS${{matrix.target}}_${{matrix.soc}}'
|
||||
PACKAGE_SUFFIX: '${{matrix.package_suffix}}'
|
||||
CMAKE_GENERATOR: ${{matrix.cmake_generator}}
|
||||
CMAKE_GENERATOR_PLATFORM: ${{matrix.cmake_generator_platform}}
|
||||
USE_CCACHE: ${{matrix.use_ccache}}
|
||||
VCPKG_DISABLE_METRICS: 1
|
||||
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
|
||||
# macOS-specific environment variables, will be ignored on Windows
|
||||
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
||||
# macOS runner have 3 cores usually - only the macos-13 image has 4:
|
||||
# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
|
||||
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
||||
run: |
|
||||
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
|
||||
then
|
||||
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security set-keychain-settings -t 3600 -l build.keychain
|
||||
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
fi
|
||||
.ci/compile.sh --server --parallel ${{matrix.core_count}}
|
||||
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
|
||||
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
|
||||
run: .ci/compile.sh --server --test --vcpkg
|
||||
|
||||
- name: Sign app bundle
|
||||
if: matrix.make_package
|
||||
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
|
||||
env:
|
||||
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
||||
@@ -295,7 +416,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Notarize app bundle
|
||||
if: matrix.make_package
|
||||
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
|
||||
env:
|
||||
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
|
||||
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
|
||||
@@ -306,20 +427,20 @@ jobs:
|
||||
# Store the notarization credentials so that we can prevent a UI password dialog from blocking the CI
|
||||
echo "Create keychain profile"
|
||||
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$MACOS_NOTARIZATION_APPLE_ID" --team-id "$MACOS_NOTARIZATION_TEAM_ID" --password "$MACOS_NOTARIZATION_PWD"
|
||||
|
||||
|
||||
# We can't notarize an app bundle directly, but we need to compress it as an archive.
|
||||
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
|
||||
# notarization service
|
||||
echo "Creating temp notarization archive"
|
||||
ditto -c -k --keepParent ${{steps.build.outputs.path}} "notarization.zip"
|
||||
|
||||
|
||||
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
|
||||
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
|
||||
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
|
||||
# you're curious
|
||||
echo "Notarize app"
|
||||
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
|
||||
|
||||
|
||||
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
|
||||
# validated by macOS even when an internet connection is not available.
|
||||
echo "Attach staple"
|
||||
@@ -327,95 +448,17 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload artifact
|
||||
id: upload_artifact
|
||||
if: matrix.make_package
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: macOS${{matrix.target}}${{ matrix.soc == 'Intel' && '_Intel' || '' }}${{ matrix.type == 'Debug' && '_Debug' || '' }}-package
|
||||
path: ${{steps.build.outputs.path}}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload to release
|
||||
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
|
||||
shell: bash
|
||||
env:
|
||||
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-windows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: 7
|
||||
qt_version: 5.15.*
|
||||
qt_arch: msvc2019_64
|
||||
|
||||
- target: 10
|
||||
qt_version: 6.6.*
|
||||
qt_arch: msvc2019_64
|
||||
qt_modules: "qtimageformats qtmultimedia qtwebsockets"
|
||||
|
||||
name: Windows ${{matrix.target}}
|
||||
needs: configure
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
CMAKE_GENERATOR: 'Visual Studio 17 2022'
|
||||
|
||||
steps:
|
||||
- name: Add msbuild to PATH
|
||||
id: add-msbuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
msbuild-architecture: x64
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Qt ${{matrix.qt_version}}
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
cache: true
|
||||
setup-python: false
|
||||
version: ${{matrix.qt_version}}
|
||||
arch: win64_${{matrix.qt_arch}}
|
||||
tools: ${{matrix.qt_tools}}
|
||||
modules: ${{matrix.qt_modules}}
|
||||
|
||||
- name: Run vcpkg
|
||||
uses: lukka/run-vcpkg@v11
|
||||
with:
|
||||
runVcpkgInstall: true
|
||||
doNotCache: false
|
||||
env:
|
||||
VCPKG_DEFAULT_TRIPLET: 'x64-windows'
|
||||
VCPKG_DISABLE_METRICS: 1
|
||||
|
||||
- name: Build Cockatrice
|
||||
id: build
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_SUFFIX: '-Win${{matrix.target}}'
|
||||
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
|
||||
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@v4
|
||||
with:
|
||||
name: Windows${{matrix.target}}-installer
|
||||
name: ${{matrix.artifact_name}}
|
||||
path: ${{steps.build.outputs.path}}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload pdb database
|
||||
uses: actions/upload-artifact@v4
|
||||
if: matrix.os == 'Windows'
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: Windows${{matrix.target}}-debug-pdbs
|
||||
path: |
|
||||
@@ -424,7 +467,8 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload to release
|
||||
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
|
||||
id: upload_release
|
||||
if: needs.configure.outputs.tag != null
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{github.token}}
|
||||
@@ -432,3 +476,18 @@ jobs:
|
||||
asset_path: ${{steps.build.outputs.path}}
|
||||
asset_name: ${{steps.build.outputs.name}}
|
||||
run: gh release upload "$tag_name" "$asset_path#$asset_name"
|
||||
|
||||
- name: Attest binary provenance
|
||||
id: attestation
|
||||
if: steps.upload_release.outcome == 'success'
|
||||
uses: actions/attest-build-provenance@v3
|
||||
with:
|
||||
subject-name: ${{steps.build.outputs.name}}
|
||||
subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
|
||||
|
||||
- name: Verify binary attestation
|
||||
if: steps.attestation.outcome == 'success'
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{github.token}}
|
||||
run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice
|
||||
|
||||
24
.github/workflows/desktop-lint.yml
vendored
@@ -1,12 +1,22 @@
|
||||
name: Code Style (C++)
|
||||
|
||||
on:
|
||||
# push trigger not needed for linting, we do not allow direct pushes to master
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'webclient/**'
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.github/workflows/translations-*.yml'
|
||||
paths:
|
||||
- '*/**' # matches all files not in root
|
||||
- '!**.md'
|
||||
- '!.ci/**'
|
||||
- '!.github/**'
|
||||
- '!.husky/**'
|
||||
- '!.tx/**'
|
||||
- '!doc/**'
|
||||
- '!webclient/**'
|
||||
- '.ci/lint_cpp.sh'
|
||||
- '.github/workflows/desktop-lint.yml'
|
||||
- '.clang-format'
|
||||
- '.cmake-format.json'
|
||||
- 'format.sh'
|
||||
|
||||
jobs:
|
||||
format:
|
||||
@@ -14,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 20 # should be enough to find merge base
|
||||
|
||||
@@ -22,7 +32,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends clang-format cmake-format
|
||||
sudo apt-get install -y --no-install-recommends clang-format cmake-format shellcheck
|
||||
|
||||
- name: Check code formatting
|
||||
shell: bash
|
||||
|
||||
66
.github/workflows/docker-release.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*Release*'
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/docker-release.yml'
|
||||
- 'Dockerfile'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: amd64 & arm64
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Docker metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/cockatrice/servatrice
|
||||
labels: |
|
||||
org.opencontainers.image.title=Servatrice
|
||||
org.opencontainers.image.url=https://cockatrice.github.io/
|
||||
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
|
||||
annotations: |
|
||||
org.opencontainers.image.title=Servatrice
|
||||
org.opencontainers.image.url=https://cockatrice.github.io/
|
||||
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.ref_type == 'tag'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.ref_type == 'tag' }}
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
61
.github/workflows/documentation-build.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Generate Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*' # Only re-generate docs when a new tagged version is pushed
|
||||
pull_request:
|
||||
paths:
|
||||
- 'doc/doxygen/**'
|
||||
- '.github/workflows/documentation-build.yml'
|
||||
- 'Doxyfile'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Doxygen
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Graphviz
|
||||
run: |
|
||||
sudo apt-get install -y graphviz
|
||||
dot -V
|
||||
|
||||
- name: Install Doxygen
|
||||
uses: ssciwr/doxygen-install@v1
|
||||
with:
|
||||
version: "1.14.0"
|
||||
|
||||
- name: Update Doxygen Configuration
|
||||
run: |
|
||||
git diff Doxyfile
|
||||
doxygen -u Doxyfile
|
||||
if git diff --quiet Doxyfile; then
|
||||
echo "::notice::No config changes in Doxyfile detected."
|
||||
else
|
||||
echo "::error::Config changes in Doxyfile detected! Please update the file by running 'doxygen -u Doxyfile'."
|
||||
echo ""
|
||||
git diff --color=always Doxyfile
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Generate Documentation
|
||||
if: always()
|
||||
run: doxygen Doxyfile
|
||||
|
||||
- name: Deploy to cockatrice.github.io
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
|
||||
external_repository: Cockatrice/cockatrice.github.io
|
||||
publish_branch: master
|
||||
publish_dir: ./docs/html
|
||||
destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/
|
||||
5
.github/workflows/translations-pull.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
- cron: '0 0 15 1,4,7,10 *'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.tx/**'
|
||||
- '.github/workflows/translations-pull.yml'
|
||||
|
||||
jobs:
|
||||
@@ -19,7 +20,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Pull translated strings from Transifex
|
||||
uses: transifex/cli-action@v2
|
||||
@@ -51,7 +52,7 @@ jobs:
|
||||
*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/
|
||||
[1]: https://explore.transifex.com/cockatrice/cockatrice/
|
||||
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
|
||||
labels: |
|
||||
CI
|
||||
|
||||
11
.github/workflows/translations-push.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
- cron: '0 0 1 1,4,7,10 *'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.ci/update_translation_source_strings.sh'
|
||||
- '.github/workflows/translations-push.yml'
|
||||
|
||||
jobs:
|
||||
@@ -19,7 +20,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install lupdate
|
||||
shell: bash
|
||||
@@ -30,10 +31,10 @@ jobs:
|
||||
- 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
|
||||
run: |
|
||||
FILE="cockatrice/cockatrice_en@source.ts"
|
||||
export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
|
||||
FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh
|
||||
|
||||
- name: Update Oracle translation source
|
||||
id: oracle
|
||||
|
||||
10
.github/workflows/web-build.yml
vendored
@@ -5,14 +5,16 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.husky/**'
|
||||
- 'webclient/**'
|
||||
- '!**.md'
|
||||
- '.github/workflows/web-build.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/web-*.yml'
|
||||
- '.husky/**'
|
||||
- 'webclient/**'
|
||||
- '!**.md'
|
||||
- '.github/workflows/web-build.yml'
|
||||
|
||||
jobs:
|
||||
build-web:
|
||||
@@ -33,10 +35,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{matrix.node_version}}
|
||||
cache: 'npm'
|
||||
|
||||
7
.github/workflows/web-lint.yml
vendored
@@ -1,11 +1,12 @@
|
||||
name: Code Style (TypeScript)
|
||||
|
||||
on:
|
||||
# push trigger not needed for linting, we do not allow direct pushes to master
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/web-*.yml'
|
||||
- 'webclient/**'
|
||||
- '!**.md'
|
||||
- '.github/workflows/web-lint.yml'
|
||||
|
||||
jobs:
|
||||
ESLint:
|
||||
@@ -17,10 +18,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'webclient/package-lock.json'
|
||||
|
||||
1
.gitignore
vendored
@@ -14,3 +14,4 @@ compile_commands.json
|
||||
.cache
|
||||
.gdb_history
|
||||
cockatrice/resources/config/qtlogging.ini
|
||||
docs/
|
||||
|
||||
@@ -24,6 +24,8 @@ option(WITH_ORACLE "build oracle" ON)
|
||||
option(WITH_DBCONVERTER "build dbconverter" ON)
|
||||
# Compile tests
|
||||
option(TEST "build tests" OFF)
|
||||
# Use vcpkg regardless of OS
|
||||
option(USE_VCPKG "Use vcpkg regardless of OS" OFF)
|
||||
|
||||
# Default to "Release" build type
|
||||
# User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call
|
||||
@@ -48,8 +50,8 @@ if(USE_CCACHE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Use vcpkg toolchain on Windows
|
||||
if(WIN32 OR USE_VCPKG)
|
||||
# Use vcpkg toolchain on Windows (and on macOS in CI)
|
||||
set(CMAKE_TOOLCHAIN_FILE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake
|
||||
CACHE STRING "Vcpkg toolchain file"
|
||||
@@ -74,7 +76,7 @@ endif()
|
||||
|
||||
# A project name is needed for CPack
|
||||
# Version can be overriden by git tags, see cmake/getversion.cmake
|
||||
project("Cockatrice" VERSION 2.10.1)
|
||||
project("Cockatrice" VERSION 2.11.0)
|
||||
|
||||
# Set release name if not provided via env/cmake var
|
||||
if(NOT DEFINED GIT_TAG_RELEASENAME)
|
||||
@@ -301,6 +303,7 @@ if(UNIX)
|
||||
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
|
||||
if(Qt6_FOUND)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
|
||||
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
|
||||
elseif(Qt5_FOUND)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
|
||||
endif()
|
||||
@@ -325,7 +328,19 @@ endif()
|
||||
|
||||
include(CPack)
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_interfaces ${CMAKE_BINARY_DIR}/libcockatrice_interfaces)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_protocol ${CMAKE_BINARY_DIR}/libcockatrice_protocol)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_deck_list ${CMAKE_BINARY_DIR}/libcockatrice_deck_list)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_rng ${CMAKE_BINARY_DIR}/libcockatrice_rng)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_card ${CMAKE_BINARY_DIR}/libcockatrice_card)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_utility ${CMAKE_BINARY_DIR}/libcockatrice_utility)
|
||||
if(WITH_ORACLE OR WITH_CLIENT)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_settings ${CMAKE_BINARY_DIR}/libcockatrice_settings)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_models ${CMAKE_BINARY_DIR}/libcockatrice_models)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_filters ${CMAKE_BINARY_DIR}/libcockatrice_filters)
|
||||
endif()
|
||||
|
||||
if(WITH_SERVER)
|
||||
add_subdirectory(servatrice)
|
||||
set(CPACK_INSTALL_CMAKE_PROJECTS "Servatrice;Servatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
|
||||
|
||||
31
Dockerfile
@@ -1,8 +1,9 @@
|
||||
FROM ubuntu:24.04
|
||||
# -------- Build Stage --------
|
||||
FROM ubuntu:24.04 AS build
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y\
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
file \
|
||||
@@ -16,16 +17,28 @@ RUN apt-get update && apt-get install -y\
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools
|
||||
|
||||
COPY . /home/servatrice/code/
|
||||
WORKDIR /home/servatrice/code
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN mkdir build && cd build && \
|
||||
cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=0 && \
|
||||
make -j$(nproc) && \
|
||||
make install
|
||||
|
||||
WORKDIR build
|
||||
RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=0 &&\
|
||||
make &&\
|
||||
make install
|
||||
|
||||
# -------- Runtime Stage (clean) --------
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libprotobuf32t64 \
|
||||
libqt6sql6-mysql \
|
||||
libqt6websockets6 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Only copy installed binaries, not source
|
||||
COPY --from=build /usr/local /usr/local
|
||||
|
||||
WORKDIR /home/servatrice
|
||||
|
||||
EXPOSE 4748
|
||||
|
||||
ENTRYPOINT [ "servatrice", "--log-to-console" ]
|
||||
|
||||
9
LICENSE
@@ -2,7 +2,7 @@
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
<https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
@@ -304,8 +304,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@@ -329,8 +328,8 @@ necessary. Here is a sample; alter the names:
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
<signature of Moe Ghoul>, 1 April 1989
|
||||
Moe Ghoul, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
|
||||
211
README.md
@@ -5,154 +5,155 @@
|
||||
<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="#community-resources">Community</a> <b>|</b>
|
||||
<a href="#translations-">Translations</a> <b>|</b>
|
||||
<a href="#build--">Build</a> <b>|</b>
|
||||
<a href="#run">Run</a> <b>|</b>
|
||||
<a href="#license-">License</a>
|
||||
<a href="#related-repositories">Related</a> <b>|</b>
|
||||
<a href="#community-resources-">Community</a> <b>|</b>
|
||||
<a href="#contribute">Contribute</a> <b>|</b>
|
||||
<a href="#build---">Build</a> <b>|</b>
|
||||
<a href="#run">Run</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<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>
|
||||
</pre><br>
|
||||
<br><pre><p align='center'>
|
||||
<b>To get started with Cockatrice ⇢ [view our webpage](https://cockatrice.github.io/)</b><br>
|
||||
<b>To get support, or suggest changes to the app ⇢ [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 ⇢ learn [how to contribute](#contribute-)</b>
|
||||
</pre><p><br>
|
||||
|
||||
|
||||
# Cockatrice
|
||||
|
||||
Cockatrice is an open-source, multiplatform program for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline. This project uses C++ and the Qt5 libraries.<br>
|
||||
Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.<br><br>
|
||||
This project uses <kbd>C++</kbd> and the <kbd>Qt</kbd> libraries.<br>
|
||||
First work on a webclient with <kbd>Typescript</kbd> was started as well.<br>
|
||||
|
||||
|
||||
# Download [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice)
|
||||
# Download [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
|
||||
|
||||
Downloads are available for full releases and the current beta version in development. There is no strict release schedule for either of them.
|
||||
Downloads are available for **stable releases** and the current **beta version** in development.
|
||||
There is no strict release schedule for either of them.
|
||||
<br><pre>
|
||||
Latest <kbd>stable</kbd> release:
|
||||
[](https://github.com/cockatrice/cockatrice/releases/latest)  [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
|
||||
</pre><pre>
|
||||
Latest <kbd>beta</kbd> version:
|
||||
[](https://github.com/cockatrice/cockatrice/releases)  [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0) [](https://github.com/Cockatrice/Cockatrice/pulls?q=is%3Apr+is%3Aclosed)
|
||||
<sub><i>While incorporating the latest fixes and features, beta builds may not be stable or contain new bugs!</i></sub>
|
||||
<sub><b><i>Please report any findings and open new issues when testing them!</i></b></sub>
|
||||
</pre>
|
||||
|
||||
- Latest `stable` release: [](https://github.com/cockatrice/cockatrice/releases/latest) [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice)<br>
|
||||
- Stable versions are checkpoints featuring major feature and UI enhancements.
|
||||
- **Recommended for most users!**
|
||||
# Related Repositories
|
||||
|
||||
- Latest `beta` release: [](https://github.com/cockatrice/cockatrice/releases) [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice)
|
||||
- Beta versions include the most recently added features and bugfixes, but can be unstable.
|
||||
- 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)
|
||||
|
||||
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).
|
||||
We maintain two tags for contributors to find issues to work on:
|
||||
- [Good first issue](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Good%20first%20issue%22%20): issues tagged in this way provide a simple way to get started. They don't require much experience to be worked on.
|
||||
- [Help wanted](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Help%20Wanted%22%20): This tag is used for issues that we are looking for a contributor to work on. Often this is for feature suggestions we are willing to accept, but don't have the time to work on ourselves.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice
|
||||
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice
|
||||
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official webpage of the Cockatrice project
|
||||
|
||||
|
||||
# Community Resources
|
||||
# Community Resources [](https://discord.gg/3Z9yzmA)
|
||||
|
||||
- [Cockatrice Official Site](https://cockatrice.github.io)
|
||||
- [Cockatrice Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
|
||||
- [Cockatrice Official Discord](https://discord.gg/3Z9yzmA)
|
||||
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
|
||||
- [Official Website](https://cockatrice.github.io)
|
||||
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
|
||||
- [Official Discord](https://discord.gg/3Z9yzmA)
|
||||
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
|
||||
|
||||
|
||||
# Translations [](https://transifex.com/cockatrice/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>
|
||||
>[!IMPORTANT]
|
||||
>For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
|
||||
|
||||
|
||||
# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
|
||||
# Contribute
|
||||
|
||||
**Detailed compiling instructions can be found on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)**
|
||||
### Code
|
||||
|
||||
Dependencies: *(for minimum requirements search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
|
||||
To contribute code to the project, please review our [guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.<br>
|
||||
We maintain two tags for contributors to easier find issues to potentially work on:
|
||||
- [](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Good%20first%20issue%22%20)<br>
|
||||
Issues tagged in this way provide a simple way to get started. They don't require much experience to be worked on.
|
||||
- [](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Help%20Wanted%22%20)<br>
|
||||
This tag is used for issues that we are looking for somebody to pick up. Often this is for feature suggestions we support, but don't have the time to work on ourselves.
|
||||
|
||||
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 reviews for submitted changes.<br>
|
||||
We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on!
|
||||
|
||||
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
|
||||
|
||||
<details>
|
||||
<summary><b>Kudos to our amazing contributors ❤️</b></summary>
|
||||
<br>
|
||||
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
|
||||
</a><br>
|
||||
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
|
||||
</details>
|
||||
|
||||
### Translations [](https://explore.transifex.com/cockatrice/cockatrice/)
|
||||
|
||||
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
|
||||
|
||||
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!<br>
|
||||
|
||||
|
||||
# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
|
||||
|
||||
Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
|
||||
- [Qt](https://www.qt.io/developers/)
|
||||
- [protobuf](https://github.com/protocolbuffers/protobuf)
|
||||
- [CMake](https://www.cmake.org/)
|
||||
|
||||
Oracle can optionally use zlib and xz to load compressed files:
|
||||
Oracle can *optionally* use some packages to load compressed card files:
|
||||
- [xz](https://tukaani.org/xz/)
|
||||
- [zlib](https://www.zlib.net/)
|
||||
|
||||
To compile:
|
||||
<br>
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
|
||||
You can then run
|
||||
**Basic compilation steps:**
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
You can then
|
||||
- Create a Cockatrice installation inside the `release` folder:
|
||||
```bash
|
||||
make install
|
||||
|
||||
to get a cockatrice installation inside the `release` folder, or:
|
||||
|
||||
```
|
||||
- Or make an installation package specific to your system:
|
||||
```bash
|
||||
make package
|
||||
```
|
||||
|
||||
to create a system-specific installation package.
|
||||
>[!NOTE]
|
||||
>Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
|
||||
|
||||
The following flags can be passed to `cmake`:
|
||||
<br>
|
||||
|
||||
- `-DWITH_SERVER=1` Whether to build the server (default 0 = no).
|
||||
- `-DWITH_CLIENT=0` Whether to build the client (default 1 = yes).
|
||||
- `-DWITH_ORACLE=0` Whether to build oracle (default 1 = yes).
|
||||
- `-DCMAKE_BUILD_TYPE=Debug` Compile in debug mode. Enables extra logging output, debug symbols, and much more verbose compiler warnings (default `Release`).
|
||||
- `-DWARNING_AS_ERROR=0` Whether to treat compilation warnings as errors in debug mode (default 1 = yes).
|
||||
- `-DUPDATE_TRANSLATIONS=1` Configure `make` to update the translation .ts files for new strings in the source code. Note: Running `make clean` will remove the .ts files (default 0 = no).
|
||||
- `-DTEST=1` Enable regression tests (default 0 = no). Note: needs googletest, will be downloaded on the fly if unavailable. To run tests: ```make test```.
|
||||
- `-DFORCE_USE_QT5=1` Skip looking for Qt6 before trying to find Qt5
|
||||
The following flags (with their non-default values) can be passed to `cmake`:
|
||||
| Flag | Description |
|
||||
| --- | --- |
|
||||
| `-DWITH_SERVER=1` | Build <kbd>Servatrice</kbd> server |
|
||||
| `-DWITH_CLIENT=0` | Don't build <kbd>Cockatrice</kbd> client |
|
||||
| `-DWITH_ORACLE=0` | Don't build <kbd>Oracle</kbd> card database tool |
|
||||
| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode<br> Enables extra logging output, debug symbols, and much more verbose compiler warnings |
|
||||
| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
|
||||
| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code<br> **Note:** `make clean` will remove the .ts files |
|
||||
| `-DTEST=1` | Enable regression tests<br> **Note:** `make test` to run tests, *googletest* will be downloaded if not available |
|
||||
| `-DFORCE_USE_QT5=1` | Skip looking for Qt6 before trying to find Qt5 |
|
||||
|
||||
|
||||
# Run
|
||||
|
||||
`Cockatrice` is the game client<br>
|
||||
`Oracle` fetches card data<br>
|
||||
`Servatrice` is the server<br>
|
||||
<kbd>Cockatrice</kbd> is the game client<br>
|
||||
<kbd>Oracle</kbd> fetches card data<br>
|
||||
<kbd>Servatrice</kbd> is the server<br>
|
||||
|
||||
**Servatrice Docker container**
|
||||
#### Docker
|
||||
|
||||
You can run an instance of Servatrice (the Cockatrice server) using [Docker](https://www.docker.com/what-docker) and the Cockatrice Dockerfile.<br>
|
||||
You can build an image & deploy a <kbd>Servatrice</kbd> (Cockatrice server) container using [Docker](https://www.docker.com/resources/what-container/) and our Dockerfile yourself.<br>
|
||||
|
||||
First, create an image from the Dockerfile<br>
|
||||
`cd /path/to/Cockatrice-Repo/`
|
||||
`docker build -t servatrice .`<br>
|
||||
And then run it<br>
|
||||
`docker run -i -p 4748:4748 -t servatrice:latest`<br>
|
||||
|
||||
>Note: Running this command exposes the port 4748 of the docker container<br>
|
||||
to permit connections to the server.
|
||||
|
||||
Find more information on how to use Servatrice with Docker in our [wiki](https://github.com/Cockatrice/Cockatrice/wiki/Setting-up-Servatrice#using-docker).
|
||||
|
||||
**Docker compose**
|
||||
|
||||
There is also a docker-compose file available which will configure and run both a MySQL server and Servatrice. The docker-compose setup scripts can be found in the `servatrice/docker` folder and vary only slightly from the default sql and server .ini files. The setup scripts can either be modified in place, or docker-compose can mount alternative files into the images, as you prefer.
|
||||
|
||||
To run Servatrice via docker-compose, first install docker-compose following the [install instructions](https://docs.docker.com/compose/install/). Once installed, run the following from the root of the repository:
|
||||
```bash
|
||||
docker-compose build # Build the Servatrice image using the same Dockerfile as above.
|
||||
docker-compose up # Setup and run both the MySQL server and Servatrice.
|
||||
```
|
||||
|
||||
>Note: Similar to the above Docker setup, this will expose port 4748.
|
||||
|
||||
>Note: The first time running the docker-compose setup, the MySQL server will take a little time to run the initial setup scripts. Due to this, the Servatrice instance may fail the first few attempts to connect to the database. Servatrice is set to `restart: always` in the docker-compose.yml, which will allow it to continue attempting to start up. Once the MySQL scripts have completed, Servatrice should then connect automatically on the next attempt.
|
||||
|
||||
**Docker compose in Windows**
|
||||
A out of box working docker-compose file has been added to help setup in Windows.
|
||||
|
||||
Docker in Windows requires additional steps in form of using Docker Desktop to allow resource sharing from the drive the volumes are mapped from, as well as potential workarounds needed to get file sharing working in Windows. This [StackOverflow discussion sheds some light on it](https://stackoverflow.com/questions/42203488/settings-to-windows-firewall-to-allow-docker-for-windows-to-share-drive)
|
||||
For more details, look into our wiki section on [Setting up Servatrice](https://github.com/Cockatrice/Cockatrice/wiki/Setting-up-Servatrice#using-docker).<br>
|
||||
You'll also find more hints on our **pre-build image** there, or the **docker-compose** file which will configure and run both a MySQL server and Servatrice.
|
||||
|
||||
|
||||
# License [](https://github.com/Cockatrice/Cockatrice/blob/master/LICENSE)
|
||||
|
||||
@@ -115,4 +115,7 @@ string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MO
|
||||
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}")
|
||||
|
||||
# Core-only export (useful for headless libs)
|
||||
set(QT_CORE_MODULE "${COCKATRICE_QT_VERSION_NAME}::Core")
|
||||
|
||||
message(STATUS "Found Qt ${${COCKATRICE_QT_VERSION_NAME}_VERSION} at: ${${COCKATRICE_QT_VERSION_NAME}_DIR}")
|
||||
|
||||
@@ -4,9 +4,285 @@
|
||||
|
||||
project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
|
||||
|
||||
file(GLOB_RECURSE cockatrice_CPP_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp)
|
||||
|
||||
set(cockatrice_SOURCES ${cockatrice_CPP_FILES} ${VERSION_STRING_CPP})
|
||||
set(cockatrice_SOURCES
|
||||
${VERSION_STRING_CPP}
|
||||
# sort by alphabetical order, so that there is no debate about where to add new sources to the list
|
||||
src/client/network/update/client/update_downloader.cpp
|
||||
src/client/network/interfaces/deck_stats_interface.cpp
|
||||
src/client/network/interfaces/tapped_out_interface.cpp
|
||||
src/client/network/parsers/deck_link_to_api_transformer.cpp
|
||||
src/client/network/update/client/client_update_checker.cpp
|
||||
src/client/network/update/client/release_channel.cpp
|
||||
src/client/network/update/card_spoiler/spoiler_background_updater.cpp
|
||||
src/client/sound_engine.cpp
|
||||
src/client/settings/cache_settings.cpp
|
||||
src/client/settings/card_counter_settings.cpp
|
||||
src/client/settings/shortcut_treeview.cpp
|
||||
src/client/settings/shortcuts_settings.cpp
|
||||
src/interface/deck_loader/deck_loader.cpp
|
||||
src/interface/widgets/dialogs/dlg_connect.cpp
|
||||
src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp
|
||||
src/interface/widgets/dialogs/dlg_create_game.cpp
|
||||
src/interface/widgets/dialogs/dlg_default_tags_editor.cpp
|
||||
src/interface/widgets/dialogs/dlg_edit_avatar.cpp
|
||||
src/interface/widgets/dialogs/dlg_edit_password.cpp
|
||||
src/interface/widgets/dialogs/dlg_edit_tokens.cpp
|
||||
src/interface/widgets/dialogs/dlg_edit_user.cpp
|
||||
src/interface/widgets/dialogs/dlg_filter_games.cpp
|
||||
src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp
|
||||
src/interface/widgets/dialogs/dlg_forgot_password_request.cpp
|
||||
src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp
|
||||
src/interface/widgets/dialogs/dlg_load_deck.cpp
|
||||
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
|
||||
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
|
||||
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp
|
||||
src/interface/widgets/dialogs/dlg_manage_sets.cpp
|
||||
src/interface/widgets/dialogs/dlg_register.cpp
|
||||
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp
|
||||
src/interface/widgets/dialogs/dlg_settings.cpp
|
||||
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
|
||||
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp
|
||||
src/interface/widgets/dialogs/dlg_update.cpp
|
||||
src/interface/widgets/dialogs/dlg_view_log.cpp
|
||||
src/interface/widgets/dialogs/tip_of_the_day.cpp
|
||||
src/filters/deck_filter_string.cpp
|
||||
src/filters/filter_builder.cpp
|
||||
src/filters/filter_tree_model.cpp
|
||||
src/filters/syntax_help.cpp
|
||||
src/game/abstract_game.cpp
|
||||
src/game/board/abstract_card_drag_item.cpp
|
||||
src/game/board/abstract_card_item.cpp
|
||||
src/game/board/abstract_counter.cpp
|
||||
src/game/board/arrow_item.cpp
|
||||
src/game/board/arrow_target.cpp
|
||||
src/game/board/card_drag_item.cpp
|
||||
src/game/board/card_item.cpp
|
||||
src/game/board/card_list.cpp
|
||||
src/game/board/counter_general.cpp
|
||||
src/game/board/translate_counter_name.cpp
|
||||
src/game/deckview/deck_view.cpp
|
||||
src/game/deckview/deck_view_container.cpp
|
||||
src/game/deckview/tabbed_deck_view_container.cpp
|
||||
src/game/dialogs/dlg_create_token.cpp
|
||||
src/game/dialogs/dlg_move_top_cards_until.cpp
|
||||
src/game/dialogs/dlg_roll_dice.cpp
|
||||
src/game/game.cpp
|
||||
src/game/game_event_handler.cpp
|
||||
src/game/game_meta_info.cpp
|
||||
src/game/game_scene.cpp
|
||||
src/game/game_state.cpp
|
||||
src/game/game_view.cpp
|
||||
src/game/hand_counter.cpp
|
||||
src/game/log/message_log_widget.cpp
|
||||
src/game/phase.cpp
|
||||
src/game/phases_toolbar.cpp
|
||||
src/game/player/menu/card_menu.cpp
|
||||
src/game/player/menu/custom_zone_menu.cpp
|
||||
src/game/player/menu/grave_menu.cpp
|
||||
src/game/player/menu/hand_menu.cpp
|
||||
src/game/player/menu/library_menu.cpp
|
||||
src/game/player/menu/move_menu.cpp
|
||||
src/game/player/menu/player_menu.cpp
|
||||
src/game/player/menu/pt_menu.cpp
|
||||
src/game/player/menu/rfg_menu.cpp
|
||||
src/game/player/menu/say_menu.cpp
|
||||
src/game/player/menu/sideboard_menu.cpp
|
||||
src/game/player/menu/utility_menu.cpp
|
||||
src/game/player/player.cpp
|
||||
src/game/player/player_actions.cpp
|
||||
src/game/player/player_area.cpp
|
||||
src/game/player/player_event_handler.cpp
|
||||
src/game/player/player_graphics_item.cpp
|
||||
src/game/player/player_info.cpp
|
||||
src/game/player/player_list_widget.cpp
|
||||
src/game/player/player_manager.cpp
|
||||
src/game/player/player_target.cpp
|
||||
src/game/replay.cpp
|
||||
src/game/zones/card_zone.cpp
|
||||
src/game/zones/hand_zone.cpp
|
||||
src/game/zones/logic/card_zone_logic.cpp
|
||||
src/game/zones/logic/hand_zone_logic.cpp
|
||||
src/game/zones/logic/pile_zone_logic.cpp
|
||||
src/game/zones/logic/stack_zone_logic.cpp
|
||||
src/game/zones/logic/table_zone_logic.cpp
|
||||
src/game/zones/logic/view_zone_logic.cpp
|
||||
src/game/zones/pile_zone.cpp
|
||||
src/game/zones/select_zone.cpp
|
||||
src/game/zones/stack_zone.cpp
|
||||
src/game/zones/table_zone.cpp
|
||||
src/game/zones/view_zone.cpp
|
||||
src/game/zones/view_zone_widget.cpp
|
||||
src/game_graphics/board/abstract_graphics_item.cpp
|
||||
src/interface/card_picture_loader/card_picture_loader.cpp
|
||||
src/interface/card_picture_loader/card_picture_loader_local.cpp
|
||||
src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.cpp
|
||||
src/interface/card_picture_loader/card_picture_loader_status_bar.cpp
|
||||
src/interface/card_picture_loader/card_picture_loader_worker.cpp
|
||||
src/interface/card_picture_loader/card_picture_loader_worker_work.cpp
|
||||
src/interface/card_picture_loader/card_picture_to_load.cpp
|
||||
src/interface/layouts/flow_layout.cpp
|
||||
src/interface/layouts/overlap_layout.cpp
|
||||
src/interface/widgets/utility/line_edit_completer.cpp
|
||||
src/interface/pixel_map_generator.cpp
|
||||
src/interface/theme_manager.cpp
|
||||
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
|
||||
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
|
||||
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
|
||||
src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
|
||||
src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
|
||||
src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
|
||||
src/interface/widgets/cards/card_info_display_widget.cpp
|
||||
src/interface/widgets/cards/card_info_frame_widget.cpp
|
||||
src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp
|
||||
src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp
|
||||
src/interface/widgets/cards/card_info_picture_widget.cpp
|
||||
src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
|
||||
src/interface/widgets/cards/card_info_text_widget.cpp
|
||||
src/interface/widgets/cards/card_size_widget.cpp
|
||||
src/interface/widgets/cards/deck_card_zone_display_widget.cpp
|
||||
src/interface/widgets/cards/deck_preview_card_picture_widget.cpp
|
||||
src/interface/widgets/deck_analytics/deck_analytics_widget.cpp
|
||||
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
|
||||
src/interface/widgets/deck_analytics/mana_base_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
|
||||
src/interface/widgets/deck_analytics/mana_devotion_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
||||
src/interface/widgets/general/background_sources.cpp
|
||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||
src/interface/widgets/general/display/banner_widget.cpp
|
||||
src/interface/widgets/general/display/bar_widget.cpp
|
||||
src/interface/widgets/general/display/color_bar.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_label.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||
src/interface/widgets/general/display/labeled_input.cpp
|
||||
src/interface/widgets/general/display/percent_bar_widget.cpp
|
||||
src/interface/widgets/general/display/shadow_background_label.cpp
|
||||
src/interface/widgets/general/home_styled_button.cpp
|
||||
src/interface/widgets/general/home_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
|
||||
src/interface/widgets/general/layout_containers/overlap_widget.cpp
|
||||
src/interface/widgets/menus/deck_editor_menu.cpp
|
||||
src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp
|
||||
src/interface/widgets/printing_selector/card_amount_widget.cpp
|
||||
src/interface/widgets/printing_selector/printing_selector.cpp
|
||||
src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp
|
||||
src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
|
||||
src/interface/widgets/printing_selector/printing_selector_card_search_widget.cpp
|
||||
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
|
||||
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
|
||||
src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
|
||||
src/interface/widgets/quick_settings/settings_button_widget.cpp
|
||||
src/interface/widgets/quick_settings/settings_popup_widget.cpp
|
||||
src/interface/widgets/replay/replay_manager.cpp
|
||||
src/interface/widgets/replay/replay_timeline_widget.cpp
|
||||
src/interface/widgets/server/chat_view/chat_view.cpp
|
||||
src/interface/widgets/server/game_selector.cpp
|
||||
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
|
||||
src/interface/widgets/server/games_model.cpp
|
||||
src/interface/widgets/server/handle_public_servers.cpp
|
||||
src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp
|
||||
src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
|
||||
src/interface/widgets/server/user/user_context_menu.cpp
|
||||
src/interface/widgets/server/user/user_info_box.cpp
|
||||
src/interface/widgets/server/user/user_info_connection.cpp
|
||||
src/interface/widgets/server/user/user_list_manager.cpp
|
||||
src/interface/widgets/server/user/user_list_widget.cpp
|
||||
src/interface/widgets/utility/custom_line_edit.cpp
|
||||
src/interface/widgets/utility/get_text_with_max.cpp
|
||||
src/interface/widgets/utility/sequence_edit.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp
|
||||
src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
|
||||
src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp
|
||||
src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
|
||||
src/interface/window_main.cpp
|
||||
src/main.cpp
|
||||
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp
|
||||
src/interface/widgets/tabs/tab.cpp
|
||||
src/interface/widgets/tabs/tab_account.cpp
|
||||
src/interface/widgets/tabs/tab_admin.cpp
|
||||
src/interface/widgets/tabs/tab_deck_editor.cpp
|
||||
src/interface/widgets/tabs/tab_deck_storage.cpp
|
||||
src/interface/widgets/tabs/tab_game.cpp
|
||||
src/interface/widgets/tabs/tab_home.cpp
|
||||
src/interface/widgets/tabs/tab_logs.cpp
|
||||
src/interface/widgets/tabs/tab_message.cpp
|
||||
src/interface/widgets/tabs/tab_replays.cpp
|
||||
src/interface/widgets/tabs/tab_room.cpp
|
||||
src/interface/widgets/tabs/tab_server.cpp
|
||||
src/interface/widgets/tabs/tab_supervisor.cpp
|
||||
src/interface/widgets/tabs/tab_visual_database_display.cpp
|
||||
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp
|
||||
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp
|
||||
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
|
||||
src/interface/key_signals.cpp
|
||||
src/interface/logger.cpp
|
||||
)
|
||||
|
||||
add_subdirectory(sounds)
|
||||
add_subdirectory(themes)
|
||||
@@ -17,15 +293,23 @@ configure_file(
|
||||
set(cockatrice_RESOURCES cockatrice.qrc)
|
||||
|
||||
if(UPDATE_TRANSLATIONS)
|
||||
# Cockatrice main sources
|
||||
file(GLOB_RECURSE translate_cockatrice_SRCS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp
|
||||
${CMAKE_SOURCE_DIR}/cockatrice/src/*.h
|
||||
)
|
||||
file(GLOB_RECURSE translate_common_SRCS ${CMAKE_SOURCE_DIR}/common/*.cpp ${CMAKE_SOURCE_DIR}/common/*.h)
|
||||
set(translate_SRCS ${translate_cockatrice_SRCS} ${translate_common_SRCS})
|
||||
|
||||
# All libcockatrice_* libraries (recursively)
|
||||
file(GLOB_RECURSE translate_lib_SRCS ${CMAKE_SOURCE_DIR}/libcockatrice_*/**/*.cpp
|
||||
${CMAKE_SOURCE_DIR}/libcockatrice_*/**/*.h
|
||||
)
|
||||
|
||||
# Combine all sources for translation
|
||||
set(translate_SRCS ${translate_cockatrice_SRCS} ${translate_lib_SRCS})
|
||||
|
||||
set(cockatrice_TS "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice_en@source.ts")
|
||||
else()
|
||||
file(GLOB cockatrice_TS "${CMAKE_CURRENT_SOURCE_DIR}/translations/*.ts")
|
||||
endif(UPDATE_TRANSLATIONS)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(cockatrice_SOURCES ${cockatrice_SOURCES} cockatrice.rc)
|
||||
@@ -55,12 +339,6 @@ set(DESKTOPDIR
|
||||
CACHE STRING "desktop file destination"
|
||||
)
|
||||
|
||||
# Include directories
|
||||
include_directories(../common)
|
||||
include_directories(${PROTOBUF_INCLUDE_DIR})
|
||||
include_directories(${CMAKE_BINARY_DIR}/common)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations")
|
||||
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
|
||||
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
|
||||
@@ -100,9 +378,31 @@ elseif(Qt5_FOUND)
|
||||
endif()
|
||||
|
||||
if(Qt5_FOUND)
|
||||
target_link_libraries(cockatrice cockatrice_common ${COCKATRICE_QT_MODULES})
|
||||
target_link_libraries(
|
||||
cockatrice
|
||||
libcockatrice_card
|
||||
libcockatrice_deck_list
|
||||
libcockatrice_filters
|
||||
libcockatrice_utility
|
||||
libcockatrice_network
|
||||
libcockatrice_models
|
||||
libcockatrice_rng
|
||||
libcockatrice_settings
|
||||
${COCKATRICE_QT_MODULES}
|
||||
)
|
||||
else()
|
||||
target_link_libraries(cockatrice PUBLIC cockatrice_common ${COCKATRICE_QT_MODULES})
|
||||
target_link_libraries(
|
||||
cockatrice
|
||||
PUBLIC libcockatrice_card
|
||||
libcockatrice_deck_list
|
||||
libcockatrice_filters
|
||||
libcockatrice_utility
|
||||
libcockatrice_network
|
||||
libcockatrice_models
|
||||
libcockatrice_rng
|
||||
libcockatrice_settings
|
||||
${COCKATRICE_QT_MODULES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
|
||||
@@ -7,11 +7,14 @@
|
||||
|
||||
<file>resources/icons/arrow_bottom_green.svg</file>
|
||||
<file>resources/icons/arrow_down_green.svg</file>
|
||||
<file>resources/icons/arrow_history.svg</file>
|
||||
<file>resources/icons/arrow_left_green.svg</file>
|
||||
<file>resources/icons/arrow_redo.svg</file>
|
||||
<file>resources/icons/arrow_right_blue.svg</file>
|
||||
<file>resources/icons/arrow_right_green.svg</file>
|
||||
<file>resources/icons/arrow_top_green.svg</file>
|
||||
<file>resources/icons/arrow_up_green.svg</file>
|
||||
<file>resources/icons/arrow_undo.svg</file>
|
||||
<file>resources/icons/clearsearch.svg</file>
|
||||
<file>resources/icons/cogwheel.svg</file>
|
||||
<file>resources/icons/conceded.svg</file>
|
||||
@@ -25,6 +28,7 @@
|
||||
<file>resources/icons/lock.svg</file>
|
||||
<file>resources/icons/not_ready_start.svg</file>
|
||||
<file>resources/icons/pencil.svg</file>
|
||||
<file>resources/icons/pin.svg</file>
|
||||
<file>resources/icons/player.svg</file>
|
||||
<file>resources/icons/ready_start.svg</file>
|
||||
<file>resources/icons/reload.svg</file>
|
||||
@@ -33,6 +37,7 @@
|
||||
<file>resources/icons/scales.svg</file>
|
||||
<file>resources/icons/search.svg</file>
|
||||
<file>resources/icons/settings.svg</file>
|
||||
<file>resources/icons/share.svg</file>
|
||||
<file>resources/icons/spectator.svg</file>
|
||||
<file>resources/icons/swap.svg</file>
|
||||
<file>resources/icons/sync.svg</file>
|
||||
@@ -46,6 +51,8 @@
|
||||
<file>resources/icons/mana/U.svg</file>
|
||||
<file>resources/icons/mana/W.svg</file>
|
||||
|
||||
<file>resources/backgrounds/home.png</file>
|
||||
|
||||
<file>resources/config/general.svg</file>
|
||||
<file>resources/config/appearance.svg</file>
|
||||
<file>resources/config/interface.svg</file>
|
||||
@@ -379,5 +386,6 @@
|
||||
<file>resources/tips/tips_of_the_day.xml</file>
|
||||
|
||||
<file>resources/help/search.md</file>
|
||||
<file>resources/help/deck_search.md</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
BIN
cockatrice/resources/backgrounds/home.png
Normal file
|
After Width: | Height: | Size: 12 MiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 9.9 KiB |
@@ -1,56 +1,68 @@
|
||||
[Rules]
|
||||
# Uncomment a rule to disable logging for that category
|
||||
# The default log level is info
|
||||
*.debug = false
|
||||
|
||||
# main = false
|
||||
# qt_translator = false
|
||||
# window_main.* = false
|
||||
# release_channel = false
|
||||
# spoiler_background_updater = false
|
||||
# theme_manager = false
|
||||
# sound_engine = false
|
||||
# tapped_out_interface = false
|
||||
# Uncomment a rule to see debug level logs for that category,
|
||||
# or set <category> = false to disable logging
|
||||
|
||||
# tab_game = false
|
||||
# tab_message = false
|
||||
# tab_supervisor = false
|
||||
#main = true
|
||||
#qt_translator = true
|
||||
#window_main.* = true
|
||||
#release_channel = true
|
||||
#spoiler_background_updater = true
|
||||
#theme_manager = true
|
||||
#sound_engine = true
|
||||
#tapped_out_interface = true
|
||||
|
||||
# dlg_edit_avatar = false
|
||||
# dlg_settings = false
|
||||
# dlg_tip_of_the_day = false
|
||||
# dlg_update = false
|
||||
#tab_game = true
|
||||
#tab_message = true
|
||||
#tab_supervisor = true
|
||||
|
||||
# settings_cache = false
|
||||
# servers_settings = false
|
||||
# shortcuts_settings = false
|
||||
#dlg_edit_avatar = true
|
||||
#dlg_load_deck_from_website = true
|
||||
#dlg_settings = true
|
||||
#dlg_tip_of_the_day = true
|
||||
#dlg_update = true
|
||||
|
||||
# remote_client = false
|
||||
#settings_cache = true
|
||||
#servers_settings = true
|
||||
#shortcuts_settings = true
|
||||
|
||||
# player = false
|
||||
# game_scene = false
|
||||
# game_scene.player_addition_removal = false
|
||||
# card_zone = false
|
||||
# view_zone = false
|
||||
#local_client = true
|
||||
#remote_client = true
|
||||
|
||||
# user_info_connection = false
|
||||
#player = true
|
||||
#game_scene = true
|
||||
#game_scene.player_addition_removal = true
|
||||
#card_zone = true
|
||||
#view_zone = true
|
||||
|
||||
# picture_loader = false
|
||||
# picture_loader.worker = false
|
||||
# picture_loader.card_back_cache_fail = false
|
||||
# picture_loader.picture_to_load = false
|
||||
# deck_loader = false
|
||||
# card_database = false
|
||||
# card_database.loading = false
|
||||
# card_database.loading.success_or_failure = false
|
||||
# cockatrice_xml.* = false
|
||||
# cockatrice_xml.xml_3_parser = false
|
||||
# cockatrice_xml.xml_4_parser = false
|
||||
# card_info = false
|
||||
# card_list = false
|
||||
#game_event_handler = true
|
||||
|
||||
#flow_layout = false
|
||||
#flow_widget = false
|
||||
#flow_widget.size = false
|
||||
#user_info_connection = true
|
||||
|
||||
# pixel_map_generator = false
|
||||
#card_picture_loader = true
|
||||
#card_picture_loader.worker = true
|
||||
#card_picture_loader.card_back_cache_fail = true
|
||||
#card_picture_loader.picture_to_load = true
|
||||
#deck_loader = true
|
||||
#card_database = true
|
||||
#card_database.loading = true
|
||||
#card_database.loading.success_or_failure = true
|
||||
#cockatrice_xml.* = true
|
||||
#cockatrice_xml.xml_3_parser = true
|
||||
#cockatrice_xml.xml_4_parser = true
|
||||
#card_info = true
|
||||
#card_list = true
|
||||
|
||||
# filter_string = false
|
||||
#flow_layout = true
|
||||
#flow_widget = true
|
||||
#flow_widget.size = true
|
||||
|
||||
#card_info_picture_widget = true
|
||||
|
||||
#pixel_map_generator = true
|
||||
|
||||
#deck_filter_string = true
|
||||
#filter_string = true
|
||||
#syntax_help = true
|
||||
|
||||
42
cockatrice/resources/help/deck_search.md
Normal file
@@ -0,0 +1,42 @@
|
||||
@page deck_search_syntax_help Deck Search Syntax Help
|
||||
|
||||
## Deck Search Syntax Help
|
||||
-----
|
||||
The search bar recognizes a set of special commands.<br>
|
||||
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all
|
||||
searches are case insensitive.
|
||||
<dl>
|
||||
<dt>Display Name (The deck name, or the filename if the deck name isn't set):</dt>
|
||||
<dd>[red deck wins](#red deck wins) <small>(Any deck with a display name containing the words red, deck, and wins)</small></dd>
|
||||
<dd>["red deck wins"](#%22red deck wins%22) <small>(Any deck with a display name containing the exact phrase "red deck wins")</small></dd>
|
||||
|
||||
<dt>Deck <u>N</u>ame:</dt>
|
||||
<dd>[n:aggro](#n:aggro) <small>(Any deck with a name containing the word aggro)</small></dd>
|
||||
<dd>[n:red n:deck n:wins](#n:red n:deck n:wins) <small>(Any deck with a name containing the words red, deck, and wins)</small></dd>
|
||||
<dd>[n:"red deck wins"](#n:%22red deck wins%22) <small>(Any deck with a name containing the exact phrase "red deck wins")</small></dd>
|
||||
|
||||
<dt><u>F</u>ile Name:</dt>
|
||||
<dd>[f:aggro](#f:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd>
|
||||
<dd>[f:red f:deck f:wins](#f:red f:deck f:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd>
|
||||
<dd>[f:"red deck wins"](#f:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd>
|
||||
|
||||
<dt>Relative <u>P</u>ath (starting from the deck folder):</dt>
|
||||
<dd>[p:aggro](#p:aggro) <small>(Any deck that has "aggro" somewhere in its relative path)</small></dd>
|
||||
<dd>[p:edh/](#p:edh/) <small>(Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)</small></dd>
|
||||
|
||||
<dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt>
|
||||
<dd><a href="#[[plains]]">[[plains]]</a> <small>(Any deck that contains at least one card with "plains" in its name)</small></dd>
|
||||
<dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>
|
||||
<dd><a href="#[[t:legendary]]>5">[[t:legendary]]>5</a> <small>(Any card that contains at least 5 legendaries)</small></dd>
|
||||
<dd><a href="#[[]]:100">[[]]:100</a> <small>(Any deck that contains exactly 100 cards)</small></dd>
|
||||
|
||||
<dt>Negate:</dt>
|
||||
<dd>[soldier -aggro](#soldier -aggro) <small>(Any deck filename that contains "soldier", but not "aggro")</small></dd>
|
||||
|
||||
<dt>Branching:</dt>
|
||||
<dd>[t:aggro OR o:control](#t:aggro OR o:control) <small>(Any deck filename that contains either aggro or control)</small></dd>
|
||||
|
||||
<dt>Grouping:</dt>
|
||||
<dd><a href="#red -([[]]:100 or aggro)">red -([[]]:100 or aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
|
||||
|
||||
</dl>
|
||||
@@ -1,3 +1,5 @@
|
||||
@page search_syntax_help Search Syntax Help
|
||||
|
||||
## Search Syntax Help
|
||||
-----
|
||||
The search bar recognizes a set of special commands similar to some other card databases.<br>
|
||||
@@ -58,4 +60,9 @@ In this list of examples below, each entry has an explanation and can be clicked
|
||||
<dt>Grouping:</dt>
|
||||
<dd><a href="#t:angel -(angel or c:w)">t:angel -(angel or c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd>
|
||||
|
||||
<dt>Regular Expression:</dt>
|
||||
<dd>[/^fell/](#/^fell/) <small>(Any card name that begins with "fell")</small></dd>
|
||||
<dd>[o:/counter target .* spell/](#o:/counter target .* spell/) <small>(Any card text with "counter target *something* spell")</small></dd>
|
||||
<dd>[o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) <small>(/'s can be escaped with a \)</small></dd>
|
||||
|
||||
</dl>
|
||||
|
||||
8
cockatrice/resources/icons/arrow_history.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<title>
|
||||
history
|
||||
</title>
|
||||
<path d="M9 6v5h.06l2.48 2.47 1.41-1.41L11 10.11V6H9z"/>
|
||||
<path d="M10 1a9 9 0 0 0-7.85 13.35L.5 16H6v-5.5l-2.38 2.38A7 7 0 1 1 10 17v2a9 9 0 0 0 0-18z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 332 B |
40
cockatrice/resources/icons/arrow_redo.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="windows-1252"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px"
|
||||
y="0px" width="485.212px" height="485.212px" viewBox="0 0 485.212 485.212"
|
||||
style="enable-background:new 0 0 485.212 485.212;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M242.607,424.559c-75.252,0-136.468-61.209-136.468-136.465c0-75.252,61.216-136.466,136.468-136.466v90.978 l151.629-121.302L242.607,0v90.978c-108.687,0-197.117,88.432-197.117,197.117c0,108.691,88.43,197.118,197.117,197.118 c108.687,0,197.114-88.427,197.114-197.118h-60.645C379.077,363.35,317.859,424.559,242.607,424.559z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1018 B |
40
cockatrice/resources/icons/arrow_undo.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="windows-1252"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px"
|
||||
y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<path d="M256,448c79.406,0,144-64.594,144-144s-64.594-144-144-144v96L96,128L256,0v96c114.688,0,208,93.313,208,208 c0,114.688-93.312,208-208,208c-114.687,0-208-93.312-208-208h64C112,383.406,176.594,448,256,448z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 874 B |
24
cockatrice/resources/icons/pin.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="64" height="64">
|
||||
<g transform="matrix(0 1 -1 0 99.465813 0)" opacity="0.7">
|
||||
<path fill="#000000" fill-rule="evenodd" clip-rule="evenodd"
|
||||
stroke="#ffffff"
|
||||
stroke-width="4"
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
d="M65.5 62
|
||||
L78 49
|
||||
C73.5 44.5 69.5 42 63 44
|
||||
L45 31 C47 25 46 22 41.5 18
|
||||
L19 41.5
|
||||
C23 45.5 25 46.5 31 45
|
||||
L44 62.5
|
||||
C42.3 69 45 73.5 49 78
|
||||
L61.5 65.5
|
||||
L84 87
|
||||
L87 87
|
||||
L87.5 86.5
|
||||
L87.5 83.5 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 736 B |
25
cockatrice/resources/icons/share.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg height="800"
|
||||
width="800"
|
||||
version="1.1"
|
||||
id="_x32_"
|
||||
viewBox="0 0 512 512"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs1"/>
|
||||
<style type="text/css"
|
||||
id="style1">
|
||||
.st0{fill:#64C0FF;stroke:black;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1}
|
||||
</style>
|
||||
<g id="g1"
|
||||
transform="matrix(0.87097097,0,0,1.0008579,38.609049,-0.21963163)"
|
||||
style="stroke-width:3.42738;stroke-dasharray:none">
|
||||
<path class="st0"
|
||||
d="M 512,255.995 277.045,65.394 v 103.574 c -17.255,0 -36.408,0 -57.542,0 -208.59,0 -249.35,153.44 -201.394,266.128 9.586,-103.098 142.053,-100.701 237.358,-100.701 7.247,0 14.446,0 21.578,0 v 112.211 z"
|
||||
id="path1"
|
||||
style="stroke-width:20;stroke:#000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -363,6 +363,6 @@
|
||||
d="m 38.011063,984.77381 -10.143601,-5.23583 -10.063711,5.38779 1.845023,-11.2651 -8.233948,-7.90624 11.283888,-1.72639 4.974851,-10.27411 5.128803,10.19813 11.308575,1.55649 -8.114112,8.02918 z"
|
||||
inkscape:transform-center-x="0.094945927"
|
||||
inkscape:transform-center-y="-3.9764964"
|
||||
transform="matrix(2.3768784,0,0,2.4799382,-15.920285,-1400.1716)" />
|
||||
transform="matrix(-2.3768784,0,0,2.4799382,115.920285,-1400.1716)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,14 +1,13 @@
|
||||
#include "deck_stats_interface.h"
|
||||
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
|
||||
DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent)
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
@@ -67,26 +66,15 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck)
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
struct CopyIfNotAToken
|
||||
{
|
||||
CardDatabase &cardDatabase;
|
||||
DeckList &destination;
|
||||
|
||||
CopyIfNotAToken(CardDatabase &_cardDatabase, DeckList &_destination)
|
||||
: cardDatabase(_cardDatabase), destination(_destination){};
|
||||
|
||||
void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const
|
||||
{
|
||||
CardInfoPtr dbCard = cardDatabase.getCard(card->getName());
|
||||
if (dbCard && !dbCard->getIsToken()) {
|
||||
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName());
|
||||
addedCard->setNumber(card->getNumber());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination)
|
||||
{
|
||||
CopyIfNotAToken copyIfNotAToken(cardDatabase, destination);
|
||||
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
|
||||
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
||||
if (dbCard && !dbCard->getIsToken()) {
|
||||
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
|
||||
addedCard->setNumber(card->getNumber());
|
||||
}
|
||||
};
|
||||
|
||||
source.forEachCard(copyIfNotAToken);
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
/**
|
||||
* @file deck_stats_interface.h
|
||||
* @ingroup ApiInterfaces
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef DECKSTATS_INTERFACE_H
|
||||
#define DECKSTATS_INTERFACE_H
|
||||
|
||||
#include "../game/cards/card_database.h"
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class QByteArray;
|
||||
class QNetworkAccessManager;
|
||||
@@ -1,14 +1,13 @@
|
||||
#include "tapped_out_interface.h"
|
||||
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
|
||||
TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
@@ -33,7 +32,7 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
||||
* can be extracted from the header. The http status is a 302 "redirect".
|
||||
*/
|
||||
QString deckUrl = reply->rawHeader("Location");
|
||||
qCDebug(TappedOutInterfaceLog) << "Tappedout: good reply, http status" << httpStatus << "location" << deckUrl;
|
||||
qCInfo(TappedOutInterfaceLog) << "Tappedout: good reply, http status" << httpStatus << "location" << deckUrl;
|
||||
QDesktopServices::openUrl("https://tappedout.net" + deckUrl);
|
||||
} else {
|
||||
/*
|
||||
@@ -57,8 +56,8 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
||||
}
|
||||
|
||||
QString errorMessage = errorMessageList.join("\n");
|
||||
qCDebug(TappedOutInterfaceLog) << "Tappedout: bad reply, http status" << httpStatus << "size" << data.size()
|
||||
<< "message" << errorMessage;
|
||||
qCWarning(TappedOutInterfaceLog) << "Tappedout: bad reply, http status" << httpStatus << "size" << data.size()
|
||||
<< "message" << errorMessage;
|
||||
|
||||
QMessageBox::critical(nullptr, tr("Error"), errorMessage);
|
||||
}
|
||||
@@ -92,31 +91,20 @@ void TappedOutInterface::analyzeDeck(DeckList *deck)
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
struct CopyMainOrSide
|
||||
void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard)
|
||||
{
|
||||
CardDatabase &cardDatabase;
|
||||
DeckList &mainboard, &sideboard;
|
||||
|
||||
CopyMainOrSide(CardDatabase &_cardDatabase, DeckList &_mainboard, DeckList &_sideboard)
|
||||
: cardDatabase(_cardDatabase), mainboard(_mainboard), sideboard(_sideboard){};
|
||||
|
||||
void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const
|
||||
{
|
||||
CardInfoPtr dbCard = cardDatabase.getCard(card->getName());
|
||||
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
|
||||
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
||||
if (!dbCard || dbCard->getIsToken())
|
||||
return;
|
||||
|
||||
DecklistCardNode *addedCard;
|
||||
if (node->getName() == DECK_ZONE_SIDE)
|
||||
addedCard = sideboard.addCard(card->getName(), node->getName());
|
||||
addedCard = sideboard.addCard(card->getName(), node->getName(), -1);
|
||||
else
|
||||
addedCard = mainboard.addCard(card->getName(), node->getName());
|
||||
addedCard = mainboard.addCard(card->getName(), node->getName(), -1);
|
||||
addedCard->setNumber(card->getNumber());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard)
|
||||
{
|
||||
CopyMainOrSide copyMainOrSide(cardDatabase, mainboard, sideboard);
|
||||
source.forEachCard(copyMainOrSide);
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
/**
|
||||
* @file tapped_out_interface.h
|
||||
* @ingroup ApiInterfaces
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef TAPPEDOUT_INTERFACE_H
|
||||
#define TAPPEDOUT_INTERFACE_H
|
||||
|
||||
#include "../game/cards/card_database.h"
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface");
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "deck_link_to_api_transformer.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace DeckLinkToApiTransformer
|
||||
{
|
||||
|
||||
static const QString TAPPEDOUT_BASE = "https://tappedout.net/mtg-decks/";
|
||||
static const QString TAPPEDOUT_SUFFIX = "/?fmt=txt";
|
||||
|
||||
static const QString ARCHIDEKT_BASE = "https://archidekt.com/api/decks/";
|
||||
static const QString ARCHIDEKT_SUFFIX = "/?format=json";
|
||||
|
||||
static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/";
|
||||
static const QString MOXFIELD_SUFFIX = "/";
|
||||
|
||||
static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1";
|
||||
|
||||
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
|
||||
{
|
||||
static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)");
|
||||
static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)");
|
||||
static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)");
|
||||
static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)");
|
||||
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
if ((match = rxTappedOut.match(url)).hasMatch()) {
|
||||
QString slug = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = TAPPEDOUT_BASE,
|
||||
.deckID = slug,
|
||||
.fullUrl = TAPPEDOUT_BASE + slug + TAPPEDOUT_SUFFIX,
|
||||
.provider = DeckProvider::TappedOut};
|
||||
return true;
|
||||
} else if ((match = rxArchidekt.match(url)).hasMatch()) {
|
||||
QString deckID = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = ARCHIDEKT_BASE,
|
||||
.deckID = deckID,
|
||||
.fullUrl = ARCHIDEKT_BASE + deckID + ARCHIDEKT_SUFFIX,
|
||||
.provider = DeckProvider::Archidekt};
|
||||
return true;
|
||||
} else if ((match = rxMoxfield.match(url)).hasMatch()) {
|
||||
QString deckID = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = MOXFIELD_BASE,
|
||||
.deckID = deckID,
|
||||
.fullUrl = MOXFIELD_BASE + deckID + MOXFIELD_SUFFIX,
|
||||
.provider = DeckProvider::Moxfield};
|
||||
return true;
|
||||
} else if ((match = rxDeckstats.match(url)).hasMatch()) {
|
||||
QString deckPath = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = "https://deckstats.net/decks/",
|
||||
.deckID = deckPath,
|
||||
.fullUrl = "https://deckstats.net/decks/" + deckPath + DECKSTATS_SUFFIX,
|
||||
.provider = DeckProvider::Deckstats};
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace DeckLinkToApiTransformer
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @file deck_link_to_api_transformer.h
|
||||
* @ingroup ApiInterfaces
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef DECK_LINK_TO_API_TRANSFORMER_H
|
||||
#define DECK_LINK_TO_API_TRANSFORMER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
enum class DeckProvider
|
||||
{
|
||||
TappedOut,
|
||||
Archidekt,
|
||||
Moxfield,
|
||||
Deckstats,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct ParsedDeckInfo
|
||||
{
|
||||
QString baseUrl;
|
||||
QString deckID;
|
||||
QString fullUrl;
|
||||
DeckProvider provider;
|
||||
};
|
||||
|
||||
namespace DeckLinkToApiTransformer
|
||||
{
|
||||
|
||||
// Returns true if the input URL is recognized and fills outInfo.
|
||||
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo);
|
||||
|
||||
} // namespace DeckLinkToApiTransformer
|
||||
|
||||
#endif // DECK_LINK_TO_API_TRANSFORMER_H
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @file interface_json_deck_parser.h
|
||||
* @ingroup ApiInterfaces
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef INTERFACE_JSON_DECK_PARSER_H
|
||||
#define INTERFACE_JSON_DECK_PARSER_H
|
||||
#include "../../../interface/deck_loader/deck_loader.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
class IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
virtual ~IJsonDeckParser() = default;
|
||||
|
||||
virtual DeckLoader *parse(const QJsonObject &obj) = 0;
|
||||
};
|
||||
|
||||
class ArchidektJsonParser : public IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
DeckLoader *parse(const QJsonObject &obj) override
|
||||
{
|
||||
DeckLoader *loader = new DeckLoader(nullptr);
|
||||
|
||||
QString deckName = obj.value("name").toString();
|
||||
QString deckDescription = obj.value("description").toString();
|
||||
|
||||
loader->getDeckList()->setName(deckName);
|
||||
loader->getDeckList()->setComments(deckDescription);
|
||||
|
||||
QString outputText;
|
||||
QTextStream outStream(&outputText);
|
||||
|
||||
for (auto entry : obj.value("cards").toArray()) {
|
||||
auto quantity = entry.toObject().value("quantity").toInt();
|
||||
|
||||
auto card = entry.toObject().value("card").toObject();
|
||||
auto oracleCard = card.value("oracleCard").toObject();
|
||||
QString cardName = oracleCard.value("name").toString();
|
||||
QString setName = card.value("edition").toObject().value("editioncode").toString().toUpper();
|
||||
QString collectorNumber = card.value("collectorNumber").toString();
|
||||
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
loader->getDeckList()->loadFromStream_Plain(outStream, false);
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
|
||||
|
||||
return loader;
|
||||
}
|
||||
};
|
||||
|
||||
class MoxfieldJsonParser : public IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
DeckLoader *parse(const QJsonObject &obj) override
|
||||
{
|
||||
DeckLoader *loader = new DeckLoader(nullptr);
|
||||
|
||||
QString deckName = obj.value("name").toString();
|
||||
QString deckDescription = obj.value("description").toString();
|
||||
|
||||
loader->getDeckList()->setName(deckName);
|
||||
loader->getDeckList()->setComments(deckDescription);
|
||||
|
||||
QString outputText;
|
||||
QTextStream outStream(&outputText);
|
||||
|
||||
for (auto entry : obj.value("mainboard").toObject()) {
|
||||
auto quantity = entry.toObject().value("quantity").toInt();
|
||||
|
||||
auto card = entry.toObject().value("card").toObject();
|
||||
QString cardName = card.value("name").toString();
|
||||
QString setName = card.value("set").toString().toUpper();
|
||||
QString collectorNumber = card.value("cn").toString();
|
||||
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
outStream << '\n';
|
||||
|
||||
for (auto entry : obj.value("sideboard").toObject()) {
|
||||
auto quantity = entry.toObject().value("quantity").toInt();
|
||||
|
||||
auto card = entry.toObject().value("card").toObject();
|
||||
QString cardName = card.value("name").toString();
|
||||
QString setName = card.value("set").toString().toUpper();
|
||||
QString collectorNumber = card.value("cn").toString();
|
||||
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
loader->getDeckList()->loadFromStream_Plain(outStream, false);
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
|
||||
|
||||
QJsonObject commandersObj = obj.value("commanders").toObject();
|
||||
if (!commandersObj.isEmpty()) {
|
||||
for (auto it = commandersObj.begin(); it != commandersObj.end(); ++it) {
|
||||
QJsonObject cardData = it.value().toObject().value("card").toObject();
|
||||
QString commanderName = cardData.value("name").toString();
|
||||
QString setName = cardData.value("set").toString().toUpper();
|
||||
QString collectorNumber = cardData.value("cn").toString();
|
||||
QString providerId = cardData.value("scryfall_id").toString();
|
||||
|
||||
loader->getDeckList()->setBannerCard({commanderName, providerId});
|
||||
loader->getDeckList()->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
|
||||
}
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INTERFACE_JSON_DECK_PARSER_H
|
||||
@@ -1,21 +1,19 @@
|
||||
#include "spoiler_background_updater.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 "../../../../interface/window_main.h"
|
||||
#include "../../../../main.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QLocale>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
||||
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
|
||||
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
|
||||
@@ -28,7 +26,7 @@ SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(
|
||||
// File exists means we're in spoiler season
|
||||
startSpoilerDownloadProcess(SPOILERS_STATUS_URL, false);
|
||||
} else {
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoilers Disabled";
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoilers Disabled";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +65,7 @@ void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
|
||||
reply->deleteLater();
|
||||
emit spoilerCheckerDone();
|
||||
} else {
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Error downloading spoilers file" << errorCode;
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Error downloading spoilers file" << errorCode;
|
||||
emit spoilerCheckerDone();
|
||||
}
|
||||
}
|
||||
@@ -81,11 +79,11 @@ bool SpoilerBackgroundUpdater::deleteSpoilerFile()
|
||||
|
||||
// Delete the spoiler.xml file
|
||||
if (file.exists() && file.remove()) {
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Deleting spoiler.xml";
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Deleting spoiler.xml";
|
||||
return true;
|
||||
}
|
||||
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Error: Spoiler.xml not found or not deleted";
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Error: Spoiler.xml not found or not deleted";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -101,24 +99,24 @@ void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
|
||||
trayIcon->showMessage(tr("Spoilers season has ended"), tr("Deleting spoiler.xml. Please run Oracle"));
|
||||
}
|
||||
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoiler Season Offline";
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Season Offline";
|
||||
emit spoilerCheckerDone();
|
||||
} else if (errorCode == QNetworkReply::NoError) {
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoiler Service Online";
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Service Online";
|
||||
startSpoilerDownloadProcess(SPOILERS_URL, true);
|
||||
} else if (errorCode == QNetworkReply::HostNotFoundError) {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers download failed"), tr("No internet connection"));
|
||||
}
|
||||
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoiler download failed due to no internet connection";
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler download failed due to no internet connection";
|
||||
emit spoilerCheckerDone();
|
||||
} else {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers download failed"), tr("Error") + " " + (short)errorCode);
|
||||
}
|
||||
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoiler download failed with reason" << errorCode;
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler download failed with reason" << errorCode;
|
||||
emit spoilerCheckerDone();
|
||||
}
|
||||
}
|
||||
@@ -139,19 +137,19 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
||||
trayIcon->showMessage(tr("Spoilers already up to date"), tr("No new spoilers added"));
|
||||
}
|
||||
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoilers Up to Date";
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoilers Up to Date";
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoiler Service Error: File open (w) failed for" << fileName;
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler Service Error: File open (w) failed for" << fileName;
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.write(data) == -1) {
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoiler Service Error: File write (w) failed for" << fileName;
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler Service Error: File write (w) failed for" << fileName;
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
@@ -159,7 +157,7 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
||||
file.close();
|
||||
|
||||
// Data written, so reload the card database
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Spoiler Service Data Written";
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Service Data Written";
|
||||
const auto reloadOk = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
|
||||
|
||||
// If the user has notifications enabled, let them know
|
||||
@@ -202,12 +200,12 @@ QByteArray SpoilerBackgroundUpdater::getHash(const QString fileName)
|
||||
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
||||
hash.addData(bytes);
|
||||
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "File Hash =" << hash.result();
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "File Hash =" << hash.result();
|
||||
|
||||
file.close();
|
||||
return hash.result();
|
||||
} else {
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "getHash ReadOnly failed!";
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "getHash ReadOnly failed!";
|
||||
file.close();
|
||||
return QByteArray();
|
||||
}
|
||||
@@ -221,7 +219,7 @@ QByteArray SpoilerBackgroundUpdater::getHash(QByteArray data)
|
||||
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
||||
hash.addData(bytes);
|
||||
|
||||
qCDebug(SpoilerBackgroundUpdaterLog) << "Data Hash =" << hash.result();
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Data Hash =" << hash.result();
|
||||
|
||||
return hash.result();
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @file spoiler_background_updater.h
|
||||
* @ingroup Client
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
#define COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
|
||||
@@ -16,7 +22,7 @@ public:
|
||||
inline QString getCardUpdaterBinaryName()
|
||||
{
|
||||
return "oracle";
|
||||
};
|
||||
}
|
||||
QByteArray getHash(const QString fileName);
|
||||
QByteArray getHash(QByteArray data);
|
||||
static bool deleteSpoilerFile();
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "client_update_checker.h"
|
||||
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "release_channel.h"
|
||||
|
||||
ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent)
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @file client_update_checker.h
|
||||
* @ingroup ClientUpdate
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CLIENT_UPDATE_CHECKER_H
|
||||
#define CLIENT_UPDATE_CHECKER_H
|
||||
#include <QObject>
|
||||
@@ -38,7 +38,7 @@ ReleaseChannel::~ReleaseChannel()
|
||||
void ReleaseChannel::checkForUpdates()
|
||||
{
|
||||
QString releaseChannelUrl = getReleaseChannelUrl();
|
||||
qCDebug(ReleaseChannelLog) << "Searching for updates on the channel: " << releaseChannelUrl;
|
||||
qCInfo(ReleaseChannelLog) << "Searching for updates on the channel: " << releaseChannelUrl;
|
||||
response = netMan->get(QNetworkRequest(releaseChannelUrl));
|
||||
connect(response, &QNetworkReply::finished, this, &ReleaseChannel::releaseListFinished);
|
||||
}
|
||||
@@ -152,15 +152,15 @@ void StableReleaseChannel::releaseListFinished()
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qCDebug(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
qCInfo(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
|
||||
qCDebug(ReleaseChannelLog) << "Got reply from release server, name=" << lastRelease->getName()
|
||||
<< "desc=" << lastRelease->getDescriptionUrl()
|
||||
<< "date=" << lastRelease->getPublishDate() << "url=" << lastRelease->getDownloadUrl();
|
||||
qCInfo(ReleaseChannelLog) << "Got reply from release server, name=" << lastRelease->getName()
|
||||
<< "desc=" << lastRelease->getDescriptionUrl() << "date=" << lastRelease->getPublishDate()
|
||||
<< "url=" << lastRelease->getDownloadUrl();
|
||||
|
||||
const QString &tagName = resultMap["tag_name"].toString();
|
||||
QString url = QString(STABLETAG_URL) + tagName;
|
||||
qCDebug(ReleaseChannelLog) << "Searching for commit hash corresponding to stable channel tag: " << tagName;
|
||||
qCInfo(ReleaseChannelLog) << "Searching for commit hash corresponding to stable channel tag: " << tagName;
|
||||
response = netMan->get(QNetworkRequest(url));
|
||||
connect(response, &QNetworkReply::finished, this, &StableReleaseChannel::tagListFinished);
|
||||
}
|
||||
@@ -185,11 +185,11 @@ void StableReleaseChannel::tagListFinished()
|
||||
}
|
||||
|
||||
lastRelease->setCommitHash(resultMap["object"].toMap()["sha"].toString());
|
||||
qCDebug(ReleaseChannelLog) << "Got reply from tag server, commit=" << lastRelease->getCommitHash();
|
||||
qCInfo(ReleaseChannelLog) << "Got reply from tag server, commit=" << lastRelease->getCommitHash();
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qCDebug(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
qCInfo(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
const bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
|
||||
|
||||
emit finishedCheck(needToUpdate, lastRelease->isCompatibleVersionFound(), lastRelease);
|
||||
@@ -256,13 +256,13 @@ void BetaReleaseChannel::releaseListFinished()
|
||||
lastRelease->setName(QString("%1 (%2)").arg(resultMap["tag_name"].toString()).arg(shortHash));
|
||||
lastRelease->setDescriptionUrl(QString(BETARELEASE_CHANGESURL).arg(VERSION_COMMIT, shortHash));
|
||||
|
||||
qCDebug(ReleaseChannelLog) << "Got reply from release server, size=" << resultMap.size()
|
||||
<< "name=" << lastRelease->getName() << "desc=" << lastRelease->getDescriptionUrl()
|
||||
<< "commit=" << lastRelease->getCommitHash() << "date=" << lastRelease->getPublishDate();
|
||||
qCInfo(ReleaseChannelLog) << "Got reply from release server, size=" << resultMap.size()
|
||||
<< "name=" << lastRelease->getName() << "desc=" << lastRelease->getDescriptionUrl()
|
||||
<< "commit=" << lastRelease->getCommitHash() << "date=" << lastRelease->getPublishDate();
|
||||
|
||||
QString betaBuildDownloadUrl = resultMap["assets_url"].toString();
|
||||
|
||||
qCDebug(ReleaseChannelLog) << "Searching for a corresponding file on the beta channel: " << betaBuildDownloadUrl;
|
||||
qCInfo(ReleaseChannelLog) << "Searching for a corresponding file on the beta channel: " << betaBuildDownloadUrl;
|
||||
response = netMan->get(QNetworkRequest(betaBuildDownloadUrl));
|
||||
connect(response, &QNetworkReply::finished, this, &BetaReleaseChannel::fileListFinished);
|
||||
}
|
||||
@@ -282,7 +282,7 @@ void BetaReleaseChannel::fileListFinished()
|
||||
QVariantList resultList = jsonResponse.toVariant().toList();
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qCDebug(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
qCInfo(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
|
||||
bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
|
||||
bool compatibleVersion = false;
|
||||
@@ -299,7 +299,7 @@ void BetaReleaseChannel::fileListFinished()
|
||||
if (downloadMatchesCurrentOS(*url)) {
|
||||
compatibleVersion = true;
|
||||
lastRelease->setDownloadUrl(*url);
|
||||
qCDebug(ReleaseChannelLog) << "Found compatible version url=" << *url;
|
||||
qCInfo(ReleaseChannelLog) << "Found compatible version url=" << *url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @file release_channel.h
|
||||
* @ingroup ClientUpdate
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef RELEASECHANNEL_H
|
||||
#define RELEASECHANNEL_H
|
||||
|
||||
@@ -51,27 +57,27 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
QString getName() const
|
||||
[[nodiscard]] QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
QString getDescriptionUrl() const
|
||||
[[nodiscard]] QString getDescriptionUrl() const
|
||||
{
|
||||
return descriptionUrl;
|
||||
}
|
||||
QString getDownloadUrl() const
|
||||
[[nodiscard]] QString getDownloadUrl() const
|
||||
{
|
||||
return downloadUrl;
|
||||
}
|
||||
QString getCommitHash() const
|
||||
[[nodiscard]] QString getCommitHash() const
|
||||
{
|
||||
return commitHash;
|
||||
}
|
||||
QDate getPublishDate() const
|
||||
[[nodiscard]] QDate getPublishDate() const
|
||||
{
|
||||
return publishDate;
|
||||
}
|
||||
bool isCompatibleVersionFound() const
|
||||
[[nodiscard]] bool isCompatibleVersionFound() const
|
||||
{
|
||||
return compatibleVersionFound;
|
||||
}
|
||||
@@ -91,15 +97,15 @@ protected:
|
||||
|
||||
protected:
|
||||
static bool downloadMatchesCurrentOS(const QString &fileName);
|
||||
virtual QString getReleaseChannelUrl() const = 0;
|
||||
[[nodiscard]] virtual QString getReleaseChannelUrl() const = 0;
|
||||
|
||||
public:
|
||||
Release *getLastRelease()
|
||||
{
|
||||
return lastRelease;
|
||||
}
|
||||
virtual QString getManualDownloadUrl() const = 0;
|
||||
virtual QString getName() const = 0;
|
||||
[[nodiscard]] virtual QString getManualDownloadUrl() const = 0;
|
||||
[[nodiscard]] virtual QString getName() const = 0;
|
||||
void checkForUpdates();
|
||||
signals:
|
||||
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
|
||||
@@ -116,12 +122,12 @@ public:
|
||||
explicit StableReleaseChannel() = default;
|
||||
~StableReleaseChannel() override = default;
|
||||
|
||||
QString getManualDownloadUrl() const override;
|
||||
[[nodiscard]] QString getManualDownloadUrl() const override;
|
||||
|
||||
QString getName() const override;
|
||||
[[nodiscard]] QString getName() const override;
|
||||
|
||||
protected:
|
||||
QString getReleaseChannelUrl() const override;
|
||||
[[nodiscard]] QString getReleaseChannelUrl() const override;
|
||||
protected slots:
|
||||
|
||||
void releaseListFinished() override;
|
||||
@@ -137,12 +143,12 @@ public:
|
||||
BetaReleaseChannel() = default;
|
||||
~BetaReleaseChannel() override = default;
|
||||
|
||||
QString getManualDownloadUrl() const override;
|
||||
[[nodiscard]] QString getManualDownloadUrl() const override;
|
||||
|
||||
QString getName() const override;
|
||||
[[nodiscard]] QString getName() const override;
|
||||
|
||||
protected:
|
||||
QString getReleaseChannelUrl() const override;
|
||||
[[nodiscard]] QString getReleaseChannelUrl() const override;
|
||||
protected slots:
|
||||
|
||||
void releaseListFinished() override;
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "update_downloader.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
|
||||
UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr)
|
||||
@@ -1,13 +1,13 @@
|
||||
//
|
||||
// Created by miguel on 28/12/15.
|
||||
//
|
||||
/**
|
||||
* @file update_downloader.h
|
||||
* @ingroup ClientUpdate
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_UPDATEDOWNLOADER_H
|
||||
#define COCKATRICE_UPDATEDOWNLOADER_H
|
||||
|
||||
#include <QDate>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
#include <QtNetwork>
|
||||
|
||||
class UpdateDownloader : public QObject
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "cache_settings.h"
|
||||
|
||||
#include "../client/network/release_channel.h"
|
||||
#include "card_override_settings.h"
|
||||
#include "../network/update/client/release_channel.h"
|
||||
#include "card_counter_settings.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -10,9 +11,15 @@
|
||||
#include <QGlobalStatic>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <libcockatrice/settings/card_override_settings.h>
|
||||
#include <utility>
|
||||
|
||||
Q_GLOBAL_STATIC(SettingsCache, settingsCache);
|
||||
Q_GLOBAL_STATIC(SettingsCache, settingsCache)
|
||||
|
||||
SettingsCache &SettingsCache::instance()
|
||||
{
|
||||
return *settingsCache; // returns a QT managed singleton reference
|
||||
}
|
||||
|
||||
QString SettingsCache::getDataPath()
|
||||
{
|
||||
@@ -59,9 +66,9 @@ void SettingsCache::translateLegacySettings()
|
||||
QStringList setsGroups = legacySetting.childGroups();
|
||||
for (int i = 0; i < setsGroups.size(); i++) {
|
||||
legacySetting.beginGroup(setsGroups.at(i));
|
||||
cardDatabase().setEnabled(setsGroups.at(i), legacySetting.value("enabled").toBool());
|
||||
cardDatabase().setIsKnown(setsGroups.at(i), legacySetting.value("isknown").toBool());
|
||||
cardDatabase().setSortKey(setsGroups.at(i), legacySetting.value("sortkey").toUInt());
|
||||
cardDatabase()->setEnabled(setsGroups.at(i), legacySetting.value("enabled").toBool());
|
||||
cardDatabase()->setIsKnown(setsGroups.at(i), legacySetting.value("isknown").toBool());
|
||||
cardDatabase()->setSortKey(setsGroups.at(i), legacySetting.value("sortkey").toUInt());
|
||||
legacySetting.endGroup();
|
||||
}
|
||||
QStringList setsKeys = legacySetting.allKeys();
|
||||
@@ -140,7 +147,7 @@ QString SettingsCache::getSafeConfigPath(QString configEntry, QString defaultPat
|
||||
// ensure that the defaut path exists and return it
|
||||
if (tmp.isEmpty() || !QDir(tmp).exists()) {
|
||||
if (!QDir().mkpath(defaultPath))
|
||||
qCDebug(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath;
|
||||
qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath;
|
||||
tmp = defaultPath;
|
||||
}
|
||||
return tmp;
|
||||
@@ -161,7 +168,7 @@ SettingsCache::SettingsCache()
|
||||
// first, figure out if we are running in portable mode
|
||||
isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
|
||||
if (isPortableBuild)
|
||||
qCDebug(SettingsCacheLog) << "Portable mode enabled";
|
||||
qCInfo(SettingsCacheLog) << "Portable mode enabled";
|
||||
|
||||
// define a dummy context that will be used where needed
|
||||
QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
|
||||
@@ -179,6 +186,8 @@ SettingsCache::SettingsCache()
|
||||
cardOverrideSettings = new CardOverrideSettings(settingsPath, this);
|
||||
debugSettings = new DebugSettings(settingsPath, this);
|
||||
|
||||
cardCounterSettings = new CardCounterSettings(settingsPath, this);
|
||||
|
||||
if (!QFile(settingsPath + "global.ini").exists())
|
||||
translateLegacySettings();
|
||||
|
||||
@@ -190,6 +199,11 @@ SettingsCache::SettingsCache()
|
||||
mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
|
||||
|
||||
checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool();
|
||||
startupCardUpdateCheckPromptForUpdate =
|
||||
settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
|
||||
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
|
||||
cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt();
|
||||
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
|
||||
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
|
||||
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
|
||||
updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt();
|
||||
@@ -208,6 +222,9 @@ SettingsCache::SettingsCache()
|
||||
|
||||
themeName = settings->value("theme/name").toString();
|
||||
|
||||
homeTabBackgroundSource = settings->value("home/background", "themed").toString();
|
||||
homeTabBackgroundShuffleFrequency = settings->value("home/background/shuffleTimer", 0).toInt();
|
||||
|
||||
tabVisualDeckStorageOpen = settings->value("tabs/visualDeckStorage", true).toBool();
|
||||
tabServerOpen = settings->value("tabs/server", true).toBool();
|
||||
tabAccountOpen = settings->value("tabs/account", true).toBool();
|
||||
@@ -233,6 +250,7 @@ SettingsCache::SettingsCache()
|
||||
redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt();
|
||||
|
||||
picDownload = settings->value("personal/picturedownload", true).toBool();
|
||||
showStatusBar = settings->value("personal/showStatusBar", false).toBool();
|
||||
|
||||
mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray();
|
||||
tokenDialogGeometry = settings->value("interface/token_dialog_geometry").toByteArray();
|
||||
@@ -243,6 +261,7 @@ SettingsCache::SettingsCache()
|
||||
doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool();
|
||||
clickPlaysAllSelected = settings->value("interface/clickPlaysAllSelected", true).toBool();
|
||||
playToStack = settings->value("interface/playtostack", true).toBool();
|
||||
doNotDeleteArrowsInSubPhases = settings->value("interface/doNotDeleteArrowsInSubPhases", true).toBool();
|
||||
startingHandSize = settings->value("interface/startinghandsize", 7).toInt();
|
||||
annotateTokens = settings->value("interface/annotatetokens", false).toBool();
|
||||
tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray();
|
||||
@@ -251,6 +270,7 @@ SettingsCache::SettingsCache()
|
||||
cardViewInitialRowsMax = settings->value("interface/cardViewInitialRowsMax", 14).toInt();
|
||||
cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt();
|
||||
closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool();
|
||||
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
|
||||
|
||||
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
|
||||
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
|
||||
@@ -260,13 +280,18 @@ SettingsCache::SettingsCache()
|
||||
bumpSetsWithCardsInDeckToTop = settings->value("cards/bumpsetswithcardsindecktotop", true).toBool();
|
||||
printingSelectorSortOrder = settings->value("cards/printingselectorsortorder", 1).toInt();
|
||||
printingSelectorCardSize = settings->value("cards/printingselectorcardsize", 100).toInt();
|
||||
includeOnlineOnlyCards = settings->value("cards/includeonlineonlycards", false).toBool();
|
||||
includeRebalancedCards = settings->value("cards/includerebalancedcards", true).toBool();
|
||||
printingSelectorNavigationButtonsVisible =
|
||||
settings->value("cards/printingselectornavigationbuttonsvisible", true).toBool();
|
||||
deckEditorBannerCardComboBoxVisible =
|
||||
settings->value("interface/deckeditorbannercardcomboboxvisible", true).toBool();
|
||||
deckEditorTagsWidgetVisible = settings->value("interface/deckeditortagswidgetvisible", true).toBool();
|
||||
visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt();
|
||||
visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt();
|
||||
visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool();
|
||||
visualDeckStorageShowTagFilter = settings->value("interface/visualdeckstorageshowtagfilter", true).toBool();
|
||||
visualDeckStorageDefaultTagsList =
|
||||
settings->value("interface/visualdeckstoragedefaulttagslist", defaultTags).toStringList();
|
||||
visualDeckStorageSearchFolderNames = settings->value("interface/visualdeckstoragesearchfoldernames", true).toBool();
|
||||
visualDeckStorageShowBannerCardComboBox =
|
||||
settings->value("interface/visualdeckstorageshowbannercardcombobox", true).toBool();
|
||||
@@ -276,10 +301,24 @@ SettingsCache::SettingsCache()
|
||||
settings->value("interface/visualdeckstoragedrawunusedcoloridentities", true).toBool();
|
||||
visualDeckStorageUnusedColorIdentitiesOpacity =
|
||||
settings->value("interface/visualdeckstorageunusedcoloridentitiesopacity", 15).toInt();
|
||||
visualDeckStorageTooltipType = settings->value("interface/visualdeckstoragetooltiptype", 0).toInt();
|
||||
visualDeckStoragePromptForConversion =
|
||||
settings->value("interface/visualdeckstoragepromptforconversion", true).toBool();
|
||||
visualDeckStorageAlwaysConvert = settings->value("interface/visualdeckstoragealwaysconvert", false).toBool();
|
||||
visualDeckStorageInGame = settings->value("interface/visualdeckstorageingame", true).toBool();
|
||||
visualDeckStorageSelectionAnimation =
|
||||
settings->value("interface/visualdeckstorageselectionanimation", true).toBool();
|
||||
defaultDeckEditorType = settings->value("interface/defaultDeckEditorType", 1).toInt();
|
||||
visualDatabaseDisplayFilterToMostRecentSetsEnabled =
|
||||
settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsenabled", false).toBool();
|
||||
visualDatabaseDisplayFilterToMostRecentSetsAmount =
|
||||
settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsamount", 10).toInt();
|
||||
visualDeckEditorSampleHandSize = settings->value("interface/visualdeckeditorsamplehandsize", 7).toInt();
|
||||
visualDeckEditorCardSize = settings->value("interface/visualdeckeditorcardsize", 100).toInt();
|
||||
visualDatabaseDisplayCardSize = settings->value("interface/visualdatabasedisplaycardsize", 100).toInt();
|
||||
edhrecCardSize = settings->value("interface/edhreccardsize", 100).toInt();
|
||||
archidektPreviewSize = settings->value("interface/archidektpreviewsize", 100).toInt();
|
||||
|
||||
horizontalHand = settings->value("hand/horizontal", true).toBool();
|
||||
invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool();
|
||||
minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt();
|
||||
@@ -330,6 +369,7 @@ SettingsCache::SettingsCache()
|
||||
spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool();
|
||||
createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool();
|
||||
defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
|
||||
shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool();
|
||||
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
|
||||
clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
|
||||
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
|
||||
@@ -360,6 +400,12 @@ void SettingsCache::setCloseEmptyCardView(QT_STATE_CHANGED_T value)
|
||||
settings->setValue("interface/closeEmptyCardView", closeEmptyCardView);
|
||||
}
|
||||
|
||||
void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T value)
|
||||
{
|
||||
focusCardViewSearchBar = value;
|
||||
settings->setValue("interface/focusCardViewSearchBar", focusCardViewSearchBar);
|
||||
}
|
||||
|
||||
void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures)
|
||||
{
|
||||
knownMissingFeatures = _knownMissingFeatures;
|
||||
@@ -451,6 +497,12 @@ void SettingsCache::setDeckPath(const QString &_deckPath)
|
||||
settings->setValue("paths/decks", deckPath);
|
||||
}
|
||||
|
||||
void SettingsCache::setFiltersPath(const QString &_filtersPath)
|
||||
{
|
||||
filtersPath = _filtersPath;
|
||||
settings->setValue("paths/filters", filtersPath);
|
||||
}
|
||||
|
||||
void SettingsCache::setReplaysPath(const QString &_replaysPath)
|
||||
{
|
||||
replaysPath = _replaysPath;
|
||||
@@ -512,6 +564,20 @@ void SettingsCache::setThemeName(const QString &_themeName)
|
||||
emit themeChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setHomeTabBackgroundSource(const QString &_backgroundSource)
|
||||
{
|
||||
homeTabBackgroundSource = _backgroundSource;
|
||||
settings->setValue("home/background", homeTabBackgroundSource);
|
||||
emit homeTabBackgroundSourceChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setHomeTabBackgroundShuffleFrequency(int _frequency)
|
||||
{
|
||||
homeTabBackgroundShuffleFrequency = _frequency;
|
||||
settings->setValue("home/background/shuffleTimer", homeTabBackgroundShuffleFrequency);
|
||||
emit homeTabBackgroundShuffleFrequencyChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setTabVisualDeckStorageOpen(bool value)
|
||||
{
|
||||
tabVisualDeckStorageOpen = value;
|
||||
@@ -561,6 +627,13 @@ void SettingsCache::setPicDownload(QT_STATE_CHANGED_T _picDownload)
|
||||
emit picDownloadChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setShowStatusBar(bool value)
|
||||
{
|
||||
showStatusBar = value;
|
||||
settings->setValue("personal/showStatusBar", showStatusBar);
|
||||
emit showStatusBarChanged(value);
|
||||
}
|
||||
|
||||
void SettingsCache::setNotificationsEnabled(QT_STATE_CHANGED_T _notificationsEnabled)
|
||||
{
|
||||
notificationsEnabled = static_cast<bool>(_notificationsEnabled);
|
||||
@@ -597,6 +670,12 @@ void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T _playToStack)
|
||||
settings->setValue("interface/playtostack", playToStack);
|
||||
}
|
||||
|
||||
void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases)
|
||||
{
|
||||
doNotDeleteArrowsInSubPhases = static_cast<bool>(_doNotDeleteArrowsInSubPhases);
|
||||
settings->setValue("interface/doNotDeleteArrowsInSubPhases", doNotDeleteArrowsInSubPhases);
|
||||
}
|
||||
|
||||
void SettingsCache::setStartingHandSize(int _startingHandSize)
|
||||
{
|
||||
startingHandSize = _startingHandSize;
|
||||
@@ -656,11 +735,14 @@ void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize)
|
||||
emit printingSelectorCardSizeChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setIncludeOnlineOnlyCards(bool _includeOnlineOnlyCards)
|
||||
void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards)
|
||||
{
|
||||
includeOnlineOnlyCards = _includeOnlineOnlyCards;
|
||||
settings->setValue("cards/includeonlineonlycards", includeOnlineOnlyCards);
|
||||
emit includeOnlineOnlyCardsChanged(includeOnlineOnlyCards);
|
||||
if (includeRebalancedCards == _includeRebalancedCards)
|
||||
return;
|
||||
|
||||
includeRebalancedCards = _includeRebalancedCards;
|
||||
settings->setValue("cards/includerebalancedcards", includeRebalancedCards);
|
||||
emit includeRebalancedCardsChanged(includeRebalancedCards);
|
||||
}
|
||||
|
||||
void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible)
|
||||
@@ -670,6 +752,20 @@ void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED
|
||||
emit printingSelectorNavigationButtonsVisibleChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setDeckEditorBannerCardComboBoxVisible(QT_STATE_CHANGED_T _deckEditorBannerCardComboBoxVisible)
|
||||
{
|
||||
deckEditorBannerCardComboBoxVisible = _deckEditorBannerCardComboBoxVisible;
|
||||
settings->setValue("interface/deckeditorbannercardcomboboxvisible", deckEditorBannerCardComboBoxVisible);
|
||||
emit deckEditorBannerCardComboBoxVisibleChanged(deckEditorBannerCardComboBoxVisible);
|
||||
}
|
||||
|
||||
void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEditorTagsWidgetVisible)
|
||||
{
|
||||
deckEditorTagsWidgetVisible = _deckEditorTagsWidgetVisible;
|
||||
settings->setValue("interface/deckeditortagswidgetvisible", deckEditorTagsWidgetVisible);
|
||||
emit deckEditorTagsWidgetVisibleChanged(deckEditorTagsWidgetVisible);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder)
|
||||
{
|
||||
visualDeckStorageSortingOrder = _visualDeckStorageSortingOrder;
|
||||
@@ -689,6 +785,13 @@ void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTa
|
||||
emit visualDeckStorageShowTagFilterChanged(visualDeckStorageShowTagFilter);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStorageDefaultTagsList(QStringList _defaultTagsList)
|
||||
{
|
||||
visualDeckStorageDefaultTagsList = _defaultTagsList;
|
||||
settings->setValue("interface/visualdeckstoragedefaulttagslist", visualDeckStorageDefaultTagsList);
|
||||
emit visualDeckStorageDefaultTagsListChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value)
|
||||
{
|
||||
visualDeckStorageSearchFolderNames = value;
|
||||
@@ -733,13 +836,19 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visual
|
||||
emit visualDeckStorageUnusedColorIdentitiesOpacityChanged(visualDeckStorageUnusedColorIdentitiesOpacity);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion)
|
||||
void SettingsCache::setVisualDeckStorageTooltipType(int value)
|
||||
{
|
||||
visualDeckStorageTooltipType = value;
|
||||
settings->setValue("interface/visualdeckstoragetooltiptype", visualDeckStorageTooltipType);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStoragePromptForConversion(bool _visualDeckStoragePromptForConversion)
|
||||
{
|
||||
visualDeckStoragePromptForConversion = _visualDeckStoragePromptForConversion;
|
||||
settings->setValue("interface/visualdeckstoragepromptforconversion", visualDeckStoragePromptForConversion);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert)
|
||||
void SettingsCache::setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert)
|
||||
{
|
||||
visualDeckStorageAlwaysConvert = _visualDeckStorageAlwaysConvert;
|
||||
settings->setValue("interface/visualdeckstoragealwaysconvert", visualDeckStorageAlwaysConvert);
|
||||
@@ -752,6 +861,70 @@ void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T value)
|
||||
emit visualDeckStorageInGameChanged(visualDeckStorageInGame);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T value)
|
||||
{
|
||||
visualDeckStorageSelectionAnimation = value;
|
||||
settings->setValue("interface/visualdeckstorageselectionanimation", visualDeckStorageSelectionAnimation);
|
||||
emit visualDeckStorageSelectionAnimationChanged(visualDeckStorageSelectionAnimation);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckEditorCardSize(int _visualDeckEditorCardSize)
|
||||
{
|
||||
visualDeckEditorCardSize = _visualDeckEditorCardSize;
|
||||
settings->setValue("interface/visualdeckeditorcardsize", visualDeckEditorCardSize);
|
||||
emit visualDeckEditorCardSizeChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDatabaseDisplayCardSize(int _visualDatabaseDisplayCardSize)
|
||||
{
|
||||
visualDatabaseDisplayCardSize = _visualDatabaseDisplayCardSize;
|
||||
settings->setValue("interface/visualdatabasedisplaycardsize", visualDatabaseDisplayCardSize);
|
||||
emit visualDatabaseDisplayCardSizeChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setEDHRecCardSize(int _edhrecCardSize)
|
||||
{
|
||||
edhrecCardSize = _edhrecCardSize;
|
||||
settings->setValue("interface/edhreccardsize", edhrecCardSize);
|
||||
emit edhRecCardSizeChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setArchidektPreviewCardSize(int _archidektPreviewCardSize)
|
||||
{
|
||||
archidektPreviewSize = _archidektPreviewCardSize;
|
||||
settings->setValue("interface/archidektpreviewsize", archidektPreviewSize);
|
||||
emit archidektPreviewSizeChanged();
|
||||
}
|
||||
|
||||
void SettingsCache::setDefaultDeckEditorType(int value)
|
||||
{
|
||||
defaultDeckEditorType = value;
|
||||
settings->setValue("interface/defaultDeckEditorType", defaultDeckEditorType);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T _enabled)
|
||||
{
|
||||
visualDatabaseDisplayFilterToMostRecentSetsEnabled = _enabled;
|
||||
settings->setValue("interface/visualdatabasedisplayfiltertomostrecentsetsenabled",
|
||||
visualDatabaseDisplayFilterToMostRecentSetsEnabled);
|
||||
emit visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged(visualDatabaseDisplayFilterToMostRecentSetsEnabled);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int _amount)
|
||||
{
|
||||
visualDatabaseDisplayFilterToMostRecentSetsAmount = _amount;
|
||||
settings->setValue("interface/visualdatabasedisplayfiltertomostrecentsetsamount",
|
||||
visualDatabaseDisplayFilterToMostRecentSetsAmount);
|
||||
emit visualDatabaseDisplayFilterToMostRecentSetsAmountChanged(visualDatabaseDisplayFilterToMostRecentSetsAmount);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckEditorSampleHandSize(int _amount)
|
||||
{
|
||||
visualDeckEditorSampleHandSize = _amount;
|
||||
settings->setValue("interface/visualdeckeditorsamplehandsize", visualDeckEditorSampleHandSize);
|
||||
emit visualDeckEditorSampleHandSizeAmountChanged(visualDeckEditorSampleHandSize);
|
||||
}
|
||||
|
||||
void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand)
|
||||
{
|
||||
horizontalHand = static_cast<bool>(_horizontalHand);
|
||||
@@ -1250,7 +1423,13 @@ void SettingsCache::setDefaultStartingLifeTotal(const int _defaultStartingLifeTo
|
||||
{
|
||||
defaultStartingLifeTotal = _defaultStartingLifeTotal;
|
||||
settings->setValue("game/defaultstartinglifetotal", defaultStartingLifeTotal);
|
||||
};
|
||||
}
|
||||
|
||||
void SettingsCache::setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad)
|
||||
{
|
||||
shareDecklistsOnLoad = _shareDecklistsOnLoad;
|
||||
settings->setValue("game/sharedecklistsonload", shareDecklistsOnLoad);
|
||||
}
|
||||
|
||||
void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value)
|
||||
{
|
||||
@@ -1258,6 +1437,30 @@ void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value)
|
||||
settings->setValue("personal/startupUpdateCheck", checkUpdatesOnStartup);
|
||||
}
|
||||
|
||||
void SettingsCache::setStartupCardUpdateCheckPromptForUpdate(bool value)
|
||||
{
|
||||
startupCardUpdateCheckPromptForUpdate = value;
|
||||
settings->setValue("personal/startupCardUpdateCheckPromptForUpdate", startupCardUpdateCheckPromptForUpdate);
|
||||
}
|
||||
|
||||
void SettingsCache::setStartupCardUpdateCheckAlwaysUpdate(bool value)
|
||||
{
|
||||
startupCardUpdateCheckAlwaysUpdate = value;
|
||||
settings->setValue("personal/startupCardUpdateCheckAlwaysUpdate", startupCardUpdateCheckAlwaysUpdate);
|
||||
}
|
||||
|
||||
void SettingsCache::setCardUpdateCheckInterval(int value)
|
||||
{
|
||||
cardUpdateCheckInterval = value;
|
||||
settings->setValue("personal/cardUpdateCheckInterval", cardUpdateCheckInterval);
|
||||
}
|
||||
|
||||
void SettingsCache::setLastCardUpdateCheck(QDate value)
|
||||
{
|
||||
lastCardUpdateCheck = value;
|
||||
settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck);
|
||||
}
|
||||
|
||||
void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
|
||||
{
|
||||
rememberGameSettings = _rememberGameSettings;
|
||||
@@ -1309,6 +1512,7 @@ void SettingsCache::loadPaths()
|
||||
{
|
||||
QString dataPath = getDataPath();
|
||||
deckPath = getSafeConfigPath("paths/decks", dataPath + "/decks/");
|
||||
filtersPath = getSafeConfigPath("paths/filters", dataPath + "/filters/");
|
||||
replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/");
|
||||
themesPath = getSafeConfigPath("paths/themes", dataPath + "/themes/");
|
||||
picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/");
|
||||
@@ -1341,7 +1545,7 @@ void SettingsCache::resetPaths()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCache &SettingsCache::instance()
|
||||
CardCounterSettings &SettingsCache::cardCounters() const
|
||||
{
|
||||
return *settingsCache;
|
||||
return *cardCounterSettings;
|
||||
}
|
||||
56
cockatrice/src/client/settings/card_counter_settings.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "card_counter_settings.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QSettings>
|
||||
#include <QtMath>
|
||||
|
||||
CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *parent)
|
||||
: SettingsManager(settingsPath + "global.ini", "cards", "counters", parent)
|
||||
{
|
||||
}
|
||||
|
||||
void CardCounterSettings::setColor(int counterId, const QColor &color)
|
||||
{
|
||||
QString key = QString("cards/counters/%1/color").arg(counterId);
|
||||
|
||||
if (settings.value(key).value<QColor>() == color)
|
||||
return;
|
||||
|
||||
settings.setValue(key, color);
|
||||
emit colorChanged(counterId, color);
|
||||
}
|
||||
|
||||
QColor CardCounterSettings::color(int counterId) const
|
||||
{
|
||||
QColor defaultColor;
|
||||
|
||||
if (counterId < 6) {
|
||||
// Preserve legacy colors
|
||||
defaultColor = QColor::fromHsv(counterId * 60, 150, 255);
|
||||
} else {
|
||||
// Future-proof support for more counters with pseudo-random colors
|
||||
int h = (counterId * 37) % 360;
|
||||
int s = 128 + 64 * qSin((counterId * 97) * 0.1); // 64-192
|
||||
int v = 196 + 32 * qSin((counterId * 101) * 0.07); // 164-228
|
||||
|
||||
defaultColor = QColor::fromHsv(h, s, v);
|
||||
}
|
||||
|
||||
return settings.value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value<QColor>();
|
||||
}
|
||||
|
||||
QString CardCounterSettings::displayName(int counterId) const
|
||||
{
|
||||
// Currently, card counters name are fixed to A, B, ..., Z, AA, AB, ...
|
||||
|
||||
auto nChars = 1 + counterId / 26;
|
||||
QString str;
|
||||
str.resize(nChars);
|
||||
|
||||
for (auto it = str.rbegin(); it != str.rend(); ++it) {
|
||||
*it = QChar('A' + (counterId) % 26);
|
||||
counterId /= 26;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
33
cockatrice/src/client/settings/card_counter_settings.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @file card_counter_settings.h
|
||||
* @ingroup GameSettings
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CARD_COUNTER_SETTINGS_H
|
||||
#define CARD_COUNTER_SETTINGS_H
|
||||
|
||||
#include <libcockatrice/settings/settings_manager.h>
|
||||
|
||||
class QSettings;
|
||||
class QColor;
|
||||
|
||||
class CardCounterSettings : public SettingsManager
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CardCounterSettings(const QString &settingsPath, QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] QColor color(int counterId) const;
|
||||
|
||||
[[nodiscard]] QString displayName(int counterId) const;
|
||||
|
||||
public slots:
|
||||
void setColor(int counterId, const QColor &color);
|
||||
|
||||
signals:
|
||||
void colorChanged(int counterId, const QColor &color);
|
||||
};
|
||||
|
||||
#endif // CARD_COUNTER_SETTINGS_H
|
||||
@@ -150,7 +150,7 @@ void ShortcutTreeView::currentChanged(const QModelIndex ¤t, const QModelIn
|
||||
*/
|
||||
void ShortcutTreeView::updateSearchString(const QString &searchString)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
|
||||
const auto skipEmptyParts = Qt::SkipEmptyParts;
|
||||
#else
|
||||
const auto skipEmptyParts = QString::SkipEmptyParts;
|
||||
@@ -1,7 +1,12 @@
|
||||
/**
|
||||
* @file shortcut_treeview.h
|
||||
* @ingroup CoreSettings
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef SHORTCUT_TREEVIEW_H
|
||||
#define SHORTCUT_TREEVIEW_H
|
||||
|
||||
#include <QModelIndex>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
@@ -16,7 +21,7 @@ public:
|
||||
explicit ShortcutFilterProxyModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
class ShortcutTreeView : public QTreeView
|
||||
@@ -74,7 +74,7 @@ void ShortcutsSettings::migrateShortcuts()
|
||||
shortCutsFile.beginGroup(custom);
|
||||
|
||||
if (shortCutsFile.contains("Textbox/unfocusTextBox")) {
|
||||
qCDebug(ShortcutsSettingsLog)
|
||||
qCInfo(ShortcutsSettingsLog)
|
||||
<< "[ShortcutsSettings] Textbox/unfocusTextBox shortcut found. Migrating to Player/unfocusTextBox.";
|
||||
QString unfocusTextBox = shortCutsFile.value("Textbox/unfocusTextBox", "").toString();
|
||||
this->setShortcuts("Player/unfocusTextBox", unfocusTextBox);
|
||||
@@ -82,7 +82,7 @@ void ShortcutsSettings::migrateShortcuts()
|
||||
}
|
||||
|
||||
if (shortCutsFile.contains("tab_game/aFocusChat")) {
|
||||
qCDebug(ShortcutsSettingsLog)
|
||||
qCInfo(ShortcutsSettingsLog)
|
||||
<< "[ShortcutsSettings] tab_game/aFocusChat shortcut found. Migrating to Player/aFocusChat.";
|
||||
QString aFocusChat = shortCutsFile.value("tab_game/aFocusChat", "").toString();
|
||||
this->setShortcuts("Player/aFocusChat", aFocusChat);
|
||||
@@ -91,7 +91,7 @@ void ShortcutsSettings::migrateShortcuts()
|
||||
|
||||
// PR #5564 changes "MainWindow/aDeckEditor" to "Tabs/aTabDeckEditor"
|
||||
if (shortCutsFile.contains("MainWindow/aDeckEditor")) {
|
||||
qCDebug(ShortcutsSettingsLog) << "MainWindow/aDeckEditor shortcut found. Migrating to Tabs/aTabDeckEditor.";
|
||||
qCInfo(ShortcutsSettingsLog) << "MainWindow/aDeckEditor shortcut found. Migrating to Tabs/aTabDeckEditor.";
|
||||
QString keySequence = shortCutsFile.value("MainWindow/aDeckEditor", "").toString();
|
||||
this->setShortcuts("Tabs/aTabDeckEditor", keySequence);
|
||||
shortCutsFile.remove("MainWindow/aDeckEditor");
|
||||
@@ -115,6 +115,13 @@ ShortcutKey ShortcutsSettings::getShortcut(const QString &name) const
|
||||
return getDefaultShortcut(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first shortcut for the given action.
|
||||
*
|
||||
* NOTE: In most cases you should be using ShortcutsSettings::getShortcut instead,
|
||||
* as that will return all shortcuts if there are multiple shortcuts.
|
||||
* The only reason to use this method is if an object does not accept multiple shortcuts, such as with QButtons.
|
||||
*/
|
||||
QKeySequence ShortcutsSettings::getSingleShortcut(const QString &name) const
|
||||
{
|
||||
return getShortcut(name).at(0);
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @file shortcuts_settings.h
|
||||
* @ingroup CoreSettings
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef SHORTCUTSSETTINGS_H
|
||||
#define SHORTCUTSSETTINGS_H
|
||||
|
||||
@@ -27,6 +33,7 @@ public:
|
||||
Move_bottom,
|
||||
Gameplay,
|
||||
Drawing,
|
||||
Hand,
|
||||
Chat_room,
|
||||
Game_window,
|
||||
Load_deck,
|
||||
@@ -65,6 +72,8 @@ public:
|
||||
return QApplication::translate("shortcutsTab", "Gameplay");
|
||||
case Drawing:
|
||||
return QApplication::translate("shortcutsTab", "Drawing");
|
||||
case Hand:
|
||||
return QApplication::translate("shortcutsTab", "Hand");
|
||||
case Chat_room:
|
||||
return QApplication::translate("shortcutsTab", "Chat Room");
|
||||
case Game_window:
|
||||
@@ -90,15 +99,15 @@ public:
|
||||
void setSequence(const QList &_sequence)
|
||||
{
|
||||
QList::operator=(_sequence);
|
||||
};
|
||||
QString getName() const
|
||||
}
|
||||
[[nodiscard]] QString getName() const
|
||||
{
|
||||
return QApplication::translate("shortcutsTab", name.toUtf8().data());
|
||||
};
|
||||
QString getGroupName() const
|
||||
}
|
||||
[[nodiscard]] QString getGroupName() const
|
||||
{
|
||||
return ShortcutGroup::getGroupName(group);
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
QString name;
|
||||
@@ -111,24 +120,24 @@ class ShortcutsSettings : public QObject
|
||||
public:
|
||||
explicit ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr);
|
||||
|
||||
ShortcutKey getDefaultShortcut(const QString &name) const;
|
||||
ShortcutKey getShortcut(const QString &name) const;
|
||||
QKeySequence getSingleShortcut(const QString &name) const;
|
||||
QString getDefaultShortcutString(const QString &name) const;
|
||||
QString getShortcutString(const QString &name) const;
|
||||
QString getShortcutFriendlyName(const QString &shortcutName) const;
|
||||
QList<QString> getAllShortcutKeys() const
|
||||
[[nodiscard]] ShortcutKey getDefaultShortcut(const QString &name) const;
|
||||
[[nodiscard]] ShortcutKey getShortcut(const QString &name) const;
|
||||
[[nodiscard]] QKeySequence getSingleShortcut(const QString &name) const;
|
||||
[[nodiscard]] QString getDefaultShortcutString(const QString &name) const;
|
||||
[[nodiscard]] QString getShortcutString(const QString &name) const;
|
||||
[[nodiscard]] QString getShortcutFriendlyName(const QString &shortcutName) const;
|
||||
[[nodiscard]] QList<QString> getAllShortcutKeys() const
|
||||
{
|
||||
return shortCuts.keys();
|
||||
};
|
||||
}
|
||||
|
||||
void setShortcuts(const QString &name, const QList<QKeySequence> &Sequence);
|
||||
void setShortcuts(const QString &name, const QKeySequence &Sequence);
|
||||
void setShortcuts(const QString &name, const QString &sequences);
|
||||
|
||||
bool isKeyAllowed(const QString &name, const QString &sequences) const;
|
||||
bool isValid(const QString &name, const QString &sequences) const;
|
||||
QStringList findOverlaps(const QString &name, const QString &sequences) const;
|
||||
[[nodiscard]] bool isKeyAllowed(const QString &name, const QString &sequences) const;
|
||||
[[nodiscard]] bool isValid(const QString &name, const QString &sequences) const;
|
||||
[[nodiscard]] QStringList findOverlaps(const QString &name, const QString &sequences) const;
|
||||
|
||||
void resetAllShortcuts();
|
||||
void clearAllShortcuts();
|
||||
@@ -143,8 +152,8 @@ private:
|
||||
QString settingsFilePath;
|
||||
QHash<QString, ShortcutKey> shortCuts;
|
||||
|
||||
QString stringifySequence(const QList<QKeySequence> &Sequence) const;
|
||||
QList<QKeySequence> parseSequenceString(const QString &stringSequence) const;
|
||||
[[nodiscard]] QString stringifySequence(const QList<QKeySequence> &Sequence) const;
|
||||
[[nodiscard]] QList<QKeySequence> parseSequenceString(const QString &stringSequence) const;
|
||||
|
||||
const QHash<QString, ShortcutKey> defaultShortCuts = {
|
||||
{"MainWindow/aCheckCardUpdates", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Check for Card Updates..."),
|
||||
@@ -173,6 +182,9 @@ private:
|
||||
{"MainWindow/aWatchReplay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Watch Replay..."),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Main_Window)},
|
||||
{"MainWindow/aStatusBar", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Show Status Bar"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Main_Window)},
|
||||
{"TabDeckEditor/aAnalyzeDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck (deckstats.net)"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Deck_Editor)},
|
||||
@@ -267,6 +279,13 @@ private:
|
||||
{"DeckViewContainer/loadRemoteButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Remote Deck..."),
|
||||
parseSequenceString("Ctrl+Alt+O"),
|
||||
ShortcutGroup::Game_Lobby)},
|
||||
{"DeckViewContainer/loadFromClipboardButton",
|
||||
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck from Clipboard..."),
|
||||
parseSequenceString("Ctrl+Shift+V"),
|
||||
ShortcutGroup::Game_Lobby)},
|
||||
{"DeckViewContainer/unloadDeckButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Unload Deck"),
|
||||
parseSequenceString("Ctrl+Alt+U"),
|
||||
ShortcutGroup::Game_Lobby)},
|
||||
{"DeckViewContainer/readyStartButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Ready to Start"),
|
||||
parseSequenceString("Ctrl+Shift+S"),
|
||||
ShortcutGroup::Game_Lobby)},
|
||||
@@ -274,31 +293,61 @@ private:
|
||||
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Sideboard Lock"),
|
||||
parseSequenceString("Ctrl+Shift+B"),
|
||||
ShortcutGroup::Game_Lobby)},
|
||||
{"Player/aCCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Green Counter"),
|
||||
{"DeckViewContainer/forceStartGameButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Force Start"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Game_Lobby)},
|
||||
{"Player/aCCMagenta", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (F)"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aRCMagenta", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (F)"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aSCMagenta", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (F)..."),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aCCPurple", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (E)"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aRCPurple", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (E)"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aSCPurple", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (E)..."),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aCCCyan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter(D)"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aRCCyan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (D)"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aSCCyan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (D)..."),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aCCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (C)"),
|
||||
parseSequenceString("Ctrl+>"),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aRCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Green Counter"),
|
||||
{"Player/aRCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (C)"),
|
||||
parseSequenceString("Ctrl+<"),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aSCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Green Counters..."),
|
||||
{"Player/aSCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (C)..."),
|
||||
parseSequenceString("Ctrl+?"),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aCCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Yellow Counter"),
|
||||
{"Player/aCCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (B)"),
|
||||
parseSequenceString("Ctrl+."),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aRCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Yellow Counter"),
|
||||
{"Player/aRCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (B)"),
|
||||
parseSequenceString("Ctrl+,"),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aSCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Yellow Counters..."),
|
||||
{"Player/aSCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (B)..."),
|
||||
parseSequenceString("Ctrl+/"),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aCCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Red Counter"),
|
||||
{"Player/aCCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (A)"),
|
||||
parseSequenceString("Alt+."),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aRCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Red Counter"),
|
||||
{"Player/aRCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (A)"),
|
||||
parseSequenceString("Alt+,"),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aSCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Red Counters..."),
|
||||
{"Player/aSCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (A)..."),
|
||||
parseSequenceString("Alt+/"),
|
||||
ShortcutGroup::Card_Counters)},
|
||||
{"Player/aInc", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Life Counter"),
|
||||
@@ -373,6 +422,10 @@ private:
|
||||
{"Player/aSetCounter_storm", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Other Counters..."),
|
||||
parseSequenceString("Ctrl+\\"),
|
||||
ShortcutGroup::Player_Counters)},
|
||||
{"Player/aIncrementAllCardCounters",
|
||||
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increment all card counters"),
|
||||
parseSequenceString("Ctrl+Shift+A"),
|
||||
ShortcutGroup::Playing_Area)},
|
||||
{"Player/aIncP", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Power (+1/+0)"),
|
||||
parseSequenceString("Ctrl++;Ctrl+="),
|
||||
ShortcutGroup::Power_Toughness)},
|
||||
@@ -490,6 +543,9 @@ private:
|
||||
{"Player/aSelectColumn", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Column"),
|
||||
parseSequenceString("Ctrl+Shift+C"),
|
||||
ShortcutGroup::Playing_Area)},
|
||||
{"Player/aRevealToAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reveal Selected Cards to All Players"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Playing_Area)},
|
||||
{"Player/aMoveToBottomLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom of Library"),
|
||||
parseSequenceString("Ctrl+B"),
|
||||
ShortcutGroup::Move_selected)},
|
||||
@@ -619,6 +675,22 @@ private:
|
||||
{"Player/aAlwaysLookAtTopCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Always Look At Top Card"),
|
||||
parseSequenceString("Ctrl+Shift+N"),
|
||||
ShortcutGroup::Drawing)},
|
||||
{"Player/aSortHandByName", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Sort Hand by Name"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Hand)},
|
||||
{"Player/aSortHandByType", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Sort Hand by Type"),
|
||||
parseSequenceString("Ctrl+Shift+H"),
|
||||
ShortcutGroup::Hand)},
|
||||
{"Player/aSortHandByManaValue", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Sort Hand by Mana Value"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Hand)},
|
||||
{"Player/aRevealHandToAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reveal Hand to All Players"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Hand)},
|
||||
{"Player/aRevealRandomHandCardToAll",
|
||||
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reveal Random Card to All Players"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Hand)},
|
||||
{"Player/aRotateViewCW", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Rotate View Clockwise"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Gameplay)},
|
||||
@@ -660,6 +732,8 @@ private:
|
||||
ShortcutGroup::Replays)},
|
||||
{"Tabs/aTabDeckEditor",
|
||||
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Deck Editor"), parseSequenceString(""), ShortcutGroup::Tabs)},
|
||||
{"Tabs/aTabHome",
|
||||
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Home"), parseSequenceString(""), ShortcutGroup::Tabs)},
|
||||
{"Tabs/aTabVisualDeckStorage", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Visual Deck Storage"),
|
||||
parseSequenceString(""),
|
||||
ShortcutGroup::Tabs)},
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "sound_engine.h"
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "settings/cache_settings.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QMediaPlayer>
|
||||
@@ -37,7 +37,7 @@ SoundEngine::~SoundEngine()
|
||||
void SoundEngine::soundEnabledChanged()
|
||||
{
|
||||
if (SettingsCache::instance().getSoundEnabled()) {
|
||||
qCDebug(SoundEngineLog) << "SoundEngine: enabling sound with" << audioData.size() << "sounds";
|
||||
qCInfo(SoundEngineLog) << "SoundEngine: enabling sound with" << audioData.size() << "sounds";
|
||||
if (!player) {
|
||||
player = new QMediaPlayer;
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
@@ -46,7 +46,7 @@ void SoundEngine::soundEnabledChanged()
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
qCDebug(SoundEngineLog) << "SoundEngine: disabling sound";
|
||||
qCInfo(SoundEngineLog) << "SoundEngine: disabling sound";
|
||||
if (player) {
|
||||
player->stop();
|
||||
player->deleteLater();
|
||||
@@ -90,7 +90,7 @@ void SoundEngine::ensureThemeDirectoryExists()
|
||||
{
|
||||
if (SettingsCache::instance().getSoundThemeName().isEmpty() ||
|
||||
!getAvailableThemes().contains(SettingsCache::instance().getSoundThemeName())) {
|
||||
qCDebug(SoundEngineLog) << "Sounds theme name not set, setting default value";
|
||||
qCInfo(SoundEngineLog) << "Sounds theme name not set, setting default value";
|
||||
SettingsCache::instance().setSoundThemeName(DEFAULT_THEME_NAME);
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ QStringMap &SoundEngine::getAvailableThemes()
|
||||
void SoundEngine::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getSoundThemeName();
|
||||
qCDebug(SoundEngineLog) << "Sound theme changed:" << themeName;
|
||||
qCInfo(SoundEngineLog) << "Sound theme changed:" << themeName;
|
||||
|
||||
QDir dir = getAvailableThemes().value(themeName);
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @file sound_engine.h
|
||||
* @ingroup Core
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef SOUNDENGINE_H
|
||||
#define SOUNDENGINE_H
|
||||
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
#ifndef TAB_GENERIC_DECK_EDITOR_H
|
||||
#define TAB_GENERIC_DECK_EDITOR_H
|
||||
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../menus/deck_editor/deck_editor_menu.h"
|
||||
#include "../ui/widgets/deck_editor/deck_editor_card_info_dock_widget.h"
|
||||
#include "../ui/widgets/deck_editor/deck_editor_database_display_widget.h"
|
||||
#include "../ui/widgets/deck_editor/deck_editor_deck_dock_widget.h"
|
||||
#include "../ui/widgets/deck_editor/deck_editor_filter_dock_widget.h"
|
||||
#include "../ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h"
|
||||
#include "../ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
|
||||
#include "tab.h"
|
||||
|
||||
class CardDatabaseModel;
|
||||
class CardDatabaseDisplayModel;
|
||||
|
||||
class CardInfoFrameWidget;
|
||||
class DeckLoader;
|
||||
class DeckEditorMenu;
|
||||
class DeckEditorCardInfoDockWidget;
|
||||
class DeckEditorDatabaseDisplayWidget;
|
||||
class DeckEditorDeckDockWidget;
|
||||
class DeckEditorFilterDockWidget;
|
||||
class DeckEditorPrintingSelectorDockWidget;
|
||||
class DeckPreviewDeckTagsDisplayWidget;
|
||||
class Response;
|
||||
class FilterTreeModel;
|
||||
class FilterBuilder;
|
||||
|
||||
class QTreeView;
|
||||
class QTextEdit;
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class QGroupBox;
|
||||
class QMessageBox;
|
||||
class QHBoxLayout;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
class QDockWidget;
|
||||
class QMenu;
|
||||
class QAction;
|
||||
|
||||
class AbstractTabDeckEditor : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AbstractTabDeckEditor(TabSupervisor *_tabSupervisor);
|
||||
|
||||
// UI and Navigation
|
||||
virtual void createMenus() = 0;
|
||||
[[nodiscard]] virtual QString getTabText() const override = 0;
|
||||
bool confirmClose();
|
||||
virtual void retranslateUi() override = 0;
|
||||
|
||||
// Deck Management
|
||||
void openDeck(DeckLoader *deck);
|
||||
DeckLoader *getDeckList() const;
|
||||
void setModified(bool _windowModified);
|
||||
|
||||
// UI Elements
|
||||
DeckEditorMenu *deckMenu;
|
||||
DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget;
|
||||
DeckEditorCardInfoDockWidget *cardInfoDockWidget;
|
||||
DeckEditorDeckDockWidget *deckDockWidget;
|
||||
DeckEditorFilterDockWidget *filterDockWidget;
|
||||
DeckEditorPrintingSelectorDockWidget *printingSelectorDockWidget;
|
||||
|
||||
public slots:
|
||||
void onDeckChanged();
|
||||
void updateCard(CardInfoPtr _card);
|
||||
void actAddCard(CardInfoPtr info);
|
||||
void actAddCardToSideboard(CardInfoPtr info);
|
||||
void actDecrementCard(CardInfoPtr info);
|
||||
void actDecrementCardFromSideboard(CardInfoPtr info);
|
||||
void actOpenRecent(const QString &fileName);
|
||||
void filterTreeChanged(FilterTree *filterTree);
|
||||
void closeRequest(bool forced = false) override;
|
||||
virtual void showPrintingSelector() = 0;
|
||||
virtual void dockTopLevelChanged(bool topLevel) = 0;
|
||||
|
||||
signals:
|
||||
void openDeckEditor(const DeckLoader *deckLoader);
|
||||
void deckEditorClosing(AbstractTabDeckEditor *tab);
|
||||
void decrementCard(CardInfoPtr card, QString zoneName);
|
||||
|
||||
protected slots:
|
||||
// Deck Operations
|
||||
virtual void actNewDeck();
|
||||
void cleanDeckAndResetModified();
|
||||
virtual void actLoadDeck();
|
||||
bool actSaveDeck();
|
||||
bool actSaveDeckAs();
|
||||
virtual void actLoadDeckFromClipboard();
|
||||
void actEditDeckInClipboard();
|
||||
void actEditDeckInClipboardRaw();
|
||||
void actSaveDeckToClipboard();
|
||||
void actSaveDeckToClipboardNoSetInfo();
|
||||
void actSaveDeckToClipboardRaw();
|
||||
void actSaveDeckToClipboardRawNoSetInfo();
|
||||
void actPrintDeck();
|
||||
void actExportDeckDecklist();
|
||||
void actExportDeckDecklistXyz();
|
||||
void actAnalyzeDeckDeckstats();
|
||||
void actAnalyzeDeckTappedout();
|
||||
|
||||
// Remote Save
|
||||
void saveDeckRemoteFinished(const Response &r);
|
||||
|
||||
// UI Layout Management
|
||||
virtual void loadLayout() = 0;
|
||||
virtual void restartLayout() = 0;
|
||||
virtual void freeDocksSize() = 0;
|
||||
virtual void refreshShortcuts() = 0;
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
virtual void dockVisibleTriggered() = 0;
|
||||
virtual void dockFloatingTriggered() = 0;
|
||||
|
||||
private:
|
||||
virtual void setDeck(DeckLoader *_deck);
|
||||
void editDeckInClipboard(bool annotated);
|
||||
void exportToDecklistWebsite(DeckLoader::DecklistWebsite website);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Enum for selecting deck open location
|
||||
*/
|
||||
enum DeckOpenLocation
|
||||
{
|
||||
CANCELLED,
|
||||
SAME_TAB,
|
||||
NEW_TAB
|
||||
};
|
||||
|
||||
DeckOpenLocation confirmOpen(bool openInSameTabIfBlank = true);
|
||||
QMessageBox *createSaveConfirmationWindow();
|
||||
bool isBlankNewDeck() const;
|
||||
|
||||
// Helper functions for card actions
|
||||
void addCardHelper(CardInfoPtr info, QString zoneName);
|
||||
void actSwapCard(CardInfoPtr info, QString zoneName);
|
||||
virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
|
||||
|
||||
// UI Menu Elements
|
||||
QMenu *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu;
|
||||
|
||||
QAction *aResetLayout;
|
||||
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating;
|
||||
QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
|
||||
|
||||
bool modified = false;
|
||||
};
|
||||
|
||||
#endif // TAB_GENERIC_DECK_EDITOR_H
|
||||
@@ -1,41 +0,0 @@
|
||||
#include "edhrec_commander_api_response_card_details_display_widget.h"
|
||||
|
||||
#include "../../../../game/cards/card_database_manager.h"
|
||||
|
||||
EdhrecCommanderApiResponseCardDetailsDisplayWidget::EdhrecCommanderApiResponseCardDetailsDisplayWidget(
|
||||
QWidget *parent,
|
||||
const EdhrecCommanderApiResponseCardDetails &_toDisplay)
|
||||
: QWidget(parent), toDisplay(_toDisplay)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
cardPictureWidget = new CardInfoPictureWidget(this);
|
||||
cardPictureWidget->setCard(CardDatabaseManager::getInstance()->getCard(toDisplay.name));
|
||||
|
||||
label = new QLabel(this);
|
||||
label->setText(toDisplay.name + "\n" + toDisplay.label);
|
||||
label->setAlignment(Qt::AlignHCenter);
|
||||
|
||||
// Set label color based on inclusion rate
|
||||
int inclusionRate = (toDisplay.numDecks * 100) / toDisplay.potentialDecks;
|
||||
|
||||
QColor labelColor;
|
||||
if (inclusionRate <= 30) {
|
||||
labelColor = QColor(255, 0, 0); // Red
|
||||
} else if (inclusionRate <= 60) {
|
||||
int red = 255 - ((inclusionRate - 30) * 2);
|
||||
int green = (inclusionRate - 30) * 4; // Adjust green to make the transition smoother
|
||||
labelColor = QColor(red, green, 0); // purple-ish
|
||||
} else if (inclusionRate <= 90) {
|
||||
int green = (inclusionRate - 60) * 5; // Increase green
|
||||
labelColor = QColor(100, green, 100); // Green shades
|
||||
} else {
|
||||
labelColor = QColor(100, 200, 100); // Dark Green
|
||||
}
|
||||
|
||||
label->setStyleSheet(QString("color: %1").arg(labelColor.name()));
|
||||
|
||||
layout->addWidget(cardPictureWidget);
|
||||
layout->addWidget(label);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
|
||||
#define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../ui/widgets/cards/card_info_picture_widget.h"
|
||||
#include "api_response/edhrec_commander_api_response_card_details.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
class EdhrecCommanderApiResponseCardDetailsDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EdhrecCommanderApiResponseCardDetailsDisplayWidget(
|
||||
QWidget *parent,
|
||||
const EdhrecCommanderApiResponseCardDetails &_toDisplay);
|
||||
|
||||
private:
|
||||
EdhrecCommanderApiResponseCardDetails toDisplay;
|
||||
QVBoxLayout *layout;
|
||||
CardInfoPictureWidget *cardPictureWidget;
|
||||
QLabel *label;
|
||||
};
|
||||
|
||||
#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "edhrec_commander_api_response_card_list_display_widget.h"
|
||||
|
||||
#include "../../../ui/widgets/general/display/banner_widget.h"
|
||||
#include "edhrec_commander_api_response_card_details_display_widget.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
EdhrecCommanderApiResponseCardListDisplayWidget::EdhrecCommanderApiResponseCardListDisplayWidget(
|
||||
QWidget *parent,
|
||||
EdhrecCommanderApiResponseCardList toDisplay)
|
||||
: QWidget(parent)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
header = new BannerWidget(this, toDisplay.header);
|
||||
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
header->setBuddy(flowWidget);
|
||||
|
||||
foreach (EdhrecCommanderApiResponseCardDetails card_detail, toDisplay.cardViews) {
|
||||
auto widget = new EdhrecCommanderApiResponseCardDetailsDisplayWidget(flowWidget, card_detail);
|
||||
flowWidget->addWidget(widget);
|
||||
}
|
||||
|
||||
layout->addWidget(header);
|
||||
layout->addWidget(flowWidget);
|
||||
}
|
||||
|
||||
void EdhrecCommanderApiResponseCardListDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
qDebug() << event->size();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#include "edhrec_commander_api_response_commander_details_display_widget.h"
|
||||
|
||||
#include "../../../../game/cards/card_database_manager.h"
|
||||
#include "../../../ui/widgets/cards/card_info_picture_widget.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCommanderDetailsDisplayWidget(
|
||||
QWidget *parent,
|
||||
const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails)
|
||||
: QWidget(parent), commanderDetails(_commanderDetails)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
commanderPicture = new CardInfoPictureWidget(this);
|
||||
commanderPicture->setCard(CardDatabaseManager::getInstance()->getCard(commanderDetails.getName()));
|
||||
|
||||
commanderDetails.debugPrint();
|
||||
|
||||
label = new QLabel(this);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
salt = new QLabel(this);
|
||||
salt->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(commanderPicture);
|
||||
layout->addWidget(label);
|
||||
layout->addWidget(salt);
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi()
|
||||
{
|
||||
label->setText(commanderDetails.getLabel());
|
||||
salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt()));
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef WINDOW_DECKEDITOR_H
|
||||
#define WINDOW_DECKEDITOR_H
|
||||
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../game_logic/key_signals.h"
|
||||
#include "../ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
|
||||
#include "abstract_tab_deck_editor.h"
|
||||
|
||||
class CardDatabaseModel;
|
||||
class CardDatabaseDisplayModel;
|
||||
class DeckListModel;
|
||||
|
||||
class QLabel;
|
||||
class DeckLoader;
|
||||
|
||||
class TabDeckEditor : public AbstractTabDeckEditor
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected slots:
|
||||
void loadLayout() override;
|
||||
void restartLayout() override;
|
||||
void freeDocksSize() override;
|
||||
void refreshShortcuts() override;
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void dockVisibleTriggered() override;
|
||||
void dockFloatingTriggered() override;
|
||||
void dockTopLevelChanged(bool topLevel) override;
|
||||
|
||||
public:
|
||||
explicit TabDeckEditor(TabSupervisor *_tabSupervisor);
|
||||
void retranslateUi() override;
|
||||
QString getTabText() const override;
|
||||
void createMenus() override;
|
||||
|
||||
public slots:
|
||||
void showPrintingSelector() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,274 +0,0 @@
|
||||
#ifndef TAB_GAME_H
|
||||
#define TAB_GAME_H
|
||||
|
||||
#include "../../client/tearoff_menu.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../ui/widgets/visual_deck_storage/visual_deck_storage_widget.h"
|
||||
#include "pb/event_leave.pb.h"
|
||||
#include "pb/serverinfo_game.pb.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game");
|
||||
|
||||
class UserListProxy;
|
||||
class DeckViewContainer;
|
||||
class AbstractClient;
|
||||
class CardDatabase;
|
||||
class GameView;
|
||||
class GameScene;
|
||||
class CardInfoFrameWidget;
|
||||
class MessageLogWidget;
|
||||
class QTimer;
|
||||
class QSplitter;
|
||||
class QLabel;
|
||||
class QToolButton;
|
||||
class QMenu;
|
||||
class ZoneViewLayout;
|
||||
class ZoneViewWidget;
|
||||
class PhasesToolbar;
|
||||
class PlayerListWidget;
|
||||
class ReplayTimelineWidget;
|
||||
class Response;
|
||||
class GameEventContainer;
|
||||
class GameEventContext;
|
||||
class GameCommand;
|
||||
class CommandContainer;
|
||||
class Event_GameJoined;
|
||||
class Event_GameStateChanged;
|
||||
class Event_PlayerPropertiesChanged;
|
||||
class Event_Join;
|
||||
class Event_Leave;
|
||||
class Event_GameHostChanged;
|
||||
class Event_GameClosed;
|
||||
class Event_GameStart;
|
||||
class Event_SetActivePlayer;
|
||||
class Event_SetActivePhase;
|
||||
class Event_Ping;
|
||||
class Event_GameSay;
|
||||
class Event_Kicked;
|
||||
class Event_ReverseTurn;
|
||||
class CardZone;
|
||||
class AbstractCardItem;
|
||||
class CardItem;
|
||||
class DeckLoader;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
class GameReplay;
|
||||
class ServerInfo_User;
|
||||
class PendingCommand;
|
||||
class LineEditCompleter;
|
||||
class QDockWidget;
|
||||
class QStackedWidget;
|
||||
|
||||
class TabGame : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QTimer *gameTimer;
|
||||
int secondsElapsed;
|
||||
const UserListProxy *userListProxy;
|
||||
QList<AbstractClient *> clients;
|
||||
ServerInfo_Game gameInfo;
|
||||
QMap<int, QString> roomGameTypes;
|
||||
int hostId;
|
||||
int localPlayerId;
|
||||
const bool isLocalGame;
|
||||
bool spectator;
|
||||
bool judge;
|
||||
QMap<int, Player *> players;
|
||||
QMap<int, ServerInfo_User> spectators;
|
||||
bool gameStateKnown;
|
||||
bool resuming;
|
||||
QStringList phasesList;
|
||||
int currentPhase;
|
||||
int activePlayer;
|
||||
CardItem *activeCard;
|
||||
bool gameClosed;
|
||||
QStringList gameTypes;
|
||||
QCompleter *completer;
|
||||
QStringList autocompleteUserList;
|
||||
QStackedWidget *mainWidget;
|
||||
|
||||
// Replay related members
|
||||
GameReplay *replay;
|
||||
int currentReplayStep;
|
||||
QList<int> replayTimeline;
|
||||
ReplayTimelineWidget *timelineWidget;
|
||||
QToolButton *replayPlayButton, *replayFastForwardButton;
|
||||
QAction *aReplaySkipForward, *aReplaySkipBackward, *aReplaySkipForwardBig, *aReplaySkipBackwardBig;
|
||||
|
||||
CardInfoFrameWidget *cardInfoFrameWidget;
|
||||
PlayerListWidget *playerListWidget;
|
||||
QLabel *timeElapsedLabel;
|
||||
MessageLogWidget *messageLog;
|
||||
QLabel *sayLabel;
|
||||
LineEditCompleter *sayEdit;
|
||||
PhasesToolbar *phasesToolbar;
|
||||
GameScene *scene;
|
||||
GameView *gameView;
|
||||
QMap<int, DeckViewContainer *> deckViewContainers;
|
||||
QVBoxLayout *cardVInfoLayout, *messageLogLayout, *gamePlayAreaVBox, *deckViewContainerLayout;
|
||||
QHBoxLayout *cardHInfoLayout, *sayHLayout, *mainHLayout, *replayControlLayout;
|
||||
QWidget *cardBoxLayoutWidget, *messageLogLayoutWidget, *gamePlayAreaWidget, *deckViewContainerWidget,
|
||||
*replayControlWidget;
|
||||
QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock;
|
||||
QAction *playersSeparator;
|
||||
QMenu *gameMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, *replayDockMenu;
|
||||
TearOffMenu *phasesMenu;
|
||||
QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextPhaseAction, *aNextTurn,
|
||||
*aReverseTurn, *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout;
|
||||
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating,
|
||||
*aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating;
|
||||
QAction *aFocusChat;
|
||||
QList<QAction *> phaseActions;
|
||||
|
||||
Player *addPlayer(int playerId, const ServerInfo_User &info);
|
||||
|
||||
bool isMainPlayerConceded() const;
|
||||
|
||||
void startGame(bool resuming);
|
||||
void stopGame();
|
||||
void closeGame();
|
||||
bool leaveGame();
|
||||
|
||||
void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
|
||||
|
||||
void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event,
|
||||
int eventPlayerId,
|
||||
const GameEventContext &context);
|
||||
void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context);
|
||||
Player *setActivePlayer(int id);
|
||||
void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context);
|
||||
void setActivePhase(int phase);
|
||||
void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/);
|
||||
void emitUserEvent();
|
||||
void createMenuItems();
|
||||
void createReplayMenuItems();
|
||||
void createViewMenuItems();
|
||||
void createCardInfoDock(bool bReplay = false);
|
||||
void createPlayerListDock(bool bReplay = false);
|
||||
void createMessageDock(bool bReplay = false);
|
||||
void createPlayAreaWidget(bool bReplay = false);
|
||||
void createDeckViewContainerWidget(bool bReplay = false);
|
||||
void createReplayDock();
|
||||
QString getLeaveReason(Event_Leave::LeaveReason reason);
|
||||
signals:
|
||||
void gameClosing(TabGame *tab);
|
||||
void playerAdded(Player *player);
|
||||
void playerRemoved(Player *player);
|
||||
void containerProcessingStarted(const GameEventContext &context);
|
||||
void containerProcessingDone();
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void openDeckEditor(const DeckLoader *deck);
|
||||
void notIdle();
|
||||
private slots:
|
||||
void replayNextEvent(Player::EventProcessingOptions options);
|
||||
void replayFinished();
|
||||
void replayPlayButtonToggled(bool checked);
|
||||
void replayFastForwardButtonToggled(bool checked);
|
||||
void replayRewind();
|
||||
|
||||
void incrementGameTime();
|
||||
void adminLockChanged(bool lock);
|
||||
void newCardAdded(AbstractCardItem *card);
|
||||
void updateCardMenu(AbstractCardItem *card);
|
||||
|
||||
void actGameInfo();
|
||||
void actConcede();
|
||||
void actRemoveLocalArrows();
|
||||
void actRotateViewCW();
|
||||
void actRotateViewCCW();
|
||||
void actSay();
|
||||
void actPhaseAction();
|
||||
void actNextPhase();
|
||||
void actNextPhaseAction();
|
||||
void actNextTurn();
|
||||
void actReverseTurn();
|
||||
|
||||
void addMentionTag(const QString &value);
|
||||
void linkCardToChat(const QString &cardName);
|
||||
void commandFinished(const Response &response);
|
||||
|
||||
void refreshShortcuts();
|
||||
|
||||
void loadLayout();
|
||||
void actCompleterChanged();
|
||||
void actResetLayout();
|
||||
void freeDocksSize();
|
||||
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void dockVisibleTriggered();
|
||||
void dockFloatingTriggered();
|
||||
void dockTopLevelChanged(bool topLevel);
|
||||
|
||||
public:
|
||||
TabGame(TabSupervisor *_tabSupervisor,
|
||||
QList<AbstractClient *> &_clients,
|
||||
const Event_GameJoined &event,
|
||||
const QMap<int, QString> &_roomGameTypes);
|
||||
TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay);
|
||||
~TabGame() override;
|
||||
void retranslateUi() override;
|
||||
void updatePlayerListDockTitle();
|
||||
void closeRequest(bool forced = false) override;
|
||||
const QMap<int, Player *> &getPlayers() const
|
||||
{
|
||||
return players;
|
||||
}
|
||||
CardItem *getCard(int playerId, const QString &zoneName, int cardId) const;
|
||||
bool isHost() const
|
||||
{
|
||||
return hostId == localPlayerId;
|
||||
}
|
||||
bool getIsLocalGame() const
|
||||
{
|
||||
return isLocalGame;
|
||||
}
|
||||
int getGameId() const
|
||||
{
|
||||
return gameInfo.game_id();
|
||||
}
|
||||
QString getTabText() const override;
|
||||
bool getSpectator() const
|
||||
{
|
||||
return spectator;
|
||||
}
|
||||
bool getSpectatorsSeeEverything() const
|
||||
{
|
||||
return gameInfo.spectators_omniscient();
|
||||
}
|
||||
bool isSpectator();
|
||||
Player *getActiveLocalPlayer() const;
|
||||
AbstractClient *getClientForPlayer(int playerId) const;
|
||||
|
||||
void setActiveCard(CardItem *card);
|
||||
CardItem *getActiveCard() const
|
||||
{
|
||||
return activeCard;
|
||||
}
|
||||
|
||||
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, const QString &providerId = "") const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef OVERLAP_LAYOUT_H
|
||||
#define OVERLAP_LAYOUT_H
|
||||
|
||||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
class OverlapLayout : public QLayout
|
||||
{
|
||||
public:
|
||||
OverlapLayout(QWidget *parent = nullptr,
|
||||
int overlapPercentage = 10,
|
||||
int maxColumns = 2,
|
||||
int maxRows = 2,
|
||||
Qt::Orientation direction = Qt::Horizontal);
|
||||
~OverlapLayout();
|
||||
|
||||
void addItem(QLayoutItem *item) override;
|
||||
int count() const override;
|
||||
QLayoutItem *itemAt(int index) const override;
|
||||
QLayoutItem *takeAt(int index) override;
|
||||
void setGeometry(const QRect &rect) override;
|
||||
QSize minimumSize() const override;
|
||||
QSize sizeHint() const override;
|
||||
void setMaxColumns(int _maxColumns);
|
||||
void setMaxRows(int _maxRows);
|
||||
int calculateMaxColumns() const;
|
||||
int calculateRowsForColumns(int columns) const;
|
||||
int calculateMaxRows() const;
|
||||
int calculateColumnsForRows(int rows) const;
|
||||
void setDirection(Qt::Orientation _direction);
|
||||
|
||||
private:
|
||||
QList<QLayoutItem *> itemList;
|
||||
int overlapPercentage;
|
||||
int maxColumns;
|
||||
int maxRows;
|
||||
Qt::Orientation direction;
|
||||
|
||||
// Calculate the preferred size of the layout
|
||||
QSize calculatePreferredSize() const;
|
||||
};
|
||||
|
||||
#endif // OVERLAP_LAYOUT_H
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef PICTURELOADER_H
|
||||
#define PICTURELOADER_H
|
||||
|
||||
#include "../../../game/cards/card_database.h"
|
||||
#include "picture_loader_worker.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(PictureLoaderLog, "picture_loader");
|
||||
inline Q_LOGGING_CATEGORY(PictureLoaderCardBackCacheFailLog, "picture_loader.card_back_cache_fail");
|
||||
|
||||
class PictureLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static PictureLoader &getInstance()
|
||||
{
|
||||
static PictureLoader instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit PictureLoader();
|
||||
~PictureLoader() override;
|
||||
// Singleton - Don't implement copy constructor and assign operator
|
||||
PictureLoader(PictureLoader const &);
|
||||
void operator=(PictureLoader const &);
|
||||
|
||||
PictureLoaderWorker *worker;
|
||||
|
||||
public:
|
||||
static void getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size);
|
||||
static void getCardBackPixmap(QPixmap &pixmap, QSize size);
|
||||
static void getCardBackLoadingInProgressPixmap(QPixmap &pixmap, QSize size);
|
||||
static void getCardBackLoadingFailedPixmap(QPixmap &pixmap, QSize size);
|
||||
static void clearPixmapCache(CardInfoPtr card);
|
||||
static void clearPixmapCache();
|
||||
static void cacheCardPixmaps(QList<CardInfoPtr> cards);
|
||||
static bool hasCustomArt();
|
||||
|
||||
public slots:
|
||||
static void clearNetworkCache();
|
||||
|
||||
private slots:
|
||||
void picDownloadChanged();
|
||||
void picsPathChanged();
|
||||
|
||||
public slots:
|
||||
void imageLoaded(CardInfoPtr card, const QImage &image);
|
||||
};
|
||||
#endif
|
||||
@@ -1,481 +0,0 @@
|
||||
#include "picture_loader_worker.h"
|
||||
|
||||
#include "../../../game/cards/card_database_manager.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDirIterator>
|
||||
#include <QMovie>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QNetworkReply>
|
||||
#include <QThread>
|
||||
|
||||
// Card back returned by gatherer when card is not found
|
||||
QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441";
|
||||
|
||||
PictureLoaderWorker::PictureLoaderWorker()
|
||||
: QObject(nullptr), picsPath(SettingsCache::instance().getPicsPath()),
|
||||
customPicsPath(SettingsCache::instance().getCustomPicsPath()),
|
||||
picDownload(SettingsCache::instance().getPicDownload()), downloadRunning(false), loadQueueRunning(false),
|
||||
overrideAllCardArtWithPersonalPreference(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference())
|
||||
{
|
||||
connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection);
|
||||
connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
|
||||
connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
|
||||
connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this,
|
||||
&PictureLoaderWorker::setOverrideAllCardArtWithPersonalPreference);
|
||||
|
||||
networkManager = new QNetworkAccessManager(this);
|
||||
// We need a timeout to ensure requests don't hang indefinitely in case of
|
||||
// cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397
|
||||
// Use Qt's default timeout (30s, as of 2023-02-22)
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
networkManager->setTransferTimeout();
|
||||
#endif
|
||||
auto cache = new QNetworkDiskCache(this);
|
||||
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
|
||||
cache->setMaximumCacheSize(1024L * 1024L *
|
||||
static_cast<qint64>(SettingsCache::instance().getNetworkCacheSizeInMB()));
|
||||
// Note: the settings is in MB, but QNetworkDiskCache uses bytes
|
||||
connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache,
|
||||
[cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB)); });
|
||||
networkManager->setCache(cache);
|
||||
// Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished
|
||||
// We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache
|
||||
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
|
||||
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *)));
|
||||
|
||||
cacheFilePath = SettingsCache::instance().getRedirectCachePath() + REDIRECT_CACHE_FILENAME;
|
||||
loadRedirectCache();
|
||||
cleanStaleEntries();
|
||||
|
||||
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this,
|
||||
&PictureLoaderWorker::saveRedirectCache);
|
||||
|
||||
pictureLoaderThread = new QThread;
|
||||
pictureLoaderThread->start(QThread::LowPriority);
|
||||
moveToThread(pictureLoaderThread);
|
||||
}
|
||||
|
||||
PictureLoaderWorker::~PictureLoaderWorker()
|
||||
{
|
||||
pictureLoaderThread->deleteLater();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::processLoadQueue()
|
||||
{
|
||||
if (loadQueueRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadQueueRunning = true;
|
||||
while (true) {
|
||||
mutex.lock();
|
||||
if (loadQueue.isEmpty()) {
|
||||
mutex.unlock();
|
||||
loadQueueRunning = false;
|
||||
return;
|
||||
}
|
||||
cardBeingLoaded = loadQueue.takeFirst();
|
||||
mutex.unlock();
|
||||
|
||||
QString setName = cardBeingLoaded.getSetName();
|
||||
QString cardName = cardBeingLoaded.getCard()->getName();
|
||||
QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName();
|
||||
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardName << " set: " << setName << "]: Trying to load picture";
|
||||
|
||||
// FIXME: This is a hack so that to keep old Cockatrice behavior
|
||||
// (ignoring provider ID) when the "override all card art with personal
|
||||
// preference" is set.
|
||||
//
|
||||
// Figure out a proper way to integrate the two systems at some point.
|
||||
//
|
||||
// Note: need to go through a member for
|
||||
// overrideAllCardArtWithPersonalPreference as reading from the
|
||||
// SettingsCache instance from the PictureLoaderWorker thread could
|
||||
// cause race conditions.
|
||||
//
|
||||
// XXX: Reading from the CardDatabaseManager instance from the
|
||||
// PictureLoaderWorker thread might not be safe either
|
||||
bool searchCustomPics = overrideAllCardArtWithPersonalPreference ||
|
||||
CardDatabaseManager::getInstance()->isProviderIdForPreferredPrinting(
|
||||
cardName, cardBeingLoaded.getCard()->getPixmapCacheKey());
|
||||
if (searchCustomPics && cardImageExistsOnDisk(setName, correctedCardName, searchCustomPics)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardName << " set: " << setName << "]: No custom picture, trying to download";
|
||||
cardsToDownload.append(cardBeingLoaded);
|
||||
cardBeingLoaded.clear();
|
||||
if (!downloadRunning) {
|
||||
startNextPicDownload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &correctedCardname, bool searchCustomPics)
|
||||
{
|
||||
QImage image;
|
||||
QImageReader imgReader;
|
||||
imgReader.setDecideFormatFromContent(true);
|
||||
QList<QString> picsPaths = QList<QString>();
|
||||
|
||||
if (searchCustomPics) {
|
||||
QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
|
||||
// Recursively check all subdirectories of the CUSTOM folder
|
||||
while (it.hasNext()) {
|
||||
QString thisPath(it.next());
|
||||
QFileInfo thisFileInfo(thisPath);
|
||||
|
||||
if (thisFileInfo.isFile() &&
|
||||
(thisFileInfo.fileName() == correctedCardname || thisFileInfo.completeBaseName() == correctedCardname ||
|
||||
thisFileInfo.baseName() == correctedCardname)) {
|
||||
picsPaths << thisPath; // Card found in the CUSTOM directory, somewhere
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!setName.isEmpty()) {
|
||||
picsPaths << picsPath + "/" + setName + "/" + correctedCardname
|
||||
// We no longer store downloaded images there, but don't just ignore
|
||||
// stuff that old versions have put there.
|
||||
<< picsPath + "/downloadedPics/" + setName + "/" + correctedCardname;
|
||||
}
|
||||
|
||||
// Iterates through the list of paths, searching for images with the desired
|
||||
// name with any QImageReader-supported
|
||||
// extension
|
||||
for (const auto &_picsPath : picsPaths) {
|
||||
imgReader.setFileName(_picsPath);
|
||||
if (imgReader.read(&image)) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << correctedCardname << " set: " << setName << "]: Picture found on disk.";
|
||||
imageLoaded(cardBeingLoaded.getCard(), image);
|
||||
return true;
|
||||
}
|
||||
imgReader.setFileName(_picsPath + ".full");
|
||||
if (imgReader.read(&image)) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << correctedCardname << " set: " << setName << "]: Picture.full found on disk.";
|
||||
imageLoaded(cardBeingLoaded.getCard(), image);
|
||||
return true;
|
||||
}
|
||||
imgReader.setFileName(_picsPath + ".xlhq");
|
||||
if (imgReader.read(&image)) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << correctedCardname << " set: " << setName << "]: Picture.xlhq found on disk.";
|
||||
imageLoaded(cardBeingLoaded.getCard(), image);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::startNextPicDownload()
|
||||
{
|
||||
if (cardsToDownload.isEmpty()) {
|
||||
cardBeingDownloaded.clear();
|
||||
downloadRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
downloadRunning = true;
|
||||
|
||||
cardBeingDownloaded = cardsToDownload.takeFirst();
|
||||
|
||||
QString picUrl = cardBeingDownloaded.getCurrentUrl();
|
||||
|
||||
if (picUrl.isEmpty()) {
|
||||
downloadRunning = false;
|
||||
picDownloadFailed();
|
||||
} else {
|
||||
QUrl url(picUrl);
|
||||
qCDebug(PictureLoaderWorkerLog).nospace() << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Trying to fetch picture from url " << url.toDisplayString();
|
||||
makeRequest(url);
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picDownloadFailed()
|
||||
{
|
||||
/* Take advantage of short circuiting here to call the nextUrl until one
|
||||
is not available. Only once nextUrl evaluates to false will this move
|
||||
on to nextSet. If the Urls for a particular card are empty, this will
|
||||
effectively go through the sets for that card. */
|
||||
if (cardBeingDownloaded.nextUrl() || cardBeingDownloaded.nextSet()) {
|
||||
mutex.lock();
|
||||
loadQueue.prepend(cardBeingDownloaded);
|
||||
mutex.unlock();
|
||||
} else {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getCorrectedName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Picture NOT found, "
|
||||
<< (picDownload ? "download failed" : "downloads disabled")
|
||||
<< ", no more url combinations to try: BAILING OUT";
|
||||
imageLoaded(cardBeingDownloaded.getCard(), QImage());
|
||||
cardBeingDownloaded.clear();
|
||||
}
|
||||
emit startLoadQueue();
|
||||
}
|
||||
|
||||
bool PictureLoaderWorker::imageIsBlackListed(const QByteArray &picData)
|
||||
{
|
||||
QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex();
|
||||
return md5Blacklist.contains(md5sum);
|
||||
}
|
||||
|
||||
QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url)
|
||||
{
|
||||
// Check if the redirect is cached
|
||||
QUrl cachedRedirect = getCachedRedirect(url);
|
||||
if (!cachedRedirect.isEmpty()) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getCorrectedName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Using cached redirect for " << url.toDisplayString()
|
||||
<< " to " << cachedRedirect.toDisplayString();
|
||||
return makeRequest(cachedRedirect); // Use the cached redirect
|
||||
}
|
||||
|
||||
QNetworkRequest req(url);
|
||||
|
||||
if (!picDownload) {
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
|
||||
}
|
||||
|
||||
QNetworkReply *reply = networkManager->get(req);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, url]() {
|
||||
QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
|
||||
if (redirectTarget.isValid()) {
|
||||
QUrl redirectUrl = redirectTarget.toUrl();
|
||||
if (redirectUrl.isRelative()) {
|
||||
redirectUrl = url.resolved(redirectUrl);
|
||||
}
|
||||
|
||||
cacheRedirect(url, redirectUrl);
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getCorrectedName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Caching redirect from " << url.toDisplayString()
|
||||
<< " to " << redirectUrl.toDisplayString();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl)
|
||||
{
|
||||
redirectCache[originalUrl] = qMakePair(redirectUrl, QDateTime::currentDateTimeUtc());
|
||||
saveRedirectCache();
|
||||
}
|
||||
|
||||
QUrl PictureLoaderWorker::getCachedRedirect(const QUrl &originalUrl) const
|
||||
{
|
||||
if (redirectCache.contains(originalUrl)) {
|
||||
return redirectCache[originalUrl].first;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::loadRedirectCache()
|
||||
{
|
||||
QSettings settings(cacheFilePath, QSettings::IniFormat);
|
||||
|
||||
redirectCache.clear();
|
||||
int size = settings.beginReadArray(REDIRECT_HEADER_NAME);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
QUrl originalUrl = settings.value(REDIRECT_ORIGINAL_URL).toUrl();
|
||||
QUrl redirectUrl = settings.value(REDIRECT_URL).toUrl();
|
||||
QDateTime timestamp = settings.value(REDIRECT_TIMESTAMP).toDateTime();
|
||||
|
||||
if (originalUrl.isValid() && redirectUrl.isValid()) {
|
||||
redirectCache[originalUrl] = qMakePair(redirectUrl, timestamp);
|
||||
}
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::saveRedirectCache() const
|
||||
{
|
||||
QSettings settings(cacheFilePath, QSettings::IniFormat);
|
||||
|
||||
settings.beginWriteArray(REDIRECT_HEADER_NAME, static_cast<int>(redirectCache.size()));
|
||||
int index = 0;
|
||||
for (auto it = redirectCache.cbegin(); it != redirectCache.cend(); ++it) {
|
||||
settings.setArrayIndex(index++);
|
||||
settings.setValue(REDIRECT_ORIGINAL_URL, it.key());
|
||||
settings.setValue(REDIRECT_URL, it.value().first);
|
||||
settings.setValue(REDIRECT_TIMESTAMP, it.value().second);
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::cleanStaleEntries()
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
|
||||
auto it = redirectCache.begin();
|
||||
while (it != redirectCache.end()) {
|
||||
if (it.value().second.addDays(SettingsCache::instance().getRedirectCacheTtl()) < now) {
|
||||
it = redirectCache.erase(it); // Remove stale entry
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
|
||||
{
|
||||
bool isFromCache = reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
|
||||
|
||||
if (reply->error()) {
|
||||
if (isFromCache) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Removing corrupted cache file for url " << reply->url().toDisplayString() << " and retrying ("
|
||||
<< reply->errorString() << ")";
|
||||
|
||||
networkManager->cache()->remove(reply->url());
|
||||
|
||||
makeRequest(reply->url());
|
||||
} else {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: " << (picDownload ? "Download" : "Cache search") << " failed for url "
|
||||
<< reply->url().toDisplayString() << " (" << reply->errorString() << ")";
|
||||
|
||||
picDownloadFailed();
|
||||
startNextPicDownload();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// List of status codes from https://doc.qt.io/qt-6/qnetworkreply.html#redirected
|
||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 ||
|
||||
statusCode == 308) {
|
||||
QUrl redirectUrl = reply->header(QNetworkRequest::LocationHeader).toUrl();
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: following " << (isFromCache ? "cached redirect" : "redirect") << " to "
|
||||
<< redirectUrl.toDisplayString();
|
||||
makeRequest(redirectUrl);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// peek is used to keep the data in the buffer for use by QImageReader
|
||||
const QByteArray &picData = reply->peek(reply->size());
|
||||
|
||||
if (imageIsBlackListed(picData)) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Picture found, but blacklisted, will consider it as not found";
|
||||
|
||||
picDownloadFailed();
|
||||
reply->deleteLater();
|
||||
startNextPicDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
QImage testImage;
|
||||
|
||||
QImageReader imgReader;
|
||||
imgReader.setDecideFormatFromContent(true);
|
||||
imgReader.setDevice(reply);
|
||||
|
||||
bool logSuccessMessage = false;
|
||||
|
||||
static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h
|
||||
auto replyHeader = reply->peek(riffHeaderSize);
|
||||
|
||||
if (replyHeader.startsWith("RIFF") && replyHeader.endsWith("WEBP")) {
|
||||
auto imgBuf = QBuffer(this);
|
||||
imgBuf.setData(reply->readAll());
|
||||
|
||||
auto movie = QMovie(&imgBuf);
|
||||
movie.start();
|
||||
movie.stop();
|
||||
|
||||
imageLoaded(cardBeingDownloaded.getCard(), movie.currentImage());
|
||||
logSuccessMessage = true;
|
||||
} else if (imgReader.read(&testImage)) {
|
||||
imageLoaded(cardBeingDownloaded.getCard(), testImage);
|
||||
logSuccessMessage = true;
|
||||
} else {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Possible " << (isFromCache ? "cached" : "downloaded") << " picture at "
|
||||
<< reply->url().toDisplayString() << " could not be loaded: " << reply->errorString();
|
||||
|
||||
picDownloadFailed();
|
||||
}
|
||||
|
||||
if (logSuccessMessage) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Image successfully " << (isFromCache ? "loaded from cached" : "downloaded from") << " url "
|
||||
<< reply->url().toDisplayString();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
startNextPicDownload();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
|
||||
// avoid queueing the same card more than once
|
||||
if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const PictureToLoad &pic : loadQueue) {
|
||||
if (pic.getCard() == card)
|
||||
return;
|
||||
}
|
||||
|
||||
for (const PictureToLoad &pic : cardsToDownload) {
|
||||
if (pic.getCard() == card)
|
||||
return;
|
||||
}
|
||||
|
||||
loadQueue.append(PictureToLoad(card));
|
||||
emit startLoadQueue();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picDownloadChanged()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
picDownload = SettingsCache::instance().getPicDownload();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picsPathChanged()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
picsPath = SettingsCache::instance().getPicsPath();
|
||||
customPicsPath = SettingsCache::instance().getCustomPicsPath();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArtWithPersonalPreference)
|
||||
{
|
||||
overrideAllCardArtWithPersonalPreference = _overrideAllCardArtWithPersonalPreference;
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::clearNetworkCache()
|
||||
{
|
||||
networkManager->cache()->clear();
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
#ifndef PICTURE_LOADER_WORKER_H
|
||||
#define PICTURE_LOADER_WORKER_H
|
||||
|
||||
#include "../../../game/cards/card_database.h"
|
||||
#include "picture_to_load.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QMutex>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
#define REDIRECT_HEADER_NAME "redirects"
|
||||
#define REDIRECT_ORIGINAL_URL "original"
|
||||
#define REDIRECT_URL "redirect"
|
||||
#define REDIRECT_TIMESTAMP "timestamp"
|
||||
#define REDIRECT_CACHE_FILENAME "cache.ini"
|
||||
|
||||
inline Q_LOGGING_CATEGORY(PictureLoaderWorkerLog, "picture_loader.worker");
|
||||
|
||||
class PictureLoaderWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PictureLoaderWorker();
|
||||
~PictureLoaderWorker() override;
|
||||
|
||||
void enqueueImageLoad(CardInfoPtr card);
|
||||
void clearNetworkCache();
|
||||
|
||||
private:
|
||||
static QStringList md5Blacklist;
|
||||
|
||||
QThread *pictureLoaderThread;
|
||||
QString picsPath, customPicsPath;
|
||||
QList<PictureToLoad> loadQueue;
|
||||
QMutex mutex;
|
||||
QNetworkAccessManager *networkManager;
|
||||
QHash<QUrl, QPair<QUrl, QDateTime>> redirectCache; // Stores redirect and timestamp
|
||||
QString cacheFilePath; // Path to persistent storage
|
||||
static constexpr int CacheTTLInDays = 30; // TODO: Make user configurable
|
||||
QList<PictureToLoad> cardsToDownload;
|
||||
PictureToLoad cardBeingLoaded;
|
||||
PictureToLoad cardBeingDownloaded;
|
||||
bool picDownload, downloadRunning, loadQueueRunning;
|
||||
bool overrideAllCardArtWithPersonalPreference;
|
||||
void startNextPicDownload();
|
||||
|
||||
/** Emit the `imageLoaded` signal and return `true` if a picture is found on
|
||||
disk, return `false` otherwise.
|
||||
|
||||
If `searchCustomPics` is `true`, the CUSTOM folder is searched for a
|
||||
matching image first; otherwise, only the set-based folders are used. */
|
||||
bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName, bool searchCustomPics);
|
||||
|
||||
bool imageIsBlackListed(const QByteArray &);
|
||||
QNetworkReply *makeRequest(const QUrl &url);
|
||||
void cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl);
|
||||
QUrl getCachedRedirect(const QUrl &originalUrl) const;
|
||||
void loadRedirectCache();
|
||||
void saveRedirectCache() const;
|
||||
void cleanStaleEntries();
|
||||
|
||||
private slots:
|
||||
void picDownloadFinished(QNetworkReply *reply);
|
||||
void picDownloadFailed();
|
||||
|
||||
void picDownloadChanged();
|
||||
void picsPathChanged();
|
||||
void setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArtWithPersonalPreference);
|
||||
public slots:
|
||||
void processLoadQueue();
|
||||
|
||||
signals:
|
||||
void startLoadQueue();
|
||||
void imageLoaded(CardInfoPtr card, const QImage &image);
|
||||
};
|
||||
|
||||
#endif // PICTURE_LOADER_WORKER_H
|
||||
@@ -1,64 +0,0 @@
|
||||
#ifndef PICTURE_TO_LOAD_H
|
||||
#define PICTURE_TO_LOAD_H
|
||||
|
||||
#include "../../../game/cards/card_database.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(PictureToLoadLog, "picture_loader.picture_to_load");
|
||||
|
||||
class PictureToLoad
|
||||
{
|
||||
private:
|
||||
class SetDownloadPriorityComparator
|
||||
{
|
||||
public:
|
||||
/*
|
||||
* Returns true if a has higher download priority than b
|
||||
* Enabled sets have priority over disabled sets
|
||||
* Both groups follows the user-defined order
|
||||
*/
|
||||
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
|
||||
{
|
||||
if (a->getEnabled()) {
|
||||
return !b->getEnabled() || a->getSortKey() < b->getSortKey();
|
||||
} else {
|
||||
return !b->getEnabled() && a->getSortKey() < b->getSortKey();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CardInfoPtr card;
|
||||
QList<CardSetPtr> sortedSets;
|
||||
QList<QString> urlTemplates;
|
||||
QList<QString> currentSetUrls;
|
||||
QString currentUrl;
|
||||
CardSetPtr currentSet;
|
||||
|
||||
public:
|
||||
explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr());
|
||||
|
||||
CardInfoPtr getCard() const
|
||||
{
|
||||
return card;
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
card.clear();
|
||||
}
|
||||
QString getCurrentUrl() const
|
||||
{
|
||||
return currentUrl;
|
||||
}
|
||||
CardSetPtr getCurrentSet() const
|
||||
{
|
||||
return currentSet;
|
||||
}
|
||||
QString getSetName() const;
|
||||
QString transformUrl(const QString &urlTemplate) const;
|
||||
bool nextSet();
|
||||
bool nextUrl();
|
||||
void populateSetUrls();
|
||||
};
|
||||
|
||||
#endif // PICTURE_TO_LOAD_H
|
||||
@@ -1,80 +0,0 @@
|
||||
#include "card_info_text_widget.h"
|
||||
|
||||
#include "../../../../game/cards/card_item.h"
|
||||
#include "../../../../game/game_specific_terms.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
|
||||
CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent), info(nullptr)
|
||||
{
|
||||
nameLabel = new QLabel;
|
||||
nameLabel->setOpenExternalLinks(false);
|
||||
nameLabel->setWordWrap(true);
|
||||
connect(nameLabel, SIGNAL(linkActivated(const QString &)), this, SIGNAL(linkActivated(const QString &)));
|
||||
|
||||
textLabel = new QTextEdit();
|
||||
textLabel->setReadOnly(true);
|
||||
|
||||
auto *grid = new QGridLayout(this);
|
||||
grid->addWidget(nameLabel, 0, 0);
|
||||
grid->addWidget(textLabel, 1, 0, -1, 2);
|
||||
grid->setRowStretch(1, 1);
|
||||
grid->setColumnStretch(1, 1);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void CardInfoTextWidget::setCard(CardInfoPtr card)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
nameLabel->setText("");
|
||||
textLabel->setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = "<table width=\"100%\" border=0 cellspacing=0 cellpadding=0>";
|
||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
||||
.arg(tr("Name:"), card->getName().toHtmlEscaped());
|
||||
|
||||
QStringList cardProps = card->getProperties();
|
||||
for (const QString &key : cardProps) {
|
||||
if (key.contains("-"))
|
||||
continue;
|
||||
QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":";
|
||||
text +=
|
||||
QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(keyText, card->getProperty(key).toHtmlEscaped());
|
||||
}
|
||||
|
||||
auto relatedCards = card->getAllRelatedCards();
|
||||
if (!relatedCards.empty()) {
|
||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>").arg(tr("Related cards:"));
|
||||
|
||||
for (auto *relatedCard : relatedCards) {
|
||||
QString tmp = relatedCard->getName().toHtmlEscaped();
|
||||
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
|
||||
}
|
||||
|
||||
text += "</td></tr>";
|
||||
}
|
||||
|
||||
text += "</table>";
|
||||
nameLabel->setText(text);
|
||||
textLabel->setText(card->getText());
|
||||
}
|
||||
|
||||
void CardInfoTextWidget::setInvalidCardName(const QString &cardName)
|
||||
{
|
||||
nameLabel->setText(tr("Unknown card:") + " " + cardName);
|
||||
textLabel->setText("");
|
||||
}
|
||||
|
||||
void CardInfoTextWidget::retranslateUi()
|
||||
{
|
||||
/*
|
||||
* There's no way we can really translate the text currently being rendered.
|
||||
* The best we can do is invalidate the current text.
|
||||
*/
|
||||
setInvalidCardName("");
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#include "settings_popup_widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFocusEvent>
|
||||
#include <QPainter>
|
||||
|
||||
SettingsPopupWidget::SettingsPopupWidget(QWidget *parent) : QWidget(parent, Qt::Popup | Qt::FramelessWindowHint)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
}
|
||||
|
||||
void SettingsPopupWidget::addSettingsWidget(QWidget *toAdd) const
|
||||
{
|
||||
layout->addWidget(toAdd);
|
||||
}
|
||||
|
||||
void SettingsPopupWidget::focusOutEvent(QFocusEvent *event)
|
||||
{
|
||||
if (!this->isAncestorOf(QApplication::focusWidget())) {
|
||||
close();
|
||||
}
|
||||
QWidget::focusOutEvent(event);
|
||||
}
|
||||
|
||||
void SettingsPopupWidget::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
emit aboutToClose();
|
||||
QWidget::closeEvent(event);
|
||||
}
|
||||
|
||||
void SettingsPopupWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setPen(Qt::gray);
|
||||
painter.drawRect(rect().adjusted(0, 0, -1, -1));
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
#include "deck_preview_tag_dialog.h"
|
||||
|
||||
#include "deck_preview_tag_item_widget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const QStringList &activeTags, QWidget *parent)
|
||||
: QDialog(parent), activeTags_(activeTags)
|
||||
{
|
||||
resize(400, 500);
|
||||
|
||||
QStringList defaultTags = {
|
||||
// Strategies
|
||||
"🏃️ Aggro",
|
||||
"🧙️ Control",
|
||||
"⚔️ Midrange",
|
||||
"🌀 Combo",
|
||||
"🪓 Mill",
|
||||
"🔒 Stax",
|
||||
"🗺️ Landfall",
|
||||
"🛡️ Pillowfort",
|
||||
"🌱 Ramp",
|
||||
"⚡ Storm",
|
||||
"💀 Aristocrats",
|
||||
"☠️ Reanimator",
|
||||
"👹 Sacrifice",
|
||||
"🔥 Burn",
|
||||
"🌟 Lifegain",
|
||||
"🔮 Spellslinger",
|
||||
"👥 Tokens",
|
||||
"🎭 Blink",
|
||||
"⏳ Time Manipulation",
|
||||
"🌍 Domain",
|
||||
"💫 Proliferate",
|
||||
"📜 Saga",
|
||||
"🎲 Chaos",
|
||||
"🪄 Auras",
|
||||
"🔫 Pingers",
|
||||
|
||||
// Themes
|
||||
"👑 Monarch",
|
||||
"🚀 Vehicles",
|
||||
"💉 Infect",
|
||||
"🩸 Madness",
|
||||
"🌀 Morph",
|
||||
|
||||
// Card Types
|
||||
"⚔️ Creature",
|
||||
"💎 Artifact",
|
||||
"🌔 Enchantment",
|
||||
"📖 Sorcery",
|
||||
"⚡ Instant",
|
||||
"🌌 Planeswalker",
|
||||
"🌏 Land",
|
||||
"🪄 Aura",
|
||||
|
||||
// Kindred Types
|
||||
"🐉 Kindred",
|
||||
"🧙 Humans",
|
||||
"⚔️ Soldiers",
|
||||
"🛡️ Knights",
|
||||
"🎻 Bards",
|
||||
"🧝 Elves",
|
||||
"🌲 Dryads",
|
||||
"😇 Angels",
|
||||
"🎩 Wizards",
|
||||
"🧛 Vampires",
|
||||
"🦴 Skeletons",
|
||||
"💀 Zombies",
|
||||
"👹 Demons",
|
||||
"👾 Eldrazi",
|
||||
"🐉 Dragons",
|
||||
"🐠 Merfolk",
|
||||
"🦁 Cats",
|
||||
"🐺 Wolves",
|
||||
"🐺 Werewolves",
|
||||
"🦇 Bats",
|
||||
"🐀 Rats",
|
||||
"🦅 Birds",
|
||||
"🦗 Insects",
|
||||
"🍄 Fungus",
|
||||
"🐚 Sea Creatures",
|
||||
"🐗 Boars",
|
||||
"🦊 Foxes",
|
||||
"🦄 Unicorns",
|
||||
"🐘 Elephants",
|
||||
"🐻 Bears",
|
||||
"🦏 Rhinos",
|
||||
"🦂 Scorpions",
|
||||
};
|
||||
|
||||
// Merge knownTags with defaultTags, ensuring no duplicates
|
||||
QStringList combinedTags = defaultTags + knownTags + activeTags;
|
||||
combinedTags.removeDuplicates();
|
||||
|
||||
// Main layout
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
// Filter bar
|
||||
filterInput_ = new QLineEdit(this);
|
||||
mainLayout->addWidget(filterInput_);
|
||||
|
||||
connect(filterInput_, &QLineEdit::textChanged, this, &DeckPreviewTagDialog::filterTags);
|
||||
|
||||
// Instruction label
|
||||
instructionLabel = new QLabel(this);
|
||||
instructionLabel->setWordWrap(true);
|
||||
mainLayout->addWidget(instructionLabel);
|
||||
|
||||
// Tag list view
|
||||
tagListView_ = new QListWidget(this);
|
||||
mainLayout->addWidget(tagListView_);
|
||||
|
||||
// Populate combined tags
|
||||
for (const auto &tag : combinedTags) {
|
||||
auto *item = new QListWidgetItem(tagListView_);
|
||||
auto *tagWidget = new DeckPreviewTagItemWidget(tag, activeTags.contains(tag), this);
|
||||
tagListView_->addItem(item);
|
||||
tagListView_->setItemWidget(item, tagWidget);
|
||||
|
||||
connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged);
|
||||
}
|
||||
|
||||
// Add tag input layout
|
||||
auto *addTagLayout = new QHBoxLayout();
|
||||
newTagInput_ = new QLineEdit(this);
|
||||
addTagButton_ = new QPushButton(this);
|
||||
addTagLayout->addWidget(newTagInput_);
|
||||
addTagLayout->addWidget(addTagButton_);
|
||||
mainLayout->addLayout(addTagLayout);
|
||||
|
||||
connect(addTagButton_, &QPushButton::clicked, this, &DeckPreviewTagDialog::addTag);
|
||||
|
||||
// OK and Cancel buttons
|
||||
auto *buttonLayout = new QHBoxLayout();
|
||||
okButton = new QPushButton(this);
|
||||
cancelButton = new QPushButton(this);
|
||||
buttonLayout->addStretch();
|
||||
buttonLayout->addWidget(okButton);
|
||||
buttonLayout->addWidget(cancelButton);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
connect(okButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::accept);
|
||||
connect(cancelButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::reject);
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckPreviewTagDialog::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Deck Tags Manager"));
|
||||
instructionLabel->setText(tr("Manage your deck tags. Check or uncheck tags as needed, or add new ones:"));
|
||||
newTagInput_->setPlaceholderText(tr("Add a new tag (e.g., Aggro️)"));
|
||||
addTagButton_->setText(tr("Add Tag"));
|
||||
filterInput_->setPlaceholderText(tr("Filter tags..."));
|
||||
okButton->setText(tr("OK"));
|
||||
cancelButton->setText(tr("Cancel"));
|
||||
}
|
||||
|
||||
QStringList DeckPreviewTagDialog::getActiveTags() const
|
||||
{
|
||||
return activeTags_;
|
||||
}
|
||||
|
||||
void DeckPreviewTagDialog::addTag()
|
||||
{
|
||||
QString newTag = newTagInput_->text().trimmed();
|
||||
if (newTag.isEmpty()) {
|
||||
QMessageBox::warning(this, tr("Invalid Input"), tr("Tag name cannot be empty!"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent duplicate tags
|
||||
for (int i = 0; i < tagListView_->count(); ++i) {
|
||||
auto *item = tagListView_->item(i);
|
||||
auto *tagWidget = qobject_cast<DeckPreviewTagItemWidget *>(tagListView_->itemWidget(item));
|
||||
if (tagWidget && tagWidget->checkBox()->text() == newTag) {
|
||||
QMessageBox::warning(this, tr("Duplicate Tag"), tr("This tag already exists."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new tag
|
||||
auto *item = new QListWidgetItem(tagListView_);
|
||||
auto *tagWidget = new DeckPreviewTagItemWidget(newTag, true, this);
|
||||
tagListView_->addItem(item);
|
||||
tagListView_->setItemWidget(item, tagWidget);
|
||||
activeTags_.append(newTag);
|
||||
|
||||
connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged);
|
||||
|
||||
// Clear the input field
|
||||
newTagInput_->clear();
|
||||
}
|
||||
|
||||
void DeckPreviewTagDialog::filterTags(const QString &text)
|
||||
{
|
||||
for (int i = 0; i < tagListView_->count(); ++i) {
|
||||
auto *item = tagListView_->item(i);
|
||||
auto *tagWidget = qobject_cast<DeckPreviewTagItemWidget *>(tagListView_->itemWidget(item));
|
||||
if (tagWidget) {
|
||||
bool matches = tagWidget->checkBox()->text().contains(text, Qt::CaseInsensitive);
|
||||
item->setHidden(!matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeckPreviewTagDialog::onCheckboxStateChanged()
|
||||
{
|
||||
activeTags_.clear();
|
||||
for (int i = 0; i < tagListView_->count(); ++i) {
|
||||
auto *item = tagListView_->item(i);
|
||||
auto *tagWidget = qobject_cast<DeckPreviewTagItemWidget *>(tagListView_->itemWidget(item));
|
||||
if (tagWidget && tagWidget->checkBox()->isChecked()) {
|
||||
activeTags_.append(tagWidget->checkBox()->text());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef COCKATRICE_SETTINGS_CARD_PREFERENCE_PROVIDER_H
|
||||
#define COCKATRICE_SETTINGS_CARD_PREFERENCE_PROVIDER_H
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
|
||||
#include <libcockatrice/interfaces/interface_card_preference_provider.h>
|
||||
|
||||
class SettingsCardPreferenceProvider : public ICardPreferenceProvider
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] QString getCardPreferenceOverride(const QString &cardName) const override
|
||||
{
|
||||
return SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardName);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool getIncludeRebalancedCards() const override
|
||||
{
|
||||
return SettingsCache::instance().getIncludeRebalancedCards();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_SETTINGS_CARD_PREFERENCE_PROVIDER_H
|
||||
@@ -1,578 +0,0 @@
|
||||
#include "deck_list_model.h"
|
||||
|
||||
#include "../game/cards/card_database_manager.h"
|
||||
#include "../main.h"
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "deck_loader.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QFont>
|
||||
#include <QPrinter>
|
||||
#include <QProgressDialog>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
#include <QTextStream>
|
||||
#include <QTextTable>
|
||||
|
||||
DeckListModel::DeckListModel(QObject *parent)
|
||||
: QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder)
|
||||
{
|
||||
deckList = new DeckLoader;
|
||||
deckList->setParent(this);
|
||||
connect(deckList, &DeckLoader::deckLoaded, this, &DeckListModel::rebuildTree);
|
||||
connect(deckList, &DeckLoader::deckHashChanged, this, &DeckListModel::deckHashChanged);
|
||||
root = new InnerDecklistNode;
|
||||
}
|
||||
|
||||
DeckListModel::~DeckListModel()
|
||||
{
|
||||
delete root;
|
||||
}
|
||||
|
||||
void DeckListModel::rebuildTree()
|
||||
{
|
||||
beginResetModel();
|
||||
root->clearTree();
|
||||
|
||||
InnerDecklistNode *listRoot = deckList->getRoot();
|
||||
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
auto *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
auto *node = new InnerDecklistNode(currentZone->getName(), root);
|
||||
|
||||
for (int j = 0; j < currentZone->size(); j++) {
|
||||
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
||||
|
||||
// TODO: better sanity checking
|
||||
if (currentCard == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName());
|
||||
QString cardType = info ? info->getMainCardType() : "unknown";
|
||||
|
||||
auto *cardTypeNode = dynamic_cast<InnerDecklistNode *>(node->findChild(cardType));
|
||||
|
||||
if (!cardTypeNode) {
|
||||
cardTypeNode = new InnerDecklistNode(cardType, node);
|
||||
}
|
||||
|
||||
new DecklistModelCardNode(currentCard, cardTypeNode);
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
int DeckListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// debugIndexInfo("rowCount", parent);
|
||||
auto *node = getNode<InnerDecklistNode *>(parent);
|
||||
if (node) {
|
||||
return node->size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int DeckListModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
QVariant DeckListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
// debugIndexInfo("data", index);
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.column() >= columnCount()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto *temp = static_cast<AbstractDecklistNode *>(index.internalPointer());
|
||||
auto *card = dynamic_cast<DecklistModelCardNode *>(temp);
|
||||
if (card == nullptr) {
|
||||
const auto *node = dynamic_cast<InnerDecklistNode *>(temp);
|
||||
switch (role) {
|
||||
case Qt::FontRole: {
|
||||
QFont f;
|
||||
f.setBold(true);
|
||||
return f;
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole: {
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return node->recursiveCount(true);
|
||||
case 1: {
|
||||
if (role == Qt::DisplayRole)
|
||||
return node->getVisibleName();
|
||||
return node->getName();
|
||||
}
|
||||
case 2: {
|
||||
return node->getCardSetShortName();
|
||||
}
|
||||
case 3: {
|
||||
return node->getCardCollectorNumber();
|
||||
}
|
||||
case 4: {
|
||||
return node->getCardProviderId();
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
case Qt::BackgroundRole: {
|
||||
int color = 90 + 60 * node->depth();
|
||||
return QBrush(QColor(color, 255, color));
|
||||
}
|
||||
case Qt::ForegroundRole: {
|
||||
return QBrush(QColor(0, 0, 0));
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole: {
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return card->getNumber();
|
||||
case 1:
|
||||
return card->getName();
|
||||
case 2:
|
||||
return card->getCardSetShortName();
|
||||
case 3:
|
||||
return card->getCardCollectorNumber();
|
||||
case 4:
|
||||
return card->getCardProviderId();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
case Qt::BackgroundRole: {
|
||||
int color = 255 - (index.row() % 2) * 30;
|
||||
return QBrush(QColor(color, color, color));
|
||||
}
|
||||
case Qt::ForegroundRole: {
|
||||
return QBrush(QColor(0, 0, 0));
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant DeckListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (section >= columnCount()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (section) {
|
||||
case 0:
|
||||
return tr("Count");
|
||||
case 1:
|
||||
return tr("Card");
|
||||
case 2:
|
||||
return tr("Set");
|
||||
case 3:
|
||||
return tr("Number");
|
||||
case 4:
|
||||
return tr("Provider ID");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
// debugIndexInfo("index", parent);
|
||||
if (!hasIndex(row, column, parent)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto *parentNode = getNode<InnerDecklistNode *>(parent);
|
||||
return row >= parentNode->size() ? QModelIndex() : createIndex(row, column, parentNode->at(row));
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::parent(const QModelIndex &ind) const
|
||||
{
|
||||
if (!ind.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return nodeToIndex(static_cast<AbstractDecklistNode *>(ind.internalPointer())->getParent());
|
||||
}
|
||||
|
||||
Qt::ItemFlags DeckListModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
Qt::ItemFlags result = Qt::ItemIsEnabled;
|
||||
result |= Qt::ItemIsSelectable;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeckListModel::emitRecursiveUpdates(const QModelIndex &index)
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit dataChanged(index, index);
|
||||
emitRecursiveUpdates(index.parent());
|
||||
}
|
||||
|
||||
bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, const int role)
|
||||
{
|
||||
auto *node = getNode<DecklistModelCardNode *>(index);
|
||||
if (!node || (role != Qt::EditRole)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
node->setNumber(value.toInt());
|
||||
break;
|
||||
case 1:
|
||||
node->setName(value.toString());
|
||||
break;
|
||||
case 2:
|
||||
node->setCardSetShortName(value.toString());
|
||||
break;
|
||||
case 3:
|
||||
node->setCardCollectorNumber(value.toString());
|
||||
break;
|
||||
case 4:
|
||||
node->setCardProviderId(value.toString());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
emitRecursiveUpdates(index);
|
||||
deckList->refreshDeckHash();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
auto *node = getNode<InnerDecklistNode *>(parent);
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row + count > node->size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
for (int i = 0; i < count; i++) {
|
||||
AbstractDecklistNode *toDelete = node->takeAt(row);
|
||||
if (auto *temp = dynamic_cast<DecklistModelCardNode *>(toDelete)) {
|
||||
deckList->deleteNode(temp->getDataNode());
|
||||
}
|
||||
delete toDelete;
|
||||
}
|
||||
endRemoveRows();
|
||||
|
||||
if (node->empty() && (node != root)) {
|
||||
removeRows(parent.row(), 1, parent.parent());
|
||||
} else {
|
||||
emitRecursiveUpdates(parent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
InnerDecklistNode *DeckListModel::createNodeIfNeeded(const QString &name, InnerDecklistNode *parent)
|
||||
{
|
||||
auto *newNode = dynamic_cast<InnerDecklistNode *>(parent->findChild(name));
|
||||
if (!newNode) {
|
||||
beginInsertRows(nodeToIndex(parent), parent->size(), parent->size());
|
||||
newNode = new InnerDecklistNode(name, parent);
|
||||
endInsertRows();
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId,
|
||||
const QString &cardNumber) const
|
||||
{
|
||||
InnerDecklistNode *zoneNode, *typeNode;
|
||||
CardInfoPtr info;
|
||||
QString cardType;
|
||||
|
||||
zoneNode = dynamic_cast<InnerDecklistNode *>(root->findChild(zoneName));
|
||||
if (!zoneNode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
info = CardDatabaseManager::getInstance()->getCard(cardName);
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cardType = info->getMainCardType();
|
||||
typeNode = dynamic_cast<InnerDecklistNode *>(zoneNode->findChild(cardType));
|
||||
if (!typeNode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return dynamic_cast<DecklistModelCardNode *>(
|
||||
typeNode->findCardChildByNameProviderIdAndNumber(cardName, providerId, cardNumber));
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::findCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId,
|
||||
const QString &cardNumber) const
|
||||
{
|
||||
DecklistModelCardNode *cardNode;
|
||||
|
||||
cardNode = findCardNode(cardName, zoneName, providerId, cardNumber);
|
||||
if (!cardNode) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return nodeToIndex(cardNode);
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::addPreferredPrintingCard(const QString &cardName, const QString &zoneName, bool abAddAnyway)
|
||||
{
|
||||
return addCard(cardName, CardDatabaseManager::getInstance()->getPreferredSetForCard(cardName), zoneName,
|
||||
abAddAnyway);
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::addCard(const QString &cardName,
|
||||
const CardInfoPerSet &cardInfoSet,
|
||||
const QString &zoneName,
|
||||
bool abAddAnyway)
|
||||
{
|
||||
CardInfoPtr cardInfo =
|
||||
CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, cardInfoSet.getProperty("uuid"));
|
||||
|
||||
if (cardInfo == nullptr) {
|
||||
if (abAddAnyway) {
|
||||
// We need to keep this card added no matter what
|
||||
// This is usually called from tab_deck_editor
|
||||
// So we'll create a new CardInfo with the name
|
||||
// and default values for all fields
|
||||
cardInfo = CardInfo::newInstance(cardName);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
InnerDecklistNode *zoneNode = createNodeIfNeeded(zoneName, root);
|
||||
|
||||
const QString cardType = cardInfo->getMainCardType();
|
||||
InnerDecklistNode *cardTypeNode = createNodeIfNeeded(cardType, zoneNode);
|
||||
|
||||
const QModelIndex parentIndex = nodeToIndex(cardTypeNode);
|
||||
auto *cardNode = dynamic_cast<DecklistModelCardNode *>(cardTypeNode->findCardChildByNameProviderIdAndNumber(
|
||||
cardName, cardInfoSet.getProperty("uuid"), cardInfoSet.getProperty("num")));
|
||||
const auto cardSetName = cardInfoSet.getPtr().isNull() ? "" : cardInfoSet.getPtr()->getCorrectedShortName();
|
||||
|
||||
if (!cardNode) {
|
||||
auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, cardSetName,
|
||||
cardInfoSet.getProperty("num"), cardInfoSet.getProperty("uuid"));
|
||||
beginInsertRows(parentIndex, static_cast<int>(cardTypeNode->size()), static_cast<int>(cardTypeNode->size()));
|
||||
cardNode = new DecklistModelCardNode(decklistCard, cardTypeNode);
|
||||
endInsertRows();
|
||||
} else {
|
||||
cardNode->setNumber(cardNode->getNumber() + 1);
|
||||
cardNode->setCardSetShortName(cardSetName);
|
||||
cardNode->setCardCollectorNumber(cardInfoSet.getProperty("num"));
|
||||
cardNode->setCardProviderId(cardInfoSet.getProperty("uuid"));
|
||||
deckList->refreshDeckHash();
|
||||
}
|
||||
sort(lastKnownColumn, lastKnownOrder);
|
||||
emitRecursiveUpdates(parentIndex);
|
||||
return nodeToIndex(cardNode);
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::nodeToIndex(AbstractDecklistNode *node) const
|
||||
{
|
||||
if (node == nullptr || node == root) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return createIndex(node->getParent()->indexOf(node), 0, node);
|
||||
}
|
||||
|
||||
void DeckListModel::sortHelper(InnerDecklistNode *node, Qt::SortOrder order)
|
||||
{
|
||||
// Sort children of node and save the information needed to
|
||||
// update the list of persistent indexes.
|
||||
QVector<QPair<int, int>> sortResult = node->sort(order);
|
||||
|
||||
QModelIndexList from, to;
|
||||
int columns = columnCount();
|
||||
for (int i = sortResult.size() - 1; i >= 0; --i) {
|
||||
const int fromRow = sortResult[i].first;
|
||||
const int toRow = sortResult[i].second;
|
||||
AbstractDecklistNode *temp = node->at(toRow);
|
||||
for (int j = 0; j < columns; ++j) {
|
||||
from << createIndex(fromRow, j, temp);
|
||||
to << createIndex(toRow, j, temp);
|
||||
}
|
||||
}
|
||||
changePersistentIndexList(from, to);
|
||||
|
||||
// Recursion
|
||||
for (int i = node->size() - 1; i >= 0; --i) {
|
||||
auto *subNode = dynamic_cast<InnerDecklistNode *>(node->at(i));
|
||||
if (subNode) {
|
||||
sortHelper(subNode, order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeckListModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
lastKnownColumn = column;
|
||||
lastKnownOrder = order;
|
||||
|
||||
emit layoutAboutToBeChanged();
|
||||
DeckSortMethod sortMethod;
|
||||
switch (column) {
|
||||
case 0:
|
||||
sortMethod = ByNumber;
|
||||
break;
|
||||
case 1:
|
||||
sortMethod = ByName;
|
||||
break;
|
||||
default:
|
||||
sortMethod = ByName;
|
||||
}
|
||||
|
||||
root->setSortMethod(sortMethod);
|
||||
sortHelper(root, order);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void DeckListModel::cleanList()
|
||||
{
|
||||
setDeckList(new DeckLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param _deck The deck. Takes ownership of the object
|
||||
*/
|
||||
void DeckListModel::setDeckList(DeckLoader *_deck)
|
||||
{
|
||||
deckList->deleteLater();
|
||||
deckList = _deck;
|
||||
deckList->setParent(this);
|
||||
connect(deckList, &DeckLoader::deckLoaded, this, &DeckListModel::rebuildTree);
|
||||
connect(deckList, &DeckLoader::deckHashChanged, this, &DeckListModel::deckHashChanged);
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
void DeckListModel::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node)
|
||||
{
|
||||
const int totalColumns = 2;
|
||||
|
||||
if (node->height() == 1) {
|
||||
QTextBlockFormat blockFormat;
|
||||
QTextCharFormat charFormat;
|
||||
charFormat.setFontPointSize(11);
|
||||
charFormat.setFontWeight(QFont::Bold);
|
||||
cursor->insertBlock(blockFormat, charFormat);
|
||||
|
||||
QTextTableFormat tableFormat;
|
||||
tableFormat.setCellPadding(0);
|
||||
tableFormat.setCellSpacing(0);
|
||||
tableFormat.setBorder(0);
|
||||
QTextTable *table = cursor->insertTable(node->size() + 1, totalColumns, tableFormat);
|
||||
for (int i = 0; i < node->size(); i++) {
|
||||
auto *card = dynamic_cast<AbstractDecklistCardNode *>(node->at(i));
|
||||
|
||||
QTextCharFormat cellCharFormat;
|
||||
cellCharFormat.setFontPointSize(9);
|
||||
|
||||
QTextTableCell cell = table->cellAt(i, 0);
|
||||
cell.setFormat(cellCharFormat);
|
||||
QTextCursor cellCursor = cell.firstCursorPosition();
|
||||
cellCursor.insertText(QString("%1 ").arg(card->getNumber()));
|
||||
|
||||
cell = table->cellAt(i, 1);
|
||||
cell.setFormat(cellCharFormat);
|
||||
cellCursor = cell.firstCursorPosition();
|
||||
cellCursor.insertText(card->getName());
|
||||
}
|
||||
} else if (node->height() == 2) {
|
||||
QTextBlockFormat blockFormat;
|
||||
QTextCharFormat charFormat;
|
||||
charFormat.setFontPointSize(14);
|
||||
charFormat.setFontWeight(QFont::Bold);
|
||||
|
||||
cursor->insertBlock(blockFormat, charFormat);
|
||||
|
||||
QTextTableFormat tableFormat;
|
||||
tableFormat.setCellPadding(10);
|
||||
tableFormat.setCellSpacing(0);
|
||||
tableFormat.setBorder(0);
|
||||
QVector<QTextLength> constraints;
|
||||
for (int i = 0; i < totalColumns; i++) {
|
||||
constraints << QTextLength(QTextLength::PercentageLength, 100.0 / totalColumns);
|
||||
}
|
||||
tableFormat.setColumnWidthConstraints(constraints);
|
||||
|
||||
QTextTable *table = cursor->insertTable(1, totalColumns, tableFormat);
|
||||
for (int i = 0; i < node->size(); i++) {
|
||||
QTextCursor cellCursor = table->cellAt(0, (i * totalColumns) / node->size()).lastCursorPosition();
|
||||
printDeckListNode(&cellCursor, dynamic_cast<InnerDecklistNode *>(node->at(i)));
|
||||
}
|
||||
}
|
||||
|
||||
cursor->movePosition(QTextCursor::End);
|
||||
}
|
||||
|
||||
void DeckListModel::printDeckList(QPrinter *printer)
|
||||
{
|
||||
QTextDocument doc;
|
||||
|
||||
QFont font("Serif");
|
||||
font.setStyleHint(QFont::Serif);
|
||||
doc.setDefaultFont(font);
|
||||
|
||||
QTextCursor cursor(&doc);
|
||||
|
||||
QTextBlockFormat headerBlockFormat;
|
||||
QTextCharFormat headerCharFormat;
|
||||
headerCharFormat.setFontPointSize(16);
|
||||
headerCharFormat.setFontWeight(QFont::Bold);
|
||||
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
cursor.insertText(deckList->getName());
|
||||
|
||||
headerCharFormat.setFontPointSize(12);
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
cursor.insertText(deckList->getComments());
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
cursor.insertHtml("<br><img src=theme:hr.jpg>");
|
||||
// cursor.insertHtml("<hr>");
|
||||
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
||||
|
||||
printDeckListNode(&cursor, dynamic_cast<InnerDecklistNode *>(root->at(i)));
|
||||
}
|
||||
|
||||
doc.print(printer);
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
#ifndef DECKLISTMODEL_H
|
||||
#define DECKLISTMODEL_H
|
||||
|
||||
#include "../game/cards/card_database.h"
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QList>
|
||||
|
||||
class DeckLoader;
|
||||
class CardDatabase;
|
||||
class QPrinter;
|
||||
class QTextCursor;
|
||||
|
||||
class DecklistModelCardNode : public AbstractDecklistCardNode
|
||||
{
|
||||
private:
|
||||
DecklistCardNode *dataNode;
|
||||
|
||||
public:
|
||||
DecklistModelCardNode(DecklistCardNode *_dataNode, InnerDecklistNode *_parent)
|
||||
: AbstractDecklistCardNode(_parent), dataNode(_dataNode)
|
||||
{
|
||||
}
|
||||
int getNumber() const override
|
||||
{
|
||||
return dataNode->getNumber();
|
||||
}
|
||||
void setNumber(int _number) override
|
||||
{
|
||||
dataNode->setNumber(_number);
|
||||
}
|
||||
QString getName() const override
|
||||
{
|
||||
return dataNode->getName();
|
||||
}
|
||||
void setName(const QString &_name) override
|
||||
{
|
||||
dataNode->setName(_name);
|
||||
}
|
||||
QString getCardProviderId() const override
|
||||
{
|
||||
return dataNode->getCardProviderId();
|
||||
}
|
||||
void setCardProviderId(const QString &_cardProviderId) override
|
||||
{
|
||||
dataNode->setCardProviderId(_cardProviderId);
|
||||
}
|
||||
QString getCardSetShortName() const override
|
||||
{
|
||||
return dataNode->getCardSetShortName();
|
||||
}
|
||||
void setCardSetShortName(const QString &_cardSetShortName) override
|
||||
{
|
||||
dataNode->setCardSetShortName(_cardSetShortName);
|
||||
}
|
||||
QString getCardCollectorNumber() const override
|
||||
{
|
||||
return dataNode->getCardCollectorNumber();
|
||||
}
|
||||
void setCardCollectorNumber(const QString &_cardSetNumber) override
|
||||
{
|
||||
dataNode->setCardCollectorNumber(_cardSetNumber);
|
||||
}
|
||||
DecklistCardNode *getDataNode() const
|
||||
{
|
||||
return dataNode;
|
||||
}
|
||||
[[nodiscard]] bool isDeckHeader() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class DeckListModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void rebuildTree();
|
||||
public slots:
|
||||
void printDeckList(QPrinter *printer);
|
||||
signals:
|
||||
void deckHashChanged();
|
||||
|
||||
public:
|
||||
explicit DeckListModel(QObject *parent = nullptr);
|
||||
~DeckListModel() override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override;
|
||||
QModelIndex findCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId = "",
|
||||
const QString &cardNumber = "") const;
|
||||
QModelIndex addPreferredPrintingCard(const QString &cardName, const QString &zoneName, bool abAddAnyway);
|
||||
QModelIndex addCard(const ::QString &cardName,
|
||||
const CardInfoPerSet &cardInfoSet,
|
||||
const QString &zoneName,
|
||||
bool abAddAnyway = false);
|
||||
void sort(int column, Qt::SortOrder order) override;
|
||||
void cleanList();
|
||||
DeckLoader *getDeckList() const
|
||||
{
|
||||
return deckList;
|
||||
}
|
||||
void setDeckList(DeckLoader *_deck);
|
||||
|
||||
private:
|
||||
DeckLoader *deckList;
|
||||
InnerDecklistNode *root;
|
||||
int lastKnownColumn;
|
||||
Qt::SortOrder lastKnownOrder;
|
||||
InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent);
|
||||
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
||||
DecklistModelCardNode *findCardNode(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId = "",
|
||||
const QString &cardNumber = "") const;
|
||||
void emitRecursiveUpdates(const QModelIndex &index);
|
||||
void sortHelper(InnerDecklistNode *node, Qt::SortOrder order);
|
||||
|
||||
void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node);
|
||||
|
||||
template <typename T> T getNode(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return dynamic_cast<T>(root);
|
||||
return dynamic_cast<T>(static_cast<AbstractDecklistNode *>(index.internalPointer()));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,105 +0,0 @@
|
||||
#ifndef DECK_LOADER_H
|
||||
#define DECK_LOADER_H
|
||||
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader")
|
||||
|
||||
class DeckLoader : public DeckList
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void deckLoaded();
|
||||
void loadFinished(bool success);
|
||||
|
||||
public:
|
||||
enum FileFormat
|
||||
{
|
||||
PlainTextFormat,
|
||||
CockatriceFormat
|
||||
};
|
||||
|
||||
/**
|
||||
* Supported file extensions for decklist files
|
||||
*/
|
||||
static const QStringList ACCEPTED_FILE_EXTENSIONS;
|
||||
|
||||
/**
|
||||
* For use with `QFileDialog::setNameFilters`
|
||||
*/
|
||||
static const QStringList FILE_NAME_FILTERS;
|
||||
|
||||
enum DecklistWebsite
|
||||
{
|
||||
DecklistOrg,
|
||||
DecklistXyz
|
||||
};
|
||||
|
||||
private:
|
||||
QString lastFileName;
|
||||
FileFormat lastFileFormat;
|
||||
int lastRemoteDeckId;
|
||||
|
||||
public:
|
||||
DeckLoader();
|
||||
explicit DeckLoader(const QString &nativeString);
|
||||
explicit DeckLoader(const DeckList &other);
|
||||
DeckLoader(const DeckLoader &other);
|
||||
const QString &getLastFileName() const
|
||||
{
|
||||
return lastFileName;
|
||||
}
|
||||
void setLastFileName(const QString &_lastFileName)
|
||||
{
|
||||
lastFileName = _lastFileName;
|
||||
}
|
||||
FileFormat getLastFileFormat() const
|
||||
{
|
||||
return lastFileFormat;
|
||||
}
|
||||
int getLastRemoteDeckId() const
|
||||
{
|
||||
return lastRemoteDeckId;
|
||||
}
|
||||
|
||||
bool hasNotBeenLoaded() const
|
||||
{
|
||||
return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1;
|
||||
}
|
||||
|
||||
void clearSetNamesAndNumbers();
|
||||
static FileFormat getFormatFromName(const QString &fileName);
|
||||
|
||||
bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false);
|
||||
bool loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest);
|
||||
bool loadFromRemote(const QString &nativeString, int remoteDeckId);
|
||||
bool saveToFile(const QString &fileName, FileFormat fmt);
|
||||
bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt);
|
||||
QString exportDeckToDecklist(DecklistWebsite website);
|
||||
|
||||
void resolveSetNameAndNumberToProviderID();
|
||||
|
||||
void saveToClipboard(bool addComments = true, bool addSetNameAndNumber = true) const;
|
||||
|
||||
// overload
|
||||
bool saveToStream_Plain(QTextStream &out, bool addComments = true, bool addSetNameAndNumber = true) const;
|
||||
bool convertToCockatriceFormat(QString fileName);
|
||||
|
||||
protected:
|
||||
void saveToStream_DeckHeader(QTextStream &out) const;
|
||||
void saveToStream_DeckZone(QTextStream &out,
|
||||
const InnerDecklistNode *zoneNode,
|
||||
bool addComments = true,
|
||||
bool addSetNameAndNumber = true) const;
|
||||
void saveToStream_DeckZoneCards(QTextStream &out,
|
||||
const InnerDecklistNode *zoneNode,
|
||||
QList<DecklistCardNode *> cards,
|
||||
bool addComments = true,
|
||||
bool addSetNameAndNumber = true) const;
|
||||
[[nodiscard]] QString getCardZoneFromName(QString cardName, QString currentZoneName) override;
|
||||
[[nodiscard]] QString getCompleteCardName(const QString &cardName) const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,58 +0,0 @@
|
||||
#include "dlg_view_log.h"
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "../utility/logger.h"
|
||||
|
||||
#include <QPlainTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DlgViewLog::DlgViewLog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
|
||||
logArea = new QPlainTextEdit;
|
||||
logArea->setReadOnly(true);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(logArea);
|
||||
|
||||
coClearLog = new QCheckBox;
|
||||
coClearLog->setText(tr("Clear log when closing"));
|
||||
coClearLog->setChecked(SettingsCache::instance().servers().getClearDebugLogStatus(false));
|
||||
connect(coClearLog, SIGNAL(toggled(bool)), this, SLOT(actCheckBoxChanged(bool)));
|
||||
mainLayout->addWidget(coClearLog);
|
||||
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Debug Log"));
|
||||
resize(800, 500);
|
||||
|
||||
loadInitialLogBuffer();
|
||||
connect(&Logger::getInstance(), SIGNAL(logEntryAdded(QString)), this, SLOT(logEntryAdded(QString)));
|
||||
}
|
||||
|
||||
void DlgViewLog::actCheckBoxChanged(bool abNewValue)
|
||||
{
|
||||
SettingsCache::instance().servers().setClearDebugLogStatus(abNewValue);
|
||||
}
|
||||
|
||||
void DlgViewLog::loadInitialLogBuffer()
|
||||
{
|
||||
QList<QString> logBuffer = Logger::getInstance().getLogBuffer();
|
||||
for (const QString &message : logBuffer)
|
||||
logEntryAdded(message);
|
||||
}
|
||||
|
||||
void DlgViewLog::logEntryAdded(QString message)
|
||||
{
|
||||
logArea->appendPlainText(message);
|
||||
}
|
||||
|
||||
void DlgViewLog::closeEvent(QCloseEvent * /* event */)
|
||||
{
|
||||
if (coClearLog->isChecked()) {
|
||||
logArea->clear();
|
||||
|
||||
logArea->appendPlainText(Logger::getInstance().getClientVersion());
|
||||
logArea->appendPlainText(Logger::getInstance().getSystemArchitecture());
|
||||
}
|
||||
}
|
||||
194
cockatrice/src/filters/deck_filter_string.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "deck_filter_string.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/filters/filter_string.h>
|
||||
#include <libcockatrice/utility/peglib.h>
|
||||
|
||||
static peg::parser search(R"(
|
||||
Start <- QueryPartList
|
||||
~ws <- [ ]+
|
||||
QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
|
||||
|
||||
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
|
||||
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
|
||||
|
||||
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery
|
||||
|
||||
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
|
||||
|
||||
DeckContentQuery <- CardSearch NumericExpression?
|
||||
CardSearch <- '[[' CardFilterString ']]'
|
||||
CardFilterString <- (!']]'.)*
|
||||
|
||||
DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String
|
||||
FileNameQuery <- [Ff] ('ile' 'name'?)? [:] String
|
||||
PathQuery <- [Pp] 'ath'? [:] String
|
||||
|
||||
GenericQuery <- String
|
||||
|
||||
NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["].
|
||||
NonSingleQuoteUnlessEscaped <- "\\\'". / !['].
|
||||
UnescapedStringListPart <- !['":<>=! ].
|
||||
SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)*
|
||||
|
||||
String <- SingleApostropheString / UnescapedStringListPart+ / ["] <NonDoubleQuoteUnlessEscaped*> ["] / ['] <NonSingleQuoteUnlessEscaped*> [']
|
||||
|
||||
NumericExpression <- NumericOperator ws? NumericValue
|
||||
NumericOperator <- [=:] / <[><!][=]?>
|
||||
NumericValue <- [0-9]+
|
||||
)");
|
||||
|
||||
static std::once_flag init;
|
||||
|
||||
static void setupParserRules()
|
||||
{
|
||||
// plumbing
|
||||
auto passthru = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
return !sv.empty() ? std::any_cast<DeckFilter>(sv[0]) : nullptr;
|
||||
};
|
||||
|
||||
search["Start"] = passthru;
|
||||
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
|
||||
auto matchesFilter = [&deck, &info](const std::any &query) {
|
||||
return std::any_cast<DeckFilter>(query)(deck, info);
|
||||
};
|
||||
return std::all_of(sv.begin(), sv.end(), matchesFilter);
|
||||
};
|
||||
};
|
||||
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
|
||||
auto matchesFilter = [&deck, &info](const std::any &query) {
|
||||
return std::any_cast<DeckFilter>(query)(deck, info);
|
||||
};
|
||||
return std::any_of(sv.begin(), sv.end(), matchesFilter);
|
||||
};
|
||||
};
|
||||
search["SomewhatComplexQueryPart"] = passthru;
|
||||
search["QueryPart"] = passthru;
|
||||
search["NotQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
const auto dependent = std::any_cast<DeckFilter>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) -> bool {
|
||||
return !dependent(deck, info);
|
||||
};
|
||||
};
|
||||
|
||||
search["String"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
if (sv.choice() == 0) {
|
||||
return QString::fromStdString(std::string(sv.sv()));
|
||||
}
|
||||
|
||||
return QString::fromStdString(std::string(sv.token(0)));
|
||||
};
|
||||
|
||||
search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
|
||||
const auto arg = std::any_cast<int>(sv[1]);
|
||||
const auto op = std::any_cast<QString>(sv[0]);
|
||||
|
||||
if (op == ">")
|
||||
return [=](const int s) { return s > arg; };
|
||||
if (op == ">=")
|
||||
return [=](const int s) { return s >= arg; };
|
||||
if (op == "<")
|
||||
return [=](const int s) { return s < arg; };
|
||||
if (op == "<=")
|
||||
return [=](const int s) { return s <= arg; };
|
||||
if (op == "=")
|
||||
return [=](const int s) { return s == arg; };
|
||||
if (op == ":")
|
||||
return [=](const int s) { return s == arg; };
|
||||
if (op == "!=")
|
||||
return [=](const int s) { return s != arg; };
|
||||
return [](int) { return false; };
|
||||
};
|
||||
|
||||
search["NumericValue"] = [](const peg::SemanticValues &sv) -> int {
|
||||
return QString::fromStdString(std::string(sv.sv())).toInt();
|
||||
};
|
||||
|
||||
search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
return QString::fromStdString(std::string(sv.sv()));
|
||||
};
|
||||
|
||||
// actual functionality
|
||||
search["DeckContentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto cardFilter = FilterString(std::any_cast<QString>(sv[0]));
|
||||
auto numberMatcher = sv.size() > 1 ? std::any_cast<NumberMatcher>(sv[1]) : [](int count) { return count > 0; };
|
||||
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool {
|
||||
int count = 0;
|
||||
deck->deckLoader->getDeckList()->forEachCard([&](InnerDecklistNode *, const DecklistCardNode *node) {
|
||||
auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||
if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) {
|
||||
count += node->getNumber();
|
||||
}
|
||||
});
|
||||
return numberMatcher(count);
|
||||
};
|
||||
};
|
||||
|
||||
search["CardSearch"] = [](const peg::SemanticValues &sv) -> QString { return std::any_cast<QString>(sv[0]); };
|
||||
|
||||
search["CardFilterString"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
return QString::fromStdString(std::string(sv.sv()));
|
||||
};
|
||||
|
||||
search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto name = std::any_cast<QString>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
||||
return deck->deckLoader->getDeckList()->getName().contains(name, Qt::CaseInsensitive);
|
||||
};
|
||||
};
|
||||
|
||||
search["FileNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto name = std::any_cast<QString>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
||||
auto filename = QFileInfo(deck->filePath).fileName();
|
||||
return filename.contains(name, Qt::CaseInsensitive);
|
||||
};
|
||||
};
|
||||
|
||||
search["PathQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto name = std::any_cast<QString>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *, const ExtraDeckSearchInfo &info) {
|
||||
return info.relativeFilePath.contains(name, Qt::CaseInsensitive);
|
||||
};
|
||||
};
|
||||
|
||||
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto name = std::any_cast<QString>(sv[0]);
|
||||
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
|
||||
return deck->getDisplayName().contains(name, Qt::CaseInsensitive);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
DeckFilterString::DeckFilterString()
|
||||
{
|
||||
filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
|
||||
_error = "Not initialized";
|
||||
}
|
||||
|
||||
DeckFilterString::DeckFilterString(const QString &expr)
|
||||
{
|
||||
QByteArray ba = expr.simplified().toUtf8();
|
||||
|
||||
std::call_once(init, setupParserRules);
|
||||
|
||||
_error = QString();
|
||||
|
||||
if (ba.isEmpty()) {
|
||||
filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return true; };
|
||||
return;
|
||||
}
|
||||
|
||||
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
|
||||
_error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg));
|
||||
});
|
||||
|
||||
if (!search.parse(ba.data(), filter)) {
|
||||
qCInfo(DeckFilterStringLog).nospace() << "DeckFilterString error for " << expr << "; " << qPrintable(_error);
|
||||
filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
|
||||
}
|
||||
}
|
||||
55
cockatrice/src/filters/deck_filter_string.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @file deck_filter_string.h
|
||||
* @ingroup DeckStorageWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef DECK_FILTER_STRING_H
|
||||
#define DECK_FILTER_STRING_H
|
||||
|
||||
#include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string");
|
||||
|
||||
/**
|
||||
* Extra info relevant to filtering that isn't present in the DeckPreviewWidget
|
||||
*/
|
||||
struct ExtraDeckSearchInfo
|
||||
{
|
||||
/**
|
||||
* The relative filepath starting from the deck folder
|
||||
*/
|
||||
QString relativeFilePath;
|
||||
};
|
||||
|
||||
typedef std::function<bool(const DeckPreviewWidget *, const ExtraDeckSearchInfo &)> DeckFilter;
|
||||
|
||||
class DeckFilterString
|
||||
{
|
||||
public:
|
||||
DeckFilterString();
|
||||
explicit DeckFilterString(const QString &expr);
|
||||
bool check(const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) const
|
||||
{
|
||||
return filter(deck, info);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool valid() const
|
||||
{
|
||||
return _error.isEmpty();
|
||||
}
|
||||
|
||||
QString error()
|
||||
{
|
||||
return _error;
|
||||
}
|
||||
|
||||
private:
|
||||
QString _error;
|
||||
DeckFilter filter;
|
||||
};
|
||||
#endif // DECK_FILTER_STRING_H
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "filter_builder.h"
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "filter_card.h"
|
||||
#include "../interface/widgets/utility/custom_line_edit.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <libcockatrice/filters/filter_card.h>
|
||||
|
||||
FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @file filter_builder.h
|
||||
* @ingroup CardDatabaseModelFilters
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef FILTERBUILDER_H
|
||||
#define FILTERBUILDER_H
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#include "filter_tree_model.h"
|
||||
|
||||
#include "filter_card.h"
|
||||
#include "filter_tree.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <libcockatrice/filters/filter_card.h>
|
||||
#include <libcockatrice/filters/filter_tree.h>
|
||||
|
||||
FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent)
|
||||
{
|
||||
@@ -78,6 +77,69 @@ void FilterTreeModel::addFilter(const CardFilter *f)
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void FilterTreeModel::removeFilter(const CardFilter *f)
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
fTree->removeFilter(f);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void FilterTreeModel::clearFiltersOfType(CardFilter::Attr filterType)
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
|
||||
// Recursively remove all nodes with the given filter type
|
||||
fTree->removeFiltersByAttr(filterType);
|
||||
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
QList<const CardFilter *> FilterTreeModel::getFiltersOfType(CardFilter::Attr filterType) const
|
||||
{
|
||||
QList<const CardFilter *> filters;
|
||||
if (!fTree) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
std::function<void(const FilterTreeNode *)> traverse;
|
||||
traverse = [&](const FilterTreeNode *node) {
|
||||
if (const auto *filterItem = dynamic_cast<const FilterItem *>(node)) {
|
||||
if (filterItem->attr() == filterType) {
|
||||
QString text = filterItem->text();
|
||||
filters.append(new CardFilter(text, filterItem->type(), filterItem->attr()));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < node->childCount(); ++i) {
|
||||
traverse(node->nodeAt(i));
|
||||
}
|
||||
};
|
||||
|
||||
traverse(fTree);
|
||||
return filters;
|
||||
}
|
||||
|
||||
QList<const CardFilter *> FilterTreeModel::allFilters() const
|
||||
{
|
||||
QList<const CardFilter *> filters;
|
||||
if (!fTree) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
std::function<void(const FilterTreeNode *)> traverse;
|
||||
traverse = [&](const FilterTreeNode *node) {
|
||||
if (const auto *filterItem = dynamic_cast<const FilterItem *>(node)) {
|
||||
QString text = filterItem->text();
|
||||
filters.append(new CardFilter(text, filterItem->type(), filterItem->attr()));
|
||||
}
|
||||
for (int i = 0; i < node->childCount(); ++i) {
|
||||
traverse(node->nodeAt(i));
|
||||
}
|
||||
};
|
||||
|
||||
traverse(fTree);
|
||||
return filters;
|
||||
}
|
||||
|
||||
int FilterTreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
const FilterTreeNode *node;
|
||||
@@ -257,3 +319,10 @@ bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilterTreeModel::clear()
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
fTree->clear();
|
||||
emit layoutChanged();
|
||||
}
|
||||
58
cockatrice/src/filters/filter_tree_model.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @file filter_tree_model.h
|
||||
* @ingroup CardDatabaseModelFilters
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef FILTERTREEMODEL_H
|
||||
#define FILTERTREEMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <libcockatrice/filters/filter_card.h>
|
||||
|
||||
class FilterTree;
|
||||
class CardFilter;
|
||||
class FilterTreeNode;
|
||||
|
||||
class FilterTreeModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
FilterTree *fTree;
|
||||
|
||||
public slots:
|
||||
void addFilter(const CardFilter *f);
|
||||
void removeFilter(const CardFilter *f);
|
||||
void clearFiltersOfType(CardFilter::Attr filterType);
|
||||
[[nodiscard]] QList<const CardFilter *> getFiltersOfType(CardFilter::Attr filterType) const;
|
||||
[[nodiscard]] QList<const CardFilter *> allFilters() const;
|
||||
|
||||
private slots:
|
||||
void proxyBeginInsertRow(const FilterTreeNode *, int);
|
||||
void proxyEndInsertRow(const FilterTreeNode *, int);
|
||||
void proxyBeginRemoveRow(const FilterTreeNode *, int);
|
||||
void proxyEndRemoveRow(const FilterTreeNode *, int);
|
||||
|
||||
private:
|
||||
[[nodiscard]] FilterTreeNode *indexToNode(const QModelIndex &idx) const;
|
||||
QModelIndex nodeIndex(const FilterTreeNode *node, int row, int column) const;
|
||||
|
||||
public:
|
||||
FilterTreeModel(QObject *parent = nullptr);
|
||||
~FilterTreeModel() override;
|
||||
[[nodiscard]] FilterTree *filterTree() const
|
||||
{
|
||||
return fTree;
|
||||
}
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
[[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
[[nodiscard]] QModelIndex parent(const QModelIndex &ind) const override;
|
||||
[[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override;
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||